kitgit

tirbofish/dropbear · commit

e0632fe9cac4c227ac16fb8c8490b830891e46a5

feature: did a little bit here and there

Unverified

Thribhu K <4tkbytes@pm.me> · 2026-02-01 05:51

view full diff

diff --git a/crates/kino-ui/src/lib.rs b/crates/kino-ui/src/lib.rs
index 8354703..febd76d 100644
--- a/crates/kino-ui/src/lib.rs
+++ b/crates/kino-ui/src/lib.rs
@@ -243,6 +243,7 @@ impl KinoState {
                         device,
                         queue,
                         self.renderer.texture_bind_group_layout(),
+                        self.renderer.format,
                     );
                     return self.assets.add_texture_with_label(label, fallback);
                 }
@@ -262,6 +263,7 @@ impl KinoState {
             raw.as_ref(),
             width,
             height,
+            self.renderer.format,
         );
         self.assets.add_texture_with_label(label, texture)
     }
@@ -271,7 +273,8 @@ impl KinoState {
         self.assets.get_texture_handle(label)
     }
 
-    pub(crate) fn input(&self) -> &KinoWinitWindowing {
+    /// Returns a reference to the windowing. Used for input detection.
+    pub fn input(&self) -> &KinoWinitWindowing {
         &self.windowing
     }
 
@@ -348,11 +351,23 @@ impl KinoState {
 
 /// The id of the widget, often being a hash.
 #[derive(Clone, Copy, Hash, Eq, PartialEq, Debug)]
-pub struct WidgetId(pub u64);
+pub struct WidgetId(u64);
 
 impl WidgetId {
-    pub fn from_str(str: &str) -> Self {
-        str.into()
+    /// Creates a new [`WidgetId`] from an object that can be hashed.
+    pub fn new<H: Hash>(value: H) -> Self {
+        let mut hasher = DefaultHasher::new();
+        value.hash(&mut hasher);
+        WidgetId(hasher.finish())
+    }
+
+    /// Creates a new [`WidgetId`] from a simple u64 value.
+    pub fn from_raw(value: u64) -> Self {
+        WidgetId(value)
+    }
+
+    pub fn get_id(&self) -> u64 {
+        self.0
     }
 }
 
@@ -364,6 +379,29 @@ impl Into<WidgetId> for &str {
     }
 }
 
+impl Into<WidgetId> for String {
+    fn into(self) -> WidgetId {
+        let mut hasher = DefaultHasher::new();
+        self.hash(&mut hasher);
+        WidgetId(hasher.finish())
+    }
+}
+
+impl Into<WidgetId> for u64 {
+    fn into(self) -> WidgetId {
+        WidgetId(self)
+    }
+}
+
+impl<T: Hash, U: Hash> Into<WidgetId> for (T, U) {
+    fn into(self) -> WidgetId {
+        let mut hasher = DefaultHasher::new();
+        self.0.hash(&mut hasher);
+        self.1.hash(&mut hasher);
+        WidgetId(hasher.finish())
+    }
+}
+
 pub enum UiInstructionType {
     Containered(ContaineredWidgetType),
     Widget(Box<dyn NativeWidget>),
diff --git a/crates/kino-ui/src/rendering.rs b/crates/kino-ui/src/rendering.rs
index 156fc72..236ee72 100644
--- a/crates/kino-ui/src/rendering.rs
+++ b/crates/kino-ui/src/rendering.rs
@@ -14,6 +14,7 @@ pub mod batching;
 pub struct KinoWGPURenderer {
     pipeline: KinoRendererPipeline,
     default_texture: texture::Texture,
+    pub format: wgpu::TextureFormat,
     pub size: Vec2,
     // pub text: KinoTextRenderer,
 
@@ -53,13 +54,14 @@ impl KinoWGPURenderer {
             }],
         });
 
-        let default_texture = texture::Texture::create_default(&device, &queue, &pipeline.texture_bind_group_layout);
+        let default_texture = texture::Texture::create_default(&device, &queue, &pipeline.texture_bind_group_layout, surface_format);
         // let text = KinoTextRenderer::new(&device, &queue, surface_format);
 
         log::debug!("Created KinoWGPURenderer");
         Self {
             pipeline,
             default_texture,
+            format: surface_format,
             size: Vec2::from_array(size),
             // text,
             camera: CameraRendering {
diff --git a/crates/kino-ui/src/rendering/texture.rs b/crates/kino-ui/src/rendering/texture.rs
index d0884fe..b5e7bd7 100644
--- a/crates/kino-ui/src/rendering/texture.rs
+++ b/crates/kino-ui/src/rendering/texture.rs
@@ -27,6 +27,7 @@ impl Texture {
         data: &[u8],
         width: u32,
         height: u32,
+        texture_format: TextureFormat,
     ) -> Self {
         log::debug!("Creating new texture");
         
@@ -40,7 +41,7 @@ impl Texture {
             mip_level_count: 1,
             sample_count: 1,
             dimension: TextureDimension::D2,
-            format: TextureFormat::Rgba8UnormSrgb,
+            format: texture_format,
             usage: TextureUsages::TEXTURE_BINDING | TextureUsages::COPY_DST,
             view_formats: &[],
         });
@@ -94,9 +95,9 @@ impl Texture {
     /// Creates a 1×1 white fallback texture
     ///
     /// Used when no valid texture is provided for a draw call
-    pub fn create_default(device: &Device, queue: &Queue, layout: &BindGroupLayout) -> Self {
+    pub fn create_default(device: &Device, queue: &Queue, layout: &BindGroupLayout, texture_format: TextureFormat) -> Self {
         log::debug!("Creating standard white texture");
-        Self::from_bytes(device, queue, layout, &[255u8, 255, 255, 255], 1, 1)
+        Self::from_bytes(device, queue, layout, &[255u8, 255, 255, 255], 1, 1, texture_format)
     }
 
     /// Binds this texture at the given index in the render pass
diff --git a/crates/kino-ui/src/widgets/mod.rs b/crates/kino-ui/src/widgets/mod.rs
index b63ccfa..6544624 100644
--- a/crates/kino-ui/src/widgets/mod.rs
+++ b/crates/kino-ui/src/widgets/mod.rs
@@ -9,7 +9,12 @@ pub enum Anchor {
     TopLeft,
 }
 
+/// Defines a widget with no children. 
 pub trait NativeWidget: Send + Sync {
+    /// Renders the widget. 
+    /// 
+    /// The state is provided for you to manipulate, such as adding a new response based on the
+    /// [`WidgetId`]. 
     fn render(self: Box<Self>, state: &mut KinoState);
     fn id(&self) -> WidgetId;
     fn as_any(&self) -> &dyn Any;
@@ -19,4 +24,43 @@ pub trait ContaineredWidget: Send + Sync {
     fn render(self: Box<Self>, children: Vec<UiNode>, state: &mut KinoState);
     fn id(&self) -> WidgetId;
     fn as_any(&self) -> &dyn Any;
+}
+
+/// Describes the colour that the widget will be filled in with. 
+#[derive(Copy, Clone, Debug, PartialEq)]
+pub struct Fill {
+    /// The colour of the fill described as RGBA between the range `0.0` <-> `1.0`.
+    ///
+    /// If a texture is applied to the colour, it will create a tinted texture on the quad.
+    pub colour: [f32; 4],
+}
+
+impl Fill {
+    /// Creates a new [`Fill`]
+    pub fn new(colour: [f32; 4]) -> Self {
+        Fill { colour }
+    }
+}
+
+impl Default for Fill {
+    fn default() -> Self {
+        Fill { colour: [1.0, 1.0, 1.0, 1.0] }
+    }
+}
+
+/// Describes the properties of the border/stroke of the widget. 
+#[derive(Copy, Clone, Debug, PartialEq)]
+pub struct Border {
+    /// The colour of the border described as RGBA between the range `0.0` <-> `1.0`.
+    pub colour: [f32; 4],
+    
+    /// The width of the border. 
+    pub width: f32,
+}
+
+impl Border {
+    /// Creates a new [`Border`]. 
+    pub fn new(colour: [f32; 4], width: f32) -> Self {
+        Self { colour, width }
+    }
 }
\ No newline at end of file
diff --git a/crates/kino-ui/src/widgets/rect.rs b/crates/kino-ui/src/widgets/rect.rs
index bd7736a..12407af 100644
--- a/crates/kino-ui/src/widgets/rect.rs
+++ b/crates/kino-ui/src/widgets/rect.rs
@@ -1,3 +1,5 @@
+//! Defines the primitive [`Rectangle`] widget.
+
 use std::any::Any;
 use glam::{vec2, Mat2, Vec2};
 use crate::{KinoState, WidgetId};
@@ -5,50 +7,70 @@ use crate::asset::Handle;
 use crate::math::Rect;
 use crate::rendering::texture::Texture;
 use crate::rendering::vertex::Vertex;
-use crate::widgets::{Anchor, NativeWidget};
+use crate::widgets::{Anchor, Border, Fill, NativeWidget};
 use crate::resp::WidgetResponse;
 use winit::event::{ElementState, MouseButton};
 
+/// A simple and humble rectangle.
 pub struct Rectangle {
     /// The identifier of the widget.
     ///
     /// To make life easier, a text id works pretty well.
     pub id: WidgetId,
 
+    /// The positioning of the rectangle.
+    ///
+    /// Default: [`Anchor::TopLeft`]
     pub anchor: Anchor,
 
     /// The position of the rectangle
+    ///
+    /// Default: [`Vec2::ZERO`]
     pub position: Vec2,
 
     /// The size of the rectangle
+    ///
+    /// Default: [`vec2(64.0, 64.0)`]
     pub size: Vec2,
 
     /// The texture that this
-    pub texture: Option<Handle<Texture>>,
-
-    /// Colour described as RGBA.
     ///
-    /// If a texture is applied to the colour, it will create a tinted texture on the quad.
-    pub colour: [f32; 4], // for now until colour is properly implemented
+    /// Default: [`None`]
+    pub texture: Option<Handle<Texture>>,
 
     /// Rotation of the rectangle in radians
+    ///
+    /// Default: `0.0` rad / `0.0` degrees
     pub rotation: f32,
 
     /// The UV of the textures.
+    ///
+    /// Default: `[[0.0, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 1.0]]`
     pub uvs: [[f32; 2]; 4],
+
+    /// The fill properties of the rectangle
+    ///
+    /// Default: `[1.0, 1.0, 1.0, 1.0]`
+    pub fill: Fill,
+
+    /// The stroke/border properties of the rectangle
+    ///
+    /// Default: [`None`]
+    pub border: Option<Border>,
 }
 
 impl Rectangle {
-    pub fn new(id: WidgetId) -> Self {
+    pub fn new(id: impl Into<WidgetId>) -> Self {
         Self {
-            id,
+            id: id.into(),
             anchor: Anchor::TopLeft,
             position: Vec2::ZERO,
             size: vec2(64.0, 64.0),
             rotation: 0.0,
-            colour: [1.0, 1.0, 1.0, 1.0],
             uvs: [[0.0, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 1.0]],
+            fill: Fill::default(),
             texture: None,
+            border: None,
         }
     }
 
@@ -58,6 +80,7 @@ impl Rectangle {
         self.size = rect.size;
         self
     }
+
     /// Sets the anchor point of the rectangle
     /// Defaults to [`Anchor::TopLeft`].
     pub fn anchor(mut self, anchor: Anchor) -> Self {
@@ -74,22 +97,32 @@ impl Rectangle {
         self.size = size;
         self
     }
-    /// Sets the colour of the rectangle
-    pub fn colour(mut self, colour: [f32; 4]) -> Self {
-        self.colour = colour;
+
+    /// Sets the fill properties of the rectangle
+    pub fn fill(mut self, fill: Fill) -> Self {
+        self.fill = fill;
         self
     }
+
+    /// Sets the border properties of the rectangle
+    pub fn border(mut self, border: Border) -> Self {
+        self.border = Some(border);
+        self
+    }
+
     /// Sets rotation (in radians) around the rectangle's center
     /// 0 radians points up (positive Y), increasing clockwise
     pub fn rotate(mut self, angle: f32) -> Self {
         self.rotation = angle + std::f32::consts::FRAC_PI_2;
         self
     }
+
     /// Sets the texture ID for the rectangle
     pub fn texture(mut self, id: Handle<Texture>) -> Self {
         self.texture = Some(id);
         self
     }
+
     /// Custom UV coordinates
     /// Defaults to covering the full texture ((0,0) - (1,1))
     pub fn uv(mut self, coords: [[f32; 2]; 4]) -> Self {
@@ -106,6 +139,7 @@ impl NativeWidget for Rectangle {
         };
         let top_left = self.position + offset;
         let rect = Rect::new(top_left, self.size);
+
         let input = state.input();
         let hovering = rect.contains(input.mouse_position);
         let clicked = hovering
@@ -118,18 +152,53 @@ impl NativeWidget for Rectangle {
                 hovering,
             },
         );
+
         let rot = Mat2::from_angle(self.rotation);
-        let verts: Vec<_> = rect
+
+        let fill_verts: Vec<_> = rect
             .corners()
             .iter()
             .zip(self.uvs.iter())
             .map(|(&corner, &uv)| {
                 let world = rot * (corner - rect.center()) + rect.center();
-                Vertex::new(world.to_array(), self.colour, uv)
+                Vertex::new(world.to_array(), self.fill.colour, uv)
             })
             .collect();
 
-        state.batch.push(&verts, &[0, 1, 2, 2, 3, 0], self.texture);
+        state.batch.push(&fill_verts, &[0, 1, 2, 2, 3, 0], self.texture);
+
+        if let Some(border) = self.border {
+            let half_width = border.width / 2.0;
+            let outer_rect = Rect::new(
+                top_left - Vec2::splat(half_width),
+                self.size + Vec2::splat(border.width)
+            );
+            let inner_rect = Rect::new(
+                top_left + Vec2::splat(half_width),
+                self.size - Vec2::splat(border.width)
+            );
+
+            let outer_corners = outer_rect.corners();
+            let inner_corners = inner_rect.corners();
+
+            let mut border_verts = Vec::with_capacity(8);
+            for i in 0..4 {
+                let outer_world = rot * (outer_corners[i] - rect.center()) + rect.center();
+                border_verts.push(Vertex::new(outer_world.to_array(), border.colour, [0.0, 0.0]));
+
+                let inner_world = rot * (inner_corners[i] - rect.center()) + rect.center();
+                border_verts.push(Vertex::new(inner_world.to_array(), border.colour, [0.0, 0.0]));
+            }
+
+            let border_indices = [
+                0, 1, 3, 3, 2, 0,
+                2, 3, 5, 5, 4, 2,
+                4, 5, 7, 7, 6, 4,
+                6, 7, 1, 1, 0, 6,
+            ];
+
+            state.batch.push(&border_verts, &border_indices, None);
+        }
     }
 
     fn id(&self) -> WidgetId {
diff --git a/crates/redback-runtime/src/scene.rs b/crates/redback-runtime/src/scene.rs
index 295ad58..5506bc0 100644
--- a/crates/redback-runtime/src/scene.rs
+++ b/crates/redback-runtime/src/scene.rs
@@ -7,13 +7,12 @@ use eucalyptus_core::egui::CentralPanel;
 use eucalyptus_core::physics::collider::ColliderGroup;
 use eucalyptus_core::physics::collider::ColliderShapeKey;
 use eucalyptus_core::physics::collider::shader::ColliderInstanceRaw;
-use glam::{DMat4, DQuat, DVec3, Quat, Vec2};
+use glam::{vec2, DMat4, DQuat, DVec3, Quat, Vec2};
 use hecs::Entity;
-use wgpu::{Color};
+use wgpu::Color;
 use wgpu::util::DeviceExt;
 use winit::event_loop::ActiveEventLoop;
 use winit::event::WindowEvent;
-use yakui_wgpu::SurfaceInfo;
 use dropbear_engine::camera::Camera;
 use dropbear_engine::buffer::ResizableBuffer;
 use dropbear_engine::entity::{EntityTransform, MeshRenderer, Transform};
@@ -22,7 +21,6 @@ use dropbear_engine::lighting::{Light, LightComponent};
 use dropbear_engine::lighting::MAX_LIGHTS;
 use dropbear_engine::model::{DrawLight, DrawModel, ModelId, MODEL_CACHE};
 use dropbear_engine::scene::{Scene, SceneCommand};
-use dropbear_engine::texture::Texture;
 use eucalyptus_core::camera::CameraComponent;
 use eucalyptus_core::command::CommandBufferPoller;
 use eucalyptus_core::hierarchy::{EntityTransformExt, Parent};
@@ -32,12 +30,10 @@ use eucalyptus_core::rapier3d::geometry::SharedShape;
 use eucalyptus_core::states::{Label, PROJECT};
 use eucalyptus_core::states::SCENES;
 use eucalyptus_core::scene::loading::{IsSceneLoaded, SceneLoadResult, SCENE_LOADER};
-use crate::{PlayMode};
+use crate::PlayMode;
 use eucalyptus_core::physics::collider::shader::create_wireframe_geometry;
 use eucalyptus_core::ui::UI_CONTEXT;
-use kino_ui::math::Rect;
-use kino_ui::WidgetId;
-use kino_ui::widgets::{Anchor, NativeWidget};
+use kino_ui::widgets::{Border, Fill};
 use kino_ui::widgets::rect::Rectangle;
 
 impl Scene for PlayMode {
@@ -534,11 +530,20 @@ impl Scene for PlayMode {
                     });
 
                     if let Some(kino) = &mut self.kino {
-                        let peter_griffen = kino.add_texture_from_bytes(&graphics.device, &graphics.queue, "theres nothing", include_bytes!("../../../resources/textures/no-texture.png"), 256, 256);
+                        #[allow(dead_code)]
+                        let no_texture = kino.add_texture_from_bytes(
+                            &graphics.device, &graphics.queue,
+                            "no texture",
+                            include_bytes!("../../../resources/textures/no-texture.png"),
+                            256, 256
+                        );
 
                         let rect = kino.add_widget(Box::new(
-                            Rectangle::new("rect".into())
-                                .texture(peter_griffen)
+                            Rectangle::new("rect")
+                                .texture(no_texture)
+                                .size(vec2(128.0, 100.0))
+                                .border(Border::new([1.0, 1.0, 1.0, 1.0], 3.0))
+                                .fill(Fill::new([1.0, 1.0, 1.0, 0.5])),
                         ));
 
                         kino.poll();