diff --git a/flumi/Scenes/Tags/button.tscn b/flumi/Scenes/Tags/button.tscn index 68a4708..0a5ef6d 100644 --- a/flumi/Scenes/Tags/button.tscn +++ b/flumi/Scenes/Tags/button.tscn @@ -3,22 +3,16 @@ [ext_resource type="Script" uid="uid://cks35eudcm1wj" path="res://Scripts/Tags/button.gd" id="1_button"] [ext_resource type="Theme" uid="uid://bn6rbmdy60lhr" path="res://Scenes/Styles/BrowserText.tres" id="2_theme"] -[node name="Button" type="Control"] -custom_minimum_size = Vector2(64, 30) -layout_mode = 3 -anchors_preset = 0 -offset_right = 64.0 -offset_bottom = 30.0 +[node name="Button" type="HBoxContainer"] +offset_right = 54.0 +offset_bottom = 23.0 script = ExtResource("1_button") [node name="ButtonNode" type="Button" parent="."] -custom_minimum_size = Vector2(64, 0) -layout_mode = 1 -anchors_preset = 15 -anchor_right = 1.0 -anchor_bottom = 1.0 -grow_horizontal = 2 -grow_vertical = 2 +custom_minimum_size = Vector2(64, 30) +layout_mode = 2 +size_flags_horizontal = 0 +size_flags_vertical = 4 mouse_default_cursor_shape = 2 theme = ExtResource("2_theme") text = "Button" diff --git a/flumi/Scenes/Tags/option.tscn b/flumi/Scenes/Tags/option.tscn index ab0b65b..955b304 100644 --- a/flumi/Scenes/Tags/option.tscn +++ b/flumi/Scenes/Tags/option.tscn @@ -1,5 +1,6 @@ -[gd_scene load_steps=2 format=3 uid="uid://bopt1234568aa"] +[gd_scene load_steps=3 format=3 uid="uid://bopt1234568aa"] +[ext_resource type="Script" uid="uid://c66r24cncb1dp" path="res://Scripts/Tags/option.gd" id="1_tq7db"] [ext_resource type="Theme" uid="uid://bn6rbmdy60lhr" path="res://Scenes/Styles/BrowserText.tres" id="2_theme"] [node name="option" type="Control"] @@ -7,6 +8,7 @@ layout_mode = 3 anchors_preset = 10 anchor_right = 1.0 grow_horizontal = 2 +script = ExtResource("1_tq7db") [node name="RichTextLabel" type="RichTextLabel" parent="."] layout_mode = 2 diff --git a/flumi/Scripts/B9/CSSParser.gd b/flumi/Scripts/B9/CSSParser.gd index 865dad9..4c745ac 100644 --- a/flumi/Scripts/B9/CSSParser.gd +++ b/flumi/Scripts/B9/CSSParser.gd @@ -75,6 +75,16 @@ class CSSStylesheet: func add_rule(rule: CSSRule): rules.append(rule) + func find_rule_by_selector(selector: String) -> CSSRule: + for rule in rules: + if rule.selector == selector and rule.event_prefix == "": + return rule + + for rule in rules: + if rule.selector == selector: + return rule + return null + func get_styles_for_element(tag_name: String, event: String = "", class_names: Array[String] = [], element: HTMLParser.HTMLElement = null) -> Dictionary: var styles = {} @@ -362,8 +372,13 @@ func parse_utility_class(rule: CSSRule, utility_name: String) -> void: if utility_name.begins_with(prefix): var actual_utility = utility_name.substr(prefix.length()) var pseudo_rule = CSSRule.new() - pseudo_rule.selector = rule.selector + ":" + pseudo - pseudo_rule.init(pseudo_rule.selector) + + pseudo_rule.selector = rule.selector + pseudo_rule.event_prefix = pseudo + pseudo_rule.selector_type = "simple" + pseudo_rule.selector_parts = [rule.selector] + pseudo_rule.calculate_specificity() + parse_utility_class_internal(pseudo_rule, actual_utility) stylesheet.add_rule(pseudo_rule) return @@ -374,10 +389,19 @@ func parse_utility_class(rule: CSSRule, utility_name: String) -> void: # Parses a utility class (e.g. "text-red-500") and adds properties to the rule (e.g. "color: red") # Used as a translation layer for Tailwind-like utility classes, as it becomes easier to manage these programmatically static func parse_utility_class_internal(rule: CSSRule, utility_name: String) -> void: - # Handle color classes like text-[#ff0000] + # Handle font size classes like text-[16px] or color classes like text-[#ff0000] if utility_name.begins_with("text-[") and utility_name.ends_with("]"): - var color_value = SizeUtils.extract_bracket_content(utility_name, 5) # after 'text-' - var parsed_color = ColorUtils.parse_color(color_value) + var bracket_content = SizeUtils.extract_bracket_content(utility_name, 5) # after 'text-' + + # Check if it's a font size by looking for size units or being a valid number + if bracket_content.ends_with("px") or bracket_content.ends_with("em") or bracket_content.ends_with("rem") or bracket_content.is_valid_int() or bracket_content.is_valid_float(): + var font_size_value = SizingUtils.parse_size_value(bracket_content) + if font_size_value != null and typeof(font_size_value) != TYPE_STRING: + rule.properties["font-size"] = font_size_value + return + + # Parse as color + var parsed_color = ColorUtils.parse_color(bracket_content) rule.properties["color"] = parsed_color return diff --git a/flumi/Scripts/B9/HTMLParser.gd b/flumi/Scripts/B9/HTMLParser.gd index bdb1068..f745aea 100644 --- a/flumi/Scripts/B9/HTMLParser.gd +++ b/flumi/Scripts/B9/HTMLParser.gd @@ -200,12 +200,30 @@ func parse_inline_style_with_event(style_string: String, event: String = "") -> CSSParser.parse_utility_class_internal(rule, actual_utility) for property in rule.properties: properties[property] = rule.properties[property] + else: + # Check if this is a CSS class that might have pseudo-class rules + if parse_result.css_parser and parse_result.css_parser.stylesheet: + var pseudo_styles = parse_result.css_parser.stylesheet.get_styles_for_element("", event, [utility_name], null) + if not pseudo_styles.is_empty(): + for property in pseudo_styles: + properties[property] = pseudo_styles[property] else: if not utility_name.contains(":"): - var rule = CSSParser.CSSRule.new() - CSSParser.parse_utility_class_internal(rule, utility_name) - for property in rule.properties: - properties[property] = rule.properties[property] + if parse_result.css_parser and parse_result.css_parser.stylesheet: + var css_rule = parse_result.css_parser.stylesheet.find_rule_by_selector("." + utility_name) + if css_rule: + for property in css_rule.properties: + properties[property] = css_rule.properties[property] + else: + var rule = CSSParser.CSSRule.new() + CSSParser.parse_utility_class_internal(rule, utility_name) + for property in rule.properties: + properties[property] = rule.properties[property] + else: + var rule = CSSParser.CSSRule.new() + CSSParser.parse_utility_class_internal(rule, utility_name) + for property in rule.properties: + properties[property] = rule.properties[property] return properties diff --git a/flumi/Scripts/B9/Lua.gd b/flumi/Scripts/B9/Lua.gd index c8dc9b5..ee11acd 100644 --- a/flumi/Scripts/B9/Lua.gd +++ b/flumi/Scripts/B9/Lua.gd @@ -213,6 +213,25 @@ func _element_index_handler(vm: LuauVM) -> int: vm.lua_rawseti(-2, index) index += 1 + return 1 + "classList": + # Create classList object with add, remove, toggle methods + vm.lua_newtable() + + # Add methods to classList using the utility class + vm.lua_pushcallable(_element_classlist_add_wrapper, "classList.add") + vm.lua_setfield(-2, "add") + + vm.lua_pushcallable(_element_classlist_remove_wrapper, "classList.remove") + vm.lua_setfield(-2, "remove") + + vm.lua_pushcallable(_element_classlist_toggle_wrapper, "classList.toggle") + vm.lua_setfield(-2, "toggle") + + # Store element reference for the classList methods + vm.lua_getfield(1, "_element_id") + vm.lua_setfield(-2, "_element_id") + return 1 _: # Fall back to checking the original table for methods @@ -333,6 +352,15 @@ func _element_remove_handler(vm: LuauVM) -> int: return 0 +func _element_classlist_add_wrapper(vm: LuauVM) -> int: + return LuaClassListUtils.element_classlist_add_handler(vm, dom_parser) + +func _element_classlist_remove_wrapper(vm: LuauVM) -> int: + return LuaClassListUtils.element_classlist_remove_handler(vm, dom_parser) + +func _element_classlist_toggle_wrapper(vm: LuauVM) -> int: + return LuaClassListUtils.element_classlist_toggle_handler(vm, dom_parser) + 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") diff --git a/flumi/Scripts/Constants.gd b/flumi/Scripts/Constants.gd index 27b017b..dce64bd 100644 --- a/flumi/Scripts/Constants.gd +++ b/flumi/Scripts/Constants.gd @@ -21,7 +21,7 @@ code { text-xl font-mono } a { text-[#1a0dab] } pre { text-xl font-mono } -button { bg-[#1b1b1b] rounded-md text-white hover:bg-[#2a2a2a] active:bg-[#101010] } +button { text-[16px] bg-[#1b1b1b] rounded-md text-white hover:bg-[#2a2a2a] active:bg-[#101010] } button[disabled] { bg-[#666666] text-[#999999] cursor-not-allowed } """ @@ -100,7 +100,7 @@ var HTML_CONTENT2 = """ """.to_utf8_buffer() -var HTML_CONTENTbv = """ +var HTML_CONTENTvv = """ My cool web @@ -599,9 +599,10 @@ var HTML_CONTENT = """ @@ -714,6 +730,14 @@ var HTML_CONTENT = """

