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
+
+
+
+
+
+
+
+ 👤 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]