Skip to content
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
Original file line number Diff line number Diff line change
Expand Up @@ -167,10 +167,10 @@ extension KeyboardSettingsPane.PreviewGroup {
private static func mediaKeySampleGroups() -> [[KeycapPreviewSample]] {
[
[
.media(.brightnessUp)
.media(.brightnessUp, isPressed: true)
],
[
.media(.play)
.media(.play, isPressed: true)
],
]
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,9 @@ final class KeyboardVisualizer {
self.prepareForNextContentEvent()

let keycap = KeycapItemFactory.mediaKeyItem(for: mediaKey, palette: self.visualizerSettings.palette)
self.eventCoordinator.handleStandalone(
self.eventCoordinator.handleMediaKey(
kind: mediaKey.kind,
isPressed: keycap.isPressed,
items: [keycap],
appendGroup: { self.visualizerWindow.appendGroup(with: $0) },
updateGroup: { group, items in self.visualizerWindow.updateGroup(group, with: items) }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,22 @@ protocol KeycapGroupItem {
}

final class KeycapEventCoordinator<GroupView: AnyObject, Item: KeycapGroupItem> {
private enum TrackedButtonIdentifier: Hashable {
case mouse(MouseEvent.Kind)
case media(MediaKeyEvent.Kind)
}

private var pendingModifierGroup: GroupView?
private var completedModifierGroup: GroupView?
private var activeKeyGroups: [UInt16: GroupView] = [:]
private var activeMouseGroups: [MouseEvent.Kind: GroupView] = [:]
private var activeButtonGroups: [TrackedButtonIdentifier: GroupView] = [:]
private var groupItems: [ObjectIdentifier: [Item]] = [:]

func reset() {
self.pendingModifierGroup = nil
self.completedModifierGroup = nil
self.activeKeyGroups.removeAll(keepingCapacity: true)
self.activeMouseGroups.removeAll(keepingCapacity: true)
self.activeButtonGroups.removeAll(keepingCapacity: true)
self.groupItems.removeAll(keepingCapacity: true)
}

Expand All @@ -35,7 +40,7 @@ final class KeycapEventCoordinator<GroupView: AnyObject, Item: KeycapGroupItem>
self.completedModifierGroup = nil
}
self.activeKeyGroups = self.activeKeyGroups.filter { $0.value !== group }
self.activeMouseGroups = self.activeMouseGroups.filter { $0.value !== group }
self.activeButtonGroups = self.activeButtonGroups.filter { $0.value !== group }
self.groupItems[ObjectIdentifier(group)] = nil
}

Expand Down Expand Up @@ -130,17 +135,75 @@ final class KeycapEventCoordinator<GroupView: AnyObject, Item: KeycapGroupItem>
items: [Item],
appendGroup: ([Item]) -> GroupView,
updateGroup: (GroupView, [Item]) -> Void
) {
self.handleTrackedButton(
identifier: .mouse(kind),
isPressed: isPressed,
items: items,
appendGroup: appendGroup,
updateGroup: updateGroup
)
}

func handleStandalone(
items: [Item],
appendGroup: ([Item]) -> GroupView,
updateGroup: (GroupView, [Item]) -> Void
) {
guard !items.isEmpty else { return }

if let pendingModifierGroup, self.canAbsorbIntoPendingChord(pendingModifierGroup) {
let merged = self.ordered(items: self.merged(items: items, into: self.storedItems(for: pendingModifierGroup)))
self.groupItems[ObjectIdentifier(pendingModifierGroup)] = merged
updateGroup(pendingModifierGroup, merged)
self.completedModifierGroup = pendingModifierGroup
return
}

let group = appendGroup(items)
self.groupItems[ObjectIdentifier(group)] = items
}

func handleMediaKey(
kind: MediaKeyEvent.Kind,
isPressed: Bool,
items: [Item],
appendGroup: ([Item]) -> GroupView,
updateGroup: (GroupView, [Item]) -> Void
) {
self.handleTrackedButton(
identifier: .media(kind),
isPressed: isPressed,
items: items,
appendGroup: appendGroup,
updateGroup: updateGroup
)
}
}

// MARK: - Private API
private extension KeycapEventCoordinator {
private func storedItems(for group: GroupView) -> [Item] {
self.groupItems[ObjectIdentifier(group)] ?? []
}

private func handleTrackedButton(
identifier: TrackedButtonIdentifier,
isPressed: Bool,
items: [Item],
appendGroup: ([Item]) -> GroupView,
updateGroup: (GroupView, [Item]) -> Void
) {
guard !items.isEmpty else { return }

if let activeGroup = self.activeMouseGroups[kind] {
if let activeGroup = self.activeButtonGroups[identifier] {
let merged = self.ordered(items: self.merged(items: items, into: self.storedItems(for: activeGroup)))
self.groupItems[ObjectIdentifier(activeGroup)] = merged
updateGroup(activeGroup, merged)
self.pendingModifierGroup = activeGroup
self.completedModifierGroup = activeGroup
if !isPressed {
self.activeMouseGroups[kind] = nil
self.activeButtonGroups[identifier] = nil
}
return
}
Expand All @@ -151,7 +214,7 @@ final class KeycapEventCoordinator<GroupView: AnyObject, Item: KeycapGroupItem>
updateGroup(pendingModifierGroup, merged)
self.completedModifierGroup = pendingModifierGroup
if isPressed {
self.activeMouseGroups[kind] = pendingModifierGroup
self.activeButtonGroups[identifier] = pendingModifierGroup
}
return
}
Expand All @@ -161,36 +224,10 @@ final class KeycapEventCoordinator<GroupView: AnyObject, Item: KeycapGroupItem>
self.groupItems[ObjectIdentifier(group)] = orderedItems
self.completedModifierGroup = group
if isPressed {
self.activeMouseGroups[kind] = group
self.activeButtonGroups[identifier] = group
}
}

func handleStandalone(
items: [Item],
appendGroup: ([Item]) -> GroupView,
updateGroup: (GroupView, [Item]) -> Void
) {
guard !items.isEmpty else { return }

if let pendingModifierGroup, self.canAbsorbIntoPendingChord(pendingModifierGroup) {
let merged = self.ordered(items: self.merged(items: items, into: self.storedItems(for: pendingModifierGroup)))
self.groupItems[ObjectIdentifier(pendingModifierGroup)] = merged
updateGroup(pendingModifierGroup, merged)
self.completedModifierGroup = pendingModifierGroup
return
}

let group = appendGroup(items)
self.groupItems[ObjectIdentifier(group)] = items
}
}

// MARK: - Private API
private extension KeycapEventCoordinator {
private func storedItems(for group: GroupView) -> [Item] {
self.groupItems[ObjectIdentifier(group)] ?? []
}

/// Only pure modifier previews can absorb later modifier-only updates.
private func canAppendModifiers(to group: GroupView) -> Bool {
let items = self.storedItems(for: group)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ enum KeycapItemFactory {
}

static func mediaKeyItem(for mediaKey: MediaKeyEvent, palette: KeycapThemePalette) -> KeycapItem {
Self.mediaKeyItem(for: mediaKey.kind, isPressed: false, palette: palette)
Self.mediaKeyItem(for: mediaKey.kind, isPressed: mediaKey.isPressed, palette: palette)
}

static func mediaKeyItem(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ extension CaptureController: EventTapDelegate {
}

func eventTap(_ tap: EventTap, noteMediaKey mediaKey: MediaKeyEvent) {
guard self.isCapturing, mediaKey.isPressed, mediaKey.isRecognized else { return }
guard self.isCapturing, mediaKey.isRecognized else { return }
self.eventProcessor.noteMediaKey(mediaKey)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,16 @@ final class KeyboardSettingsPreviewTests: XCTestCase {
])
}

func testMediaPreviewGroupsRenderPressedMediaKeys() {
let groups = KeyboardSettingsPane.PreviewGroup.previewGroups(settings: settings)
let mediaGroups = groups.filter { $0.category == .mediaKey }

XCTAssertEqual(mediaGroups.count, 2)
XCTAssertTrue(mediaGroups.allSatisfy { group in
group.items.allSatisfy(\.isPressed)
})
}

func testPreviewGroupsFallBackToKeyboardCategoryOnly() {
settings.onlyShowModifiedKeystrokes = true
settings.showSpecialKeys = false
Expand Down
Loading
Loading