move standalone projects into own directory

This commit is contained in:
2023-04-27 11:39:41 -06:00
parent 5d593e22b0
commit acda704057
369 changed files with 1170 additions and 5 deletions

View File

@@ -0,0 +1,182 @@
import sys.io.Process;
import haxe.xml.Access;
import sys.FileSystem;
import sys.io.File;
using StringTools;
class Setup {
public static function main() {
// Checking haxelib for godot externs.
final haxelibCheck = new Process("haxelib", ["path", "godot"]);
if (haxelibCheck.exitCode() != 0) {
Sys.print("haxelib");
return;
}
// Find unique csproj file.
var csproj = null;
for (entry in FileSystem.readDirectory(".")) {
if (FileSystem.isDirectory(entry)) {
continue;
}
if (entry.endsWith(".csproj")) {
if (csproj != null) {
Sys.print("multiple_csproj");
return;
}
csproj = entry;
}
}
if (csproj == null) {
Sys.print("csproj");
return;
}
// Dirty check.
final dirty = ["build.hxml", "build/", "scripts/"].filter(entry -> FileSystem.exists(entry));
if (dirty.length != 0) {
Sys.print("dirty:" + dirty.join(" "));
return;
}
// Update csproj file.
final csprojData = new Access(Xml.parse(File.getContent(csproj)));
final propertyGroup = csprojData.node.Project.node.PropertyGroup;
for (property in propertyGroup.elements) {
switch (property.name) {
case "AllowUnsafeBlocks", "TargetFramework":
propertyGroup.x.removeChild(property.x);
default:
}
}
propertyGroup.x.addChild(Xml.parse("<AllowUnsafeBlocks>true</AllowUnsafeBlocks>"));
propertyGroup.x.addChild(Xml.parse("<TargetFramework>netstandard2.1</TargetFramework>"));
File.saveContent(csproj, XmlPrinter.print(csprojData.x));
// Create project.
FileSystem.createDirectory("scripts");
File.saveContent("scripts/import.hx", "import godot.*;\nimport godot.GD.*;\n\nusing godot.Utils;\n");
File.saveContent("build.hxml", "--cs build\n--define net-ver=50\n--define no-compilation\n--define analyzer-optimize\n--class-path scripts\n--library godot\n--macro godot.Godot.buildProject()\n--dce full\n");
final ret = Sys.command("haxe", ["build.hxml"]);
if (ret == 0) {
Sys.print("ok");
}
}
}
// Modified version of haxe.xml.Printer
class XmlPrinter {
static public function print(xml:Xml) {
final printer = new XmlPrinter();
printer.writeNode(xml, "");
return printer.output.toString();
}
var output:StringBuf;
function new() {
output = new StringBuf();
}
function writeNode(value:Xml, indent:String) {
switch (value.nodeType) {
case CData:
write(indent + "<![CDATA[");
write(value.nodeValue);
write("]]>");
newline();
case Comment:
var commentContent = value.nodeValue;
commentContent = ~/[\n\r\t]+/g.replace(commentContent, "");
commentContent = "<!--" + commentContent + "-->";
write(indent);
write(StringTools.trim(commentContent));
newline();
case Document:
for (child in value) {
writeNode(child, indent);
}
case Element:
write(indent + "<");
write(value.nodeName);
for (attribute in value.attributes()) {
write(" " + attribute + "=\"");
write(StringTools.htmlEscape(value.get(attribute), true));
write("\"");
}
if (hasChildren(value)) {
final textOnly = hasTextOnly(value);
write(">");
if (!textOnly) {
newline();
}
for (child in value) {
writeNode(child, textOnly ? "" : (indent + " "));
}
write((textOnly ? "" : indent) + "</");
write(value.nodeName);
write(">");
newline();
} else {
write("/>");
newline();
}
case PCData:
final nodeValue = value.nodeValue.trim();
if (nodeValue.length != 0) {
write(indent + StringTools.htmlEscape(nodeValue));
}
case ProcessingInstruction:
write("<?" + value.nodeValue + "?>");
newline();
case DocType:
write("<!DOCTYPE " + value.nodeValue + ">");
newline();
}
}
inline function write(input:String) {
output.add(input);
}
inline function newline() {
output.add("\n");
}
function hasTextOnly(value:Xml):Bool {
for (child in value) {
switch (child.nodeType) {
case PCData:
default:
return false;
}
}
return true;
}
function hasChildren(value:Xml):Bool {
for (child in value) {
switch (child.nodeType) {
case Element, PCData:
return true;
case CData, Comment:
if (StringTools.ltrim(child.nodeValue).length != 0) {
return true;
}
case _:
}
}
return false;
}
}

