git.lucas.co / cce-ui
GPU-accelerated UI toolkit (Vulkan)
git clone https://git.lucas.co/cce-ui.git

commite864618e578dfbd9caa9005f4a0aee35c287e98f
parent8d1f8b7426
authorLucas Galante <[email protected]>
date2026-06-18 01:31
Introduce Window container widget in cce-ui

 src/widget/container/mod.rs    |   2 +
 src/widget/container/window.rs | 262 +++++++++++++++++++++++++++++++++++++++++
 src/widget/mod.rs              |   2 +-
 3 files changed, 265 insertions(+), 1 deletion(-)

diff --git a/src/widget/container/mod.rs b/src/widget/container/mod.rs
index fc48ecd..277cbcc 100644
--- a/src/widget/container/mod.rs
+++ b/src/widget/container/mod.rs
@@ -13,6 +13,7 @@ pub mod paginator;
 pub mod switcher;
 pub mod layer;
 pub mod page;
+pub mod window;
 
 pub use container::Container;
 pub use header::Header;
@@ -29,3 +30,4 @@ pub use paginator::Paginator;
 pub use switcher::Switcher;
 pub use layer::Layer;
 pub use page::Page;
+pub use window::Window;
diff --git a/src/widget/container/window.rs b/src/widget/container/window.rs
new file mode 100644
index 0000000..d0608ed
--- /dev/null
+++ b/src/widget/container/window.rs
@@ -0,0 +1,262 @@
+use crate::widget::*;
+use crate::context::UiContext;
+use crate::widget::display::TextLabel;
+
+#[derive(Debug, Clone)]
+pub struct Window {
+    pub base: Layer,
+    pub border_color: Option<[f32; 4]>,
+    pub border_thickness: f32,
+    pub radius: f32,
+    pub background_color: Option<[f32; 4]>,
+    pub visible: bool,
+}
+
+impl Window {
+    pub fn new(x: f32, y: f32, w: f32, h: f32) -> Self {
+        Self {
+            base: Layer::new(x, y, w, h),
+            border_color: None,
+            border_thickness: 1.0,
+            radius: 12.0,
+            background_color: None,
+            visible: true,
+        }
+    }
+
+    pub fn with_border(mut self, color: [f32; 4], thickness: f32) -> Self {
+        self.border_color = Some(color);
+        self.border_thickness = thickness;
+        self
+    }
+
+    pub fn with_background(mut self, color: [f32; 4]) -> Self {
+        self.background_color = Some(color);
+        self
+    }
+
+    pub fn with_radius(mut self, radius: f32) -> Self {
+        self.radius = radius;
+        self
+    }
+}
+
+impl Element for Window {
+    fn base(&self) -> Option<&Widget> {
+        Some(&self.base.base)
+    }
+
+    fn base_mut(&mut self) -> Option<&mut Widget> {
+        Some(&mut self.base.base)
+    }
+
+    fn as_any(&self) -> &dyn std::any::Any {
+        self
+    }
+
+    fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
+        self
+    }
+
+    fn as_ptr(&self) -> *mut (dyn Element + 'static) {
+        self as *const Self as *mut Self as *mut (dyn Element + 'static)
+    }
+
+    fn as_ptr_mut(&mut self) -> *mut (dyn Element + 'static) {
+        self as *mut Self as *mut (dyn Element + 'static)
+    }
+
+    fn rect(&self) -> (f32, f32, f32, f32) {
+        self.base.rect()
+    }
+
+    fn set_rect(&mut self, x: f32, y: f32, w: f32, h: f32) {
+        self.base.set_rect(x, y, w, h);
+    }
+
+    fn color(&self) -> [f32; 4] {
+        self.background_color.unwrap_or([0.0, 0.0, 0.0, 0.0])
+    }
+
+    fn visible(&self) -> bool {
+        self.visible
+    }
+
+    fn set_visible(&mut self, visible: bool) {
+        self.visible = visible;
+        self.base.visible = visible;
+    }
+
+    fn add_child(&mut self, child: *mut (dyn Element + 'static), ctx: &mut UiContext) {
+        let id = self.base.base.id();
+        let self_ptr = self.as_ptr();
+        self.base.children.push(child);
+        unsafe {
+            if let Some(c_id) = (*child).base().map(|b| b.id()) {
+                ctx.register_widget(c_id, child);
+                ctx.link_ids(id, c_id);
+                (*child).set_parent(Some(self_ptr), ctx);
+            }
+        }
+    }
+
+    fn clear_children(&mut self, ctx: &mut UiContext) {
+        self.base.clear_children(ctx);
+    }
+
+    fn children(&self, ctx: &UiContext) -> Vec<*mut (dyn Element + 'static)> {
+        self.base.children(ctx)
+    }
+
+    fn set_parent(&mut self, parent: Option<*mut (dyn Element + 'static)>, ctx: &mut UiContext) {
+        self.base.set_parent(parent, ctx);
+    }
+
+    fn parent(&self, ctx: &UiContext) -> Option<*mut (dyn Element + 'static)> {
+        self.base.parent(ctx)
+    }
+
+    fn set_modifiers(&mut self, ctrl: bool, shift: bool, alt: bool) {
+        for &child_ptr in &self.base.children {
+            unsafe {
+                (*child_ptr).set_modifiers(ctrl, shift, alt);
+            }
+        }
+    }
+
+    fn solid_border(&self) -> Option<([f32; 4], f32)> {
+        self.border_color.map(|c| (c, self.border_thickness))
+    }
+
+    fn corner_radius(&self) -> f32 {
+        self.radius
+    }
+
+    fn rounded_corners(&self) -> (bool, bool, bool, bool) {
+        if self.radius > 0.1 {
+            (true, true, true, true)
+        } else {
+            (false, false, false, false)
+        }
+    }
+
+    fn all_quads(&self, ctx: &UiContext) -> Vec<(f32, f32, f32, f32, [f32; 4])> {
+        if !self.visible {
+            return Vec::new();
+        }
+        let mut quads = Vec::new();
+        let bg_color = self.color();
+        if bg_color[3] != 0.0 {
+            let (wx, wy, ww, wh) = self.rect();
+            quads.push((wx, wy, ww, wh, bg_color));
+        }
+        quads.extend(self.base.all_quads(ctx));
+        quads
+    }
+
+    fn text_labels(&self) -> Vec<TextLabel> {
+        self.base.text_labels()
+    }
+
+    fn text_labels_with_bounds(&self, ctx: &UiContext) -> Vec<(TextLabel, Option<[f32; 4]>)> {
+        self.base.text_labels_with_bounds(ctx)
+    }
+
+    fn text_labels_with_font_and_bounds(&self, ctx: &UiContext) -> Vec<(TextLabel, Option<String>, Option<[f32; 4]>)> {
+        self.base.text_labels_with_font_and_bounds(ctx)
+    }
+
+    fn get_text_items(&self) -> Vec<(&glyphon::Buffer, f32, f32, glyphon::Color)> {
+        self.base.get_text_items()
+    }
+
+    fn hit_test(&self, px: f32, py: f32, ctx: &UiContext) -> bool {
+        if ctx.is_coordinate_covered(self as *const Self as *const () as usize, px, py) {
+            return false;
+        }
+        let (x, y, w, h) = self.rect();
+        if px < x || px >= x + w || py < y || py >= y + h {
+            return false;
+        }
+        let r = self.radius.min(w * 0.5).min(h * 0.5);
+        if r <= 0.1 {
+            return true;
+        }
+        if px < x + r && py < y + r {
+            let dx = px - (x + r);
+            let dy = py - (y + r);
+            return dx * dx + dy * dy <= r * r;
+        }
+        if px >= x + w - r && py < y + r {
+            let dx = px - (x + w - r);
+            let dy = py - (y + r);
+            return dx * dx + dy * dy <= r * r;
+        }
+        if px >= x + w - r && py >= y + h - r {
+            let dx = px - (x + w - r);
+            let dy = py - (y + h - r);
+            return dx * dx + dy * dy <= r * r;
+        }
+        if px < x + r && py >= y + h - r {
+            let dx = px - (x + r);
+            let dy = py - (y + h - r);
+            return dx * dx + dy * dy <= r * r;
+        }
+        true
+    }
+}
+
+impl Drop for Window {
+    fn drop(&mut self) {
+        clear_widget_references(self);
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn test_window_creation_and_builders() {
+        let win = Window::new(10.0, 20.0, 100.0, 200.0)
+            .with_background([0.1, 0.2, 0.3, 0.4])
+            .with_border([1.0, 0.0, 0.0, 1.0], 2.5)
+            .with_radius(8.0);
+
+        assert_eq!(win.rect(), (10.0, 20.0, 100.0, 200.0));
+        assert_eq!(win.color(), [0.1, 0.2, 0.3, 0.4]);
+        assert_eq!(win.solid_border(), Some(([1.0, 0.0, 0.0, 1.0], 2.5)));
+        assert_eq!(win.corner_radius(), 8.0);
+        assert_eq!(win.rounded_corners(), (true, true, true, true));
+    }
+
+    #[test]
+    fn test_window_visibility() {
+        let mut win = Window::new(0.0, 0.0, 100.0, 100.0);
+        assert!(win.visible());
+        assert!(win.base.visible);
+
+        win.set_visible(false);
+        assert!(!win.visible());
+        assert!(!win.base.visible);
+    }
+
+    #[test]
+    fn test_window_children_and_parent() {
+        let mut ctx = UiContext::new();
+        let mut win = Window::new(0.0, 0.0, 100.0, 100.0);
+        let child = Layer::new(10.0, 10.0, 50.0, 50.0);
+
+        assert_eq!(win.children(&ctx).len(), 0);
+
+        win.add_child(child.as_ptr(), &mut ctx);
+        assert_eq!(win.children(&ctx).len(), 1);
+        assert_eq!(unsafe { (*win.children(&ctx)[0]).rect() }, (10.0, 10.0, 50.0, 50.0));
+
+        // Child's parent should point to Window
+        assert_eq!(unsafe { (*child.as_ptr()).parent(&ctx) }, Some(win.as_ptr()));
+
+        win.clear_children(&mut ctx);
+        assert_eq!(win.children(&ctx).len(), 0);
+    }
+}
diff --git a/src/widget/mod.rs b/src/widget/mod.rs
index 2eaaba7..5ae6400 100644
--- a/src/widget/mod.rs
+++ b/src/widget/mod.rs
@@ -545,7 +545,7 @@ pub use self::input::{
 pub use self::container::{
     Container, Header, ContentBg, ViewportBg, ParametersBg, ScrollingList,
     ScrollBox, Menu, MenuBar, Spreadsheet, Breadcrumb, Plate,
-    Paginator, Switcher, Layer, Page
+    Paginator, Switcher, Layer, Page, Window
 };
 pub use self::display::{
     TextLabel, Label, SectionHeader, StyledLabel, TextItem, Svg, UsageBar,