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
21 changes: 12 additions & 9 deletions Sources/WLKit/OAIProtocol.swift
Original file line number Diff line number Diff line change
Expand Up @@ -152,17 +152,20 @@ public enum Pad {
/// when turning everything off, not just the visible keys.
public static let maxThreadID = 19
/// Agent slot N lights and answers to `agentKeyIDs[N]`, in READING order:
/// left to right, top to bottom as you look at the pad. The firmware's top
/// row is wired right to left — key 0 is the top-RIGHT key — so reading
/// order starts 1, 0. The other rows run left to right in index order.
public static let agentKeyIDs = [1, 0, 2, 3, 4, 5]
/// The rows as you look at the pad, for on-screen mirrors. `rows` stays in
/// firmware order because the keymap file is addressed that way.
public static let displayRows: [[Int]] = [
[1, 0],
/// left to right, top to bottom as you look at the pad. Every row runs left
/// to right in index order, so this is plain index order: key 0 is the
/// top-LEFT key (checked on hardware — the top row is not reversed).
public static let agentKeyIDs = [0, 1, 2, 3, 4, 5]
/// The rows as you look at the pad, for on-screen mirrors, on the pad's
/// four-column grid: nil is a column with no key in it. The top row sits
/// in the middle two columns and the bottom row starts one column in.
/// `rows` stays in firmware order because the keymap file is addressed
/// that way.
public static let displayRows: [[Int?]] = [
[nil, 0, 1, nil],
[2, 3, 4, 5],
[6, 7, 8, 9],
[10, 11, 12],
[nil, 10, 11, 12],
]

/// The agent slot a key answers for, or nil for non-agent keys.
Expand Down
8 changes: 7 additions & 1 deletion Sources/WLMicroManager/EmulatorPanel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,13 @@ struct EmulatorView: View {
// Rows 0–2 are plain grids of single keys.
ForEach(Array(Pad.displayRows.prefix(3).enumerated()), id: \.offset) { _, row in
HStack(spacing: Self.gap) {
ForEach(row, id: \.self) { key in keyButton(key) }
ForEach(Array(row.enumerated()), id: \.offset) { _, key in
if let key {
keyButton(key)
} else {
Color.clear.frame(width: Self.keyW, height: Self.keyH)
}
}
}
}
// The bottom row starts one column in, and its wide keycap
Expand Down
17 changes: 13 additions & 4 deletions Sources/WLMicroManager/MenuPanelView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -229,14 +229,23 @@ struct MenuPanelView: View {
VStack(alignment: .leading, spacing: 6) {
ForEach(Array(Pad.displayRows.enumerated()), id: \.offset) { _, row in
HStack(spacing: 6) {
ForEach(row, id: \.self) { key in keyView(key) }
ForEach(Array(row.enumerated()), id: \.offset) { _, key in
if let key {
keyView(key)
} else {
Color.clear.frame(width: Self.keyW, height: Self.keyH)
}
}
Spacer(minLength: 0)
}
}
}
.padding(.horizontal, 14).padding(.vertical, 12)
}

private static let keyW: CGFloat = 34
private static let keyH: CGFloat = 26

private func keyView(_ index: Int) -> some View {
let color = bridge.keyColors[index]
let isBound = Pad.boundKeyIDs.contains(index)
Expand All @@ -247,8 +256,8 @@ struct MenuPanelView: View {
let isUnavailable = bridge.isRemote && (isStackKey || isLandKey)
let macroText = bridge.keyBindings.text(for: index)
let isVoiceKey = macroText == nil && Pad.voiceKeyIDs.contains(index)
// Key index and agent slot are different orderings — the top row is
// wired right to left — so the slot lookup goes through the pad map.
// Only some keys are agent keys, so the slot lookup goes through the
// pad map.
let slot = Pad.agentSlot(for: index)
let agent = slot.flatMap { $0 < bridge.agents.count ? bridge.agents[$0] : nil }

Expand All @@ -269,7 +278,7 @@ struct MenuPanelView: View {
} label: {
RoundedRectangle(cornerRadius: 5)
.fill(color ?? Color.secondary.opacity(isBound ? 0.16 : 0.07))
.frame(width: 34, height: 26)
.frame(width: Self.keyW, height: Self.keyH)
.overlay(
RoundedRectangle(cornerRadius: 5)
.strokeBorder(Color.secondary.opacity(0.25), lineWidth: 0.5)
Expand Down
11 changes: 8 additions & 3 deletions Tests/WLKitTests/PlaceholderTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,13 @@ import XCTest
final class PlaceholderTests: XCTestCase {
func testPadGeometry() {
XCTAssertEqual(Pad.rows.flatMap { $0 }.count, Pad.keyCount)
// Reading order: the top row is wired right to left.
XCTAssertEqual(Pad.agentKeyIDs, [1, 0, 2, 3, 4, 5])
XCTAssertEqual(Pad.displayRows.flatMap { $0 }.sorted(), Pad.rows.flatMap { $0 }.sorted())
// Reading order is index order: key 0 is the top-left key.
XCTAssertEqual(Pad.agentKeyIDs, [0, 1, 2, 3, 4, 5])
XCTAssertEqual(Pad.displayRows.flatMap { $0 }.compactMap { $0 }.sorted(),
Pad.rows.flatMap { $0 }.sorted())
// Every display row spans the pad's four columns, so gaps line up.
XCTAssertEqual(Pad.displayRows.map(\.count), [4, 4, 4, 4])
XCTAssertEqual(Pad.displayRows.first, [nil, 0, 1, nil])
XCTAssertEqual(Pad.displayRows.last, [nil, 10, 11, 12])
}
}
6 changes: 3 additions & 3 deletions Tests/WLKitTests/StatusMapperTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,9 @@ final class StatusMapperTests: XCTestCase {
agent("working"), agent("blocked"), agent("idle"),
])
XCTAssertEqual(threads.count, 6, "one entry per agent key")
// The pad's top row is wired right to left, so reading order starts
// at key 1: the first agent lights the top-LEFT key.
XCTAssertEqual(threads.map(\.id), [1, 0, 2, 3, 4, 5], "keys in reading order")
// Every row runs left to right, so reading order is index order: the
// first agent lights key 0, the top-LEFT key.
XCTAssertEqual(threads.map(\.id), [0, 1, 2, 3, 4, 5], "keys in reading order")
XCTAssertEqual(threads[0].color, 0xFFA000)
XCTAssertEqual(threads[1].color, 0xFF2D2D)
XCTAssertEqual(threads[2].color, 0x00C853)
Expand Down
22 changes: 12 additions & 10 deletions docs/hacking.md
Original file line number Diff line number Diff line change
Expand Up @@ -375,23 +375,25 @@ to take a whole row's keycodes with it.

### Geometry

The matrix is `[2, 4, 4, 3]` and the key index runs row-major from 0. One
surprise: **the top row is wired right to left**, so index 0 is the top-**right**
key.
The matrix is `[2, 4, 4, 3]` and the key index runs row-major from 0, every
row left to right, on a four-column grid. The top row's two keys sit in the
middle columns, between the dial and the joystick; the bottom row starts one
column in.

```
┌─────────────┬─────────────┐
│ 1 │ 0 │ row 0 <- reversed: 0 is on the RIGHT
├──────┬──────┼──────┬──────┤
┌──────┬──────┐
(dial) │ 0 │ 1 │ (joy) row 0
┌──────┼──────┼──────┼──────┐
│ 2 │ 3 │ 4 │ 5 │ row 1
├──────┼──────┼──────┼──────┤
│ 6 │ 7 │ 8 │ 9 │ row 2
├──────┴──────┼──────┴──────┤
│ 10 + 11 │ 12 │ row 3
└─────────────┴─────────────┘
└──────┼──────┴──────┼──────┤
│ 10 + 11 │ 12 │ row 3
└─────────────┴──────┘
```

Rows 1–3 run left to right in index order. Matrix positions 10 and 11 sit under
An earlier version of this guide had the top row wired right to left; on the
hardware, pressing the top-left key reports `AG00`. Matrix positions 10 and 11 sit under
one wide keycap — the stock map leaves 11 as `KC_NONE`; bind and light both if
you want the whole cap to glow evenly.

Expand Down
Loading