kitgit

tirbofish/dropbear · diff

cd3689d · Thribhu K

feature: input detection works with buttons wip: button **still** doesnt render text. Unverified

diff --git a/.cargo/config.toml b/.cargo/config.toml
index d29d6c3..d2312c6 100644
--- a/.cargo/config.toml
+++ b/.cargo/config.toml
@@ -1,3 +1,3 @@
 [target.x86_64-unknown-linux-gnu]
 linker = "clang"
-rustflags = ["-C", "link-arg=-fuse-ld=mold"]
+rustflags = ["-C", "link-arg=-fuse-ld=mold", "-C", "relocation-model=pic"]
diff --git a/Cargo.toml b/Cargo.toml
index 4748b0b..41fa125 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -74,7 +74,7 @@ rapier3d = { version = "0.32", features = [ "simd-stable", "serde-serialize" ] }
 cbindgen = { version = "0.29.2" }
 postcard = { version = "1.1"}
 pollster = "0.4"
-yakui = { git = "https://github.com/SecondHalfGames/yakui", rev = "af8fc1f" }
+yakui = { git = "https://github.com/SecondHalfGames/yakui", rev = "af8fc1f", features = ["default-fonts"] }
 yakui-wgpu = { git = "https://github.com/SecondHalfGames/yakui", rev = "af8fc1f" }
 yakui-winit = { git = "https://github.com/SecondHalfGames/yakui", rev = "af8fc1f" }
 thiserror = "2.0"
diff --git a/README.md b/README.md
index 2bd5fb9..c1410ff 100644
--- a/README.md
+++ b/README.md
@@ -3,6 +3,8 @@
   <h1 align="center">dropbear</h1>
 </p>
 
+---
+
 dropbear is a game engine used to create games, made in Rust and scripted with the Kotlin Language.
 
 It's name is a double entendre, with it being the nickname of koalas but also fits in nicely with the theme of rust utilising memory management with "drops".
