diff --git a/flumi/Scripts/B9/HTMLParser.gd b/flumi/Scripts/B9/HTMLParser.gd index 8911e73..edd3535 100644 --- a/flumi/Scripts/B9/HTMLParser.gd +++ b/flumi/Scripts/B9/HTMLParser.gd @@ -43,12 +43,19 @@ class HTMLElement: func is_inline_element() -> bool: return tag_name in ["b", "i", "u", "small", "mark", "code", "span", "a", "input"] +class HTMLBody extends HTMLElement: + var body_node: Node = null + + func _init(): + super._init("body") + class ParseResult: var root: HTMLElement var all_elements: Array[HTMLElement] = [] var errors: Array[String] = [] var css_parser: CSSParser = null var inline_styles: Dictionary = {} + var dom_nodes: Dictionary = {} func _init(): root = HTMLElement.new("document") @@ -271,6 +278,11 @@ func find_by_id(element_id: String) -> HTMLElement: return null +func register_dom_node(element: HTMLElement, node: Control) -> void: + var element_id = element.get_id() + if element_id.length() > 0: + parse_result.dom_nodes[element_id] = node + func find_first(tag: String, attribute: String = "") -> HTMLElement: var results = find_all(tag, attribute) return results[0] if results.size() > 0 else null @@ -334,6 +346,24 @@ func get_all_images() -> Array[String]: func get_all_scripts() -> Array[String]: return get_attribute_values("script", "src") +func process_scripts(lua_api: LuaAPI, lua_vm) -> void: + if not lua_api or not lua_vm: + print("Warning: Lua API or VM not available for script processing") + return + + lua_api.dom_parser = self + + for script_element in find_all("script"): + var src = script_element.get_attribute("src") + var inline_code = script_element.text_content.strip_edges() + + if not src.is_empty(): + # TODO: add support for external Lua script + print("External script found: ", src) + elif not inline_code.is_empty(): + print("Executing inline Lua script") + lua_api.execute_lua_script(inline_code, lua_vm) + func get_all_stylesheets() -> Array[String]: return get_attribute_values("style", "src") diff --git a/flumi/Scripts/B9/Lua.gd b/flumi/Scripts/B9/Lua.gd new file mode 100644 index 0000000..b660250 --- /dev/null +++ b/flumi/Scripts/B9/Lua.gd @@ -0,0 +1,371 @@ +class_name LuaAPI +extends Node + +class EventSubscription: + var id: int + var element_id: String + var event_name: String + var callback_ref: int + var vm: LuauVM + var lua_api: LuaAPI + var connected_signal: String = "" + var connected_node: Node = null + +var dom_parser: HTMLParser +var event_subscriptions: Dictionary = {} +var next_subscription_id: int = 1 +var next_callback_ref: int = 1 + +func _gurt_select_handler(vm: LuauVM) -> int: + var selector: String = vm.luaL_checkstring(1) + + var element_id = "" + if selector.begins_with("#"): + element_id = selector.substr(1) + else: + vm.lua_pushnil() + return 1 + + var dom_node = dom_parser.parse_result.dom_nodes.get(element_id, null) + if not dom_node: + vm.lua_pushnil() + return 1 + + vm.lua_newtable() + vm.lua_pushstring(element_id) + vm.lua_setfield(-2, "_element_id") + + add_element_methods(vm) + return 1 + +func add_element_methods(vm: LuauVM) -> void: + vm.lua_pushcallable(_element_set_text_handler, "element.set_text") + vm.lua_setfield(-2, "set_text") + + vm.lua_pushcallable(_element_get_text_handler, "element.get_text") + vm.lua_setfield(-2, "get_text") + + vm.lua_pushcallable(_element_on_event_handler, "element.on") + vm.lua_setfield(-2, "on") + +# Element manipulation handlers +func _element_set_text_handler(vm: LuauVM) -> int: + vm.luaL_checktype(1, vm.LUA_TTABLE) + var text: String = vm.luaL_checkstring(2) + + vm.lua_getfield(1, "_element_id") + var element_id: String = vm.lua_tostring(-1) + vm.lua_pop(1) + + var dom_node = dom_parser.parse_result.dom_nodes.get(element_id, null) + var text_node = get_dom_node(dom_node, "text") + if text_node: + text_node.text = text + return 0 + +func _element_get_text_handler(vm: LuauVM) -> int: + vm.luaL_checktype(1, vm.LUA_TTABLE) + + vm.lua_getfield(1, "_element_id") + var element_id: String = vm.lua_tostring(-1) + vm.lua_pop(1) + + var dom_node = dom_parser.parse_result.dom_nodes.get(element_id, null) + var text = "" + + var text_node = get_dom_node(dom_node, "text") + if text_node: + if text_node.has_method("get_text"): + text = text_node.get_text() + else: + text = text_node.text + + vm.lua_pushstring(text) + return 1 + +# Event system handlers +func _element_on_event_handler(vm: LuauVM) -> int: + vm.luaL_checktype(1, vm.LUA_TTABLE) + var event_name: String = vm.luaL_checkstring(2) + vm.luaL_checktype(3, vm.LUA_TFUNCTION) + + vm.lua_getfield(1, "_element_id") + var element_id: String = vm.lua_tostring(-1) + vm.lua_pop(1) + + var dom_node = dom_parser.parse_result.dom_nodes.get(element_id, null) + if not dom_node: + vm.lua_pushnil() + return 1 + + var subscription = _create_subscription(vm, element_id, event_name) + event_subscriptions[subscription.id] = subscription + + var signal_node = get_dom_node(dom_node, "signal") + var success = LuaEventUtils.connect_element_event(signal_node, event_name, subscription) + + return _handle_subscription_result(vm, subscription, success) + +func _body_on_event_handler(vm: LuauVM) -> int: + vm.luaL_checktype(1, vm.LUA_TTABLE) + var event_name: String = vm.luaL_checkstring(2) + vm.luaL_checktype(3, vm.LUA_TFUNCTION) + + var subscription = _create_subscription(vm, "body", event_name) + event_subscriptions[subscription.id] = subscription + + var success = LuaEventUtils.connect_body_event(event_name, subscription, self) + + return _handle_subscription_result(vm, subscription, success) + +func _subscription_unsubscribe_handler(vm: LuauVM) -> int: + vm.luaL_checktype(1, vm.LUA_TTABLE) + + vm.lua_getfield(1, "_subscription_id") + var subscription_id: int = vm.lua_tointeger(-1) + vm.lua_pop(1) + + var subscription = event_subscriptions.get(subscription_id, null) + if subscription: + LuaEventUtils.disconnect_subscription(subscription, self) + event_subscriptions.erase(subscription_id) + vm.lua_pushnil() + vm.lua_rawseti(vm.LUA_REGISTRYINDEX, subscription.callback_ref) + + return 0 + +# Subscription management +func _create_subscription(vm: LuauVM, element_id: String, event_name: String) -> EventSubscription: + var subscription_id = next_subscription_id + next_subscription_id += 1 + var callback_ref = next_callback_ref + next_callback_ref += 1 + + vm.lua_pushvalue(3) + vm.lua_rawseti(vm.LUA_REGISTRYINDEX, callback_ref) + + var subscription = EventSubscription.new() + subscription.id = subscription_id + subscription.element_id = element_id + subscription.event_name = event_name + subscription.callback_ref = callback_ref + subscription.vm = vm + subscription.lua_api = self + + return subscription + +func _handle_subscription_result(vm: LuauVM, subscription: EventSubscription, success: bool) -> int: + if success: + vm.lua_newtable() + vm.lua_pushinteger(subscription.id) + vm.lua_setfield(-2, "_subscription_id") + + vm.lua_pushcallable(_subscription_unsubscribe_handler, "subscription.unsubscribe") + vm.lua_setfield(-2, "unsubscribe") + + return 1 + else: + vm.lua_pushnil() + vm.lua_rawseti(vm.LUA_REGISTRYINDEX, subscription.callback_ref) + event_subscriptions.erase(subscription.id) + vm.lua_pushnil() + return 1 + +# Event callbacks +func _on_event_triggered(subscription: EventSubscription) -> void: + if not event_subscriptions.has(subscription.id): + return + + subscription.vm.lua_rawgeti(subscription.vm.LUA_REGISTRYINDEX, subscription.callback_ref) + if subscription.vm.lua_isfunction(-1): + if subscription.vm.lua_pcall(0, 0, 0) != subscription.vm.LUA_OK: + print("GURT ERROR in event callback: ", subscription.vm.lua_tostring(-1)) + subscription.vm.lua_pop(1) + else: + subscription.vm.lua_pop(1) + +func _on_gui_input_click(event: InputEvent, subscription: EventSubscription) -> void: + if not event_subscriptions.has(subscription.id): + return + + if event is InputEventMouseButton: + var mouse_event = event as InputEventMouseButton + if mouse_event.button_index == MOUSE_BUTTON_LEFT and mouse_event.pressed: + _execute_lua_callback(subscription) + +func _on_gui_input_mouse_universal(event: InputEvent, signal_node: Node) -> void: + if event is InputEventMouseButton: + var mouse_event = event as InputEventMouseButton + if mouse_event.button_index == MOUSE_BUTTON_LEFT: + # Find all subscriptions for this node with mouse events + for subscription_id in event_subscriptions: + var subscription = event_subscriptions[subscription_id] + if subscription.connected_node == signal_node and subscription.connected_signal == "gui_input_mouse": + var should_trigger = false + if subscription.event_name == "mousedown" and mouse_event.pressed: + should_trigger = true + elif subscription.event_name == "mouseup" and not mouse_event.pressed: + should_trigger = true + + if should_trigger: + _execute_lua_callback(subscription) + +# Event callback handlers +func _on_gui_input_mousemove(event: InputEvent, subscription: EventSubscription) -> void: + if not event_subscriptions.has(subscription.id): + return + + if event is InputEventMouseMotion: + var mouse_event = event as InputEventMouseMotion + _handle_mousemove_event(mouse_event, subscription) + +func _on_focus_gui_input(event: InputEvent, subscription: EventSubscription) -> void: + if not event_subscriptions.has(subscription.id): + return + + if event is InputEventMouseButton: + var mouse_event = event as InputEventMouseButton + if mouse_event.button_index == MOUSE_BUTTON_LEFT and mouse_event.pressed: + if subscription.event_name == "focusin": + _execute_lua_callback(subscription) + +func _on_body_mouse_enter(subscription: EventSubscription) -> void: + if not event_subscriptions.has(subscription.id): + return + + if subscription.event_name == "mouseenter": + _execute_lua_callback(subscription) + +func _on_body_mouse_exit(subscription: EventSubscription) -> void: + if not event_subscriptions.has(subscription.id): + return + + if subscription.event_name == "mouseexit": + _execute_lua_callback(subscription) + +func _execute_lua_callback(subscription: EventSubscription, args: Array = []) -> void: + subscription.vm.lua_rawgeti(subscription.vm.LUA_REGISTRYINDEX, subscription.callback_ref) + if subscription.vm.lua_isfunction(-1): + for arg in args: + subscription.vm.lua_pushvariant(arg) + + if subscription.vm.lua_pcall(args.size(), 0, 0) != subscription.vm.LUA_OK: + print("GURT ERROR in callback: ", subscription.vm.lua_tostring(-1)) + subscription.vm.lua_pop(1) + else: + subscription.vm.lua_pop(1) + +# Global input processing +func _input(event: InputEvent) -> void: + if event is InputEventKey: + var key_event = event as InputEventKey + for subscription_id in event_subscriptions: + var subscription = event_subscriptions[subscription_id] + if subscription.element_id == "body" and subscription.connected_signal == "input": + var should_trigger = false + match subscription.event_name: + "keydown": + should_trigger = key_event.pressed + "keyup": + should_trigger = not key_event.pressed + "keypress": + should_trigger = key_event.pressed + + if should_trigger: + var key_info = { + "key": OS.get_keycode_string(key_event.keycode), + "keycode": key_event.keycode, + "ctrl": key_event.ctrl_pressed, + "shift": key_event.shift_pressed, + "alt": key_event.alt_pressed + } + _execute_lua_callback(subscription, [key_info]) + + elif event is InputEventMouseMotion: + var mouse_event = event as InputEventMouseMotion + for subscription_id in event_subscriptions: + var subscription = event_subscriptions[subscription_id] + if subscription.element_id == "body" and subscription.connected_signal == "input_mousemove": + if subscription.event_name == "mousemove": + _handle_mousemove_event(mouse_event, subscription) + +func _handle_mousemove_event(mouse_event: InputEventMouseMotion, subscription: EventSubscription) -> void: + # TODO: pass reference instead of hardcoded path + var body_container = get_node("/root/Main").website_container + + if body_container.get_parent() is MarginContainer: + body_container = body_container.get_parent() + + if not body_container: + return + + var container_rect = body_container.get_global_rect() + var local_x = mouse_event.global_position.x - container_rect.position.x + var local_y = mouse_event.global_position.y - container_rect.position.y + + # Only provide coordinates if mouse is within the container bounds + if local_x >= 0 and local_y >= 0 and local_x <= container_rect.size.x and local_y <= container_rect.size.y: + var mouse_info = { + "x": local_x, + "y": local_y, + "deltaX": mouse_event.relative.x, + "deltaY": mouse_event.relative.y + } + _execute_lua_callback(subscription, [mouse_info]) + +# DOM node utilities +func get_dom_node(node: Node, purpose: String = "general") -> Node: + if not node: + return null + + if node is MarginContainer: + node = node.get_child(0) + + match purpose: + "signal": + if node is HTMLButton: + return node.get_node_or_null("ButtonNode") + elif node is RichTextLabel: + return node + elif node.has_method("get") and node.get("rich_text_label"): + return node.get("rich_text_label") + elif node.get_node_or_null("RichTextLabel"): + return node.get_node_or_null("RichTextLabel") + else: + return node + "text": + if node.has_method("set_text") and node.has_method("get_text"): + return node + elif node is RichTextLabel: + return node + elif node.has_method("get") and node.get("rich_text_label"): + return node.get("rich_text_label") + elif node.get_node_or_null("RichTextLabel"): + return node.get_node_or_null("RichTextLabel") + else: + if "text" in node: + return node + return null + "general": + if node is HTMLButton: + return node.get_node_or_null("ButtonNode") + elif node is RichTextLabel: + return node + elif node.get_node_or_null("RichTextLabel"): + return node.get_node_or_null("RichTextLabel") + else: + return node + + return node + +# Main execution function +func execute_lua_script(code: String, vm: LuauVM): + vm.open_libraries([vm.LUA_BASE_LIB, vm.LUA_BIT32_LIB, + vm.LUA_COROUTINE_LIB, vm.LUA_MATH_LIB, vm.LUA_UTF8_LIB, + vm.LUA_TABLE_LIB, vm.LUA_STRING_LIB, vm.LUA_VECTOR_LIB]) + + LuaFunctionUtils.setup_gurt_api(vm, self, dom_parser) + + if vm.lua_dostring(code) != vm.LUA_OK: + print("LUA ERROR: ", vm.lua_tostring(-1)) + vm.lua_pop(1) diff --git a/flumi/Scripts/B9/Lua.gd.uid b/flumi/Scripts/B9/Lua.gd.uid new file mode 100644 index 0000000..cd3c752 --- /dev/null +++ b/flumi/Scripts/B9/Lua.gd.uid @@ -0,0 +1 @@ +uid://biv2ch1mi3lnn diff --git a/flumi/Scripts/Constants.gd b/flumi/Scripts/Constants.gd index e5a5a64..9d20b85 100644 --- a/flumi/Scripts/Constants.gd +++ b/flumi/Scripts/Constants.gd @@ -100,7 +100,7 @@ var HTML_CONTENT2 = """
""".to_utf8_buffer() -var HTML_CONTENT = """ +var HTML_CONTENTbv = """This paragraph should be red and bold (h1 + p)
-This paragraph should be blue (h1 ~ p)
+This paragraph should be blue (h1 ~ p)
This paragraph should be purple and bold (div p and .outer-div > p)
This paragraph should be purple but not bold (div p only)
@@ -452,20 +451,20 @@ var HTML_CONTENT_S = """ This span should have light red bg (h3 ~ span) This span should also have light red bg (h3 ~ span) -Regular paragraph in container
This page demonstrates the GURT Lua API in action.
+ +Click the button
+ +Move your mouse
+ +Move mouse over Button
+ +Type something
+ +""".to_utf8_buffer() diff --git a/flumi/Scripts/Utils/Lua/Event.gd b/flumi/Scripts/Utils/Lua/Event.gd new file mode 100644 index 0000000..d794a8f --- /dev/null +++ b/flumi/Scripts/Utils/Lua/Event.gd @@ -0,0 +1,161 @@ +class_name LuaEventUtils +extends RefCounted + +static func connect_element_event(signal_node: Node, event_name: String, subscription) -> bool: + if not signal_node: + return false + + match event_name: + "click": + if signal_node.has_signal("pressed"): + signal_node.pressed.connect(subscription.lua_api._on_event_triggered.bind(subscription)) + subscription.connected_signal = "pressed" + subscription.connected_node = signal_node if signal_node != subscription.lua_api.get_dom_node(signal_node.get_parent(), "signal") else null + return true + elif signal_node is Control: + signal_node.gui_input.connect(subscription.lua_api._on_gui_input_click.bind(subscription)) + subscription.connected_signal = "gui_input" + subscription.connected_node = signal_node + return true + "mousedown", "mouseup": + if signal_node is Control: + # Check if we already have a mouse handler connected to this node + var already_connected = false + for existing_id in subscription.lua_api.event_subscriptions: + var existing_sub = subscription.lua_api.event_subscriptions[existing_id] + if existing_sub.connected_node == signal_node and existing_sub.connected_signal == "gui_input_mouse": + already_connected = true + break + + if not already_connected: + signal_node.gui_input.connect(subscription.lua_api._on_gui_input_mouse_universal.bind(signal_node)) + + subscription.connected_signal = "gui_input_mouse" + subscription.connected_node = signal_node + return true + "mousemove": + if signal_node is Control: + signal_node.gui_input.connect(subscription.lua_api._on_gui_input_mousemove.bind(subscription)) + subscription.connected_signal = "gui_input_mousemove" + subscription.connected_node = signal_node + return true + "mouseenter": + if signal_node is Control and signal_node.has_signal("mouse_entered"): + signal_node.mouse_entered.connect(subscription.lua_api._on_event_triggered.bind(subscription)) + subscription.connected_signal = "mouse_entered" + subscription.connected_node = signal_node + return true + "mouseexit": + if signal_node is Control and signal_node.has_signal("mouse_exited"): + signal_node.mouse_exited.connect(subscription.lua_api._on_event_triggered.bind(subscription)) + subscription.connected_signal = "mouse_exited" + subscription.connected_node = signal_node + return true + "focusin": + if signal_node is Control: + signal_node.focus_mode = Control.FOCUS_ALL + if signal_node.has_signal("focus_entered"): + signal_node.focus_entered.connect(subscription.lua_api._on_event_triggered.bind(subscription)) + subscription.connected_signal = "focus_entered" + subscription.connected_node = signal_node + return true + else: + signal_node.gui_input.connect(subscription.lua_api._on_focus_gui_input.bind(subscription)) + subscription.connected_signal = "gui_input_focus" + subscription.connected_node = signal_node + return true + "focusout": + if signal_node is Control and signal_node.has_signal("focus_exited"): + signal_node.focus_exited.connect(subscription.lua_api._on_event_triggered.bind(subscription)) + subscription.connected_signal = "focus_exited" + subscription.connected_node = signal_node + return true + + return false + +static func connect_body_event(event_name: String, subscription, lua_api) -> bool: + match event_name: + "keydown", "keypress", "keyup": + lua_api.set_process_input(true) + subscription.connected_signal = "input" + subscription.connected_node = lua_api + return true + "mousemove": + lua_api.set_process_input(true) + subscription.connected_signal = "input_mousemove" + subscription.connected_node = lua_api + return true + "mouseenter", "mouseexit": + var main_container = lua_api.dom_parser.parse_result.dom_nodes.get("body", null) + if main_container: + if event_name == "mouseenter": + main_container.mouse_entered.connect(lua_api._on_body_mouse_enter.bind(subscription)) + subscription.connected_signal = "mouse_entered" + elif event_name == "mouseexit": + main_container.mouse_exited.connect(lua_api._on_body_mouse_exit.bind(subscription)) + subscription.connected_signal = "mouse_exited" + subscription.connected_node = main_container + return true + "focusin", "focusout": + subscription.connected_signal = "focus_events" + subscription.connected_node = lua_api + return true + + return false + +static func _count_active_input_subscriptions(lua_api) -> int: + var count = 0 + for sub_id in lua_api.event_subscriptions: + var sub = lua_api.event_subscriptions[sub_id] + if sub.connected_signal in ["input", "input_mousemove"]: + count += 1 + return count + +static func disconnect_subscription(subscription, lua_api) -> void: + var target_node = subscription.connected_node if subscription.connected_node else lua_api.dom_parser.parse_result.dom_nodes.get(subscription.element_id, null) + + if target_node and subscription.connected_signal: + match subscription.connected_signal: + "pressed": + if target_node.has_signal("pressed"): + target_node.pressed.disconnect(lua_api._on_event_triggered.bind(subscription)) + "gui_input": + if target_node.has_signal("gui_input"): + target_node.gui_input.disconnect(lua_api._on_gui_input_click.bind(subscription)) + "gui_input_mouse": + if target_node.has_signal("gui_input"): + target_node.gui_input.disconnect(lua_api._on_gui_input_mouse_universal.bind(target_node)) + "gui_input_mousemove": + if target_node.has_signal("gui_input"): + target_node.gui_input.disconnect(lua_api._on_gui_input_mousemove.bind(subscription)) + "gui_input_focus": + if target_node.has_signal("gui_input"): + target_node.gui_input.disconnect(lua_api._on_focus_gui_input.bind(subscription)) + "mouse_entered": + if target_node.has_signal("mouse_entered"): + # Check if this is a body event or element event + if subscription.element_id == "body": + target_node.mouse_entered.disconnect(lua_api._on_body_mouse_enter.bind(subscription)) + else: + target_node.mouse_entered.disconnect(lua_api._on_event_triggered.bind(subscription)) + "mouse_exited": + if target_node.has_signal("mouse_exited"): + # Check if this is a body event or element event + if subscription.element_id == "body": + target_node.mouse_exited.disconnect(lua_api._on_body_mouse_exit.bind(subscription)) + else: + target_node.mouse_exited.disconnect(lua_api._on_event_triggered.bind(subscription)) + "focus_entered": + if target_node.has_signal("focus_entered"): + target_node.focus_entered.disconnect(lua_api._on_event_triggered.bind(subscription)) + "focus_exited": + if target_node.has_signal("focus_exited"): + target_node.focus_exited.disconnect(lua_api._on_event_triggered.bind(subscription)) + "input": + # Only disable input processing if no other input subscriptions remain + if _count_active_input_subscriptions(lua_api) <= 1: + lua_api.set_process_input(false) + "input_mousemove": + # Only disable input processing if no other input subscriptions remain + if _count_active_input_subscriptions(lua_api) <= 1: + lua_api.set_process_input(false) diff --git a/flumi/Scripts/Utils/Lua/Event.gd.uid b/flumi/Scripts/Utils/Lua/Event.gd.uid new file mode 100644 index 0000000..f682a24 --- /dev/null +++ b/flumi/Scripts/Utils/Lua/Event.gd.uid @@ -0,0 +1 @@ +uid://b7bck02xxt0u6 diff --git a/flumi/Scripts/Utils/Lua/Function.gd b/flumi/Scripts/Utils/Lua/Function.gd new file mode 100644 index 0000000..a0898c1 --- /dev/null +++ b/flumi/Scripts/Utils/Lua/Function.gd @@ -0,0 +1,57 @@ +class_name LuaFunctionUtils +extends RefCounted + +# Core Lua handler functions that extend Lua functionality + +static func table_tostring_handler(vm: LuauVM) -> int: + vm.luaL_checktype(1, vm.LUA_TTABLE) + var table_string = LuaPrintUtils.table_to_string(vm, 1) + vm.lua_pushstring(table_string) + return 1 + +static func setup_gurt_api(vm: LuauVM, lua_api, dom_parser: HTMLParser) -> void: + # override global print + # This makes print() behave like gurt.log() + vm.lua_pushcallable(LuaPrintUtils.lua_print, "print") + vm.lua_setglobal("print") + + # Add table.tostring utility + vm.lua_getglobal("table") + if vm.lua_isnil(-1): + vm.lua_pop(1) + vm.lua_newtable() + vm.lua_setglobal("table") + vm.lua_getglobal("table") + + vm.lua_pushcallable(LuaFunctionUtils.table_tostring_handler, "table.tostring") + vm.lua_setfield(-2, "tostring") + vm.lua_pop(1) # Pop table from stack + + vm.lua_newtable() + + vm.lua_pushcallable(LuaPrintUtils.lua_print, "gurt.log") + vm.lua_setfield(-2, "log") + + vm.lua_pushcallable(lua_api._gurt_select_handler, "gurt.select") + vm.lua_setfield(-2, "select") + + # Add body element access + var body_element = dom_parser.find_first("body") + if body_element: + vm.lua_newtable() + vm.lua_pushstring("body") + vm.lua_setfield(-2, "_element_id") + + # NOTE: same code as add_element_methods, but lazy to handle body.xxxx prop + vm.lua_pushcallable(lua_api._element_set_text_handler, "body.set_text") + vm.lua_setfield(-2, "set_text") + + vm.lua_pushcallable(lua_api._element_get_text_handler, "body.get_text") + vm.lua_setfield(-2, "get_text") + + vm.lua_pushcallable(lua_api._body_on_event_handler, "body.on") + vm.lua_setfield(-2, "on") + + vm.lua_setfield(-2, "body") + + vm.lua_setglobal("gurt") diff --git a/flumi/Scripts/Utils/Lua/Function.gd.uid b/flumi/Scripts/Utils/Lua/Function.gd.uid new file mode 100644 index 0000000..b63974c --- /dev/null +++ b/flumi/Scripts/Utils/Lua/Function.gd.uid @@ -0,0 +1 @@ +uid://dbsrdfnhv4h16 diff --git a/flumi/Scripts/Utils/Lua/Print.gd b/flumi/Scripts/Utils/Lua/Print.gd new file mode 100644 index 0000000..631528a --- /dev/null +++ b/flumi/Scripts/Utils/Lua/Print.gd @@ -0,0 +1,87 @@ +class_name LuaPrintUtils +extends RefCounted + +static func lua_print(vm: LuauVM) -> int: + var message_parts: Array[String] = [] + var num_args: int = vm.lua_gettop() + + for i in range(1, num_args + 1): + var value_str = lua_value_to_string(vm, i) + message_parts.append(value_str) + + var final_message = "\t".join(message_parts) + print("GURT LOG: ", final_message) + return 0 + +static func lua_value_to_string(vm: LuauVM, index: int) -> String: + var lua_type = vm.lua_type(index) + + match lua_type: + vm.LUA_TNIL: + return "nil" + vm.LUA_TBOOLEAN: + return "true" if vm.lua_toboolean(index) else "false" + vm.LUA_TNUMBER: + return str(vm.lua_tonumber(index)) + vm.LUA_TSTRING: + return vm.lua_tostring(index) + vm.LUA_TTABLE: + return table_to_string(vm, index) + vm.LUA_TFUNCTION: + return "[function]" + vm.LUA_TUSERDATA: + return "[userdata]" + vm.LUA_TVECTOR: + var vec = vm.lua_tovector(index) + return "vector(" + str(vec.x) + ", " + str(vec.y) + ", " + str(vec.z) + ", " + str(vec.w) + ")" + _: + return "[" + vm.lua_typename(lua_type) + "]" + +static func table_to_string(vm: LuauVM, index: int, max_depth: int = 3, current_depth: int = 0) -> String: + if current_depth >= max_depth: + return "{...}" + + var result = "{" + var first = true + var count = 0 + var max_items = 10 + + # Convert negative index to positive + if index < 0: + index = vm.lua_gettop() + index + 1 + + # Iterate through table + vm.lua_pushnil() # First key + while vm.lua_next(index): + if count >= max_items: + # We need to pop the value and key before breaking + vm.lua_pop(2) # Remove value and key + break + + if not first: + result += ", " + first = false + + # Get key + var key_str = lua_value_to_string(vm, -2) + # Get value + var value_str = "" + if vm.lua_type(-1) == vm.LUA_TTABLE: + value_str = table_to_string(vm, -1, max_depth, current_depth + 1) + else: + value_str = lua_value_to_string(vm, -1) + + # Check if key is a valid identifier (for shorthand) + if key_str.is_valid_identifier(): + result += key_str + ": " + value_str + else: + result += "[" + key_str + "]: " + value_str + + vm.lua_pop(1) # Remove value, keep key for next iteration + count += 1 + + if count >= max_items: + result += ", ..." + + result += "}" + return result \ No newline at end of file diff --git a/flumi/Scripts/Utils/Lua/Print.gd.uid b/flumi/Scripts/Utils/Lua/Print.gd.uid new file mode 100644 index 0000000..85d0c4d --- /dev/null +++ b/flumi/Scripts/Utils/Lua/Print.gd.uid @@ -0,0 +1 @@ +uid://dvdnveday3ev1 diff --git a/flumi/Scripts/Utils/LuaEventUtils.gd.uid b/flumi/Scripts/Utils/LuaEventUtils.gd.uid new file mode 100644 index 0000000..4b9aef4 --- /dev/null +++ b/flumi/Scripts/Utils/LuaEventUtils.gd.uid @@ -0,0 +1 @@ +uid://bt0njoa0olpb7 diff --git a/flumi/Scripts/Utils/LuaFunctionUtils.gd.uid b/flumi/Scripts/Utils/LuaFunctionUtils.gd.uid new file mode 100644 index 0000000..a5f3901 --- /dev/null +++ b/flumi/Scripts/Utils/LuaFunctionUtils.gd.uid @@ -0,0 +1 @@ +uid://dxti5hc236dcp diff --git a/flumi/Scripts/Utils/LuaPrintUtils.gd.uid b/flumi/Scripts/Utils/LuaPrintUtils.gd.uid new file mode 100644 index 0000000..50e2f05 --- /dev/null +++ b/flumi/Scripts/Utils/LuaPrintUtils.gd.uid @@ -0,0 +1 @@ +uid://bwbbe8r7u10ov diff --git a/flumi/Scripts/main.gd b/flumi/Scripts/main.gd index c58182f..a40e087 100644 --- a/flumi/Scripts/main.gd +++ b/flumi/Scripts/main.gd @@ -99,7 +99,18 @@ func render() -> void: if body: StyleManager.apply_body_styles(body, parser, website_container, website_background) + + parser.register_dom_node(body, website_container) + var scripts = parser.find_all("script") + var lua_vm = null + var lua_api = null + if scripts.size() > 0: + lua_vm = LuauVM.new() + lua_api = LuaAPI.new() + add_child(lua_vm) + add_child(lua_api) + var i = 0 while i < body.children.size(): var element: HTMLParser.HTMLElement = body.children[i] @@ -118,6 +129,8 @@ func render() -> void: for inline_element in inline_elements: var inline_node = await create_element_node(inline_element, parser) if inline_node: + parser.register_dom_node(inline_element, inline_node) + safe_add_child(hbox, inline_node) # Handle hyperlinks for all inline elements if contains_hyperlink(inline_element) and inline_node is RichTextLabel: @@ -130,6 +143,8 @@ func render() -> void: var element_node = await create_element_node(element, parser) if element_node: + parser.register_dom_node(element, element_node) + # ul/ol handle their own adding if element.tag_name != "ul" and element.tag_name != "ol": safe_add_child(website_container, element_node) @@ -144,6 +159,9 @@ func render() -> void: print("Couldn't parse unsupported HTML tag \"%s\"" % element.tag_name) i += 1 + + if scripts.size() > 0 and lua_vm and lua_api: + parser.process_scripts(lua_api, lua_vm) static func safe_add_child(parent: Node, child: Node) -> void: if child.get_parent(): @@ -232,7 +250,15 @@ func create_element_node(element: HTMLParser.HTMLElement, parser: HTMLParser) -> # Apply flex CONTAINER properties if it's a flex container if is_flex_container: - StyleManager.apply_flex_container_properties(final_node, styles) + var flex_container_node = final_node + # If the node was wrapped in a MarginContainer, get the inner FlexContainer + if final_node is MarginContainer and final_node.get_child_count() > 0: + var first_child = final_node.get_child(0) + if first_child is FlexContainer: + flex_container_node = first_child + + if flex_container_node is FlexContainer: + StyleManager.apply_flex_container_properties(flex_container_node, styles) # Apply flex ITEM properties StyleManager.apply_flex_item_properties(final_node, styles) @@ -252,6 +278,7 @@ func create_element_node(element: HTMLParser.HTMLElement, parser: HTMLParser) -> if not child_element.is_inline_element() or is_flex_container: var child_node = await create_element_node(child_element, parser) if child_node and is_instance_valid(container_for_children): + parser.register_dom_node(child_element, child_node) safe_add_child(container_for_children, child_node) return final_node @@ -340,7 +367,7 @@ func create_element_node_internal(element: HTMLParser.HTMLElement, parser: HTMLP node = BackgroundUtils.create_panel_container_with_background(styles, hover_styles) else: node = DIV.instantiate() - node.init(element, parser) + node.init(element) var has_only_text = is_text_only_element(element) diff --git a/flumi/addons/gdluau/LICENSE b/flumi/addons/gdluau/LICENSE new file mode 100644 index 0000000..667db1f --- /dev/null +++ b/flumi/addons/gdluau/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 Tigran Mamedov + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/flumi/addons/gdluau/bin/linux/.gitkeep b/flumi/addons/gdluau/bin/linux/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/flumi/addons/gdluau/bin/linux/libgdluau.linux.template_debug.x86_64.so b/flumi/addons/gdluau/bin/linux/libgdluau.linux.template_debug.x86_64.so new file mode 100644 index 0000000..3c4f478 Binary files /dev/null and b/flumi/addons/gdluau/bin/linux/libgdluau.linux.template_debug.x86_64.so differ diff --git a/flumi/addons/gdluau/bin/linux/libgdluau.linux.template_release.x86_64.so b/flumi/addons/gdluau/bin/linux/libgdluau.linux.template_release.x86_64.so new file mode 100644 index 0000000..05f41c0 Binary files /dev/null and b/flumi/addons/gdluau/bin/linux/libgdluau.linux.template_release.x86_64.so differ diff --git a/flumi/addons/gdluau/bin/macos/.gitkeep b/flumi/addons/gdluau/bin/macos/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/flumi/addons/gdluau/bin/macos/macos.framework/libgdluau.macos.template_debug b/flumi/addons/gdluau/bin/macos/macos.framework/libgdluau.macos.template_debug new file mode 100644 index 0000000..5c13496 Binary files /dev/null and b/flumi/addons/gdluau/bin/macos/macos.framework/libgdluau.macos.template_debug differ diff --git a/flumi/addons/gdluau/bin/macos/macos.framework/libgdluau.macos.template_release b/flumi/addons/gdluau/bin/macos/macos.framework/libgdluau.macos.template_release new file mode 100644 index 0000000..cd1efaf Binary files /dev/null and b/flumi/addons/gdluau/bin/macos/macos.framework/libgdluau.macos.template_release differ diff --git a/flumi/addons/gdluau/bin/windows/.gitkeep b/flumi/addons/gdluau/bin/windows/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/flumi/addons/gdluau/bin/windows/gdluau.windows.template_debug.x86_64.dll b/flumi/addons/gdluau/bin/windows/gdluau.windows.template_debug.x86_64.dll new file mode 100644 index 0000000..7e832a1 Binary files /dev/null and b/flumi/addons/gdluau/bin/windows/gdluau.windows.template_debug.x86_64.dll differ diff --git a/flumi/addons/gdluau/bin/windows/gdluau.windows.template_release.x86_64.dll b/flumi/addons/gdluau/bin/windows/gdluau.windows.template_release.x86_64.dll new file mode 100644 index 0000000..37fd5d1 Binary files /dev/null and b/flumi/addons/gdluau/bin/windows/gdluau.windows.template_release.x86_64.dll differ diff --git a/flumi/addons/gdluau/bin/windows/libgcc_s_seh-1.dll b/flumi/addons/gdluau/bin/windows/libgcc_s_seh-1.dll new file mode 100644 index 0000000..0fb510d Binary files /dev/null and b/flumi/addons/gdluau/bin/windows/libgcc_s_seh-1.dll differ diff --git a/flumi/addons/gdluau/bin/windows/libstdc++-6.dll b/flumi/addons/gdluau/bin/windows/libstdc++-6.dll new file mode 100644 index 0000000..80a8a1e Binary files /dev/null and b/flumi/addons/gdluau/bin/windows/libstdc++-6.dll differ diff --git a/flumi/addons/gdluau/bin/windows/libwinpthread-1.dll b/flumi/addons/gdluau/bin/windows/libwinpthread-1.dll new file mode 100644 index 0000000..8ca3a8c Binary files /dev/null and b/flumi/addons/gdluau/bin/windows/libwinpthread-1.dll differ diff --git a/flumi/addons/gdluau/bin/windows/~gdluau.windows.template_debug.x86_64.dll b/flumi/addons/gdluau/bin/windows/~gdluau.windows.template_debug.x86_64.dll new file mode 100644 index 0000000..7e832a1 Binary files /dev/null and b/flumi/addons/gdluau/bin/windows/~gdluau.windows.template_debug.x86_64.dll differ diff --git a/flumi/addons/gdluau/gdluau.gdextension b/flumi/addons/gdluau/gdluau.gdextension new file mode 100644 index 0000000..8c1e8f8 --- /dev/null +++ b/flumi/addons/gdluau/gdluau.gdextension @@ -0,0 +1,13 @@ +[configuration] + +entry_symbol = "gdluau_library_init" +compatibility_minimum = "4.2" + +[libraries] + +windows.debug.x86_64 = "res://addons/gdluau/bin/windows/gdluau.windows.template_debug.x86_64.dll" +windows.release.x86_64 = "res://addons/gdluau/bin/windows/gdluau.windows.template_release.x86_64.dll" +linux.debug.x86_64 = "res://addons/gdluau/bin/linux/libgdluau.linux.template_debug.x86_64.so" +linux.release.x86_64 = "res://addons/gdluau/bin/linux/libgdluau.linux.template_release.x86_64.so" +macos.debug = "res://addons/gdluau/bin/macos/macos.framework/libgdluau.macos.template_debug" +macos.release = "res://addons/gdluau/bin/macos/macos.framework/libgdluau.macos.template_release" \ No newline at end of file diff --git a/flumi/addons/gdluau/gdluau.gdextension.uid b/flumi/addons/gdluau/gdluau.gdextension.uid new file mode 100644 index 0000000..12236b7 --- /dev/null +++ b/flumi/addons/gdluau/gdluau.gdextension.uid @@ -0,0 +1 @@ +uid://c4jyopuvj0sq1 diff --git a/flumi/addons/godot-flexbox/bin/windows/~godot-flexbox.windows.template_release.double.x86_64.dll b/flumi/addons/godot-flexbox/bin/windows/~godot-flexbox.windows.template_release.double.x86_64.dll new file mode 100644 index 0000000..dc6a041 Binary files /dev/null and b/flumi/addons/godot-flexbox/bin/windows/~godot-flexbox.windows.template_release.double.x86_64.dll differ