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 = [
|
||||
"client",
|
||||
], rev = "c8d3a1c" }
|
||||
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 = { git = "https://github.com/pop-os/cosmic-settings-subscriptions" }
|
||||
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",
|
||||
"once_cell",
|
||||
] }
|
||||
|
||||
|
||||
futures = "0.3"
|
||||
futures-util = "0.3"
|
||||
i18n-embed = { version = "0.14.1", features = [
|
||||
|
|
@ -38,7 +45,7 @@ i18n-embed = { version = "0.14.1", features = [
|
|||
"desktop-requester",
|
||||
] }
|
||||
i18n-embed-fl = "0.8"
|
||||
libcosmic = { git = "https://github.com/pop-os/libcosmic", default-features = false, features = [
|
||||
libcosmic = { path = "../libcosmic", features = [
|
||||
"applet",
|
||||
"applet-token",
|
||||
"tokio",
|
||||
|
|
@ -76,3 +83,8 @@ sctk = { git = "https://github.com/smithay/client-toolkit//", package = "smithay
|
|||
libcosmic = { path = "../libcosmic" }
|
||||
cosmic-config = { path = "../libcosmic/cosmic-config" }
|
||||
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
|
||||
# Indicate that the applet should be shrunk if the panel is too small to fit it
|
||||
X-OverflowPriority=100
|
||||
X-OverflowMinSize=4
|
||||
X-OverflowMinSize=5
|
||||
X-HostWaylandDisplay=true
|
||||
|
|
|
|||
|
|
@ -24,8 +24,8 @@ use cosmic::{
|
|||
},
|
||||
cosmic_config::{Config, CosmicConfigEntry},
|
||||
desktop::IconSource,
|
||||
iced,
|
||||
iced::{
|
||||
self,
|
||||
event::listen_with,
|
||||
wayland::{
|
||||
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)]
|
||||
struct DockItem {
|
||||
// ID used internally in the applet. Each dock item
|
||||
// have an unique id
|
||||
id: DockItemId,
|
||||
id: u32,
|
||||
toplevels: Vec<(ZcosmicToplevelHandleV1, ToplevelInfo, Option<WaylandImage>)>,
|
||||
// Information found in the .desktop file
|
||||
desktop_info: DesktopEntry<'static>,
|
||||
|
|
@ -148,12 +165,13 @@ impl DockItem {
|
|||
fn as_icon(
|
||||
&self,
|
||||
applet: &Context,
|
||||
rectangle_tracker: Option<&RectangleTracker<u32>>,
|
||||
rectangle_tracker: Option<&RectangleTracker<DockItemId>>,
|
||||
interaction_enabled: bool,
|
||||
dnd_source_enabled: bool,
|
||||
gpus: Option<&[Gpu]>,
|
||||
is_focused: bool,
|
||||
dot_border_radius: [f32; 4],
|
||||
window_id: window::Id,
|
||||
) -> Element<'_, Message> {
|
||||
let Self {
|
||||
toplevels,
|
||||
|
|
@ -258,14 +276,15 @@ impl DockItem {
|
|||
} else if toplevels.len() == 1 {
|
||||
toplevels.first().map(|t| Message::Toggle(t.0.clone()))
|
||||
} else {
|
||||
Some(Message::TopLevelListPopup(*id))
|
||||
Some(Message::TopLevelListPopup((*id).into(), window_id))
|
||||
})
|
||||
.width(Length::Shrink)
|
||||
.height(Length::Shrink),
|
||||
)
|
||||
.on_right_release(Message::Popup(*id))
|
||||
.on_right_release(Message::Popup((*id).into(), window_id))
|
||||
.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()
|
||||
} else {
|
||||
|
|
@ -275,7 +294,7 @@ impl DockItem {
|
|||
let icon_button = if dnd_source_enabled && interaction_enabled {
|
||||
dnd_source(icon_button)
|
||||
.drag_threshold(16.)
|
||||
.on_drag(|_, _| Message::StartDrag(*id))
|
||||
.on_drag(|_, _| Message::StartDrag((*id).into()))
|
||||
.on_cancelled(Message::DragFinished)
|
||||
.on_finished(Message::DragFinished)
|
||||
} else {
|
||||
|
|
@ -283,7 +302,7 @@ impl DockItem {
|
|||
};
|
||||
|
||||
if let Some(tracker) = rectangle_tracker {
|
||||
tracker.container(*id, icon_button).into()
|
||||
tracker.container((*id).into(), icon_button).into()
|
||||
} else {
|
||||
icon_button.into()
|
||||
}
|
||||
|
|
@ -296,10 +315,18 @@ struct DndOffer {
|
|||
preview_index: usize,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Popup {
|
||||
parent: window::Id,
|
||||
id: window::Id,
|
||||
dock_item: DockItem,
|
||||
popup_type: PopupType,
|
||||
}
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
struct CosmicAppList {
|
||||
core: cosmic::app::Core,
|
||||
popup: Option<(window::Id, DockItemId, PopupType)>,
|
||||
popup: Option<Popup>,
|
||||
subscription_ctr: u32,
|
||||
item_ctr: u32,
|
||||
active_list: Vec<DockItem>,
|
||||
|
|
@ -316,9 +343,11 @@ struct CosmicAppList {
|
|||
active_workspaces: Vec<ZcosmicWorkspaceHandleV1>,
|
||||
output_list: HashMap<WlOutput, OutputInfo>,
|
||||
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 {
|
||||
RightClickMenu,
|
||||
TopLevelList,
|
||||
|
|
@ -328,10 +357,10 @@ pub enum PopupType {
|
|||
#[derive(Debug, Clone)]
|
||||
enum Message {
|
||||
Wayland(WaylandUpdate),
|
||||
PinApp(DockItemId),
|
||||
UnpinApp(DockItemId),
|
||||
Popup(DockItemId),
|
||||
TopLevelListPopup(DockItemId),
|
||||
PinApp(u32),
|
||||
UnpinApp(u32),
|
||||
Popup(u32, window::Id),
|
||||
TopLevelListPopup(u32, window::Id),
|
||||
GpuRequest(Option<Vec<Gpu>>),
|
||||
CloseRequested(window::Id),
|
||||
ClosePopup,
|
||||
|
|
@ -343,7 +372,7 @@ enum Message {
|
|||
NewSeat(WlSeat),
|
||||
RemovedSeat(WlSeat),
|
||||
Rectangle(RectangleUpdate<DockItemId>),
|
||||
StartDrag(DockItemId),
|
||||
StartDrag(u32),
|
||||
DragFinished,
|
||||
DndEnter(f32, f32),
|
||||
DndExit,
|
||||
|
|
@ -354,6 +383,8 @@ enum Message {
|
|||
StopListeningForDnd,
|
||||
IncrementSubscriptionCtr,
|
||||
ConfigUpdated(AppListConfig),
|
||||
OpenFavorites,
|
||||
OpenActive,
|
||||
}
|
||||
|
||||
fn index_in_list(
|
||||
|
|
@ -643,9 +674,23 @@ impl cosmic::Application for CosmicAppList {
|
|||
message: Self::Message,
|
||||
) -> iced::Command<cosmic::app::Message<Self::Message>> {
|
||||
match message {
|
||||
Message::Popup(id) => {
|
||||
if let Some((popup_id, _toplevel, _)) = self.popup.take() {
|
||||
return destroy_popup(popup_id);
|
||||
Message::Popup(id, parent_window_id) => {
|
||||
if let Some(Popup {
|
||||
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
|
||||
.active_list
|
||||
|
|
@ -653,16 +698,25 @@ impl cosmic::Application for CosmicAppList {
|
|||
.chain(self.pinned_list.iter())
|
||||
.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,
|
||||
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();
|
||||
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(
|
||||
window::Id::MAIN,
|
||||
parent_window_id,
|
||||
new_id,
|
||||
None,
|
||||
None,
|
||||
|
|
@ -687,9 +741,23 @@ impl cosmic::Application for CosmicAppList {
|
|||
return Command::batch([gpu_update, get_popup(popup_settings)]);
|
||||
}
|
||||
}
|
||||
Message::TopLevelListPopup(id) => {
|
||||
if let Some((popup_id, _toplevel, _)) = self.popup.take() {
|
||||
return destroy_popup(popup_id);
|
||||
Message::TopLevelListPopup(id, parent_window_id) => {
|
||||
if let Some(Popup {
|
||||
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
|
||||
.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,
|
||||
None => return Command::none(),
|
||||
};
|
||||
|
||||
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(
|
||||
window::Id::MAIN,
|
||||
parent_window_id,
|
||||
new_id,
|
||||
None,
|
||||
None,
|
||||
|
|
@ -764,7 +837,7 @@ impl cosmic::Application for CosmicAppList {
|
|||
);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
@ -777,12 +850,12 @@ impl cosmic::Application for CosmicAppList {
|
|||
&Config::new(APP_ID, AppListConfig::VERSION).unwrap(),
|
||||
);
|
||||
|
||||
self.rectangles.remove(&entry.id);
|
||||
self.rectangles.remove(&entry.id.into());
|
||||
if !entry.toplevels.is_empty() {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
@ -791,7 +864,7 @@ impl cosmic::Application for CosmicAppList {
|
|||
let _ = tx.send(WaylandRequest::Toplevel(ToplevelRequest::Activate(handle)));
|
||||
}
|
||||
if let Some(p) = self.popup.take() {
|
||||
return destroy_popup(p.0);
|
||||
return destroy_popup(p.id);
|
||||
}
|
||||
}
|
||||
Message::Toggle(handle) => {
|
||||
|
|
@ -805,7 +878,7 @@ impl cosmic::Application for CosmicAppList {
|
|||
));
|
||||
}
|
||||
if let Some(p) = self.popup.take() {
|
||||
return destroy_popup(p.0);
|
||||
return destroy_popup(p.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);
|
||||
}
|
||||
}
|
||||
|
|
@ -1155,7 +1228,7 @@ impl cosmic::Application for CosmicAppList {
|
|||
Message::Ignore => {}
|
||||
Message::ClosePopup => {
|
||||
if let Some(p) = self.popup.take() {
|
||||
return destroy_popup(p.0);
|
||||
return destroy_popup(p.id);
|
||||
}
|
||||
}
|
||||
Message::StartListeningForDnd => {
|
||||
|
|
@ -1205,13 +1278,87 @@ impl cosmic::Application for CosmicAppList {
|
|||
.collect();
|
||||
}
|
||||
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;
|
||||
}
|
||||
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) => {
|
||||
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()
|
||||
|
|
@ -1226,9 +1373,29 @@ impl cosmic::Application for CosmicAppList {
|
|||
PanelAnchor::Top | PanelAnchor::Bottom => true,
|
||||
PanelAnchor::Left | PanelAnchor::Right => false,
|
||||
};
|
||||
let mut favorites: Vec<_> = self
|
||||
.pinned_list
|
||||
let (favorite_popup_cutoff, active_popup_cutoff) = self.panel_overflow_lengths();
|
||||
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()
|
||||
.rev()
|
||||
.map(|dock_item| {
|
||||
dock_item.as_icon(
|
||||
&self.core.applet,
|
||||
|
|
@ -1241,10 +1408,34 @@ impl cosmic::Application for CosmicAppList {
|
|||
.iter()
|
||||
.any(|y| focused_item.contains(&y.0)),
|
||||
theme.cosmic().radius_xs(),
|
||||
window::Id::MAIN,
|
||||
)
|
||||
})
|
||||
.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
|
||||
.dnd_offer
|
||||
.as_ref()
|
||||
|
|
@ -1260,6 +1451,7 @@ impl cosmic::Application for CosmicAppList {
|
|||
self.gpus.as_deref(),
|
||||
item.toplevels.iter().any(|y| focused_item.contains(&y.0)),
|
||||
dot_radius,
|
||||
window::Id::MAIN,
|
||||
),
|
||||
);
|
||||
} 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
|
||||
.active_list
|
||||
let mut active: Vec<_> = self.active_list[..active_popup_cutoff
|
||||
.map(|n| {
|
||||
if n < self.active_list.len() {
|
||||
n.saturating_sub(1)
|
||||
} else {
|
||||
n
|
||||
}
|
||||
})
|
||||
.unwrap_or(self.active_list.len())]
|
||||
.iter()
|
||||
.map(|dock_item| {
|
||||
dock_item.as_icon(
|
||||
|
|
@ -1289,9 +1488,34 @@ impl cosmic::Application for CosmicAppList {
|
|||
.iter()
|
||||
.any(|y| focused_item.contains(&y.0)),
|
||||
dot_radius,
|
||||
window::Id::MAIN,
|
||||
)
|
||||
})
|
||||
.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 max_num = if self.core.applet.is_horizontal() {
|
||||
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> {
|
||||
let theme = self.core.system_theme();
|
||||
|
||||
if let Some((_, item, _)) = self.dnd_source.as_ref().filter(|s| s.0 == id) {
|
||||
IconSource::from_unknown(item.desktop_info.icon().unwrap_or_default())
|
||||
.as_cosmic_icon()
|
||||
.size(self.core.applet.suggested_size(false).0)
|
||||
.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 (
|
||||
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 {
|
||||
let suggested = self.core.applet.suggested_size(false);
|
||||
iced::widget::row!()
|
||||
|
|
@ -1665,6 +2033,84 @@ impl cosmic::Application for 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> {
|
||||
if self.active_workspaces.is_empty() {
|
||||
return Vec::new();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue