Major UI overhaul and bug fixes
- Fix Save Layer creating empty layers (clone image before creating cel) - Fix Copy to Clipboard (use MaskAll instead of SelectAll) - Remove Layer saved alert dialog - Rename tabs: Setup to Animations, Render to Preview - Config button now visible on all tabs - Animation selection: Add/Remove buttons, brackets on selected, Selected label - Delete animation now asks for confirmation - Source canvas: left-click add or scroll drag, mouse wheel zoom - Frames strip: click to select, drag and drop to reorder frames - New frame actions: Flip X, Flip Y, Move Left, Move Right, Clear Anim - Added vertical flip support for frames - Removed destructive Clear All button - Added Preview canvas in Animations tab with wheel zoom - Updated all hint labels to be more descriptive - Backward-compatible prefs migration for old tab names Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
23d74a6f2a
commit
534fc09da3
341
aniphallow.lua
341
aniphallow.lua
|
|
@ -23,7 +23,7 @@ local SOURCE_VIEWPORT_W = 300
|
|||
local SOURCE_VIEWPORT_H = 250
|
||||
|
||||
local MAX_ANIMS = 20 -- max number of dynamic animations
|
||||
local TABS = { "Setup", "Render", "GB" }
|
||||
local TABS = { "Animations", "Preview", "GB" }
|
||||
local GB_TILE = 8 -- Game Boy tile size (fixed 8x8)
|
||||
local GB_COLS = 16 -- tiles per row in optimized image (128px = GB standard)
|
||||
|
||||
|
|
@ -40,7 +40,7 @@ local S = {
|
|||
sourceZoom = DEFAULT_SOURCE_ZOOM,
|
||||
animFrame = 0,
|
||||
currentAnim = 1, -- index into animNames
|
||||
currentTab = "Setup",
|
||||
currentTab = "Animations",
|
||||
sourceImage = nil,
|
||||
scrollX = 0,
|
||||
scrollY = 0,
|
||||
|
|
@ -80,9 +80,18 @@ local S = {
|
|||
gbLayerName = "auto-optimized-tiles", -- default layer name
|
||||
gbAlwaysOverwrite = false, -- overwrite layer checkbox
|
||||
gbTileSize = GB_TILE, -- current tile size used (8 for pixel, from tileset for tile)
|
||||
-- Dynamic animations: each frame is {x, y, flipped}
|
||||
-- Dynamic animations: each frame is {x, y, flipped, flippedV}
|
||||
animNames = {}, -- ordered list of animation names
|
||||
anims = {}, -- name -> list of {x, y, flipped}
|
||||
anims = {}, -- name -> list of {x, y, flipped, flippedV}
|
||||
-- Frame selection & drag
|
||||
selectedFrame = 0,
|
||||
frameDragging = false,
|
||||
frameDragFrom = 0,
|
||||
frameDragTo = 0,
|
||||
-- Source canvas click vs drag
|
||||
srcClickX = 0,
|
||||
srcClickY = 0,
|
||||
srcIsDrag = false,
|
||||
}
|
||||
|
||||
----------------------------------------------------------------------
|
||||
|
|
@ -90,11 +99,12 @@ local S = {
|
|||
----------------------------------------------------------------------
|
||||
-- Setup tab elements (static IDs, dynamic ones added at runtime)
|
||||
local SETUP_IDS = {
|
||||
"btnConfig",
|
||||
"sepAnims", "lblCurrentAnim", "btnNewAnim", "btnDelAnim",
|
||||
"sepAnims", "btnNewAnim", "btnDelAnim",
|
||||
"lblSelectedAnim",
|
||||
"sepSource", "canvasSource",
|
||||
"sepFrames", "canvasStrips",
|
||||
"sepActions", "btnClearAnim", "btnClearAll",
|
||||
"sepActions", "btnFlipX", "btnFlipY", "btnMoveLeft", "btnMoveRight", "btnClearAnim",
|
||||
"sepPreviewAnim", "canvasPreviewAnim",
|
||||
}
|
||||
|
||||
-- Render tab elements (static, dynamic anim canvases handled separately)
|
||||
|
|
@ -121,7 +131,7 @@ local PREFS_FILE = app.fs.joinPath(app.fs.userConfigPath, "aniphallow_prefs.ini"
|
|||
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"))
|
||||
table.insert(parts, f.x .. "," .. f.y .. "," .. (f.flipped and "1" or "0") .. "," .. (f.flippedV and "1" or "0"))
|
||||
end
|
||||
return table.concat(parts, ";")
|
||||
end
|
||||
|
|
@ -131,11 +141,12 @@ 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, fl = string.match(part, "([%d-]+),([%d-]+),?(%d?)")
|
||||
local x, y, fh, fv = string.match(part, "([%d-]+),([%d-]+),?(%d?),?(%d?)")
|
||||
if x and y then
|
||||
table.insert(frames, {
|
||||
x = tonumber(x), y = tonumber(y),
|
||||
flipped = (fl == "1")
|
||||
flipped = (fh == "1"),
|
||||
flippedV = (fv == "1")
|
||||
})
|
||||
end
|
||||
end
|
||||
|
|
@ -153,7 +164,12 @@ local function loadPrefs()
|
|||
if k == "previewZoom" then S.previewZoom = tonumber(v) or DEFAULT_PREVIEW_ZOOM end
|
||||
if k == "sourceZoom" then S.sourceZoom = tonumber(v) or DEFAULT_SOURCE_ZOOM end
|
||||
if k == "currentAnim" then S.currentAnim = tonumber(v) or 1 end
|
||||
if k == "currentTab" and v ~= "" then S.currentTab = v end
|
||||
if k == "currentTab" and v ~= "" then
|
||||
-- Migrate old tab names
|
||||
if v == "Setup" then v = "Animations"
|
||||
elseif v == "Render" then v = "Preview" end
|
||||
S.currentTab = v
|
||||
end
|
||||
if k == "animNames" and v ~= "" then
|
||||
S.animNames = {}
|
||||
for name in string.gmatch(v, "([^|]+)") do
|
||||
|
|
@ -261,6 +277,17 @@ local function flipImageH(src)
|
|||
return flipped
|
||||
end
|
||||
|
||||
local function flipImageV(src)
|
||||
local flipped = Image(src.width, src.height, ColorMode.RGB)
|
||||
flipped:clear(pc.rgba(0, 0, 0, 0))
|
||||
for y = 0, src.height - 1 do
|
||||
for x = 0, src.width - 1 do
|
||||
flipped:putPixel(x, src.height - 1 - y, src:getPixel(x, y))
|
||||
end
|
||||
end
|
||||
return flipped
|
||||
end
|
||||
|
||||
local function drawCheckerboard(gc, w, h, zoom, offsetX, offsetY)
|
||||
local ox = offsetX or 0
|
||||
local oy = offsetY or 0
|
||||
|
|
@ -869,6 +896,7 @@ local function run()
|
|||
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
|
||||
|
||||
|
|
@ -882,7 +910,13 @@ local function run()
|
|||
|
||||
local function updateAnimLabel()
|
||||
local name = currentAnimName() or "(none)"
|
||||
dlg:modify{ id = "lblCurrentAnim", text = "-> " .. name }
|
||||
dlg:modify{ id = "lblSelectedAnim", text = "Selected -> " .. name }
|
||||
for i = 1, #S.animNames do
|
||||
pcall(function()
|
||||
local label = (i == S.currentAnim) and ("[" .. S.animNames[i] .. "]") or S.animNames[i]
|
||||
dlg:modify{ id = "btnAnim" .. i, text = label }
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
local function startAnimTimer()
|
||||
|
|
@ -916,8 +950,8 @@ local function run()
|
|||
-- Switch visible tab
|
||||
local function switchTab(tab)
|
||||
S.currentTab = tab
|
||||
local isSetup = (tab == "Setup")
|
||||
local isRender = (tab == "Render")
|
||||
local isSetup = (tab == "Animations")
|
||||
local isRender = (tab == "Preview")
|
||||
local isGB = (tab == "GB")
|
||||
for _, id in ipairs(SETUP_IDS) do
|
||||
dlg:modify{ id = id, visible = isSetup }
|
||||
|
|
@ -1012,14 +1046,9 @@ local function run()
|
|||
----------------------------------------------------------------
|
||||
dlg:separator{ id = "sepAnims", text = "Animations" }
|
||||
|
||||
dlg:label{
|
||||
id = "lblCurrentAnim",
|
||||
text = "-> " .. (currentAnimName() or "(none)"),
|
||||
}
|
||||
|
||||
dlg:button{
|
||||
id = "btnNewAnim",
|
||||
text = "+",
|
||||
text = "Add Animation",
|
||||
onclick = function()
|
||||
local d = Dialog{ title = "New Animation" }
|
||||
d:entry{ id = "name", label = "Name:", text = "" }
|
||||
|
|
@ -1042,13 +1071,20 @@ local function run()
|
|||
|
||||
dlg:button{
|
||||
id = "btnDelAnim",
|
||||
text = "-",
|
||||
text = "Remove Animation",
|
||||
onclick = function()
|
||||
local name = currentAnimName()
|
||||
if not name then return end
|
||||
local result = app.alert{
|
||||
title = "Delete Animation",
|
||||
text = "Delete animation '" .. name .. "'?",
|
||||
buttons = { "Delete", "Cancel" }
|
||||
}
|
||||
if result ~= 1 then return end
|
||||
S.anims[name] = nil
|
||||
table.remove(S.animNames, S.currentAnim)
|
||||
S.currentAnim = math.min(S.currentAnim, math.max(1, #S.animNames))
|
||||
S.selectedFrame = 0
|
||||
savePrefs()
|
||||
dlg:close()
|
||||
run()
|
||||
|
|
@ -1057,13 +1093,22 @@ local function run()
|
|||
|
||||
dlg:newrow()
|
||||
|
||||
-- Create buttons only for existing animations
|
||||
dlg:label{
|
||||
id = "lblSelectedAnim",
|
||||
text = "Selected -> " .. (currentAnimName() or "(none)"),
|
||||
}
|
||||
|
||||
dlg:newrow()
|
||||
|
||||
-- Create buttons for existing animations (selected one gets [brackets])
|
||||
for i = 1, #S.animNames do
|
||||
local label = (i == S.currentAnim) and ("[" .. S.animNames[i] .. "]") or S.animNames[i]
|
||||
dlg:button{
|
||||
id = "btnAnim" .. i,
|
||||
text = S.animNames[i],
|
||||
text = label,
|
||||
onclick = function()
|
||||
S.currentAnim = i
|
||||
S.selectedFrame = 0
|
||||
updateAnimLabel()
|
||||
dlg:repaint()
|
||||
end
|
||||
|
|
@ -1073,7 +1118,7 @@ local function run()
|
|||
----------------------------------------------------------------
|
||||
-- Source canvas (L-click=add, R-click=add flipped, M-click=scroll)
|
||||
----------------------------------------------------------------
|
||||
dlg:separator{ id = "sepSource", text = "Source (L=add, R=add flipped)" }
|
||||
dlg:separator{ id = "sepSource", text = "Source (left-click=add frame, right-click=add flipped)" }
|
||||
|
||||
dlg:canvas{
|
||||
id = "canvasSource",
|
||||
|
|
@ -1125,7 +1170,6 @@ local function run()
|
|||
local rw = S.tileW * S.sourceZoom
|
||||
local rh = S.tileH * S.sourceZoom
|
||||
if rx + rw > 0 and rx < vw and ry + rh > 0 and ry < vh then
|
||||
-- Blue for normal, orange for flipped
|
||||
gc.color = f.flipped
|
||||
and Color(255, 160, 0, 160)
|
||||
or Color(100, 200, 255, 160)
|
||||
|
|
@ -1140,37 +1184,55 @@ local function run()
|
|||
end,
|
||||
onmousedown = function(ev)
|
||||
if ev.button == MouseButton.LEFT then
|
||||
local pixelX = math.floor((ev.x + S.scrollX) / S.sourceZoom)
|
||||
local pixelY = math.floor((ev.y + S.scrollY) / S.sourceZoom)
|
||||
captureCell(pixelX, pixelY, false)
|
||||
S.srcClickX = ev.x
|
||||
S.srcClickY = ev.y
|
||||
S.srcIsDrag = false
|
||||
S.dragging = true
|
||||
S.dragLastX = ev.x
|
||||
S.dragLastY = ev.y
|
||||
elseif ev.button == MouseButton.RIGHT then
|
||||
local pixelX = math.floor((ev.x + S.scrollX) / S.sourceZoom)
|
||||
local pixelY = math.floor((ev.y + S.scrollY) / S.sourceZoom)
|
||||
captureCell(pixelX, pixelY, true)
|
||||
else
|
||||
S.dragging = true
|
||||
S.dragLastX = ev.x
|
||||
S.dragLastY = ev.y
|
||||
end
|
||||
end,
|
||||
onmousemove = function(ev)
|
||||
if S.dragging then
|
||||
local dx = math.abs(ev.x - S.srcClickX)
|
||||
local dy = math.abs(ev.y - S.srcClickY)
|
||||
if dx > 3 or dy > 3 then
|
||||
S.srcIsDrag = true
|
||||
end
|
||||
if S.srcIsDrag then
|
||||
S.scrollX = S.scrollX + (S.dragLastX - ev.x)
|
||||
S.scrollY = S.scrollY + (S.dragLastY - ev.y)
|
||||
clampScroll()
|
||||
end
|
||||
S.dragLastX = ev.x
|
||||
S.dragLastY = ev.y
|
||||
end
|
||||
end,
|
||||
onmouseup = function(ev)
|
||||
if S.dragging and not S.srcIsDrag then
|
||||
local pixelX = math.floor((S.srcClickX + S.scrollX) / S.sourceZoom)
|
||||
local pixelY = math.floor((S.srcClickY + S.scrollY) / S.sourceZoom)
|
||||
captureCell(pixelX, pixelY, false)
|
||||
end
|
||||
S.dragging = false
|
||||
S.srcIsDrag = false
|
||||
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 strip for current animation
|
||||
----------------------------------------------------------------
|
||||
dlg:separator{ id = "sepFrames", text = "Frames (right-click to remove)" }
|
||||
dlg:separator{ id = "sepFrames", text = "Frames (click to select, drag to reorder)" }
|
||||
|
||||
local STRIP_TOTAL_W = SOURCE_VIEWPORT_W
|
||||
local STRIP_CELL = THUMB_SIZE + 1
|
||||
|
|
@ -1228,11 +1290,37 @@ local function run()
|
|||
gc:fillRect(Rectangle(tx + THUMB_SIZE - 4, ty, 4, 4))
|
||||
end
|
||||
|
||||
-- Current frame indicator
|
||||
-- Flip V indicator (green dot)
|
||||
if frames[i].flippedV then
|
||||
gc.color = Color(0, 200, 0, 220)
|
||||
gc:fillRect(Rectangle(tx, ty, 4, 4))
|
||||
end
|
||||
|
||||
-- Current playback frame indicator
|
||||
if cnt > 0 and ((S.animFrame % cnt)) == (i - 1) then
|
||||
gc.color = Color(100, 200, 255, 200)
|
||||
gc:fillRect(Rectangle(tx, ty - 1, THUMB_SIZE, 2))
|
||||
end
|
||||
|
||||
-- Selected frame highlight (yellow border)
|
||||
if S.selectedFrame == i then
|
||||
gc.color = Color(255, 255, 0, 220)
|
||||
gc:fillRect(Rectangle(tx, ty, THUMB_SIZE, 2))
|
||||
gc:fillRect(Rectangle(tx, ty + THUMB_SIZE - 2, THUMB_SIZE, 2))
|
||||
gc:fillRect(Rectangle(tx, ty, 2, THUMB_SIZE))
|
||||
gc:fillRect(Rectangle(tx + THUMB_SIZE - 2, ty, 2, THUMB_SIZE))
|
||||
end
|
||||
end
|
||||
|
||||
-- Drag insertion indicator
|
||||
if S.frameDragging and S.frameDragTo >= 1 and S.frameDragFrom ~= S.frameDragTo then
|
||||
local insertIdx = S.frameDragTo
|
||||
local col = (insertIdx - 1) % thumbsPerRow
|
||||
local row = math.floor((insertIdx - 1) / thumbsPerRow)
|
||||
local lx = col * STRIP_CELL
|
||||
local ly = 2 + row * STRIP_CELL
|
||||
gc.color = Color(255, 100, 100, 220)
|
||||
gc:fillRect(Rectangle(lx - 1, ly, 2, THUMB_SIZE))
|
||||
end
|
||||
end,
|
||||
onmousedown = function(ev)
|
||||
|
|
@ -1245,36 +1333,174 @@ local function run()
|
|||
local row = math.floor((ev.y - 2) / STRIP_CELL)
|
||||
local idx = row * thumbsPerRow + col + 1
|
||||
if idx >= 1 and idx <= #frames then
|
||||
if ev.button == MouseButton.RIGHT then
|
||||
table.remove(frames, idx)
|
||||
if ev.button == MouseButton.LEFT then
|
||||
S.selectedFrame = idx
|
||||
S.frameDragging = true
|
||||
S.frameDragFrom = idx
|
||||
S.frameDragTo = idx
|
||||
dlg:repaint()
|
||||
end
|
||||
end
|
||||
end,
|
||||
onmousemove = function(ev)
|
||||
if S.frameDragging then
|
||||
local name = currentAnimName()
|
||||
local frames = name and S.anims[name] or {}
|
||||
local thumbsPerRow = math.max(1, math.floor(STRIP_TOTAL_W / STRIP_CELL))
|
||||
local col = math.floor(ev.x / STRIP_CELL)
|
||||
local row = math.floor((ev.y - 2) / STRIP_CELL)
|
||||
local idx = row * thumbsPerRow + col + 1
|
||||
S.frameDragTo = clamp(idx, 1, #frames)
|
||||
dlg:repaint()
|
||||
end
|
||||
end,
|
||||
onmouseup = function(ev)
|
||||
if S.frameDragging then
|
||||
local from = S.frameDragFrom
|
||||
local to = S.frameDragTo
|
||||
if from ~= to then
|
||||
local name = currentAnimName()
|
||||
if name and S.anims[name] then
|
||||
local frames = S.anims[name]
|
||||
local frame = table.remove(frames, from)
|
||||
table.insert(frames, to, frame)
|
||||
S.selectedFrame = to
|
||||
end
|
||||
end
|
||||
S.frameDragging = false
|
||||
dlg:repaint()
|
||||
end
|
||||
end
|
||||
}
|
||||
|
||||
----------------------------------------------------------------
|
||||
-- Clear buttons
|
||||
-- Frame action buttons
|
||||
----------------------------------------------------------------
|
||||
dlg:separator{ id = "sepActions" }
|
||||
|
||||
dlg:button{
|
||||
id = "btnFlipX",
|
||||
text = "Flip X",
|
||||
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].flipped = not frames[S.selectedFrame].flipped
|
||||
dlg:repaint()
|
||||
end
|
||||
}
|
||||
|
||||
dlg:button{
|
||||
id = "btnFlipY",
|
||||
text = "Flip Y",
|
||||
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].flippedV = not frames[S.selectedFrame].flippedV
|
||||
dlg:repaint()
|
||||
end
|
||||
}
|
||||
|
||||
dlg:button{
|
||||
id = "btnMoveLeft",
|
||||
text = "Move Left",
|
||||
onclick = function()
|
||||
local name = currentAnimName()
|
||||
if not name or S.selectedFrame < 2 then return end
|
||||
local frames = S.anims[name]
|
||||
if not frames then return end
|
||||
local idx = S.selectedFrame
|
||||
frames[idx], frames[idx - 1] = frames[idx - 1], frames[idx]
|
||||
S.selectedFrame = idx - 1
|
||||
dlg:repaint()
|
||||
end
|
||||
}
|
||||
|
||||
dlg:button{
|
||||
id = "btnMoveRight",
|
||||
text = "Move 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
|
||||
local idx = S.selectedFrame
|
||||
frames[idx], frames[idx + 1] = frames[idx + 1], frames[idx]
|
||||
S.selectedFrame = idx + 1
|
||||
dlg:repaint()
|
||||
end
|
||||
}
|
||||
|
||||
dlg:button{
|
||||
id = "btnClearAnim",
|
||||
text = "Clear Anim",
|
||||
onclick = function()
|
||||
local name = currentAnimName()
|
||||
if name then S.anims[name] = {} end
|
||||
if not name then return end
|
||||
local result = app.alert{
|
||||
title = "Clear Animation",
|
||||
text = "Clear all frames from '" .. name .. "'?",
|
||||
buttons = { "Clear", "Cancel" }
|
||||
}
|
||||
if result ~= 1 then return end
|
||||
S.anims[name] = {}
|
||||
S.selectedFrame = 0
|
||||
dlg:repaint()
|
||||
end
|
||||
}
|
||||
|
||||
dlg:button{
|
||||
id = "btnClearAll",
|
||||
text = "Clear All",
|
||||
onclick = function()
|
||||
for _, name in ipairs(S.animNames) do
|
||||
S.anims[name] = {}
|
||||
----------------------------------------------------------------
|
||||
-- Preview canvas (animation playback in Animations tab)
|
||||
----------------------------------------------------------------
|
||||
dlg:separator{ id = "sepPreviewAnim", text = "Preview" }
|
||||
|
||||
local PREVIEW_ANIM_H = 60
|
||||
|
||||
dlg:canvas{
|
||||
id = "canvasPreviewAnim",
|
||||
width = SOURCE_VIEWPORT_W,
|
||||
height = PREVIEW_ANIM_H,
|
||||
autoscaling = false,
|
||||
onpaint = function(ev)
|
||||
local gc = ev.context
|
||||
local name = currentAnimName()
|
||||
|
||||
gc.color = Color(30, 30, 30)
|
||||
gc:fillRect(Rectangle(0, 0, SOURCE_VIEWPORT_W, PREVIEW_ANIM_H))
|
||||
|
||||
if not name then return end
|
||||
local frames = S.anims[name] or {}
|
||||
local totalFrames = #frames
|
||||
if totalFrames == 0 then return end
|
||||
|
||||
local tw = S.tileW * S.previewZoom
|
||||
local th = S.tileH * S.previewZoom
|
||||
local ox = math.max(0, math.floor((SOURCE_VIEWPORT_W - tw) / 2))
|
||||
local oy = math.max(0, math.floor((PREVIEW_ANIM_H - th) / 2))
|
||||
|
||||
if S.useBgColor then
|
||||
gc.color = S.bgColor
|
||||
gc:fillRect(Rectangle(ox, oy, tw, th))
|
||||
else
|
||||
drawCheckerboard(gc, tw, th, S.previewZoom, ox, oy)
|
||||
end
|
||||
|
||||
local idx = (S.animFrame % totalFrames) + 1
|
||||
local fimg = getFrameImage(name, idx)
|
||||
if fimg then
|
||||
gc:drawImage(
|
||||
fimg,
|
||||
Rectangle(0, 0, S.tileW, S.tileH),
|
||||
Rectangle(ox, oy, tw, th)
|
||||
)
|
||||
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
|
||||
}
|
||||
|
|
@ -1282,7 +1508,7 @@ local function run()
|
|||
----------------------------------------------------------------
|
||||
-- RENDER TAB (single canvas with all animations in 2 columns)
|
||||
----------------------------------------------------------------
|
||||
dlg:separator{ id = "sepRender", text = "Animations", visible = false }
|
||||
dlg:separator{ id = "sepRender", text = "All Animations Preview", visible = false }
|
||||
|
||||
local RENDER_MARGIN = 2
|
||||
|
||||
|
|
@ -1767,18 +1993,16 @@ local function run()
|
|||
end
|
||||
end
|
||||
|
||||
local imgCopy = S.gbOptImage:clone()
|
||||
app.transaction("Save Optimized Layer", function()
|
||||
if existingLayer then
|
||||
if S.gbAlwaysOverwrite then
|
||||
-- Overwrite: delete old cels, add new cel with image
|
||||
existingLayer.isEditable = true
|
||||
for _, cel in ipairs(existingLayer.cels) do
|
||||
sp:deleteCel(cel)
|
||||
end
|
||||
existingLayer.isEditable = true
|
||||
sp:newCel(existingLayer, app.frame, S.gbOptImage, Point(0, 0))
|
||||
existingLayer.isEditable = false
|
||||
sp:newCel(existingLayer, app.frame, imgCopy, Point(0, 0))
|
||||
else
|
||||
-- Create new layer with incremented name
|
||||
local suffix = 2
|
||||
local newName = layerName .. "-" .. suffix
|
||||
local nameExists = true
|
||||
|
|
@ -1795,21 +2019,14 @@ local function run()
|
|||
end
|
||||
local newLayer = sp:newLayer()
|
||||
newLayer.name = newName
|
||||
-- Move just above existing layer
|
||||
if existingIdx then
|
||||
sp:newCel(newLayer, app.frame, S.gbOptImage, Point(0, 0))
|
||||
newLayer.isEditable = false
|
||||
end
|
||||
sp:newCel(newLayer, app.frame, imgCopy, Point(0, 0))
|
||||
end
|
||||
else
|
||||
-- Create at top of layer stack
|
||||
local newLayer = sp:newLayer()
|
||||
newLayer.name = layerName
|
||||
sp:newCel(newLayer, app.frame, S.gbOptImage, Point(0, 0))
|
||||
newLayer.isEditable = false
|
||||
sp:newCel(newLayer, app.frame, imgCopy, Point(0, 0))
|
||||
end
|
||||
end)
|
||||
app.alert("Layer saved: " .. layerName)
|
||||
end
|
||||
}
|
||||
|
||||
|
|
@ -1823,15 +2040,15 @@ local function run()
|
|||
app.alert("Run Analyze first.")
|
||||
return
|
||||
end
|
||||
local origSprite = app.sprite
|
||||
local optW = S.gbOptImage.width
|
||||
local optH = S.gbOptImage.height
|
||||
local tmpSprite = Sprite(optW, optH, ColorMode.RGB)
|
||||
local cel = tmpSprite.cels[1]
|
||||
cel.image:drawImage(S.gbOptImage)
|
||||
app.command.SelectAll()
|
||||
cel.image = S.gbOptImage:clone()
|
||||
app.command.MaskAll()
|
||||
app.command.CopyMerged()
|
||||
tmpSprite:close()
|
||||
app.alert("Copied!")
|
||||
end
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue