object-fit, multithreaded image loading

This commit is contained in:
Face
2025-08-09 15:54:59 +03:00
parent d47f811670
commit 3a722fdf1e
7 changed files with 150 additions and 17 deletions

View File

@@ -890,6 +890,13 @@ static func parse_utility_class_internal(rule: CSSRule, utility_name: String) ->
rule.properties["opacity"] = val.to_int() / 100.0
return
# Handle object-fit classes for images
match utility_name:
"object-none": rule.properties["object-fit"] = "none"; return
"object-fill": rule.properties["object-fit"] = "fill"; return
"object-contain": rule.properties["object-fit"] = "contain"; return
"object-cover": rule.properties["object-fit"] = "cover"; return
# Handle more utility classes as needed
# Add more cases here for other utilities

View File

@@ -2014,7 +2014,7 @@ var HTML_CONTENTy = """<head>
</div>
</body>
""".to_utf8_buffer()
var HTML_CONTENT = """<head>
var HTML_CONTENTyea = """<head>
<title>setInterval & Network Image Demo</title>
<icon src="https://upload.wikimedia.org/wikipedia/commons/thumb/c/cf/Lua-Logo.svg/256px-Lua-Logo.svg.png">
<meta name="theme-color" content="#7c3aed">
@@ -2182,3 +2182,77 @@ var HTML_CONTENT = """<head>
</div>
</body>
""".to_utf8_buffer()
var HTML_CONTENT = """<head>
<title>Object-Fit CSS Demo</title>
<icon src="https://picsum.photos/32/32?random=1">
<meta name="theme-color" content="#0ea5e9">
<meta name="description" content="Demonstrating object-fit CSS properties for images">
<style>
body { bg-[#f1f5f9] p-8 }
h1 { text-[#0ea5e9] text-4xl font-bold text-center mb-8 }
h2 { text-[#0369a1] text-2xl font-semibold mb-4 }
.demo-grid { flex flex-wrap gap-6 justify-center }
.demo-item { bg-white p-6 rounded-xl shadow-lg }
.image-container { w-[300px] h-[200px] border-2 border-[#cbd5e1] rounded-lg overflow-hidden mb-4 }
.code-block { bg-[#1e293b] text-[#e2e8f0] p-3 rounded font-mono text-sm }
.description { text-[#475569] text-sm mb-2 }
</style>
</head>
<body>
<h1>🖼️ Object-Fit CSS Demonstration</h1>
<div style="demo-grid">
<!-- Object-fit: none (STRETCH_KEEP) -->
<div style="demo-item">
<h2>object-none</h2>
<div style="description">Image keeps original size, may overflow container</div>
<div style="image-container">
<img src="https://picsum.photos/400/700?random=1" style="w-full h-full object-none" />
</div>
<div style="code-block">object-none</div>
</div>
<!-- Object-fit: fill (STRETCH_SCALE) -->
<div style="demo-item">
<h2>object-fill</h2>
<div style="description">Image fills container, may distort aspect ratio</div>
<div style="image-container">
<img src="https://picsum.photos/500/600?random=2" style="w-full h-full object-fill" />
</div>
<div style="code-block">object-fill</div>
</div>
<!-- Object-fit: contain (STRETCH_KEEP_ASPECT) -->
<div style="demo-item">
<h2>object-contain</h2>
<div style="description">Image fits inside container while preserving aspect ratio</div>
<div style="image-container">
<img src="https://picsum.photos/700/600?random=3" style="w-full h-full object-contain" />
</div>
<div style="code-block">object-contain</div>
</div>
<!-- Object-fit: cover (STRETCH_KEEP_ASPECT_COVERED) -->
<div style="demo-item">
<h2>object-cover</h2>
<div style="description">Image covers entire container while preserving aspect ratio</div>
<div style="image-container">
<img src="https://picsum.photos/200/300?random=4" style="w-full h-full object-cover" />
</div>
<div style="code-block">object-cover</div>
</div>
</div>
<div style="bg-white p-6 rounded-xl shadow-lg mt-8 max-w-4xl mx-auto">
<h2>📝 Object-fit Property Mapping</h2>
<div style="text-[#475569]">
<p><strong>object-none:</strong> Godot's STRETCH_KEEP - Image keeps original dimensions</p>
<p><strong>object-fill:</strong> Godot's STRETCH_SCALE - Image stretches to fill container</p>
<p><strong>object-contain:</strong> Godot's STRETCH_KEEP_ASPECT - Image fits inside with preserved aspect ratio</p>
<p><strong>object-cover:</strong> Godot's STRETCH_KEEP_ASPECT_COVERED - Image covers container with preserved aspect ratio</p>
</div>
</div>
</body>
""".to_utf8_buffer()

View File

@@ -37,10 +37,10 @@ func fetch_image(url: String) -> ImageTexture:
var load_error
# Load image based on content type
if content_type.contains("png") or url.to_lower().ends_with(".png"):
load_error = image.load_png_from_buffer(body)
elif content_type.contains("jpeg") or content_type.contains("jpg") or url.to_lower().ends_with(".jpg") or url.to_lower().ends_with(".jpeg"):
if content_type.contains("jpeg") or content_type.contains("jpg") or url.to_lower().ends_with(".jpg") or url.to_lower().ends_with(".jpeg"):
load_error = image.load_jpg_from_buffer(body)
elif content_type.contains("png") or url.to_lower().ends_with(".png"):
load_error = image.load_png_from_buffer(body)
elif content_type.contains("webp") or url.to_lower().ends_with(".webp"):
load_error = image.load_webp_from_buffer(body)
elif content_type.contains("bmp"):
@@ -48,10 +48,10 @@ func fetch_image(url: String) -> ImageTexture:
elif content_type.contains("tga"):
load_error = image.load_tga_from_buffer(body)
else:
print("Unknown or missing content-type. Attempting bruteforce converting across PNG, JPG and WebP...")
load_error = image.load_png_from_buffer(body)
print("Unknown or missing content-type. Attempting bruteforce converting across JPEG, PNG and WebP...")
load_error = image.load_jpg_from_buffer(body)
if load_error != OK:
load_error = image.load_jpg_from_buffer(body)
load_error = image.load_png_from_buffer(body)
if load_error != OK:
load_error = image.load_webp_from_buffer(body)

