mirror of
https://github.com/Insality/druid
synced 2025-06-27 02:17:52 +02:00
Update editor scripts
This commit is contained in:
parent
8d2b8c25a0
commit
50e59d9469
239
druid/editor_scripts/assign_layers.lua
Normal file
239
druid/editor_scripts/assign_layers.lua
Normal file
@ -0,0 +1,239 @@
|
||||
--- Module for assigning layers to GUI nodes based on textures and fonts
|
||||
|
||||
local defold_parser = require("druid.editor_scripts.defold_parser.defold_parser")
|
||||
local system = require("druid.editor_scripts.defold_parser.system.system")
|
||||
|
||||
local M = {}
|
||||
|
||||
|
||||
---Create a backup of a file
|
||||
---@param file_path string - The path of the file to backup
|
||||
---@return string|nil - The backup file path, or nil if backup failed
|
||||
local function create_backup(file_path)
|
||||
local backup_path = file_path .. ".backup"
|
||||
print("Creating backup at:", backup_path)
|
||||
|
||||
-- Read and write using system module
|
||||
local content, err_read = system.read_file(file_path)
|
||||
if not content then
|
||||
print("Error reading original file for backup:", err_read)
|
||||
return nil
|
||||
end
|
||||
|
||||
local success, err_write = system.write_file(backup_path, content)
|
||||
if not success then
|
||||
print("Error creating backup file:", err_write)
|
||||
return nil
|
||||
end
|
||||
|
||||
print("Backup created successfully")
|
||||
return backup_path
|
||||
end
|
||||
|
||||
|
||||
---Restore from a backup file
|
||||
---@param backup_path string - The path of the backup file
|
||||
---@param original_path string - The path to restore to
|
||||
---@return boolean - True if restore was successful
|
||||
local function restore_from_backup(backup_path, original_path)
|
||||
print("Restoring from backup:", backup_path)
|
||||
|
||||
-- Read backup file
|
||||
local content, err = system.read_file(backup_path)
|
||||
if not content then
|
||||
print("Error reading backup file:", err)
|
||||
return false
|
||||
end
|
||||
|
||||
-- Write to original file
|
||||
local success, err = system.write_file(original_path, content)
|
||||
if not success then
|
||||
print("Error restoring from backup:", err)
|
||||
return false
|
||||
end
|
||||
|
||||
print("Restored successfully from backup")
|
||||
return true
|
||||
end
|
||||
|
||||
|
||||
---Remove a backup file
|
||||
---@param backup_path string - The path of the backup file to remove
|
||||
local function remove_backup(backup_path)
|
||||
print("Removing backup file:", backup_path)
|
||||
local success, err = os.remove(backup_path)
|
||||
if not success then
|
||||
print("Warning: Could not remove backup file:", err)
|
||||
print("You may want to manually remove it:", backup_path)
|
||||
else
|
||||
print("Backup file removed successfully")
|
||||
end
|
||||
end
|
||||
|
||||
---Assign layers to GUI nodes based on textures and fonts
|
||||
---@param gui_resource string - The GUI resource to process
|
||||
---@return table - Editor command to reload the resource
|
||||
function M.assign_layers(gui_resource)
|
||||
local gui_path = editor.get(gui_resource, "path")
|
||||
print("Setting up layers for", gui_path)
|
||||
|
||||
-- Get the absolute path to the file
|
||||
local absolute_project_path = editor.external_file_attributes(".").path
|
||||
if not absolute_project_path:match("[\\/]$") then
|
||||
absolute_project_path = absolute_project_path .. "/"
|
||||
end
|
||||
local clean_gui_path = gui_path
|
||||
if clean_gui_path:sub(1, 1) == "/" then
|
||||
clean_gui_path = clean_gui_path:sub(2)
|
||||
end
|
||||
local gui_absolute_path = absolute_project_path .. clean_gui_path
|
||||
|
||||
-- Create a backup before modifying the file
|
||||
local backup_path = create_backup(gui_absolute_path)
|
||||
if not backup_path then
|
||||
print("Failed to create backup, aborting...")
|
||||
return {}
|
||||
end
|
||||
|
||||
-- Parse the GUI file using defold_parser
|
||||
print("Parsing GUI file...")
|
||||
local gui_data = defold_parser.load_from_file(gui_absolute_path)
|
||||
if not gui_data then
|
||||
print("Error: Failed to parse GUI file")
|
||||
return {}
|
||||
end
|
||||
|
||||
-- Collect all textures and fonts
|
||||
print("Collecting all available textures and fonts...")
|
||||
local all_textures = {}
|
||||
local all_fonts = {}
|
||||
|
||||
-- Get textures
|
||||
if gui_data.textures then
|
||||
for _, texture in ipairs(gui_data.textures) do
|
||||
print("Found texture:", texture.name)
|
||||
all_textures[texture.name] = true
|
||||
end
|
||||
end
|
||||
|
||||
-- Get fonts
|
||||
if gui_data.fonts then
|
||||
for _, font in ipairs(gui_data.fonts) do
|
||||
print("Found font:", font.name)
|
||||
all_fonts[font.name] = true
|
||||
end
|
||||
end
|
||||
|
||||
-- Track which textures and fonts are actually used by nodes
|
||||
print("Finding used textures and fonts...")
|
||||
local used_layers = {}
|
||||
|
||||
-- First pass: find all used textures and fonts
|
||||
if gui_data.nodes then
|
||||
for _, node in ipairs(gui_data.nodes) do
|
||||
if node.texture then
|
||||
local layer_name = node.texture:match("([^/]+)")
|
||||
if layer_name and all_textures[layer_name] then
|
||||
used_layers[layer_name] = true
|
||||
print("Node", node.id, "uses texture:", layer_name)
|
||||
end
|
||||
elseif node.font then
|
||||
local layer_name = node.font
|
||||
if all_fonts[layer_name] then
|
||||
used_layers[layer_name] = true
|
||||
print("Node", node.id, "uses font:", layer_name)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Create a set of existing layer names for faster lookup
|
||||
print("Checking existing layers...")
|
||||
local existing_layers = {}
|
||||
if gui_data.layers then
|
||||
for _, layer in ipairs(gui_data.layers) do
|
||||
if layer.name then
|
||||
existing_layers[layer.name] = true
|
||||
print("Found existing layer:", layer.name)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Convert set to array of used layers
|
||||
local layers = {}
|
||||
for layer_name in pairs(used_layers) do
|
||||
if not existing_layers[layer_name] then
|
||||
table.insert(layers, layer_name)
|
||||
print("Adding new layer:", layer_name)
|
||||
else
|
||||
print("Layer already exists:", layer_name)
|
||||
end
|
||||
end
|
||||
|
||||
-- Sort new layers for consistent output
|
||||
table.sort(layers)
|
||||
|
||||
print("Found", #layers, "new layers to add")
|
||||
|
||||
-- Add new layers (preserving existing ones)
|
||||
print("Adding new layers...")
|
||||
gui_data.layers = gui_data.layers or {}
|
||||
for _, layer_name in ipairs(layers) do
|
||||
table.insert(gui_data.layers, {
|
||||
name = layer_name,
|
||||
})
|
||||
end
|
||||
|
||||
-- Create a lookup table for faster matching - include both existing and new layers
|
||||
local layer_lookup = {}
|
||||
for layer_name in pairs(existing_layers) do
|
||||
layer_lookup[layer_name] = true
|
||||
end
|
||||
for _, layer_name in ipairs(layers) do
|
||||
layer_lookup[layer_name] = true
|
||||
end
|
||||
|
||||
-- Update nodes to use the correct layer
|
||||
print("Updating node layers...")
|
||||
if gui_data.nodes then
|
||||
for _, node in ipairs(gui_data.nodes) do
|
||||
if node.texture then
|
||||
local layer_name = node.texture:match("([^/]+)")
|
||||
if layer_name and layer_lookup[layer_name] then
|
||||
print("Assigning node", node.id, "to layer:", layer_name)
|
||||
node.layer = layer_name
|
||||
end
|
||||
elseif node.font then
|
||||
local layer_name = node.font
|
||||
if layer_lookup[layer_name] then
|
||||
print("Assigning node", node.id, "to layer:", layer_name)
|
||||
node.layer = layer_name
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Write the updated GUI file
|
||||
print("Writing updated GUI file...")
|
||||
local success = defold_parser.save_to_file(gui_absolute_path, gui_data)
|
||||
|
||||
if not success then
|
||||
print("Error: Failed to save GUI file")
|
||||
print("Attempting to restore from backup...")
|
||||
local restored = restore_from_backup(backup_path, gui_absolute_path)
|
||||
if not restored then
|
||||
print("Critical: Failed to restore from backup. Manual intervention may be required.")
|
||||
end
|
||||
return {}
|
||||
end
|
||||
|
||||
-- Everything worked, remove the backup
|
||||
remove_backup(backup_path)
|
||||
|
||||
print("Successfully assigned layers for GUI:", gui_path)
|
||||
|
||||
return {}
|
||||
end
|
||||
|
||||
|
||||
return M
|
@ -1,132 +0,0 @@
|
||||
import os
|
||||
import sys
|
||||
import deftree
|
||||
|
||||
current_filepath = os.path.abspath(os.path.dirname(__file__))
|
||||
TEMPLATE_PATH = current_filepath + "/widget.lua_template"
|
||||
|
||||
component_annotations = ""
|
||||
component_functions = ""
|
||||
component_define = ""
|
||||
|
||||
def to_camel_case(snake_str):
|
||||
components = snake_str.split('_')
|
||||
return ''.join(x.title() for x in components[0:])
|
||||
|
||||
|
||||
def get_id(node_name):
|
||||
return node_name.upper().replace("/", "_")
|
||||
|
||||
|
||||
def process_component(node_name, component_name):
|
||||
global component_annotations
|
||||
global component_functions
|
||||
global component_define
|
||||
|
||||
if node_name == "root":
|
||||
component_annotations += "\n---@field root node"
|
||||
component_define += "\n\tself.root = self:get_node(\"root\")"
|
||||
|
||||
if node_name.startswith("button"):
|
||||
component_annotations += "\n---@field {0} druid.button".format(node_name)
|
||||
component_functions += "\nfunction M:_on_{0}()\n\tprint(\"Click on {0}\")\nend\n\n".format(node_name)
|
||||
component_define += "\n\tself.{0} = self.druid:new_button(\"{1}\", self._on_{0})".format(node_name, node_name)
|
||||
|
||||
if node_name.startswith("text"):
|
||||
component_annotations += "\n---@field {0} druid.text".format(node_name)
|
||||
component_define += "\n\tself.{0} = self.druid:new_text(\"{1}\")".format(node_name, node_name)
|
||||
|
||||
if node_name.startswith("lang_text"):
|
||||
component_annotations += "\n---@field {0} druid.text".format(node_name)
|
||||
component_define += "\n\tself.{0} = self.druid:new_lang_text(\"{1}\", \"lang_id\")".format(node_name, node_name)
|
||||
|
||||
if node_name.startswith("grid") or node_name.startswith("static_grid"):
|
||||
component_annotations += "\n---@field {0} druid.grid".format(node_name)
|
||||
component_define += "\n--TODO: Replace prefab_name with grid element prefab"
|
||||
component_define += "\n\tself.{0} = self.druid:new_grid(\"{1}\", \"prefab_name\", 1)".format(node_name, node_name)
|
||||
|
||||
if node_name.startswith("scroll_view"):
|
||||
field_name = node_name.replace("_view", "")
|
||||
content_name = node_name.replace("_view", "_content")
|
||||
component_annotations += "\n---@field {0} druid.scroll".format(field_name)
|
||||
component_define += "\n\tself.{0} = self.druid:new_scroll(\"{1}\", \"{2}\")".format(field_name, node_name, content_name)
|
||||
|
||||
if node_name.startswith("blocker"):
|
||||
component_annotations += "\n---@field {0} druid.blocker".format(node_name)
|
||||
component_define += "\n\tself.{0} = self.druid:new_blocker(\"{1}\")".format(node_name, node_name)
|
||||
|
||||
if node_name.startswith("slider"):
|
||||
component_annotations += "\n---@field {0} druid.slider".format(node_name)
|
||||
component_define += "\n--TODO: Replace slider end position. It should be only vertical or horizontal"
|
||||
component_define += "\n\tself.{0} = self.druid:new_slider(\"{1}\", vmath.vector3(100, 0, 0), self._on_{0}_change)".format(node_name, node_name)
|
||||
component_functions += "\nfunction M:_on_{0}_change(value)\n\tprint(\"Slider change:\", value)\nend\n\n".format(node_name)
|
||||
|
||||
if node_name.startswith("progress"):
|
||||
component_annotations += "\n---@field {0} druid.progress".format(node_name)
|
||||
component_define += "\n\tself.{0} = self.druid:new_progress(\"{1}\", \"x\")".format(node_name, get_id(node_name))
|
||||
|
||||
if node_name.startswith("timer"):
|
||||
component_annotations += "\n---@field {0} druid.timer".format(node_name)
|
||||
component_define += "\n\tself.{0} = self.druid:new_timer(\"{1}\", 59, 0, self._on_{0}_end)".format(node_name, get_id(node_name))
|
||||
component_functions += "\nfunction M:_on_{0}_end()\n\tprint(\"Timer {0} trigger\")\nend\n\n".format(node_name)
|
||||
|
||||
|
||||
def main():
|
||||
global component_annotations
|
||||
global component_functions
|
||||
global component_define
|
||||
|
||||
filename = sys.argv[1]
|
||||
print("Create Druid component from gui file", filename)
|
||||
tree = deftree.parse(filename)
|
||||
root = tree.get_root()
|
||||
|
||||
output_directory = os.path.dirname(filename)
|
||||
output_filename = os.path.splitext(os.path.basename(filename))[0]
|
||||
|
||||
output_full_path = os.path.join(output_directory, output_filename + ".lua")
|
||||
is_already_exists = os.path.exists(output_full_path)
|
||||
if is_already_exists:
|
||||
print("Error: The file is already exists")
|
||||
print("File:", output_full_path)
|
||||
return
|
||||
|
||||
component_require_path = os.path.join(output_directory, output_filename).replace("/", ".").replace("..", "")
|
||||
component_name = to_camel_case(output_filename)
|
||||
component_type = output_filename
|
||||
scheme_list = []
|
||||
|
||||
# Gather nodes from GUI scene
|
||||
for node in root.iter_elements("nodes"):
|
||||
node_name = node.get_attribute("id").value
|
||||
scheme_list.append("\t" + get_id(node_name) + " = \"" + node_name + "\"")
|
||||
|
||||
is_template = node.get_attribute("template")
|
||||
is_in_template = "/" in node_name
|
||||
if not is_template and not is_in_template:
|
||||
process_component(node_name, component_name)
|
||||
|
||||
if len(component_define) > 2:
|
||||
component_define = "\n" + component_define
|
||||
|
||||
template_file = open(TEMPLATE_PATH, "r")
|
||||
filedata = template_file.read()
|
||||
template_file.close()
|
||||
|
||||
filedata = filedata.replace("{COMPONENT_NAME}", component_name)
|
||||
filedata = filedata.replace("{COMPONENT_TYPE}", component_type)
|
||||
filedata = filedata.replace("{COMPONENT_PATH}", component_require_path)
|
||||
filedata = filedata.replace("{COMPONENT_DEFINE}", component_define)
|
||||
filedata = filedata.replace("{COMPONENT_FUNCTIONS}", component_functions)
|
||||
filedata = filedata.replace("{COMPONENT_ANNOTATIONS}", component_annotations)
|
||||
#filedata = filedata.replace("{SCHEME_LIST}", ",\n".join(scheme_list))
|
||||
|
||||
output_file = open(output_full_path, "w")
|
||||
output_file.write(filedata)
|
||||
output_file.close()
|
||||
|
||||
print("Success: The file is created")
|
||||
print("File:", output_full_path)
|
||||
|
||||
|
||||
main()
|
61
druid/editor_scripts/create_druid_widget.lua
Normal file
61
druid/editor_scripts/create_druid_widget.lua
Normal file
@ -0,0 +1,61 @@
|
||||
local M = {}
|
||||
|
||||
local function to_camel_case(snake_str)
|
||||
local components = {}
|
||||
for component in snake_str:gmatch("[^_]+") do
|
||||
table.insert(components, component:sub(1, 1):upper() .. component:sub(2))
|
||||
end
|
||||
return table.concat(components, "")
|
||||
end
|
||||
|
||||
|
||||
function M.create_druid_widget(opts)
|
||||
local gui_filepath = editor.get(opts.selection, "path")
|
||||
local filename = gui_filepath:match("([^/]+)%.gui$")
|
||||
print("Create Druid widget for", gui_filepath)
|
||||
|
||||
local absolute_project_path = editor.external_file_attributes(".").path
|
||||
local widget_resource_path = gui_filepath:gsub("%.gui$", ".lua")
|
||||
local new_widget_absolute_path = absolute_project_path .. widget_resource_path
|
||||
|
||||
local widget_name = to_camel_case(filename)
|
||||
local widget_type = filename
|
||||
|
||||
-- Check if file already exists
|
||||
local f = io.open(new_widget_absolute_path, "r")
|
||||
if f then
|
||||
f:close()
|
||||
print("Widget file already exists at " .. new_widget_absolute_path)
|
||||
print("Creation aborted to prevent overwriting")
|
||||
return
|
||||
end
|
||||
|
||||
-- Get template path from preferences
|
||||
local template_path = editor.prefs.get("druid.widget_template_path")
|
||||
|
||||
-- Get template content using the path from preferences
|
||||
local template_content = editor.get(template_path, "text")
|
||||
if not template_content then
|
||||
print("Error: Could not load template from", template_path)
|
||||
print("Check the template path in [Druid] Settings")
|
||||
return
|
||||
end
|
||||
|
||||
-- Replace template variables
|
||||
template_content = template_content:gsub("{COMPONENT_NAME}", widget_name)
|
||||
template_content = template_content:gsub("{COMPONENT_TYPE}", widget_type)
|
||||
|
||||
-- Write file
|
||||
local file, err = io.open(new_widget_absolute_path, "w")
|
||||
if not file then
|
||||
print("Error creating widget file:", err)
|
||||
return
|
||||
end
|
||||
file:write(template_content)
|
||||
file:close()
|
||||
|
||||
print("Widget created at " .. widget_resource_path)
|
||||
end
|
||||
|
||||
|
||||
return M
|
153
druid/editor_scripts/defold_parser/defold_parser.lua
Normal file
153
druid/editor_scripts/defold_parser/defold_parser.lua
Normal file
@ -0,0 +1,153 @@
|
||||
--- Defold Text Proto format encoder/decoder to lua table
|
||||
|
||||
local config = require("druid.editor_scripts.defold_parser.system.config")
|
||||
local system = require("druid.editor_scripts.defold_parser.system.system")
|
||||
|
||||
local M = {}
|
||||
|
||||
|
||||
--- Decode a Defold object from a string
|
||||
---@param text string
|
||||
---@return table
|
||||
function M.decode_defold_object(text)
|
||||
-- Create a root object, which will contain all the file data
|
||||
local root = {}
|
||||
-- Stack to keep track of nested objects. Always insert data to the last object in the stack
|
||||
local stack = { root }
|
||||
|
||||
-- For each line in the text, we go through the following steps:
|
||||
for raw_line in text:gmatch("[^\r\n]+") do
|
||||
system.parse_line(raw_line, stack)
|
||||
end
|
||||
|
||||
return root
|
||||
end
|
||||
|
||||
|
||||
-- Encoding Functions
|
||||
function M.encode_defold_object(obj, spaces, data_level, extension)
|
||||
spaces = spaces or 0
|
||||
data_level = data_level or 0
|
||||
local key_order = extension and config.KEY_ORDER[extension] or {}
|
||||
|
||||
local result = ''
|
||||
local tabString = string.rep(' ', spaces)
|
||||
|
||||
local keys = {}
|
||||
for key in pairs(obj) do
|
||||
table.insert(keys, key)
|
||||
end
|
||||
|
||||
table.sort(keys, function(a, b)
|
||||
local index_a = system.contains(key_order, a) or 0
|
||||
local index_b = system.contains(key_order, b) or 0
|
||||
return index_a < index_b
|
||||
end)
|
||||
|
||||
-- Iterate over the sorted keys
|
||||
for _, key in ipairs(keys) do
|
||||
local value = obj[key]
|
||||
local value_type = type(value)
|
||||
|
||||
-- Handle different types of values
|
||||
if value_type == "table" then
|
||||
-- Check if it's an array-like table
|
||||
if #value > 0 then
|
||||
-- It's an array-like table, process each element
|
||||
for _, array_item in ipairs(value) do
|
||||
local item_type = type(array_item)
|
||||
|
||||
if key == "data" and item_type == "table" then
|
||||
-- Handle nested data
|
||||
local encodedChild = M.encode_defold_object(array_item, spaces + 2, data_level + 1, extension)
|
||||
result = result .. tabString .. key .. ': "' .. encodedChild .. '"\n'
|
||||
elseif item_type == "number" or item_type == "boolean" then
|
||||
local is_contains_dot = string.find(key, "%.")
|
||||
if item_type == "number" and (system.contains(config.with_dot_params, key) and not is_contains_dot) then
|
||||
result = result .. tabString .. key .. ': ' .. string.format("%.1f", array_item) .. '\n'
|
||||
else
|
||||
result = result .. tabString .. key .. ': ' .. tostring(array_item) .. '\n'
|
||||
end
|
||||
elseif item_type == "string" then
|
||||
-- Handle multiline text
|
||||
if key == "text" then
|
||||
result = result .. tabString .. key .. ': "' .. array_item:gsub("\n", '\\n"\n' .. tabString .. '"') .. '"\n'
|
||||
else
|
||||
-- Check if the key should not have quotes
|
||||
local is_uppercase = (array_item == string.upper(array_item))
|
||||
local is_boolean = (array_item == "true" or array_item == "false")
|
||||
if (is_uppercase and not config.string_keys[key]) or is_boolean then
|
||||
result = result .. tabString .. key .. ': ' .. array_item .. '\n'
|
||||
else
|
||||
result = result .. tabString .. key .. ': "' .. array_item .. '"\n'
|
||||
end
|
||||
end
|
||||
elseif item_type == "table" then
|
||||
result = result .. tabString .. key .. ' {\n' .. M.encode_defold_object(array_item, spaces + 2, data_level, extension) .. tabString .. '}\n'
|
||||
end
|
||||
end
|
||||
else
|
||||
-- It's a dictionary-like table
|
||||
result = result .. tabString .. key .. ' {\n' .. M.encode_defold_object(value, spaces + 2, data_level, extension) .. tabString .. '}\n'
|
||||
end
|
||||
else
|
||||
-- Handle scalar values (string, number, boolean)
|
||||
if value_type == "number" or value_type == "boolean" then
|
||||
local is_contains_dot = string.find(key, "%.")
|
||||
if value_type == "number" and (system.contains(config.with_dot_params, key) and not is_contains_dot) then
|
||||
result = result .. tabString .. key .. ': ' .. string.format("%.1f", value) .. '\n'
|
||||
else
|
||||
result = result .. tabString .. key .. ': ' .. tostring(value) .. '\n'
|
||||
end
|
||||
elseif value_type == "string" then
|
||||
-- Handle multiline text
|
||||
if key == "text" then
|
||||
result = result .. tabString .. key .. ': "' .. value:gsub("\n", '\\n"\n' .. tabString .. '"') .. '"\n'
|
||||
else
|
||||
-- Check if the key should not have quotes
|
||||
local is_uppercase = (value == string.upper(value))
|
||||
local is_boolean = (value == "true" or value == "false")
|
||||
if (is_uppercase and not config.string_keys[key]) or is_boolean then
|
||||
result = result .. tabString .. key .. ': ' .. value .. '\n'
|
||||
else
|
||||
result = result .. tabString .. key .. ': "' .. value .. '"\n'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return result
|
||||
end
|
||||
|
||||
|
||||
---Load lua table from file in Defold Text Proto format
|
||||
---@param file_path string
|
||||
---@return table|nil, string|nil
|
||||
function M.load_from_file(file_path)
|
||||
local content, reason = system.read_file(file_path)
|
||||
if not content then
|
||||
return nil, reason
|
||||
end
|
||||
|
||||
return M.decode_defold_object(content), nil
|
||||
end
|
||||
|
||||
|
||||
---Write lua table to file in Defold Text Proto format
|
||||
---The path file extension will be used to determine the Defold format (*.atlas, *.gui, *.font, etc)
|
||||
---@param file_path string
|
||||
---@param lua_table table
|
||||
---@return boolean, string|nil
|
||||
function M.save_to_file(file_path, lua_table)
|
||||
-- Get extension without the dot
|
||||
local defold_format_name = file_path:match("^.+%.(.+)$")
|
||||
print("File extension:", defold_format_name)
|
||||
|
||||
local encoded_object = M.encode_defold_object(lua_table, nil, nil, defold_format_name)
|
||||
|
||||
return system.write_file(file_path, encoded_object)
|
||||
end
|
||||
|
||||
|
||||
return M
|
179
druid/editor_scripts/defold_parser/system/config.lua
Normal file
179
druid/editor_scripts/defold_parser/system/config.lua
Normal file
@ -0,0 +1,179 @@
|
||||
local M = {}
|
||||
|
||||
-- Define a set of keys that should not have quotes
|
||||
M.string_keys = {
|
||||
text = true,
|
||||
id = true,
|
||||
value = true,
|
||||
rename_patterns = true,
|
||||
}
|
||||
|
||||
|
||||
M.ALWAYS_LIST = {
|
||||
attributes = true,
|
||||
nodes = true,
|
||||
images = true,
|
||||
children = true,
|
||||
fonts = true,
|
||||
layers = true,
|
||||
textures = true,
|
||||
embedded_components = true,
|
||||
embedded_instances = true,
|
||||
collection_instances = true,
|
||||
instances = true,
|
||||
}
|
||||
|
||||
|
||||
M.with_dot_params = {
|
||||
"x",
|
||||
"y",
|
||||
"z",
|
||||
"w",
|
||||
"alpha",
|
||||
"outline_alpha",
|
||||
"shadow_alpha",
|
||||
"text_leading",
|
||||
"text_tracking",
|
||||
"pieFillAngle",
|
||||
"innerRadius",
|
||||
"leading",
|
||||
"tracking",
|
||||
"data",
|
||||
"t_x",
|
||||
"t_y",
|
||||
"spread",
|
||||
"start_delay",
|
||||
"inherit_velocity",
|
||||
"start_delay_spread",
|
||||
"duration_spread",
|
||||
"start_offset",
|
||||
"outline_width",
|
||||
"shadow_x",
|
||||
"shadow_y",
|
||||
"aspect_ratio",
|
||||
"far_z",
|
||||
"mass",
|
||||
"linear_damping",
|
||||
"angular_damping",
|
||||
"gain",
|
||||
"pan",
|
||||
"speed",
|
||||
"duration"
|
||||
}
|
||||
|
||||
M.KEY_ORDER = {
|
||||
["font"] = {
|
||||
"extrude_borders",
|
||||
"images",
|
||||
"inner_padding",
|
||||
"margin",
|
||||
"font",
|
||||
"material",
|
||||
"size",
|
||||
"antialias",
|
||||
"alpha",
|
||||
"outline_alpha",
|
||||
"outline_width",
|
||||
"shadow_alpha",
|
||||
"shadow_blur",
|
||||
"shadow_x",
|
||||
"shadow_y",
|
||||
"extra_characters",
|
||||
"output_format",
|
||||
"all_chars",
|
||||
"cache_width",
|
||||
"cache_height",
|
||||
"render_mode",
|
||||
},
|
||||
["atlas"] = {
|
||||
"id",
|
||||
"images",
|
||||
"playback",
|
||||
"fps",
|
||||
"flip_horizontal",
|
||||
"flip_vertical",
|
||||
"image",
|
||||
"sprite_trim_mode",
|
||||
"images",
|
||||
"animations",
|
||||
"margin",
|
||||
"extrude_borders",
|
||||
"inner_padding",
|
||||
"max_page_width",
|
||||
"max_page_height",
|
||||
"rename_patterns",
|
||||
},
|
||||
["gui"] = {
|
||||
"position",
|
||||
"rotation",
|
||||
"scale",
|
||||
"size",
|
||||
"color",
|
||||
"type",
|
||||
"blend_mode",
|
||||
"text",
|
||||
"texture",
|
||||
"font",
|
||||
"id",
|
||||
"xanchor",
|
||||
"yanchor",
|
||||
"pivot",
|
||||
"outline",
|
||||
"shadow",
|
||||
"adjust_mode",
|
||||
"line_break",
|
||||
"parent",
|
||||
"layer",
|
||||
"inherit_alpha",
|
||||
"slice9",
|
||||
"outerBounds",
|
||||
"innerRadius",
|
||||
"perimeterVertices",
|
||||
"pieFillAngle",
|
||||
"clipping_mode",
|
||||
"clipping_visible",
|
||||
"clipping_inverted",
|
||||
"alpha",
|
||||
"outline_alpha",
|
||||
"shadow_alpha",
|
||||
"overridden_fields",
|
||||
"template",
|
||||
"template_node_child",
|
||||
"text_leading",
|
||||
"text_tracking",
|
||||
"size_mode",
|
||||
"spine_scene",
|
||||
"spine_default_animation",
|
||||
"spine_skin",
|
||||
"spine_node_child",
|
||||
"particlefx",
|
||||
"custom_type",
|
||||
"enabled",
|
||||
"visible",
|
||||
|
||||
-- Scene
|
||||
"scripts",
|
||||
"fonts",
|
||||
"textures",
|
||||
"background_color",
|
||||
"nodes",
|
||||
"layers",
|
||||
"material",
|
||||
"layouts",
|
||||
"adjust_reference",
|
||||
"max_nodes",
|
||||
"spine_scenes",
|
||||
"particlefxs",
|
||||
"resources",
|
||||
"materials",
|
||||
"max_dynamic_textures",
|
||||
|
||||
-- Vectors
|
||||
"x",
|
||||
"y",
|
||||
"z",
|
||||
"w",
|
||||
},
|
||||
}
|
||||
|
||||
return M
|
139
druid/editor_scripts/defold_parser/system/parser.lua
Normal file
139
druid/editor_scripts/defold_parser/system/parser.lua
Normal file
@ -0,0 +1,139 @@
|
||||
local config = require("druid.editor_scripts.defold_parser.system.config")
|
||||
|
||||
local M = {}
|
||||
|
||||
-- Example: "name: value"
|
||||
M.REGEX_KEY_COLUM_VALUE = "^%s*([%w_]+):%s*(.+)$"
|
||||
-- Example: "name {"
|
||||
M.REGEX_START_TABLE = "^%s*([%w_]*)%s*{%s*$"
|
||||
-- Example: "}"
|
||||
M.REGEX_END_TABLE = "^%s*}%s*$"
|
||||
|
||||
|
||||
---@param value string
|
||||
---@return string
|
||||
function M.unescape_text_field(value)
|
||||
-- Splitting the value by new lines and processing each line
|
||||
local lines = {}
|
||||
for line in value:gmatch("[^\r\n]+") do
|
||||
line = line:gsub('\\"', '"') -- Unescaping quotes
|
||||
line = line:gsub("\\n", "") -- Removing newline escapes
|
||||
line = line:gsub("\\", "") -- Unescaping backslashes
|
||||
table.insert(lines, line)
|
||||
end
|
||||
|
||||
-- Reconstructing the value
|
||||
value = table.concat(lines, "\n")
|
||||
return value
|
||||
end
|
||||
|
||||
|
||||
function M.is_multiline_value(value)
|
||||
return value:find("\\n\"") ~= nil
|
||||
end
|
||||
|
||||
|
||||
---@param value any
|
||||
---@param property_name string|nil
|
||||
---@return any
|
||||
function M.decode_value(value, property_name)
|
||||
if value:match('^".*"$') then
|
||||
-- Removing the quotes from the string
|
||||
value = value:sub(2, -2)
|
||||
|
||||
-- Check if value is escaped
|
||||
-- If ends with \n
|
||||
if value:sub(-2) == "\\n" then
|
||||
value = value:gsub('\\"', '"') -- Unescaping quotes
|
||||
value = value:gsub("\\n", "")
|
||||
value = value:gsub("\\", "")
|
||||
end
|
||||
|
||||
elseif value:match('^%-?[0-9.E%-]+$') then
|
||||
-- Converting to number
|
||||
value = tonumber(value)
|
||||
end
|
||||
|
||||
-- Specific handling for the "text" property
|
||||
if property_name == "text" then
|
||||
value = tostring(value)
|
||||
else
|
||||
if value == "true" then
|
||||
value = true
|
||||
elseif value == "false" then
|
||||
value = false
|
||||
end
|
||||
end
|
||||
|
||||
if property_name == "text" and M.is_multiline_value(value) and type(value) == "string" then
|
||||
value = M.unescape_text_field(value)
|
||||
end
|
||||
|
||||
return value
|
||||
end
|
||||
|
||||
|
||||
---@param parent_object table
|
||||
---@param name string
|
||||
---@param stack table
|
||||
function M.new_inner_struct(parent_object, name, stack)
|
||||
local new_object = {}
|
||||
M.apply_value(parent_object, name, new_object)
|
||||
|
||||
local is_object_always_list = config.ALWAYS_LIST[name]
|
||||
if is_object_always_list and not M.is_array(parent_object[name]) then
|
||||
parent_object[name] = { parent_object[name] }
|
||||
end
|
||||
|
||||
table.insert(stack, new_object)
|
||||
end
|
||||
|
||||
|
||||
---Apply value to the object, if the value is already present, convert it to an array
|
||||
---@param object table
|
||||
---@param name string
|
||||
---@param value any
|
||||
---@return table object
|
||||
function M.apply_value(object, name, value)
|
||||
local is_object_always_list = config.ALWAYS_LIST[name]
|
||||
if object[name] == nil then
|
||||
object[name] = value
|
||||
if is_object_always_list then
|
||||
object[name] = { object[name] }
|
||||
end
|
||||
return object
|
||||
end
|
||||
|
||||
-- Convert to array if not already
|
||||
if not M.is_array(object[name]) then
|
||||
object[name] = { object[name] }
|
||||
end
|
||||
|
||||
table.insert(object[name], value)
|
||||
return object
|
||||
end
|
||||
|
||||
|
||||
---@param object table
|
||||
---@param value string
|
||||
---@return table @object
|
||||
function M.apply_multiline_value(object, name, value)
|
||||
if object[name] == nil then
|
||||
object[name] = value
|
||||
else
|
||||
object[name] = object[name] .. "\n" .. value
|
||||
end
|
||||
|
||||
return object
|
||||
end
|
||||
|
||||
|
||||
--- Check if table is array
|
||||
---@param t table
|
||||
---@return boolean
|
||||
function M.is_array(t)
|
||||
return type(t) == "table" and t[1] ~= nil
|
||||
end
|
||||
|
||||
|
||||
return M
|
163
druid/editor_scripts/defold_parser/system/system.lua
Normal file
163
druid/editor_scripts/defold_parser/system/system.lua
Normal file
@ -0,0 +1,163 @@
|
||||
local parser = require("druid.editor_scripts.defold_parser.system.parser")
|
||||
|
||||
local M = {}
|
||||
|
||||
|
||||
--- Check if table-array contains element
|
||||
---@param table table
|
||||
---@param element any
|
||||
---@return number|boolean index of element or false
|
||||
function M.contains(table, element)
|
||||
for index, value in pairs(table) do
|
||||
if value == element then
|
||||
return index
|
||||
end
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
|
||||
---@param file_path string
|
||||
---@return string|nil, string|nil @success, reason
|
||||
function M.read_file(file_path)
|
||||
local file = io.open(file_path, "r")
|
||||
if file == nil then
|
||||
return nil, "Could not open file: " .. file_path
|
||||
end
|
||||
|
||||
local content = file:read("*a")
|
||||
file:close()
|
||||
|
||||
return content, nil
|
||||
end
|
||||
|
||||
|
||||
---@param file_path string
|
||||
---@param content string
|
||||
---@return boolean, string|nil @success, reason
|
||||
function M.write_file(file_path, content)
|
||||
local file = io.open(file_path, "w")
|
||||
if file == nil then
|
||||
return false, "Could not open file: " .. file_path
|
||||
end
|
||||
|
||||
file:write(content)
|
||||
file:close()
|
||||
|
||||
return true, nil
|
||||
end
|
||||
|
||||
|
||||
---@param line string
|
||||
function M.unescape_line(line)
|
||||
-- Trim whitespaces
|
||||
line = line:match("^%s*(.-)%s*$")
|
||||
|
||||
-- Remove first and last quote symbols only if exists
|
||||
if line:sub(1, 1) == '"' and line:sub(-1) == '"' then
|
||||
line = line:sub(2, -2)
|
||||
end
|
||||
|
||||
-- Trim whitespaces
|
||||
line = line:match("^%s*(.-)%s*$")
|
||||
|
||||
-- Splitting the value by new lines and processing each line
|
||||
line = line:gsub('\\"', '"') -- Unescaping quotes
|
||||
line = line:gsub("\\n", "") -- Removing newline escapes
|
||||
line = line:gsub("\\", "") -- Unescaping backslashes
|
||||
|
||||
return line
|
||||
end
|
||||
|
||||
|
||||
---@param line string
|
||||
---@return string, string, string, boolean @new_object_name, name, value, end_struct_flag
|
||||
function M.split_line(line)
|
||||
local new_object_name = line:match(parser.REGEX_START_TABLE)
|
||||
local name, value = line:match(parser.REGEX_KEY_COLUM_VALUE)
|
||||
local end_struct_flag = line:match(parser.REGEX_END_TABLE)
|
||||
|
||||
-- We hit a line what is contains only value, like multiline strings
|
||||
if not name and not value then
|
||||
value = line
|
||||
end
|
||||
|
||||
return new_object_name, name, value, end_struct_flag
|
||||
end
|
||||
|
||||
|
||||
-- what a crap...
|
||||
local LAST_USED_NAME = nil
|
||||
|
||||
---@param unescaped_line string @line to parse
|
||||
---@param stack table @stack of objects
|
||||
---@return boolean
|
||||
function M.parse_line(unescaped_line, stack)
|
||||
unescaped_line = unescaped_line:match("^%s*(.-)%s*$")
|
||||
|
||||
-- Use last object to insert data
|
||||
local object = stack[#stack]
|
||||
local line = M.unescape_line(unescaped_line)
|
||||
local inner_object_name, name, value, end_struct_flag = M.split_line(line)
|
||||
|
||||
local is_just_new_line = (unescaped_line == "\"\\n\"")
|
||||
if not end_struct_flag and (line == "\"" or line == "") and (not is_just_new_line) then
|
||||
if LAST_USED_NAME ~= "text" then
|
||||
end_struct_flag = true
|
||||
end
|
||||
end
|
||||
|
||||
if inner_object_name then
|
||||
parser.new_inner_struct(object, inner_object_name, stack)
|
||||
object = stack[#stack]
|
||||
end
|
||||
|
||||
if name and value ~= nil then
|
||||
-- If value is nested object...
|
||||
if value:sub(1, 1) == '"' then
|
||||
value = value:sub(2, -1)
|
||||
end
|
||||
if value:sub(-1) == '"' then
|
||||
value = value:sub(1, -2)
|
||||
end
|
||||
|
||||
local unescape_line = M.unescape_line(value)
|
||||
local new_object_name, field_name, _, end_flag = M.split_line(unescape_line)
|
||||
|
||||
if (new_object_name or field_name or end_flag) and name ~= "text" then
|
||||
parser.new_inner_struct(object, name, stack)
|
||||
object = stack[#stack]
|
||||
|
||||
M.parse_line(value, stack)
|
||||
else
|
||||
-- Just a hack honestly
|
||||
-- If first character is a quote, then remove it
|
||||
if value:sub(1, 1) == '"' then
|
||||
value = value:sub(2, -1)
|
||||
end
|
||||
if value:sub(-1) == '"' then
|
||||
value = value:sub(1, -2)
|
||||
end
|
||||
|
||||
value = parser.decode_value(value, name)
|
||||
LAST_USED_NAME = name
|
||||
parser.apply_value(object, name, value)
|
||||
end
|
||||
end
|
||||
|
||||
if not name and value and not inner_object_name and not end_struct_flag then
|
||||
-- We should to add value to the last as a multiline data
|
||||
parser.apply_multiline_value(object, LAST_USED_NAME, value)
|
||||
end
|
||||
|
||||
if end_struct_flag then
|
||||
-- Go back to the parent object
|
||||
table.remove(stack)
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
|
||||
return M
|
@ -1,25 +1,24 @@
|
||||
local assign_layers = require("druid.editor_scripts.assign_layers")
|
||||
local create_druid_widget = require("druid.editor_scripts.create_druid_widget")
|
||||
local druid_settings = require("druid.editor_scripts.druid_settings")
|
||||
|
||||
local M = {}
|
||||
|
||||
local DEFAULT_WIDGET_TEMPLATE_PATH = "/druid/templates/widget_full.lua.template"
|
||||
|
||||
local function ends_with(str, ending)
|
||||
return ending == "" or str:sub(-#ending) == ending
|
||||
end
|
||||
|
||||
|
||||
local function save_file_from_dependency(dependency_file_path, output_file_path)
|
||||
local content = editor.get(dependency_file_path, "text")
|
||||
local file, err = io.open(output_file_path, "w")
|
||||
if not file then
|
||||
print("Error:", err)
|
||||
return false
|
||||
end
|
||||
file:write(content)
|
||||
file:close()
|
||||
print("Write file at", output_file_path)
|
||||
return true
|
||||
|
||||
---Define preferences schema
|
||||
function M.get_prefs_schema()
|
||||
return {
|
||||
["druid.widget_template_path"] = editor.prefs.schema.string({
|
||||
default = DEFAULT_WIDGET_TEMPLATE_PATH,
|
||||
scope = editor.prefs.SCOPE.PROJECT
|
||||
})
|
||||
}
|
||||
end
|
||||
|
||||
|
||||
---Define the editor commands
|
||||
function M.get_commands()
|
||||
return {
|
||||
{
|
||||
@ -28,24 +27,10 @@ function M.get_commands()
|
||||
query = { selection = {type = "resource", cardinality = "one"} },
|
||||
active = function(opts)
|
||||
local path = editor.get(opts.selection, "path")
|
||||
return ends_with(path, ".gui")
|
||||
return path:match("%.gui$") ~= nil
|
||||
end,
|
||||
run = function(opts)
|
||||
local file = opts.selection
|
||||
print("Run script for", editor.get(file, "path"))
|
||||
save_file_from_dependency('/druid/editor_scripts/run_python_script_on_gui.sh', "./build/run_python_script_on_gui.sh")
|
||||
save_file_from_dependency('/druid/editor_scripts/setup_layers.py', "./build/setup_layers.py")
|
||||
return {
|
||||
{
|
||||
action = "shell",
|
||||
command = {
|
||||
"bash",
|
||||
"./build/run_python_script_on_gui.sh",
|
||||
"./build/setup_layers.py",
|
||||
"." .. editor.get(file, "path")
|
||||
}
|
||||
}
|
||||
}
|
||||
return assign_layers.assign_layers(opts.selection)
|
||||
end
|
||||
},
|
||||
|
||||
@ -55,25 +40,18 @@ function M.get_commands()
|
||||
query = { selection = {type = "resource", cardinality = "one"} },
|
||||
active = function(opts)
|
||||
local path = editor.get(opts.selection, "path")
|
||||
return ends_with(path, ".gui")
|
||||
return path:match("%.gui$") ~= nil
|
||||
end,
|
||||
run = function(opts)
|
||||
local file = opts.selection
|
||||
print("Run script for", editor.get(file, "path"))
|
||||
save_file_from_dependency('/druid/editor_scripts/run_python_script_on_gui.sh', "./build/run_python_script_on_gui.sh")
|
||||
save_file_from_dependency('/druid/editor_scripts/create_druid_component.py', "./build/create_druid_component.py")
|
||||
save_file_from_dependency('/druid/editor_scripts/widget.lua_template', "./build/widget.lua_template")
|
||||
return {
|
||||
return create_druid_widget.create_druid_widget(opts.selection)
|
||||
end
|
||||
},
|
||||
|
||||
{
|
||||
action = "shell",
|
||||
command = {
|
||||
"bash",
|
||||
"./build/run_python_script_on_gui.sh",
|
||||
"./build/create_druid_component.py",
|
||||
"." .. editor.get(file, "path")
|
||||
}
|
||||
}
|
||||
}
|
||||
label = "[Druid] Settings",
|
||||
locations = { "Edit" },
|
||||
run = function()
|
||||
return druid_settings.open_settings()
|
||||
end
|
||||
}
|
||||
}
|
||||
|
122
druid/editor_scripts/druid_settings.lua
Normal file
122
druid/editor_scripts/druid_settings.lua
Normal file
@ -0,0 +1,122 @@
|
||||
local M = {}
|
||||
|
||||
|
||||
function M.open_settings()
|
||||
print("Opening Druid settings")
|
||||
|
||||
local dialog_component = editor.ui.component(function(props)
|
||||
local template_path, set_template_path = editor.ui.use_state(editor.prefs.get("druid.widget_template_path"))
|
||||
|
||||
-- Check if the template path is valid
|
||||
local path_valid = editor.ui.use_memo(function(path)
|
||||
-- Use resource_exists to check if the resource exists
|
||||
local exists = false
|
||||
pcall(function()
|
||||
-- If we can get the text property, the resource exists
|
||||
local content = editor.get(path, "text")
|
||||
exists = content ~= nil
|
||||
end)
|
||||
return exists
|
||||
end, template_path)
|
||||
|
||||
return editor.ui.dialog({
|
||||
title = "Druid Settings",
|
||||
content = editor.ui.vertical({
|
||||
spacing = editor.ui.SPACING.MEDIUM,
|
||||
padding = editor.ui.PADDING.MEDIUM,
|
||||
children = {
|
||||
editor.ui.label({
|
||||
text = "Widget Template Path:"
|
||||
}),
|
||||
editor.ui.resource_field({
|
||||
value = template_path,
|
||||
on_value_changed = set_template_path,
|
||||
extensions = {"lua", "template"},
|
||||
padding = editor.ui.PADDING.SMALL
|
||||
}),
|
||||
not path_valid and editor.ui.label({
|
||||
text = "Warning: Path not found!",
|
||||
color = editor.ui.COLOR.WARNING
|
||||
}) or nil,
|
||||
|
||||
-- Links section title
|
||||
editor.ui.label({
|
||||
text = "Documentation:",
|
||||
color = editor.ui.COLOR.TEXT
|
||||
}),
|
||||
|
||||
-- Documentation buttons
|
||||
editor.ui.horizontal({
|
||||
spacing = editor.ui.SPACING.SMALL,
|
||||
children = {
|
||||
editor.ui.button({
|
||||
text = "Project Repository",
|
||||
on_pressed = function()
|
||||
editor.browse("https://github.com/Insality/druid")
|
||||
end
|
||||
}),
|
||||
editor.ui.button({
|
||||
text = "Open Quick API Reference",
|
||||
on_pressed = function()
|
||||
editor.browse("https://github.com/Insality/druid/blob/develop/api/quick_api_reference.md")
|
||||
end
|
||||
}),
|
||||
}
|
||||
}),
|
||||
|
||||
-- Sponsor section
|
||||
editor.ui.label({
|
||||
text = "Support the project:",
|
||||
color = editor.ui.COLOR.TEXT
|
||||
}),
|
||||
editor.ui.horizontal({
|
||||
spacing = editor.ui.SPACING.SMALL,
|
||||
children = {
|
||||
editor.ui.button({
|
||||
text = "❤️ Sponsor on GitHub",
|
||||
on_pressed = function()
|
||||
editor.browse("https://github.com/sponsors/Insality")
|
||||
end
|
||||
}),
|
||||
editor.ui.button({
|
||||
text = "☕ Ko-fi",
|
||||
on_pressed = function()
|
||||
editor.browse("https://ko-fi.com/insality")
|
||||
end
|
||||
}),
|
||||
editor.ui.button({
|
||||
text = "☕ Buy Me A Coffee",
|
||||
on_pressed = function()
|
||||
editor.browse("https://buymeacoffee.com/insality")
|
||||
end
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}),
|
||||
buttons = {
|
||||
editor.ui.dialog_button({
|
||||
text = "Cancel",
|
||||
cancel = true
|
||||
}),
|
||||
editor.ui.dialog_button({
|
||||
text = "Save",
|
||||
default = true,
|
||||
result = { template_path = template_path }
|
||||
})
|
||||
}
|
||||
})
|
||||
end)
|
||||
|
||||
local result = editor.ui.show_dialog(dialog_component({}))
|
||||
if result and result.template_path then
|
||||
-- Update the preferences
|
||||
editor.prefs.set("druid.widget_template_path", result.template_path)
|
||||
print("Widget template path updated to:", result.template_path)
|
||||
end
|
||||
|
||||
return result
|
||||
end
|
||||
|
||||
|
||||
return M
|
@ -1,24 +0,0 @@
|
||||
#!/bin/bash
|
||||
# @license MIT, Insality 2022
|
||||
# @source https://github.com/Insality/druid
|
||||
|
||||
echo "Run bash for $1"
|
||||
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
|
||||
|
||||
# Check if pip3 is installed
|
||||
if command -v pip3 &> /dev/null; then
|
||||
PIP_CMD="pip3"
|
||||
PYTHON_CMD="python3"
|
||||
else
|
||||
PIP_CMD="pip"
|
||||
PYTHON_CMD="python"
|
||||
fi
|
||||
|
||||
is_defree_installed=$($PIP_CMD list --disable-pip-version-check | grep -E "deftree")
|
||||
if [ -z "$is_defree_installed" ]; then
|
||||
echo "The python deftree is not installed. Please install it via"
|
||||
echo "$ $PIP_CMD install deftree"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
$PYTHON_CMD $1 $2
|
@ -1,44 +0,0 @@
|
||||
# @license MIT, Insality 2021
|
||||
# @source https://github.com/Insality/druid
|
||||
|
||||
import sys
|
||||
import deftree
|
||||
|
||||
def main():
|
||||
filename = sys.argv[1]
|
||||
print("Auto setup layers for file", filename)
|
||||
tree = deftree.parse(filename)
|
||||
root = tree.get_root()
|
||||
|
||||
layers = []
|
||||
for texture in root.iter_elements("textures"):
|
||||
layers.append(texture.get_attribute("name").value)
|
||||
|
||||
for fonts in root.iter_elements("fonts"):
|
||||
layers.append(fonts.get_attribute("name").value)
|
||||
|
||||
to_remove_layers = []
|
||||
for layer in root.iter_elements("layers"):
|
||||
to_remove_layers.append(layer)
|
||||
for layer in to_remove_layers:
|
||||
root.remove(layer)
|
||||
|
||||
for layer in layers:
|
||||
new_layer = root.add_element("layers")
|
||||
new_layer.add_attribute("name", layer)
|
||||
|
||||
for node in root.iter_elements("nodes"):
|
||||
texture = node.get_attribute("texture")
|
||||
font = node.get_attribute("font")
|
||||
|
||||
if texture and texture.value:
|
||||
layer = texture.value.split("/")[0]
|
||||
node.set_attribute("layer", layer)
|
||||
|
||||
if font:
|
||||
layer = font.value
|
||||
node.set_attribute("layer", layer)
|
||||
|
||||
tree.write()
|
||||
|
||||
main()
|
9
druid/templates/widget.lua.template
Normal file
9
druid/templates/widget.lua.template
Normal file
@ -0,0 +1,9 @@
|
||||
---@class widget.{COMPONENT_TYPE}: druid.widget
|
||||
local M = {}
|
||||
|
||||
|
||||
function M:init()
|
||||
end
|
||||
|
||||
|
||||
return M
|
@ -13,6 +13,7 @@ function M:init()
|
||||
-- self.druid:new_button([node_id], [callback])
|
||||
-- self.druid:new_text([node_id], [text])
|
||||
-- And all functions from component.lua file
|
||||
|
||||
self.root = self:get_node("root")
|
||||
self.button = self.druid:new_button("button", self.on_button, self)
|
||||
end
|
@ -14,7 +14,7 @@ function M:init()
|
||||
self.druid:new_hover("slider_back", nil, self.on_slider_back_hover)
|
||||
|
||||
for index = 1, 13 do
|
||||
self.druid:new_button("button" .. index .. "/root", self.on_button_click)
|
||||
self.druid:new_button("button" .. index .. "/root", self.on_button_click, index)
|
||||
end
|
||||
end
|
||||
|
||||
@ -30,8 +30,10 @@ function M:on_slider(value)
|
||||
end
|
||||
|
||||
|
||||
---@param params any
|
||||
---@param button druid.button
|
||||
function M:on_button_click(_, button)
|
||||
function M:on_button_click(params, button)
|
||||
print("on_button_click", params, button)
|
||||
local node = button.node
|
||||
self.scroll:scroll_to(gui.get_position(node))
|
||||
end
|
||||
|
@ -13,8 +13,8 @@ high_dpi = 1
|
||||
update_frequency = 60
|
||||
|
||||
[project]
|
||||
title = druid
|
||||
version = 1.0
|
||||
title = Druid
|
||||
version = 1.1
|
||||
publisher = Insality
|
||||
developer = Maksim Tuprikov
|
||||
custom_resources = /example/locales
|
||||
|
@ -79,7 +79,7 @@ Widgets are reusable UI components that encapsulate multiple **Druid** component
|
||||
|
||||
### Creating a Widget
|
||||
|
||||
Create a new Lua file for your widget class. This file better to be placed near the corresponding GUI file with the same name.
|
||||
Create a new Lua file for your widget class. This file better to be placed near the corresponding GUI file with the same name. You can use the Druid's editor script to create a widget by right-clicking on the GUI file in the editor or in "Edit" menu panel, while GUI file is opened.
|
||||
|
||||
Define `init` function to initialize the widget.
|
||||
|
||||
|
@ -5,6 +5,8 @@ Custom compomnents from 1.1 release are deprecated. Now we have a new way to cre
|
||||
|
||||
Custom components are will exists for more system things like basic components. You don't have to migrate to widgets.
|
||||
|
||||
The editor script for creating custom components is removed. Now you can create widgets with the new editor script.
|
||||
|
||||
Read more about widgets in [widgets.md](widgets.md)
|
||||
|
||||
## Overview
|
||||
@ -17,7 +19,7 @@ Every component is a child of the Basic Druid component. You can call methods of
|
||||
|
||||
### Basic Component Template
|
||||
|
||||
A basic custom component template looks like this (you can copy it from `/druid/templates/component.template.lua`):
|
||||
A basic custom component template looks like this (you can copy it from `/druid/templates/component.lua.template`):
|
||||
|
||||
```lua
|
||||
local component = require("druid.component")
|
||||
@ -58,7 +60,7 @@ end
|
||||
|
||||
### Full Component Template
|
||||
|
||||
A full custom component template looks like this (you can copy it from `/druid/templates/component_full.template.lua`):
|
||||
A full custom component template looks like this (you can copy it from `/druid/templates/component_full.lua.template`):
|
||||
|
||||
```lua
|
||||
local component = require("druid.component")
|
||||
@ -138,29 +140,6 @@ function init(self)
|
||||
end
|
||||
```
|
||||
|
||||
## Create Druid Component Editor Script
|
||||
|
||||
Druid provides an editor script to assist you in creating Lua files for your GUI scenes. You can find the commands under the menu `Edit -> Create Druid Component` when working with *.gui scenes.
|
||||
|
||||
The script analyzes the current GUI scene and generates a Lua file with stubs for all Druid components found. The output file is named after the current GUI scene and placed in the same directory. Note that the script does not override any existing *.lua files. If you want to regenerate a file, delete the previous version first.
|
||||
|
||||
The script requires `python` with `deftree` installed. If `deftree` is not installed, the instructions will be displayed in the console.
|
||||
|
||||
### Auto-Layout Components
|
||||
|
||||
The generator script also checks the current GUI scene for Druid components and creates stubs for them. If a node name starts with a specific keyword, the script generates component stubs in the Lua file. For example, nodes named `button` and `button_exit` will result in the generation of two Druid Button components with callback stubs.
|
||||
|
||||
Available keywords:
|
||||
- `button`: Adds a [Druid Button](01-components.md#button) component and generates the callback stub.
|
||||
- `text`: Adds a [Druid Text](01-components.md#text) component.
|
||||
- `lang_text`: Adds a [Druid Lang Text](01-components.md#lang-text) component.
|
||||
- `grid` or `static_grid`: Adds a [Druid Static Grid](01-components.md#static-grid) component. You should set up the Grid prefab for this component after generating the file.
|
||||
- `scroll_view`: Adds a [Druid Scroll](01-components.md#scroll) component. It also adds a `scroll_content` node with the same postfix. Ensure that it's the correct node.
|
||||
- `blocker`: Adds a [Druid Blocker](01-components.md#blocker) component.
|
||||
- `slider`: Adds a [Druid Slider](01-components.md#slider) component. You should adjust the end position of the Slider after generating the file.
|
||||
- `progress`: Adds a [Druid Progress](01-components.md#progress) component.
|
||||
- `timer`: Adds a [Druid Timer](01-components.md#timer) component.
|
||||
|
||||
## The Power of Using Templates
|
||||
|
||||
With Druid, you can use a single component but create and customize templates for it. Templates only need to match the component scheme. For example, you can have a component named `player_panel` and two GUI templates named `player_panel` and `enemy_panel` with different layouts. The same component script can be used for both templates.
|
||||
|
@ -168,3 +168,16 @@ function init(self)
|
||||
end
|
||||
```
|
||||
|
||||
## Create Druid Widget Editor Script
|
||||
|
||||
Druid provides an editor script to assist you in creating Lua files for your GUI scenes. You can find the commands under the menu `Edit -> Create Druid Widget` when working with *.gui scenes.
|
||||
|
||||
This script will create a new widget lua file with the same name and basic template for the widget.
|
||||
|
||||
The Druid provides two templates:
|
||||
|
||||
- `/druid/templates/widget.lua.template` - Basic template for the widget.
|
||||
- `/druid/templates/widget_full.lua.template` - Full template for the widget.
|
||||
|
||||
You can change the path to the template in the `[Druid] Settings` option in the `Edit` menu.
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user