Implement Continuum compositor and shell motion
This commit is contained in:
1 parent
259f7e93b6
commit
f8bc17e913
42 files changed
+1873
-647
No files matched your search
@@ -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
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -241,6 +241,7 @@ pub(super) fn overview_strip_elements(
|
||||
output,
|
||||
programs.clone(),
|
||||
ConstrainScaleBehavior::Stretch,
|
||||
None,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in new issue
Block a user