Unify window presentation and frame-driven shell motion
Share window geometry across overview, resize snapshots and retained closes. Advance close handoffs and group retained rendering by stacking position. Drive shell motion from surface frames, reuse settled widget content, and synchronize notification materials with each card's fade. Replace physical config keys with duration and bounce, and update documentation and regression coverage.
This commit is contained in:
1 parent
f8bc17e913
commit
64a80dc069
33 files changed
+1742
-600
No files matched your search
@@ -1,21 +1,19 @@
|
||||
use super::*;
|
||||
use ferese_animation::{AnimatedRect, AnimatedValue, CrossingPolicy, SpringConfig};
|
||||
use ferese_animation::{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 presentation: crate::presentation::WindowPresentation,
|
||||
pub output: OutputId,
|
||||
pub below: Option<WindowId>,
|
||||
pub bounds: AnimatedRect,
|
||||
pub opacity: AnimatedValue,
|
||||
pub snapshot: ResizeSnapshot,
|
||||
pub source: Rectangle<f64, Buffer>,
|
||||
// Keep a resize handoff's visible content when the client unmaps mid-resize.
|
||||
pub handoff: Option<(ResizeSnapshot, Option<ferese_animation::ClientSize>)>,
|
||||
pub radius: f64,
|
||||
pub shape: CornerShape,
|
||||
pub focus: f64,
|
||||
pub decorations: f64,
|
||||
pub dim: f64,
|
||||
pub fill: Option<[f32; 4]>,
|
||||
@@ -28,9 +26,22 @@ pub(crate) struct ClosedWindow {
|
||||
}
|
||||
|
||||
impl ClosedWindow {
|
||||
pub fn bytes(&self) -> usize {
|
||||
self.snapshot.bytes() + self.handoff.as_ref().map_or(0, |(snapshot, _)| snapshot.bytes())
|
||||
}
|
||||
|
||||
pub fn advance(&mut self, delta: Duration, spring: SpringConfig) -> bool {
|
||||
self.bounds.advance(delta, spring);
|
||||
let opacity = self.opacity.advance_with_policy(
|
||||
// The client is gone, so this handoff no longer waits for configure
|
||||
// acknowledgements. Delta already includes the global motion speed.
|
||||
if let Some((snapshot, _)) = &mut self.handoff {
|
||||
snapshot.elapsed = snapshot.elapsed.saturating_add(delta);
|
||||
snapshot.commit.increment();
|
||||
if snapshot.elapsed >= crate::presentation::HANDOFF {
|
||||
self.handoff = None;
|
||||
}
|
||||
}
|
||||
self.presentation.bounds.advance(delta, spring);
|
||||
let opacity = self.presentation.opacity.advance_with_policy(
|
||||
delta,
|
||||
SpringConfig {
|
||||
position_tolerance: 0.001,
|
||||
@@ -39,58 +50,64 @@ impl ClosedWindow {
|
||||
},
|
||||
CrossingPolicy::NoCrossing,
|
||||
);
|
||||
self.presentation
|
||||
.emphasis
|
||||
.advance(delta, crate::presentation::emphasis_spring(spring));
|
||||
self.presentation
|
||||
.shadow
|
||||
.advance(delta, crate::presentation::shadow_spring(spring));
|
||||
self.snapshot.commit.increment();
|
||||
// Once invisible there is no reason to keep a texture (including with
|
||||
// intentionally undamped, overshooting geometry configurations).
|
||||
// Texture lifetime ends when the shared presentation becomes invisible.
|
||||
opacity
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn elements(
|
||||
pub(super) fn grouped_elements(
|
||||
state: &mut Ferese,
|
||||
renderer: &mut GlesRenderer,
|
||||
output: &Output,
|
||||
below: Option<WindowId>,
|
||||
live: &std::collections::HashSet<WindowId>,
|
||||
live: impl IntoIterator<Item = WindowId>,
|
||||
delta: Duration,
|
||||
) -> Vec<AnimatedWindowRenderElement> {
|
||||
if state.session_lock.active() {
|
||||
return Vec::new();
|
||||
) -> std::collections::HashMap<Option<WindowId>, Vec<AnimatedWindowRenderElement>> {
|
||||
if state.render.closing.is_empty() || state.session_lock.active() {
|
||||
return Default::default();
|
||||
}
|
||||
let Some(output_id) = state.output_id(output) else {
|
||||
return Vec::new();
|
||||
return Default::default();
|
||||
};
|
||||
let Some(output_geometry) = state.space.output_geometry(output) else {
|
||||
return Vec::new();
|
||||
return Default::default();
|
||||
};
|
||||
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()
|
||||
})
|
||||
.filter(|window| window.output == output_id && window.snapshot.context == renderer.context_id().erased())
|
||||
.cloned()
|
||||
.collect();
|
||||
let mut elements = Vec::new();
|
||||
if windows.is_empty() {
|
||||
return Default::default();
|
||||
}
|
||||
let live: std::collections::HashSet<_> = live.into_iter().collect();
|
||||
let mut groups = std::collections::HashMap::new();
|
||||
for mut window in windows {
|
||||
let anchor = window.below.filter(|id| live.contains(id));
|
||||
let elements: &mut Vec<AnimatedWindowRenderElement> = groups.entry(anchor).or_default();
|
||||
if !delta.is_zero() {
|
||||
window.advance(delta, state.spring_config);
|
||||
}
|
||||
let visual = window.bounds.current;
|
||||
let visual = window.presentation.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;
|
||||
let alpha = window.presentation.alpha();
|
||||
if let Some(dim) = window_tint_element(
|
||||
&mut state.render,
|
||||
renderer,
|
||||
window.id,
|
||||
window.presentation.id,
|
||||
constrain,
|
||||
corners,
|
||||
[0.0, 0.0, 0.0, window.dim as f32 * alpha],
|
||||
@@ -104,44 +121,53 @@ pub(super) fn elements(
|
||||
&mut state.render,
|
||||
theme,
|
||||
renderer,
|
||||
window.id,
|
||||
window.presentation.id,
|
||||
constrain,
|
||||
corners,
|
||||
scale,
|
||||
theme.border_width + (theme.focus_ring_width - theme.border_width) * window.focus,
|
||||
theme.border_width + (theme.focus_ring_width - theme.border_width) * window.presentation.focus(),
|
||||
theme.border_color.0,
|
||||
theme.border_gradient,
|
||||
window.focus as f32,
|
||||
window.presentation.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)],
|
||||
if let Some((snapshot, native_size)) = &window.handoff {
|
||||
let mut presentation = window.presentation;
|
||||
presentation.scale_content = native_size.is_none();
|
||||
presentation.native_size = *native_size;
|
||||
if let Some(element) = super::window_content::snapshot_element(
|
||||
snapshot,
|
||||
presentation,
|
||||
corners,
|
||||
scale,
|
||||
output,
|
||||
&programs,
|
||||
crate::presentation::handoff_alpha(snapshot.elapsed),
|
||||
) {
|
||||
elements.push(element.into());
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
if let Some(element) = super::window_content::snapshot_element(
|
||||
&window.snapshot,
|
||||
window.presentation,
|
||||
corners,
|
||||
scale,
|
||||
output,
|
||||
&programs,
|
||||
1.0,
|
||||
) {
|
||||
elements.push(element.into());
|
||||
}
|
||||
if let Some(mut fill) = window.fill {
|
||||
fill[3] = alpha;
|
||||
if let Some(fill) = window_tint_element(
|
||||
&mut state.render,
|
||||
renderer,
|
||||
window.id,
|
||||
window.presentation.id,
|
||||
constrain,
|
||||
corners,
|
||||
fill,
|
||||
@@ -151,8 +177,8 @@ pub(super) fn elements(
|
||||
elements.push(fill.into());
|
||||
}
|
||||
}
|
||||
if let Some((surface, role, generation, opacity)) = &window.material {
|
||||
if let Some((material, _)) = material_element_with_role(
|
||||
if let Some((surface, role, generation, opacity)) = &window.material
|
||||
&& let Some((material, _)) = material_element_with_role(
|
||||
state,
|
||||
renderer,
|
||||
output,
|
||||
@@ -165,21 +191,22 @@ pub(super) fn elements(
|
||||
alpha,
|
||||
},
|
||||
(*role, *generation, *opacity),
|
||||
) {
|
||||
elements.push(material);
|
||||
}
|
||||
)
|
||||
{
|
||||
elements.push(material);
|
||||
}
|
||||
let theme = &state.theme_settings;
|
||||
let (offset_factor, blur_factor, opacity_factor) = window.presentation.shadow_factors();
|
||||
if let Some(shadow) = window_shadow_element(
|
||||
&mut state.render,
|
||||
renderer,
|
||||
window.id,
|
||||
window.presentation.id,
|
||||
constrain,
|
||||
corners,
|
||||
scale,
|
||||
theme.shadow_offset_y,
|
||||
theme.shadow_blur,
|
||||
theme.shadow_opacity * f64::from(alpha) * window.decorations,
|
||||
theme.shadow_offset_y * offset_factor,
|
||||
theme.shadow_blur * blur_factor,
|
||||
theme.shadow_opacity * opacity_factor * f64::from(alpha) * window.decorations,
|
||||
theme.shadow_color.0,
|
||||
output,
|
||||
&programs,
|
||||
@@ -187,5 +214,5 @@ pub(super) fn elements(
|
||||
elements.push(shadow.into());
|
||||
}
|
||||
}
|
||||
elements
|
||||
groups
|
||||
}
|
||||
Reference in new issue
Block a user