kitgit

tirbofish/dropbear · commit

fd6bf0d98f2e9bb9d6de38e8cc157d6e865eeaf7

feature: created a new grid for the UI editor with pan and zoom.

Unverified

Thribhu K <4tkbytes@pm.me> · 2026-03-08 13:09

view full diff

diff --git a/crates/eucalyptus-core/src/animation.rs b/crates/eucalyptus-core/src/animation.rs
index d9b0212..e2c69bc 100644
--- a/crates/eucalyptus-core/src/animation.rs
+++ b/crates/eucalyptus-core/src/animation.rs
@@ -251,7 +251,7 @@ fn collect_available_animations(
     ),
     c
 )]
-fn animation_component_exists_for_entity(
+fn exists_for_entity(
     #[dropbear_macro::define(WorldPtr)] world: &World,
     #[dropbear_macro::entity] entity: Entity,
 ) -> DropbearNativeResult<bool> {
diff --git a/crates/eucalyptus-editor/src/editor/ui/mod.rs b/crates/eucalyptus-editor/src/editor/ui/mod.rs
index a31aee7..35266c3 100644
--- a/crates/eucalyptus-editor/src/editor/ui/mod.rs
+++ b/crates/eucalyptus-editor/src/editor/ui/mod.rs
@@ -1,15 +1,419 @@
+use std::sync::Arc;
+use bytemuck::{Pod, Zeroable};
+use dropbear_engine::graphics::SharedGraphicsContext;
+use egui::TextureId;
+use glam::{Mat4, Vec2, Vec4};
+use kino_ui::camera::Camera2D;
+use wgpu::TextureFormat;
+
+pub mod viewport;
+
 pub struct UiEditor {
+    pub grid_pipeline: Option<UIGridPipeline>,
+
     pub active_entity: Option<hecs::Entity>,
+    pub camera: Camera2D,
+    zoom: f32,
+    camera_position: Vec2,
 }
 
 impl UiEditor {
     pub fn new() -> Self {
+        let mut camera = Camera2D::default();
+        camera.set_zoom(1.0);
+        camera.target(Vec2::ZERO);
+
         Self {
+            grid_pipeline: None,
             active_entity: None,
+            camera,
+            zoom: 1.0,
+            camera_position: Vec2::ZERO,
         }
     }
 
     pub fn update(&mut self) {
 
     }
+
+    pub fn render(&mut self, graphics: Arc<SharedGraphicsContext>, width: u32, height: u32) {
+        if self.grid_pipeline.is_none() {
+            self.setup(graphics.clone());
+        }
+
+        let screen_size = Vec2::new(width.max(1) as f32, height.max(1) as f32);
+        let view_proj = self.view_proj(screen_size);
+        let inv_view_proj = self.inv_view_proj(screen_size);
+
+        if let Some(grid) = self.grid_pipeline.as_mut() {
+            grid.render_to_texture(graphics, width, height, view_proj, inv_view_proj);
+        }
+    }
+
+    pub fn zoom_by(&mut self, delta: f32) {
+        self.zoom = (self.zoom + delta).clamp(0.1, 10.0);
+        self.camera.set_zoom(self.zoom);
+        self.camera.target(self.camera_position);
+    }
+
+    pub fn zoom(&self) -> f32 {
+        self.zoom
+    }
+
+    pub fn pan_by_pixels(&mut self, delta_pixels: Vec2) {
+        self.camera_position -= delta_pixels / self.zoom.max(0.1);
+        self.camera.target(self.camera_position);
+    }
+
+    pub fn view_proj(&self, screen_size: Vec2) -> Mat4 {
+        let width = screen_size.x.max(1.0);
+        let height = screen_size.y.max(1.0);
+        let half_w = width / (2.0 * self.zoom.max(0.1));
+        let half_h = height / (2.0 * self.zoom.max(0.1));
+
+        let view = Mat4::from_translation((-self.camera_position).extend(0.0));
+        let proj = Mat4::orthographic_rh(
+            -half_w,
+            half_w,
+            half_h,
+            -half_h,
+            -1.0,
+            1.0,
+        );
+
+        proj * view
+    }
+
+    pub fn inv_view_proj(&self, screen_size: Vec2) -> Mat4 {
+        self.view_proj(screen_size).inverse()
+    }
+
+    pub fn world_from_screen_pixels(&self, pixel: Vec2, viewport_size: Vec2) -> Vec2 {
+        let viewport_size = Vec2::new(viewport_size.x.max(1.0), viewport_size.y.max(1.0));
+        let ndc = Vec2::new(
+            (pixel.x / viewport_size.x) * 2.0 - 1.0,
+            1.0 - (pixel.y / viewport_size.y) * 2.0,
+        );
+
+        let world = self.inv_view_proj(viewport_size) * Vec4::new(ndc.x, ndc.y, 0.0, 1.0);
+        Vec2::new(world.x, world.y) / world.w
+    }
+
+    pub fn texture_id(&self) -> Option<TextureId> {
+        self.grid_pipeline.as_ref().and_then(|pipeline| pipeline.texture_id)
+    }
+
+    fn setup(&mut self, graphics: Arc<SharedGraphicsContext>) {
+        self.grid_pipeline = Some(UIGridPipeline::new(graphics.clone()));
+    }
+}
+
+pub struct UIGridPipeline {
+    size: wgpu::Extent3d,
+    sample_count: u32,
+    format: TextureFormat,
+
+    texture_id: Option<TextureId>,
+    resolve_texture: Option<wgpu::Texture>,
+    resolve_view: Option<wgpu::TextureView>,
+    msaa_texture: Option<wgpu::Texture>,
+    msaa_view: Option<wgpu::TextureView>,
+
+    camera_buffer: wgpu::Buffer,
+    camera_bind_group: wgpu::BindGroup,
+
+    pipeline: wgpu::RenderPipeline,
+}
+
+impl UIGridPipeline {
+    pub fn new(graphics: Arc<SharedGraphicsContext>) -> Self {
+        let shader = graphics
+            .device
+            .create_shader_module(wgpu::include_wgsl!("shader/grid.wgsl"));
+
+        let camera_bind_group_layout = graphics
+            .device
+            .create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
+                label: Some("ui grid camera bind group layout"),
+                entries: &[wgpu::BindGroupLayoutEntry {
+                    binding: 0,
+                    visibility: wgpu::ShaderStages::FRAGMENT,
+                    ty: wgpu::BindingType::Buffer {
+                        ty: wgpu::BufferBindingType::Uniform,
+                        has_dynamic_offset: false,
+                        min_binding_size: None,
+                    },
+                    count: None,
+                }],
+            });
+
+        let camera_buffer = graphics.device.create_buffer(&wgpu::BufferDescriptor {
+            label: Some("ui grid camera uniform buffer"),
+            size: std::mem::size_of::<UIGridCameraUniform>() as u64,
+            usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
+            mapped_at_creation: false,
+        });
+
+        let camera_bind_group = graphics.device.create_bind_group(&wgpu::BindGroupDescriptor {
+            label: Some("ui grid camera bind group"),
+            layout: &camera_bind_group_layout,
+            entries: &[wgpu::BindGroupEntry {
+                binding: 0,
+                resource: camera_buffer.as_entire_binding(),
+            }],
+        });
+
+        let layout = graphics.device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
+            label: Some("ui grid pipeline layout"),
+            bind_group_layouts: &[&camera_bind_group_layout],
+            push_constant_ranges: &[],
+        });
+
+        let sample_count: u32 = (*graphics.antialiasing.read()).into();
+        let format = graphics.surface_format.add_srgb_suffix();
+
+        let pipeline = graphics.device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
+            label: Some("ui grid pipeline"),
+            layout: Some(&layout),
+            vertex: wgpu::VertexState {
+                module: &shader,
+                entry_point: Some("vs_main"),
+                compilation_options: Default::default(),
+                buffers: &[],
+            },
+            primitive: wgpu::PrimitiveState {
+                topology: wgpu::PrimitiveTopology::TriangleList,
+                strip_index_format: None,
+                front_face: wgpu::FrontFace::Ccw,
+                cull_mode: None,
+                polygon_mode: wgpu::PolygonMode::Fill,
+                unclipped_depth: false,
+                conservative: false,
+            },
+            depth_stencil: None,
+            multisample: wgpu::MultisampleState {
+                count: sample_count,
+                mask: !0,
+                alpha_to_coverage_enabled: false,
+            },
+            fragment: Some(wgpu::FragmentState {
+                module: &shader,
+                entry_point: Some("fs_main"),
+                compilation_options: Default::default(),
+                targets: &[Some(wgpu::ColorTargetState {
+                    format,
+                    blend: Some(wgpu::BlendState::ALPHA_BLENDING),
+                    write_mask: wgpu::ColorWrites::ALL,
+                })],
+            }),
+            multiview: None,
+            cache: None,
+        });
+
+        Self {
+            size: wgpu::Extent3d {
+                width: 1,
+                height: 1,
+                depth_or_array_layers: 1,
+            },
+            sample_count,
+            format,
+            texture_id: None,
+            resolve_texture: None,
+            resolve_view: None,
+            msaa_texture: None,
+            msaa_view: None,
+            camera_buffer,
+            camera_bind_group,
+            pipeline,
+        }
+    }
+
+    fn update_camera_uniform(
+        &self,
+        graphics: &SharedGraphicsContext,
+        view_proj: Mat4,
+        inv_view_proj: Mat4,
+        width: u32,
+        height: u32,
+    ) {
+        let screen_size = Vec2::new(width.max(1) as f32, height.max(1) as f32);
+
+        let uniform = UIGridCameraUniform {
+            view_proj: view_proj.to_cols_array_2d(),
+            inv_view_proj: inv_view_proj.to_cols_array_2d(),
+            viewport_size: [screen_size.x, screen_size.y],
+            _padding: [0.0, 0.0],
+        };
+
+        graphics
+            .queue
+            .write_buffer(&self.camera_buffer, 0, bytemuck::bytes_of(&uniform));
+    }
+
+    fn create_texture(
+        device: &wgpu::Device,
+        size: wgpu::Extent3d,
+        format: TextureFormat,
+        sample_count: u32,
+        label: &'static str,
+    ) -> (wgpu::Texture, wgpu::TextureView) {
+        let texture = device.create_texture(&wgpu::TextureDescriptor {
+            label: Some(label),
+            size,
+            mip_level_count: 1,
+            sample_count,
+            dimension: wgpu::TextureDimension::D2,
+            format,
+            usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING,
+            view_formats: &[],
+        });
+        let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
+        (texture, view)
+    }
+
+    fn recreate_targets_if_needed(
+        &mut self,
+        graphics: &SharedGraphicsContext,
+        width: u32,
+        height: u32,
+    ) {
+        let width = width.max(1);
+        let height = height.max(1);
+        let new_sample_count: u32 = (*graphics.antialiasing.read()).into();
+
+        let needs_recreate = self.size.width != width
+            || self.size.height != height
+            || self.sample_count != new_sample_count
+            || self.resolve_view.is_none();
+
+        if !needs_recreate {
+            return;
+        }
+
+        self.size = wgpu::Extent3d {
+            width,
+            height,
+            depth_or_array_layers: 1,
+        };
+        self.sample_count = new_sample_count;
+
+        let (resolve_texture, resolve_view) = Self::create_texture(
+            &graphics.device,
+            self.size,
+            self.format,
+            1,
+            "ui viewport resolve texture",
+        );
+
+        if let Some(texture_id) = self.texture_id {
+            graphics
+                .egui_renderer
+                .lock()
+                .renderer()
+                .update_egui_texture_from_wgpu_texture(
+                    &graphics.device,
+                    &resolve_view,
+                    wgpu::FilterMode::Linear,
+                    texture_id,
+                );
+        } else {
+            let texture_id = graphics
+                .egui_renderer
+                .lock()
+                .renderer()
+                .register_native_texture(
+                    &graphics.device,
+                    &resolve_view,
+                    wgpu::FilterMode::Linear,
+                );
+            self.texture_id = Some(texture_id);
+        }
+
+        self.resolve_texture = Some(resolve_texture);
+        self.resolve_view = Some(resolve_view);
+
+        if self.sample_count > 1 {
+            let (msaa_texture, msaa_view) = Self::create_texture(
+                &graphics.device,
+                self.size,
+                self.format,
+                self.sample_count,
+                "ui viewport msaa texture",
+            );
+            self.msaa_texture = Some(msaa_texture);
+            self.msaa_view = Some(msaa_view);
+        } else {
+            self.msaa_texture = None;
+            self.msaa_view = None;
+        }
+    }
+
+    pub fn render_to_texture(
+        &mut self,
+        graphics: Arc<SharedGraphicsContext>,
+        width: u32,
+        height: u32,
+        view_proj: Mat4,
+        inv_view_proj: Mat4,
+    ) {
+        self.recreate_targets_if_needed(&graphics, width, height);
+        self.update_camera_uniform(&graphics, view_proj, inv_view_proj, width, height);
+
+        let Some(resolve_view) = self.resolve_view.as_ref() else {
+            return;
+        };
+
+        let mut encoder = graphics
+            .device
+            .create_command_encoder(&wgpu::CommandEncoderDescriptor {
+                label: Some("ui viewport grid encoder"),
+            });
+
+        let (color_view, resolve_target) = if self.sample_count > 1 {
+            let Some(msaa_view) = self.msaa_view.as_ref() else {
+                return;
+            };
+            (msaa_view, Some(resolve_view))
+        } else {
+            (resolve_view, None)
+        };
+
+        {
+            let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
+                label: Some("ui viewport grid render pass"),
+                color_attachments: &[Some(wgpu::RenderPassColorAttachment {
+                    view: color_view,
+                    depth_slice: None,
+                    resolve_target,
+                    ops: wgpu::Operations {
+                        load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT),
+                        store: wgpu::StoreOp::Store,
+                    },
+                })],
+                depth_stencil_attachment: None,
+                timestamp_writes: None,
+                occlusion_query_set: None,
+            });
+
+            self.render(&mut render_pass);
+        }
+
+        graphics.queue.submit(std::iter::once(encoder.finish()));
+    }
+
+    pub fn render<'a>(&'a self, render_pass: &mut wgpu::RenderPass<'a>) {
+        render_pass.set_pipeline(&self.pipeline);
+        render_pass.set_bind_group(0, &self.camera_bind_group, &[]);
+        render_pass.draw(0..3, 0..1);
+    }
+}
+
+#[repr(C)]
+#[derive(Copy, Clone, Pod, Zeroable)]
+struct UIGridCameraUniform {
+    view_proj: [[f32; 4]; 4],
+    inv_view_proj: [[f32; 4]; 4],
+    viewport_size: [f32; 2],
+    _padding: [f32; 2],
 }
