2025-08-04 16:31:16 +03:00
|
|
|
class_name BaseListContainer
|
|
|
|
|
extends VBoxContainer
|
|
|
|
|
|
|
|
|
|
const BROWSER_TEXT = preload("res://Scenes/Styles/BrowserText.tres")
|
|
|
|
|
|
|
|
|
|
var list_type: String
|
|
|
|
|
var marker_width: float
|
|
|
|
|
var parser_ref: HTMLParser = null
|
|
|
|
|
var is_ordered: bool = false
|
|
|
|
|
|
|
|
|
|
func _ready():
|
|
|
|
|
child_entered_tree.connect(_on_child_added)
|
|
|
|
|
child_exiting_tree.connect(_on_child_removed)
|
|
|
|
|
|
2025-08-07 14:05:41 +03:00
|
|
|
func init(element: HTMLParser.HTMLElement, parser: HTMLParser) -> void:
|
2025-08-04 16:31:16 +03:00
|
|
|
list_type = element.get_attribute("type").to_lower()
|
|
|
|
|
if list_type == "":
|
|
|
|
|
list_type = "disc" if not is_ordered else "decimal"
|
|
|
|
|
parser_ref = parser
|
|
|
|
|
|
|
|
|
|
marker_width = await calculate_marker_width(element)
|
|
|
|
|
|
|
|
|
|
var index = 1
|
|
|
|
|
for child_element in element.children:
|
|
|
|
|
if child_element.tag_name == "li":
|
|
|
|
|
var li_node = create_li_node(child_element, index, parser)
|
|
|
|
|
if li_node:
|
|
|
|
|
add_child(li_node)
|
|
|
|
|
index += 1
|
|
|
|
|
|
|
|
|
|
func calculate_marker_width(element: HTMLParser.HTMLElement) -> 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 sample_text = ""
|
|
|
|
|
if is_ordered:
|
|
|
|
|
var item_count = 0
|
|
|
|
|
for child_element in element.children:
|
|
|
|
|
if child_element.tag_name == "li":
|
|
|
|
|
item_count += 1
|
|
|
|
|
sample_text = str(item_count) + "."
|
|
|
|
|
else:
|
|
|
|
|
match list_type:
|
|
|
|
|
"circle":
|
|
|
|
|
sample_text = "◦"
|
|
|
|
|
"disc":
|
|
|
|
|
sample_text = "•"
|
|
|
|
|
"square":
|
|
|
|
|
sample_text = "■"
|
|
|
|
|
"none":
|
|
|
|
|
sample_text = " "
|
|
|
|
|
_:
|
|
|
|
|
sample_text = "•"
|
|
|
|
|
|
|
|
|
|
StyleManager.apply_styles_to_label(temp_label, {}, null, null, sample_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, 20.0 if not is_ordered else 30.0)
|
|
|
|
|
|
2025-08-07 14:05:41 +03:00
|
|
|
func create_li_node(element: HTMLParser.HTMLElement, index: int, parser: HTMLParser) -> Control:
|
2025-08-04 16:31:16 +03:00
|
|
|
var li_container = HBoxContainer.new()
|
|
|
|
|
|
|
|
|
|
# Create 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.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
|
|
|
|
marker_label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
|
|
|
|
|
marker_label.theme = BROWSER_TEXT
|
|
|
|
|
|
|
|
|
|
var marker_text = get_marker_text(index)
|
|
|
|
|
var marker_styles = parser.get_element_styles_with_inheritance(element, "", []) if parser else {}
|
|
|
|
|
StyleManager.apply_styles_to_label(marker_label, marker_styles, element, parser, 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
|
|
|
|
|
|
|
|
|
|
var content_text = element.get_bbcode_formatted_text(parser)
|
|
|
|
|
var content_styles = parser.get_element_styles_with_inheritance(element, "", []) if parser else {}
|
|
|
|
|
StyleManager.apply_styles_to_label(content_label, content_styles, element, parser, content_text)
|
|
|
|
|
|
|
|
|
|
li_container.add_theme_constant_override("separation", 0)
|
|
|
|
|
li_container.add_child(marker_label)
|
|
|
|
|
li_container.add_child(content_label)
|
|
|
|
|
|
|
|
|
|
# Store element metadata on the container for renumbering
|
|
|
|
|
li_container.set_meta("html_element", element)
|
|
|
|
|
|
|
|
|
|
var styles = parser.get_element_styles_with_inheritance(element, "", [])
|
|
|
|
|
if BackgroundUtils.needs_background_wrapper(styles):
|
|
|
|
|
var panel_container = BackgroundUtils.create_panel_container_with_background(styles)
|
|
|
|
|
panel_container.name = "Li"
|
|
|
|
|
# Store element metadata on the panel container too
|
|
|
|
|
panel_container.set_meta("html_element", element)
|
|
|
|
|
# Get the VBoxContainer inside PanelContainer and replace it with our HBoxContainer
|
|
|
|
|
var vbox = panel_container.get_child(0)
|
|
|
|
|
panel_container.remove_child(vbox)
|
|
|
|
|
vbox.queue_free()
|
|
|
|
|
panel_container.add_child(li_container)
|
|
|
|
|
return panel_container
|
|
|
|
|
else:
|
|
|
|
|
return li_container
|
|
|
|
|
|
|
|
|
|
func _on_child_added(child: Node):
|
|
|
|
|
if child.has_meta("html_element"):
|
|
|
|
|
var element = child.get_meta("html_element")
|
|
|
|
|
if element is HTMLParser.HTMLElement and element.tag_name == "li":
|
|
|
|
|
|
|
|
|
|
call_deferred("_process_dynamic_li", child, element)
|
|
|
|
|
|
|
|
|
|
func _process_dynamic_li(child: Node, element: HTMLParser.HTMLElement):
|
|
|
|
|
child_entered_tree.disconnect(_on_child_added)
|
|
|
|
|
|
|
|
|
|
# Get the correct index for this new item
|
|
|
|
|
var current_li_count = 0
|
|
|
|
|
for existing_child in get_children():
|
|
|
|
|
if existing_child != child:
|
|
|
|
|
current_li_count += 1
|
|
|
|
|
|
|
|
|
|
# Remove the basic li node and replace with properly formatted one
|
|
|
|
|
if child.get_parent() == self:
|
|
|
|
|
remove_child(child)
|
|
|
|
|
var li_node = create_li_node(element, current_li_count + 1, parser_ref)
|
|
|
|
|
if li_node:
|
|
|
|
|
var element_id = element.get_attribute("id")
|
|
|
|
|
if parser_ref and element_id:
|
|
|
|
|
parser_ref.parse_result.dom_nodes[element_id] = li_node
|
|
|
|
|
add_child(li_node)
|
|
|
|
|
child.queue_free()
|
|
|
|
|
|
|
|
|
|
# Reconnect signal
|
|
|
|
|
child_entered_tree.connect(_on_child_added)
|
|
|
|
|
|
|
|
|
|
func _on_child_removed(_child: Node):
|
|
|
|
|
if is_ordered: # Only OL needs renumbering
|
|
|
|
|
call_deferred("_renumber_list")
|
|
|
|
|
|
|
|
|
|
func _renumber_list():
|
|
|
|
|
# Temporarily disconnect signals to avoid recursion
|
|
|
|
|
child_entered_tree.disconnect(_on_child_added)
|
|
|
|
|
child_exiting_tree.disconnect(_on_child_removed)
|
|
|
|
|
|
|
|
|
|
# Get all current li children
|
|
|
|
|
var li_children = []
|
|
|
|
|
for child in get_children():
|
|
|
|
|
var is_li = false
|
|
|
|
|
if child is HBoxContainer:
|
|
|
|
|
is_li = true
|
|
|
|
|
elif child is PanelContainer and child.get_child_count() > 0:
|
|
|
|
|
var inner_child = child.get_child(0)
|
|
|
|
|
if inner_child is HBoxContainer:
|
|
|
|
|
is_li = true
|
|
|
|
|
|
|
|
|
|
if is_li:
|
|
|
|
|
li_children.append(child)
|
|
|
|
|
|
|
|
|
|
# Renumber all existing items
|
|
|
|
|
for i in range(li_children.size()):
|
|
|
|
|
var child = li_children[i]
|
|
|
|
|
var marker_label = null
|
|
|
|
|
|
|
|
|
|
# Find the marker label within the child structure
|
|
|
|
|
if child is HBoxContainer and child.get_child_count() > 0:
|
|
|
|
|
marker_label = child.get_child(0)
|
|
|
|
|
elif child is PanelContainer and child.get_child_count() > 0:
|
|
|
|
|
var hbox = child.get_child(0)
|
|
|
|
|
if hbox is HBoxContainer and hbox.get_child_count() > 0:
|
|
|
|
|
marker_label = hbox.get_child(0)
|
|
|
|
|
|
|
|
|
|
# Update the marker text - recreate it completely to avoid BBCode corruption
|
|
|
|
|
if marker_label and marker_label is RichTextLabel:
|
|
|
|
|
var index = i + 1
|
|
|
|
|
var new_marker_text = get_marker_text(index)
|
|
|
|
|
# Get the HTMLElement from the li container to reapply styles properly
|
|
|
|
|
var element = null
|
|
|
|
|
if child.has_meta("html_element"):
|
|
|
|
|
element = child.get_meta("html_element")
|
|
|
|
|
elif child is PanelContainer and child.get_child_count() > 0:
|
|
|
|
|
var hbox = child.get_child(0)
|
|
|
|
|
if hbox.has_meta("html_element"):
|
|
|
|
|
element = hbox.get_meta("html_element")
|
|
|
|
|
|
|
|
|
|
if element and parser_ref:
|
|
|
|
|
var marker_styles = parser_ref.get_element_styles_with_inheritance(element, "", [])
|
|
|
|
|
StyleManager.apply_styles_to_label(marker_label, marker_styles, element, parser_ref, new_marker_text)
|
|
|
|
|
else:
|
|
|
|
|
# Fallback - just set the text
|
|
|
|
|
marker_label.text = new_marker_text
|
|
|
|
|
|
|
|
|
|
# Reconnect signals
|
|
|
|
|
child_entered_tree.connect(_on_child_added)
|
|
|
|
|
child_exiting_tree.connect(_on_child_removed)
|
|
|
|
|
|
|
|
|
|
func refresh_list():
|
|
|
|
|
# Force refresh of all li children for dynamically added content
|
|
|
|
|
var children_to_process = []
|
|
|
|
|
for child in get_children():
|
|
|
|
|
if child.has_meta("html_element"):
|
|
|
|
|
var element = child.get_meta("html_element")
|
|
|
|
|
if element is HTMLParser.HTMLElement and element.tag_name == "li":
|
|
|
|
|
children_to_process.append([child, element])
|
|
|
|
|
|
|
|
|
|
# Clear all children first
|
|
|
|
|
for child_data in children_to_process:
|
|
|
|
|
var child = child_data[0]
|
|
|
|
|
remove_child(child)
|
|
|
|
|
child.queue_free()
|
|
|
|
|
|
|
|
|
|
# Recalculate marker width if needed
|
|
|
|
|
var new_count = children_to_process.size()
|
|
|
|
|
if new_count > 0 and is_ordered:
|
|
|
|
|
marker_width = await calculate_marker_width(children_to_process[0][1])
|
|
|
|
|
|
|
|
|
|
# Re-add with correct indices
|
|
|
|
|
for i in range(children_to_process.size()):
|
|
|
|
|
var element = children_to_process[i][1]
|
|
|
|
|
var li_node = create_li_node(element, i + 1, parser_ref)
|
|
|
|
|
if li_node:
|
|
|
|
|
add_child(li_node)
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
|
|
|
|
func get_marker_text(index: int) -> String:
|
|
|
|
|
if is_ordered:
|
|
|
|
|
match list_type:
|
|
|
|
|
"decimal":
|
|
|
|
|
return str(index) + "."
|
|
|
|
|
"zero-lead":
|
|
|
|
|
return "%02d." % index
|
|
|
|
|
"lower-alpha":
|
|
|
|
|
return char(96 + index) + "."
|
|
|
|
|
"lower-roman":
|
|
|
|
|
return int_to_roman(index).to_lower() + "."
|
|
|
|
|
"upper-alpha":
|
|
|
|
|
return char(64 + index) + "."
|
|
|
|
|
"upper-roman":
|
|
|
|
|
return int_to_roman(index) + "."
|
|
|
|
|
"none":
|
|
|
|
|
return ""
|
|
|
|
|
_:
|
|
|
|
|
return str(index) + "."
|
|
|
|
|
else:
|
|
|
|
|
match list_type:
|
|
|
|
|
"circle":
|
|
|
|
|
return "◦"
|
|
|
|
|
"disc":
|
|
|
|
|
return "•"
|
|
|
|
|
"square":
|
|
|
|
|
return "■"
|
|
|
|
|
"none":
|
|
|
|
|
return " "
|
|
|
|
|
_:
|
2025-08-04 17:04:35 +03:00
|
|
|
return "•"
|