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:
Cidwel Highwind 2026-04-03 16:57:45 +02:00
parent 99ca77ef3b
commit cdeefd642d
1 changed files with 263 additions and 282 deletions

View File

@ -23,7 +23,7 @@ local SOURCE_VIEWPORT_W = 300
local SOURCE_VIEWPORT_H = 400 local SOURCE_VIEWPORT_H = 400
local MAX_ANIMS = 20 -- max number of dynamic animations 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_W = 8 -- Default Game Boy tile width
local DEFAULT_GB_TILE_H = 8 -- Default Game Boy tile height local DEFAULT_GB_TILE_H = 8 -- Default Game Boy tile height
local GB_COLS = 16 -- tiles per row in optimized image (128px = GB standard) 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", "btnFrameUp", "btnFrameDown", "btnFrameLeft", "btnFrameRight", "btnFrameReset",
} }
-- Render tab elements
local RENDER_IDS = {
"sepRender", "btnSeparateWindow", "canvasRender",
}
-- GB tab elements (reordered: Flip, Offset, Silhouette, Compress) -- GB tab elements (reordered: Flip, Offset, Silhouette, Compress)
local GB_IDS = { local GB_IDS = {
"sepGb", "btnAnalyze", "gbFlipOpt", "gbOffsetOpt", "gbSilhouette", "gbCompress", "sepGb", "btnAnalyze", "gbFlipOpt", "gbOffsetOpt", "gbSilhouette", "gbCompress",
@ -132,6 +127,24 @@ local GB_IDS = {
"btnGbSaveLayer", "btnGbCopyClipboard", "btnGbSave", "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 -- Preferences
---------------------------------------------------------------------- ----------------------------------------------------------------------
@ -182,7 +195,8 @@ local function loadPrefs()
if k == "currentTab" and v ~= "" then if k == "currentTab" and v ~= "" then
-- Migrate old tab names -- Migrate old tab names
if v == "Setup" then v = "Animations" 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 S.currentTab = v
end end
if k == "animNames" and v ~= "" then if k == "animNames" and v ~= "" then
@ -391,6 +405,35 @@ local function getDarkestPaletteColor()
return Color(0, 0, 0) return Color(0, 0, 0)
end 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) -- GB Tile Hashing & Deduplication (parameterized by tw, th)
@ -870,69 +913,152 @@ local function buildOptimizedImage(img, tiles, compress, silhouette, silhouetteC
end end
---------------------------------------------------------------------- ----------------------------------------------------------------------
-- Separate preview window (module-level to persist) -- Preview Window (primary window)
---------------------------------------------------------------------- ----------------------------------------------------------------------
local previewDlg = nil openPreviewWindow = function()
local previewTimer = nil -- Close existing preview window if open
if previewDlg then
---------------------------------------------------------------------- pcall(function() previewDlg:close() end)
-- Dialog end
---------------------------------------------------------------------- if previewTimer then
local function run() pcall(function() previewTimer:stop() end)
if not app.sprite then previewTimer = nil
app.alert("No sprite is open.") end
return if pvRefreshTimer then
pcall(function() pvRefreshTimer:stop() end)
pvRefreshTimer = nil
end end
-- Force tab to Animations on start local pvAnimFrame = { value = 0 }
S.currentTab = "Animations" local PV_RENDER_MARGIN = 2
local animTimer = nil -- Calculate content-based size
local refreshTimer = nil 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() previewDlg = Dialog{
if not app.sprite then return end title = "AniPhallow Preview",
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",
onclose = function() onclose = function()
if animTimer then animTimer:stop() end if previewTimer then pcall(function() previewTimer:stop() end) end
if refreshTimer then refreshTimer:stop() end if pvRefreshTimer then pcall(function() pvRefreshTimer:stop() end) end
previewTimer = nil
pvRefreshTimer = nil
previewDlg = nil
S.previewWindowOpen = false
savePrefs() savePrefs()
-- Update main dialog button if open
if mainDlg then
pcall(function() mainDlg:modify{ id = "tabPreview", text = " Preview " } end)
end
end end
} }
-- Get a frame image for an animation at index (handles flipped frames) previewDlg:button{
local function getFrameImage(animName, idx) id = "pvSetup",
local frames = S.anims[animName] text = "Setup",
if not frames then return nil end onclick = function()
local f = frames[idx] openMainDialog()
if not f or not S.sourceImage then return nil end 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 previewDlg:canvas{
return img 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 end
-- Get current animation name 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
local dlg
local function currentAnimName() local function currentAnimName()
if S.currentAnim >= 1 and S.currentAnim <= #S.animNames then if S.currentAnim >= 1 and S.currentAnim <= #S.animNames then
return S.animNames[S.currentAnim] return S.animNames[S.currentAnim]
@ -940,16 +1066,8 @@ local function run()
return nil return nil
end end
local function startAnimTimer() local function getFrameImage(animName, idx)
if animTimer then animTimer:stop() end return getFrameImageGlobal(animName, idx)
animTimer = Timer{
interval = S.animSpeed / 1000.0,
ontick = function()
S.animFrame = S.animFrame + 1
dlg:repaint()
end
}
animTimer:start()
end end
local function captureCell(pixelX, pixelY, flipped) local function captureCell(pixelX, pixelY, flipped)
@ -968,34 +1086,56 @@ local function run()
dlg:repaint() dlg:repaint()
end end
-- Switch visible tab -- Switch visible tab (only Animations and GB)
local function switchTab(tab) local function switchTab(tab)
S.currentTab = tab S.currentTab = tab
local isSetup = (tab == "Animations") local isSetup = (tab == "Animations")
local isRender = (tab == "Preview")
local isGB = (tab == "GB") local isGB = (tab == "GB")
for _, id in ipairs(SETUP_IDS) do for _, id in ipairs(SETUP_IDS) do
pcall(function() dlg:modify{ id = id, visible = isSetup } end) pcall(function() dlg:modify{ id = id, visible = isSetup } end)
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 for _, id in ipairs(GB_IDS) do
pcall(function() dlg:modify{ id = id, visible = isGB } end) pcall(function() dlg:modify{ id = id, visible = isGB } end)
end end
-- Update tab button labels -- Update tab button labels for Animations and GB only
for _, t in ipairs(TABS) do for _, t in ipairs(TABS) do
local label = (t == tab) and ("[" .. t .. "]") or (" " .. t .. " ") 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 end
dlg:repaint() dlg:repaint()
end end
-- Forward-declare openPreviewWindow so it can be used from button and auto-open local function startAnimTimer()
local openPreviewWindow 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 for _, tab in ipairs(TABS) do
local label = (tab == S.currentTab) and ("[" .. tab .. "]") or (" " .. tab .. " ") local label = (tab == S.currentTab) and ("[" .. tab .. "]") or (" " .. tab .. " ")
@ -1008,9 +1148,22 @@ local function run()
} }
end end
---------------------------------------------------------------- -- Preview toggle button
-- SETUP TAB 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 -- 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 if th < 4 then th = 4 elseif th > 128 then th = 128 end
S.tileH = th S.tileH = th
S.animSpeed = d.data.animSpeed 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.useBgColor = d.data.useBgColor
S.bgColor = d.data.bgColor S.bgColor = d.data.bgColor
local gtw = d.data.gbTileW local gtw = d.data.gbTileW
@ -1107,7 +1261,7 @@ local function run()
end end
savePrefs() savePrefs()
dlg:close() dlg:close()
run() openMainDialog()
end end
end end
end end
@ -1131,7 +1285,7 @@ local function run()
S.selectedFrame = 0 S.selectedFrame = 0
savePrefs() savePrefs()
dlg:close() dlg:close()
run() openMainDialog()
end end
} }
@ -1160,7 +1314,7 @@ local function run()
end end
savePrefs() savePrefs()
dlg:close() dlg:close()
run() openMainDialog()
end end
end end
end end
@ -1199,7 +1353,7 @@ local function run()
end end
savePrefs() savePrefs()
dlg:close() dlg:close()
run() openMainDialog()
end end
end end
end end
@ -1483,7 +1637,7 @@ local function run()
---------------------------------------------------------------- ----------------------------------------------------------------
-- Frame action buttons -- 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{ dlg:button{
id = "btnFlipX", id = "btnFlipX",
@ -1564,7 +1718,7 @@ local function run()
S.selectedFrame = 0 S.selectedFrame = 0
savePrefs() savePrefs()
dlg:close() dlg:close()
run() openMainDialog()
return return
end end
@ -1649,186 +1803,6 @@ local function run()
end 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 -- GB TAB
---------------------------------------------------------------- ----------------------------------------------------------------
@ -1952,7 +1926,6 @@ 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 = 150
-- Flip colors -- Flip colors
local FLIP_COLORS = { local FLIP_COLORS = {
@ -2118,7 +2091,6 @@ local function run()
-- Optimized Spritesheet canvas (L-click=select) -- Optimized Spritesheet canvas (L-click=select)
---------------------------------------------------------------- ----------------------------------------------------------------
local GB_OPT_W = SOURCE_VIEWPORT_W local GB_OPT_W = SOURCE_VIEWPORT_W
local GB_OPT_H = 150
dlg:canvas{ dlg:canvas{
id = "canvasGbOpt", id = "canvasGbOpt",
@ -2359,7 +2331,7 @@ local function run()
end 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 -- Save as Layer button
dlg:button{ dlg:button{
@ -2495,19 +2467,19 @@ local function run()
} }
---------------------------------------------------------------- ----------------------------------------------------------------
-- Timers -- Timers for main dialog
---------------------------------------------------------------- ----------------------------------------------------------------
animTimer = Timer{ mainAnimTimer = Timer{
interval = S.animSpeed / 1000.0, interval = S.animSpeed / 1000.0,
ontick = function() ontick = function()
S.animFrame = S.animFrame + 1 S.animFrame = S.animFrame + 1
dlg:repaint() pcall(function() dlg:repaint() end)
end end
} }
animTimer:start() mainAnimTimer:start()
local gbRefreshCounter = 0 local gbRefreshCounter = 0
refreshTimer = Timer{ mainRefreshTimer = Timer{
interval = 0.5, interval = 0.5,
ontick = function() ontick = function()
local isDragging = S.dragging or S.gbDragging or S.gbSrcDragging local isDragging = S.dragging or S.gbDragging or S.gbSrcDragging
@ -2517,26 +2489,35 @@ local function run()
gbRefreshCounter = 0 gbRefreshCounter = 0
refreshSource() refreshSource()
end end
dlg:repaint() pcall(function() dlg:repaint() end)
else else
gbRefreshCounter = 0 gbRefreshCounter = 0
if not isDragging then if not isDragging then
refreshSource() refreshSource()
end end
dlg:repaint() pcall(function() dlg:repaint() end)
end end
end end
} }
refreshTimer:start() mainRefreshTimer:start()
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
----------------------------------------------------------------------
-- 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 end
run() run()