View File

@@ -14,8 +14,6 @@ static func parse_size(val):
if val.ends_with("%") or (val.ends_with("]") and "%" in val):
var clean_val = val.replace("[", "").replace("]", "")
return clean_val
if val == "full":
return null
return float(val)
static func apply_element_styles(node: Control, element: HTMLParser.HTMLElement, parser: HTMLParser) -> Control:
@@ -34,6 +32,8 @@ static func apply_element_styles(node: Control, element: HTMLParser.HTMLElement,
if element.tag_name == "input":
apply_input_border_styles(node, styles)
elif element.tag_name == "img":
apply_image_styles(node, styles)
# Unified font applying for label and button
if target and styles.has("font-family") and styles["font-family"] not in ["sans-serif", "serif", "monospace"]:
@@ -89,8 +89,14 @@ static func apply_element_styles(node: Control, element: HTMLParser.HTMLElement,
if not element_styles.has("height"):
node.size_flags_vertical = orig_v_flag
else:
# regular controls
SizingUtils.apply_regular_control_sizing(node, width, height, styles)
if element.tag_name == "img" and SizingUtils.is_percentage(width) and SizingUtils.is_percentage(height):
node.size_flags_horizontal = Control.SIZE_EXPAND_FILL
node.size_flags_vertical = Control.SIZE_EXPAND_FILL
# Clear any hardcoded sizing
node.custom_minimum_size = Vector2.ZERO
else:
# regular controls
SizingUtils.apply_regular_control_sizing(node, width, height, styles)
if label and label != node:
label.anchors_preset = Control.PRESET_FULL_RECT
@@ -627,3 +633,21 @@ static func apply_input_border_styles(input_node: Control, styles: Dictionary) -
control.add_theme_stylebox_override("focus", style_box)
elif control is Button:
control.add_theme_stylebox_override("normal", style_box)
static func apply_image_styles(image_node: Control, styles: Dictionary) -> void:
if not image_node is TextureRect:
return
var texture_rect = image_node as TextureRect
if styles.has("object-fit"):
var object_fit = styles["object-fit"]
match object_fit:
"none":
texture_rect.stretch_mode = TextureRect.STRETCH_KEEP
"fill":
texture_rect.stretch_mode = TextureRect.STRETCH_SCALE
"contain":
texture_rect.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT
"cover":
texture_rect.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_COVERED

View File

@@ -1,11 +1,38 @@
extends TextureRect
func init(element: HTMLParser.HTMLElement, _parser: HTMLParser) -> void:
func init(element: HTMLParser.HTMLElement, parser: HTMLParser) -> void:
var src = element.get_attribute("src")
if !src: return print("Ignoring <img/> tag without \"src\" attribute.")
load_image_async(src, element, parser)
func load_image_async(src: String, element: HTMLParser.HTMLElement, parser: HTMLParser) -> void:
# Wait until this node is in the scene tree
if not is_inside_tree():
await tree_entered
texture = await Network.fetch_image(src)
var texture_size = texture.get_size()
custom_minimum_size = texture_size
size = texture_size
if !is_instance_valid(texture):
print("Failed to load image: ", src)
return
var element_styles = parser.get_element_styles_internal(element, "")
var has_width = element_styles.has("width")
var has_height = element_styles.has("height")
if not has_width and not has_height:
var texture_size = texture.get_size()
custom_minimum_size = texture_size
size = texture_size
else:
var width_val = element_styles.get("width", "")
var height_val = element_styles.get("height", "")
if width_val == "100%" and height_val == "100%" or width_val == "full" and height_val == "full":
size_flags_horizontal = Control.SIZE_EXPAND_FILL
size_flags_vertical = Control.SIZE_EXPAND_FILL
custom_minimum_size = Vector2.ZERO
else:
custom_minimum_size = Vector2(1, 1)
size = Vector2(100, 100) # StyleManager will handle this

View File

@@ -158,7 +158,7 @@ static func create_panel_container_with_background(styles: Dictionary, hover_sty
vbox.name = "VBoxContainer"
# Allow mouse events to pass through to the parent PanelContainer
vbox.mouse_filter = Control.MOUSE_FILTER_IGNORE
vbox.size_flags_vertical = Control.SIZE_SHRINK_BEGIN
vbox.size_flags_vertical = Control.SIZE_EXPAND_FILL
panel_container.add_child(vbox)
var style_box = create_stylebox_from_styles(styles)

View File

@@ -12,7 +12,8 @@ static func parse_size(val: String) -> String:
"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"
"xl": "576px", "2xl": "672px", "3xl": "768px", "4xl": "896px", "5xl": "1024px", "6xl": "1152px", "7xl": "1280px",
"full": "100%"
}
if named.has(val):
return named[val]