feat: app tray overflow menus
This commit is contained in:
parent
0571c97b86
commit
f46f50e8e2
4 changed files with 924 additions and 512 deletions
886
Cargo.lock
generated
886
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
18
Cargo.toml
18
Cargo.toml
|
|
@ -26,11 +26,18 @@ cctk = { git = "https://github.com/pop-os/cosmic-protocols", package = "cosmic-c
|
||||||
cosmic-protocols = { git = "https://github.com/pop-os/cosmic-protocols", default-features = false, features = [
|
cosmic-protocols = { git = "https://github.com/pop-os/cosmic-protocols", default-features = false, features = [
|
||||||
"client",
|
"client",
|
||||||
], rev = "c8d3a1c" }
|
], rev = "c8d3a1c" }
|
||||||
cosmic-settings-subscriptions = { git = "https://github.com/pop-os/cosmic-settings-subscriptions" }
|
# cosmic-settings-subscriptions = { git = "https://github.com/pop-os/cosmic-settings-subscriptions" }
|
||||||
cosmic-time = { git = "https://github.com/pop-os/cosmic-time", default-features = false, features = [
|
cosmic-settings-subscriptions = { path = "../cosmic-settings-subscriptions" }
|
||||||
|
# cosmic-time = { git = "https://github.com/pop-os/cosmic-time", default-features = false, features = [
|
||||||
|
# "libcosmic",
|
||||||
|
# "once_cell",
|
||||||
|
# ] }
|
||||||
|
cosmic-time = { path = "../cosmic-time", default-features = false, features = [
|
||||||
"libcosmic",
|
"libcosmic",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
] }
|
] }
|
||||||
|
|
||||||
|
|
||||||
futures = "0.3"
|
futures = "0.3"
|
||||||
futures-util = "0.3"
|
futures-util = "0.3"
|
||||||
i18n-embed = { version = "0.14.1", features = [
|
i18n-embed = { version = "0.14.1", features = [
|
||||||
|
|
@ -38,7 +45,7 @@ i18n-embed = { version = "0.14.1", features = [
|
||||||
"desktop-requester",
|
"desktop-requester",
|
||||||
] }
|
] }
|
||||||
i18n-embed-fl = "0.8"
|
i18n-embed-fl = "0.8"
|
||||||
libcosmic = { git = "https://github.com/pop-os/libcosmic", default-features = false, features = [
|
libcosmic = { path = "../libcosmic", features = [
|
||||||
"applet",
|
"applet",
|
||||||
"applet-token",
|
"applet-token",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
|
@ -76,3 +83,8 @@ sctk = { git = "https://github.com/smithay/client-toolkit//", package = "smithay
|
||||||
libcosmic = { path = "../libcosmic" }
|
libcosmic = { path = "../libcosmic" }
|
||||||
cosmic-config = { path = "../libcosmic/cosmic-config" }
|
cosmic-config = { path = "../libcosmic/cosmic-config" }
|
||||||
cosmic-theme = { path = "../libcosmic/cosmic-theme" }
|
cosmic-theme = { path = "../libcosmic/cosmic-theme" }
|
||||||
|
iced = { path = "../libcosmic/iced" }
|
||||||
|
cosmic-time = { path = "../cosmic-time", default-features = false, features = [
|
||||||
|
"libcosmic",
|
||||||
|
"once_cell",
|
||||||
|
] }
|
||||||
|
|
|
||||||
|
|
@ -13,5 +13,5 @@ X-CosmicApplet=true
|
||||||
X-MinimizeApplet=5
|
X-MinimizeApplet=5
|
||||||
# Indicate that the applet should be shrunk if the panel is too small to fit it
|
# Indicate that the applet should be shrunk if the panel is too small to fit it
|
||||||
X-OverflowPriority=100
|
X-OverflowPriority=100
|
||||||
X-OverflowMinSize=4
|
X-OverflowMinSize=5
|
||||||
X-HostWaylandDisplay=true
|
X-HostWaylandDisplay=true
|
||||||
|
|
|
||||||
|
|
@ -24,8 +24,8 @@ use cosmic::{
|
||||||
},
|
},
|
||||||
cosmic_config::{Config, CosmicConfigEntry},
|
cosmic_config::{Config, CosmicConfigEntry},
|
||||||
desktop::IconSource,
|
desktop::IconSource,
|
||||||
iced,
|
|
||||||
iced::{
|
iced::{
|
||||||
|
self,
|
||||||
event::listen_with,
|
event::listen_with,
|
||||||
wayland::{
|
wayland::{
|
||||||
actions::data_device::{DataFromMimeType, DndIcon},
|
actions::data_device::{DataFromMimeType, DndIcon},
|
||||||
|
|
@ -113,13 +113,30 @@ impl AppletIconData {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type DockItemId = u32;
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||||
|
pub enum DockItemId {
|
||||||
|
Item(u32),
|
||||||
|
ActiveOverflow,
|
||||||
|
FavoritesOverflow,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<u32> for DockItemId {
|
||||||
|
fn from(id: u32) -> Self {
|
||||||
|
DockItemId::Item(id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<usize> for DockItemId {
|
||||||
|
fn from(id: usize) -> Self {
|
||||||
|
DockItemId::Item(id as u32)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
struct DockItem {
|
struct DockItem {
|
||||||
// ID used internally in the applet. Each dock item
|
// ID used internally in the applet. Each dock item
|
||||||
// have an unique id
|
// have an unique id
|
||||||
id: DockItemId,
|
id: u32,
|
||||||
toplevels: Vec<(ZcosmicToplevelHandleV1, ToplevelInfo, Option<WaylandImage>)>,
|
toplevels: Vec<(ZcosmicToplevelHandleV1, ToplevelInfo, Option<WaylandImage>)>,
|
||||||
// Information found in the .desktop file
|
// Information found in the .desktop file
|
||||||
desktop_info: DesktopEntry<'static>,
|
desktop_info: DesktopEntry<'static>,
|
||||||
|
|
@ -148,12 +165,13 @@ impl DockItem {
|
||||||
fn as_icon(
|
fn as_icon(
|
||||||
&self,
|
&self,
|
||||||
applet: &Context,
|
applet: &Context,
|
||||||
rectangle_tracker: Option<&RectangleTracker<u32>>,
|
rectangle_tracker: Option<&RectangleTracker<DockItemId>>,
|
||||||
interaction_enabled: bool,
|
interaction_enabled: bool,
|
||||||
dnd_source_enabled: bool,
|
dnd_source_enabled: bool,
|
||||||
gpus: Option<&[Gpu]>,
|
gpus: Option<&[Gpu]>,
|
||||||
is_focused: bool,
|
is_focused: bool,
|
||||||
dot_border_radius: [f32; 4],
|
dot_border_radius: [f32; 4],
|
||||||
|
window_id: window::Id,
|
||||||
) -> Element<'_, Message> {
|
) -> Element<'_, Message> {
|
||||||
let Self {
|
let Self {
|
||||||
toplevels,
|
toplevels,
|
||||||
|
|
@ -258,14 +276,15 @@ impl DockItem {
|
||||||
} else if toplevels.len() == 1 {
|
} else if toplevels.len() == 1 {
|
||||||
toplevels.first().map(|t| Message::Toggle(t.0.clone()))
|
toplevels.first().map(|t| Message::Toggle(t.0.clone()))
|
||||||
} else {
|
} else {
|
||||||
Some(Message::TopLevelListPopup(*id))
|
Some(Message::TopLevelListPopup((*id).into(), window_id))
|
||||||
})
|
})
|
||||||
.width(Length::Shrink)
|
.width(Length::Shrink)
|
||||||
.height(Length::Shrink),
|
.height(Length::Shrink),
|
||||||
)
|
)
|
||||||
.on_right_release(Message::Popup(*id))
|
.on_right_release(Message::Popup((*id).into(), window_id))
|
||||||
.on_middle_release({
|
.on_middle_release({
|
||||||
launch_on_preferred_gpu(desktop_info, gpus).unwrap_or_else(|| Message::Popup(*id))
|
launch_on_preferred_gpu(desktop_info, gpus)
|
||||||
|
.unwrap_or_else(|| Message::Popup((*id).into(), window_id))
|
||||||
})
|
})
|
||||||
.into()
|
.into()
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -275,7 +294,7 @@ impl DockItem {
|
||||||
let icon_button = if dnd_source_enabled && interaction_enabled {
|
let icon_button = if dnd_source_enabled && interaction_enabled {
|
||||||
dnd_source(icon_button)
|
dnd_source(icon_button)
|
||||||
.drag_threshold(16.)
|
.drag_threshold(16.)
|
||||||
.on_drag(|_, _| Message::StartDrag(*id))
|
.on_drag(|_, _| Message::StartDrag((*id).into()))
|
||||||
.on_cancelled(Message::DragFinished)
|
.on_cancelled(Message::DragFinished)
|
||||||
.on_finished(Message::DragFinished)
|
.on_finished(Message::DragFinished)
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -283,7 +302,7 @@ impl DockItem {
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(tracker) = rectangle_tracker {
|
if let Some(tracker) = rectangle_tracker {
|
||||||
tracker.container(*id, icon_button).into()
|
tracker.container((*id).into(), icon_button).into()
|
||||||
} else {
|
} else {
|
||||||
icon_button.into()
|
icon_button.into()
|
||||||
}
|
}
|
||||||
|
|
@ -296,10 +315,18 @@ struct DndOffer {
|
||||||
preview_index: usize,
|
preview_index: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Popup {
|
||||||
|
parent: window::Id,
|
||||||
|
id: window::Id,
|
||||||
|
dock_item: DockItem,
|
||||||
|
popup_type: PopupType,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Default)]
|
#[derive(Clone, Default)]
|
||||||
struct CosmicAppList {
|
struct CosmicAppList {
|
||||||
core: cosmic::app::Core,
|
core: cosmic::app::Core,
|
||||||
popup: Option<(window::Id, DockItemId, PopupType)>,
|
popup: Option<Popup>,
|
||||||
subscription_ctr: u32,
|
subscription_ctr: u32,
|
||||||
item_ctr: u32,
|
item_ctr: u32,
|
||||||
active_list: Vec<DockItem>,
|
active_list: Vec<DockItem>,
|
||||||
|
|
@ -316,9 +343,11 @@ struct CosmicAppList {
|
||||||
active_workspaces: Vec<ZcosmicWorkspaceHandleV1>,
|
active_workspaces: Vec<ZcosmicWorkspaceHandleV1>,
|
||||||
output_list: HashMap<WlOutput, OutputInfo>,
|
output_list: HashMap<WlOutput, OutputInfo>,
|
||||||
locales: Vec<String>,
|
locales: Vec<String>,
|
||||||
|
overflow_favorites_popup: Option<window::Id>,
|
||||||
|
overflow_active_popup: Option<window::Id>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub enum PopupType {
|
pub enum PopupType {
|
||||||
RightClickMenu,
|
RightClickMenu,
|
||||||
TopLevelList,
|
TopLevelList,
|
||||||
|
|
@ -328,10 +357,10 @@ pub enum PopupType {
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
enum Message {
|
enum Message {
|
||||||
Wayland(WaylandUpdate),
|
Wayland(WaylandUpdate),
|
||||||
PinApp(DockItemId),
|
PinApp(u32),
|
||||||
UnpinApp(DockItemId),
|
UnpinApp(u32),
|
||||||
Popup(DockItemId),
|
Popup(u32, window::Id),
|
||||||
TopLevelListPopup(DockItemId),
|
TopLevelListPopup(u32, window::Id),
|
||||||
GpuRequest(Option<Vec<Gpu>>),
|
GpuRequest(Option<Vec<Gpu>>),
|
||||||
CloseRequested(window::Id),
|
CloseRequested(window::Id),
|
||||||
ClosePopup,
|
ClosePopup,
|
||||||
|
|
@ -343,7 +372,7 @@ enum Message {
|
||||||
NewSeat(WlSeat),
|
NewSeat(WlSeat),
|
||||||
RemovedSeat(WlSeat),
|
RemovedSeat(WlSeat),
|
||||||
Rectangle(RectangleUpdate<DockItemId>),
|
Rectangle(RectangleUpdate<DockItemId>),
|
||||||
StartDrag(DockItemId),
|
StartDrag(u32),
|
||||||
DragFinished,
|
DragFinished,
|
||||||
DndEnter(f32, f32),
|
DndEnter(f32, f32),
|
||||||
DndExit,
|
DndExit,
|
||||||
|
|
@ -354,6 +383,8 @@ enum Message {
|
||||||
StopListeningForDnd,
|
StopListeningForDnd,
|
||||||
IncrementSubscriptionCtr,
|
IncrementSubscriptionCtr,
|
||||||
ConfigUpdated(AppListConfig),
|
ConfigUpdated(AppListConfig),
|
||||||
|
OpenFavorites,
|
||||||
|
OpenActive,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn index_in_list(
|
fn index_in_list(
|
||||||
|
|
@ -643,9 +674,23 @@ impl cosmic::Application for CosmicAppList {
|
||||||
message: Self::Message,
|
message: Self::Message,
|
||||||
) -> iced::Command<cosmic::app::Message<Self::Message>> {
|
) -> iced::Command<cosmic::app::Message<Self::Message>> {
|
||||||
match message {
|
match message {
|
||||||
Message::Popup(id) => {
|
Message::Popup(id, parent_window_id) => {
|
||||||
if let Some((popup_id, _toplevel, _)) = self.popup.take() {
|
if let Some(Popup {
|
||||||
return destroy_popup(popup_id);
|
parent,
|
||||||
|
id: popup_id,
|
||||||
|
..
|
||||||
|
}) = self.popup.take()
|
||||||
|
{
|
||||||
|
if parent == parent_window_id {
|
||||||
|
return destroy_popup(popup_id);
|
||||||
|
} else {
|
||||||
|
self.overflow_active_popup = None;
|
||||||
|
self.overflow_favorites_popup = None;
|
||||||
|
return Command::batch(vec![
|
||||||
|
destroy_popup(popup_id),
|
||||||
|
destroy_popup(parent),
|
||||||
|
]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if let Some(toplevel_group) = self
|
if let Some(toplevel_group) = self
|
||||||
.active_list
|
.active_list
|
||||||
|
|
@ -653,16 +698,25 @@ impl cosmic::Application for CosmicAppList {
|
||||||
.chain(self.pinned_list.iter())
|
.chain(self.pinned_list.iter())
|
||||||
.find(|t| t.id == id)
|
.find(|t| t.id == id)
|
||||||
{
|
{
|
||||||
let rectangle = match self.rectangles.get(&toplevel_group.id) {
|
let rectangle = match self.rectangles.get(&toplevel_group.id.into()) {
|
||||||
Some(r) => r,
|
Some(r) => r,
|
||||||
None => return Command::none(),
|
None => {
|
||||||
|
tracing::error!("No rectangle found for toplevel group");
|
||||||
|
return Command::none();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
dbg!("create popup2");
|
||||||
|
|
||||||
let new_id = window::Id::unique();
|
let new_id = window::Id::unique();
|
||||||
self.popup = Some((new_id, toplevel_group.id, PopupType::RightClickMenu));
|
self.popup = Some(Popup {
|
||||||
|
parent: parent_window_id,
|
||||||
|
id: new_id,
|
||||||
|
dock_item: toplevel_group.clone(),
|
||||||
|
popup_type: PopupType::RightClickMenu,
|
||||||
|
});
|
||||||
|
|
||||||
let mut popup_settings = self.core.applet.get_popup_settings(
|
let mut popup_settings = self.core.applet.get_popup_settings(
|
||||||
window::Id::MAIN,
|
parent_window_id,
|
||||||
new_id,
|
new_id,
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
|
|
@ -687,9 +741,23 @@ impl cosmic::Application for CosmicAppList {
|
||||||
return Command::batch([gpu_update, get_popup(popup_settings)]);
|
return Command::batch([gpu_update, get_popup(popup_settings)]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Message::TopLevelListPopup(id) => {
|
Message::TopLevelListPopup(id, parent_window_id) => {
|
||||||
if let Some((popup_id, _toplevel, _)) = self.popup.take() {
|
if let Some(Popup {
|
||||||
return destroy_popup(popup_id);
|
parent,
|
||||||
|
id: popup_id,
|
||||||
|
..
|
||||||
|
}) = self.popup.take()
|
||||||
|
{
|
||||||
|
if parent == parent_window_id {
|
||||||
|
return destroy_popup(popup_id);
|
||||||
|
} else {
|
||||||
|
self.overflow_active_popup = None;
|
||||||
|
self.overflow_favorites_popup = None;
|
||||||
|
return Command::batch(vec![
|
||||||
|
destroy_popup(popup_id),
|
||||||
|
destroy_popup(parent),
|
||||||
|
]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if let Some(toplevel_group) = self
|
if let Some(toplevel_group) = self
|
||||||
.active_list
|
.active_list
|
||||||
|
|
@ -703,16 +771,21 @@ impl cosmic::Application for CosmicAppList {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let rectangle = match self.rectangles.get(&toplevel_group.id) {
|
let rectangle = match self.rectangles.get(&toplevel_group.id.into()) {
|
||||||
Some(r) => r,
|
Some(r) => r,
|
||||||
None => return Command::none(),
|
None => return Command::none(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let new_id = window::Id::unique();
|
let new_id = window::Id::unique();
|
||||||
self.popup = Some((new_id, toplevel_group.id, PopupType::TopLevelList));
|
self.popup = Some(Popup {
|
||||||
|
parent: parent_window_id,
|
||||||
|
id: new_id,
|
||||||
|
dock_item: toplevel_group.clone(),
|
||||||
|
popup_type: PopupType::TopLevelList,
|
||||||
|
});
|
||||||
|
|
||||||
let mut popup_settings = self.core.applet.get_popup_settings(
|
let mut popup_settings = self.core.applet.get_popup_settings(
|
||||||
window::Id::MAIN,
|
parent_window_id,
|
||||||
new_id,
|
new_id,
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
|
|
@ -764,7 +837,7 @@ impl cosmic::Application for CosmicAppList {
|
||||||
);
|
);
|
||||||
self.pinned_list.push(entry);
|
self.pinned_list.push(entry);
|
||||||
}
|
}
|
||||||
if let Some((popup_id, _toplevel, _)) = self.popup.take() {
|
if let Some(Popup { id: popup_id, .. }) = self.popup.take() {
|
||||||
return destroy_popup(popup_id);
|
return destroy_popup(popup_id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -777,12 +850,12 @@ impl cosmic::Application for CosmicAppList {
|
||||||
&Config::new(APP_ID, AppListConfig::VERSION).unwrap(),
|
&Config::new(APP_ID, AppListConfig::VERSION).unwrap(),
|
||||||
);
|
);
|
||||||
|
|
||||||
self.rectangles.remove(&entry.id);
|
self.rectangles.remove(&entry.id.into());
|
||||||
if !entry.toplevels.is_empty() {
|
if !entry.toplevels.is_empty() {
|
||||||
self.active_list.push(entry);
|
self.active_list.push(entry);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if let Some((popup_id, _toplevel, _)) = self.popup.take() {
|
if let Some(Popup { id: popup_id, .. }) = self.popup.take() {
|
||||||
return destroy_popup(popup_id);
|
return destroy_popup(popup_id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -791,7 +864,7 @@ impl cosmic::Application for CosmicAppList {
|
||||||
let _ = tx.send(WaylandRequest::Toplevel(ToplevelRequest::Activate(handle)));
|
let _ = tx.send(WaylandRequest::Toplevel(ToplevelRequest::Activate(handle)));
|
||||||
}
|
}
|
||||||
if let Some(p) = self.popup.take() {
|
if let Some(p) = self.popup.take() {
|
||||||
return destroy_popup(p.0);
|
return destroy_popup(p.id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Message::Toggle(handle) => {
|
Message::Toggle(handle) => {
|
||||||
|
|
@ -805,7 +878,7 @@ impl cosmic::Application for CosmicAppList {
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
if let Some(p) = self.popup.take() {
|
if let Some(p) = self.popup.take() {
|
||||||
return destroy_popup(p.0);
|
return destroy_popup(p.id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Message::Quit(id) => {
|
Message::Quit(id) => {
|
||||||
|
|
@ -823,7 +896,7 @@ impl cosmic::Application for CosmicAppList {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if let Some((popup_id, _toplevel, _)) = self.popup.take() {
|
if let Some(Popup { id: popup_id, .. }) = self.popup.take() {
|
||||||
return destroy_popup(popup_id);
|
return destroy_popup(popup_id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1155,7 +1228,7 @@ impl cosmic::Application for CosmicAppList {
|
||||||
Message::Ignore => {}
|
Message::Ignore => {}
|
||||||
Message::ClosePopup => {
|
Message::ClosePopup => {
|
||||||
if let Some(p) = self.popup.take() {
|
if let Some(p) = self.popup.take() {
|
||||||
return destroy_popup(p.0);
|
return destroy_popup(p.id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Message::StartListeningForDnd => {
|
Message::StartListeningForDnd => {
|
||||||
|
|
@ -1205,13 +1278,87 @@ impl cosmic::Application for CosmicAppList {
|
||||||
.collect();
|
.collect();
|
||||||
}
|
}
|
||||||
Message::CloseRequested(id) => {
|
Message::CloseRequested(id) => {
|
||||||
if Some(id) == self.popup.as_ref().map(|p| p.0) {
|
if Some(id) == self.popup.as_ref().map(|p| p.id) {
|
||||||
self.popup = None;
|
self.popup = None;
|
||||||
}
|
}
|
||||||
|
if self.overflow_active_popup.is_some_and(|p| p == id) {
|
||||||
|
self.overflow_active_popup = None;
|
||||||
|
}
|
||||||
|
if self.overflow_favorites_popup.is_some_and(|p| p == id) {
|
||||||
|
self.overflow_favorites_popup = None;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Message::GpuRequest(gpus) => {
|
Message::GpuRequest(gpus) => {
|
||||||
self.gpus = gpus;
|
self.gpus = gpus;
|
||||||
}
|
}
|
||||||
|
Message::OpenActive => {
|
||||||
|
let create_new = self.overflow_active_popup.is_none();
|
||||||
|
let mut cmds = vec![self.close_popups()];
|
||||||
|
|
||||||
|
// create a popup with the active list
|
||||||
|
if create_new {
|
||||||
|
let new_id = window::Id::unique();
|
||||||
|
self.overflow_active_popup = Some(new_id);
|
||||||
|
let rectangle = self.rectangles.get(&DockItemId::ActiveOverflow);
|
||||||
|
let mut popup_settings = self.core.applet.get_popup_settings(
|
||||||
|
window::Id::MAIN,
|
||||||
|
new_id,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
if let Some(iced::Rectangle {
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
}) = rectangle
|
||||||
|
{
|
||||||
|
popup_settings.positioner.anchor_rect = iced::Rectangle::<i32> {
|
||||||
|
x: *x as i32,
|
||||||
|
y: *y as i32,
|
||||||
|
width: *width as i32,
|
||||||
|
height: *height as i32,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
cmds.push(get_popup(popup_settings));
|
||||||
|
}
|
||||||
|
return Command::batch(cmds);
|
||||||
|
}
|
||||||
|
Message::OpenFavorites => {
|
||||||
|
let create_new = self.overflow_favorites_popup.is_none();
|
||||||
|
let mut cmds = vec![self.close_popups()];
|
||||||
|
|
||||||
|
// create a popup with the favorites list
|
||||||
|
if create_new {
|
||||||
|
let new_id = window::Id::unique();
|
||||||
|
self.overflow_favorites_popup = Some(new_id);
|
||||||
|
let rectangle = self.rectangles.get(&DockItemId::FavoritesOverflow);
|
||||||
|
let mut popup_settings = self.core.applet.get_popup_settings(
|
||||||
|
window::Id::MAIN,
|
||||||
|
new_id,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
if let Some(iced::Rectangle {
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
}) = rectangle
|
||||||
|
{
|
||||||
|
popup_settings.positioner.anchor_rect = iced::Rectangle::<i32> {
|
||||||
|
x: *x as i32,
|
||||||
|
y: *y as i32,
|
||||||
|
width: *width as i32,
|
||||||
|
height: *height as i32,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
cmds.push(get_popup(popup_settings));
|
||||||
|
}
|
||||||
|
return Command::batch(cmds);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Command::none()
|
Command::none()
|
||||||
|
|
@ -1226,9 +1373,29 @@ impl cosmic::Application for CosmicAppList {
|
||||||
PanelAnchor::Top | PanelAnchor::Bottom => true,
|
PanelAnchor::Top | PanelAnchor::Bottom => true,
|
||||||
PanelAnchor::Left | PanelAnchor::Right => false,
|
PanelAnchor::Left | PanelAnchor::Right => false,
|
||||||
};
|
};
|
||||||
let mut favorites: Vec<_> = self
|
let (favorite_popup_cutoff, active_popup_cutoff) = self.panel_overflow_lengths();
|
||||||
.pinned_list
|
let mut favorite_to_remove = if let Some(cutoff) = favorite_popup_cutoff {
|
||||||
|
if cutoff < self.pinned_list.len() {
|
||||||
|
self.pinned_list.len() - cutoff + 1
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
};
|
||||||
|
let favorites: Vec<_> = (&mut self.pinned_list.iter().rev())
|
||||||
|
.filter(|f| {
|
||||||
|
if favorite_to_remove > 0 && f.toplevels.is_empty() {
|
||||||
|
favorite_to_remove -= 1;
|
||||||
|
false
|
||||||
|
} else {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
let mut favorites: Vec<_> = favorites[favorite_to_remove..]
|
||||||
.iter()
|
.iter()
|
||||||
|
.rev()
|
||||||
.map(|dock_item| {
|
.map(|dock_item| {
|
||||||
dock_item.as_icon(
|
dock_item.as_icon(
|
||||||
&self.core.applet,
|
&self.core.applet,
|
||||||
|
|
@ -1241,10 +1408,34 @@ impl cosmic::Application for CosmicAppList {
|
||||||
.iter()
|
.iter()
|
||||||
.any(|y| focused_item.contains(&y.0)),
|
.any(|y| focused_item.contains(&y.0)),
|
||||||
theme.cosmic().radius_xs(),
|
theme.cosmic().radius_xs(),
|
||||||
|
window::Id::MAIN,
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
|
if favorite_popup_cutoff.is_some() {
|
||||||
|
// button to show more favorites
|
||||||
|
let icon = match self.core.applet.anchor {
|
||||||
|
PanelAnchor::Bottom => "go-up-symbolic",
|
||||||
|
PanelAnchor::Left => "go-next-symbolic",
|
||||||
|
PanelAnchor::Right => "go-previous-symbolic",
|
||||||
|
PanelAnchor::Top => "go-down-symbolic",
|
||||||
|
};
|
||||||
|
let btn = self
|
||||||
|
.core
|
||||||
|
.applet
|
||||||
|
.icon_button(icon)
|
||||||
|
.on_press(Message::OpenFavorites);
|
||||||
|
let btn: Element<_> = if let Some(rectangle_tracker) = self.rectangle_tracker.as_ref() {
|
||||||
|
rectangle_tracker
|
||||||
|
.container(DockItemId::FavoritesOverflow, btn)
|
||||||
|
.into()
|
||||||
|
} else {
|
||||||
|
btn.into()
|
||||||
|
};
|
||||||
|
favorites.push(btn);
|
||||||
|
}
|
||||||
|
|
||||||
if let Some((item, index)) = self
|
if let Some((item, index)) = self
|
||||||
.dnd_offer
|
.dnd_offer
|
||||||
.as_ref()
|
.as_ref()
|
||||||
|
|
@ -1260,6 +1451,7 @@ impl cosmic::Application for CosmicAppList {
|
||||||
self.gpus.as_deref(),
|
self.gpus.as_deref(),
|
||||||
item.toplevels.iter().any(|y| focused_item.contains(&y.0)),
|
item.toplevels.iter().any(|y| focused_item.contains(&y.0)),
|
||||||
dot_radius,
|
dot_radius,
|
||||||
|
window::Id::MAIN,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
} else if self.is_listening_for_dnd && self.pinned_list.is_empty() {
|
} else if self.is_listening_for_dnd && self.pinned_list.is_empty() {
|
||||||
|
|
@ -1274,8 +1466,15 @@ impl cosmic::Application for CosmicAppList {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut active: Vec<_> = self
|
let mut active: Vec<_> = self.active_list[..active_popup_cutoff
|
||||||
.active_list
|
.map(|n| {
|
||||||
|
if n < self.active_list.len() {
|
||||||
|
n.saturating_sub(1)
|
||||||
|
} else {
|
||||||
|
n
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.unwrap_or(self.active_list.len())]
|
||||||
.iter()
|
.iter()
|
||||||
.map(|dock_item| {
|
.map(|dock_item| {
|
||||||
dock_item.as_icon(
|
dock_item.as_icon(
|
||||||
|
|
@ -1289,9 +1488,34 @@ impl cosmic::Application for CosmicAppList {
|
||||||
.iter()
|
.iter()
|
||||||
.any(|y| focused_item.contains(&y.0)),
|
.any(|y| focused_item.contains(&y.0)),
|
||||||
dot_radius,
|
dot_radius,
|
||||||
|
window::Id::MAIN,
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
|
if active_popup_cutoff.is_some_and(|n| n < self.active_list.len()) {
|
||||||
|
// button to show more active
|
||||||
|
let icon = match self.core.applet.anchor {
|
||||||
|
PanelAnchor::Bottom => "go-up-symbolic",
|
||||||
|
PanelAnchor::Left => "go-next-symbolic",
|
||||||
|
PanelAnchor::Right => "go-previous-symbolic",
|
||||||
|
PanelAnchor::Top => "go-down-symbolic",
|
||||||
|
};
|
||||||
|
let btn = self
|
||||||
|
.core
|
||||||
|
.applet
|
||||||
|
.icon_button(icon)
|
||||||
|
.on_press(Message::OpenActive);
|
||||||
|
let btn: Element<_> = if let Some(rectangle_tracker) = self.rectangle_tracker.as_ref() {
|
||||||
|
rectangle_tracker
|
||||||
|
.container(DockItemId::ActiveOverflow, btn)
|
||||||
|
.into()
|
||||||
|
} else {
|
||||||
|
btn.into()
|
||||||
|
};
|
||||||
|
active.push(btn);
|
||||||
|
}
|
||||||
|
|
||||||
let window_size = self.core.applet.configure.as_ref();
|
let window_size = self.core.applet.configure.as_ref();
|
||||||
let max_num = if self.core.applet.is_horizontal() {
|
let max_num = if self.core.applet.is_horizontal() {
|
||||||
let suggested_width = self.core.applet.suggested_size(false).0
|
let suggested_width = self.core.applet.suggested_size(false).0
|
||||||
|
|
@ -1412,12 +1636,18 @@ impl cosmic::Application for CosmicAppList {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn view_window(&self, id: window::Id) -> Element<Message> {
|
fn view_window(&self, id: window::Id) -> Element<Message> {
|
||||||
|
let theme = self.core.system_theme();
|
||||||
|
|
||||||
if let Some((_, item, _)) = self.dnd_source.as_ref().filter(|s| s.0 == id) {
|
if let Some((_, item, _)) = self.dnd_source.as_ref().filter(|s| s.0 == id) {
|
||||||
IconSource::from_unknown(item.desktop_info.icon().unwrap_or_default())
|
IconSource::from_unknown(item.desktop_info.icon().unwrap_or_default())
|
||||||
.as_cosmic_icon()
|
.as_cosmic_icon()
|
||||||
.size(self.core.applet.suggested_size(false).0)
|
.size(self.core.applet.suggested_size(false).0)
|
||||||
.into()
|
.into()
|
||||||
} else if let Some((_popup_id, id, popup_type)) = self.popup.as_ref().filter(|p| id == p.0)
|
} else if let Some(Popup {
|
||||||
|
dock_item: DockItem { id, .. },
|
||||||
|
popup_type,
|
||||||
|
..
|
||||||
|
}) = self.popup.as_ref().filter(|p| id == p.id)
|
||||||
{
|
{
|
||||||
let (
|
let (
|
||||||
DockItem {
|
DockItem {
|
||||||
|
|
@ -1599,6 +1829,144 @@ impl cosmic::Application for CosmicAppList {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
} else if self
|
||||||
|
.overflow_active_popup
|
||||||
|
.as_ref()
|
||||||
|
.is_some_and(|overflow_id| overflow_id == &id)
|
||||||
|
{
|
||||||
|
let (_favorite_popup_cutoff, active_popup_cutoff) = self.panel_overflow_lengths();
|
||||||
|
|
||||||
|
let focused_item = self.currently_active_toplevel();
|
||||||
|
let dot_radius = theme.cosmic().radius_xs();
|
||||||
|
// show the overflow popup for active list
|
||||||
|
let active: Vec<_> = self.active_list[active_popup_cutoff
|
||||||
|
.map(|n| {
|
||||||
|
if n < self.active_list.len() {
|
||||||
|
n.saturating_sub(1)
|
||||||
|
} else {
|
||||||
|
n - 1
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.unwrap_or(self.active_list.len() - 1)..]
|
||||||
|
.iter()
|
||||||
|
.map(|dock_item| {
|
||||||
|
dock_item.as_icon(
|
||||||
|
&self.core.applet,
|
||||||
|
self.rectangle_tracker.as_ref(),
|
||||||
|
self.popup.is_none(),
|
||||||
|
self.config.enable_drag_source,
|
||||||
|
self.gpus.as_deref(),
|
||||||
|
dock_item
|
||||||
|
.toplevels
|
||||||
|
.iter()
|
||||||
|
.any(|y| focused_item.contains(&y.0)),
|
||||||
|
dot_radius,
|
||||||
|
id,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
let content = match &self.core.applet.anchor {
|
||||||
|
PanelAnchor::Left | PanelAnchor::Right => container(
|
||||||
|
Column::with_children(active)
|
||||||
|
.spacing(4.0)
|
||||||
|
.align_items(Alignment::Center)
|
||||||
|
.width(Length::Shrink)
|
||||||
|
.height(Length::Shrink),
|
||||||
|
),
|
||||||
|
PanelAnchor::Top | PanelAnchor::Bottom => container(
|
||||||
|
Row::with_children(active)
|
||||||
|
.spacing(4.0)
|
||||||
|
.align_items(Alignment::Center)
|
||||||
|
.width(Length::Shrink)
|
||||||
|
.height(Length::Shrink),
|
||||||
|
),
|
||||||
|
};
|
||||||
|
// send clear popup on press content if there is an active popup
|
||||||
|
let content: Element<_> = if self.popup.is_some() {
|
||||||
|
mouse_area(content)
|
||||||
|
.on_right_release(Message::ClosePopup)
|
||||||
|
.on_press(Message::ClosePopup)
|
||||||
|
.into()
|
||||||
|
} else {
|
||||||
|
content.into()
|
||||||
|
};
|
||||||
|
self.core.applet.popup_container(content).into()
|
||||||
|
} else if self
|
||||||
|
.overflow_favorites_popup
|
||||||
|
.as_ref()
|
||||||
|
.is_some_and(|popup_id| popup_id == &id)
|
||||||
|
{
|
||||||
|
let (favorite_popup_cutoff, _active_popup_cutoff) = self.panel_overflow_lengths();
|
||||||
|
|
||||||
|
let focused_item = self.currently_active_toplevel();
|
||||||
|
let dot_radius = theme.cosmic().radius_xs();
|
||||||
|
// show the overflow popup for favorites list
|
||||||
|
let mut favorite_to_remove = if let Some(cutoff) = favorite_popup_cutoff {
|
||||||
|
if cutoff < self.pinned_list.len() {
|
||||||
|
self.pinned_list.len() - cutoff + 1
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
};
|
||||||
|
let mut favorites_extra = Vec::with_capacity(favorite_to_remove);
|
||||||
|
let mut favorites: Vec<_> = (&mut self.pinned_list.iter().rev())
|
||||||
|
.filter(|f| {
|
||||||
|
if favorite_to_remove > 0 && f.toplevels.is_empty() {
|
||||||
|
favorite_to_remove -= 1;
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
favorites_extra.push(*f);
|
||||||
|
false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
favorites.extend(favorites_extra[..favorite_to_remove].into_iter().cloned());
|
||||||
|
let favorites: Vec<_> = favorites
|
||||||
|
.iter()
|
||||||
|
.rev()
|
||||||
|
.map(|dock_item| {
|
||||||
|
dock_item.as_icon(
|
||||||
|
&self.core.applet,
|
||||||
|
self.rectangle_tracker.as_ref(),
|
||||||
|
self.popup.is_none(),
|
||||||
|
self.config.enable_drag_source,
|
||||||
|
self.gpus.as_deref(),
|
||||||
|
dock_item
|
||||||
|
.toplevels
|
||||||
|
.iter()
|
||||||
|
.any(|y| focused_item.contains(&y.0)),
|
||||||
|
dot_radius,
|
||||||
|
id,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
let content = match &self.core.applet.anchor {
|
||||||
|
PanelAnchor::Left | PanelAnchor::Right => container(
|
||||||
|
Column::with_children(favorites)
|
||||||
|
.spacing(4.0)
|
||||||
|
.align_items(Alignment::Center)
|
||||||
|
.width(Length::Shrink)
|
||||||
|
.height(Length::Shrink),
|
||||||
|
),
|
||||||
|
PanelAnchor::Top | PanelAnchor::Bottom => container(
|
||||||
|
Row::with_children(favorites)
|
||||||
|
.spacing(4.0)
|
||||||
|
.align_items(Alignment::Center)
|
||||||
|
.width(Length::Shrink)
|
||||||
|
.height(Length::Shrink),
|
||||||
|
),
|
||||||
|
};
|
||||||
|
let content: Element<_> = if self.popup.is_some() {
|
||||||
|
mouse_area(content)
|
||||||
|
.on_right_release(Message::ClosePopup)
|
||||||
|
.on_press(Message::ClosePopup)
|
||||||
|
.into()
|
||||||
|
} else {
|
||||||
|
content.into()
|
||||||
|
};
|
||||||
|
self.core.applet.popup_container(content).into()
|
||||||
} else {
|
} else {
|
||||||
let suggested = self.core.applet.suggested_size(false);
|
let suggested = self.core.applet.suggested_size(false);
|
||||||
iced::widget::row!()
|
iced::widget::row!()
|
||||||
|
|
@ -1665,6 +2033,84 @@ impl cosmic::Application for CosmicAppList {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CosmicAppList {
|
impl CosmicAppList {
|
||||||
|
/// Close any open popups.
|
||||||
|
fn close_popups(&mut self) -> Command<cosmic::app::Message<Message>> {
|
||||||
|
let mut commands = Vec::new();
|
||||||
|
if let Some(popup) = self.popup.take() {
|
||||||
|
commands.push(destroy_popup(popup.id));
|
||||||
|
}
|
||||||
|
if let Some(popup) = self.overflow_active_popup.take() {
|
||||||
|
commands.push(destroy_popup(popup));
|
||||||
|
}
|
||||||
|
if let Some(popup) = self.overflow_favorites_popup.take() {
|
||||||
|
commands.push(destroy_popup(popup));
|
||||||
|
}
|
||||||
|
Command::batch(commands)
|
||||||
|
}
|
||||||
|
/// Returns the length of the group in the favorite list after which items are displayed in a popup.
|
||||||
|
/// Shrink the favorite list until it only has active windows, or until it fits in the length provided.
|
||||||
|
fn panel_overflow_lengths(&self) -> (Option<usize>, Option<usize>) {
|
||||||
|
let mut favorite_index;
|
||||||
|
let mut active_index = None;
|
||||||
|
let Some(max_major_axis_len) = self.core.applet.configure.as_ref().and_then(|c| {
|
||||||
|
// if we have a configure for width and height, we're in a overflow popup
|
||||||
|
match self.core.applet.anchor {
|
||||||
|
PanelAnchor::Top | PanelAnchor::Bottom => c.new_size.0,
|
||||||
|
PanelAnchor::Left | PanelAnchor::Right => c.new_size.1,
|
||||||
|
}
|
||||||
|
}) else {
|
||||||
|
return (None, active_index);
|
||||||
|
};
|
||||||
|
let mut max_major_axis_len = max_major_axis_len.get();
|
||||||
|
// tracing::error!("{} {}", max_major_axis_len, self.pinned_list.len());
|
||||||
|
// subtract the divider width
|
||||||
|
max_major_axis_len -= 1;
|
||||||
|
let applet_icon = AppletIconData::new(&self.core.applet);
|
||||||
|
|
||||||
|
let button_total_size = self.core.applet.suggested_size(true).0
|
||||||
|
+ self.core.applet.suggested_padding(true) * 2
|
||||||
|
+ applet_icon.icon_spacing as u16;
|
||||||
|
|
||||||
|
let favorite_active_cnt = self
|
||||||
|
.pinned_list
|
||||||
|
.iter()
|
||||||
|
.filter(|t| !t.toplevels.is_empty())
|
||||||
|
.count();
|
||||||
|
|
||||||
|
// initial calculation of favorite_index
|
||||||
|
let btn_count = max_major_axis_len / button_total_size as u32;
|
||||||
|
if btn_count >= self.pinned_list.len() as u32 + self.active_list.len() as u32 {
|
||||||
|
return (None, active_index);
|
||||||
|
} else {
|
||||||
|
favorite_index = (btn_count as usize).min(favorite_active_cnt).max(2);
|
||||||
|
}
|
||||||
|
// tracing::error!(
|
||||||
|
// "{}, {}, {}, {}",
|
||||||
|
// btn_count,
|
||||||
|
// button_total_size,
|
||||||
|
// favorite_active_cnt,
|
||||||
|
// favorite_index
|
||||||
|
// );
|
||||||
|
// calculation of active_index based on favorite_index if there is still not enough space
|
||||||
|
let active_index_max = (btn_count as i32)
|
||||||
|
- (self.pinned_list.len() as i32).saturating_sub(favorite_index as i32);
|
||||||
|
if active_index_max >= self.active_list.len() as i32 {
|
||||||
|
active_index = Some(self.active_list.len());
|
||||||
|
} else {
|
||||||
|
active_index = Some((active_index_max.max(2) as usize).min(self.active_list.len()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// final calculation of favorite_index if there is still not enough space
|
||||||
|
if let Some(active_index) = active_index {
|
||||||
|
let favorite_index_max = (btn_count as i32) - active_index as i32;
|
||||||
|
favorite_index = favorite_index_max.max(2) as usize;
|
||||||
|
} else {
|
||||||
|
favorite_index = (btn_count as usize).min(self.pinned_list.len());
|
||||||
|
}
|
||||||
|
// tracing::error!("{} {} {:?}", btn_count, favorite_index, active_index);
|
||||||
|
return (Some(favorite_index), active_index);
|
||||||
|
}
|
||||||
|
|
||||||
fn currently_active_toplevel(&self) -> Vec<ZcosmicToplevelHandleV1> {
|
fn currently_active_toplevel(&self) -> Vec<ZcosmicToplevelHandleV1> {
|
||||||
if self.active_workspaces.is_empty() {
|
if self.active_workspaces.is_empty() {
|
||||||
return Vec::new();
|
return Vec::new();
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue