Define Workspaces and Toplevels structs

This way methods can be called while other parts of `App` are mutably
borrowed.
This commit is contained in:
Ian Douglas Scott 2025-06-04 10:00:40 -07:00 committed by Ian Douglas Scott
parent 20b694386d
commit 6d86762152
2 changed files with 52 additions and 59 deletions

View file

@ -181,8 +181,8 @@ struct App {
capture_filter: backend::CaptureFilter, capture_filter: backend::CaptureFilter,
layer_surfaces: HashMap<SurfaceId, LayerSurface>, layer_surfaces: HashMap<SurfaceId, LayerSurface>,
outputs: Vec<Output>, outputs: Vec<Output>,
workspaces: Vec<Workspace>, workspaces: Workspaces,
toplevels: Vec<Toplevel>, toplevels: Toplevels,
toplevel_capabilities: toplevel_capabilities:
Vec<zcosmic_toplevel_manager_v1::ZcosmicToplelevelManagementCapabilitiesV1>, Vec<zcosmic_toplevel_manager_v1::ZcosmicToplelevelManagementCapabilitiesV1>,
conn: Option<Connection>, conn: Option<Connection>,
@ -195,35 +195,36 @@ struct App {
scroll: Option<(f32, Instant)>, scroll: Option<(f32, Instant)>,
} }
impl App { #[derive(Debug, Default)]
fn workspace_for_handle(&self, handle: &ExtWorkspaceHandleV1) -> Option<&Workspace> { struct Workspaces(Vec<Workspace>);
self.workspaces.iter().find(|i| i.handle() == handle)
impl Workspaces {
fn for_handle(&self, handle: &ExtWorkspaceHandleV1) -> Option<&Workspace> {
self.0.iter().find(|i| i.handle() == handle)
} }
fn workspace_for_handle_mut( fn for_handle_mut(&mut self, handle: &ExtWorkspaceHandleV1) -> Option<&mut Workspace> {
&mut self, self.0.iter_mut().find(|i| i.handle() == handle)
handle: &ExtWorkspaceHandleV1,
) -> Option<&mut Workspace> {
self.workspaces.iter_mut().find(|i| i.handle() == handle)
} }
// TODO iterate in order based on `coordinates` fn for_output<'a>(
fn workspaces_for_output<'a>(
&'a self, &'a self,
output: &'a wl_output::WlOutput, output: &'a wl_output::WlOutput,
) -> impl Iterator<Item = &'a Workspace> + 'a { ) -> impl Iterator<Item = &'a Workspace> + 'a {
self.workspaces self.0.iter().filter(|w| w.outputs.contains(output))
.iter()
.filter(|w| w.outputs.contains(output))
} }
}
fn toplevel_for_handle_mut( #[derive(Debug, Default)]
&mut self, struct Toplevels(Vec<Toplevel>);
handle: &ExtForeignToplevelHandleV1,
) -> Option<&mut Toplevel> { impl Toplevels {
self.toplevels.iter_mut().find(|i| &i.handle == handle) fn for_handle_mut(&mut self, handle: &ExtForeignToplevelHandleV1) -> Option<&mut Toplevel> {
self.0.iter_mut().find(|i| &i.handle == handle)
} }
}
impl App {
fn create_surface(&mut self, output: wl_output::WlOutput) -> Task<cosmic::Action<Msg>> { fn create_surface(&mut self, output: wl_output::WlOutput) -> Task<cosmic::Action<Msg>> {
let id = SurfaceId::unique(); let id = SurfaceId::unique();
self.layer_surfaces.insert( self.layer_surfaces.insert(
@ -309,6 +310,7 @@ impl App {
self.outputs.iter().map(|x| x.handle.clone()).collect(); self.outputs.iter().map(|x| x.handle.clone()).collect();
capture_filter.toplevels_on_workspaces = self capture_filter.toplevels_on_workspaces = self
.workspaces .workspaces
.0
.iter() .iter()
.filter(|x| x.is_active()) .filter(|x| x.is_active())
.map(|x| x.handle().clone()) .map(|x| x.handle().clone())
@ -317,12 +319,12 @@ impl App {
// Drop `CaptureImage` for workspaces and toplevels not matching new // Drop `CaptureImage` for workspaces and toplevels not matching new
// filter. // filter.
for workspace in &mut self.workspaces { for workspace in &mut self.workspaces.0 {
if !capture_filter.workspace_outputs_matches(&workspace.outputs) { if !capture_filter.workspace_outputs_matches(&workspace.outputs) {
workspace.img = None; workspace.img = None;
} }
} }
for toplevel in &mut self.toplevels { for toplevel in &mut self.toplevels.0 {
if !capture_filter.toplevel_matches(&toplevel.info) { if !capture_filter.toplevel_matches(&toplevel.info) {
toplevel.img = None; toplevel.img = None;
} }
@ -421,18 +423,15 @@ 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);
self.workspaces = Vec::new();
for (outputs, workspace) in workspaces { for (outputs, workspace) in workspaces {
// XXX efficiency // XXX efficiency
let old_workspace = old_workspaces let old_workspace = old_workspaces.for_handle(&workspace.handle);
.iter()
.find(|i| *i.handle() == workspace.handle);
let img = old_workspace.map(|i| i.img.clone()).unwrap_or_default(); let img = old_workspace.map(|i| i.img.clone()).unwrap_or_default();
let has_cursor = old_workspace.is_some_and(|w| w.has_cursor); let has_cursor = old_workspace.is_some_and(|w| w.has_cursor);
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.push(Workspace { self.workspaces.0.push(Workspace {
info: workspace, info: workspace,
outputs, outputs,
img, img,
@ -450,7 +449,7 @@ impl Application for App {
move |path| Msg::UpdateToplevelIcon(app_id.clone(), path), move |path| Msg::UpdateToplevelIcon(app_id.clone(), path),
) )
.map(cosmic::Action::App); .map(cosmic::Action::App);
self.toplevels.push(Toplevel { self.toplevels.0.push(Toplevel {
icon: None, icon: None,
handle, handle,
info, info,
@ -464,9 +463,7 @@ impl Application for App {
return icon_task; return icon_task;
} }
backend::Event::UpdateToplevel(handle, info) => { backend::Event::UpdateToplevel(handle, info) => {
if let Some(toplevel) = if let Some(toplevel) = self.toplevels.for_handle_mut(&handle) {
self.toplevels.iter_mut().find(|x| x.handle == handle)
{
let mut task = Task::none(); let mut task = Task::none();
if toplevel.info.app_id != info.app_id { if toplevel.info.app_id != info.app_id {
let app_id = info.app_id.clone(); let app_id = info.app_id.clone();
@ -481,24 +478,26 @@ impl Application for App {
} }
} }
backend::Event::CloseToplevel(handle) => { backend::Event::CloseToplevel(handle) => {
if let Some(idx) = self.toplevels.iter().position(|x| x.handle == handle) { if let Some(idx) = self.toplevels.0.iter().position(|x| x.handle == handle)
self.toplevels.remove(idx); {
self.toplevels.0.remove(idx);
} }
} }
backend::Event::WorkspaceCapture(handle, image) => { backend::Event::WorkspaceCapture(handle, image) => {
//println!("Workspace capture"); //println!("Workspace capture");
let capture_filter = self.capture_filter.clone(); // XXX if let Some(workspace) = self.workspaces.for_handle_mut(&handle) {
if let Some(workspace) = self.workspace_for_handle_mut(&handle) { if self
if capture_filter.workspace_outputs_matches(&workspace.outputs) { .capture_filter
.workspace_outputs_matches(&workspace.outputs)
{
workspace.img = Some(image); workspace.img = Some(image);
} }
} }
} }
backend::Event::ToplevelCapture(handle, image) => { backend::Event::ToplevelCapture(handle, image) => {
let capture_filter = self.capture_filter.clone(); // XXX if let Some(toplevel) = self.toplevels.for_handle_mut(&handle) {
if let Some(toplevel) = self.toplevel_for_handle_mut(&handle) {
// println!("Got toplevel image!"); // println!("Got toplevel image!");
if capture_filter.toplevel_matches(&toplevel.info) { if self.capture_filter.toplevel_matches(&toplevel.info) {
toplevel.img = Some(image); toplevel.img = Some(image);
} }
} }
@ -512,7 +511,7 @@ impl Application for App {
return self.hide(); return self.hide();
} }
Msg::ActivateWorkspace(workspace_handle) => { Msg::ActivateWorkspace(workspace_handle) => {
if let Some(workspace) = self.workspace_for_handle(&workspace_handle) { if let Some(workspace) = self.workspaces.for_handle(&workspace_handle) {
if workspace.is_active() { if workspace.is_active() {
return self.hide(); return self.hide();
} }
@ -596,7 +595,7 @@ impl Application for App {
self.conf.bg = c; self.conf.bg = c;
} }
Msg::UpdateToplevelIcon(app_id, path) => { Msg::UpdateToplevelIcon(app_id, path) => {
for toplevel in self.toplevels.iter_mut() { for toplevel in self.toplevels.0.iter_mut() {
if toplevel.info.app_id == app_id { if toplevel.info.app_id == app_id {
toplevel.icon = path.clone(); toplevel.icon = path.clone();
} }
@ -651,7 +650,7 @@ impl Application for App {
}; };
// TODO assumes only one active workspace per output // TODO assumes only one active workspace per output
let workspaces = self.workspaces_for_output(&output).collect::<Vec<_>>(); let workspaces = self.workspaces.for_output(&output).collect::<Vec<_>>();
if let Some(workspace_idx) = workspaces.iter().position(|i| i.is_active()) { if let Some(workspace_idx) = workspaces.iter().position(|i| i.is_active()) {
let workspace = match direction { let workspace = match direction {
// Next workspace on output // Next workspace on output
@ -674,9 +673,8 @@ impl Application for App {
DropTarget::WorkspaceSidebarEntry(other_handle, _output) DropTarget::WorkspaceSidebarEntry(other_handle, _output)
| DropTarget::WorkspaceSidebarDragPlaceholder(other_handle, _output), | DropTarget::WorkspaceSidebarDragPlaceholder(other_handle, _output),
) => { ) => {
let workspace = self.workspaces.iter().find(|i| i.handle() == handle); let workspace = self.workspaces.for_handle(handle);
let other_workspace = let other_workspace = self.workspaces.for_handle(&other_handle);
self.workspaces.iter().find(|i| *i.handle() == other_handle);
if let (Some(workspace), Some(other_workspace)) = if let (Some(workspace), Some(other_workspace)) =
(workspace, other_workspace) (workspace, other_workspace)
{ {
@ -699,11 +697,7 @@ impl Application for App {
} }
} }
Msg::TogglePinned(workspace_handle) => { Msg::TogglePinned(workspace_handle) => {
if let Some(workspace) = self if let Some(workspace) = self.workspaces.for_handle(&workspace_handle) {
.workspaces
.iter()
.find(|w| *w.handle() == workspace_handle)
{
self.send_wayland_cmd(backend::Cmd::SetWorkspacePinned( self.send_wayland_cmd(backend::Cmd::SetWorkspacePinned(
workspace_handle, workspace_handle,
!workspace.is_pinned(), !workspace.is_pinned(),
@ -711,11 +705,7 @@ impl Application for App {
} }
} }
Msg::EnteredWorkspaceSidebarEntry(workspace_handle, entered) => { Msg::EnteredWorkspaceSidebarEntry(workspace_handle, entered) => {
if let Some(workspace) = self if let Some(workspace) = self.workspaces.for_handle_mut(&workspace_handle) {
.workspaces
.iter_mut()
.find(|w| *w.handle() == workspace_handle)
{
workspace.has_cursor = entered; workspace.has_cursor = entered;
} }
} }

View file

@ -94,12 +94,13 @@ pub(crate) fn layer_surface<'a>(
#[allow(clippy::mutable_key_type)] #[allow(clippy::mutable_key_type)]
let workspaces_with_toplevels = app let workspaces_with_toplevels = app
.toplevels .toplevels
.0
.iter() .iter()
.flat_map(|t| &t.info.workspace) .flat_map(|t| &t.info.workspace)
.collect::<HashSet<_>>(); .collect::<HashSet<_>>();
let layout = app.conf.workspace_config.workspace_layout; let layout = app.conf.workspace_config.workspace_layout;
let sidebar = workspaces_sidebar( let sidebar = workspaces_sidebar(
app.workspaces_for_output(&surface.output), app.workspaces.for_output(&surface.output),
&workspaces_with_toplevels, &workspaces_with_toplevels,
&surface.output, &surface.output,
layout, layout,
@ -107,13 +108,14 @@ pub(crate) fn layer_surface<'a>(
drag_workspace, drag_workspace,
); );
let toplevels = toplevel_previews( let toplevels = toplevel_previews(
app.toplevels.iter().filter(|i| { app.toplevels.0.iter().filter(|i| {
if !i.info.output.contains(&surface.output) { if !i.info.output.contains(&surface.output) {
return false; return false;
} }
i.info.workspace.iter().any(|workspace| { i.info.workspace.iter().any(|workspace| {
app.workspace_for_handle(workspace) app.workspaces
.for_handle(workspace)
.is_some_and(|x| x.is_active()) .is_some_and(|x| x.is_active())
}) })
}), }),
@ -122,7 +124,8 @@ pub(crate) fn layer_surface<'a>(
); );
// TODO multiple active workspaces? Not currently supported by cosmic. // TODO multiple active workspaces? Not currently supported by cosmic.
let first_active_workspace = app let first_active_workspace = app
.workspaces_for_output(&surface.output) .workspaces
.for_output(&surface.output)
.find(|w| w.is_active()); .find(|w| w.is_active());
let toplevels = if let Some(workspace) = first_active_workspace { let toplevels = if let Some(workspace) = first_active_workspace {
dnd_destination_for_target( dnd_destination_for_target(