flexbox, code cleanup

This commit is contained in:
Face
2025-07-28 15:22:34 +03:00
parent e0a8b6f414
commit d02b109743
222 changed files with 5163 additions and 530 deletions

View File

@@ -0,0 +1,131 @@
@tool
class_name AutoSizingFlexContainer
extends FlexContainer
signal flex_resized
var bgcolor: Color = Color(0,0,0,0)
func set_background_color(color: Color) -> void:
bgcolor = color
queue_redraw()
func _notification(what: int) -> void:
super._notification(what)
if what == NOTIFICATION_DRAW and bgcolor.a > 0:
draw_rect(Rect2(Vector2.ZERO, size), bgcolor)
# This is the overridden layout logic for the auto-sizing container
func _resort() -> void:
#size_flags_horizontal = Control.SIZE_SHRINK_CENTER
if debug_draw:
_draw_rects.clear()
var child_count = get_child_count()
var valid_child_index = 0
for i in range(child_count):
var c = get_child(i)
if not c is Control or c.is_set_as_top_level():
continue
var cid = c.get_instance_id()
var target_index = _find_index_from_flex_list(_flex_list, cid)
var flexbox: Flexbox
# If the child is not visible, remove its corresponding flexbox node
if not c.is_visible_in_tree():
if target_index != -1:
_root.remove_child_at(target_index)
_flex_list.remove_at(target_index)
continue
# Find, swap, or create a new flexbox node for the child
if target_index != -1:
var old_flex_data = _flex_list[valid_child_index]
var new_flex_data = _flex_list[target_index]
flexbox = new_flex_data[FlexDataType.FLEXBOX]
if old_flex_data[FlexDataType.CID] != cid:
_root.swap_child(valid_child_index, target_index)
_flex_list[target_index] = old_flex_data
_flex_list[valid_child_index] = new_flex_data
else:
flexbox = Flexbox.new()
_root.insert_child(flexbox, valid_child_index)
_flex_list.insert(valid_child_index, [cid, flexbox, c])
# Set the minimum size and apply flex properties for the child
_set_control_min_size(c, flexbox)
var flex_metas = c.get_meta("flex_metas", {})
if flex_metas.size():
apply_flex_meta(flexbox, flex_metas)
if flex_metas.has("padding"):
padding_wrapper(c, flex_metas.get("padding"))
valid_child_index += 1
# Clean up any flexbox nodes for children that were removed
child_count = valid_child_index
if child_count != _flex_list.size():
for i in range(_flex_list.size() - 1, child_count - 1, -1):
_root.remove_child_at(i)
_flex_list.resize(child_count)
_root.mark_dirty_and_propogate()
var auto_size_width = not has_meta("custom_css_width")
var auto_size_height = not has_meta("custom_css_height")
var available_width = NAN if auto_size_width else size.x
var available_height = NAN if auto_size_height else size.y
_root.calculate_layout(available_width, available_height, 1) # 1 = LTR direction
# Get the size computed by Yoga
var computed_size = Vector2(
_root.get_computed_width(),
_root.get_computed_height()
)
# 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"))
var custom_h = 0.0
if has_meta("custom_css_height"):
custom_h = float(get_meta("custom_css_height"))
var needed_size = Vector2(
max(custom_w, computed_size.x),
max(custom_h, computed_size.y)
)
# Construct the new minimum size for this container
var new_min_size = custom_minimum_size
if auto_size_width:
new_min_size.x = needed_size.x
if auto_size_height:
new_min_size.y = needed_size.y
if not custom_minimum_size.is_equal_approx(new_min_size):
custom_minimum_size = new_min_size
# Apply the calculated layout to each child control
for flex_data in _flex_list:
var flexbox = flex_data[FlexDataType.FLEXBOX]
var c = flex_data[FlexDataType.CONTROL]
var offset = Vector2(flexbox.get_computed_left(), flexbox.get_computed_top())
var rect_size = Vector2(flexbox.get_computed_width(), flexbox.get_computed_height())
_fit_child_in_rect(c, Rect2(offset, rect_size))
if debug_draw:
_draw_debug_rect(Rect2(offset, rect_size), Color(1, 0, 0, 0.8))
if has_meta("custom_css_background_color"):
set_background_color(get_meta("custom_css_background_color"))
queue_redraw()
emit_signal("flex_resized")

View File

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

View File

