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:
Cidwel Highwind 2026-04-03 13:14:10 +02:00
parent 23d74a6f2a
commit 534fc09da3
1 changed files with 282 additions and 65 deletions

View File

@ -23,7 +23,7 @@ local SOURCE_VIEWPORT_W = 300
local SOURCE_VIEWPORT_H = 250 local SOURCE_VIEWPORT_H = 250
local MAX_ANIMS = 20 -- max number of dynamic animations 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_TILE = 8 -- Game Boy tile size (fixed 8x8)
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)
@ -40,7 +40,7 @@ local S = {
sourceZoom = DEFAULT_SOURCE_ZOOM, sourceZoom = DEFAULT_SOURCE_ZOOM,
animFrame = 0, animFrame = 0,
currentAnim = 1, -- index into animNames currentAnim = 1, -- index into animNames
currentTab = "Setup", currentTab = "Animations",
sourceImage = nil, sourceImage = nil,
scrollX = 0, scrollX = 0,
scrollY = 0, scrollY = 0,
@ -80,9 +80,18 @@ local S = {
gbLayerName = "auto-optimized-tiles", -- default layer name gbLayerName = "auto-optimized-tiles", -- default layer name
gbAlwaysOverwrite = false, -- overwrite layer checkbox gbAlwaysOverwrite = false, -- overwrite layer checkbox
gbTileSize = GB_TILE, -- current tile size used (8 for pixel, from tileset for tile) 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 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) -- Setup tab elements (static IDs, dynamic ones added at runtime)
local SETUP_IDS = { local SETUP_IDS = {
"btnConfig", "sepAnims", "btnNewAnim", "btnDelAnim",
"sepAnims", "lblCurrentAnim", "btnNewAnim", "btnDelAnim", "lblSelectedAnim",
"sepSource", "canvasSource", "sepSource", "canvasSource",
"sepFrames", "canvasStrips", "sepFrames", "canvasStrips",
"sepActions", "btnClearAnim", "btnClearAll", "sepActions", "btnFlipX", "btnFlipY", "btnMoveLeft", "btnMoveRight", "btnClearAnim",
"sepPreviewAnim", "canvasPreviewAnim",
} }
-- Render tab elements (static, dynamic anim canvases handled separately) -- 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 function serializeFrames(frames)
local parts = {} local parts = {}
for _, f in ipairs(frames) do for _, f in ipairs(frames) do
table.insert(parts, f.x .. "," .. f.y .. "," .. (f.flipped and "1" or "0")) table.insert(parts, f.x .. "," .. f.y .. "," .. (f.flipped and "1" or "0") .. "," .. (f.flippedV and "1" or "0"))
end end
return table.concat(parts, ";") return table.concat(parts, ";")
end end
@ -131,11 +141,12 @@ local function deserializeFrames(str)
local frames = {} local frames = {}
if not str or str == "" then return frames end if not str or str == "" then return frames end
for part in string.gmatch(str, "([^;]+)") do for part in string.gmatch(str, "([^;]+)") do
local x, y, fl = string.match(part, "([%d-]+),([%d-]+),?(%d?)") local x, y, fh, fv = string.match(part, "([%d-]+),([%d-]+),?(%d?),?(%d?)")
if x and y then if x and y then
table.insert(frames, { table.insert(frames, {
x = tonumber(x), y = tonumber(y), x = tonumber(x), y = tonumber(y),
flipped = (fl == "1") flipped = (fh == "1"),
flippedV = (fv == "1")
}) })
end end
end end
@ -153,7 +164,12 @@ local function loadPrefs()
if k == "previewZoom" then S.previewZoom = tonumber(v) or DEFAULT_PREVIEW_ZOOM end 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 == "sourceZoom" then S.sourceZoom = tonumber(v) or DEFAULT_SOURCE_ZOOM end
if k == "currentAnim" then S.currentAnim = tonumber(v) or 1 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 if k == "animNames" and v ~= "" then
S.animNames = {} S.animNames = {}
for name in string.gmatch(v, "([^|]+)") do for name in string.gmatch(v, "([^|]+)") do
@ -261,6 +277,17 @@ local function flipImageH(src)
return flipped return flipped
end 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 function drawCheckerboard(gc, w, h, zoom, offsetX, offsetY)
local ox = offsetX or 0 local ox = offsetX or 0
local oy = offsetY 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 if not f or not S.sourceImage then return nil end
local img = extractCell(S.sourceImage, f.x, f.y, S.tileW, S.tileH) local img = extractCell(S.sourceImage, f.x, f.y, S.tileW, S.tileH)
if f.flipped then img = flipImageH(img) end if f.flipped then img = flipImageH(img) end
if f.flippedV then img = flipImageV(img) end
return img return img
end end
@ -882,7 +910,13 @@ local function run()
local function updateAnimLabel() local function updateAnimLabel()
local name = currentAnimName() or "(none)" 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 end
local function startAnimTimer() local function startAnimTimer()
@ -916,8 +950,8 @@ local function run()
-- Switch visible tab -- Switch visible tab
local function switchTab(tab) local function switchTab(tab)
S.currentTab = tab S.currentTab = tab
local isSetup = (tab == "Setup") local isSetup = (tab == "Animations")
local isRender = (tab == "Render") 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
dlg:modify{ id = id, visible = isSetup } dlg:modify{ id = id, visible = isSetup }
@ -1012,14 +1046,9 @@ local function run()
---------------------------------------------------------------- ----------------------------------------------------------------
dlg:separator{ id = "sepAnims", text = "Animations" } dlg:separator{ id = "sepAnims", text = "Animations" }
dlg:label{
id = "lblCurrentAnim",
text = "-> " .. (currentAnimName() or "(none)"),
}
dlg:button{ dlg:button{
id = "btnNewAnim", id = "btnNewAnim",
text = "+", text = "Add Animation",
onclick = function() onclick = function()
local d = Dialog{ title = "New Animation" } local d = Dialog{ title = "New Animation" }
d:entry{ id = "name", label = "Name:", text = "" } d:entry{ id = "name", label = "Name:", text = "" }
@ -1042,13 +1071,20 @@ local function run()
dlg:button{ dlg:button{
id = "btnDelAnim", id = "btnDelAnim",
text = "-", text = "Remove Animation",
onclick = function() onclick = function()
local name = currentAnimName() local name = currentAnimName()
if not name then return end 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 S.anims[name] = nil
table.remove(S.animNames, S.currentAnim) table.remove(S.animNames, S.currentAnim)
S.currentAnim = math.min(S.currentAnim, math.max(1, #S.animNames)) S.currentAnim = math.min(S.currentAnim, math.max(1, #S.animNames))
S.selectedFrame = 0
savePrefs() savePrefs()
dlg:close() dlg:close()
run() run()
@ -1057,13 +1093,22 @@ local function run()
dlg:newrow() 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 for i = 1, #S.animNames do
local label = (i == S.currentAnim) and ("[" .. S.animNames[i] .. "]") or S.animNames[i]
dlg:button{ dlg:button{
id = "btnAnim" .. i, id = "btnAnim" .. i,
text = S.animNames[i], text = label,
onclick = function() onclick = function()
S.currentAnim = i S.currentAnim = i
S.selectedFrame = 0
updateAnimLabel() updateAnimLabel()
dlg:repaint() dlg:repaint()
end end
@ -1073,7 +1118,7 @@ local function run()
---------------------------------------------------------------- ----------------------------------------------------------------
-- Source canvas (L-click=add, R-click=add flipped, M-click=scroll) -- 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{ dlg:canvas{
id = "canvasSource", id = "canvasSource",
@ -1125,7 +1170,6 @@ local function run()
local rw = S.tileW * S.sourceZoom local rw = S.tileW * S.sourceZoom
local rh = S.tileH * S.sourceZoom local rh = S.tileH * S.sourceZoom
if rx + rw > 0 and rx < vw and ry + rh > 0 and ry < vh then 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 gc.color = f.flipped
and Color(255, 160, 0, 160) and Color(255, 160, 0, 160)
or Color(100, 200, 255, 160) or Color(100, 200, 255, 160)
@ -1140,37 +1184,55 @@ local function run()
end, end,
onmousedown = function(ev) onmousedown = function(ev)
if ev.button == MouseButton.LEFT then if ev.button == MouseButton.LEFT then
local pixelX = math.floor((ev.x + S.scrollX) / S.sourceZoom) S.srcClickX = ev.x
local pixelY = math.floor((ev.y + S.scrollY) / S.sourceZoom) S.srcClickY = ev.y
captureCell(pixelX, pixelY, false) S.srcIsDrag = false
S.dragging = true
S.dragLastX = ev.x
S.dragLastY = ev.y
elseif ev.button == MouseButton.RIGHT then elseif ev.button == MouseButton.RIGHT then
local pixelX = math.floor((ev.x + S.scrollX) / S.sourceZoom) local pixelX = math.floor((ev.x + S.scrollX) / S.sourceZoom)
local pixelY = math.floor((ev.y + S.scrollY) / S.sourceZoom) local pixelY = math.floor((ev.y + S.scrollY) / S.sourceZoom)
captureCell(pixelX, pixelY, true) captureCell(pixelX, pixelY, true)
else
S.dragging = true
S.dragLastX = ev.x
S.dragLastY = ev.y
end end
end, end,
onmousemove = function(ev) onmousemove = function(ev)
if S.dragging then 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.scrollX = S.scrollX + (S.dragLastX - ev.x)
S.scrollY = S.scrollY + (S.dragLastY - ev.y) S.scrollY = S.scrollY + (S.dragLastY - ev.y)
clampScroll() clampScroll()
end
S.dragLastX = ev.x S.dragLastX = ev.x
S.dragLastY = ev.y S.dragLastY = ev.y
end end
end, end,
onmouseup = function(ev) 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.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 end
} }
---------------------------------------------------------------- ----------------------------------------------------------------
-- Frame strip for current animation -- 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_TOTAL_W = SOURCE_VIEWPORT_W
local STRIP_CELL = THUMB_SIZE + 1 local STRIP_CELL = THUMB_SIZE + 1
@ -1228,11 +1290,37 @@ local function run()
gc:fillRect(Rectangle(tx + THUMB_SIZE - 4, ty, 4, 4)) gc:fillRect(Rectangle(tx + THUMB_SIZE - 4, ty, 4, 4))
end 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 if cnt > 0 and ((S.animFrame % cnt)) == (i - 1) then
gc.color = Color(100, 200, 255, 200) gc.color = Color(100, 200, 255, 200)
gc:fillRect(Rectangle(tx, ty - 1, THUMB_SIZE, 2)) gc:fillRect(Rectangle(tx, ty - 1, THUMB_SIZE, 2))
end 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
end, end,
onmousedown = function(ev) onmousedown = function(ev)
@ -1245,36 +1333,174 @@ local function run()
local row = math.floor((ev.y - 2) / STRIP_CELL) local row = math.floor((ev.y - 2) / STRIP_CELL)
local idx = row * thumbsPerRow + col + 1 local idx = row * thumbsPerRow + col + 1
if idx >= 1 and idx <= #frames then if idx >= 1 and idx <= #frames then
if ev.button == MouseButton.RIGHT then if ev.button == MouseButton.LEFT then
table.remove(frames, idx) S.selectedFrame = idx
S.frameDragging = true
S.frameDragFrom = idx
S.frameDragTo = idx
dlg:repaint() dlg:repaint()
end end
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 end
} }
---------------------------------------------------------------- ----------------------------------------------------------------
-- Clear buttons -- Frame action buttons
---------------------------------------------------------------- ----------------------------------------------------------------
dlg:separator{ id = "sepActions" } 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{ dlg:button{
id = "btnClearAnim", id = "btnClearAnim",
text = "Clear Anim", text = "Clear Anim",
onclick = function() onclick = function()
local name = currentAnimName() 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() dlg:repaint()
end end
} }
dlg:button{ ----------------------------------------------------------------
id = "btnClearAll", -- Preview canvas (animation playback in Animations tab)
text = "Clear All", ----------------------------------------------------------------
onclick = function() dlg:separator{ id = "sepPreviewAnim", text = "Preview" }
for _, name in ipairs(S.animNames) do
S.anims[name] = {} 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 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() dlg:repaint()
end end
} }
@ -1282,7 +1508,7 @@ local function run()
---------------------------------------------------------------- ----------------------------------------------------------------
-- RENDER TAB (single canvas with all animations in 2 columns) -- 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 local RENDER_MARGIN = 2
@ -1767,18 +1993,16 @@ local function run()
end end
end end
local imgCopy = S.gbOptImage:clone()
app.transaction("Save Optimized Layer", function() app.transaction("Save Optimized Layer", function()
if existingLayer then if existingLayer then
if S.gbAlwaysOverwrite then if S.gbAlwaysOverwrite then
-- Overwrite: delete old cels, add new cel with image existingLayer.isEditable = true
for _, cel in ipairs(existingLayer.cels) do for _, cel in ipairs(existingLayer.cels) do
sp:deleteCel(cel) sp:deleteCel(cel)
end end
existingLayer.isEditable = true sp:newCel(existingLayer, app.frame, imgCopy, Point(0, 0))
sp:newCel(existingLayer, app.frame, S.gbOptImage, Point(0, 0))
existingLayer.isEditable = false
else else
-- Create new layer with incremented name
local suffix = 2 local suffix = 2
local newName = layerName .. "-" .. suffix local newName = layerName .. "-" .. suffix
local nameExists = true local nameExists = true
@ -1795,21 +2019,14 @@ local function run()
end end
local newLayer = sp:newLayer() local newLayer = sp:newLayer()
newLayer.name = newName newLayer.name = newName
-- Move just above existing layer sp:newCel(newLayer, app.frame, imgCopy, Point(0, 0))
if existingIdx then
sp:newCel(newLayer, app.frame, S.gbOptImage, Point(0, 0))
newLayer.isEditable = false
end
end end
else else
-- Create at top of layer stack
local newLayer = sp:newLayer() local newLayer = sp:newLayer()
newLayer.name = layerName newLayer.name = layerName
sp:newCel(newLayer, app.frame, S.gbOptImage, Point(0, 0)) sp:newCel(newLayer, app.frame, imgCopy, Point(0, 0))
newLayer.isEditable = false
end end
end) end)
app.alert("Layer saved: " .. layerName)
end end
} }
@ -1823,15 +2040,15 @@ local function run()
app.alert("Run Analyze first.") app.alert("Run Analyze first.")
return return
end end
local origSprite = app.sprite
local optW = S.gbOptImage.width local optW = S.gbOptImage.width
local optH = S.gbOptImage.height local optH = S.gbOptImage.height
local tmpSprite = Sprite(optW, optH, ColorMode.RGB) local tmpSprite = Sprite(optW, optH, ColorMode.RGB)
local cel = tmpSprite.cels[1] local cel = tmpSprite.cels[1]
cel.image:drawImage(S.gbOptImage) cel.image = S.gbOptImage:clone()
app.command.SelectAll() app.command.MaskAll()
app.command.CopyMerged() app.command.CopyMerged()
tmpSprite:close() tmpSprite:close()
app.alert("Copied!")
end end
} }