Skip to content

Commit 3c74553

Browse files
committed
GdExtension class icon
- if no individual class icon is set, it will act as an extension wide default - if you set an individual class icon, it use that INSTEAD, via #[class(icon = PATH)] - the GdExtension [icons] declarations will not do anything if you use #[gdextension(icon = PATH]) - #[gdextension] generates ExtensionConfig which is very similar to ClassConfig but just holds the icon path
1 parent a4993f8 commit 3c74553

File tree

4 files changed

+71
-0
lines changed

4 files changed

+71
-0
lines changed

godot-core/src/init/mod.rs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,22 @@ struct InitUserData {
2727
main_loop_callbacks: sys::GDExtensionMainLoopCallbacks,
2828
}
2929

30+
#[derive(Copy, Clone, Default)]
31+
pub struct ExtensionConfig {
32+
pub icon: Option<&'static str>,
33+
}
34+
35+
impl ExtensionConfig {
36+
pub const fn new() -> Self {
37+
Self { icon: None }
38+
}
39+
40+
pub const fn with_icon(mut self, icon: &'static str) -> Self {
41+
self.icon = Some(icon);
42+
self
43+
}
44+
}
45+
3046
#[cfg(since_api = "4.5")]
3147
unsafe extern "C" fn startup_func<E: ExtensionLibrary>() {
3248
E::on_stage_init(InitStage::MainLoop);
@@ -117,6 +133,11 @@ pub unsafe fn __gdext_load_library<E: ExtensionLibrary>(
117133
is_success.unwrap_or(0)
118134
}
119135

136+
/// This contains metadata declared through [`#[gdextension]`](attr.gdextension.html)
137+
pub fn extension_config() -> ExtensionConfig {
138+
crate::private::extension_config()
139+
}
140+
120141
static LEVEL_SERVERS_CORE_LOADED: AtomicBool = AtomicBool::new(false);
121142

122143
unsafe extern "C" fn ffi_initialize_layer<E: ExtensionLibrary>(
@@ -289,6 +310,18 @@ fn gdext_on_level_deinit(level: InitLevel) {
289310
/// Note that this only changes the name. You cannot provide your own function -- use the [`on_level_init()`][ExtensionLibrary::on_level_init]
290311
/// hook for custom startup logic.
291312
///
313+
/// # Extension metadata
314+
/// Provide additional metadata directly on the attribute. For example, on Godot 4.4+ you can assign the icon shown in the editor's
315+
/// extension list like so:
316+
/// ```no_run
317+
/// # use godot::init::*;
318+
/// struct MyExtension;
319+
///
320+
/// #[gdextension(icon = "res://addons/my_extension/icon.svg")]
321+
/// unsafe impl ExtensionLibrary for MyExtension {}
322+
/// ```
323+
/// The values provided here are exposed via [`extension_config()`] for tooling and build scripts.
324+
///
292325
/// # Availability of Godot APIs during init and deinit
293326
// Init order: see also special_cases.rs > classify_codegen_level().
294327
/// Godot loads functionality gradually during its startup routines, and unloads it during shutdown. As a result, Godot classes are only

godot-core/src/private.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ static ERROR_PRINT_LEVEL: atomic::AtomicU8 = atomic::AtomicU8::new(2);
5757
sys::plugin_registry!(pub __GODOT_PLUGIN_REGISTRY: ClassPlugin);
5858
#[cfg(all(since_api = "4.3", feature = "register-docs"))]
5959
sys::plugin_registry!(pub __GODOT_DOCS_REGISTRY: DocsPlugin);
60+
sys::plugin_registry!(pub __GODOT_EXTENSION_CONFIG_REGISTRY: crate::init::ExtensionConfig);
6061

6162
// ----------------------------------------------------------------------------------------------------------------------------------------------
6263
// Call error handling
@@ -151,6 +152,11 @@ pub(crate) fn iterate_plugins(mut visitor: impl FnMut(&ClassPlugin)) {
151152
sys::plugin_foreach!(__GODOT_PLUGIN_REGISTRY; visitor);
152153
}
153154

155+
pub(crate) fn extension_config() -> crate::init::ExtensionConfig {
156+
let guard = __GODOT_EXTENSION_CONFIG_REGISTRY.lock().unwrap();
157+
guard.last().copied().unwrap_or_default()
158+
}
159+
154160
#[cfg(all(since_api = "4.3", feature = "register-docs"))]
155161
pub(crate) fn iterate_docs_plugins(mut visitor: impl FnMut(&DocsPlugin)) {
156162
sys::plugin_foreach!(__GODOT_DOCS_REGISTRY; visitor);

godot-core/src/registry/class.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -588,6 +588,23 @@ fn register_class_raw(mut info: ClassRegistrationInfo) {
588588
info.godot_params.get_virtual_func = info.user_virtual_fn.or(info.default_virtual_fn);
589589
}
590590

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+
591608
// The explicit () type notifies us if Godot API ever adds a return type.
592609
let registration_failed = unsafe {
593610
// Try to register class...

godot-macros/src/gdextension.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ pub fn attribute_gdextension(item: venial::Item) -> ParseResult<TokenStream> {
2929
let mut parser = KvParser::parse_required(&drained_attributes, "gdextension", &impl_decl)?;
3030
let entry_point = parser.handle_ident("entry_point")?;
3131
let entry_symbol = parser.handle_ident("entry_symbol")?;
32+
let icon = parser.handle_expr("icon")?;
3233
parser.finish()?;
3334

3435
if entry_point.is_some() && entry_symbol.is_some() {
@@ -50,7 +51,21 @@ pub fn attribute_gdextension(item: venial::Item) -> ParseResult<TokenStream> {
5051

5152
let impl_ty = &impl_decl.self_ty;
5253

54+
let mut modifiers = Vec::new();
55+
if icon.is_some() {
56+
let icon_expr = icon.unwrap();
57+
modifiers.push(quote! { with_icon(#icon_expr) });
58+
}
59+
60+
let extension_config_registration = quote! {
61+
::godot::sys::plugin_add!(
62+
::godot::private::__GODOT_EXTENSION_CONFIG_REGISTRY;
63+
::godot::init::ExtensionConfig::new()#( .#modifiers )*
64+
);
65+
};
66+
5367
Ok(quote! {
68+
#extension_config_registration
5469
#deprecation
5570
#impl_decl
5671

0 commit comments

Comments
 (0)