View File

@@ -0,0 +1,16 @@
tool
class_name Building
extends Control
func build_haxe_project():
print("Building haxe project...");
var res = OS.execute("haxe", ["build.hxml"], true);
$ProgressBar.value = 1
yield(VisualServer, 'frame_post_draw')
print("Project builded with code: ", res)
queue_free()

View File

@@ -0,0 +1,6 @@
tool
class_name HaxePluginConstants
const SETTING_HIDE_NATIVE_SCRIPT_FIELD := "haxe/hide_native_script_field"
const SETTING_EXTERNAL_EDITOR := "haxe/external_editor"
const BUILD_ON_PLAY := "haxe/build_on_play"

View File

@@ -0,0 +1,164 @@
tool
class_name HaxePluginEditorProperty
extends EditorProperty
var haxe_icon := preload("res://addons/haxe/icons/haxe.svg")
var new_script_dialog := preload("res://addons/haxe/scenes/new_script.tscn")
var base:Control
var object:Node
var script_name := ""
var script_path := ""
var b:MenuButton
var b2:MenuButton
func setup(base:Control, object:Node) -> void:
self.base = base
self.object = object
label = "Haxe Script"
var h := HBoxContainer.new()
# TODO revert icon
b = MenuButton.new()
b.flat = true
h.add_child(b)
b2 = MenuButton.new()
b2.flat = true
b2.icon = base.get_icon("GuiDropdown", "EditorIcons")
h.add_child(b2)
add_child(h)
update_property()
func setup_menu(base:Control, button:MenuButton, has_script:bool) -> void:
if not button.is_connected("gui_input", self, "on_menu_gui"):
button.connect("gui_input", self, "on_menu_gui")
var menu := button.get_popup()
for i in range(menu.get_item_count()):
menu.remove_item(0)
if not has_script:
menu.add_icon_item(base.get_icon("ScriptCreate", "EditorIcons"), "New Haxe Script")
else:
menu.add_icon_item(base.get_icon("ScriptRemove", "EditorIcons"), "Remove Haxe Script")
menu.add_icon_item(base.get_icon("Load", "EditorIcons"), "Load Haxe Script")
if has_script:
menu.add_icon_item(base.get_icon("Edit", "EditorIcons"), "Edit")
if not menu.is_connected("index_pressed", self, "on_popup_select"):
menu.connect("index_pressed", self, "on_popup_select", [has_script])
func on_menu_gui(event:InputEvent) -> void:
# If is right click then pretend it's a left click
if event is InputEventMouseButton and event.pressed and event.button_index == 2:
event.button_index = 1
func on_popup_select(id:int, has_script:bool) -> void:
if id == 0: # New/Remove
if not has_script: # New
var dialog := new_script_dialog.instance()
dialog.setup(base, object.get_class(), object.get_path().get_name(object.get_path().get_name_count() - 1))
dialog.theme = base.theme
dialog.connect("create", self, "on_create")
base.add_child(dialog)
dialog.popup_centered()
else: # Remove
object.remove_meta("haxe_script")
object.set_script(null)
elif id == 1: # Load
var dialog := EditorFileDialog.new()
base.add_child(dialog)
dialog.access = EditorFileDialog.ACCESS_RESOURCES
dialog.current_dir = "res://scripts/"
dialog.mode = EditorFileDialog.MODE_OPEN_FILE
dialog.theme = base.theme
dialog.add_filter("*.hx ; Haxe script")
dialog.connect("file_selected", self, "on_load_file")
dialog.popup_centered_ratio()
elif id == 2: # Edit
open_file(script_path)
else:
print("Unknown entry: ", id)
func on_create(is_load:bool, class_value:String, path_value:String) -> void:
if not is_load:
var f := path_value.find_last("/")
var name := path_value.substr(f + 1, path_value.find_last(".hx") - f - 1)
var d := path_value.substr(14).split("/")
d.remove(d.size() - 1)
var pack := d.join(".")
if not pack.empty():
pack = " " + pack;
if class_value == name:
class_value = "godot." + class_value
var file := File.new()
file.open(path_value, File.WRITE)
file.store_string("package" + pack + ";\n\nclass " + name + " extends " + class_value + " {\n}\n")
file.close()
open_file(path_value)
on_load_file(path_value)
func open_file(path:String) -> void:
var editor:String = ProjectSettings.get(HaxePluginConstants.SETTING_EXTERNAL_EDITOR)
if editor == "None":
pass
elif editor == "VSCode":
OS.execute("code", [ProjectSettings.globalize_path(path)], false)
else:
print("Unknown external editor: " + editor)
func on_load_file(path:String) -> void:
object.set_meta("haxe_script", path)
var cs_path := path.replace("res://scripts", "")
var p := cs_path.find_last("/")
var name := cs_path.substr(p, cs_path.length() - 2 - p) + "cs"
cs_path = "build/src" + cs_path.substr(0, p)
var d := Directory.new()
d.make_dir_recursive(cs_path)
var file_path := "res://" + cs_path + name
var cs_file := File.new()
if not cs_file.file_exists(file_path):
cs_file.open(file_path, File.WRITE)
cs_file.store_string("\n")
cs_file.close()
object.set_script(load(file_path))
func update_property() -> void:
var script_name := "[empty]"
if object.has_meta("haxe_script"):
if not object.get_script():
object.remove_meta("haxe_script")
else:
script_path = object.get_meta("haxe_script")
var p := script_path.find_last("/")
script_name = script_path.substr(p + 1)
var has_script := script_path != ""
b.size_flags_horizontal = MenuButton.SIZE_EXPAND_FILL
if has_script:
b.icon = haxe_icon
b.text = script_name
b.hint_tooltip = script_path
setup_menu(base, b, has_script)
setup_menu(base, b2, has_script)
func get_tooltip_text() -> String:
return "Haxe Script"

