Round 5: preview layout config, single/all mode, offset indicator
- Frame offset indicator: cyan dot on thumbnails when offX/offY != 0 - GB canvases taller (220px each) to match Animations tab height - Preview layout config: auto/fixedCols/fixedRows with value setting - Preview Show All/Single toggle with <- -> navigation in single mode - Preview canvas resizes responsively on zoom and mode change - calcPreviewSize() and getPreviewGrid() helpers for layout math - Preview zoom and mode saved/loaded in preferences - previewSingleIdx clamped on load and mode switch
This commit is contained in:
parent
cdeefd642d
commit
fa6c3d3615
223
aniphallow.lua
223
aniphallow.lua
|
|
@ -103,6 +103,11 @@ local S = {
|
|||
srcClickX = 0,
|
||||
srcClickY = 0,
|
||||
srcIsDrag = false,
|
||||
-- Preview layout configuration
|
||||
previewLayout = "auto", -- "auto", "fixedCols", "fixedRows"
|
||||
previewLayoutValue = 2, -- number for fixed cols/rows
|
||||
previewMode = "all", -- "all" or "single"
|
||||
previewSingleIdx = 1, -- which animation to show in single mode
|
||||
}
|
||||
|
||||
----------------------------------------------------------------------
|
||||
|
|
@ -128,8 +133,8 @@ local GB_IDS = {
|
|||
}
|
||||
|
||||
-- GB canvas heights (taller to match Animations tab and prevent resize on tab switch)
|
||||
local GB_SRC_H = 200
|
||||
local GB_OPT_H = 200
|
||||
local GB_SRC_H = 220
|
||||
local GB_OPT_H = 220
|
||||
|
||||
----------------------------------------------------------------------
|
||||
-- Module-level window/timer variables
|
||||
|
|
@ -235,6 +240,11 @@ local function loadPrefs()
|
|||
end
|
||||
-- Load previewWindowOpen
|
||||
if k == "previewWindowOpen" then S.previewWindowOpen = (v == "true") end
|
||||
-- Preview layout prefs
|
||||
if k == "previewLayout" then S.previewLayout = v end
|
||||
if k == "previewLayoutValue" then S.previewLayoutValue = tonumber(v) or 2 end
|
||||
if k == "previewMode" then S.previewMode = v end
|
||||
if k == "previewSingleIdx" then S.previewSingleIdx = tonumber(v) or 1 end
|
||||
-- Dynamic anim frames: anim_0, anim_1, ...
|
||||
local animIdx = k and string.match(k, "^anim_(%d+)$")
|
||||
if animIdx then
|
||||
|
|
@ -261,6 +271,11 @@ local function loadPrefs()
|
|||
-- Clamp currentAnim to valid range
|
||||
if S.currentAnim < 1 then S.currentAnim = 1 end
|
||||
if S.currentAnim > #S.animNames then S.currentAnim = math.max(1, #S.animNames) end
|
||||
-- Clamp previewSingleIdx
|
||||
if S.previewSingleIdx < 1 then S.previewSingleIdx = 1 end
|
||||
if #S.animNames > 0 and S.previewSingleIdx > #S.animNames then
|
||||
S.previewSingleIdx = #S.animNames
|
||||
end
|
||||
end
|
||||
|
||||
local function savePrefs()
|
||||
|
|
@ -302,6 +317,11 @@ local function savePrefs()
|
|||
f:write("gbTileH=" .. S.gbTileH .. "\n")
|
||||
-- Save previewWindowOpen
|
||||
f:write("previewWindowOpen=" .. tostring(S.previewWindowOpen) .. "\n")
|
||||
-- Save preview layout prefs
|
||||
f:write("previewLayout=" .. S.previewLayout .. "\n")
|
||||
f:write("previewLayoutValue=" .. S.previewLayoutValue .. "\n")
|
||||
f:write("previewMode=" .. S.previewMode .. "\n")
|
||||
f:write("previewSingleIdx=" .. S.previewSingleIdx .. "\n")
|
||||
for i, name in ipairs(S.animNames) do
|
||||
f:write("anim_" .. (i - 1) .. "=" .. serializeFrames(S.anims[name] or {}) .. "\n")
|
||||
end
|
||||
|
|
@ -405,6 +425,26 @@ local function getDarkestPaletteColor()
|
|||
return Color(0, 0, 0)
|
||||
end
|
||||
|
||||
----------------------------------------------------------------------
|
||||
-- Preview grid helper (module-level)
|
||||
----------------------------------------------------------------------
|
||||
local function getPreviewGrid(numAnims)
|
||||
if numAnims <= 0 then return 1, 1 end
|
||||
if S.previewLayout == "fixedCols" then
|
||||
local cols = math.max(1, S.previewLayoutValue)
|
||||
local rows = math.max(1, math.ceil(numAnims / cols))
|
||||
return cols, rows
|
||||
elseif S.previewLayout == "fixedRows" then
|
||||
local rows = math.max(1, S.previewLayoutValue)
|
||||
local cols = math.max(1, math.ceil(numAnims / rows))
|
||||
return cols, rows
|
||||
else -- auto: tend to square
|
||||
local cols = math.max(1, math.ceil(math.sqrt(numAnims)))
|
||||
local rows = math.max(1, math.ceil(numAnims / cols))
|
||||
return cols, rows
|
||||
end
|
||||
end
|
||||
|
||||
----------------------------------------------------------------------
|
||||
-- Module-level refreshSource
|
||||
----------------------------------------------------------------------
|
||||
|
|
@ -930,18 +970,35 @@ openPreviewWindow = function()
|
|||
end
|
||||
|
||||
local pvAnimFrame = { value = 0 }
|
||||
local PV_RENDER_MARGIN = 2
|
||||
local PV_MARGIN = 2
|
||||
|
||||
-- Calculate content-based size
|
||||
local numAnims = #S.animNames
|
||||
local ptw = S.tileW * S.previewZoom
|
||||
local pth = S.tileH * S.previewZoom
|
||||
local cellW = ptw + PV_RENDER_MARGIN * 2
|
||||
local cellH = pth + PV_RENDER_MARGIN * 2
|
||||
local pvCols = 2
|
||||
local pvRows = math.max(1, math.ceil(numAnims / pvCols))
|
||||
local pvWidth = math.max(pvCols * cellW, 120)
|
||||
local pvHeight = math.max(pvRows * cellH, 40)
|
||||
-- Clamp previewSingleIdx
|
||||
if #S.animNames > 0 then
|
||||
if S.previewSingleIdx < 1 then S.previewSingleIdx = 1 end
|
||||
if S.previewSingleIdx > #S.animNames then S.previewSingleIdx = #S.animNames end
|
||||
else
|
||||
S.previewSingleIdx = 1
|
||||
end
|
||||
|
||||
-- calcPreviewSize helper
|
||||
local function calcPreviewSize()
|
||||
local ptw = S.tileW * S.previewZoom
|
||||
local pth = S.tileH * S.previewZoom
|
||||
local cellW = ptw + PV_MARGIN * 2
|
||||
local cellH = pth + PV_MARGIN * 2
|
||||
if S.previewMode == "single" then
|
||||
return cellW, cellH
|
||||
else
|
||||
local numAnims = #S.animNames
|
||||
local cols, rows = getPreviewGrid(numAnims)
|
||||
return cols * cellW, rows * cellH
|
||||
end
|
||||
end
|
||||
|
||||
-- Calculate initial size
|
||||
local pvWidth, pvHeight = calcPreviewSize()
|
||||
pvWidth = math.max(pvWidth, 120)
|
||||
pvHeight = math.max(pvHeight, 40)
|
||||
|
||||
previewDlg = Dialog{
|
||||
title = "AniPhallow Preview",
|
||||
|
|
@ -968,6 +1025,57 @@ openPreviewWindow = function()
|
|||
end
|
||||
}
|
||||
|
||||
previewDlg:button{
|
||||
id = "pvToggleMode",
|
||||
text = S.previewMode == "all" and "Single" or "Show All",
|
||||
onclick = function()
|
||||
if S.previewMode == "all" then
|
||||
S.previewMode = "single"
|
||||
-- Clamp
|
||||
if #S.animNames > 0 then
|
||||
if S.previewSingleIdx < 1 then S.previewSingleIdx = 1 end
|
||||
if S.previewSingleIdx > #S.animNames then S.previewSingleIdx = #S.animNames end
|
||||
end
|
||||
previewDlg:modify{ id = "pvToggleMode", text = "Show All" }
|
||||
previewDlg:modify{ id = "pvPrev", visible = true }
|
||||
previewDlg:modify{ id = "pvNext", visible = true }
|
||||
else
|
||||
S.previewMode = "all"
|
||||
previewDlg:modify{ id = "pvToggleMode", text = "Single" }
|
||||
previewDlg:modify{ id = "pvPrev", visible = false }
|
||||
previewDlg:modify{ id = "pvNext", visible = false }
|
||||
end
|
||||
-- Resize canvas to fit new content
|
||||
local pw, ph = calcPreviewSize()
|
||||
previewDlg:modify{ id = "pvCanvas", width = math.max(pw, 120), height = math.max(ph, 40) }
|
||||
previewDlg:repaint()
|
||||
end
|
||||
}
|
||||
previewDlg:button{
|
||||
id = "pvPrev",
|
||||
text = "<-",
|
||||
visible = (S.previewMode == "single"),
|
||||
onclick = function()
|
||||
if #S.animNames > 0 then
|
||||
S.previewSingleIdx = S.previewSingleIdx - 1
|
||||
if S.previewSingleIdx < 1 then S.previewSingleIdx = #S.animNames end
|
||||
previewDlg:repaint()
|
||||
end
|
||||
end
|
||||
}
|
||||
previewDlg:button{
|
||||
id = "pvNext",
|
||||
text = "->",
|
||||
visible = (S.previewMode == "single"),
|
||||
onclick = function()
|
||||
if #S.animNames > 0 then
|
||||
S.previewSingleIdx = S.previewSingleIdx + 1
|
||||
if S.previewSingleIdx > #S.animNames then S.previewSingleIdx = 1 end
|
||||
previewDlg:repaint()
|
||||
end
|
||||
end
|
||||
}
|
||||
|
||||
previewDlg:canvas{
|
||||
id = "pvCanvas",
|
||||
width = pvWidth,
|
||||
|
|
@ -980,39 +1088,68 @@ openPreviewWindow = function()
|
|||
|
||||
local pw = S.tileW * S.previewZoom
|
||||
local ph = S.tileH * S.previewZoom
|
||||
local cW = pw + PV_RENDER_MARGIN * 2
|
||||
local cH = ph + PV_RENDER_MARGIN * 2
|
||||
local cols = 2
|
||||
local cW = pw + PV_MARGIN * 2
|
||||
local cH = ph + PV_MARGIN * 2
|
||||
local af = pvAnimFrame.value
|
||||
|
||||
for i, name in ipairs(S.animNames) do
|
||||
local col = (i - 1) % cols
|
||||
local row = math.floor((i - 1) / cols)
|
||||
local ox = col * cW
|
||||
local oy = row * cH
|
||||
|
||||
if S.previewMode == "single" and S.previewSingleIdx >= 1 and S.previewSingleIdx <= #S.animNames then
|
||||
-- Single mode: draw only one animation centered
|
||||
local name = S.animNames[S.previewSingleIdx]
|
||||
local frames = S.anims[name] or {}
|
||||
-- Draw background
|
||||
if S.useBgColor then
|
||||
gc.color = S.bgColor
|
||||
gc:fillRect(Rectangle(ox + PV_RENDER_MARGIN, oy + PV_RENDER_MARGIN, pw, ph))
|
||||
gc:fillRect(Rectangle(PV_MARGIN, PV_MARGIN, pw, ph))
|
||||
else
|
||||
drawCheckerboard(gc, pw, ph, S.previewZoom,
|
||||
ox + PV_RENDER_MARGIN, oy + PV_RENDER_MARGIN)
|
||||
drawCheckerboard(gc, pw, ph, S.previewZoom, PV_MARGIN, PV_MARGIN)
|
||||
end
|
||||
|
||||
local frames = S.anims[name] or {}
|
||||
-- Draw frame with offset
|
||||
local totalFrames = #frames
|
||||
if totalFrames > 0 then
|
||||
local idx = (af % totalFrames) + 1
|
||||
local fimg = getFrameImageGlobal(name, idx)
|
||||
local f = frames[idx]
|
||||
local offX = (f.offX or 0) * S.previewZoom
|
||||
local offY = (f.offY or 0) * S.previewZoom
|
||||
if fimg then
|
||||
local f = frames[idx]
|
||||
local fOffX = (f.offX or 0) * S.previewZoom
|
||||
local fOffY = (f.offY or 0) * S.previewZoom
|
||||
gc:drawImage(
|
||||
fimg,
|
||||
gc:drawImage(fimg,
|
||||
Rectangle(0, 0, S.tileW, S.tileH),
|
||||
Rectangle(ox + PV_RENDER_MARGIN + fOffX, oy + PV_RENDER_MARGIN + fOffY, pw, ph)
|
||||
)
|
||||
Rectangle(PV_MARGIN + offX, PV_MARGIN + offY, pw, ph))
|
||||
end
|
||||
end
|
||||
else
|
||||
-- All mode: draw all animations using grid
|
||||
local cols, rows = getPreviewGrid(na)
|
||||
|
||||
for i, name in ipairs(S.animNames) do
|
||||
local col = (i - 1) % cols
|
||||
local row = math.floor((i - 1) / cols)
|
||||
local ox = col * cW
|
||||
local oy = row * cH
|
||||
|
||||
if S.useBgColor then
|
||||
gc.color = S.bgColor
|
||||
gc:fillRect(Rectangle(ox + PV_MARGIN, oy + PV_MARGIN, pw, ph))
|
||||
else
|
||||
drawCheckerboard(gc, pw, ph, S.previewZoom,
|
||||
ox + PV_MARGIN, oy + PV_MARGIN)
|
||||
end
|
||||
|
||||
local frames = S.anims[name] or {}
|
||||
local totalFrames = #frames
|
||||
if totalFrames > 0 then
|
||||
local idx = (af % totalFrames) + 1
|
||||
local fimg = getFrameImageGlobal(name, idx)
|
||||
if fimg then
|
||||
local f = frames[idx]
|
||||
local fOffX = (f.offX or 0) * S.previewZoom
|
||||
local fOffY = (f.offY or 0) * S.previewZoom
|
||||
gc:drawImage(
|
||||
fimg,
|
||||
Rectangle(0, 0, S.tileW, S.tileH),
|
||||
Rectangle(ox + PV_MARGIN + fOffX, oy + PV_MARGIN + fOffY, pw, ph)
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -1020,7 +1157,9 @@ openPreviewWindow = function()
|
|||
onwheel = function(ev)
|
||||
local dz = ev.deltaY < 0 and 1 or -1
|
||||
S.previewZoom = math.max(1, math.min(MAX_PREVIEW_ZOOM, S.previewZoom + dz))
|
||||
pcall(function() previewDlg:repaint() end)
|
||||
local pw, ph = calcPreviewSize()
|
||||
previewDlg:modify{ id = "pvCanvas", width = math.max(pw, 120), height = math.max(ph, 40) }
|
||||
previewDlg:repaint()
|
||||
end
|
||||
}
|
||||
|
||||
|
|
@ -1188,6 +1327,9 @@ openMainDialog = function()
|
|||
d:color{ id = "silhouetteColor", label = "Silhouette:", color = silColor }
|
||||
d:entry{ id = "layerName", label = "Layer name:", text = S.gbLayerName }
|
||||
d:check{ id = "alwaysOverwrite", text = "Always overwrite layer", selected = S.gbAlwaysOverwrite }
|
||||
d:separator{ text = "Preview" }
|
||||
d:combobox{ id = "previewLayout", label = "Layout:", option = S.previewLayout, options = {"auto", "fixedCols", "fixedRows"} }
|
||||
d:number{ id = "previewLayoutValue", label = "Value:", text = tostring(S.previewLayoutValue), decimals = 0 }
|
||||
d:button{ id = "ok", text = "OK" }
|
||||
d:button{ text = "Cancel" }
|
||||
d:show()
|
||||
|
|
@ -1213,6 +1355,11 @@ openMainDialog = function()
|
|||
S.gbSilhouetteColorSet = true
|
||||
S.gbLayerName = d.data.layerName
|
||||
S.gbAlwaysOverwrite = d.data.alwaysOverwrite
|
||||
-- Preview layout settings
|
||||
S.previewLayout = d.data.previewLayout
|
||||
local plv = d.data.previewLayoutValue
|
||||
if plv < 1 then plv = 1 elseif plv > 20 then plv = 20 end
|
||||
S.previewLayoutValue = plv
|
||||
dlg:repaint()
|
||||
end
|
||||
end
|
||||
|
|
@ -1544,6 +1691,12 @@ openMainDialog = function()
|
|||
gc:fillRect(Rectangle(tx, ty, 4, 4))
|
||||
end
|
||||
|
||||
-- Offset indicator (cyan dot, bottom-left)
|
||||
if (frames[i].offX or 0) ~= 0 or (frames[i].offY or 0) ~= 0 then
|
||||
gc.color = Color(0, 200, 255, 220)
|
||||
gc:fillRect(Rectangle(tx, ty + THUMB_SIZE - 4, 4, 4))
|
||||
end
|
||||
|
||||
-- Current playback frame indicator
|
||||
if cnt > 0 and ((S.animFrame % cnt)) == (i - 1) then
|
||||
gc.color = Color(100, 200, 255, 200)
|
||||
|
|
|
|||
Loading…
Reference in New Issue