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

commit6f630b8bc47626ec4185a5c2da2f9090ae6c6c02
parent9ddb5d0635
authorLucas Galante <[email protected]>
date2026-06-18 12:25
Add page and layer background color and opacity support

 src/color.rs                  | 33 ++++++++++++++++++
 src/layout.rs                 | 79 +++++++++++++++++++++++++++++++++++++++++++
 src/widget/container/layer.rs | 28 ++++++++++++---
 src/widget/container/page.rs  | 17 ++++++++--
 src/widget/mod.rs             |  3 ++
 5 files changed, 154 insertions(+), 6 deletions(-)

diff --git a/src/color.rs b/src/color.rs
index aec8056..006a08c 100644
--- a/src/color.rs
+++ b/src/color.rs
@@ -38,6 +38,9 @@ static TOGGLE_OFF_COLOR: RwLock<[f32; 4]> = RwLock::new(TOGGLE_OFF);
 static SCROLLINGLIST_BG_COLOR: RwLock<[f32; 4]> = RwLock::new([0.08, 0.08, 0.12, 0.3]);
 static BREADCRUMB_BG_COLOR: RwLock<[f32; 4]> = RwLock::new([0.08, 0.08, 0.12, 1.0]);
 static POPOVER_BG_COLOR: RwLock<[f32; 4]> = RwLock::new([0.08, 0.08, 0.12, 1.0]);
