CSS selectors, input styling (border/bg), fix <a> styling removing link
This commit is contained in:
@@ -6,6 +6,8 @@ class CSSRule:
|
|||||||
var event_prefix: String = ""
|
var event_prefix: String = ""
|
||||||
var properties: Dictionary = {}
|
var properties: Dictionary = {}
|
||||||
var specificity: int = 0
|
var specificity: int = 0
|
||||||
|
var selector_type: String = "simple" # simple, descendant, child, adjacent_sibling, general_sibling, attribute
|
||||||
|
var selector_parts: Array = [] # For complex selectors
|
||||||
|
|
||||||
func init(sel: String = ""):
|
func init(sel: String = ""):
|
||||||
selector = sel
|
selector = sel
|
||||||
@@ -19,10 +21,51 @@ class CSSRule:
|
|||||||
selector = parts[0]
|
selector = parts[0]
|
||||||
event_prefix = parts[1]
|
event_prefix = parts[1]
|
||||||
|
|
||||||
|
# Parse complex selectors
|
||||||
|
if selector.contains(" > "):
|
||||||
|
selector_type = "child"
|
||||||
|
selector_parts = selector.split(" > ")
|
||||||
|
elif selector.contains(" + "):
|
||||||
|
selector_type = "adjacent_sibling"
|
||||||
|
selector_parts = selector.split(" + ")
|
||||||
|
elif selector.contains(" ~ "):
|
||||||
|
selector_type = "general_sibling"
|
||||||
|
selector_parts = selector.split(" ~ ")
|
||||||
|
elif selector.contains("["):
|
||||||
|
selector_type = "attribute"
|
||||||
|
parse_attribute_selector()
|
||||||
|
elif selector.contains(" "):
|
||||||
|
selector_type = "descendant"
|
||||||
|
selector_parts = selector.split(" ")
|
||||||
|
else:
|
||||||
|
selector_type = "simple"
|
||||||
|
selector_parts = [selector]
|
||||||
|
|
||||||
|
func parse_attribute_selector():
|
||||||
|
var bracket_start = selector.find("[")
|
||||||
|
var bracket_end = selector.find("]")
|
||||||
|
if bracket_start != -1 and bracket_end != -1:
|
||||||
|
var element_part = selector.substr(0, bracket_start)
|
||||||
|
var attribute_part = selector.substr(bracket_start + 1, bracket_end - bracket_start - 1)
|
||||||
|
selector_parts = [element_part, attribute_part]
|
||||||
|
|
||||||
func calculate_specificity():
|
func calculate_specificity():
|
||||||
specificity = 1
|
specificity = 1
|
||||||
if selector.begins_with("."):
|
if selector.begins_with("."):
|
||||||
specificity += 10 # Class selectors have higher specificity than tag selectors
|
specificity += 10
|
||||||
|
if selector.contains("["):
|
||||||
|
specificity += 10 # Attribute selectors
|
||||||
|
match selector_type:
|
||||||
|
"child":
|
||||||
|
specificity += 8
|
||||||
|
"adjacent_sibling":
|
||||||
|
specificity += 7
|
||||||
|
"attribute":
|
||||||
|
specificity += 6
|
||||||
|
"general_sibling":
|
||||||
|
specificity += 5
|
||||||
|
"descendant":
|
||||||
|
specificity += 4
|
||||||
if event_prefix.length() > 0:
|
if event_prefix.length() > 0:
|
||||||
specificity += 10
|
specificity += 10
|
||||||
|
|
||||||
@@ -32,13 +75,13 @@ class CSSStylesheet:
|
|||||||
func add_rule(rule: CSSRule):
|
func add_rule(rule: CSSRule):
|
||||||
rules.append(rule)
|
rules.append(rule)
|
||||||
|
|
||||||
func get_styles_for_element(tag_name: String, event: String = "", class_names: Array[String] = []) -> Dictionary:
|
func get_styles_for_element(tag_name: String, event: String = "", class_names: Array[String] = [], element: HTMLParser.HTMLElement = null) -> Dictionary:
|
||||||
var styles = {}
|
var styles = {}
|
||||||
|
|
||||||
# Sort rules by specificity
|
# Sort rules by specificity
|
||||||
var applicable_rules: Array[CSSRule] = []
|
var applicable_rules: Array[CSSRule] = []
|
||||||
for rule in rules:
|
for rule in rules:
|
||||||
if selector_matches(rule, tag_name, event, class_names):
|
if selector_matches(rule, tag_name, event, class_names, element):
|
||||||
applicable_rules.append(rule)
|
applicable_rules.append(rule)
|
||||||
|
|
||||||
applicable_rules.sort_custom(func(a, b): return a.specificity < b.specificity)
|
applicable_rules.sort_custom(func(a, b): return a.specificity < b.specificity)
|
||||||
@@ -50,21 +93,175 @@ class CSSStylesheet:
|
|||||||
|
|
||||||
return styles
|
return styles
|
||||||
|
|
||||||
func selector_matches(rule: CSSRule, tag_name: String, event: String = "", cls_names: Array[String] = []) -> bool:
|
func selector_matches(rule: CSSRule, tag_name: String, event: String = "", cls_names: Array[String] = [], element: HTMLParser.HTMLElement = null) -> bool:
|
||||||
# Handle class selectors
|
|
||||||
if rule.selector.begins_with("."):
|
|
||||||
var cls = rule.selector.substr(1) # Remove the "." prefix
|
|
||||||
if not cls in cls_names:
|
|
||||||
return false
|
|
||||||
else:
|
|
||||||
# Handle tag selectors
|
|
||||||
if rule.selector != tag_name:
|
|
||||||
return false
|
|
||||||
|
|
||||||
if rule.event_prefix.length() > 0:
|
if rule.event_prefix.length() > 0:
|
||||||
return rule.event_prefix == event
|
if rule.event_prefix != event:
|
||||||
|
return false
|
||||||
|
elif event.length() > 0:
|
||||||
|
return false
|
||||||
|
|
||||||
return event.length() == 0
|
match rule.selector_type:
|
||||||
|
"simple":
|
||||||
|
return matches_simple_selector(rule.selector_parts[0], tag_name, cls_names)
|
||||||
|
"descendant":
|
||||||
|
return matches_descendant_selector(rule.selector_parts, element)
|
||||||
|
"child":
|
||||||
|
return matches_child_selector(rule.selector_parts, element)
|
||||||
|
"adjacent_sibling":
|
||||||
|
return matches_adjacent_sibling_selector(rule.selector_parts, element)
|
||||||
|
"general_sibling":
|
||||||
|
return matches_general_sibling_selector(rule.selector_parts, element)
|
||||||
|
"attribute":
|
||||||
|
return matches_attribute_selector(rule.selector_parts, tag_name, cls_names, element)
|
||||||
|
|
||||||
|
return false
|
||||||
|
|
||||||
|
func matches_simple_selector(selector: String, tag_name: String, cls_names: Array[String]) -> bool:
|
||||||
|
if selector.begins_with("."):
|
||||||
|
var cls = selector.substr(1)
|
||||||
|
return cls in cls_names
|
||||||
|
else:
|
||||||
|
return selector == tag_name
|
||||||
|
|
||||||
|
func matches_descendant_selector(parts: Array, element: HTMLParser.HTMLElement) -> bool:
|
||||||
|
if not element or parts.size() < 2:
|
||||||
|
return false
|
||||||
|
|
||||||
|
# Last part should match current element
|
||||||
|
var last_part = parts[-1].strip_edges()
|
||||||
|
if not matches_simple_selector(last_part, element.tag_name, get_element_class_names(element)):
|
||||||
|
return false
|
||||||
|
|
||||||
|
# Check ancestors for remaining parts
|
||||||
|
var current_element = element.parent
|
||||||
|
var part_index = parts.size() - 2
|
||||||
|
|
||||||
|
while current_element and part_index >= 0:
|
||||||
|
var part = parts[part_index].strip_edges()
|
||||||
|
if matches_simple_selector(part, current_element.tag_name, get_element_class_names(current_element)):
|
||||||
|
part_index -= 1
|
||||||
|
if part_index < 0:
|
||||||
|
return true
|
||||||
|
current_element = current_element.parent
|
||||||
|
|
||||||
|
return false
|
||||||
|
|
||||||
|
func matches_child_selector(parts: Array, element: HTMLParser.HTMLElement) -> bool:
|
||||||
|
if not element or not element.parent or parts.size() != 2:
|
||||||
|
return false
|
||||||
|
|
||||||
|
var child_part = parts[1].strip_edges()
|
||||||
|
var parent_part = parts[0].strip_edges()
|
||||||
|
|
||||||
|
# Element must match the child part
|
||||||
|
if not matches_simple_selector(child_part, element.tag_name, get_element_class_names(element)):
|
||||||
|
return false
|
||||||
|
|
||||||
|
# Parent must match the parent part
|
||||||
|
return matches_simple_selector(parent_part, element.parent.tag_name, get_element_class_names(element.parent))
|
||||||
|
|
||||||
|
func matches_adjacent_sibling_selector(parts: Array, element: HTMLParser.HTMLElement) -> bool:
|
||||||
|
if not element or not element.parent or parts.size() != 2:
|
||||||
|
return false
|
||||||
|
|
||||||
|
var second_part = parts[1].strip_edges()
|
||||||
|
var first_part = parts[0].strip_edges()
|
||||||
|
|
||||||
|
if not matches_simple_selector(second_part, element.tag_name, get_element_class_names(element)):
|
||||||
|
return false
|
||||||
|
|
||||||
|
# Find previous sibling
|
||||||
|
var siblings = element.parent.children
|
||||||
|
var element_index = siblings.find(element)
|
||||||
|
if element_index <= 0:
|
||||||
|
return false
|
||||||
|
|
||||||
|
var prev_sibling = siblings[element_index - 1]
|
||||||
|
return matches_simple_selector(first_part, prev_sibling.tag_name, get_element_class_names(prev_sibling))
|
||||||
|
|
||||||
|
func matches_general_sibling_selector(parts: Array, element: HTMLParser.HTMLElement) -> bool:
|
||||||
|
if not element or not element.parent or parts.size() != 2:
|
||||||
|
return false
|
||||||
|
|
||||||
|
var second_part = parts[1].strip_edges()
|
||||||
|
var first_part = parts[0].strip_edges()
|
||||||
|
|
||||||
|
if not matches_simple_selector(second_part, element.tag_name, get_element_class_names(element)):
|
||||||
|
return false
|
||||||
|
|
||||||
|
# Check all previous siblings
|
||||||
|
var siblings = element.parent.children
|
||||||
|
var element_index = siblings.find(element)
|
||||||
|
|
||||||
|
for i in range(element_index):
|
||||||
|
var sibling = siblings[i]
|
||||||
|
if matches_simple_selector(first_part, sibling.tag_name, get_element_class_names(sibling)):
|
||||||
|
return true
|
||||||
|
|
||||||
|
return false
|
||||||
|
|
||||||
|
func matches_attribute_selector(parts: Array, tag_name: String, cls_names: Array[String], element: HTMLParser.HTMLElement) -> bool:
|
||||||
|
if not element or parts.size() != 2:
|
||||||
|
return false
|
||||||
|
|
||||||
|
var element_part = parts[0].strip_edges()
|
||||||
|
var attribute_part = parts[1].strip_edges()
|
||||||
|
|
||||||
|
# Check if element matches
|
||||||
|
if element_part != "" and not matches_simple_selector(element_part, tag_name, cls_names):
|
||||||
|
return false
|
||||||
|
|
||||||
|
# Parse attribute condition
|
||||||
|
if attribute_part.contains("="):
|
||||||
|
var parsed = {}
|
||||||
|
var element_value = ""
|
||||||
|
|
||||||
|
if attribute_part.contains("^="):
|
||||||
|
# Starts with
|
||||||
|
parsed = parse_attribute_value(attribute_part, "^=")
|
||||||
|
element_value = element.get_attribute(parsed.name)
|
||||||
|
return element_value.begins_with(parsed.value)
|
||||||
|
elif attribute_part.contains("$="):
|
||||||
|
# Ends with
|
||||||
|
parsed = parse_attribute_value(attribute_part, "$=")
|
||||||
|
element_value = element.get_attribute(parsed.name)
|
||||||
|
return element_value.ends_with(parsed.value)
|
||||||
|
elif attribute_part.contains("*="):
|
||||||
|
# Contains
|
||||||
|
parsed = parse_attribute_value(attribute_part, "*=")
|
||||||
|
element_value = element.get_attribute(parsed.name)
|
||||||
|
return element_value.contains(parsed.value)
|
||||||
|
else:
|
||||||
|
# Exact match
|
||||||
|
parsed = parse_attribute_value(attribute_part, "=")
|
||||||
|
return element.get_attribute(parsed.name) == parsed.value
|
||||||
|
else:
|
||||||
|
# Just check if attribute exists
|
||||||
|
return element.has_attribute(attribute_part)
|
||||||
|
|
||||||
|
func parse_attribute_value(attribute_part: String, operator: String) -> Dictionary:
|
||||||
|
var attr_parts = attribute_part.split(operator)
|
||||||
|
var attr_name = attr_parts[0].strip_edges()
|
||||||
|
var attr_value = attr_parts[1].strip_edges()
|
||||||
|
|
||||||
|
# Remove quotes
|
||||||
|
if attr_value.begins_with('"') and attr_value.ends_with('"'):
|
||||||
|
attr_value = attr_value.substr(1, attr_value.length() - 2)
|
||||||
|
elif attr_value.begins_with("'") and attr_value.ends_with("'"):
|
||||||
|
attr_value = attr_value.substr(1, attr_value.length() - 2)
|
||||||
|
|
||||||
|
return {"name": attr_name, "value": attr_value}
|
||||||
|
|
||||||
|
func get_element_class_names(element: HTMLParser.HTMLElement) -> Array[String]:
|
||||||
|
var class_names: Array[String] = []
|
||||||
|
var class_attr = element.get_attribute("class")
|
||||||
|
if class_attr.length() > 0:
|
||||||
|
var classes = class_attr.split(" ")
|
||||||
|
for cls in classes:
|
||||||
|
cls = cls.strip_edges()
|
||||||
|
if cls.length() > 0:
|
||||||
|
class_names.append(cls)
|
||||||
|
return class_names
|
||||||
|
|
||||||
var stylesheet: CSSStylesheet
|
var stylesheet: CSSStylesheet
|
||||||
var css_text: String
|
var css_text: String
|
||||||
|
|||||||
@@ -35,7 +35,10 @@ class HTMLElement:
|
|||||||
return text_content
|
return text_content
|
||||||
|
|
||||||
func get_bbcode_formatted_text(parser: HTMLParser = null) -> String:
|
func get_bbcode_formatted_text(parser: HTMLParser = null) -> String:
|
||||||
return HTMLParser.get_bbcode_with_styles(self, {}, parser) # Pass empty dict for default
|
var styles = {}
|
||||||
|
if parser != null:
|
||||||
|
styles = parser.get_element_styles_with_inheritance(self, "", [])
|
||||||
|
return HTMLParser.get_bbcode_with_styles(self, styles, parser)
|
||||||
|
|
||||||
func is_inline_element() -> bool:
|
func is_inline_element() -> bool:
|
||||||
return tag_name in ["b", "i", "u", "small", "mark", "code", "span", "a", "input"]
|
return tag_name in ["b", "i", "u", "small", "mark", "code", "span", "a", "input"]
|
||||||
@@ -132,8 +135,8 @@ func get_element_styles_with_inheritance(element: HTMLElement, event: String = "
|
|||||||
|
|
||||||
var styles = {}
|
var styles = {}
|
||||||
|
|
||||||
var class_names = extract_class_names_from_style(element)
|
var class_names = get_css_class_names(element)
|
||||||
styles.merge(parse_result.css_parser.stylesheet.get_styles_for_element(element.tag_name, event, class_names))
|
styles.merge(parse_result.css_parser.stylesheet.get_styles_for_element(element.tag_name, event, class_names, element))
|
||||||
# Apply inline styles (higher priority) - force override CSS rules
|
# Apply inline styles (higher priority) - force override CSS rules
|
||||||
var inline_style = element.get_attribute("style")
|
var inline_style = element.get_attribute("style")
|
||||||
if inline_style.length() > 0:
|
if inline_style.length() > 0:
|
||||||
@@ -159,8 +162,8 @@ func get_element_styles_internal(element: HTMLElement, event: String = "") -> Di
|
|||||||
|
|
||||||
# Apply CSS rules
|
# Apply CSS rules
|
||||||
if parse_result.css_parser:
|
if parse_result.css_parser:
|
||||||
var class_names = extract_class_names_from_style(element)
|
var class_names = get_css_class_names(element)
|
||||||
styles.merge(parse_result.css_parser.stylesheet.get_styles_for_element(element.tag_name, event, class_names))
|
styles.merge(parse_result.css_parser.stylesheet.get_styles_for_element(element.tag_name, event, class_names, element))
|
||||||
|
|
||||||
# Apply inline styles (higher priority) - force override CSS rules
|
# Apply inline styles (higher priority) - force override CSS rules
|
||||||
var inline_style = element.get_attribute("style")
|
var inline_style = element.get_attribute("style")
|
||||||
@@ -199,6 +202,17 @@ func parse_inline_style_with_event(style_string: String, event: String = "") ->
|
|||||||
|
|
||||||
return properties
|
return properties
|
||||||
|
|
||||||
|
func get_css_class_names(element: HTMLElement) -> Array[String]:
|
||||||
|
var class_names: Array[String] = []
|
||||||
|
var class_attr = element.get_attribute("class")
|
||||||
|
if class_attr.length() > 0:
|
||||||
|
var classes = class_attr.split(" ")
|
||||||
|
for cls in classes:
|
||||||
|
cls = cls.strip_edges()
|
||||||
|
if cls.length() > 0:
|
||||||
|
class_names.append(cls)
|
||||||
|
return class_names
|
||||||
|
|
||||||
func extract_class_names_from_style(element: HTMLElement) -> Array[String]:
|
func extract_class_names_from_style(element: HTMLElement) -> Array[String]:
|
||||||
var class_names: Array[String] = []
|
var class_names: Array[String] = []
|
||||||
var style_attr = element.get_attribute("style")
|
var style_attr = element.get_attribute("style")
|
||||||
@@ -291,7 +305,7 @@ func get_title() -> String:
|
|||||||
|
|
||||||
func get_icon() -> String:
|
func get_icon() -> String:
|
||||||
var icon_element = find_first("icon")
|
var icon_element = find_first("icon")
|
||||||
return icon_element.get_attribute("src")
|
return icon_element.get_attribute("src") if icon_element != null else ""
|
||||||
|
|
||||||
func process_fonts() -> void:
|
func process_fonts() -> void:
|
||||||
var font_elements = find_all("font")
|
var font_elements = find_all("font")
|
||||||
@@ -330,6 +344,48 @@ func apply_element_styles(node: Control, element: HTMLElement, parser: HTMLParse
|
|||||||
var text = HTMLParser.get_bbcode_with_styles(element, styles, parser)
|
var text = HTMLParser.get_bbcode_with_styles(element, styles, parser)
|
||||||
label.text = text
|
label.text = text
|
||||||
|
|
||||||
|
static func apply_element_bbcode_formatting(element: HTMLElement, styles: Dictionary, content: String) -> String:
|
||||||
|
match element.tag_name:
|
||||||
|
"b":
|
||||||
|
if styles.has("font-bold") and styles["font-bold"]:
|
||||||
|
return "[b]" + content + "[/b]"
|
||||||
|
"i":
|
||||||
|
if styles.has("font-italic") and styles["font-italic"]:
|
||||||
|
return "[i]" + content + "[/i]"
|
||||||
|
"u":
|
||||||
|
if styles.has("underline") and styles["underline"]:
|
||||||
|
return "[u]" + content + "[/u]"
|
||||||
|
"small":
|
||||||
|
if styles.has("font-size"):
|
||||||
|
return "[font_size=%d]%s[/font_size]" % [styles["font-size"], content]
|
||||||
|
else:
|
||||||
|
return "[font_size=20]%s[/font_size]" % content
|
||||||
|
"mark":
|
||||||
|
if styles.has("bg"):
|
||||||
|
var color = styles["bg"]
|
||||||
|
if typeof(color) == TYPE_COLOR:
|
||||||
|
color = color.to_html(false)
|
||||||
|
return "[bgcolor=#%s]%s[/bgcolor]" % [color, content]
|
||||||
|
else:
|
||||||
|
return "[bgcolor=#FFFF00]%s[/bgcolor]" % content
|
||||||
|
"code":
|
||||||
|
if styles.has("font-size"):
|
||||||
|
return "[font_size=%d][code]%s[/code][/font_size]" % [styles["font-size"], content]
|
||||||
|
else:
|
||||||
|
return "[font_size=20][code]%s[/code][/font_size]" % content
|
||||||
|
"a":
|
||||||
|
var href = element.get_attribute("href")
|
||||||
|
var color = "#1a0dab"
|
||||||
|
if styles.has("color"):
|
||||||
|
var c = styles["color"]
|
||||||
|
if typeof(c) == TYPE_COLOR:
|
||||||
|
color = "#" + c.to_html(false)
|
||||||
|
else:
|
||||||
|
color = str(c)
|
||||||
|
if href.length() > 0:
|
||||||
|
return "[color=%s][url=%s]%s[/url][/color]" % [color, href, content]
|
||||||
|
return content
|
||||||
|
|
||||||
static func get_bbcode_with_styles(element: HTMLElement, styles: Dictionary, parser: HTMLParser) -> String:
|
static func get_bbcode_with_styles(element: HTMLElement, styles: Dictionary, parser: HTMLParser) -> String:
|
||||||
var text = ""
|
var text = ""
|
||||||
if element.text_content.length() > 0:
|
if element.text_content.length() > 0:
|
||||||
@@ -340,46 +396,10 @@ static func get_bbcode_with_styles(element: HTMLElement, styles: Dictionary, par
|
|||||||
if parser != null:
|
if parser != null:
|
||||||
child_styles = parser.get_element_styles_with_inheritance(child, "", [])
|
child_styles = parser.get_element_styles_with_inheritance(child, "", [])
|
||||||
var child_content = HTMLParser.get_bbcode_with_styles(child, child_styles, parser)
|
var child_content = HTMLParser.get_bbcode_with_styles(child, child_styles, parser)
|
||||||
match child.tag_name:
|
child_content = apply_element_bbcode_formatting(child, child_styles, child_content)
|
||||||
"b":
|
|
||||||
if child_styles.has("font-bold") and child_styles["font-bold"]:
|
|
||||||
child_content = "[b]" + child_content + "[/b]"
|
|
||||||
"i":
|
|
||||||
if child_styles.has("font-italic") and child_styles["font-italic"]:
|
|
||||||
child_content = "[i]" + child_content + "[/i]"
|
|
||||||
"u":
|
|
||||||
if child_styles.has("underline") and child_styles["underline"]:
|
|
||||||
child_content = "[u]" + child_content + "[/u]"
|
|
||||||
"small":
|
|
||||||
if child_styles.has("font-size"):
|
|
||||||
child_content = "[font_size=%d]%s[/font_size]" % [child_styles["font-size"], child_content]
|
|
||||||
else:
|
|
||||||
child_content = "[font_size=20]%s[/font_size]" % child_content
|
|
||||||
"mark":
|
|
||||||
if child_styles.has("bg"):
|
|
||||||
var color = child_styles["bg"]
|
|
||||||
if typeof(color) == TYPE_COLOR:
|
|
||||||
color = color.to_html(false)
|
|
||||||
child_content = "[bgcolor=#%s]%s[/bgcolor]" % [color, child_content]
|
|
||||||
else:
|
|
||||||
child_content = "[bgcolor=#FFFF00]%s[/bgcolor]" % child_content
|
|
||||||
"code":
|
|
||||||
if child_styles.has("font-size"):
|
|
||||||
child_content = "[font_size=%d][code]%s[/code][/font_size]" % [child_styles["font-size"], child_content]
|
|
||||||
else:
|
|
||||||
child_content = "[font_size=20][code]%s[/code][/font_size]" % child_content
|
|
||||||
"a":
|
|
||||||
var href = child.get_attribute("href")
|
|
||||||
var color = "#1a0dab"
|
|
||||||
if child_styles.has("color"):
|
|
||||||
var c = child_styles["color"]
|
|
||||||
if typeof(c) == TYPE_COLOR:
|
|
||||||
color = "#" + c.to_html(false)
|
|
||||||
else:
|
|
||||||
color = str(c)
|
|
||||||
if href.length() > 0:
|
|
||||||
child_content = "[color=%s][url=%s]%s[/url][/color]" % [color, href, child_content]
|
|
||||||
_:
|
|
||||||
pass
|
|
||||||
text += child_content
|
text += child_content
|
||||||
|
|
||||||
|
# Apply formatting to the current element itself
|
||||||
|
text = apply_element_bbcode_formatting(element, styles, text)
|
||||||
|
|
||||||
return text
|
return text
|
||||||
|
|||||||
@@ -99,7 +99,7 @@ var HTML_CONTENT2 = """<head>
|
|||||||
|
|
||||||
</body>
|
</body>
|
||||||
""".to_utf8_buffer()
|
""".to_utf8_buffer()
|
||||||
var HTML_CONTENT = """<head>
|
var HTML_CONTENT4 = """<head>
|
||||||
<title>My cool web</title>
|
<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\">
|
<icon src=\"https://upload.wikimedia.org/wikipedia/commons/thumb/c/c1/Google_%22G%22_logo.svg/768px-Google_%22G%22_logo.svg.png\">
|
||||||
|
|
||||||
@@ -342,6 +342,84 @@ line breaks
|
|||||||
</div>
|
</div>
|
||||||
</body>""".to_utf8_buffer()
|
</body>""".to_utf8_buffer()
|
||||||
|
|
||||||
|
var HTML_CONTENT = """<head>
|
||||||
|
<title>CSS Selector Tests</title>
|
||||||
|
<style>
|
||||||
|
/* Descendant selectors */
|
||||||
|
div p { text-[#663399] }
|
||||||
|
.container span { bg-[#ffeeaa] }
|
||||||
|
|
||||||
|
/* Direct child selectors */
|
||||||
|
.outer-div > p { font-bold }
|
||||||
|
.parent > button { bg-[#44cc88] }
|
||||||
|
|
||||||
|
/* Adjacent sibling selectors */
|
||||||
|
h1 + p { text-[#ff0000] font-bold }
|
||||||
|
h2 + div { bg-[#eeffee] }
|
||||||
|
|
||||||
|
/* General sibling selectors */
|
||||||
|
h1 ~ p { text-[#0000ff] }
|
||||||
|
h1 ~ .second-p { text-[#0000ff] }
|
||||||
|
h3 ~ span { bg-[#ffdddd] }
|
||||||
|
|
||||||
|
/* Attribute selectors */
|
||||||
|
input[type="text"] { border border-[#cccccc] bg-[#f9f9f9] }
|
||||||
|
a[href^="https"] { text-[#008000] font-bold }
|
||||||
|
button[disabled] { bg-[#888888] text-[#cccccc] }
|
||||||
|
input[placeholder*="email"] { border-2 border-[#0066cc] bg-[#ffffff] }
|
||||||
|
div[class$="special"] { bg-[#ffffaa] }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<h1>CSS Selector Test Page</h1>
|
||||||
|
<p>This paragraph should be red and bold (h1 + p)</p>
|
||||||
|
<p class="second-p">This paragraph should be blue (h1 ~ p)</p>
|
||||||
|
|
||||||
|
<h2>Descendant vs Child Selectors</h2>
|
||||||
|
<div class="outer-div">
|
||||||
|
<p>This paragraph should be purple and bold (div p and .outer-div > p)</p>
|
||||||
|
<div>
|
||||||
|
<p>This paragraph should be purple but not bold (div p only)</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3>Attribute Selectors</h3>
|
||||||
|
<input type="text" placeholder="Enter your name" />
|
||||||
|
<input type="text" placeholder="Enter your email address" />
|
||||||
|
<input type="password" placeholder="Enter password" />
|
||||||
|
|
||||||
|
<br />
|
||||||
|
<a href="http://example.com">HTTP Link (normal)</a>
|
||||||
|
<br />
|
||||||
|
<a href="https://secure.com">HTTPS Link (green and bold)</a>
|
||||||
|
|
||||||
|
<br />
|
||||||
|
<button>Normal Button</button>
|
||||||
|
<button disabled="true">Disabled Button (gray)</button>
|
||||||
|
|
||||||
|
<h3>Sibling Selectors</h3>
|
||||||
|
<div style="bg-[#eeffee]">This div should have light green bg (h2 + div)</div>
|
||||||
|
<span>This span should have light red bg (h3 ~ span)</span>
|
||||||
|
<span>This span should also have light red bg (h3 ~ span)</span>
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
<span>This span should have yellow bg (.container span)</span>
|
||||||
|
<p>Regular paragraph in container</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="parent">
|
||||||
|
<button>This button should be green (.parent > button)</button>
|
||||||
|
<div>
|
||||||
|
<button>This button should be normal (not direct child)</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="item-special">This div should have yellow bg (class ends with 'special')</div>
|
||||||
|
<div class="special-item">This div should be normal</div>
|
||||||
|
</body>
|
||||||
|
""".to_utf8_buffer()
|
||||||
|
|
||||||
var HTML_CONTENT3 = """<head>
|
var HTML_CONTENT3 = """<head>
|
||||||
<title>Task Manager</title>
|
<title>Task Manager</title>
|
||||||
<icon src="https://cdn-icons-png.flaticon.com/512/126/126472.png">
|
<icon src="https://cdn-icons-png.flaticon.com/512/126/126472.png">
|
||||||
|
|||||||
@@ -32,6 +32,9 @@ static func apply_element_styles(node: Control, element: HTMLParser.HTMLElement,
|
|||||||
if button_node:
|
if button_node:
|
||||||
target = button_node
|
target = button_node
|
||||||
|
|
||||||
|
if element.tag_name == "input":
|
||||||
|
apply_input_border_styles(node, styles)
|
||||||
|
|
||||||
# Unified font applying for label and button
|
# Unified font applying for label and button
|
||||||
if target and styles.has("font-family") and styles["font-family"] not in ["sans-serif", "serif", "monospace"]:
|
if target and styles.has("font-family") and styles["font-family"] not in ["sans-serif", "serif", "monospace"]:
|
||||||
var main_node = Engine.get_main_loop().current_scene
|
var main_node = Engine.get_main_loop().current_scene
|
||||||
@@ -145,34 +148,34 @@ static func apply_styles_to_label(label: Control, styles: Dictionary, element: H
|
|||||||
# Apply font size
|
# Apply font size
|
||||||
if styles.has("font-size"):
|
if styles.has("font-size"):
|
||||||
font_size = int(styles["font-size"])
|
font_size = int(styles["font-size"])
|
||||||
|
var has_existing_bbcode = text.contains("[url=") or text.contains("[color=")
|
||||||
|
|
||||||
# Apply color
|
# Apply color
|
||||||
var color_tag = ""
|
var color_tag = ""
|
||||||
if styles.has("color"):
|
if not has_existing_bbcode and styles.has("color"):
|
||||||
var color = styles["color"] as Color
|
var color = styles["color"] as Color
|
||||||
|
|
||||||
if color == Color.BLACK and StyleManager.body_text_color != Color.BLACK:
|
if color == Color.BLACK and StyleManager.body_text_color != Color.BLACK:
|
||||||
color = StyleManager.body_text_color
|
color = StyleManager.body_text_color
|
||||||
color_tag = "[color=#%s]" % color.to_html(false)
|
color_tag = "[color=#%s]" % color.to_html(false)
|
||||||
else:
|
elif not has_existing_bbcode and StyleManager.body_text_color != Color.BLACK:
|
||||||
if StyleManager.body_text_color != Color.BLACK:
|
|
||||||
color_tag = "[color=#%s]" % StyleManager.body_text_color.to_html(false)
|
color_tag = "[color=#%s]" % StyleManager.body_text_color.to_html(false)
|
||||||
|
|
||||||
# Apply bold
|
# Apply text styling (but not for text with existing BBCode)
|
||||||
var bold_open = ""
|
var bold_open = ""
|
||||||
var bold_close = ""
|
var bold_close = ""
|
||||||
if styles.has("font-bold") and styles["font-bold"]:
|
if not has_existing_bbcode and styles.has("font-bold") and styles["font-bold"]:
|
||||||
bold_open = "[b]"
|
bold_open = "[b]"
|
||||||
bold_close = "[/b]"
|
bold_close = "[/b]"
|
||||||
# Apply italic
|
|
||||||
var italic_open = ""
|
var italic_open = ""
|
||||||
var italic_close = ""
|
var italic_close = ""
|
||||||
if styles.has("font-italic") and styles["font-italic"]:
|
if not has_existing_bbcode and styles.has("font-italic") and styles["font-italic"]:
|
||||||
italic_open = "[i]"
|
italic_open = "[i]"
|
||||||
italic_close = "[/i]"
|
italic_close = "[/i]"
|
||||||
# Apply underline
|
|
||||||
var underline_open = ""
|
var underline_open = ""
|
||||||
var underline_close = ""
|
var underline_close = ""
|
||||||
if styles.has("underline") and styles["underline"]:
|
if not has_existing_bbcode and styles.has("underline") and styles["underline"]:
|
||||||
underline_open = "[u]"
|
underline_open = "[u]"
|
||||||
underline_close = "[/u]"
|
underline_close = "[/u]"
|
||||||
# Apply monospace font
|
# Apply monospace font
|
||||||
@@ -295,3 +298,45 @@ static func apply_font_to_button(button: Button, styles: Dictionary) -> void:
|
|||||||
|
|
||||||
if font_resource:
|
if font_resource:
|
||||||
button.add_theme_font_override("font", font_resource)
|
button.add_theme_font_override("font", font_resource)
|
||||||
|
|
||||||
|
static func apply_input_border_styles(input_node: Control, styles: Dictionary) -> void:
|
||||||
|
if not BackgroundUtils.needs_background_wrapper(styles):
|
||||||
|
return
|
||||||
|
|
||||||
|
# Find the appropriate input control to style
|
||||||
|
var styleable_controls = []
|
||||||
|
|
||||||
|
# Get all potential input controls that support StyleBox
|
||||||
|
var line_edit = input_node.get_node_or_null("LineEdit")
|
||||||
|
var spinbox = input_node.get_node_or_null("SpinBox")
|
||||||
|
var file_container = input_node.get_node_or_null("FileContainer")
|
||||||
|
|
||||||
|
if line_edit: styleable_controls.append(line_edit)
|
||||||
|
if spinbox: styleable_controls.append(spinbox)
|
||||||
|
if file_container:
|
||||||
|
var file_button = file_container.get_node_or_null("FileButton")
|
||||||
|
if file_button: styleable_controls.append(file_button)
|
||||||
|
|
||||||
|
# Apply styles using BackgroundUtils
|
||||||
|
for control in styleable_controls:
|
||||||
|
var style_box = BackgroundUtils.create_stylebox_from_styles(styles)
|
||||||
|
|
||||||
|
# Set appropriate content margins for inputs if not specified
|
||||||
|
if not styles.has("padding") and not styles.has("padding-left"):
|
||||||
|
style_box.content_margin_left = 5.0
|
||||||
|
if not styles.has("padding") and not styles.has("padding-right"):
|
||||||
|
style_box.content_margin_right = 5.0
|
||||||
|
if not styles.has("padding") and not styles.has("padding-top"):
|
||||||
|
style_box.content_margin_top = 2.0
|
||||||
|
if not styles.has("padding") and not styles.has("padding-bottom"):
|
||||||
|
style_box.content_margin_bottom = 2.0
|
||||||
|
|
||||||
|
# Apply the style to the appropriate states
|
||||||
|
if control is LineEdit:
|
||||||
|
control.add_theme_stylebox_override("normal", style_box)
|
||||||
|
control.add_theme_stylebox_override("focus", style_box)
|
||||||
|
elif control is SpinBox:
|
||||||
|
control.add_theme_stylebox_override("normal", style_box)
|
||||||
|
control.add_theme_stylebox_override("focus", style_box)
|
||||||
|
elif control is Button:
|
||||||
|
control.add_theme_stylebox_override("normal", style_box)
|
||||||
|
|||||||
@@ -34,6 +34,19 @@ const MIN_SIZE = Vector2i(750, 200)
|
|||||||
|
|
||||||
var font_dependent_elements: Array = []
|
var font_dependent_elements: Array = []
|
||||||
|
|
||||||
|
func should_group_as_inline(element: HTMLParser.HTMLElement) -> bool:
|
||||||
|
# Don't group inputs unless they're inside a form
|
||||||
|
if element.tag_name == "input":
|
||||||
|
# Check if this element has a form ancestor
|
||||||
|
var parent = element.parent
|
||||||
|
while parent:
|
||||||
|
if parent.tag_name == "form":
|
||||||
|
return true
|
||||||
|
parent = parent.parent
|
||||||
|
return false
|
||||||
|
|
||||||
|
return element.is_inline_element()
|
||||||
|
|
||||||
func _ready():
|
func _ready():
|
||||||
ProjectSettings.set_setting("display/window/size/min_width", MIN_SIZE.x)
|
ProjectSettings.set_setting("display/window/size/min_width", MIN_SIZE.x)
|
||||||
ProjectSettings.set_setting("display/window/size/min_height", MIN_SIZE.y)
|
ProjectSettings.set_setting("display/window/size/min_height", MIN_SIZE.y)
|
||||||
@@ -79,11 +92,11 @@ func render() -> void:
|
|||||||
while i < body.children.size():
|
while i < body.children.size():
|
||||||
var element: HTMLParser.HTMLElement = body.children[i]
|
var element: HTMLParser.HTMLElement = body.children[i]
|
||||||
|
|
||||||
if element.is_inline_element():
|
if should_group_as_inline(element):
|
||||||
# Create an HBoxContainer for consecutive inline elements
|
# Create an HBoxContainer for consecutive inline elements
|
||||||
var inline_elements: Array[HTMLParser.HTMLElement] = []
|
var inline_elements: Array[HTMLParser.HTMLElement] = []
|
||||||
|
|
||||||
while i < body.children.size() and body.children[i].is_inline_element():
|
while i < body.children.size() and should_group_as_inline(body.children[i]):
|
||||||
inline_elements.append(body.children[i])
|
inline_elements.append(body.children[i])
|
||||||
i += 1
|
i += 1
|
||||||
|
|
||||||
@@ -95,8 +108,8 @@ func render() -> void:
|
|||||||
if inline_node:
|
if inline_node:
|
||||||
safe_add_child(hbox, inline_node)
|
safe_add_child(hbox, inline_node)
|
||||||
# Handle hyperlinks for all inline elements
|
# Handle hyperlinks for all inline elements
|
||||||
if contains_hyperlink(inline_element) and inline_node.rich_text_label:
|
if contains_hyperlink(inline_element) and inline_node is RichTextLabel:
|
||||||
inline_node.rich_text_label.meta_clicked.connect(func(meta): OS.shell_open(str(meta)))
|
inline_node.meta_clicked.connect(func(meta): OS.shell_open(str(meta)))
|
||||||
else:
|
else:
|
||||||
print("Failed to create inline element node: ", inline_element.tag_name)
|
print("Failed to create inline element node: ", inline_element.tag_name)
|
||||||
|
|
||||||
@@ -110,7 +123,10 @@ func render() -> void:
|
|||||||
safe_add_child(website_container, element_node)
|
safe_add_child(website_container, element_node)
|
||||||
|
|
||||||
# Handle hyperlinks for all elements
|
# Handle hyperlinks for all elements
|
||||||
if contains_hyperlink(element) and element_node.has_method("get") and element_node.get("rich_text_label"):
|
if contains_hyperlink(element):
|
||||||
|
if element_node is RichTextLabel:
|
||||||
|
element_node.meta_clicked.connect(func(meta): OS.shell_open(str(meta)))
|
||||||
|
elif 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)))
|
element_node.rich_text_label.meta_clicked.connect(func(meta): OS.shell_open(str(meta)))
|
||||||
else:
|
else:
|
||||||
print("Couldn't parse unsupported HTML tag \"%s\"" % element.tag_name)
|
print("Couldn't parse unsupported HTML tag \"%s\"" % element.tag_name)
|
||||||
|
|||||||
Reference in New Issue
Block a user