| 1 | //! What a frame draws. The space holds final geometry for input, while the |
| 2 | //! scene draws windows where the animator says they are right now. |
| 3 | |
| 4 | use crate::wm::{LastFrame, Wm}; |
| 5 | use monolith_config::Decoration; |
| 6 | use monolith_layout::{OutputId, Rect, WindowId}; |
| 7 | use monolith_render_vk::{Clip, Effect, VulkanFrame, VulkanRenderer, VulkanTexture}; |
| 8 | use smithay::backend::renderer::element::memory::MemoryRenderBufferRenderElement; |
| 9 | use smithay::backend::renderer::element::solid::{SolidColorBuffer, SolidColorRenderElement}; |
| 10 | use smithay::backend::renderer::element::surface::WaylandSurfaceRenderElement; |
| 11 | use smithay::backend::renderer::element::surface::render_elements_from_surface_tree; |
| 12 | use smithay::backend::renderer::element::texture::TextureRenderElement; |
| 13 | use smithay::backend::renderer::element::utils::{CropRenderElement, RescaleRenderElement}; |
| 14 | use smithay::backend::renderer::element::{AsRenderElements, Element, Id, Kind, RenderElement}; |
| 15 | use smithay::backend::renderer::utils::with_renderer_surface_state; |
| 16 | use smithay::backend::renderer::utils::{CommitCounter, DamageSet, OpaqueRegions}; |
| 17 | use smithay::backend::renderer::{ContextId, Renderer, Texture}; |
| 18 | use smithay::desktop::{PopupManager, Window}; |
| 19 | use smithay::output::Output; |
| 20 | use smithay::utils::user_data::UserDataMap; |
| 21 | use smithay::utils::{Buffer, Logical, Physical, Point, Rectangle, Scale, Size, Transform}; |
| 22 | use smithay::wayland::compositor::{TraversalAction, with_surface_tree_downward}; |
| 23 | use smithay::wayland::shell::wlr_layer::Layer; |
| 24 | use smithay::xwayland::X11Surface; |
| 25 | |
| 26 | smithay::backend::renderer::element::render_elements! { |
| 27 | pub Draw<=VulkanRenderer>; |
| 28 | Surface=WaylandSurfaceRenderElement<VulkanRenderer>, |
| 29 | Cropped=CropRenderElement<WaylandSurfaceRenderElement<VulkanRenderer>>, |
| 30 | Texture=TextureRenderElement<VulkanTexture>, |
| 31 | Effect=EffectElement, |
| 32 | Border=SolidColorRenderElement, |
| 33 | Memory=MemoryRenderBufferRenderElement<VulkanRenderer>, |
| 34 | Rescaled=RescaleRenderElement<WaylandSurfaceRenderElement<VulkanRenderer>>, |
| 35 | Clipped=ClipElement, |
| 36 | } |
| 37 | |
| 38 | /// Elements for output `o` in its own coordinates, front to back. |
| 39 | #[profiling::function] |
| 40 | pub fn build( |
| 41 | wm: &mut Wm, |
| 42 | o: OutputId, |
| 43 | renderer: &mut VulkanRenderer, |
| 44 | now: u64, |
| 45 | deco: &Decoration, |
| 46 | output: Option<&Output>, |
| 47 | unmanaged: &[X11Surface], |
| 48 | ) -> Vec<Draw> { |
| 49 | let crate::wm::Parts { |
| 50 | layout, |
| 51 | anim, |
| 52 | windows, |
| 53 | last_frames, |
| 54 | } = wm.parts(); |
| 55 | let Some(area) = layout.output(o).map(|out| out.area) else { |
| 56 | return Vec::new(); |
| 57 | }; |
| 58 | let local = |r: Rect| Rect::new(r.x - area.x, r.y - area.y, r.w, r.h); |
| 59 | let mut ghosts = Vec::new(); |
| 60 | let mut tiled = Vec::new(); |
| 61 | let mut above = Vec::new(); |
| 62 | let mut fading = Vec::new(); |
| 63 | for g in anim.ghosts().iter().filter(|g| g.output == o) { |
| 64 | let Some(frame) = last_frames.get(&g.id) else { |
| 65 | continue; |
| 66 | }; |
| 67 | match g.fixed { |
| 68 | Some(r) => fading.push((g, frame, local(r))), |
| 69 | None => { |
| 70 | if let Some(r) = anim.tiled(o, g.id, now) { |
| 71 | ghosts.push((g, frame, local(r))); |
| 72 | } |
| 73 | } |
| 74 | } |
| 75 | } |
| 76 | let arr = layout.arrange(o); |
| 77 | let fullscreen_at_rest = arr |
| 78 | .fullscreen |
| 79 | .is_some_and(|w| anim.gliding(w, now).is_none()); |
| 80 | for p in &arr.tiled { |
| 81 | if Some(p.window) == arr.fullscreen || fullscreen_at_rest { |
| 82 | continue; |
| 83 | } |
| 84 | match anim.gliding(p.window, now) { |
| 85 | Some(r) => above.push((p.window, local(r), true, true)), |
| 86 | None => { |
| 87 | let r = anim.tiled(o, p.window, now).unwrap_or(p.rect); |
| 88 | tiled.push((p.window, local(r), true, false)); |
| 89 | } |
| 90 | } |
| 91 | } |
| 92 | let float = |&(w, r): &(WindowId, Rect)| { |
| 93 | let glide = anim.gliding(w, now); |
| 94 | (w, local(glide.unwrap_or(r)), true, glide.is_some()) |
| 95 | }; |
| 96 | let (over, under): (Vec<_>, Vec<_>) = |
| 97 | arr.floating.iter().partition(|f| arr.over.contains(&f.0)); |
| 98 | above.extend(under.into_iter().map(float)); |
| 99 | if let Some(w) = arr.fullscreen { |
| 100 | let glide = anim.gliding(w, now); |
| 101 | above.push((w, local(glide.unwrap_or(area)), false, glide.is_some())); |
| 102 | } |
| 103 | above.extend(over.into_iter().map(float)); |
| 104 | let focused = layout.focused(); |
| 105 | let mut out = Vec::new(); |
| 106 | for x in unmanaged.iter().rev() { |
| 107 | let Some(surface) = x.wl_surface() else { |
| 108 | continue; |
| 109 | }; |
| 110 | let loc = x.geometry().loc; |
| 111 | let at = Point::<i32, Physical>::from((loc.x - area.x as i32, loc.y - area.y as i32)); |
| 112 | out.extend( |
| 113 | render_elements_from_surface_tree(renderer, &surface, at, 1.0, 1.0, Kind::Unspecified) |
| 114 | .into_iter() |
| 115 | .map(Draw::Surface), |
| 116 | ); |
| 117 | } |
| 118 | if let Some(output) = output { |
| 119 | crate::layers::elements(&mut out, renderer, output, Layer::Overlay); |
| 120 | if !fullscreen_at_rest { |
| 121 | crate::layers::elements(&mut out, renderer, output, Layer::Top); |
| 122 | } |
| 123 | } |
| 124 | for (g, frame, r) in fading.into_iter().rev() { |
| 125 | out.extend(ghost_element(frame, r, anim.ghost_effect(g, now), deco)); |
| 126 | } |
| 127 | for (id, r, framed, gliding) in tiled.into_iter().chain(above).rev() { |
| 128 | let Some(state) = windows.get_mut(id) else { |
| 129 | continue; |
| 130 | }; |
| 131 | let effect = anim.effect(id, now); |
| 132 | let first = out.len(); |
| 133 | window_elements(&mut out, renderer, &state.window, r, effect, gliding); |
| 134 | let clip = framed.then(|| corner_clip(r, deco)).flatten(); |
| 135 | if let Some(clip) = clip { |
| 136 | clip_window(&mut out, first, &state.window, clip); |
| 137 | } |
| 138 | if framed && deco.border > 0 { |
| 139 | let color = if focused == Some(id) { |
| 140 | deco.focused.0 |
| 141 | } else { |
| 142 | deco.unfocused.0 |
| 143 | }; |
| 144 | let alpha = effect.map_or(1.0, |e| e.progress); |
| 145 | let borders = &mut state.borders; |
| 146 | let first = out.len(); |
| 147 | border_elements(&mut out, borders, r, deco, color, alpha); |
| 148 | if let Some(clip) = clip { |
| 149 | ring_borders(&mut out, first, clip, deco.border as i32); |
| 150 | } |
| 151 | } |
| 152 | } |
| 153 | for (g, frame, r) in ghosts.into_iter().rev() { |
| 154 | out.extend(ghost_element(frame, r, anim.ghost_effect(g, now), deco)); |
| 155 | } |
| 156 | if let Some(output) = output { |
| 157 | crate::layers::elements(&mut out, renderer, output, Layer::Bottom); |
| 158 | crate::layers::elements(&mut out, renderer, output, Layer::Background); |
| 159 | } |
| 160 | out |
| 161 | } |
| 162 | |
| 163 | /// Draws `window` filling `r`. In a planned move a buffer bigger than `r` is |
| 164 | /// cropped and one smaller has its last row and column stretched over the |
| 165 | /// rest, keeping content aligned with its neighbours. A gliding window is |
| 166 | /// scaled instead, so the client resizing mid-glide does not jump. |
| 167 | fn window_elements( |
| 168 | out: &mut Vec<Draw>, |
| 169 | renderer: &mut VulkanRenderer, |
| 170 | window: &Window, |
| 171 | r: Rect, |
| 172 | effect: Option<Effect>, |
| 173 | scaled: bool, |
| 174 | ) { |
| 175 | let Some(((x, y), (w, h))) = pixels(r) else { |
| 176 | return; |
| 177 | }; |
| 178 | let geo = window.geometry(); |
| 179 | let loc = Point::<i32, Physical>::from((x - geo.loc.x, y - geo.loc.y)); |
| 180 | let alpha = effect.map_or(1.0, |e| e.progress); |
| 181 | let elems: Vec<WaylandSurfaceRenderElement<VulkanRenderer>> = |
| 182 | window.render_elements(renderer, loc, Scale::from(1.0), alpha); |
| 183 | if let Some(effect) = effect { |
| 184 | effect_elements(out, renderer, window, elems, ((x, y), (w, h)), effect); |
| 185 | return; |
| 186 | } |
| 187 | if scaled && geo.size.w > 0 && geo.size.h > 0 { |
| 188 | let scale = Scale::from(( |
| 189 | f64::from(w) / f64::from(geo.size.w), |
| 190 | f64::from(h) / f64::from(geo.size.h), |
| 191 | )); |
| 192 | let origin = Point::<i32, Physical>::from((x, y)); |
| 193 | out.extend( |
| 194 | elems |
| 195 | .into_iter() |
| 196 | .map(|e| Draw::Rescaled(RescaleRenderElement::from_element(e, origin, scale))), |
| 197 | ); |
| 198 | return; |
| 199 | } |
| 200 | if (w, h) == (geo.size.w, geo.size.h) { |
| 201 | out.extend(elems.into_iter().map(Draw::Surface)); |
| 202 | return; |
| 203 | } |
| 204 | let crop = Rectangle::new((x, y).into(), (w, h).into()); |
| 205 | out.extend( |
| 206 | elems |
| 207 | .into_iter() |
| 208 | .filter_map(|e| CropRenderElement::from_element(e, 1.0, crop)) |
| 209 | .map(Draw::Cropped), |
| 210 | ); |
| 211 | let Some(root) = crate::wm::root(window) else { |
| 212 | return; |
| 213 | }; |
| 214 | let context = renderer.context_id(); |
| 215 | let Some((tex, scale, transform)) = with_renderer_surface_state(&root, |s| { |
| 216 | s.texture::<VulkanTexture>(context.clone()) |
| 217 | .cloned() |
| 218 | .map(|t| (t, s.buffer_scale(), s.buffer_transform())) |
| 219 | }) |
| 220 | .flatten() else { |
| 221 | return; |
| 222 | }; |
| 223 | let (gw, gh) = (geo.size.w, geo.size.h); |
| 224 | let (cw, ch) = (w.min(gw), h.min(gh)); |
| 225 | let (gx, gy) = (geo.loc.x as f64, geo.loc.y as f64); |
| 226 | let mut strip = |src: Rectangle<f64, Logical>, at: (i32, i32), size: (i32, i32)| { |
| 227 | if size.0 >= 1 && size.1 >= 1 { |
| 228 | out.push(texture(&context, &tex, scale, transform, src, at, size)); |
| 229 | } |
| 230 | }; |
| 231 | let right = gx + f64::from(gw) - 1.0; |
| 232 | let bottom = gy + f64::from(gh) - 1.0; |
| 233 | strip( |
| 234 | Rectangle::new((right, gy).into(), (1.0, f64::from(ch)).into()), |
| 235 | (x + gw, y), |
| 236 | (w - gw, ch), |
| 237 | ); |
| 238 | strip( |
| 239 | Rectangle::new((gx, bottom).into(), (f64::from(cw), 1.0).into()), |
| 240 | (x, y + gh), |
| 241 | (cw, h - gh), |
| 242 | ); |
| 243 | strip( |
| 244 | Rectangle::new((right, bottom).into(), (1.0, 1.0).into()), |
| 245 | (x + gw, y + gh), |
| 246 | (w - gw, h - gh), |
| 247 | ); |
| 248 | } |
| 249 | |
| 250 | /// Four strips just outside `r`, in the gap, so the client keeps its size. |
| 251 | /// With rounded corners they reach in by the largest radius, to cover the |
| 252 | /// band the ring curves through inside the window's corners. |
| 253 | fn border_elements( |
| 254 | out: &mut Vec<Draw>, |
| 255 | borders: &mut Option<[SolidColorBuffer; 4]>, |
| 256 | r: Rect, |
| 257 | deco: &Decoration, |
| 258 | color: [f32; 4], |
| 259 | alpha: f32, |
| 260 | ) { |
| 261 | let Some(((x, y), (w, h))) = pixels(r) else { |
| 262 | return; |
| 263 | }; |
| 264 | let b = deco.border as i32; |
| 265 | let reach = deco.radius.0.into_iter().max().unwrap_or(0) as i32; |
| 266 | let reach = reach.min(w / 2).min(h / 2); |
| 267 | let strips = [ |
| 268 | ((x - b, y - b), (w + 2 * b, b + reach)), |
| 269 | ((x - b, y + h - reach), (w + 2 * b, b + reach)), |
| 270 | ((x - b, y + reach), (b + reach, h - 2 * reach)), |
| 271 | ((x + w - reach, y + reach), (b + reach, h - 2 * reach)), |
| 272 | ]; |
| 273 | let buffers = borders |
| 274 | .get_or_insert_with(|| std::array::from_fn(|_| SolidColorBuffer::new((0, 0), color))); |
| 275 | for (buffer, (at, size)) in buffers.iter_mut().zip(strips) { |
| 276 | buffer.update(size, color); |
| 277 | out.push(Draw::Border(SolidColorRenderElement::from_buffer( |
| 278 | buffer, |
| 279 | at, |
| 280 | 1.0, |
| 281 | alpha, |
| 282 | Kind::Unspecified, |
| 283 | ))); |
| 284 | } |
| 285 | } |
| 286 | |
| 287 | /// Snaps a logical rect to whole pixels by rounding its edges, so neighbours |
| 288 | /// that share an edge stay flush. |
| 289 | fn pixels(r: Rect) -> Option<((i32, i32), (i32, i32))> { |
| 290 | let (x, y) = (r.x.round() as i32, r.y.round() as i32); |
| 291 | let w = r.right().round() as i32 - x; |
| 292 | let h = r.bottom().round() as i32 - y; |
| 293 | (w >= 1 && h >= 1).then_some(((x, y), (w, h))) |
| 294 | } |
| 295 | |
| 296 | fn texture( |
| 297 | context: &ContextId<VulkanTexture>, |
| 298 | texture: &VulkanTexture, |
| 299 | scale: i32, |
| 300 | transform: Transform, |
| 301 | src: Rectangle<f64, Logical>, |
| 302 | at: (i32, i32), |
| 303 | size: (i32, i32), |
| 304 | ) -> Draw { |
| 305 | Draw::Texture(TextureRenderElement::from_static_texture( |
| 306 | Id::new(), |
| 307 | context.clone(), |
| 308 | Point::<f64, Physical>::from((f64::from(at.0), f64::from(at.1))), |
| 309 | texture.clone(), |
| 310 | scale, |
| 311 | transform, |
| 312 | None, |
| 313 | Some(src), |
| 314 | Some(Size::from(size)), |
| 315 | None, |
| 316 | Kind::Unspecified, |
| 317 | )) |
| 318 | } |
| 319 | |
| 320 | /// A window in a spawn transition. Its root surface goes through the effect |
| 321 | /// shader, clamped to fill its rect, and its subsurfaces and popups fade with it. |
| 322 | fn effect_elements( |
| 323 | out: &mut Vec<Draw>, |
| 324 | renderer: &mut VulkanRenderer, |
| 325 | window: &Window, |
| 326 | elems: Vec<WaylandSurfaceRenderElement<VulkanRenderer>>, |
| 327 | (at, size): ((i32, i32), (i32, i32)), |
| 328 | effect: Effect, |
| 329 | ) { |
| 330 | let Some(root) = crate::wm::root(window) else { |
| 331 | return; |
| 332 | }; |
| 333 | let root_id = Id::from_wayland_resource(&root); |
| 334 | let crop = Rectangle::new(at.into(), size.into()); |
| 335 | let context = renderer.context_id(); |
| 336 | let root_tex = with_renderer_surface_state(&root, |s| { |
| 337 | s.texture::<VulkanTexture>(context.clone()) |
| 338 | .cloned() |
| 339 | .map(|t| (t, s.buffer_scale(), s.buffer_transform())) |
| 340 | }) |
| 341 | .flatten(); |
| 342 | out.extend( |
| 343 | elems |
| 344 | .into_iter() |
| 345 | .filter(|e| root_tex.is_none() || *e.id() != root_id) |
| 346 | .filter_map(|e| CropRenderElement::from_element(e, 1.0, crop)) |
| 347 | .map(Draw::Cropped), |
| 348 | ); |
| 349 | if let Some((tex, scale, transform)) = root_tex { |
| 350 | let geo = window.geometry(); |
| 351 | let src = Rectangle::new( |
| 352 | geo.loc.to_f64(), |
| 353 | (f64::from(size.0), f64::from(size.1)).into(), |
| 354 | ); |
| 355 | out.push(Draw::Effect(EffectElement::new( |
| 356 | &tex, scale, transform, src, at, size, effect, |
| 357 | ))); |
| 358 | } |
| 359 | } |
| 360 | |
| 361 | /// A texture drawn through the transition hook in the effect shader. |
| 362 | pub struct EffectElement { |
| 363 | id: Id, |
| 364 | texture: VulkanTexture, |
| 365 | src: Rectangle<f64, Buffer>, |
| 366 | geometry: Rectangle<i32, Physical>, |
| 367 | transform: Transform, |
| 368 | effect: Effect, |
| 369 | } |
| 370 | |
| 371 | impl EffectElement { |
| 372 | fn new( |
| 373 | texture: &VulkanTexture, |
| 374 | scale: i32, |
| 375 | transform: Transform, |
| 376 | src: Rectangle<f64, Logical>, |
| 377 | at: (i32, i32), |
| 378 | size: (i32, i32), |
| 379 | effect: Effect, |
| 380 | ) -> Self { |
| 381 | let bounds = texture.size().to_logical(scale, transform).to_f64(); |
| 382 | Self { |
| 383 | id: Id::new(), |
| 384 | texture: texture.clone(), |
| 385 | src: src.to_buffer(f64::from(scale), transform, &bounds), |
| 386 | geometry: Rectangle::new(at.into(), size.into()), |
| 387 | transform, |
| 388 | effect, |
| 389 | } |
| 390 | } |
| 391 | } |
| 392 | |
| 393 | impl Element for EffectElement { |
| 394 | fn id(&self) -> &Id { |
| 395 | &self.id |
| 396 | } |
| 397 | |
| 398 | fn current_commit(&self) -> CommitCounter { |
| 399 | CommitCounter::default() |
| 400 | } |
| 401 | |
| 402 | fn src(&self) -> Rectangle<f64, Buffer> { |
| 403 | self.src |
| 404 | } |
| 405 | |
| 406 | fn geometry(&self, _scale: Scale<f64>) -> Rectangle<i32, Physical> { |
| 407 | self.geometry |
| 408 | } |
| 409 | |
| 410 | fn transform(&self) -> Transform { |
| 411 | self.transform |
| 412 | } |
| 413 | } |
| 414 | |
| 415 | impl RenderElement<VulkanRenderer> for EffectElement { |
| 416 | fn draw( |
| 417 | &self, |
| 418 | frame: &mut VulkanFrame<'_, '_>, |
| 419 | src: Rectangle<f64, Buffer>, |
| 420 | dst: Rectangle<i32, Physical>, |
| 421 | damage: &[Rectangle<i32, Physical>], |
| 422 | _opaque_regions: &[Rectangle<i32, Physical>], |
| 423 | _cache: Option<&UserDataMap>, |
| 424 | ) -> Result<(), monolith_render_vk::Error> { |
| 425 | frame.render_effect( |
| 426 | &self.texture, |
| 427 | src, |
| 428 | dst, |
| 429 | damage, |
| 430 | self.transform, |
| 431 | 1.0, |
| 432 | self.effect, |
| 433 | ) |
| 434 | } |
| 435 | } |
| 436 | |
| 437 | fn ghost_element(frame: &LastFrame, r: Rect, effect: Effect, deco: &Decoration) -> Option<Draw> { |
| 438 | let (at, size) = pixels(r)?; |
| 439 | let src = Rectangle::new( |
| 440 | frame.geometry.loc.to_f64(), |
| 441 | (f64::from(size.0), f64::from(size.1)).into(), |
| 442 | ); |
| 443 | let ghost = Draw::Effect(EffectElement::new( |
| 444 | &frame.texture, |
| 445 | frame.scale, |
| 446 | frame.transform, |
| 447 | src, |
| 448 | at, |
| 449 | size, |
| 450 | effect, |
| 451 | )); |
| 452 | Some(match corner_clip(r, deco) { |
| 453 | Some(clip) => Draw::Clipped(ClipElement::new(ghost, clip)), |
| 454 | None => ghost, |
| 455 | }) |
| 456 | } |
| 457 | |
| 458 | /// The rounded outline of a framed window at `r`, if it has one. |
| 459 | fn corner_clip(r: Rect, deco: &Decoration) -> Option<Clip> { |
| 460 | let radii = deco.radius.0; |
| 461 | if radii == [0; 4] { |
| 462 | return None; |
| 463 | } |
| 464 | let (at, size) = pixels(r)?; |
| 465 | Some(Clip { |
| 466 | rect: Rectangle::new(at.into(), size.into()), |
| 467 | radii: radii.map(|r| r as f32), |
| 468 | ring: 0.0, |
| 469 | }) |
| 470 | } |
| 471 | |
| 472 | /// Clips what a window drew from `first` on, leaving its popups whole. |
| 473 | fn clip_window(out: &mut [Draw], first: usize, window: &Window, clip: Clip) { |
| 474 | let mut popups = std::collections::HashSet::new(); |
| 475 | if let Some(root) = crate::wm::root(window) { |
| 476 | for (popup, _) in PopupManager::popups_for_surface(&root) { |
| 477 | with_surface_tree_downward( |
| 478 | popup.wl_surface(), |
| 479 | (), |
| 480 | |_, _, _| TraversalAction::DoChildren(()), |
| 481 | |s, _, _| { |
| 482 | popups.insert(Id::from_wayland_resource(s)); |
| 483 | }, |
| 484 | |_, _, _| true, |
| 485 | ); |
| 486 | } |
| 487 | } |
| 488 | for draw in &mut out[first..] { |
| 489 | if !popups.contains(draw.id()) { |
| 490 | wrap(draw, clip); |
| 491 | } |
| 492 | } |
| 493 | } |
| 494 | |
| 495 | /// Turns the border strips from `first` on into a ring around the window's |
| 496 | /// rounded outline. |
| 497 | fn ring_borders(out: &mut [Draw], first: usize, window: Clip, b: i32) { |
| 498 | let grow = |r: f32| if r > 0.0 { r + b as f32 } else { 0.0 }; |
| 499 | let ring = Clip { |
| 500 | rect: Rectangle::new( |
| 501 | window.rect.loc - Point::from((b, b)), |
| 502 | window.rect.size + Size::from((2 * b, 2 * b)), |
| 503 | ), |
| 504 | radii: window.radii.map(grow), |
| 505 | ring: b as f32, |
| 506 | }; |
| 507 | for draw in &mut out[first..] { |
| 508 | wrap(draw, ring); |
| 509 | } |
| 510 | } |
| 511 | |
| 512 | fn wrap(draw: &mut Draw, clip: Clip) { |
| 513 | let inner = std::mem::replace(draw, Draw::Clipped(ClipElement::empty())); |
| 514 | *draw = Draw::Clipped(ClipElement::new(inner, clip)); |
| 515 | } |
| 516 | |
| 517 | /// Another element drawn through a rounded clip. |
| 518 | pub struct ClipElement { |
| 519 | inner: Option<Box<Draw>>, |
| 520 | clip: Clip, |
| 521 | id: Id, |
| 522 | } |
| 523 | |
| 524 | impl ClipElement { |
| 525 | fn new(inner: Draw, clip: Clip) -> Self { |
| 526 | Self { |
| 527 | id: inner.id().clone(), |
| 528 | inner: Some(Box::new(inner)), |
| 529 | clip, |
| 530 | } |
| 531 | } |
| 532 | |
| 533 | /// A placeholder while an element is moved into its wrapper. |
| 534 | fn empty() -> Self { |
| 535 | Self { |
| 536 | inner: None, |
| 537 | clip: Clip { |
| 538 | rect: Rectangle::default(), |
| 539 | radii: [0.0; 4], |
| 540 | ring: 0.0, |
| 541 | }, |
| 542 | id: Id::new(), |
| 543 | } |
| 544 | } |
| 545 | |
| 546 | fn inner(&self) -> &Draw { |
| 547 | self.inner.as_deref().expect("only placeholders are empty") |
| 548 | } |
| 549 | } |
| 550 | |
| 551 | impl Element for ClipElement { |
| 552 | fn id(&self) -> &Id { |
| 553 | &self.id |
| 554 | } |
| 555 | |
| 556 | fn current_commit(&self) -> CommitCounter { |
| 557 | self.inner().current_commit() |
| 558 | } |
| 559 | |
| 560 | fn location(&self, scale: Scale<f64>) -> Point<i32, Physical> { |
| 561 | self.inner().location(scale) |
| 562 | } |
| 563 | |
| 564 | fn src(&self) -> Rectangle<f64, Buffer> { |
| 565 | self.inner().src() |
| 566 | } |
| 567 | |
| 568 | fn transform(&self) -> Transform { |
| 569 | self.inner().transform() |
| 570 | } |
| 571 | |
| 572 | fn geometry(&self, scale: Scale<f64>) -> Rectangle<i32, Physical> { |
| 573 | self.inner().geometry(scale) |
| 574 | } |
| 575 | |
| 576 | fn damage_since( |
| 577 | &self, |
| 578 | scale: Scale<f64>, |
| 579 | commit: Option<CommitCounter>, |
| 580 | ) -> DamageSet<i32, Physical> { |
| 581 | self.inner().damage_since(scale, commit) |
| 582 | } |
| 583 | |
| 584 | /// A ring hides nothing behind it, and a rounded window hides nothing |
| 585 | /// behind its corners. |
| 586 | fn opaque_regions(&self, scale: Scale<f64>) -> OpaqueRegions<i32, Physical> { |
| 587 | if self.clip.ring > 0.0 { |
| 588 | return OpaqueRegions::default(); |
| 589 | } |
| 590 | let loc = self.geometry(scale).loc; |
| 591 | let c = self.clip.rect; |
| 592 | let (right, bottom) = (c.loc.x + c.size.w, c.loc.y + c.size.h); |
| 593 | let [tl, tr, br, bl] = self.clip.radii.map(|r| r.ceil() as i32); |
| 594 | let corners = [ |
| 595 | Rectangle::new(c.loc, (tl, tl).into()), |
| 596 | Rectangle::new((right - tr, c.loc.y).into(), (tr, tr).into()), |
| 597 | Rectangle::new((right - br, bottom - br).into(), (br, br).into()), |
| 598 | Rectangle::new((c.loc.x, bottom - bl).into(), (bl, bl).into()), |
| 599 | ] |
| 600 | .map(|mut r| { |
| 601 | r.loc -= loc; |
| 602 | r |
| 603 | }); |
| 604 | Rectangle::subtract_rects_many(self.inner().opaque_regions(scale), corners) |
| 605 | .into_iter() |
| 606 | .collect() |
| 607 | } |
| 608 | |
| 609 | fn alpha(&self) -> f32 { |
| 610 | self.inner().alpha() |
| 611 | } |
| 612 | |
| 613 | fn kind(&self) -> Kind { |
| 614 | self.inner().kind() |
| 615 | } |
| 616 | } |
| 617 | |
| 618 | impl RenderElement<VulkanRenderer> for ClipElement { |
| 619 | fn draw( |
| 620 | &self, |
| 621 | frame: &mut VulkanFrame<'_, '_>, |
| 622 | src: Rectangle<f64, Buffer>, |
| 623 | dst: Rectangle<i32, Physical>, |
| 624 | damage: &[Rectangle<i32, Physical>], |
| 625 | opaque_regions: &[Rectangle<i32, Physical>], |
| 626 | cache: Option<&UserDataMap>, |
| 627 | ) -> Result<(), monolith_render_vk::Error> { |
| 628 | frame.set_clip(Some(self.clip)); |
| 629 | let drawn = self |
| 630 | .inner() |
| 631 | .draw(frame, src, dst, damage, opaque_regions, cache); |
| 632 | frame.set_clip(None); |
| 633 | drawn |
| 634 | } |
| 635 | } |