add class selectors

This commit is contained in:
Face
2025-07-30 19:34:10 +03:00
parent 49802fdcf6
commit af8afd6e5b
4 changed files with 68 additions and 11 deletions

View File

@@ -1,7 +1,10 @@
gurted (verb)
> “to do something smart, but also dangerous”
Sir facedev you should use the appropriate feature for this (issues) instead of using the readme, it's not professional.
wayfinder (noun)
> “a person helping others navigate”
In traditional web, you might be familiar with the term "browser." A **wayfinder** is that, but for the **GURT** protocol.
TODO:
1. Write a new **selection system**. Godot's built-in `RichTextLabel` selection is limited by the node's boundaries. In normal web, holding click and dragging your mouse across the screen will select text across multiple nodes. Godot doesn't have a "set_selected_text" property, despite having one for "get_selected_text".

View File

@@ -21,6 +21,8 @@ class CSSRule:
func calculate_specificity():
specificity = 1
if selector.begins_with("."):
specificity += 10 # Class selectors have higher specificity than tag selectors
if event_prefix.length() > 0:
specificity += 10
@@ -30,13 +32,13 @@ class CSSStylesheet:
func add_rule(rule: CSSRule):
rules.append(rule)
func get_styles_for_element(tag_name: String, event: String = "") -> Dictionary:
func get_styles_for_element(tag_name: String, event: String = "", class_names: Array[String] = []) -> Dictionary:
var styles = {}
# Sort rules by specificity
var applicable_rules: Array[CSSRule] = []
for rule in rules:
if selector_matches(rule, tag_name, event):
if selector_matches(rule, tag_name, event, class_names):
applicable_rules.append(rule)
applicable_rules.sort_custom(func(a, b): return a.specificity < b.specificity)
@@ -48,9 +50,16 @@ class CSSStylesheet:
return styles
func selector_matches(rule: CSSRule, tag_name: String, event: String = "") -> bool:
if rule.selector != tag_name:
return false
func selector_matches(rule: CSSRule, tag_name: String, event: String = "", cls_names: Array[String] = []) -> 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:
return rule.event_prefix == event
@@ -556,6 +565,39 @@ static func parse_inline_style(style_string: String) -> Dictionary:
return rule.properties
# TODO: probably a better idea to precompile the patterns, though idk how much more efficient that would be, if at all
static func is_utility_class(cls: String) -> bool:
var utility_patterns = [
"^text-(xs|sm|base|lg|xl|2xl|3xl|4xl|5xl|6xl)$", # font sizes
"^text-(left|center|right|justify)$", # text alignment
"^text-\\[.*\\]$", # custom text colors
"^text-(white|black|transparent|slate-\\d+|gray-\\d+|red-\\d+|green-\\d+|blue-\\d+|yellow-\\d+)$", # text colors
"^bg-\\[.*\\]$", # custom bg colors
"^bg-(white|black|transparent|slate-\\d+|gray-\\d+|red-\\d+|green-\\d+|blue-\\d+|yellow-\\d+)$", # bg colors
"^(w|h|min-w|min-h|max-w|max-h)-", # sizing
"^font-(bold|mono|italic)$", # font styles
"^underline$",
"^flex", # flex utilities
"^items-", # align items
"^justify-", # justify content
"^content-", # align content
"^self-", # align self
"^order-", # order
"^gap-", # gap
"^(p|px|py|pt|pr|pb|pl)-", # padding
"^rounded", # border radius
"^basis-", # flex basis
"^(hover|active):", # pseudo classes
]
for pattern in utility_patterns:
var regex = RegEx.new()
regex.compile(pattern)
if regex.search(cls):
return true
return false
static func get_color(color_name: String) -> Color:
# Common colors
match color_name:

View File

@@ -132,8 +132,8 @@ func get_element_styles_with_inheritance(element: HTMLElement, event: String = "
var styles = {}
styles.merge(parse_result.css_parser.stylesheet.get_styles_for_element(element.tag_name, event))
var class_names = extract_class_names_from_style(element)
styles.merge(parse_result.css_parser.stylesheet.get_styles_for_element(element.tag_name, event, class_names))
# Apply inline styles (higher priority) - force override CSS rules
var inline_style = element.get_attribute("style")
if inline_style.length() > 0:
@@ -159,7 +159,8 @@ func get_element_styles_internal(element: HTMLElement, event: String = "") -> Di
# Apply CSS rules
if parse_result.css_parser:
styles.merge(parse_result.css_parser.stylesheet.get_styles_for_element(element.tag_name, event))
var class_names = extract_class_names_from_style(element)
styles.merge(parse_result.css_parser.stylesheet.get_styles_for_element(element.tag_name, event, class_names))
# Apply inline styles (higher priority) - force override CSS rules
var inline_style = element.get_attribute("style")
@@ -198,6 +199,17 @@ func parse_inline_style_with_event(style_string: String, event: String = "") ->
return properties
func extract_class_names_from_style(element: HTMLElement) -> Array[String]:
var class_names: Array[String] = []
var style_attr = element.get_attribute("style")
if style_attr.length() > 0:
var style_tokens = style_attr.split(" ")
for token in style_tokens:
token = token.strip_edges()
if token.length() > 0 and not CSSParser.is_utility_class(token):
class_names.append(token)
return class_names
# Creates element from CURRENT xml parser node
func create_element() -> HTMLElement:
var element = HTMLElement.new(xml_parser.get_node_name())
@@ -230,7 +242,7 @@ func find_all_by_class(tag: String, the_class_name: String) -> Array[HTMLElement
var results: Array[HTMLElement] = []
for element in parse_result.all_elements:
if element.tag_name == tag and element.get_class() == the_class_name:
if element.tag_name == tag and element.get_class_name() == the_class_name:
results.append(element)
return results

View File

@@ -35,7 +35,7 @@ var HTML_CONTENT = """<head>
h2 { text-[#cbd5e1] text-xl }
p { text-[#94a3b8] text-base }
button { bg-[#4ade80] text-[#ffffff] hover:bg-[#22c55e] active:bg-[#15803d] }
card { bg-[#1e293b] text-[#f8fafc] rounded-xl p-4 shadow-lg }
.card { bg-[#1e293b] text-[#f8fafc] rounded-xl p-4 shadow-lg }
</style>
</head>