diff --git a/flumi/Scripts/B9/CSSParser.gd b/flumi/Scripts/B9/CSSParser.gd index 2662d33..a5b45a5 100644 --- a/flumi/Scripts/B9/CSSParser.gd +++ b/flumi/Scripts/B9/CSSParser.gd @@ -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 diff --git a/flumi/Scripts/Constants.gd b/flumi/Scripts/Constants.gd index e0c5dfb..7634fee 100644 --- a/flumi/Scripts/Constants.gd +++ b/flumi/Scripts/Constants.gd @@ -2014,7 +2014,7 @@ var HTML_CONTENTy = """ """.to_utf8_buffer() -var HTML_CONTENT = """ +var HTML_CONTENTyea = """ setInterval & Network Image Demo @@ -2182,3 +2182,77 @@ var HTML_CONTENT = """ """.to_utf8_buffer() + +var HTML_CONTENT = """ + Object-Fit CSS Demo + + + + + + + +

🖼️ Object-Fit CSS Demonstration

+ +
+ +
+

object-none

+
Image keeps original size, may overflow container
+
+ +
+
object-none
+
+ + +
+

object-fill

+
Image fills container, may distort aspect ratio
+
+ +
+
object-fill
+
+ + +
+

object-contain

+
Image fits inside container while preserving aspect ratio
+
+ +
+
object-contain
+
+ + +
+

object-cover

+
Image covers entire container while preserving aspect ratio
+
+ +
+
object-cover
+
+
+ +
+

📝 Object-fit Property Mapping

+
+

object-none: Godot's STRETCH_KEEP - Image keeps original dimensions

+

object-fill: Godot's STRETCH_SCALE - Image stretches to fill container

+

object-contain: Godot's STRETCH_KEEP_ASPECT - Image fits inside with preserved aspect ratio

+

object-cover: Godot's STRETCH_KEEP_ASPECT_COVERED - Image covers container with preserved aspect ratio

+
+
+ +""".to_utf8_buffer() diff --git a/flumi/Scripts/Network.gd b/flumi/Scripts/Network.gd index 9678f9f..1f04040 100644 --- a/flumi/Scripts/Network.gd +++ b/flumi/Scripts/Network.gd @@ -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) diff --git a/flumi/Scripts/StyleManager.gd b/flumi/Scripts/StyleManager.gd index 9b6f752..bac6d76 100644 --- a/flumi/Scripts/StyleManager.gd +++ b/flumi/Scripts/StyleManager.gd @@ -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 diff --git a/flumi/Scripts/Tags/img.gd b/flumi/Scripts/Tags/img.gd index 0e82b45..87226ca 100644 --- a/flumi/Scripts/Tags/img.gd +++ b/flumi/Scripts/Tags/img.gd @@ -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 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 \ No newline at end of file diff --git a/flumi/Scripts/Utils/BackgroundUtils.gd b/flumi/Scripts/Utils/BackgroundUtils.gd index 5c0bd68..de9d2d2 100644 --- a/flumi/Scripts/Utils/BackgroundUtils.gd +++ b/flumi/Scripts/Utils/BackgroundUtils.gd @@ -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) diff --git a/flumi/Scripts/Utils/SizeUtils.gd b/flumi/Scripts/Utils/SizeUtils.gd index a64dbac..5001a43 100644 --- a/flumi/Scripts/Utils/SizeUtils.gd +++ b/flumi/Scripts/Utils/SizeUtils.gd @@ -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]