View File

@@ -0,0 +1,103 @@
tool
class_name HaxePlugin
extends EditorPlugin
var about_dialog := preload("res://addons/haxe/scenes/about.tscn")
var tab := preload("res://addons/haxe/scenes/tab.tscn").instance()
var build_dialog := preload("res://addons/haxe/scenes/building.tscn")
var inspector_plugin:HaxePluginInspectorPlugin
func _enter_tree() -> void:
var base := get_editor_interface().get_base_control()
# Init
setup_settings()
# Inspector plugin
inspector_plugin = HaxePluginInspectorPlugin.new()
inspector_plugin.setup(base)
add_inspector_plugin(inspector_plugin)
# Tool menu entry
var menu := PopupMenu.new()
menu.add_item("About")
menu.add_item("Setup")
menu.connect("index_pressed", self, "on_menu")
add_tool_submenu_item("Haxe", menu)
# Bottom dock tab
tab.setup(base)
add_control_to_bottom_panel(tab, "Haxe")
func _exit_tree() -> void:
# TODO tab.gd still leaks?
remove_control_from_bottom_panel(tab)
tab.queue_free()
remove_tool_menu_item("Haxe")
remove_inspector_plugin(inspector_plugin)
func setup_settings() -> void:
if not ProjectSettings.has_setting(HaxePluginConstants.SETTING_HIDE_NATIVE_SCRIPT_FIELD):
ProjectSettings.set_setting(HaxePluginConstants.SETTING_HIDE_NATIVE_SCRIPT_FIELD, true)
if not ProjectSettings.has_setting(HaxePluginConstants.SETTING_EXTERNAL_EDITOR):
ProjectSettings.set_setting(HaxePluginConstants.SETTING_EXTERNAL_EDITOR, "VSCode")
ProjectSettings.add_property_info({
"name": HaxePluginConstants.SETTING_EXTERNAL_EDITOR,
"type": TYPE_STRING,
"hint": PROPERTY_HINT_ENUM,
"hint_string": "None,VSCode"
});
if not ProjectSettings.has_setting(HaxePluginConstants.BUILD_ON_PLAY):
ProjectSettings.set_setting(HaxePluginConstants.BUILD_ON_PLAY, false)
func on_menu(id:int) -> void:
var theme := get_editor_interface().get_base_control().theme
if id == 0: # About
var dialog := about_dialog.instance()
add_child(dialog)
dialog.theme = theme
dialog.popup_centered()
elif id == 1: # Setup
var output := []
OS.execute("haxe", ["--class-path", "addons/haxe/scripts", "--run", "Setup"], true, output, true)
var dialog := AcceptDialog.new()
add_child(dialog)
if output.size() != 1:
dialog.dialog_text = "Unknown error:\n" + PoolStringArray(output).join("\n")
elif "command not found" in output[0].to_lower():
dialog.dialog_text = "Haxe command not found."
elif output[0] == "haxelib":
dialog.dialog_text = "Godot externs not found.\nRun 'haxelib install godot' first."
elif output[0] == "multiple_csproj":
dialog.dialog_text = "Multiple C# solutions found.\nCannot setup."
elif output[0] == "csproj":
dialog.dialog_text = "C# solution not found (.csproj file).\nYou need to setup Godot Mono first:\nProject -> Tools -> Mono -> Create C# solution."
elif output[0].begins_with("dirty:"):
dialog.dialog_text = "Project already contains: " + output[0].substr(6) + "\nTo avoid data loss the setup wasn't run."
elif output[0] == "ok":
dialog.dialog_text = "Setup successful."
else:
dialog.dialog_text = "Unknown error: " + output[0]
dialog.theme = theme
dialog.window_title = "Haxe Setup"
dialog.popup_centered()
else:
print("Unknown menu: ", id)
func _input(event):
if event is InputEventKey and ProjectSettings.get_setting(HaxePluginConstants.BUILD_ON_PLAY):
if event.scancode == KEY_F5 or event.scancode == KEY_F6 and event.echo:
var dialog = build_dialog.instance()
add_child(dialog)
yield(VisualServer, 'frame_post_draw')
dialog.call("build_haxe_project")

