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: