Round 4: two-window architecture, preview-first launch
- Plugin now opens Preview Window first on launch (primary view) - Preview window has "Setup" button to open the Main Dialog - Main dialog tabs: [Animations] [GB] + [Preview] toggle button - Preview button toggles separate preview window on/off - Tab order: Animations, GB, Preview (GB is now second) - Removed embedded Preview tab content (redundant with window) - Fix Analyze nil compare error (GB_OPT_H now module-level) - Labels changed to separators for consistent styling - Frames label: "Frames (L-click=select, R-click=remove, drag=reorder)" - GB canvases taller (200px each) to match Animations tab height - refreshSource() moved to module level for both windows - getFrameImageGlobal() at module level for shared access - Cross-dialog updates (preview close updates main dialog button) - Preview window has its own refresh timer for source updates
This commit is contained in:
parent
99ca77ef3b
commit
cdeefd642d
545
aniphallow.lua
545
aniphallow.lua
|
|
@ -23,7 +23,7 @@ local SOURCE_VIEWPORT_W = 300
|
|||
local SOURCE_VIEWPORT_H = 400
|
||||
|
||||
local MAX_ANIMS = 20 -- max number of dynamic animations
|
||||
local TABS = { "Animations", "Preview", "GB" }
|
||||
local TABS = { "Animations", "GB" }
|
||||
local DEFAULT_GB_TILE_W = 8 -- Default Game Boy tile width
|
||||
local DEFAULT_GB_TILE_H = 8 -- Default Game Boy tile height
|
||||
local GB_COLS = 16 -- tiles per row in optimized image (128px = GB standard)
|
||||
|
|
@ -117,11 +117,6 @@ local SETUP_IDS = {
|
|||
"btnFrameUp", "btnFrameDown", "btnFrameLeft", "btnFrameRight", "btnFrameReset",
|
||||
}
|
||||
|
||||
-- Render tab elements
|
||||
local RENDER_IDS = {
|
||||
"sepRender", "btnSeparateWindow", "canvasRender",
|
||||
}
|
||||
|
||||
-- GB tab elements (reordered: Flip, Offset, Silhouette, Compress)
|
||||
local GB_IDS = {
|
||||
"sepGb", "btnAnalyze", "gbFlipOpt", "gbOffsetOpt", "gbSilhouette", "gbCompress",
|
||||
|
|
@ -132,6 +127,24 @@ local GB_IDS = {
|
|||
"btnGbSaveLayer", "btnGbCopyClipboard", "btnGbSave",
|
||||
}
|
||||
|
||||
-- GB canvas heights (taller to match Animations tab and prevent resize on tab switch)
|
||||
local GB_SRC_H = 200
|
||||
local GB_OPT_H = 200
|
||||
|
||||
----------------------------------------------------------------------
|
||||
-- Module-level window/timer variables
|
||||
----------------------------------------------------------------------
|
||||
local previewDlg = nil
|
||||
local previewTimer = nil
|
||||
local pvRefreshTimer = nil
|
||||
local mainDlg = nil
|
||||
local mainAnimTimer = nil
|
||||
local mainRefreshTimer = nil
|
||||
|
||||
-- Forward declarations
|
||||
local openPreviewWindow
|
||||
local openMainDialog
|
||||
|
||||
----------------------------------------------------------------------
|
||||
-- Preferences
|
||||
----------------------------------------------------------------------
|
||||
|
|
@ -182,7 +195,8 @@ local function loadPrefs()
|
|||
if k == "currentTab" and v ~= "" then
|
||||
-- Migrate old tab names
|
||||
if v == "Setup" then v = "Animations"
|
||||
elseif v == "Render" then v = "Preview" end
|
||||
elseif v == "Render" then v = "Animations"
|
||||
elseif v == "Preview" then v = "Animations" end
|
||||
S.currentTab = v
|
||||
end
|
||||
if k == "animNames" and v ~= "" then
|
||||
|
|
@ -391,6 +405,35 @@ local function getDarkestPaletteColor()
|
|||
return Color(0, 0, 0)
|
||||
end
|
||||
|
||||
----------------------------------------------------------------------
|
||||
-- Module-level refreshSource
|
||||
----------------------------------------------------------------------
|
||||
local function refreshSource()
|
||||
if not app.sprite then return end
|
||||
local sp = app.sprite
|
||||
local rgbSpec = ImageSpec{
|
||||
width = sp.width, height = sp.height,
|
||||
colorMode = ColorMode.RGB, transparentColor = 0
|
||||
}
|
||||
local img = Image(rgbSpec)
|
||||
img:clear()
|
||||
img:drawSprite(sp, app.frame)
|
||||
S.sourceImage = img
|
||||
end
|
||||
|
||||
----------------------------------------------------------------------
|
||||
-- Module-level getFrameImage
|
||||
----------------------------------------------------------------------
|
||||
local function getFrameImageGlobal(animName, idx)
|
||||
local frames = S.anims[animName]
|
||||
if not frames then return nil end
|
||||
local f = frames[idx]
|
||||
if not f or not S.sourceImage then return nil end
|
||||
local img = extractCell(S.sourceImage, f.x, f.y, S.tileW, S.tileH)
|
||||
if f.flipped then img = flipImageH(img) end
|
||||
if f.flippedV then img = flipImageV(img) end
|
||||
return img
|
||||
end
|
||||
|
||||
----------------------------------------------------------------------
|
||||
-- GB Tile Hashing & Deduplication (parameterized by tw, th)
|
||||
|
|
@ -870,69 +913,152 @@ local function buildOptimizedImage(img, tiles, compress, silhouette, silhouetteC
|
|||
end
|
||||
|
||||
----------------------------------------------------------------------
|
||||
-- Separate preview window (module-level to persist)
|
||||
-- Preview Window (primary window)
|
||||
----------------------------------------------------------------------
|
||||
local previewDlg = nil
|
||||
local previewTimer = nil
|
||||
|
||||
----------------------------------------------------------------------
|
||||
-- Dialog
|
||||
----------------------------------------------------------------------
|
||||
local function run()
|
||||
if not app.sprite then
|
||||
app.alert("No sprite is open.")
|
||||
return
|
||||
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)
|
||||
previewTimer = nil
|
||||
end
|
||||
if pvRefreshTimer then
|
||||
pcall(function() pvRefreshTimer:stop() end)
|
||||
pvRefreshTimer = nil
|
||||
end
|
||||
|
||||
-- Force tab to Animations on start
|
||||
S.currentTab = "Animations"
|
||||
local pvAnimFrame = { value = 0 }
|
||||
local PV_RENDER_MARGIN = 2
|
||||
|
||||
local animTimer = nil
|
||||
local refreshTimer = nil
|
||||
-- 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)
|
||||
|
||||
local function refreshSource()
|
||||
if not app.sprite then return end
|
||||
local sp = app.sprite
|
||||
local rgbSpec = ImageSpec{
|
||||
width = sp.width,
|
||||
height = sp.height,
|
||||
colorMode = ColorMode.RGB,
|
||||
transparentColor = 0
|
||||
}
|
||||
local img = Image(rgbSpec)
|
||||
img:clear()
|
||||
img:drawSprite(sp, app.frame)
|
||||
S.sourceImage = img
|
||||
end
|
||||
|
||||
refreshSource()
|
||||
|
||||
local contentW, contentH = getSourceContentSize()
|
||||
local viewW = math.min(contentW, SOURCE_VIEWPORT_W)
|
||||
local viewH = math.min(contentH, SOURCE_VIEWPORT_H)
|
||||
|
||||
local dlg = Dialog{
|
||||
title = "AniPhallow - Animation Builder",
|
||||
previewDlg = Dialog{
|
||||
title = "AniPhallow Preview",
|
||||
onclose = function()
|
||||
if animTimer then animTimer:stop() end
|
||||
if refreshTimer then refreshTimer:stop() end
|
||||
if previewTimer then pcall(function() previewTimer:stop() end) end
|
||||
if pvRefreshTimer then pcall(function() pvRefreshTimer:stop() end) end
|
||||
previewTimer = nil
|
||||
pvRefreshTimer = nil
|
||||
previewDlg = nil
|
||||
S.previewWindowOpen = false
|
||||
savePrefs()
|
||||
-- Update main dialog button if open
|
||||
if mainDlg then
|
||||
pcall(function() mainDlg:modify{ id = "tabPreview", text = " Preview " } end)
|
||||
end
|
||||
end
|
||||
}
|
||||
|
||||
-- Get a frame image for an animation at index (handles flipped frames)
|
||||
local function getFrameImage(animName, idx)
|
||||
local frames = S.anims[animName]
|
||||
if not frames then return nil end
|
||||
local f = frames[idx]
|
||||
if not f or not S.sourceImage then return nil end
|
||||
local img = extractCell(S.sourceImage, f.x, f.y, S.tileW, S.tileH)
|
||||
if f.flipped then img = flipImageH(img) end
|
||||
if f.flippedV then img = flipImageV(img) end
|
||||
return img
|
||||
previewDlg:button{
|
||||
id = "pvSetup",
|
||||
text = "Setup",
|
||||
onclick = function()
|
||||
openMainDialog()
|
||||
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 = 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_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()
|
||||
|
||||
pvRefreshTimer = Timer{
|
||||
interval = 0.5,
|
||||
ontick = function()
|
||||
refreshSource()
|
||||
pcall(function() previewDlg:repaint() end)
|
||||
end
|
||||
}
|
||||
pvRefreshTimer:start()
|
||||
|
||||
S.previewWindowOpen = true
|
||||
savePrefs()
|
||||
|
||||
previewDlg:show{ wait = false }
|
||||
end
|
||||
|
||||
----------------------------------------------------------------------
|
||||
-- Main Dialog (secondary window - setup)
|
||||
----------------------------------------------------------------------
|
||||
openMainDialog = function()
|
||||
-- If main dialog already open, just bring focus (close and reopen)
|
||||
if mainDlg then
|
||||
pcall(function() mainDlg:close() end)
|
||||
end
|
||||
|
||||
-- Get current animation name
|
||||
local dlg
|
||||
|
||||
local function currentAnimName()
|
||||
if S.currentAnim >= 1 and S.currentAnim <= #S.animNames then
|
||||
return S.animNames[S.currentAnim]
|
||||
|
|
@ -940,16 +1066,8 @@ local function run()
|
|||
return nil
|
||||
end
|
||||
|
||||
local function startAnimTimer()
|
||||
if animTimer then animTimer:stop() end
|
||||
animTimer = Timer{
|
||||
interval = S.animSpeed / 1000.0,
|
||||
ontick = function()
|
||||
S.animFrame = S.animFrame + 1
|
||||
dlg:repaint()
|
||||
end
|
||||
}
|
||||
animTimer:start()
|
||||
local function getFrameImage(animName, idx)
|
||||
return getFrameImageGlobal(animName, idx)
|
||||
end
|
||||
|
||||
local function captureCell(pixelX, pixelY, flipped)
|
||||
|
|
@ -968,34 +1086,56 @@ local function run()
|
|||
dlg:repaint()
|
||||
end
|
||||
|
||||
-- Switch visible tab
|
||||
-- Switch visible tab (only Animations and GB)
|
||||
local function switchTab(tab)
|
||||
S.currentTab = tab
|
||||
local isSetup = (tab == "Animations")
|
||||
local isRender = (tab == "Preview")
|
||||
local isGB = (tab == "GB")
|
||||
for _, id in ipairs(SETUP_IDS) do
|
||||
pcall(function() dlg:modify{ id = id, visible = isSetup } end)
|
||||
end
|
||||
for _, id in ipairs(RENDER_IDS) do
|
||||
pcall(function() dlg:modify{ id = id, visible = isRender } end)
|
||||
end
|
||||
for _, id in ipairs(GB_IDS) do
|
||||
pcall(function() dlg:modify{ id = id, visible = isGB } end)
|
||||
end
|
||||
-- Update tab button labels
|
||||
-- Update tab button labels for Animations and GB only
|
||||
for _, t in ipairs(TABS) do
|
||||
local label = (t == tab) and ("[" .. t .. "]") or (" " .. t .. " ")
|
||||
dlg:modify{ id = "tab" .. t, text = label }
|
||||
pcall(function() dlg:modify{ id = "tab" .. t, text = label } end)
|
||||
end
|
||||
dlg:repaint()
|
||||
end
|
||||
|
||||
-- Forward-declare openPreviewWindow so it can be used from button and auto-open
|
||||
local openPreviewWindow
|
||||
local function startAnimTimer()
|
||||
if mainAnimTimer then mainAnimTimer:stop() end
|
||||
mainAnimTimer = Timer{
|
||||
interval = S.animSpeed / 1000.0,
|
||||
ontick = function()
|
||||
S.animFrame = S.animFrame + 1
|
||||
pcall(function() dlg:repaint() end)
|
||||
end
|
||||
}
|
||||
mainAnimTimer:start()
|
||||
end
|
||||
|
||||
local contentW, contentH = getSourceContentSize()
|
||||
local viewW = math.min(contentW, SOURCE_VIEWPORT_W)
|
||||
local viewH = math.min(contentH, SOURCE_VIEWPORT_H)
|
||||
|
||||
dlg = Dialog{
|
||||
title = "AniPhallow - Animation Builder",
|
||||
onclose = function()
|
||||
if mainAnimTimer then pcall(function() mainAnimTimer:stop() end) end
|
||||
if mainRefreshTimer then pcall(function() mainRefreshTimer:stop() end) end
|
||||
mainAnimTimer = nil
|
||||
mainRefreshTimer = nil
|
||||
mainDlg = nil
|
||||
savePrefs()
|
||||
end
|
||||
}
|
||||
mainDlg = dlg
|
||||
|
||||
----------------------------------------------------------------
|
||||
-- TAB BUTTONS
|
||||
-- TAB BUTTONS (Animations, GB, Preview toggle)
|
||||
----------------------------------------------------------------
|
||||
for _, tab in ipairs(TABS) do
|
||||
local label = (tab == S.currentTab) and ("[" .. tab .. "]") or (" " .. tab .. " ")
|
||||
|
|
@ -1008,9 +1148,22 @@ local function run()
|
|||
}
|
||||
end
|
||||
|
||||
----------------------------------------------------------------
|
||||
-- SETUP TAB
|
||||
----------------------------------------------------------------
|
||||
-- Preview toggle button
|
||||
dlg:button{
|
||||
id = "tabPreview",
|
||||
text = S.previewWindowOpen and "[Preview]" or " Preview ",
|
||||
onclick = function()
|
||||
if previewDlg then
|
||||
-- Close it
|
||||
pcall(function() previewDlg:close() end)
|
||||
-- previewDlg onclose handler sets S.previewWindowOpen = false
|
||||
dlg:modify{ id = "tabPreview", text = " Preview " }
|
||||
else
|
||||
openPreviewWindow()
|
||||
dlg:modify{ id = "tabPreview", text = "[Preview]" }
|
||||
end
|
||||
end
|
||||
}
|
||||
|
||||
----------------------------------------------------------------
|
||||
-- Config button (opens sub-dialog) - visible on ALL tabs
|
||||
|
|
@ -1046,7 +1199,8 @@ local function run()
|
|||
if th < 4 then th = 4 elseif th > 128 then th = 128 end
|
||||
S.tileH = th
|
||||
S.animSpeed = d.data.animSpeed
|
||||
if animTimer then animTimer.interval = S.animSpeed / 1000.0 end
|
||||
if mainAnimTimer then mainAnimTimer.interval = S.animSpeed / 1000.0 end
|
||||
if previewTimer then previewTimer.interval = S.animSpeed / 1000.0 end
|
||||
S.useBgColor = d.data.useBgColor
|
||||
S.bgColor = d.data.bgColor
|
||||
local gtw = d.data.gbTileW
|
||||
|
|
@ -1107,7 +1261,7 @@ local function run()
|
|||
end
|
||||
savePrefs()
|
||||
dlg:close()
|
||||
run()
|
||||
openMainDialog()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -1131,7 +1285,7 @@ local function run()
|
|||
S.selectedFrame = 0
|
||||
savePrefs()
|
||||
dlg:close()
|
||||
run()
|
||||
openMainDialog()
|
||||
end
|
||||
}
|
||||
|
||||
|
|
@ -1160,7 +1314,7 @@ local function run()
|
|||
end
|
||||
savePrefs()
|
||||
dlg:close()
|
||||
run()
|
||||
openMainDialog()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -1199,7 +1353,7 @@ local function run()
|
|||
end
|
||||
savePrefs()
|
||||
dlg:close()
|
||||
run()
|
||||
openMainDialog()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -1483,7 +1637,7 @@ local function run()
|
|||
----------------------------------------------------------------
|
||||
-- Frame action buttons
|
||||
----------------------------------------------------------------
|
||||
dlg:label{ id = "lblFrames", text = "Frames: L-click=select, R-click=remove, drag=reorder" }
|
||||
dlg:separator{ id = "lblFrames", text = "Frames (L-click=select, R-click=remove, drag=reorder)" }
|
||||
|
||||
dlg:button{
|
||||
id = "btnFlipX",
|
||||
|
|
@ -1564,7 +1718,7 @@ local function run()
|
|||
S.selectedFrame = 0
|
||||
savePrefs()
|
||||
dlg:close()
|
||||
run()
|
||||
openMainDialog()
|
||||
return
|
||||
end
|
||||
|
||||
|
|
@ -1649,186 +1803,6 @@ local function run()
|
|||
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()
|
||||
openPreviewWindow()
|
||||
end
|
||||
}
|
||||
|
||||
local RENDER_MARGIN = 2
|
||||
|
||||
dlg:canvas{
|
||||
id = "canvasRender",
|
||||
width = SOURCE_VIEWPORT_W,
|
||||
height = SOURCE_VIEWPORT_H,
|
||||
autoscaling = false,
|
||||
visible = 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
|
||||
|
||||
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 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 = (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 + fOffX, oy + RENDER_MARGIN + fOffY, 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))
|
||||
dlg:repaint()
|
||||
end
|
||||
}
|
||||
|
||||
----------------------------------------------------------------
|
||||
-- GB TAB
|
||||
----------------------------------------------------------------
|
||||
|
|
@ -1952,7 +1926,6 @@ local function run()
|
|||
dlg:separator{ id = "sepGbSource", text = "Occurrences in Source", visible = false }
|
||||
|
||||
local GB_SRC_W = SOURCE_VIEWPORT_W
|
||||
local GB_SRC_H = 150
|
||||
|
||||
-- Flip colors
|
||||
local FLIP_COLORS = {
|
||||
|
|
@ -2118,7 +2091,6 @@ local function run()
|
|||
-- Optimized Spritesheet canvas (L-click=select)
|
||||
----------------------------------------------------------------
|
||||
local GB_OPT_W = SOURCE_VIEWPORT_W
|
||||
local GB_OPT_H = 150
|
||||
|
||||
dlg:canvas{
|
||||
id = "canvasGbOpt",
|
||||
|
|
@ -2359,7 +2331,7 @@ local function run()
|
|||
end
|
||||
}
|
||||
|
||||
dlg:label{ id = "lblGbOpt", text = "Optimized Spritesheet (L-click=select)", visible = false }
|
||||
dlg:separator{ id = "lblGbOpt", text = "Optimized Spritesheet (L-click=select)", visible = false }
|
||||
|
||||
-- Save as Layer button
|
||||
dlg:button{
|
||||
|
|
@ -2495,19 +2467,19 @@ local function run()
|
|||
}
|
||||
|
||||
----------------------------------------------------------------
|
||||
-- Timers
|
||||
-- Timers for main dialog
|
||||
----------------------------------------------------------------
|
||||
animTimer = Timer{
|
||||
mainAnimTimer = Timer{
|
||||
interval = S.animSpeed / 1000.0,
|
||||
ontick = function()
|
||||
S.animFrame = S.animFrame + 1
|
||||
dlg:repaint()
|
||||
pcall(function() dlg:repaint() end)
|
||||
end
|
||||
}
|
||||
animTimer:start()
|
||||
mainAnimTimer:start()
|
||||
|
||||
local gbRefreshCounter = 0
|
||||
refreshTimer = Timer{
|
||||
mainRefreshTimer = Timer{
|
||||
interval = 0.5,
|
||||
ontick = function()
|
||||
local isDragging = S.dragging or S.gbDragging or S.gbSrcDragging
|
||||
|
|
@ -2517,26 +2489,35 @@ local function run()
|
|||
gbRefreshCounter = 0
|
||||
refreshSource()
|
||||
end
|
||||
dlg:repaint()
|
||||
pcall(function() dlg:repaint() end)
|
||||
else
|
||||
gbRefreshCounter = 0
|
||||
if not isDragging then
|
||||
refreshSource()
|
||||
end
|
||||
dlg:repaint()
|
||||
pcall(function() dlg:repaint() end)
|
||||
end
|
||||
end
|
||||
}
|
||||
refreshTimer:start()
|
||||
mainRefreshTimer:start()
|
||||
|
||||
startAnimTimer()
|
||||
switchTab(S.currentTab)
|
||||
dlg:show{ wait = false }
|
||||
end
|
||||
|
||||
-- Auto-reopen separate preview window if it was open
|
||||
if S.previewWindowOpen then
|
||||
openPreviewWindow()
|
||||
----------------------------------------------------------------------
|
||||
-- Entry point
|
||||
----------------------------------------------------------------------
|
||||
local function run()
|
||||
if not app.sprite then
|
||||
app.alert("No sprite is open.")
|
||||
return
|
||||
end
|
||||
S.currentTab = "Animations"
|
||||
refreshSource()
|
||||
-- Always open preview window on launch
|
||||
openPreviewWindow()
|
||||
end
|
||||
|
||||
run()
|
||||
|
|
|
|||
Loading…
Reference in New Issue