@@ -258,6 +258,96 @@ func parse_utility_class(rule: CSSRule, utility_name: String) -> void:
rule.properties["max-height"] = parse_size(val)
return
# Flex container
if utility_name == "flex":
rule.properties["display"] = "flex"
return
if utility_name == "inline-flex":
rule.properties["display"] = "inline-flex"
return
# Flex direction
match utility_name:
"flex-row": rule.properties["flex-direction"] = "row"; return
"flex-row-reverse": rule.properties["flex-direction"] = "row-reverse"; return
"flex-col": rule.properties["flex-direction"] = "column"; return
"flex-col-reverse": rule.properties["flex-direction"] = "column-reverse"; return
# Flex wrap
match utility_name:
"flex-nowrap": rule.properties["flex-wrap"] = "nowrap"; return
"flex-wrap": rule.properties["flex-wrap"] = "wrap"; return
"flex-wrap-reverse": rule.properties["flex-wrap"] = "wrap-reverse"; return
# Justify content
match utility_name:
"justify-start": rule.properties["justify-content"] = "flex-start"; return
"justify-end": rule.properties["justify-content"] = "flex-end"; return
"justify-center": rule.properties["justify-content"] = "center"; return
"justify-between": rule.properties["justify-content"] = "space-between"; return
"justify-around": rule.properties["justify-content"] = "space-around"; return
"justify-evenly": rule.properties["justify-content"] = "space-evenly"; return
# Align items
match utility_name:
"items-start": rule.properties["align-items"] = "flex-start"; return
"items-end": rule.properties["align-items"] = "flex-end"; return
"items-center": rule.properties["align-items"] = "center"; return
"items-baseline": rule.properties["align-items"] = "baseline"; return
"items-stretch": rule.properties["align-items"] = "stretch"; return
# Align content
match utility_name:
"content-start": rule.properties["align-content"] = "flex-start"; return
"content-end": rule.properties["align-content"] = "flex-end"; return
"content-center": rule.properties["align-content"] = "center"; return
"content-between": rule.properties["align-content"] = "space-between"; return
"content-around": rule.properties["align-content"] = "space-around"; return
"content-stretch": rule.properties["align-content"] = "stretch"; return
# Gap
if utility_name.begins_with("gap-"):
var val = utility_name.substr(4)
rule.properties["gap"] = parse_size(val)
return
if utility_name.begins_with("row-gap-"):
var val = utility_name.substr(8)
rule.properties["row-gap"] = parse_size(val)
return
if utility_name.begins_with("col-gap-"):
var val = utility_name.substr(8)
rule.properties["column-gap"] = parse_size(val)
return
# FLEX ITEM PROPERTIES
if utility_name.begins_with("flex-grow-"):
var val = utility_name.substr(10)
rule.properties["flex-grow"] = val.to_float()
return
if utility_name.begins_with("flex-shrink-"):
var val = utility_name.substr(12)
rule.properties["flex-shrink"] = val.to_float()
return
if utility_name.begins_with("basis-"):
var val = utility_name.substr(6)
rule.properties["flex-basis"] = parse_size(val)
return
# Align self
match utility_name:
"self-auto": rule.properties["align-self"] = "auto"; return
"self-start": rule.properties["align-self"] = "flex-start"; return
"self-end": rule.properties["align-self"] = "flex-end"; return
"self-center": rule.properties["align-self"] = "center"; return
"self-stretch": rule.properties["align-self"] = "stretch"; return
"self-baseline": rule.properties["align-self"] = "baseline"; return
# Order
if utility_name.begins_with("order-"):
var val = utility_name.substr(6)
rule.properties["order"] = val.to_int()
return
# Handle more utility classes as needed
# Add more cases here for other utilities
@@ -292,7 +382,6 @@ func extract_bracket_content(string: String, start_idx: int) -> String:
return string.substr(open_idx + 1, close_idx - open_idx - 1)
func parse_color(color_string: String) -> Color:
print("DEBUG: parsing color: ", color_string)
color_string = color_string.strip_edges()
# Handle hex colors

View File

@@ -55,21 +55,6 @@ var xml_parser: XMLParser
var bitcode: PackedByteArray
var parse_result: ParseResult
var DEFAULT_CSS := """
h1 { text-5xl font-bold }
h2 { text-4xl font-bold }
h3 { text-3xl font-bold }
h4 { text-2xl font-bold }
h5 { text-xl font-bold }
b { font-bold }
i { font-italic }
u { underline }
small { text-xl }
mark { bg-[#FFFF00] }
code { text-xl font-mono }
a { text-[#1a0dab] }
"""
func _init(data: PackedByteArray):
bitcode = data
xml_parser = XMLParser.new()
@@ -125,7 +110,7 @@ func process_styles() -> void:
return
# Collect all style element content
var css_content = DEFAULT_CSS
var css_content = Constants.DEFAULT_CSS
var style_elements = find_all("style")
for style_element in style_elements:
if style_element.get_attribute("src").is_empty():

View File

