<ul>, <ol>, <li>

This commit is contained in:
Face
2025-07-25 15:46:18 +03:00
parent a8f77e1bd4
commit 1435ec6f94
11 changed files with 467 additions and 5 deletions

6
Scripts/Tags/li.gd Normal file
View File

@@ -0,0 +1,6 @@
extends Control
func init(element: HTMLParser.HTMLElement) -> void:
# This is mainly for cases where <li> appears outside of <ul>/<ol>
var label: RichTextLabel = $RichTextLabel
label.text = "[font_size=24]%s[/font_size]" % element.get_bbcode_formatted_text()

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

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

101
Scripts/Tags/ol.gd Normal file
View File

@@ -0,0 +1,101 @@
extends VBoxContainer
const BROWSER_TEXT = preload("res://Scenes/Styles/BrowserText.tres")
func init(element: HTMLParser.HTMLElement) -> void:
var list_type = element.get_attribute("type").to_lower()
if list_type == "": list_type = "decimal" # Default
var item_count = 0
for child_element in element.children:
if child_element.tag_name == "li":
item_count += 1
var marker_min_width = await calculate_marker_width(list_type, item_count)
var index = 1
for child_element in element.children:
if child_element.tag_name == "li":
var li_node = create_li_node(child_element, list_type, index, marker_min_width)
if li_node:
add_child(li_node)
index += 1
func calculate_marker_width(list_type: String, max_index: int) -> float:
var temp_label = RichTextLabel.new()
temp_label.bbcode_enabled = true
temp_label.fit_content = true
temp_label.scroll_active = false
temp_label.theme = BROWSER_TEXT
add_child(temp_label)
var marker_text = get_marker_for_type(list_type, max_index)
temp_label.text = "[font_size=24]%s[/font_size]" % marker_text
await get_tree().process_frame
var width = temp_label.get_content_width() + 5
remove_child(temp_label)
temp_label.queue_free()
return max(width, 30) # Minimum pixels
func create_li_node(element: HTMLParser.HTMLElement, list_type: String, index: int, marker_width: float = 30) -> Control:
var li_container = HBoxContainer.new()
# Create number/letter marker
var marker_label = RichTextLabel.new()
marker_label.custom_minimum_size = Vector2(marker_width, 0)
marker_label.size_flags_horizontal = Control.SIZE_SHRINK_CENTER
marker_label.size_flags_vertical = Control.SIZE_SHRINK_CENTER
marker_label.bbcode_enabled = true
marker_label.fit_content = true
marker_label.scroll_active = false
marker_label.theme = BROWSER_TEXT
var marker_text = get_marker_for_type(list_type, index)
marker_label.text = "[font_size=24]%s[/font_size]" % marker_text
# Create content
var content_label = RichTextLabel.new()
content_label.size_flags_horizontal = Control.SIZE_EXPAND_FILL
content_label.bbcode_enabled = true
content_label.fit_content = true
content_label.scroll_active = false
content_label.theme = BROWSER_TEXT
content_label.text = "[font_size=24]%s[/font_size]" % element.get_bbcode_formatted_text()
li_container.add_theme_constant_override("separation", 0)
li_container.add_child(marker_label)
li_container.add_child(content_label)
return li_container
func get_marker_for_type(list_type: String, index: int) -> String:
match list_type:
"decimal":
return str(index) + "."
"zero-lead":
return "%02d." % index
"lower-alpha", "lower-roman":
return char(96 + index) + "." if list_type == "lower-alpha" else int_to_roman(index).to_lower() + "."
"upper-alpha", "upper-roman":
return char(64 + index) + "." if list_type == "upper-alpha" else int_to_roman(index) + "."
"none":
return ""
_:
return str(index) + "." # Default to decimal
func int_to_roman(num: int) -> String:
var values = [1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1]
var symbols = ["M", "CM", "D", "CD", "C", "XC", "L", "XL", "X", "IX", "V", "IV", "I"]
var result = ""
for i in range(values.size()):
while num >= values[i]:
result += symbols[i]
num -= values[i]
return result

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

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

