diff --git a/README.md b/README.md index e810707..497e305 100644 --- a/README.md +++ b/README.md @@ -59,4 +59,4 @@ Supported styles: - `flex-shrink-{n}` (flex-shrink) - `basis-{size}` (flex-basis) - `self-auto`, `self-start`, `self-end`, `self-center`, `self-stretch`, `self-baseline` (align-self) -- `order-{n}` (order) \ No newline at end of file +- `order-{n}` (order) diff --git a/Scenes/main.tscn b/Scenes/main.tscn index 689f383..8808591 100644 --- a/Scenes/main.tscn +++ b/Scenes/main.tscn @@ -266,6 +266,7 @@ layout_mode = 2 mouse_filter = 1 [node name="WebsiteBackground" type="Panel" parent="."] +unique_name_in_owner = true z_index = -1 layout_mode = 1 anchors_preset = 15 diff --git a/Scripts/AutoSizingFlexContainer.gd b/Scripts/AutoSizingFlexContainer.gd index 91a93ad..0b8d9e0 100644 --- a/Scripts/AutoSizingFlexContainer.gd +++ b/Scripts/AutoSizingFlexContainer.gd @@ -85,38 +85,17 @@ func _resort() -> void: _root.mark_dirty_and_propogate() - var auto_size_width = not has_meta("custom_css_width") and not has_meta("should_fill_horizontal") - var auto_size_height = not has_meta("custom_css_height") and not has_meta("should_fill_vertical") + var auto_size_width = not has_meta("custom_css_width") and not has_meta("should_fill_horizontal") and not has_meta("custom_css_width_percentage") + var auto_size_height = not has_meta("custom_css_height") and not has_meta("should_fill_vertical") and not has_meta("custom_css_height_percentage") var available_width = NAN var available_height = NAN if not auto_size_width: - if has_meta("should_fill_horizontal"): - # For w-full, use the full parent size if available - var parent_container = get_parent() - if parent_container and parent_container.size.x > 0: - available_width = parent_container.size.x - elif size.x > 0: - available_width = size.x - else: - # Fallback: try to get from custom_minimum_size - available_width = custom_minimum_size.x if custom_minimum_size.x > 0 else NAN - else: - available_width = size.x + available_width = calculate_available_dimension(true) if not auto_size_height: - if has_meta("should_fill_vertical"): - # For h-full, use the full parent size if available - var parent_container = get_parent() - if parent_container and parent_container.size.y > 0: - available_height = parent_container.size.y - elif size.y > 0: - available_height = size.y - else: - available_height = custom_minimum_size.y if custom_minimum_size.y > 0 else NAN - else: - available_height = size.y + available_height = calculate_available_dimension(false) _root.calculate_layout(available_width, available_height, 1) # 1 = LTR direction @@ -127,31 +106,8 @@ func _resort() -> void: ) # Respect any explicit width/height set via metadata - var custom_w = 0.0 - if has_meta("custom_css_width"): - custom_w = float(get_meta("custom_css_width")) - elif has_meta("should_fill_horizontal"): - # Use the same logic as available_width calculation - var parent_container = get_parent() - if parent_container and parent_container.size.x > 0: - custom_w = parent_container.size.x - elif size.x > 0: - custom_w = size.x - else: - custom_w = custom_minimum_size.x if custom_minimum_size.x > 0 else 0.0 - - var custom_h = 0.0 - if has_meta("custom_css_height"): - custom_h = float(get_meta("custom_css_height")) - elif has_meta("should_fill_vertical"): - # Use the same logic as available_height calculation - var parent_container = get_parent() - if parent_container and parent_container.size.y > 0: - custom_h = parent_container.size.y - elif size.y > 0: - custom_h = size.y - else: - custom_h = custom_minimum_size.y if custom_minimum_size.y > 0 else 0.0 + var custom_w = calculate_custom_dimension(true) + var custom_h = calculate_custom_dimension(false) var needed_size = Vector2( max(custom_w, computed_size.x), @@ -200,3 +156,56 @@ func _resort() -> void: queue_redraw() 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" + + if has_meta(fill_key): + return get_parent_or_fallback_size(is_width) + elif has_meta(percentage_key): + var percentage_str = get_meta(percentage_key) + var percentage = float(percentage_str.replace("%", "")) / 100.0 + var parent_size = get_parent_size(is_width) + return parent_size * percentage if parent_size > 0 else (custom_minimum_size.x if is_width else custom_minimum_size.y) + else: + return size.x if is_width else size.y + +func calculate_custom_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" + + if has_meta(dimension_key): + return float(get_meta(dimension_key)) + elif has_meta(percentage_key): + var percentage_str = get_meta(percentage_key) + var percentage = float(percentage_str.replace("%", "")) / 100.0 + var parent_size = get_parent_size(is_width) + if parent_size > 0: + return parent_size * percentage + elif (size.x if is_width else size.y) > 0: + return (size.x if is_width else size.y) * percentage + else: + return 0.0 + elif has_meta(fill_key): + return get_parent_or_fallback_size(is_width) + else: + return 0.0 + +func get_parent_size(is_width: bool) -> float: + var parent_container = get_parent() + if parent_container: + return parent_container.size.x if is_width else parent_container.size.y + return 0.0 + +func get_parent_or_fallback_size(is_width: bool) -> float: + var parent_container = get_parent() + if parent_container and (parent_container.size.x if is_width else parent_container.size.y) > 0: + return parent_container.size.x if is_width else parent_container.size.y + elif (size.x if is_width else size.y) > 0: + return size.x if is_width else size.y + else: + var fallback = custom_minimum_size.x if is_width else custom_minimum_size.y + return fallback if fallback > 0 else NAN diff --git a/Scripts/B9/CSSParser.gd b/Scripts/B9/CSSParser.gd index 46d5c73..0abeb94 100644 --- a/Scripts/B9/CSSParser.gd +++ b/Scripts/B9/CSSParser.gd @@ -249,11 +249,15 @@ static func parse_utility_class_internal(rule: CSSRule, utility_name: String) -> # Width if utility_name.begins_with("w-"): 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) 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) return # Min width @@ -367,6 +371,54 @@ static func parse_utility_class_internal(rule: CSSRule, utility_name: String) -> rule.properties["order"] = val.to_int() return + # Handle border-radius classes like rounded-8, rounded-lg, etc. + if utility_name.begins_with("rounded-"): + var val = utility_name.substr(8) + rule.properties["border-radius"] = parse_size(val) + return + if utility_name == "rounded": + rule.properties["border-radius"] = "4px" # Default rounded + return + + # 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) + rule.properties["padding"] = padding_value + return + if utility_name.begins_with("px-"): + var val = utility_name.substr(3) + var padding_value = 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) + 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) + 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) + 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) + 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) + rule.properties["padding-left"] = padding_value + return + # Handle border radius classes like rounded, rounded-lg, rounded-[12px] if utility_name == "rounded": rule.properties["border-radius"] = "4px" diff --git a/Scripts/Constants.gd b/Scripts/Constants.gd index 29dfa11..156c25b 100644 --- a/Scripts/Constants.gd +++ b/Scripts/Constants.gd @@ -6,6 +6,7 @@ const SECONDARY_COLOR = Color(43/255.0, 43/255.0, 43/255.0, 1) const HOVER_COLOR = Color(0, 0, 0, 1) const DEFAULT_CSS = """ +body { text-base text-[#000000] text-left } h1 { text-5xl font-bold } h2 { text-4xl font-bold } h3 { text-3xl font-bold } @@ -23,6 +24,82 @@ pre { text-xl font-mono } button { bg-[#1b1b1b] rounded-md text-white hover:bg-[#2a2a2a] active:bg-[#101010] } """ +var HTML_CONTENT = """ + My Custom Dashboard + + + + + + + + + +

