object-fit, multithreaded image loading
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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)
|
||||
|
||||
@@ -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]
|
||||
|
||||
Reference in New Issue
Block a user