git.lucas.co / cce-cloud
cloud storage client
git clone https://git.lucas.co/cce-cloud.git

commit539ded6c9b0badea4b656109ca6cc0eec075ba84
parent1192adf065
authorLucas Galante <[email protected]>
date2026-06-01 06:54
Implement smooth fade-out animation on exit

 src/main.rs | 108 ++++++++++++++++++++++++++++++++++++++++++++++++++++--------
 1 file changed, 94 insertions(+), 14 deletions(-)

diff --git a/src/main.rs b/src/main.rs
index 45d2a6c..14eebbe 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1175,6 +1175,31 @@ struct StdinState {
     new_data: bool,
 }
 
+fn read_opacity_if_configured() -> f32 {
+    let config_path = "/home/lsgalante/.config/ccec/config.toml";
+    let content = std::fs::read_to_string(config_path).unwrap_or_default();
+    
+    let mut in_section = false;
+    for line in content.lines() {
+        let trimmed = line.trim();
+        if trimmed == "[transparency]" {
+            in_section = true;
+            continue;
+        }
+        if trimmed.starts_with('[') && in_section {
+            break;
+        }
+        if in_section && trimmed.starts_with("opacity") {
+            if let Some(val) = trimmed.split('=').nth(1) {
+                if let Ok(o) = val.trim().parse::<f32>() {
+                    return o.clamp(0.0, 1.0);
+                }
+            }
+        }
+    }
+    0.20 // default opacity for clear-cloud
+}
+
 struct State {
     window: LayerSurface,
     wl_surface: wl_surface::WlSurface,
@@ -1205,6 +1230,8 @@ struct State {
     stdin_state: Arc<Mutex<StdinState>>,
     mode: LauncherMode,
     apps: Vec<AppInfo>,
+    opacity: f32,
+    fade_factor: f32,
 }
 
 impl State {
@@ -1264,7 +1291,7 @@ impl State {
 
         let adapter = instance
             .request_adapter(&wgpu::RequestAdapterOptions {
-                power_preference: wgpu::PowerPreference::HighPerformance,
+                power_preference: wgpu::PowerPreference::LowPower,
                 compatible_surface: Some(&surface),
                 force_fallback_adapter: false,
             })
@@ -1415,6 +1442,8 @@ impl State {
             None
         };
 
+        let opacity = read_opacity_if_configured();
+
         let mut state = Self {
             window,
             wl_surface,
@@ -1442,6 +1471,8 @@ impl State {
             stdin_state,
             mode,
             apps,
+            opacity,
+            fade_factor: 1.0,
         };
 
         state.check_stdin_updates();
@@ -1488,7 +1519,12 @@ impl State {
     }
 
     fn upload_vertices(&mut self) {
-        let verts = self.collect_vertices();
+        let mut verts = self.collect_vertices();
+        if self.fade_factor < 1.0 {
+            for v in &mut verts {
+                v.color[3] *= self.fade_factor;
+            }
+        }
         self.vertex_count = verts.len() as u32;
         if self.vertex_count == 0 {
             return;
@@ -1554,6 +1590,12 @@ impl State {
             let left = if is_color_mode { label.x } else { label.x * scale_f32 };
             let top = if is_color_mode { label.y } else { label.y * scale_f32 };
             let scale = if is_color_mode { 1.0 } else { scale_f32 };
+            let default_color = if self.fade_factor < 1.0 {
+                let alpha = (self.fade_factor * 255.0) as u8;
+                glyphon::Color::rgba(label.color[0], label.color[1], label.color[2], alpha)
+            } else {
+                glyphon::Color::rgb(label.color[0], label.color[1], label.color[2])
+            };
             areas.push(TextArea {
                 buffer: buf,
                 left,
@@ -1565,7 +1607,7 @@ impl State {
                     right: *physical_width as i32,
                     bottom: *physical_height as i32,
                 },
-                default_color: glyphon::Color::rgb(label.color[0], label.color[1], label.color[2]),
+                default_color,
                 custom_glyphs: &[],
             });
         }
@@ -1595,7 +1637,9 @@ impl State {
         }
     }
 
-    fn render(&mut self) -> bool {
+    fn render(&mut self, fade_factor: f32) -> bool {
+        self.fade_factor = fade_factor;
+        self.upload_vertices();
         self.prepare_text();
 
         let output = match self.surface.get_current_texture() {
@@ -1625,7 +1669,7 @@ impl State {
                     resolve_target: None,
                     ops: wgpu::Operations {
                         load: wgpu::LoadOp::Clear(wgpu::Color {
-                            r: 0.05, g: 0.05, b: 0.08, a: 0.20,
+                            r: 0.05, g: 0.05, b: 0.08, a: (self.opacity * fade_factor) as f64,
                         }),
                         store: wgpu::StoreOp::Store,
                     },
@@ -1676,6 +1720,19 @@ struct AppState {
     exit: bool,
     redraw: bool,
     ctrl_pressed: bool,
+    fade_out: bool,
+    fade_start: Option<std::time::Instant>,
+    fade_factor: f32,
+}
+
+impl AppState {
+    fn trigger_close(&mut self) {
+        if !self.fade_out {
+            self.fade_out = true;
+            self.fade_start = Some(std::time::Instant::now());
+            self.redraw = true;
+        }
+    }
 }
 
 impl CompositorHandler for AppState {
@@ -1820,6 +1877,7 @@ impl PointerHandler for AppState {
         events: &[smithay_client_toolkit::seat::pointer::PointerEvent],
     ) {
         use smithay_client_toolkit::seat::pointer::PointerEventKind;
+        let mut should_close = false;
         for event in events {
             if let Some(st) = &mut self.state {
                 let (cx, cy) = clear_ui::wayland::scale_pointer_pos(event.position, st.scale);
@@ -1869,10 +1927,10 @@ impl PointerHandler for AppState {
                                     match action {
                                         ColorAction::Apply => {
                                             println!("{}", hex);
-                                            self.exit = true;
+                                            should_close = true;
                                         }
                                         ColorAction::Cancel => {
-                                            self.exit = true;
+                                            should_close = true;
                                         }
                                     }
                                 }
@@ -1900,7 +1958,7 @@ impl PointerHandler for AppState {
                                                 LauncherMode::Dmenu => {}
                                                 LauncherMode::Color => {}
                                             }
-                                            self.exit = true;
+                                            should_close = true;
                                         }
                                     }
                                     st.upload_vertices();
@@ -1957,6 +2015,9 @@ impl PointerHandler for AppState {
                 }
             }
         }
+        if should_close {
+            self.trigger_close();
+        }
     }
 }
 
@@ -1980,7 +2041,7 @@ impl KeyboardHandler for AppState {
         _surface: &wl_surface::WlSurface,
         _serial: u32,
     ) {
-        self.exit = true;
+        self.trigger_close();
     }
 
     fn press_key(
@@ -2102,18 +2163,19 @@ impl AppState {
             }
         };
 
+        let mut should_close = false;
         if let Some(st) = &mut self.state {
             let mut handled = true;
             if st.mode == LauncherMode::Color {
                 match &logical_key {
                     Key::Named(NamedKey::Escape) => {
-                        self.exit = true;
+                        should_close = true;
                     }
                     Key::Named(NamedKey::Enter) => {
                         if let Some(cp) = &st.color_picker {
                             println!("{}", cp.hex());
                         }
-                        self.exit = true;
+                        should_close = true;
                     }
                     _ => {
                         handled = false;
@@ -2122,7 +2184,7 @@ impl AppState {
             } else {
                 match &logical_key {
                     Key::Named(NamedKey::Escape) => {
-                        self.exit = true;
+                        should_close = true;
                     }
                     Key::Named(NamedKey::Enter) => {
                         if let Some(item) = st.fuzzel.filtered_items.get(st.fuzzel.selected) {
@@ -2139,7 +2201,7 @@ impl AppState {
                                 LauncherMode::Dmenu => {}
                                 LauncherMode::Color => {}
                             }
-                            self.exit = true;
+                            should_close = true;
                         }
                     }
                     Key::Named(NamedKey::ArrowDown) => {
@@ -2184,6 +2246,9 @@ impl AppState {
                 self.redraw = true;
             }
         }
+        if should_close {
+            self.trigger_close();
+        }
     }
 }
 
@@ -2284,6 +2349,9 @@ fn main() {
         exit: false,
         redraw: true,
         ctrl_pressed: false,
+        fade_out: false,
+        fade_start: None,
+        fade_factor: 1.0,
     };
 
     // Perform a roundtrip to populate output_state with active output scales
@@ -2325,6 +2393,18 @@ fn main() {
     }).unwrap();
 
     loop {
+        if app.fade_out {
+            if let Some(start) = app.fade_start {
+                let elapsed = start.elapsed().as_secs_f32();
+                app.fade_factor = (1.0 - elapsed / 0.15).max(0.0);
+                if app.fade_factor <= 0.0 {
+                    app.exit = true;
+                } else {
+                    app.redraw = true;
+                }
+            }
+        }
+
         let timeout = if app.redraw {
             std::time::Duration::from_millis(0)
         } else {
@@ -2339,7 +2419,7 @@ fn main() {
         if app.redraw {
             app.redraw = false;
             if let Some(state) = &mut app.state {
-                if state.render() {
+                if state.render(app.fade_factor) {
                     app.redraw = true;
                 }
             }