Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 12 additions & 25 deletions cosmic-panel-bin/src/space/layout.rs
Original file line number Diff line number Diff line change
Expand Up @@ -921,16 +921,10 @@ impl PanelSpace {
bail!("output missing");
};
let mut loc = match self.config.anchor {
PanelAnchor::Left => [
self.config.margin as f32 + self.anchor_gap as f32,
container_lengthwise_pos as f32,
],
PanelAnchor::Right => [-self.anchor_gap as f32, container_lengthwise_pos as f32],
PanelAnchor::Bottom => [container_lengthwise_pos as f32, -self.anchor_gap as f32],
PanelAnchor::Top => [
container_lengthwise_pos as f32,
self.config.margin as f32 + self.anchor_gap as f32,
],
PanelAnchor::Left => [self.config.margin as f32, container_lengthwise_pos as f32],
PanelAnchor::Right => [0., container_lengthwise_pos as f32],
PanelAnchor::Bottom => [container_lengthwise_pos as f32, 0.],
PanelAnchor::Top => [container_lengthwise_pos as f32, self.config.margin as f32],
};

let border_radius = self.border_radius().min(w as u32).min(h as u32) as f32 / 2.;
Expand Down Expand Up @@ -985,7 +979,6 @@ impl PanelSpace {
self.is_background_dirty = false;
}
input_region.subtract(0, 0, i32::MAX, i32::MAX);
let anim_gap = self.anchor_gap;

