mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-19 12:15:31 +02:00
updated skin studio
This commit is contained in:
@@ -0,0 +1,109 @@
|
||||
local Platform = require("src.core.Platform")
|
||||
local HostShell = require("src.core.HostShell")
|
||||
|
||||
local FilePicker = {}
|
||||
|
||||
FilePicker.IMAGE = {
|
||||
label = "Image",
|
||||
exts = { "png", "jpg", "jpeg" },
|
||||
tempName = "pokeport_image_pick",
|
||||
}
|
||||
|
||||
local function trim(value)
|
||||
return value and value:gsub("^%s+", ""):gsub("%s+$", "") or ""
|
||||
end
|
||||
|
||||
local function shellSafe(s)
|
||||
s = tostring(s):gsub("%%", "%%%%")
|
||||
return s:gsub('"', '\\"'):gsub("'", "''")
|
||||
end
|
||||
|
||||
local function commandOutput(command)
|
||||
if not Platform.canSpawnProcess() then return nil end
|
||||
local pipe = HostShell.popen(command)
|
||||
if not pipe then return nil end
|
||||
local result = pipe:read("*a")
|
||||
HostShell.pclose(pipe)
|
||||
result = trim(result)
|
||||
return result ~= "" and result or nil
|
||||
end
|
||||
|
||||
local function appleTypes(exts)
|
||||
local out = {}
|
||||
for _, ext in ipairs(exts) do out[#out + 1] = '"' .. ext .. '"' end
|
||||
return table.concat(out, ", ")
|
||||
end
|
||||
|
||||
local function windowsPatterns(exts)
|
||||
local out = {}
|
||||
for _, ext in ipairs(exts) do out[#out + 1] = "*." .. ext end
|
||||
return table.concat(out, ";")
|
||||
end
|
||||
|
||||
local function globPatterns(exts)
|
||||
local out = {}
|
||||
for _, ext in ipairs(exts) do out[#out + 1] = "*." .. ext end
|
||||
return table.concat(out, " ")
|
||||
end
|
||||
|
||||
function FilePicker.available()
|
||||
return Platform.canSpawnProcess()
|
||||
end
|
||||
|
||||
function FilePicker.matches(name, kind)
|
||||
local lower = tostring(name or ""):lower()
|
||||
for _, ext in ipairs(kind.exts) do
|
||||
if lower:match("%." .. ext .. "$") then return true end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
function FilePicker.open(prompt, kind)
|
||||
if not Platform.canSpawnProcess() then return nil end
|
||||
local title = shellSafe(prompt)
|
||||
local platform = love.system.getOS()
|
||||
if platform == "OS X" then
|
||||
return commandOutput(
|
||||
([[osascript -e 'POSIX path of (choose file with prompt "%s" of type {%s})' 2>/dev/null]])
|
||||
:format(title, appleTypes(kind.exts)))
|
||||
elseif platform == "Windows" then
|
||||
local script = table.concat({
|
||||
"Add-Type -AssemblyName System.Windows.Forms;",
|
||||
"$d=New-Object System.Windows.Forms.OpenFileDialog;",
|
||||
"$d.Title='" .. title .. "';",
|
||||
"$d.Filter='" .. kind.label .. " (" .. windowsPatterns(kind.exts) .. ")|"
|
||||
.. windowsPatterns(kind.exts) .. "|All files (*.*)|*.*';",
|
||||
"if($d.ShowDialog() -eq 'OK'){",
|
||||
"$n=[IO.Path]::GetFileName($d.FileName) -replace '[^\\x20-\\x7E]','_';",
|
||||
"$t=Join-Path $env:TEMP $n;",
|
||||
"Copy-Item -LiteralPath $d.FileName -Destination $t -Force;",
|
||||
"[Console]::OutputEncoding=[Text.Encoding]::UTF8;",
|
||||
"[Console]::Write($t)}",
|
||||
})
|
||||
return commandOutput(
|
||||
'powershell -NoProfile -STA -Command "' .. script .. '"')
|
||||
elseif platform == "Linux" then
|
||||
local path = commandOutput(
|
||||
([[zenity --file-selection --title="%s" --file-filter="%s | %s" 2>/dev/null]])
|
||||
:format(title, kind.label, globPatterns(kind.exts)))
|
||||
if path then return path end
|
||||
return commandOutput(([[kdialog --getopenfilename "$HOME" "%s|%s" 2>/dev/null]])
|
||||
:format(globPatterns(kind.exts), kind.label))
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
function FilePicker.read(path)
|
||||
local file, openError = io.open(path, "rb")
|
||||
if not file then return nil, openError end
|
||||
local data = file:read("*a")
|
||||
file:close()
|
||||
if not data or data == "" then return nil, "empty file" end
|
||||
return data
|
||||
end
|
||||
|
||||
function FilePicker.basename(path)
|
||||
return tostring(path or ""):match("([^/\\]+)$") or tostring(path or "")
|
||||
end
|
||||
|
||||
return FilePicker
|
||||
+98
-18
@@ -4,6 +4,7 @@ local PAL = Theme.PAL
|
||||
local TouchSkin = require("src.core.TouchSkin")
|
||||
local TouchControls = require("src.core.TouchControls")
|
||||
local SaveData = require("src.core.SaveData")
|
||||
local FilePicker = require("src.core.FilePicker")
|
||||
|
||||
local Studio = {}
|
||||
|
||||
@@ -214,12 +215,19 @@ function Studio.cycleImage(dir)
|
||||
markDirty()
|
||||
end
|
||||
|
||||
function Studio.imageTargetLabel()
|
||||
local target = Studio.imageTarget
|
||||
if target == "bezel" or not Studio.selectedControl() then return "bezel" end
|
||||
return target == "pressed" and "pressed art" or "idle art"
|
||||
end
|
||||
|
||||
function Studio.assignImage(rel)
|
||||
local page, ctl = Studio.page(), Studio.selectedControl()
|
||||
local img = TouchSkin.resolveImage(Studio.skin.root, rel)
|
||||
if ctl and Studio.imageTarget == "pressed" then
|
||||
local target = Studio.imageTarget
|
||||
if ctl and target == "pressed" then
|
||||
ctl.pressedImagePath, ctl.pressedImage = rel, img
|
||||
elseif ctl and Studio.imageTarget == "idle" then
|
||||
elseif ctl and target == "idle" then
|
||||
ctl.imagePath, ctl.image = rel, img
|
||||
elseif page then
|
||||
page.imagePath, page.image = rel, img
|
||||
@@ -228,11 +236,69 @@ function Studio.assignImage(rel)
|
||||
Studio.dirty = true
|
||||
end
|
||||
|
||||
local function commitSkinId()
|
||||
local skin = Studio.skin
|
||||
if not skin then return end
|
||||
local id = (Studio.skinIdField or ""):gsub("[^%w_%-]", "")
|
||||
if id == "" or id == skin.id then return end
|
||||
TouchSkin.saveTo(skin, id)
|
||||
Studio.available = TouchSkin.list()
|
||||
end
|
||||
|
||||
function Studio.adoptImage(name, data, target)
|
||||
if not Studio.skin then return false end
|
||||
if target then Studio.imageTarget = target end
|
||||
if not FilePicker.matches(name, FilePicker.IMAGE) then
|
||||
Studio.status = "Pick a PNG or JPG."
|
||||
return false
|
||||
end
|
||||
commitSkinId()
|
||||
local rel, err = TouchSkin.importImage(Studio.skin, name, data)
|
||||
if not rel then
|
||||
Studio.status = "Import failed: " .. tostring(err)
|
||||
return false
|
||||
end
|
||||
local where = Studio.imageTargetLabel()
|
||||
Studio.assignImage(rel)
|
||||
Studio.skinIdField = Studio.skin.id
|
||||
Studio.status = "Imported " .. rel .. " as " .. where
|
||||
if where == "bezel" and not Studio.canvas().lockViewport then
|
||||
Studio.status = Studio.status
|
||||
.. " -- use Detect screen from bezel to place the screen"
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
function Studio.importImageFile(target)
|
||||
if not Studio.skin then return end
|
||||
target = target or Studio.imageTarget
|
||||
Studio.imageTarget = target
|
||||
if target ~= "bezel" and not Studio.selectedControl() then
|
||||
Studio.status = "Select a control first, or import a bezel image."
|
||||
return
|
||||
end
|
||||
if not FilePicker.available() then
|
||||
Studio.status = "No file picker here -- drag a PNG onto the window instead."
|
||||
return
|
||||
end
|
||||
local prompt = (target == "bezel") and "Choose a bezel image"
|
||||
or "Choose a button image"
|
||||
local path = FilePicker.open(prompt, FilePicker.IMAGE)
|
||||
if not path then return end
|
||||
local base = FilePicker.basename(path)
|
||||
local data, err = FilePicker.read(path)
|
||||
if not data then
|
||||
Studio.status = "Could not read " .. base .. ": " .. tostring(err)
|
||||
return
|
||||
end
|
||||
Studio.adoptImage(base, data, target)
|
||||
end
|
||||
|
||||
function Studio.filedropped(file)
|
||||
if not Studio.skin then return end
|
||||
local path = (file.getFilename and file:getFilename()) or ""
|
||||
local base = path:match("([^/\\]+)$") or path
|
||||
if not base:lower():match("%.png$") and not base:lower():match("%.jpe?g$") then
|
||||
local base = FilePicker.basename(path)
|
||||
if not FilePicker.matches(base, FilePicker.IMAGE) then
|
||||
Studio.status = "Drop a PNG or JPG to use it as art."
|
||||
return
|
||||
end
|
||||
@@ -246,17 +312,7 @@ function Studio.filedropped(file)
|
||||
Studio.status = "Could not read " .. base
|
||||
return
|
||||
end
|
||||
local rel, err = TouchSkin.importImage(Studio.skin, base, data)
|
||||
if not rel then
|
||||
Studio.status = "Import failed: " .. tostring(err)
|
||||
return
|
||||
end
|
||||
Studio.assignImage(rel)
|
||||
local where = Studio.selectedControl()
|
||||
and (Studio.imageTarget == "pressed" and "pressed art" or "idle art")
|
||||
or "bezel"
|
||||
Studio.status = "Imported " .. rel .. " as " .. where
|
||||
Studio.skinIdField = Studio.skin.id
|
||||
Studio.adoptImage(base, data)
|
||||
end
|
||||
|
||||
function Studio.detectViewport()
|
||||
@@ -617,10 +673,16 @@ local function inspectorBody(x, y, w)
|
||||
|
||||
if page then
|
||||
local bezel = page.imagePath or "(none)"
|
||||
if Kit.button(x, cy, w, rowH, "Bezel: " .. bezel, { id = "bezel" }) then
|
||||
local pickW = 82 * Kit.scale
|
||||
local cycleW = w - pickW - gap
|
||||
if Kit.button(x, cy, cycleW, rowH, "Bezel: " .. bezel, { id = "bezel" }) then
|
||||
Studio.imageTarget = "bezel"
|
||||
Studio.cycleImage(1)
|
||||
end
|
||||
if Kit.button(x + cycleW + gap, cy, pickW, rowH, "Import",
|
||||
{ id = "bezelpick" }) then
|
||||
Studio.importImageFile("bezel")
|
||||
end
|
||||
cy = cy + rowH + gap
|
||||
local vpLabel = page.viewport and "Screen cutout: ON" or "Screen cutout: OFF"
|
||||
if Kit.button(x, cy, half, rowH, vpLabel, { id = "vp",
|
||||
@@ -701,17 +763,25 @@ local function inspectorBody(x, y, w)
|
||||
Kit.text("small", ("canvas %dx%d px"):format(canvas.w, canvas.h), x, cy, PAL.faint)
|
||||
cy = cy + Kit.textHeight("small") + gap
|
||||
|
||||
local pickW = 82 * Kit.scale
|
||||
local artW = w - pickW - gap
|
||||
local idle = ctl.imagePath or "(none)"
|
||||
if Kit.button(x, cy, w, rowH, "Idle art: " .. idle, { id = "img" }) then
|
||||
if Kit.button(x, cy, artW, rowH, "Idle art: " .. idle, { id = "img" }) then
|
||||
Studio.imageTarget = "idle"
|
||||
Studio.cycleImage(1)
|
||||
end
|
||||
if Kit.button(x + artW + gap, cy, pickW, rowH, "Import", { id = "imgpick" }) then
|
||||
Studio.importImageFile("idle")
|
||||
end
|
||||
cy = cy + rowH + gap
|
||||
local pressed = ctl.pressedImagePath or "(none)"
|
||||
if Kit.button(x, cy, w, rowH, "Pressed art: " .. pressed, { id = "imgp" }) then
|
||||
if Kit.button(x, cy, artW, rowH, "Pressed art: " .. pressed, { id = "imgp" }) then
|
||||
Studio.imageTarget = "pressed"
|
||||
Studio.cycleImage(1)
|
||||
end
|
||||
if Kit.button(x + artW + gap, cy, pickW, rowH, "Import", { id = "imgppick" }) then
|
||||
Studio.importImageFile("pressed")
|
||||
end
|
||||
return cy + rowH
|
||||
end
|
||||
|
||||
@@ -885,6 +955,16 @@ function Studio.wheelmoved(_, dy)
|
||||
Studio.wheel = dy
|
||||
end
|
||||
|
||||
function Studio.focus()
|
||||
Studio.drag = nil
|
||||
Studio.clicked = false
|
||||
if Studio.testing then TouchControls:reset() end
|
||||
end
|
||||
|
||||
function Studio.visible()
|
||||
Studio.focus()
|
||||
end
|
||||
|
||||
function Studio.textinput(text)
|
||||
Kit.textinput(text)
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user