@@ -53,6 +53,14 @@ fn global_dyn_traits_by_typeid() -> GlobalGuard<'static, HashMap<any::TypeId, Ve
5353 lock_or_panic ( & DYN_TRAITS_BY_TYPEID , "dyn traits" )
5454}
5555
56+ #[ cfg( since_api = "4.4" ) ]
57+ fn global_icon_strings ( ) -> GlobalGuard < ' static , HashMap < ClassId , crate :: builtin:: GString > > {
58+ static ICON_STRINGS_BY_NAME : Global < HashMap < ClassId , crate :: builtin:: GString > > =
59+ Global :: default ( ) ;
60+
61+ lock_or_panic ( & ICON_STRINGS_BY_NAME , "icon strings (by name)" )
62+ }
63+
5664// ----------------------------------------------------------------------------------------------------------------------------------------------
5765
5866/// Represents a class which is currently loaded and retained in memory.
@@ -426,6 +434,7 @@ fn fill_class_info(item: PluginItem, c: &mut ClassRegistrationInfo) {
426434 is_instantiable,
427435 reference_fn,
428436 unreference_fn,
437+ icon,
429438 } ) => {
430439 c. parent_class_name = Some ( base_class_name) ;
431440 c. default_virtual_fn = default_get_virtual_fn;
@@ -458,6 +467,26 @@ fn fill_class_info(item: PluginItem, c: &mut ClassRegistrationInfo) {
458467 . expect ( "duplicate: recreate_instance_func (def)" ) ;
459468
460469 c. godot_params . is_exposed = sys:: conv:: bool_to_sys ( !is_internal) ;
470+ #[ cfg( before_api = "4.4" ) ]
471+ let _ = icon; // mark used
472+ #[ cfg( since_api = "4.4" ) ]
473+ if let Some ( icon_path) = icon {
474+ // Convert to GString and store in global map to keep it alive for program lifetime
475+ let icon_gstring = crate :: builtin:: GString :: from ( icon_path) ;
476+
477+ let mut icon_map = global_icon_strings ( ) ;
478+ icon_map. insert ( c. class_name , icon_gstring) ;
479+
480+ // Get pointer after insertion, while lock is still held
481+ // SAFETY: The GString is stored in a static HashMap, so the pointer remains valid
482+ // even after the lock guard is dropped. We must retrieve the pointer while holding
483+ // the lock to ensure the HashMap isn't being modified concurrently.
484+ let icon_ptr = icon_map. get ( & c. class_name ) . unwrap ( ) . string_sys ( )
485+ as sys:: GDExtensionConstStringPtr ;
486+
487+ // Set the pointer in godot_params
488+ c. godot_params . icon_path = icon_ptr;
489+ }
461490
462491 #[ cfg( before_api = "4.3" ) ]
463492 let _ = is_tool; // mark used
@@ -559,6 +588,23 @@ fn register_class_raw(mut info: ClassRegistrationInfo) {
559588 info. godot_params . get_virtual_func = info. user_virtual_fn . or ( info. default_virtual_fn ) ;
560589 }
561590
591+ // If no class-level icon was set, use the extension-level icon as fallback (if available)
592+ #[ cfg( since_api = "4.4" ) ]
593+ if info. godot_params . icon_path . is_null ( ) {
594+ if let Some ( extension_icon) = crate :: init:: extension_config ( ) . icon {
595+ let icon_gstring = crate :: builtin:: GString :: from ( extension_icon) ;
596+
597+ // SAFETY: The GString is stored in a static HashMap, so the pointer remains valid
598+ // even after the lock guard is dropped. We must retrieve the pointer while holding
599+ // the lock to ensure the HashMap isn't being modified concurrently.
600+ let mut icon_map = global_icon_strings ( ) ;
601+ let icon_ptr = icon_gstring. string_sys ( ) as sys:: GDExtensionConstStringPtr ;
602+ icon_map. insert ( class_name, icon_gstring) ;
603+
604+ info. godot_params . icon_path = icon_ptr;
605+ }
606+ }
607+
562608 // The explicit () type notifies us if Godot API ever adds a return type.
563609 let registration_failed = unsafe {
564610 // Try to register class...
0 commit comments