diff --git a/aniphallow.lua b/aniphallow.lua index b7478df..f71d7bf 100644 --- a/aniphallow.lua +++ b/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)