Files
alchemist-accessibility/accessibility/states/_itemgrid.lua
T

155 lines
5.1 KiB
Lua
Raw Normal View History

-- Shared descriptor for the item-grid screens: inventory, craft, refine, trade.
--
-- All four build a `views/inventoryWindow` and move a cursor over a grid of
-- cells. The inventoryCursorPopup patch speaks item details, but ONLY when a cell
-- actually holds an item -- so empty cells and empty tabs were silent and you
-- could not tell the cursor had moved. This owns narration for these screens
-- instead (patches/popups.lua stands down wherever a descriptor exists).
--
-- Returns a descriptor; call with the state's window upvalue name.
2026-08-02 02:59:21 +02:00
--
-- Second thing this owns: the ITEM ACTION MENU. Pressing enter/z on an occupied
-- cell calls inventoryWindow.enableItemMenu(), which builds a little popup list
-- of the actions that item currently allows ("Switch on", "Wield", "Drink",
-- "Read"...) from modules/item/getActionsList.lua. It is a second, nested
-- selection -- up/down move within it, enter activates, anything else cancels --
-- and nothing about it goes through console.print or the cursor popup, so it was
-- completely silent. All the player heard was the sound.select click that
-- enableItemMenu plays on success.
--
-- The menu is a closure (modules/item/itemMenu.lua) with no getters, so its
-- `lines` and `currentSelectedAction` are read as upvalues of its own draw().
local itemCategories = require("modules.item.categories")
return function(options)
options = options or {}
local windowName = options.window or "inventoryWindow"
local lastTab
2026-08-02 02:59:21 +02:00
local lastMenu
-- Read the open item action menu, if there is one.
local function readItemMenu(window, menus)
local itemMenu = window.itemMenu
if type(itemMenu) ~= "table" or type(itemMenu.draw) ~= "function" then
lastMenu = nil
return nil, nil
end
local lines = menus.getUpvalue(itemMenu.draw, "lines")
local selected = menus.getUpvalue(itemMenu.draw, "currentSelectedAction")
if type(lines) ~= "table" or type(selected) ~= "number" then
return nil, nil
end
local line = lines[selected]
if type(line) ~= "table" or not line.text then
return nil, nil
end
local parts = {}
-- Lead with the item only when this menu just opened, so arrowing through
-- two or three actions doesn't repeat the name every time. `lastMenu` holds
-- the menu table itself rather than a flag, which keeps read() idempotent:
-- calling it twice for the same focus produces the same words both times.
if itemMenu ~= lastMenu then
lastMenu = itemMenu
local ok, item = pcall(window.getItemOnCursor)
if ok and item and item.name then
parts[#parts + 1] = item.name
end
parts[#parts + 1] = "actions"
end
parts[#parts + 1] = line.text
parts[#parts + 1] = selected .. " of " .. #lines
return table.concat(parts, ", "),
"itemmenu:" .. tostring(selected) .. "/" .. tostring(#lines)
end
return {
2026-08-02 02:59:21 +02:00
reset = function()
lastTab = nil
lastMenu = nil
end,
read = function(stateTable, menus)
2026-08-02 02:59:21 +02:00
local window = menus.getAnyUpvalue(stateTable, windowName)
if type(window) ~= "table" or not window.getItemOnCursor then
return nil, nil
end
-- The action menu is modal over the grid: while it is open, up/down
-- move within IT, not over the cells. So it is checked first, and the
-- grid cell stays quiet underneath it.
if window.itemMenu then
return readItemMenu(window, menus)
end
lastMenu = nil
-- While the quantity confirmation window is open, the slider owns
-- speech (see patches/slider.lua). Announcing the grid cell over the
-- top of it would fight the number the player is adjusting.
local confirming = menus.getAnyUpvalue(stateTable, "confirmationWindowEnabled")
if confirming then
return nil, nil
end
local position = window.cursorPosition
if type(position) ~= "number" then
return nil, nil
end
-- activeTabIndex lives inside the window closure, not the state file,
-- so reach it through one of the window's own methods.
local tabIndex
if window.adjustTab then
tabIndex = menus.getUpvalue(window.adjustTab, "activeTabIndex")
end
local parts = {}
if tabIndex and tabIndex ~= lastTab then
lastTab = tabIndex
local category = itemCategories[tabIndex]
parts[#parts + 1] = (category and category.header) or ("tab " .. tabIndex)
end
local ok, item = pcall(window.getItemOnCursor)
if ok and item then
parts[#parts + 1] = item.name or item.templateId or "item"
if item.quantity and item.quantity > 1 then
parts[#parts + 1] = "x" .. item.quantity
end
2026-08-02 02:59:21 +02:00
if item.isEquipped then
parts[#parts + 1] = "equipped"
end
if item.isUnavailable then
parts[#parts + 1] = "unavailable"
end
else
parts[#parts + 1] = "empty"
end
parts[#parts + 1] = "slot " .. position
2026-08-02 02:59:21 +02:00
-- isEquipped is part of the focus key on purpose: after "Switch on"
-- the menu closes and the cursor has not moved, so without it the
-- re-read would be suppressed as unchanged and the player would get no
-- confirmation that the action took effect.
return table.concat(parts, ", "),
table.concat({
tostring(tabIndex), tostring(position),
tostring(ok and item and (item.name or "?")),
2026-08-02 02:59:21 +02:00
tostring(ok and item and item.isEquipped or false),
}, ":")
end,
}
end