Compare commits

...

24 commits

Author SHA1 Message Date
bdcdef9f5c chore: fige Cargo.lock (epoch-1.9.0, [patch] locaux)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-24 21:03:54 +02:00
9e11562a43 merge: epoch-1.9.0 d'upstream 2026-09-24 20:46:53 +02:00
Michael Murphy
28e257daa8
Translations update from Hosted Weblate (#332)
Translations update from [Hosted Weblate](https://hosted.weblate.org)
for [Pop OS/COSMIC
Workspaces](https://hosted.weblate.org/projects/pop-os/cosmic-workspaces-epoch/).



Current translation status:

![Weblate translation
status](https://hosted.weblate.org/widget/pop-os/cosmic-workspaces-epoch/matrix-auto.svg)
2026-09-15 21:51:15 +02:00
Hosted Weblate
f1ceefe4b1
i18n: translation updates from weblate
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Quentin PAGÈS <quentinantonin@free.fr>
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-workspaces-epoch/oc/
Translation: Pop OS/COSMIC Workspaces
2026-09-14 05:51:28 +02:00
d65f381006 Merge tag 'epoch-1.8.0' into local/yoda-main
Epoch 1.8.0
2026-09-10 17:56:29 +02:00
Hosted Weblate
38d09960b7 i18n: translation updates from weblate
Co-authored-by: CYAXXX <85353920+CYAXXX@users.noreply.github.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Kurt Harren Catindig <kurtharrencatindig03@gmail.com>
Co-authored-by: Zahid Rizky Fakhri <zahidrizkyfakhri@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-workspaces-epoch/id/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-workspaces-epoch/kmr/
Translation: Pop OS/COSMIC Workspaces
2026-09-01 19:42:12 +02:00
Hojjat
35c2a4eae5 fix: prevent window from disappearing when dropped on same workspace 2026-08-31 14:14:46 -04:00
53a177c85b chore: fige Cargo.lock tel que resolu apres le merge 1.7.0
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-27 07:58:50 +02:00
d2910685cc Merge remote-tracking branch 'upstream/master' into local/yoda-main
# Conflicts:
#	Cargo.lock
#	src/view/mod.rs
2026-08-19 22:00:26 +02:00
Michael Murphy
9c5f5a2584
i18n: translation update from Hosted Weblate (#327) 2026-08-18 14:04:57 +02:00
Hosted Weblate
ce0977e189
i18n: translation updates from weblate
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Martin Meizoso <martinmeizoso@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-workspaces/gl/
Translation: Pop OS/COSMIC Workspaces
2026-08-05 16:01:53 +00:00
Michael Murphy
8faab4c2a9
i18n: translation update from Hosted Weblate (#311)
Translations update from [Hosted Weblate](https://hosted.weblate.org)
for [Pop OS/COSMIC
Workspaces](https://hosted.weblate.org/projects/pop-os/cosmic-workspaces/).



Current translation status:

![Weblate translation
status](https://hosted.weblate.org/widget/pop-os/cosmic-workspaces/matrix-auto.svg)
2026-07-28 16:48:35 +02:00
Hosted Weblate
e28e408cc4
i18n: translation updates from weblate
Co-authored-by: Adolfo Jayme Barrientos <fitojb@ubuntu.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Nara Díaz Viñolas <nara.diaz.vinolas@gmail.com>
Co-authored-by: Prasanta-Hembram <Prasantahembram720@gmail.com>
Co-authored-by: taijuin Lee <taijuin@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-workspaces/ca/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-workspaces/zh_Hant/
Translation: Pop OS/COSMIC Workspaces
2026-07-23 18:51:04 +02:00
Ashley Wulber
6511d2f1a6 fix: clean up blur rectangles when a toplevel is closed 2026-07-21 12:31:15 -04:00
Levi Portenier
3da8907d7a
chore: update libcosmic (#316) 2026-07-10 10:34:14 -06:00
Ashley Wulber
9e4c9e4bb2 chore: update libcosmic 2026-07-09 20:43:13 -04:00
Ashley Wulber
28a2bfedad fix: stop tracking rects when dragging
subscription counter changes also ensure that when a window is dropped, it gets a new rectangle.
2026-07-09 14:37:16 -06:00
Ashley Wulber
01b5836a97 refactor: recreate rectangles for all active workspaces on toplevel update and workspaces list changes 2026-07-09 14:37:16 -06:00
Ashley Wulber
20ac821954 fix: cleanup toplevel rects after the update confirming it has moved 2026-07-09 14:37:16 -06:00
Ashley Wulber
a8998b3eb0 fix: rectangle logic for activation and dragging 2026-07-09 14:37:16 -06:00
Ashley Wulber
4b103b0b07 fix: update libcosmic
fix for issues related to scaling
2026-07-09 14:37:16 -06:00
Ashley Wulber
fa4d8d68a4 fix: cleanup rectangles on workspace swap 2026-07-09 14:37:16 -06:00
Ashley Wulber
127b966e3e chore: update libcosmic 2026-07-09 14:37:16 -06:00
Ashley Wulber
4bafd82500 feat: frosted glass 2026-07-09 14:37:16 -06:00
12 changed files with 836 additions and 575 deletions

932
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]
# Not usable at opt-level 0, at least with software renderer
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

View file

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

View file

@ -1,2 +1,2 @@
workspace = Ruang kerja { $number }
workspace = Ruang Kerja { $number }
new-workspace = Ruang Kerja Baru

View file

@ -1,2 +1,2 @@
workspace = Qadanda xebatê { $number }
workspace = Qada xebatê { $number }
new-workspace = Qada xebatê nû

View file

@ -0,0 +1,2 @@
workspace = Espaci de trabalh { $number }
new-workspace = Espaci de trabalh novèl

View file

View file

@ -1,2 +1,2 @@
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 clap::Parser;
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::event::wayland::{Event as WaylandEvent, LayerEvent, OutputEvent};
use cosmic::iced::keyboard::key::{Key, Named};
use cosmic::iced::mouse::ScrollDelta;
use cosmic::iced::platform_specific::shell::commands::layer_surface::{
destroy_layer_surface, get_layer_surface,
};
use cosmic::iced::platform_specific::shell::commands::layer_surface::destroy_layer_surface;
use cosmic::iced::runtime::platform_specific::wayland::CornerRadius;
use cosmic::iced::runtime::platform_specific::wayland::layer_surface::{
IcedOutput, SctkLayerSurfaceSettings,
IcedMargin, IcedOutput, SctkLayerSurfaceSettings,
};
use cosmic::iced::window::Id as SurfaceId;
use cosmic::iced::{self, Size, Subscription, Task};
use cosmic::iced::window::{self, Id as SurfaceId};
use cosmic::iced::{self, Rectangle, Size, Subscription, Task};
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_config::CosmicConfigEntry;
use cosmic_config::cosmic_config_derive::CosmicConfigEntry;
@ -114,6 +119,7 @@ enum Msg {
PanelConfig(CosmicPanelConfig),
ActionOnTyping(String),
Ignore,
Rectangle(RectangleUpdate<RectId>),
}
#[derive(Clone, Debug)]
@ -150,6 +156,7 @@ struct Toplevel {
info: ToplevelInfo,
img: Option<backend::CaptureImage>,
icon: Option<PathBuf>,
pub pending_move: Option<ExtWorkspaceHandleV1>,
}
#[derive(Clone)]
@ -174,6 +181,14 @@ struct Conf {
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)]
struct App {
capture_filter: backend::CaptureFilter,
@ -194,6 +209,9 @@ struct App {
dbus_interface: Option<dbus::Interface>,
panel_configs: HashMap<String, Option<CosmicPanelConfig>>,
action_on_typing_activated: bool,
rectangle_tracker: Option<RectangleTracker<RectId>>,
rects: HashMap<RectId, Rectangle>,
sub_ctr: u128,
}
#[derive(Debug, Default)]
@ -234,16 +252,24 @@ impl App {
output: output.clone(),
},
);
get_layer_surface(SctkLayerSurfaceSettings {
id,
keyboard_interactivity: KeyboardInteractivity::Exclusive,
namespace: "cosmic-workspace-overview".into(),
layer: Layer::Top,
size: Some((None, None)),
output: IcedOutput::Output(output),
anchor: Anchor::all(),
..Default::default()
})
cosmic::surface::surface_task::<Msg>(cosmic::surface::action::simple_layer_shell::<Msg>(
|| LiveSettings {
padding: Some(IcedMargin::default()),
corners: Some(CornerRadius::default()),
blur: Some(false),
},
move || SctkLayerSurfaceSettings {
id,
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>> {
@ -383,7 +409,7 @@ impl App {
// TODO: If compositor supports overlap notify, also use that?
// Or otherwise verify the panel is actually running.
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(
Some((output.width as u32, output.height as u32)),
None,
@ -421,6 +447,72 @@ impl App {
}
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 {
@ -429,7 +521,9 @@ impl Application for App {
type Flags = Args;
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 {
core,
@ -443,6 +537,27 @@ impl Application for App {
fn update(&mut self, message: Msg) -> Task<cosmic::Action<Msg>> {
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 => {
self.drag_surface = None;
}
@ -501,6 +616,8 @@ impl Application for App {
if self.layer_surfaces.remove(&id).is_none() {
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) => {
workspaces.sort_by(|(_, w1), (_, w2)| w1.coordinates.cmp(&w2.coordinates));
let old_workspaces = mem::take(&mut self.workspaces);
let mut new_active = Vec::new();
for (outputs, workspace) in workspaces {
// XXX efficiency
let old_workspace = old_workspaces.for_handle(&workspace.handle);
@ -520,15 +638,25 @@ impl Application for App {
let dnd_source_id = old_workspace
.map_or_else(iced::id::Id::unique, |w| w.dnd_source_id.clone());
self.workspaces.0.push(Workspace {
let w = Workspace {
info: workspace,
outputs,
img,
has_cursor,
dnd_source_id,
});
};
if w.is_active() {
new_active.push(w.handle().clone());
}
self.workspaces.0.push(w);
}
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) => {
log::debug!("New toplevel: {info:?}");
@ -543,6 +671,7 @@ impl Application for App {
handle,
info,
img: None,
pending_move: None,
});
// Close workspaces view if a window spawns while open
#[cfg(not(feature = "mock-backend"))]
@ -552,6 +681,8 @@ impl Application for App {
return icon_task;
}
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) {
let mut task = Task::none();
if toplevel.info.app_id != info.app_id {
@ -562,15 +693,70 @@ impl Application for 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;
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) => {
if let Some(idx) = self.toplevels.0.iter().position(|x| x.handle == handle)
{
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) => {
//println!("Workspace capture");
@ -604,7 +790,11 @@ impl Application for App {
{
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) => {
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) => {
// TODO confirmation?
self.send_wayland_cmd(backend::Cmd::CloseToplevel(toplevel_handle));
}
Msg::StartDrag(drag_surface) => {
// if let DragSurface::Toplevel(t) = &drag_surface {}
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) => {
self.drop_target = Some(drop_target);
@ -640,6 +855,21 @@ impl Application for App {
if self.drop_target == Some(drop_target) {
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) => {
if let Some((DragSurface::Toplevel(handle), _)) = &self.drag_surface {
@ -648,11 +878,34 @@ impl Application for App {
DropTarget::WorkspaceSidebarEntry(workspace, output)
| DropTarget::OutputToplevels(workspace, output),
) => {
self.send_wayland_cmd(backend::Cmd::MoveToplevelToWorkspace(
handle.clone(),
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(
handle.clone(),
workspace,
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(
DropTarget::WorkspacesBar(_)
@ -879,6 +1132,7 @@ impl Application for App {
config_subscription,
comp_config_subscription,
bg_subscription,
rectangle_tracker_subscription(self.sub_ctr).map(|update| Msg::Rectangle(update.1)),
];
if let Some(conn) = self.conn.clone() {
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> {
if let Some(surface) = self.layer_surfaces.get(&id) {
return view::layer_surface(self, surface);
if let Some((surface, rectangle_track)) = self
.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);
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::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_protocols::ext::workspace::v1::client::ext_workspace_handle_v1;
use cosmic::iced::advanced::layout::flex::Axis;
use cosmic::iced::clipboard::mime::{AllowedMimeTypes, AsMimeTypes};
use cosmic::iced::core::Shadow;
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::widget::{column, row};
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 std::collections::HashSet;
use crate::backend::{self, CaptureImage};
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>(
drag_content: D,
@ -69,6 +70,8 @@ where
pub(crate) fn layer_surface<'a>(
app: &'a App,
surface: &'a LayerSurface,
window_id: window::Id,
rectangle_track: &rectangle_tracker::RectangleTracker<RectId>,
) -> cosmic::Element<'a, Msg> {
let mut drag_toplevel = None;
let mut drag_workspace = None;
@ -89,6 +92,7 @@ pub(crate) fn layer_surface<'a>(
.flat_map(|t| &t.info.workspace)
.collect::<HashSet<_>>();
let layout = app.conf.workspace_config.workspace_layout;
// track this rectangle
let sidebar = workspaces_sidebar(
app.workspaces.for_output(&surface.output),
&workspaces_with_toplevels,
@ -96,6 +100,8 @@ pub(crate) fn layer_surface<'a>(
layout,
app.drop_target.as_ref(),
drag_workspace,
window_id,
rectangle_track,
);
let toplevels = toplevel_previews(
app.toplevels.0.iter().filter(|i| {
@ -111,6 +117,8 @@ pub(crate) fn layer_surface<'a>(
}),
layout,
drag_toplevel,
window_id,
rectangle_track,
);
// TODO multiple active workspaces? Not currently supported by cosmic.
let first_active_workspace = app
@ -146,6 +154,7 @@ pub(crate) fn layer_surface<'a>(
let container = widget::container(container).padding(panel_regions);
let output = surface.output.clone();
widget::mouse_area(container)
.on_scroll(move |delta| Msg::OnScroll(output.clone(), delta))
.into()
@ -162,12 +171,12 @@ fn pin_button_style(theme: &cosmic::Theme, is_pinned: bool) -> cosmic::widget::b
let bg_color = if is_pinned {
theme.cosmic().accent.base.into()
} else {
theme.cosmic().primary(false).base.into()
theme.cosmic().primary(theme.transparent).base.into()
};
let icon_color = if is_pinned {
theme.cosmic().accent.on.into()
} else {
theme.cosmic().primary(false).on.into()
theme.cosmic().primary(theme.transparent).on.into()
};
cosmic::widget::button::Style {
icon_color: Some(icon_color),
@ -322,6 +331,7 @@ fn workspace_item(
{
button = button.on_press(Msg::ActivateWorkspace(workspace.handle().clone()));
}
button.into()
}
@ -420,6 +430,8 @@ fn workspaces_sidebar<'a>(
layout: WorkspaceLayout,
drop_target: Option<&DropTarget>,
drag_workspace: Option<&'a backend::ExtWorkspaceHandleV1>,
window_id: window::Id,
rectangle_track: &rectangle_tracker::RectangleTracker<RectId>,
) -> cosmic::Element<'a, Msg> {
let mut sidebar_entries = Vec::new();
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(
widget::container(sidebar_entries_container)
.width(width)
.height(height)
.class(cosmic::theme::Container::custom(|theme| {
cosmic::iced::widget::container::Style {
text_color: Some(theme.cosmic().on_bg_color().into()),
icon_color: Some(theme.cosmic().on_bg_color().into()),
background: Some(iced::Color::from(theme.cosmic().background(false).base).into()),
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,
}
})),
rectangle_track.container(
RectId {
id: window_id,
toplevel_id: None,
widget_id: None,
workspaces_id: None,
},
widget::container(sidebar_entries_container)
.width(width)
.height(height)
.class(cosmic::theme::Container::custom(|theme| {
cosmic::iced::widget::container::Style {
text_color: Some(theme.cosmic().on_bg_color().into()),
icon_color: Some(theme.cosmic().on_bg_color().into()),
background: Some(
iced::Color::from(theme.cosmic().background(theme.transparent).base)
.into(),
),
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)
.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 {
space_xxs, space_s, ..
} = cosmic::theme::active().cosmic().spacing;
@ -524,6 +552,8 @@ fn toplevel_preview(toplevel: &Toplevel, is_being_dragged: bool) -> cosmic::Elem
.align_y(Alignment::Center);
let title = row![
// TODO tracker for each of these buttons?
// So that they can be blurred individually like the sidebar?
widget::button::custom(label)
.on_press(Msg::ActivateToplevel(toplevel.handle.clone()))
.class(cosmic::theme::Button::Icon)
@ -549,17 +579,30 @@ fn toplevel_preview(toplevel: &Toplevel, is_being_dragged: bool) -> cosmic::Elem
.spacing(8)
.padding([0, 0, 2, 0])
.align_y(Alignment::Center);
let alpha = if is_being_dragged { 0.5 } else { 1.0 };
let preview = widget::button::custom(capture_image(toplevel.img.as_ref(), alpha))
.selected(
toplevel
.info
.state
.contains(&zcosmic_toplevel_handle_v1::State::Activated),
)
.class(cosmic::theme::Button::Image)
.on_press(Msg::ActivateToplevel(toplevel.handle.clone()));
let content = capture_image(toplevel.img.as_ref(), alpha);
let preview = widget::button::custom(if toplevel.pending_move.is_some() || is_being_dragged {
Element::from(content)
} else {
Element::from(rectangle_track.container(
RectId {
id: window_id,
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(
vec![title.into(), preview.into()],
@ -570,23 +613,26 @@ fn toplevel_preview(toplevel: &Toplevel, is_being_dragged: bool) -> cosmic::Elem
.into()
}
fn toplevel_previews_entry(
toplevel: &Toplevel,
fn toplevel_previews_entry<'a>(
toplevel: &'a Toplevel,
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
// shown.
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,
);
let toplevel2 = toplevel.clone();
let track = rectangle_track.clone();
dnd_source_with_drag_surface(
DragToplevel {},
DragSurface::Toplevel(toplevel.handle.clone()),
None,
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>,
layout: WorkspaceLayout,
drag_toplevel: Option<&'a backend::ExtForeignToplevelHandleV1>,
window_id: window::Id,
rectangle_track: &rectangle_tracker::RectangleTracker<RectId>,
) -> cosmic::Element<'a, Msg> {
let (width, height) = match layout {
WorkspaceLayout::Vertical => (Length::FillPortion(4), Length::Fill),
WorkspaceLayout::Horizontal => (Length::Fill, Length::FillPortion(4)),
};
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();
//row(entries)
widget::mouse_area(