diff --git a/Scripts/AutoSizingFlexContainer.gd b/Scripts/AutoSizingFlexContainer.gd index 0b8d9e0..029f8af 100644 --- a/Scripts/AutoSizingFlexContainer.gd +++ b/Scripts/AutoSizingFlexContainer.gd @@ -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" diff --git a/Scripts/B9/CSSParser.gd b/Scripts/B9/CSSParser.gd index d09e635..eaa6296 100644 --- a/Scripts/B9/CSSParser.gd +++ b/Scripts/B9/CSSParser.gd @@ -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 diff --git a/Scripts/B9/HTMLParser.gd b/Scripts/B9/HTMLParser.gd index e109ce0..fd6da2b 100644 --- a/Scripts/B9/HTMLParser.gd +++ b/Scripts/B9/HTMLParser.gd @@ -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 diff --git a/Scripts/StyleManager.gd b/Scripts/StyleManager.gd index f71ff41..933566c 100644 --- a/Scripts/StyleManager.gd +++ b/Scripts/StyleManager.gd @@ -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) diff --git a/Scripts/Tags/button.gd b/Scripts/Tags/button.gd index 8493ddd..62b0116 100644 --- a/Scripts/Tags/button.gd +++ b/Scripts/Tags/button.gd @@ -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: diff --git a/Scripts/Tags/div.gd b/Scripts/Tags/div.gd index 4c48b09..70f4f5e 100644 --- a/Scripts/Tags/div.gd +++ b/Scripts/Tags/div.gd @@ -1,4 +1,6 @@ +# NOTE: some weird warning, wcyd +@tool extends FlexContainer -func init(a): +func init(_element: HTMLParser.HTMLElement): pass diff --git a/Scripts/Tags/h1.gd b/Scripts/Tags/h1.gd index 33b31d9..36cfe15 100644 --- a/Scripts/Tags/h1.gd +++ b/Scripts/Tags/h1.gd @@ -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 diff --git a/Scripts/Tags/h2.gd b/Scripts/Tags/h2.gd index 33b31d9..36cfe15 100644 --- a/Scripts/Tags/h2.gd +++ b/Scripts/Tags/h2.gd @@ -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 diff --git a/Scripts/Tags/h3.gd b/Scripts/Tags/h3.gd index d12c949..36cfe15 100644 --- a/Scripts/Tags/h3.gd +++ b/Scripts/Tags/h3.gd @@ -2,5 +2,5 @@ extends Control @onready var rich_text_label: RichTextLabel = $RichTextLabel -func init(element: HTMLParser.HTMLElement, parser: HTMLParser = null) -> void: - pass \ No newline at end of file +func init(_element: HTMLParser.HTMLElement) -> void: + pass diff --git a/Scripts/Tags/h4.gd b/Scripts/Tags/h4.gd index d12c949..36cfe15 100644 --- a/Scripts/Tags/h4.gd +++ b/Scripts/Tags/h4.gd @@ -2,5 +2,5 @@ extends Control @onready var rich_text_label: RichTextLabel = $RichTextLabel -func init(element: HTMLParser.HTMLElement, parser: HTMLParser = null) -> void: - pass \ No newline at end of file +func init(_element: HTMLParser.HTMLElement) -> void: + pass diff --git a/Scripts/Tags/h5.gd b/Scripts/Tags/h5.gd index d12c949..36cfe15 100644 --- a/Scripts/Tags/h5.gd +++ b/Scripts/Tags/h5.gd @@ -2,5 +2,5 @@ extends Control @onready var rich_text_label: RichTextLabel = $RichTextLabel -func init(element: HTMLParser.HTMLElement, parser: HTMLParser = null) -> void: - pass \ No newline at end of file +func init(_element: HTMLParser.HTMLElement) -> void: + pass diff --git a/Scripts/Tags/h6.gd b/Scripts/Tags/h6.gd index d12c949..36cfe15 100644 --- a/Scripts/Tags/h6.gd +++ b/Scripts/Tags/h6.gd @@ -2,5 +2,5 @@ extends Control @onready var rich_text_label: RichTextLabel = $RichTextLabel -func init(element: HTMLParser.HTMLElement, parser: HTMLParser = null) -> void: - pass \ No newline at end of file +func init(_element: HTMLParser.HTMLElement) -> void: + pass diff --git a/Scripts/Tags/input.gd b/Scripts/Tags/input.gd index eeb06e0..70c25e3 100644 --- a/Scripts/Tags/input.gd +++ b/Scripts/Tags/input.gd @@ -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(): diff --git a/Scripts/Tags/pre.gd b/Scripts/Tags/pre.gd index 442c6d4..2b6562a 100644 --- a/Scripts/Tags/pre.gd +++ b/Scripts/Tags/pre.gd @@ -1,5 +1,5 @@ class_name Pre extends VBoxContainer -func init(element: HTMLParser.HTMLElement) -> void: +func init(_element: HTMLParser.HTMLElement) -> void: pass diff --git a/Scripts/Utils/ColorUtils.gd b/Scripts/Utils/ColorUtils.gd new file mode 100644 index 0000000..c1464cb --- /dev/null +++ b/Scripts/Utils/ColorUtils.gd @@ -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 diff --git a/Scripts/Utils/ColorUtils.gd.uid b/Scripts/Utils/ColorUtils.gd.uid new file mode 100644 index 0000000..0eb54b9 --- /dev/null +++ b/Scripts/Utils/ColorUtils.gd.uid @@ -0,0 +1 @@ +uid://cuwui6hmwuiip diff --git a/Scripts/Utils/FlexUtils.gd b/Scripts/Utils/FlexUtils.gd new file mode 100644 index 0000000..30b3504 --- /dev/null +++ b/Scripts/Utils/FlexUtils.gd @@ -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 diff --git a/Scripts/Utils/FlexUtils.gd.uid b/Scripts/Utils/FlexUtils.gd.uid new file mode 100644 index 0000000..eb62b9f --- /dev/null +++ b/Scripts/Utils/FlexUtils.gd.uid @@ -0,0 +1 @@ +uid://dibofhomw401r diff --git a/Scripts/Utils/SizeUtils.gd b/Scripts/Utils/SizeUtils.gd new file mode 100644 index 0000000..a64dbac --- /dev/null +++ b/Scripts/Utils/SizeUtils.gd @@ -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 diff --git a/Scripts/Utils/SizeUtils.gd.uid b/Scripts/Utils/SizeUtils.gd.uid new file mode 100644 index 0000000..909afd7 --- /dev/null +++ b/Scripts/Utils/SizeUtils.gd.uid @@ -0,0 +1 @@ +uid://ciqaxogsxvs24 diff --git a/Scripts/Utils/SizingUtils.gd b/Scripts/Utils/SizingUtils.gd new file mode 100644 index 0000000..d1123b8 --- /dev/null +++ b/Scripts/Utils/SizingUtils.gd @@ -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 diff --git a/Scripts/Utils/SizingUtils.gd.uid b/Scripts/Utils/SizingUtils.gd.uid new file mode 100644 index 0000000..23e320b --- /dev/null +++ b/Scripts/Utils/SizingUtils.gd.uid @@ -0,0 +1 @@ +uid://bsjm8qf4ry06e diff --git a/Scripts/Utils/UtilityClassValidator.gd b/Scripts/Utils/UtilityClassValidator.gd new file mode 100644 index 0000000..4f9b43e --- /dev/null +++ b/Scripts/Utils/UtilityClassValidator.gd @@ -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 \ No newline at end of file diff --git a/Scripts/Utils/UtilityClassValidator.gd.uid b/Scripts/Utils/UtilityClassValidator.gd.uid new file mode 100644 index 0000000..9f09963 --- /dev/null +++ b/Scripts/Utils/UtilityClassValidator.gd.uid @@ -0,0 +1 @@ +uid://criqj88gjrh4m diff --git a/Scripts/main.gd b/Scripts/main.gd index 648a84f..869cf5e 100644 --- a/Scripts/main.gd +++ b/Scripts/main.gd @@ -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)