View File

@@ -0,0 +1,22 @@
tool
class_name HaxePluginInspectorPlugin
extends EditorInspectorPlugin
var base:Control
func setup(base:Control) -> void:
self.base = base
#warning-ignore:unused_argument
func can_handle(object:Object) -> bool:
return true
#warning-ignore:unused_argument
func parse_property(object:Object, type:int, path:String, hint:int, hint_text:String, usage:int) -> bool:
if object is Node and type == TYPE_OBJECT and path == "script":
var e := HaxePluginEditorProperty.new()
e.setup(base, object)
add_custom_control(e)
return ProjectSettings.get_setting(HaxePluginConstants.SETTING_HIDE_NATIVE_SCRIPT_FIELD)
return false

View File

@@ -0,0 +1,157 @@
tool
extends WindowDialog
signal create(is_load, class_value, path_value)
var base:Control
var cancel_button:Button
var create_button:Button
var class_valid := true
var path_valid := true
var name_valid := true
var name_warning := false
var extension_valid := true
var is_load := false
var class_value := ""
var path_value := ""
func setup(base:Control, class_value:String, name:String) -> void:
self.base = base
var left := $MarginContainer/VBoxContainer/Buttons/Left
var right := $MarginContainer/VBoxContainer/Buttons/Right
if OS.get_name() == "Windows" or OS.get_name() == "UWP":
setup_buttons(right, left)
else:
setup_buttons(left, right)
$MarginContainer/VBoxContainer/GridContainer/ClassValue.connect("text_changed", self, "on_class")
$MarginContainer/VBoxContainer/GridContainer/Path/PathValue.connect("text_changed", self, "on_path")
var path_button := $MarginContainer/VBoxContainer/GridContainer/Path/Load
path_button.icon = base.get_icon("Folder", "EditorIcons")
path_button.connect("button_down", self, "on_folder")
on_class(class_value)
on_path("res://scripts/" + name.substr(0, 1).to_upper() + name.substr(1) + ".hx")
func setup_buttons(cancel:Button, create:Button) -> void:
cancel.text = "Cancel"
cancel.connect("button_down", self, "on_cancel")
cancel_button = cancel
create.text = "Create"
create.connect("button_down", self, "on_create")
create_button = create
func on_cancel() -> void:
hide()
func on_create() -> void:
hide()
emit_signal("create", is_load, class_value, path_value)
func on_class(value:String) -> void:
class_value = value
class_valid = ClassDB.class_exists(value) and ClassDB.can_instance(value)
revalidate()
func on_folder() -> void:
var file := path_value
file = file.substr(file.find_last("/") + 1)
var dialog := EditorFileDialog.new()
base.add_child(dialog)
dialog.access = EditorFileDialog.ACCESS_RESOURCES
dialog.current_dir = "res://scripts/"
dialog.current_file = file
dialog.disable_overwrite_warning = true
dialog.theme = base.theme
dialog.window_title = "Open Haxe Script / Choose Location"
dialog.add_filter("*.hx ; Haxe script")
dialog.connect("file_selected", self, "on_path")
dialog.get_ok().text = "Open"
dialog.popup_centered_ratio()
func on_path(fullpath:String) -> void:
path_value = fullpath
var dir_p := fullpath.find_last("/")
var ext_p := fullpath.find_last(".")
var path := ""
var file := ""
if dir_p < ext_p:
path = fullpath.substr(0, dir_p)
file = fullpath.substr(dir_p + 1)
else:
path = fullpath
var d := Directory.new()
var f := File.new()
is_load = f.file_exists(path_value)
extension_valid = file.ends_with(".hx")
name_valid = ext_p < fullpath.length() - 1 and ext_p > dir_p + 1
name_warning = name_valid && extension_valid && isBuiltin(file.substr(0, file.length() - 3))
path_valid = path.begins_with("res://") and d.dir_exists(path)
revalidate()
func isBuiltin(name:String) -> bool:
var haxeGodotBuiltins = ["Action", "CustomSignal", "CustomSignalUsings", "Godot", "Nullable1", "Signal", "SignalUsings", "Utils"]
return ClassDB.class_exists(name) or haxeGodotBuiltins.has(name)
func revalidate() -> void:
var text_edit := $MarginContainer/VBoxContainer/TextEdit
text_edit.bbcode_text = ""
var valid_color := Color(0.062775, 0.730469, 0.062775)
var error_color := Color(0.820312, 0.028839, 0.028839)
var warning_color := Color(0.9375, 0.537443, 0.06958)
if not class_valid:
text_edit.push_color(error_color)
text_edit.append_bbcode("- Invalid inherited parent name.\n\n")
text_edit.pop()
elif not extension_valid:
text_edit.push_color(error_color)
text_edit.append_bbcode("- Invalid extension.\n\n")
text_edit.pop()
elif not path_valid:
text_edit.push_color(error_color)
text_edit.append_bbcode("- Invalid path.\n\n")
text_edit.pop()
elif not name_valid:
text_edit.push_color(error_color)
text_edit.append_bbcode("- Invalid filename.\n\n")
text_edit.pop()
else:
text_edit.push_color(valid_color)
text_edit.append_bbcode("- Haxe script path is valid.\n\n")
if is_load:
text_edit.append_bbcode("- Will load an existing Haxe script.\n\n")
else:
text_edit.append_bbcode("- Will create a new Haxe script.\n\n")
text_edit.pop()
if name_warning:
text_edit.push_color(warning_color)
text_edit.append_bbcode("Warning: Having the script name be the same as a built-in type is usually not desired.\n\n")
text_edit.pop()
var class_edit:LineEdit = $MarginContainer/VBoxContainer/GridContainer/ClassValue
var class_edit_column := class_edit.caret_position
class_edit.text = class_value
class_edit.caret_position = class_edit_column if class_edit_column <= class_value.length() else class_value.length()
var path_edit:LineEdit = $MarginContainer/VBoxContainer/GridContainer/Path/PathValue
var path_edit_column := path_edit.caret_position
path_edit.text = path_value
path_edit.caret_position = path_edit_column if path_edit_column <= path_value.length() else path_value.length()

