Merge remote-tracking branch 'upstream/master' into local/yoda-main

# Conflicts:
#	Cargo.lock
#	src/view/mod.rs
This commit is contained in:
Lionel DARNIS 2026-08-19 22:00:26 +02:00
commit d2910685cc
8 changed files with 999 additions and 555 deletions

1091
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -92,3 +92,11 @@ mime = { path = "../window_clipboard/mime" }
[profile.dev] [profile.dev]
# Not usable at opt-level 0, at least with software renderer # Not usable at opt-level 0, at least with software renderer
opt-level = 1 opt-level = 1
# [patch.'https://github.com/pop-os/libcosmic']
# libcosmic = { path = "../libcosmic" }
# cosmic-config = { path = "../libcosmic/cosmic-config" }
# cosmic-theme = { path = "../libcosmic/cosmic-theme" }
# libcosmic = { git = "https://github.com/pop-os/libcosmic//", branch = "theme-v2" }
# cosmic-config = { git = "https://github.com/pop-os/libcosmic//", branch = "theme-v2" }
# cosmic-theme = { git = "https://github.com/pop-os/libcosmic//", branch = "theme-v2" }

View file

@ -0,0 +1,2 @@
workspace = Espai de treball { $number }
new-workspace = Nou espai de treball

View file

@ -0,0 +1,2 @@
workspace = Espazo de traballo { $number }
new-workspace = Novo espazo de traballo

View file

View file

@ -1,2 +1,2 @@
workspace = 工作區 { $number } workspace = 工作區 { $number }
new-workspace = 新建工作區 new-workspace = 新增工作區

View file