+static PAGE_COLOR: RwLock<[f32; 4]> = RwLock::new([0.0, 0.0, 0.0, 0.0]);
+static LAYER_COLOR: RwLock<[f32; 4]> = RwLock::new([0.0, 0.0, 0.0, 0.0]);
+
 
 
 pub fn node_color() -> [f32; 4] {
@@ -164,6 +167,13 @@ fn parse_and_set_colors(content: &str) {
         if let Some(c) = parse_hex(trimmed, "popover_bg_color") {
             parsed_popover_bg = Some(c);
         }
+        if let Some(c) = parse_hex(trimmed, "page_color") {
+            if let Ok(mut lock) = PAGE_COLOR.write() { *lock = c; }
+        }
+        if let Some(c) = parse_hex(trimmed, "layer_color") {
+            if let Ok(mut lock) = LAYER_COLOR.write() { *lock = c; }
+        }
+
     }
     if let Some(c) = parsed_scrollinglist_bg {
         if let Ok(mut lock) = SCROLLINGLIST_BG_COLOR.write() {
@@ -219,6 +229,29 @@ pub fn set_page_low_color(color: [f32; 4]) {
     }
 }
 
+pub fn page_color() -> [f32; 4] {
+    load_colors_once();
+    *PAGE_COLOR.read().unwrap()
+}
+
+pub fn set_page_color(color: [f32; 4]) {
+    if let Ok(mut lock) = PAGE_COLOR.write() {
+        *lock = color;
+    }
+}
+
+pub fn layer_color() -> [f32; 4] {
+    load_colors_once();
+    *LAYER_COLOR.read().unwrap()
+}
+
+pub fn set_layer_color(color: [f32; 4]) {
+    if let Ok(mut lock) = LAYER_COLOR.write() {
+        *lock = color;
+    }
+}
+
+
 pub fn color_borders_color() -> [f32; 4] {
     load_colors_once();
     *COLOR_BORDERS_COLOR.read().unwrap()
diff --git a/src/layout.rs b/src/layout.rs
index f42d9d3..4879ce1 100644
--- a/src/layout.rs
+++ b/src/layout.rs
@@ -61,6 +61,9 @@ static DROPDOWN_CORNER_RADIUS: RwLock<f32> = RwLock::new(4.0);
 static TOGGLE_CORNER_RADIUS: RwLock<f32> = RwLock::new(4.0);
 static PLATE_CORNER_RADIUS: RwLock<f32> = RwLock::new(12.0);
 static PLATE_OPACITY: RwLock<f32> = RwLock::new(1.0);
+static PAGE_OPACITY: RwLock<f32> = RwLock::new(1.0);
+static LAYER_OPACITY: RwLock<f32> = RwLock::new(1.0);
+
 
 
 
@@ -223,6 +226,25 @@ pub fn reload_config() {
                     }
                 }
             }
+            if let Some(rest) = trimmed.strip_prefix("page_opacity") {
+                let rest = rest.trim_start_matches(|c: char| c == ' ' || c == '=' || c == '"');
+                let val_str = rest.trim_end_matches('"').trim();
+                if let Ok(val) = val_str.parse::<f32>() {
+                    if let Ok(mut lock) = PAGE_OPACITY.write() {
+                        *lock = val;
+                    }
+                }
+            }
+            if let Some(rest) = trimmed.strip_prefix("layer_opacity") {
+                let rest = rest.trim_start_matches(|c: char| c == ' ' || c == '=' || c == '"');
+                let val_str = rest.trim_end_matches('"').trim();
+                if let Ok(val) = val_str.parse::<f32>() {
+                    if let Ok(mut lock) = LAYER_OPACITY.write() {
+                        *lock = val;
+                    }
+                }
+            }
+
 
             if let Some(rest) = trimmed.strip_prefix("toggle_height") {
 
@@ -781,6 +803,63 @@ pub fn set_plate_opacity(opacity: f32) {
     }
 }
 
+pub fn page_opacity() -> f32 {
+    use std::sync::Once;
+    static INIT: Once = Once::new();
+    INIT.call_once(|| {
+        if let Some(content) = read_config() {
+            for line in content.lines() {
+                let trimmed = line.trim();
+                if let Some(rest) = trimmed.strip_prefix("page_opacity") {
+                    let rest = rest.trim_start_matches(|c: char| c == ' ' || c == '=' || c == '"');
+                    let val_str = rest.trim_end_matches('"').trim();
+                    if let Ok(val) = val_str.parse::<f32>() {
+                        if let Ok(mut lock) = PAGE_OPACITY.write() {
+                            *lock = val;
+                        }
+                    }
+                }
+            }
+        }
+    });
+    *PAGE_OPACITY.read().unwrap()
+}
+
+pub fn set_page_opacity(opacity: f32) {
+    if let Ok(mut lock) = PAGE_OPACITY.write() {
+        *lock = opacity;
+    }
+}
+
+pub fn layer_opacity() -> f32 {
+    use std::sync::Once;
+    static INIT: Once = Once::new();
+    INIT.call_once(|| {
+        if let Some(content) = read_config() {
+            for line in content.lines() {
+                let trimmed = line.trim();
+                if let Some(rest) = trimmed.strip_prefix("layer_opacity") {
+                    let rest = rest.trim_start_matches(|c: char| c == ' ' || c == '=' || c == '"');
+                    let val_str = rest.trim_end_matches('"').trim();
+                    if let Ok(val) = val_str.parse::<f32>() {
+                        if let Ok(mut lock) = LAYER_OPACITY.write() {
+                            *lock = val;
+                        }
+                    }
+                }
+            }
+        }
+    });
+    *LAYER_OPACITY.read().unwrap()
+}
+
+pub fn set_layer_opacity(opacity: f32) {
+    if let Ok(mut lock) = LAYER_OPACITY.write() {
+        *lock = opacity;
+    }
+}
+
+
 
 
 pub fn color_selector_height() -> f32 {
diff --git a/src/widget/container/layer.rs b/src/widget/container/layer.rs
index e3de6d1..6afa901 100644
--- a/src/widget/container/layer.rs
+++ b/src/widget/container/layer.rs
@@ -30,6 +30,8 @@ impl Layer {
 
 impl Element for Layer {
     fn base(&self) -> Option<&Widget> { Some(&self.base) }
+    fn is_layer(&self) -> bool { true }
+
     fn base_mut(&mut self) -> Option<&mut Widget> { Some(&mut self.base) }
     fn as_any(&self) -> &dyn std::any::Any { self }
     fn as_any_mut(&mut self) -> &mut dyn std::any::Any { self }
@@ -52,9 +54,12 @@ impl Element for Layer {
     }
 
     fn color(&self) -> [f32; 4] {
-        [0.0, 0.0, 0.0, 0.0]
+        let mut c = crate::colors::layer_color();
+        c[3] *= crate::layout::layer_opacity();
+        c
     }
 
+
     fn visible(&self) -> bool {
         self.visible
     }
@@ -98,12 +103,27 @@ impl Element for Layer {
             return Vec::new();
         }
         let mut quads = Vec::new();
+        let mut has_page_parent = false;
+        if let Some(parent_ptr) = self.parent {
+            unsafe {
+                if (*parent_ptr).is_page() {
+                    has_page_parent = true;
+                }
+            }
+        }
+        if !has_page_parent {
+            let c = self.color();
+            if c[3] > 0.0 {
+                let (x, y, w, h) = self.rect();
+                quads.push((x, y, w, h, c));
+            }
+        }
         for &child_ptr in &self.children {
             let widget = unsafe { &*child_ptr };
-            let c = widget.color();
-            if c[3] > 0.0 {
+            let cc = widget.color();
+            if cc[3] > 0.0 {
                 let (wx, wy, ww, wh) = widget.rect();
-                quads.push((wx, wy, ww, wh, c));
+                quads.push((wx, wy, ww, wh, cc));
             }
             quads.extend(widget.all_quads(ctx));
         }
diff --git a/src/widget/container/page.rs b/src/widget/container/page.rs
index c774895..f4c5665 100644
--- a/src/widget/container/page.rs
+++ b/src/widget/container/page.rs
@@ -122,6 +122,8 @@ impl Page {
 
 impl Element for Page {
     fn base(&self) -> Option<&Widget> { Some(&self.base.base) }
+    fn is_page(&self) -> bool { true }
+
     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 }
@@ -145,9 +147,12 @@ impl Element for Page {
     }
 
     fn color(&self) -> [f32; 4] {
-        [0.0, 0.0, 0.0, 0.0]
+        let mut c = crate::colors::page_color();
+        c[3] *= crate::layout::page_opacity();
+        c
     }
 
+
     fn visible(&self) -> bool {
         self.visible
     }
@@ -189,9 +194,17 @@ impl Element for Page {
         if !self.visible {
             return Vec::new();
         }
-        self.base.all_quads(ctx)
+        let mut quads = Vec::new();
+        let c = self.color();
+        if c[3] > 0.0 {
+            let (x, y, w, h) = self.rect();
+            quads.push((x, y, w, h, c));
+        }
+        quads.extend(self.base.all_quads(ctx));
+        quads
     }
 
+
     fn text_labels(&self) -> Vec<TextLabel> {
         if !self.visible {
             return Vec::new();
diff --git a/src/widget/mod.rs b/src/widget/mod.rs
index 5ae6400..af96410 100644
--- a/src/widget/mod.rs
+++ b/src/widget/mod.rs
@@ -494,7 +494,10 @@ pub trait Element {
 
     fn z_index(&self) -> i32 { 0 }
     fn is_plate(&self) -> bool { false }
+    fn is_page(&self) -> bool { false }
+    fn is_layer(&self) -> bool { false }
     fn rounded_corners(&self) -> (bool, bool, bool, bool) { (false, false, false, false) }
+
     fn corner_radius(&self) -> f32 { 12.0 }
     fn layout_ignore(&self) -> bool { false }
     fn color_u8(&self) -> Option<[u8; 4]> { None }