- Back entry: a chip in the top strip (back/folder, a light blue in the parent's back colour) with its label from skin_config dan_back_label, so it follows the language (ja 戻る, en Back, zh 返回, ko 뒤로). - Chip names are black with no outline: dan_chip_name_black moves into screen.options, where the engine reads it. - The selected chip drops below the strip and eases back; the cursor glow covers the chip and its name but not the clear stamp. - Course rows: the 1st/2nd/3rd labels are centred on their row, and the level is one "star + number" image per level (yellow_box/difficulty_level, 1..10 and 10+) in place of the separate star and digits. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
266 lines
11 KiB
Lua
266 lines
11 KiB
Lua
-- DAN_SELECT paint surface: selection cursor breathing loop and arrow chips,
|
|
-- sampled from the cabinet's own clips. The dan boxes themselves are drawn by the
|
|
-- parent skin's dan_select.lua, which is loaded first and extended here.
|
|
|
|
-- the parent skin's copy of this script: the second match on package.path (the first is this file)
|
|
local function load_parent(name)
|
|
local seen = false
|
|
for tmpl in package.path:gmatch("[^;]+") do
|
|
local chunk = loadfile((tmpl:gsub("%?", name)))
|
|
if chunk then
|
|
if seen then return chunk end
|
|
seen = true
|
|
end
|
|
end
|
|
end
|
|
local parent = load_parent("dan_select/dan_select")
|
|
if parent then parent() end
|
|
if not DanSelect then
|
|
DanSelect = {}
|
|
DanSelect.__index = DanSelect
|
|
end
|
|
local parent_new = DanSelect.new
|
|
|
|
local ok, anim = pcall(require, "anim/sampler")
|
|
if not ok then anim = { safe = function()
|
|
local n = {}
|
|
n.at = function() return nil end; n.get = function() return nil end
|
|
n.label = function() return nil end; n.has = function() return false end
|
|
n.first = function() return 0 end; n.last = function() return 0 end
|
|
return n
|
|
end } end
|
|
|
|
local CUR = anim.safe("anim/dan_cursor")
|
|
local ARW = anim.safe("anim/dan_arrow")
|
|
local ARW_OFF = ARW:label("arrow_off") or 61
|
|
|
|
-- No real clip data at all -> no draw_cursor: the engine draws the cursor from its
|
|
-- inline tables (the boxes are still drawn by the parent's DanSelect).
|
|
local HAS_CLIPS = CUR:has("#259@0") or ARW:has("#261@0")
|
|
|
|
function DanSelect.new()
|
|
local self = parent_new and parent_new() or setmetatable({}, DanSelect)
|
|
self.t_cursor = tex.get_texture("dan_select/box/cursor")
|
|
self.t_arrow_r = tex.get_texture("dan_select/box/arrow_r")
|
|
self.t_arrow_l = tex.get_texture("dan_select/box/arrow_l")
|
|
return self
|
|
end
|
|
|
|
-- pos: selected chip x (the textures' own json positions carry the rest);
|
|
-- now_ms: engine clock; moved_ms: ms since the last ribbon step (-1 = never);
|
|
-- y: how far the selected chip has dropped (see draw_box below).
|
|
local function draw_cursor(self, pos, now_ms, moved_ms, y)
|
|
y = y or 0
|
|
if self.t_cursor then
|
|
local f = (now_ms * 0.06) % 60
|
|
local a = CUR:get("#259@0", f, "a") or 1
|
|
tex.draw_texture(self.t_cursor, { x = pos, y = y, fade = a })
|
|
end
|
|
if moved_ms and moved_ms >= 0 then
|
|
local f = moved_ms * 0.06
|
|
if f <= (ARW_OFF - 1) then
|
|
local drift = ARW:get("#261@0", f, "tx") or 0
|
|
local a = ARW:get("#261@0", f, "a") or 0
|
|
if a > 0 then
|
|
if self.t_arrow_r then
|
|
tex.draw_texture(self.t_arrow_r, { x = pos + drift, y = y, fade = a })
|
|
end
|
|
if self.t_arrow_l then
|
|
tex.draw_texture(self.t_arrow_l, { x = pos - drift, y = y, fade = a })
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
if HAS_CLIPS then DanSelect.draw_cursor = draw_cursor end
|
|
|
|
-- The level is the cabinet's own art, one "star + number" image per level
|
|
-- (yellow_box/difficulty_level, frames 1..10 and 10+), in place of the parent's star and
|
|
-- level digits, which are held back.
|
|
local function draw_level(level, x, y, f)
|
|
local id = tex.get_texture("dan_select/yellow_box/difficulty_level")
|
|
if not id or level < 1 then return end
|
|
tex.draw_texture(id, { frame = math.min(level, 11) - 1, x = x, y = y, fade = f })
|
|
end
|
|
|
|
-- The course's clear stamp (yellow_box/clear_mark) sits over the dan plate and Don, as the
|
|
-- cabinet's stamp_detail does (it is above don_1p). The parent draws it with the box, under
|
|
-- both, so it is held back there and drawn with the title, which the engine draws after
|
|
-- the chara and the nameplate.
|
|
local parent_open, parent_title = DanSelect.draw_dan_open, DanSelect.draw_title
|
|
local held_stamp -- shared: the boxes and the title can be drawn through different instances
|
|
if parent_open then
|
|
function DanSelect:draw_dan_open(box)
|
|
local stamp = tex.get_texture("dan_select/yellow_box/clear_mark")
|
|
local level = tex.get_texture("dan_select/yellow_box/difficulty_level")
|
|
local hide = {}
|
|
if level then
|
|
for _, n in ipairs({ "difficulty_star", "difficulty_num", "difficulty_x" }) do
|
|
local t = tex.get_texture("dan_select/yellow_box/" .. n)
|
|
if t then hide[#hide + 1] = t end
|
|
end
|
|
end
|
|
local draw = tex.draw_texture
|
|
tex.draw_texture = function(id, p)
|
|
if id == stamp then return end
|
|
for _, t in ipairs(hide) do -- handles compare with ==, not as table keys
|
|
if id == t then return end
|
|
end
|
|
return draw(id, p)
|
|
end
|
|
local ok, err = pcall(parent_open, self, box)
|
|
tex.draw_texture = draw
|
|
if not ok then error(err, 0) end
|
|
local f = box.open_fade.attribute
|
|
local off = tex.get_skin_config("dan_yellow_box_offset") or { x = 0, y = 0 }
|
|
if level then
|
|
for i = 0, box:song_count() - 1 do
|
|
draw_level(box:song_level(i), i * (off.x or 0), i * (off.y or 0), f)
|
|
end
|
|
end
|
|
if stamp and box.clear_mark > 0 then
|
|
held_stamp = { id = stamp, frame = box.clear_mark - 1, fade = f }
|
|
end
|
|
end
|
|
|
|
function DanSelect:draw_title()
|
|
if parent_title then parent_title(self) end
|
|
local s = held_stamp
|
|
held_stamp = nil
|
|
if s then tex.draw_texture(s.id, { frame = s.frame, fade = s.fade }) end
|
|
end
|
|
end
|
|
|
|
-- The back entry is one more chip in the top strip (back/folder, laid out like box/folder);
|
|
-- the parent's opening board is its own layout, so nothing opens under it here.
|
|
-- Its label comes from skin_config dan_back_label, so it follows the language setting. It is set
|
|
-- like the dan chip names (black, no outline: dan_chip_name_black), vertical for CJK and
|
|
-- across, squeezed to the chip, for other languages.
|
|
local CJK = { ja = true, zh = true, ko = true, tw = true, cn = true }
|
|
local back_label
|
|
local function get_back_label()
|
|
if back_label == nil then
|
|
local cfg = tex.get_skin_config("dan_back_label")
|
|
if not cfg then back_label = false; return false end
|
|
local lang = tex.language()
|
|
local vertical = CJK[lang] or CJK[lang:sub(1, 2)] or false
|
|
back_label = {
|
|
cfg = cfg,
|
|
vertical = vertical,
|
|
main = text.create_raw_text(tex.skin_text("dan_back_label"), cfg.font_size,
|
|
{ 0, 0, 0, 255 }, { 0, 0, 0, 255 }, vertical, 0, 2),
|
|
}
|
|
end
|
|
return back_label
|
|
end
|
|
|
|
local function draw_back_label(bx, fade, y)
|
|
local label = get_back_label()
|
|
if not label then return end
|
|
local cfg, main = label.cfg, label.main
|
|
local sx, sy = 1, 1
|
|
if label.vertical then
|
|
sy = math.min(1, cfg.height / main.height)
|
|
else
|
|
sx = math.min(1, cfg.width / main.width) -- width: chip face less the border
|
|
end
|
|
local w, h = main.width * sx, main.height * sy
|
|
main:draw({ x = bx + cfg.x - w / 2, y = cfg.y + (cfg.height - h) / 2 + y,
|
|
x2 = w - main.width, y2 = h - main.height, fade = fade })
|
|
end
|
|
|
|
-- The selected chip drops below the strip, as the cabinet's does, easing there and back.
|
|
-- Only draw_cursor (called after every box) is told where the cursor is, so the boxes are
|
|
-- held back in draw_box and drawn from draw_cursor.
|
|
local SELECTED_DROP = 22
|
|
local DROP_MS = 60 -- time constant of the ease
|
|
local drop = {} -- chip key -> current drop
|
|
local held_boxes = {}
|
|
local last_ms
|
|
|
|
local function chip_key(box)
|
|
return box.is_back and "\0back" or box.text_name
|
|
end
|
|
|
|
local function chip_drop(box)
|
|
return drop[chip_key(box)] or 0
|
|
end
|
|
|
|
local function draw_chip_name(box, bx, y, fade)
|
|
local name = box:chip_name()
|
|
if not name then return end
|
|
local nb = tex.get_skin_config("dan_chip_name") or { x = 0, y = 0 }
|
|
name:draw({
|
|
x = bx + nb.x - name.width / 2,
|
|
y = nb.y + y,
|
|
y2 = math.min(name.height, nb.height or name.height) - name.height,
|
|
fade = fade,
|
|
})
|
|
end
|
|
|
|
-- the chip as the parent draws it (folder, stamp, name), at its drop
|
|
function DanSelect:draw_dan_closed(box)
|
|
local bx, fade, y = box:box_x(), box.fade.attribute, chip_drop(box)
|
|
local id = tex.get_texture("dan_select/box/folder")
|
|
if id then tex.draw_texture(id, { frame = box.dan_color, x = bx, y = y, fade = fade }) end
|
|
local mark = box.clear_mark > 0 and tex.get_texture("dan_select/box/clear_mark")
|
|
if mark then tex.draw_texture(mark, { frame = box.clear_mark - 1, x = bx, y = y, fade = fade }) end
|
|
draw_chip_name(box, bx, y, fade)
|
|
end
|
|
|
|
function DanSelect:draw_back_box(box)
|
|
local bx, fade, y = box:box_x(), box.fade.attribute, chip_drop(box)
|
|
local id = tex.get_texture("dan_select/back/folder")
|
|
if id then tex.draw_texture(id, { x = bx, y = y, fade = fade }) end
|
|
draw_back_label(bx, fade, y)
|
|
end
|
|
|
|
-- The cursor's glow goes over the chip, its emblem and name (they grey under it, as on the
|
|
-- cabinet) but not over the clear stamp, which is drawn again on top of it.
|
|
local function draw_chip_front(box)
|
|
if box.is_back or box.clear_mark <= 0 then return end
|
|
local mark = tex.get_texture("dan_select/box/clear_mark")
|
|
if mark then
|
|
tex.draw_texture(mark, { frame = box.clear_mark - 1, x = box:box_x(), y = chip_drop(box),
|
|
fade = box.fade.attribute })
|
|
end
|
|
end
|
|
|
|
local parent_draw_box = DanSelect.draw_box
|
|
function DanSelect:draw_box(box)
|
|
held_boxes[#held_boxes + 1] = box
|
|
end
|
|
|
|
local cursor_parent = DanSelect.draw_cursor
|
|
function DanSelect:draw_cursor(pos, now_ms, moved_ms)
|
|
local boxes = held_boxes
|
|
held_boxes = {}
|
|
local k = last_ms and math.min(1, math.max(0, now_ms - last_ms) / DROP_MS) or 1
|
|
last_ms = now_ms
|
|
local selected
|
|
for _, box in ipairs(boxes) do
|
|
local key = chip_key(box)
|
|
local is_sel = math.abs(box:box_x() - pos) < 1
|
|
if is_sel then selected = box end
|
|
local d = drop[key] or 0
|
|
d = d + ((is_sel and SELECTED_DROP or 0) - d) * k
|
|
drop[key] = (math.abs(d) >= 0.05) and d or nil
|
|
end
|
|
-- draw every box even if one fails, then report the first failure
|
|
local err
|
|
for _, box in ipairs(boxes) do
|
|
local ok, e = pcall(parent_draw_box, self, box)
|
|
if not ok and not err then err = e end
|
|
end
|
|
if err then error(err, 0) end
|
|
local y = selected and chip_drop(selected) or 0
|
|
if cursor_parent then
|
|
cursor_parent(self, pos, now_ms, moved_ms, y)
|
|
elseif self.t_cursor then
|
|
tex.draw_texture(self.t_cursor, { x = pos, y = y })
|
|
end
|
|
if selected then draw_chip_front(selected) end
|
|
return true
|
|
end
|