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

commit671c330cf589f4d2bad43ad96613b9b0630d61d7
parentf365599f70
authorLucas Galante <[email protected]>
date2026-06-24 20:56
Implement window opacity per window layout mode

 src/backend/window_runner.rs   | 21 ++++++++++
 src/color.rs                   | 91 ++++++++++++++++++++++++++++++++++++++++++
 src/scale.rs                   | 33 +++++++++++++++
 src/widget/container/window.rs |  6 ++-
 4 files changed, 150 insertions(+), 1 deletion(-)

diff --git a/src/backend/window_runner.rs b/src/backend/window_runner.rs
index 25f95cf..f957310 100644
--- a/src/backend/window_runner.rs
+++ b/src/backend/window_runner.rs
@@ -938,6 +938,9 @@ pub trait Application: Sized + 'static {
     fn overlay_quads(&mut self, _quads: &mut Vec<(f32, f32, f32, f32, [f32; 4])>, _size: LogicalSize, _scale: f64) {}
     fn text_items(&self) -> &[TextItem];
     fn render_popovers(&self, _pc: &mut dyn crate::layout::RenderTarget) {}
+    fn input_regions(&self) -> Option<Vec<(i32, i32, i32, i32)>> {
+        None
+    }
     
     fn ui_context(&self) -> Option<&crate::context::UiContext> {
         None
@@ -1184,6 +1187,18 @@ impl<A: Application> EngineState<A> {
         
         let mut quads = Vec::new();
         self.inner.view(&mut quads, LogicalSize::new(logical_w, logical_h), scale_factor);
+
+        if let Some(ref surface) = self.surface {
+            if let Some(regions) = self.inner.input_regions() {
+                let compositor = self.compositor_state.wl_compositor();
+                let wl_region = compositor.create_region(&self.qh, ());
+                for &(rx, ry, rw, rh) in &regions {
+                    wl_region.add(rx, ry, rw, rh);
+                }
+                surface.set_input_region(Some(&wl_region));
+                wl_region.destroy();
+            }
+        }
         
         let mut rounded_quads = Vec::new();
         self.inner.view_rounded_quads(&mut rounded_quads, LogicalSize::new(logical_w, logical_h), scale_factor);
@@ -1571,6 +1586,11 @@ impl<A: Application> WindowHandler for EngineState<A> {
         configure: WindowConfigure,
         _serial: u32,
     ) {
+        let is_fs = configure.is_fullscreen();
+        let is_max = configure.is_maximized();
+        crate::scale::set_fullscreen(is_fs);
+        crate::scale::set_maximized(is_max);
+
         let (w, h) = configure.new_size;
         if let (Some(w), Some(h)) = (w, h) {
             let width = w.get();
@@ -2068,6 +2088,7 @@ pub fn run<A: Application>() {
 
     let inner = A::new(&qh, sender.clone());
     let settings = inner.settings();
+    crate::scale::set_app_id(settings.app_id.clone());
 
     let mut engine_state = EngineState {
         registry_state: RegistryState::new(&globals),
diff --git a/src/color.rs b/src/color.rs
index 11f342c..ce43de7 100644
--- a/src/color.rs
+++ b/src/color.rs
@@ -508,3 +508,94 @@ pub fn active_theme() -> Theme {
         hover_overlay: [1.0, 1.0, 1.0, 0.08],
     }
 }
+
+pub fn active_window_mode() -> String {
+    let app_id = crate::scale::app_id();
+    if app_id.is_empty() {
+        return "floating".to_string();
+    }
+
+    let config_path = "/home/lsgalante/.config/cce/config.json";
+    let content = match std::fs::read_to_string(config_path) {
+        Ok(c) => c,
+        Err(_) => return "floating".to_string(),
+    };
+    let val: serde_json::Value = match serde_json::from_str(&content) {
+        Ok(v) => v,
+        Err(_) => return "floating".to_string(),
+    };
+
+    if let Some(mode_rules) = val.get("mode_rule").and_then(|r| r.as_array()) {
+        for rule in mode_rules {
+            if rule.get("app_id").and_then(|id| id.as_str()) == Some(&app_id) {
+                if let Some(mode) = rule.get("mode").and_then(|m| m.as_str()) {
+                    return mode.to_string();
+                }
+            }
+        }
+    }
+
+    let mut default_tile_mode = "cascade".to_string();
+    if let Some(tag_layouts) = val.get("tag_layout").and_then(|l| l.as_array()) {
+        for tl in tag_layouts {
+            if tl.get("tag").and_then(|t| t.as_u64()) == Some(1) {
+                if let Some(m) = tl.get("mode").and_then(|m| m.as_str()) {
+                    default_tile_mode = m.to_string();
+                }
+            }
+        }
+    }
+
+    if crate::scale::is_fullscreen() {
+        return "fullscreen".to_string();
+    }
+    if crate::scale::is_maximized() {
+        return default_tile_mode;
+    }
+
+    "floating".to_string()
+}
+
+pub fn active_window_opacity() -> f32 {
+    let mode = active_window_mode();
+    let config_path = "/home/lsgalante/.config/cce/config.json";
+    let content = match std::fs::read_to_string(config_path) {
+        Ok(c) => c,
+        Err(_) => return 0.9,
+    };
+    let val: serde_json::Value = match serde_json::from_str(&content) {
+        Ok(v) => v,
+        Err(_) => return 0.9,
+    };
+
+    let key = match mode.as_str() {
+        "fullscreen" => "fullscreen_opacity",
+        "cascade" => "cascade_opacity",
+        "grid" => "grid_opacity",
+        "floating" => "floating_opacity",
+        "side-panel" | "pinned" => "pinned_opacity",
+        "popup" => "popup_opacity",
+        _ => "window_opacity",
+    };
+
+    if let Some(opacity) = val.pointer(&format!("/layout/{}", key)).and_then(|v| v.as_f64()) {
+        return opacity as f32;
+    }
+
+    // Fallbacks if not present:
+    match key {
+        "fullscreen_opacity" => 0.95,
+        "cascade_opacity" => 0.05,
+        "grid_opacity" => 0.05,
+        "floating_opacity" => 0.9,
+        "pinned_opacity" => 0.05,
+        "popup_opacity" => 0.20,
+        _ => {
+            val.pointer("/surfaces/window_opacity")
+                .and_then(|v| v.as_f64())
+                .map(|v| v as f32)
+                .unwrap_or(0.9)
+        }
+    }
+}
+
diff --git a/src/scale.rs b/src/scale.rs
index 5c4b475..f0b8eda 100644
--- a/src/scale.rs
+++ b/src/scale.rs
@@ -1,6 +1,9 @@
 use std::sync::RwLock;
 
 static SCALE_FACTOR: RwLock<f32> = RwLock::new(1.0);
+static APP_ID: RwLock<String> = RwLock::new(String::new());
+static FULLSCREEN: RwLock<bool> = RwLock::new(false);
+static MAXIMIZED: RwLock<bool> = RwLock::new(false);
 
 pub fn scale_factor() -> f32 {
     *SCALE_FACTOR.read().unwrap()
@@ -11,3 +14,33 @@ pub fn set_scale_factor(scale: f32) {
         *lock = scale;
     }
 }
+
+pub fn app_id() -> String {
+    APP_ID.read().unwrap().clone()
+}
+
+pub fn set_app_id(id: String) {
+    if let Ok(mut lock) = APP_ID.write() {
+        *lock = id;
+    }
+}
+
+pub fn is_fullscreen() -> bool {
+    *FULLSCREEN.read().unwrap()
+}
+
+pub fn set_fullscreen(fs: bool) {
+    if let Ok(mut lock) = FULLSCREEN.write() {
+        *lock = fs;
+    }
+}
+
+pub fn is_maximized() -> bool {
+    *MAXIMIZED.read().unwrap()
+}
+
+pub fn set_maximized(m: bool) {
+    if let Ok(mut lock) = MAXIMIZED.write() {
+        *lock = m;
+    }
+}
diff --git a/src/widget/container/window.rs b/src/widget/container/window.rs
index 4ee5bfe..2693882 100644
--- a/src/widget/container/window.rs
+++ b/src/widget/container/window.rs
@@ -89,7 +89,11 @@ impl Element for Window {
     }
 
     fn color(&self) -> [f32; 4] {
-        self.background_color.unwrap_or([0.0, 0.0, 0.0, 0.0])
+        let mut base_color = self.background_color.unwrap_or([0.0, 0.0, 0.0, 0.0]);
+        if base_color[3] > 0.001 {
+            base_color[3] = crate::color::active_window_opacity();
+        }
+        base_color
     }
 
     fn visible(&self) -> bool {