@@ -4,3 +4,229 @@ const MAIN_COLOR = Color(27/255.0, 27/255.0, 27/255.0, 1)
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 = """
h1 { text-5xl font-bold }
h2 { text-4xl font-bold }
h3 { text-3xl font-bold }
h4 { text-2xl font-bold }
h5 { text-xl font-bold }
b { font-bold }
i { font-italic }
u { underline }
small { text-xl }
mark { bg-[#FFFF00] }
code { text-xl font-mono }
a { text-[#1a0dab] }
pre { text-xl font-mono }
"""
var HTML_CONTENT = "<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\">
<meta name=\"theme-color\" content=\"#000000\">
<meta name=\"description\" content=\"My cool web\">
<style>
h1 { text-[#ff0000] font-italic hover:text-[#00ff00] }
p { text-[#333333] text-2xl }
</style>
<style src=\"styles.css\">
<script src=\"script.lua\" />
</head>
<body>
<h1>Header 1</h1>
<h2>Header 2</h2>
<h3>Header 3</h3>
<h4>Header 4</h4>
<h5>Header 5</h5>
<h6>Header 6</h6>
<p>Hey there! this is a test</p>
<b>This is bold</b>
<i>This is italic <mark>actually, and it's pretty <u>cool</u></mark></i>
<u>This is underline</u>
<small>this is small</small>
<mark>this is marked</mark>
<code>this is code<span> THIS IS A SPAN AND SHOULDNT BE ANY DIFFERENT</span></code>
<p>
<a href=\"https://youtube.com\">Hello gang</a>
</p>
<pre>
Text in a pre element
is displayed in a fixed-width
font, and it preserves
both spaces and
line breaks
</pre>
<p style=\"text-center w-32 h-32\">
So
</p>
<select style=\"text-center max-w-5 max-h-32\">
<option value=\"test1\">Test 1</option>
<option value=\"test2\" selected=\"true\">Test 2</option>
<option value=\"test3\">Test 3</option>
<option value=\"test4\" disabled=\"true\">Test 4</option>
<option value=\"test5\">Test 5</option>
</select>
<textarea />
<textarea cols=\"30\" />
<textarea rows=\"2\" />
<textarea maxlength=\"20\" />
<textarea readonly=\"true\">le skibidi le toilet</textarea>
<textarea disabled=\"true\" value=\"DISABLED\" />
<textarea placeholder=\"this is a placeholder...\" />
<!-- action, method, and type=submit are for when we implement Lua -->
<form action=\"/submit\" method=\"POST\">
<span>Name:</span>
<input type=\"text\" placeholder=\"First name\" value=\"John\" maxlength=\"20\" minlength=\"3\" />
<span>Email regex:</span>
<input type=\"text\" placeholder=\"Last name\" value=\"Doe\" pattern=\"^[^@\\s]+@[^@\\s]+\\.[^@\\s]+$\" />
<span>Smart:</span>
<input type=\"checkbox\" />
<input type=\"checkbox\" value=\"true\" />
<p>favorite food</p>
<input type=\"radio\" group=\"food\" />
<span>Pizza</span>
<input type=\"radio\" group=\"food\" />
<span>Berry</span>
<input type=\"radio\" group=\"food\" />
<span>Gary</span>
<h2>Color</h2>
<input type=\"color\" value=\"#ff0000\" />
<h2>Date</h2>
<input type=\"date\" value=\"2018-07-22\" />
<h2>Range Slider</h2>
<input style=\"max-w-2 max-h-2\" type=\"range\" min=\"0\" max=\"100\" step=\"5\" value=\"50\" />
<h2>Number Input</h2>
<input type=\"number\" min=\"1\" max=\"10\" step=\"0.5\" value=\"5\" placeholder=\"Enter number\" />
<h2>File Upload</h2>
<input type=\"file\" accept=\".txt,.pdf,image/*\" />
<input type=\"password\" placeholder=\"your password...\" />
<button type=\"submit\">Submit</button>
</form>
<separator direction=\"horizontal\" />
# Ordered list
<ol>
<li>hello gang</li>
<li>this</li>
<li>is</li>
</ol>
<ol type=\"zero-lead\">
<li>hello gang</li>
<li>this</li>
<li>is</li>
<li>a test</li>
</ol>
<ol type=\"lower-alpha\">
<li>hello gang</li>
<li>this</li>
<li>is</li>
<li>a test</li>
</ol>
<ol type=\"upper-alpha\">
<li>hello gang</li>
<li>this</li>
<li>is</li>
<li>a test</li>
</ol>
<ol type=\"lower-roman\">
<li>hello gang</li>
<li>this</li>
<li>is</li>
<li>a test</li>
</ol>
<ol type=\"upper-roman\">
<li>hello gang</li>
<li>this</li>
<li>is</li>
<li>a test</li>
</ol>
<ul>
<li>hello gang</li>
<li>this</li>
<li>is</li>
<li>a test</li>
</ul>
<ul type=\"circle\">
<li>hello gang</li>
<li>this</li>
<li>is</li>
<li>a test</li>
</ul>
<ul type=\"none\">
<li>hello gang</li>
<li>this</li>
<li>is</li>
<li>a test</li>
</ul>
<ul type=\"square\">
<li>hello gang</li>
<li>this</li>
<li>is</li>
<li>a test</li>
</ul>
<img style=\"text-center max-w-24 max-h-24\" src=\"https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQMNUPIKabszX0Js_c0kfa4cz_JQYKfGTuBUA&s\" />
<separator direction=\"vertical\" />
<!-- FLEXBOX EXAMPLES -->
<h2>Flex Row (gap, justify-between, items-center)</h2>
<div style=\"flex flex-row gap-4 justify-between items-center w-64 h-16 bg-[#f0f0f0]\">
<span style=\"bg-[#ffaaaa] w-16 h-8 flex items-center justify-center\">A</span>
<span style=\"bg-[#aaffaa] w-16 h-8 flex items-center justify-center\">B</span>
<span style=\"bg-[#aaaaff] w-16 h-8 flex items-center justify-center\">C</span>
</div>
<h2>Flex Column (gap, items-center, content-center)</h2>
<div style=\"flex flex-col gap-2 items-center content-center h-32 w-32 bg-[#e0e0e0]\">
<span style=\"bg-[#ffaaaa] w-16 h-6 flex items-center justify-center\">1</span>
<span style=\"bg-[#aaffaa] w-16 h-6 flex items-center justify-center\">2</span>
<span style=\"bg-[#aaaaff] w-16 h-6 flex items-center justify-center\">3</span>
</div>
<h2>Flex Wrap (row, wrap, gap)</h2>
<div style=\"flex flex-row flex-wrap gap-2 w-40 bg-[#f8f8f8]\">
<span style=\"bg-[#ffaaaa] w-16 h-6 flex items-center justify-center\">X</span>
<span style=\"bg-[#aaffaa] w-16 h-6 flex items-center justify-center\">Y</span>
<span style=\"bg-[#aaaaff] w-16 h-6 flex items-center justify-center\">Z</span>
<span style=\"bg-[#ffffaa] w-16 h-6 flex items-center justify-center\">W</span>
</div>
<h2>Flex Grow/Shrink/Basis</h2>
<div style=\"flex flex-row gap-2 w-64 bg-[#f0f0f0]\">
<span style=\"bg-[#ffaaaa] flex-grow-1 h-8 flex items-center justify-center\">Grow 1</span>
<span style=\"bg-[#aaffaa] flex-grow-2 h-8 flex items-center justify-center\">Grow 2</span>
<span style=\"bg-[#aaaaff] flex-shrink-0 w-8 h-8 flex items-center justify-center\">No Shrink</span>
</div>
<h2>Align Self</h2>
<div style=\"flex flex-row h-24 bg-[#f0f0f0] items-stretch gap-2 w-64\">
<span style=\"bg-[#ffaaaa] w-12 h-8 self-start flex items-center justify-center\">Start</span>
<span style=\"bg-[#aaffaa] w-12 h-8 self-center flex items-center justify-center\">Center</span>
<span style=\"bg-[#aaaaff] w-12 h-8 self-end flex items-center justify-center\">End</span>
<span style=\"bg-[#ffffaa] w-12 h-8 self-stretch flex items-center justify-center\">Stretch</span>
</div>
</body>".to_utf8_buffer()

231
Scripts/StyleManager.gd Normal file
View File

@@ -0,0 +1,231 @@
class_name StyleManager
extends RefCounted
static func parse_size(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("%"):
# Not supported directly, skip
return null
return float(val)
static func apply_element_styles(node: Control, element: HTMLParser.HTMLElement, parser: HTMLParser) -> Control:
var styles = parser.get_element_styles(element)
var label = null
if not (node is FlexContainer):
label = node if node is RichTextLabel else node.get_node_or_null("RichTextLabel")
var width = null
var height = null
if styles.has("width"):
width = parse_size(styles["width"])
if styles.has("height"):
height = parse_size(styles["height"])
# Apply size directly to the node given.
if width != null or height != null:
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
if label and label != node:
label.anchors_preset = Control.PRESET_FULL_RECT
# Apply background color
if styles.has("background-color"):
var target_node_for_bg = node if node is FlexContainer else label
if target_node_for_bg:
print("SETTING BACKGROUND FOR ", target_node_for_bg, " TO COLOR: ", styles)
target_node_for_bg.set_meta("custom_css_background_color", styles["background-color"])
if target_node_for_bg.has_method("add_background_rect"):
target_node_for_bg.call_deferred("add_background_rect")
if label:
apply_styles_to_label(label, styles, element, parser)
return node
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"):
font_size = int(styles["font-size"])
# Apply color
var color_tag = ""
if styles.has("color"):
var color = styles["color"] as Color
color_tag = "[color=#%s]" % color.to_html(false)
# Apply bold
var bold_open = ""
var bold_close = ""
if styles.has("font-bold") and styles["font-bold"]:
bold_open = "[b]"
bold_close = "[/b]"
# Apply italic
var italic_open = ""
var italic_close = ""
if styles.has("font-italic") and styles["font-italic"]:
italic_open = "[i]"
italic_close = "[/i]"
# Apply underline
var underline_open = ""
var underline_close = ""
if styles.has("underline") and styles["underline"]:
underline_open = "[u]"
underline_close = "[/u]"
# Apply monospace font
var mono_open = ""
var mono_close = ""
if styles.has("font-mono") and styles["font-mono"]:
mono_open = "[code]"
mono_close = "[/code]"
if styles.has("text-align"):
match styles["text-align"]:
"left":
label.horizontal_alignment = HORIZONTAL_ALIGNMENT_LEFT
"center":
label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
"right":
label.horizontal_alignment = HORIZONTAL_ALIGNMENT_RIGHT
"justify":
label.horizontal_alignment = HORIZONTAL_ALIGNMENT_FILL
# Construct final text
var styled_text = "[font_size=%d]%s%s%s%s%s%s%s%s%s%s%s[/font_size]" % [
font_size,
color_tag,
bold_open,
italic_open,
underline_open,
mono_open,
text,
mono_close,
underline_close,
italic_close,
bold_close,
"[/color]" if color_tag.length() > 0 else "",
]
label.text = styled_text
static func apply_flex_container_properties(node: FlexContainer, styles: Dictionary) -> void:
# Flex direction
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
# 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"):
node.set_meta("custom_css_width", parse_size(styles["width"]))
if styles.has("height"):
node.set_meta("custom_css_height", parse_size(styles["height"]))
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)
# The parent FlexContainer must be notified to update its layout.
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("%"):
# 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

View File

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

4
Scripts/Tags/div.gd Normal file
View File

@@ -0,0 +1,4 @@
extends FlexContainer
func init(a):
pass

1
Scripts/Tags/div.gd.uid Normal file
View File

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

View File

@@ -3,5 +3,4 @@ extends Control
@onready var rich_text_label: RichTextLabel = $RichTextLabel
func init(element: HTMLParser.HTMLElement, parser: HTMLParser = null) -> void:
var label: RichTextLabel = $RichTextLabel
label.text = "[font_size=48][b]%s[/b][/font_size]" % element.get_bbcode_formatted_text(parser)
pass

View File

@@ -3,5 +3,4 @@ extends Control
@onready var rich_text_label: RichTextLabel = $RichTextLabel
func init(element: HTMLParser.HTMLElement, parser: HTMLParser = null) -> void:
var label: RichTextLabel = $RichTextLabel
label.text = "[font_size=36][b]%s[/b][/font_size]" % element.get_bbcode_formatted_text(parser)
pass

View File

@@ -3,5 +3,4 @@ extends Control
@onready var rich_text_label: RichTextLabel = $RichTextLabel
func init(element: HTMLParser.HTMLElement, parser: HTMLParser = null) -> void:
var label: RichTextLabel = $RichTextLabel
label.text = "[font_size=28][b]%s[/b][/font_size]" % element.get_bbcode_formatted_text(parser)
pass

View File

@@ -3,5 +3,4 @@ extends Control
@onready var rich_text_label: RichTextLabel = $RichTextLabel
func init(element: HTMLParser.HTMLElement, parser: HTMLParser = null) -> void:
var label: RichTextLabel = $RichTextLabel
label.text = "[font_size=24][b]%s[/b][/font_size]" % element.get_bbcode_formatted_text(parser)
pass

View File

@@ -3,5 +3,4 @@ extends Control
@onready var rich_text_label: RichTextLabel = $RichTextLabel
func init(element: HTMLParser.HTMLElement, parser: HTMLParser = null) -> void:
var label: RichTextLabel = $RichTextLabel
label.text = "[font_size=20][b]%s[/b][/font_size]" % element.get_bbcode_formatted_text(parser)
pass

View File

@@ -3,5 +3,4 @@ extends Control
@onready var rich_text_label: RichTextLabel = $RichTextLabel
func init(element: HTMLParser.HTMLElement, parser: HTMLParser = null) -> void:
var label: RichTextLabel = $RichTextLabel
label.text = "[font_size=16][b]%s[/b][/font_size]" % element.get_bbcode_formatted_text(parser)
pass

View File

@@ -1,5 +1,5 @@
class_name Pre
extends VBoxContainer
func init(element: HTMLParser.HTMLElement) -> void:
var label: RichTextLabel = $RichTextLabel
label.text = "[font_size=20][code]%s[/code][/font_size]" % element.get_preserved_text()
pass

View File

@@ -1,6 +1,16 @@
extends RichTextLabel
@onready var rich_text_label: RichTextLabel = self
@onready var background_rect: ColorRect = $BackgroundRect
func init(element: HTMLParser.HTMLElement, parser: HTMLParser = null) -> void:
text = "[font_size=24]%s[/font_size]" % element.get_bbcode_formatted_text(parser)
func _ready():
if has_meta("custom_css_background_color"):
add_background_rect()
func add_background_rect():
var color = get_meta("custom_css_background_color")
background_rect.color = color
background_rect.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)

View File

@@ -4,6 +4,7 @@ extends Control
@onready var website_container: Control = %WebsiteContainer
@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")
const P = preload("res://Scenes/Tags/p.tscn")
const IMG = preload("res://Scenes/Tags/img.tscn")
@@ -26,6 +27,7 @@ const LI = preload("res://Scenes/Tags/li.tscn")
const SELECT = preload("res://Scenes/Tags/select.tscn")
const OPTION = preload("res://Scenes/Tags/option.tscn")
const TEXTAREA = preload("res://Scenes/Tags/textarea.tscn")
const DIV = preload("res://Scenes/Tags/div.tscn")
const MIN_SIZE = Vector2i(750, 200)
@@ -34,182 +36,12 @@ func _ready():
ProjectSettings.set_setting("display/window/size/min_height", MIN_SIZE.y)
DisplayServer.window_set_min_size(MIN_SIZE)
func render():
func render() -> void:
# Clear existing content
for child in website_container.get_children():
child.queue_free()
var html_bytes = "<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\">
<meta name=\"theme-color\" content=\"#000000\">
<meta name=\"description\" content=\"My cool web\">
<style>
h1 { text-[#ff0000] font-italic hover:text-[#00ff00] }
p { text-[#333333] text-2xl }
</style>
<style src=\"styles.css\">
<script src=\"script.lua\" />
</head>
<body>
<h1>Header 1</h1>
<h2>Header 2</h2>
<h3>Header 3</h3>
<h4>Header 4</h4>
<h5>Header 5</h5>
<h6>Header 6</h6>
<p>Hey there! this is a test</p>
<b>This is bold</b>
<i>This is italic <mark>actually, and it's pretty <u>cool</u></mark></i>
<u>This is underline</u>
<small>this is small</small>
<mark>this is marked</mark>
<code>this is code<span> THIS IS A SPAN AND SHOULDNT BE ANY DIFFERENT</span></code>
<p>
<a href=\"https://youtube.com\">Hello gang</a>
</p>
<pre>
Text in a pre element
is displayed in a fixed-width
font, and it preserves
both spaces and
line breaks
</pre>
<p style=\"text-center w-32 h-32\">
So
</p>
<select style=\"text-center max-w-5 max-h-32\">
<option value=\"test1\">Test 1</option>
<option value=\"test2\" selected=\"true\">Test 2</option>
<option value=\"test3\">Test 3</option>
<option value=\"test4\" disabled=\"true\">Test 4</option>
<option value=\"test5\">Test 5</option>
</select>
<textarea />
<textarea cols=\"30\" />
<textarea rows=\"2\" />
<textarea maxlength=\"20\" />
<textarea readonly=\"true\">le skibidi le toilet</textarea>
<textarea disabled=\"true\" value=\"DISABLED\" />
<textarea placeholder=\"this is a placeholder...\" />
<!-- action, method, and type=submit are for when we implement Lua -->
<form action=\"/submit\" method=\"POST\">
<span>Name:</span>
<input type=\"text\" placeholder=\"First name\" value=\"John\" maxlength=\"20\" minlength=\"3\" />
<span>Email regex:</span>
<input type=\"text\" placeholder=\"Last name\" value=\"Doe\" pattern=\"^[^@\\s]+@[^@\\s]+\\.[^@\\s]+$\" />
<span>Smart:</span>
<input type=\"checkbox\" />
<input type=\"checkbox\" value=\"true\" />
<p>favorite food</p>
<input type=\"radio\" group=\"food\" />
<span>Pizza</span>
<input type=\"radio\" group=\"food\" />
<span>Berry</span>
<input type=\"radio\" group=\"food\" />
<span>Gary</span>
<h2>Color</h2>
<input type=\"color\" value=\"#ff0000\" />
<h2>Date</h2>
<input type=\"date\" value=\"2018-07-22\" />
<h2>Range Slider</h2>
<input style=\"max-w-2 max-h-2\" type=\"range\" min=\"0\" max=\"100\" step=\"5\" value=\"50\" />
<h2>Number Input</h2>
<input type=\"number\" min=\"1\" max=\"10\" step=\"0.5\" value=\"5\" placeholder=\"Enter number\" />
<h2>File Upload</h2>
<input type=\"file\" accept=\".txt,.pdf,image/*\" />
<input type=\"password\" placeholder=\"your password...\" />
<button type=\"submit\">Submit</button>
</form>
<separator direction=\"horizontal\" />
# Ordered list
<ol>
<li>hello gang</li>
<li>this</li>
<li>is</li>
</ol>
<ol type=\"zero-lead\">
<li>hello gang</li>
<li>this</li>
<li>is</li>
<li>a test</li>
</ol>
<ol type=\"lower-alpha\">
<li>hello gang</li>
<li>this</li>
<li>is</li>
<li>a test</li>
</ol>
<ol type=\"upper-alpha\">
<li>hello gang</li>
<li>this</li>
<li>is</li>
<li>a test</li>
</ol>
<ol type=\"lower-roman\">
<li>hello gang</li>
<li>this</li>
<li>is</li>
<li>a test</li>
</ol>
<ol type=\"upper-roman\">
<li>hello gang</li>
<li>this</li>
<li>is</li>
<li>a test</li>
</ol>
<ul>
<li>hello gang</li>
<li>this</li>
<li>is</li>
<li>a test</li>
</ul>
<ul type=\"circle\">
<li>hello gang</li>
<li>this</li>
<li>is</li>
<li>a test</li>
</ul>
<ul type=\"none\">
<li>hello gang</li>
<li>this</li>
<li>is</li>
<li>a test</li>
</ul>
<ul type=\"square\">
<li>hello gang</li>
<li>this</li>
<li>is</li>
<li>a test</li>
</ul>
<img style=\"text-center max-w-24 max-h-24\" src=\"https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQMNUPIKabszX0Js_c0kfa4cz_JQYKfGTuBUA&s\" />
<separator direction=\"vertical\" />
</body>".to_utf8_buffer()
var html_bytes = Constants.HTML_CONTENT
var parser: HTMLParser = HTMLParser.new(html_bytes)
var parse_result = parser.parse()
@@ -252,6 +84,8 @@ So
# Handle hyperlinks for all inline elements
if contains_hyperlink(inline_element) and inline_node.rich_text_label:
inline_node.rich_text_label.meta_clicked.connect(func(meta): OS.shell_open(str(meta)))
else:
print("Failed to create inline element node: ", inline_element.tag_name)
safe_add_child(website_container, hbox)
continue
@@ -270,171 +104,11 @@ So
i += 1
func safe_add_child(parent: Node, child: Node) -> void:
static func safe_add_child(parent: Node, child: Node) -> void:
if child.get_parent():
child.get_parent().remove_child(child)
parent.add_child(child)
func parse_size(val):
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("%"):
# Not supported directly, skip
return null
return float(val)
func apply_element_styles(node: Control, element: HTMLParser.HTMLElement, parser: HTMLParser) -> Control:
var styles = parser.get_element_styles(element)
var label = node if node is RichTextLabel else node.get_node_or_null("RichTextLabel")
var max_width = null
var max_height = null
var min_width = null
var min_height = null
var width = null
var height = null
# Handle width/height/min/max
if styles.has("width"):
width = parse_size(styles["width"])
if styles.has("height"):
height = parse_size(styles["height"])
if styles.has("min-width"):
min_width = parse_size(styles["min-width"])
if styles.has("min-height"):
min_height = parse_size(styles["min-height"])
if styles.has("max-width"):
max_width = parse_size(styles["max-width"])
if styles.has("max-height"):
max_height = parse_size(styles["max-height"])
# Apply min size
if min_width != null or min_height != null:
node.custom_minimum_size = Vector2(
min_width if min_width != null else node.custom_minimum_size.x,
min_height if min_height != null else node.custom_minimum_size.y
)
# Apply w/h size
if width != null or height != null:
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
)
# Set size flags to shrink (without center) so it doesn't expand beyond minimum
if width != null:
node.size_flags_horizontal = Control.SIZE_SHRINK_BEGIN
if height != null:
node.size_flags_vertical = Control.SIZE_SHRINK_BEGIN
if label and label != node: # If label is a child of node
label.anchors_preset = Control.PRESET_FULL_RECT
label.size_flags_horizontal = Control.SIZE_EXPAND_FILL
label.size_flags_vertical = Control.SIZE_EXPAND_FILL
# Apply max constraints via MaxSizeContainer
var result_node = node
if max_width != null or max_height != null:
var max_container = MaxSizeControl.new()
max_container.max_size = Vector2(
max_width if max_width != null else -1,
max_height if max_height != null else -1
)
safe_add_child(website_container, max_container)
result_node = max_container
if label:
apply_styles_to_label(label, styles, element, parser)
return result_node
func apply_styles_to_label(label: RichTextLabel, styles: Dictionary, element: HTMLParser.HTMLElement, parser) -> void:
var text = element.get_bbcode_formatted_text(parser) # pass parser
var font_size = 24 # default
print("applying styles to: ", text)
print("applying styles to label: ", label.text, " | styles: ")
for child in styles:
print(child)
# Apply font size
if styles.has("font-size"):
font_size = int(styles["font-size"])
# Apply color
var color_tag = ""
if styles.has("color"):
var color = styles["color"] as Color
color_tag = "[color=#%s]" % color.to_html(false)
# Apply background color
var bg_color_tag = ""
var bg_color_close = ""
if styles.has("background-color"):
var bg_color = styles["background-color"] as Color
bg_color_tag = "[bgcolor=#%s]" % bg_color.to_html(false)
bg_color_close = "[/bgcolor]"
# Apply bold
var bold_open = ""
var bold_close = ""
if styles.has("font-bold") and styles["font-bold"]:
bold_open = "[b]"
bold_close = "[/b]"
# Apply italic
var italic_open = ""
var italic_close = ""
if styles.has("font-italic") and styles["font-italic"]:
italic_open = "[i]"
italic_close = "[/i]"
# Apply underline
var underline_open = ""
var underline_close = ""
if styles.has("underline") and styles["underline"]:
underline_open = "[u]"
underline_close = "[/u]"
# Apply monospace font
var mono_open = ""
var mono_close = ""
if styles.has("font-mono") and styles["font-mono"]:
mono_open = "[code]"
mono_close = "[/code]"
if styles.has("text-align"):
match styles["text-align"]:
"left":
label.horizontal_alignment = HORIZONTAL_ALIGNMENT_LEFT
"center":
label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
"right":
label.horizontal_alignment = HORIZONTAL_ALIGNMENT_RIGHT
"justify":
label.horizontal_alignment = HORIZONTAL_ALIGNMENT_FILL
# Construct final text
var styled_text = "[font_size=%d]%s%s%s%s%s%s%s%s%s%s%s%s%s[/font_size]" % [
font_size,
bg_color_tag,
color_tag,
bold_open,
italic_open,
underline_open,
mono_open,
text,
mono_close,
underline_close,
italic_close,
bold_close,
"[/color]" if color_tag.length() > 0 else "",
bg_color_close
]
label.text = styled_text
func contains_hyperlink(element: HTMLParser.HTMLElement) -> bool:
if element.tag_name == "a":
return true
@@ -445,143 +119,133 @@ func contains_hyperlink(element: HTMLParser.HTMLElement) -> bool:
return false
func create_element_node(element: HTMLParser.HTMLElement, parser: HTMLParser = null) -> Control:
func create_element_node(element: HTMLParser.HTMLElement, parser: HTMLParser) -> Control:
var styles = parser.get_element_styles(element)
var is_flex_container = styles.has("display") and ("flex" in styles["display"])
var final_node: Control
var container_for_children: Node
# If this is an inline element AND not a flex container, do NOT recursively add child nodes for its children.
# Only create a node for the outermost inline group; nested inline tags are handled by BBCode.
if element.is_inline_element() and not is_flex_container:
final_node = await create_element_node_internal(element, parser)
if not final_node:
return null
final_node = StyleManager.apply_element_styles(final_node, element, parser)
# Flex item properties may still apply
StyleManager.apply_flex_item_properties(final_node, styles)
return final_node
if is_flex_container:
# The element's primary identity IS a flex container.
# We create it directly.
final_node = AUTO_SIZING_FLEX_CONTAINER.new()
final_node.name = "Flex_" + element.tag_name
container_for_children = final_node
# If the element itself has text (like <span style="flex">TEXT</span>)
if not element.text_content.is_empty():
var new_node = await create_element_node_internal(element, parser)
container_for_children.add_child(new_node)
else:
final_node = await create_element_node_internal(element, parser)
if not final_node:
return null # Unsupported tag
# Children will be added to this node.
container_for_children = final_node
# Applies background, size, etc. to the FlexContainer (top-level node)
final_node = StyleManager.apply_element_styles(final_node, element, parser)
# Apply flex CONTAINER properties if it's a flex container
if is_flex_container:
StyleManager.apply_flex_container_properties(final_node, styles)
# Apply flex ITEM properties
StyleManager.apply_flex_item_properties(final_node, styles)
# Add child elements (but NOT for ul/ol which handle their own children)
if element.tag_name != "ul" and element.tag_name != "ol":
for child_element in element.children:
# Only add child nodes if the child is NOT an inline element
# UNLESS the parent is a flex container (inline elements become flex items)
if not child_element.is_inline_element() or is_flex_container:
var child_node = await create_element_node(child_element, parser)
if child_node and is_instance_valid(container_for_children):
safe_add_child(container_for_children, child_node)
return final_node
func create_element_node_internal(element: HTMLParser.HTMLElement, parser: HTMLParser = null) -> Control:
var node: Control = null
match element.tag_name:
"p":
node = P.instantiate()
node.init(element)
if parser:
node = apply_element_styles(node, element, parser)
"h1":
node = H1.instantiate()
node.init(element)
if parser:
node = apply_element_styles(node, element, parser)
"h2":
node = H2.instantiate()
node.init(element)
if parser:
node = apply_element_styles(node, element, parser)
"h3":
node = H3.instantiate()
node.init(element)
if parser:
node = apply_element_styles(node, element, parser)
"h4":
node = H4.instantiate()
node.init(element)
if parser:
node = apply_element_styles(node, element, parser)
"h5":
node = H5.instantiate()
node.init(element)
"h6":
node = H6.instantiate()
node.init(element)
"pre":
node = PRE.instantiate()
node.init(element)
"h1", "h2", "h3", "h4", "h5", "h6":
match element.tag_name:
"h1": node = H1.instantiate()
"h2": node = H2.instantiate()
"h3": node = H3.instantiate()
"h4": node = H4.instantiate()
"h5": node = H5.instantiate()
"h6": node = H6.instantiate()
node.init(element)
"br":
node = BR.instantiate()
node.init(element)
"img":
node = IMG.instantiate()
node.init(element)
if parser:
node = apply_element_styles(node, element, parser)
"separator":
node = SEPARATOR.instantiate()
node.init(element)
"form":
node = FORM.instantiate()
node.init(element)
# Forms need to manually process their children
for child_element in element.children:
var child_node = await create_element_node(child_element)
if child_node:
node.add_child(child_node)
var child_node = await create_element_node(child_element, parser)
safe_add_child(node, child_node)
"input":
node = INPUT.instantiate()
node.init(element)
if parser:
node = apply_element_styles(node, element, parser)
"button":
node = BUTTON.instantiate()
node.init(element)
if parser:
node = apply_element_styles(node, element, parser)
"span":
"span", "b", "i", "u", "small", "mark", "code", "a":
node = SPAN.instantiate()
node.init(element)
if parser:
node = apply_element_styles(node, element, parser)
"b":
node = SPAN.instantiate()
node.init(element)
if parser:
node = apply_element_styles(node, element, parser)
"i":
node = SPAN.instantiate()
node.init(element)
if parser:
node = apply_element_styles(node, element, parser)
"u":
node = SPAN.instantiate()
node.init(element)
if parser:
node = apply_element_styles(node, element, parser)
"small":
node = SPAN.instantiate()
node.init(element)
if parser:
node = apply_element_styles(node, element, parser)
"mark":
node = SPAN.instantiate()
node.init(element)
if parser:
node = apply_element_styles(node, element, parser)
"code":
node = SPAN.instantiate()
node.init(element)
if parser:
node = apply_element_styles(node, element, parser)
"a":
node = SPAN.instantiate()
node.init(element)
if parser:
node = apply_element_styles(node, element, parser)
"ul":
node = UL.instantiate()
website_container.add_child(node) # Add to scene tree first
website_container.add_child(node)
await node.init(element)
return node # Return early since we already added it
return node
"ol":
node = OL.instantiate()
website_container.add_child(node) # Add to scene tree first
website_container.add_child(node)
await node.init(element)
return node # Return early since we already added it
return node
"li":
node = LI.instantiate()
node.init(element)
if parser:
node = apply_element_styles(node, element, parser)
"select":
node = SELECT.instantiate()
node.init(element)
if parser:
node = apply_element_styles(node, element, parser)
"option":
node = OPTION.instantiate()
node.init(element)
if parser:
node = apply_element_styles(node, element, parser)
"textarea":
node = TEXTAREA.instantiate()
node.init(element)
if parser:
node = apply_element_styles(node, element, parser)
"div":
node = DIV.instantiate()
node.init(element)
_:
return null