Implement Continuum compositor and shell motion

This commit is contained in:
Samuel Onoja committed 2026-10-03 03:19:11 +01:00
1 parent 259f7e93b6
commit f8bc17e913
42 files changed
+1873 -647

No files matched your search

+191
View File
@@ -0,0 +1,191 @@
use super::*;
use ferese_animation::{AnimatedRect, AnimatedValue, CrossingPolicy, SpringConfig};
use ferese_core::OutputId;
use ferese_layout::WindowId;
/// Detached presentation only: no live window, input, focus, or layout entry.
#[derive(Clone)]
pub(crate) struct ClosedWindow {
pub id: WindowId,
pub output: OutputId,
pub below: Option<WindowId>,
pub bounds: AnimatedRect,
pub opacity: AnimatedValue,
pub snapshot: ResizeSnapshot,
pub source: Rectangle<f64, Buffer>,
pub radius: f64,
pub shape: CornerShape,
pub focus: f64,
pub decorations: f64,
pub dim: f64,
pub fill: Option<[f32; 4]>,
pub material: Option<(
smithay::reexports::wayland_server::protocol::wl_surface::WlSurface,
crate::effects::SemanticRole,
u64,
f32,
)>,
}
impl ClosedWindow {
pub fn advance(&mut self, delta: Duration, spring: SpringConfig) -> bool {
self.bounds.advance(delta, spring);
let opacity = self.opacity.advance_with_policy(
delta,
SpringConfig {
position_tolerance: 0.001,
velocity_tolerance: 0.001,
..spring
},
CrossingPolicy::NoCrossing,
);
self.snapshot.commit.increment();
// Once invisible there is no reason to keep a texture (including with
// intentionally undamped, overshooting geometry configurations).
opacity
}
}
pub(super) fn elements(
state: &mut Ferese,
renderer: &mut GlesRenderer,
output: &Output,
below: Option<WindowId>,
live: &std::collections::HashSet<WindowId>,
delta: Duration,
) -> Vec<AnimatedWindowRenderElement> {
if state.session_lock.active() {
return Vec::new();
}
let Some(output_id) = state.output_id(output) else {
return Vec::new();
};
let Some(output_geometry) = state.space.output_geometry(output) else {
return Vec::new();
};
let scale = output.current_scale().fractional_scale();
let windows: Vec<_> = state
.render
.closing
.iter()
.filter(|window| {
window.output == output_id
&& window.below.filter(|id| live.contains(id)) == below
&& window.snapshot.context == renderer.context_id().erased()
})
.cloned()
.collect();
let mut elements = Vec::new();
for mut window in windows {
if !delta.is_zero() {
window.advance(delta, state.spring_config);
}
let visual = window.bounds.current;
let corners = RoundedRect::new(visual, output_geometry.loc, scale, window.radius).with_shape(window.shape);
let constrain = rounded_visual_rect(visual, output_geometry.loc);
let Some(programs) = corner_program(&mut state.render, renderer, window.shape) else {
continue;
};
let alpha = window.opacity.current.clamp(0.0, 1.0) as f32;
if let Some(dim) = window_tint_element(
&mut state.render,
renderer,
window.id,
constrain,
corners,
[0.0, 0.0, 0.0, window.dim as f32 * alpha],
false,
output,
) {
elements.push(dim.into());
}
let theme = &state.theme_settings;
if let Some(border) = window_border_element(
&mut state.render,
theme,
renderer,
window.id,
constrain,
corners,
scale,
theme.border_width + (theme.focus_ring_width - theme.border_width) * window.focus,
theme.border_color.0,
theme.border_gradient,
window.focus as f32,
alpha * window.decorations as f32,
output,
&programs,
) {
elements.push(border.into());
}
let clip = framebuffer_clip_rect(
corners.rect,
output.current_mode().unwrap().size,
output.current_transform().invert(),
);
elements.push(
NativeTextureElement {
id: window.snapshot.id.clone(),
commit: window.snapshot.commit,
texture: window.snapshot.texture.clone(),
geometry: corners.rect,
source: window.source,
alpha,
program: Some(programs.texture.clone()),
uniforms: vec![Uniform::new("clip_rect", clip), Uniform::new("radius", corners.radius)],
}
.into(),
);
if let Some(mut fill) = window.fill {
fill[3] = alpha;
if let Some(fill) = window_tint_element(
&mut state.render,
renderer,
window.id,
constrain,
corners,
fill,
true,
output,
) {
elements.push(fill.into());
}
}
if let Some((surface, role, generation, opacity)) = &window.material {
if let Some((material, _)) = material_element_with_role(
state,
renderer,
output,
surface,
MaterialSurface {
geometry: constrain,
corners,
index: 0,
capture_geometry: constrain,
alpha,
},
(*role, *generation, *opacity),
) {
elements.push(material);
}
}
let theme = &state.theme_settings;
if let Some(shadow) = window_shadow_element(
&mut state.render,
renderer,
window.id,
constrain,
corners,
scale,
theme.shadow_offset_y,
theme.shadow_blur,
theme.shadow_opacity * f64::from(alpha) * window.decorations,
theme.shadow_color.0,
output,
&programs,
) {
elements.push(shadow.into());
}
}
elements
}
+10 -13
View File
@@ -380,6 +380,7 @@ pub(super) fn rounded_window_elements(
output: &Output,
programs: RoundedClipPrograms,
behavior: ConstrainScaleBehavior,
native_size: Option<ferese_animation::ClientSize>,
) -> Vec<AnimatedWindowRenderElement> {
let Some(toplevel) = window.toplevel() else {
return Vec::new();
@@ -390,7 +391,13 @@ pub(super) fn rounded_window_elements(
let physical_constrain = corners.rect;
let geometry = window.geometry();
let reference = geometry.to_physical_precise_round(scale);
let mut reference = geometry.to_physical_precise_round(scale);
let behavior = if let Some(size) = native_size {
reference.size = Size::<i32, Logical>::from((size.width, size.height)).to_physical_precise_round(scale);
ConstrainScaleBehavior::Stretch
} else {
behavior
};
let location = physical_constrain.loc - geometry.loc.to_physical_precise_round(scale);
let clip = framebuffer_clip_rect(physical_constrain, mode.size, output.current_transform().invert());
let radius = corners.radius;
@@ -462,18 +469,8 @@ pub(super) fn resize_content_behavior(intentional_scale: bool) -> ConstrainScale
}
}
pub(super) fn scaled_visual_rect(rect: ferese_layout::Rect, scale: f64) -> ferese_layout::Rect {
let scale = scale.clamp(0.0, 1.0);
let width = rect.width * scale;
let height = rect.height * scale;
ferese_layout::Rect::new(
rect.x + (rect.width - width) / 2.0,
rect.y + (rect.height - height) / 2.0,
width,
height,
)
}
#[cfg(test)]
pub(super) use crate::presentation::scaled_visual_rect;
pub(super) fn color_with_alpha(mut color: [f32; 4], alpha: f32) -> [f32; 4] {
color[3] *= alpha;
+20 -2
View File
@@ -128,6 +128,25 @@ pub(super) fn material_element(
output: &Output,
surface: &smithay::reexports::wayland_server::protocol::wl_surface::WlSurface,
surface_geometry: MaterialSurface,
) -> Option<(AnimatedWindowRenderElement, AnimatedWindowRenderElement)> {
let (role, generation) = crate::effects::surface_role(surface)?;
material_element_with_role(
state,
renderer,
output,
surface,
surface_geometry,
(role, generation, crate::effects::surface_opacity(surface)),
)
}
pub(super) fn material_element_with_role(
state: &mut Ferese,
renderer: &mut GlesRenderer,
output: &Output,
surface: &smithay::reexports::wayland_server::protocol::wl_surface::WlSurface,
surface_geometry: MaterialSurface,
(role, generation, opacity): (crate::effects::SemanticRole, u64, f32),
) -> Option<(AnimatedWindowRenderElement, AnimatedWindowRenderElement)> {
let MaterialSurface {
geometry,
@@ -136,13 +155,12 @@ pub(super) fn material_element(
capture_geometry,
alpha,
} = surface_geometry;
let (role, generation) = crate::effects::surface_role(surface)?;
let material = crate::effects::resolve_material(
role,
state.theme_settings.material_style,
state.theme_settings.shell_opacity as f32,
);
let presentation_alpha = crate::effects::surface_opacity(surface) * alpha;
let presentation_alpha = opacity * alpha;
let mode = output.current_mode()?;
let scale = output.current_scale().fractional_scale();
let transform = output.current_transform().invert();
+4 -2
View File
@@ -1,5 +1,7 @@
mod backdrop;
mod capture;
mod closing;
pub(crate) use closing::ClosedWindow;
#[cfg(test)]
mod corner_tests;
mod decorations;
@@ -78,7 +80,7 @@ render_elements! {
Native=NativeTextureElement,
}
#[derive(Debug)]
#[derive(Clone, Debug)]
pub(crate) struct ResizeSnapshot {
pub texture: GlesTexture,
pub context: ErasedContextId,
@@ -134,7 +136,7 @@ fn corner_shader_for(source: &str, shape: CornerShape) -> String {
}
}
fn window_corner_shape(window: &smithay::desktop::Window) -> CornerShape {
pub(crate) fn window_corner_shape(window: &smithay::desktop::Window) -> CornerShape {
// Semantic shell surfaces keep their client-matching circular outlines.
corner_shape_for_role(
window
+1
View File
@@ -241,6 +241,7 @@ pub(super) fn overview_strip_elements(
output,
programs.clone(),
ConstrainScaleBehavior::Stretch,
None,
));
}
}
+21
View File
@@ -8,6 +8,7 @@ use super::*;
/// context. Desktop policy and animation values do not live in these caches.
#[derive(Default)]
pub(crate) struct RenderResources {
pub closing: Vec<ClosedWindow>,
pub(super) windows: HashMap<WindowId, WindowBuffers>,
pub(super) contexts: HashMap<ErasedContextId, ContextPrograms>,
pub(super) outputs: HashMap<OutputId, OverviewScrim>,
@@ -71,6 +72,16 @@ impl RenderResources {
}
pub fn remove_output(&mut self, id: OutputId) {
let removed: Vec<_> = self
.closing
.iter()
.filter(|window| window.output == id)
.map(|window| window.id)
.collect();
self.closing.retain(|window| window.output != id);
for id in removed {
self.remove_window(id);
}
self.outputs.remove(&id);
for window in self.windows.values_mut() {
@@ -124,6 +135,16 @@ impl RenderResources {
}
pub fn forget_context(&mut self, context: &ErasedContextId) {
let removed: Vec<_> = self
.closing
.iter()
.filter(|window| &window.snapshot.context == context)
.map(|window| window.id)
.collect();
self.closing.retain(|window| &window.snapshot.context != context);
for id in removed {
self.remove_window(id);
}
self.contexts.remove(context);
for window in self.windows.values_mut() {
+54 -9
View File
@@ -124,8 +124,8 @@ pub(crate) fn sampled_output_elements(
return None;
}
let sample = frame.windows.get(&id)?;
let visual = scaled_visual_rect(sample.rect, sample.close_scale);
let close_alpha = sample.close_alpha;
let visual = sample.rect;
let close_alpha = sample.alpha;
let decoration_progress = sample.geometry.decorations.clamp(0.0, 1.0);
Some((window.clone(), id, sample, visual, decoration_progress, close_alpha))
@@ -145,7 +145,16 @@ pub(crate) fn sampled_output_elements(
.collect::<Vec<_>>()
};
let live = windows.iter().map(|(_, id, ..)| *id).collect();
for (window, id, sample, visual, decoration_progress, close_alpha) in windows {
elements.extend(super::closing::elements(
state,
renderer,
output,
Some(id),
&live,
frame.delta,
));
let constrain = rounded_visual_rect(visual, output_geometry.loc);
let material_surface = window
.toplevel()
@@ -165,7 +174,7 @@ pub(crate) fn sampled_output_elements(
let rounded_clip_program = corner_program(&mut state.render, renderer, shape);
let pixels = corners.rect;
// Only overview/close intentionally scale the complete application.
let scale_content = frame.overview.is_presenting() || sample.close_scale != 1.0;
let scale_content = sample.scale_content;
let behavior = resize_content_behavior(scale_content);
let dim = sample.dim;
@@ -227,13 +236,33 @@ pub(crate) fn sampled_output_elements(
output,
&programs,
);
if !scale_content
&& let Some(snapshot) = state.render.snapshot(&id)
if let Some(snapshot) = state.render.snapshot(&id)
&& snapshot.context == renderer.context_id().erased()
&& (snapshot.scale - scale).abs() < 0.001
{
let size = snapshot.texture.size();
let visible = Rectangle::new(pixels.loc, (size.w, size.h).into()).intersection(pixels);
let (sx, sy) = if scale_content {
(
f64::from(pixels.size.w) / f64::from(size.w),
f64::from(pixels.size.h) / f64::from(size.h),
)
} else if let Some(native) = sample.native_size {
(
f64::from(pixels.size.w) / (f64::from(native.width) * scale).round().max(1.0),
f64::from(pixels.size.h) / (f64::from(native.height) * scale).round().max(1.0),
)
} else {
(1.0, 1.0)
};
let visible = Rectangle::new(
pixels.loc,
(
(f64::from(size.w) * sx).round() as i32,
(f64::from(size.h) * sy).round() as i32,
)
.into(),
)
.intersection(pixels);
if let Some(visible) = visible {
let clip = framebuffer_clip_rect(
pixels,
@@ -247,8 +276,8 @@ pub(crate) fn sampled_output_elements(
texture: snapshot.texture.clone(),
geometry: visible,
source: Rectangle::from_size(Size::from((
f64::from(visible.size.w),
f64::from(visible.size.h),
f64::from(visible.size.w) / sx,
f64::from(visible.size.h) / sy,
))),
alpha: crate::presentation::handoff_alpha(snapshot.elapsed) * close_alpha,
program: Some(programs.texture.clone()),
@@ -268,6 +297,7 @@ pub(crate) fn sampled_output_elements(
output,
programs.clone(),
behavior,
sample.native_size,
));
let source = window.geometry().size;
if material_surface.is_none()
@@ -322,6 +352,14 @@ pub(crate) fn sampled_output_elements(
));
}
}
elements.extend(super::closing::elements(
state,
renderer,
output,
None,
&live,
frame.delta,
));
elements.extend(layer_elements(
state,
renderer,
@@ -374,7 +412,14 @@ pub(super) fn layer_elements(
.collect::<Vec<_>>()
};
state.render.surfaces.retain(|surface, _| surface.is_alive());
state.render.surfaces.retain(|surface, _| {
surface.is_alive()
|| state
.render
.closing
.iter()
.any(|window| window.material.as_ref().is_some_and(|(key, ..)| key == surface))
});
let mut elements = Vec::new();
for (geometry, layer) in layers {