diff --git a/aniphallow.lua b/aniphallow.lua index 29a44fe..995562b 100644 --- a/aniphallow.lua +++ b/aniphallow.lua @@ -34,6 +34,7 @@ local GB_COLS = 16 -- tiles per row in optimized image (128px = GB standard) local PRESETS_DIR = app.fs.joinPath(app.fs.userConfigPath, "aniphallow_presets") local MASTER_FILE = app.fs.joinPath(app.fs.userConfigPath, "aniphallow_master.ini") local OLD_PREFS_FILE = app.fs.joinPath(app.fs.userConfigPath, "aniphallow_prefs.ini") +local LOCK_FILE = app.fs.joinPath(app.fs.userConfigPath, "aniphallow.lock") ---------------------------------------------------------------------- -- State @@ -226,6 +227,23 @@ local function resetToDefaults() S.selectedFrame = 0 end +---------------------------------------------------------------------- +-- Preset name helpers +---------------------------------------------------------------------- +local function addPresetName(name) + for _, n in ipairs(knownPresetNames) do + if n == name then return end + end + table.insert(knownPresetNames, name) + table.sort(knownPresetNames, function(a, b) return a:lower() < b:lower() end) +end + +local function removePresetName(name) + for i, n in ipairs(knownPresetNames) do + if n == name then table.remove(knownPresetNames, i); return end + end +end + ---------------------------------------------------------------------- -- Preset system: save/load preset files and master file ---------------------------------------------------------------------- @@ -1225,6 +1243,13 @@ local function buildOptimizedImage(img, tiles, compress, silhouette, silhouetteC end end +---------------------------------------------------------------------- +-- Lock file helpers +---------------------------------------------------------------------- +local function removeLockFile() + os.remove(LOCK_FILE) +end + ---------------------------------------------------------------------- -- Preview Window (primary window) ---------------------------------------------------------------------- @@ -1275,7 +1300,7 @@ openPreviewWindow = function() pvHeight = math.max(pvHeight, 40) previewDlg = Dialog{ - title = "AniPhallow Preview", + title = "AniPhallow (" .. S.currentPreset .. ")", onclose = function() if previewTimer then pcall(function() previewTimer:stop() end) end if pvRefreshTimer then pcall(function() pvRefreshTimer:stop() end) end @@ -1284,6 +1309,8 @@ openPreviewWindow = function() previewDlg = nil S.previewWindowOpen = false saveAll() + -- If main dialog is also closed, remove lock + if not mainDlg then removeLockFile() end end } @@ -1474,6 +1501,9 @@ openPreviewWindow = function() pcall(function() mainDlg:close() end) openMainDialog() end + -- Reopen preview to update title + pcall(function() previewDlg:close() end) + openPreviewWindow() end end end @@ -1521,6 +1551,7 @@ openMainDialog = function() 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, offX = 0, offY = 0 }) + saveAll() dlg:repaint() end @@ -1560,7 +1591,7 @@ openMainDialog = function() local viewH = math.min(contentH, SOURCE_VIEWPORT_H) dlg = Dialog{ - title = "AniPhallow - Animation Builder", + title = "AniPhallow (" .. S.currentPreset .. ")", onclose = function() if mainAnimTimer then pcall(function() mainAnimTimer:stop() end) end if mainRefreshTimer then pcall(function() mainRefreshTimer:stop() end) end @@ -1568,6 +1599,8 @@ openMainDialog = function() mainRefreshTimer = nil mainDlg = nil saveAll() + -- If preview is also closed, remove lock + if not previewDlg then removeLockFile() end end } mainDlg = dlg @@ -1599,151 +1632,168 @@ openMainDialog = function() -- Presets section -------------------------------------------------------- d:separator{ text = "Presets" } - local presetNames = getAllPresetNames() - d:combobox{ id = "presetList", label = "Preset:", option = S.currentPreset, options = presetNames } - d:button{ id = "btnPresetSave", text = "Save", onclick = function() - local saveDlg = Dialog{ title = "Save Preset" } - saveDlg:entry{ id = "name", label = "Name:", text = S.currentPreset } - saveDlg:button{ id = "ok", text = "Save" } - saveDlg:button{ text = "Cancel" } - saveDlg:show() - if saveDlg.data.ok then - local pname = saveDlg.data.name - if pname and pname ~= "" then - -- Check if name exists and differs from current - if pname ~= S.currentPreset then - -- Check if preset already exists - local exists = false - for _, n in ipairs(knownPresetNames) do - if n == pname then exists = true; break end - end - if exists then - local confirm = app.alert{ - title = "Overwrite Preset", - text = "Preset '" .. pname .. "' already exists. Overwrite?", - buttons = { "Overwrite", "Cancel" } - } - if confirm ~= 1 then return end - end - end - S.currentPreset = pname - -- Add to known names if new - local found = false - for _, n in ipairs(knownPresetNames) do - if n == pname then found = true; break end - end - if not found then - table.insert(knownPresetNames, pname) - end - -- Associate with current file - if app.sprite and app.sprite.filename and app.sprite.filename ~= "" then - filePresetMap[app.sprite.filename] = pname - end - saveAll() - -- Update combobox - pcall(function() - d:modify{ id = "presetList", options = getAllPresetNames(), option = S.currentPreset } - end) + d:combobox{ id = "presetSelect", option = S.currentPreset, options = getAllPresetNames() } + d:button{ id = "presetNew", text = "New", onclick = function() + local nd = Dialog{ title = "New Preset" } + nd:entry{ id = "name", label = "Name:", text = "" } + nd:button{ id = "ok", text = "OK" } + nd:button{ text = "Cancel" } + nd:show() + if nd.data.ok and nd.data.name and nd.data.name ~= "" then + local newName = nd.data.name + -- Check if exists + local exists = false + for _, n in ipairs(getAllPresetNames()) do + if n == newName then exists = true; break end + end + if exists then + local r = app.alert{ title = "Overwrite?", text = "Preset '" .. newName .. "' already exists. Overwrite?", buttons = {"Overwrite", "Cancel"} } + if r ~= 1 then return end end - end - end } - d:button{ id = "btnPresetLoad", text = "Load", onclick = function() - local selectedPreset = d.data.presetList - if selectedPreset and selectedPreset ~= "" then -- Save current preset first savePreset(S.currentPreset) - -- Load the new preset - loadPreset(selectedPreset) - S.currentPreset = selectedPreset - -- Associate with current file - if app.sprite and app.sprite.filename and app.sprite.filename ~= "" then - filePresetMap[app.sprite.filename] = selectedPreset - end + -- Reset to defaults and save as new preset + resetToDefaults() + S.currentPreset = newName + addPresetName(newName) + savePreset(newName) + local currentFile = app.sprite and app.sprite.filename or "" + if currentFile ~= "" then filePresetMap[currentFile] = newName end saveMaster() - -- Close config dialog and reopen main dialog + -- Close config and reopen main d:close() - if mainDlg then - pcall(function() mainDlg:close() end) - end + if mainDlg then pcall(function() mainDlg:close() end) end openMainDialog() - end - end } - d:button{ id = "btnPresetClone", text = "Clone", onclick = function() - local cloneDlg = Dialog{ title = "Clone Preset" } - cloneDlg:entry{ id = "name", label = "New name:", text = S.currentPreset .. "_copy" } - cloneDlg:button{ id = "ok", text = "Clone" } - cloneDlg:button{ text = "Cancel" } - cloneDlg:show() - if cloneDlg.data.ok then - local newName = cloneDlg.data.name - if newName and newName ~= "" then - -- Check if name already exists - local exists = false - for _, n in ipairs(knownPresetNames) do - if n == newName then exists = true; break end - end - if exists then - app.alert("Preset '" .. newName .. "' already exists.") - return - end - -- Save current state as the new preset - savePreset(newName) - table.insert(knownPresetNames, newName) - S.currentPreset = newName - -- Associate with current file - if app.sprite and app.sprite.filename and app.sprite.filename ~= "" then - filePresetMap[app.sprite.filename] = newName - end - saveMaster() - -- Update combobox - pcall(function() - d:modify{ id = "presetList", options = getAllPresetNames(), option = S.currentPreset } - end) + -- Reopen preview to update title + if previewDlg then + pcall(function() previewDlg:close() end) + openPreviewWindow() end end end } - d:button{ id = "btnPresetDelete", text = "Delete", onclick = function() - local selectedPreset = d.data.presetList - if not selectedPreset or selectedPreset == "" then return end - if selectedPreset == "Default" then + d:button{ id = "presetClone", text = "Clone", onclick = function() + local cd = Dialog{ title = "Clone Preset" } + cd:entry{ id = "name", label = "New name:", text = S.currentPreset .. "_copy" } + cd:button{ id = "ok", text = "Clone" } + cd:button{ text = "Cancel" } + cd:show() + if cd.data.ok and cd.data.name and cd.data.name ~= "" then + local newName = cd.data.name + -- Check if name already exists + local exists = false + for _, n in ipairs(getAllPresetNames()) do + if n == newName then exists = true; break end + end + if exists then + app.alert("Preset '" .. newName .. "' already exists.") + return + end + -- Save current state as the new preset (clone) + savePreset(newName) + addPresetName(newName) + S.currentPreset = newName + local currentFile = app.sprite and app.sprite.filename or "" + if currentFile ~= "" then filePresetMap[currentFile] = newName end + saveMaster() + -- Close config and reopen main + d:close() + if mainDlg then pcall(function() mainDlg:close() end) end + openMainDialog() + -- Reopen preview to update title + if previewDlg then + pcall(function() previewDlg:close() end) + openPreviewWindow() + end + end + end } + d:button{ id = "presetRename", text = "Rename", onclick = function() + if S.currentPreset == "Default" then + app.alert("Cannot rename the Default preset.") + return + end + local rd = Dialog{ title = "Rename Preset" } + rd:entry{ id = "name", label = "New name:", text = S.currentPreset } + rd:button{ id = "ok", text = "OK" } + rd:button{ text = "Cancel" } + rd:show() + if rd.data.ok and rd.data.name and rd.data.name ~= "" then + local newName = rd.data.name + if newName == S.currentPreset then return end + -- Check if name already exists + local exists = false + for _, n in ipairs(getAllPresetNames()) do + if n == newName then exists = true; break end + end + if exists then + app.alert("Preset '" .. newName .. "' already exists.") + return + end + local oldName = S.currentPreset + -- Rename the preset file + local oldPath = app.fs.joinPath(PRESETS_DIR, oldName .. ".ini") + local newPath = app.fs.joinPath(PRESETS_DIR, newName .. ".ini") + os.rename(oldPath, newPath) + -- Update known names + removePresetName(oldName) + addPresetName(newName) + -- Update file associations + for filepath, preset in pairs(filePresetMap) do + if preset == oldName then + filePresetMap[filepath] = newName + end + end + S.currentPreset = newName + saveMaster() + -- Close config and reopen main + d:close() + if mainDlg then pcall(function() mainDlg:close() end) end + openMainDialog() + -- Reopen preview to update title + if previewDlg then + pcall(function() previewDlg:close() end) + openPreviewWindow() + end + end + end } + d:button{ id = "presetDelete", text = "Delete", onclick = function() + if S.currentPreset == "Default" then app.alert("Cannot delete the Default preset.") return end local confirm = app.alert{ title = "Delete Preset", - text = "Delete preset '" .. selectedPreset .. "'?", + text = "Delete preset '" .. S.currentPreset .. "'?", buttons = { "Delete", "Cancel" } } if confirm ~= 1 then return end + local deleteName = S.currentPreset -- Delete the preset file - local path = app.fs.joinPath(PRESETS_DIR, selectedPreset .. ".ini") + local path = app.fs.joinPath(PRESETS_DIR, deleteName .. ".ini") os.remove(path) -- Remove from known names - for i, n in ipairs(knownPresetNames) do - if n == selectedPreset then - table.remove(knownPresetNames, i) - break - end - end + removePresetName(deleteName) -- Remove file associations pointing to this preset for filepath, preset in pairs(filePresetMap) do - if preset == selectedPreset then + if preset == deleteName then filePresetMap[filepath] = nil end end - -- If current preset was deleted, switch to Default - if S.currentPreset == selectedPreset then - loadPreset("Default") - S.currentPreset = "Default" - if app.sprite and app.sprite.filename and app.sprite.filename ~= "" then - filePresetMap[app.sprite.filename] = nil - end + -- Switch to Default + loadPreset("Default") + S.currentPreset = "Default" + if app.sprite and app.sprite.filename and app.sprite.filename ~= "" then + filePresetMap[app.sprite.filename] = nil end saveMaster() - -- Update combobox - pcall(function() - d:modify{ id = "presetList", options = getAllPresetNames(), option = S.currentPreset } - end) + -- Close config and reopen main + d:close() + if mainDlg then pcall(function() mainDlg:close() end) end + openMainDialog() + -- Reopen preview to update title + if previewDlg then + pcall(function() previewDlg:close() end) + openPreviewWindow() + end end } -------------------------------------------------------- @@ -1806,6 +1856,28 @@ openMainDialog = function() local plv = d.data.previewLayoutValue if plv < 1 then plv = 1 elseif plv > 20 then plv = 20 end S.previewLayoutValue = plv + -- Check if preset changed via combobox + local selectedPreset = d.data.presetSelect + local presetChanged = false + if selectedPreset and selectedPreset ~= S.currentPreset then + savePreset(S.currentPreset) -- Save current first + loadPreset(selectedPreset) -- Load new + S.currentPreset = selectedPreset + local currentFile = app.sprite and app.sprite.filename or "" + if currentFile ~= "" then filePresetMap[currentFile] = selectedPreset end + presetChanged = true + end + saveAll() + if presetChanged then + dlg:close() + openMainDialog() + -- Reopen preview to update title + if previewDlg then + pcall(function() previewDlg:close() end) + openPreviewWindow() + end + return + end dlg:repaint() end end @@ -2198,6 +2270,7 @@ openMainDialog = function() S.selectedFrame = #frames end if #frames == 0 then S.selectedFrame = 0 end + saveAll() dlg:repaint() end end @@ -2225,6 +2298,7 @@ openMainDialog = function() local frame = table.remove(frames, from) table.insert(frames, to, frame) S.selectedFrame = to + saveAll() end end S.frameDragging = false @@ -2251,6 +2325,7 @@ openMainDialog = function() local frames = S.anims[name] if not frames or S.selectedFrame > #frames then return end frames[S.selectedFrame].flipped = not frames[S.selectedFrame].flipped + saveAll() dlg:repaint() end } @@ -2264,6 +2339,7 @@ openMainDialog = function() local frames = S.anims[name] if not frames or S.selectedFrame > #frames then return end frames[S.selectedFrame].flippedV = not frames[S.selectedFrame].flippedV + saveAll() dlg:repaint() end } @@ -2279,6 +2355,7 @@ openMainDialog = function() local idx = S.selectedFrame frames[idx], frames[idx - 1] = frames[idx - 1], frames[idx] S.selectedFrame = idx - 1 + saveAll() dlg:repaint() end } @@ -2294,6 +2371,7 @@ openMainDialog = function() local idx = S.selectedFrame frames[idx], frames[idx + 1] = frames[idx + 1], frames[idx] S.selectedFrame = idx + 1 + saveAll() dlg:repaint() end } @@ -2336,6 +2414,7 @@ openMainDialog = function() S.selectedFrame = #frames end if #frames == 0 then S.selectedFrame = 0 end + saveAll() dlg:repaint() end } @@ -2353,6 +2432,7 @@ openMainDialog = function() 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 + saveAll() dlg:repaint() end } @@ -2365,6 +2445,7 @@ openMainDialog = function() 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 + saveAll() dlg:repaint() end } @@ -2377,6 +2458,7 @@ openMainDialog = function() 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 + saveAll() dlg:repaint() end } @@ -2389,6 +2471,7 @@ openMainDialog = function() 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 + saveAll() dlg:repaint() end } @@ -2402,6 +2485,7 @@ openMainDialog = function() if not frames or S.selectedFrame > #frames then return end frames[S.selectedFrame].offX = 0 frames[S.selectedFrame].offY = 0 + saveAll() dlg:repaint() end } @@ -2482,6 +2566,7 @@ openMainDialog = function() width = math.min(S.gbOptImage.width * z, SOURCE_VIEWPORT_W), height = math.min(S.gbOptImage.height * z, GB_OPT_H) } end + saveAll() dlg:repaint() end } @@ -3117,6 +3202,7 @@ openMainDialog = function() if not path or path == "" then return end S.gbLastSavePath = path + saveAll() local optW = S.gbOptImage.width local optH = S.gbOptImage.height @@ -3171,6 +3257,11 @@ openMainDialog = function() S.currentPreset = presetForFile pcall(function() dlg:close() end) openMainDialog() + -- Reopen preview to update title + if previewDlg then + pcall(function() previewDlg:close() end) + openPreviewWindow() + end end end end @@ -3190,12 +3281,17 @@ local function run() app.alert("No sprite is open.") return end - -- Toggle: if already running, close everything - if previewDlg then - pcall(function() previewDlg:close() end) - if mainDlg then pcall(function() mainDlg:close() end) end + -- Check if already running via lock file + local lockF = io.open(LOCK_FILE, "r") + if lockF then + lockF:close() + app.alert("AniPhallow is already running.") return end + -- Create lock file + local lf = io.open(LOCK_FILE, "w") + if lf then lf:write("running"); lf:close() end + S.currentTab = "Animations" refreshSource() -- Always open preview window on launch