diff --git a/flumi/Scenes/Tags/li.tscn b/flumi/Scenes/Tags/li.tscn deleted file mode 100644 index 5929bb0..0000000 --- a/flumi/Scenes/Tags/li.tscn +++ /dev/null @@ -1,9 +0,0 @@ -[gd_scene load_steps=2 format=3 uid="uid://bli1234568aa"] - -[ext_resource type="Script" uid="uid://ps8duq0aw3tu" path="res://Scripts/Tags/li.gd" id="1_li"] - -[node name="li" type="VBoxContainer"] -anchors_preset = 10 -anchor_right = 1.0 -grow_horizontal = 2 -script = ExtResource("1_li") diff --git a/flumi/Scenes/Tags/option.tscn b/flumi/Scenes/Tags/option.tscn index 767c64a..ab0b65b 100644 --- a/flumi/Scenes/Tags/option.tscn +++ b/flumi/Scenes/Tags/option.tscn @@ -1,6 +1,5 @@ -[gd_scene load_steps=3 format=3 uid="uid://bopt1234568aa"] +[gd_scene load_steps=2 format=3 uid="uid://bopt1234568aa"] -[ext_resource type="Script" uid="uid://ps8duq0aw3tu" path="res://Scripts/Tags/li.gd" id="1_option"] [ext_resource type="Theme" uid="uid://bn6rbmdy60lhr" path="res://Scenes/Styles/BrowserText.tres" id="2_theme"] [node name="option" type="Control"] @@ -8,7 +7,6 @@ layout_mode = 3 anchors_preset = 10 anchor_right = 1.0 grow_horizontal = 2 -script = ExtResource("1_option") [node name="RichTextLabel" type="RichTextLabel" parent="."] layout_mode = 2 diff --git a/flumi/Scripts/B9/HTMLParser.gd b/flumi/Scripts/B9/HTMLParser.gd index edd3535..bdb1068 100644 --- a/flumi/Scripts/B9/HTMLParser.gd +++ b/flumi/Scripts/B9/HTMLParser.gd @@ -18,8 +18,8 @@ class HTMLElement: func has_attribute(name_: String) -> bool: return attributes.has(name_) - func get_class_name() -> String: - return get_attribute("class") + func set_attribute(name_: String, value: String) -> void: + attributes.set(name_, value) func get_id() -> String: return get_attribute("id") diff --git a/flumi/Scripts/B9/Lua.gd b/flumi/Scripts/B9/Lua.gd index b660250..f4194bc 100644 --- a/flumi/Scripts/B9/Lua.gd +++ b/flumi/Scripts/B9/Lua.gd @@ -16,6 +16,30 @@ var event_subscriptions: Dictionary = {} var next_subscription_id: int = 1 var next_callback_ref: int = 1 +var timeout_manager: LuaTimeoutManager +var element_id_counter: int = 1 +var element_id_registry: Dictionary = {} + +func _init(): + timeout_manager = LuaTimeoutManager.new() + +func get_or_assign_element_id(element: HTMLParser.HTMLElement) -> String: + var existing_id = element.get_attribute("id") + if not existing_id.is_empty(): + element_id_registry[element] = existing_id + return existing_id + + if element_id_registry.has(element): + return element_id_registry[element] + + var new_id = "auto_" + str(element_id_counter) + element_id_counter += 1 + + element.set_attribute("id", new_id) + element_id_registry[element] = new_id + + return new_id + func _gurt_select_handler(vm: LuauVM) -> int: var selector: String = vm.luaL_checkstring(1) @@ -38,6 +62,87 @@ func _gurt_select_handler(vm: LuauVM) -> int: add_element_methods(vm) return 1 +# selectAll() function to find multiple elements +func _gurt_select_all_handler(vm: LuauVM) -> int: + var selector: String = vm.luaL_checkstring(1) + + var elements: Array[HTMLParser.HTMLElement] = [] + + # Handle different selector types + if selector.begins_with("#"): + # ID selector - find single element + var element_id = selector.substr(1) + var element = dom_parser.find_by_id(element_id) + if element: + elements.append(element) + LuaPrintUtils.lua_print_direct("WARNING: Using ID selector in select_all is not recommended, use select instead.") + elif selector.begins_with("."): + # Class selector - find all elements with class + var cls = selector.substr(1) + for element in dom_parser.parse_result.all_elements: + var element_classes = CSSParser.smart_split_utility_classes(element.get_attribute("style")) + if cls in element_classes: + elements.append(element) + else: + # Tag selector - find all elements with tag name + elements = dom_parser.find_all(selector) + + vm.lua_newtable() + var index = 1 + + for element in elements: + var element_id = get_or_assign_element_id(element) + + # Create element wrapper + vm.lua_newtable() + vm.lua_pushstring(element_id) + vm.lua_setfield(-2, "_element_id") + vm.lua_pushstring(element.tag_name) + vm.lua_setfield(-2, "_tag_name") + + add_element_methods(vm) + + # Add to array at index + vm.lua_rawseti(-2, index) + index += 1 + + return 1 + +# create() function to create HTML element +func _gurt_create_handler(vm: LuauVM) -> int: + var tag_name: String = vm.luaL_checkstring(1) + var options: Dictionary = {} + + if vm.lua_gettop() >= 2 and vm.lua_istable(2): + options = vm.lua_todictionary(2) + + var element = HTMLParser.HTMLElement.new(tag_name) + + # Apply options as attributes and content + for key in options: + if key == "text": + element.text_content = str(options[key]) + else: + element.attributes[str(key)] = str(options[key]) + + # Add to parser's element collection first + dom_parser.parse_result.all_elements.append(element) + + # Get or assign stable ID + var unique_id = get_or_assign_element_id(element) + + # Create Lua element wrapper with methods + vm.lua_newtable() + vm.lua_pushstring(unique_id) + vm.lua_setfield(-2, "_element_id") + vm.lua_pushstring(tag_name) + vm.lua_setfield(-2, "_tag_name") + vm.lua_pushboolean(true) + vm.lua_setfield(-2, "_is_dynamic") + + 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") @@ -47,6 +152,45 @@ func add_element_methods(vm: LuauVM) -> void: vm.lua_pushcallable(_element_on_event_handler, "element.on") vm.lua_setfield(-2, "on") + + vm.lua_pushcallable(_element_append_handler, "element.append") + vm.lua_setfield(-2, "append") + + vm.lua_pushcallable(_element_remove_handler, "element.remove") + vm.lua_setfield(-2, "remove") + + vm.lua_pushcallable(_element_get_children_handler, "element.get_children") + vm.lua_setfield(-2, "get_children") + +func _element_get_children_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) + + # Find the element + var element: HTMLParser.HTMLElement = null + if element_id == "body": + element = dom_parser.find_first("body") + else: + element = dom_parser.find_by_id(element_id) + + vm.lua_newtable() + var index = 1 + + if element: + for child in element.children: + vm.lua_newtable() + vm.lua_pushstring(child.tag_name) + vm.lua_setfield(-2, "tag_name") + vm.lua_pushstring(child.get_text_content()) + vm.lua_setfield(-2, "text") + + vm.lua_rawseti(-2, index) + index += 1 + + return 1 # Element manipulation handlers func _element_set_text_handler(vm: LuauVM) -> int: @@ -83,6 +227,129 @@ func _element_get_text_handler(vm: LuauVM) -> int: vm.lua_pushstring(text) return 1 +# append() function to add a child element +func _element_append_handler(vm: LuauVM) -> int: + vm.luaL_checktype(1, vm.LUA_TTABLE) + vm.luaL_checktype(2, vm.LUA_TTABLE) + + # Get parent element info + vm.lua_getfield(1, "_element_id") + var parent_element_id: String = vm.lua_tostring(-1) + vm.lua_pop(1) + + # Get child element info + vm.lua_getfield(2, "_element_id") + var child_element_id: String = vm.lua_tostring(-1) + vm.lua_pop(1) + + vm.lua_getfield(2, "_is_dynamic") + vm.lua_pop(1) + + # Find parent element + var parent_element: HTMLParser.HTMLElement = null + if parent_element_id == "body": + parent_element = dom_parser.find_first("body") + else: + parent_element = dom_parser.find_by_id(parent_element_id) + + if not parent_element: + return 0 + + # Find child element + var child_element = dom_parser.find_by_id(child_element_id) + if not child_element: + return 0 + + # Add child to parent in DOM tree + child_element.parent = parent_element + parent_element.children.append(child_element) + + # If the parent is already rendered, we need to create and add the visual node + var parent_dom_node: Node = null + if parent_element_id == "body": + var main_scene = get_node("/root/Main") + if main_scene: + parent_dom_node = main_scene.website_container + else: + parent_dom_node = dom_parser.parse_result.dom_nodes.get(parent_element_id, null) + + if parent_dom_node: + _render_new_element.call_deferred(child_element, parent_dom_node) + + return 0 + +# remove() function to remove an element +func _element_remove_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) + + # Find the element in DOM + var element = dom_parser.find_by_id(element_id) + if not element: + return 0 + + # Remove from parent's children array + if element.parent: + var parent_children = element.parent.children + var idx = parent_children.find(element) + if idx >= 0: + parent_children.remove_at(idx) + + # Remove the visual node + var dom_node = dom_parser.parse_result.dom_nodes.get(element_id, null) + if dom_node: + dom_node.queue_free() + dom_parser.parse_result.dom_nodes.erase(element_id) + + # Remove from all_elements array + var all_elements = dom_parser.parse_result.all_elements + var index = all_elements.find(element) + if index >= 0: + all_elements.remove_at(index) + + # Remove from element_id_registry to avoid memory leaks + if element_id_registry.has(element): + element_id_registry.erase(element) + + return 0 + +func _render_new_element(element: HTMLParser.HTMLElement, parent_node: Node) -> void: + # Get reference to main scene for rendering + var main_scene = get_node("/root/Main") + if not main_scene: + return + + # Create the visual node for the element + var element_node = await main_scene.create_element_node(element, dom_parser) + if not element_node: + LuaPrintUtils.lua_print_direct("Failed to create visual node for element: " + str(element)) + return + + # Set metadata so ul/ol can detect dynamically added li elements + element_node.set_meta("html_element", element) + + # Register the DOM node + dom_parser.register_dom_node(element, element_node) + + # Add to parent - handle body special case + var container_node = parent_node + if parent_node is MarginContainer and parent_node.get_child_count() > 0: + container_node = parent_node.get_child(0) + elif parent_node == main_scene.website_container: + container_node = parent_node + + main_scene.safe_add_child(container_node, element_node) + +# Timeout management handlers +func _gurt_set_timeout_handler(vm: LuauVM) -> int: + return timeout_manager.set_timeout_handler(vm, self) + +func _gurt_clear_timeout_handler(vm: LuauVM) -> int: + return timeout_manager.clear_timeout_handler(vm) + # Event system handlers func _element_on_event_handler(vm: LuauVM) -> int: vm.luaL_checktype(1, vm.LUA_TTABLE) diff --git a/flumi/Scripts/Constants.gd b/flumi/Scripts/Constants.gd index 9d20b85..c1477c0 100644 --- a/flumi/Scripts/Constants.gd +++ b/flumi/Scripts/Constants.gd @@ -590,7 +590,8 @@ var HTML_CONTENT3 = """ """.to_utf8_buffer() -var HTML_CONTENT = """ +var HTML_CONTENT = """ + Lua API Demo @@ -663,6 +664,38 @@ var HTML_CONTENT = """ else gurt.log('Could not find button or event log element') end + + -- DOM Manipulation Demo + gurt.log('Testing DOM manipulation...') + + -- Create a new div with styling + local new_div = gurt.create('div', { style = 'bg-red-500 p-4 rounded-lg mb-4' }) + + -- Create a paragraph with text + local new_p = gurt.create('p', { + style = 'text-white font-bold text-lg', + text = 'This element was created dynamically with Lua!' + }) + + -- Append paragraph to div + new_div:append(new_p) + + -- Append div to body + gurt.body:append(new_div) + + -- Create another element to test removal + local temp_element = gurt.create('div', { + style = 'bg-yellow-400 p-2 rounded text-black', + text = 'This will be removed in 3 seconds...' + }) + gurt.body:append(temp_element) + + local test = gurt.set_timeout(function() + print('removed') + temp_element:remove() + end, 3000) + + -- gurt.clear_timeout(test) @@ -681,5 +714,61 @@ var HTML_CONTENT = """

