use godot::classes::EditorInterface; use godot::classes::EditorPlugin; use godot::classes::IEditorPlugin; use godot::classes::ImageTexture; use godot::classes::MenuButton; use godot::classes::ResourceLoader; use godot::classes::Texture2D; use godot::classes::editor_plugin::CustomControlContainer; use godot::prelude::*; use crate::world::World; #[derive(GodotClass)] #[class(init, base=EditorPlugin, tool)] pub struct WorldPlugin { #[base] base: Base, toolbar_button: Option>, } #[godot_api] impl WorldPlugin { // Called when a menu item is selected #[func] fn on_menu_item_pressed(&self, id: i32) { let mut world = match self.find_world_node() { Some(world) => world, None => { godot_error!( "No World node found in the current scene. Make sure it's named 'World' and is a child of the scene root." ); return; } }; match id { 0 => { world.call("clear_terrain", &[]); world.call("generate_terrain", &[]); } 1 => { world.call("clear_terrain", &[]); } _ => { godot_print!("Unknown item"); } } // Here you can call your actual world management methods. // For example: // if let Some(world_manager) = self.get_world_manager() { // match id { // 0 => world_manager.call("rebuild_world", &[]), // 1 => world_manager.call("clear_world", &[]), // 2 => world_manager.call("reload_chunks", &[]), // _ => (), // } // } } fn find_world_node(&self) -> Option> { let editor = EditorInterface::singleton(); let scene_root = editor.get_edited_scene_root()?; // Assuming the World node is a direct child of the root and named "World" scene_root.try_get_node_as::("World") } } #[godot_api] impl IEditorPlugin for WorldPlugin { fn enter_tree(&mut self) { // Create the MenuButton let mut menu_button = MenuButton::new_alloc(); menu_button.set_text("FastVoxel"); // Button label // menu_button.set_flat(false); // Make it look like a normal button (optional) // --- Load an SVG icon and set it --- // Path to your SVG file (relative to the project, e.g., in addons/fastvoxel/fastvoxel.svg) if let Some(icon) = ResourceLoader::singleton().load("res://addons/fastvoxel/fastvoxel.svg") { let icon_texture: Gd = icon.cast(); // Cast to Texture2D // Get image data, resize, create new texture let mut image = icon_texture.get_image(); // Requires Texture2D to have image data image .as_mut() .expect("failed to load the fastvoxel icon") .resize(24, 24); let small_texture = ImageTexture::create_from_image(image.as_ref()); menu_button.set_button_icon(small_texture.as_ref()); } else { godot_error!("Failed to load plugin icon"); } // --- Build the dropdown menu --- let mut popup = menu_button.get_popup(); // Returns a PopupMenu popup.as_mut().expect("no popup").add_item("Rebuild World"); // item text, id popup.as_mut().expect("no popup").add_item("Clear World"); popup.as_mut().expect("no popup").add_item("Reload Chunks"); // You can also add separators, submenus, etc. // Connect the popup's "id_pressed" signal to our handler let callable = self.base().callable("on_menu_item_pressed"); popup.unwrap().connect("id_pressed", &callable); // Add the button to the editor's main toolbar (top row, next to play buttons) self.base_mut().add_control_to_container( CustomControlContainer::SPATIAL_EDITOR_MENU, menu_button.to_godot(), ); // Store the button reference so we can remove it later self.toolbar_button = Some(menu_button); } fn exit_tree(&mut self) { // Clean up: remove the button from the toolbar if let Some(button) = self.toolbar_button.take() { self.base_mut().remove_control_from_container( CustomControlContainer::SPATIAL_EDITOR_MENU, button.to_godot(), ); // The button will be freed automatically when no longer referenced } } }