diff --git a/aniphallow.lua b/aniphallow.lua index 714e55f..88cf312 100644 --- a/aniphallow.lua +++ b/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_=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()