81
Scripts/Tags/ul.gd Normal file
View File

@@ -0,0 +1,81 @@
extends VBoxContainer
const BROWSER_TEXT = preload("res://Scenes/Styles/BrowserText.tres")
func init(element: HTMLParser.HTMLElement) -> void:
var list_type = element.get_attribute("type").to_lower()
if list_type == "": list_type = "disc" # Default
var marker_min_width = await calculate_marker_width(list_type)
for child_element in element.children:
if child_element.tag_name == "li":
var li_node = create_li_node(child_element, list_type, marker_min_width)
if li_node:
add_child(li_node)
func calculate_marker_width(list_type: String) -> float:
var temp_label = RichTextLabel.new()
temp_label.bbcode_enabled = true
temp_label.fit_content = true
temp_label.scroll_active = false
temp_label.theme = BROWSER_TEXT
add_child(temp_label)
var bullet_text = get_bullet_for_type(list_type)
temp_label.text = "[font_size=24]%s[/font_size]" % bullet_text
await get_tree().process_frame
var width = temp_label.get_content_width() + 5 # padding
remove_child(temp_label)
temp_label.queue_free()
return max(width, 20) # Minimum pixels
func create_li_node(element: HTMLParser.HTMLElement, list_type: String, marker_width: float = 20) -> Control:
var li_container = HBoxContainer.new()
# Create bullet point
var bullet_label = RichTextLabel.new()
bullet_label.custom_minimum_size = Vector2(marker_width, 0)
bullet_label.size_flags_horizontal = Control.SIZE_SHRINK_CENTER
bullet_label.size_flags_vertical = Control.SIZE_SHRINK_CENTER
bullet_label.bbcode_enabled = true
bullet_label.fit_content = true
bullet_label.scroll_active = false
bullet_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
bullet_label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
bullet_label.theme = BROWSER_TEXT
var bullet_text = get_bullet_for_type(list_type)
bullet_label.text = "[font_size=24]%s[/font_size]" % bullet_text
# Create content
var content_label = RichTextLabel.new()
content_label.size_flags_horizontal = Control.SIZE_EXPAND_FILL
content_label.bbcode_enabled = true
content_label.fit_content = true
content_label.theme = BROWSER_TEXT
content_label.scroll_active = false
content_label.text = "[font_size=24]%s[/font_size]" % element.get_bbcode_formatted_text()
li_container.add_theme_constant_override("separation", 0)
li_container.add_child(bullet_label)
li_container.add_child(content_label)
return li_container
func get_bullet_for_type(list_type: String) -> String:
match list_type:
"circle":
return ""
"disc":
return ""
"square":
return ""
"none":
return " "
_:
return "" # Default to disc

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

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

View File

