flexbox, code cleanup
This commit is contained in:
131
Scripts/AutoSizingFlexContainer.gd
Normal file
131
Scripts/AutoSizingFlexContainer.gd
Normal 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")
|
||||
1
Scripts/AutoSizingFlexContainer.gd.uid
Normal file
1
Scripts/AutoSizingFlexContainer.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://feiw2baeu5ye
|
||||
@@ -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
|
||||
|
||||
@@ -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():
|
||||
|
||||
@@ -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
231
Scripts/StyleManager.gd
Normal 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
|
||||
1
Scripts/StyleManager.gd.uid
Normal file
1
Scripts/StyleManager.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://dg2rhkcabusn1
|
||||
4
Scripts/Tags/div.gd
Normal file
4
Scripts/Tags/div.gd
Normal file
@@ -0,0 +1,4 @@
|
||||
extends FlexContainer
|
||||
|
||||
func init(a):
|
||||
pass
|
||||
1
Scripts/Tags/div.gd.uid
Normal file
1
Scripts/Tags/div.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://ckks1ccehq6al
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
508
Scripts/main.gd
508
Scripts/main.gd
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user