From 76b6bc9fe7d101ead710fe0625479138074385c1 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 24 Mar 2026 08:51:29 +0000 Subject: [PATCH 1/5] Add TAB-hold shortcuts menu for quick multiplayer actions Pressing TAB shows a context-aware overlay with keyboard shortcuts: - Main menu (connected): V to join from clipboard, J to join lobby, C to create lobby - Main menu (in lobby): C to copy code, V to view code, L to leave - Main menu (disconnected): R to reconnect - In-game (in lobby): C to copy code, I for lobby info Shortcuts can be triggered by pressing the key while TAB is held, or by clicking the shortcut row. Menu dismisses on TAB release. https://claude.ai/code/session_014FaSJ5FwuXMfcUmM5Hiy81 --- localization/en-us.lua | 10 ++ ui/shortcuts_menu.lua | 286 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 296 insertions(+) create mode 100644 ui/shortcuts_menu.lua diff --git a/localization/en-us.lua b/localization/en-us.lua index 8df7f5fc..82e15dfe 100644 --- a/localization/en-us.lua +++ b/localization/en-us.lua @@ -1113,6 +1113,16 @@ return { "Once per Ante", "Off", }, + k_sc_title = "SHORTCUTS", + k_sc_hint = "Press key or release TAB to close", + b_sc_join_clipboard = "Join from Clipboard", + b_sc_join_lobby = "Join Lobby", + b_sc_create_lobby = "Create Lobby", + b_sc_copy_code = "Copy Lobby Code", + b_sc_view_code = "View Lobby Code", + b_sc_leave_lobby = "Leave Lobby", + b_sc_reconnect = "Reconnect", + b_sc_lobby_info = "Lobby Info", loc_ready = "Ready for PvP", loc_selecting = "Selecting a Blind", loc_shop = "Shopping", diff --git a/ui/shortcuts_menu.lua b/ui/shortcuts_menu.lua new file mode 100644 index 00000000..e57548c1 --- /dev/null +++ b/ui/shortcuts_menu.lua @@ -0,0 +1,286 @@ +-- TAB-hold shortcuts menu +-- Shows a context-aware overlay of quick actions while TAB is held + +MP.SHORTCUTS = { + visible = false, + ui = nil, +} + +-- Build the list of available shortcuts based on current state +local function get_shortcuts() + local shortcuts = {} + local in_lobby = MP.LOBBY.code ~= nil + local connected = MP.LOBBY.connected + local in_game = G.STAGE == G.STAGES.RUN + local in_menu = G.STAGE == G.STAGES.MAIN_MENU + + if in_menu then + if in_lobby then + table.insert(shortcuts, { + label = localize("b_sc_copy_code"), + key = "C", + action = function() + MP.UTILS.copy_to_clipboard(MP.LOBBY.code) + end, + }) + table.insert(shortcuts, { + label = localize("b_sc_view_code"), + key = "V", + action = function() + MP.UI.UTILS.overlay_message(MP.LOBBY.code) + end, + }) + table.insert(shortcuts, { + label = localize("b_sc_leave_lobby"), + key = "L", + action = function() + G.FUNCS.lobby_leave() + end, + }) + elseif connected then + table.insert(shortcuts, { + label = localize("b_sc_join_clipboard"), + key = "V", + action = function() + G.FUNCS.join_from_clipboard() + end, + }) + table.insert(shortcuts, { + label = localize("b_sc_join_lobby"), + key = "J", + action = function() + G.FUNCS.join_lobby() + end, + }) + table.insert(shortcuts, { + label = localize("b_sc_create_lobby"), + key = "C", + action = function() + G.FUNCS.create_lobby() + end, + }) + else + table.insert(shortcuts, { + label = localize("b_sc_reconnect"), + key = "R", + action = function() + G.FUNCS.reconnect() + end, + }) + end + end + + if in_game and in_lobby then + table.insert(shortcuts, { + label = localize("b_sc_copy_code"), + key = "C", + action = function() + MP.UTILS.copy_to_clipboard(MP.LOBBY.code) + end, + }) + table.insert(shortcuts, { + label = localize("b_sc_lobby_info"), + key = "I", + action = function() + G.FUNCS.lobby_info() + end, + }) + end + + return shortcuts +end + +-- Create the UI definition for the shortcuts overlay +local function create_shortcuts_ui(shortcuts) + local rows = {} + + -- Header + table.insert(rows, { + n = G.UIT.R, + config = { align = "cm", padding = 0.08 }, + nodes = { + { + n = G.UIT.T, + config = { + text = localize("k_sc_title"), + scale = 0.5, + colour = G.C.UI.TEXT_LIGHT, + shadow = true, + }, + }, + }, + }) + + -- Shortcut rows + for _, sc in ipairs(shortcuts) do + table.insert(rows, { + n = G.UIT.R, + config = { + align = "cm", + padding = 0.04, + r = 0.08, + colour = G.C.L_BLACK, + hover = true, + button = "mp_shortcut_exec", + ref_table = sc, + }, + nodes = { + { + n = G.UIT.C, + config = { align = "cm", minw = 1 }, + nodes = { + { + n = G.UIT.R, + config = { + align = "cm", + padding = 0.04, + r = 0.05, + colour = G.C.PURPLE, + minw = 0.6, + }, + nodes = { + { + n = G.UIT.T, + config = { + text = sc.key, + scale = 0.4, + colour = G.C.UI.TEXT_LIGHT, + shadow = true, + }, + }, + }, + }, + }, + }, + { + n = G.UIT.C, + config = { align = "cl", minw = 3.5 }, + nodes = { + { + n = G.UIT.T, + config = { + text = " " .. sc.label, + scale = 0.38, + colour = G.C.UI.TEXT_LIGHT, + }, + }, + }, + }, + }, + }) + end + + -- Footer hint + table.insert(rows, { + n = G.UIT.R, + config = { align = "cm", padding = 0.06 }, + nodes = { + { + n = G.UIT.T, + config = { + text = localize("k_sc_hint"), + scale = 0.3, + colour = G.C.UI.TEXT_INACTIVE, + }, + }, + }, + }) + + return { + n = G.UIT.ROOT, + config = { + align = "cm", + colour = { 0, 0, 0, 0.85 }, + r = 0.15, + padding = 0.15, + minw = 5, + }, + nodes = { + { + n = G.UIT.C, + config = { align = "cm", padding = 0.05 }, + nodes = rows, + }, + }, + } +end + +function G.FUNCS.mp_shortcut_exec(e) + if e.config.ref_table and e.config.ref_table.action then + MP.SHORTCUTS.hide() + e.config.ref_table.action() + end +end + +function MP.SHORTCUTS.show() + if MP.SHORTCUTS.visible then return end + + local shortcuts = get_shortcuts() + if #shortcuts == 0 then return end + + MP.SHORTCUTS.visible = true + MP.SHORTCUTS.current_shortcuts = shortcuts + + MP.SHORTCUTS.ui = UIBox({ + definition = create_shortcuts_ui(shortcuts), + config = { + align = "cm", + offset = { x = 0, y = 0 }, + major = G.ROOM_ATTACH, + bond = "Weak", + }, + }) +end + +function MP.SHORTCUTS.hide() + if not MP.SHORTCUTS.visible then return end + + MP.SHORTCUTS.visible = false + if MP.SHORTCUTS.ui then + MP.SHORTCUTS.ui:remove() + MP.SHORTCUTS.ui = nil + end + MP.SHORTCUTS.current_shortcuts = nil +end + +-- Execute a shortcut by its key letter +function MP.SHORTCUTS.execute_key(key) + if not MP.SHORTCUTS.current_shortcuts then return false end + + local upper_key = string.upper(key) + for _, sc in ipairs(MP.SHORTCUTS.current_shortcuts) do + if sc.key == upper_key then + MP.SHORTCUTS.hide() + sc.action() + return true + end + end + return false +end + +-- Hook into Controller to detect TAB press and shortcut key presses +local key_press_update_ref = Controller.key_press_update +function Controller:key_press_update(key, dt) + -- Intercept shortcut key presses while menu is visible + if MP.SHORTCUTS.visible and #key == 1 then + if MP.SHORTCUTS.execute_key(key) then + return + end + end + + if key == "tab" then + if not G.OVERLAY_MENU then + MP.SHORTCUTS.show() + return + end + end + key_press_update_ref(self, key, dt) +end + +local key_release_update_ref = Controller.key_release_update +function Controller:key_release_update(key, dt) + if key == "tab" then + MP.SHORTCUTS.hide() + end + key_release_update_ref(self, key, dt) +end From fcc9b8709762a101e098b97161d06097c68d6853 Mon Sep 17 00:00:00 2001 From: Stephen Kirk Date: Mon, 6 Apr 2026 00:24:10 +0200 Subject: [PATCH 2/5] Adjust shortcuts menu overlay: 40% opacity, left-aligned positioning --- ui/shortcuts_menu.lua | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ui/shortcuts_menu.lua b/ui/shortcuts_menu.lua index e57548c1..5606f4f7 100644 --- a/ui/shortcuts_menu.lua +++ b/ui/shortcuts_menu.lua @@ -189,8 +189,8 @@ local function create_shortcuts_ui(shortcuts) return { n = G.UIT.ROOT, config = { - align = "cm", - colour = { 0, 0, 0, 0.85 }, + align = "cl", + colour = { 0, 0, 0, 0.4 }, r = 0.15, padding = 0.15, minw = 5, @@ -198,7 +198,7 @@ local function create_shortcuts_ui(shortcuts) nodes = { { n = G.UIT.C, - config = { align = "cm", padding = 0.05 }, + config = { align = "cl", padding = 0.05 }, nodes = rows, }, }, @@ -225,7 +225,7 @@ function MP.SHORTCUTS.show() definition = create_shortcuts_ui(shortcuts), config = { align = "cm", - offset = { x = 0, y = 0 }, + offset = { x = -5, y = 0 }, major = G.ROOM_ATTACH, bond = "Weak", }, From 821135123bbce242bb76f8813bb9e32bb1a50135 Mon Sep 17 00:00:00 2001 From: Stephen Kirk Date: Tue, 7 Apr 2026 21:48:18 +0200 Subject: [PATCH 3/5] Reuse existing localization keys for shortcuts menu, add deck picker shortcut - Replace duplicate b_sc_* keys with existing b_copy_code, b_view_code, b_join_lobby, b_create_lobby, b_leave_lobby, b_reconnect, b_lobby_info - Re-add View Code shortcut using existing b_view_code - Add Choose Deck/Stake shortcut (D key), gated on host or different_decks - Only new loc keys: k_sc_title, k_sc_hint, b_sc_choose_deck --- localization/en-us.lua | 9 +-------- ui/shortcuts_menu.lua | 27 ++++++++++++++++++--------- 2 files changed, 19 insertions(+), 17 deletions(-) diff --git a/localization/en-us.lua b/localization/en-us.lua index 82e15dfe..5baea84c 100644 --- a/localization/en-us.lua +++ b/localization/en-us.lua @@ -1115,14 +1115,7 @@ return { }, k_sc_title = "SHORTCUTS", k_sc_hint = "Press key or release TAB to close", - b_sc_join_clipboard = "Join from Clipboard", - b_sc_join_lobby = "Join Lobby", - b_sc_create_lobby = "Create Lobby", - b_sc_copy_code = "Copy Lobby Code", - b_sc_view_code = "View Lobby Code", - b_sc_leave_lobby = "Leave Lobby", - b_sc_reconnect = "Reconnect", - b_sc_lobby_info = "Lobby Info", + b_sc_choose_deck = "Choose Deck/Stake", loc_ready = "Ready for PvP", loc_selecting = "Selecting a Blind", loc_shop = "Shopping", diff --git a/ui/shortcuts_menu.lua b/ui/shortcuts_menu.lua index 5606f4f7..cb673ecf 100644 --- a/ui/shortcuts_menu.lua +++ b/ui/shortcuts_menu.lua @@ -17,21 +17,30 @@ local function get_shortcuts() if in_menu then if in_lobby then table.insert(shortcuts, { - label = localize("b_sc_copy_code"), + label = localize("b_copy_code"), key = "C", action = function() MP.UTILS.copy_to_clipboard(MP.LOBBY.code) end, }) table.insert(shortcuts, { - label = localize("b_sc_view_code"), + label = localize("b_view_code"), key = "V", action = function() MP.UI.UTILS.overlay_message(MP.LOBBY.code) end, }) + if MP.LOBBY.is_host or MP.LOBBY.config.different_decks then + table.insert(shortcuts, { + label = localize("b_sc_choose_deck"), + key = "D", + action = function() + G.FUNCS.lobby_choose_deck() + end, + }) + end table.insert(shortcuts, { - label = localize("b_sc_leave_lobby"), + label = localize("b_leave_lobby"), key = "L", action = function() G.FUNCS.lobby_leave() @@ -39,21 +48,21 @@ local function get_shortcuts() }) elseif connected then table.insert(shortcuts, { - label = localize("b_sc_join_clipboard"), + label = localize("b_join_lobby_clipboard"), key = "V", action = function() G.FUNCS.join_from_clipboard() end, }) table.insert(shortcuts, { - label = localize("b_sc_join_lobby"), + label = localize("b_join_lobby"), key = "J", action = function() G.FUNCS.join_lobby() end, }) table.insert(shortcuts, { - label = localize("b_sc_create_lobby"), + label = localize("b_create_lobby"), key = "C", action = function() G.FUNCS.create_lobby() @@ -61,7 +70,7 @@ local function get_shortcuts() }) else table.insert(shortcuts, { - label = localize("b_sc_reconnect"), + label = localize("b_reconnect"), key = "R", action = function() G.FUNCS.reconnect() @@ -72,14 +81,14 @@ local function get_shortcuts() if in_game and in_lobby then table.insert(shortcuts, { - label = localize("b_sc_copy_code"), + label = localize("b_copy_code"), key = "C", action = function() MP.UTILS.copy_to_clipboard(MP.LOBBY.code) end, }) table.insert(shortcuts, { - label = localize("b_sc_lobby_info"), + label = localize("b_lobby_info"), key = "I", action = function() G.FUNCS.lobby_info() From 10deae7e69583e761920bd162eb706b7a54f6efc Mon Sep 17 00:00:00 2001 From: Stephen Kirk Date: Tue, 7 Apr 2026 21:51:36 +0200 Subject: [PATCH 4/5] Fix crash when opening deck picker from shortcuts menu lobby_choose_deck passes its argument to vanilla setup_run which indexes e.config. Pass a stub UI element table to avoid nil index. --- ui/shortcuts_menu.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/shortcuts_menu.lua b/ui/shortcuts_menu.lua index cb673ecf..4fb805ef 100644 --- a/ui/shortcuts_menu.lua +++ b/ui/shortcuts_menu.lua @@ -35,7 +35,7 @@ local function get_shortcuts() label = localize("b_sc_choose_deck"), key = "D", action = function() - G.FUNCS.lobby_choose_deck() + G.FUNCS.lobby_choose_deck({ config = {} }) end, }) end From 4a123140705c2db95d0b362f5082bf9886de7c96 Mon Sep 17 00:00:00 2001 From: Stephen Kirk Date: Tue, 7 Apr 2026 21:52:34 +0200 Subject: [PATCH 5/5] Remove in-game shortcuts from TAB menu No need for lobby code or lobby info shortcuts during gameplay. --- ui/shortcuts_menu.lua | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/ui/shortcuts_menu.lua b/ui/shortcuts_menu.lua index 4fb805ef..1c4b4818 100644 --- a/ui/shortcuts_menu.lua +++ b/ui/shortcuts_menu.lua @@ -11,7 +11,6 @@ local function get_shortcuts() local shortcuts = {} local in_lobby = MP.LOBBY.code ~= nil local connected = MP.LOBBY.connected - local in_game = G.STAGE == G.STAGES.RUN local in_menu = G.STAGE == G.STAGES.MAIN_MENU if in_menu then @@ -79,23 +78,6 @@ local function get_shortcuts() end end - if in_game and in_lobby then - table.insert(shortcuts, { - label = localize("b_copy_code"), - key = "C", - action = function() - MP.UTILS.copy_to_clipboard(MP.LOBBY.code) - end, - }) - table.insert(shortcuts, { - label = localize("b_lobby_info"), - key = "I", - action = function() - G.FUNCS.lobby_info() - end, - }) - end - return shortcuts end