fix: cleanup rectangles on workspace swap

This commit is contained in:
Ashley Wulber 2026-07-08 08:56:54 -04:00 • committed by Jacob Kauffmann
parent 127b966e3e
commit fa4d8d68a4
2 changed files with 118 additions and 32 deletions

View file

@ -184,6 +184,7 @@ struct Conf {
pub struct RectId { pub struct RectId {
id: window::Id, id: window::Id,
toplevel_id: Option<ObjectId>, toplevel_id: Option<ObjectId>,
workspaces_id: Option<Vec<ObjectId>>,
widget_id: Option<widget::Id>, widget_id: Option<widget::Id>,
} }
@ -444,6 +445,55 @@ impl App {
} }
regions regions
} }
fn update_active_workspace(
&mut self,
workspace_handle: ExtWorkspaceHandleV1,
) -> Option<Task<cosmic::Action<Msg>>> {
if let Some((cur_window, _)) = self.layer_surfaces.iter().find(|(_, layer_surface)| {
self.workspaces
.for_output(&layer_surface.output)
.any(|w| *w.handle() == workspace_handle)
}) {
self.rects.retain(|id, r| {
id.toplevel_id == None
|| (id.id == *cur_window
&& id
.workspaces_id
.as_ref()
.is_some_and(|l| l.iter().any(|o| *o == workspace_handle.id())))
});
let active = cosmic::theme::active();
let rad = active
.cosmic()
.radius_s()
.map(|x| if x < 4.0 { x } else { x + 8.0 });
let strips: Vec<Rectangle> = self
.rects
.iter()
.map(|(id, rect)| {
let rad = CornerRadius {
top_left: rad[0] as u32,
top_right: rad[1] as u32,
bottom_left: rad[3] as u32,
bottom_right: rad[2] as u32,
};
cosmic::surface::corner_radius::rounded_rect_strips(*rect, rad)
})
.flatten()
.collect();
return Some(
cosmic::iced::platform_specific::shell::commands::blur::blur(
*cur_window,
Some(strips),
)
.discard(),
);
}
None
}
} }
impl Application for App { impl Application for App {
@ -470,18 +520,20 @@ impl Application for App {
match message { match message {
Msg::Rectangle(u) => match u { Msg::Rectangle(u) => match u {
RectangleUpdate::Rectangle(r) => { RectangleUpdate::Rectangle(r) => {
// TODO should we reuse previously tracked rectangles on surface creation
self.rects.insert(r.0.clone(), r.1); self.rects.insert(r.0.clone(), r.1);
let active = cosmic::theme::active(); let active = cosmic::theme::active();
let rad = active let rad = active
.cosmic() .cosmic()
.radius_s() .radius_s()
.map(|x| if x < 4.0 { x } else { x + 8.0 }); .map(|x| if x < 4.0 { x } else { x + 8.0 });
let strips = self let mut rects = Vec::new();
let mut strips: Vec<Rectangle> = self
.rects .rects
.iter() .iter()
.filter(|(id, _)| r.0.id == id.id) .filter_map(|(id, rect)| {
.map(|(id, r)| { if r.0.id != id.id {
return None;
}
let rad = if id.toplevel_id.is_none() { let rad = if id.toplevel_id.is_none() {
CornerRadius { CornerRadius {
top_left: rad[0] as u32, top_left: rad[0] as u32,
@ -490,16 +542,21 @@ impl Application for App {
bottom_right: rad[2] as u32, bottom_right: rad[2] as u32,
} }
} else { } else {
CornerRadius::default() rects.push(*rect);
return None;
}; };
cosmic::surface::corner_radius::rounded_rect_strips(*r, rad) Some(cosmic::surface::corner_radius::rounded_rect_strips(
*rect, rad,
))
}) })
.flatten(); .flatten()
.collect();
strips.append(&mut rects);
return cosmic::iced::platform_specific::shell::commands::blur::blur( return cosmic::iced::platform_specific::shell::commands::blur::blur(
r.0.id, r.0.id,
Some(strips.collect()), Some(strips),
) )
.discard(); .discard();
} }
@ -576,6 +633,12 @@ impl Application for App {
backend::Event::Workspaces(mut workspaces) => { backend::Event::Workspaces(mut workspaces) => {
workspaces.sort_by(|(_, w1), (_, w2)| w1.coordinates.cmp(&w2.coordinates)); workspaces.sort_by(|(_, w1), (_, w2)| w1.coordinates.cmp(&w2.coordinates));
let old_workspaces = mem::take(&mut self.workspaces); let old_workspaces = mem::take(&mut self.workspaces);
let old_active: HashSet<_> = old_workspaces
.0
.iter()
.filter_map(|w| w.is_active().then(|| w.handle().clone()))
.collect();
let mut new_active = Vec::new();
for (outputs, workspace) in workspaces { for (outputs, workspace) in workspaces {
// XXX efficiency // XXX efficiency
let old_workspace = old_workspaces.for_handle(&workspace.handle); let old_workspace = old_workspaces.for_handle(&workspace.handle);
@ -584,15 +647,26 @@ impl Application for App {
let dnd_source_id = old_workspace let dnd_source_id = old_workspace
.map_or_else(iced::id::Id::unique, |w| w.dnd_source_id.clone()); .map_or_else(iced::id::Id::unique, |w| w.dnd_source_id.clone());
self.workspaces.0.push(Workspace { let w = Workspace {
info: workspace, info: workspace,
outputs, outputs,
img, img,
has_cursor, has_cursor,
dnd_source_id, dnd_source_id,
}); };
if w.is_active() {
new_active.push(w.handle().clone());
}
self.workspaces.0.push(w);
} }
self.update_capture_filter(); self.update_capture_filter();
return Task::batch(new_active.into_iter().map(|new| {
if old_active.contains(&new) {
Task::none()
} else {
self.update_active_workspace(new).unwrap_or(Task::none())
}
}));
} }
backend::Event::NewToplevel(handle, info) => { backend::Event::NewToplevel(handle, info) => {
log::debug!("New toplevel: {info:?}"); log::debug!("New toplevel: {info:?}");
@ -668,7 +742,11 @@ impl Application for App {
{ {
return self.hide(); return self.hide();
} }
self.send_wayland_cmd(backend::Cmd::ActivateWorkspace(workspace_handle));
self.send_wayland_cmd(backend::Cmd::ActivateWorkspace(workspace_handle.clone()));
if let Some(value) = self.update_active_workspace(workspace_handle) {
return value;
}
} }
Msg::ActivateToplevel(toplevel_handle) => { Msg::ActivateToplevel(toplevel_handle) => {
self.send_wayland_cmd(backend::Cmd::ActivateToplevel(toplevel_handle)); self.send_wayland_cmd(backend::Cmd::ActivateToplevel(toplevel_handle));

View file

@ -497,6 +497,7 @@ fn workspaces_sidebar<'a>(
id: window_id, id: window_id,
toplevel_id: None, toplevel_id: None,
widget_id: None, widget_id: None,
workspaces_id: None,
}, },
widget::container(sidebar_entries_container) widget::container(sidebar_entries_container)
.width(width) .width(width)
@ -527,7 +528,12 @@ fn workspaces_sidebar<'a>(
.into() .into()
} }
fn toplevel_preview(toplevel: &Toplevel, is_being_dragged: bool) -> cosmic::Element<'static, Msg> { fn toplevel_preview(
toplevel: &Toplevel,
is_being_dragged: bool,
window_id: window::Id,
rectangle_track: &rectangle_tracker::RectangleTracker<RectId>,
) -> cosmic::Element<'static, Msg> {
let cosmic::cosmic_theme::Spacing { let cosmic::cosmic_theme::Spacing {
space_xxs, space_s, .. space_xxs, space_s, ..
} = cosmic::theme::active().cosmic().spacing; } = cosmic::theme::active().cosmic().spacing;
@ -575,15 +581,23 @@ fn toplevel_preview(toplevel: &Toplevel, is_being_dragged: bool) -> cosmic::Elem
.align_y(Alignment::Center); .align_y(Alignment::Center);
let alpha = if is_being_dragged { 0.5 } else { 1.0 }; let alpha = if is_being_dragged { 0.5 } else { 1.0 };
let preview = widget::button::custom(capture_image(toplevel.img.as_ref(), alpha)) let preview = widget::button::custom(rectangle_track.container(
.selected( RectId {
toplevel id: window_id,
.info toplevel_id: Some(toplevel.handle.id()),
.state widget_id: None,
.contains(&zcosmic_toplevel_handle_v1::State::Activated), workspaces_id: Some(toplevel.info.workspace.iter().map(|h| h.id()).collect()),
) },
.class(cosmic::theme::Button::Image) capture_image(toplevel.img.as_ref(), alpha),
.on_press(Msg::ActivateToplevel(toplevel.handle.clone())); ))
.selected(
toplevel
.info
.state
.contains(&zcosmic_toplevel_handle_v1::State::Activated),
)
.class(cosmic::theme::Button::Image)
.on_press(Msg::ActivateToplevel(toplevel.handle.clone()));
widget::mouse_area(crate::widgets::size_cross_nth( widget::mouse_area(crate::widgets::size_cross_nth(
vec![title.into(), preview.into()], vec![title.into(), preview.into()],
@ -602,24 +616,18 @@ fn toplevel_previews_entry<'a>(
) -> cosmic::Element<'a, Msg> { ) -> cosmic::Element<'a, Msg> {
// Dragged window still takes up space until moved, but isn't rendered while drag surface is // Dragged window still takes up space until moved, but isn't rendered while drag surface is
// shown. // shown.
let preview = rectangle_track.container( let preview = crate::widgets::visibility_wrapper(
RectId { toplevel_preview(toplevel, is_being_dragged, window_id, rectangle_track),
id: window_id, !is_being_dragged,
toplevel_id: Some(toplevel.handle.id()),
widget_id: None,
},
crate::widgets::visibility_wrapper(
toplevel_preview(toplevel, is_being_dragged),
!is_being_dragged,
),
); );
let toplevel2 = toplevel.clone(); let toplevel2 = toplevel.clone();
let track = rectangle_track.clone();
dnd_source_with_drag_surface( dnd_source_with_drag_surface(
DragToplevel {}, DragToplevel {},
DragSurface::Toplevel(toplevel.handle.clone()), DragSurface::Toplevel(toplevel.handle.clone()),
None, None,
preview.into(), preview.into(),
move || toplevel_preview(&toplevel2, true), move || toplevel_preview(&toplevel2, true, window_id, &track),
) )
} }