// disable input regions for hidden stacked panels
if !matches!(self.visibility, Visibility::Hidden) || self.additional_gap == 0 {
Expand All @@ -1000,24 +993,18 @@ impl PanelSpace {
let (mut loc, mut size) = match self.config.anchor {
PanelAnchor::Left => (
(-1, side as i32),
(
new_logical_crosswise_dim + self.gap() as i32 + 1 + anim_gap,
container_length,
),
(new_logical_crosswise_dim + self.gap() as i32 + 1, container_length),
),
PanelAnchor::Right => (
(-anim_gap, side as i32),
(0, side as i32),
(new_logical_crosswise_dim + self.gap() as i32 + 1, container_length),
),
PanelAnchor::Top => (
(side as i32, -1),
(
container_length,
new_logical_crosswise_dim + self.gap() as i32 + 1 + anim_gap,
),
(container_length, new_logical_crosswise_dim + self.gap() as i32 + 1),
),
PanelAnchor::Bottom => (
(side as i32, 0 - anim_gap),
(side as i32, 0),
(container_length, new_logical_crosswise_dim + self.gap() as i32 + 1),
),
};
Expand All @@ -1041,10 +1028,10 @@ impl PanelSpace {
input_region.add(loc.0, loc.1, size.0, size.1);
} else {
let (mut loc, mut size) = match self.config.anchor {
PanelAnchor::Left => ((-1, 0), (new_dim.w + 1 + anim_gap, new_dim.h)),
PanelAnchor::Right => ((-anim_gap, 0), (new_dim.w + 1 + anim_gap, new_dim.h)),
PanelAnchor::Top => ((0, -1), (new_dim.w, new_dim.h + 1 + anim_gap)),
PanelAnchor::Bottom => ((0, -anim_gap), (new_dim.w, new_dim.h + 1 + anim_gap)),
PanelAnchor::Left => ((-1, 0), (new_dim.w + 1, new_dim.h)),
PanelAnchor::Right => ((0, 0), (new_dim.w + 1, new_dim.h)),
PanelAnchor::Top => ((0, -1), (new_dim.w, new_dim.h + 1)),
PanelAnchor::Bottom => ((0, 0), (new_dim.w, new_dim.h + 1)),
};

if is_overlapping_start {
Expand Down
108 changes: 77 additions & 31 deletions cosmic-panel-bin/src/space/panel_space.rs
Original file line number Diff line number Diff line change
Expand Up @@ -858,22 +858,17 @@ impl PanelSpace {
if self.show_delay_done(intellihide_no_toplevel) {
self.transitioning = true;
// start transition to visible
let margin = match self.config.anchor() {
let hidden_offset = match self.config.anchor() {
PanelAnchor::Left | PanelAnchor::Right => -(self.dimensions.w),
PanelAnchor::Top | PanelAnchor::Bottom => -(self.dimensions.h),
} + self.config.get_hide_handle().unwrap() as i32;
self.is_dirty = true;
self.visibility = Visibility::TransitionToVisible {
last_instant: Instant::now(),
progress: Duration::new(0, 0),
prev_margin: margin,
prev_margin: hidden_offset,
};
Self::set_margin(
self.config.anchor,
self.config.get_margin() as i32,
self.additional_gap,
layer_surface,
);
// Don't reset margin here - let the animation handle it frame by frame
} else {
self.start_show_delay();
}
Expand All @@ -894,6 +889,12 @@ impl PanelSpace {
{
self.transitioning = true;
self.is_dirty = true;
// Set exclusive zone to handle size at START of hide animation
// to avoid per-frame relayout in the compositor
if self.config.exclusive_zone() {
let handle = self.config.get_hide_handle().unwrap() as i32;
layer_surface.set_exclusive_zone(handle);
}
self.visibility = Visibility::TransitionToHidden {
last_instant: Instant::now(),
progress: Duration::new(0, 0),
Expand Down Expand Up @@ -941,19 +942,28 @@ impl PanelSpace {
let cur_pix = (progress_norm * target as f32) as i32;

if progress >= total_t {
if self.config.exclusive_zone() {
layer_surface.set_exclusive_zone(panel_size);
}

self.anchor_gap = target;
// Exclusive zone was already set to handle size at animation start
Self::set_margin_with_offset(
self.config.anchor,
self.config.get_margin() as i32,
self.additional_gap,
target,
layer_surface,
true, // commit immediately at end of animation
);
self.visibility = Visibility::Hidden;
} else {
if prev_margin != cur_pix {
if self.config.exclusive_zone() {
layer_surface.set_exclusive_zone(panel_size - cur_pix);
}

self.anchor_gap = cur_pix;
// Only update margin during animation, not exclusive zone
// Exclusive zone was set at animation start to avoid per-frame relayout
Self::set_margin_with_offset(
self.config.anchor,
self.config.get_margin() as i32,
self.additional_gap,
cur_pix,
layer_surface,
true, // commit immediately during animation for smooth frames
);
}
self.close_popups(|_| false);
self.visibility = Visibility::TransitionToHidden {
Expand Down Expand Up @@ -1004,11 +1014,11 @@ impl PanelSpace {
let cur_pix = ((1.0 - progress_norm) * start as f32) as i32;

if progress >= total_t {
// Set exclusive zone to full panel size at END of show animation
if self.config.exclusive_zone() {
layer_surface.set_exclusive_zone(panel_size);
}

self.anchor_gap = 0;
self.visibility = Visibility::Visible;
Self::set_margin(
self.config.anchor,
Expand All @@ -1018,11 +1028,16 @@ impl PanelSpace {
);
} else {
if prev_margin != cur_pix {
if self.config.exclusive_zone() {
layer_surface.set_exclusive_zone(panel_size - cur_pix);
}

self.anchor_gap = cur_pix;
// Only update margin during animation, not exclusive zone
// Exclusive zone will be set at animation end to avoid per-frame relayout
Self::set_margin_with_offset(
self.config.anchor,
self.config.get_margin() as i32,
self.additional_gap,
cur_pix,
layer_surface,
true, // commit immediately during animation for smooth frames
);
}
self.visibility = Visibility::TransitionToVisible {
last_instant: now,
Expand Down Expand Up @@ -1091,13 +1106,42 @@ impl PanelSpace {
margin: i32,
additional_gap: i32,
layer_surface: &LayerSurface,
) {
Self::set_margin_with_offset(anchor, margin, additional_gap, 0, layer_surface, false);
}

/// Set margin with an additional offset on the anchor edge for animation.
/// `anchor_offset` is negative to push the panel off-screen (hiding),
/// and zero when fully visible.
///
/// If `commit_immediately` is true, commits the surface right away without
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this really necessary? I don't think we want to render without waiting for frame callbacks.

/// waiting for the next frame callback. This is used during animations to
/// reduce latency.
fn set_margin_with_offset(
anchor: PanelAnchor,
margin: i32,
additional_gap: i32,
anchor_offset: i32,
layer_surface: &LayerSurface,
commit_immediately: bool,
) {
match anchor {
PanelAnchor::Left => layer_surface.set_margin(margin, 0, margin, additional_gap),
PanelAnchor::Right => layer_surface.set_margin(margin, additional_gap, margin, 0),
PanelAnchor::Top => layer_surface.set_margin(additional_gap, margin, 0, margin),
PanelAnchor::Bottom => layer_surface.set_margin(0, margin, additional_gap, margin),
PanelAnchor::Left => {
layer_surface.set_margin(margin, 0, margin, additional_gap + anchor_offset)
},
PanelAnchor::Right => {
layer_surface.set_margin(margin, additional_gap + anchor_offset, margin, 0)
},
PanelAnchor::Top => {
layer_surface.set_margin(additional_gap + anchor_offset, margin, 0, margin)
},
PanelAnchor::Bottom => {
layer_surface.set_margin(0, margin, additional_gap + anchor_offset, margin)
},
};
if commit_immediately {
layer_surface.wl_surface().commit();
}
}

pub fn constrain_dim(
Expand Down Expand Up @@ -1268,14 +1312,16 @@ impl PanelSpace {
if self.config.exclusive_zone() {
layer_surface.set_exclusive_zone(list_thickness as i32);
}
Self::set_margin(
let hidden_offset = -(list_thickness as i32)
+ self.config.get_hide_handle().unwrap_or_default() as i32;
Self::set_margin_with_offset(
self.config.anchor,
self.config.get_margin() as i32,
self.additional_gap,
hidden_offset,
layer_surface,
false, // don't commit here, we commit below
);
self.anchor_gap = -(list_thickness as i32)
+ self.config.get_hide_handle().unwrap_or_default() as i32;
}
layer_surface.wl_surface().commit();
layer_surface.wl_surface().frame(qh, layer_surface.wl_surface().clone());
Expand Down
12 changes: 1 addition & 11 deletions cosmic-panel-bin/src/space/render.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ use cctk::wayland_client::{Proxy, QueueHandle};
use itertools::Itertools;

use crate::xdg_shell_wrapper::shared_state::GlobalState;
use cosmic_panel_config::PanelAnchor;
use sctk::shell::WaylandSurface;
use smithay::{
backend::renderer::{
Expand Down Expand Up @@ -161,14 +160,6 @@ impl PanelSpace {
return Ok(());
}

let anim_gap_physical = (self.anchor_gap as f64) * self.scale;
let anim_gap_translation = Point::from(match self.config.anchor {
PanelAnchor::Left => (anim_gap_physical, 0.),
PanelAnchor::Right => (-anim_gap_physical, 0.),
PanelAnchor::Top => (0., anim_gap_physical),
PanelAnchor::Bottom => (0., -anim_gap_physical),
})
.to_i32_round();
if let Some((o, _info)) = &self.output.as_ref().map(|(_, o, info)| (o, info)) {
let mut elements = self
.space
Expand All @@ -180,8 +171,7 @@ impl PanelSpace {
.unwrap_or_default()
.to_f64()
.to_physical(self.scale)
.to_i32_round()
+ anim_gap_translation;
.to_i32_round();

if let CosmicMappedInternal::OverflowButton(b) = w {
return Some(
Expand Down
44 changes: 32 additions & 12 deletions cosmic-panel-bin/src/xdg_shell_wrapper/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -100,14 +100,20 @@ pub fn run(
loop {
let iter_start = Instant::now();

let visibility = matches!(global_state.space.visibility(), Visibility::Hidden);
let visibility = global_state.space.visibility();
let is_hidden = matches!(visibility, Visibility::Hidden);
let is_animating = matches!(
visibility,
Visibility::TransitionToHidden { .. } | Visibility::TransitionToVisible { .. }
);
// dispatch desktop client events
let dur = if matches!(global_state.space.visibility(), Visibility::Hidden) {
Duration::from_millis(300)
// Use fast 16ms polling during animations for smooth frames
let dur = if is_hidden && !is_animating {
Duration::from_millis(300).max(prev_dur)
} else {
// During animation or when visible, always use 16ms for smooth 60fps
Duration::from_millis(16)
}
.max(prev_dur);
};

event_loop.dispatch(dur, &mut global_state)?;

Expand Down Expand Up @@ -139,23 +145,37 @@ pub fn run(
}
global_state.iter_count += 1;

let new_visibility_hidden = matches!(global_state.space.visibility(), Visibility::Hidden);
let new_visibility = global_state.space.visibility();
let new_is_hidden = matches!(new_visibility, Visibility::Hidden);
let new_is_animating = matches!(
new_visibility,
Visibility::TransitionToHidden { .. } | Visibility::TransitionToVisible { .. }
);

if visibility != new_visibility_hidden {
// Reset timing when visibility state changes or animation starts/stops
if is_hidden != new_is_hidden || is_animating != new_is_animating {
prev_dur = Duration::from_millis(16);
continue;
}
if let Some(dur) = Instant::now()
.checked_duration_since(iter_start)
.and_then(|spent| dur.checked_sub(spent))
{
std::thread::sleep(dur.min(Duration::from_millis(if new_visibility_hidden {
50
// During animation, don't sleep - process frames as fast as possible
// When hidden (not animating), can sleep longer to save power
let max_sleep = if new_is_animating {
Duration::from_millis(1) // Minimal sleep during animation
} else if new_is_hidden {
Duration::from_millis(50)
} else {
16
})));
Duration::from_millis(16)
};
std::thread::sleep(dur.min(max_sleep));
} else {
prev_dur = prev_dur.checked_mul(2).unwrap_or(prev_dur).min(Duration::from_millis(100));
// Only increase prev_dur when not animating
if !new_is_animating {
prev_dur = prev_dur.checked_mul(2).unwrap_or(prev_dur).min(Duration::from_millis(100));
}
}
}
}