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

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