📊 My Dashboard

+ + +
+
+

Users

+

1,240

+
+
+

Sales

+

$9,842

+
+
+

Visitors

+

3,590

+
+
+ + + + +

👤 User Panel

+
+
+

Name: Jane Doe

+

Email: jane@example.com

+

Status: Active

+
+
+

Plan: Pro

+

Projects: 8

+

Tasks: 42

+
+
+ + + + +

📝 Recent Activity

+
+
    +
  • ✅ Task "Update UI" marked as complete
  • +
  • 🔔 New comment on "Bug Fix #224"
  • +
  • 📤 Exported report "Q2 Metrics"
  • +
+
+ + + + +

🔧 Actions

+
+ + + +
+ + +""".to_utf8_buffer() var HTML_CONTENT2 = " My cool web @@ -230,7 +307,7 @@ So ".to_utf8_buffer() -var HTML_CONTENT = """ +var HTML_CONTENT3 = """ Task Manager diff --git a/Scripts/StyleManager.gd b/Scripts/StyleManager.gd index 696b8ab..ef6cb06 100644 --- a/Scripts/StyleManager.gd +++ b/Scripts/StyleManager.gd @@ -9,9 +9,9 @@ static func parse_size(val): return float(val.replace("px", "")) if val.ends_with("rem"): return float(val.replace("rem", "")) * 16.0 - if val.ends_with("%"): - # Not supported directly, skip - return null + 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) @@ -35,14 +35,18 @@ static func apply_element_styles(node: Control, element: HTMLParser.HTMLElement, var skip_sizing = should_skip_sizing(node, element, parser) if (width != null or height != null) and not skip_sizing: - node.custom_minimum_size = Vector2( - width if width != null else node.custom_minimum_size.x, - height if height != null else node.custom_minimum_size.y - ) - if width != null: - node.size_flags_horizontal = Control.SIZE_SHRINK_BEGIN - if height != null: - node.size_flags_vertical = Control.SIZE_SHRINK_BEGIN + # FlexContainers handle percentage sizing differently than regular controls + if node is FlexContainer: + if width != null and typeof(width) != TYPE_STRING: + node.custom_minimum_size.x = width + if height != null and typeof(height) != TYPE_STRING: + 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) + else: + # regular controls + apply_regular_control_sizing(node, width, height) if label and label != node: label.anchors_preset = Control.PRESET_FULL_RECT @@ -62,6 +66,7 @@ static func apply_element_styles(node: Control, element: HTMLParser.HTMLElement, static func apply_styles_to_label(label: RichTextLabel, styles: Dictionary, element: HTMLParser.HTMLElement, parser) -> void: var text = element.get_preserved_text() if element.tag_name == "pre" else element.get_bbcode_formatted_text(parser) + var font_size = 24 # default # Apply font size if styles.has("font-size"): @@ -120,7 +125,6 @@ static func apply_styles_to_label(label: RichTextLabel, styles: Dictionary, elem bold_close, "[/color]" if color_tag.length() > 0 else "", ] - 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 @@ -180,6 +184,8 @@ static func apply_flex_container_properties(node: FlexContainer, styles: Diction 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"): @@ -187,6 +193,8 @@ static func apply_flex_container_properties(node: FlexContainer, styles: Diction 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"): @@ -273,6 +281,137 @@ static func should_skip_sizing(node: Control, element: HTMLParser.HTMLElement, p 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 + +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, "", []) + + # Apply background color + if styles.has("background-color"): + var style_box = StyleBoxFlat.new() + style_box.bg_color = styles["background-color"] as Color + website_background.add_theme_stylebox_override("panel", style_box) + + # Apply padding + var has_padding = styles.has("padding") or styles.has("padding-top") or styles.has("padding-right") or styles.has("padding-bottom") or styles.has("padding-left") + + if has_padding: + var margin_container = MarginContainer.new() + margin_container.name = "BodyMarginContainer" + margin_container.size_flags_horizontal = website_container.size_flags_horizontal + margin_container.size_flags_vertical = website_container.size_flags_vertical + + # ScrollContainer + # |__ BodyMarginContainer + # |__ WebsiteContainer + var original_parent = website_container.get_parent() + var container_index = website_container.get_index() + + original_parent.remove_child(website_container) + original_parent.add_child(margin_container) + original_parent.move_child(margin_container, container_index) + margin_container.add_child(website_container) + + var margin_val = parse_size(styles["padding"]) + + margin_container.add_theme_constant_override("margin_left", margin_val) + margin_container.add_theme_constant_override("margin_right", margin_val) + margin_container.add_theme_constant_override("margin_top", margin_val) + margin_container.add_theme_constant_override("margin_bottom", margin_val) + + # Apply individual padding values + var padding_sides = [ + ["padding-top", "margin_top"], + ["padding-right", "margin_right"], + ["padding-bottom", "margin_bottom"], + ["padding-left", "margin_left"] + ] + + for side_pair in padding_sides: + var style_key = side_pair[0] + var margin_key = side_pair[1] + if styles.has(style_key): + var margin_val2 = parse_size(styles[style_key]) + 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", "")) diff --git a/Scripts/main.gd b/Scripts/main.gd index b0c3dce..648a84f 100644 --- a/Scripts/main.gd +++ b/Scripts/main.gd @@ -2,6 +2,7 @@ class_name Main extends Control @onready var website_container: Control = %WebsiteContainer +@onready var website_background: Control = %WebsiteBackground @onready var tab_container: TabManager = $VBoxContainer/TabContainer const LOADER_CIRCLE = preload("res://Assets/Icons/loader-circle.svg") const AUTO_SIZING_FLEX_CONTAINER = preload("res://Scripts/AutoSizingFlexContainer.gd") @@ -62,6 +63,10 @@ func render() -> void: tab.update_icon_from_url(icon) var body = parser.find_first("body") + + if body: + StyleManager.apply_body_styles(body, parser, website_container, website_background) + var i = 0 while i < body.children.size(): var element: HTMLParser.HTMLElement = body.children[i]