@@ -20,6 +20,9 @@ const H6 = preload("res://Scenes/Tags/h6.tscn")
const FORM = preload("res://Scenes/Tags/form.tscn")
const INPUT = preload("res://Scenes/Tags/input.tscn")
const BUTTON = preload("res://Scenes/Tags/button.tscn")
const UL = preload("res://Scenes/Tags/ul.tscn")
const OL = preload("res://Scenes/Tags/ol.tscn")
const LI = preload("res://Scenes/Tags/li.tscn")
const MIN_SIZE = Vector2i(750, 200)
@@ -106,11 +109,220 @@ line breaks
<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>
<li>a test</li>
<li>a test</li>
<li>a test</li>
<li>a test</li>
<li>a test</li>
<li>a test</li>
<li>a test</li>
<li>a test</li>
<li>a test</li>
<li>a test</li>
<li>a test</li>
<li>a test</li>
<li>a test</li>
<li>a test</li>
<li>a test</li>
<li>a test</li>
<li>a test</li>
<li>a test</li>
<li>a test</li>
<li>a test</li>
<li>a test</li>
<li>a test</li>
<li>a test</li>
<li>a test</li>
<li>a test</li>
<li>a test</li>
<li>a test</li>
<li>a test</li>
<li>a test</li>
<li>a test</li>
<li>a test</li>
<li>a test</li>
<li>a test</li>
<li>a test</li>
<li>a test</li>
<li>a test</li>
<li>a test</li>
<li>a test</li>
<li>a test</li>
<li>a test</li>
<li>a test</li>
<li>a test</li>
<li>a test</li>
<li>a test</li>
<li>a test</li>
<li>a test</li>
<li>a test</li>
<li>a test</li>
<li>a test</li>
<li>a test</li>
<li>a test</li>
<li>a test</li>
<li>a test</li>
<li>a test</li>
<li>a test</li>
<li>a test</li>
<li>a test</li>
<li>a test</li>
<li>a test</li>
<li>a test</li>
<li>a test</li>
<li>a test</li>
<li>a test</li>
<li>a test</li>
<li>a test</li>
<li>a test</li>
<li>a test</li>
<li>a test</li>
<li>a test</li>
<li>a test</li>
<li>a test</li>
<li>a test</li>
<li>a test</li>
<li>a test</li>
<li>a test</li>
<li>a test</li>
<li>a test</li>
<li>a test</li>
<li>a test</li>
<li>a test</li>
<li>a test</li>
<li>a test</li>
<li>a test</li>
<li>a test</li>
<li>a test</li>
<li>a test</li>
<li>a test</li>
<li>a test</li>
<li>a test</li>
<li>a test</li>
<li>a test</li>
<li>a test</li>
<li>a test</li>
<li>a test</li>
<li>a test</li>
<li>a test</li>
<li>a test</li>
<li>a test</li>
<li>a test</li>
<li>a test</li>
<li>a test</li>
<li>a test</li>
<li>a test</li>
<li>a test</li>
<li>a test</li>
<li>a test</li>
<li>a test</li>
<li>a test</li>
<li>a test</li>
<li>a test</li>
<li>a test</li>
<li>a test</li>
<li>a test</li>
<li>a test</li>
<li>a test</li>
<li>a test</li>
<li>a test</li>
<li>a test</li>
<li>a test</li>
<li>a test</li>
<li>a test</li>
<li>a test</li>
<li>a test</li>
<li>a test</li>
<li>a test</li>
<li>a test</li>
<li>a test</li>
<li>a test</li>
<li>a test</li>
<li>a test</li>
<li>a test</li>
<li>a test</li>
<li>a test</li>
<li>a test</li>
<li>a test</li>
<li>a test</li>
<li>a test</li>
<li>a test</li>
<li>a test</li>
<li>a test</li>
<li>a test</li>
<li>a test</li>
<li>a test</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 src=\"https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQMNUPIKabszX0Js_c0kfa4cz_JQYKfGTuBUA&s\" />
<separator direction=\"vertical\" />
</body>".to_utf8_buffer()
@@ -160,9 +372,12 @@ line breaks
continue
var element_node = create_element_node(element)
var element_node = await create_element_node(element)
if element_node:
website_container.add_child(element_node)
# ul/ol handle their own adding
if element.tag_name != "ul" and element.tag_name != "ol":
website_container.add_child(element_node)
# Handle hyperlinks for all elements
if contains_hyperlink(element) and element_node.has_method("get") and element_node.get("rich_text_label"):
element_node.rich_text_label.meta_clicked.connect(func(meta): OS.shell_open(str(meta)))
@@ -223,7 +438,7 @@ func create_element_node(element: HTMLParser.HTMLElement) -> Control:
node.init(element)
for child_element in element.children:
var child_node = create_element_node(child_element)
var child_node = await create_element_node(child_element)
if child_node:
node.add_child(child_node)
"input":
@@ -235,6 +450,19 @@ func create_element_node(element: HTMLParser.HTMLElement) -> Control:
"span":
node = SPAN.instantiate()
node.init(element)
"ul":
node = UL.instantiate()
website_container.add_child(node) # Add to scene tree first
await node.init(element)
return node # Return early since we already added it
"ol":
node = OL.instantiate()
website_container.add_child(node) # Add to scene tree first
await node.init(element)
return node # Return early since we already added it
"li":
node = LI.instantiate()
node.init(element)
_:
return null