codebase refactoring (split utils across files)

This commit is contained in:
Face
2025-07-30 20:56:45 +03:00
parent af8afd6e5b
commit ae733d4121
25 changed files with 520 additions and 470 deletions

View File

@@ -158,7 +158,6 @@ func _resort() -> void:
emit_signal("flex_resized")
func calculate_available_dimension(is_width: bool) -> float:
var dimension_key = "custom_css_width" if is_width else "custom_css_height"
var percentage_key = "custom_css_width_percentage" if is_width else "custom_css_height_percentage"
var fill_key = "should_fill_horizontal" if is_width else "should_fill_vertical"

View File

@@ -179,57 +179,57 @@ func parse_utility_class(rule: CSSRule, utility_name: String) -> void:
static func parse_utility_class_internal(rule: CSSRule, utility_name: String) -> void:
# Handle color classes like text-[#ff0000]
if utility_name.begins_with("text-[") and utility_name.ends_with("]"):
var color_value = extract_bracket_content(utility_name, 5) # after 'text-'
rule.properties["color"] = parse_color(color_value)
var color_value = SizeUtils.extract_bracket_content(utility_name, 5) # after 'text-'
rule.properties["color"] = ColorUtils.parse_color(color_value)
return
# Handle standard text color classes like text-white, text-black, etc.
# But exclude text alignment classes
if utility_name.begins_with("text-") and not utility_name in ["text-left", "text-center", "text-right", "text-justify"]:
var color_name = utility_name.substr(5) # after 'text-'
var color = get_color(color_name)
var color = ColorUtils.get_color(color_name)
if color != null:
rule.properties["color"] = color
return
# Handle background color classes like bg-[#ff0000]
if utility_name.begins_with("bg-[") and utility_name.ends_with("]"):
var color_value = extract_bracket_content(utility_name, 3) # after 'bg-'
var color = parse_color(color_value)
var color_value = SizeUtils.extract_bracket_content(utility_name, 3) # after 'bg-'
var color = ColorUtils.parse_color(color_value)
rule.properties["background-color"] = color
return
# Handle standard background color classes like bg-white, bg-black, etc.
if utility_name.begins_with("bg-"):
var color_name = utility_name.substr(3) # after 'bg-'
var color = get_color(color_name)
var color = ColorUtils.get_color(color_name)
if color != null:
rule.properties["background-color"] = color
return
# e.g. max-w-[123px], w-[50%], h-[2rem]
if utility_name.match("^max-w-\\[.*\\]$"):
var val = extract_bracket_content(utility_name, 6)
var val = SizeUtils.extract_bracket_content(utility_name, 6)
rule.properties["max-width"] = val
return
if utility_name.match("^max-h-\\[.*\\]$"):
var val = extract_bracket_content(utility_name, 6)
var val = SizeUtils.extract_bracket_content(utility_name, 6)
rule.properties["max-height"] = val
return
if utility_name.match("^min-w-\\[.*\\]$"):
var val = extract_bracket_content(utility_name, 6)
var val = SizeUtils.extract_bracket_content(utility_name, 6)
rule.properties["min-width"] = val
return
if utility_name.match("^min-h-\\[.*\\]$"):
var val = extract_bracket_content(utility_name, 6)
var val = SizeUtils.extract_bracket_content(utility_name, 6)
rule.properties["min-height"] = val
return
if utility_name.match("^w-\\[.*\\]$"):
var val = extract_bracket_content(utility_name, 2)
var val = SizeUtils.extract_bracket_content(utility_name, 2)
rule.properties["width"] = val
return
if utility_name.match("^h-\\[.*\\]$"):
var val = extract_bracket_content(utility_name, 2)
var val = SizeUtils.extract_bracket_content(utility_name, 2)
rule.properties["height"] = val
return
@@ -277,34 +277,34 @@ static func parse_utility_class_internal(rule: CSSRule, utility_name: String) ->
var val = utility_name.substr(2)
if val.begins_with("[") and val.ends_with("]"):
val = val.substr(1, val.length() - 2)
rule.properties["width"] = parse_size(val)
rule.properties["width"] = SizeUtils.parse_size(val)
return
# Height
if utility_name.begins_with("h-"):
var val = utility_name.substr(2)
if val.begins_with("[") and val.ends_with("]"):
val = val.substr(1, val.length() - 2)
rule.properties["height"] = parse_size(val)
rule.properties["height"] = SizeUtils.parse_size(val)
return
# Min width
if utility_name.begins_with("min-w-"):
var val = utility_name.substr(6)
rule.properties["min-width"] = parse_size(val)
rule.properties["min-width"] = SizeUtils.parse_size(val)
return
# Min height
if utility_name.begins_with("min-h-"):
var val = utility_name.substr(6)
rule.properties["min-height"] = parse_size(val)
rule.properties["min-height"] = SizeUtils.parse_size(val)
return
# Max width
if utility_name.begins_with("max-w-"):
var val = utility_name.substr(6)
rule.properties["max-width"] = parse_size(val)
rule.properties["max-width"] = SizeUtils.parse_size(val)
return
# Max height
if utility_name.begins_with("max-h-"):
var val = utility_name.substr(6)
rule.properties["max-height"] = parse_size(val)
rule.properties["max-height"] = SizeUtils.parse_size(val)
return
# Flex container
@@ -357,15 +357,15 @@ static func parse_utility_class_internal(rule: CSSRule, utility_name: String) ->
# Gap
if utility_name.begins_with("gap-"):
var val = utility_name.substr(4)
rule.properties["gap"] = parse_size(val)
rule.properties["gap"] = SizeUtils.parse_size(val)
return
if utility_name.begins_with("row-gap-"):
var val = utility_name.substr(8)
rule.properties["row-gap"] = parse_size(val)
rule.properties["row-gap"] = SizeUtils.parse_size(val)
return
if utility_name.begins_with("col-gap-"):
var val = utility_name.substr(8)
rule.properties["column-gap"] = parse_size(val)
rule.properties["column-gap"] = SizeUtils.parse_size(val)
return
# FLEX ITEM PROPERTIES
@@ -379,7 +379,7 @@ static func parse_utility_class_internal(rule: CSSRule, utility_name: String) ->
return
if utility_name.begins_with("basis-"):
var val = utility_name.substr(6)
rule.properties["flex-basis"] = parse_size(val)
rule.properties["flex-basis"] = SizeUtils.parse_size(val)
return
# Align self
@@ -404,39 +404,39 @@ static func parse_utility_class_internal(rule: CSSRule, utility_name: String) ->
# Handle padding classes like p-8, px-4, py-2, etc.
if utility_name.begins_with("p-"):
var val = utility_name.substr(2)
var padding_value = parse_size(val)
var padding_value = SizeUtils.parse_size(val)
rule.properties["padding"] = padding_value
return
if utility_name.begins_with("px-"):
var val = utility_name.substr(3)
var padding_value = parse_size(val)
var padding_value = SizeUtils.parse_size(val)
rule.properties["padding-left"] = padding_value
rule.properties["padding-right"] = padding_value
return
if utility_name.begins_with("py-"):
var val = utility_name.substr(3)
var padding_value = parse_size(val)
var padding_value = SizeUtils.parse_size(val)
rule.properties["padding-top"] = padding_value
rule.properties["padding-bottom"] = padding_value
return
if utility_name.begins_with("pt-"):
var val = utility_name.substr(3)
var padding_value = parse_size(val)
var padding_value = SizeUtils.parse_size(val)
rule.properties["padding-top"] = padding_value
return
if utility_name.begins_with("pr-"):
var val = utility_name.substr(3)
var padding_value = parse_size(val)
var padding_value = SizeUtils.parse_size(val)
rule.properties["padding-right"] = padding_value
return
if utility_name.begins_with("pb-"):
var val = utility_name.substr(3)
var padding_value = parse_size(val)
var padding_value = SizeUtils.parse_size(val)
rule.properties["padding-bottom"] = padding_value
return
if utility_name.begins_with("pl-"):
var val = utility_name.substr(3)
var padding_value = parse_size(val)
var padding_value = SizeUtils.parse_size(val)
rule.properties["padding-left"] = padding_value
return
@@ -471,7 +471,7 @@ static func parse_utility_class_internal(rule: CSSRule, utility_name: String) ->
# Handle custom border radius like rounded-[12px]
if utility_name.begins_with("rounded-[") and utility_name.ends_with("]"):
var radius_value = extract_bracket_content(utility_name, 8) # after 'rounded-'
var radius_value = SizeUtils.extract_bracket_content(utility_name, 8) # after 'rounded-'
rule.properties["border-radius"] = radius_value
return
@@ -485,72 +485,6 @@ static func parse_utility_class_internal(rule: CSSRule, utility_name: String) ->
# Handle more utility classes as needed
# Add more cases here for other utilities
static func parse_size(val: String) -> String:
var named = {
"0": "0px", "1": "4px", "2": "8px", "3": "12px", "4": "16px", "5": "20px", "6": "24px", "8": "32px", "10": "40px",
"12": "48px", "16": "64px", "20": "80px", "24": "96px", "28": "112px", "32": "128px", "36": "144px", "40": "160px",
"44": "176px", "48": "192px", "52": "208px", "56": "224px", "60": "240px", "64": "256px", "72": "288px", "80": "320px", "96": "384px",
"3xs": "256px", "2xs": "288px", "xs": "320px", "sm": "384px", "md": "448px", "lg": "512px",
"xl": "576px", "2xl": "672px", "3xl": "768px", "4xl": "896px", "5xl": "1024px", "6xl": "1152px", "7xl": "1280px"
}
if named.has(val):
return named[val]
# Fractional (e.g. 1/2, 1/3)
if val.find("/") != -1:
var parts = val.split("/")
if parts.size() == 2 and parts[1].is_valid_int() and parts[0].is_valid_int():
var frac = float(parts[0]) / float(parts[1])
return str(frac * 100.0) + "%"
if val.is_valid_int():
return str(int(val) * 16) + "px"
return val
# Helper to extract content inside first matching brackets after a given index
static func extract_bracket_content(string: String, start_idx: int) -> String:
var open_idx = string.find("[", start_idx)
if open_idx == -1:
return ""
var close_idx = string.find("]", open_idx)
if close_idx == -1:
return ""
return string.substr(open_idx + 1, close_idx - open_idx - 1)
static func parse_color(color_string: String) -> Color:
color_string = color_string.strip_edges()
# Handle hex colors
if color_string.begins_with("#"):
return Color.from_string(color_string, Color.WHITE)
# Handle rgb/rgba
if color_string.begins_with("rgb"):
var regex = RegEx.new()
regex.compile("rgba?\\(([^)]+)\\)")
var result = regex.search(color_string)
if result:
var values = result.get_string(1).split(",")
if values.size() >= 3:
var r = values[0].strip_edges().to_float() / 255.0
var g = values[1].strip_edges().to_float() / 255.0
var b = values[2].strip_edges().to_float() / 255.0
var a = 1.0
if values.size() >= 4:
a = values[3].strip_edges().to_float()
return Color(r, g, b, a)
# Handle named colors
# TODO: map to actual Tailwind colors
match color_string.to_lower():
"red": return Color.RED
"green": return Color.GREEN
"blue": return Color.BLUE
"white": return Color.WHITE
"black": return Color.BLACK
"yellow": return Color.YELLOW
"cyan": return Color.CYAN
"magenta": return Color.MAGENTA
_: return Color.from_string(color_string, Color.WHITE)
static func parse_inline_style(style_string: String) -> Dictionary:
var rule = CSSRule.new()
rule.selector = ""
@@ -564,116 +498,3 @@ static func parse_inline_style(style_string: String) -> Dictionary:
parse_utility_class_internal(rule, utility_name)
return rule.properties
# TODO: probably a better idea to precompile the patterns, though idk how much more efficient that would be, if at all
static func is_utility_class(cls: String) -> bool:
var utility_patterns = [
"^text-(xs|sm|base|lg|xl|2xl|3xl|4xl|5xl|6xl)$", # font sizes
"^text-(left|center|right|justify)$", # text alignment
"^text-\\[.*\\]$", # custom text colors
"^text-(white|black|transparent|slate-\\d+|gray-\\d+|red-\\d+|green-\\d+|blue-\\d+|yellow-\\d+)$", # text colors
"^bg-\\[.*\\]$", # custom bg colors
"^bg-(white|black|transparent|slate-\\d+|gray-\\d+|red-\\d+|green-\\d+|blue-\\d+|yellow-\\d+)$", # bg colors
"^(w|h|min-w|min-h|max-w|max-h)-", # sizing
"^font-(bold|mono|italic)$", # font styles
"^underline$",
"^flex", # flex utilities
"^items-", # align items
"^justify-", # justify content
"^content-", # align content
"^self-", # align self
"^order-", # order
"^gap-", # gap
"^(p|px|py|pt|pr|pb|pl)-", # padding
"^rounded", # border radius
"^basis-", # flex basis
"^(hover|active):", # pseudo classes
]
for pattern in utility_patterns:
var regex = RegEx.new()
regex.compile(pattern)
if regex.search(cls):
return true
return false
static func get_color(color_name: String) -> Color:
# Common colors
match color_name:
"white": return Color.WHITE
"black": return Color.BLACK
"transparent": return Color.TRANSPARENT
# Gray scale
"slate-50": return Color.from_string("#f8fafc", Color.WHITE)
"slate-100": return Color.from_string("#f1f5f9", Color.WHITE)
"slate-200": return Color.from_string("#e2e8f0", Color.WHITE)
"slate-300": return Color.from_string("#cbd5e1", Color.WHITE)
"slate-400": return Color.from_string("#94a3b8", Color.WHITE)
"slate-500": return Color.from_string("#64748b", Color.WHITE)
"slate-600": return Color.from_string("#475569", Color.WHITE)
"slate-700": return Color.from_string("#334155", Color.WHITE)
"slate-800": return Color.from_string("#1e293b", Color.WHITE)
"slate-900": return Color.from_string("#0f172a", Color.WHITE)
"gray-50": return Color.from_string("#f9fafb", Color.WHITE)
"gray-100": return Color.from_string("#f3f4f6", Color.WHITE)
"gray-200": return Color.from_string("#e5e7eb", Color.WHITE)
"gray-300": return Color.from_string("#d1d5db", Color.WHITE)
"gray-400": return Color.from_string("#9ca3af", Color.WHITE)
"gray-500": return Color.from_string("#6b7280", Color.WHITE)
"gray-600": return Color.from_string("#4b5563", Color.WHITE)
"gray-700": return Color.from_string("#374151", Color.WHITE)
"gray-800": return Color.from_string("#1f2937", Color.WHITE)
"gray-900": return Color.from_string("#111827", Color.WHITE)
# Red
"red-50": return Color.from_string("#fef2f2", Color.WHITE)
"red-100": return Color.from_string("#fee2e2", Color.WHITE)
"red-200": return Color.from_string("#fecaca", Color.WHITE)
"red-300": return Color.from_string("#fca5a5", Color.WHITE)
"red-400": return Color.from_string("#f87171", Color.WHITE)
"red-500": return Color.from_string("#ef4444", Color.WHITE)
"red-600": return Color.from_string("#dc2626", Color.WHITE)
"red-700": return Color.from_string("#b91c1c", Color.WHITE)
"red-800": return Color.from_string("#991b1b", Color.WHITE)
"red-900": return Color.from_string("#7f1d1d", Color.WHITE)
# Green
"green-50": return Color.from_string("#f0fdf4", Color.WHITE)
"green-100": return Color.from_string("#dcfce7", Color.WHITE)
"green-200": return Color.from_string("#bbf7d0", Color.WHITE)
"green-300": return Color.from_string("#86efac", Color.WHITE)
"green-400": return Color.from_string("#4ade80", Color.WHITE)
"green-500": return Color.from_string("#22c55e", Color.WHITE)
"green-600": return Color.from_string("#16a34a", Color.WHITE)
"green-700": return Color.from_string("#15803d", Color.WHITE)
"green-800": return Color.from_string("#166534", Color.WHITE)
"green-900": return Color.from_string("#14532d", Color.WHITE)
# Blue
"blue-50": return Color.from_string("#eff6ff", Color.WHITE)
"blue-100": return Color.from_string("#dbeafe", Color.WHITE)
"blue-200": return Color.from_string("#bfdbfe", Color.WHITE)
"blue-300": return Color.from_string("#93c5fd", Color.WHITE)
"blue-400": return Color.from_string("#60a5fa", Color.WHITE)
"blue-500": return Color.from_string("#3b82f6", Color.WHITE)
"blue-600": return Color.from_string("#2563eb", Color.WHITE)
"blue-700": return Color.from_string("#1d4ed8", Color.WHITE)
"blue-800": return Color.from_string("#1e40af", Color.WHITE)
"blue-900": return Color.from_string("#1e3a8a", Color.WHITE)
# Yellow
"yellow-50": return Color.from_string("#fefce8", Color.WHITE)
"yellow-100": return Color.from_string("#fef9c3", Color.WHITE)
"yellow-200": return Color.from_string("#fef08a", Color.WHITE)
"yellow-300": return Color.from_string("#fde047", Color.WHITE)
"yellow-400": return Color.from_string("#facc15", Color.WHITE)
"yellow-500": return Color.from_string("#eab308", Color.WHITE)
"yellow-600": return Color.from_string("#ca8a04", Color.WHITE)
"yellow-700": return Color.from_string("#a16207", Color.WHITE)
"yellow-800": return Color.from_string("#854d0e", Color.WHITE)
"yellow-900": return Color.from_string("#713f12", Color.WHITE)
_: return Color.BLACK