@ -11,20 +11,25 @@ use cctk::wayland_client::{Connection, Proxy};
use cctk::wayland_protocols::ext::workspace::v1::client::ext_workspace_handle_v1; use cctk::wayland_protocols::ext::workspace::v1::client::ext_workspace_handle_v1;
use clap::Parser; use clap::Parser;
use cosmic::app::{Application, CosmicFlags}; use cosmic::app::{Application, CosmicFlags};
use cosmic::cctk::wayland_client::backend::ObjectId;
use cosmic::core::Auto;
use cosmic::iced::clipboard::dnd::{DndEvent, SourceEvent}; use cosmic::iced::clipboard::dnd::{DndEvent, SourceEvent};
use cosmic::iced::event::wayland::{Event as WaylandEvent, LayerEvent, OutputEvent}; use cosmic::iced::event::wayland::{Event as WaylandEvent, LayerEvent, OutputEvent};
use cosmic::iced::keyboard::key::{Key, Named}; use cosmic::iced::keyboard::key::{Key, Named};
use cosmic::iced::mouse::ScrollDelta; use cosmic::iced::mouse::ScrollDelta;
use cosmic::iced::platform_specific::shell::commands::layer_surface::{ use cosmic::iced::platform_specific::shell::commands::layer_surface::destroy_layer_surface;
destroy_layer_surface, get_layer_surface, use cosmic::iced::runtime::platform_specific::wayland::CornerRadius;
};
use cosmic::iced::runtime::platform_specific::wayland::layer_surface::{ use cosmic::iced::runtime::platform_specific::wayland::layer_surface::{
IcedOutput, SctkLayerSurfaceSettings, IcedMargin, IcedOutput, SctkLayerSurfaceSettings,
}; };
use cosmic::iced::window::Id as SurfaceId; use cosmic::iced::window::{self, Id as SurfaceId};
use cosmic::iced::{self, Size, Subscription, Task}; use cosmic::iced::{self, Rectangle, Size, Subscription, Task};
use cosmic::scroll::DiscreteScrollState; use cosmic::scroll::DiscreteScrollState;
use cosmic::{cctk, dbus_activation}; use cosmic::surface::action::LiveSettings;
use cosmic::widget::rectangle_tracker::{
RectangleTracker, RectangleUpdate, rectangle_tracker_subscription,
};
use cosmic::{cctk, dbus_activation, widget};
use cosmic_comp_config::CosmicCompConfig; use cosmic_comp_config::CosmicCompConfig;
use cosmic_config::CosmicConfigEntry; use cosmic_config::CosmicConfigEntry;
use cosmic_config::cosmic_config_derive::CosmicConfigEntry; use cosmic_config::cosmic_config_derive::CosmicConfigEntry;
@ -114,6 +119,7 @@ enum Msg {
PanelConfig(CosmicPanelConfig), PanelConfig(CosmicPanelConfig),
ActionOnTyping(String), ActionOnTyping(String),
Ignore, Ignore,
Rectangle(RectangleUpdate<RectId>),
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
@ -150,6 +156,7 @@ struct Toplevel {
info: ToplevelInfo, info: ToplevelInfo,
img: Option<backend::CaptureImage>, img: Option<backend::CaptureImage>,
icon: Option<PathBuf>, icon: Option<PathBuf>,
pub pending_move: Option<ExtWorkspaceHandleV1>,
} }
#[derive(Clone)] #[derive(Clone)]
@ -174,6 +181,14 @@ struct Conf {
bg: cosmic_bg_config::state::State, bg: cosmic_bg_config::state::State,
} }
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct RectId {
id: window::Id,
toplevel_id: Option<ObjectId>,
workspaces_id: Option<Vec<ObjectId>>,
widget_id: Option<widget::Id>,
}
#[derive(Default)] #[derive(Default)]
struct App { struct App {
capture_filter: backend::CaptureFilter, capture_filter: backend::CaptureFilter,
@ -194,6 +209,9 @@ struct App {
dbus_interface: Option<dbus::Interface>, dbus_interface: Option<dbus::Interface>,
panel_configs: HashMap<String, Option<CosmicPanelConfig>>, panel_configs: HashMap<String, Option<CosmicPanelConfig>>,
action_on_typing_activated: bool, action_on_typing_activated: bool,
rectangle_tracker: Option<RectangleTracker<RectId>>,
rects: HashMap<RectId, Rectangle>,
sub_ctr: u128,
} }
#[derive(Debug, Default)] #[derive(Debug, Default)]
@ -234,16 +252,24 @@ impl App {
output: output.clone(), output: output.clone(),
}, },
); );
get_layer_surface(SctkLayerSurfaceSettings { cosmic::surface::surface_task::<Msg>(cosmic::surface::action::simple_layer_shell::<Msg>(
id, || LiveSettings {
keyboard_interactivity: KeyboardInteractivity::Exclusive, padding: Some(IcedMargin::default()),
namespace: "cosmic-workspace-overview".into(), corners: Some(CornerRadius::default()),
layer: Layer::Top, blur: Some(false),
size: Some((None, None)), },
output: IcedOutput::Output(output), move || SctkLayerSurfaceSettings {
anchor: Anchor::all(), id,
..Default::default() keyboard_interactivity: KeyboardInteractivity::Exclusive,
}) namespace: "cosmic-workspace-overview".into(),
layer: Layer::Top,
size: Some((None, None)),
output: IcedOutput::Output(output.clone()),
anchor: Anchor::all(),
..Default::default()
},
None::<fn() -> cosmic::Element<'static, cosmic::Action<Msg>>>,
))
} }
fn destroy_surface(&mut self, output: &wl_output::WlOutput) -> Task<cosmic::Action<Msg>> { fn destroy_surface(&mut self, output: &wl_output::WlOutput) -> Task<cosmic::Action<Msg>> {
@ -383,7 +409,7 @@ impl App {
// TODO: If compositor supports overlap notify, also use that? // TODO: If compositor supports overlap notify, also use that?
// Or otherwise verify the panel is actually running. // Or otherwise verify the panel is actually running.
for config in self.panel_configs.values().flatten() { for config in self.panel_configs.values().flatten() {
if config.autohide.is_some() && !config.exclusive_zone { if config.autohide_enabled() && !config.exclusive_zone {
let dimention_constraints = config.get_dimensions( let dimention_constraints = config.get_dimensions(
Some((output.width as u32, output.height as u32)), Some((output.width as u32, output.height as u32)),
None, None,
@ -421,6 +447,72 @@ impl App {
} }
regions regions
} }
fn update_active_workspace(
&mut self,
workspace_handle: ObjectId,
) -> 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().id() == workspace_handle)
}) {
let active = cosmic::theme::active();
let rad = active
.cosmic()
.radius_s()
.map(|x| if x < 4.0 { x } else { x + 8.0 });
let mut rects = Vec::new();
let mut strips: Vec<Rectangle> = self
.rects
.iter()
.filter_map(|(id, rect)| {
if self.drag_surface.as_ref().is_some_and(|(s, _)| match s {
DragSurface::Workspace(_) => false,
DragSurface::Toplevel(ext_foreign_toplevel_handle_v1) => id
.toplevel_id
.as_ref()
.is_some_and(|t| *t == ext_foreign_toplevel_handle_v1.id()),
}) || id.id != *cur_window
|| id
.workspaces_id
.as_ref()
.is_some_and(|l| !l.iter().any(|o| *o == workspace_handle))
{
return None;
}
let rad = if id.toplevel_id.is_none() {
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,
}
} else {
rects.push(*rect);
return None;
};
Some(cosmic::surface::corner_radius::rounded_rect_strips(
*rect, rad,
))
})
.flatten()
.collect();
strips.append(&mut rects);
return Some(
cosmic::iced::platform_specific::shell::commands::blur::blur(
*cur_window,
Some(strips),
)
.discard(),
);
}
None
}
} }
impl Application for App { impl Application for App {
@ -429,7 +521,9 @@ impl Application for App {
type Flags = Args; type Flags = Args;
const APP_ID: &'static str = "com.system76.CosmicWorkspaces"; const APP_ID: &'static str = "com.system76.CosmicWorkspaces";
fn init(core: cosmic::app::Core, _flags: Self::Flags) -> (Self, Task<cosmic::Action<Msg>>) { fn init(mut core: cosmic::app::Core, _flags: Self::Flags) -> (Self, Task<cosmic::Action<Msg>>) {
core.set_app_type(cosmic::core::AppType::System);
core.set_auto_blur(Auto::Popup | Auto::Window);
( (
Self { Self {
core, core,
@ -443,6 +537,27 @@ impl Application for App {
fn update(&mut self, message: Msg) -> Task<cosmic::Action<Msg>> { fn update(&mut self, message: Msg) -> Task<cosmic::Action<Msg>> {
match message { match message {
Msg::Rectangle(u) => match u {
RectangleUpdate::Rectangle(r) => {
self.rects.insert(r.0.clone(), r.1);
if self.visible {
let to_update: Vec<_> = self
.workspaces
.0
.iter()
.filter_map(|w| w.is_active().then(|| w.handle().id()))
.collect();
return Task::batch(
to_update
.into_iter()
.filter_map(|w| self.update_active_workspace(w)),
);
}
}
RectangleUpdate::Init(tracker) => {
self.rectangle_tracker = Some(tracker);
}
},
Msg::SourceFinished => { Msg::SourceFinished => {
self.drag_surface = None; self.drag_surface = None;
} }
@ -501,6 +616,8 @@ impl Application for App {
if self.layer_surfaces.remove(&id).is_none() { if self.layer_surfaces.remove(&id).is_none() {
log::error!("removing non-existant layer shell id {}?", id); log::error!("removing non-existant layer shell id {}?", id);
} }
self.sub_ctr += 1;
self.rects.retain(|k, _| k.id != id);
} }
_ => {} _ => {}
}, },
@ -512,6 +629,7 @@ 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 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);
@ -520,15 +638,25 @@ 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();
if self.visible {
return Task::batch(new_active.into_iter().map(|new| {
self.update_active_workspace(new.id())
.unwrap_or(Task::none())
}));
}
} }
backend::Event::NewToplevel(handle, info) => { backend::Event::NewToplevel(handle, info) => {
log::debug!("New toplevel: {info:?}"); log::debug!("New toplevel: {info:?}");
@ -543,6 +671,7 @@ impl Application for App {
handle, handle,
info, info,
img: None, img: None,
pending_move: None,
}); });
// Close workspaces view if a window spawns while open // Close workspaces view if a window spawns while open
#[cfg(not(feature = "mock-backend"))] #[cfg(not(feature = "mock-backend"))]
@ -552,6 +681,8 @@ impl Application for App {
return icon_task; return icon_task;
} }
backend::Event::UpdateToplevel(handle, info) => { backend::Event::UpdateToplevel(handle, info) => {
let mut t_w = None;
let mut tasks = Vec::new();
if let Some(toplevel) = self.toplevels.for_handle_mut(&handle) { if let Some(toplevel) = self.toplevels.for_handle_mut(&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 {
@ -562,15 +693,70 @@ impl Application for App {
) )
.map(cosmic::Action::App); .map(cosmic::Action::App);
} }
// XX must clean up rectangles after the window has moved
t_w = Some((handle.id(), info.workspace.clone()));
if toplevel
.pending_move
.as_ref()
.is_some_and(|w| info.workspace.contains(&w.id()))
{
toplevel.pending_move = None;
}
toplevel.info = info; toplevel.info = info;
return task;
tasks.push(task);
} }
if let Some((t, w)) = t_w {
for w in w {
let old_len = self.rects.len();
self.rects.retain(|id, _| {
id.toplevel_id.as_ref().is_none_or(|old| *old != t)
|| id
.workspaces_id
.as_ref()
.is_some_and(|old| old.contains(&w.id()))
});
if old_len != self.rects.len() {
self.sub_ctr += 1;
}
}
}
if self.visible {
let to_update: Vec<_> = self
.workspaces
.0
.iter()
.filter_map(|w| w.is_active().then(|| w.handle().id()))
.collect();
tasks.push(Task::batch(
to_update
.into_iter()
.filter_map(|w| self.update_active_workspace(w)),
));
}
return Task::batch(tasks);
} }
backend::Event::CloseToplevel(handle) => { backend::Event::CloseToplevel(handle) => {
if let Some(idx) = self.toplevels.0.iter().position(|x| x.handle == handle) if let Some(idx) = self.toplevels.0.iter().position(|x| x.handle == handle)
{ {
self.toplevels.0.remove(idx); self.toplevels.0.remove(idx);
} }
if let Some(k) = self
.rects
.keys()
.find(|k| k.toplevel_id.as_ref().is_some_and(|t| handle.id() == *t))
.cloned()
{
self.rects.remove(&k);
if let Some(w) = k.workspaces_id {
return Task::batch(
w.into_iter()
.filter_map(|w| self.update_active_workspace(w)),
);
}
}
} }
backend::Event::WorkspaceCapture(handle, image) => { backend::Event::WorkspaceCapture(handle, image) => {
//println!("Workspace capture"); //println!("Workspace capture");
@ -604,7 +790,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.id()) {
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));
@ -623,13 +813,38 @@ impl Application for App {
} }
} }
*/ */
if self.visible {
let to_update: Vec<_> = self
.workspaces
.0
.iter()
.filter_map(|w| w.is_active().then(|| w.handle().id()))
.collect();
return Task::batch(
to_update
.into_iter()
.filter_map(|w| self.update_active_workspace(w)),
);
}
} }
Msg::CloseToplevel(toplevel_handle) => { Msg::CloseToplevel(toplevel_handle) => {
// TODO confirmation? // TODO confirmation?
self.send_wayland_cmd(backend::Cmd::CloseToplevel(toplevel_handle)); self.send_wayland_cmd(backend::Cmd::CloseToplevel(toplevel_handle));
} }
Msg::StartDrag(drag_surface) => { Msg::StartDrag(drag_surface) => {
// if let DragSurface::Toplevel(t) = &drag_surface {}
self.drag_surface = Some((drag_surface, Default::default())); self.drag_surface = Some((drag_surface, Default::default()));
let to_update: Vec<_> = self
.workspaces
.0
.iter()
.filter_map(|w| w.is_active().then(|| w.handle().id()))
.collect();
return Task::batch(
to_update
.into_iter()
.filter_map(|w| self.update_active_workspace(w)),
);
} }
Msg::DndEnter(drop_target, _x, _y, _mimes) => { Msg::DndEnter(drop_target, _x, _y, _mimes) => {
self.drop_target = Some(drop_target); self.drop_target = Some(drop_target);
@ -640,6 +855,21 @@ impl Application for App {
if self.drop_target == Some(drop_target) { if self.drop_target == Some(drop_target) {
self.drop_target = None; self.drop_target = None;
} }
self.sub_ctr += 1;
if self.visible {
let to_update: Vec<_> = self
.workspaces
.0
.iter()
.filter_map(|w| w.is_active().then(|| w.handle().id()))
.collect();
return Task::batch(
to_update
.into_iter()
.filter_map(|w| self.update_active_workspace(w)),
);
}
} }
Msg::DndToplevelDrop(_toplevel) => { Msg::DndToplevelDrop(_toplevel) => {
if let Some((DragSurface::Toplevel(handle), _)) = &self.drag_surface { if let Some((DragSurface::Toplevel(handle), _)) = &self.drag_surface {
@ -648,11 +878,34 @@ impl Application for App {
DropTarget::WorkspaceSidebarEntry(workspace, output) DropTarget::WorkspaceSidebarEntry(workspace, output)
| DropTarget::OutputToplevels(workspace, output), | DropTarget::OutputToplevels(workspace, output),
) => { ) => {
if let Some(t) = self.toplevels.for_handle_mut(handle)
&& !t.info.workspace.contains(&workspace.id())
{
self.rects.retain(|r, _| {
r.toplevel_id.as_ref().is_none_or(|o| *o != handle.id())
});
self.sub_ctr += 1;
t.pending_move = Some(workspace.clone());
}
self.send_wayland_cmd(backend::Cmd::MoveToplevelToWorkspace( self.send_wayland_cmd(backend::Cmd::MoveToplevelToWorkspace(
handle.clone(), handle.clone(),
workspace, workspace,
output, output,
)); ));
self.drag_surface = None;
let to_update: Vec<_> = self
.workspaces
.0
.iter()
.filter_map(|w| w.is_active().then(|| w.handle().id()))
.collect();
return Task::batch(
to_update
.into_iter()
.filter_map(|w| self.update_active_workspace(w)),
);
} }
Some( Some(
DropTarget::WorkspacesBar(_) DropTarget::WorkspacesBar(_)
@ -879,6 +1132,7 @@ impl Application for App {
config_subscription, config_subscription,
comp_config_subscription, comp_config_subscription,
bg_subscription, bg_subscription,
rectangle_tracker_subscription(self.sub_ctr).map(|update| Msg::Rectangle(update.1)),
]; ];
if let Some(conn) = self.conn.clone() { if let Some(conn) = self.conn.clone() {
subscriptions.push(backend::subscription(conn).map(Msg::Wayland)); subscriptions.push(backend::subscription(conn).map(Msg::Wayland));
@ -895,8 +1149,12 @@ impl Application for App {
} }
fn view_window(&self, id: iced::window::Id) -> cosmic::Element<'_, Self::Message> { fn view_window(&self, id: iced::window::Id) -> cosmic::Element<'_, Self::Message> {
if let Some(surface) = self.layer_surfaces.get(&id) { if let Some((surface, rectangle_track)) = self
return view::layer_surface(self, surface); .layer_surfaces
.get(&id)
.zip(self.rectangle_tracker.as_ref())
{
return view::layer_surface(self, surface, id, rectangle_track);
} }
log::error!("non-existant layer shell id {}?", id); log::error!("non-existant layer shell id {}?", id);
cosmic::widget::text("workspaces").into() cosmic::widget::text("workspaces").into()

View file

@ -1,22 +1,23 @@
use cosmic::Apply;
use cosmic::cctk::cosmic_protocols::toplevel_info::v1::client::zcosmic_toplevel_handle_v1; use cosmic::cctk::cosmic_protocols::toplevel_info::v1::client::zcosmic_toplevel_handle_v1;
use cosmic::cctk::cosmic_protocols::workspace::v2::client::zcosmic_workspace_handle_v2; use cosmic::cctk::cosmic_protocols::workspace::v2::client::zcosmic_workspace_handle_v2;
use cosmic::cctk::wayland_client::Proxy;
use cosmic::cctk::wayland_client::protocol::wl_output; use cosmic::cctk::wayland_client::protocol::wl_output;
use cosmic::cctk::wayland_protocols::ext::workspace::v1::client::ext_workspace_handle_v1; use cosmic::cctk::wayland_protocols::ext::workspace::v1::client::ext_workspace_handle_v1;
use cosmic::iced::advanced::layout::flex::Axis; use cosmic::iced::advanced::layout::flex::Axis;
use cosmic::iced::clipboard::mime::{AllowedMimeTypes, AsMimeTypes}; use cosmic::iced::clipboard::mime::{AllowedMimeTypes, AsMimeTypes};
use cosmic::iced::core::Shadow;
use cosmic::iced::core::text::{Ellipsize, EllipsizeHeightLimit}; use cosmic::iced::core::text::{Ellipsize, EllipsizeHeightLimit};
use cosmic::iced::core::{Shadow, window};
use cosmic::iced::platform_specific::shell::subsurface_widget::Subsurface; use cosmic::iced::platform_specific::shell::subsurface_widget::Subsurface;
use cosmic::iced::widget::{column, row}; use cosmic::iced::widget::{column, row};
use cosmic::iced::{self, Alignment, Border, Length}; use cosmic::iced::{self, Alignment, Border, Length};
use cosmic::widget::{self, Widget}; use cosmic::widget::{self, Widget, rectangle_tracker};
use cosmic::{Apply, Element};
use cosmic_comp_config::workspace::WorkspaceLayout; use cosmic_comp_config::workspace::WorkspaceLayout;
use std::collections::HashSet; use std::collections::HashSet;
use crate::backend::{self, CaptureImage}; use crate::backend::{self, CaptureImage};
use crate::dnd::{Drag, DragSurface, DragToplevel, DragWorkspace, DropTarget}; use crate::dnd::{Drag, DragSurface, DragToplevel, DragWorkspace, DropTarget};
use crate::{App, LayerSurface, Msg, Toplevel, Workspace}; use crate::{App, LayerSurface, Msg, RectId, Toplevel, Workspace};
fn dnd_source_with_drag_surface<D: AsMimeTypes + Send + Clone + 'static>( fn dnd_source_with_drag_surface<D: AsMimeTypes + Send + Clone + 'static>(
drag_content: D, drag_content: D,
@ -69,6 +70,8 @@ where
pub(crate) fn layer_surface<'a>( pub(crate) fn layer_surface<'a>(
app: &'a App, app: &'a App,
surface: &'a LayerSurface, surface: &'a LayerSurface,
window_id: window::Id,
rectangle_track: &rectangle_tracker::RectangleTracker<RectId>,
) -> cosmic::Element<'a, Msg> { ) -> cosmic::Element<'a, Msg> {
let mut drag_toplevel = None; let mut drag_toplevel = None;
let mut drag_workspace = None; let mut drag_workspace = None;
@ -89,6 +92,7 @@ pub(crate) fn layer_surface<'a>(
.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;
// track this rectangle
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,
@ -96,6 +100,8 @@ pub(crate) fn layer_surface<'a>(
layout, layout,
app.drop_target.as_ref(), app.drop_target.as_ref(),
drag_workspace, drag_workspace,
window_id,
rectangle_track,
); );
let toplevels = toplevel_previews( let toplevels = toplevel_previews(
app.toplevels.0.iter().filter(|i| { app.toplevels.0.iter().filter(|i| {
@ -111,6 +117,8 @@ pub(crate) fn layer_surface<'a>(
}), }),
layout, layout,
drag_toplevel, drag_toplevel,
window_id,
rectangle_track,
); );
// 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
@ -146,6 +154,7 @@ pub(crate) fn layer_surface<'a>(
let container = widget::container(container).padding(panel_regions); let container = widget::container(container).padding(panel_regions);
let output = surface.output.clone(); let output = surface.output.clone();
widget::mouse_area(container) widget::mouse_area(container)
.on_scroll(move |delta| Msg::OnScroll(output.clone(), delta)) .on_scroll(move |delta| Msg::OnScroll(output.clone(), delta))
.into() .into()
@ -162,12 +171,12 @@ fn pin_button_style(theme: &cosmic::Theme, is_pinned: bool) -> cosmic::widget::b
let bg_color = if is_pinned { let bg_color = if is_pinned {
theme.cosmic().accent.base.into() theme.cosmic().accent.base.into()
} else { } else {
theme.cosmic().primary(false).base.into() theme.cosmic().primary(theme.transparent).base.into()
}; };
let icon_color = if is_pinned { let icon_color = if is_pinned {
theme.cosmic().accent.on.into() theme.cosmic().accent.on.into()
} else { } else {
theme.cosmic().primary(false).on.into() theme.cosmic().primary(theme.transparent).on.into()
}; };
cosmic::widget::button::Style { cosmic::widget::button::Style {
icon_color: Some(icon_color), icon_color: Some(icon_color),
@ -322,6 +331,7 @@ fn workspace_item(
{ {
button = button.on_press(Msg::ActivateWorkspace(workspace.handle().clone())); button = button.on_press(Msg::ActivateWorkspace(workspace.handle().clone()));
} }
button.into() button.into()
} }
@ -420,6 +430,8 @@ fn workspaces_sidebar<'a>(
layout: WorkspaceLayout, layout: WorkspaceLayout,
drop_target: Option<&DropTarget>, drop_target: Option<&DropTarget>,
drag_workspace: Option<&'a backend::ExtWorkspaceHandleV1>, drag_workspace: Option<&'a backend::ExtWorkspaceHandleV1>,
window_id: window::Id,
rectangle_track: &rectangle_tracker::RectangleTracker<RectId>,
) -> cosmic::Element<'a, Msg> { ) -> cosmic::Element<'a, Msg> {
let mut sidebar_entries = Vec::new(); let mut sidebar_entries = Vec::new();
for workspace in workspaces { for workspace in workspaces {
@ -480,32 +492,48 @@ fn workspaces_sidebar<'a>(
widget::container(crate::widgets::workspace_bar(sidebar_entries, axis)).padding(8.0); widget::container(crate::widgets::workspace_bar(sidebar_entries, axis)).padding(8.0);
widget::container( widget::container(
widget::container(sidebar_entries_container) rectangle_track.container(
.width(width) RectId {
.height(height) id: window_id,
.class(cosmic::theme::Container::custom(|theme| { toplevel_id: None,
cosmic::iced::widget::container::Style { widget_id: None,
text_color: Some(theme.cosmic().on_bg_color().into()), workspaces_id: None,
icon_color: Some(theme.cosmic().on_bg_color().into()), },
background: Some(iced::Color::from(theme.cosmic().background(false).base).into()), widget::container(sidebar_entries_container)
border: Border { .width(width)
radius: theme .height(height)
.cosmic() .class(cosmic::theme::Container::custom(|theme| {
.radius_s() cosmic::iced::widget::container::Style {
.map(|x| if x < 4.0 { x } else { x + 8.0 }) text_color: Some(theme.cosmic().on_bg_color().into()),
.into(), icon_color: Some(theme.cosmic().on_bg_color().into()),
..Default::default() background: Some(
}, iced::Color::from(theme.cosmic().background(theme.transparent).base)
shadow: Shadow::default(), .into(),
snap: true, ),
} border: Border {
})), radius: theme
.cosmic()
.radius_s()
.map(|x| if x < 4.0 { x } else { x + 8.0 })
.into(),
..Default::default()
},
shadow: Shadow::default(),
snap: true,
}
})),
),
) )
.padding(8) .padding(8)
.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;
@ -524,6 +552,8 @@ fn toplevel_preview(toplevel: &Toplevel, is_being_dragged: bool) -> cosmic::Elem
.align_y(Alignment::Center); .align_y(Alignment::Center);
let title = row![ let title = row![
// TODO tracker for each of these buttons?
// So that they can be blurred individually like the sidebar?
widget::button::custom(label) widget::button::custom(label)
.on_press(Msg::ActivateToplevel(toplevel.handle.clone())) .on_press(Msg::ActivateToplevel(toplevel.handle.clone()))
.class(cosmic::theme::Button::Icon) .class(cosmic::theme::Button::Icon)
@ -549,17 +579,30 @@ fn toplevel_preview(toplevel: &Toplevel, is_being_dragged: bool) -> cosmic::Elem
.spacing(8) .spacing(8)
.padding([0, 0, 2, 0]) .padding([0, 0, 2, 0])
.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 content = capture_image(toplevel.img.as_ref(), alpha);
.selected(
toplevel let preview = widget::button::custom(if toplevel.pending_move.is_some() || is_being_dragged {
.info Element::from(content)
.state } else {
.contains(&zcosmic_toplevel_handle_v1::State::Activated), Element::from(rectangle_track.container(
) RectId {
.class(cosmic::theme::Button::Image) id: window_id,
.on_press(Msg::ActivateToplevel(toplevel.handle.clone())); toplevel_id: Some(toplevel.handle.id()),
widget_id: None,
workspaces_id: Some(toplevel.info.workspace.iter().map(|h| h.id()).collect()),
},
content,
))
})
.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()],
@ -570,23 +613,26 @@ fn toplevel_preview(toplevel: &Toplevel, is_being_dragged: bool) -> cosmic::Elem
.into() .into()
} }
fn toplevel_previews_entry( fn toplevel_previews_entry<'a>(
toplevel: &Toplevel, toplevel: &'a Toplevel,
is_being_dragged: bool, is_being_dragged: bool,
) -> cosmic::Element<'_, Msg> { window_id: window::Id,
rectangle_track: &rectangle_tracker::RectangleTracker<RectId>,
) -> 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 = crate::widgets::visibility_wrapper( let preview = crate::widgets::visibility_wrapper(
toplevel_preview(toplevel, is_being_dragged), toplevel_preview(toplevel, is_being_dragged, window_id, rectangle_track),
!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),
) )
} }
@ -594,13 +640,22 @@ fn toplevel_previews<'a>(
toplevels: impl Iterator<Item = &'a Toplevel>, toplevels: impl Iterator<Item = &'a Toplevel>,
layout: WorkspaceLayout, layout: WorkspaceLayout,
drag_toplevel: Option<&'a backend::ExtForeignToplevelHandleV1>, drag_toplevel: Option<&'a backend::ExtForeignToplevelHandleV1>,
window_id: window::Id,
rectangle_track: &rectangle_tracker::RectangleTracker<RectId>,
) -> cosmic::Element<'a, Msg> { ) -> cosmic::Element<'a, Msg> {
let (width, height) = match layout { let (width, height) = match layout {
WorkspaceLayout::Vertical => (Length::FillPortion(4), Length::Fill), WorkspaceLayout::Vertical => (Length::FillPortion(4), Length::Fill),
WorkspaceLayout::Horizontal => (Length::Fill, Length::FillPortion(4)), WorkspaceLayout::Horizontal => (Length::Fill, Length::FillPortion(4)),
}; };
let entries = toplevels let entries = toplevels
.map(|t| toplevel_previews_entry(t, drag_toplevel == Some(&t.handle))) .map(|t| {
toplevel_previews_entry(
t,
drag_toplevel == Some(&t.handle),
window_id,
rectangle_track,
)
})
.collect(); .collect();
//row(entries) //row(entries)
widget::mouse_area( widget::mouse_area(