@@ -16,6 +18,7 @@ If you might have not realised, all the crates/projects names are after Australi
 - [eucalyptus-core](https://github.com/tirbofish/dropbear/tree/main/crates/eucalyptus-core) is the library used by both `redback-runtime` and `eucalyptus-editor` to share configs and metadata between each other.
 - [redback-runtime](https://github.com/tirbofish/dropbear/tree/main/crates/redback-runtime) is the runtime used to load .eupak files and run the game loaded on them.
 - [magna-carta](https://github.com/tirbofish/dropbear/tree/main/crates/magna-carta) is a rust library used to generate compile-time Kotlin/Native and Kotlin/JVM metadata for searching.
+- [slank](https://github.com/tirbofish/dropbear/tree/main/crates/slank) is a slang compiler that compiles .slang files into shaders during build.rs compilation time. 
 
 [//]: # (- [eucalyptus-sdk]&#40;https://github.com/tirbofish/dropbear/tree/main/eucalyptus-sdk&#41; is used to develop plugins to be used with the `eucalyptus-editor`)
 
diff --git a/crates/dropbear-engine/src/lib.rs b/crates/dropbear-engine/src/lib.rs
index e327b00..35467fb 100644
--- a/crates/dropbear-engine/src/lib.rs
+++ b/crates/dropbear-engine/src/lib.rs
@@ -1004,6 +1004,8 @@ impl ApplicationHandler for App {
             .lock()
             .handle_input(&state.window, &event);
 
+        state.scene_manager.handle_event(&event);
+
         match event {
             WindowEvent::Resized(size) => {
                 state.resize(size.width, size.height);
diff --git a/crates/dropbear-engine/src/mipmap.rs b/crates/dropbear-engine/src/mipmap.rs
index fe20ae4..8f78a2f 100644
--- a/crates/dropbear-engine/src/mipmap.rs
+++ b/crates/dropbear-engine/src/mipmap.rs
@@ -12,7 +12,7 @@ pub struct MipMapper {
 
 impl MipMapper {
     pub fn new(device: &wgpu::Device) -> Self {
-        let blit_shader = device.create_shader_module(slank::compiled::CompiledSlangShader::from_bytes(
+        let blit_shader = device.create_shader_module(slank::CompiledSlangShader::from_bytes(
             "mipmap blit_shader", 
             include_slang!("blit_shader")
         ).create_wgpu_shader());
diff --git a/crates/dropbear-engine/src/pipelines/light_cube.rs b/crates/dropbear-engine/src/pipelines/light_cube.rs
index 3975d61..ab5a07c 100644
--- a/crates/dropbear-engine/src/pipelines/light_cube.rs
+++ b/crates/dropbear-engine/src/pipelines/light_cube.rs
@@ -27,7 +27,7 @@ pub struct LightCubePipeline {
 
 impl DropbearShaderPipeline for LightCubePipeline {
     fn new(graphics: Arc<SharedGraphicsContext>) -> Self {
-        let shader = Shader::from_slang(graphics.clone(), &slank::compiled::CompiledSlangShader::from_bytes("light cube", include_slang!("light_cube")));
+        let shader = Shader::from_slang(graphics.clone(), &slank::CompiledSlangShader::from_bytes("light cube", include_slang!("light_cube")));
 
         let pipeline_layout = graphics.device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
             label: Some("light cube pipeline layout"),
diff --git a/crates/dropbear-engine/src/scene.rs b/crates/dropbear-engine/src/scene.rs
index 5ceca77..4dc785c 100644
--- a/crates/dropbear-engine/src/scene.rs
+++ b/crates/dropbear-engine/src/scene.rs
@@ -4,6 +4,7 @@
 
 use winit::event_loop::ActiveEventLoop;
 use winit::window::WindowId;
+use winit::event::WindowEvent;
 
 use crate::{WindowData, graphics::{SharedGraphicsContext}, input};
 use parking_lot::RwLock;
@@ -15,6 +16,7 @@ pub trait Scene {
     fn update(&mut self, dt: f32, graphics: Arc<SharedGraphicsContext>);
     fn render<'a>(&mut self, graphics: Arc<SharedGraphicsContext>);
     fn exit(&mut self, event_loop: &ActiveEventLoop);
+    fn handle_event(&mut self, _event: &WindowEvent) {}
     /// By far a mess of a trait however it works.
     ///
     /// This struct allows you to add in a SceneCommand enum and send it to the scene management for them
@@ -168,6 +170,14 @@ impl Manager {
         }
     }
 
+    pub fn handle_event(&mut self, event: &WindowEvent) {
+        if let Some(scene_name) = &self.current_scene
+            && let Some(scene) = self.scenes.get_mut(scene_name)
+        {
+            scene.write().handle_event(event);
+        }
+    }
+
     pub fn has_scene(&self) -> bool {
         self.current_scene.is_some()
     }
diff --git a/crates/dropbear-engine/src/shader.rs b/crates/dropbear-engine/src/shader.rs
index 4681432..e955d17 100644
--- a/crates/dropbear-engine/src/shader.rs
+++ b/crates/dropbear-engine/src/shader.rs
@@ -3,7 +3,7 @@
 use std::ops::Deref;
 use crate::graphics::SharedGraphicsContext;
 use std::sync::Arc;
-use slank::{compiled::CompiledSlangShader, utils::WgpuUtils};
+use slank::{CompiledSlangShader, utils::WgpuUtils};
 use wgpu::ShaderModule;
 
 /// A nice little struct that stored basic information about a WGPU shaders.
@@ -48,7 +48,7 @@ impl Shader {
 
         log::debug!("Created new shaders under the label: {:?}", label);
 
-        slank::compiled::CompiledSlangShader::from_bytes("light cube", slank::include_slang!("light_cube"));
+        CompiledSlangShader::from_bytes("light cube", slank::include_slang!("light_cube"));
 
         Self {
             label: match label {
diff --git a/crates/eucalyptus-core/src/ui.rs b/crates/eucalyptus-core/src/ui.rs
index a6426a1..561ad2e 100644
--- a/crates/eucalyptus-core/src/ui.rs
+++ b/crates/eucalyptus-core/src/ui.rs
@@ -30,8 +30,6 @@ pub struct UIComponent {
     pub ui_file: ResourceReference,
 }
 
-// note for tomorrow: use UIInstruction like that of asm
-
 #[derive(Clone, Copy, Debug, Default)]
 pub struct WidgetState {
     pub clicked: bool,
@@ -60,21 +58,13 @@ impl NativeWidget for WrapperWidget<yakui::widgets::Button> {
 
 pub trait WidgetParser: Send + Sync {
     fn parse(&self, env: &mut JNIEnv, obj: &JObject) -> DropbearNativeResult<Option<Box<dyn NativeWidget>>>;
+    fn name(&self) -> String;
 }
 
-static PARSERS: std::sync::OnceLock<Mutex<Vec<Box<dyn WidgetParser>>>> = std::sync::OnceLock::new();
-
 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)
-    })
+    UI_CONTEXT.with(|v| {
+        v.borrow().parsers.lock().push(Box::new(parser));
+    });
 }
 
 struct ButtonParser;
@@ -84,6 +74,7 @@ impl WidgetParser for ButtonParser {
         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();
+        // println!("ButtonParser obj get_string result: {}", name_string);
 
         if name_string.contains("ButtonInstruction$Button") {
             let button_obj = env.get_field(obj, "button", "Lcom/dropbear/ui/widgets/Button;")?.l()?;
@@ -100,6 +91,10 @@ impl WidgetParser for ButtonParser {
 
         Ok(None)
     }
+
+    fn name(&self) -> String {
+        String::from("ButtonParser")
+    }
 }
 
 
@@ -107,6 +102,7 @@ pub struct UiContext {
     pub yakui_state: Mutex<Yakui>,
     pub instruction_set: Mutex<Vec<Box<dyn NativeWidget>>>,
     pub widget_states: Mutex<HashMap<i64, WidgetState>>,
+    pub parsers: Mutex<Vec<Box<dyn WidgetParser>>>,
 }
 
 pub fn poll() {
@@ -114,11 +110,7 @@ pub fn poll() {
         let ctx = v.borrow();
         let mut instructions = ctx.instruction_set.lock();
         let mut widget_states = ctx.widget_states.lock();
-        // Clear previous states before rebuild? 
-        // Or yakui persistent state implies we should keep? 
-        // If we clear, and script runs before poll, it might see empty.
-        // But script reads states from PREVIOUS frame usually.
-        // Let's clear to avoid stale data from removed widgets.
+
         widget_states.clear();
         
         let current_instructions = instructions.drain(..).collect::<Vec<Box<dyn NativeWidget>>>();
@@ -130,10 +122,17 @@ pub fn poll() {
 
 impl UiContext {
     pub fn new() -> Self {
+        let mut parsers: Vec<Box<dyn WidgetParser>> = Vec::new();
+
+        parsers.push(Box::new(ButtonParser));
+
+        let yakui = Yakui::new();
+
         Self {
-            yakui_state: Mutex::new(Yakui::new()),
+            yakui_state: Mutex::new(yakui),
             instruction_set: Default::default(),
             widget_states: Default::default(),
+            parsers: Mutex::new(parsers),
         }
     }
 }
@@ -148,11 +147,11 @@ pub trait UiWidgetType: FromJObject {
 pub mod jni {
     #![allow(non_snake_case)]
 
-    use jni::sys::jlong;
+    use jni::sys::{jboolean, jlong};
     use jni::objects::{JClass, JObjectArray};
     use jni::JNIEnv;
     use crate::convert_ptr;
-    use crate::ui::{UiContext, get_parsers};
+    use crate::ui::{UiContext};
 
     #[unsafe(no_mangle)]
     pub extern "system" fn Java_com_dropbear_ui_UINative_renderUI(
@@ -161,12 +160,12 @@ pub mod jni {
         ui_buf_ptr: jlong,
         instructions: JObjectArray,
     ) {
-        println!("[Java_com_dropbear_ui_UINative_renderUI] received new renderUI request");
+        // println!("[Java_com_dropbear_ui_UINative_renderUI] received new renderUI request");
         let ui = convert_ptr!(ui_buf_ptr => UiContext);
         let mut rust_instructions = Vec::new();
 
         let count = env.get_array_length(&instructions).unwrap_or(0);
-        let parsers_guard = get_parsers().lock();
+        let parsers_guard = ui.parsers.lock();
 
         for i in 0..count {
             let obj = match env.get_object_array_element(&instructions, i) {
@@ -178,13 +177,13 @@ pub mod jni {
             for parser in parsers_guard.iter() {
                 match parser.parse(&mut env, &obj) {
                     Ok(Some(widget)) => {
-                        println!("[Java_com_dropbear_ui_UINative_renderUI] successfully located widget: {:?}", widget);
+                        // println!("Received widget: {:?}", widget);
                         rust_instructions.push(widget);
                         break;
                     },
                     Ok(None) => {println!("[Java_com_dropbear_ui_UINative_renderUI] Ok but None"); continue},
                     Err(e) => {
-                        eprintln!("Error converting UI instruction: {:?}", e);
+                        eprintln!("[Java_com_dropbear_ui_UINative_renderUI] Error converting UI instruction: {:?}", e);
                     }
                 }
             }
@@ -199,10 +198,10 @@ pub mod jni {
         _class: JClass,
         ui_buf_ptr: jlong,
         id: jlong,
-    ) -> bool {
+    ) -> jboolean {
          let ui = convert_ptr!(ui_buf_ptr => UiContext);
          let states = ui.widget_states.lock();
-         states.get(&id).map(|s| s.clicked).unwrap_or(false)
+         states.get(&id).map(|s| s.clicked).unwrap_or(false).into()
     }
 
     #[unsafe(no_mangle)]
@@ -211,9 +210,9 @@ pub mod jni {
         _class: JClass,
         ui_buf_ptr: jlong,
         id: jlong,
-    ) -> bool {
+    ) -> jboolean {
          let ui = convert_ptr!(ui_buf_ptr => UiContext);
          let states = ui.widget_states.lock();
-         states.get(&id).map(|s| s.hovering).unwrap_or(false)
+         states.get(&id).map(|s| s.hovering).unwrap_or(false).into()
     }
 }
\ No newline at end of file
diff --git a/crates/eucalyptus-core/src/ui/button.rs b/crates/eucalyptus-core/src/ui/button.rs
index a2a801d..a56dc72 100644
--- a/crates/eucalyptus-core/src/ui/button.rs
+++ b/crates/eucalyptus-core/src/ui/button.rs
@@ -12,14 +12,14 @@ impl FromJObject for Button {
         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(
+        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, "alignment", "Lcom/dropbear/ui/styling/Alignment;")?.l()?;
-        let alignment = Alignment::from_jobject(
+        let f = env.get_field(obj, "padding", "Lcom/dropbear/ui/styling/Padding;")?.l()?;
+        let padding = Pad::from_jobject(
             env,
             &f
         )?;
diff --git a/crates/eucalyptus-core/src/ui/utils.rs b/crates/eucalyptus-core/src/ui/utils.rs
index 413a661..7067d9d 100644
--- a/crates/eucalyptus-core/src/ui/utils.rs
+++ b/crates/eucalyptus-core/src/ui/utils.rs
@@ -2,11 +2,11 @@ use jni::JNIEnv;
 use jni::objects::{JObject, JByteArray};
 use crate::scripting::jni::utils::FromJObject;
 use crate::scripting::result::DropbearNativeResult;
-use yakui::Color;
+use yakui::{Border, 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};
+use yakui::cosmic_text::{Attrs, AttrsOwned, FamilyOwned, Weight, Style, Stretch, CacheKeyFlags, FontFeatures, Feature, FeatureTag, Metrics, CacheMetrics};
 
 impl FromJObject for FeatureTag {
     fn from_jobject(env: &mut JNIEnv, obj: &JObject) -> DropbearNativeResult<Self> {
@@ -54,6 +54,30 @@ impl FromJObject for FontFeatures {
     }
 }
 
+impl FromJObject for CacheMetrics {
+    fn from_jobject(env: &mut JNIEnv, obj: &JObject) -> DropbearNativeResult<Self> {
+        let font_size = env.get_field(obj, "fontSize", "D")?.d()? as f32;
+        let line_height = env.get_field(obj, "lineHeight", "D")?.d()? as f32;
+
+        Ok(CacheMetrics::from(Metrics {
+            font_size,
+            line_height,
+        }))
+    }
+}
+
+impl FromJObject for Border {
+    fn from_jobject(env: &mut JNIEnv, obj: &JObject) -> DropbearNativeResult<Self> {
+        let color_obj = env.get_field(obj, "colour", "Lcom/dropbear/utils/Colour;")?.l()?;
+        let color = Color::from_jobject(env, &color_obj)?;
+        let width = env.get_field(obj, "width", "D")?.d()? as f32;
+        Ok(Border {
+            color,
+            width,
+        })
+    }
+}
+
 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;
@@ -214,6 +238,16 @@ impl FromJObject for AttrsOwned {
             FontFeatures::default()
         };
 
+        let cache_key_flags_val = env.get_field(obj, "cacheKeyFlags", "I")?.i()? as u32;
+        let cache_key_flags = CacheKeyFlags::from_bits_truncate(cache_key_flags_val);
+        
+        let metrics_obj = env.get_field(obj, "metricsOptions", "Lcom/dropbear/ui/styling/fonts/CacheMetrics;")?.l()?;
+        let metrics_opt = if !metrics_obj.is_null() {
+            Some(CacheMetrics::from_jobject(env, &metrics_obj)?)
+        } else {
+            None
+        };
+
         Ok(AttrsOwned::new(&Attrs {
             family,
             stretch,
@@ -221,8 +255,8 @@ impl FromJObject for AttrsOwned {
             weight,
             metadata,
             color_opt,
-            cache_key_flags: CacheKeyFlags::empty(),
-            metrics_opt: None,
+            cache_key_flags,
+            metrics_opt,
             letter_spacing_opt,
             font_features,
         }))
@@ -243,11 +277,15 @@ impl FromJObject for TextStyle {
             Color::WHITE
         };
 
+        let attrs_obj = env.get_field(obj, "attrs", "Lcom/dropbear/ui/styling/fonts/FontAttributes;")?.l()?;
+        let attrs = AttrsOwned::from_jobject(env, &attrs_obj)?;
+
         Ok(TextStyle {
             align,
             font_size,
+            line_height_override: None,
             color,
-            ..TextStyle::default()
+            attrs,
         })
     }
 }
@@ -258,6 +296,7 @@ impl FromJObject for DynamicButtonStyle {
         let text = if !text_obj.is_null() {
             TextStyle::from_jobject(env, &text_obj)?
         } else {
+            println!("Text is null, setting to default");
             TextStyle::default()
         };
 
@@ -268,10 +307,17 @@ impl FromJObject for DynamicButtonStyle {
             Color::GRAY
         };
 
+        let border_obj = env.get_field(obj, "border", "Lcom/dropbear/ui/styling/Border;")?.l()?;
+        let border = if !border_obj.is_null() {
+            Some(Border::from_jobject(env, &border_obj)?)
+        } else {
+            None
+        };
+
         Ok(DynamicButtonStyle {
             text,
             fill,
-            border: None,
+            border,
         })
     }
 }
diff --git a/crates/redback-runtime/Cargo.toml b/crates/redback-runtime/Cargo.toml
index 2456a7a..658ce9e 100644
--- a/crates/redback-runtime/Cargo.toml
+++ b/crates/redback-runtime/Cargo.toml
@@ -28,12 +28,13 @@ serde.workspace = true
 crossbeam-channel.workspace = true
 gilrs.workspace = true
 futures.workspace = true
+yakui-winit.workspace = true
+yakui.workspace = true
+egui.workspace = true
+egui_extras.workspace = true
 glam.workspace = true
 wgpu.workspace = true
-egui.workspace = true
-yakui.workspace = true
 yakui-wgpu.workspace = true
-egui_extras.workspace = true
 
 [features]
 debug = []
diff --git a/crates/redback-runtime/src/lib.rs b/crates/redback-runtime/src/lib.rs
index 1108dc4..2ada09a 100644
--- a/crates/redback-runtime/src/lib.rs
+++ b/crates/redback-runtime/src/lib.rs
@@ -25,6 +25,7 @@ use eucalyptus_core::command::COMMAND_BUFFER;
 use eucalyptus_core::scene::loading::IsSceneLoaded;
 use std::collections::HashMap;
 use std::path::PathBuf;
+use yakui_winit::YakuiWinit;
 use eucalyptus_core::physics::PhysicsState;
 use eucalyptus_core::rapier3d::prelude::*;
 use eucalyptus_core::register_components;
@@ -109,6 +110,9 @@ pub struct PlayMode {
     collider_wireframe_geometry_cache: HashMap<ColliderShapeKey, WireframeGeometry>,
     collider_instance_buffer: Option<ResizableBuffer<ColliderInstanceRaw>>,
     viewport_offset: (f32, f32),
+
+    // ui
+    yakui_winit: Option<YakuiWinit>,
 }
 
 impl PlayMode {
@@ -155,6 +159,7 @@ impl PlayMode {
             collision_event_receiver: Some(ce_r),
             collision_force_event_receiver: Some(cfe_r),
             event_collector,
+            yakui_winit: None,
         };
 
         log::debug!("Created new play mode instance");
diff --git a/crates/redback-runtime/src/scene.rs b/crates/redback-runtime/src/scene.rs
index d113714..f3f562c 100644
--- a/crates/redback-runtime/src/scene.rs
+++ b/crates/redback-runtime/src/scene.rs
@@ -12,6 +12,8 @@ use hecs::Entity;
 use wgpu::{Color};
 use wgpu::util::DeviceExt;
 use winit::event_loop::ActiveEventLoop;
+use winit::event::WindowEvent;
+use yakui::font::Fonts;
 use yakui_wgpu::SurfaceInfo;
 use dropbear_engine::camera::Camera;
 use dropbear_engine::buffer::ResizableBuffer;
@@ -37,6 +39,11 @@ use eucalyptus_core::ui::UI_CONTEXT;
 
 impl Scene for PlayMode {
     fn load(&mut self, graphics: Arc<SharedGraphicsContext>) {
+        let mut yak = yakui_winit::YakuiWinit::new(&graphics.window);
+        yak.set_automatic_viewport(false);
+        yak.set_automatic_scale_factor(false);
+        self.yakui_winit = Some(yak);
+        
         if self.current_scene.is_none() {
             let initial_scene = if let Some(s) = &self.initial_scene {
                 s.clone()
@@ -461,6 +468,11 @@ impl Scene for PlayMode {
                         yakui.set_surface_size(yakui::geometry::Vec2::new(display_width, display_height));
                         yakui.start();
 
+                        let fonts = yakui.dom().get_global_or_init(Fonts::default);
+                        fonts.with_system(|v| {
+                            log_once::debug_once!("font len: {}", v.db().len());
+                        });
+
                         eucalyptus_core::ui::poll();
 
                         // Layer::new().show(|| {
@@ -869,6 +881,17 @@ impl Scene for PlayMode {
         }
     }
 
+    fn handle_event(&mut self, event: &WindowEvent) {
+        UI_CONTEXT.with(|yakui_cell| {
+            let yak = yakui_cell.borrow();
+            let mut yakui = yak.yakui_state.lock();
+            if let Some(yak) = &mut self.yakui_winit {
+                yak.handle_window_event(&mut yakui, event);
+
+            }
+        });
+    }
+
     fn exit(&mut self, _event_loop: &ActiveEventLoop) {}
 
     fn run_command(&mut self) -> SceneCommand {
diff --git a/crates/slank/Cargo.toml b/crates/slank/Cargo.toml
index 7a19e95..40b1ac0 100644
--- a/crates/slank/Cargo.toml
+++ b/crates/slank/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "slank"
-version = "0.1.0"
+version.workspace = true
 edition.workspace = true
 license.workspace = true
 repository.workspace = true
diff --git a/scripting/commonMain/kotlin/com/dropbear/ui/styling/BorderRadius.kt b/scripting/commonMain/kotlin/com/dropbear/ui/styling/BorderRadius.kt
index c279e7a..e8390d1 100644
--- a/scripting/commonMain/kotlin/com/dropbear/ui/styling/BorderRadius.kt
+++ b/scripting/commonMain/kotlin/com/dropbear/ui/styling/BorderRadius.kt
@@ -1,14 +1,13 @@
 package com.dropbear.ui.styling
 
-class BorderRadius(
-    topLeft: Double = 0.0,
-    topRight: Double = 0.0,
-    bottomLeft: Double = 0.0,
-    bottomRight: Double = 0.0
+data class BorderRadius(
+    var topLeft: Double = 0.0,
+    var topRight: Double = 0.0,
+    var bottomLeft: Double = 0.0,
+    var bottomRight: Double = 0.0
 ) {
     companion object {
         fun uniform(value: Double): BorderRadius {
-            UInt
             return BorderRadius(
                 value,
                 value,
diff --git a/scripting/commonMain/kotlin/com/dropbear/ui/styling/DynamicButtonStyle.kt b/scripting/commonMain/kotlin/com/dropbear/ui/styling/DynamicButtonStyle.kt
index 314397e..e2d2e00 100644
--- a/scripting/commonMain/kotlin/com/dropbear/ui/styling/DynamicButtonStyle.kt
+++ b/scripting/commonMain/kotlin/com/dropbear/ui/styling/DynamicButtonStyle.kt
@@ -1,9 +1,10 @@
 package com.dropbear.ui.styling
 
+import com.dropbear.ui.styling.fonts.TextAlignment
 import com.dropbear.utils.Colour
 
 data class DynamicButtonStyle(
-    var text: TextStyle = TextStyle.label(),
+    var text: TextStyle = TextStyle(align = TextAlignment.Center),
     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 5fefb68..23329b1 100644
--- a/scripting/commonMain/kotlin/com/dropbear/ui/styling/TextStyle.kt
+++ b/scripting/commonMain/kotlin/com/dropbear/ui/styling/TextStyle.kt
@@ -1,30 +1,23 @@
 package com.dropbear.ui.styling
 
+import com.dropbear.ui.styling.fonts.Family
 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
 
 data class TextStyle(
-    var font: FontName,
-    var fontSize: Double,
-    var colour: Colour,
-    var align: TextAlignment,
-    var attrs: FontAttributes,
+    var fontSize: Double = 14.0,
+    var lineHeightOverride: Double? = null,
+    var colour: Colour = Colour.WHITE,
+    var align: TextAlignment = TextAlignment.Start,
+    var attrs: FontAttributes = FontAttributes(
+        family = Family.SansSerif,
+    ),
 ) {
     companion object {
         fun label(): TextStyle {
-            return TextStyle(
-                font = FontName("default"),
-                fontSize = 14.0,
-                colour = Colour.WHITE,
-                align = TextAlignment.Start,
-                attrs = FontAttributes()
-            )
+            return TextStyle()
         }
     }
-
-    override fun toString(): String {
-        return "TextStyle(font=$font, fontSize=$fontSize, colour=$colour)"
-    }
 }
\ 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
index 8a4d231..fcb6c48 100644
--- a/scripting/commonMain/kotlin/com/dropbear/ui/styling/fonts/CacheMetrics.kt
+++ b/scripting/commonMain/kotlin/com/dropbear/ui/styling/fonts/CacheMetrics.kt
@@ -1,6 +1,6 @@
 package com.dropbear.ui.styling.fonts
 
 class CacheMetrics(
-    val fontSizeBits: UInt,
-    val lineHeightBits: UInt,
+    val fontSize: Double,
+    val lineHeight: Double,
 )
\ 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
index 2f774a6..a033d4b 100644
--- a/scripting/commonMain/kotlin/com/dropbear/ui/styling/fonts/FontAttributes.kt
+++ b/scripting/commonMain/kotlin/com/dropbear/ui/styling/fonts/FontAttributes.kt
@@ -4,7 +4,7 @@ import com.dropbear.utils.Colour
 
 class FontAttributes(
     var colourOptions: Colour? = null,
-    var family: Family = Family.SansSerif,
+    var family: Family = Family.Name("default"),
     var stretch: Stretch = Stretch.Normal,
     var style: FontStyle = FontStyle.Normal,
     var weight: FontWeight = FontWeight.NORMAL,
diff --git a/scripting/commonMain/kotlin/com/dropbear/ui/widgets/Button.kt b/scripting/commonMain/kotlin/com/dropbear/ui/widgets/Button.kt
index 288de03..348f8ba 100644
--- a/scripting/commonMain/kotlin/com/dropbear/ui/widgets/Button.kt
+++ b/scripting/commonMain/kotlin/com/dropbear/ui/widgets/Button.kt
@@ -5,9 +5,12 @@ 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.Border
 import com.dropbear.ui.styling.BorderRadius
 import com.dropbear.ui.styling.DynamicButtonStyle
 import com.dropbear.ui.styling.Padding
+import com.dropbear.ui.styling.TextStyle
+import com.dropbear.ui.styling.fonts.TextAlignment
 import com.dropbear.utils.Colour
 
 class Button(
@@ -28,23 +31,45 @@ class Button(
         get() = getHovering()
 
     companion object {
-        fun styled(text: String) : Button {
+        fun styled(text: String, id: String = text) : Button {
+            val style = DynamicButtonStyle(
+                fill = Colour.BACKGROUND_3,
+                text = TextStyle(
+                    colour = Colour.WHITE.adjust(0.8),
+                    align = TextAlignment.Center
+                ),
+                border = Border(
+                    colour = Colour.BACKGROUND_1,
+                    width = 1.0
+                )
+            )
+
+            val hoverStyle = DynamicButtonStyle(
+                fill = Colour.BACKGROUND_3.adjust(1.2),
+                border = Border(Colour.WHITE.adjust(0.75), 1.0),
+            )
+
+            val downStyle = DynamicButtonStyle(
+                fill = Colour.BACKGROUND_3.adjust(0.8),
+                border = Border(Colour.WHITE, 1.0)
+            )
+
             val result = Button(
                 text = text,
                 alignment = Alignment.CENTER,
                 padding = Padding.balanced(20.0, 10.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)),
+                style = style,
+                hoverStyle = hoverStyle,
+                downStyle = downStyle,
             )
 
-            result.id = WidgetId(result.hashCode().toLong())
+            result.id = WidgetId(id.hashCode().toLong())
 
             return result
         }
 
-        fun unstyled(text: String) : Button {
+        fun unstyled(text: String, id: String = text) : Button {
             val result = Button(
                 text = text,
                 alignment = Alignment.CENTER,
@@ -55,7 +80,7 @@ class Button(
                 downStyle = DynamicButtonStyle(),
             )
 
-            result.id = WidgetId(result.hashCode().toLong())
+            result.id = WidgetId(id.hashCode().toLong())
 
             return result
         }
diff --git a/scripting/commonMain/kotlin/com/dropbear/utils/Colour.kt b/scripting/commonMain/kotlin/com/dropbear/utils/Colour.kt
index c993a23..626bbeb 100644
--- a/scripting/commonMain/kotlin/com/dropbear/utils/Colour.kt
+++ b/scripting/commonMain/kotlin/com/dropbear/utils/Colour.kt
@@ -26,8 +26,8 @@ class Colour(
         val FUCHSIA = Colour.rgb(255u, 255u, 0u)
         val GRAY = Colour.rgb(127u, 127u, 127u)
         val TRANSPARENT = Colour(0u, 0u, 0u, 0u)
-        val WHITE = Colour.rgb(0u, 0u, 0u)
-        val BLACK = Colour.rgb(255u, 255u, 255u)
+        val WHITE = Colour.rgb(255u, 255u, 255u)
+        val BLACK = Colour.rgb(0u, 0u, 0u)
 
         val BACKGROUND_1 = Colour.rgb(31u, 31u, 31u)
         val BACKGROUND_2 = Colour.rgb(42u, 42u, 42u)
@@ -63,10 +63,10 @@ class Colour(
      */
     fun normalize(): Vector4d {
         return Vector4d(
-            x=(r/ 255u).toDouble(),
-            y=(g/ 255u).toDouble(),
-            z=(b/ 255u).toDouble(),
-            w=(a/ 255u).toDouble(),
+            x=(r.toDouble() / 255.0),
+            y=(g.toDouble() / 255.0),
+            z=(b.toDouble() / 255.0),
+            w=(a.toDouble() / 255.0),
         )
     }
 
diff --git a/scripting/jvmMain/kotlin/com/dropbear/DropbearEngine.jvm.kt b/scripting/jvmMain/kotlin/com/dropbear/DropbearEngine.jvm.kt
index 808543e..bf95bcd 100644
--- a/scripting/jvmMain/kotlin/com/dropbear/DropbearEngine.jvm.kt
+++ b/scripting/jvmMain/kotlin/com/dropbear/DropbearEngine.jvm.kt
@@ -1,5 +1,6 @@
 package com.dropbear
 
+import com.dropbear.logging.Logger
 import com.dropbear.ui.UIInstruction
 import com.dropbear.ui.UINative
 
@@ -16,5 +17,6 @@ internal actual fun quit() {
 }
 
 internal actual fun renderUI(instructions: List<UIInstruction>) {
+    Logger.debug("instructions: $instructions")
     UINative.renderUI(DropbearEngine.native.uiBufferHandle, instructions.toTypedArray())
 }
\ No newline at end of file