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 @@ -24,6 +24,15 @@ extension KeyboardModifierKey {
enum Location: CaseIterable, Hashable {
case left
case right

var canonicalDisplayOrderIndex: Int {
switch self {
case .left:
return 0
case .right:
return 1
}
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ extension KeyboardModifierKey {
case option
case control

/// Modifier order guidance from Apple style guide:
/// https://support.apple.com/guide/applestyleguide/k-apsgf9067ae8/1.0/web/1.0
static let canonicalDisplayOrder: [Self] = [.control, .option, .shift, .command]

var glyph: String {
switch self {
case .command: return UnicodeToken.command.string
Expand Down Expand Up @@ -65,5 +69,9 @@ extension KeyboardModifierKey {
case (.control, .right): return .controlRight
}
}

var canonicalDisplayOrderIndex: Int {
Self.canonicalDisplayOrder.firstIndex(of: self) ?? Self.canonicalDisplayOrder.endIndex
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -282,7 +282,17 @@ private extension KeycapEventCoordinator {
}

private func ordered(items: [Item]) -> [Item] {
let modifiers = items.filter { $0.identity.isModifier }
let modifiers = items
.filter { $0.identity.isModifier }
.sorted {
(
$0.identity.modifierKind?.canonicalDisplayOrderIndex ?? Int.max,
$0.identity.modifierLocation?.canonicalDisplayOrderIndex ?? Int.max
) < (
$1.identity.modifierKind?.canonicalDisplayOrderIndex ?? Int.max,
$1.identity.modifierLocation?.canonicalDisplayOrderIndex ?? Int.max
)
}
let others = items.filter { !$0.identity.isModifier }
return modifiers + others
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ enum KeycapPreviewSample {
/// `KeycapIdentity`, so per-key-type theming applies uniformly across every branch.
enum KeycapItemFactory {
private static let mouseIconHeight: CGFloat = 44
private static let orderedModifiers: [KeyboardModifierKey.Kind] = [.command, .shift, .option, .control]
private static let orderedModifierLocations: [KeyboardModifierKey.Location] = [.left, .right]

static func keycapItems(for keystroke: StandardKeyEvent, palette: KeycapThemePalette) -> [KeycapItem] {
Expand Down Expand Up @@ -156,7 +155,7 @@ enum KeycapItemFactory {
let currentModifierKeys = KeyboardModifierKey.keys(in: currentFlags)
let releasedModifierKeys = KeyboardModifierKey.keys(in: releasedFlags)

for modifier in Self.orderedModifiers {
for modifier in KeyboardModifierKey.Kind.canonicalDisplayOrder {
let modifierKeys = Self.orderedModifierKeys(
for: modifier,
currentModifierKeys: currentModifierKeys,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,13 @@ import XCTest
@testable import Keyty

final class KeyboardModifierKeyKindTests: XCTestCase {
func testCanonicalDisplayOrderMatchesAppleMenuOrder() {
XCTAssertEqual(
KeyboardModifierKey.Kind.canonicalDisplayOrder,
[.control, .option, .shift, .command]
)
}

func testFlagMapsKindToAggregateModifierFlag() {
XCTAssertEqual(KeyboardModifierKey.Kind.command.flag, .command)
XCTAssertEqual(KeyboardModifierKey.Kind.shift.flag, .shift)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,42 @@ final class KeycapEventCoordinatorTests: XCTestCase {

XCTAssertEqual(appendedGroups.count, 1)
XCTAssertEqual(updatedGroups.count, 1)
XCTAssertEqual(updatedGroups[0].map(\.identity), [.modifier(.leftCommand), .modifier(.leftShift)])
XCTAssertEqual(updatedGroups[0].map(\.identity), [.modifier(.leftShift), .modifier(.leftCommand)])
}

func testFlagsChangedReordersModifiersIntoCanonicalDisplayOrder() {
let coordinator = KeycapEventCoordinator<TestGroupView, TestItem>()
var updatedGroups: [[TestItem]] = []

coordinator.handleFlagsChanged(
currentTrackedFlags: [.command],
releasedTrackedFlags: [],
buildItems: { _, _ in
[TestItem(identity: .modifier(.leftCommand))]
},
appendGroup: { _ in TestGroupView() },
updateGroup: { _, items in
updatedGroups.append(items)
}
)

coordinator.handleFlagsChanged(
currentTrackedFlags: [.command, .shift],
releasedTrackedFlags: [],
buildItems: { _, _ in
[
TestItem(identity: .modifier(.leftCommand)),
TestItem(identity: .modifier(.leftShift)),
]
},
appendGroup: { _ in TestGroupView() },
updateGroup: { _, items in
updatedGroups.append(items)
}
)

XCTAssertEqual(updatedGroups.count, 1)
XCTAssertEqual(updatedGroups[0].map(\.identity), [.modifier(.leftShift), .modifier(.leftCommand)])
}

func testTrackedKeyUpdateDoesNotAppendLaterModifierToExistingKeyGroup() {
Expand Down Expand Up @@ -315,10 +350,10 @@ final class KeycapEventCoordinatorTests: XCTestCase {
)

XCTAssertEqual(appendedGroups.count, 2)
XCTAssertEqual(appendedGroups[0].map(\.identity), [.modifier(.leftCommand), .modifier(.leftShift)])
XCTAssertEqual(updatedGroups[0].map(\.identity), [.modifier(.leftCommand), .modifier(.leftShift), .keyCode(29)])
XCTAssertEqual(updatedGroups[1].map(\.identity), [.modifier(.leftCommand), .modifier(.leftShift), .keyCode(29)])
XCTAssertEqual(appendedGroups[1].map(\.identity), [.modifier(.leftCommand), .modifier(.leftShift), .keyCode(18)])
XCTAssertEqual(appendedGroups[0].map(\.identity), [.modifier(.leftShift), .modifier(.leftCommand)])
XCTAssertEqual(updatedGroups[0].map(\.identity), [.modifier(.leftShift), .modifier(.leftCommand), .keyCode(29)])
XCTAssertEqual(updatedGroups[1].map(\.identity), [.modifier(.leftShift), .modifier(.leftCommand), .keyCode(29)])
XCTAssertEqual(appendedGroups[1].map(\.identity), [.modifier(.leftShift), .modifier(.leftCommand), .keyCode(18)])
}

func testStandaloneItemAbsorbsExistingModifierGroup() {
Expand Down Expand Up @@ -353,9 +388,9 @@ final class KeycapEventCoordinatorTests: XCTestCase {
)

XCTAssertEqual(appendedGroups.count, 1)
XCTAssertEqual(appendedGroups[0].map(\.identity), [.modifier(.leftCommand), .modifier(.leftShift)])
XCTAssertEqual(appendedGroups[0].map(\.identity), [.modifier(.leftShift), .modifier(.leftCommand)])
XCTAssertEqual(updatedGroups.count, 1)
XCTAssertEqual(updatedGroups[0].map(\.identity), [.modifier(.leftCommand), .modifier(.leftShift), .mouse(.leftButton)])
XCTAssertEqual(updatedGroups[0].map(\.identity), [.modifier(.leftShift), .modifier(.leftCommand), .mouse(.leftButton)])
}

func testMouseButtonReleaseUpdatesExistingChordGroup() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,20 @@ final class KeycapItemFactoryTests: XCTestCase {
XCTAssertEqual(items.map(\.layoutHints.alignment), [.right, .left])
}

func testModifierItemsFollowCanonicalDisplayOrder() {
let items = KeycapItemFactory.modifierItems(
currentFlags: [.command, .option, .shift],
releasedFlags: [],
palette: Self.makePalette()
)

XCTAssertEqual(items.map(\.identity), [
.modifier(.leftOption),
.modifier(.leftShift),
.modifier(.leftCommand),
])
}

func testModifierItemsUseInwardAlignmentForNonCommandModifierKeys() {
let items = KeycapItemFactory.modifierItems(
currentFlags: NSEvent.ModifierFlags.shift.addingRawMasks(
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Loading