classList:add, classList:remove, classList:toggle, style handling

This commit is contained in:
Face
2025-08-05 18:01:16 +03:00
parent 7f90dfb716
commit ba2f49559e
12 changed files with 589 additions and 150 deletions

View File

@@ -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"

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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")

View File

@@ -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 = """<head>
</body>
""".to_utf8_buffer()
var HTML_CONTENTbv = """<head>
var HTML_CONTENTvv = """<head>
<title>My cool web</title>
<icon src=\"https://upload.wikimedia.org/wikipedia/commons/thumb/c/c1/Google_%22G%22_logo.svg/768px-Google_%22G%22_logo.svg.png\">
@@ -599,9 +599,10 @@ var HTML_CONTENT = """
<style>
body { bg-[#f8f9fa] p-6 }
h1 { text-[#2563eb] text-4xl font-bold }
h1 { text-[#2563eb] text-2xl font-bold }
.container { bg-[#ffffff] p-4 rounded-lg shadow-lg }
.demo-button { bg-[#3b82f6] text-white px-4 py-2 rounded hover:bg-[#2563eb] }
.fancy { bg-green-500 text-red-500 p-2 rounded-full mt-2 mb-2 text-2xl hover:bg-red-300 hover:text-[#2563eb] }
</style>
<script>
@@ -696,6 +697,21 @@ var HTML_CONTENT = """
end, 3000)
-- gurt.clearTimeout(test)
local addBtn = gurt.select('#add-class')
local removeBtn = gurt.select('#remove-class')
local btnTarget = gurt.select('#btnTarget')
addBtn:on('click', function()
btnTarget.classList:add('fancy')
-- btnTarget.classList:toggle('fancy')
print('Class added')
end)
removeBtn:on('click', function()
btnTarget.classList:remove('fancy')
print('Class removed')
end)
</script>
</head>
@@ -714,6 +730,14 @@ var HTML_CONTENT = """
<p id="btnmouse" style="mt-4 p-4 bg-[#f3f4f6] rounded min-h-24">Move mouse over Button</p>
<p id="type" style="mt-4 p-4 bg-[#f3f4f6] rounded min-h-24">Type something</p>
<div style="mt-6 flex gap-4 items-center">
<div style="text-lg font-semibold">Style Controls:</div>
<button id="add-class" style="bg-green-600 text-white px-4 py-2 rounded hover:bg-green-700">Add Class</button>
<button id="remove-class" style="bg-red-600 text-white px-4 py-2 rounded hover:bg-red-700">Remove Class</button>
</div>
<button id="btnTarget" style="bg-gray-600">Button</button>
</body>""".to_utf8_buffer()
var HTML_CONTENT_ADD_REMOVE = """<head>

View File

@@ -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)

View File

@@ -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

View File

@@ -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

View File

@@ -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)

View File

@@ -0,0 +1 @@
uid://dauvw3w3ly087

View File

@@ -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: