kitgit

tirbofish/dropbear · diff

a237d87 · Thribhu K

fix: animation header fix

Unverified

diff --git a/crates/eucalyptus-core/src/animation.rs b/crates/eucalyptus-core/src/animation.rs
new file mode 100644
index 0000000..8e18334
--- /dev/null
+++ b/crates/eucalyptus-core/src/animation.rs
@@ -0,0 +1,535 @@
+use std::sync::Arc;
+use egui::{CollapsingHeader, ComboBox, Ui};
+use hecs::{Entity, World};
+use dropbear_engine::animation::{AnimationComponent, AnimationSettings};
+use dropbear_engine::asset::ASSET_REGISTRY;
+use dropbear_engine::entity::MeshRenderer;
+use dropbear_engine::graphics::SharedGraphicsContext;
+use jni::objects::JObject;
+use jni::JNIEnv;
+use crate::component::{Component, ComponentDescriptor, InspectableComponent, SerializedComponent};
+use crate::ptr::WorldPtr;
+use crate::scripting::jni::utils::ToJObject;
+use crate::scripting::native::DropbearNativeError;
+use crate::scripting::result::DropbearNativeResult;
+
+#[typetag::serde]
+impl SerializedComponent for AnimationComponent {}
+
+impl Component for AnimationComponent {
+    type SerializedForm = Self;
+    type RequiredComponentTypes = (Self, );
+
+    fn descriptor() -> ComponentDescriptor {
+        ComponentDescriptor {
+            fqtn: "dropbear_engine::animation::AnimationComponent".to_string(),
+            type_name: "AnimationComponent".to_string(),
+            category: Some("Animation".to_string()),
+            description: Some("Animates a 3D MeshRenderer".to_string()),
+        }
+    }
+
+    fn init<'a>(
+        ser: &'a Self::SerializedForm,
+        _graphics: Arc<SharedGraphicsContext>,
+    ) -> crate::component::ComponentInitFuture<'a, Self> {
+        Box::pin(async move { Ok((ser.clone(), )) })
+    }
+
+    fn update_component(&mut self, world: &World, _physics: &mut crate::physics::PhysicsState, entity: Entity, dt: f32, graphics: Arc<SharedGraphicsContext>) {
+        let Ok(renderer) = world.get::<&MeshRenderer>(entity) else {
+            return;
+        };
+
+        let handle = renderer.model();
+        if handle.is_null() {
+            return;
+        }
+
+        let registry = ASSET_REGISTRY.read();
+        let Some(model) = registry.get_model(handle) else {
+            return;
+        };
+
+        self.update(dt, model);
+
+        self.prepare_gpu_resources(graphics.clone());
+    }
+
+    fn save(&self, _world: &World, _entity: Entity) -> Box<dyn SerializedComponent> {
+        Box::new(self.clone())
+    }
+}
+
+impl InspectableComponent for AnimationComponent {
+    fn inspect(&mut self, ui: &mut Ui, _graphics: Arc<SharedGraphicsContext>) {
+        CollapsingHeader::new("Animation").default_open(true).show(ui, |ui| {
+            let has_animations = !self.available_animations.is_empty();
+            let mut enabled = self.active_animation_index.is_some() && has_animations;
+
+            let mut selected_index = self
+                .active_animation_index
+                .unwrap_or(0)
+                .min(self.available_animations.len().saturating_sub(1));
+
+            let selected_label = if has_animations {
+                self.available_animations
+                    .get(selected_index)
+                    .map(String::as_str)
+                    .unwrap_or("Unnamed Animation")
+            } else {
+                "No Animations"
+            };
+
+            let mut selection_changed = false;
+            ComboBox::from_label("Animation")
+                .selected_text(selected_label)
+                .show_ui(ui, |ui| {
+                    for (index, name) in self.available_animations.iter().enumerate() {
+                        if ui.selectable_value(&mut selected_index, index, name).changed() {
+                            selection_changed = true;
+                        }
+                    }
+                });
+
+            if selection_changed && has_animations {
+                self.active_animation_index = Some(selected_index);
+                enabled = true;
+            }
+
+            ui.horizontal(|ui| {
+                ui.label("Active");
+
+                if ui.checkbox(&mut enabled, "Enable").changed() {
+                    self.active_animation_index = if enabled && has_animations {
+                        Some(selected_index)
+                    } else {
+                        None
+                    };
+                }
+            });
+
+            if has_animations {
+                let settings = self
+                    .animation_settings
+                    .entry(selected_index)
+                    .or_insert_with(|| AnimationSettings {
+                        time: self.time,
+                        speed: self.speed,
+                        looping: self.looping,
+                        is_playing: self.is_playing,
+                    });
+
+                ui.horizontal(|ui| {
+                    ui.label("Playing");
+                    ui.checkbox(&mut settings.is_playing, "");
+                });
+
+                ui.horizontal(|ui| {
+                    ui.label("Looping");
+                    ui.checkbox(&mut settings.looping, "");
+                });
+
+                ui.horizontal(|ui| {
+                    ui.label("Speed");
+                    ui.add(egui::DragValue::new(&mut settings.speed).speed(0.01).range(0.0..=10.0));
+                });
+
+                ui.horizontal(|ui| {
+                    ui.label("Start Time");
+                    ui.add(egui::DragValue::new(&mut settings.time).speed(0.01).range(0.0..=1_000_000.0));
+                    if ui.button("Reset").clicked() {
+                        settings.time = 0.0;
+                    }
+                });
+
+                if self.active_animation_index == Some(selected_index) {
+                    self.time = settings.time;
+                    self.speed = settings.speed;
+                    self.looping = settings.looping;
+                    self.is_playing = settings.is_playing;
+                }
+            }
+        });
+    }
+}
+
+fn collect_available_animations(
+    world: &World,
+    entity: Entity,
+    component: &AnimationComponent,
+) -> Vec<String> {
+    if !component.available_animations.is_empty() {
+        return component.available_animations.clone();
+    }
+
+    let Ok(renderer) = world.get::<&MeshRenderer>(entity) else {
+        return Vec::new();
+    };
+
+    let handle = renderer.model();
+    if handle.is_null() {
+        return Vec::new();
+    }
+
+    let registry = ASSET_REGISTRY.read();
+    let Some(model) = registry.get_model(handle) else {
+        return Vec::new();
+    };
+
+    model
+        .animations
+        .iter()
+        .map(|animation| animation.name.clone())
+        .collect()
+}
+
+#[dropbear_macro::export(
+    kotlin(class = "com.dropbear.animation.AnimationComponentNative", func = "animationComponentExistsForEntity"),
+    c
+)]
+fn animation_component_exists_for_entity(
+    #[dropbear_macro::define(WorldPtr)]
+    world: &World,
+    #[dropbear_macro::entity]
+    entity: Entity,
+) -> DropbearNativeResult<bool> {
+    Ok(world.get::<&AnimationComponent>(entity).is_ok())
+}
+
+#[dropbear_macro::export(
+    kotlin(class = "com.dropbear.animation.AnimationComponentNative", func = "getActiveAnimationIndex"),
+    c
+)]
+fn get_active_animation_index(
+    #[dropbear_macro::define(WorldPtr)]
+    world: &World,
+    #[dropbear_macro::entity]
+    entity: Entity,
+) -> DropbearNativeResult<Option<i32>> {
+    let component = world
+        .get::<&AnimationComponent>(entity)
+        .map_err(|_| DropbearNativeError::MissingComponent)?;
+    Ok(component.active_animation_index.map(|index| index as i32))
+}
+
+#[dropbear_macro::export(
+    kotlin(class = "com.dropbear.animation.AnimationComponentNative", func = "setActiveAnimationIndex"),
+    c
+)]
+fn set_active_animation_index(
+    #[dropbear_macro::define(WorldPtr)]
+    world: &mut World,
+    #[dropbear_macro::entity]
+    entity: Entity,
+    index: &Option<i32>,
+) -> DropbearNativeResult<()> {
+    let mut component = world
+        .get::<&mut AnimationComponent>(entity)
+        .map_err(|_| DropbearNativeError::MissingComponent)?;
+
+    let index = match index {
+        Some(value) if *value >= 0 => Some(*value as usize),
+        Some(_) => return Err(DropbearNativeError::InvalidArgument),
+        None => None,
+    };
+
+    if let Some(value) = index {
+        if !component.available_animations.is_empty()
+            && value >= component.available_animations.len()
+        {
+            return Err(DropbearNativeError::InvalidArgument);
+        }
+    }
+
+    component.active_animation_index = index;
+    Ok(())
+}
+
+#[dropbear_macro::export(
+    kotlin(class = "com.dropbear.animation.AnimationComponentNative", func = "getTime"),
+    c
+)]
+fn get_time(
+    #[dropbear_macro::define(WorldPtr)]
+    world: &World,
+    #[dropbear_macro::entity]
+    entity: Entity,
+) -> DropbearNativeResult<f64> {
+    let component = world
+        .get::<&AnimationComponent>(entity)
+        .map_err(|_| DropbearNativeError::MissingComponent)?;
+
+    if let Some(index) = component.active_animation_index {
+        if let Some(settings) = component.animation_settings.get(&index) {
+            return Ok(settings.time as f64);
+        }
+    }
+
+    Ok(component.time as f64)
+}
+
+#[dropbear_macro::export(
+    kotlin(class = "com.dropbear.animation.AnimationComponentNative", func = "setTime"),
+    c
+)]
+fn set_time(
+    #[dropbear_macro::define(WorldPtr)]
+    world: &mut World,
+    #[dropbear_macro::entity]
+    entity: Entity,
+    value: f64,
+) -> DropbearNativeResult<()> {
+    let mut component = world
+        .get::<&mut AnimationComponent>(entity)
+        .map_err(|_| DropbearNativeError::MissingComponent)?;
+
+    component.time = value as f32;
+
+    if let Some(index) = component.active_animation_index {
+        let (time, speed, looping, is_playing) = (
+            component.time,
+            component.speed,
+            component.looping,
+            component.is_playing,
+        );
+        let settings = component
+            .animation_settings
+            .entry(index)
+            .or_insert_with(|| AnimationSettings {
+                time,
+                speed,
+                looping,
+                is_playing,
+            });
+        settings.time = time;
+    }
+
+    Ok(())
+}
+
+#[dropbear_macro::export(
+    kotlin(class = "com.dropbear.animation.AnimationComponentNative", func = "getSpeed"),
+    c
+)]
+fn get_speed(
+    #[dropbear_macro::define(WorldPtr)]
+    world: &World,
+    #[dropbear_macro::entity]
+    entity: Entity,
+) -> DropbearNativeResult<f64> {
+    let component = world
+        .get::<&AnimationComponent>(entity)
+        .map_err(|_| DropbearNativeError::MissingComponent)?;
+
+    if let Some(index) = component.active_animation_index {
+        if let Some(settings) = component.animation_settings.get(&index) {
+            return Ok(settings.speed as f64);
+        }
+    }
+
+    Ok(component.speed as f64)
+}
+
+#[dropbear_macro::export(
+    kotlin(class = "com.dropbear.animation.AnimationComponentNative", func = "setSpeed"),
+    c
+)]
+fn set_speed(
+    #[dropbear_macro::define(WorldPtr)]
+    world: &mut World,
+    #[dropbear_macro::entity]
+    entity: Entity,
+    value: f64,
+) -> DropbearNativeResult<()> {
+    let mut component = world
+        .get::<&mut AnimationComponent>(entity)
+        .map_err(|_| DropbearNativeError::MissingComponent)?;
+
+    component.speed = value as f32;
+
+    if let Some(index) = component.active_animation_index {
+        let (time, speed, looping, is_playing) = (
+            component.time,
+            component.speed,
+            component.looping,
+            component.is_playing,
+        );
+        let settings = component
+            .animation_settings
+            .entry(index)
+            .or_insert_with(|| AnimationSettings {
+                time,
+                speed,
+                looping,
+                is_playing,
+            });
+        settings.speed = speed;
+    }
+
+    Ok(())
+}
+
+#[dropbear_macro::export(
+    kotlin(class = "com.dropbear.animation.AnimationComponentNative", func = "getLooping"),
+    c
+)]
+fn get_looping(
+    #[dropbear_macro::define(WorldPtr)]
+    world: &World,
+    #[dropbear_macro::entity]
+    entity: Entity,
+) -> DropbearNativeResult<bool> {
+    let component = world
+        .get::<&AnimationComponent>(entity)
+        .map_err(|_| DropbearNativeError::MissingComponent)?;
+
+    if let Some(index) = component.active_animation_index {
+        if let Some(settings) = component.animation_settings.get(&index) {
+            return Ok(settings.looping);
+        }
+    }
+
+    Ok(component.looping)
+}
+
+#[dropbear_macro::export(
+    kotlin(class = "com.dropbear.animation.AnimationComponentNative", func = "setLooping"),
+    c
+)]
+fn set_looping(
+    #[dropbear_macro::define(WorldPtr)]
+    world: &mut World,
+    #[dropbear_macro::entity]
+    entity: Entity,
+    value: bool,
+) -> DropbearNativeResult<()> {
+    let mut component = world
+        .get::<&mut AnimationComponent>(entity)
+        .map_err(|_| DropbearNativeError::MissingComponent)?;
+
+    component.looping = value;
+
+    if let Some(index) = component.active_animation_index {
+        let (time, speed, looping, is_playing) = (
+            component.time,
+            component.speed,
+            component.looping,
+            component.is_playing,
+        );
+        let settings = component
+            .animation_settings
+            .entry(index)
+            .or_insert_with(|| AnimationSettings {
+                time,
+                speed,
+                looping,
+                is_playing,
+            });
+        settings.looping = value;
+    }
+
+    Ok(())
+}
+
+#[dropbear_macro::export(
+    kotlin(class = "com.dropbear.animation.AnimationComponentNative", func = "getIsPlaying"),
+    c
+)]
+fn get_is_playing(
+    #[dropbear_macro::define(WorldPtr)]
+    world: &World,
+    #[dropbear_macro::entity]
+    entity: Entity,
+) -> DropbearNativeResult<bool> {
+    let component = world
+        .get::<&AnimationComponent>(entity)
+        .map_err(|_| DropbearNativeError::MissingComponent)?;
+
+    if let Some(index) = component.active_animation_index {
+        if let Some(settings) = component.animation_settings.get(&index) {
+            return Ok(settings.is_playing);
+        }
+    }
+
+    Ok(component.is_playing)
+}
+
+#[dropbear_macro::export(
+    kotlin(class = "com.dropbear.animation.AnimationComponentNative", func = "setIsPlaying"),
+    c
+)]
+fn set_is_playing(
+    #[dropbear_macro::define(WorldPtr)]
+    world: &mut World,
+    #[dropbear_macro::entity]
+    entity: Entity,
+    value: bool,
+) -> DropbearNativeResult<()> {
+    let mut component = world
+        .get::<&mut AnimationComponent>(entity)
+        .map_err(|_| DropbearNativeError::MissingComponent)?;
+
+    component.is_playing = value;
+
+    if let Some(index) = component.active_animation_index {
+        let (time, speed, looping, is_playing) = (
+            component.time,
+            component.speed,
+            component.looping,
+            component.is_playing,
+        );
+        let settings = component
+            .animation_settings
+            .entry(index)
+            .or_insert_with(|| AnimationSettings {
+                time,
+                speed,
+                looping,
+                is_playing,
+            });
+        settings.is_playing = value;
+    }
+
+    Ok(())
+}
+
+#[dropbear_macro::export(
+    kotlin(class = "com.dropbear.animation.AnimationComponentNative", func = "getIndexFromString"),
+    c
+)]
+fn get_index_from_string(
+    #[dropbear_macro::define(WorldPtr)]
+    world: &World,
+    #[dropbear_macro::entity]
+    entity: Entity,
+    name: String,
+) -> DropbearNativeResult<Option<i32>> {
+    let component = world
+        .get::<&AnimationComponent>(entity)
+        .map_err(|_| DropbearNativeError::MissingComponent)?;
+
+    Ok(component.available_animations.iter().enumerate().find_map(|(i, l)| {
+        if *l == name {
+            Some(i as i32)
+        } else {
+            None
+        }
+    }))
+}
+
+#[dropbear_macro::export(
+    kotlin(class = "com.dropbear.animation.AnimationComponentNative", func = "getAvailableAnimations"),
+    c
+)]
+fn get_available_animations(
+    #[dropbear_macro::define(WorldPtr)]
+    world: &World,
+    #[dropbear_macro::entity]
+    entity: Entity,
+) -> DropbearNativeResult<Vec<String>> {
+    let component = world
+        .get::<&AnimationComponent>(entity)
+        .map_err(|_| DropbearNativeError::MissingComponent)?;
+    Ok(collect_available_animations(world, entity, &component))
+}
\ No newline at end of file
diff --git a/crates/eucalyptus-core/src/animation/mod.rs b/crates/eucalyptus-core/src/animation/mod.rs
deleted file mode 100644
index 07f2a19..0000000
--- a/crates/eucalyptus-core/src/animation/mod.rs
+++ /dev/null
@@ -1,546 +0,0 @@
-use std::sync::Arc;
-use egui::{CollapsingHeader, ComboBox, Ui};
-use hecs::{Entity, World};
-use dropbear_engine::animation::{AnimationComponent, AnimationSettings};
-use dropbear_engine::asset::ASSET_REGISTRY;
-use dropbear_engine::entity::MeshRenderer;
-use dropbear_engine::graphics::SharedGraphicsContext;
-use jni::objects::JObject;
-use jni::JNIEnv;
-use crate::component::{Component, ComponentDescriptor, InspectableComponent, SerializedComponent};
-use crate::ptr::WorldPtr;
-use crate::scripting::jni::utils::ToJObject;
-use crate::scripting::native::DropbearNativeError;
-use crate::scripting::result::DropbearNativeResult;
-
-#[typetag::serde]
-impl SerializedComponent for AnimationComponent {}
-
-impl Component for AnimationComponent {
-    type SerializedForm = Self;
-    type RequiredComponentTypes = (Self, );
-
-    fn descriptor() -> ComponentDescriptor {
-        ComponentDescriptor {
-            fqtn: "dropbear_engine::animation::AnimationComponent".to_string(),
-            type_name: "AnimationComponent".to_string(),
-            category: Some("Animation".to_string()),
-            description: Some("Animates a 3D MeshRenderer".to_string()),
-        }
-    }
-
-    fn init<'a>(
-        ser: &'a Self::SerializedForm,
-        _graphics: Arc<SharedGraphicsContext>,
-    ) -> crate::component::ComponentInitFuture<'a, Self> {
-        Box::pin(async move { Ok((ser.clone(), )) })
-    }
-
-    fn update_component(&mut self, world: &World, _physics: &mut crate::physics::PhysicsState, entity: Entity, dt: f32, graphics: Arc<SharedGraphicsContext>) {
-        let Ok(renderer) = world.get::<&MeshRenderer>(entity) else {
-            return;
-        };
-
-        let handle = renderer.model();
-        if handle.is_null() {
-            return;
-        }
-
-        let registry = ASSET_REGISTRY.read();
-        let Some(model) = registry.get_model(handle) else {
-            return;
-        };
-
-        self.update(dt, model);
-
-        self.prepare_gpu_resources(graphics.clone());
-    }
-
-    fn save(&self, _world: &World, _entity: Entity) -> Box<dyn SerializedComponent> {
-        Box::new(self.clone())
-    }
-}
-
-impl InspectableComponent for AnimationComponent {
-    fn inspect(&mut self, ui: &mut Ui, _graphics: Arc<SharedGraphicsContext>) {
-        CollapsingHeader::new("Animation").default_open(true).show(ui, |ui| {
-            let has_animations = !self.available_animations.is_empty();
-            let mut enabled = self.active_animation_index.is_some() && has_animations;
-
-            let mut selected_index = self
-                .active_animation_index
-                .unwrap_or(0)
-                .min(self.available_animations.len().saturating_sub(1));
-
-            let selected_label = if has_animations {
-                self.available_animations
-                    .get(selected_index)
-                    .map(String::as_str)
-                    .unwrap_or("Unnamed Animation")
-            } else {
-                "No Animations"
-            };
-
-            let mut selection_changed = false;
-            ComboBox::from_label("Animation")
-                .selected_text(selected_label)
-                .show_ui(ui, |ui| {
-                    for (index, name) in self.available_animations.iter().enumerate() {
-                        if ui.selectable_value(&mut selected_index, index, name).changed() {
-                            selection_changed = true;
-                        }
-                    }
-                });
-
-            if selection_changed && has_animations {
-                self.active_animation_index = Some(selected_index);
-                enabled = true;
-            }
-
-            ui.horizontal(|ui| {
-                ui.label("Active");
-
-                if ui.checkbox(&mut enabled, "Enable").changed() {
-                    self.active_animation_index = if enabled && has_animations {
-                        Some(selected_index)
-                    } else {
-                        None
-                    };
-                }
-            });
-
-            if has_animations {
-                let settings = self
-                    .animation_settings
-                    .entry(selected_index)
-                    .or_insert_with(|| AnimationSettings {
-                        time: self.time,
-                        speed: self.speed,
-                        looping: self.looping,
-                        is_playing: self.is_playing,
-                    });
-
-                ui.horizontal(|ui| {
-                    ui.label("Playing");
-                    ui.checkbox(&mut settings.is_playing, "");
-                });
-
-                ui.horizontal(|ui| {
-                    ui.label("Looping");
-                    ui.checkbox(&mut settings.looping, "");
-                });
-
-                ui.horizontal(|ui| {
-                    ui.label("Speed");
-                    ui.add(egui::DragValue::new(&mut settings.speed).speed(0.01).range(0.0..=10.0));
-                });
-
-                ui.horizontal(|ui| {
-                    ui.label("Start Time");
-                    ui.add(egui::DragValue::new(&mut settings.time).speed(0.01).range(0.0..=1_000_000.0));
-                    if ui.button("Reset").clicked() {
-                        settings.time = 0.0;
-                    }
-                });
-
-                if self.active_animation_index == Some(selected_index) {
-                    self.time = settings.time;
-                    self.speed = settings.speed;
-                    self.looping = settings.looping;
-                    self.is_playing = settings.is_playing;
-                }
-            }
-        });
-    }
-}
-
-fn collect_available_animations(
-    world: &World,
-    entity: Entity,
-    component: &AnimationComponent,
-) -> Vec<String> {
-    if !component.available_animations.is_empty() {
-        return component.available_animations.clone();
-    }
-
-    let Ok(renderer) = world.get::<&MeshRenderer>(entity) else {
-        return Vec::new();
-    };
-
-    let handle = renderer.model();
-    if handle.is_null() {
-        return Vec::new();
-    }
-
-    let registry = ASSET_REGISTRY.read();
-    let Some(model) = registry.get_model(handle) else {
-        return Vec::new();
-    };
-
-    model
-        .animations
-        .iter()
-        .map(|animation| animation.name.clone())
-        .collect()
-}
-
-struct NStringArray {
-    values: Vec<String>,
-}
-
-impl ToJObject for NStringArray {
-    fn to_jobject<'a>(&self, env: &mut JNIEnv<'a>) -> DropbearNativeResult<JObject<'a>> {
-        let array = env
-            .new_object_array(self.values.len() as i32, "java/lang/String", JObject::null())
-            .map_err(|_| DropbearNativeError::JNIFailedToCreateObject)?;
-
-        for (index, value) in self.values.iter().enumerate() {
-            let jstring = env
-                .new_string(value)
-                .map_err(|_| DropbearNativeError::JNIFailedToCreateObject)?;
-            env.set_object_array_element(&array, index as i32, JObject::from(jstring))
-                .map_err(|_| DropbearNativeError::JNIFailedToCreateObject)?;
-        }
-
-        Ok(JObject::from(array))
-    }
-}
-
-#[dropbear_macro::export(
-    kotlin(class = "com.dropbear.animation.AnimationComponentNative", func = "animationComponentExistsForEntity")
-)]
-fn animation_component_exists_for_entity(
-    #[dropbear_macro::define(WorldPtr)]
-    world: &World,
-    #[dropbear_macro::entity]
-    entity: Entity,
-) -> DropbearNativeResult<bool> {
-    Ok(world.get::<&AnimationComponent>(entity).is_ok())
-}
-
-#[dropbear_macro::export(
-    kotlin(class = "com.dropbear.animation.AnimationComponentNative", func = "getActiveAnimationIndex")
-)]
-fn get_active_animation_index(
-    #[dropbear_macro::define(WorldPtr)]
-    world: &World,
-    #[dropbear_macro::entity]
-    entity: Entity,
-) -> DropbearNativeResult<Option<i32>> {
-    let component = world
-        .get::<&AnimationComponent>(entity)
-        .map_err(|_| DropbearNativeError::MissingComponent)?;
-    Ok(component.active_animation_index.map(|index| index as i32))
-}
-
-#[dropbear_macro::export(
-    kotlin(class = "com.dropbear.animation.AnimationComponentNative", func = "setActiveAnimationIndex")
-)]
-fn set_active_animation_index(
-    #[dropbear_macro::define(WorldPtr)]
-    world: &mut World,
-    #[dropbear_macro::entity]
-    entity: Entity,
-    index: Option<i32>,
-) -> DropbearNativeResult<()> {
-    let mut component = world
-        .get::<&mut AnimationComponent>(entity)
-        .map_err(|_| DropbearNativeError::MissingComponent)?;
-
-    let index = match index {
-        Some(value) if value >= 0 => Some(value as usize),
-        Some(_) => return Err(DropbearNativeError::InvalidArgument),
-        None => None,
-    };
-
-    if let Some(value) = index {
-        if !component.available_animations.is_empty()
-            && value >= component.available_animations.len()
-        {
-            return Err(DropbearNativeError::InvalidArgument);
-        }
-    }
-
-    component.active_animation_index = index;
-    Ok(())
-}
-
-#[dropbear_macro::export(
-    kotlin(class = "com.dropbear.animation.AnimationComponentNative", func = "getTime")
-)]
-fn get_time(
-    #[dropbear_macro::define(WorldPtr)]
-    world: &World,
-    #[dropbear_macro::entity]
-    entity: Entity,
-) -> DropbearNativeResult<f64> {
-    let component = world
-        .get::<&AnimationComponent>(entity)
-        .map_err(|_| DropbearNativeError::MissingComponent)?;
-
-    if let Some(index) = component.active_animation_index {
-        if let Some(settings) = component.animation_settings.get(&index) {
-            return Ok(settings.time as f64);
-        }
-    }
-
-    Ok(component.time as f64)
-}
-
-#[dropbear_macro::export(
-    kotlin(class = "com.dropbear.animation.AnimationComponentNative", func = "setTime")
-)]
-fn set_time(
-    #[dropbear_macro::define(WorldPtr)]
-    world: &mut World,
-    #[dropbear_macro::entity]
-    entity: Entity,
-    value: f64,
-) -> DropbearNativeResult<()> {
-    let mut component = world
-        .get::<&mut AnimationComponent>(entity)
-        .map_err(|_| DropbearNativeError::MissingComponent)?;
-
-    component.time = value as f32;
-
-    if let Some(index) = component.active_animation_index {
-        let (time, speed, looping, is_playing) = (
-            component.time,
-            component.speed,
-            component.looping,
-            component.is_playing,
-        );
-        let settings = component
-            .animation_settings
-            .entry(index)
-            .or_insert_with(|| AnimationSettings {
-                time,
-                speed,
-                looping,
-                is_playing,
-            });
-        settings.time = time;
-    }
-
-    Ok(())
-}
-
-#[dropbear_macro::export(
-    kotlin(class = "com.dropbear.animation.AnimationComponentNative", func = "getSpeed")
-)]
-fn get_speed(
-    #[dropbear_macro::define(WorldPtr)]
-    world: &World,
-    #[dropbear_macro::entity]
-    entity: Entity,
-) -> DropbearNativeResult<f64> {
-    let component = world
-        .get::<&AnimationComponent>(entity)
-        .map_err(|_| DropbearNativeError::MissingComponent)?;
-
-    if let Some(index) = component.active_animation_index {
-        if let Some(settings) = component.animation_settings.get(&index) {
-            return Ok(settings.speed as f64);
-        }
-    }
-
-    Ok(component.speed as f64)
-}
-
-#[dropbear_macro::export(
-    kotlin(class = "com.dropbear.animation.AnimationComponentNative", func = "setSpeed")
-)]
-fn set_speed(
-    #[dropbear_macro::define(WorldPtr)]
-    world: &mut World,
-    #[dropbear_macro::entity]
-    entity: Entity,
-    value: f64,
-) -> DropbearNativeResult<()> {
-    let mut component = world
-        .get::<&mut AnimationComponent>(entity)
-        .map_err(|_| DropbearNativeError::MissingComponent)?;
-
-    component.speed = value as f32;
-
-    if let Some(index) = component.active_animation_index {
-        let (time, speed, looping, is_playing) = (
-            component.time,
-            component.speed,
-            component.looping,
-            component.is_playing,
-        );
-        let settings = component
-            .animation_settings
-            .entry(index)
-            .or_insert_with(|| AnimationSettings {
-                time,
-                speed,
-                looping,
-                is_playing,
-            });
-        settings.speed = speed;
-    }
-
-    Ok(())
-}
-
-#[dropbear_macro::export(
-    kotlin(class = "com.dropbear.animation.AnimationComponentNative", func = "getLooping")
-)]
-fn get_looping(
-    #[dropbear_macro::define(WorldPtr)]
-    world: &World,
-    #[dropbear_macro::entity]
-    entity: Entity,
-) -> DropbearNativeResult<bool> {
-    let component = world
-        .get::<&AnimationComponent>(entity)
-        .map_err(|_| DropbearNativeError::MissingComponent)?;
-
-    if let Some(index) = component.active_animation_index {
-        if let Some(settings) = component.animation_settings.get(&index) {
-            return Ok(settings.looping);
-        }
-    }
-
-    Ok(component.looping)
-}
-
-#[dropbear_macro::export(
-    kotlin(class = "com.dropbear.animation.AnimationComponentNative", func = "setLooping")
-)]
-fn set_looping(
-    #[dropbear_macro::define(WorldPtr)]
-    world: &mut World,
-    #[dropbear_macro::entity]
-    entity: Entity,
-    value: bool,
-) -> DropbearNativeResult<()> {
-    let mut component = world
-        .get::<&mut AnimationComponent>(entity)
-        .map_err(|_| DropbearNativeError::MissingComponent)?;
-
-    component.looping = value;
-
-    if let Some(index) = component.active_animation_index {
-        let (time, speed, looping, is_playing) = (
-            component.time,
-            component.speed,
-            component.looping,
-            component.is_playing,
-        );
-        let settings = component
-            .animation_settings
-            .entry(index)
-            .or_insert_with(|| AnimationSettings {
-                time,
-                speed,
-                looping,
-                is_playing,
-            });
-        settings.looping = value;
-    }
-
-    Ok(())
-}
-
-#[dropbear_macro::export(
-    kotlin(class = "com.dropbear.animation.AnimationComponentNative", func = "getIsPlaying")
-)]
-fn get_is_playing(
-    #[dropbear_macro::define(WorldPtr)]
-    world: &World,
-    #[dropbear_macro::entity]
-    entity: Entity,
-) -> DropbearNativeResult<bool> {
-    let component = world
-        .get::<&AnimationComponent>(entity)
-        .map_err(|_| DropbearNativeError::MissingComponent)?;
-
-    if let Some(index) = component.active_animation_index {
-        if let Some(settings) = component.animation_settings.get(&index) {
-            return Ok(settings.is_playing);
-        }
-    }
-
-    Ok(component.is_playing)
-}
-
-#[dropbear_macro::export(
-    kotlin(class = "com.dropbear.animation.AnimationComponentNative", func = "setIsPlaying")
-)]
-fn set_is_playing(
-    #[dropbear_macro::define(WorldPtr)]
-    world: &mut World,
-    #[dropbear_macro::entity]
-    entity: Entity,
-    value: bool,
-) -> DropbearNativeResult<()> {
-    let mut component = world
-        .get::<&mut AnimationComponent>(entity)
-        .map_err(|_| DropbearNativeError::MissingComponent)?;
-
-    component.is_playing = value;
-
-    if let Some(index) = component.active_animation_index {
-        let (time, speed, looping, is_playing) = (
-            component.time,
-            component.speed,
-            component.looping,
-            component.is_playing,
-        );
-        let settings = component
-            .animation_settings
-            .entry(index)
-            .or_insert_with(|| AnimationSettings {
-                time,
-                speed,
-                looping,
-                is_playing,
-            });
-        settings.is_playing = value;
-    }
-
-    Ok(())
-}
-
-#[dropbear_macro::export(
-    kotlin(class = "com.dropbear.animation.AnimationComponentNative", func = "getIndexFromString")
-)]
-fn get_index_from_string(
-    #[dropbear_macro::define(WorldPtr)]
-    world: &World,
-    #[dropbear_macro::entity]
-    entity: Entity,
-    name: String,
-) -> DropbearNativeResult<Option<i32>> {
-    let component = world
-        .get::<&AnimationComponent>(entity)
-        .map_err(|_| DropbearNativeError::MissingComponent)?;
-
-    Ok(component.available_animations.iter().enumerate().find_map(|(i, l)| {
-        if *l == name {
-            Some(i as i32)
-        } else {
-            None
-        }
-    }))
-}
-
-#[dropbear_macro::export(
-    kotlin(class = "com.dropbear.animation.AnimationComponentNative", func = "getAvailableAnimations")
-)]
-fn get_available_animations(
-    #[dropbear_macro::define(WorldPtr)]
-    world: &World,
-    #[dropbear_macro::entity]
-    entity: Entity,
-) -> DropbearNativeResult<NStringArray> {
-    let component = world
-        .get::<&AnimationComponent>(entity)
-        .map_err(|_| DropbearNativeError::MissingComponent)?;
-    Ok(NStringArray {
-        values: collect_available_animations(world, entity, &component),
-    })
-}
\ No newline at end of file
diff --git a/crates/eucalyptus-core/src/scripting/jni/primitives.rs b/crates/eucalyptus-core/src/scripting/jni/primitives.rs
index 58c9b70..e4bb252 100644
--- a/crates/eucalyptus-core/src/scripting/jni/primitives.rs
+++ b/crates/eucalyptus-core/src/scripting/jni/primitives.rs
@@ -162,4 +162,11 @@ impl ToJObject for &[u64] {
         env.set_long_array_region(&array, 0, &buf)?;
         Ok(JObject::from(array))
     }
