width, height, max-width, max-height, min-width, min-height

This commit is contained in:
Face
2025-07-27 12:55:43 +03:00
parent b88509307d
commit e0a8b6f414
10 changed files with 361 additions and 29 deletions

1
Assets/Icons/globe.svg Normal file
View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#ffffff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-globe-icon lucide-globe"><circle cx="12" cy="12" r="10"/><path d="M12 2a14.5 14.5 0 0 0 0 20 14.5 14.5 0 0 0 0-20"/><path d="M2 12h20"/></svg>

After

Width:  |  Height:  |  Size: 339 B

View File

@@ -0,0 +1,37 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://bqpx2lgo0yecb"
path="res://.godot/imported/globe.svg-2e20bad5dc0399b0e6d7dae13a9b962d.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://Assets/Icons/globe.svg"
dest_files=["res://.godot/imported/globe.svg-2e20bad5dc0399b0e6d7dae13a9b962d.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
svg/scale=1.0
editor/scale_with_editor_scale=false
editor/convert_colors_with_editor_theme=false

1
Assets/gurted.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 79 KiB

37
Assets/gurted.svg.import Normal file
View File

@@ -0,0 +1,37 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://ctpe0lbehepen"
path="res://.godot/imported/gurted.svg-77b32dc1cb120dd6b0c05e9883f93cc4.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://Assets/gurted.svg"
dest_files=["res://.godot/imported/gurted.svg-77b32dc1cb120dd6b0c05e9883f93cc4.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
svg/scale=1.0
editor/scale_with_editor_scale=false
editor/convert_colors_with_editor_theme=false

View File

@@ -6,7 +6,7 @@
[ext_resource type="Texture2D" uid="uid://c7u7a1u1v04bx" path="res://Scenes/Styles/TabGradientDefault.tres" id="3_q3baj"]
[ext_resource type="StyleBox" uid="uid://bx3sgro1ageff" path="res://Scenes/Styles/TabDefault.tres" id="4_ib6pj"]
[ext_resource type="Texture2D" uid="uid://dglkjumm1q4lo" path="res://Assets/Icons/23x23-empty.svg" id="5_ib6pj"]
[ext_resource type="Texture2D" uid="uid://bslojb4cmwnvn" path="res://icon.svg" id="6_ib6pj"]
[ext_resource type="Texture2D" uid="uid://bqpx2lgo0yecb" path="res://Assets/Icons/globe.svg" id="6_ib6pj"]
[ext_resource type="StyleBox" uid="uid://dn8exdnk8tjce" path="res://Scenes/Styles/CloseButtonHover.tres" id="6_pisds"]
[ext_resource type="StyleBox" uid="uid://dn6r16retee3l" path="res://Scenes/Styles/CloseButtonNormal.tres" id="7_1ohlo"]

View File

@@ -162,6 +162,32 @@ func parse_utility_class(rule: CSSRule, utility_name: String) -> void:
rule.properties["background-color"] = color
return
# e.g. max-w-[123px], w-[50%], h-[2rem]
if utility_name.match("^max-w-\\[.*\\]$"):
var val = extract_bracket_content(utility_name, 6)
rule.properties["max-width"] = val
return
if utility_name.match("^max-h-\\[.*\\]$"):
var val = extract_bracket_content(utility_name, 6)
rule.properties["max-height"] = val
return
if utility_name.match("^min-w-\\[.*\\]$"):
var val = extract_bracket_content(utility_name, 6)
rule.properties["min-width"] = val
return
if utility_name.match("^min-h-\\[.*\\]$"):
var val = extract_bracket_content(utility_name, 6)
rule.properties["min-height"] = val
return
if utility_name.match("^w-\\[.*\\]$"):
var val = extract_bracket_content(utility_name, 2)
rule.properties["width"] = val
return
if utility_name.match("^h-\\[.*\\]$"):
var val = extract_bracket_content(utility_name, 2)
rule.properties["height"] = val
return
# Handle font weight
if utility_name == "font-bold":
rule.properties["font-bold"] = true
@@ -194,19 +220,76 @@ func parse_utility_class(rule: CSSRule, utility_name: String) -> void:
"text-4xl": rule.properties["font-size"] = 36
"text-5xl": rule.properties["font-size"] = 48
"text-6xl": rule.properties["font-size"] = 60
# Handle text alignment classes
"text-left": rule.properties["text-align"] = "left"
"text-center": rule.properties["text-align"] = "center"
"text-right": rule.properties["text-align"] = "right"
"text-justify": rule.properties["text-align"] = "justify"
# Width
if utility_name.begins_with("w-"):
var val = utility_name.substr(2)
rule.properties["width"] = parse_size(val)
return
# Height
if utility_name.begins_with("h-"):
var val = utility_name.substr(2)
rule.properties["height"] = parse_size(val)
return
# Min width
if utility_name.begins_with("min-w-"):
var val = utility_name.substr(6)
rule.properties["min-width"] = parse_size(val)
return
# Min height
if utility_name.begins_with("min-h-"):
var val = utility_name.substr(6)
rule.properties["min-height"] = parse_size(val)
return
# Max width
if utility_name.begins_with("max-w-"):
var val = utility_name.substr(6)
rule.properties["max-width"] = parse_size(val)
return
# Max height
if utility_name.begins_with("max-h-"):
var val = utility_name.substr(6)
rule.properties["max-height"] = parse_size(val)
return
# Handle more utility classes as needed
# Add more cases here for other utilities
func parse_size(val: String) -> String:
var named = {
"0": "0px", "1": "4px", "2": "8px", "3": "12px", "4": "16px", "5": "20px", "6": "24px", "8": "32px", "10": "40px",
"12": "48px", "16": "64px", "20": "80px", "24": "96px", "28": "112px", "32": "128px", "36": "144px", "40": "160px",
"44": "176px", "48": "192px", "52": "208px", "56": "224px", "60": "240px", "64": "256px", "72": "288px", "80": "320px", "96": "384px",
"3xs": "256px", "2xs": "288px", "xs": "320px", "sm": "384px", "md": "448px", "lg": "512px",
"xl": "576px", "2xl": "672px", "3xl": "768px", "4xl": "896px", "5xl": "1024px", "6xl": "1152px", "7xl": "1280px"
}
if named.has(val):
return named[val]
# Fractional (e.g. 1/2, 1/3)
if val.find("/") != -1:
var parts = val.split("/")
if parts.size() == 2 and parts[1].is_valid_int() and parts[0].is_valid_int():
var frac = float(parts[0]) / float(parts[1])
return str(frac * 100.0) + "%"
if val.is_valid_int():
return str(int(val) * 16) + "px"
return val
# Helper to extract content inside first matching brackets after a given index
func extract_bracket_content(str: String, start_idx: int) -> String:
var open_idx = str.find("[", start_idx)
func extract_bracket_content(string: String, start_idx: int) -> String:
var open_idx = string.find("[", start_idx)
if open_idx == -1:
return ""
var close_idx = str.find("]", open_idx)
var close_idx = string.find("]", open_idx)
if close_idx == -1:
return ""
return str.substr(open_idx + 1, close_idx - open_idx - 1)
return string.substr(open_idx + 1, close_idx - open_idx - 1)
func parse_color(color_string: String) -> Color:
print("DEBUG: parsing color: ", color_string)

68
Scripts/MaxSizeControl.gd Normal file
View File

@@ -0,0 +1,68 @@
@tool
class_name MaxSizeControl
extends Control
@export var max_size: Vector2 = Vector2(-1, -1):
set(value):
max_size = value
_enforce_size_limits()
var content_node: Control
func _ready():
# Auto-detect content node
if get_child_count() > 0:
setup_content_node(get_child(0))
# Connect to our own resize
resized.connect(_on_resized)
func setup_content_node(node: Control):
content_node = node
if content_node:
# Make content fill the container initially
content_node.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
# Connect to content changes
if not content_node.minimum_size_changed.is_connected(_on_content_changed):
content_node.minimum_size_changed.connect(_on_content_changed)
_enforce_size_limits()
func _on_content_changed():
_enforce_size_limits()
func _on_resized():
_enforce_size_limits()
func _enforce_size_limits():
if not content_node:
return
var target_width = max_size.x if max_size.x > 0 else content_node.get_combined_minimum_size().x
var target_height = max_size.y if max_size.y > 0 else content_node.get_combined_minimum_size().y
custom_minimum_size = Vector2(target_width, target_height)
# Set children's minimum size to match the constrained size
for child in get_children():
if child is Control:
child.custom_minimum_size = Vector2(target_width, target_height)
# Force the content to fit within our bounds and enable clipping
content_node.size = Vector2(target_width, target_height)
content_node.position = Vector2.ZERO
# Always enable clipping if max_size is set
var needs_clipping = max_size.x > 0 or max_size.y > 0
content_node.clip_contents = needs_clipping
clip_contents = true
func _get_minimum_size() -> Vector2:
# Only use max_size, ignore content's natural size
var final_size = Vector2.ZERO
if max_size.x > 0:
final_size.x = max_size.x
if max_size.y > 0:
final_size.y = max_size.y
return final_size

View File

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

View File

@@ -82,7 +82,11 @@ both spaces and
line breaks
</pre>
<select>
<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>
@@ -122,7 +126,7 @@ line breaks
<input type=\"date\" value=\"2018-07-22\" />
<h2>Range Slider</h2>
<input type=\"range\" min=\"0\" max=\"100\" step=\"5\" value=\"50\" />
<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\" />
@@ -203,7 +207,7 @@ line breaks
<li>is</li>
<li>a test</li>
</ul>
<img src=\"https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQMNUPIKabszX0Js_c0kfa4cz_JQYKfGTuBUA&s\" />
<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()
@@ -244,19 +248,20 @@ line breaks
for inline_element in inline_elements:
var inline_node = await create_element_node(inline_element, parser)
if inline_node:
hbox.add_child(inline_node)
safe_add_child(hbox, inline_node)
# 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)))
website_container.add_child(hbox)
safe_add_child(website_container, hbox)
continue
var element_node = await create_element_node(element, parser)
if element_node:
# ul/ol handle their own adding
if element.tag_name != "ul" and element.tag_name != "ol":
website_container.add_child(element_node)
safe_add_child(website_container, 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)))
@@ -265,12 +270,87 @@ line breaks
i += 1
func apply_element_styles(node: Control, element: HTMLParser.HTMLElement, parser: HTMLParser) -> void:
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
@@ -325,6 +405,16 @@ func apply_styles_to_label(label: RichTextLabel, styles: Dictionary, element: HT
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,
@@ -363,27 +453,27 @@ func create_element_node(element: HTMLParser.HTMLElement, parser: HTMLParser = n
node = P.instantiate()
node.init(element)
if parser:
apply_element_styles(node, element, parser)
node = apply_element_styles(node, element, parser)
"h1":
node = H1.instantiate()
node.init(element)
if parser:
apply_element_styles(node, element, parser)
node = apply_element_styles(node, element, parser)
"h2":
node = H2.instantiate()
node.init(element)
if parser:
apply_element_styles(node, element, parser)
node = apply_element_styles(node, element, parser)
"h3":
node = H3.instantiate()
node.init(element)
if parser:
apply_element_styles(node, element, parser)
node = apply_element_styles(node, element, parser)
"h4":
node = H4.instantiate()
node.init(element)
if parser:
apply_element_styles(node, element, parser)
node = apply_element_styles(node, element, parser)
"h5":
node = H5.instantiate()
node.init(element)
@@ -399,6 +489,8 @@ func create_element_node(element: HTMLParser.HTMLElement, parser: HTMLParser = n
"img":
node = IMG.instantiate()
node.init(element)
if parser:
node = apply_element_styles(node, element, parser)
"separator":
node = SEPARATOR.instantiate()
node.init(element)
@@ -413,49 +505,53 @@ func create_element_node(element: HTMLParser.HTMLElement, parser: HTMLParser = n
"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":
node = SPAN.instantiate()
node.init(element)
if parser:
apply_element_styles(node, element, parser)
node = apply_element_styles(node, element, parser)
"b":
node = SPAN.instantiate()
node.init(element)
if parser:
apply_element_styles(node, element, parser)
node = apply_element_styles(node, element, parser)
"i":
node = SPAN.instantiate()
node.init(element)
if parser:
apply_element_styles(node, element, parser)
node = apply_element_styles(node, element, parser)
"u":
node = SPAN.instantiate()
node.init(element)
if parser:
apply_element_styles(node, element, parser)
node = apply_element_styles(node, element, parser)
"small":
node = SPAN.instantiate()
node.init(element)
if parser:
apply_element_styles(node, element, parser)
node = apply_element_styles(node, element, parser)
"mark":
node = SPAN.instantiate()
node.init(element)
if parser:
apply_element_styles(node, element, parser)
node = apply_element_styles(node, element, parser)
"code":
node = SPAN.instantiate()
node.init(element)
if parser:
apply_element_styles(node, element, parser)
node = apply_element_styles(node, element, parser)
"a":
node = SPAN.instantiate()
node.init(element)
if parser:
apply_element_styles(node, element, parser)
node = apply_element_styles(node, element, parser)
"ul":
node = UL.instantiate()
website_container.add_child(node) # Add to scene tree first
@@ -469,15 +565,23 @@ func create_element_node(element: HTMLParser.HTMLElement, parser: HTMLParser = n
"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)
_:
return null

View File

@@ -10,10 +10,10 @@ config_version=5
[application]
config/name="gurted"
config/name="Gurted"
run/main_scene="uid://bytm7bt2s4ak8"
config/features=PackedStringArray("4.4", "Forward Plus")
config/icon="res://icon.svg"
config/icon="uid://ctpe0lbehepen"
[autoload]
@@ -28,7 +28,7 @@ window/stretch/aspect="ignore"
[editor_plugins]
enabled=PackedStringArray("res://addons/DatePicker/plugin.cfg", "res://addons/SmoothScroll/plugin.cfg")
enabled=PackedStringArray("res://addons/DatePicker/plugin.cfg", "res://addons/MaxSizeContainer/plugin.cfg", "res://addons/SmoothScroll/plugin.cfg")
[file_customization]