Files
gen1recomp/src/ui/ShaderFXScreen.lua
T
2026-09-02 05:58:45 -04:00

184 lines
6.9 KiB
Lua

-- The SHADER FX preset picker: OFF, every .slangp ShaderFX.list() finds, then a
-- permanent DOWNLOAD SHADERS action row. `slot` picks which of ShaderFX.SLOTS
-- A activates and which save.options key persists it. See docs/shaderfx.md.
local ListMenu = require("src.ui.ListMenu")
local ShaderFX = require("src.render.ShaderFX")
local Strings = require("src.core.Strings")
local Screens = require("src.ui.Screens")
local ShaderFXScreen = setmetatable({}, { __index = ListMenu })
ShaderFXScreen.__index = ShaderFXScreen
-- OFF plus the preset's display name with its extension stripped.
local function label(entry)
if not entry then return Strings("OFF") end
return Strings((entry.name:gsub("%.slangp$", "")))
end
-- One row's display state from its entry.converted, shared by new() and
-- onChoose() so a successful convert updates a row exactly as building it did.
local function applyRowState(item, canConvert)
if not item.entry then return end
-- NOT `cond and a or b`: that idiom breaks when `a` is itself nil/false.
if item.entry.converted then
item.muted = false
item.right = nil
elseif canConvert then
item.muted = true
item.right = Strings("CONVERT")
else
item.muted = true
item.right = Strings("UPDATE")
end
end
-- OFF, every real preset, then the permanent DOWNLOAD SHADERS action row.
local function buildItems(active, canConvert)
local items = { { label = label(nil), entry = nil } }
local selected = 1
for _, entry in ipairs(ShaderFX.list()) do
local item = { label = label(entry), entry = entry }
applyRowState(item, canConvert)
items[#items + 1] = item
if active and active.name == entry.name then selected = #items end
end
items[#items + 1] = { label = Strings("DOWNLOAD SHADERS"), download = true }
return items, selected
end
-- `slot` defaults to "main" so a caller that still pushes this screen with no
-- argument keeps today's behavior rather than erroring on a nil slot key.
function ShaderFXScreen.new(game, slot)
slot = slot or "main"
local optKey = ShaderFX.OPTION_KEY[slot]
local title = (slot == "secondary") and "SHADER FX 2" or "SHADER FX"
local canConvert = ShaderFX.canConvert()
local items, selected = buildItems(ShaderFX.activeEntry(slot), canConvert)
local self = setmetatable(ListMenu.new(game, title, items, {
-- 7 rows leaves the title line and the one-line footer hint free.
rows = 7,
footer = Strings("SELECT:EDIT PARAMS"),
}), ShaderFXScreen)
-- Opens on the active preset (or OFF).
self.index = selected
self.downloadJob = nil
-- Rebuilds from ShaderFX.list() (e.g. after an install adds presets), keeping
-- the cursor on the same row index (clamped) rather than resetting to OFF.
local function refresh()
local newItems = buildItems(ShaderFX.activeEntry(slot), canConvert)
self.items = newItems
self.index = math.max(1, math.min(self.index, #newItems))
end
-- Polls the in-flight buildbot download alongside ListMenu's own input
-- handling. installDownloaded() itself is synchronous, a sub-second job.
local baseUpdate = ListMenu.update
self.update = function(self_, dt)
if self_.downloadJob then
local st = ShaderFX.downloadStatus(self_.downloadJob)
local row = self_.items[#self_.items]
if st.status == "pending" then
local pct = st.progress and (" %d%%"):format(st.progress * 100) or ""
row.right = Strings("...") .. pct
elseif st.status == "ok" then
local copied, err, unchanged = ShaderFX.installDownloaded(st.notModified)
self_.downloadJob = nil
if unchanged then
require("src.core.Logger").info("ShaderFXScreen: buildbot presets already up to date")
row.right = Strings("UP TO DATE")
elseif copied then
require("src.core.Logger").info("ShaderFXScreen: buildbot install copied %d files", copied)
refresh()
else
require("src.core.Logger").error("ShaderFXScreen: buildbot install failed: %s", tostring(err))
self_.items[#self_.items].right = Strings("FAILED")
end
else -- "error" or "cancelled"
self_.downloadJob = nil
require("src.core.Logger").error("ShaderFXScreen: buildbot download failed: %s", tostring(st.err))
row.right = Strings("FAILED")
end
end
baseUpdate(self_, dt)
end
self.onChoose = function(item)
if item.download then
-- Ignore a repeat press while one is already in flight.
if self.downloadJob then return end
self.downloadJob = ShaderFX.downloadPresets()
item.right = Strings("...")
return
end
-- A on an unconverted preset converts it in place instead of activating;
-- the screen stays open either way, since a convert is not a selection.
if item.entry and not item.entry.converted then
if not canConvert then
self.footer = Strings("Reinstall the app")
return
end
ShaderFX.clearBridgeQuarantine()
local ok, err = ShaderFX.convert(item.entry)
if not ok then
require("src.core.Logger").error("ShaderFXScreen: convert failed for %s: %s",
item.entry.name, tostring(err))
end
applyRowState(item, canConvert)
if not ok then item.right = Strings("FAILED") end
return
end
local opts = game.save and game.save.options
if not item.entry then
ShaderFX.deactivate(slot)
if opts then opts[optKey] = nil end
else
-- isConverted() is existence-only with no staleness check, so an explicit
-- selection always reconverts. Human-paced, CPU-only, never per frame.
if canConvert then
ShaderFX.clearBridgeQuarantine()
local convOk, convErr = ShaderFX.convert(item.entry)
if not convOk then
require("src.core.Logger").error("ShaderFXScreen: reconvert failed for %s: %s",
item.entry.name, tostring(convErr))
end
end
local overrides = opts and opts.shaderfxParams and opts.shaderfxParams[item.entry.name]
local ok, err = ShaderFX.activate(slot, item.entry, overrides)
if opts then opts[optKey] = ok and item.entry.name or nil end
if not ok then
require("src.core.Logger").error("ShaderFXScreen: activate failed for %s: %s",
item.entry.name, tostring(err))
item.right = Strings("FAILED")
if game.writeOptions then
game:writeOptions()
elseif game.persistOptions then
game:persistOptions()
end
return
end
end
if game.writeOptions then
game:writeOptions()
elseif game.persistOptions then
game:persistOptions()
end
self:close()
end
-- SELECT on a real, converted row opens its pragma-parameter editor; OFF, the
-- action row and an unconverted preset have nothing to read metadata from.
self.onSelectKey = function(item)
if not item.entry or item.download or not item.entry.converted then return end
Screens.push(game, "ShaderFXParamsScreen", item.entry)
end
return self
end
return ShaderFXScreen