View File

@@ -206,7 +206,7 @@ func extract_class_names_from_style(element: HTMLElement) -> Array[String]:
var style_tokens = style_attr.split(" ")
for token in style_tokens:
token = token.strip_edges()
if token.length() > 0 and not CSSParser.is_utility_class(token):
if token.length() > 0 and not UtilityClassValidator.is_utility_class(token):
class_names.append(token)
return class_names

View File

@@ -34,7 +34,7 @@ static func apply_element_styles(node: Control, element: HTMLParser.HTMLElement,
height = parse_size(styles["height"])
# Skip width/height inheritance for buttons when inheriting from auto-sized containers
var skip_sizing = should_skip_sizing(node, element, parser)
var skip_sizing = SizingUtils.should_skip_sizing(node, element, parser)
if (width != null or height != null) and not skip_sizing:
# FlexContainers handle percentage sizing differently than regular controls
@@ -45,10 +45,10 @@ static func apply_element_styles(node: Control, element: HTMLParser.HTMLElement,
node.custom_minimum_size.y = height
elif node is VBoxContainer or node is HBoxContainer or node is Container:
# Hcontainer nodes (like ul, ol)
apply_container_dimension_sizing(node, width, height)
SizingUtils.apply_container_dimension_sizing(node, width, height)
else:
# regular controls
apply_regular_control_sizing(node, width, height)
SizingUtils.apply_regular_control_sizing(node, width, height)
if label and label != node:
label.anchors_preset = Control.PRESET_FULL_RECT
@@ -136,240 +136,14 @@ static func apply_styles_to_label(label: RichTextLabel, styles: Dictionary, elem
label.text = styled_text
static func apply_flex_container_properties(node: FlexContainer, styles: Dictionary, element: HTMLParser.HTMLElement, parser: HTMLParser) -> void:
# Flex direction - default to row if not specified
if styles.has("flex-direction"):
match styles["flex-direction"]:
"row": node.flex_direction = FlexContainer.FlexDirection.Row
"row-reverse": node.flex_direction = FlexContainer.FlexDirection.RowReverse
"column": node.flex_direction = FlexContainer.FlexDirection.Column
"column-reverse": node.flex_direction = FlexContainer.FlexDirection.ColumnReverse
else:
node.flex_direction = FlexContainer.FlexDirection.Row
# Flex wrap
if styles.has("flex-wrap"):
match styles["flex-wrap"]:
"nowrap": node.flex_wrap = FlexContainer.FlexWrap.NoWrap
"wrap": node.flex_wrap = FlexContainer.FlexWrap.Wrap
"wrap-reverse": node.flex_wrap = FlexContainer.FlexWrap.WrapReverse
# Justify content
if styles.has("justify-content"):
match styles["justify-content"]:
"flex-start": node.justify_content = FlexContainer.JustifyContent.FlexStart
"flex-end": node.justify_content = FlexContainer.JustifyContent.FlexEnd
"center": node.justify_content = FlexContainer.JustifyContent.Center
"space-between": node.justify_content = FlexContainer.JustifyContent.SpaceBetween
"space-around": node.justify_content = FlexContainer.JustifyContent.SpaceAround
"space-evenly": node.justify_content = FlexContainer.JustifyContent.SpaceEvenly
# Align items
if styles.has("align-items"):
match styles["align-items"]:
"flex-start": node.align_items = FlexContainer.AlignItems.FlexStart
"flex-end": node.align_items = FlexContainer.AlignItems.FlexEnd
"center": node.align_items = FlexContainer.AlignItems.Center
"stretch": node.align_items = FlexContainer.AlignItems.Stretch
"baseline": node.align_items = FlexContainer.AlignItems.Baseline
# Align content
if styles.has("align-content"):
match styles["align-content"]:
"flex-start": node.align_content = FlexContainer.AlignContent.FlexStart
"flex-end": node.align_content = FlexContainer.AlignContent.FlexEnd
"center": node.align_content = FlexContainer.AlignContent.Center
"stretch": node.align_content = FlexContainer.AlignContent.Stretch
"space-between": node.align_content = FlexContainer.AlignContent.SpaceBetween
"space-around": node.align_content = FlexContainer.AlignContent.SpaceAround
# Gap
if styles.has("gap"):
# YGGutterAll = 2
node._root.set_gap(2, parse_flex_value(styles["gap"]))
if styles.has("row-gap"):
# YGGutterRow = 1
node._root.set_gap(1, parse_flex_value(styles["row-gap"]))
if styles.has("column-gap"):
# YGGutterColumn = 0
node._root.set_gap(0, parse_flex_value(styles["column-gap"]))
if styles.has("width"):
var width_val = styles["width"]
if width_val == "full":
# For flex containers, w-full should expand to fill parent
node.set_meta("should_fill_horizontal", true)
elif typeof(width_val) == TYPE_STRING and width_val.ends_with("%"):
node.set_meta("custom_css_width_percentage", width_val)
else:
node.set_meta("custom_css_width", parse_size(width_val))
if styles.has("height"):
var height_val = styles["height"]
if height_val == "full":
# For flex containers, h-full should expand to fill parent
node.set_meta("should_fill_vertical", true)
elif typeof(height_val) == TYPE_STRING and height_val.ends_with("%"):
node.set_meta("custom_css_height_percentage", height_val)
else:
node.set_meta("custom_css_height", parse_size(height_val))
if styles.has("background-color"):
node.set_meta("custom_css_background_color", styles["background-color"])
node.update_layout()
static func apply_flex_container_properties(node: FlexContainer, styles: Dictionary) -> void:
FlexUtils.apply_flex_container_properties(node, styles)
static func apply_flex_item_properties(node: Control, styles: Dictionary) -> void:
var properties: Dictionary = node.get_meta("flex_metas", {}).duplicate(true)
var changed = false
if styles.has("flex-grow"):
properties["grow"] = float(styles["flex-grow"])
changed = true
if styles.has("flex-shrink"):
properties["shrink"] = float(styles["flex-shrink"])
changed = true
if styles.has("flex-basis"):
properties["basis"] = parse_flex_value(styles["flex-basis"])
changed = true
if styles.has("align-self"):
var align_self_value = -1
match styles["align-self"]:
"auto": align_self_value = FlexContainer.AlignItems.Auto
"flex-start": align_self_value = FlexContainer.AlignItems.FlexStart
"flex-end": align_self_value = FlexContainer.AlignItems.FlexEnd
"center": align_self_value = FlexContainer.AlignItems.Center
"stretch": align_self_value = FlexContainer.AlignItems.Stretch
"baseline": align_self_value = FlexContainer.AlignItems.Baseline
if align_self_value != -1:
properties["align_self"] = align_self_value
changed = true
if changed:
node.set_meta("flex_metas", properties)
# The parent FlexContainer must be notified to update its layout.
var parent = node.get_parent()
if parent is FlexContainer:
parent.update_layout()
FlexUtils.apply_flex_item_properties(node, styles)
static func parse_flex_value(val):
if val is float or val is int:
return float(val)
if val is String:
var s_val = val.strip_edges()
if s_val.is_valid_float():
return s_val.to_float()
if s_val.ends_with("%"):
# NOTE: Flex-basis percentages not supported by flexbox
return s_val.trim_suffix("%").to_float() / 100.0
if s_val.ends_with("px"):
return s_val.trim_suffix("px").to_float()
if s_val == "auto":
return "auto"
return null
static func should_skip_sizing(node: Control, element: HTMLParser.HTMLElement, parser: HTMLParser) -> bool:
# Cache style lookups to avoid repeated calls
var element_styles = parser.get_element_styles_internal(element, "")
# Button sizing rules: Skip sizing only when button has no explicit size
# AND parent doesn't have explicit width (auto-inherited sizing)
if node.get_script() and node.get_script().get_path().ends_with("button.gd"):
# If button has explicit size, don't skip sizing
if element_styles.has("width") or element_styles.has("height"):
return false
# Check if width is being inherited from parent with explicit size
var parent_element = element.parent
if parent_element:
var parent_styles = parser.get_element_styles_internal(parent_element, "")
var parent_has_explicit_width = parent_styles.has("width")
# Skip only if parent doesn't have explicit width (auto-inherited)
return not parent_has_explicit_width
return true
# Span sizing rules: Always skip sizing for spans since they're inline elements
# (flex containers use AutoSizingFlexContainer, not span.gd)
elif node.get_script() and node.get_script().get_path().ends_with("span.gd"):
return true
return false
static func apply_container_dimension_sizing(node: Control, width, height) -> void:
if width != null:
if is_percentage(width):
node.set_meta("container_percentage_width", width)
node.size_flags_horizontal = Control.SIZE_EXPAND_FILL
apply_container_percentage_sizing(node)
else:
node.custom_minimum_size.x = width
node.size_flags_horizontal = Control.SIZE_SHRINK_BEGIN
if height != null:
if is_percentage(height):
node.set_meta("container_percentage_height", height)
node.size_flags_vertical = Control.SIZE_EXPAND_FILL
apply_container_percentage_sizing(node)
else:
node.custom_minimum_size.y = height
node.size_flags_vertical = Control.SIZE_SHRINK_BEGIN
static func apply_regular_control_sizing(node: Control, width, height) -> void:
if width != null:
if is_percentage(width):
var estimated_width = calculate_percentage_size(width, 800.0)
node.custom_minimum_size.x = estimated_width
node.size_flags_horizontal = Control.SIZE_EXPAND_FILL
else:
node.custom_minimum_size.x = width
node.size_flags_horizontal = Control.SIZE_SHRINK_BEGIN
if height != null:
if is_percentage(height):
var estimated_height = calculate_percentage_size(height, 600.0)
node.custom_minimum_size.y = estimated_height
node.size_flags_vertical = Control.SIZE_EXPAND_FILL
else:
node.custom_minimum_size.y = height
node.size_flags_vertical = Control.SIZE_SHRINK_BEGIN
static func is_percentage(value) -> bool:
return typeof(value) == TYPE_STRING and value.ends_with("%")
static func calculate_percentage_size(percentage_str: String, fallback_size: float) -> float:
var clean_percentage = percentage_str.replace("%", "")
var percentage = float(clean_percentage) / 100.0
return fallback_size * percentage
static func apply_container_percentage_sizing(node: Control) -> void:
var parent = node.get_parent()
if not parent:
return
var new_min_size = node.custom_minimum_size
if node.has_meta("container_percentage_width"):
var percentage_str = node.get_meta("container_percentage_width")
var parent_width = get_parent_dimension(parent, true, 800.0)
new_min_size.x = calculate_percentage_size(percentage_str, parent_width)
if node.has_meta("container_percentage_height"):
var percentage_str = node.get_meta("container_percentage_height")
var parent_height = get_parent_dimension(parent, false, 600.0)
new_min_size.y = calculate_percentage_size(percentage_str, parent_height)
node.custom_minimum_size = new_min_size
static func get_parent_dimension(parent: Control, is_width: bool, fallback: float) -> float:
var size_value = parent.size.x if is_width else parent.size.y
if size_value > 0:
return size_value
var rect_size = parent.get_rect().size.x if is_width else parent.get_rect().size.y
if rect_size > 0:
return rect_size
var min_size = parent.custom_minimum_size.x if is_width else parent.custom_minimum_size.y
if min_size > 0:
return min_size
return fallback
return FlexUtils.parse_flex_value(val)
static func apply_body_styles(body: HTMLParser.HTMLElement, parser: HTMLParser, website_container: Control, website_background: Control) -> void:
var styles = parser.get_element_styles_with_inheritance(body, "", [])
@@ -425,11 +199,4 @@ static func apply_body_styles(body: HTMLParser.HTMLElement, parser: HTMLParser,
margin_container.add_theme_constant_override(margin_key, margin_val2)
static func parse_radius(radius_str: String) -> int:
if radius_str.ends_with("px"):
return int(radius_str.replace("px", ""))
elif radius_str.ends_with("rem"):
return int(radius_str.replace("rem", "")) * 16
elif radius_str.is_valid_float():
return int(radius_str)
else:
return 0
return SizeUtils.parse_radius(radius_str)

View File

@@ -90,9 +90,9 @@ func apply_button_styles(element: HTMLParser.HTMLElement, parser: HTMLParser, na
var height = null
if styles.has("width"):
width = StyleManager.parse_size(styles["width"])
width = SizingUtils.parse_size_value(styles["width"])
if styles.has("height"):
height = StyleManager.parse_size(styles["height"])
height = SizingUtils.parse_size_value(styles["height"])
# Only apply size flags if there's explicit sizing
if width != null or height != null:

View File

@@ -1,4 +1,6 @@
# NOTE: some weird warning, wcyd
@tool
extends FlexContainer
func init(a):
func init(_element: HTMLParser.HTMLElement):
pass

View File

@@ -2,5 +2,5 @@ extends Control
@onready var rich_text_label: RichTextLabel = $RichTextLabel
func init(element: HTMLParser.HTMLElement, parser: HTMLParser = null) -> void:
func init(_element: HTMLParser.HTMLElement) -> void:
pass

View File

@@ -2,5 +2,5 @@ extends Control
@onready var rich_text_label: RichTextLabel = $RichTextLabel
func init(element: HTMLParser.HTMLElement, parser: HTMLParser = null) -> void:
func init(_element: HTMLParser.HTMLElement) -> void:
pass

View File

@@ -2,5 +2,5 @@ extends Control
@onready var rich_text_label: RichTextLabel = $RichTextLabel
func init(element: HTMLParser.HTMLElement, parser: HTMLParser = null) -> void:
pass
func init(_element: HTMLParser.HTMLElement) -> void:
pass

View File

@@ -2,5 +2,5 @@ extends Control
@onready var rich_text_label: RichTextLabel = $RichTextLabel
func init(element: HTMLParser.HTMLElement, parser: HTMLParser = null) -> void:
pass
func init(_element: HTMLParser.HTMLElement) -> void:
pass

View File

@@ -2,5 +2,5 @@ extends Control
@onready var rich_text_label: RichTextLabel = $RichTextLabel
func init(element: HTMLParser.HTMLElement, parser: HTMLParser = null) -> void:
pass
func init(_element: HTMLParser.HTMLElement) -> void:
pass

View File

@@ -2,5 +2,5 @@ extends Control
@onready var rich_text_label: RichTextLabel = $RichTextLabel
func init(element: HTMLParser.HTMLElement, parser: HTMLParser = null) -> void:
pass
func init(_element: HTMLParser.HTMLElement) -> void:
pass

View File

@@ -309,13 +309,13 @@ func apply_input_styles(element: HTMLParser.HTMLElement, parser: HTMLParser) ->
if styles["width"] == "full":
var parent_styles = parser.get_element_styles_with_inheritance(element.parent, "", []) if element.parent else {}
if parent_styles.has("width"):
var parent_width = StyleManager.parse_size(parent_styles["width"])
var parent_width = SizingUtils.parse_size_value(parent_styles["width"])
if parent_width:
width = parent_width
else:
width = StyleManager.parse_size(styles["width"])
width = SizingUtils.parse_size_value(styles["width"])
if styles.has("height"):
height = StyleManager.parse_size(styles["height"])
height = SizingUtils.parse_size_value(styles["height"])
var active_child = null
for child in get_children():

View File

@@ -1,5 +1,5 @@
class_name Pre
extends VBoxContainer
func init(element: HTMLParser.HTMLElement) -> void:
func init(_element: HTMLParser.HTMLElement) -> void:
pass

108
Scripts/Utils/ColorUtils.gd Normal file
View File

@@ -0,0 +1,108 @@
class_name ColorUtils
extends RefCounted
static func parse_color(color_string: String) -> Color:
color_string = color_string.strip_edges()
# Handle hex colors
if color_string.begins_with("#"):
return Color.from_string(color_string, Color.WHITE)
# Handle rgb/rgba
if color_string.begins_with("rgb"):
var regex = RegEx.new()
regex.compile("rgba?\\(([^)]+)\\)")
var result = regex.search(color_string)
if result:
var values = result.get_string(1).split(",")
if values.size() >= 3:
var r = values[0].strip_edges().to_float() / 255.0
var g = values[1].strip_edges().to_float() / 255.0
var b = values[2].strip_edges().to_float() / 255.0
var a = 1.0
if values.size() >= 4:
a = values[3].strip_edges().to_float()
return Color(r, g, b, a)
# Handle named colors - delegate to get_color function
return get_color(color_string)
static func get_color(color_name: String) -> Color:
# Common colors
match color_name:
"white": return Color.WHITE
"black": return Color.BLACK
"transparent": return Color.TRANSPARENT
# Gray scale
"slate-50": return Color.from_string("#f8fafc", Color.WHITE)
"slate-100": return Color.from_string("#f1f5f9", Color.WHITE)
"slate-200": return Color.from_string("#e2e8f0", Color.WHITE)
"slate-300": return Color.from_string("#cbd5e1", Color.WHITE)
"slate-400": return Color.from_string("#94a3b8", Color.WHITE)
"slate-500": return Color.from_string("#64748b", Color.WHITE)
"slate-600": return Color.from_string("#475569", Color.WHITE)
"slate-700": return Color.from_string("#334155", Color.WHITE)
"slate-800": return Color.from_string("#1e293b", Color.WHITE)
"slate-900": return Color.from_string("#0f172a", Color.WHITE)
"gray-50": return Color.from_string("#f9fafb", Color.WHITE)
"gray-100": return Color.from_string("#f3f4f6", Color.WHITE)
"gray-200": return Color.from_string("#e5e7eb", Color.WHITE)
"gray-300": return Color.from_string("#d1d5db", Color.WHITE)
"gray-400": return Color.from_string("#9ca3af", Color.WHITE)
"gray-500": return Color.from_string("#6b7280", Color.WHITE)
"gray-600": return Color.from_string("#4b5563", Color.WHITE)
"gray-700": return Color.from_string("#374151", Color.WHITE)
"gray-800": return Color.from_string("#1f2937", Color.WHITE)
"gray-900": return Color.from_string("#111827", Color.WHITE)
# Red
"red-50": return Color.from_string("#fef2f2", Color.WHITE)
"red-100": return Color.from_string("#fee2e2", Color.WHITE)
"red-200": return Color.from_string("#fecaca", Color.WHITE)
"red-300": return Color.from_string("#fca5a5", Color.WHITE)
"red-400": return Color.from_string("#f87171", Color.WHITE)
"red-500": return Color.from_string("#ef4444", Color.WHITE)
"red-600": return Color.from_string("#dc2626", Color.WHITE)
"red-700": return Color.from_string("#b91c1c", Color.WHITE)
"red-800": return Color.from_string("#991b1b", Color.WHITE)
"red-900": return Color.from_string("#7f1d1d", Color.WHITE)
# Green
"green-50": return Color.from_string("#f0fdf4", Color.WHITE)
"green-100": return Color.from_string("#dcfce7", Color.WHITE)
"green-200": return Color.from_string("#bbf7d0", Color.WHITE)
"green-300": return Color.from_string("#86efac", Color.WHITE)
"green-400": return Color.from_string("#4ade80", Color.WHITE)
"green-500": return Color.from_string("#22c55e", Color.WHITE)
"green-600": return Color.from_string("#16a34a", Color.WHITE)
"green-700": return Color.from_string("#15803d", Color.WHITE)
"green-800": return Color.from_string("#166534", Color.WHITE)
"green-900": return Color.from_string("#14532d", Color.WHITE)
# Blue
"blue-50": return Color.from_string("#eff6ff", Color.WHITE)
"blue-100": return Color.from_string("#dbeafe", Color.WHITE)
"blue-200": return Color.from_string("#bfdbfe", Color.WHITE)
"blue-300": return Color.from_string("#93c5fd", Color.WHITE)
"blue-400": return Color.from_string("#60a5fa", Color.WHITE)
"blue-500": return Color.from_string("#3b82f6", Color.WHITE)
"blue-600": return Color.from_string("#2563eb", Color.WHITE)
"blue-700": return Color.from_string("#1d4ed8", Color.WHITE)
"blue-800": return Color.from_string("#1e40af", Color.WHITE)
"blue-900": return Color.from_string("#1e3a8a", Color.WHITE)
# Yellow
"yellow-50": return Color.from_string("#fefce8", Color.WHITE)
"yellow-100": return Color.from_string("#fef9c3", Color.WHITE)
"yellow-200": return Color.from_string("#fef08a", Color.WHITE)
"yellow-300": return Color.from_string("#fde047", Color.WHITE)
"yellow-400": return Color.from_string("#facc15", Color.WHITE)
"yellow-500": return Color.from_string("#eab308", Color.WHITE)
"yellow-600": return Color.from_string("#ca8a04", Color.WHITE)
"yellow-700": return Color.from_string("#a16207", Color.WHITE)
"yellow-800": return Color.from_string("#854d0e", Color.WHITE)
"yellow-900": return Color.from_string("#713f12", Color.WHITE)
_: return Color.BLACK

View File

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

127
Scripts/Utils/FlexUtils.gd Normal file
View File

@@ -0,0 +1,127 @@
class_name FlexUtils
extends RefCounted
static func apply_flex_container_properties(node, styles: Dictionary) -> void:
# Flex direction - default to row if not specified
if styles.has("flex-direction"):
match styles["flex-direction"]:
"row": node.flex_direction = FlexContainer.FlexDirection.Row
"row-reverse": node.flex_direction = FlexContainer.FlexDirection.RowReverse
"column": node.flex_direction = FlexContainer.FlexDirection.Column
"column-reverse": node.flex_direction = FlexContainer.FlexDirection.ColumnReverse
else:
node.flex_direction = FlexContainer.FlexDirection.Row
# Flex wrap
if styles.has("flex-wrap"):
match styles["flex-wrap"]:
"nowrap": node.flex_wrap = FlexContainer.FlexWrap.NoWrap
"wrap": node.flex_wrap = FlexContainer.FlexWrap.Wrap
"wrap-reverse": node.flex_wrap = FlexContainer.FlexWrap.WrapReverse
# Justify content
if styles.has("justify-content"):
match styles["justify-content"]:
"flex-start": node.justify_content = FlexContainer.JustifyContent.FlexStart
"flex-end": node.justify_content = FlexContainer.JustifyContent.FlexEnd
"center": node.justify_content = FlexContainer.JustifyContent.Center
"space-between": node.justify_content = FlexContainer.JustifyContent.SpaceBetween
"space-around": node.justify_content = FlexContainer.JustifyContent.SpaceAround
"space-evenly": node.justify_content = FlexContainer.JustifyContent.SpaceEvenly
# Align items
if styles.has("align-items"):
match styles["align-items"]:
"flex-start": node.align_items = FlexContainer.AlignItems.FlexStart
"flex-end": node.align_items = FlexContainer.AlignItems.FlexEnd
"center": node.align_items = FlexContainer.AlignItems.Center
"stretch": node.align_items = FlexContainer.AlignItems.Stretch
"baseline": node.align_items = FlexContainer.AlignItems.Baseline
# Align content
if styles.has("align-content"):
match styles["align-content"]:
"flex-start": node.align_content = FlexContainer.AlignContent.FlexStart
"flex-end": node.align_content = FlexContainer.AlignContent.FlexEnd
"center": node.align_content = FlexContainer.AlignContent.Center
"stretch": node.align_content = FlexContainer.AlignContent.Stretch
"space-between": node.align_content = FlexContainer.AlignContent.SpaceBetween
"space-around": node.align_content = FlexContainer.AlignContent.SpaceAround
# Gap
if styles.has("gap"):
# YGGutterAll = 2
node._root.set_gap(2, parse_flex_value(styles["gap"]))
if styles.has("row-gap"):
# YGGutterRow = 1
node._root.set_gap(1, parse_flex_value(styles["row-gap"]))
if styles.has("column-gap"):
# YGGutterColumn = 0
node._root.set_gap(0, parse_flex_value(styles["column-gap"]))
if styles.has("width"):
var width_val = styles["width"]
if width_val == "full":
# For flex containers, w-full should expand to fill parent
node.set_meta("should_fill_horizontal", true)
elif typeof(width_val) == TYPE_STRING and width_val.ends_with("%"):
node.set_meta("custom_css_width_percentage", width_val)
else:
node.set_meta("custom_css_width", SizingUtils.parse_size_value(width_val))
if styles.has("height"):
var height_val = styles["height"]
if height_val == "full":
# For flex containers, h-full should expand to fill parent
node.set_meta("should_fill_vertical", true)
elif typeof(height_val) == TYPE_STRING and height_val.ends_with("%"):
node.set_meta("custom_css_height_percentage", height_val)
else:
node.set_meta("custom_css_height", SizingUtils.parse_size_value(height_val))
if styles.has("background-color"):
node.set_meta("custom_css_background_color", styles["background-color"])
node.update_layout()
static func apply_flex_item_properties(node: Control, styles: Dictionary) -> void:
var properties: Dictionary = node.get_meta("flex_metas", {}).duplicate(true)
var changed = false
if styles.has("flex-grow"):
properties["grow"] = float(styles["flex-grow"])
changed = true
if styles.has("flex-shrink"):
properties["shrink"] = float(styles["flex-shrink"])
changed = true
if styles.has("flex-basis"):
properties["basis"] = parse_flex_value(styles["flex-basis"])
changed = true
if styles.has("align-self"):
var align_self_value = -1
match styles["align-self"]:
"auto": align_self_value = FlexContainer.AlignItems.Auto
"flex-start": align_self_value = FlexContainer.AlignItems.FlexStart
"flex-end": align_self_value = FlexContainer.AlignItems.FlexEnd
"center": align_self_value = FlexContainer.AlignItems.Center
"stretch": align_self_value = FlexContainer.AlignItems.Stretch
"baseline": align_self_value = FlexContainer.AlignItems.Baseline
if align_self_value != -1:
properties["align_self"] = align_self_value
changed = true
if changed:
node.set_meta("flex_metas", properties)
var parent = node.get_parent()
if parent is FlexContainer:
parent.update_layout()
static func parse_flex_value(val):
if val is float or val is int:
return float(val)
if val is String:
var s_val = val.strip_edges()
if s_val.is_valid_float():
return s_val.to_float()
if s_val.ends_with("%"):
return s_val.trim_suffix("%").to_float() / 100.0
if s_val.ends_with("px"):
return s_val.trim_suffix("px").to_float()
if s_val == "auto":
return "auto"
return null

View File

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

View File

@@ -0,0 +1,50 @@
class_name SizeUtils
extends RefCounted
# Utility functions for parsing CSS size values
static func parse_size(val: String) -> String:
if val == null or val.is_empty():
return "0px"
var named = {
"0": "0px", "1": "4px", "2": "8px", "3": "12px", "4": "16px", "5": "20px", "6": "24px", "8": "32px", "10": "40px",
"12": "48px", "16": "64px", "20": "80px", "24": "96px", "28": "112px", "32": "128px", "36": "144px", "40": "160px",
"44": "176px", "48": "192px", "52": "208px", "56": "224px", "60": "240px", "64": "256px", "72": "288px", "80": "320px", "96": "384px",
"3xs": "256px", "2xs": "288px", "xs": "320px", "sm": "384px", "md": "448px", "lg": "512px",
"xl": "576px", "2xl": "672px", "3xl": "768px", "4xl": "896px", "5xl": "1024px", "6xl": "1152px", "7xl": "1280px"
}
if named.has(val):
return named[val]
# Fractional (e.g. 1/2, 1/3)
if val.find("/") != -1:
var parts = val.split("/")
if parts.size() == 2 and \
parts[1].is_valid_int() and \
parts[0].is_valid_int() and \
int(parts[1]) != 0:
var frac = float(parts[0]) / float(parts[1])
return str(frac * 100.0) + "%"
if val.is_valid_int():
return str(int(val) * 16) + "px"
return val
static func extract_bracket_content(string: String, start_idx: int) -> String:
var open_idx = string.find("[", start_idx)
if open_idx == -1:
return ""
var close_idx = string.find("]", open_idx)
if close_idx == -1:
return ""
return string.substr(open_idx + 1, close_idx - open_idx - 1)
static func parse_radius(radius_str: String) -> int:
if radius_str.ends_with("px"):
return int(radius_str.replace("px", ""))
elif radius_str.ends_with("rem"):
return int(radius_str.replace("rem", "")) * 16
elif radius_str.is_valid_float():
return int(radius_str)
else:
return 0

View File

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

View File

@@ -0,0 +1,129 @@
class_name SizingUtils
extends RefCounted
# Utility functions for handling sizes in the UI
const DEFAULT_VIEWPORT_WIDTH = 800.0
const DEFAULT_VIEWPORT_HEIGHT = 600.0
static func parse_size_value(val):
if val == null: return null
if typeof(val) == TYPE_INT or typeof(val) == TYPE_FLOAT:
return float(val)
if val.ends_with("px"):
return float(val.replace("px", ""))
if val.ends_with("rem"):
return float(val.replace("rem", "")) * 16.0
if val.ends_with("%") or (val.ends_with("]") and "%" in val):
var clean_val = val.replace("[", "").replace("]", "")
return clean_val
if val == "full":
return null
return float(val)
static func should_skip_sizing(node: Control, element, parser) -> bool:
var element_styles = parser.get_element_styles_internal(element, "")
# Button sizing rules: Skip sizing only when button has no explicit size
# AND parent doesn't have explicit width (auto-inherited sizing)
if node.get_script() and node.get_script().get_path().ends_with("button.gd"):
# If button has explicit size, don't skip sizing
if element_styles.has("width") or element_styles.has("height"):
return false
# Check if width is being inherited from parent with explicit size
var parent_element = element.parent
if parent_element:
var parent_styles = parser.get_element_styles_internal(parent_element, "")
var parent_has_explicit_width = parent_styles.has("width")
# Skip only if parent doesn't have explicit width (auto-inherited)
return not parent_has_explicit_width
return true
# Span sizing rules: Always skip sizing for spans since they're inline elements
# (flex containers use AutoSizingFlexContainer, not span.gd)
elif node.get_script() and node.get_script().get_path().ends_with("span.gd"):
return true
return false
static func apply_container_dimension_sizing(node: Control, width, height) -> void:
if width != null:
if is_percentage(width):
node.set_meta("container_percentage_width", width)
node.size_flags_horizontal = Control.SIZE_EXPAND_FILL
apply_container_percentage_sizing(node)
else:
node.custom_minimum_size.x = width
node.size_flags_horizontal = Control.SIZE_SHRINK_BEGIN
if height != null:
if is_percentage(height):
node.set_meta("container_percentage_height", height)
node.size_flags_vertical = Control.SIZE_EXPAND_FILL
apply_container_percentage_sizing(node)
else:
node.custom_minimum_size.y = height
node.size_flags_vertical = Control.SIZE_SHRINK_BEGIN
static func apply_regular_control_sizing(node: Control, width, height) -> void:
if width != null:
if is_percentage(width):
var estimated_width = calculate_percentage_size(width, DEFAULT_VIEWPORT_WIDTH)
node.custom_minimum_size.x = estimated_width
node.size_flags_horizontal = Control.SIZE_EXPAND_FILL
else:
node.custom_minimum_size.x = width
node.size_flags_horizontal = Control.SIZE_SHRINK_BEGIN
if height != null:
if is_percentage(height):
var estimated_height = calculate_percentage_size(height, DEFAULT_VIEWPORT_HEIGHT)
node.custom_minimum_size.y = estimated_height
node.size_flags_vertical = Control.SIZE_EXPAND_FILL
else:
node.custom_minimum_size.y = height
node.size_flags_vertical = Control.SIZE_SHRINK_BEGIN
static func is_percentage(value) -> bool:
return typeof(value) == TYPE_STRING and value.ends_with("%")
static func calculate_percentage_size(percentage_str: String, fallback_size: float) -> float:
var clean_percentage = percentage_str.replace("%", "")
var percentage = float(clean_percentage) / 100.0
return fallback_size * percentage
static func apply_container_percentage_sizing(node: Control) -> void:
var parent = node.get_parent()
if not parent:
return
var new_min_size = node.custom_minimum_size
if node.has_meta("container_percentage_width"):
var percentage_str = node.get_meta("container_percentage_width")
var parent_width = get_parent_dimension(parent, true, DEFAULT_VIEWPORT_WIDTH)
new_min_size.x = calculate_percentage_size(percentage_str, parent_width)
if node.has_meta("container_percentage_height"):
var percentage_str = node.get_meta("container_percentage_height")
var parent_height = get_parent_dimension(parent, false, DEFAULT_VIEWPORT_HEIGHT)
new_min_size.y = calculate_percentage_size(percentage_str, parent_height)
node.custom_minimum_size = new_min_size
static func get_parent_dimension(parent: Control, is_width: bool, fallback: float) -> float:
var size_value = parent.size.x if is_width else parent.size.y
if size_value > 0:
return size_value
var rect_size = parent.get_rect().size.x if is_width else parent.get_rect().size.y
if rect_size > 0:
return rect_size
var min_size = parent.custom_minimum_size.x if is_width else parent.custom_minimum_size.y
if min_size > 0:
return min_size
return fallback

View File

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

View File

@@ -0,0 +1,42 @@
class_name UtilityClassValidator
extends RefCounted
static var compiled_patterns: Array = []
static func init_patterns():
if compiled_patterns.size() == 0:
var utility_patterns = [
"^text-(xs|sm|base|lg|xl|2xl|3xl|4xl|5xl|6xl)$", # font sizes
"^text-(left|center|right|justify)$", # text alignment
"^text-\\[.*\\]$", # custom text colors
"^text-(white|black|transparent|slate-\\d+|gray-\\d+|red-\\d+|green-\\d+|blue-\\d+|yellow-\\d+)$", # text colors
"^bg-\\[.*\\]$", # custom bg colors
"^bg-(white|black|transparent|slate-\\d+|gray-\\d+|red-\\d+|green-\\d+|blue-\\d+|yellow-\\d+)$", # bg colors
"^(w|h|min-w|min-h|max-w|max-h)-", # sizing
"^font-(bold|mono|italic)$", # font styles
"^underline$",
"^flex", # flex utilities
"^items-", # align items
"^justify-", # justify content
"^content-", # align content
"^self-", # align self
"^order-", # order
"^gap-", # gap
"^(p|px|py|pt|pr|pb|pl)-", # padding
"^rounded", # border radius
"^basis-", # flex basis
"^(hover|active):", # pseudo classes
]
for pattern in utility_patterns:
var regex = RegEx.new()
regex.compile(pattern)
compiled_patterns.append(regex)
static func is_utility_class(cls: String) -> bool:
# once
init_patterns()
for regex in compiled_patterns:
if regex.search(cls):
return true
return false

View File

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

View File

@@ -164,7 +164,7 @@ func create_element_node(element: HTMLParser.HTMLElement, parser: HTMLParser) ->
# Apply flex CONTAINER properties if it's a flex container
if is_flex_container:
StyleManager.apply_flex_container_properties(final_node, styles, element, parser)
StyleManager.apply_flex_container_properties(final_node, styles)
# Apply flex ITEM properties
StyleManager.apply_flex_item_properties(final_node, styles)