use { crate::{ async_engine::AsyncEngine, cmm::{cmm_manager::ColorManager, cmm_render_intent::RenderIntent}, ifs::wl_output::WlOutputGlobal, rect::{Rect, Region}, renderer::renderer_base::RendererBase, state::State, theme::Color, time::Time, utils::{asyncevent::AsyncEvent, errorfmt::ErrorFmt, timer::TimerFd}, }, isnt::std_1::primitive::IsntSliceExt, std::{ cell::{Cell, RefCell}, collections::VecDeque, rc::Rc, time::Duration, }, uapi::c::CLOCK_MONOTONIC, }; pub use jay_damage::*; pub async fn visualize_damage(state: Rc) { let timer = match TimerFd::new(CLOCK_MONOTONIC) { Ok(t) => t, Err(e) => { log::error!("Could not create timer fd: {}", ErrorFmt(e)); return; } }; loop { state.damage_visualizer.entry_added.triggered().await; let duration = Duration::from_millis(50); let res = timer.program(Some(duration), Some(duration)); if let Err(e) = res { log::error!("Could not program timer: {}", ErrorFmt(e)); return; } loop { let res = timer.expired(&state.ring).await; if let Err(e) = res { log::error!("Could not wait for timer to expire: {}", ErrorFmt(e)); return; } if state.damage_visualizer.entries.borrow_mut().is_empty() { break; } damage_all(&state); } let res = timer.program(None, None); if let Err(e) = res { log::error!("Could not disable timer: {}", ErrorFmt(e)); return; } } } fn damage_all(state: &State) { for connector in state.connectors.lock().values() { if connector.connected.get() { let damage = &mut *connector.damage.borrow_mut(); damage.clear(); damage.push(connector.damage_intersect.get()); connector.damage(); } } } pub struct DamageVisualizer { eng: Rc, entries: RefCell>, entry_added: AsyncEvent, enabled: Cell, decay: Cell, color: Cell, color_manager: Rc, } const MAX_RECTS: usize = 100_000; struct Damage { time: Time, rect: Rect, } impl DamageVisualizer { pub fn new(eng: &Rc, color_manager: &Rc) -> Self { Self { eng: eng.clone(), entries: Default::default(), entry_added: Default::default(), enabled: Default::default(), decay: Cell::new(Duration::from_secs(2)), color: Cell::new(Color::from_srgba_straight(255, 0, 0, 128)), color_manager: color_manager.clone(), } } pub fn add(&self, rect: Rect) { if !self.enabled.get() { return; } let entries = &mut *self.entries.borrow_mut(); if entries.is_empty() { self.entry_added.trigger(); } entries.push_back(Damage { time: self.eng.now(), rect, }); if entries.len() > MAX_RECTS { entries.pop_front(); } } pub fn set_enabled(&self, state: &State, enabled: bool) { self.enabled.set(enabled); if !enabled { self.entries.borrow_mut().clear(); damage_all(state); } } pub fn set_decay(&self, decay: Duration) { let millis = decay.as_millis(); if millis == 0 || millis > u64::MAX as u128 { return; } self.decay.set(decay); } pub fn set_color(&self, color: Color) { self.color.set(color); } fn trim(&self) { let now = self.eng.now(); let entries = &mut *self.entries.borrow_mut(); let decay = self.decay.get(); while let Some(first) = entries.front() { if now - first.time >= decay { entries.pop_front(); } else { break; } } } pub fn render(&self, cursor_rect: &Rect, renderer: &mut RendererBase<'_>) { if !self.enabled.get() { return; } self.trim(); let now = self.eng.now(); let entries = &*self.entries.borrow(); let decay = self.decay.get(); let base_color = self.color.get(); let mut used = Region::default(); let dx = -cursor_rect.x1(); let dy = -cursor_rect.y1(); let decay_millis = decay.as_millis() as u64 as f32; renderer.sync(); let srgb = &self.color_manager.srgb_gamma22().linear; for entry in entries.iter().rev() { let region = Region::new(entry.rect); let region = region.subtract_cow(&used); if region.is_not_empty() { let age = (now - entry.time).as_millis() as u64 as f32 / decay_millis; let color = base_color * (1.0 - age); renderer.fill_boxes2( region.rects(), &color, srgb, RenderIntent::Perceptual, dx, dy, ); used = used.union_cow(®ion).into_owned(); } } } pub fn copy_damage(&self, output: &WlOutputGlobal) { if !self.enabled.get() { return; } self.trim(); let entries = &*self.entries.borrow(); let pos = output.pos.get(); for entry in entries { if entry.rect.intersects(&pos) { output.add_damage_area(&entry.rect); } } } }