Move mouse over Button

Type something

+ +
+
Style Controls:
+ + +
+ + """.to_utf8_buffer() var HTML_CONTENT_ADD_REMOVE = """ diff --git a/flumi/Scripts/StyleManager.gd b/flumi/Scripts/StyleManager.gd index b3baee4..9b6f752 100644 --- a/flumi/Scripts/StyleManager.gd +++ b/flumi/Scripts/StyleManager.gd @@ -126,42 +126,180 @@ static func apply_element_styles(node: Control, element: HTMLParser.HTMLElement, # Check for margins first and wrap in MarginContainer if needed var has_margin = styles.has("margin") or styles.has("margin-top") or styles.has("margin-right") or styles.has("margin-bottom") or styles.has("margin-left") + node = handle_margin_wrapper(node, styles, has_margin) - if has_margin: - node = apply_margin_wrapper(node, styles) - - # Apply background color, border radius, borders - var needs_styling = styles.has("background-color") or styles.has("border-radius") or styles.has("border-width") or styles.has("border-top-width") or styles.has("border-right-width") or styles.has("border-bottom-width") or styles.has("border-left-width") or styles.has("border-color") + var needs_styling = styles.has("background-color") or styles.has("border-radius") or styles.has("border-width") or styles.has("border-top-width") or styles.has("border-right-width") or styles.has("border-bottom-width") or styles.has("border-left-width") or styles.has("border-color") or styles.has("padding") or styles.has("padding-top") or styles.has("padding-right") or styles.has("padding-bottom") or styles.has("padding-left") if needs_styling: - var target_node_for_bg = node if node is FlexContainer else label + # If node is a MarginContainer wrapper, get the actual content node for styling + var content_node = node + if node is MarginContainer and node.name.begins_with("MarginWrapper_"): + if node.get_child_count() > 0: + content_node = node.get_child(0) + + var target_node_for_bg = content_node if content_node is FlexContainer else (label if label else content_node) if target_node_for_bg: - if styles.has("background-color"): - target_node_for_bg.set_meta("custom_css_background_color", styles["background-color"]) - if styles.has("border-radius"): - target_node_for_bg.set_meta("custom_css_border_radius", styles["border-radius"]) + # Clear existing metadata first to ensure clean state + clear_styling_metadata(target_node_for_bg) - # Border properties - if styles.has("border-width"): - target_node_for_bg.set_meta("custom_css_border_width", styles["border-width"]) - if styles.has("border-color"): - target_node_for_bg.set_meta("custom_css_border_color", styles["border-color"]) + # Set new metadata based on current styles + set_styling_metadata(target_node_for_bg, styles) + + if target_node_for_bg is FlexContainer: + BackgroundUtils.update_background_panel(target_node_for_bg) + elif target_node_for_bg is PanelContainer: + apply_stylebox_to_panel_container(target_node_for_bg, styles) + else: + apply_stylebox_to_container_direct(target_node_for_bg, styles) - # Individual border sides - var border_sides = ["top", "right", "bottom", "left"] - for side in border_sides: - var width_key = "border-" + side + "-width" - if styles.has(width_key): - target_node_for_bg.set_meta("custom_css_" + width_key.replace("-", "_"), styles[width_key]) - if target_node_for_bg.has_method("add_background_rect"): target_node_for_bg.call_deferred("add_background_rect") + else: + var content_node = node + if node is MarginContainer and node.name.begins_with("MarginWrapper_"): + if node.get_child_count() > 0: + content_node = node.get_child(0) + + var target_node_for_bg = content_node if content_node is FlexContainer else (label if label else content_node) + if target_node_for_bg: + clear_styling_metadata(target_node_for_bg) + + if target_node_for_bg is FlexContainer: + BackgroundUtils.update_background_panel(target_node_for_bg) + elif target_node_for_bg is PanelContainer: + target_node_for_bg.remove_theme_stylebox_override("panel") + else: + target_node_for_bg.remove_theme_stylebox_override("panel") + target_node_for_bg.remove_theme_stylebox_override("background") if label: apply_styles_to_label(label, styles, element, parser) return node +static func apply_stylebox_to_panel_container(panel_container: PanelContainer, styles: Dictionary) -> void: + var has_visual_styles = BackgroundUtils.needs_background_wrapper(styles) + + if has_visual_styles: + var style_box = BackgroundUtils.create_stylebox_from_styles(styles, panel_container) + panel_container.add_theme_stylebox_override("panel", style_box) + else: + panel_container.remove_theme_stylebox_override("panel") + clear_styling_metadata(panel_container) + +static func apply_stylebox_to_container_direct(container: Control, styles: Dictionary) -> void: + var has_visual_styles = BackgroundUtils.needs_background_wrapper(styles) + + if has_visual_styles: + var style_box = BackgroundUtils.create_stylebox_from_styles(styles, container) + + container.add_theme_stylebox_override("panel", style_box) + container.add_theme_stylebox_override("background", style_box) + else: + container.remove_theme_stylebox_override("panel") + container.remove_theme_stylebox_override("background") + clear_styling_metadata(container) + +static func set_styling_metadata(node: Control, styles: Dictionary) -> void: + # Basic styling properties + var basic_properties = [ + ["background-color", "custom_css_background_color"], + ["border-radius", "custom_css_border_radius"], + ["border-width", "custom_css_border_width"], + ["border-color", "custom_css_border_color"] + ] + + for prop in basic_properties: + if styles.has(prop[0]): + node.set_meta(prop[1], styles[prop[0]]) + + # Padding properties + var padding_properties = [ + ["padding", "padding"], + ["padding-top", "padding_top"], + ["padding-right", "padding_right"], + ["padding-bottom", "padding_bottom"], + ["padding-left", "padding_left"] + ] + + for prop in padding_properties: + if styles.has(prop[0]): + node.set_meta(prop[1], styles[prop[0]]) + + # Individual border sides + var border_sides = ["top", "right", "bottom", "left"] + for side in border_sides: + var width_key = "border-" + side + "-width" + if styles.has(width_key): + node.set_meta("custom_css_" + width_key.replace("-", "_"), styles[width_key]) + +static func clear_styling_metadata(node: Control) -> void: + var metadata_keys = [ + "custom_css_background_color", + "custom_css_border_radius", + "custom_css_border_width", + "custom_css_border_color", + "padding", + "padding_top", + "padding_right", + "padding_bottom", + "padding_left" + ] + + for key in metadata_keys: + if node.has_meta(key): + node.remove_meta(key) + +static func handle_margin_wrapper(node: Control, styles: Dictionary, needs_margin: bool): + var current_wrapper = null + + if node is MarginContainer and node.name.begins_with("MarginWrapper_"): + current_wrapper = node + + elif node.get_parent() and node.get_parent() is MarginContainer: + var parent = node.get_parent() + if parent.name.begins_with("MarginWrapper_"): + current_wrapper = parent + + if needs_margin: + if current_wrapper: + update_margin_wrapper(current_wrapper, styles) + return current_wrapper + else: + return apply_margin_wrapper(node, styles) + else: + if current_wrapper: + if current_wrapper == node: + if node.get_child_count() > 0: + var content_node = node.get_child(0) + return remove_margin_wrapper(current_wrapper, content_node) + else: + return remove_margin_wrapper(current_wrapper, node) + else: + return node + +static func update_margin_wrapper(margin_container: MarginContainer, styles: Dictionary) -> void: + clear_margin_overrides(margin_container) + apply_margin_styles_to_container(margin_container, styles) + +static func remove_margin_wrapper(margin_container: MarginContainer, original_node: Control) -> Control: + var original_parent = margin_container.get_parent() + var node_index = margin_container.get_index() + + original_node.size_flags_horizontal = margin_container.size_flags_horizontal + original_node.size_flags_vertical = margin_container.size_flags_vertical + + margin_container.remove_child(original_node) + + if original_parent: + original_parent.remove_child(margin_container) + original_parent.add_child(original_node) + original_parent.move_child(original_node, node_index) + + margin_container.queue_free() + + return original_node + static func apply_margin_wrapper(node: Control, styles: Dictionary) -> Control: var margin_container = MarginContainer.new() margin_container.name = "MarginWrapper_" + node.name @@ -170,35 +308,7 @@ static func apply_margin_wrapper(node: Control, styles: Dictionary) -> Control: margin_container.size_flags_horizontal = node.size_flags_horizontal margin_container.size_flags_vertical = node.size_flags_vertical - # Set margin values using theme overrides - var general_margin_str = null - if styles.has("margin"): - general_margin_str = styles["margin"] - - if general_margin_str != null: - var general_margin = parse_size(general_margin_str) - if general_margin != null: - margin_container.add_theme_constant_override("margin_top", general_margin) - margin_container.add_theme_constant_override("margin_right", general_margin) - margin_container.add_theme_constant_override("margin_bottom", general_margin) - margin_container.add_theme_constant_override("margin_left", general_margin) - - # Individual margin overrides - var margin_sides = [ - ["margin-top", "margin_top"], - ["margin-right", "margin_right"], - ["margin-bottom", "margin_bottom"], - ["margin-left", "margin_left"] - ] - - for side_pair in margin_sides: - var style_key = side_pair[0] - var theme_key = side_pair[1] - if styles.has(style_key): - var margin_val_str = styles[style_key] - var margin_val = parse_size(margin_val_str) - if margin_val != null: - margin_container.add_theme_constant_override(theme_key, margin_val) + apply_margin_styles_to_container(margin_container, styles) # Reset the original node's size flags since they're now handled by the wrapper node.size_flags_horizontal = Control.SIZE_EXPAND_FILL @@ -217,6 +327,37 @@ static func apply_margin_wrapper(node: Control, styles: Dictionary) -> Control: return margin_container +static func clear_margin_overrides(margin_container: MarginContainer) -> void: + margin_container.remove_theme_constant_override("margin_top") + margin_container.remove_theme_constant_override("margin_right") + margin_container.remove_theme_constant_override("margin_bottom") + margin_container.remove_theme_constant_override("margin_left") + +static func apply_margin_styles_to_container(margin_container: MarginContainer, styles: Dictionary) -> void: + # Apply general margin first + if styles.has("margin"): + var general_margin = parse_size(styles["margin"]) + if general_margin != null: + var margin_sides = ["margin_top", "margin_right", "margin_bottom", "margin_left"] + for side in margin_sides: + margin_container.add_theme_constant_override(side, general_margin) + + # Apply individual margin overrides + var margin_mappings = [ + ["margin-top", "margin_top"], + ["margin-right", "margin_right"], + ["margin-bottom", "margin_bottom"], + ["margin-left", "margin_left"] + ] + + for mapping in margin_mappings: + var style_key = mapping[0] + var theme_key = mapping[1] + if styles.has(style_key): + var margin_val = parse_size(styles[style_key]) + if margin_val != null: + margin_container.add_theme_constant_override(theme_key, margin_val) + static func apply_styles_to_label(label: Control, styles: Dictionary, element: HTMLParser.HTMLElement, parser, text_override: String = "") -> void: if label is Button: apply_font_to_button(label, styles) @@ -359,27 +500,28 @@ static func apply_body_styles(body: HTMLParser.HTMLElement, parser: HTMLParser, original_parent.move_child(margin_container, container_index) margin_container.add_child(website_container) - var margin_val = parse_size(styles["padding"]) + var padding_val = parse_size(styles["padding"]) - margin_container.add_theme_constant_override("margin_left", margin_val) - margin_container.add_theme_constant_override("margin_right", margin_val) - margin_container.add_theme_constant_override("margin_top", margin_val) - margin_container.add_theme_constant_override("margin_bottom", margin_val) + margin_container.add_theme_constant_override("margin_left", padding_val) + margin_container.add_theme_constant_override("margin_right", padding_val) + margin_container.add_theme_constant_override("margin_top", padding_val) + margin_container.add_theme_constant_override("margin_bottom", padding_val) - # Apply individual padding values - var padding_sides = [ + # Apply individual padding values using our helper function + var padding_mappings = [ ["padding-top", "margin_top"], ["padding-right", "margin_right"], ["padding-bottom", "margin_bottom"], ["padding-left", "margin_left"] ] - for side_pair in padding_sides: - var style_key = side_pair[0] - var margin_key = side_pair[1] + for mapping in padding_mappings: + var style_key = mapping[0] + var margin_key = mapping[1] if styles.has(style_key): - var margin_val2 = parse_size(styles[style_key]) - margin_container.add_theme_constant_override(margin_key, margin_val2) + var margin_val = parse_size(styles[style_key]) + if margin_val != null: + margin_container.add_theme_constant_override(margin_key, margin_val) static func parse_radius(radius_str: String) -> int: return SizeUtils.parse_radius(radius_str) diff --git a/flumi/Scripts/Tags/button.gd b/flumi/Scripts/Tags/button.gd index 071f527..eab12b4 100644 --- a/flumi/Scripts/Tags/button.gd +++ b/flumi/Scripts/Tags/button.gd @@ -1,5 +1,5 @@ class_name HTMLButton -extends Control +extends HBoxContainer var current_element: HTMLParser.HTMLElement var current_parser: HTMLParser @@ -18,26 +18,17 @@ func init(element: HTMLParser.HTMLElement, parser: HTMLParser = null) -> void: if button_text.length() > 0: button_node.text = button_text - var natural_size = button_node.get_theme_default_font().get_string_size( - button_node.text, - HORIZONTAL_ALIGNMENT_LEFT, - -1, - button_node.get_theme_default_font_size() - ) + Vector2(20, 10) # Add padding - - # Force our container to use the natural size - custom_minimum_size = natural_size + # Set container to shrink to fit content size_flags_horizontal = Control.SIZE_SHRINK_BEGIN size_flags_vertical = Control.SIZE_SHRINK_BEGIN - # Make button node fill the container - button_node.custom_minimum_size = Vector2.ZERO - button_node.size_flags_horizontal = Control.SIZE_FILL - button_node.size_flags_vertical = Control.SIZE_FILL + # Let button size itself naturally + button_node.size_flags_horizontal = Control.SIZE_SHRINK_BEGIN + button_node.size_flags_vertical = Control.SIZE_SHRINK_BEGIN - apply_button_styles(element, parser, natural_size) + apply_button_styles(element, parser) -func apply_button_styles(element: HTMLParser.HTMLElement, parser: HTMLParser, natural_size: Vector2) -> void: +func apply_button_styles(element: HTMLParser.HTMLElement, parser: HTMLParser) -> void: if not element or not parser: return @@ -51,6 +42,11 @@ func apply_button_styles(element: HTMLParser.HTMLElement, parser: HTMLParser, na mouse_default_cursor_shape = cursor_shape button_node.mouse_default_cursor_shape = cursor_shape + if styles.has("font-size"): + var font_size = int(styles["font-size"]) + print("SETTING FONT SIZE: ", font_size, " FOR BUTTON NAME: ", element.tag_name) + button_node.add_theme_font_size_override("font_size", font_size) + # Apply text color with state-dependent colors apply_button_text_color(button_node, styles, hover_styles, active_styles) @@ -87,12 +83,7 @@ func apply_button_styles(element: HTMLParser.HTMLElement, parser: HTMLParser, na # Fallback: if hover is defined but active isn't, use hover for active active_color = hover_color - apply_button_color_with_states(button_node, normal_color, hover_color, active_color) - - # Apply corner radius - if styles.has("border-radius"): - var radius = StyleManager.parse_radius(styles["border-radius"]) - apply_button_radius(button_node, radius) + apply_button_color_with_states(button_node, normal_color, hover_color, active_color, styles) var width = null var height = null @@ -102,16 +93,10 @@ func apply_button_styles(element: HTMLParser.HTMLElement, parser: HTMLParser, na if styles.has("height"): height = SizingUtils.parse_size_value(styles["height"]) - # Only apply size flags if there's explicit sizing + # Apply explicit sizing if provided if width != null or height != null: - apply_size_and_flags(self, width, height) - apply_size_and_flags(button_node, width, height, false) - else: - # Keep the natural sizing we set earlier - custom_minimum_size = natural_size - # Also ensure the ButtonNode doesn't override our size - button_node.custom_minimum_size = Vector2.ZERO - button_node.anchors_preset = Control.PRESET_FULL_RECT + apply_size_and_flags(button_node, width, height) + # Container will automatically resize to fit the button func apply_button_text_color(button: Button, normal_styles: Dictionary, hover_styles: Dictionary, active_styles: Dictionary) -> void: var normal_color = normal_styles.get("color", Color.WHITE) @@ -126,16 +111,23 @@ func apply_button_text_color(button: Button, normal_styles: Dictionary, hover_st if button.disabled: button.add_theme_color_override("font_disabled_color", normal_color) -func apply_button_color_with_states(button: Button, normal_color: Color, hover_color: Color, active_color: Color) -> void: +func apply_button_color_with_states(button: Button, normal_color: Color, hover_color: Color, active_color: Color, styles: Dictionary = {}) -> void: var style_normal = StyleBoxFlat.new() var style_hover = StyleBoxFlat.new() var style_pressed = StyleBoxFlat.new() var radius: int = 0 + if styles.has("border-radius"): + radius = StyleManager.parse_radius(styles["border-radius"]) style_normal.set_corner_radius_all(radius) style_hover.set_corner_radius_all(radius) style_pressed.set_corner_radius_all(radius) + + # Apply padding to all style boxes + apply_padding_to_stylebox(style_normal, styles) + apply_padding_to_stylebox(style_hover, styles) + apply_padding_to_stylebox(style_pressed, styles) # Set normal color style_normal.bg_color = normal_color @@ -161,28 +153,49 @@ func apply_button_color_with_states(button: Button, normal_color: Color, hover_c button.add_theme_stylebox_override("pressed", style_pressed) func apply_button_radius(button: Button, radius: int) -> void: - var style_normal = button.get_theme_stylebox("normal") - var style_hover = button.get_theme_stylebox("hover") - var style_pressed = button.get_theme_stylebox("pressed") + # Radius is now handled in create_button_stylebox + # This method is kept for backward compatibility but is deprecated + pass - style_normal.set_corner_radius_all(radius) - style_hover.set_corner_radius_all(radius) - style_pressed.set_corner_radius_all(radius) - button.add_theme_stylebox_override("normal", style_normal) - button.add_theme_stylebox_override("hover", style_hover) - button.add_theme_stylebox_override("pressed", style_pressed) +func apply_padding_to_stylebox(style_box: StyleBoxFlat, styles: Dictionary) -> void: + # Apply general padding first + if styles.has("padding"): + var padding_val = StyleManager.parse_size(styles["padding"]) + if padding_val != null: + style_box.content_margin_top = padding_val + style_box.content_margin_right = padding_val + style_box.content_margin_bottom = padding_val + style_box.content_margin_left = padding_val + + # Apply individual padding overrides + if styles.has("padding-top"): + var padding_val = StyleManager.parse_size(styles["padding-top"]) + if padding_val != null: + style_box.content_margin_top = padding_val + + if styles.has("padding-right"): + var padding_val = StyleManager.parse_size(styles["padding-right"]) + if padding_val != null: + style_box.content_margin_right = padding_val + + if styles.has("padding-bottom"): + var padding_val = StyleManager.parse_size(styles["padding-bottom"]) + if padding_val != null: + style_box.content_margin_bottom = padding_val + + if styles.has("padding-left"): + var padding_val = StyleManager.parse_size(styles["padding-left"]) + if padding_val != null: + style_box.content_margin_left = padding_val -func apply_size_and_flags(ctrl: Control, width: Variant, height: Variant, reset_layout := false) -> void: +func apply_size_and_flags(ctrl: Control, width: Variant, height: Variant) -> void: if width != null or height != null: ctrl.custom_minimum_size = Vector2( - width if width != null else ctrl.custom_minimum_size.x, - height if height != null else ctrl.custom_minimum_size.y + width if width != null else 0, + height if height != null else 0 ) if width != null: ctrl.size_flags_horizontal = Control.SIZE_SHRINK_BEGIN if height != null: ctrl.size_flags_vertical = Control.SIZE_SHRINK_BEGIN - if reset_layout: - ctrl.position = Vector2.ZERO - ctrl.anchors_preset = Control.PRESET_FULL_RECT diff --git a/flumi/Scripts/Utils/BackgroundUtils.gd b/flumi/Scripts/Utils/BackgroundUtils.gd index d9f8268..5972271 100644 --- a/flumi/Scripts/Utils/BackgroundUtils.gd +++ b/flumi/Scripts/Utils/BackgroundUtils.gd @@ -89,7 +89,7 @@ static func create_stylebox_from_styles(styles: Dictionary = {}, container: Cont if styles.size() > 0: has_padding = styles.has("padding") or styles.has("padding-top") or styles.has("padding-right") or styles.has("padding-bottom") or styles.has("padding-left") elif container: - has_padding = container.has_meta("padding") or container.has_meta("padding-top") or container.has_meta("padding-right") or container.has_meta("padding-bottom") or container.has_meta("padding-left") + has_padding = container.has_meta("padding") or container.has_meta("padding_top") or container.has_meta("padding_right") or container.has_meta("padding_bottom") or container.has_meta("padding_left") if has_padding: # General padding @@ -106,27 +106,22 @@ static func create_stylebox_from_styles(styles: Dictionary = {}, container: Cont style_box.content_margin_bottom = padding_val # Individual padding values override general padding - var padding_keys = [["padding-left", "content_margin_left"], ["padding-right", "content_margin_right"], ["padding-top", "content_margin_top"], ["padding-bottom", "content_margin_bottom"]] + var padding_mappings = [["padding-left", "content_margin_left"], ["padding-right", "content_margin_right"], ["padding-top", "content_margin_top"], ["padding-bottom", "content_margin_bottom"]] - for pair in padding_keys: - var key = pair[0] - var property = pair[1] - var val = null + for mapping in padding_mappings: + var style_key = mapping[0] + var property_key = mapping[1] + var val = get_style_or_meta_value(styles, container, style_key) - if styles.has(key): - val = StyleManager.parse_size(styles[key]) - elif container and container.has_meta(key): - val = StyleManager.parse_size(container.get_meta(key)) - - if val: - style_box.set(property, val) + if val != null: + style_box.set(property_key, val) return style_box # for AutoSizingFlexContainer static func update_background_panel(container: Control) -> void: var needs_background = container.has_meta("custom_css_background_color") or container.has_meta("custom_css_border_radius") - var needs_padding = container.has_meta("padding") or container.has_meta("padding-top") or container.has_meta("padding-right") or container.has_meta("padding-bottom") or container.has_meta("padding-left") + var needs_padding = container.has_meta("padding") or container.has_meta("padding_top") or container.has_meta("padding_right") or container.has_meta("padding_bottom") or container.has_meta("padding_left") var background_panel = get_background_panel(container) if needs_background or needs_padding: @@ -203,3 +198,10 @@ static func _on_panel_mouse_exited(panel: PanelContainer): static func needs_background_wrapper(styles: Dictionary) -> bool: return styles.has("background-color") or styles.has("border-radius") or styles.has("padding") or styles.has("padding-top") or styles.has("padding-right") or styles.has("padding-bottom") or styles.has("padding-left") or styles.has("border-width") or styles.has("border-top-width") or styles.has("border-right-width") or styles.has("border-bottom-width") or styles.has("border-left-width") or styles.has("border-color") or styles.has("border-style") or styles.has("border-top-color") or styles.has("border-right-color") or styles.has("border-bottom-color") or styles.has("border-left-color") + +static func get_style_or_meta_value(styles: Dictionary, container: Control, key: String): + if styles.has(key): + return StyleManager.parse_size(styles[key]) + elif container and container.has_meta(key): + return StyleManager.parse_size(container.get_meta(key)) + return null diff --git a/flumi/Scripts/Utils/Lua/Class.gd b/flumi/Scripts/Utils/Lua/Class.gd new file mode 100644 index 0000000..b718c9f --- /dev/null +++ b/flumi/Scripts/Utils/Lua/Class.gd @@ -0,0 +1,188 @@ +extends RefCounted +class_name LuaClassListUtils + +static func element_classlist_add_handler(vm: LuauVM, dom_parser: HTMLParser) -> int: + vm.luaL_checktype(1, vm.LUA_TTABLE) + var css_class: String = vm.luaL_checkstring(2) + + vm.lua_getfield(1, "_element_id") + var element_id: String = vm.lua_tostring(-1) + vm.lua_pop(1) + + # Find the element + var element = dom_parser.find_by_id(element_id) + if not element: + print("DEBUG: Element not found!") + return 0 + + # Get classes + var current_style = element.get_attribute("style", "") + var style_classes = CSSParser.smart_split_utility_classes(current_style) if current_style.length() > 0 else [] + + # Add new css_class if not already present + if css_class not in style_classes: + style_classes.append(css_class) + var new_style_attr = " ".join(style_classes) + element.set_attribute("style", new_style_attr) + trigger_element_restyle(element, dom_parser) + else: + print("DEBUG: classList.add - Class already exists") + + return 0 + +static func element_classlist_remove_handler(vm: LuauVM, dom_parser: HTMLParser) -> int: + vm.luaL_checktype(1, vm.LUA_TTABLE) + var css_class: String = vm.luaL_checkstring(2) + + vm.lua_getfield(1, "_element_id") + var element_id: String = vm.lua_tostring(-1) + vm.lua_pop(1) + + # Find the element + var element = dom_parser.find_by_id(element_id) + if not element: + return 0 + + # Get style attribute + var current_style = element.get_attribute("style", "") + if current_style.length() == 0: + return 0 + + var style_classes = CSSParser.smart_split_utility_classes(current_style) + var clean_classes = [] + for style_cls in style_classes: + if style_cls != css_class: + clean_classes.append(style_cls) + + # Update style attribute + if clean_classes.size() > 0: + var new_style_attr = " ".join(clean_classes) + element.set_attribute("style", new_style_attr) + else: + element.attributes.erase("style") + + trigger_element_restyle(element, dom_parser) + return 0 + +static func element_classlist_toggle_handler(vm: LuauVM, dom_parser: HTMLParser) -> int: + vm.luaL_checktype(1, vm.LUA_TTABLE) + var css_class: String = vm.luaL_checkstring(2) + + vm.lua_getfield(1, "_element_id") + var element_id: String = vm.lua_tostring(-1) + vm.lua_pop(1) + + # Find the element + var element = dom_parser.find_by_id(element_id) + if not element: + vm.lua_pushboolean(false) + return 1 + + # Get style attribute + var current_style = element.get_attribute("style", "") + var style_classes = CSSParser.smart_split_utility_classes(current_style) if current_style.length() > 0 else [] + + var has_css_class = css_class in style_classes + + if has_css_class: + # Remove css_class + var new_classes = [] + for style_cls in style_classes: + if style_cls != css_class: + new_classes.append(style_cls) + + if new_classes.size() > 0: + element.set_attribute("style", " ".join(new_classes)) + else: + element.attributes.erase("style") + + vm.lua_pushboolean(false) + else: + # Add css_class + style_classes.append(css_class) + element.set_attribute("style", " ".join(style_classes)) + vm.lua_pushboolean(true) + + trigger_element_restyle(element, dom_parser) + return 1 + +static func trigger_element_restyle(element: HTMLParser.HTMLElement, dom_parser: HTMLParser) -> void: + # Find DOM node for element + var element_id = element.get_attribute("id") + var dom_node = dom_parser.parse_result.dom_nodes.get(element_id, null) + if not dom_node: + return + + # margins, wrappers, etc. + var updated_dom_node = StyleManager.apply_element_styles(dom_node, element, dom_parser) + + # If the node was wrapped/unwrapped by margin handling, update DOM registration + if updated_dom_node != dom_node: + dom_parser.parse_result.dom_nodes[element_id] = updated_dom_node + dom_node = updated_dom_node + + # Find node + var actual_element_node = dom_node + if dom_node is MarginContainer and dom_node.name.begins_with("MarginWrapper_"): + if dom_node.get_child_count() > 0: + actual_element_node = dom_node.get_child(0) + + if actual_element_node is HTMLButton: + actual_element_node.apply_button_styles(element, dom_parser) + elif element.tag_name == "div": + update_div_hover_styles(actual_element_node, element, dom_parser) + else: + update_element_text_content(actual_element_node, element, dom_parser) + + if actual_element_node.has_method("init"): + actual_element_node.init(element, dom_parser) + +static func update_element_text_content(dom_node: Control, element: HTMLParser.HTMLElement, dom_parser: HTMLParser) -> void: + # Get node + var content_node = dom_node + if dom_node is MarginContainer and dom_node.name.begins_with("MarginWrapper_"): + if dom_node.get_child_count() > 0: + content_node = dom_node.get_child(0) + + # Handle RichTextLabel elements (p, span, etc.) + if content_node is RichTextLabel: + var styles = dom_parser.get_element_styles_with_inheritance(element, "", []) + StyleManager.apply_styles_to_label(content_node, styles, element, dom_parser) + return + + # Handle div elements that might contain RichTextLabel children + if element.tag_name == "div": + update_text_labels_recursive(content_node, element, dom_parser) + return + +static func update_text_labels_recursive(node: Node, element: HTMLParser.HTMLElement, dom_parser: HTMLParser) -> void: + if node is RichTextLabel: + var styles = dom_parser.get_element_styles_with_inheritance(element, "", []) + StyleManager.apply_styles_to_label(node, styles, element, dom_parser) + return + + for child in node.get_children(): + update_text_labels_recursive(child, element, dom_parser) + +static func update_div_hover_styles(dom_node: Control, element: HTMLParser.HTMLElement, dom_parser: HTMLParser) -> void: + var styles = dom_parser.get_element_styles_with_inheritance(element, "", []) + var hover_styles = dom_parser.get_element_styles_with_inheritance(element, "hover", []) + + if dom_node is PanelContainer: + var normal_stylebox = BackgroundUtils.create_stylebox_from_styles(styles) + dom_node.add_theme_stylebox_override("panel", normal_stylebox) + + if hover_styles.size() > 0: + BackgroundUtils.setup_panel_hover_support(dom_node, styles, hover_styles) + else: + if dom_node.has_meta("normal_stylebox"): + dom_node.remove_meta("normal_stylebox") + if dom_node.has_meta("hover_stylebox"): + dom_node.remove_meta("hover_stylebox") + + if dom_node.mouse_entered.is_connected(BackgroundUtils._on_panel_mouse_entered): + dom_node.mouse_entered.disconnect(BackgroundUtils._on_panel_mouse_entered) + if dom_node.mouse_exited.is_connected(BackgroundUtils._on_panel_mouse_exited): + dom_node.mouse_exited.disconnect(BackgroundUtils._on_panel_mouse_exited) + + update_element_text_content(dom_node, element, dom_parser) diff --git a/flumi/Scripts/Utils/Lua/Class.gd.uid b/flumi/Scripts/Utils/Lua/Class.gd.uid new file mode 100644 index 0000000..b4a8951 --- /dev/null +++ b/flumi/Scripts/Utils/Lua/Class.gd.uid @@ -0,0 +1 @@ +uid://dauvw3w3ly087 diff --git a/flumi/Scripts/Utils/UtilityClassValidator.gd b/flumi/Scripts/Utils/UtilityClassValidator.gd index bea488e..2a418e9 100644 --- a/flumi/Scripts/Utils/UtilityClassValidator.gd +++ b/flumi/Scripts/Utils/UtilityClassValidator.gd @@ -38,6 +38,9 @@ static func init_patterns(): "^border-(t|r|b|l)-\\[.*\\]$", # custom individual border sides (e.g., border-t-[2px]) "^border-(t|r|b|l)-(white|black|transparent|slate-\\d+|gray-\\d+|red-\\d+|green-\\d+|blue-\\d+|yellow-\\d+)$", # individual border side colors "^border-(white|black|transparent|slate-\\d+|gray-\\d+|red-\\d+|green-\\d+|blue-\\d+|yellow-\\d+)$", # border colors + "^opacity-\\[.*\\]$", # custom opacity values + "^z-\\[.*\\]$", # custom z-index values + "^cursor-[a-zA-Z-]+$", # cursor types "^(hover|active):", # pseudo classes ] for pattern in utility_patterns: