Round 3: frame offsets, compact layout, auto-reopen preview
- Remove preview selection/offset buttons (reverted jump feature) - Add per-frame pixel offsets (Up/Down/Left/Right/Reset) in Animations tab for visual jump effects without creating extra tiles - Frame offsets applied in all previews (main + separate window) - Remove separator between Source and Frames canvases for compact layout - Move "Frames" label below frames canvas, remove action separator - Shared zoom between Source and Frames canvases (wheel affects both) - Separate preview window: content-based sizing instead of copying main - Remember and auto-reopen separate preview window on plugin launch - GB: both Occurrences and Optimized canvases same height (150px) - GB: remove separator between canvases, label below Optimized - GB: shared zoom between both canvases - Config: remove view heights options (didn't work)
This commit is contained in:
parent
6449f20a35
commit
99ca77ef3b
540
aniphallow.lua
540
aniphallow.lua
|
|
@ -82,13 +82,8 @@ local S = {
|
|||
gbAlwaysOverwrite = false, -- overwrite layer checkbox
|
||||
gbTileW = DEFAULT_GB_TILE_W, -- configurable tile width for GB analysis
|
||||
gbTileH = DEFAULT_GB_TILE_H, -- configurable tile height for GB analysis
|
||||
-- Configurable view heights
|
||||
sourceViewH = 400, -- configurable source canvas height
|
||||
gbSrcViewH = 150, -- configurable occurrences canvas height
|
||||
gbOptViewH = 120, -- configurable optimized canvas height
|
||||
-- Preview offsets
|
||||
previewOffsets = {}, -- name -> {x=0, y=0} for preview visual offsets
|
||||
previewSelected = nil, -- name of selected animation in Preview tab
|
||||
-- Remember if separate preview window was open
|
||||
previewWindowOpen = false,
|
||||
-- Click vs drag for GB canvases
|
||||
gbOptClickX = 0,
|
||||
gbOptClickY = 0,
|
||||
|
|
@ -96,9 +91,9 @@ local S = {
|
|||
gbSrcClickX = 0,
|
||||
gbSrcClickY = 0,
|
||||
gbSrcIsDrag = false,
|
||||
-- Dynamic animations: each frame is {x, y, flipped, flippedV}
|
||||
-- Dynamic animations: each frame is {x, y, flipped, flippedV, offX, offY}
|
||||
animNames = {}, -- ordered list of animation names (sorted alphabetically)
|
||||
anims = {}, -- name -> list of {x, y, flipped, flippedV}
|
||||
anims = {}, -- name -> list of {x, y, flipped, flippedV, offX, offY}
|
||||
-- Frame selection & drag
|
||||
selectedFrame = 0,
|
||||
frameDragging = false,
|
||||
|
|
@ -117,14 +112,14 @@ local S = {
|
|||
local SETUP_IDS = {
|
||||
"sepAnims", "cmbAnimList", "btnNewAnim", "btnDelAnim", "btnRenameAnim", "btnCloneAnim",
|
||||
"sepSource", "canvasSource",
|
||||
"sepFrames", "canvasStrips",
|
||||
"sepActions", "btnFlipX", "btnFlipY", "btnMoveLeft", "btnMoveRight", "btnRemoveFrame",
|
||||
"canvasStrips",
|
||||
"lblFrames", "btnFlipX", "btnFlipY", "btnMoveLeft", "btnMoveRight", "btnRemoveFrame",
|
||||
"btnFrameUp", "btnFrameDown", "btnFrameLeft", "btnFrameRight", "btnFrameReset",
|
||||
}
|
||||
|
||||
-- Render tab elements
|
||||
local RENDER_IDS = {
|
||||
"sepRender", "btnSeparateWindow", "canvasRender",
|
||||
"pvBtnUp", "pvBtnDown", "pvBtnLeft", "pvBtnRight", "pvBtnReset",
|
||||
}
|
||||
|
||||
-- GB tab elements (reordered: Flip, Offset, Silhouette, Compress)
|
||||
|
|
@ -133,7 +128,7 @@ local GB_IDS = {
|
|||
"gbAnalyzeMode",
|
||||
"gbSimilarThreshold", "btnFindSimilar",
|
||||
"sepGbSource", "canvasGbSource",
|
||||
"sepGbOpt", "canvasGbOpt",
|
||||
"canvasGbOpt", "lblGbOpt",
|
||||
"btnGbSaveLayer", "btnGbCopyClipboard", "btnGbSave",
|
||||
}
|
||||
|
||||
|
|
@ -142,11 +137,11 @@ local GB_IDS = {
|
|||
----------------------------------------------------------------------
|
||||
local PREFS_FILE = app.fs.joinPath(app.fs.userConfigPath, "aniphallow_prefs.ini")
|
||||
|
||||
-- Serialize frames list: "x1,y1,f;x2,y2,f;..." (f=0 normal, f=1 flipped)
|
||||
-- Serialize frames list: "x1,y1,f,fv,ox,oy;x2,y2,f,fv,ox,oy;..."
|
||||
local function serializeFrames(frames)
|
||||
local parts = {}
|
||||
for _, f in ipairs(frames) do
|
||||
table.insert(parts, f.x .. "," .. f.y .. "," .. (f.flipped and "1" or "0") .. "," .. (f.flippedV and "1" or "0"))
|
||||
table.insert(parts, f.x .. "," .. f.y .. "," .. (f.flipped and "1" or "0") .. "," .. (f.flippedV and "1" or "0") .. "," .. (f.offX or 0) .. "," .. (f.offY or 0))
|
||||
end
|
||||
return table.concat(parts, ";")
|
||||
end
|
||||
|
|
@ -156,12 +151,14 @@ local function deserializeFrames(str)
|
|||
local frames = {}
|
||||
if not str or str == "" then return frames end
|
||||
for part in string.gmatch(str, "([^;]+)") do
|
||||
local x, y, fh, fv = string.match(part, "([%d-]+),([%d-]+),?(%d?),?(%d?)")
|
||||
local x, y, fh, fv, ox, oy = string.match(part, "([%d-]+),([%d-]+),?(%d?),?(%d?),?([%d-]*),?([%d-]*)")
|
||||
if x and y then
|
||||
table.insert(frames, {
|
||||
x = tonumber(x), y = tonumber(y),
|
||||
flipped = (fh == "1"),
|
||||
flippedV = (fv == "1")
|
||||
flippedV = (fv == "1"),
|
||||
offX = tonumber(ox) or 0,
|
||||
offY = tonumber(oy) or 0
|
||||
})
|
||||
end
|
||||
end
|
||||
|
|
@ -222,20 +219,8 @@ local function loadPrefs()
|
|||
S.gbTileW = ts
|
||||
S.gbTileH = ts
|
||||
end
|
||||
-- Load configurable view heights
|
||||
if k == "sourceViewH" then S.sourceViewH = tonumber(v) or 400 end
|
||||
if k == "gbSrcViewH" then S.gbSrcViewH = tonumber(v) or 150 end
|
||||
if k == "gbOptViewH" then S.gbOptViewH = tonumber(v) or 120 end
|
||||
-- Load preview offsets: pvOff_<name>=x,y
|
||||
if k then
|
||||
local pvName = string.match(k, "^pvOff_(.+)$")
|
||||
if pvName then
|
||||
local ox, oy = string.match(v, "([%d-]+),([%d-]+)")
|
||||
if ox and oy then
|
||||
S.previewOffsets[pvName] = { x = tonumber(ox) or 0, y = tonumber(oy) or 0 }
|
||||
end
|
||||
end
|
||||
end
|
||||
-- Load previewWindowOpen
|
||||
if k == "previewWindowOpen" then S.previewWindowOpen = (v == "true") end
|
||||
-- Dynamic anim frames: anim_0, anim_1, ...
|
||||
local animIdx = k and string.match(k, "^anim_(%d+)$")
|
||||
if animIdx then
|
||||
|
|
@ -301,14 +286,8 @@ local function savePrefs()
|
|||
f:write("gbAlwaysOverwrite=" .. tostring(S.gbAlwaysOverwrite) .. "\n")
|
||||
f:write("gbTileW=" .. S.gbTileW .. "\n")
|
||||
f:write("gbTileH=" .. S.gbTileH .. "\n")
|
||||
-- Save configurable view heights
|
||||
f:write("sourceViewH=" .. S.sourceViewH .. "\n")
|
||||
f:write("gbSrcViewH=" .. S.gbSrcViewH .. "\n")
|
||||
f:write("gbOptViewH=" .. S.gbOptViewH .. "\n")
|
||||
-- Save preview offsets
|
||||
for name, off in pairs(S.previewOffsets) do
|
||||
f:write("pvOff_" .. name .. "=" .. off.x .. "," .. off.y .. "\n")
|
||||
end
|
||||
-- Save previewWindowOpen
|
||||
f:write("previewWindowOpen=" .. tostring(S.previewWindowOpen) .. "\n")
|
||||
for i, name in ipairs(S.animNames) do
|
||||
f:write("anim_" .. (i - 1) .. "=" .. serializeFrames(S.anims[name] or {}) .. "\n")
|
||||
end
|
||||
|
|
@ -382,7 +361,7 @@ end
|
|||
local function clampScroll()
|
||||
local contentW, contentH = getSourceContentSize()
|
||||
local maxScrollX = math.max(0, contentW - SOURCE_VIEWPORT_W)
|
||||
local maxScrollY = math.max(0, contentH - S.sourceViewH)
|
||||
local maxScrollY = math.max(0, contentH - SOURCE_VIEWPORT_H)
|
||||
S.scrollX = clamp(S.scrollX, 0, maxScrollX)
|
||||
S.scrollY = clamp(S.scrollY, 0, maxScrollY)
|
||||
end
|
||||
|
|
@ -930,7 +909,7 @@ local function run()
|
|||
|
||||
local contentW, contentH = getSourceContentSize()
|
||||
local viewW = math.min(contentW, SOURCE_VIEWPORT_W)
|
||||
local viewH = math.min(contentH, S.sourceViewH)
|
||||
local viewH = math.min(contentH, SOURCE_VIEWPORT_H)
|
||||
|
||||
local dlg = Dialog{
|
||||
title = "AniPhallow - Animation Builder",
|
||||
|
|
@ -985,7 +964,7 @@ local function run()
|
|||
if gridX < 0 or gridX + S.tileW > S.sourceImage.width then return end
|
||||
if gridY < 0 or gridY + S.tileH > S.sourceImage.height then return end
|
||||
if not S.anims[name] then S.anims[name] = {} end
|
||||
table.insert(S.anims[name], { x = gridX, y = gridY, flipped = flipped })
|
||||
table.insert(S.anims[name], { x = gridX, y = gridY, flipped = flipped, offX = 0, offY = 0 })
|
||||
dlg:repaint()
|
||||
end
|
||||
|
||||
|
|
@ -1009,26 +988,12 @@ local function run()
|
|||
local label = (t == tab) and ("[" .. t .. "]") or (" " .. t .. " ")
|
||||
dlg:modify{ id = "tab" .. t, text = label }
|
||||
end
|
||||
-- If not Preview tab, deselect preview and hide buttons
|
||||
if tab ~= "Preview" then
|
||||
S.previewSelected = nil
|
||||
pcall(function() dlg:modify{ id = "pvBtnUp", visible = false } end)
|
||||
pcall(function() dlg:modify{ id = "pvBtnDown", visible = false } end)
|
||||
pcall(function() dlg:modify{ id = "pvBtnLeft", visible = false } end)
|
||||
pcall(function() dlg:modify{ id = "pvBtnRight", visible = false } end)
|
||||
pcall(function() dlg:modify{ id = "pvBtnReset", visible = false } end)
|
||||
else
|
||||
-- On Preview tab, show buttons only if something is selected
|
||||
local pvVis = (S.previewSelected ~= nil)
|
||||
pcall(function() dlg:modify{ id = "pvBtnUp", visible = pvVis } end)
|
||||
pcall(function() dlg:modify{ id = "pvBtnDown", visible = pvVis } end)
|
||||
pcall(function() dlg:modify{ id = "pvBtnLeft", visible = pvVis } end)
|
||||
pcall(function() dlg:modify{ id = "pvBtnRight", visible = pvVis } end)
|
||||
pcall(function() dlg:modify{ id = "pvBtnReset", visible = pvVis } end)
|
||||
end
|
||||
dlg:repaint()
|
||||
end
|
||||
|
||||
-- Forward-declare openPreviewWindow so it can be used from button and auto-open
|
||||
local openPreviewWindow
|
||||
|
||||
----------------------------------------------------------------
|
||||
-- TAB BUTTONS
|
||||
----------------------------------------------------------------
|
||||
|
|
@ -1070,10 +1035,6 @@ local function run()
|
|||
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 = "View Heights" }
|
||||
d:number{ id = "sourceViewH", label = "Source View H:", text = tostring(S.sourceViewH), decimals = 0 }
|
||||
d:number{ id = "gbSrcViewH", label = "Occurrences View H:", text = tostring(S.gbSrcViewH), decimals = 0 }
|
||||
d:number{ id = "gbOptViewH", label = "Optimized View H:", text = tostring(S.gbOptViewH), decimals = 0 }
|
||||
d:button{ id = "ok", text = "OK" }
|
||||
d:button{ text = "Cancel" }
|
||||
d:show()
|
||||
|
|
@ -1098,26 +1059,6 @@ local function run()
|
|||
S.gbSilhouetteColorSet = true
|
||||
S.gbLayerName = d.data.layerName
|
||||
S.gbAlwaysOverwrite = d.data.alwaysOverwrite
|
||||
-- View heights
|
||||
local oldSrcH = S.sourceViewH
|
||||
local oldGbSrcH = S.gbSrcViewH
|
||||
local oldGbOptH = S.gbOptViewH
|
||||
local svh = d.data.sourceViewH
|
||||
if svh < 50 then svh = 50 elseif svh > 600 then svh = 600 end
|
||||
S.sourceViewH = svh
|
||||
local gsvh = d.data.gbSrcViewH
|
||||
if gsvh < 50 then gsvh = 50 elseif gsvh > 600 then gsvh = 600 end
|
||||
S.gbSrcViewH = gsvh
|
||||
local govh = d.data.gbOptViewH
|
||||
if govh < 50 then govh = 50 elseif govh > 600 then govh = 600 end
|
||||
S.gbOptViewH = govh
|
||||
-- If view heights changed, rebuild dialog
|
||||
if svh ~= oldSrcH or gsvh ~= oldGbSrcH or govh ~= oldGbOptH then
|
||||
savePrefs()
|
||||
dlg:close()
|
||||
run()
|
||||
return
|
||||
end
|
||||
dlg:repaint()
|
||||
end
|
||||
end
|
||||
|
|
@ -1212,11 +1153,6 @@ local function run()
|
|||
S.anims[newName] = S.anims[name]
|
||||
S.anims[name] = nil
|
||||
S.animNames[S.currentAnim] = newName
|
||||
-- Transfer preview offset if exists
|
||||
if S.previewOffsets[name] then
|
||||
S.previewOffsets[newName] = S.previewOffsets[name]
|
||||
S.previewOffsets[name] = nil
|
||||
end
|
||||
-- Re-sort alphabetically
|
||||
table.sort(S.animNames, function(a, b) return a:lower() < b:lower() end)
|
||||
for i, n in ipairs(S.animNames) do
|
||||
|
|
@ -1248,7 +1184,7 @@ local function run()
|
|||
local srcFrames = S.anims[name] or {}
|
||||
local newFrames = {}
|
||||
for _, fr in ipairs(srcFrames) do
|
||||
local nf = { x = fr.x, y = fr.y, flipped = fr.flipped, flippedV = fr.flippedV }
|
||||
local nf = { x = fr.x, y = fr.y, flipped = fr.flipped, flippedV = fr.flippedV, offX = fr.offX or 0, offY = fr.offY or 0 }
|
||||
if d.data.flipX then
|
||||
nf.flipped = not nf.flipped
|
||||
end
|
||||
|
|
@ -1283,7 +1219,7 @@ local function run()
|
|||
local gc = ev.context
|
||||
local cW, cH = getSourceContentSize()
|
||||
local vw = math.min(cW, SOURCE_VIEWPORT_W)
|
||||
local vh = math.min(cH, S.sourceViewH)
|
||||
local vh = math.min(cH, SOURCE_VIEWPORT_H)
|
||||
|
||||
drawCheckerboard(gc, vw, vh, S.sourceZoom)
|
||||
|
||||
|
|
@ -1386,8 +1322,6 @@ local function run()
|
|||
----------------------------------------------------------------
|
||||
-- Frame strip for current animation
|
||||
----------------------------------------------------------------
|
||||
dlg:separator{ id = "sepFrames", text = "Frames (L-click=select, R-click=remove, drag=reorder)" }
|
||||
|
||||
local STRIP_TOTAL_W = SOURCE_VIEWPORT_W
|
||||
local STRIP_CELL = THUMB_SIZE + 1
|
||||
local STRIP_H = STRIP_CELL * 2 + 4
|
||||
|
|
@ -1537,13 +1471,19 @@ local function run()
|
|||
S.frameDragging = false
|
||||
dlg:repaint()
|
||||
end
|
||||
end,
|
||||
onwheel = function(ev)
|
||||
local dz = ev.deltaY < 0 and 1 or -1
|
||||
S.sourceZoom = math.max(1, math.min(10, S.sourceZoom + dz))
|
||||
clampScroll()
|
||||
dlg:repaint()
|
||||
end
|
||||
}
|
||||
|
||||
----------------------------------------------------------------
|
||||
-- Frame action buttons
|
||||
----------------------------------------------------------------
|
||||
dlg:separator{ id = "sepActions" }
|
||||
dlg:label{ id = "lblFrames", text = "Frames: L-click=select, R-click=remove, drag=reorder" }
|
||||
|
||||
dlg:button{
|
||||
id = "btnFlipX",
|
||||
|
|
@ -1643,105 +1583,191 @@ local function run()
|
|||
end
|
||||
}
|
||||
|
||||
----------------------------------------------------------------
|
||||
-- Frame offset buttons
|
||||
----------------------------------------------------------------
|
||||
dlg:newrow()
|
||||
dlg:button{
|
||||
id = "btnFrameUp",
|
||||
text = "Up",
|
||||
onclick = function()
|
||||
local name = currentAnimName()
|
||||
if not name or S.selectedFrame < 1 then return end
|
||||
local frames = S.anims[name]
|
||||
if not frames or S.selectedFrame > #frames then return end
|
||||
frames[S.selectedFrame].offY = (frames[S.selectedFrame].offY or 0) - 1
|
||||
dlg:repaint()
|
||||
end
|
||||
}
|
||||
dlg:button{
|
||||
id = "btnFrameDown",
|
||||
text = "Down",
|
||||
onclick = function()
|
||||
local name = currentAnimName()
|
||||
if not name or S.selectedFrame < 1 then return end
|
||||
local frames = S.anims[name]
|
||||
if not frames or S.selectedFrame > #frames then return end
|
||||
frames[S.selectedFrame].offY = (frames[S.selectedFrame].offY or 0) + 1
|
||||
dlg:repaint()
|
||||
end
|
||||
}
|
||||
dlg:button{
|
||||
id = "btnFrameLeft",
|
||||
text = "Left",
|
||||
onclick = function()
|
||||
local name = currentAnimName()
|
||||
if not name or S.selectedFrame < 1 then return end
|
||||
local frames = S.anims[name]
|
||||
if not frames or S.selectedFrame > #frames then return end
|
||||
frames[S.selectedFrame].offX = (frames[S.selectedFrame].offX or 0) - 1
|
||||
dlg:repaint()
|
||||
end
|
||||
}
|
||||
dlg:button{
|
||||
id = "btnFrameRight",
|
||||
text = "Right",
|
||||
onclick = function()
|
||||
local name = currentAnimName()
|
||||
if not name or S.selectedFrame < 1 then return end
|
||||
local frames = S.anims[name]
|
||||
if not frames or S.selectedFrame > #frames then return end
|
||||
frames[S.selectedFrame].offX = (frames[S.selectedFrame].offX or 0) + 1
|
||||
dlg:repaint()
|
||||
end
|
||||
}
|
||||
dlg:button{
|
||||
id = "btnFrameReset",
|
||||
text = "Reset",
|
||||
onclick = function()
|
||||
local name = currentAnimName()
|
||||
if not name or S.selectedFrame < 1 then return end
|
||||
local frames = S.anims[name]
|
||||
if not frames or S.selectedFrame > #frames then return end
|
||||
frames[S.selectedFrame].offX = 0
|
||||
frames[S.selectedFrame].offY = 0
|
||||
dlg:repaint()
|
||||
end
|
||||
}
|
||||
|
||||
----------------------------------------------------------------
|
||||
-- RENDER TAB (Preview - all animations)
|
||||
----------------------------------------------------------------
|
||||
dlg:separator{ id = "sepRender", text = "All Animations Preview", visible = false }
|
||||
|
||||
-- Define openPreviewWindow function
|
||||
openPreviewWindow = function()
|
||||
-- Close existing preview window if open
|
||||
if previewDlg then
|
||||
pcall(function() previewDlg:close() end)
|
||||
end
|
||||
if previewTimer then
|
||||
pcall(function() previewTimer:stop() end)
|
||||
end
|
||||
|
||||
local pvAnimFrame = { value = 0 }
|
||||
local PV_RENDER_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 = pvCols * cellW
|
||||
local pvHeight = pvRows * cellH
|
||||
|
||||
previewDlg = Dialog{
|
||||
title = "AniPhallow Preview",
|
||||
onclose = function()
|
||||
if previewTimer then
|
||||
pcall(function() previewTimer:stop() end)
|
||||
end
|
||||
previewTimer = nil
|
||||
previewDlg = nil
|
||||
S.previewWindowOpen = false
|
||||
savePrefs()
|
||||
end
|
||||
}
|
||||
|
||||
previewDlg:canvas{
|
||||
id = "pvCanvas",
|
||||
width = pvWidth,
|
||||
height = pvHeight,
|
||||
autoscaling = false,
|
||||
onpaint = function(ev)
|
||||
local gc = ev.context
|
||||
local na = #S.animNames
|
||||
if na == 0 then return end
|
||||
|
||||
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 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.useBgColor then
|
||||
gc.color = S.bgColor
|
||||
gc:fillRect(Rectangle(ox + PV_RENDER_MARGIN, oy + PV_RENDER_MARGIN, pw, ph))
|
||||
else
|
||||
drawCheckerboard(gc, pw, ph, S.previewZoom,
|
||||
ox + PV_RENDER_MARGIN, oy + PV_RENDER_MARGIN)
|
||||
end
|
||||
|
||||
local frames = S.anims[name] or {}
|
||||
local totalFrames = #frames
|
||||
if totalFrames > 0 then
|
||||
local idx = (af % totalFrames) + 1
|
||||
local fimg = getFrameImage(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_RENDER_MARGIN + fOffX, oy + PV_RENDER_MARGIN + fOffY, pw, ph)
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end,
|
||||
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)
|
||||
end
|
||||
}
|
||||
|
||||
previewTimer = Timer{
|
||||
interval = S.animSpeed / 1000.0,
|
||||
ontick = function()
|
||||
pvAnimFrame.value = pvAnimFrame.value + 1
|
||||
pcall(function() previewDlg:repaint() end)
|
||||
end
|
||||
}
|
||||
previewTimer:start()
|
||||
|
||||
S.previewWindowOpen = true
|
||||
savePrefs()
|
||||
|
||||
previewDlg:show{ wait = false }
|
||||
end
|
||||
|
||||
dlg:button{
|
||||
id = "btnSeparateWindow",
|
||||
text = "Separate Window",
|
||||
visible = false,
|
||||
onclick = function()
|
||||
-- Close existing preview window if open
|
||||
if previewDlg then
|
||||
pcall(function() previewDlg:close() end)
|
||||
end
|
||||
if previewTimer then
|
||||
pcall(function() previewTimer:stop() end)
|
||||
end
|
||||
|
||||
local pvAnimFrame = { value = 0 }
|
||||
local RENDER_MARGIN = 2
|
||||
|
||||
previewDlg = Dialog{
|
||||
title = "AniPhallow Preview",
|
||||
onclose = function()
|
||||
if previewTimer then
|
||||
pcall(function() previewTimer:stop() end)
|
||||
end
|
||||
previewTimer = nil
|
||||
previewDlg = nil
|
||||
end
|
||||
}
|
||||
|
||||
previewDlg:canvas{
|
||||
id = "pvCanvas",
|
||||
width = SOURCE_VIEWPORT_W,
|
||||
height = S.sourceViewH,
|
||||
autoscaling = false,
|
||||
onpaint = function(ev)
|
||||
local gc = ev.context
|
||||
local numAnims = #S.animNames
|
||||
if numAnims == 0 then return end
|
||||
|
||||
local ptw = S.tileW * S.previewZoom
|
||||
local pth = S.tileH * S.previewZoom
|
||||
local cellW = ptw + RENDER_MARGIN * 2
|
||||
local cellH = pth + RENDER_MARGIN * 2
|
||||
local cols = 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 * cellW
|
||||
local oy = row * cellH
|
||||
|
||||
-- Apply preview offsets
|
||||
local pvOff = S.previewOffsets[name]
|
||||
local offX = pvOff and (pvOff.x * S.previewZoom) or 0
|
||||
local offY = pvOff and (pvOff.y * S.previewZoom) or 0
|
||||
|
||||
if S.useBgColor then
|
||||
gc.color = S.bgColor
|
||||
gc:fillRect(Rectangle(ox + RENDER_MARGIN, oy + RENDER_MARGIN, ptw, pth))
|
||||
else
|
||||
drawCheckerboard(gc, ptw, pth, S.previewZoom,
|
||||
ox + RENDER_MARGIN, oy + RENDER_MARGIN)
|
||||
end
|
||||
|
||||
local frames = S.anims[name] or {}
|
||||
local totalFrames = #frames
|
||||
if totalFrames > 0 then
|
||||
local idx = (af % totalFrames) + 1
|
||||
local fimg = getFrameImage(name, idx)
|
||||
if fimg then
|
||||
gc:drawImage(
|
||||
fimg,
|
||||
Rectangle(0, 0, S.tileW, S.tileH),
|
||||
Rectangle(ox + RENDER_MARGIN + offX, oy + RENDER_MARGIN + offY, ptw, pth)
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end,
|
||||
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)
|
||||
end
|
||||
}
|
||||
|
||||
previewTimer = Timer{
|
||||
interval = S.animSpeed / 1000.0,
|
||||
ontick = function()
|
||||
pvAnimFrame.value = pvAnimFrame.value + 1
|
||||
pcall(function() previewDlg:repaint() end)
|
||||
end
|
||||
}
|
||||
previewTimer:start()
|
||||
previewDlg:show{ wait = false }
|
||||
openPreviewWindow()
|
||||
end
|
||||
}
|
||||
|
||||
|
|
@ -1750,7 +1776,7 @@ local function run()
|
|||
dlg:canvas{
|
||||
id = "canvasRender",
|
||||
width = SOURCE_VIEWPORT_W,
|
||||
height = S.sourceViewH,
|
||||
height = SOURCE_VIEWPORT_H,
|
||||
autoscaling = false,
|
||||
visible = false,
|
||||
onpaint = function(ev)
|
||||
|
|
@ -1770,11 +1796,6 @@ local function run()
|
|||
local ox = col * cellW
|
||||
local oy = row * cellH
|
||||
|
||||
-- Apply preview offsets
|
||||
local pvOff = S.previewOffsets[name]
|
||||
local offX = pvOff and (pvOff.x * S.previewZoom) or 0
|
||||
local offY = pvOff and (pvOff.y * S.previewZoom) or 0
|
||||
|
||||
if S.useBgColor then
|
||||
gc.color = S.bgColor
|
||||
gc:fillRect(Rectangle(ox + RENDER_MARGIN, oy + RENDER_MARGIN, ptw, pth))
|
||||
|
|
@ -1789,59 +1810,16 @@ local function run()
|
|||
local idx = (S.animFrame % totalFrames) + 1
|
||||
local fimg = getFrameImage(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 + RENDER_MARGIN + offX, oy + RENDER_MARGIN + offY, ptw, pth)
|
||||
Rectangle(ox + RENDER_MARGIN + fOffX, oy + RENDER_MARGIN + fOffY, ptw, pth)
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
-- Draw border around selected animation
|
||||
if S.previewSelected == name then
|
||||
gc.color = Color(255, 255, 0, 220)
|
||||
gc:fillRect(Rectangle(ox, oy, cellW, 2))
|
||||
gc:fillRect(Rectangle(ox, oy + cellH - 2, cellW, 2))
|
||||
gc:fillRect(Rectangle(ox, oy, 2, cellH))
|
||||
gc:fillRect(Rectangle(ox + cellW - 2, oy, 2, cellH))
|
||||
end
|
||||
end
|
||||
end,
|
||||
onmousedown = function(ev)
|
||||
if ev.button == MouseButton.LEFT then
|
||||
local numAnims = #S.animNames
|
||||
if numAnims == 0 then return end
|
||||
|
||||
local ptw = S.tileW * S.previewZoom
|
||||
local pth = S.tileH * S.previewZoom
|
||||
local cellW = ptw + RENDER_MARGIN * 2
|
||||
local cellH = pth + RENDER_MARGIN * 2
|
||||
local cols = 2
|
||||
|
||||
for i, name in ipairs(S.animNames) do
|
||||
local col = (i - 1) % cols
|
||||
local row = math.floor((i - 1) / cols)
|
||||
local ox = col * cellW
|
||||
local oy = row * cellH
|
||||
|
||||
if ev.x >= ox and ev.x < ox + cellW and ev.y >= oy and ev.y < oy + cellH then
|
||||
-- Toggle selection
|
||||
if S.previewSelected == name then
|
||||
S.previewSelected = nil
|
||||
else
|
||||
S.previewSelected = name
|
||||
end
|
||||
-- Show/hide preview offset buttons
|
||||
local pvVis = (S.previewSelected ~= nil)
|
||||
pcall(function() dlg:modify{ id = "pvBtnUp", visible = pvVis } end)
|
||||
pcall(function() dlg:modify{ id = "pvBtnDown", visible = pvVis } end)
|
||||
pcall(function() dlg:modify{ id = "pvBtnLeft", visible = pvVis } end)
|
||||
pcall(function() dlg:modify{ id = "pvBtnRight", visible = pvVis } end)
|
||||
pcall(function() dlg:modify{ id = "pvBtnReset", visible = pvVis } end)
|
||||
dlg:repaint()
|
||||
return
|
||||
end
|
||||
end
|
||||
end
|
||||
end,
|
||||
onwheel = function(ev)
|
||||
|
|
@ -1851,75 +1829,6 @@ local function run()
|
|||
end
|
||||
}
|
||||
|
||||
-- Preview offset buttons (hidden by default)
|
||||
dlg:button{
|
||||
id = "pvBtnUp",
|
||||
text = "Up",
|
||||
visible = false,
|
||||
onclick = function()
|
||||
if not S.previewSelected then return end
|
||||
if not S.previewOffsets[S.previewSelected] then
|
||||
S.previewOffsets[S.previewSelected] = { x = 0, y = 0 }
|
||||
end
|
||||
S.previewOffsets[S.previewSelected].y = S.previewOffsets[S.previewSelected].y - 1
|
||||
savePrefs()
|
||||
dlg:repaint()
|
||||
end
|
||||
}
|
||||
dlg:button{
|
||||
id = "pvBtnDown",
|
||||
text = "Down",
|
||||
visible = false,
|
||||
onclick = function()
|
||||
if not S.previewSelected then return end
|
||||
if not S.previewOffsets[S.previewSelected] then
|
||||
S.previewOffsets[S.previewSelected] = { x = 0, y = 0 }
|
||||
end
|
||||
S.previewOffsets[S.previewSelected].y = S.previewOffsets[S.previewSelected].y + 1
|
||||
savePrefs()
|
||||
dlg:repaint()
|
||||
end
|
||||
}
|
||||
dlg:button{
|
||||
id = "pvBtnLeft",
|
||||
text = "Left",
|
||||
visible = false,
|
||||
onclick = function()
|
||||
if not S.previewSelected then return end
|
||||
if not S.previewOffsets[S.previewSelected] then
|
||||
S.previewOffsets[S.previewSelected] = { x = 0, y = 0 }
|
||||
end
|
||||
S.previewOffsets[S.previewSelected].x = S.previewOffsets[S.previewSelected].x - 1
|
||||
savePrefs()
|
||||
dlg:repaint()
|
||||
end
|
||||
}
|
||||
dlg:button{
|
||||
id = "pvBtnRight",
|
||||
text = "Right",
|
||||
visible = false,
|
||||
onclick = function()
|
||||
if not S.previewSelected then return end
|
||||
if not S.previewOffsets[S.previewSelected] then
|
||||
S.previewOffsets[S.previewSelected] = { x = 0, y = 0 }
|
||||
end
|
||||
S.previewOffsets[S.previewSelected].x = S.previewOffsets[S.previewSelected].x + 1
|
||||
savePrefs()
|
||||
dlg:repaint()
|
||||
end
|
||||
}
|
||||
dlg:button{
|
||||
id = "pvBtnReset",
|
||||
text = "Reset",
|
||||
visible = false,
|
||||
onclick = function()
|
||||
if not S.previewSelected then return end
|
||||
S.previewOffsets[S.previewSelected] = { x = 0, y = 0 }
|
||||
savePrefs()
|
||||
dlg:repaint()
|
||||
end
|
||||
}
|
||||
|
||||
----------------------------------------------------------------
|
||||
-- GB TAB
|
||||
----------------------------------------------------------------
|
||||
|
|
@ -1994,7 +1903,7 @@ local function run()
|
|||
local z = S.gbZoomOpt
|
||||
dlg:modify{ id = "canvasGbOpt",
|
||||
width = math.min(S.gbOptImage.width * z, SOURCE_VIEWPORT_W),
|
||||
height = math.min(S.gbOptImage.height * z, S.gbOptViewH) }
|
||||
height = math.min(S.gbOptImage.height * z, GB_OPT_H) }
|
||||
end
|
||||
dlg:repaint()
|
||||
end
|
||||
|
|
@ -2043,7 +1952,7 @@ local function run()
|
|||
dlg:separator{ id = "sepGbSource", text = "Occurrences in Source", visible = false }
|
||||
|
||||
local GB_SRC_W = SOURCE_VIEWPORT_W
|
||||
local GB_SRC_H = S.gbSrcViewH
|
||||
local GB_SRC_H = 150
|
||||
|
||||
-- Flip colors
|
||||
local FLIP_COLORS = {
|
||||
|
|
@ -2198,7 +2107,9 @@ local function run()
|
|||
end,
|
||||
onwheel = function(ev)
|
||||
local dz = ev.deltaY < 0 and 1 or -1
|
||||
S.gbZoomSrc = math.max(1, math.min(10, S.gbZoomSrc + dz))
|
||||
local newZoom = math.max(1, math.min(10, S.gbZoomSrc + dz))
|
||||
S.gbZoomSrc = newZoom
|
||||
S.gbZoomOpt = newZoom
|
||||
dlg:repaint()
|
||||
end
|
||||
}
|
||||
|
|
@ -2206,10 +2117,8 @@ local function run()
|
|||
----------------------------------------------------------------
|
||||
-- Optimized Spritesheet canvas (L-click=select)
|
||||
----------------------------------------------------------------
|
||||
dlg:separator{ id = "sepGbOpt", text = "Optimized Spritesheet (L-click=select)", visible = false }
|
||||
|
||||
local GB_OPT_W = SOURCE_VIEWPORT_W
|
||||
local GB_OPT_H = S.gbOptViewH
|
||||
local GB_OPT_H = 150
|
||||
|
||||
dlg:canvas{
|
||||
id = "canvasGbOpt",
|
||||
|
|
@ -2443,11 +2352,15 @@ local function run()
|
|||
end,
|
||||
onwheel = function(ev)
|
||||
local dz = ev.deltaY < 0 and 1 or -1
|
||||
S.gbZoomOpt = math.max(1, math.min(10, S.gbZoomOpt + dz))
|
||||
local newZoom = math.max(1, math.min(10, S.gbZoomOpt + dz))
|
||||
S.gbZoomOpt = newZoom
|
||||
S.gbZoomSrc = newZoom
|
||||
dlg:repaint()
|
||||
end
|
||||
}
|
||||
|
||||
dlg:label{ id = "lblGbOpt", text = "Optimized Spritesheet (L-click=select)", visible = false }
|
||||
|
||||
-- Save as Layer button
|
||||
dlg:button{
|
||||
id = "btnGbSaveLayer",
|
||||
|
|
@ -2619,6 +2532,11 @@ local function run()
|
|||
startAnimTimer()
|
||||
switchTab(S.currentTab)
|
||||
dlg:show{ wait = false }
|
||||
|
||||
-- Auto-reopen separate preview window if it was open
|
||||
if S.previewWindowOpen then
|
||||
openPreviewWindow()
|
||||
end
|
||||
end
|
||||
|
||||
run()
|
||||
|
|
|
|||
Loading…
Reference in New Issue