-- pxl8 tile properties dialog menu -- provides a ui for editing custom properties on tilemap tiles local DEBUG = false local function log(msg) if DEBUG then print("[tile-properties] " .. msg) end end local function get_selected_tile() log("get_selected_tile() called") if not app then log("ERROR: app is nil!") return nil end local sprite = app.sprite log("sprite: " .. tostring(sprite)) if not sprite then log("No sprite selected") return nil end local layer = app.layer log("layer: " .. tostring(layer)) if not layer or not layer.isTilemap then log("Layer is not a tilemap") return nil end local tileset = layer.tileset log("tileset: " .. tostring(tileset)) if not tileset then log("No tileset in layer") return nil end local tileIndex = app.fgTile log("tileIndex: " .. tostring(tileIndex)) if not tileIndex or tileIndex < 0 then log("Invalid tile index") return nil end log("Selected tile: tileset=" .. tostring(tileset) .. ", index=" .. tostring(tileIndex)) return tileset, tileIndex end local function get_tile_properties(tileset, tile_index) log("get_tile_properties() called for tile index: " .. tostring(tile_index)) local tile = tileset:tile(tile_index) if not tile then log("Could not get tile object") return {} end if not tile.properties then log("Tile has no properties") return {} end local props = {} for key, value in pairs(tile.properties) do local propType = "string" if type(value) == "boolean" then propType = "boolean" elseif type(value) == "number" then propType = "number" end table.insert(props, { key = key, type = propType, value = value }) end log("Found " .. #props .. " properties") return props end local function set_tile_properties(tileset, tile_index, props) log("set_tile_properties() called for tile index: " .. tostring(tile_index)) local tile = tileset:tile(tile_index) if not tile then log("Could not get tile object") return end tile.properties = {} local count = 0 for _, prop in ipairs(props) do if prop.key and prop.key ~= "" then local value = prop.value if prop.type == "boolean" then value = (value == true or value == "true") elseif prop.type == "number" then value = tonumber(value) or 0 end tile.properties[prop.key] = value count = count + 1 end end log("Set " .. count .. " properties") end local function show_property_editor() log("show_property_editor() called") local tileset, tile_index = get_selected_tile() if not tileset then log("No tileset selected, showing alert") app.alert("Please select a tile in the tileset") return end log("Getting properties for tile") local properties = get_tile_properties(tileset, tile_index) local function build_dialog() local dlg = Dialog("Tile Properties - Tile #" .. tile_index) for i, prop in ipairs(properties) do local id_prefix = "prop_" .. i .. "_" local prop_value = prop dlg:separator{ id = id_prefix .. "sep" } dlg:newrow() dlg:entry{ id = id_prefix .. "key", label = "Name:", text = prop_value.key } dlg:newrow() dlg:combobox{ id = id_prefix .. "type", label = "Type:", option = prop_value.type, options = { "boolean", "number", "string" }, onchange = function() local new_type = dlg.data[id_prefix .. "type"] if new_type ~= prop_value.type then if new_type == "boolean" then properties[i].value = false elseif new_type == "number" then properties[i].value = 0 else properties[i].value = "" end properties[i].type = new_type rebuild_from_dialog(dlg) end end } dlg:newrow() if prop_value.type == "boolean" then dlg:check{ id = id_prefix .. "value", label = "Value:", selected = prop_value.value } elseif prop_value.type == "number" then dlg:number{ id = id_prefix .. "value", label = "Value:", text = tostring(prop_value.value), decimals = 0 } else dlg:entry{ id = id_prefix .. "value", label = "Value:", text = tostring(prop_value.value) } end dlg:button{ id = id_prefix .. "delete", text = "Delete", onclick = function() sync_from_dialog(dlg) table.remove(properties, i) rebuild_from_dialog(dlg) end } dlg:newrow() end dlg:separator() dlg:button{ id = "add_btn", text = "Add Property", onclick = function() sync_from_dialog(dlg) table.insert(properties, { key = "", type = "string", value = "" }) rebuild_from_dialog(dlg) end } dlg:button{ text = "Apply Changes", onclick = function() sync_from_dialog(dlg) local new_props = {} for _, prop in ipairs(properties) do if prop.key and prop.key ~= "" then table.insert(new_props, prop) end end set_tile_properties(tileset, tile_index, new_props) end } dlg:separator() dlg:button{ text = "OK", focus = true, onclick = function() sync_from_dialog(dlg) local new_props = {} for _, prop in ipairs(properties) do if prop.key and prop.key ~= "" then table.insert(new_props, prop) end end set_tile_properties(tileset, tile_index, new_props) dlg:close() end } dlg:button{ text = "Cancel" } return dlg end function sync_from_dialog(dlg) log("sync_from_dialog() called") for i = 1, #properties do local id_prefix = "prop_" .. i .. "_" local data = dlg.data log(" Property " .. i .. ":") if data[id_prefix .. "key"] then properties[i].key = data[id_prefix .. "key"] log(" key = " .. tostring(properties[i].key)) end if data[id_prefix .. "type"] then properties[i].type = data[id_prefix .. "type"] log(" type = " .. tostring(properties[i].type)) end if data[id_prefix .. "value"] ~= nil then properties[i].value = data[id_prefix .. "value"] log(" value = " .. tostring(properties[i].value) .. " (type: " .. type(properties[i].value) .. ")") else log(" WARNING: value is nil in dialog data!") end end end function rebuild_from_dialog(old_dlg) old_dlg:close() local new_dlg = build_dialog() new_dlg:show() end local dlg = build_dialog() dlg:show() end function init(plugin) if not plugin then print("[tile-properties] ERROR: plugin is nil!") return end plugin:newCommand{ id = "TilePropertiesEditor", title = "Tile Properties", group = "sprite_properties", onenabled = function() local tileset, tile_index = get_selected_tile() return tileset ~= nil and tile_index ~= nil end, onclick = show_property_editor } end function exit(plugin) end