% width/height, body background color, margin/padding

This commit is contained in:
Face
2025-07-30 14:39:33 +03:00
parent c67b8dc552
commit 4b58a77ec2
7 changed files with 347 additions and 64 deletions

View File

@@ -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)
- `order-{n}` (order)

View File

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

View File

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

View File

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

View File

@@ -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 = """<head>
<title>My Custom Dashboard</title>
<icon src="https://cdn-icons-png.flaticon.com/512/1828/1828774.png">
<meta name="theme-color" content="#1a202c">
<meta name="description" content="A stylish no-script dashboard">
<style>
h1 { text-[#ffffff] text-3xl font-bold }
h2 { text-[#cbd5e1] text-xl }
p { text-[#94a3b8] text-base }
button { bg-[#4ade80] text-[#ffffff] hover:bg-[#22c55e] active:bg-[#15803d] }
card { bg-[#1e293b] text-[#f8fafc] rounded-xl p-4 shadow-lg }
</style>
</head>
<body style="bg-[#0f172a] p-8 text-white">
<h1 style="text-center mb-4">📊 My Dashboard</h1>
<!-- Top Summary Cards -->
<div style="flex flex-row gap-4 justify-center flex-wrap">
<div style="card w-48 h-24 flex flex-col justify-center items-center">
<h2>Users</h2>
<p>1,240</p>
</div>
<div style="card w-48 h-24 flex flex-col justify-center items-center">
<h2>Sales</h2>
<p>$9,842</p>
</div>
<div style="card w-48 h-24 flex flex-col justify-center items-center">
<h2>Visitors</h2>
<p>3,590</p>
</div>
</div>
<separator direction="horizontal" />
<!-- User Info Panel -->
<h2 style="text-center mt-6">👤 User Panel</h2>
<div style="flex flex-row gap-4 justify-center mt-2">
<div style="card w-64">
<p>Name: Jane Doe</p>
<p>Email: jane@example.com</p>
<p>Status: <span style="text-[#22c55e]">Active</span></p>
</div>
<div style="card w-64">
<p>Plan: Pro</p>
<p>Projects: 8</p>
<p>Tasks: 42</p>
</div>
</div>
<separator direction="horizontal" />
<!-- Recent Activity Log -->
<h2 style="text-center mt-6">📝 Recent Activity</h2>
<div style="w-[80%] mx-auto mt-2">
<ul>
<li style="bg-[#334155] px-4 py-2 rounded mb-1">✅ Task "Update UI" marked as complete</li>
<li style="bg-[#334155] px-4 py-2 rounded mb-1">🔔 New comment on "Bug Fix #224"</li>
<li style="bg-[#334155] px-4 py-2 rounded mb-1">📤 Exported report "Q2 Metrics"</li>
</ul>
</div>
<separator direction="horizontal" />
<!-- Action Buttons -->
<h2 style="text-center mt-6">🔧 Actions</h2>
<div style="flex flex-row gap-2 justify-center mt-2">
<button style="rounded-lg px-4 py-2">Create Report</button>
<button style="rounded-lg px-4 py-2 bg-[#3b82f6] hover:bg-[#2563eb] active:bg-[#1e40af]">Invite User</button>
<button style="rounded-lg px-4 py-2 bg-[#facc15] text-[#000] hover:bg-[#eab308] active:bg-[#ca8a04]">Upgrade Plan</button>
</div>
</body>
""".to_utf8_buffer()
var HTML_CONTENT2 = "<head>
<title>My cool web</title>
<icon src=\"https://upload.wikimedia.org/wikipedia/commons/thumb/c/c1/Google_%22G%22_logo.svg/768px-Google_%22G%22_logo.svg.png\">
@@ -230,7 +307,7 @@ So
</div>
</body>".to_utf8_buffer()
var HTML_CONTENT = """<head>
var HTML_CONTENT3 = """<head>
<title>Task Manager</title>
<icon src="https://cdn-icons-png.flaticon.com/512/126/126472.png">

View File

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

View File

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