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(),