diff --git a/examples/window.rs b/examples/window.rs index 61e9ed6..5699b94 100644 --- a/examples/window.rs +++ b/examples/window.rs @@ -84,7 +84,7 @@ fn main() { registry_state, seat_state, output_state, - compositor_state, + compositor_state: Arc::new(compositor_state), subcompositor_state: Arc::new(subcompositor_state), shm_state, _xdg_shell_state: xdg_shell_state, @@ -120,7 +120,7 @@ struct SimpleWindow { registry_state: RegistryState, seat_state: SeatState, output_state: OutputState, - compositor_state: CompositorState, + compositor_state: Arc, subcompositor_state: Arc, shm_state: Shm, _xdg_shell_state: XdgShell, @@ -226,6 +226,7 @@ impl WindowHandler for SimpleWindow { let mut frame = AdwaitaFrame::new( &self.window, &self.shm_state, + self.compositor_state.clone(), self.subcompositor_state.clone(), qh.clone(), FrameConfig::auto(), diff --git a/src/lib.rs b/src/lib.rs index 0d9170a..8cae50b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -18,7 +18,7 @@ use smithay_client_toolkit::reexports::csd_frame::{ CursorIcon, DecorationsFrame, FrameAction, FrameClick, WindowManagerCapabilities, WindowState, }; -use smithay_client_toolkit::compositor::SurfaceData; +use smithay_client_toolkit::compositor::{CompositorState, Region, SurfaceData}; use smithay_client_toolkit::shell::WaylandSurface; use smithay_client_toolkit::shm::{slot::SlotPool, Shm}; use smithay_client_toolkit::subcompositor::SubcompositorState; @@ -28,6 +28,7 @@ mod buttons; mod config; mod parts; mod pointer; +mod shadow; pub mod theme; mod title; mod wl_typed; @@ -41,6 +42,7 @@ use buttons::Buttons; use config::get_button_layout_config; use parts::DecorationParts; use pointer::{Location, MouseState}; +use shadow::Shadow; use title::TitleText; use wl_typed::WlTyped; @@ -53,6 +55,8 @@ pub struct AdwaitaFrame { /// The base surface used to create the window. base_surface: WlTyped, + compositor: Arc, + /// Subcompositor to create/drop subsurfaces ondemand. subcompositor: Arc, @@ -84,6 +88,7 @@ pub struct AdwaitaFrame { theme: ColorTheme, title: Option, title_text: Option, + shadow: Shadow, } impl AdwaitaFrame @@ -93,6 +98,7 @@ where pub fn new( base_surface: &impl WaylandSurface, shm: &Shm, + compositor: Arc, subcompositor: Arc, queue_handle: QueueHandle, frame_config: FrameConfig, @@ -113,6 +119,7 @@ where base_surface, decorations, pool, + compositor, subcompositor, queue_handle, dirty: true, @@ -126,6 +133,7 @@ where state: WindowState::empty(), wm_capabilities: WindowManagerCapabilities::all(), resizable: true, + shadow: Shadow::default(), }) } @@ -142,7 +150,7 @@ where x: f64, y: f64, ) -> Location { - let header_width = decoration.header().width; + let header_width = decoration.header().surface_rect.width; let side_height = decoration.side_height(); let left_corner_x = BORDER_SIZE + RESIZE_HANDLE_CORNER_SIZE; @@ -226,25 +234,24 @@ where { let scale = self.scale_factor; + let mut rect = part.surface_rect; // XXX to perfectly align the visible borders we draw them with // the header, otherwise rounded corners won't look 'smooth' at the // start. To achieve that, we enlargen the width of the header by // 2 * `VISIBLE_BORDER_SIZE`, and move `x` by `VISIBLE_BORDER_SIZE` // to the left. - let (width, height, pos) = if idx == DecorationParts::HEADER && draw_borders { - ( - (part.width + 2 * VISIBLE_BORDER_SIZE) * scale, - part.height * scale, - (part.pos.0 - VISIBLE_BORDER_SIZE as i32, part.pos.1), - ) - } else { - (part.width * scale, part.height * scale, part.pos) - }; + if idx == DecorationParts::HEADER && draw_borders { + rect.width += 2 * VISIBLE_BORDER_SIZE; + rect.x -= VISIBLE_BORDER_SIZE as i32; + } + + rect.width *= scale; + rect.height *= scale; let (buffer, canvas) = match self.pool.create_buffer( - width as i32, - height as i32, - width as i32 * 4, + rect.width as i32, + rect.height as i32, + rect.width as i32 * 4, wl_shm::Format::Argb8888, ) { Ok((buffer, canvas)) => (buffer, canvas), @@ -252,12 +259,21 @@ where }; // Create the pixmap and fill with transparent color. - let mut pixmap = PixmapMut::from_bytes(canvas, width, height)?; + let mut pixmap = PixmapMut::from_bytes(canvas, rect.width, rect.height)?; // Fill everything with transparent background, since we draw rounded corners and // do invisible borders to enlarge the input zone. pixmap.fill(Color::TRANSPARENT); + if !self.state.intersects(WindowState::TILED) { + self.shadow.draw( + &mut pixmap, + scale, + self.state.contains(WindowState::ACTIVATED), + idx, + ); + } + match idx { DecorationParts::HEADER => { if let Some(title_text) = self.title_text.as_mut() { @@ -282,34 +298,34 @@ where // XXX we do all the match using integral types and then convert to f32 in the // end to ensure that result is finite. - let rect = match border { + let border_rect = match border { DecorationParts::LEFT => { - let x = (pos.0.unsigned_abs() * scale) - visible_border_size; - let y = pos.1.unsigned_abs() * scale; + let x = (rect.x.unsigned_abs() * scale) - visible_border_size; + let y = rect.y.unsigned_abs() * scale; Rect::from_xywh( x as f32, y as f32, visible_border_size as f32, - (height - y) as f32, + (rect.height - y) as f32, ) } DecorationParts::RIGHT => { - let y = pos.1.unsigned_abs() * scale; + let y = rect.y.unsigned_abs() * scale; Rect::from_xywh( 0., y as f32, visible_border_size as f32, - (height - y) as f32, + (rect.height - y) as f32, ) } // We draw small visible border only bellow the window surface, no need to // handle `TOP`. DecorationParts::BOTTOM => { - let x = (pos.0.unsigned_abs() * scale) - visible_border_size; + let x = (rect.x.unsigned_abs() * scale) - visible_border_size; Rect::from_xywh( x as f32, 0., - (width - 2 * x) as f32, + (rect.width - 2 * x) as f32, visible_border_size as f32, ) } @@ -317,8 +333,8 @@ where }; // Fill the visible border, if present. - if let Some(rect) = rect { - pixmap.fill_rect(rect, &border_paint, Transform::identity(), None); + if let Some(border_rect) = border_rect { + pixmap.fill_rect(border_rect, &border_paint, Transform::identity(), None); } } }; @@ -331,7 +347,7 @@ where part.surface.set_buffer_scale(scale as i32); - part.subsurface.set_position(pos.0, pos.1); + part.subsurface.set_position(rect.x, rect.y); buffer.attach_to(&part.surface).ok()?; if part.surface.version() >= 4 { @@ -340,6 +356,19 @@ where part.surface.damage(0, 0, i32::MAX, i32::MAX); } + if let Some(input_rect) = part.input_rect { + let input_region = Region::new(&*self.compositor).ok()?; + input_region.add( + input_rect.x, + input_rect.y, + input_rect.width as i32, + input_rect.height as i32, + ); + + part.surface + .set_input_region(Some(input_region.wl_region())); + } + part.surface.commit(); } diff --git a/src/parts.rs b/src/parts.rs index ee20769..7ae54ed 100644 --- a/src/parts.rs +++ b/src/parts.rs @@ -9,7 +9,7 @@ use smithay_client_toolkit::{ subcompositor::{SubcompositorState, SubsurfaceData}, }; -use crate::theme::{BORDER_SIZE, HEADER_SIZE}; +use crate::theme::{BORDER_SIZE, HEADER_SIZE, RESIZE_HANDLE_SIZE}; use crate::{pointer::Location, wl_typed::WlTyped}; /// The decoration's 'parts'. @@ -20,11 +20,13 @@ pub struct DecorationParts { impl DecorationParts { // XXX keep in sync with `Self;:new`. - pub const HEADER: usize = 0; - pub const TOP: usize = 1; - pub const LEFT: usize = 2; - pub const RIGHT: usize = 3; - pub const BOTTOM: usize = 4; + // Order is important. The lower the number, the earlier the part gets drawn. + // Because the header can overlap other parts, we draw it last. + pub const TOP: usize = 0; + pub const LEFT: usize = 1; + pub const RIGHT: usize = 2; + pub const BOTTOM: usize = 3; + pub const HEADER: usize = 4; pub fn new( base_surface: &WlTyped, @@ -36,53 +38,90 @@ impl DecorationParts { { // XXX the order must be in sync with associated constants. let parts = [ - // Header. + // Top. Part::new( base_surface, subcompositor, queue_handle, - 0, - HEADER_SIZE, - (0, -(HEADER_SIZE as i32)), + Rect { + x: -(BORDER_SIZE as i32), + y: -(HEADER_SIZE as i32 + BORDER_SIZE as i32), + width: 0, // Defined by `Self::resize`. + height: BORDER_SIZE, + }, + Some(Rect { + x: BORDER_SIZE as i32 - RESIZE_HANDLE_SIZE as i32, + y: BORDER_SIZE as i32 - RESIZE_HANDLE_SIZE as i32, + width: 0, // Defined by `Self::resize`. + height: RESIZE_HANDLE_SIZE, + }), ), - // Top. + // Left. Part::new( base_surface, subcompositor, queue_handle, - 0, - BORDER_SIZE, - ( - -(BORDER_SIZE as i32), - -(HEADER_SIZE as i32 + BORDER_SIZE as i32), - ), + Rect { + x: -(BORDER_SIZE as i32), + y: -(HEADER_SIZE as i32), + width: BORDER_SIZE, + height: 0, // Defined by `Self::resize`. + }, + Some(Rect { + x: BORDER_SIZE as i32 - RESIZE_HANDLE_SIZE as i32, + y: 0, + width: RESIZE_HANDLE_SIZE, + height: 0, // Defined by `Self::resize`. + }), ), - // Left. + // Right. Part::new( base_surface, subcompositor, queue_handle, - BORDER_SIZE, - 0, - (-(BORDER_SIZE as i32), -(HEADER_SIZE as i32)), + Rect { + x: 0, // Defined by `Self::resize`. + y: -(HEADER_SIZE as i32), + width: BORDER_SIZE, + height: 0, // Defined by `Self::resize`. + }, + Some(Rect { + x: 0, + y: 0, + width: RESIZE_HANDLE_SIZE, + height: 0, // Defined by `Self::resize`. + }), ), - // Right. + // Bottom. Part::new( base_surface, subcompositor, queue_handle, - BORDER_SIZE, - 0, - (-(BORDER_SIZE as i32), -(HEADER_SIZE as i32)), + Rect { + x: -(BORDER_SIZE as i32), + y: 0, // Defined by `Self::resize`. + width: 0, // Defined by `Self::resize`. + height: BORDER_SIZE, + }, + Some(Rect { + x: BORDER_SIZE as i32 - RESIZE_HANDLE_SIZE as i32, + y: 0, + width: 0, // Defined by `Self::resize`, + height: RESIZE_HANDLE_SIZE, + }), ), - // Bottom. + // Header. Part::new( base_surface, subcompositor, queue_handle, - 0, - BORDER_SIZE, - (-(BORDER_SIZE as i32), 0), + Rect { + x: 0, + y: -(HEADER_SIZE as i32), + width: 0, // Defined by `Self::resize`. + height: HEADER_SIZE, + }, + None, ), ]; @@ -108,18 +147,30 @@ impl DecorationParts { } } + // These unwraps are guaranteed to succeed because the affected options are filled above + // and then never emptied afterwards. + #[allow(clippy::unwrap_used)] pub fn resize(&mut self, width: u32, height: u32) { - self.parts[Self::HEADER].width = width; + self.parts[Self::HEADER].surface_rect.width = width; - self.parts[Self::BOTTOM].width = width + 2 * BORDER_SIZE; - self.parts[Self::BOTTOM].pos.1 = height as i32; + self.parts[Self::BOTTOM].surface_rect.width = width + 2 * BORDER_SIZE; + self.parts[Self::BOTTOM].surface_rect.y = height as i32; + self.parts[Self::BOTTOM].input_rect.as_mut().unwrap().width = + self.parts[Self::BOTTOM].surface_rect.width - (BORDER_SIZE * 2) + + (RESIZE_HANDLE_SIZE * 2); - self.parts[Self::TOP].width = self.parts[Self::BOTTOM].width; + self.parts[Self::TOP].surface_rect.width = self.parts[Self::BOTTOM].surface_rect.width; + self.parts[Self::TOP].input_rect.as_mut().unwrap().width = + self.parts[Self::TOP].surface_rect.width - (BORDER_SIZE * 2) + (RESIZE_HANDLE_SIZE * 2); - self.parts[Self::LEFT].height = height + HEADER_SIZE; + self.parts[Self::LEFT].surface_rect.height = height + HEADER_SIZE; + self.parts[Self::LEFT].input_rect.as_mut().unwrap().height = + self.parts[Self::LEFT].surface_rect.height; - self.parts[Self::RIGHT].height = self.parts[Self::LEFT].height; - self.parts[Self::RIGHT].pos.0 = width as i32; + self.parts[Self::RIGHT].surface_rect.height = self.parts[Self::LEFT].surface_rect.height; + self.parts[Self::RIGHT].surface_rect.x = width as i32; + self.parts[Self::RIGHT].input_rect.as_mut().unwrap().height = + self.parts[Self::RIGHT].surface_rect.height; } pub fn header(&self) -> &Part { @@ -127,7 +178,7 @@ impl DecorationParts { } pub fn side_height(&self) -> u32 { - self.parts[Self::LEFT].height + self.parts[Self::LEFT].surface_rect.height } pub fn find_surface(&self, surface: &ObjectId) -> Location { @@ -151,15 +202,25 @@ impl DecorationParts { } } +#[derive(Debug, Clone, Copy)] +pub struct Rect { + pub x: i32, + pub y: i32, + pub width: u32, + pub height: u32, +} + #[derive(Debug)] pub struct Part { pub surface: WlTyped, pub subsurface: WlTyped, - pub width: u32, - pub height: u32, - - pub pos: (i32, i32), + /// Positioned relative to the main surface. + pub surface_rect: Rect, + /// Positioned relative to the local surface, aka. `surface_rect`. + /// + /// `None` if it fully covers `surface_rect`. + pub input_rect: Option, } impl Part { @@ -167,9 +228,8 @@ impl Part { parent: &WlTyped, subcompositor: &SubcompositorState, queue_handle: &QueueHandle, - width: u32, - height: u32, - pos: (i32, i32), + surface_rect: Rect, + input_rect: Option, ) -> Part where State: Dispatch + Dispatch + 'static, @@ -186,9 +246,8 @@ impl Part { Part { surface, subsurface, - width, - height, - pos, + surface_rect, + input_rect, } } } diff --git a/src/shadow.rs b/src/shadow.rs new file mode 100644 index 0000000..efa9133 --- /dev/null +++ b/src/shadow.rs @@ -0,0 +1,397 @@ +use crate::{parts::DecorationParts, theme}; +use std::collections::BTreeMap; +use tiny_skia::{Pixmap, PixmapMut, PixmapRef, Point, PremultipliedColorU8}; + +// These values were generated from a screenshot of an libadwaita window using a script. +// For more details see: https://github.com/PolyMeilex/sctk-adwaita/pull/43 +pub const SHADOW_SIZE: u32 = 43; +const SHADOW_PARAMS_ACTIVE: (f32, f32, f32) = (0.206_505_5, 0.104_617_53, -0.000_542_446_2); +const SHADOW_PARAMS_INACTIVE: (f32, f32, f32) = (0.168_297_29, 0.204_299_8, 0.001_769_798_6); + +fn shadow(pixel_dist: f32, scale: u32, active: bool) -> f32 { + let (a, b, c) = if active { + SHADOW_PARAMS_ACTIVE + } else { + SHADOW_PARAMS_INACTIVE + }; + + a * (-b * (pixel_dist / scale as f32)).exp() + c +} + +#[derive(Debug)] +struct RenderedShadow { + side: Pixmap, + edges: Pixmap, +} + +impl RenderedShadow { + fn new(scale: u32, active: bool) -> RenderedShadow { + let shadow_size = SHADOW_SIZE * scale; + let corner_radius = theme::CORNER_RADIUS * scale; + + #[allow(clippy::unwrap_used)] + let mut side = Pixmap::new(shadow_size, 1).unwrap(); + for x in 0..side.width() as usize { + let alpha = (shadow(x as f32 + 0.5, scale, active) * u8::MAX as f32).round() as u8; + + #[allow(clippy::unwrap_used)] + let color = PremultipliedColorU8::from_rgba(0, 0, 0, alpha).unwrap(); + side.pixels_mut()[x] = color; + } + + let edges_size = (corner_radius + shadow_size) * 2; + #[allow(clippy::unwrap_used)] + let mut edges = Pixmap::new(edges_size, edges_size).unwrap(); + let edges_middle = Point::from_xy(edges_size as f32 / 2.0, edges_size as f32 / 2.0); + for y in 0..edges_size as usize { + let y_pos = y as f32 + 0.5; + for x in 0..edges_size as usize { + let dist = edges_middle.distance(Point::from_xy(x as f32 + 0.5, y_pos)) + - corner_radius as f32; + let alpha = (shadow(dist, scale, active) * u8::MAX as f32).round() as u8; + + #[allow(clippy::unwrap_used)] + let color = PremultipliedColorU8::from_rgba(0, 0, 0, alpha).unwrap(); + edges.pixels_mut()[y * edges_size as usize + x] = color; + } + } + + RenderedShadow { side, edges } + } + + fn side_draw( + &self, + flipped: bool, + rotated: bool, + stack: usize, + dst_pixmap: &mut PixmapMut, + dst_left: usize, + dst_top: usize, + ) { + fn iter_copy<'a>( + src: impl Iterator, + dst: impl Iterator, + ) { + src.zip(dst).for_each(|(src, dst)| *dst = *src) + } + + let dst_width = dst_pixmap.width() as usize; + let dst_pixels = dst_pixmap.pixels_mut(); + match (flipped, rotated) { + (false, false) => (0..stack).for_each(|i| { + let dst = dst_pixels + .iter_mut() + .skip((dst_top + i) * dst_width + dst_left); + iter_copy(self.side.pixels().iter(), dst); + }), + (false, true) => (0..stack).for_each(|i| { + let dst = dst_pixels + .iter_mut() + .skip(dst_top * dst_width + dst_left + i) + .step_by(dst_width); + iter_copy(self.side.pixels().iter(), dst); + }), + (true, false) => (0..stack).for_each(|i| { + let dst = dst_pixels + .iter_mut() + .skip((dst_top + i) * dst_width + dst_left); + iter_copy(self.side.pixels().iter().rev(), dst); + }), + (true, true) => (0..stack).for_each(|i| { + let dst = dst_pixels + .iter_mut() + .skip(dst_top * dst_width + dst_left + i) + .step_by(dst_width); + iter_copy(self.side.pixels().iter().rev(), dst); + }), + } + } + + #[allow(clippy::too_many_arguments)] + fn edges_draw( + &self, + src_x_offset: isize, + src_y_offset: isize, + dst_pixmap: &mut PixmapMut, + dst_rect_left: usize, + dst_rect_top: usize, + dst_rect_width: usize, + dst_rect_height: usize, + ) { + let src_width = self.edges.width() as usize; + let src_pixels = self.edges.pixels(); + let dst_width = dst_pixmap.width() as usize; + let dst_pixels = dst_pixmap.pixels_mut(); + for y in 0..dst_rect_height { + let dst_y = dst_rect_top + y; + let src_y = y as isize + src_y_offset; + if src_y < 0 { + continue; + } + + let src_y = src_y as usize; + for x in 0..dst_rect_width { + let dst_x = dst_rect_left + x; + let src_x = x as isize + src_x_offset; + if src_x < 0 { + continue; + } + + let src = src_pixels.get(src_y * src_width + src_x as usize); + let dst = dst_pixels.get_mut(dst_y * dst_width + dst_x); + if let (Some(src), Some(dst)) = (src, dst) { + *dst = *src; + } + } + } + } + + fn draw(&self, dst_pixmap: &mut PixmapMut, scale: u32, part_idx: usize) { + let shadow_size = (SHADOW_SIZE * scale) as usize; + let visible_border_size = (theme::VISIBLE_BORDER_SIZE * scale) as usize; + let corner_radius = (theme::CORNER_RADIUS * scale) as usize; + assert!(corner_radius > visible_border_size); + + let dst_width = dst_pixmap.width() as usize; + let dst_height = dst_pixmap.height() as usize; + let edges_half = self.edges.width() as usize / 2; + match part_idx { + DecorationParts::TOP => { + let left_edge_width = edges_half; + let right_edge_width = edges_half; + let side_width = dst_width + .saturating_sub(left_edge_width) + .saturating_sub(right_edge_width); + + self.edges_draw( + 0, + -(visible_border_size as isize), + dst_pixmap, + 0, + 0, + left_edge_width, + dst_height, + ); + + self.side_draw( + true, + true, + side_width, + dst_pixmap, + left_edge_width, + visible_border_size, + ); + + self.edges_draw( + edges_half as isize, + -(visible_border_size as isize), + dst_pixmap, + left_edge_width + side_width, + 0, + right_edge_width, + dst_height, + ); + } + DecorationParts::LEFT => { + let top_edge_height = corner_radius; + let bottom_edge_height = corner_radius - visible_border_size; + let side_height = dst_height + .saturating_sub(top_edge_height) + .saturating_sub(bottom_edge_height); + + self.edges_draw( + 0, + shadow_size as isize, + dst_pixmap, + 0, + 0, + dst_width.saturating_sub(visible_border_size), + top_edge_height, + ); + + self.side_draw(true, false, side_height, dst_pixmap, 0, top_edge_height); + + self.edges_draw( + 0, + edges_half as isize, + dst_pixmap, + 0, + top_edge_height + side_height, + dst_width.saturating_sub(visible_border_size), + bottom_edge_height, + ); + } + DecorationParts::RIGHT => { + let top_edge_height = corner_radius; + let bottom_edge_height = corner_radius - visible_border_size; + let side_height = dst_height + .saturating_sub(top_edge_height) + .saturating_sub(bottom_edge_height); + + self.edges_draw( + edges_half as isize + corner_radius as isize, + shadow_size as isize, + dst_pixmap, + visible_border_size, + 0, + dst_width.saturating_sub(visible_border_size), + top_edge_height, + ); + + self.side_draw( + false, + false, + side_height, + dst_pixmap, + visible_border_size, + top_edge_height, + ); + + self.edges_draw( + edges_half as isize + corner_radius as isize, + edges_half as isize, + dst_pixmap, + visible_border_size, + top_edge_height + side_height, + dst_width.saturating_sub(visible_border_size), + bottom_edge_height, + ); + } + DecorationParts::BOTTOM => { + let left_edge_width = edges_half; + let right_edge_width = edges_half; + let side_width = dst_width + .saturating_sub(left_edge_width) + .saturating_sub(right_edge_width); + + self.edges_draw( + 0, + edges_half as isize + (corner_radius - visible_border_size) as isize, + dst_pixmap, + 0, + 0, + left_edge_width, + dst_height, + ); + + self.side_draw( + false, + true, + side_width, + dst_pixmap, + left_edge_width, + visible_border_size, + ); + + self.edges_draw( + edges_half as isize, + edges_half as isize + (corner_radius - visible_border_size) as isize, + dst_pixmap, + left_edge_width + side_width, + 0, + right_edge_width, + dst_height, + ); + } + DecorationParts::HEADER => { + self.edges_draw( + shadow_size as isize, + shadow_size as isize, + dst_pixmap, + 0, + 0, + corner_radius, + corner_radius, + ); + + self.edges_draw( + edges_half as isize, + shadow_size as isize, + dst_pixmap, + dst_width.saturating_sub(corner_radius), + 0, + corner_radius, + corner_radius, + ); + } + _ => unreachable!(), + } + } +} + +#[derive(Debug)] +struct CachedPart { + pixmap: Pixmap, + scale: u32, + active: bool, +} + +impl CachedPart { + fn new( + dst_pixmap: &PixmapRef, + rendered: &RenderedShadow, + scale: u32, + active: bool, + part_idx: usize, + ) -> CachedPart { + #[allow(clippy::unwrap_used)] + let mut pixmap = Pixmap::new(dst_pixmap.width(), dst_pixmap.height()).unwrap(); + rendered.draw(&mut pixmap.as_mut(), scale, part_idx); + + CachedPart { + pixmap, + scale, + active, + } + } + + fn matches(&self, dst_pixmap: &PixmapRef, dst_scale: u32, dst_active: bool) -> bool { + self.pixmap.width() == dst_pixmap.width() + && self.pixmap.height() == dst_pixmap.height() + && self.scale == dst_scale + && self.active == dst_active + } + + fn draw(&self, dst_pixmap: &mut PixmapMut) { + let src_data = self.pixmap.data(); + dst_pixmap.data_mut()[..src_data.len()].copy_from_slice(src_data); + } +} + +#[derive(Default, Debug)] +pub struct Shadow { + part_cache: [Option; 5], + // (scale, active) -> RenderedShadow + rendered: BTreeMap<(u32, bool), RenderedShadow>, +} + +impl Shadow { + pub fn draw(&mut self, pixmap: &mut PixmapMut, scale: u32, active: bool, part_idx: usize) { + let cache = &mut self.part_cache[part_idx]; + + if let Some(cache_value) = cache { + if !cache_value.matches(&pixmap.as_ref(), scale, active) { + *cache = None; + } + } + + if cache.is_none() { + let rendered = self + .rendered + .entry((scale, active)) + .or_insert_with(|| RenderedShadow::new(scale, active)); + + *cache = Some(CachedPart::new( + &pixmap.as_ref(), + rendered, + scale, + active, + part_idx, + )); + } + + // We filled the cache above. + #[allow(clippy::unwrap_used)] + cache.as_ref().unwrap().draw(pixmap); + } +} diff --git a/src/theme.rs b/src/theme.rs index f89af74..8feeddf 100644 --- a/src/theme.rs +++ b/src/theme.rs @@ -5,7 +5,7 @@ use tiny_skia::{Paint, Shader}; pub(crate) const RESIZE_HANDLE_SIZE: u32 = 12; // https://gitlab.gnome.org/GNOME/gtk/-/blob/1bf88f1d81043fd99740e2f91e56ade7ede7303b/gtk/gtkwindow.c#L166 pub(crate) const RESIZE_HANDLE_CORNER_SIZE: u32 = 24; -pub(crate) const BORDER_SIZE: u32 = VISIBLE_BORDER_SIZE + RESIZE_HANDLE_SIZE; +pub(crate) const BORDER_SIZE: u32 = crate::shadow::SHADOW_SIZE + VISIBLE_BORDER_SIZE; pub(crate) const HEADER_SIZE: u32 = 35; pub(crate) const CORNER_RADIUS: u32 = 10; pub(crate) const VISIBLE_BORDER_SIZE: u32 = 1;