Move mouse over Button

Type something

+""".to_utf8_buffer() + +var HTML_CONTENT_ADD_REMOVE = """ + Lua List Manipulation Demo + + + + + + + + + + +

List Manipulation with Lua

+ +
+

Use the buttons below to add or remove items from the list:

+ + +
+ + """.to_utf8_buffer() diff --git a/flumi/Scripts/StyleManager.gd b/flumi/Scripts/StyleManager.gd index 3cb493a..b3baee4 100644 --- a/flumi/Scripts/StyleManager.gd +++ b/flumi/Scripts/StyleManager.gd @@ -204,7 +204,16 @@ static func apply_margin_wrapper(node: Control, styles: Dictionary) -> Control: node.size_flags_horizontal = Control.SIZE_EXPAND_FILL node.size_flags_vertical = Control.SIZE_EXPAND_FILL - margin_container.add_child(node) + # Handle reparenting properly + var original_parent = node.get_parent() + if original_parent: + var node_index = node.get_index() + original_parent.remove_child(node) + margin_container.add_child(node) + original_parent.add_child(margin_container) + original_parent.move_child(margin_container, node_index) + else: + margin_container.add_child(node) return margin_container diff --git a/flumi/Scripts/Tags/BaseListContainer.gd b/flumi/Scripts/Tags/BaseListContainer.gd new file mode 100644 index 0000000..d981dbb --- /dev/null +++ b/flumi/Scripts/Tags/BaseListContainer.gd @@ -0,0 +1,282 @@ +class_name BaseListContainer +extends VBoxContainer + +const BROWSER_TEXT = preload("res://Scenes/Styles/BrowserText.tres") + +var list_type: String +var marker_width: float +var parser_ref: HTMLParser = null +var is_ordered: bool = false + +func _ready(): + child_entered_tree.connect(_on_child_added) + child_exiting_tree.connect(_on_child_removed) + +func init(element: HTMLParser.HTMLElement, parser: HTMLParser = null) -> void: + list_type = element.get_attribute("type").to_lower() + if list_type == "": + list_type = "disc" if not is_ordered else "decimal" + parser_ref = parser + + marker_width = await calculate_marker_width(element) + + var index = 1 + for child_element in element.children: + if child_element.tag_name == "li": + var li_node = create_li_node(child_element, index, parser) + if li_node: + add_child(li_node) + index += 1 + +func calculate_marker_width(element: HTMLParser.HTMLElement) -> float: + var temp_label = RichTextLabel.new() + temp_label.bbcode_enabled = true + temp_label.fit_content = true + temp_label.scroll_active = false + temp_label.theme = BROWSER_TEXT + add_child(temp_label) + + var sample_text = "" + if is_ordered: + var item_count = 0 + for child_element in element.children: + if child_element.tag_name == "li": + item_count += 1 + sample_text = str(item_count) + "." + else: + match list_type: + "circle": + sample_text = "◦" + "disc": + sample_text = "•" + "square": + sample_text = "■" + "none": + sample_text = " " + _: + sample_text = "•" + + StyleManager.apply_styles_to_label(temp_label, {}, null, null, sample_text) + + await get_tree().process_frame + + var width = temp_label.get_content_width() + 5 + + remove_child(temp_label) + temp_label.queue_free() + + return max(width, 20.0 if not is_ordered else 30.0) + +func create_li_node(element: HTMLParser.HTMLElement, index: int, parser: HTMLParser = null) -> Control: + var li_container = HBoxContainer.new() + + # Create marker + var marker_label = RichTextLabel.new() + marker_label.custom_minimum_size = Vector2(marker_width, 0) + marker_label.size_flags_horizontal = Control.SIZE_SHRINK_CENTER + marker_label.size_flags_vertical = Control.SIZE_SHRINK_CENTER + marker_label.bbcode_enabled = true + marker_label.fit_content = true + marker_label.scroll_active = false + marker_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER + marker_label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER + marker_label.theme = BROWSER_TEXT + + var marker_text = get_marker_text(index) + var marker_styles = parser.get_element_styles_with_inheritance(element, "", []) if parser else {} + StyleManager.apply_styles_to_label(marker_label, marker_styles, element, parser, marker_text) + + # Create content + var content_label = RichTextLabel.new() + content_label.size_flags_horizontal = Control.SIZE_EXPAND_FILL + content_label.bbcode_enabled = true + content_label.fit_content = true + content_label.scroll_active = false + content_label.theme = BROWSER_TEXT + + var content_text = element.get_bbcode_formatted_text(parser) + var content_styles = parser.get_element_styles_with_inheritance(element, "", []) if parser else {} + StyleManager.apply_styles_to_label(content_label, content_styles, element, parser, content_text) + + li_container.add_theme_constant_override("separation", 0) + li_container.add_child(marker_label) + li_container.add_child(content_label) + + # Store element metadata on the container for renumbering + li_container.set_meta("html_element", element) + + var styles = parser.get_element_styles_with_inheritance(element, "", []) + if BackgroundUtils.needs_background_wrapper(styles): + var panel_container = BackgroundUtils.create_panel_container_with_background(styles) + panel_container.name = "Li" + # Store element metadata on the panel container too + panel_container.set_meta("html_element", element) + # Get the VBoxContainer inside PanelContainer and replace it with our HBoxContainer + var vbox = panel_container.get_child(0) + panel_container.remove_child(vbox) + vbox.queue_free() + panel_container.add_child(li_container) + return panel_container + else: + return li_container + +func _on_child_added(child: Node): + if child.has_meta("html_element"): + var element = child.get_meta("html_element") + if element is HTMLParser.HTMLElement and element.tag_name == "li": + + call_deferred("_process_dynamic_li", child, element) + +func _process_dynamic_li(child: Node, element: HTMLParser.HTMLElement): + child_entered_tree.disconnect(_on_child_added) + + # Get the correct index for this new item + var current_li_count = 0 + for existing_child in get_children(): + if existing_child != child: + current_li_count += 1 + + # Remove the basic li node and replace with properly formatted one + if child.get_parent() == self: + remove_child(child) + var li_node = create_li_node(element, current_li_count + 1, parser_ref) + if li_node: + var element_id = element.get_attribute("id") + if parser_ref and element_id: + parser_ref.parse_result.dom_nodes[element_id] = li_node + add_child(li_node) + child.queue_free() + + # Reconnect signal + child_entered_tree.connect(_on_child_added) + +func _on_child_removed(_child: Node): + if is_ordered: # Only OL needs renumbering + call_deferred("_renumber_list") + +func _renumber_list(): + # Temporarily disconnect signals to avoid recursion + child_entered_tree.disconnect(_on_child_added) + child_exiting_tree.disconnect(_on_child_removed) + + # Get all current li children + var li_children = [] + for child in get_children(): + var is_li = false + if child is HBoxContainer: + is_li = true + elif child is PanelContainer and child.get_child_count() > 0: + var inner_child = child.get_child(0) + if inner_child is HBoxContainer: + is_li = true + + if is_li: + li_children.append(child) + + # Renumber all existing items + for i in range(li_children.size()): + var child = li_children[i] + var marker_label = null + + # Find the marker label within the child structure + if child is HBoxContainer and child.get_child_count() > 0: + marker_label = child.get_child(0) + elif child is PanelContainer and child.get_child_count() > 0: + var hbox = child.get_child(0) + if hbox is HBoxContainer and hbox.get_child_count() > 0: + marker_label = hbox.get_child(0) + + # Update the marker text - recreate it completely to avoid BBCode corruption + if marker_label and marker_label is RichTextLabel: + var index = i + 1 + var new_marker_text = get_marker_text(index) + # Get the HTMLElement from the li container to reapply styles properly + var element = null + if child.has_meta("html_element"): + element = child.get_meta("html_element") + elif child is PanelContainer and child.get_child_count() > 0: + var hbox = child.get_child(0) + if hbox.has_meta("html_element"): + element = hbox.get_meta("html_element") + + if element and parser_ref: + var marker_styles = parser_ref.get_element_styles_with_inheritance(element, "", []) + StyleManager.apply_styles_to_label(marker_label, marker_styles, element, parser_ref, new_marker_text) + else: + # Fallback - just set the text + marker_label.text = new_marker_text + + # Reconnect signals + child_entered_tree.connect(_on_child_added) + child_exiting_tree.connect(_on_child_removed) + +func refresh_list(): + # Force refresh of all li children for dynamically added content + var children_to_process = [] + for child in get_children(): + if child.has_meta("html_element"): + var element = child.get_meta("html_element") + if element is HTMLParser.HTMLElement and element.tag_name == "li": + children_to_process.append([child, element]) + + # Clear all children first + for child_data in children_to_process: + var child = child_data[0] + remove_child(child) + child.queue_free() + + # Recalculate marker width if needed + var new_count = children_to_process.size() + if new_count > 0 and is_ordered: + marker_width = await calculate_marker_width(children_to_process[0][1]) + + # Re-add with correct indices + for i in range(children_to_process.size()): + var element = children_to_process[i][1] + var li_node = create_li_node(element, i + 1, parser_ref) + if li_node: + add_child(li_node) + +func int_to_roman(num: int) -> String: + var values = [1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1] + var symbols = ["M", "CM", "D", "CD", "C", "XC", "L", "XL", "X", "IX", "V", "IV", "I"] + var result = "" + + for i in range(values.size()): + while num >= values[i]: + result += symbols[i] + num -= values[i] + + return result + +func get_marker_text(index: int) -> String: + if is_ordered: + match list_type: + "decimal": + return str(index) + "." + "zero-lead": + return "%02d." % index + "lower-alpha": + return char(96 + index) + "." + "lower-roman": + return int_to_roman(index).to_lower() + "." + "upper-alpha": + return char(64 + index) + "." + "upper-roman": + return int_to_roman(index) + "." + "none": + return "" + _: + return str(index) + "." + else: + match list_type: + "circle": + return "◦" + "disc": + return "•" + "square": + return "■" + "none": + return " " + _: + return "•" \ No newline at end of file diff --git a/flumi/Scripts/Tags/BaseListContainer.gd.uid b/flumi/Scripts/Tags/BaseListContainer.gd.uid new file mode 100644 index 0000000..aabd1a2 --- /dev/null +++ b/flumi/Scripts/Tags/BaseListContainer.gd.uid @@ -0,0 +1 @@ +uid://cbdk823naa108 diff --git a/flumi/Scripts/Tags/li.gd b/flumi/Scripts/Tags/li.gd deleted file mode 100644 index 9f48b24..0000000 --- a/flumi/Scripts/Tags/li.gd +++ /dev/null @@ -1,8 +0,0 @@ -extends Control - -func init(element: HTMLParser.HTMLElement, parser: HTMLParser = null) -> void: - # This is mainly for cases where
  • appears outside of
      /
        - var label: RichTextLabel = $RichTextLabel - var styles = parser.get_element_styles_with_inheritance(element, "", []) - - StyleManager.apply_styles_to_label(label, styles, element, parser) diff --git a/flumi/Scripts/Tags/li.gd.uid b/flumi/Scripts/Tags/li.gd.uid deleted file mode 100644 index 968d6d5..0000000 --- a/flumi/Scripts/Tags/li.gd.uid +++ /dev/null @@ -1 +0,0 @@ -uid://ps8duq0aw3tu diff --git a/flumi/Scripts/Tags/ol.gd b/flumi/Scripts/Tags/ol.gd index 9c82f84..8ac587a 100644 --- a/flumi/Scripts/Tags/ol.gd +++ b/flumi/Scripts/Tags/ol.gd @@ -1,117 +1,5 @@ -extends VBoxContainer +extends BaseListContainer -const BROWSER_TEXT = preload("res://Scenes/Styles/BrowserText.tres") - -func init(element: HTMLParser.HTMLElement, parser: HTMLParser = null) -> void: - var list_type = element.get_attribute("type").to_lower() - if list_type == "": list_type = "decimal" # Default - - var item_count = 0 - for child_element in element.children: - if child_element.tag_name == "li": - item_count += 1 - - var marker_min_width = await calculate_marker_width(list_type, item_count) - - var index = 1 - for child_element in element.children: - if child_element.tag_name == "li": - var li_node = create_li_node(child_element, list_type, index, marker_min_width, parser) - if li_node: - add_child(li_node) - index += 1 - -func calculate_marker_width(list_type: String, max_index: int) -> float: - var temp_label = RichTextLabel.new() - temp_label.bbcode_enabled = true - temp_label.fit_content = true - temp_label.scroll_active = false - temp_label.theme = BROWSER_TEXT - add_child(temp_label) - - var marker_text = get_marker_for_type(list_type, max_index) - StyleManager.apply_styles_to_label(temp_label, {}, null, null, marker_text) - - await get_tree().process_frame - - var width = temp_label.get_content_width() + 5 - - remove_child(temp_label) - temp_label.queue_free() - - return max(width, 30) # Minimum pixels - -func create_li_node(element: HTMLParser.HTMLElement, list_type: String, index: int, marker_width: float = 30, parser: HTMLParser = null) -> Control: - var li_container = HBoxContainer.new() - - # Create number/letter marker - var marker_label = RichTextLabel.new() - marker_label.custom_minimum_size = Vector2(marker_width, 0) - marker_label.size_flags_horizontal = Control.SIZE_SHRINK_CENTER - marker_label.size_flags_vertical = Control.SIZE_SHRINK_CENTER - marker_label.bbcode_enabled = true - marker_label.fit_content = true - marker_label.scroll_active = false - marker_label.theme = BROWSER_TEXT - - var marker_text = get_marker_for_type(list_type, index) - - var marker_styles = parser.get_element_styles_with_inheritance(element, "", []) if parser else {} - StyleManager.apply_styles_to_label(marker_label, marker_styles, element, parser, marker_text) - - # Create content - var content_label = RichTextLabel.new() - content_label.size_flags_horizontal = Control.SIZE_EXPAND_FILL - content_label.bbcode_enabled = true - content_label.fit_content = true - content_label.scroll_active = false - content_label.theme = BROWSER_TEXT - - var content_text = element.get_bbcode_formatted_text(parser) - - var content_styles = parser.get_element_styles_with_inheritance(element, "", []) if parser else {} - StyleManager.apply_styles_to_label(content_label, content_styles, element, parser, content_text) - - li_container.add_theme_constant_override("separation", 0) - li_container.add_child(marker_label) - li_container.add_child(content_label) - - var styles = parser.get_element_styles_with_inheritance(element, "", []) - if BackgroundUtils.needs_background_wrapper(styles): - var panel_container = BackgroundUtils.create_panel_container_with_background(styles) - panel_container.name = "Li" - # Get the VBoxContainer inside PanelContainer and replace it with our HBoxContainer - var vbox = panel_container.get_child(0) - panel_container.remove_child(vbox) - vbox.queue_free() - panel_container.add_child(li_container) - return panel_container - else: - return li_container - -func get_marker_for_type(list_type: String, index: int) -> String: - match list_type: - "decimal": - return str(index) + "." - "zero-lead": - return "%02d." % index - "lower-alpha", "lower-roman": - return char(96 + index) + "." if list_type == "lower-alpha" else int_to_roman(index).to_lower() + "." - "upper-alpha", "upper-roman": - return char(64 + index) + "." if list_type == "upper-alpha" else int_to_roman(index) + "." - "none": - return "" - _: - return str(index) + "." # Default to decimal - -func int_to_roman(num: int) -> String: - var values = [1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1] - var symbols = ["M", "CM", "D", "CD", "C", "XC", "L", "XL", "X", "IX", "V", "IV", "I"] - var result = "" - - for i in range(values.size()): - while num >= values[i]: - result += symbols[i] - num -= values[i] - - return result +func _ready(): + is_ordered = true + super._ready() diff --git a/flumi/Scripts/Tags/ul.gd b/flumi/Scripts/Tags/ul.gd index a862daa..7b84a10 100644 --- a/flumi/Scripts/Tags/ul.gd +++ b/flumi/Scripts/Tags/ul.gd @@ -1,97 +1,5 @@ -extends VBoxContainer +extends BaseListContainer -const BROWSER_TEXT = preload("res://Scenes/Styles/BrowserText.tres") - -func init(element: HTMLParser.HTMLElement, parser: HTMLParser = null) -> void: - var list_type = element.get_attribute("type").to_lower() - if list_type == "": list_type = "disc" # Default - - var marker_min_width = await calculate_marker_width(list_type) - - for child_element in element.children: - if child_element.tag_name == "li": - var li_node = create_li_node(child_element, list_type, marker_min_width, parser) - if li_node: - add_child(li_node) - -func calculate_marker_width(list_type: String) -> float: - var temp_label = RichTextLabel.new() - temp_label.bbcode_enabled = true - temp_label.fit_content = true - temp_label.scroll_active = false - temp_label.theme = BROWSER_TEXT - add_child(temp_label) - - var bullet_text = get_bullet_for_type(list_type) - StyleManager.apply_styles_to_label(temp_label, {}, null, null, bullet_text) - - await get_tree().process_frame - - var width = temp_label.get_content_width() + 5 # padding - - remove_child(temp_label) - temp_label.queue_free() - - return max(width, 20) # Minimum pixels - -func create_li_node(element: HTMLParser.HTMLElement, list_type: String, marker_width: float = 20, parser: HTMLParser = null) -> Control: - var li_container = HBoxContainer.new() - - # Create bullet point - var bullet_label = RichTextLabel.new() - bullet_label.custom_minimum_size = Vector2(marker_width, 0) - bullet_label.size_flags_horizontal = Control.SIZE_SHRINK_CENTER - bullet_label.size_flags_vertical = Control.SIZE_SHRINK_CENTER - bullet_label.bbcode_enabled = true - bullet_label.fit_content = true - bullet_label.scroll_active = false - bullet_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER - bullet_label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER - bullet_label.theme = BROWSER_TEXT - - var bullet_text = get_bullet_for_type(list_type) - - var bullet_styles = parser.get_element_styles_with_inheritance(element, "", []) if parser else {} - StyleManager.apply_styles_to_label(bullet_label, bullet_styles, element, parser, bullet_text) - - # Create content - var content_label = RichTextLabel.new() - content_label.size_flags_horizontal = Control.SIZE_EXPAND_FILL - content_label.bbcode_enabled = true - content_label.fit_content = true - content_label.theme = BROWSER_TEXT - content_label.scroll_active = false - var content_text = element.get_bbcode_formatted_text(parser) - - var content_styles = parser.get_element_styles_with_inheritance(element, "", []) if parser else {} - StyleManager.apply_styles_to_label(content_label, content_styles, element, parser, content_text) - - li_container.add_theme_constant_override("separation", 0) - li_container.add_child(bullet_label) - li_container.add_child(content_label) - - var styles = parser.get_element_styles_with_inheritance(element, "", []) - if BackgroundUtils.needs_background_wrapper(styles): - var panel_container = BackgroundUtils.create_panel_container_with_background(styles) - panel_container.name = "Li" - # Get the VBoxContainer inside PanelContainer and replace it with our HBoxContainer - var vbox = panel_container.get_child(0) - panel_container.remove_child(vbox) - vbox.queue_free() - panel_container.add_child(li_container) - return panel_container - else: - return li_container - -func get_bullet_for_type(list_type: String) -> String: - match list_type: - "circle": - return "◦" - "disc": - return "•" - "square": - return "■" - "none": - return " " - _: - return "•" # Default to disc +func _ready(): + is_ordered = false + super._ready() diff --git a/flumi/Scripts/Utils/Lua/Function.gd b/flumi/Scripts/Utils/Lua/Function.gd index a0898c1..c9539df 100644 --- a/flumi/Scripts/Utils/Lua/Function.gd +++ b/flumi/Scripts/Utils/Lua/Function.gd @@ -35,6 +35,18 @@ static func setup_gurt_api(vm: LuauVM, lua_api, dom_parser: HTMLParser) -> void: vm.lua_pushcallable(lua_api._gurt_select_handler, "gurt.select") vm.lua_setfield(-2, "select") + vm.lua_pushcallable(lua_api._gurt_select_all_handler, "gurt.select_all") + vm.lua_setfield(-2, "select_all") + + vm.lua_pushcallable(lua_api._gurt_create_handler, "gurt.create") + vm.lua_setfield(-2, "create") + + vm.lua_pushcallable(lua_api._gurt_set_timeout_handler, "gurt.set_timeout") + vm.lua_setfield(-2, "set_timeout") + + vm.lua_pushcallable(lua_api._gurt_clear_timeout_handler, "gurt.clear_timeout") + vm.lua_setfield(-2, "clear_timeout") + # Add body element access var body_element = dom_parser.find_first("body") if body_element: @@ -52,6 +64,9 @@ static func setup_gurt_api(vm: LuauVM, lua_api, dom_parser: HTMLParser) -> void: vm.lua_pushcallable(lua_api._body_on_event_handler, "body.on") vm.lua_setfield(-2, "on") + vm.lua_pushcallable(lua_api._element_append_handler, "body.append") + vm.lua_setfield(-2, "append") + vm.lua_setfield(-2, "body") vm.lua_setglobal("gurt") diff --git a/flumi/Scripts/Utils/Lua/Print.gd b/flumi/Scripts/Utils/Lua/Print.gd index 631528a..c4f4fed 100644 --- a/flumi/Scripts/Utils/Lua/Print.gd +++ b/flumi/Scripts/Utils/Lua/Print.gd @@ -10,9 +10,12 @@ static func lua_print(vm: LuauVM) -> int: message_parts.append(value_str) var final_message = "\t".join(message_parts) - print("GURT LOG: ", final_message) + lua_print_direct(final_message) return 0 +static func lua_print_direct(msg) -> void: + print("GURT LOG: ", msg) + static func lua_value_to_string(vm: LuauVM, index: int) -> String: var lua_type = vm.lua_type(index) @@ -84,4 +87,4 @@ static func table_to_string(vm: LuauVM, index: int, max_depth: int = 3, current_ result += ", ..." result += "}" - return result \ No newline at end of file + return result diff --git a/flumi/Scripts/Utils/Lua/Timeout.gd b/flumi/Scripts/Utils/Lua/Timeout.gd new file mode 100644 index 0000000..e708e57 --- /dev/null +++ b/flumi/Scripts/Utils/Lua/Timeout.gd @@ -0,0 +1,102 @@ +class_name LuaTimeoutManager +extends RefCounted + +var active_timeouts: Dictionary = {} +var next_timeout_id: int = 1 +var next_callback_ref: int = 1 + +class TimeoutInfo: + var id: int + var callback_ref: int + var vm: LuauVM + var timer: Timer + var timeout_manager: LuaTimeoutManager + + func _init(timeout_id: int, cb_ref: int, lua_vm: LuauVM, manager: LuaTimeoutManager): + id = timeout_id + callback_ref = cb_ref + vm = lua_vm + timeout_manager = manager + +func set_timeout_handler(vm: LuauVM, parent_node: Node) -> int: + vm.luaL_checktype(1, vm.LUA_TFUNCTION) + var delay_ms: int = vm.luaL_checkint(2) + + var timeout_id = next_timeout_id + next_timeout_id += 1 + + # Store the callback function in the registry + vm.lua_pushvalue(1) + var callback_ref = vm.luaL_ref(vm.LUA_REGISTRYINDEX) + + # Create timeout info + var timeout_info = TimeoutInfo.new(timeout_id, callback_ref, vm, self) + + # Create and configure timer + var timer = Timer.new() + timer.wait_time = delay_ms / 1000.0 + timer.one_shot = true + timer.timeout.connect(_on_timeout_triggered.bind(timeout_info)) + + timeout_info.timer = timer + active_timeouts[timeout_id] = timeout_info + + # Add timer to scene tree + parent_node.add_child(timer) + timer.start() + + # Return timeout ID + vm.lua_pushinteger(timeout_id) + return 1 + +func clear_timeout_handler(vm: LuauVM) -> int: + var timeout_id: int = vm.luaL_checkint(1) + + var timeout_info = active_timeouts.get(timeout_id, null) + if timeout_info: + # Stop and remove timer + if timeout_info.timer: + timeout_info.timer.stop() + timeout_info.timer.queue_free() + + # Clean up callback reference + vm.lua_pushnil() + vm.lua_rawseti(vm.LUA_REGISTRYINDEX, timeout_info.callback_ref) + + # Remove from active timeouts + active_timeouts.erase(timeout_id) + + return 0 + +func _on_timeout_triggered(timeout_info: TimeoutInfo) -> void: + if not active_timeouts.has(timeout_info.id): + return + + # Execute the callback + timeout_info.vm.lua_rawgeti(timeout_info.vm.LUA_REGISTRYINDEX, timeout_info.callback_ref) + if timeout_info.vm.lua_isfunction(-1): + if timeout_info.vm.lua_pcall(0, 0, 0) != timeout_info.vm.LUA_OK: + print("GURT ERROR in timeout callback: ", timeout_info.vm.lua_tostring(-1)) + timeout_info.vm.lua_pop(1) + else: + timeout_info.vm.lua_pop(1) + + # Clean up timeout + timeout_info.timer.queue_free() + timeout_info.vm.lua_pushnil() + timeout_info.vm.lua_rawseti(timeout_info.vm.LUA_REGISTRYINDEX, timeout_info.callback_ref) + active_timeouts.erase(timeout_info.id) + +func cleanup_all_timeouts(): + # Clean up all active timeouts + for timeout_id in active_timeouts: + var timeout_info = active_timeouts[timeout_id] + if timeout_info.timer: + timeout_info.timer.stop() + timeout_info.timer.queue_free() + + # Release Lua callback reference + if timeout_info.vm and timeout_info.callback_ref: + timeout_info.vm.lua_pushnil() + timeout_info.vm.lua_rawseti(timeout_info.vm.LUA_REGISTRYINDEX, timeout_info.callback_ref) + active_timeouts.clear() \ No newline at end of file diff --git a/flumi/Scripts/Utils/Lua/Timeout.gd.uid b/flumi/Scripts/Utils/Lua/Timeout.gd.uid new file mode 100644 index 0000000..561bd0f --- /dev/null +++ b/flumi/Scripts/Utils/Lua/Timeout.gd.uid @@ -0,0 +1 @@ +uid://dlhj6h7qyo5hp diff --git a/flumi/Scripts/main.gd b/flumi/Scripts/main.gd index a40e087..d5202d9 100644 --- a/flumi/Scripts/main.gd +++ b/flumi/Scripts/main.gd @@ -24,7 +24,6 @@ const INPUT = preload("res://Scenes/Tags/input.tscn") const BUTTON = preload("res://Scenes/Tags/button.tscn") const UL = preload("res://Scenes/Tags/ul.tscn") const OL = preload("res://Scenes/Tags/ol.tscn") -const LI = preload("res://Scenes/Tags/li.tscn") const SELECT = preload("res://Scenes/Tags/select.tscn") const OPTION = preload("res://Scenes/Tags/option.tscn") const TEXTAREA = preload("res://Scenes/Tags/textarea.tscn") @@ -347,8 +346,8 @@ func create_element_node_internal(element: HTMLParser.HTMLElement, parser: HTMLP await node.init(element, parser) return node "li": - node = LI.instantiate() - node.init(element, parser) + node = P.instantiate() + node.init(element) "select": node = SELECT.instantiate() node.init(element)