Repository navigation
Expand file tree
/
Copy pathKeyboard.lua
More file actions
647 lines (623 loc) · 29.3 KB
/
Copy pathKeyboard.lua
File metadata and controls
647 lines (623 loc) · 29.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
-- Keystance Keyboard tab: a keyboard and mouse (or a controller, as a diagram) showing what
-- each key does, with the spell, macro or item icon of the action slot it triggers. Keys
-- take drops (Drops.lua), can be dragged to pick up, right-clicked to remove, and clicked to
-- put a held raid marker or profile switch on them. Toggles show the Shift, Ctrl
-- and Alt layers (and combinations); holding a real modifier switches the view live. The
-- heat map colours each key by how easy it is to reach from the movement keys
-- (Ergonomics.lua); every key's tooltip says so too.
-- Bound keys the drawn keyboard doesn't have are listed underneath. Read-only.
local ADDON, ns = ...
if ns.disabled then return end -- another copy of Keystance is running (Core.lua)
local L = ns.L
local ipairs, pairs, CreateFrame = ipairs, pairs, CreateFrame
local GetBindingAction, GetActionTexture, HasAction = GetBindingAction, GetActionTexture, HasAction
local GetActionInfo, GetActionText = GetActionInfo, GetActionText
local IsShiftKeyDown, IsControlKeyDown, IsAltKeyDown = IsShiftKeyDown, IsControlKeyDown, IsAltKeyDown
local U = 34 -- one key unit, in pixels
local MEDIA = "Interface\\AddOns\\" .. ADDON .. "\\media\\"
local CIRCLE = MEDIA .. "circle.tga" -- a white disc: a controller button badge's backing, the lines' dots
local ROW_GAP = 8 -- extra space under the function row
local NUMPAD_WIDTH = 830 -- the window's width while the numpad is drawn (keys stay full size)
-- The eight modifier layers, in the game's prefix order.
local PREFIXES = { "", "SHIFT-", "CTRL-", "CTRL-SHIFT-", "ALT-", "ALT-SHIFT-", "ALT-CTRL-", "ALT-CTRL-SHIFT-" }
local LAYER_NAMES = {
L["Keys on their own"], L["Keys with Shift"], L["Keys with Ctrl"], L["Keys with Ctrl + Shift"],
L["Keys with Alt"], L["Keys with Alt + Shift"], L["Keys with Alt + Ctrl"], L["Keys with Alt + Ctrl + Shift"],
}
local function LayerIndex(shift, ctrl, alt)
return 1 + (shift and 1 or 0) + (ctrl and 2 or 0) + (alt and 4 or 0)
end
-- The Shift, Ctrl and Alt toggles, kept apart for the keyboard and the controller (the
-- owner's request: a layer picked on one doesn't carry over to the other). `toggles` is the
-- shown view's set.
local keyToggles = { shift = false, ctrl = false, alt = false }
local padToggles = { shift = false, ctrl = false, alt = false }
local toggles = keyToggles
-- The layer shown: the real modifiers while any is held, else the toggles.
local function CurrentLayer()
local shift, ctrl, alt = IsShiftKeyDown(), IsControlKeyDown(), IsAltKeyDown()
if shift or ctrl or alt then return LayerIndex(shift, ctrl, alt), true end
return LayerIndex(toggles.shift, toggles.ctrl, toggles.alt), false
end
---------------------------------------------------------------------------
-- Every bound key, for the "also bound" list (rebuilt when bindings change)
---------------------------------------------------------------------------
local bound, boundStale = {}, true -- { { key, command }, ... }
local function BoundKeys()
if boundStale then
boundStale = false
local n = 0
for command, keys in pairs((ns.ReadBindings())) do
for _, key in ipairs(keys) do
n = n + 1
local e = bound[n] or {}
bound[n] = e
e[1], e[2] = key, command
end
end
for i = n + 1, #bound do bound[i] = nil end
table.sort(bound, function(a, b) return a[1] < b[1] end)
end
return bound
end
-- A command's name as the game's Key Bindings list shows it ("Toggle World Map" for
-- TOGGLEWORLDMAP). GetBindingText(command, "BINDING_NAME_") gave the raw command in game.
local function CommandName(command)
if not command or command == "" then return nil end
local text = GetBindingName and GetBindingName(command)
if type(text) == "string" and text ~= "" then return text end
return command
end
ns.CommandName = CommandName
-- A key's name as the game shows it ("Num Pad 1" for NUMPAD1).
local function KeyName(key)
local text = GetBindingText and GetBindingText(key)
if type(text) == "string" and text ~= "" then return text end
return key
end
---------------------------------------------------------------------------
-- Key caps
---------------------------------------------------------------------------
local function CapTooltip(cap)
if not cap.fullKey then return end
GameTooltip:SetOwner(cap, "ANCHOR_RIGHT")
if cap.slot and HasAction(cap.slot) then
GameTooltip:SetAction(cap.slot)
else
GameTooltip:AddLine(CommandName(cap.command) or L["Not bound"])
end
GameTooltip:AddLine(" ")
GameTooltip:AddDoubleLine(L["Key"], cap.fullKey, 0.6, 0.6, 0.6, 1, 1, 1)
if cap.command and cap.command ~= "" then
GameTooltip:AddDoubleLine(L["Binding"], cap.command, 0.6, 0.6, 0.6, 0.6, 0.6, 0.6)
end
if cap.slot then
GameTooltip:AddDoubleLine(L["Action slot"], cap.slot, 0.6, 0.6, 0.6, 0.6, 0.6, 0.6)
end
if cap.movement then
GameTooltip:AddDoubleLine(L["Reach"], L["a movement key"], 0.6, 0.6, 0.6, ns.MovementColour())
elseif cap.reach then
local _, name, r, g, b = ns.ReachLevel(cap.reach)
GameTooltip:AddDoubleLine(L["Reach"], L["%s (%d)"]:format(name, cap.reach), 0.6, 0.6, 0.6, r, g, b)
end
GameTooltip:Show()
end
local function MakeCap(f, board, info, x, y, unit)
unit = unit or U
local w = info.w or 1
local cap = CreateFrame("Button", nil, board)
cap:SetSize(unit * w - 3, unit * (info.h or 1) - 3)
cap:SetPoint("TOPLEFT", board, "TOPLEFT", x, -y)
local bg = cap:CreateTexture(nil, "BACKGROUND")
bg:SetAllPoints()
bg:SetColorTexture(0.1, 0.1, 0.12, 0.9)
cap.bg = bg
local icon = cap:CreateTexture(nil, "ARTWORK")
icon:SetPoint("TOPLEFT", 2, -2)
icon:SetPoint("BOTTOMRIGHT", -2, 2)
icon:SetTexCoord(0.08, 0.92, 0.08, 0.92)
icon:Hide()
cap.icon, cap.whole = icon, false
local label = cap:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
label:SetPoint("TOPLEFT", 3, -2)
label:SetText(ns.KeyLabel(info[1]))
cap.label = label
if info[1]:sub(1, 3) == "PAD" then
-- A controller button: its symbol as a badge on the icon's top-left corner, half
-- outside it, on a dark disc, so the spell icon stays clear.
local badge = cap:CreateTexture(nil, "OVERLAY", nil, 6)
badge:SetTexture(CIRCLE)
badge:SetVertexColor(0.05, 0.05, 0.07, 0.95)
badge:SetSize(20, 20)
badge:SetPoint("CENTER", cap, "TOPLEFT", 3, -3)
label:ClearAllPoints()
label:SetPoint("CENTER", badge, "CENTER", 0, 0)
label:SetDrawLayer("OVERLAY", 7)
cap.badge = badge
end
-- A command's name, in a small font over up to two lines ("Toggle World Map").
local name = cap:CreateFontString(nil, "OVERLAY", "GameFontNormalTiny")
name:SetPoint("BOTTOMLEFT", 1, 2)
name:SetPoint("BOTTOMRIGHT", -1, 2)
name:SetHeight(18)
name:SetWordWrap(true)
name:SetJustifyV("BOTTOM")
name:SetJustifyH("CENTER")
cap.name = name
f.texts[#f.texts + 1] = label
f.texts[#f.texts + 1] = name
cap:SetHighlightTexture("Interface\\Buttons\\ButtonHilight-Square", "ADD")
cap:SetScript("OnEnter", CapTooltip)
-- Something dragged here (a spell from the Actions panel or the spellbook, an action off a
-- bar) goes on this key; clicking while holding it does the same.
local function Drop(self)
if self.fullKey and GetCursorInfo() then ns.DropOnKey(self.fullKey, self.slot, self.command) end
end
cap:SetScript("OnReceiveDrag", Drop)
-- While a raid marker (or another command) waits for a key, a click binds it here and a
-- right-click cancels.
cap:RegisterForClicks("LeftButtonUp", "RightButtonUp")
cap:SetScript("OnClick", function(self, button)
if ns.HeldBinding() then
if button == "RightButton" then return ns.CancelBinding() end
if self.fullKey then ns.BindHeld(self.fullKey, self.command) end
return
end
if button == "RightButton" then
-- Right-click: remove what's on the key (after asking), unless that right-click
-- just dropped a held raid marker.
if ns.HoldJustEnded() then return end
if self.fullKey and not GetCursorInfo() then ns.AskRemove(self.fullKey, self.slot, self.command) end
return
end
Drop(self)
end)
-- Dragging a key picks up its action, as dragging off a real bar does.
cap:RegisterForDrag("LeftButton")
cap:SetScript("OnDragStart", function(self)
if self.slot then ns.PickupFromSlot(self.slot, self.fullKey, self.command) end
end)
cap:SetScript("OnLeave", function() GameTooltip:Hide() end)
cap.key, cap.mod, cap.blank = info[1], info.mod, info[1] == ""
cap.isKeyCap = true -- a raid marker dragged from the Actions panel looks for this under the mouse
-- The full key name for each layer ("SHIFT-1"...), made once.
cap.full = {}
for i, prefix in ipairs(PREFIXES) do cap.full[i] = prefix .. info[1] end
return cap
end
-- The controller diagram: an outline drawing in the middle and a column of buttons each
-- side, each cap joined to its button on the drawing by a line (across, then to the
-- button), with the binding's name beside it. No mouse, no numpad.
local PAD_ROW, PAD_BOARD_W = 34, 688
local PAD_NAME_W = 116
local LINE_R, LINE_G, LINE_B, LINE_A = 0.85, 0.7, 0.3, 0.75
local function BuildPad(f, board, layout, Add, caps)
local rows = math.max(#layout.left, #layout.right)
local height = rows * PAD_ROW
local dx, dy = (PAD_BOARD_W - ns.PAD_W) / 2, (height - ns.PAD_H) / 2
local drawing = board:CreateTexture(nil, "BACKGROUND")
drawing:SetSize(ns.PAD_W, ns.PAD_H)
drawing:SetPoint("TOPLEFT", board, "TOPLEFT", dx, -dy)
drawing:SetTexture(MEDIA .. "controller.tga")
-- The drawing fills the middle of a square texture (power-of-two sizes).
local crop = (1 - ns.PAD_H / ns.PAD_W) / 2
drawing:SetTexCoord(0, 1, crop, 1 - crop)
board.drawing = drawing
local function Line(x1, y1, x2, y2)
local line = board:CreateLine(nil, "ARTWORK")
line:SetThickness(1.5)
line:SetColorTexture(LINE_R, LINE_G, LINE_B, LINE_A)
line:SetStartPoint("TOPLEFT", board, x1, -y1)
line:SetEndPoint("TOPLEFT", board, x2, -y2)
return line
end
for _, side in ipairs({ "left", "right" }) do
local left = side == "left"
for i, info in ipairs(layout[side]) do
local capW = PAD_ROW - 4
local x = left and (PAD_NAME_W + 6) or (PAD_BOARD_W - PAD_NAME_W - 6 - capW)
local y = (i - 1) * PAD_ROW + 2
Add(info, x, y, PAD_ROW - 1)
local cap = caps[#caps]
cap.callout = side
-- The binding's name beside the cap, away from the drawing.
local name = cap.name
name:ClearAllPoints()
if left then
name:SetPoint("RIGHT", cap, "LEFT", -6, 0)
name:SetJustifyH("RIGHT")
else
name:SetPoint("LEFT", cap, "RIGHT", 6, 0)
name:SetJustifyH("LEFT")
end
name:SetSize(PAD_NAME_W, capW)
name:SetJustifyV("MIDDLE")
if GameFontNormalSmall then name:SetFontObject(GameFontNormalSmall) end
-- The line: across from the cap to beside the drawing, then to the button.
local cy = y + capW / 2
local fromX = left and (x + capW + 2) or (x - 2)
local elbowX = left and (dx - 8) or (dx + ns.PAD_W + 8)
local tx, ty = dx + info.at[1], dy + info.at[2]
cap.lines = { Line(fromX, cy, elbowX, cy) }
local lastX, lastY = elbowX, cy
if info.via then -- a bend, around another button
local vx, vy = dx + info.via[1], dy + info.via[2]
cap.lines[2] = Line(lastX, lastY, vx, vy)
lastX, lastY = vx, vy
end
cap.lines[#cap.lines + 1] = Line(lastX, lastY, tx, ty)
local dot = board:CreateTexture(nil, "ARTWORK", nil, 1)
dot:SetTexture(CIRCLE)
dot:SetVertexColor(LINE_R, LINE_G, LINE_B, 1)
dot:SetSize(6, 6)
dot:SetPoint("CENTER", board, "TOPLEFT", tx, -ty)
cap.dot = dot
end
end
board:SetSize(PAD_BOARD_W, height)
end
-- Draws a layout (once) and returns its caps and the set of keys it has. With `numpad`, the
-- navigation block and the numpad sit to the right (the window widens to fit), and the
-- mouse keys move to a row underneath.
local function BuildBoard(page, f, layoutKey, numpad)
local u = U
local board = CreateFrame("Frame", nil, page)
board:SetPoint("TOPLEFT", page, "TOPLEFT", 16, -48)
local caps, drawn = {}, {}
local function Add(info, x, y, unit)
local cap = MakeCap(f, board, info, x, y, unit or u)
caps[#caps + 1] = cap
if cap.blank then cap:Hide() end
drawn[info[1]] = true
end
local function RowY(r) return (r - 1) * u + (r > 1 and ROW_GAP or 0) end
local layout = ns.LAYOUTS[layoutKey]
if layout.pad then
BuildPad(f, board, layout, Add, caps)
board.caps, board.drawn = caps, drawn
return board
end
for r, row in ipairs(layout.rows) do
local x = 0
for _, info in ipairs(row) do
x = x + (info.gap or 0) * u
Add(info, x, RowY(r))
x = x + (info.w or 1) * u
end
end
local mouse = board:CreateFontString(nil, "OVERLAY", "GameFontDisableSmall")
mouse:SetText(L["Mouse"])
f.texts[#f.texts + 1] = mouse
if numpad then
for _, block in ipairs({ { ns.NAV_KEYS, 15.5 }, { ns.NUMPAD_KEYS, 19 } }) do
for _, info in ipairs(block[1]) do Add(info, (block[2] + info.x) * u, RowY(info.row)) end
end
-- The mouse, in a row under the keyboard.
local y = RowY(6) + u + 10
mouse:SetPoint("TOPLEFT", board, "TOPLEFT", 0, -y)
for i, key in ipairs(ns.MOUSE_KEYS) do Add({ key }, (i + 1) * u, y) end
board:SetSize(23 * u, y + u)
else
-- The mouse, to the right of the keyboard.
for i, key in ipairs(ns.MOUSE_KEYS) do
Add({ key }, 15 * u + 20 + ((i - 1) % 2) * u, u + ROW_GAP + math.floor((i - 1) / 2) * u)
end
mouse:SetPoint("BOTTOMLEFT", board, "TOPLEFT", 15 * u + 20, -(u + ROW_GAP) + 2)
board:SetSize(15 * u + 20 + 2 * u, 6 * u + ROW_GAP)
end
board.caps, board.drawn = caps, drawn
return board
end
---------------------------------------------------------------------------
-- The page
---------------------------------------------------------------------------
local function Tint(cap, r, g, b, a)
cap.bg:SetColorTexture(r, g, b, a)
end
-- With the heat map on, a bound key's icon sits further in, so its colour shows as a frame.
-- Changed only when it changes: redraws stay allocation-free.
local function Inset(cap, px)
if cap.inset == px then return end
cap.inset = px
cap.icon:ClearAllPoints()
cap.icon:SetPoint("TOPLEFT", px, -px)
cap.icon:SetPoint("BOTTOMRIGHT", -px, px)
end
-- What an action slot holds, by name ("Holy Light"), for a controller's callouts.
local function ActionName(slot)
local kind, id = GetActionInfo(slot)
if issecretvalue and (issecretvalue(kind) or issecretvalue(id)) then return GetActionText(slot) or "" end
local name
if kind == "spell" then
name = C_Spell.GetSpellName(id)
elseif kind == "item" then
name = C_Item.GetItemNameByID(id)
end
return name or GetActionText(slot) or ""
end
local NOT_BOUND = "|cff808080" .. L["Not bound"] .. "|r"
local function RefreshCaps(page, layer, padMods)
local heat = not padMods and page.heatOn
local map = not padMods and ns.ReachMap(page.layoutKey)
for _, cap in ipairs(page.board.caps) do
-- A modifier: Shift, Ctrl or Alt, or the controller button set to act as one.
local modAs = cap.mod and cap.key or (padMods and padMods[cap.key])
if modAs then
local on = (modAs == "SHIFT" and (layer - 1) % 2 == 1) or (modAs == "CTRL" and math.floor((layer - 1) / 2) % 2 == 1)
or (modAs == "ALT" and layer > 4)
cap.icon:Hide()
-- A controller button acting as one says so (a keyboard's Shift key already does).
cap.name:SetText(cap.mod and "" or (modAs == "SHIFT" and L["Shift"] or modAs == "CTRL" and L["Ctrl"] or L["Alt"]))
Tint(cap, on and 0.45 or 0.1, on and 0.35 or 0.1, on and 0.1 or 0.12, 0.9)
cap.fullKey, cap.command, cap.slot = nil, nil, nil
cap.reach, cap.movement = nil, nil
elseif not cap.blank then
local full = cap.full[layer]
local command = GetBindingAction(full, true)
local slot = ns.CommandSlot(command)
local texture = slot and GetActionTexture(slot)
cap.fullKey, cap.command, cap.slot = full, command, slot
-- A raid marker shows its own picture, uncropped (spell icons have a border to trim);
-- a profile's key shows the profile's icon.
local picture, crop
if not texture then picture, crop = ns.CommandIcon(command) end
if texture or picture then
local whole = (picture and not crop) and true or false
if cap.whole ~= whole then -- only when it changes: redraws stay allocation-free
cap.whole = whole
if whole then cap.icon:SetTexCoord(0, 1, 0, 1) else cap.icon:SetTexCoord(0.08, 0.92, 0.08, 0.92) end
end
cap.icon:SetTexture(texture or picture)
cap.icon:Show()
cap.name:SetText(cap.callout and (texture and ActionName(slot) or CommandName(command) or "") or "")
else
cap.icon:Hide()
if cap.callout then
cap.name:SetText(command == "" and NOT_BOUND or CommandName(command) or command)
else
cap.name:SetText(slot and "" or (CommandName(command) or ""))
end
end
-- How easy the key is to reach, from the movement keys (none on a controller).
cap.movement = map and layer == 1 and map.movement[cap.key] or nil
cap.reach = map and ns.KeyReach(page.layoutKey, cap.key, PREFIXES[layer]) or nil
if heat and cap.movement then
local r, g, b = ns.MovementColour()
Tint(cap, r, g, b, 0.85)
elseif heat and cap.reach then
local _, _, r, g, b = ns.ReachLevel(cap.reach)
Tint(cap, r, g, b, (command ~= "") and 0.95 or 0.7)
else
Tint(cap, 0.1, 0.1, 0.12, (command ~= "") and 0.9 or 0.45)
end
Inset(cap, heat and 4 or 2)
end
end
end
-- "NUMPAD1 Holy Light" for the list, cached per key and command.
local partCache = {}
local function Part(key, command)
local byCommand = partCache[key]
if not byCommand then
byCommand = {}
partCache[key] = byCommand
end
local part = byCommand[command]
if not part then
local picture = ns.CommandIcon(command)
part = KeyName(key) .. ": " .. (picture and ("|T" .. picture .. ":14|t ") or "") .. (CommandName(command) or command)
byCommand[command] = part
end
return part
end
local otherParts = {}
-- Bound keys of this kind of device (keyboard and mouse, or controller) that aren't drawn,
-- in this layer: at most MAX_OTHERS of them, then "and N more", so the list never runs
-- past the tab. A controller view doesn't list keyboard keys, nor a keyboard view
-- controller buttons.
local MAX_OTHERS = 24
local function RefreshOthers(page, layer, pad)
local prefix = PREFIXES[layer]
local drawn = page.board.drawn
local n, extra = 0, 0
for _, e in ipairs(BoundKeys()) do
local p, base = ns.SplitKey(e[1])
if p == prefix and not drawn[base] and (base:sub(1, 3) == "PAD") == (pad and true or false) then
if n < MAX_OTHERS then
n = n + 1
otherParts[n] = Part(e[1], e[2])
else
extra = extra + 1
end
end
end
for i = n + 1, #otherParts do otherParts[i] = nil end
local text = n > 0 and (L["Also bound: "] .. table.concat(otherParts, ", ")) or ""
if extra > 0 then text = text .. L[", and %d more"]:format(extra) end
page.others:SetText(text)
end
local padButton = {} -- [SHIFT/CTRL/ALT] = the controller button acting as it (reused)
local function Refresh(page)
local key = ns.LayoutKey(ns.db.settings.layout)
local pad = ns.LAYOUTS[key].pad
local numpad = not pad and ns.db.settings.numpad and true or false
toggles = pad and padToggles or keyToggles
local boardKey = key .. (numpad and "+numpad" or "")
if page.boardKey ~= boardKey then
if page.board then page.board:Hide() end
local texts = #page.window.texts
local built = not page.boards[boardKey]
page.boards[boardKey] = page.boards[boardKey] or BuildBoard(page, page.window, key, numpad)
page.board = page.boards[boardKey]
page.board:Show()
page.layoutKey, page.boardKey = key, boardKey
ns.SetWindowWidth(numpad and NUMPAD_WIDTH or nil)
page.legend:ClearAllPoints()
page.legend:SetPoint("TOPLEFT", page.board, "BOTTOMLEFT", 0, -10)
page.legend:SetPoint("RIGHT", page.handLeft, "LEFT", -8, 0)
page.handRight:ClearAllPoints()
page.handRight:SetPoint("TOPRIGHT", page.board, "BOTTOMRIGHT", 0, -6)
page.othersUnder = nil -- placed below, under the legend or the board
-- A board built just now takes the look (one built before already has it).
if built then
for i = texts + 1, #page.window.texts do ns.SkinText(page.window.texts[i]) end
end
end
page.numpad:SetShown(not pad)
if numpad then page.numpad:LockHighlight() else page.numpad:UnlockHighlight() end
-- The heat map: keyboards only.
page.heatOn = not pad and ns.db.settings.heatmap and true or false
page.heat:SetShown(not pad)
if page.heatOn then page.heat:LockHighlight() else page.heat:UnlockHighlight() end
page.heat:SetText((page.heatOn and "|cffffd100" or "") .. L["Heat map"] .. (page.heatOn and "|r" or ""))
page.legend:SetShown(page.heatOn)
if page.heatOn then page.legend:SetText(ns.ReachLegend(key)) end
-- Moving the hand (and every keybind with it), with the heat map.
page.handLeft:SetShown(page.heatOn)
page.handRight:SetShown(page.heatOn)
if page.heatOn then
page.handLeft:SetEnabled(ns.CanShiftHand(-1))
page.handRight:SetEnabled(ns.CanShiftHand(1))
end
-- The "also bound" list goes under the legend while it shows, else under the keyboard.
local under = page.heatOn and page.legend or page.board
if page.othersUnder ~= under then
page.othersUnder = under
page.others:ClearAllPoints()
page.others:SetPoint("TOPLEFT", under, "BOTTOMLEFT", 0, page.heatOn and -8 or -12)
page.others:SetPoint("BOTTOMRIGHT", page, "BOTTOMRIGHT", -16, 36) -- above the layout buttons
end
-- On a controller, the layer buttons say which controller button is Shift, Ctrl or Alt.
local padMods = pad and ns.PadModifiers() or nil
for k in pairs(padButton) do padButton[k] = nil end
if padMods then for button, mod in pairs(padMods) do padButton[mod] = button end end
page.numpad:SetText((numpad and "|cffffd100" or "") .. L["Numpad"] .. (numpad and "|r" or ""))
local layer, live = CurrentLayer()
for _, t in ipairs(page.toggles) do
local on = toggles[t.mod]
if on then t:LockHighlight() else t:UnlockHighlight() end
local button = padButton[t.mod:upper()]
local title = button and (t.title .. " " .. ns.KeyLabel(button)) or t.title
t:SetText((on and "|cffffd100" or "") .. title .. (on and "|r" or ""))
end
page.layerText:SetText(LAYER_NAMES[layer] .. (live and L[" (held)"] or ""))
page.layoutRow:Refresh()
local held = ns.HeldBinding()
page.binding:SetShown(held ~= nil)
if held then page.binding:SetText(L["Click a key for %s (right-click drops it)"]:format(held.label)) end
RefreshCaps(page, layer, padMods)
RefreshOthers(page, layer, pad)
end
local function Build(page, f)
page.window, page.boards, page.toggles = f, {}, {}
local x = 16
for _, t in ipairs({ { "shift", L["Shift"] }, { "ctrl", L["Ctrl"] }, { "alt", L["Alt"] } }) do
local b = CreateFrame("Button", nil, page, "UIPanelButtonTemplate")
b:SetSize(64, 22)
b:SetPoint("TOPLEFT", page, "TOPLEFT", x, -14)
b.mod, b.title = t[1], t[2]
b:SetScript("OnClick", function(self)
toggles[self.mod] = not toggles[self.mod]
ns.RefreshWindow()
end)
f.buttons[#f.buttons + 1] = b
page.toggles[#page.toggles + 1] = b
x = x + 68
end
local layerText = page:CreateFontString(nil, "OVERLAY", "GameFontNormal")
layerText:SetPoint("LEFT", page.toggles[3], "RIGHT", 12, 0)
page.layerText = layerText
f.texts[#f.texts + 1] = layerText
-- The keyboard drawn: automatic (from the game's language), US or UK.
local row = ns.ChoiceRow(f, page, { { "auto", L["Automatic"] }, { "ansi", L["US"] }, { "iso", L["UK"] },
{ "pad", L["Controller"] } },
function() return ns.db.settings.layout or "auto" end,
function(value)
ns.db.settings.layout = value
ns.RefreshWindow()
end, 76)
row:SetPoint("BOTTOMRIGHT", page, "BOTTOMRIGHT", -16, 8)
page.layoutRow = row
-- Also draw the navigation block (Insert, Home, arrows...) and the numpad.
local numpad = CreateFrame("Button", nil, page, "UIPanelButtonTemplate")
numpad:SetSize(76, 22)
numpad:SetPoint("RIGHT", row, "LEFT", -10, 0)
numpad:SetScript("OnClick", function()
ns.db.settings.numpad = not ns.db.settings.numpad or nil
ns.RefreshWindow()
end)
page.numpad = numpad
f.buttons[#f.buttons + 1] = numpad
-- The heat map: each key coloured by how easy it is to reach from the movement keys.
local heat = CreateFrame("Button", nil, page, "UIPanelButtonTemplate")
heat:SetSize(86, 22)
heat:SetPoint("RIGHT", numpad, "LEFT", -6, 0)
heat:SetScript("OnClick", function()
ns.db.settings.heatmap = not ns.db.settings.heatmap or nil
ns.RefreshWindow()
end)
heat:SetScript("OnEnter", function(self)
GameTooltip:SetOwner(self, "ANCHOR_TOP")
GameTooltip:AddLine(L["Heat map"])
GameTooltip:AddLine(L["Colours each key by how easy it is to reach while your hand rests on your movement keys: good places for the spells you use most."], 1, 1, 1, true)
GameTooltip:Show()
end)
heat:SetScript("OnLeave", function() GameTooltip:Hide() end)
page.heat = heat
f.buttons[#f.buttons + 1] = heat
-- Move the hand one key left or right, every keybind with it (Profiles.lua asks first).
local function HandButton(dir, text, tip)
local b = CreateFrame("Button", nil, page, "UIPanelButtonTemplate")
b:SetSize(96, 22)
b:SetText(text)
b:SetScript("OnClick", function() ns.AskShiftHand(dir) end)
b:SetMotionScriptsWhileDisabled(true)
b:SetScript("OnEnter", function(self)
GameTooltip:SetOwner(self, "ANCHOR_TOP")
GameTooltip:AddLine(text)
GameTooltip:AddLine(tip, 1, 1, 1, true)
if not self:IsEnabled() then
GameTooltip:AddLine(L["Not from where your movement keys are now."], 1, 0.3, 0.3, true)
end
GameTooltip:Show()
end)
b:SetScript("OnLeave", function() GameTooltip:Hide() end)
b:Hide()
f.buttons[#f.buttons + 1] = b
return b
end
page.handRight = HandButton(1, L["Hand right"],
L["Moves your movement keys one key right (W A S D to E S D F) and every keybind on the left of your keyboard with them, so each stays under the same finger. Asks first; Undo puts it back."])
page.handLeft = HandButton(-1, L["Hand left"],
L["Moves your movement keys one key left (E S D F to W A S D) and every keybind on the left of your keyboard with them. Asks first; Undo puts it back."])
page.handLeft:SetPoint("RIGHT", page.handRight, "LEFT", -4, 0)
-- What the colours mean, while the heat map is on.
local legend = page:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
-- Anchored under whichever keyboard is drawn (Refresh).
legend:SetJustifyH("LEFT")
legend:SetWordWrap(true)
legend:Hide()
page.legend = legend
f.texts[#f.texts + 1] = legend
local binding = page:CreateFontString(nil, "OVERLAY", "GameFontNormal")
binding:SetPoint("TOPLEFT", page, "TOPLEFT", 16, -36)
binding:SetTextColor(0.4, 0.8, 1)
binding:Hide()
page.binding = binding
f.texts[#f.texts + 1] = binding
local others = page:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
-- Anchored under whichever keyboard is drawn (Refresh).
others:SetJustifyH("LEFT")
others:SetWordWrap(true)
page.others = others
f.texts[#f.texts + 1] = others
page.Refresh = Refresh
end
ns.pageBuilders.keyboard = Build
-- Keep an open window current; a closed one costs one check.
ns.On("UPDATE_BINDINGS", function()
boundStale = true
for k in pairs(partCache) do partCache[k] = nil end -- a profile's key may have a new name or icon
ns.RequestRefresh()
end)
ns.On("MODIFIER_STATE_CHANGED", function() ns.RequestRefresh("keyboard") end)