shell/elements: Handle clipping and shadows

This commit is contained in:
Victoria Brekenfeld 2025-12-08 18:22:23 +01:00 committed by Victoria Brekenfeld
parent 2adebb5fe1
commit 59fd732982
7 changed files with 917 additions and 97 deletions

View file

@ -3,9 +3,16 @@ use super::{
window::{Focus, RESIZE_BORDER},
};
use crate::{
backend::render::cursor::CursorState,
backend::render::{
IndicatorShader, Key, Usage,
clipped_surface::ClippedSurfaceRenderElement,
cursor::CursorState,
element::{AsGlowRenderer, FromGlesError},
shadow::ShadowShader,
},
hooks::{Decorations, HOOKS},
shell::{
element::{CosmicMappedKey, CosmicMappedKeyInner},
focus::target::PointerFocusTarget,
grabs::{ReleaseMode, ResizeEdge},
layout::tiling::NodeDesc,
@ -34,9 +41,13 @@ use smithay::{
renderer::{
ImportAll, ImportMem, Renderer,
element::{
AsRenderElements, memory::MemoryRenderBufferRenderElement,
AsRenderElements, Element, Id as RendererId, Kind, RenderElement,
UnderlyingStorage, memory::MemoryRenderBufferRenderElement,
surface::WaylandSurfaceRenderElement,
},
gles::element::PixelShaderElement,
glow::GlowRenderer,
utils::{CommitCounter, DamageSet, OpaqueRegions},
},
},
desktop::{WindowSurfaceType, space::SpaceElement},
@ -56,8 +67,7 @@ use smithay::{
},
output::Output,
reexports::wayland_server::protocol::wl_surface::WlSurface,
render_elements,
utils::{Buffer, IsAlive, Logical, Physical, Point, Rectangle, Scale, Serial, Size},
utils::{Buffer, IsAlive, Logical, Physical, Point, Rectangle, Scale, Serial, Size, Transform},
wayland::seat::WaylandFocus,
};
use std::{
@ -65,7 +75,7 @@ use std::{
fmt,
hash::Hash,
sync::{
LazyLock, Mutex,
Arc, LazyLock, Mutex,
atomic::{AtomicBool, AtomicU8, AtomicUsize, Ordering},
},
};
@ -107,6 +117,8 @@ pub struct CosmicStackInternal {
override_alive: AtomicBool,
geometry: Mutex<Option<Rectangle<i32, Global>>>,
mask: Mutex<Option<tiny_skia::Mask>>,
tiled: AtomicBool,
theme: Mutex<cosmic::Theme>,
appearance_conf: Mutex<AppearanceConfig>,
}
@ -162,6 +174,8 @@ impl CosmicStack {
override_alive: AtomicBool::new(true),
geometry: Mutex::new(None),
mask: Mutex::new(None),
tiled: AtomicBool::new(false),
theme: Mutex::new(theme.clone()),
appearance_conf: Mutex::new(appearance),
},
(width, TAB_HEIGHT),
@ -479,6 +493,11 @@ impl CosmicStack {
self.0.force_redraw()
}
pub fn set_tiled(&self, tiled: bool) {
self.0
.with_program(|p| p.tiled.store(tiled, Ordering::Release));
}
pub fn surfaces(&self) -> impl Iterator<Item = CosmicSurface> {
self.0.with_program(|p| {
p.windows
@ -618,7 +637,7 @@ impl CosmicStack {
alpha: f32,
) -> Vec<C>
where
R: Renderer + ImportAll + ImportMem,
R: Renderer + AsGlowRenderer + ImportAll + ImportMem,
R::TextureId: Send + Clone + 'static,
C: From<CosmicStackRenderElement<R>>,
{
@ -637,6 +656,61 @@ impl CosmicStack {
})
}
pub fn shadow_render_element<R, C>(
&self,
renderer: &mut R,
location: Point<i32, Physical>,
scale: Scale<f64>,
alpha: f32,
) -> Option<C>
where
R: Renderer + AsGlowRenderer + ImportAll + ImportMem,
R::TextureId: Send + Clone + 'static,
C: From<CosmicStackRenderElement<R>>,
{
self.0.with_program(|p| {
let windows = p.windows.lock().unwrap();
let active = p.active.load(Ordering::SeqCst);
let activated = p.activated.load(Ordering::Acquire);
let theme = p.theme.lock().unwrap();
let appearance = p.appearance_conf.lock().unwrap();
let tiled = p.tiled.load(Ordering::Acquire);
let round = appearance.clip_tiled_windows || !tiled;
if tiled && !appearance.shadow_tiled_windows {
return None;
}
let radii = round
.then(|| {
theme
.cosmic()
.radius_s()
.map(|x| if x < 4.0 { x } else { x + 4.0 })
.map(|x| x.round() as u8)
})
.unwrap_or([0, 0, 0, 0]);
let mut geo = SpaceElement::geometry(&windows[active]).to_f64();
geo.loc += location.to_f64().to_logical(scale);
geo.size.h += TAB_HEIGHT as f64;
let window_key =
CosmicMappedKey(CosmicMappedKeyInner::Stack(Arc::downgrade(&self.0.0)));
Some(
CosmicStackRenderElement::Shadow(ShadowShader::element(
renderer,
window_key,
geo.to_i32_round().as_local(),
radii,
if activated { alpha } else { alpha * 0.75 },
scale.x,
))
.into(),
)
})
}
pub fn render_elements<R, C>(
&self,
renderer: &mut R,
@ -646,35 +720,85 @@ impl CosmicStack {
scanout_override: Option<bool>,
) -> Vec<C>
where
R: Renderer + ImportAll + ImportMem,
R: Renderer + AsGlowRenderer + ImportAll + ImportMem,
R::TextureId: Send + Clone + 'static,
C: From<CosmicStackRenderElement<R>>,
{
let offset = self
let geometry = self
.0
.with_program(|p| {
p.windows.lock().unwrap()[p.active.load(Ordering::SeqCst)]
.geometry()
.loc
})
.with_program(|p| p.windows.lock().unwrap()[p.active.load(Ordering::SeqCst)].geometry())
.to_physical_precise_round(scale);
let stack_loc = location + offset;
let stack_loc = location + geometry.loc;
let window_loc = location + Point::from((0, (TAB_HEIGHT as f64 * scale.y) as i32));
let mut elements = AsRenderElements::<R>::render_elements::<CosmicStackRenderElement<R>>(
&self.0, renderer, stack_loc, scale, alpha,
);
if !self.0.with_program(|p| p.tiled.load(Ordering::Acquire)) {}
elements.extend(self.0.with_program(|p| {
let windows = p.windows.lock().unwrap();
let active = p.active.load(Ordering::SeqCst);
let theme = p.theme.lock().unwrap();
let appearance = p.appearance_conf.lock().unwrap();
let tiled = p.tiled.load(Ordering::Acquire);
windows[active].render_elements::<R, CosmicStackRenderElement<R>>(
renderer,
window_loc,
scale,
alpha,
scanout_override,
let round = appearance.clip_tiled_windows || !tiled;
let radii = round.then(|| {
theme
.cosmic()
.radius_s()
.map(|x| if x < 4.0 { x } else { x + 4.0 })
.map(|x| x.round() as u8)
});
let mut geo = SpaceElement::geometry(&windows[active]).to_f64();
geo.loc += location.to_f64().to_logical(scale);
geo.size.h += TAB_HEIGHT as f64;
let window_key =
CosmicMappedKey(CosmicMappedKeyInner::Stack(Arc::downgrade(&self.0.0)));
let border = {
let (r, g, b, a) = theme.cosmic().bg_divider().into_components();
CosmicStackRenderElement::Border(IndicatorShader::element(
renderer,
Key::Window(Usage::Border, window_key.clone()),
geo.to_i32_round().as_local(),
1,
radii.unwrap_or([0, 0, 0, 0]),
a * alpha,
[r, g, b],
))
};
std::iter::once(border).chain(
windows[active]
.render_elements::<R, WaylandSurfaceRenderElement<R>>(
renderer,
window_loc,
scale,
alpha,
scanout_override,
)
.into_iter()
.map(move |elem| {
let radii = radii.map(|[a, _, c, _]| [a, 0, c, 0]);
if radii.is_some_and(|radii| {
ClippedSurfaceRenderElement::will_clip(&elem, scale, geo, radii)
}) {
CosmicStackRenderElement::Clipped(ClippedSurfaceRenderElement::new(
renderer,
elem,
scale,
geo,
radii.unwrap(),
))
} else {
CosmicStackRenderElement::Window(elem)
}
}),
)
}));
@ -682,6 +806,9 @@ impl CosmicStack {
}
pub(crate) fn set_theme(&self, theme: cosmic::Theme) {
self.0.with_program(|p| {
*p.theme.lock().unwrap() = theme.clone();
});
self.0.set_theme(theme);
}
@ -799,14 +926,37 @@ impl CosmicStack {
pub fn corner_radius(&self, geometry_size: Size<i32, Logical>, default_radius: u8) -> [u8; 4] {
self.0.with_program(|p| {
let active_window = &p.windows.lock().unwrap()[p.active.load(Ordering::SeqCst)];
let mut corners = active_window
.corner_radius(geometry_size)
.unwrap_or([default_radius; 4]);
let is_tiled = p.tiled.load(Ordering::Acquire);
let appearance = p.appearance_conf.lock().unwrap();
let round = appearance.clip_tiled_windows || !is_tiled;
let radii = p
.theme
.lock()
.unwrap()
.cosmic()
.radius_s()
.map(|x| if x < 4.0 { x } else { x + 4.0 })
.map(|val| val.round() as u8);
corners[1] = 8;
corners[3] = 8;
if !round {
let mut corners = active_window
.corner_radius(geometry_size)
.unwrap_or([default_radius; 4]);
corners
corners[1] = 0;
corners[3] = 0;
corners
} else {
let mut corners = active_window.corner_radius(geometry_size).unwrap_or(radii);
corners[0] = radii[0].max(corners[0]);
corners[1] = radii[1];
corners[2] = radii[2].max(corners[2]);
corners[3] = radii[3];
corners
}
})
}
}
@ -1138,10 +1288,20 @@ impl Decorations<CosmicStackInternal, Message> for DefaultDecorations {
.into(),
];
let radius = if windows[active].is_maximized(false) {
let radius = if windows[active].is_maximized(false)
|| (stack.tiled.load(Ordering::Acquire)
&& !stack.appearance_conf.lock().unwrap().clip_tiled_windows)
{
Radius::from(0.0)
} else {
Radius::from([8.0, 8.0, 0.0, 0.0])
let radii = stack
.theme
.lock()
.unwrap()
.cosmic()
.radius_s()
.map(|x| if x < 4.0 { x } else { x + 4.0 });
Radius::from([radii[0], radii[1], 0., 0.])
};
let group_focused = stack.group_focused.load(Ordering::SeqCst);
@ -1667,8 +1827,193 @@ impl TouchTarget<State> for CosmicStack {
}
}
render_elements! {
pub CosmicStackRenderElement<R> where R: ImportAll + ImportMem;
Header = MemoryRenderBufferRenderElement<R>,
Window = WaylandSurfaceRenderElement<R>,
pub enum CosmicStackRenderElement<R: Renderer + ImportAll + ImportMem> {
Header(MemoryRenderBufferRenderElement<R>),
Shadow(PixelShaderElement),
Border(PixelShaderElement),
Window(WaylandSurfaceRenderElement<R>),
Clipped(ClippedSurfaceRenderElement<R>),
}
impl<R: Renderer + ImportAll + ImportMem> From<MemoryRenderBufferRenderElement<R>>
for CosmicStackRenderElement<R>
{
fn from(value: MemoryRenderBufferRenderElement<R>) -> Self {
Self::Header(value)
}
}
impl<R: Renderer + ImportAll + ImportMem> From<WaylandSurfaceRenderElement<R>>
for CosmicStackRenderElement<R>
{
fn from(value: WaylandSurfaceRenderElement<R>) -> Self {
Self::Window(value)
}
}
impl<R: Renderer + ImportAll + ImportMem> From<ClippedSurfaceRenderElement<R>>
for CosmicStackRenderElement<R>
{
fn from(value: ClippedSurfaceRenderElement<R>) -> Self {
Self::Clipped(value)
}
}
impl<R> Element for CosmicStackRenderElement<R>
where
R: Renderer + ImportAll + ImportMem,
{
fn id(&self) -> &RendererId {
match self {
CosmicStackRenderElement::Header(elem) => elem.id(),
CosmicStackRenderElement::Shadow(elem) => elem.id(),
CosmicStackRenderElement::Border(elem) => elem.id(),
CosmicStackRenderElement::Window(elem) => elem.id(),
CosmicStackRenderElement::Clipped(elem) => elem.id(),
}
}
fn current_commit(&self) -> CommitCounter {
match self {
CosmicStackRenderElement::Header(elem) => elem.current_commit(),
CosmicStackRenderElement::Shadow(elem) => elem.current_commit(),
CosmicStackRenderElement::Border(elem) => elem.current_commit(),
CosmicStackRenderElement::Window(elem) => elem.current_commit(),
CosmicStackRenderElement::Clipped(elem) => elem.current_commit(),
}
}
fn src(&self) -> Rectangle<f64, Buffer> {
match self {
CosmicStackRenderElement::Header(elem) => elem.src(),
CosmicStackRenderElement::Shadow(elem) => elem.src(),
CosmicStackRenderElement::Border(elem) => elem.src(),
CosmicStackRenderElement::Window(elem) => elem.src(),
CosmicStackRenderElement::Clipped(elem) => elem.src(),
}
}
fn geometry(&self, scale: Scale<f64>) -> Rectangle<i32, Physical> {
match self {
CosmicStackRenderElement::Header(elem) => elem.geometry(scale),
CosmicStackRenderElement::Shadow(elem) => elem.geometry(scale),
CosmicStackRenderElement::Border(elem) => elem.geometry(scale),
CosmicStackRenderElement::Window(elem) => elem.geometry(scale),
CosmicStackRenderElement::Clipped(elem) => elem.geometry(scale),
}
}
fn location(&self, scale: Scale<f64>) -> Point<i32, Physical> {
match self {
CosmicStackRenderElement::Header(elem) => elem.location(scale),
CosmicStackRenderElement::Shadow(elem) => elem.location(scale),
CosmicStackRenderElement::Border(elem) => elem.location(scale),
CosmicStackRenderElement::Window(elem) => elem.location(scale),
CosmicStackRenderElement::Clipped(elem) => elem.location(scale),
}
}
fn transform(&self) -> Transform {
match self {
CosmicStackRenderElement::Header(elem) => elem.transform(),
CosmicStackRenderElement::Shadow(elem) => elem.transform(),
CosmicStackRenderElement::Border(elem) => elem.transform(),
CosmicStackRenderElement::Window(elem) => elem.transform(),
CosmicStackRenderElement::Clipped(elem) => elem.transform(),
}
}
fn damage_since(
&self,
scale: Scale<f64>,
commit: Option<CommitCounter>,
) -> DamageSet<i32, Physical> {
match self {
CosmicStackRenderElement::Header(elem) => elem.damage_since(scale, commit),
CosmicStackRenderElement::Shadow(elem) => elem.damage_since(scale, commit),
CosmicStackRenderElement::Border(elem) => elem.damage_since(scale, commit),
CosmicStackRenderElement::Window(elem) => elem.damage_since(scale, commit),
CosmicStackRenderElement::Clipped(elem) => elem.damage_since(scale, commit),
}
}
fn opaque_regions(&self, scale: Scale<f64>) -> OpaqueRegions<i32, Physical> {
match self {
CosmicStackRenderElement::Header(elem) => elem.opaque_regions(scale),
CosmicStackRenderElement::Shadow(elem) => elem.opaque_regions(scale),
CosmicStackRenderElement::Border(elem) => elem.opaque_regions(scale),
CosmicStackRenderElement::Window(elem) => elem.opaque_regions(scale),
CosmicStackRenderElement::Clipped(elem) => elem.opaque_regions(scale),
}
}
fn alpha(&self) -> f32 {
match self {
CosmicStackRenderElement::Header(elem) => elem.alpha(),
CosmicStackRenderElement::Shadow(elem) => elem.alpha(),
CosmicStackRenderElement::Border(elem) => elem.alpha(),
CosmicStackRenderElement::Window(elem) => elem.alpha(),
CosmicStackRenderElement::Clipped(elem) => elem.alpha(),
}
}
fn kind(&self) -> Kind {
match self {
CosmicStackRenderElement::Header(elem) => elem.kind(),
CosmicStackRenderElement::Shadow(elem) => elem.kind(),
CosmicStackRenderElement::Border(elem) => elem.kind(),
CosmicStackRenderElement::Window(elem) => elem.kind(),
CosmicStackRenderElement::Clipped(elem) => elem.kind(),
}
}
}
impl<R> RenderElement<R> for CosmicStackRenderElement<R>
where
R: Renderer + AsGlowRenderer + ImportAll + ImportMem,
R::TextureId: 'static,
R::Error: FromGlesError,
{
fn draw(
&self,
frame: &mut <R>::Frame<'_, '_>,
src: Rectangle<f64, Buffer>,
dst: Rectangle<i32, Physical>,
damage: &[Rectangle<i32, Physical>],
opaque_regions: &[Rectangle<i32, Physical>],
) -> Result<(), <R>::Error> {
match self {
CosmicStackRenderElement::Header(elem) => {
elem.draw(frame, src, dst, damage, opaque_regions)
}
CosmicStackRenderElement::Shadow(elem) | CosmicStackRenderElement::Border(elem) => {
RenderElement::<GlowRenderer>::draw(
elem,
R::glow_frame_mut(frame),
src,
dst,
damage,
opaque_regions,
)
.map_err(FromGlesError::from_gles_error)
}
CosmicStackRenderElement::Window(elem) => {
elem.draw(frame, src, dst, damage, opaque_regions)
}
CosmicStackRenderElement::Clipped(elem) => {
elem.draw(frame, src, dst, damage, opaque_regions)
}
}
}
fn underlying_storage(&self, renderer: &mut R) -> Option<UnderlyingStorage<'_>> {
match self {
CosmicStackRenderElement::Header(elem) => elem.underlying_storage(renderer),
CosmicStackRenderElement::Shadow(elem) | CosmicStackRenderElement::Border(elem) => {
elem.underlying_storage(renderer.glow_renderer_mut())
}
CosmicStackRenderElement::Window(elem) => elem.underlying_storage(renderer),
CosmicStackRenderElement::Clipped(elem) => elem.underlying_storage(renderer),
}
}
}