-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathWaypointUI_Gamepad.lua
More file actions
351 lines (297 loc) · 11.5 KB
/
WaypointUI_Gamepad.lua
File metadata and controls
351 lines (297 loc) · 11.5 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
-- WaypointUI_Gamepad.lua
-- 为 WaypointUI Prompt 弹窗添加手柄操作支持
-- 架构:叠加式 GamePad 层,不修改原插件,采用 hook 方式
local ADDON_NAME = "WaypointUI_Gamepad"
-- ==================== 状态 ====================
local activePrompt = nil
local focusIndex = 1
local focusButtons = {}
local isSwitchController = false
local lastStickTime = 0
local STICK_THROTTLE = 0.15
local STICK_DEADZONE = 0.5
-- Prompt 缩放:放大确认框使其在掌机上更易操作
-- 默认 1.5x(325px * 1.5 ≈ 487px,比原生 StaticPopup 还大)
local PROMPT_SCALE = 1.5
-- ==================== 工具函数 ====================
-- ConsolePort 存在性检测
local function HasConsolePort()
return ConsolePort ~= nil and ConsolePort.db ~= nil and ConsolePort.db.UIHandle ~= nil
end
-- 检测手柄类型
local function DetectControllerType()
isSwitchController = false
if ConsolePort then
if ConsolePort.GetControllerType then
local ctrlType = ConsolePort:GetControllerType()
isSwitchController = (ctrlType == "Switch")
elseif ConsolePort.GetDeviceType then
local deviceType = ConsolePort:GetDeviceType()
isSwitchController = (deviceType == "Switch")
end
end
-- 备用方案:通过 CVar 检测
if not isSwitchController then
local theme = GetCVar("gamepadThemeProvider")
if theme and theme:lower():find("switch") then
isSwitchController = true
end
end
end
-- 扫描活动按钮(按 element.index 排序,确保顺序与 UI 一致)
local function ScanActiveButtons(prompt)
local list = prompt.Content and prompt.Content.ButtonContainer and prompt.Content.ButtonContainer.List
if not list then return {} end
local buttons = {}
local pool = list.__elementPool
if pool then
for _, typePool in pairs(pool) do
for _, element in ipairs(typePool) do
if element.__shouldShow and element:IsShown() and element.value then
table.insert(buttons, element)
end
end
end
end
-- 按 index 排序(pairs 遍历不保证顺序)
table.sort(buttons, function(a, b)
return (a.index or 0) < (b.index or 0)
end)
return buttons
end
-- ==================== CP 集成 ====================
local function AddCPHints(prompt)
if not HasConsolePort() then return end
local handle = ConsolePort.db.UIHandle
if handle then
handle:ToggleHintFocus(prompt, true)
local confirmKey = isSwitchController and "CIRCLE" or "CROSS"
local cancelKey = isSwitchController and "CROSS" or "CIRCLE"
local confirmText = "确认"
local cancelText = "取消"
if #focusButtons > 0 and focusButtons[focusIndex] then
local focusedBtn = focusButtons[focusIndex]
if focusedBtn.value and focusedBtn.value.text then
confirmText = focusedBtn.value.text
end
end
handle:AddHint(confirmKey, confirmText)
handle:AddHint(cancelKey, cancelText)
end
end
local function UpdateCPHints()
if not HasConsolePort() then return end
local handle = ConsolePort.db.UIHandle
if handle and activePrompt then
local confirmKey = isSwitchController and "CIRCLE" or "CROSS"
local cancelKey = isSwitchController and "CROSS" or "CIRCLE"
local confirmText = "确认"
local cancelText = "取消"
if #focusButtons > 0 and focusButtons[focusIndex] then
local focusedBtn = focusButtons[focusIndex]
if focusedBtn.value and focusedBtn.value.text then
confirmText = focusedBtn.value.text
end
end
-- CP 没有单条更新 API,需要重置后重新添加
handle:ResetHintBar()
handle:AddHint(confirmKey, confirmText)
handle:AddHint(cancelKey, cancelText)
end
end
local function RemoveCPHints(prompt)
if not HasConsolePort() then return end
local handle = ConsolePort.db.UIHandle
if handle then
-- ToggleHintFocus(false) 内部会调用 ClearHintsForFrame + HideHintBar
handle:ToggleHintFocus(prompt, false)
end
end
-- ==================== 焦点导航 ====================
local function SetFocus(button)
if button and button.OnEnter then
button:OnEnter()
end
end
local function ClearFocus(button)
if button and button.OnLeave then
button:OnLeave()
end
end
local function FocusByDelta(delta)
if #focusButtons == 0 then return end
ClearFocus(focusButtons[focusIndex])
focusIndex = focusIndex + delta
if focusIndex < 1 then focusIndex = #focusButtons end
if focusIndex > #focusButtons then focusIndex = 1 end
SetFocus(focusButtons[focusIndex])
UpdateCPHints()
end
local function ClickFocusedButton()
local button = focusButtons[focusIndex]
if button and button:IsEnabled() and button.value then
-- Prompt 按钮的点击回调通过 HookMouseUp 注册(不走 OnClick),
-- 需要直接执行 value.callback + HidePrompt,模拟 OnElementClick 行为
if button.value.callback then
button.value.callback()
end
if activePrompt then
activePrompt:HidePrompt()
end
end
end
local function CancelPrompt()
if activePrompt and activePrompt.hideOnEscape then
activePrompt:OnEscape()
end
end
-- ==================== 手柄输入 ====================
local function OnGamePadButtonDown(_, button)
if not activePrompt or not activePrompt:IsShown() then return end
local confirmButton = isSwitchController and "PAD2" or "PAD1"
local cancelButton = isSwitchController and "PAD1" or "PAD2"
if button == "PADDLEFT" or button == "PADDUP" then
FocusByDelta(-1)
elseif button == "PADDRIGHT" or button == "PADDDOWN" then
FocusByDelta(1)
elseif button == confirmButton then
ClickFocusedButton()
elseif button == cancelButton then
CancelPrompt()
end
-- 阻止事件传播(非战斗时)
if not InCombatLockdown() then
SetPropagateKeyboardInput(false)
end
end
local function OnGamePadStick(_, stick, x, y, len)
if not activePrompt or not activePrompt:IsShown() then return end
if stick ~= "LStick" then return end
local now = GetTime()
if now - lastStickTime < STICK_THROTTLE then return end
-- 水平方向优先,垂直方向作为备选
if math.abs(x) > STICK_DEADZONE then
lastStickTime = now
FocusByDelta(x > 0 and 1 or -1)
elseif math.abs(y) > STICK_DEADZONE then
lastStickTime = now
FocusByDelta(y > 0 and -1 or 1)
end
end
-- ==================== Hook ====================
local function OnPromptOpen(prompt, info, ...)
if not prompt then return end
activePrompt = prompt
DetectControllerType()
-- 延迟扫描按钮(等待 List:RenderElements 完成)
C_Timer.After(0.05, function()
if not activePrompt or not activePrompt:IsShown() then return end
-- 清空旧数据
for _, btn in ipairs(focusButtons) do
ClearFocus(btn)
end
wipe(focusButtons)
focusIndex = 1
-- 扫描新按钮
local buttons = ScanActiveButtons(activePrompt)
for _, btn in ipairs(buttons) do
table.insert(focusButtons, btn)
end
-- 设置初始焦点
if #focusButtons > 0 then
SetFocus(focusButtons[1])
end
-- 注册手柄输入(战斗外)
if not InCombatLockdown() then
activePrompt:SetScript("OnGamePadButtonDown", OnGamePadButtonDown)
activePrompt:SetScript("OnGamePadStick", OnGamePadStick)
end
-- CP 集成
AddCPHints(activePrompt)
-- 放大 Prompt(掌机优化)
activePrompt:SetScale(PROMPT_SCALE)
end)
end
local function OnPromptHide(prompt)
if activePrompt and activePrompt == prompt then
-- 清理焦点
for _, btn in ipairs(focusButtons) do
ClearFocus(btn)
end
wipe(focusButtons)
focusIndex = 1
-- 清理手柄输入
if not InCombatLockdown() then
if activePrompt.SetScript then
activePrompt:SetScript("OnGamePadButtonDown", nil)
activePrompt:SetScript("OnGamePadStick", nil)
end
end
-- CP 清理
RemoveCPHints(activePrompt)
activePrompt = nil
end
end
-- ==================== 初始化 ====================
-- Dependencies: WaypointUI 保证了本插件在 WaypointUI 之后加载
-- WUISharedPrompt 在 WaypointUI 的 ADDON_LOADED 阶段就创建完毕
-- 所以在本插件的 ADDON_LOADED 时 WUISharedPrompt 已经可用
-- 战斗状态变化时重新注册/注销手柄输入
local combatFrame = CreateFrame("Frame")
combatFrame:RegisterEvent("PLAYER_REGEN_DISABLED")
combatFrame:RegisterEvent("PLAYER_REGEN_ENABLED")
combatFrame:SetScript("OnEvent", function(_, event)
if not activePrompt then return end
if event == "PLAYER_REGEN_ENABLED" then
-- 离开战斗,重新注册手柄输入
if activePrompt:IsShown() then
activePrompt:SetScript("OnGamePadButtonDown", OnGamePadButtonDown)
activePrompt:SetScript("OnGamePadStick", OnGamePadStick)
end
elseif event == "PLAYER_REGEN_DISABLED" then
-- 进入战斗,注销手柄输入(战斗中 SetScript 安全但 SetPropagateKeyboardInput 不安全)
-- 实际上 SetScript 在战斗中可用,只是 SetPropagateKeyboardInput 不行
-- 所以保留输入注册,只在 OnGamePadButtonDown 中跳过 SetPropagateKeyboardInput
end
end)
local initFrame = CreateFrame("Frame")
initFrame:RegisterEvent("ADDON_LOADED")
initFrame:SetScript("OnEvent", function(self, event, addon)
if event == "ADDON_LOADED" and addon == ADDON_NAME then
self:UnregisterEvent("ADDON_LOADED")
-- Hook Prompt
if WUISharedPrompt then
hooksecurefunc(WUISharedPrompt, "Open", function(...) OnPromptOpen(WUISharedPrompt, ...) end)
hooksecurefunc(WUISharedPrompt, "HidePrompt", function() OnPromptHide(WUISharedPrompt) end)
else
-- 极端情况:WUISharedPrompt 未就绪,等待 PLAYER_LOGIN 重试
self:RegisterEvent("PLAYER_LOGIN")
self:SetScript("OnEvent", function(s, e)
s:UnregisterEvent("PLAYER_LOGIN")
if WUISharedPrompt then
hooksecurefunc(WUISharedPrompt, "Open", function(...) OnPromptOpen(WUISharedPrompt, ...) end)
hooksecurefunc(WUISharedPrompt, "HidePrompt", function() OnPromptHide(WUISharedPrompt) end)
end
end)
end
end
end)
-- ==================== 导出供调试 ====================
local function ValidateScale(scale)
return type(scale) == "number" and scale > 0 and scale <= 5
end
_G.WaypointUI_Gamepad = {
GetActivePrompt = function() return activePrompt end,
GetFocusButtons = function() return focusButtons end,
GetFocusIndex = function() return focusIndex end,
IsSwitchController = function() return isSwitchController end,
GetPromptScale = function() return PROMPT_SCALE end,
SetPromptScale = function(scale)
if not ValidateScale(scale) then return end
PROMPT_SCALE = scale
-- 如果 Prompt 当前已显示,立即应用新缩放
if activePrompt and activePrompt:IsShown() then
activePrompt:SetScale(PROMPT_SCALE)
end
end,
}