+}
+
+impl ToJObject for String {
+    fn to_jobject<'a>(&self, env: &mut JNIEnv<'a>) -> DropbearNativeResult<JObject<'a>> {
+        let result = JObject::from(env.new_string(self)?);
+        Ok(result)
+    }
 }
\ No newline at end of file
diff --git a/crates/goanna-gen/src/lib.rs b/crates/goanna-gen/src/lib.rs
index 7bd60b5..b5d628a 100644
--- a/crates/goanna-gen/src/lib.rs
+++ b/crates/goanna-gen/src/lib.rs
@@ -734,6 +734,17 @@ fn is_opaque_ptr_name(name: &str) -> bool {
     name.ends_with("Ptr")
 }
 
+fn emit_string_typedef(
+    emitted: &mut std::collections::HashSet<String>,
+    out: &mut String,
+) {
+    if emitted.contains("String") {
+        return;
+    }
+    out.push_str("typedef char* String;\n\n");
+    emitted.insert("String".to_string());
+}
+
 fn emit_structs_recursive(
     name: &str,
     structs: &std::collections::HashMap<String, StructDef>,
@@ -750,6 +761,11 @@ fn emit_structs_recursive(
         return;
     }
 
+    if name == "String" {
+        emit_string_typedef(emitted, out);
+        return;
+    }
+
     if let Some(def) = structs.get(name) {
         for field in &def.fields {
             emit_field_type_deps(&field.ty, structs, emitted, out);
@@ -830,7 +846,9 @@ fn emit_array_struct(
     }
 
     if !emitted.contains(elem) {
-        if structs.contains_key(elem) {
+        if elem == "String" {
+            emit_string_typedef(emitted, out);
+        } else if structs.contains_key(elem) {
             emit_structs_recursive(elem, structs, emitted, out);
         } else {
             out.push_str(&format!("typedef struct {} {};// opaque\n\n", elem, elem));
diff --git a/include/dropbear.h b/include/dropbear.h
index 1d988fe..8dbc3f7 100644
--- a/include/dropbear.h
+++ b/include/dropbear.h
@@ -426,8 +426,29 @@ typedef struct RigidBodyContext {
 
 typedef void* SceneLoaderPtr;
 
+typedef char* String;
+
+typedef struct StringArray {
+    String* values;
+    size_t length;
+    size_t capacity;
+} StringArray;
+
 typedef void* WorldPtr;
 
+int32_t dropbear_animation_animation_component_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);
+int32_t dropbear_animation_get_is_playing(WorldPtr world, uint64_t entity, bool* out0);
+int32_t dropbear_animation_get_looping(WorldPtr world, uint64_t entity, bool* out0);
+int32_t dropbear_animation_get_speed(WorldPtr world, uint64_t entity, double* out0);
+int32_t dropbear_animation_get_time(WorldPtr world, uint64_t entity, double* out0);
+int32_t dropbear_animation_set_active_animation_index(WorldPtr world, uint64_t entity, const const int32_t** index);
+int32_t dropbear_animation_set_is_playing(WorldPtr world, uint64_t entity, bool value);
+int32_t dropbear_animation_set_looping(WorldPtr world, uint64_t entity, bool value);
+int32_t dropbear_animation_set_speed(WorldPtr world, uint64_t entity, double value);
+int32_t dropbear_animation_set_time(WorldPtr world, uint64_t entity, double value);
 int32_t dropbear_asset_model_get_animations(AssetRegistryPtr asset, uint64_t model_handle, NAnimationArray* out0);
 int32_t dropbear_asset_model_get_label(AssetRegistryPtr asset, uint64_t model_handle, char** out0);
 int32_t dropbear_asset_model_get_materials(AssetRegistryPtr asset, uint64_t model_handle, NMaterialArray* out0);