View File

@@ -0,0 +1,67 @@
tool
extends Control
onready var button := $VBoxContainer/Button
onready var text_log := $VBoxContainer/TextLog
var base:Control
var icon := 0
var icons := []
var mutex := Mutex.new()
var output := []
var time := 0.0
var thread:Thread = null
func setup(base:Control) -> void:
self.base = base
for i in range(8):
icons.append(base.get_icon("Progress%s"%(i + 1), "EditorIcons"))
func _ready() -> void:
button.connect("button_down", self, "build_haxe_project")
func build_haxe_project() -> void:
if thread != null:
return
thread = Thread.new()
button.icon = icons[0]
button.text = "Building Haxe Project ..."
icon = 0
text_log.text = ""
time = 0.0
output = []
thread.start(self, "run_thread")
func _process(delta:float) -> void:
if thread != null:
update_log()
time += delta
if time > 0.1:
time = 0
icon = (icon + 1) % 8
button.icon = icons[icon]
func run_thread(userdata) -> void:
var ret := OS.execute("haxe", ["build.hxml"], true, output, true)
update_log()
button.icon = base.get_icon("StatusSuccess" if ret == 0 else "StatusError", "EditorIcons")
button.text = "Build Haxe Project"
call_deferred("end_thread")
func end_thread() -> void:
thread.wait_to_finish()
thread = null
func update_log() -> void:
mutex.lock()
text_log.text = PoolStringArray(output).join("\n")
mutex.unlock()
func _exit_tree():
if thread != null:
thread.wait_to_finish()
thread = null