\ No newline at end of file
diff --git a/crates/eucalyptus-editor/src/editor/ui/shader/grid.wgsl b/crates/eucalyptus-editor/src/editor/ui/shader/grid.wgsl
new file mode 100644
index 0000000..f80262f
--- /dev/null
+++ b/crates/eucalyptus-editor/src/editor/ui/shader/grid.wgsl
@@ -0,0 +1,68 @@
+struct CameraUniform {
+    view_proj: mat4x4<f32>,
+    inv_view_proj: mat4x4<f32>,
+    viewport_size: vec2<f32>,
+    _padding: vec2<f32>,
+};
+
+@group(0) @binding(0)
+var<uniform> camera: CameraUniform;
+
+fn grid_alpha(pixel_uv: vec2<f32>, cell_size: f32, line_width: f32) -> f32 {
+    let uv = pixel_uv / cell_size;
+    let derivative = fwidth(uv) * line_width;
+    let grid = abs(fract(uv - 0.5) - 0.5) / derivative;
+    let l = min(grid.x, grid.y);
+    return 1.0 - clamp(l, 0.0, 1.0);
+}
+
+fn axis_alpha(v: f32, line_width: f32) -> f32 {
+    let d = abs(v);
+    let fw = max(fwidth(v), 1e-5) * line_width;
+    return 1.0 - smoothstep(0.0, fw, d);
+}
+
+@vertex
+fn vs_main(@builtin(vertex_index) vertex_index: u32) -> @builtin(position) vec4<f32> {
+    var positions = array<vec2<f32>, 3>(
+        vec2<f32>(-1.0, -1.0),
+        vec2<f32>(3.0, -1.0),
+        vec2<f32>(-1.0, 3.0),
+    );
+
+    let position = positions[vertex_index];
+    return vec4<f32>(position, 0.0, 1.0);
+}
+
+@fragment
+fn fs_main(@builtin(position) frag_pos: vec4<f32>) -> @location(0) vec4<f32> {
+    let ndc = vec2<f32>(
+        (frag_pos.x / camera.viewport_size.x) * 2.0 - 1.0,
+        1.0 - (frag_pos.y / camera.viewport_size.y) * 2.0,
+    );
+
+    let world = camera.inv_view_proj * vec4<f32>(ndc, 0.0, 1.0);
+    let world_pos = world.xy / world.w;
+
+    let fine = grid_alpha(world_pos, 10.0, 0.8);
+    let coarse = grid_alpha(world_pos, 100.0, 1.0);
+    let axis_x = axis_alpha(world_pos.y, 1.8);
+    let axis_y = axis_alpha(world_pos.x, 1.8);
+    let axis = max(axis_x, axis_y);
+
+    let fine_color = vec3<f32>(0.25, 0.25, 0.25);
+    let coarse_color = vec3<f32>(0.38, 0.38, 0.38);
+
+    var color = vec3<f32>(0.13, 0.13, 0.13);
+
+    var alpha = 0.0;
+    alpha = max(alpha, fine * 0.6);
+    color = mix(color, fine_color, fine * 0.6);
+    alpha = max(alpha, coarse);
+    color = mix(color, coarse_color, coarse);
+
+    color = mix(color, vec3<f32>(1.0, 1.0, 1.0), axis);
+    alpha = max(alpha, axis);
+
+    return vec4<f32>(color, alpha);
+}
\ No newline at end of file
diff --git a/crates/eucalyptus-editor/src/editor/ui/viewport.rs b/crates/eucalyptus-editor/src/editor/ui/viewport.rs
new file mode 100644
index 0000000..6bbc39b
--- /dev/null
+++ b/crates/eucalyptus-editor/src/editor/ui/viewport.rs
@@ -0,0 +1,96 @@
+use egui::Ui;
+use glam::Vec2;
+use crate::editor::{EditorTabDock, EditorTabDockDescriptor, EditorTabViewer};
+use crate::editor::page::EditorTabVisibility;
+
+pub struct UIViewport;
+
+impl EditorTabDock for UIViewport {
+    fn desc() -> EditorTabDockDescriptor {
+        EditorTabDockDescriptor {
+            id: "UI Viewport",
+            title: "UI Viewport".to_string(),
+            visibility: EditorTabVisibility::UIEditor,
+        }
+    }
+
+    fn display(viewer: &mut EditorTabViewer<'_>, ui: &mut Ui) {
+        let available_rect = ui.available_rect_before_wrap();
+        let available_size = available_rect.size();
+        let pixels_per_point = ui.ctx().pixels_per_point();
+
+        let desired_width = (available_size.x * pixels_per_point).max(1.0).round() as u32;
+        let desired_height = (available_size.y * pixels_per_point).max(1.0).round() as u32;
+
+        viewer
+            .ui_editor
+            .render(viewer.graphics.clone(), desired_width, desired_height);
+
+        if let Some(texture_id) = viewer.ui_editor.texture_id() {
+            let response = ui.add_sized(
+                [available_size.x, available_size.y],
+                egui::Image::new((texture_id, available_size)).sense(egui::Sense::drag()),
+            );
+
+            let ppp = ui.ctx().pixels_per_point();
+            let viewport_pixels = Vec2::new(available_size.x * ppp, available_size.y * ppp);
+
+            if response.hovered() {
+                let scroll_y = ui.ctx().input(|i| i.raw_scroll_delta.y);
+                if scroll_y.abs() > 0.0 {
+                    let zoom_delta = scroll_y * 0.0025;
+                    viewer.ui_editor.zoom_by(zoom_delta);
+                }
+            }
+
+            let is_middle_down = ui
+                .ctx()
+                .input(|i| i.pointer.button_down(egui::PointerButton::Middle));
+
+            if response.hovered() && is_middle_down {
+                let pointer_delta_points = ui.ctx().input(|i| i.pointer.delta());
+                if pointer_delta_points != egui::Vec2::ZERO {
+                    let delta_pixels = Vec2::new(
+                        pointer_delta_points.x * ppp,
+                        pointer_delta_points.y * ppp,
+                    );
+                    viewer.ui_editor.pan_by_pixels(delta_pixels);
+                }
+            }
+
+            let cursor_info = response.hover_pos().map(|hover_pos| {
+                let local = hover_pos - response.rect.min;
+                let pixel = Vec2::new(local.x * ppp, local.y * ppp);
+                viewer.ui_editor.world_from_screen_pixels(pixel, viewport_pixels)
+            });
+
+            let zoom_percent = viewer.ui_editor.zoom() * 100.0;
+            let hud = if let Some(world) = cursor_info {
+                format!(
+                    "Coords: ({:.1}, {:.1}) u | zoom: {:.0}%",
+                    world.x,
+                    world.y,
+                    zoom_percent
+                )
+            } else {
+                format!(
+                    "Coords: (-, -) u | zoom: {:.0}%",
+                    zoom_percent
+                )
+            };
+
+            let text_pos = response.rect.left_top() + egui::vec2(8.0, 8.0);
+            ui.painter().text(
+                text_pos,
+                egui::Align2::LEFT_TOP,
+                hud,
+                egui::TextStyle::Monospace.resolve(ui.style()),
+                egui::Color32::WHITE,
+            );
+        } else {
+            ui.centered_and_justified(|ui| {
+                ui.label("UI viewport texture is initialising...");
+            });
+        }
+    }
+}
\ No newline at end of file
diff --git a/crates/eucalyptus-editor/src/lib.rs b/crates/eucalyptus-editor/src/lib.rs
index dbfcf32..c304208 100644
--- a/crates/eucalyptus-editor/src/lib.rs
+++ b/crates/eucalyptus-editor/src/lib.rs
@@ -13,9 +13,7 @@ pub mod utils;
 pub use redback_runtime as runtime;
 
 use crate::editor::{
-    EditorTabRegistry, asset_viewer::AssetViewerDock, build_console::BuildConsoleDock,
-    dock::ConsoleDock, entity_list::EntityListDock, resource::ResourceInspectorDock,
-    viewport::ViewportDock,
+    EditorTabRegistry, asset_viewer::AssetViewerDock, build_console::BuildConsoleDock, dock::ConsoleDock, entity_list::EntityListDock, resource::ResourceInspectorDock, ui::viewport::UIViewport, viewport::ViewportDock
 };
 
 dropbear_engine::features! {
@@ -31,4 +29,5 @@ pub fn register_docks(registry: &mut EditorTabRegistry) {
     registry.register::<ResourceInspectorDock>();
     registry.register::<BuildConsoleDock>();
     registry.register::<ConsoleDock>();
+    registry.register::<UIViewport>();
 }
diff --git a/crates/eucalyptus-editor/src/signal.rs b/crates/eucalyptus-editor/src/signal.rs
index a4b672f..d82f4f0 100644
--- a/crates/eucalyptus-editor/src/signal.rs
+++ b/crates/eucalyptus-editor/src/signal.rs
@@ -511,8 +511,35 @@ impl SignalController for Editor {
                 Ok(())
             }
             Signal::AddComponent(entity, component) => {
-                let component = component.clone();
                 let registry = self.component_registry.clone();
+
+                let Some(component_id) = registry.id_for_component(component.as_ref()) else {
+                    warn!(
+                        "Failed to resolve component type for add request on entity {:?}",
+                        entity
+                    );
+                    self.signal = Signal::None;
+                    return Ok(());
+                };
+
+                if registry
+                    .find_entities_by_numeric_id(&self.world, component_id)
+                    .contains(&entity)
+                {
+                    let component_name = registry
+                        .get_descriptor_by_numeric_id(component_id)
+                        .map(|desc| desc.type_name.as_str())
+                        .unwrap_or("Unknown");
+
+                    warn!(
+                        "Entity {:?} already has component '{}'",
+                        entity,
+                        component_name
+                    );
+                    self.signal = Signal::None;
+                    return Ok(());
+                }
+
                 let graphics_clone = graphics.clone();
                 let init_future = async move {
                     let Some(loader_future) =
diff --git a/include/dropbear.h b/include/dropbear.h
index 375fd6b..3ffb323 100644
--- a/include/dropbear.h
+++ b/include/dropbear.h
@@ -448,7 +448,7 @@ typedef struct StringArray {
 
 typedef void* WorldPtr;
 
-int32_t dropbear_animation_animation_component_exists_for_entity(WorldPtr world, uint64_t entity, bool* out0);
+int32_t dropbear_animation_exists_for_entity(WorldPtr world, uint64_t entity, bool* out0);
 int32_t dropbear_animation_get_active_animation_index(WorldPtr world, uint64_t entity, int32_t* out0, bool* out0_present);
 int32_t dropbear_animation_get_available_animations(WorldPtr world, uint64_t entity, StringArray* out0);
 int32_t dropbear_animation_get_index_from_string(WorldPtr world, uint64_t entity, const char* name, int32_t* out0, bool* out0_present);