Files
YataiDONNijiiro/Scripts/song_select/file_navigator/box_folder.lua
T
2026-10-14 16:14:11 -04:00

173 lines
8.1 KiB
Lua

-- FolderBox rendering (genre kanban), split out of song_select.lua. Shared helpers and
-- the wheel/folder animation state come from box_base.
local base = require("song_select/file_navigator/box_base")
local folder_leg = base.folder_leg
local BOARD_W = base.BOARD_W
local CAP_H = base.CAP_H
local CEN_CLOSED_H, CEN_OPEN_H = base.CEN_CLOSED_H, base.CEN_OPEN_H
local FOLDER_CLOSE_MS, STALE_TIMER_MS = base.FOLDER_CLOSE_MS, base.STALE_TIMER_MS
local draw_board, draw_glow, glow_pulse = base.draw_board, base.draw_glow, base.glow_pulse
local draw_title = base.draw_title
local outline, board_outline = base.outline, base.board_outline
local box_id = base.box_id
local go_get, leg_f, box_fade, fly_offset = base.go_get, base.leg_f, base.box_fade, base.fly_offset
local is_decided = base.is_decided
local wheel_open_t, wheel_close_step = base.wheel_open_t, base.wheel_close_step
local folder_open_timeline, folder_close_timeline = base.folder_open_timeline, base.folder_close_timeline
-- Event/search folders are matched by display name (the engine folds DEFAULT/RECOMMENDED/
-- FAVORITE/DAN/DIFFICULTY into genre frame 0).
local EVENT_FOLDER_NAMES = { ["ドドンと!新曲祭り!"] = true, ["New Songs"] = true, ["New"] = true,
["おすすめ曲"] = true, ["Recommended"] = true, ["推薦歌曲"] = true }
local SORT_FOLDER_NAMES = { ["曲検索"] = true, ["Search Song"] = true, ["搜尋歌曲"] = true,
["むずかしさで探す"] = true, ["Difficulty Sort"] = true, ["難易度排序"] = true,
["むずかしさからえらぶ"] = true, ["Diff Sort"] = true }
local FOLDER_FRAME_EVENT, FOLDER_FRAME_SORT, FOLDER_FRAME_DANI = 11, 12, 10
local SORT_COLLECTIONS = { SEARCH = true, DIFFICULTY = true }
local EVENT_COLLECTIONS = { NEW = true, RECOMMENDED = true }
local function folder_frame(box)
local n = box.text_name
if n and EVENT_FOLDER_NAMES[n] then return FOLDER_FRAME_EVENT, true end
local col = box.collection
if col and col ~= "" then
if EVENT_COLLECTIONS[col] then return FOLDER_FRAME_EVENT, true end
if SORT_COLLECTIONS[col] then return FOLDER_FRAME_SORT, false end
end
if box.kind and box:kind() == "dan" then return FOLDER_FRAME_DANI, false end
if n and SORT_FOLDER_NAMES[n] then return FOLDER_FRAME_SORT, false end
return box:genre_frame(), false
end
-- instance_board_pattern: 960x120 strip centred on the board, y follows the title
function SongSelect:draw_pattern(bx, by, p, fade)
if not self._id_pattern then return end
tex.draw_texture(self._id_pattern, { x = bx - 480, y = by - 94 * p - 60, fade = fade })
end
-- The genre board title, re-made at the skin_config folder_box_name size.
local FOLDER_TITLE_SIZE = 54
function SongSelect:folder_title(box)
local key = box.text_name
if not key or key == "" then return box:name() end
local ff = folder_frame(box)
key = ff .. "\0" .. key
local t = self._folder_titles[key]
if t == nil then
t = text.create_raw_text(box.text_name, FOLDER_TITLE_SIZE, { 255, 255, 255, 255 },
board_outline(ff), false, outline(7.5), 2.0)
self._folder_titles[key] = t or false
end
return t or box:name()
end
function SongSelect:draw_folder_board(box, pb, ch_alpha, ch_off, ta, glow_a)
local bx, by = box:box_x(), box:box_y()
local fdx, fdy = fly_offset(box); bx, by = bx + fdx, by + fdy -- wheel fly-in
local cen_h = CEN_CLOSED_H + (CEN_OPEN_H - CEN_CLOSED_H) * pb
local fade = box_fade(box)
local frame, has_pattern = folder_frame(box)
-- the genre board furls/unfurls horizontally across the folder transition
local ltitle = 1.0
local lf = (box:draw_state() == "open" or is_decided(box)) and leg_f() or nil
if lf then
local sx, ba = go_get("genre_board_sx", lf, 1), go_get("genre_board_sx", lf, 2)
if sx and ba then
if ba <= 0.0 then return end -- the board is not on screen
if sx < 1.0 then -- furl / unfurl: the open slab alone
draw_board(self._id_bar_folder, frame, bx, by, CEN_OPEN_H,
fade * ba, false, sx)
return
end
ch_alpha = math.min(ch_alpha, go_get("genre_chara_a", lf, 1) or 1.0)
ta = math.min(ta, go_get("genre_intro_a", lf, 1) or 1.0)
ltitle = go_get("genre_title_a", lf, 1) or 1.0
end
end
if glow_a > 0 then draw_glow(self._id_glow, bx, by, cen_h, fade * glow_a * glow_pulse()) end
-- closed folder = bar_genre, open folder = folder_graphic (title band on top)
if pb <= 0.0 then
draw_board(self._id_bar_genre, frame, bx, by, cen_h, fade)
else
draw_board(self._id_bar_folder, frame, bx, by, cen_h, fade, pb >= 1.0)
if pb < 1.0 then draw_board(self._id_bar_genre, frame, bx, by, cen_h, fade * (1.0 - pb)) end
end
if has_pattern then self:draw_pattern(bx, by, pb, fade) end
if box:has_box_texture() then
-- a library-supplied box.png is fitted (aspect kept, shrink only) into the board rect
if ch_alpha > 0 then
box:draw_box_texture({ cx = bx, cy = by,
max_w = BOARD_W, max_h = CAP_H * 2 + CEN_OPEN_H,
fade = fade * ch_alpha })
end
-- title drawn after the art, matching the cabinet's depth order
draw_title(self:folder_title(box), bx, by - 94 * pb, fade * ltitle)
return
end
-- characters: texture has L at x=510 and R at x=1410
if ch_alpha > 0 then
local slide = 450.0 - ch_off
local gframe = box:genre_frame()
tex.draw_texture(self._id_box_chara, { frame = gframe, x = bx - 960 + slide, y = by - 240, x2 = 960 - 1920, fade = fade * ch_alpha,
src = { x = 0, y = 0, width = 960, height = 480 } })
tex.draw_texture(self._id_box_chara, { frame = gframe, x = bx - slide, y = by - 240, x2 = 960 - 1920, fade = fade * ch_alpha,
src = { x = 960, y = 0, width = 960, height = 480 } })
end
-- title rises to y-94 when open; the box explanation lines stand in for the intro text
draw_title(self:folder_title(box), bx, by - 94 * pb, fade * ltitle)
if ta > 0 then
local lines = {}
for _, l in ipairs(box:explanation() or {}) do
if l ~= "" then lines[#lines + 1] = l end
end
for i, l in ipairs(lines) do
local ckey = frame .. "\0" .. l
local label = self._count_cache[ckey]
if not label then
label = text.create_raw_text(l, 32, { 255, 255, 255, 255 }, board_outline(frame), false, outline(4.0), 2.0, 1.0, "FOT-Seurat_Pro_B.otf")
self._count_cache[ckey] = label
end
draw_title(label, bx, by + (49 + (i - (#lines + 1) / 2) * 36) * pb, fade * ta, BOARD_W - 120)
end
end
end
function SongSelect:draw_folder_closed(box)
local key = "f\0" .. box_id(box)
local e = wheel_close_step(key, FOLDER_CLOSE_MS, STALE_TIMER_MS)
if e then
local pb, ch_alpha, ch_off, ta = folder_close_timeline(e)
self:draw_folder_board(box, pb, ch_alpha, ch_off, ta, pb)
return
end
self:draw_folder_board(box, 0.0, 0.0, 340.0, 0.0, 0.0)
end
function SongSelect:draw_folder_open(box)
-- remember the decided board: the engine removes it from the wheel while its songs
-- load, and draw_overlays keeps drawing it until it has furled away
folder_leg.last_open_id, folder_leg.last_open_box = box_id(box), box
if is_decided(box) then
self._dec_frame = self._frame_no
self:draw_folder_board(box, 1.0, 1.0, 440.0, 1.0, 1.0)
return
end
local key = "f\0" .. box_id(box)
local t = wheel_open_t(key)
if t == nil then -- 508 ms pre-growth hold: fully closed
self:draw_folder_board(box, 0.0, 0.0, 340.0, 0.0, 0.0)
return
end
local pb, ch_alpha, ch_off, ta = folder_open_timeline(t)
self:draw_folder_board(box, pb, ch_alpha, ch_off, ta, pb)
end
return { folder_frame = folder_frame }