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
412
aniphallow.lua
412
aniphallow.lua
|
|
@ -82,13 +82,8 @@ local S = {
|
||||||
gbAlwaysOverwrite = false, -- overwrite layer checkbox
|
gbAlwaysOverwrite = false, -- overwrite layer checkbox
|
||||||
gbTileW = DEFAULT_GB_TILE_W, -- configurable tile width for GB analysis
|
gbTileW = DEFAULT_GB_TILE_W, -- configurable tile width for GB analysis
|
||||||
gbTileH = DEFAULT_GB_TILE_H, -- configurable tile height for GB analysis
|
gbTileH = DEFAULT_GB_TILE_H, -- configurable tile height for GB analysis
|
||||||
-- Configurable view heights
|
-- Remember if separate preview window was open
|
||||||
sourceViewH = 400, -- configurable source canvas height
|
previewWindowOpen = false,
|
||||||
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
|
|
||||||
-- Click vs drag for GB canvases
|
-- Click vs drag for GB canvases
|
||||||
gbOptClickX = 0,
|
gbOptClickX = 0,
|
||||||
gbOptClickY = 0,
|
gbOptClickY = 0,
|
||||||
|
|
@ -96,9 +91,9 @@ local S = {
|
||||||
gbSrcClickX = 0,
|
gbSrcClickX = 0,
|
||||||
gbSrcClickY = 0,
|
gbSrcClickY = 0,
|
||||||
gbSrcIsDrag = false,
|
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)
|
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
|
-- Frame selection & drag
|
||||||
selectedFrame = 0,
|
selectedFrame = 0,
|
||||||
frameDragging = false,
|
frameDragging = false,
|
||||||
|
|
@ -117,14 +112,14 @@ local S = {
|
||||||
local SETUP_IDS = {
|
local SETUP_IDS = {
|
||||||
"sepAnims", "cmbAnimList", "btnNewAnim", "btnDelAnim", "btnRenameAnim", "btnCloneAnim",
|
"sepAnims", "cmbAnimList", "btnNewAnim", "btnDelAnim", "btnRenameAnim", "btnCloneAnim",
|
||||||
"sepSource", "canvasSource",
|
"sepSource", "canvasSource",
|
||||||
"sepFrames", "canvasStrips",
|
"canvasStrips",
|
||||||
"sepActions", "btnFlipX", "btnFlipY", "btnMoveLeft", "btnMoveRight", "btnRemoveFrame",
|
"lblFrames", "btnFlipX", "btnFlipY", "btnMoveLeft", "btnMoveRight", "btnRemoveFrame",
|
||||||
|
"btnFrameUp", "btnFrameDown", "btnFrameLeft", "btnFrameRight", "btnFrameReset",
|
||||||
}
|
}
|
||||||
|
|
||||||
-- Render tab elements
|
-- Render tab elements
|
||||||
local RENDER_IDS = {
|
local RENDER_IDS = {
|
||||||
"sepRender", "btnSeparateWindow", "canvasRender",
|
"sepRender", "btnSeparateWindow", "canvasRender",
|
||||||
"pvBtnUp", "pvBtnDown", "pvBtnLeft", "pvBtnRight", "pvBtnReset",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
-- GB tab elements (reordered: Flip, Offset, Silhouette, Compress)
|
-- GB tab elements (reordered: Flip, Offset, Silhouette, Compress)
|
||||||
|
|
@ -133,7 +128,7 @@ local GB_IDS = {
|
||||||
"gbAnalyzeMode",
|
"gbAnalyzeMode",
|
||||||
"gbSimilarThreshold", "btnFindSimilar",
|
"gbSimilarThreshold", "btnFindSimilar",
|
||||||
"sepGbSource", "canvasGbSource",
|
"sepGbSource", "canvasGbSource",
|
||||||
"sepGbOpt", "canvasGbOpt",
|
"canvasGbOpt", "lblGbOpt",
|
||||||
"btnGbSaveLayer", "btnGbCopyClipboard", "btnGbSave",
|
"btnGbSaveLayer", "btnGbCopyClipboard", "btnGbSave",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -142,11 +137,11 @@ local GB_IDS = {
|
||||||
----------------------------------------------------------------------
|
----------------------------------------------------------------------
|
||||||
local PREFS_FILE = app.fs.joinPath(app.fs.userConfigPath, "aniphallow_prefs.ini")
|
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 function serializeFrames(frames)
|
||||||
local parts = {}
|
local parts = {}
|
||||||
for _, f in ipairs(frames) do
|
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
|
end
|
||||||
return table.concat(parts, ";")
|
return table.concat(parts, ";")
|
||||||
end
|
end
|
||||||
|
|
@ -156,12 +151,14 @@ local function deserializeFrames(str)
|
||||||
local frames = {}
|
local frames = {}
|
||||||
if not str or str == "" then return frames end
|
if not str or str == "" then return frames end
|
||||||
for part in string.gmatch(str, "([^;]+)") do
|
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
|
if x and y then
|
||||||
table.insert(frames, {
|
table.insert(frames, {
|
||||||
x = tonumber(x), y = tonumber(y),
|
x = tonumber(x), y = tonumber(y),
|
||||||
flipped = (fh == "1"),
|
flipped = (fh == "1"),
|
||||||
flippedV = (fv == "1")
|
flippedV = (fv == "1"),
|
||||||
|
offX = tonumber(ox) or 0,
|
||||||
|
offY = tonumber(oy) or 0
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
@ -222,20 +219,8 @@ local function loadPrefs()
|
||||||
S.gbTileW = ts
|
S.gbTileW = ts
|
||||||
S.gbTileH = ts
|
S.gbTileH = ts
|
||||||
end
|
end
|
||||||
-- Load configurable view heights
|
-- Load previewWindowOpen
|
||||||
if k == "sourceViewH" then S.sourceViewH = tonumber(v) or 400 end
|
if k == "previewWindowOpen" then S.previewWindowOpen = (v == "true") 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
|
|
||||||
-- Dynamic anim frames: anim_0, anim_1, ...
|
-- Dynamic anim frames: anim_0, anim_1, ...
|
||||||
local animIdx = k and string.match(k, "^anim_(%d+)$")
|
local animIdx = k and string.match(k, "^anim_(%d+)$")
|
||||||
if animIdx then
|
if animIdx then
|
||||||
|
|
@ -301,14 +286,8 @@ local function savePrefs()
|
||||||
f:write("gbAlwaysOverwrite=" .. tostring(S.gbAlwaysOverwrite) .. "\n")
|
f:write("gbAlwaysOverwrite=" .. tostring(S.gbAlwaysOverwrite) .. "\n")
|
||||||
f:write("gbTileW=" .. S.gbTileW .. "\n")
|
f:write("gbTileW=" .. S.gbTileW .. "\n")
|
||||||
f:write("gbTileH=" .. S.gbTileH .. "\n")
|
f:write("gbTileH=" .. S.gbTileH .. "\n")
|
||||||
-- Save configurable view heights
|
-- Save previewWindowOpen
|
||||||
f:write("sourceViewH=" .. S.sourceViewH .. "\n")
|
f:write("previewWindowOpen=" .. tostring(S.previewWindowOpen) .. "\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
|
|
||||||
for i, name in ipairs(S.animNames) do
|
for i, name in ipairs(S.animNames) do
|
||||||
f:write("anim_" .. (i - 1) .. "=" .. serializeFrames(S.anims[name] or {}) .. "\n")
|
f:write("anim_" .. (i - 1) .. "=" .. serializeFrames(S.anims[name] or {}) .. "\n")
|
||||||
end
|
end
|
||||||
|
|
@ -382,7 +361,7 @@ end
|
||||||
local function clampScroll()
|
local function clampScroll()
|
||||||
local contentW, contentH = getSourceContentSize()
|
local contentW, contentH = getSourceContentSize()
|
||||||
local maxScrollX = math.max(0, contentW - SOURCE_VIEWPORT_W)
|
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.scrollX = clamp(S.scrollX, 0, maxScrollX)
|
||||||
S.scrollY = clamp(S.scrollY, 0, maxScrollY)
|
S.scrollY = clamp(S.scrollY, 0, maxScrollY)
|
||||||
end
|
end
|
||||||
|
|
@ -930,7 +909,7 @@ local function run()
|
||||||
|
|
||||||
local contentW, contentH = getSourceContentSize()
|
local contentW, contentH = getSourceContentSize()
|
||||||
local viewW = math.min(contentW, SOURCE_VIEWPORT_W)
|
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{
|
local dlg = Dialog{
|
||||||
title = "AniPhallow - Animation Builder",
|
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 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 gridY < 0 or gridY + S.tileH > S.sourceImage.height then return end
|
||||||
if not S.anims[name] then S.anims[name] = {} 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()
|
dlg:repaint()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -1009,26 +988,12 @@ local function run()
|
||||||
local label = (t == tab) and ("[" .. t .. "]") or (" " .. t .. " ")
|
local label = (t == tab) and ("[" .. t .. "]") or (" " .. t .. " ")
|
||||||
dlg:modify{ id = "tab" .. t, text = label }
|
dlg:modify{ id = "tab" .. t, text = label }
|
||||||
end
|
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()
|
dlg:repaint()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Forward-declare openPreviewWindow so it can be used from button and auto-open
|
||||||
|
local openPreviewWindow
|
||||||
|
|
||||||
----------------------------------------------------------------
|
----------------------------------------------------------------
|
||||||
-- TAB BUTTONS
|
-- TAB BUTTONS
|
||||||
----------------------------------------------------------------
|
----------------------------------------------------------------
|
||||||
|
|
@ -1070,10 +1035,6 @@ local function run()
|
||||||
d:color{ id = "silhouetteColor", label = "Silhouette:", color = silColor }
|
d:color{ id = "silhouetteColor", label = "Silhouette:", color = silColor }
|
||||||
d:entry{ id = "layerName", label = "Layer name:", text = S.gbLayerName }
|
d:entry{ id = "layerName", label = "Layer name:", text = S.gbLayerName }
|
||||||
d:check{ id = "alwaysOverwrite", text = "Always overwrite layer", selected = S.gbAlwaysOverwrite }
|
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{ id = "ok", text = "OK" }
|
||||||
d:button{ text = "Cancel" }
|
d:button{ text = "Cancel" }
|
||||||
d:show()
|
d:show()
|
||||||
|
|
@ -1098,26 +1059,6 @@ local function run()
|
||||||
S.gbSilhouetteColorSet = true
|
S.gbSilhouetteColorSet = true
|
||||||
S.gbLayerName = d.data.layerName
|
S.gbLayerName = d.data.layerName
|
||||||
S.gbAlwaysOverwrite = d.data.alwaysOverwrite
|
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()
|
dlg:repaint()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
@ -1212,11 +1153,6 @@ local function run()
|
||||||
S.anims[newName] = S.anims[name]
|
S.anims[newName] = S.anims[name]
|
||||||
S.anims[name] = nil
|
S.anims[name] = nil
|
||||||
S.animNames[S.currentAnim] = newName
|
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
|
-- Re-sort alphabetically
|
||||||
table.sort(S.animNames, function(a, b) return a:lower() < b:lower() end)
|
table.sort(S.animNames, function(a, b) return a:lower() < b:lower() end)
|
||||||
for i, n in ipairs(S.animNames) do
|
for i, n in ipairs(S.animNames) do
|
||||||
|
|
@ -1248,7 +1184,7 @@ local function run()
|
||||||
local srcFrames = S.anims[name] or {}
|
local srcFrames = S.anims[name] or {}
|
||||||
local newFrames = {}
|
local newFrames = {}
|
||||||
for _, fr in ipairs(srcFrames) do
|
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
|
if d.data.flipX then
|
||||||
nf.flipped = not nf.flipped
|
nf.flipped = not nf.flipped
|
||||||
end
|
end
|
||||||
|
|
@ -1283,7 +1219,7 @@ local function run()
|
||||||
local gc = ev.context
|
local gc = ev.context
|
||||||
local cW, cH = getSourceContentSize()
|
local cW, cH = getSourceContentSize()
|
||||||
local vw = math.min(cW, SOURCE_VIEWPORT_W)
|
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)
|
drawCheckerboard(gc, vw, vh, S.sourceZoom)
|
||||||
|
|
||||||
|
|
@ -1386,8 +1322,6 @@ local function run()
|
||||||
----------------------------------------------------------------
|
----------------------------------------------------------------
|
||||||
-- Frame strip for current animation
|
-- 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_TOTAL_W = SOURCE_VIEWPORT_W
|
||||||
local STRIP_CELL = THUMB_SIZE + 1
|
local STRIP_CELL = THUMB_SIZE + 1
|
||||||
local STRIP_H = STRIP_CELL * 2 + 4
|
local STRIP_H = STRIP_CELL * 2 + 4
|
||||||
|
|
@ -1537,13 +1471,19 @@ local function run()
|
||||||
S.frameDragging = false
|
S.frameDragging = false
|
||||||
dlg:repaint()
|
dlg:repaint()
|
||||||
end
|
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
|
end
|
||||||
}
|
}
|
||||||
|
|
||||||
----------------------------------------------------------------
|
----------------------------------------------------------------
|
||||||
-- Frame action buttons
|
-- Frame action buttons
|
||||||
----------------------------------------------------------------
|
----------------------------------------------------------------
|
||||||
dlg:separator{ id = "sepActions" }
|
dlg:label{ id = "lblFrames", text = "Frames: L-click=select, R-click=remove, drag=reorder" }
|
||||||
|
|
||||||
dlg:button{
|
dlg:button{
|
||||||
id = "btnFlipX",
|
id = "btnFlipX",
|
||||||
|
|
@ -1643,16 +1583,79 @@ local function run()
|
||||||
end
|
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)
|
-- RENDER TAB (Preview - all animations)
|
||||||
----------------------------------------------------------------
|
----------------------------------------------------------------
|
||||||
dlg:separator{ id = "sepRender", text = "All Animations Preview", visible = false }
|
dlg:separator{ id = "sepRender", text = "All Animations Preview", visible = false }
|
||||||
|
|
||||||
dlg:button{
|
-- Define openPreviewWindow function
|
||||||
id = "btnSeparateWindow",
|
openPreviewWindow = function()
|
||||||
text = "Separate Window",
|
|
||||||
visible = false,
|
|
||||||
onclick = function()
|
|
||||||
-- Close existing preview window if open
|
-- Close existing preview window if open
|
||||||
if previewDlg then
|
if previewDlg then
|
||||||
pcall(function() previewDlg:close() end)
|
pcall(function() previewDlg:close() end)
|
||||||
|
|
@ -1662,7 +1665,18 @@ local function run()
|
||||||
end
|
end
|
||||||
|
|
||||||
local pvAnimFrame = { value = 0 }
|
local pvAnimFrame = { value = 0 }
|
||||||
local RENDER_MARGIN = 2
|
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{
|
previewDlg = Dialog{
|
||||||
title = "AniPhallow Preview",
|
title = "AniPhallow Preview",
|
||||||
|
|
@ -1672,43 +1686,40 @@ local function run()
|
||||||
end
|
end
|
||||||
previewTimer = nil
|
previewTimer = nil
|
||||||
previewDlg = nil
|
previewDlg = nil
|
||||||
|
S.previewWindowOpen = false
|
||||||
|
savePrefs()
|
||||||
end
|
end
|
||||||
}
|
}
|
||||||
|
|
||||||
previewDlg:canvas{
|
previewDlg:canvas{
|
||||||
id = "pvCanvas",
|
id = "pvCanvas",
|
||||||
width = SOURCE_VIEWPORT_W,
|
width = pvWidth,
|
||||||
height = S.sourceViewH,
|
height = pvHeight,
|
||||||
autoscaling = false,
|
autoscaling = false,
|
||||||
onpaint = function(ev)
|
onpaint = function(ev)
|
||||||
local gc = ev.context
|
local gc = ev.context
|
||||||
local numAnims = #S.animNames
|
local na = #S.animNames
|
||||||
if numAnims == 0 then return end
|
if na == 0 then return end
|
||||||
|
|
||||||
local ptw = S.tileW * S.previewZoom
|
local pw = S.tileW * S.previewZoom
|
||||||
local pth = S.tileH * S.previewZoom
|
local ph = S.tileH * S.previewZoom
|
||||||
local cellW = ptw + RENDER_MARGIN * 2
|
local cW = pw + PV_RENDER_MARGIN * 2
|
||||||
local cellH = pth + RENDER_MARGIN * 2
|
local cH = ph + PV_RENDER_MARGIN * 2
|
||||||
local cols = 2
|
local cols = 2
|
||||||
local af = pvAnimFrame.value
|
local af = pvAnimFrame.value
|
||||||
|
|
||||||
for i, name in ipairs(S.animNames) do
|
for i, name in ipairs(S.animNames) do
|
||||||
local col = (i - 1) % cols
|
local col = (i - 1) % cols
|
||||||
local row = math.floor((i - 1) / cols)
|
local row = math.floor((i - 1) / cols)
|
||||||
local ox = col * cellW
|
local ox = col * cW
|
||||||
local oy = row * cellH
|
local oy = row * cH
|
||||||
|
|
||||||
-- 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
|
if S.useBgColor then
|
||||||
gc.color = S.bgColor
|
gc.color = S.bgColor
|
||||||
gc:fillRect(Rectangle(ox + RENDER_MARGIN, oy + RENDER_MARGIN, ptw, pth))
|
gc:fillRect(Rectangle(ox + PV_RENDER_MARGIN, oy + PV_RENDER_MARGIN, pw, ph))
|
||||||
else
|
else
|
||||||
drawCheckerboard(gc, ptw, pth, S.previewZoom,
|
drawCheckerboard(gc, pw, ph, S.previewZoom,
|
||||||
ox + RENDER_MARGIN, oy + RENDER_MARGIN)
|
ox + PV_RENDER_MARGIN, oy + PV_RENDER_MARGIN)
|
||||||
end
|
end
|
||||||
|
|
||||||
local frames = S.anims[name] or {}
|
local frames = S.anims[name] or {}
|
||||||
|
|
@ -1717,10 +1728,13 @@ local function run()
|
||||||
local idx = (af % totalFrames) + 1
|
local idx = (af % totalFrames) + 1
|
||||||
local fimg = getFrameImage(name, idx)
|
local fimg = getFrameImage(name, idx)
|
||||||
if fimg then
|
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(
|
gc:drawImage(
|
||||||
fimg,
|
fimg,
|
||||||
Rectangle(0, 0, S.tileW, S.tileH),
|
Rectangle(0, 0, S.tileW, S.tileH),
|
||||||
Rectangle(ox + RENDER_MARGIN + offX, oy + RENDER_MARGIN + offY, ptw, pth)
|
Rectangle(ox + PV_RENDER_MARGIN + fOffX, oy + PV_RENDER_MARGIN + fOffY, pw, ph)
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
@ -1741,8 +1755,20 @@ local function run()
|
||||||
end
|
end
|
||||||
}
|
}
|
||||||
previewTimer:start()
|
previewTimer:start()
|
||||||
|
|
||||||
|
S.previewWindowOpen = true
|
||||||
|
savePrefs()
|
||||||
|
|
||||||
previewDlg:show{ wait = false }
|
previewDlg:show{ wait = false }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
dlg:button{
|
||||||
|
id = "btnSeparateWindow",
|
||||||
|
text = "Separate Window",
|
||||||
|
visible = false,
|
||||||
|
onclick = function()
|
||||||
|
openPreviewWindow()
|
||||||
|
end
|
||||||
}
|
}
|
||||||
|
|
||||||
local RENDER_MARGIN = 2
|
local RENDER_MARGIN = 2
|
||||||
|
|
@ -1750,7 +1776,7 @@ local function run()
|
||||||
dlg:canvas{
|
dlg:canvas{
|
||||||
id = "canvasRender",
|
id = "canvasRender",
|
||||||
width = SOURCE_VIEWPORT_W,
|
width = SOURCE_VIEWPORT_W,
|
||||||
height = S.sourceViewH,
|
height = SOURCE_VIEWPORT_H,
|
||||||
autoscaling = false,
|
autoscaling = false,
|
||||||
visible = false,
|
visible = false,
|
||||||
onpaint = function(ev)
|
onpaint = function(ev)
|
||||||
|
|
@ -1770,11 +1796,6 @@ local function run()
|
||||||
local ox = col * cellW
|
local ox = col * cellW
|
||||||
local oy = row * cellH
|
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
|
if S.useBgColor then
|
||||||
gc.color = S.bgColor
|
gc.color = S.bgColor
|
||||||
gc:fillRect(Rectangle(ox + RENDER_MARGIN, oy + RENDER_MARGIN, ptw, pth))
|
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 idx = (S.animFrame % totalFrames) + 1
|
||||||
local fimg = getFrameImage(name, idx)
|
local fimg = getFrameImage(name, idx)
|
||||||
if fimg then
|
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(
|
gc:drawImage(
|
||||||
fimg,
|
fimg,
|
||||||
Rectangle(0, 0, S.tileW, S.tileH),
|
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
|
||||||
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
|
||||||
end,
|
end,
|
||||||
onwheel = function(ev)
|
onwheel = function(ev)
|
||||||
|
|
@ -1851,75 +1829,6 @@ local function run()
|
||||||
end
|
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
|
-- GB TAB
|
||||||
----------------------------------------------------------------
|
----------------------------------------------------------------
|
||||||
|
|
@ -1994,7 +1903,7 @@ local function run()
|
||||||
local z = S.gbZoomOpt
|
local z = S.gbZoomOpt
|
||||||
dlg:modify{ id = "canvasGbOpt",
|
dlg:modify{ id = "canvasGbOpt",
|
||||||
width = math.min(S.gbOptImage.width * z, SOURCE_VIEWPORT_W),
|
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
|
end
|
||||||
dlg:repaint()
|
dlg:repaint()
|
||||||
end
|
end
|
||||||
|
|
@ -2043,7 +1952,7 @@ local function run()
|
||||||
dlg:separator{ id = "sepGbSource", text = "Occurrences in Source", visible = false }
|
dlg:separator{ id = "sepGbSource", text = "Occurrences in Source", visible = false }
|
||||||
|
|
||||||
local GB_SRC_W = SOURCE_VIEWPORT_W
|
local GB_SRC_W = SOURCE_VIEWPORT_W
|
||||||
local GB_SRC_H = S.gbSrcViewH
|
local GB_SRC_H = 150
|
||||||
|
|
||||||
-- Flip colors
|
-- Flip colors
|
||||||
local FLIP_COLORS = {
|
local FLIP_COLORS = {
|
||||||
|
|
@ -2198,7 +2107,9 @@ local function run()
|
||||||
end,
|
end,
|
||||||
onwheel = function(ev)
|
onwheel = function(ev)
|
||||||
local dz = ev.deltaY < 0 and 1 or -1
|
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()
|
dlg:repaint()
|
||||||
end
|
end
|
||||||
}
|
}
|
||||||
|
|
@ -2206,10 +2117,8 @@ local function run()
|
||||||
----------------------------------------------------------------
|
----------------------------------------------------------------
|
||||||
-- Optimized Spritesheet canvas (L-click=select)
|
-- 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_W = SOURCE_VIEWPORT_W
|
||||||
local GB_OPT_H = S.gbOptViewH
|
local GB_OPT_H = 150
|
||||||
|
|
||||||
dlg:canvas{
|
dlg:canvas{
|
||||||
id = "canvasGbOpt",
|
id = "canvasGbOpt",
|
||||||
|
|
@ -2443,11 +2352,15 @@ local function run()
|
||||||
end,
|
end,
|
||||||
onwheel = function(ev)
|
onwheel = function(ev)
|
||||||
local dz = ev.deltaY < 0 and 1 or -1
|
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()
|
dlg:repaint()
|
||||||
end
|
end
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dlg:label{ id = "lblGbOpt", text = "Optimized Spritesheet (L-click=select)", visible = false }
|
||||||
|
|
||||||
-- Save as Layer button
|
-- Save as Layer button
|
||||||
dlg:button{
|
dlg:button{
|
||||||
id = "btnGbSaveLayer",
|
id = "btnGbSaveLayer",
|
||||||
|
|
@ -2619,6 +2532,11 @@ local function run()
|
||||||
startAnimTimer()
|
startAnimTimer()
|
||||||
switchTab(S.currentTab)
|
switchTab(S.currentTab)
|
||||||
dlg:show{ wait = false }
|
dlg:show{ wait = false }
|
||||||
|
|
||||||
|
-- Auto-reopen separate preview window if it was open
|
||||||
|
if S.previewWindowOpen then
|
||||||
|
openPreviewWindow()
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
run()
|
run()
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue