kitgit

tirbofish/dropbear · diff

5eb186d · Thribhu K

fix: ensured speed was constant with deltaTime (double FPS meant double speed, not expected). feature: complete the rest of buttons types, more testing needed, still doesnt **really** work Unverified

diff --git a/Cargo.toml b/Cargo.toml
index 8d56500..dfaefea 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -77,6 +77,7 @@ yakui = { git = "https://github.com/SecondHalfGames/yakui", rev = "ddf7614" }
 yakui-wgpu = { git = "https://github.com/SecondHalfGames/yakui", rev = "ddf7614" }
 thiserror = "2.0"
 tempfile = "3.24"
+combine = "4.6"
 
 [workspace.dependencies.image]
 version = "0.25"
diff --git a/crates/dropbear-engine/src/camera.rs b/crates/dropbear-engine/src/camera.rs
index 325733d..423883a 100644
--- a/crates/dropbear-engine/src/camera.rs
+++ b/crates/dropbear-engine/src/camera.rs
@@ -240,43 +240,43 @@ impl Camera {
         self.uniform.view_proj = mvp.as_mat4().to_cols_array_2d();
     }
 
-    pub fn move_forwards(&mut self) {
+    pub fn move_forwards(&mut self, dt: f32) {
         let forward = (self.target - self.eye).normalize();
-        self.eye += forward * self.settings.speed;
-        self.target += forward * self.settings.speed;
+        self.eye += forward * self.settings.speed * dt as f64;
+        self.target += forward * self.settings.speed * dt as f64;
     }
 
-    pub fn move_back(&mut self) {
+    pub fn move_back(&mut self, dt: f32) {
         let forward = (self.target - self.eye).normalize();
-        self.eye -= forward * self.settings.speed;
-        self.target -= forward * self.settings.speed;
+        self.eye -= forward * self.settings.speed * dt as f64;
+        self.target -= forward * self.settings.speed * dt as f64;
     }
 
-    pub fn move_right(&mut self) {
+    pub fn move_right(&mut self, dt: f32) {
         let forward = (self.target - self.eye).normalize();
         // LH: right = up.cross(forward)
         let right = self.up.cross(forward).normalize();
-        self.eye += right * self.settings.speed;
-        self.target += right * self.settings.speed;
+        self.eye += right * self.settings.speed * dt as f64;
+        self.target += right * self.settings.speed * dt as f64;
     }
 
-    pub fn move_left(&mut self) {
+    pub fn move_left(&mut self, dt: f32) {
         let forward = (self.target - self.eye).normalize();
         let right = self.up.cross(forward).normalize();
-        self.eye -= right * self.settings.speed;
-        self.target -= right * self.settings.speed;
+        self.eye -= right * self.settings.speed * dt as f64;
+        self.target -= right * self.settings.speed * dt as f64;
     }
 
-    pub fn move_up(&mut self) {
+    pub fn move_up(&mut self, dt: f32) {
         let up = self.up.normalize();
-        self.eye += up * self.settings.speed;
-        self.target += up * self.settings.speed;
+        self.eye += up * self.settings.speed * dt as f64;
+        self.target += up * self.settings.speed * dt as f64;
     }
 
-    pub fn move_down(&mut self) {
+    pub fn move_down(&mut self, dt: f32) {
         let up = self.up.normalize();
-        self.eye -= up * self.settings.speed;
-        self.target -= up * self.settings.speed;
+        self.eye -= up * self.settings.speed * dt as f64;
+        self.target -= up * self.settings.speed * dt as f64;
     }
 
     pub fn track_mouse_delta(&mut self, dx: f64, dy: f64) {
diff --git a/crates/dropbear-macro/src/lib.rs b/crates/dropbear-macro/src/lib.rs
index 75f2f72..eeae874 100644
--- a/crates/dropbear-macro/src/lib.rs
+++ b/crates/dropbear-macro/src/lib.rs
@@ -108,7 +108,7 @@ fn transform_function(mut func: ItemFn) -> ItemFn {
     let pointer_check = if !is_void {
         quote! {
             if out_result.is_null() {
-                return crate::scripting::native::DropbearNativeError::NullPointer as i32;
+                return crate::scripting::native::DropbearNativeError::NullPointer.code();
             }
         }
     } else {
@@ -118,11 +118,11 @@ fn transform_function(mut func: ItemFn) -> ItemFn {
     let success_handling = if !is_void {
         quote! {
             unsafe { *out_result = val; }
-            crate::scripting::native::DropbearNativeError::Success as i32
+            crate::scripting::native::DropbearNativeError::Success.code()
         }
     } else {
         quote! {
-            crate::scripting::native::DropbearNativeError::Success as i32
+            crate::scripting::native::DropbearNativeError::Success.code()
         }
     };
 
@@ -139,7 +139,7 @@ fn transform_function(mut func: ItemFn) -> ItemFn {
                     #success_handling
                 }
                 DropbearNativeResult::Err(e) => {
-                    e as i32
+                    e.code()
                 }
             }
         }
diff --git a/crates/eucalyptus-core/Cargo.toml b/crates/eucalyptus-core/Cargo.toml
index d5da3b9..8c7901a 100644
--- a/crates/eucalyptus-core/Cargo.toml
+++ b/crates/eucalyptus-core/Cargo.toml
@@ -44,6 +44,8 @@ rustc_version_runtime.workspace = true
 rapier3d.workspace = true
 bytemuck.workspace = true
 yakui.workspace = true
+thiserror.workspace = true
+combine.workspace = true
 
 [features]
 default = []
diff --git a/crates/eucalyptus-core/src/engine.rs b/crates/eucalyptus-core/src/engine.rs
index 4229fbd..ec0c4ed 100644
--- a/crates/eucalyptus-core/src/engine.rs
+++ b/crates/eucalyptus-core/src/engine.rs
@@ -110,42 +110,4 @@ pub mod jni {
             );
         }
     }
-}
-
-#[dropbear_macro::impl_c_api]
-pub mod native {
-    use crate::command::CommandBuffer;
-    use crate::convert_ptr;
-    use crate::engine::shared::read_str;
-    use crate::ptr::{AssetRegistryPtr, CommandBufferPtr, WorldPtr};
-    use crate::scripting::result::DropbearNativeResult;
-    use dropbear_engine::asset::AssetRegistry;
-    use hecs::World;
-    use std::os::raw::c_char;
-
-    pub fn dropbear_get_entity(
-        world_ptr: WorldPtr,
-        label: *const c_char,
-    ) -> DropbearNativeResult<u64> {
-        let world = convert_ptr!(world_ptr => World);
-        let label_str = unsafe { read_str(label)? };
-
-        super::shared::get_entity(world, &label_str)
-    }
-
-    pub fn dropbear_get_asset(
-        asset_ptr: AssetRegistryPtr,
-        uri: *const c_char,
-    ) -> DropbearNativeResult<u64> {
-        let asset_registry = convert_ptr!(asset_ptr => AssetRegistry);
-        let uri_str = unsafe { read_str(uri)? };
-        super::shared::get_asset(&asset_registry, &uri_str)
-    }
-
-    pub fn dropbear_quit(
-        command_ptr: CommandBufferPtr,
-    ) -> DropbearNativeResult<()> {
-        let sender = convert_ptr!(command_ptr => crossbeam_channel::Sender<CommandBuffer>);
-        super::shared::quit(sender)
-    }
 }
\ No newline at end of file
diff --git a/crates/eucalyptus-core/src/scene/scripting.rs b/crates/eucalyptus-core/src/scene/scripting.rs
index c76985c..239ebf0 100644
--- a/crates/eucalyptus-core/src/scene/scripting.rs
+++ b/crates/eucalyptus-core/src/scene/scripting.rs
@@ -358,193 +358,6 @@ pub mod jni {
     }
 }
 
-pub mod native {
-    use crate::ptr::{CommandBufferPtr, SceneLoaderPtr};
-    use crate::scripting::native::DropbearNativeError;
-    use std::ffi::c_char;
-    use std::ffi::CStr;
-
-    #[unsafe(no_mangle)]
-    pub extern "C" fn load_scene_async(
-        command_buffer_ptr: CommandBufferPtr,
-        scene_loader_ptr: SceneLoaderPtr,
-        scene_name: *const c_char,
-        loading_scene: *const c_char,
-        out_scene_id: *mut u64,
-    ) -> DropbearNativeError {
-        if command_buffer_ptr.is_null() || scene_loader_ptr.is_null() || scene_name.is_null() {
-            return DropbearNativeError::NullPointer;
-        }
-
-        let command_buffer = unsafe { &*command_buffer_ptr };
-        let scene_loader = unsafe { &*scene_loader_ptr };
-
-        let scene_name_cstr = unsafe { CStr::from_ptr(scene_name) };
-        let scene_name_str = match scene_name_cstr.to_str() {
-            Ok(s) => s.to_string(),
-            Err(_) => return DropbearNativeError::InvalidUTF8,
-        };
-
-        let loading_scene_option = if !loading_scene.is_null() {
-            let loading_scene_cstr = unsafe { CStr::from_ptr(loading_scene) };
-            match loading_scene_cstr.to_str() {
-                Ok(s) => Some(s.to_string()),
-                Err(_) => return DropbearNativeError::InvalidUTF8,
-            }
-        } else {
-            None
-        };
-
-        match super::shared::load_scene_async(command_buffer, scene_loader, scene_name_str, loading_scene_option) {
-            Ok(scene_id) => {
-                if !out_scene_id.is_null() {
-                    unsafe { *out_scene_id = scene_id };
-                }
-                DropbearNativeError::Success
-            }
-            Err(e) => e,
-        }
-    }
-
-    #[unsafe(no_mangle)]
-    pub extern "C" fn switch_to_scene_immediate(
-        command_buffer_ptr: CommandBufferPtr,
-        scene_name: *const c_char,
-    ) -> DropbearNativeError {
-        if command_buffer_ptr.is_null() || scene_name.is_null() {
-            return DropbearNativeError::NullPointer;
-        }
-
-        let command_buffer = unsafe { &*command_buffer_ptr };
-
-        let scene_name_cstr = unsafe { CStr::from_ptr(scene_name) };
-        let scene_name_str = match scene_name_cstr.to_str() {
-            Ok(s) => s.to_string(),
-            Err(_) => return DropbearNativeError::InvalidUTF8,
-        };
-
-        match super::shared::switch_to_scene_immediate(command_buffer, scene_name_str) {
-            Ok(_) => DropbearNativeError::Success,
-            Err(e) => e,
-        }
-    }
-
-    #[unsafe(no_mangle)]
-    pub extern "C" fn get_scene_load_handle_scene_name(
-        scene_loader_ptr: SceneLoaderPtr,
-        scene_id: u64,
-        out_buffer: *mut c_char,
-        buffer_size: usize,
-    ) -> DropbearNativeError {
-        if scene_loader_ptr.is_null() || out_buffer.is_null() {
-            return DropbearNativeError::NullPointer;
-        }
-
-        let scene_loader = unsafe { &*scene_loader_ptr };
-
-        match super::shared::get_scene_load_handle_scene_name(scene_loader, scene_id) {
-            Ok(scene_name) => {
-                let c_string = match std::ffi::CString::new(scene_name) {
-                    Ok(cstr) => cstr,
-                    Err(_) => return DropbearNativeError::CStringError,
-                };
-
-                let bytes = c_string.as_bytes_with_nul();
-                if bytes.len() > buffer_size {
-                    return DropbearNativeError::BufferTooSmall;
-                }
-
-                unsafe {
-                    std::ptr::copy_nonoverlapping(bytes.as_ptr(), out_buffer as *mut u8, bytes.len());
-                }
-
-                DropbearNativeError::Success
-            }
-            Err(e) => e,
-        }
-    }
-
-    #[unsafe(no_mangle)]
-    pub extern "C" fn switch_to_scene_async(
-        command_buffer_ptr: CommandBufferPtr,
-        scene_loader_ptr: SceneLoaderPtr,
-        scene_id: u64,
-    ) -> DropbearNativeError {
-        if command_buffer_ptr.is_null() || scene_loader_ptr.is_null() {
-            return DropbearNativeError::NullPointer;
-        }
-
-        let command_buffer = unsafe { &*command_buffer_ptr };
-        let scene_loader = unsafe { &*scene_loader_ptr };
-
-        match super::shared::switch_to_scene_async(command_buffer, scene_loader, scene_id) {
-            Ok(_) => DropbearNativeError::Success,
-            Err(e) => e,
-        }
-    }
-
-    #[unsafe(no_mangle)]
-    pub extern "C" fn get_scene_load_progress(
-        scene_loader_ptr: SceneLoaderPtr,
-        scene_id: u64,
-        out_current: *mut f64,
-        out_total: *mut f64,
-        out_message: *mut c_char,
-        message_buffer_size: usize,
-    ) -> DropbearNativeError {
-        if scene_loader_ptr.is_null() {
-            return DropbearNativeError::NullPointer;
-        }
-
-        let scene_loader = unsafe { &*scene_loader_ptr };
-
-        match super::shared::get_scene_load_progress(scene_loader, scene_id) {
-            Ok(progress) => {
-                if !out_current.is_null() {
-                    unsafe { *out_current = progress.current as f64 };
-                }
-                if !out_total.is_null() {
-                    unsafe { *out_total = progress.total as f64 };
-                }
-
-                if !out_message.is_null() && message_buffer_size > 0 {
-                    let message_cstr = match std::ffi::CString::new(progress.message) {
-                        Ok(cstr) => cstr,
-                        Err(_) => return DropbearNativeError::CStringError,
-                    };
-
-                    let bytes = message_cstr.as_bytes_with_nul();
-                    let copy_len = std::cmp::min(bytes.len(), message_buffer_size);
-
-                    unsafe {
-                        std::ptr::copy_nonoverlapping(bytes.as_ptr(), out_message as *mut u8, copy_len);
-                    }
-                }
-
-                DropbearNativeError::Success
-            }
-            Err(e) => e,
-        }
-    }
-
-    #[unsafe(no_mangle)]
-    pub extern "C" fn get_scene_load_status(
-        scene_loader_ptr: SceneLoaderPtr,
-        scene_id: u64,
-    ) -> i32 {
-        if scene_loader_ptr.is_null() {
-            return DropbearNativeError::NullPointer as i32;
-        }
-
-        let scene_loader = unsafe { &*scene_loader_ptr };
-
-        match super::shared::get_scene_load_status(scene_loader, scene_id) {
-            Ok(status) => status as i32,
-            Err(e) => e as i32,
-        }
-    }
-}
-
 impl crate::scripting::jni::utils::ToJObject for crate::utils::Progress {
     fn to_jobject<'a>(&self, env: &mut JNIEnv<'a>) -> DropbearNativeResult<JObject<'a>> {
         let class = env.find_class("com/dropbear/utils/Progress")
diff --git a/crates/eucalyptus-core/src/scripting/native.rs b/crates/eucalyptus-core/src/scripting/native.rs
index c70f5bf..dc0e7eb 100644
--- a/crates/eucalyptus-core/src/scripting/native.rs
+++ b/crates/eucalyptus-core/src/scripting/native.rs
@@ -10,10 +10,14 @@ use crate::scripting::native::sig::{CollisionEvent, ContactForceEvent, DestroyAl
 use anyhow::anyhow;
 use libloading::{Library, Symbol};
 use std::ffi::CString;
-use std::fmt::{Display, Formatter};
+// use std::fmt::{Display, Formatter}; // Display derived by thiserror
 use std::path::Path;
 use crate::scripting::DropbearContext;
 use crate::types::{CollisionEvent as CollisionEventFFI, ContactForceEvent as ContactForceEventFFI};
+use thiserror::Error;
+use jni::signature::TypeSignature;
+use jni::errors::JniError;
+
 
 pub struct NativeLibrary {
     #[allow(dead_code)]
@@ -442,150 +446,199 @@ impl LastErrorMessage for NativeLibrary {
     }
 }
 
-#[repr(C)]
-#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+#[derive(Debug, Error)]
 /// Displays the types of errors that can be returned by the native library.
 pub enum DropbearNativeError {
     /// An error in the case the function returns an unsigned value.
-    ///
-    /// Subtract [`DropbearNativeError::UnsignedGenericError`] with another value
-    /// to get the alternative unsigned error.
-    UnsignedGenericError = 65535,
+    #[error("Unsigned generic error")]
+    UnsignedGenericError,
     /// An error that is thrown, but doesn't have any attached context.
-    GenericError = 1,
+    #[error("Generic error")]
+    GenericError,
     /// The default return code for a successful FFI operation.
-    Success = 0,
+    #[error("Success")]
+    Success,
     /// A null pointer was provided into the function, and cannot be read.
-    NullPointer = -1,
+    #[error("Null pointer")]
+    NullPointer,
     /// Attempting to query the current world failed for some cause.
-    QueryFailed = -2,
+    #[error("Query failed")]
+    QueryFailed,
     /// The entity does not exist in the world.
-    EntityNotFound = -3,
-
-    /// No such component exists **or** the component type id is not the same as the one in the
-    /// [`hecs::World`] database.
-    ///
-    /// # Causes
-    /// There are two potential causes for this error:
-    /// - If the component that the world is locating is not available within the entity, it will
-    ///   throw this.
-    /// - Due to Rust's compilation methods and the weird architecture of the dropbear project,
-    ///   if the `eucalyptus_core` library is not compiled with an executable
-    ///   (such as `eucalyptus-editor` or `redback-runtime`), it will throw this error.
-    ///
-    /// The querying system of `eucalyptus-core` is done with a [`hecs::World`] (which stored all the
-    /// entities), the component registry ([`dropbear_traits::registry::ComponentRegistry`]) that
-    /// stores all the potential component names (including ones from external plugins) and the
-    /// [`std::any::TypeId`] (which generates a hash of the components/types).
-    ///
-    /// If `eucalyptus-core` is externally compiled as its own thing (and not bundled with any executable),
-    /// a query will lead to a fail due to the hashes being completely different.
-    ///
-    /// When originally stumped with the issue of DLL's and EXE constantly throwing this error, a user
-    /// on the Rust discord provided me with this:
-    ///
-    /// ```txt
-    /// in short, the rules are as follows:
-    /// 1. If the compilers that produced two Rust binaries are different, or they were produced by
-    ///    compiling for different targets, or if one of them is a compilation root (not of crate
-    ///    type rlib or dylib), the two binaries have different Rust ABI
-    /// 2. If two binaries have different Rust ABIs, one cannot be used to satisfy a crate dependency
-    ///    of another (and thus, absent extern blocks and the associated unsafe, they cannot call each other's functions)
-    /// 3. If two binaries have different Rust ABI, their TypeIds will not be consistent
-    /// 4. If two Rust binaries are built from different source code, one cannot be substituted for
-    ///    another to satisfy the dependency of some other crate
-    /// ```
-    ///
-    /// Yeah, so likely if this error is thrown at you, either the compilation is wrong or you
-    /// **genuinely** messed up and didn't include the component.
-    ///
-    /// Anyhow, if you are able to confirm it was a compilation error, please open an issue
-    /// on the dropbear GitHub.
-    NoSuchComponent = -4,
-
+    #[error("Entity not found")]
+    EntityNotFound,
+    /// No such component exists.
+    #[error("No such component")]
+    NoSuchComponent,
     /// No such entity uses the specific component.
-    NoSuchEntity = -5,
+    #[error("No such entity")]
+    NoSuchEntity,
     /// Inserting something (like a component) into the world failed
-    WorldInsertError = -6,
+    #[error("World insert error")]
+    WorldInsertError,
     /// When the graphics queue fails to send its message to the receiver
-    SendError = -7,
+    #[error("Send error")]
+    SendError,
     /// Error while creating a new CString
-    CStringError = -8,
-    BufferTooSmall = -9,
+    #[error("CString error")]
+    CStringError,
+    #[error("Buffer too small")]
+    BufferTooSmall,
     /// Attempting to switch scenes before the world is loaded will throw this error.
-    PrematureSceneSwitch = -10,
+    #[error("Premature scene switch")]
+    PrematureSceneSwitch,
     /// When a gamepad is not found while querying the input state for so.
-    GamepadNotFound = -11,
+    #[error("Gamepad not found")]
+    GamepadNotFound,
     /// When the argument is invalid
-    InvalidArgument = -12,
+    #[error("Invalid argument")]
+    InvalidArgument,
     /// The handle provided does not exist. Could be for an asset, entity, or other handle type.
-    NoSuchHandle = -13,
+    #[error("No such handle")]
+    NoSuchHandle,
     /// Failed to create a Java object via JNI.
-    JNIFailedToCreateObject = -14,
+    #[error("JNI failed to create object")]
+    JNIFailedToCreateObject,
     /// Failed to get a field from a Java object via JNI.
-    JNIFailedToGetField = -15,
+    #[error("JNI failed to get field")]
+    JNIFailedToGetField,
     /// Failed to find a Java class via JNI.
-    JNIClassNotFound = -16,
+    #[error("JNI class not found")]
+    JNIClassNotFound,
     /// Failed to find a Java method via JNI.
-    JNIMethodNotFound = -17,
+    #[error("JNI method not found")]
+    JNIMethodNotFound,
     /// Failed to unwrap a Java object via JNI.
-    JNIUnwrapFailed = -18,
+    #[error("JNI unwrap failed")]
+    JNIUnwrapFailed,
     /// Generic asset error. There was an error thrown, however there is no context attached. 
-    GenericAssetError = -19,
+    #[error("Generic asset error")]
+    GenericAssetError,
     /// The provided uri (either euca:// or https) was invalid and formatted wrong.
-    InvalidURI = -20,
+    #[error("Invalid URI")]
+    InvalidURI,
     /// The asset provided by the handle is wrong.
-    AssetNotFound = -21,
+    #[error("Asset not found")]
+    AssetNotFound,
     /// When a handle has been inputted wrongly.
-    InvalidHandle = -22,
+    #[error("Invalid handle")]
+    InvalidHandle,
     /// When a physics object is not found
-    PhysicsObjectNotFound = -23,
-    
-    /// The entity provided was invalid, likely not from [hecs::Entity::from_bits].
-    InvalidEntity = -100,
-
-
-    /// The CString (or `*const c_char`) contained invalid UTF-8 while being decoded.
-    InvalidUTF8 = -108,
-    /// A generic error when the library doesn't know what happened or cannot find a
-    /// suitable error code.
-    ///
-    /// The number `1274` comes from the total sum of the word "UnknownError" in decimal
-    UnknownError = -1274,
+    #[error("Physics object not found")]
+    PhysicsObjectNotFound,
+    /// The entity provided was invalid.
+    #[error("Invalid entity")]
+    InvalidEntity,
+    /// The CString contained invalid UTF-8.
+    #[error("Invalid UTF-8")]
+    InvalidUTF8,
+    /// A generic error when the library doesn't know what happened.
+    #[error("Unknown error")]
+    UnknownError,
+
+    // JNI Errors impl
+    #[error("Invalid JValue type cast: {0}. Actual type: {1}")]
+    WrongJValueType(&'static str, &'static str),
+    #[error("Invalid constructor return type (must be void)")]
+    InvalidCtorReturn,
+    #[error("Invalid number or type of arguments passed to java method: {0}")]
+    InvalidArgList(TypeSignature),
+    #[error("Method not found: {name} {sig}")]
+    MethodNotFound { name: String, sig: String },
+    #[error("Field not found: {name} {sig}")]
+    FieldNotFound { name: String, sig: String },
+    #[error("Java exception was thrown")]
+    JavaException,
+    #[error("JNIEnv null method pointer for {0}")]
+    JNIEnvMethodNotFound(&'static str),
+    #[error("Null pointer in {0}")]
+    NullPtr(&'static str),
+    #[error("Null pointer deref in {0}")]
+    NullDeref(&'static str),
+    #[error("Mutex already locked")]
+    TryLock,
+    #[error("JavaVM null method pointer for {0}")]
+    JavaVMMethodNotFound(&'static str),
+    #[error("Field already set: {0}")]
+    FieldAlreadySet(String),
+    #[error("Throw failed with error code {0}")]
+    ThrowFailed(i32),
+    #[error("Parse failed for input: {1}")]
+    ParseFailed(#[source] combine::error::StringStreamError, String),
+    #[error("JNI call failed")]
+    JniCall(#[source] JniError),
+}
+
+impl DropbearNativeError {
+    pub fn code(&self) -> i32 {
+        match self {
+            DropbearNativeError::Success => 0,
+            DropbearNativeError::UnsignedGenericError => 65535,
+            DropbearNativeError::GenericError => 1,
+            DropbearNativeError::NullPointer => -1,
+            DropbearNativeError::QueryFailed => -2,
+            DropbearNativeError::EntityNotFound => -3,
+            DropbearNativeError::NoSuchComponent => -4,
+            DropbearNativeError::NoSuchEntity => -5,
+            DropbearNativeError::WorldInsertError => -6,
+            DropbearNativeError::SendError => -7,
+            DropbearNativeError::CStringError => -8,
+            DropbearNativeError::BufferTooSmall => -9,
+            DropbearNativeError::PrematureSceneSwitch => -10,
+            DropbearNativeError::GamepadNotFound => -11,
+            DropbearNativeError::InvalidArgument => -12,
+            DropbearNativeError::NoSuchHandle => -13,
+            DropbearNativeError::JNIFailedToCreateObject => -14,
+            DropbearNativeError::JNIFailedToGetField => -15,
+            DropbearNativeError::JNIClassNotFound => -16,
+            DropbearNativeError::JNIMethodNotFound => -17,
+            DropbearNativeError::JNIUnwrapFailed => -18,
+            DropbearNativeError::GenericAssetError => -19,
+            DropbearNativeError::InvalidURI => -20,
+            DropbearNativeError::AssetNotFound => -21,
+            DropbearNativeError::InvalidHandle => -22,
+            DropbearNativeError::PhysicsObjectNotFound => -23,
+            DropbearNativeError::InvalidEntity => -100,
+            DropbearNativeError::InvalidUTF8 => -108,
+            DropbearNativeError::UnknownError => -1274,
+            // New JNI errors start from -200 to separate them
+            DropbearNativeError::WrongJValueType(_, _) => -200,
+            DropbearNativeError::InvalidCtorReturn => -201,
+            DropbearNativeError::InvalidArgList(_) => -202,
+            DropbearNativeError::MethodNotFound { .. } => -203,
+            DropbearNativeError::FieldNotFound { .. } => -204,
+            DropbearNativeError::JavaException => -205,
+            DropbearNativeError::JNIEnvMethodNotFound(_) => -206,
+            DropbearNativeError::NullPtr(_) => -207,
+            DropbearNativeError::NullDeref(_) => -208,
+            DropbearNativeError::TryLock => -209,
+            DropbearNativeError::JavaVMMethodNotFound(_) => -210,
+            DropbearNativeError::FieldAlreadySet(_) => -211,
+            DropbearNativeError::ThrowFailed(_) => -212,
+            DropbearNativeError::ParseFailed(_, _) => -213,
+            DropbearNativeError::JniCall(_) => -214,
+        }
+    }
 }
 
-impl Display for DropbearNativeError {
-    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
-        f.write_str(match self {
-            DropbearNativeError::NullPointer => "NullPointer (-1)",
-            DropbearNativeError::QueryFailed => "QueryFailed (-2)",
-            DropbearNativeError::EntityNotFound => "EntityNotFound (-3)",
-            DropbearNativeError::NoSuchComponent => "NoSuchComponent (-4)",
-            DropbearNativeError::NoSuchEntity => "NoSuchEntity (-5)",
-            DropbearNativeError::WorldInsertError => "WorldInsertError (-6)",
-            DropbearNativeError::SendError => "SendError (-7)",
-            DropbearNativeError::InvalidUTF8 => "InvalidUTF8 (-108)",
-            DropbearNativeError::UnknownError => "UnknownError (-1274)",
-            DropbearNativeError::UnsignedGenericError => "UnsignedGenericError (65535)",
-            DropbearNativeError::Success => "Success (0) [should not be displayed]",
-            DropbearNativeError::CStringError => "CStringError (-8)",
-            DropbearNativeError::BufferTooSmall => "BufferTooSmall (-9)",
-            DropbearNativeError::PrematureSceneSwitch => "PrematureSceneSwitch (-10)",
-            DropbearNativeError::GamepadNotFound => "GamepadNotFound (-11)",
-            DropbearNativeError::InvalidArgument => "InvalidArgument (-12)",
-            DropbearNativeError::NoSuchHandle => "NoSuchHandle (-13)",
-            DropbearNativeError::JNIFailedToCreateObject => "JNIFailedToCreateObject (-14)",
-            DropbearNativeError::JNIFailedToGetField => "JNIFailedToGetField (-15)",
-            DropbearNativeError::JNIClassNotFound => "JNIClassNotFound (-16)",
-            DropbearNativeError::JNIMethodNotFound => "JNIMethodNotFound (-17)",
-            DropbearNativeError::JNIUnwrapFailed => "JNIUnwrapFailed (-18)",
-            DropbearNativeError::InvalidEntity => "InvalidEntity (-100)",
-            DropbearNativeError::GenericAssetError => "GenericAssetError (-19)",
-            DropbearNativeError::InvalidURI => "InvalidURI (-20)",
-            DropbearNativeError::AssetNotFound => "AssetNotFound (-21)",
-            DropbearNativeError::InvalidHandle => "InvalidHandle (-22)",
-            DropbearNativeError::GenericError => "GenericError (1)",
-            DropbearNativeError::PhysicsObjectNotFound => "PhysicsObjectNotFound (-23)",
-        })
+impl From<jni::errors::Error> for DropbearNativeError {
+    fn from(err: jni::errors::Error) -> Self {
+        match err {
+            jni::errors::Error::WrongJValueType(a, b) => DropbearNativeError::WrongJValueType(a, b),
+            jni::errors::Error::InvalidCtorReturn => DropbearNativeError::InvalidCtorReturn,
+            jni::errors::Error::InvalidArgList(s) => DropbearNativeError::InvalidArgList(s),
+            jni::errors::Error::MethodNotFound { name, sig } => DropbearNativeError::MethodNotFound { name, sig },
+            jni::errors::Error::FieldNotFound { name, sig } => DropbearNativeError::FieldNotFound { name, sig },
+            jni::errors::Error::JavaException => DropbearNativeError::JavaException,
+            jni::errors::Error::JNIEnvMethodNotFound(s) => DropbearNativeError::JNIEnvMethodNotFound(s),
+            jni::errors::Error::NullPtr(s) => DropbearNativeError::NullPtr(s),
+            jni::errors::Error::NullDeref(s) => DropbearNativeError::NullDeref(s),
+            jni::errors::Error::TryLock => DropbearNativeError::TryLock,
+            jni::errors::Error::JavaVMMethodNotFound(s) => DropbearNativeError::JavaVMMethodNotFound(s),
+            jni::errors::Error::FieldAlreadySet(s) => DropbearNativeError::FieldAlreadySet(s),
+            jni::errors::Error::ThrowFailed(i) => DropbearNativeError::ThrowFailed(i),
+            jni::errors::Error::ParseFailed(e, s) => DropbearNativeError::ParseFailed(e, s),
+            jni::errors::Error::JniCall(e) => DropbearNativeError::JniCall(e),
+        }
     }
-}
\ No newline at end of file
+}
diff --git a/crates/eucalyptus-core/src/ui.rs b/crates/eucalyptus-core/src/ui.rs
index 91ed930..8764363 100644
--- a/crates/eucalyptus-core/src/ui.rs
+++ b/crates/eucalyptus-core/src/ui.rs
@@ -1,10 +1,17 @@
+mod button;
+mod utils;
+
 use std::cell::RefCell;
+use ::jni::JNIEnv;
+use ::jni::objects::JObject;
 use parking_lot::Mutex;
 use serde::{Deserialize, Serialize};
-use yakui::Yakui;
+use yakui::{Yakui};
 use dropbear_engine::utils::ResourceReference;
 use dropbear_macro::SerializableComponent;
 use dropbear_traits::SerializableComponent;
+use crate::scripting::jni::utils::{FromJObject};
+use crate::scripting::result::DropbearNativeResult;
 
 thread_local! {
     pub static UI_CONTEXT: RefCell<UiContext> = RefCell::new(UiContext::new());
@@ -24,34 +31,66 @@ pub struct UIComponent {
 
 // note for tomorrow: use UIInstruction like that of asm
 
-#[derive(Default, Debug)]
-pub enum UIInstruction {
-    #[default]
-    Nothing,
+pub trait NativeWidget: Send + std::fmt::Debug {
+    fn build(self: Box<Self>);
+}
+
+impl NativeWidget for yakui::widgets::Button {
+    fn build(mut self: Box<Self>) {
+        let _ = self.show();
+    }
+}
+
+pub trait WidgetParser: Send + Sync {
+    fn parse(&self, env: &mut JNIEnv, obj: &JObject) -> DropbearNativeResult<Option<Box<dyn NativeWidget>>>;
+}
+
+static PARSERS: std::sync::OnceLock<Mutex<Vec<Box<dyn WidgetParser>>>> = std::sync::OnceLock::new();
 
-    StartColumn,
-    EndColumn,
+pub fn register_widget_parser<P: WidgetParser + 'static>(parser: P) {
+    let parsers = PARSERS.get_or_init(|| Mutex::new(Vec::new()));
+    parsers.lock().push(Box::new(parser));
 }
 
+fn get_parsers() -> &'static Mutex<Vec<Box<dyn WidgetParser>>> {
+    PARSERS.get_or_init(|| {
+        let mut vec: Vec<Box<dyn WidgetParser>> = Vec::new();
+        vec.push(Box::new(ButtonParser));
+        Mutex::new(vec)
+    })
+}
+
+struct ButtonParser;
+
+impl WidgetParser for ButtonParser {
+    fn parse(&self, env: &mut JNIEnv, obj: &JObject) -> DropbearNativeResult<Option<Box<dyn NativeWidget>>> {
+        let class = env.get_object_class(obj)?;
+        let name_str_obj = env.call_method(class, "getName", "()Ljava/lang/String;", &[])?.l()?;
+        let name_string: String = env.get_string(&name_str_obj.into())?.into();
+
+        if name_string.contains("ButtonInstruction$Button") {
+            let button_obj = env.get_field(obj, "button", "Lcom/dropbear/ui/widgets/Button;")?.l()?;
+            let btn = yakui::widgets::Button::from_jobject(env, &button_obj)?;
+            return Ok(Some(Box::new(btn)));
+        }
+
+        Ok(None)
+    }
+}
+
+
 pub struct UiContext {
     pub yakui_state: Mutex<Yakui>,
-    pub instruction_set: Mutex<Vec<UIInstruction>>,
+    pub instruction_set: Mutex<Vec<Box<dyn NativeWidget>>>,
 }
 
 pub fn poll() {
     UI_CONTEXT.with(|v| {
         let ctx = v.borrow();
-        let instructions = ctx.instruction_set.lock().drain(..).collect::<Vec<UIInstruction>>();
-        for i in instructions {
-            match i {
-                UIInstruction::StartColumn => {
-                        
-                },
-                UIInstruction::EndColumn => {
-
-                },
-                UIInstruction::Nothing => {}
-            }
+        let mut instructions = ctx.instruction_set.lock();
+        let current_instructions = instructions.drain(..).collect::<Vec<Box<dyn NativeWidget>>>();
+        for i in current_instructions {
+            i.build();
         }
     });
 }
@@ -65,30 +104,55 @@ impl UiContext {
     }
 }
 
+pub trait UiWidgetType: FromJObject {
+    type UIWidgetType;
+
+    fn as_id(&self) -> u32;
+    fn from_id(id: u32) -> Self::UIWidgetType;
+}
+
 pub mod jni {
     use jni::sys::jlong;
+    use jni::objects::{JClass, JObjectArray};
+    use jni::JNIEnv;
     use crate::convert_ptr;
-    use crate::ui::{UiContext};
+    use crate::ui::{UiContext, get_parsers};
+    use crate::scripting::jni::utils::FromJObject;
 
     #[unsafe(no_mangle)]
-    pub extern "C" fn Java_foobar_addOverlay(
-        _env: jni::JNIEnv,
-        _class: jni::objects::JClass,
+    pub extern "system" fn Java_com_dropbear_ui_UINative_renderUI(
+        mut env: JNIEnv,
+        _class: JClass,
         ui_buf_ptr: jlong,
+        instructions: JObjectArray,
     ) {
         let ui = convert_ptr!(ui_buf_ptr => UiContext);
+        let mut rust_instructions = Vec::new();
 
-        ui.instruction_set.lock().push(crate::ui::UIInstruction::Nothing);
-    }
+        let count = env.get_array_length(&instructions).unwrap_or(0);
+        let parsers_guard = get_parsers().lock();
 
-    #[unsafe(no_mangle)]
-    pub extern "C" fn Java_foobar_aisClicked(
-        _env: jni::JNIEnv,
-        _class: jni::objects::JClass,
-        ui_buf_ptr: jlong,
-    ) {
-        let _ui = convert_ptr!(ui_buf_ptr => UiContext);
+        for i in 0..count {
+            let obj = match env.get_object_array_element(&instructions, i) {
+                Ok(o) => o,
+                Err(_) => continue,
+            };
+            if obj.is_null() { continue; }
+
+            for parser in parsers_guard.iter() {
+                match parser.parse(&mut env, &obj) {
+                    Ok(Some(widget)) => {
+                        rust_instructions.push(widget);
+                        break;
+                    },
+                    Ok(None) => continue,
+                    Err(e) => {
+                        eprintln!("Error converting UI instruction: {:?}", e);
+                    }
+                }
+            }
+        }
 
-        // ui.yakui_state.lock()
+        ui.instruction_set.lock().extend(rust_instructions);
     }
 }
\ No newline at end of file
diff --git a/crates/eucalyptus-core/src/ui/button.rs b/crates/eucalyptus-core/src/ui/button.rs
new file mode 100644
index 0000000..a2a801d
--- /dev/null
+++ b/crates/eucalyptus-core/src/ui/button.rs
@@ -0,0 +1,61 @@
+use jni::JNIEnv;
+use jni::objects::JObject;
+use yakui::{Alignment, BorderRadius};
+use yakui::widgets::{Button, Pad, DynamicButtonStyle};
+use crate::scripting::jni::utils::FromJObject;
+use crate::scripting::result::DropbearNativeResult;
+use std::borrow::Cow;
+
+impl FromJObject for Button {
+    fn from_jobject(env: &mut JNIEnv, obj: &JObject) -> DropbearNativeResult<Self> {
+        let text_field = env.get_field(obj, "text", "Ljava/lang/String;")?;
+        let text_jstring = text_field.l()?.into();
+        let text: String = env.get_string(&text_jstring)?.into();
+
+        let f = env.get_field(obj, "padding", "Lcom/dropbear/ui/styling/Padding;")?.l()?;
+        let padding = Pad::from_jobject(
+            env,
+            &f
+        )?;
+
+        let f = env.get_field(obj, "alignment", "Lcom/dropbear/ui/styling/Alignment;")?.l()?;
+        let alignment = Alignment::from_jobject(
+            env,
+            &f
+        )?;
+
+        let f = env.get_field(obj, "borderRadius", "Lcom/dropbear/ui/styling/BorderRadius;")?.l()?;
+        let border_radius = BorderRadius::from_jobject(
+            env,
+            &f
+        )?;
+        
+        let f = env.get_field(obj, "style", "Lcom/dropbear/ui/styling/DynamicButtonStyle;")?.l()?;
+        let style = DynamicButtonStyle::from_jobject(
+            env,
+            &f
+        )?;
+
+        let f = env.get_field(obj, "hoverStyle", "Lcom/dropbear/ui/styling/DynamicButtonStyle;")?.l()?;
+        let hover_style = DynamicButtonStyle::from_jobject(
+            env,
+            &f
+        )?;
+
+        let f = env.get_field(obj, "downStyle", "Lcom/dropbear/ui/styling/DynamicButtonStyle;")?.l()?;
+        let down_style = DynamicButtonStyle::from_jobject(
+            env,
+            &f
+        )?;
+
+        Ok(Self {
+            text: Cow::Owned(text),
+            alignment,
+            padding,
+            border_radius,
+            style,
+            hover_style,
+            down_style,
+        })
+    }
+}
diff --git a/crates/eucalyptus-core/src/ui/utils.rs b/crates/eucalyptus-core/src/ui/utils.rs
new file mode 100644
index 0000000..413a661
--- /dev/null
+++ b/crates/eucalyptus-core/src/ui/utils.rs
@@ -0,0 +1,277 @@
+use jni::JNIEnv;
+use jni::objects::{JObject, JByteArray};
+use crate::scripting::jni::utils::FromJObject;
+use crate::scripting::result::DropbearNativeResult;
+use yakui::Color;
+use yakui::widgets::{Pad, DynamicButtonStyle};
+use yakui::{Alignment, BorderRadius};
+use yakui::style::{TextStyle, TextAlignment};
+use yakui::cosmic_text::{Attrs, AttrsOwned, FamilyOwned, Weight, Style, Stretch, CacheKeyFlags, FontFeatures, Feature, FeatureTag};
+
+impl FromJObject for FeatureTag {
+    fn from_jobject(env: &mut JNIEnv, obj: &JObject) -> DropbearNativeResult<Self> {
+        let tag_obj = env.call_method(obj, "asBytes", "()[B", &[])?.l()?;
+        let tag_bytes = env.convert_byte_array(JByteArray::from(tag_obj))?;
+        
+        let mut fixed_tag = [0u8; 4];
+        let len = tag_bytes.len().min(4);
+        fixed_tag[..len].copy_from_slice(&tag_bytes[..len]);
+        
+        Ok(FeatureTag::new(&fixed_tag))
+    }
+}
+
+impl FromJObject for Feature {
+    fn from_jobject(env: &mut JNIEnv, obj: &JObject) -> DropbearNativeResult<Self> {
+        let tag_obj = env.get_field(obj, "tag", "Lcom/dropbear/ui/styling/fonts/FeatureTag;")?.l()?;
+        let tag = FeatureTag::from_jobject(env, &tag_obj)?;
+        
+        let value_obj = env.get_field(obj, "value", "Lcom/dropbear/ui/styling/fonts/UInt;")?.l()?;
+        let value = env.get_field(&value_obj, "value", "I")?.i()? as u32;
+
+        Ok(Feature {
+            tag,
+            value,
+        })
+    }
+}
+
+impl FromJObject for FontFeatures {
+    fn from_jobject(env: &mut JNIEnv, obj: &JObject) -> DropbearNativeResult<Self> {
+        let features_list_obj = env.get_field(obj, "features", "Ljava/util/List;")?.l()?;
+        
+        let size = env.call_method(&features_list_obj, "size", "()I", &[])?.i()?;
+        let mut features = Vec::with_capacity(size as usize);
+        
+        for i in 0..size {
+            let item = env.call_method(&features_list_obj, "get", "(I)Ljava/lang/Object;", &[i.into()])?.l()?;
+            if !item.is_null() {
+                features.push(Feature::from_jobject(env, &item)?);
+            }
+        }
+        
+        Ok(FontFeatures { features })
+    }
+}
+
+impl FromJObject for Color {
+    fn from_jobject(env: &mut JNIEnv, obj: &JObject) -> DropbearNativeResult<Self> {
+        let r = env.get_field(obj, "r", "B")?.b()? as u8;
+        let g = env.get_field(obj, "g", "B")?.b()? as u8;
+        let b = env.get_field(obj, "b", "B")?.b()? as u8;
+        let a = env.get_field(obj, "a", "B")?.b()? as u8;
+        Ok(Color::rgba(r, g, b, a))
+    }
+}
+
+impl FromJObject for Pad {
+    fn from_jobject(env: &mut JNIEnv, obj: &JObject) -> DropbearNativeResult<Self> {
+        let left = env.get_field(obj, "left", "D")?.d()? as f32;
+        let right = env.get_field(obj, "right", "D")?.d()? as f32;
+        let top = env.get_field(obj, "top", "D")?.d()? as f32;
+        let bottom = env.get_field(obj, "bottom", "D")?.d()? as f32;
+        Ok(Pad {
+            left,
+            right,
+            top,
+            bottom,
+        })
+    }
+}
+
+impl FromJObject for BorderRadius {
+    fn from_jobject(env: &mut JNIEnv, obj: &JObject) -> DropbearNativeResult<Self> {
+        let top_left = env.get_field(obj, "topLeft", "D")?.d()? as f32;
+        let top_right = env.get_field(obj, "topRight", "D")?.d()? as f32;
+        let bottom_left = env.get_field(obj, "bottomLeft", "D")?.d()? as f32;
+        let bottom_right = env.get_field(obj, "bottomRight", "D")?.d()? as f32;
+        Ok(BorderRadius {
+            top_left,
+            top_right,
+            bottom_left,
+            bottom_right,
+        })
+    }
+}
+
+impl FromJObject for Alignment {
+    fn from_jobject(env: &mut JNIEnv, obj: &JObject) -> DropbearNativeResult<Self> {
+        let x = env.get_field(obj, "x", "D")?.d()? as f32;
+        let y = env.get_field(obj, "y", "D")?.d()? as f32;
+        Ok(Alignment::new(x, y))
+    }
+}
+
+impl FromJObject for TextAlignment {
+    fn from_jobject(env: &mut JNIEnv, obj: &JObject) -> DropbearNativeResult<Self> {
+        let name_obj = env.call_method(obj, "name", "()Ljava/lang/String;", &[])?.l()?;
+        let name: String = env.get_string(&name_obj.into())?.into();
+        match name.as_str() {
+            "Start" => Ok(TextAlignment::Start),
+            "Center" => Ok(TextAlignment::Center),
+            "End" => Ok(TextAlignment::End),
+            _ => Ok(TextAlignment::Start),
+        }
+    }
+}
+
+impl FromJObject for Weight {
+    fn from_jobject(env: &mut JNIEnv, obj: &JObject) -> DropbearNativeResult<Self> {
+        let val = env.get_field(obj, "value", "I")?.i()? as u16;
+        Ok(Weight(val))
+    }
+}
+
+impl FromJObject for Style {
+    fn from_jobject(env: &mut JNIEnv, obj: &JObject) -> DropbearNativeResult<Self> {
+        let name_obj = env.call_method(obj, "name", "()Ljava/lang/String;", &[])?.l()?;
+        let name: String = env.get_string(&name_obj.into())?.into();
+        match name.as_str() {
+            "Normal" => Ok(Style::Normal),
+            "Italic" => Ok(Style::Italic),
+            "Oblique" => Ok(Style::Oblique),
+            _ => Ok(Style::Normal),
+        }
+    }
+}
+
+impl FromJObject for Stretch {
+    fn from_jobject(env: &mut JNIEnv, obj: &JObject) -> DropbearNativeResult<Self> {
+        let name_obj = env.call_method(obj, "name", "()Ljava/lang/String;", &[])?.l()?;
+        let name: String = env.get_string(&name_obj.into())?.into();
+        match name.as_str() {
+            "UltraCondensed" => Ok(Stretch::UltraCondensed),
+            "ExtraCondensed" => Ok(Stretch::ExtraCondensed),
+            "Condensed" => Ok(Stretch::Condensed),
+            "SemiCondensed" => Ok(Stretch::SemiCondensed),
+            "Normal" => Ok(Stretch::Normal),
+            "SemiExpanded" => Ok(Stretch::SemiExpanded),
+            "Expanded" => Ok(Stretch::Expanded),
+            "ExtraExpanded" => Ok(Stretch::ExtraExpanded),
+            "UltraExpanded" => Ok(Stretch::UltraExpanded),
+            _ => Ok(Stretch::Normal),
+        }
+    }
+}
+
+impl FromJObject for FamilyOwned {
+    fn from_jobject(env: &mut JNIEnv, obj: &JObject) -> DropbearNativeResult<Self> {
+        if env.is_instance_of(obj, "com/dropbear/ui/styling/fonts/Family$Name")? {
+            let value_obj = env.call_method(obj, "getValue", "()Ljava/lang/String;", &[])?.l()?;
+            let val: String = env.get_string(&value_obj.into())?.into();
+            return Ok(FamilyOwned::Name(val.parse().unwrap()));
+        }
+        if env.is_instance_of(obj, "com/dropbear/ui/styling/fonts/Family$Serif")? { return Ok(FamilyOwned::Serif); }
+        if env.is_instance_of(obj, "com/dropbear/ui/styling/fonts/Family$SansSerif")? { return Ok(FamilyOwned::SansSerif); }
+        if env.is_instance_of(obj, "com/dropbear/ui/styling/fonts/Family$Cursive")? { return Ok(FamilyOwned::Cursive); }
+        if env.is_instance_of(obj, "com/dropbear/ui/styling/fonts/Family$Fantasy")? { return Ok(FamilyOwned::Fantasy); }
+        if env.is_instance_of(obj, "com/dropbear/ui/styling/fonts/Family$Monospace")? { return Ok(FamilyOwned::Monospace); }
+        
+        Ok(FamilyOwned::SansSerif)
+    }
+}
+
+impl FromJObject for AttrsOwned {
+    fn from_jobject(env: &mut JNIEnv, obj: &JObject) -> DropbearNativeResult<Self> {
+        let family_obj = env.get_field(obj, "family", "Lcom/dropbear/ui/styling/fonts/Family;")?.l()?;
+        let family_owned = FamilyOwned::from_jobject(env, &family_obj)?;
+        let family = family_owned.as_family();
+        
+        let color_obj = env.get_field(obj, "colourOptions", "Lcom/dropbear/utils/Colour;")?.l()?;
+        let color_opt = if !color_obj.is_null() {
+            let colour = Color::from_jobject(env, &color_obj)?;
+             Some(yakui::cosmic_text::Color::rgba(colour.r, colour.g, colour.b, colour.a))
+        } else {
+             None
+        };
+        
+        let stretch_obj = env.get_field(obj, "stretch", "Lcom/dropbear/ui/styling/fonts/Stretch;")?.l()?;
+        let stretch = Stretch::from_jobject(env, &stretch_obj)?;
+        
+        let style_obj = env.get_field(obj, "style", "Lcom/dropbear/ui/styling/fonts/FontStyle;")?.l()?;
+        let style = Style::from_jobject(env, &style_obj)?;
+        
+        let weight_obj = env.get_field(obj, "weight", "Lcom/dropbear/ui/styling/fonts/FontWeight;")?.l()?;
+        let weight = Weight::from_jobject(env, &weight_obj)?;
+        
+        let metadata = match env.get_field(obj, "metadata", "I") {
+             Ok(val) => val.i()? as usize,
+             Err(_) => 0, 
+        };
+
+        let letter_spacing_obj = env.get_field(obj, "letterSpacingOptions", "Ljava/lang/Double;")?.l()?;
+        let letter_spacing_opt = if !letter_spacing_obj.is_null() {
+             let val = env.call_method(&letter_spacing_obj, "doubleValue", "()D", &[])?.d()?;
+             Some(yakui::cosmic_text::LetterSpacing(val as f32))
+        } else {
+             None
+        };
+
+        let font_features_obj = env.get_field(obj, "fontFeatures", "Lcom/dropbear/ui/styling/fonts/FontFeatures;")?.l()?;
+        let font_features = if !font_features_obj.is_null() {
+            FontFeatures::from_jobject(env, &font_features_obj)?
+        } else {
+            FontFeatures::default()
+        };
+
+        Ok(AttrsOwned::new(&Attrs {
+            family,
+            stretch,
+            style,
+            weight,
+            metadata,
+            color_opt,
+            cache_key_flags: CacheKeyFlags::empty(),
+            metrics_opt: None,
+            letter_spacing_opt,
+            font_features,
+        }))
+    }
+}
+
+impl FromJObject for TextStyle {
+    fn from_jobject(env: &mut JNIEnv, obj: &JObject) -> DropbearNativeResult<Self> {
+        let align_obj = env.get_field(obj, "align", "Lcom/dropbear/ui/styling/fonts/TextAlignment;")?.l()?;
+        let align = TextAlignment::from_jobject(env, &align_obj)?;
+
+        let font_size = env.get_field(obj, "fontSize", "D")?.d()? as f32;
+        
+        let color_obj = env.get_field(obj, "colour", "Lcom/dropbear/utils/Colour;")?.l()?;
+        let color = if !color_obj.is_null() {
+            Color::from_jobject(env, &color_obj)?
+        } else {
+            Color::WHITE
+        };
+
+        Ok(TextStyle {
+            align,
+            font_size,
+            color,
+            ..TextStyle::default()
+        })
+    }
+}
+
+impl FromJObject for DynamicButtonStyle {
+    fn from_jobject(env: &mut JNIEnv, obj: &JObject) -> DropbearNativeResult<Self> {
+        let text_obj = env.get_field(obj, "text", "Lcom/dropbear/ui/styling/TextStyle;")?.l()?;
+        let text = if !text_obj.is_null() {
+            TextStyle::from_jobject(env, &text_obj)?
+        } else {
+            TextStyle::default()
+        };
+
+        let fill_obj = env.get_field(obj, "fill", "Lcom/dropbear/utils/Colour;")?.l()?;
+        let fill = if !fill_obj.is_null() {
+            Color::from_jobject(env, &fill_obj)?
+        } else {
+            Color::GRAY
+        };
+
+        Ok(DynamicButtonStyle {
+            text,
+            fill,
+            border: None,
+        })
+    }
+}
diff --git a/crates/eucalyptus-editor/src/camera.rs b/crates/eucalyptus-editor/src/camera.rs
index 1d54411..8e53574 100644
--- a/crates/eucalyptus-editor/src/camera.rs
+++ b/crates/eucalyptus-editor/src/camera.rs
@@ -56,8 +56,7 @@ impl InspectableComponent for CameraComponent {
                 ui.label("Speed:");
                 ui.add(
                     egui::DragValue::new(&mut self.settings.speed)
-                        .speed(0.1)
-                        .range(0.1..=20.0),
+                        .speed(0.1),
                 );
             });
 
@@ -66,7 +65,7 @@ impl InspectableComponent for CameraComponent {
                 ui.add(
                     egui::DragValue::new(&mut self.settings.sensitivity)
                         .speed(0.0001)
-                        .range(0.0001..=1.0),
+                        .range(0.0001..=1.5),
                 );
             });
 
diff --git a/crates/eucalyptus-editor/src/editor/scene.rs b/crates/eucalyptus-editor/src/editor/scene.rs
index 403c557..32d158a 100644
--- a/crates/eucalyptus-editor/src/editor/scene.rs
+++ b/crates/eucalyptus-editor/src/editor/scene.rs
@@ -232,12 +232,12 @@ impl Scene for Editor {
             {
                 for key in &self.input_state.pressed_keys {
                     match key {
-                        KeyCode::KeyW => camera.move_forwards(),
-                        KeyCode::KeyA => camera.move_left(),
-                        KeyCode::KeyD => camera.move_right(),
-                        KeyCode::KeyS => camera.move_back(),
-                        KeyCode::ShiftLeft => camera.move_down(),
-                        KeyCode::Space => camera.move_up(),
+                        KeyCode::KeyW => camera.move_forwards(dt),
+                        KeyCode::KeyA => camera.move_left(dt),
+                        KeyCode::KeyD => camera.move_right(dt),
+                        KeyCode::KeyS => camera.move_back(dt),
+                        KeyCode::ShiftLeft => camera.move_down(dt),
+                        KeyCode::Space => camera.move_up(dt),
                         _ => {}
                     }
                 }
diff --git a/scripting/commonMain/kotlin/com/dropbear/ui/styling/Border.kt b/scripting/commonMain/kotlin/com/dropbear/ui/styling/Border.kt
new file mode 100644
index 0000000..9d481aa
--- /dev/null
+++ b/scripting/commonMain/kotlin/com/dropbear/ui/styling/Border.kt
@@ -0,0 +1,9 @@
+package com.dropbear.ui.styling
+
+import com.dropbear.utils.Colour
+
+data class Border(
+    val colour: Colour,
+    val width: Double,
+) {
+}
\ No newline at end of file
diff --git a/scripting/commonMain/kotlin/com/dropbear/ui/styling/BorderRadius.kt b/scripting/commonMain/kotlin/com/dropbear/ui/styling/BorderRadius.kt
new file mode 100644
index 0000000..c279e7a
--- /dev/null
+++ b/scripting/commonMain/kotlin/com/dropbear/ui/styling/BorderRadius.kt
@@ -0,0 +1,56 @@
+package com.dropbear.ui.styling
+
+class BorderRadius(
+    topLeft: Double = 0.0,
+    topRight: Double = 0.0,
+    bottomLeft: Double = 0.0,
+    bottomRight: Double = 0.0
+) {
+    companion object {
+        fun uniform(value: Double): BorderRadius {
+            UInt
+            return BorderRadius(
+                value,
+                value,
+                value,
+                value,
+            )
+        }
+
+        fun top(value: Double): BorderRadius {
+            return BorderRadius(
+                value,
+                value,
+                0.0,
+                0.0,
+            )
+        }
+
+        fun bottom(value: Double): BorderRadius {
+            return BorderRadius(
+                0.0,
+                0.0,
+                value,
+                value,
+            )
+        }
+
+        fun left(value: Double): BorderRadius {
+            return BorderRadius(
+                value,
+                0.0,
+                value,
+                0.0,
+            )
+        }
+
+        fun right(value: Double): BorderRadius {
+            return BorderRadius(
+                0.0,
+                value,
+                0.0,
+                value,
+            )
+        }
+    }
+}
\ No newline at end of file
diff --git a/scripting/commonMain/kotlin/com/dropbear/ui/styling/DynamicButtonStyle.kt b/scripting/commonMain/kotlin/com/dropbear/ui/styling/DynamicButtonStyle.kt
index 40b7df4..314397e 100644
--- a/scripting/commonMain/kotlin/com/dropbear/ui/styling/DynamicButtonStyle.kt
+++ b/scripting/commonMain/kotlin/com/dropbear/ui/styling/DynamicButtonStyle.kt
@@ -5,4 +5,5 @@ import com.dropbear.utils.Colour
 data class DynamicButtonStyle(
     var text: TextStyle = TextStyle.label(),
     var fill: Colour = Colour.GRAY,
+    var border: Border? = null,
 )
\ No newline at end of file
diff --git a/scripting/commonMain/kotlin/com/dropbear/ui/styling/TextStyle.kt b/scripting/commonMain/kotlin/com/dropbear/ui/styling/TextStyle.kt
index 0249329..5fefb68 100644
--- a/scripting/commonMain/kotlin/com/dropbear/ui/styling/TextStyle.kt
+++ b/scripting/commonMain/kotlin/com/dropbear/ui/styling/TextStyle.kt
@@ -1,5 +1,6 @@
 package com.dropbear.ui.styling
 
+import com.dropbear.ui.styling.fonts.FontAttributes
 import com.dropbear.ui.styling.fonts.FontName
 import com.dropbear.ui.styling.fonts.TextAlignment
 import com.dropbear.utils.Colour
@@ -9,6 +10,7 @@ data class TextStyle(
     var fontSize: Double,
     var colour: Colour,
     var align: TextAlignment,
+    var attrs: FontAttributes,
 ) {
     companion object {
         fun label(): TextStyle {
@@ -16,7 +18,8 @@ data class TextStyle(
                 font = FontName("default"),
                 fontSize = 14.0,
                 colour = Colour.WHITE,
-                align = TextAlignment.Start
+                align = TextAlignment.Start,
+                attrs = FontAttributes()
             )
         }
     }
diff --git a/scripting/commonMain/kotlin/com/dropbear/ui/styling/fonts/CacheKeyFlags.kt b/scripting/commonMain/kotlin/com/dropbear/ui/styling/fonts/CacheKeyFlags.kt
new file mode 100644
index 0000000..4da6aef
--- /dev/null
+++ b/scripting/commonMain/kotlin/com/dropbear/ui/styling/fonts/CacheKeyFlags.kt
@@ -0,0 +1,16 @@
+package com.dropbear.ui.styling.fonts
+
+import kotlin.jvm.JvmInline
+
+@JvmInline
+value class CacheKeyFlags(val bits: Int = 0) {
+    operator fun plus(flag: CacheKeyFlags): CacheKeyFlags =
+        CacheKeyFlags(bits or flag.bits)
+
+    operator fun contains(flag: CacheKeyFlags): Boolean =
+        (bits and flag.bits) != 0
+
+    companion object {
+        val NONE = CacheKeyFlags(0)
+    }
+}
\ No newline at end of file
diff --git a/scripting/commonMain/kotlin/com/dropbear/ui/styling/fonts/CacheMetrics.kt b/scripting/commonMain/kotlin/com/dropbear/ui/styling/fonts/CacheMetrics.kt
new file mode 100644
index 0000000..8a4d231
--- /dev/null
+++ b/scripting/commonMain/kotlin/com/dropbear/ui/styling/fonts/CacheMetrics.kt
@@ -0,0 +1,6 @@
+package com.dropbear.ui.styling.fonts
+
+class CacheMetrics(
+    val fontSizeBits: UInt,
+    val lineHeightBits: UInt,
+)
\ No newline at end of file
diff --git a/scripting/commonMain/kotlin/com/dropbear/ui/styling/fonts/Family.kt b/scripting/commonMain/kotlin/com/dropbear/ui/styling/fonts/Family.kt
new file mode 100644
index 0000000..c6d50bd
--- /dev/null
+++ b/scripting/commonMain/kotlin/com/dropbear/ui/styling/fonts/Family.kt
@@ -0,0 +1,10 @@
+package com.dropbear.ui.styling.fonts
+
+sealed interface Family {
+    data class Name(val value: String) : Family
+    data object Serif : Family
+    data object SansSerif : Family
+    data object Cursive : Family
+    data object Fantasy : Family
+    data object Monospace : Family
+}
\ No newline at end of file
diff --git a/scripting/commonMain/kotlin/com/dropbear/ui/styling/fonts/FeatureTag.kt b/scripting/commonMain/kotlin/com/dropbear/ui/styling/fonts/FeatureTag.kt
new file mode 100644
index 0000000..aeb1497
--- /dev/null
+++ b/scripting/commonMain/kotlin/com/dropbear/ui/styling/fonts/FeatureTag.kt
@@ -0,0 +1,33 @@
+package com.dropbear.ui.styling.fonts
+
+import kotlin.jvm.JvmInline
+
+@JvmInline
+value class FeatureTag(val tag: ByteArray) {
+    init {
+        require(tag.size == 4) { "Tag must be exactly 4 bytes" }
+    }
+
+    companion object {
+        /** Kerning adjusts spacing between specific character pairs */
+        val KERNING = FeatureTag("kern".encodeToByteArray())
+        /** Standard ligatures (fi, fl, etc.) */
+        val STANDARD_LIGATURES = FeatureTag("liga".encodeToByteArray())
+        /** Contextual ligatures (context-dependent ligatures) */
+        val CONTEXTUAL_LIGATURES = FeatureTag("clig".encodeToByteArray())
+        /** Contextual alternates (glyph substitutions based on context) */
+        val CONTEXTUAL_ALTERNATES = FeatureTag("calt".encodeToByteArray())
+        /** Discretionary ligatures (optional stylistic ligatures) */
+        val DISCRETIONARY_LIGATURES = FeatureTag("dlig".encodeToByteArray())
+        /** Small caps (lowercase to small capitals) */
+        val SMALL_CAPS = FeatureTag("smcp".encodeToByteArray())
+        /** All small caps (uppercase and lowercase to small capitals) */
+        val ALL_SMALL_CAPS = FeatureTag("c2sc".encodeToByteArray())
+        /** Stylistic Set 1 (font-specific alternate glyphs) */
+        val STYLISTIC_SET_1 = FeatureTag("ss01".encodeToByteArray())
+        /** Stylistic Set 2 (font-specific alternate glyphs) */
+        val STYLISTIC_SET_2 = FeatureTag("ss02".encodeToByteArray())
+    }
+
+    fun asBytes(): ByteArray = tag
+}
\ No newline at end of file
diff --git a/scripting/commonMain/kotlin/com/dropbear/ui/styling/fonts/FontAttributes.kt b/scripting/commonMain/kotlin/com/dropbear/ui/styling/fonts/FontAttributes.kt
new file mode 100644
index 0000000..2f774a6
--- /dev/null
+++ b/scripting/commonMain/kotlin/com/dropbear/ui/styling/fonts/FontAttributes.kt
@@ -0,0 +1,16 @@
+package com.dropbear.ui.styling.fonts
+
+import com.dropbear.utils.Colour
+
+class FontAttributes(
+    var colourOptions: Colour? = null,
+    var family: Family = Family.SansSerif,
+    var stretch: Stretch = Stretch.Normal,
+    var style: FontStyle = FontStyle.Normal,
+    var weight: FontWeight = FontWeight.NORMAL,
+    var metadata: UInt = 0u,
+    var cacheKeyFlags: CacheKeyFlags = CacheKeyFlags.NONE,
+    var metricsOptions: CacheMetrics? = null,
+    var letterSpacingOptions: Double? = null,
+    var fontFeatures: FontFeatures = FontFeatures(),
+)
\ No newline at end of file
diff --git a/scripting/commonMain/kotlin/com/dropbear/ui/styling/fonts/FontFeature.kt b/scripting/commonMain/kotlin/com/dropbear/ui/styling/fonts/FontFeature.kt
new file mode 100644
index 0000000..250e209
--- /dev/null
+++ b/scripting/commonMain/kotlin/com/dropbear/ui/styling/fonts/FontFeature.kt
@@ -0,0 +1,6 @@
+package com.dropbear.ui.styling.fonts
+
+class FontFeature(
+    val tag: FeatureTag,
+    val value: UInt,
+)
\ No newline at end of file
diff --git a/scripting/commonMain/kotlin/com/dropbear/ui/styling/fonts/FontFeatures.kt b/scripting/commonMain/kotlin/com/dropbear/ui/styling/fonts/FontFeatures.kt
new file mode 100644
index 0000000..cdb38b1
--- /dev/null
+++ b/scripting/commonMain/kotlin/com/dropbear/ui/styling/fonts/FontFeatures.kt
@@ -0,0 +1,15 @@
+package com.dropbear.ui.styling.fonts
+
+class FontFeatures(val features: MutableList<FontFeature> = mutableListOf()) {
+    fun set(tag: FeatureTag, value: UInt) {
+        features.add(FontFeature(tag, value))
+    }
+
+    fun enable(tag: FeatureTag) {
+        set(tag, 1u)
+    }
+
+    fun disable(tag: FeatureTag) {
+        set(tag, 0u)
+    }
+}
\ No newline at end of file
diff --git a/scripting/commonMain/kotlin/com/dropbear/ui/styling/fonts/FontStyle.kt b/scripting/commonMain/kotlin/com/dropbear/ui/styling/fonts/FontStyle.kt
new file mode 100644
index 0000000..bbd4aa4
--- /dev/null
+++ b/scripting/commonMain/kotlin/com/dropbear/ui/styling/fonts/FontStyle.kt
@@ -0,0 +1,7 @@
+package com.dropbear.ui.styling.fonts
+
+enum class FontStyle {
+    Normal,
+    Italic,
+    Oblique,
+}
\ No newline at end of file
diff --git a/scripting/commonMain/kotlin/com/dropbear/ui/styling/fonts/FontWeight.kt b/scripting/commonMain/kotlin/com/dropbear/ui/styling/fonts/FontWeight.kt
new file mode 100644
index 0000000..4e3832a
--- /dev/null
+++ b/scripting/commonMain/kotlin/com/dropbear/ui/styling/fonts/FontWeight.kt
@@ -0,0 +1,15 @@
+package com.dropbear.ui.styling.fonts
+
+class FontWeight(val value: Int) {
+    companion object {
+        val THIN = FontWeight(100)
+        val EXTRA_LIGHT = FontWeight(200)
+        val LIGHT = FontWeight(300)
+        val NORMAL = FontWeight(400)
+        val MEDIUM = FontWeight(500)
+        val SEMIBOLD = FontWeight(600)
+        val BOLD = FontWeight(700)
+        val EXTRA_BOLD = FontWeight(800)
+        val BLACK = FontWeight(900)
+    }
+}
\ No newline at end of file
diff --git a/scripting/commonMain/kotlin/com/dropbear/ui/styling/fonts/Stretch.kt b/scripting/commonMain/kotlin/com/dropbear/ui/styling/fonts/Stretch.kt
new file mode 100644
index 0000000..319e4f3
--- /dev/null
+++ b/scripting/commonMain/kotlin/com/dropbear/ui/styling/fonts/Stretch.kt
@@ -0,0 +1,13 @@
+package com.dropbear.ui.styling.fonts
+
+enum class Stretch {
+    UltraCondensed,
+    ExtraCondensed,
+    Condensed,
+    SemiCondensed,
+    Normal,
+    SemiExpanded,
+    Expanded,
+    ExtraExpanded,
+    UltraExpanded,
+}
\ No newline at end of file
diff --git a/scripting/commonMain/kotlin/com/dropbear/ui/widgets/Button.kt b/scripting/commonMain/kotlin/com/dropbear/ui/widgets/Button.kt
index 8d50b57..288de03 100644
--- a/scripting/commonMain/kotlin/com/dropbear/ui/widgets/Button.kt
+++ b/scripting/commonMain/kotlin/com/dropbear/ui/widgets/Button.kt
@@ -4,14 +4,17 @@ import com.dropbear.ui.UIBuilder
 import com.dropbear.ui.UIInstruction
 import com.dropbear.ui.Widget
 import com.dropbear.ui.WidgetId
+import com.dropbear.ui.styling.Alignment
+import com.dropbear.ui.styling.BorderRadius
 import com.dropbear.ui.styling.DynamicButtonStyle
 import com.dropbear.ui.styling.Padding
 import com.dropbear.utils.Colour
 
 class Button(
     var text: String,
+    var alignment: Alignment,
     var padding: Padding,
-    var borderRadius: Double,
+    var borderRadius: BorderRadius,
     var style: DynamicButtonStyle,
     var hoverStyle: DynamicButtonStyle,
     var downStyle: DynamicButtonStyle,
@@ -28,8 +31,9 @@ class Button(
         fun styled(text: String) : Button {
             val result = Button(
                 text = text,
+                alignment = Alignment.CENTER,
                 padding = Padding.balanced(20.0, 10.0),
-                borderRadius = 6.0,
+                borderRadius = BorderRadius.uniform(6.0),
                 style = DynamicButtonStyle(fill = Colour.BACKGROUND_3),
                 hoverStyle = DynamicButtonStyle(fill = Colour.BACKGROUND_3.adjust(1.2)),
                 downStyle = DynamicButtonStyle(fill = Colour.BACKGROUND_3.adjust(0.8)),
@@ -43,8 +47,9 @@ class Button(
         fun unstyled(text: String) : Button {
             val result = Button(
                 text = text,
+                alignment = Alignment.CENTER,
                 padding = Padding.zero(),
-                borderRadius = 0.0,
+                borderRadius = BorderRadius(),
                 style = DynamicButtonStyle(),
                 hoverStyle = DynamicButtonStyle(),
                 downStyle = DynamicButtonStyle(),