2024-05-06 15:39:04 +02:00
|
|
|
// Copyright 2023 System76 <info@system76.com>
|
|
|
|
|
// SPDX-License-Identifier: GPL-3.0-only
|
|
|
|
|
|
2024-03-14 18:47:41 +01:00
|
|
|
mod localize;
|
|
|
|
|
pub(crate) mod wayland_handler;
|
|
|
|
|
pub(crate) mod wayland_subscription;
|
|
|
|
|
pub(crate) mod window_image;
|
|
|
|
|
|
2025-04-02 17:26:07 +02:00
|
|
|
use std::borrow::Cow;
|
|
|
|
|
|
2024-03-14 18:47:41 +01:00
|
|
|
use crate::localize::localize;
|
2024-07-09 15:17:44 +02:00
|
|
|
use cosmic::{
|
2025-08-12 21:38:51 +02:00
|
|
|
Task, app,
|
2024-07-09 15:17:44 +02:00
|
|
|
applet::cosmic_panel_config::PanelAnchor,
|
|
|
|
|
cctk::{
|
|
|
|
|
sctk::reexports::calloop, toplevel_info::ToplevelInfo,
|
2025-02-12 10:42:32 -08:00
|
|
|
wayland_protocols::ext::foreign_toplevel_list::v1::client::ext_foreign_toplevel_handle_v1::ExtForeignToplevelHandleV1,
|
2024-07-09 15:17:44 +02:00
|
|
|
},
|
2025-04-02 17:26:07 +02:00
|
|
|
desktop::fde,
|
2024-07-12 20:26:18 -04:00
|
|
|
iced::{
|
2025-08-12 21:38:51 +02:00
|
|
|
self, Length, Limits, Subscription,
|
2024-10-30 22:51:08 -04:00
|
|
|
id::Id as WidgetId,
|
|
|
|
|
platform_specific::shell::wayland::commands::popup::{destroy_popup, get_popup},
|
2024-07-12 20:26:18 -04:00
|
|
|
widget::text,
|
|
|
|
|
window::{self},
|
|
|
|
|
},
|
2025-03-14 13:14:51 -04:00
|
|
|
surface,
|
2024-10-30 22:51:08 -04:00
|
|
|
widget::{autosize::autosize, mouse_area},
|
2024-07-09 15:17:44 +02:00
|
|
|
};
|
2024-03-14 18:47:41 +01:00
|
|
|
|
2024-10-30 22:51:08 -04:00
|
|
|
use cosmic::iced_widget::{Column, Row};
|
2024-03-14 18:47:41 +01:00
|
|
|
|
2025-08-12 21:38:51 +02:00
|
|
|
use cosmic::{Element, widget::tooltip};
|
2025-08-12 19:59:56 +02:00
|
|
|
use std::sync::LazyLock;
|
2024-03-14 18:47:41 +01:00
|
|
|
use wayland_subscription::{
|
|
|
|
|
ToplevelRequest, ToplevelUpdate, WaylandImage, WaylandRequest, WaylandUpdate,
|
|
|
|
|
};
|
|
|
|
|
|
2025-08-12 19:59:56 +02:00
|
|
|
static AUTOSIZE_MAIN_ID: LazyLock<WidgetId> = LazyLock::new(|| WidgetId::new("autosize-main"));
|
2024-10-30 22:51:08 -04:00
|
|
|
|
2024-03-14 18:47:41 +01:00
|
|
|
pub fn run() -> cosmic::iced::Result {
|
|
|
|
|
localize();
|
2024-10-30 22:51:08 -04:00
|
|
|
cosmic::applet::run::<Minimize>(())
|
2024-03-14 18:47:41 +01:00
|
|
|
}
|
|
|
|
|
|
2025-04-02 17:26:07 +02:00
|
|
|
pub struct App {
|
|
|
|
|
desktop_entry: fde::DesktopEntry,
|
|
|
|
|
name: String,
|
|
|
|
|
icon_source: fde::IconSource,
|
|
|
|
|
toplevel_info: ToplevelInfo,
|
|
|
|
|
wayland_image: Option<WaylandImage>,
|
|
|
|
|
}
|
|
|
|
|
|
2024-03-14 18:47:41 +01:00
|
|
|
#[derive(Default)]
|
|
|
|
|
struct Minimize {
|
|
|
|
|
core: cosmic::app::Core,
|
2025-04-02 17:26:07 +02:00
|
|
|
locales: Vec<String>,
|
|
|
|
|
desktop_entries: Vec<fde::DesktopEntry>,
|
|
|
|
|
apps: Vec<App>,
|
2024-03-14 18:47:41 +01:00
|
|
|
tx: Option<calloop::channel::Sender<WaylandRequest>>,
|
2024-07-12 20:26:18 -04:00
|
|
|
overflow_popup: Option<window::Id>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Minimize {
|
|
|
|
|
fn max_icon_count(&self) -> Option<usize> {
|
|
|
|
|
let mut index = None;
|
2024-10-30 22:51:08 -04:00
|
|
|
let Some(max_major_axis_len) = self.core.applet.suggested_bounds.as_ref().map(|c| {
|
2024-07-12 20:26:18 -04:00
|
|
|
// if we have a configure for width and height, we're in a overflow popup
|
|
|
|
|
match self.core.applet.anchor {
|
2024-10-30 22:51:08 -04:00
|
|
|
PanelAnchor::Top | PanelAnchor::Bottom => c.width as u32,
|
|
|
|
|
PanelAnchor::Left | PanelAnchor::Right => c.height as u32,
|
2024-07-12 20:26:18 -04:00
|
|
|
}
|
|
|
|
|
}) else {
|
|
|
|
|
return index;
|
|
|
|
|
};
|
|
|
|
|
let button_total_size = self.core.applet.suggested_size(true).0
|
2025-10-06 16:35:20 -04:00
|
|
|
+ self.core.applet.suggested_padding(true).0 * 2
|
2024-07-12 20:26:18 -04:00
|
|
|
+ 4;
|
2024-10-30 22:51:08 -04:00
|
|
|
let btn_count = max_major_axis_len / button_total_size as u32;
|
2024-07-12 20:26:18 -04:00
|
|
|
if btn_count >= self.apps.len() as u32 {
|
|
|
|
|
index = None;
|
|
|
|
|
} else {
|
|
|
|
|
index = Some((btn_count as usize).max(2).min(self.apps.len()));
|
|
|
|
|
}
|
|
|
|
|
index
|
|
|
|
|
}
|
2025-04-02 17:26:07 +02:00
|
|
|
|
2025-04-04 03:02:31 +02:00
|
|
|
fn find_new_desktop_entry(&mut self, appid: &str) -> fde::DesktopEntry {
|
2025-04-02 17:26:07 +02:00
|
|
|
let unicase_appid = fde::unicase::Ascii::new(appid);
|
|
|
|
|
|
2025-10-04 10:51:18 +10:00
|
|
|
let de = if let Some(de) = fde::find_app_by_id(&self.desktop_entries, unicase_appid) {
|
|
|
|
|
de
|
|
|
|
|
} else {
|
|
|
|
|
// Update desktop entries in case it was not found.
|
|
|
|
|
self.update_desktop_entries();
|
|
|
|
|
if let Some(appid) = fde::find_app_by_id(&self.desktop_entries, unicase_appid) {
|
|
|
|
|
appid
|
|
|
|
|
} else {
|
|
|
|
|
tracing::warn!(appid, "could not find desktop entry for app");
|
|
|
|
|
let mut entry = fde::DesktopEntry {
|
|
|
|
|
appid: appid.to_owned(),
|
|
|
|
|
groups: Default::default(),
|
|
|
|
|
path: Default::default(),
|
|
|
|
|
ubuntu_gettext_domain: None,
|
|
|
|
|
};
|
|
|
|
|
entry.add_desktop_entry("Name".to_string(), appid.to_owned());
|
|
|
|
|
return entry;
|
2025-04-02 17:26:07 +02:00
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2025-04-04 03:02:31 +02:00
|
|
|
de.clone()
|
2025-04-02 17:26:07 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Cache all desktop entries to use when new apps are added to the dock.
|
|
|
|
|
fn update_desktop_entries(&mut self) {
|
|
|
|
|
self.desktop_entries = fde::Iter::new(fde::default_paths())
|
|
|
|
|
.filter_map(|p| fde::DesktopEntry::from_path(p, Some(&self.locales)).ok())
|
|
|
|
|
.collect::<Vec<_>>();
|
|
|
|
|
}
|
2024-03-14 18:47:41 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, Clone)]
|
|
|
|
|
enum Message {
|
|
|
|
|
Wayland(WaylandUpdate),
|
2025-02-12 10:42:32 -08:00
|
|
|
Activate(ExtForeignToplevelHandleV1),
|
2024-07-12 20:26:18 -04:00
|
|
|
Closed(window::Id),
|
|
|
|
|
OpenOverflowPopup,
|
|
|
|
|
CloseOverflowPopup,
|
2025-03-14 13:14:51 -04:00
|
|
|
Surface(surface::Action),
|
2024-03-14 18:47:41 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl cosmic::Application for Minimize {
|
|
|
|
|
type Message = Message;
|
|
|
|
|
type Executor = cosmic::SingleThreadExecutor;
|
|
|
|
|
type Flags = ();
|
|
|
|
|
const APP_ID: &'static str = "com.system76.CosmicAppletMinimize";
|
|
|
|
|
|
2024-10-30 22:51:08 -04:00
|
|
|
fn init(core: cosmic::app::Core, _flags: ()) -> (Self, app::Task<Message>) {
|
2025-04-02 17:26:07 +02:00
|
|
|
let mut app = Self {
|
|
|
|
|
core,
|
|
|
|
|
locales: fde::get_languages_from_env(),
|
|
|
|
|
..Default::default()
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
app.update_desktop_entries();
|
2025-10-06 16:35:20 -04:00
|
|
|
let t = iced::window::minimize::<cosmic::Action<Message>>(
|
|
|
|
|
app.core.main_window_id().unwrap(),
|
|
|
|
|
true,
|
|
|
|
|
);
|
|
|
|
|
(app, t)
|
2024-03-14 18:47:41 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn core(&self) -> &cosmic::app::Core {
|
|
|
|
|
&self.core
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn core_mut(&mut self) -> &mut cosmic::app::Core {
|
|
|
|
|
&mut self.core
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-31 16:34:59 -04:00
|
|
|
fn style(&self) -> Option<iced::theme::Style> {
|
2024-03-14 18:47:41 +01:00
|
|
|
Some(cosmic::applet::style())
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-30 22:51:08 -04:00
|
|
|
fn update(&mut self, message: Message) -> app::Task<Message> {
|
2024-03-14 18:47:41 +01:00
|
|
|
match message {
|
|
|
|
|
Message::Wayland(update) => match update {
|
|
|
|
|
WaylandUpdate::Init(tx) => {
|
|
|
|
|
self.tx = Some(tx);
|
|
|
|
|
}
|
|
|
|
|
WaylandUpdate::Finished => {
|
|
|
|
|
panic!("Wayland Subscription ended...")
|
|
|
|
|
}
|
|
|
|
|
WaylandUpdate::Toplevel(t) => match t {
|
2025-04-02 17:26:07 +02:00
|
|
|
ToplevelUpdate::Add(toplevel_info) | ToplevelUpdate::Update(toplevel_info) => {
|
|
|
|
|
// Temporarily take ownership to appease the borrow checker.
|
|
|
|
|
let mut apps = std::mem::take(&mut self.apps);
|
|
|
|
|
|
2025-10-05 16:36:59 +10:00
|
|
|
if let Some(pos) = apps.iter().position(|a| {
|
2025-04-02 17:26:07 +02:00
|
|
|
a.toplevel_info.foreign_toplevel == toplevel_info.foreign_toplevel
|
|
|
|
|
}) {
|
|
|
|
|
if apps[pos].toplevel_info.app_id != toplevel_info.app_id {
|
|
|
|
|
apps[pos].desktop_entry =
|
2025-04-04 03:02:31 +02:00
|
|
|
self.find_new_desktop_entry(&toplevel_info.app_id);
|
|
|
|
|
apps[pos].icon_source = fde::IconSource::from_unknown(
|
|
|
|
|
apps[pos]
|
|
|
|
|
.desktop_entry
|
|
|
|
|
.icon()
|
|
|
|
|
.unwrap_or(&apps[pos].desktop_entry.appid),
|
2025-10-04 10:51:18 +10:00
|
|
|
);
|
2024-03-14 18:47:41 +01:00
|
|
|
}
|
2025-04-02 17:26:07 +02:00
|
|
|
apps[pos].toplevel_info = toplevel_info;
|
2024-03-14 18:47:41 +01:00
|
|
|
} else {
|
2025-04-04 03:02:31 +02:00
|
|
|
let desktop_entry = self.find_new_desktop_entry(&toplevel_info.app_id);
|
2025-04-02 17:26:07 +02:00
|
|
|
|
|
|
|
|
apps.push(App {
|
|
|
|
|
name: desktop_entry
|
|
|
|
|
.full_name(&self.locales)
|
|
|
|
|
.unwrap_or(Cow::Borrowed(&desktop_entry.appid))
|
2025-10-05 16:36:59 +10:00
|
|
|
.into_owned(),
|
2025-04-02 17:26:07 +02:00
|
|
|
icon_source: fde::IconSource::from_unknown(
|
|
|
|
|
desktop_entry.icon().unwrap_or(&desktop_entry.appid),
|
|
|
|
|
),
|
|
|
|
|
desktop_entry,
|
|
|
|
|
toplevel_info,
|
|
|
|
|
wayland_image: None,
|
|
|
|
|
});
|
2024-03-14 18:47:41 +01:00
|
|
|
}
|
2025-04-02 17:26:07 +02:00
|
|
|
|
|
|
|
|
self.apps = apps;
|
2025-10-06 16:35:20 -04:00
|
|
|
|
|
|
|
|
return iced::window::maximize(self.core.main_window_id().unwrap(), true);
|
2024-03-14 18:47:41 +01:00
|
|
|
}
|
2025-01-21 09:55:55 +01:00
|
|
|
ToplevelUpdate::Remove(handle) => {
|
2025-10-06 16:35:20 -04:00
|
|
|
let prev_was_empty = self.apps.is_empty();
|
2025-04-02 17:26:07 +02:00
|
|
|
self.apps
|
|
|
|
|
.retain(|a| a.toplevel_info.foreign_toplevel != handle);
|
2025-01-21 09:55:55 +01:00
|
|
|
self.apps.shrink_to_fit();
|
2025-10-06 16:35:20 -04:00
|
|
|
let changed = prev_was_empty != self.apps.is_empty();
|
|
|
|
|
if self.apps.is_empty() && changed {
|
|
|
|
|
// hide the window
|
|
|
|
|
return iced::window::minimize(
|
|
|
|
|
self.core.main_window_id().unwrap(),
|
|
|
|
|
true,
|
|
|
|
|
);
|
|
|
|
|
}
|
2025-01-21 09:55:55 +01:00
|
|
|
}
|
2024-03-14 18:47:41 +01:00
|
|
|
},
|
|
|
|
|
WaylandUpdate::Image(handle, img) => {
|
2025-02-12 10:42:32 -08:00
|
|
|
if let Some(pos) = self
|
|
|
|
|
.apps
|
|
|
|
|
.iter()
|
2025-04-02 17:26:07 +02:00
|
|
|
.position(|a| a.toplevel_info.foreign_toplevel == handle)
|
2025-02-12 10:42:32 -08:00
|
|
|
{
|
2025-04-02 17:26:07 +02:00
|
|
|
self.apps[pos].wayland_image = Some(img);
|
2024-03-14 18:47:41 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
Message::Activate(handle) => {
|
|
|
|
|
if let Some(tx) = self.tx.as_ref() {
|
|
|
|
|
let _ = tx.send(WaylandRequest::Toplevel(ToplevelRequest::Activate(handle)));
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-07-12 20:26:18 -04:00
|
|
|
Message::Closed(id) => {
|
|
|
|
|
if self.overflow_popup.is_some_and(|i| i == id) {
|
|
|
|
|
self.overflow_popup = None;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
Message::OpenOverflowPopup => {
|
|
|
|
|
if let Some(id) = self.overflow_popup.take() {
|
|
|
|
|
return destroy_popup(id);
|
|
|
|
|
} else {
|
|
|
|
|
let new_id = window::Id::unique();
|
|
|
|
|
let pos = self.max_icon_count().unwrap_or_default();
|
|
|
|
|
|
|
|
|
|
self.overflow_popup = Some(new_id);
|
|
|
|
|
let icon_size = self.core.applet.suggested_size(true).0 as u32
|
2025-10-06 16:35:20 -04:00
|
|
|
+ 2 * self.core.applet.suggested_padding(true).1 as u32;
|
2024-07-12 20:26:18 -04:00
|
|
|
let spacing = self.core.system_theme().cosmic().space_xxs() as u32;
|
|
|
|
|
let major_axis_len = (icon_size + spacing) * (pos.saturating_sub(1) as u32);
|
|
|
|
|
let rectangle = match self.core.applet.anchor {
|
|
|
|
|
PanelAnchor::Top | PanelAnchor::Bottom => iced::Rectangle {
|
|
|
|
|
x: major_axis_len as i32,
|
|
|
|
|
y: 0,
|
|
|
|
|
width: icon_size as i32,
|
|
|
|
|
height: icon_size as i32,
|
|
|
|
|
},
|
|
|
|
|
PanelAnchor::Left | PanelAnchor::Right => iced::Rectangle {
|
|
|
|
|
x: 0,
|
|
|
|
|
y: major_axis_len as i32,
|
|
|
|
|
width: icon_size as i32,
|
|
|
|
|
height: icon_size as i32,
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
let mut popup_settings = self.core.applet.get_popup_settings(
|
2024-10-30 22:51:08 -04:00
|
|
|
self.core.main_window_id().unwrap(),
|
2024-07-12 20:26:18 -04:00
|
|
|
new_id,
|
|
|
|
|
None,
|
|
|
|
|
None,
|
|
|
|
|
None,
|
|
|
|
|
);
|
|
|
|
|
popup_settings.positioner.anchor_rect = rectangle;
|
|
|
|
|
|
|
|
|
|
return get_popup(popup_settings);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
Message::CloseOverflowPopup => todo!(),
|
2025-03-17 22:18:25 -04:00
|
|
|
Message::Surface(a) => {
|
|
|
|
|
return cosmic::task::message(cosmic::Action::Cosmic(
|
|
|
|
|
cosmic::app::Action::Surface(a),
|
|
|
|
|
));
|
|
|
|
|
}
|
2025-10-04 10:51:18 +10:00
|
|
|
}
|
2024-10-30 22:51:08 -04:00
|
|
|
Task::none()
|
2024-03-14 18:47:41 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn subscription(&self) -> Subscription<Message> {
|
|
|
|
|
wayland_subscription::wayland_subscription().map(Message::Wayland)
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-04 10:51:18 +10:00
|
|
|
fn view(&self) -> Element<'_, Self::Message> {
|
|
|
|
|
let max_icon_count = self.max_icon_count().map_or(self.apps.len(), |n| {
|
|
|
|
|
if n < self.apps.len() {
|
|
|
|
|
n - 1
|
|
|
|
|
} else {
|
|
|
|
|
self.apps.len()
|
|
|
|
|
}
|
|
|
|
|
});
|
2024-04-15 18:37:00 -04:00
|
|
|
let (width, _) = self.core.applet.suggested_size(false);
|
2025-10-06 16:35:20 -04:00
|
|
|
let (major_padding, cross_padding) = self.core.applet.suggested_padding(false);
|
|
|
|
|
let padding = if matches!(
|
|
|
|
|
self.core.applet.anchor,
|
|
|
|
|
PanelAnchor::Top | PanelAnchor::Bottom
|
|
|
|
|
) {
|
|
|
|
|
(major_padding, cross_padding)
|
|
|
|
|
} else {
|
|
|
|
|
(cross_padding, major_padding)
|
|
|
|
|
};
|
2024-03-14 18:47:41 +01:00
|
|
|
let theme = self.core.system_theme().cosmic();
|
2025-04-04 03:02:31 +02:00
|
|
|
let icon_buttons = self.apps[..max_icon_count].iter().map(|app| {
|
2025-02-27 20:32:13 -05:00
|
|
|
self.core
|
|
|
|
|
.applet
|
|
|
|
|
.applet_tooltip(
|
|
|
|
|
Element::from(crate::window_image::WindowImage::new(
|
2025-04-02 17:26:07 +02:00
|
|
|
app.wayland_image.clone(),
|
|
|
|
|
&app.icon_source,
|
2025-02-27 20:32:13 -05:00
|
|
|
width as f32,
|
2025-04-02 17:26:07 +02:00
|
|
|
Message::Activate(app.toplevel_info.foreign_toplevel.clone()),
|
2025-02-27 20:32:13 -05:00
|
|
|
padding,
|
|
|
|
|
)),
|
2025-04-02 17:26:07 +02:00
|
|
|
app.name.clone(),
|
2025-02-27 20:32:13 -05:00
|
|
|
self.overflow_popup.is_some(),
|
2025-03-14 13:14:51 -04:00
|
|
|
Message::Surface,
|
2025-04-14 09:48:59 -04:00
|
|
|
None,
|
2025-02-27 20:32:13 -05:00
|
|
|
)
|
|
|
|
|
.into()
|
2025-02-12 10:42:32 -08:00
|
|
|
});
|
2024-07-12 20:26:18 -04:00
|
|
|
let overflow_btn = if max_icon_count < self.apps.len() {
|
|
|
|
|
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)
|
2024-08-15 17:46:07 +02:00
|
|
|
.on_press_down(Message::OpenOverflowPopup);
|
2024-07-12 20:26:18 -04:00
|
|
|
|
|
|
|
|
Some(btn.into())
|
|
|
|
|
} else {
|
|
|
|
|
None
|
|
|
|
|
};
|
2024-03-14 18:47:41 +01:00
|
|
|
|
2025-10-06 16:35:20 -04:00
|
|
|
let spacing = self.core.applet.spacing;
|
2024-03-14 18:47:41 +01:00
|
|
|
// TODO optional dividers on ends if detects app list neighbor
|
|
|
|
|
// not sure the best way to tell if there is an adjacent app-list
|
2025-10-04 10:51:18 +10:00
|
|
|
let icon_buttons = icon_buttons.chain(overflow_btn);
|
2024-10-30 22:51:08 -04:00
|
|
|
let content: Element<_> = if matches!(
|
2024-03-14 18:47:41 +01:00
|
|
|
self.core.applet.anchor,
|
|
|
|
|
PanelAnchor::Top | PanelAnchor::Bottom
|
|
|
|
|
) {
|
|
|
|
|
Row::with_children(icon_buttons)
|
2024-10-30 22:51:08 -04:00
|
|
|
.align_y(cosmic::iced_core::Alignment::Center)
|
2024-03-14 18:47:41 +01:00
|
|
|
.height(Length::Shrink)
|
|
|
|
|
.width(Length::Shrink)
|
2025-10-06 16:35:20 -04:00
|
|
|
.spacing(spacing as f32)
|
2024-03-14 18:47:41 +01:00
|
|
|
.into()
|
|
|
|
|
} else {
|
|
|
|
|
Column::with_children(icon_buttons)
|
2024-10-30 22:51:08 -04:00
|
|
|
.align_x(cosmic::iced_core::Alignment::Center)
|
2024-03-14 18:47:41 +01:00
|
|
|
.height(Length::Shrink)
|
|
|
|
|
.width(Length::Shrink)
|
2025-10-06 16:35:20 -04:00
|
|
|
.spacing(spacing as f32)
|
2024-03-14 18:47:41 +01:00
|
|
|
.into()
|
2024-07-12 20:26:18 -04:00
|
|
|
};
|
2024-10-30 22:51:08 -04:00
|
|
|
|
|
|
|
|
let mut limits = Limits::NONE.min_width(1.).min_height(1.);
|
|
|
|
|
|
|
|
|
|
if let Some(b) = self.core.applet.suggested_bounds {
|
|
|
|
|
if b.width as i32 > 0 {
|
|
|
|
|
limits = limits.max_width(b.width);
|
|
|
|
|
}
|
|
|
|
|
if b.height as i32 > 0 {
|
|
|
|
|
limits = limits.max_height(b.height);
|
|
|
|
|
}
|
2024-03-14 18:47:41 +01:00
|
|
|
}
|
2024-10-30 22:51:08 -04:00
|
|
|
|
|
|
|
|
autosize(
|
|
|
|
|
if self.overflow_popup.is_some() {
|
|
|
|
|
mouse_area(content)
|
|
|
|
|
.on_press(Message::CloseOverflowPopup)
|
|
|
|
|
.into()
|
|
|
|
|
} else {
|
|
|
|
|
content
|
|
|
|
|
},
|
|
|
|
|
AUTOSIZE_MAIN_ID.clone(),
|
|
|
|
|
)
|
|
|
|
|
.limits(limits)
|
|
|
|
|
.into()
|
2024-03-14 18:47:41 +01:00
|
|
|
}
|
2024-07-12 20:26:18 -04:00
|
|
|
|
2025-10-04 10:51:18 +10:00
|
|
|
fn view_window(&self, _id: window::Id) -> Element<'_, Self::Message> {
|
|
|
|
|
let max_icon_count = self.max_icon_count().map_or(self.apps.len(), |n| {
|
|
|
|
|
if n < self.apps.len() {
|
|
|
|
|
n - 1
|
|
|
|
|
} else {
|
|
|
|
|
self.apps.len()
|
|
|
|
|
}
|
|
|
|
|
});
|
2024-07-12 20:26:18 -04:00
|
|
|
let (width, _) = self.core.applet.suggested_size(false);
|
2025-10-06 16:35:20 -04:00
|
|
|
let (major_padding, cross_padding) = self.core.applet.suggested_padding(false);
|
|
|
|
|
let padding = if matches!(
|
|
|
|
|
self.core.applet.anchor,
|
|
|
|
|
PanelAnchor::Top | PanelAnchor::Bottom
|
|
|
|
|
) {
|
|
|
|
|
(major_padding, cross_padding)
|
|
|
|
|
} else {
|
|
|
|
|
(cross_padding, major_padding)
|
|
|
|
|
};
|
2024-07-12 20:26:18 -04:00
|
|
|
let theme = self.core.system_theme().cosmic();
|
|
|
|
|
let space_xxs = theme.space_xxs();
|
2025-04-04 03:02:31 +02:00
|
|
|
let icon_buttons = self.apps[max_icon_count..].iter().map(|app| {
|
2025-02-12 10:42:32 -08:00
|
|
|
tooltip(
|
|
|
|
|
Element::from(crate::window_image::WindowImage::new(
|
2025-04-02 17:26:07 +02:00
|
|
|
app.wayland_image.clone(),
|
|
|
|
|
&app.icon_source,
|
2025-02-12 10:42:32 -08:00
|
|
|
width as f32,
|
2025-04-02 17:26:07 +02:00
|
|
|
Message::Activate(app.toplevel_info.foreign_toplevel.clone()),
|
2025-02-12 10:42:32 -08:00
|
|
|
padding,
|
|
|
|
|
)),
|
2025-04-02 17:26:07 +02:00
|
|
|
text(&app.name).shaping(text::Shaping::Advanced),
|
2025-02-12 10:42:32 -08:00
|
|
|
// tooltip::Position::FollowCursor,
|
|
|
|
|
// FIXME tooltip fails to appear when created as indicated in design
|
|
|
|
|
// maybe it should be a subsurface
|
|
|
|
|
match self.core.applet.anchor {
|
|
|
|
|
PanelAnchor::Left => tooltip::Position::Right,
|
|
|
|
|
PanelAnchor::Right => tooltip::Position::Left,
|
|
|
|
|
PanelAnchor::Top => tooltip::Position::Bottom,
|
|
|
|
|
PanelAnchor::Bottom => tooltip::Position::Top,
|
|
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
.snap_within_viewport(false)
|
|
|
|
|
.into()
|
|
|
|
|
});
|
2024-07-12 20:26:18 -04:00
|
|
|
|
|
|
|
|
// TODO optional dividers on ends if detects app list neighbor
|
|
|
|
|
// not sure the best way to tell if there is an adjacent app-list
|
|
|
|
|
|
|
|
|
|
self.core
|
|
|
|
|
.applet
|
|
|
|
|
.popup_container(
|
|
|
|
|
if matches!(
|
|
|
|
|
self.core.applet.anchor,
|
|
|
|
|
PanelAnchor::Top | PanelAnchor::Bottom
|
|
|
|
|
) {
|
|
|
|
|
Element::from(
|
|
|
|
|
Row::with_children(icon_buttons)
|
2024-10-30 22:51:08 -04:00
|
|
|
.align_y(cosmic::iced_core::Alignment::Center)
|
2024-07-12 20:26:18 -04:00
|
|
|
.height(Length::Shrink)
|
2025-10-06 16:35:20 -04:00
|
|
|
.width(Length::Shrink),
|
2024-07-12 20:26:18 -04:00
|
|
|
)
|
|
|
|
|
} else {
|
|
|
|
|
Column::with_children(icon_buttons)
|
2024-10-30 22:51:08 -04:00
|
|
|
.align_x(cosmic::iced_core::Alignment::Center)
|
2024-07-12 20:26:18 -04:00
|
|
|
.height(Length::Shrink)
|
|
|
|
|
.width(Length::Shrink)
|
|
|
|
|
.into()
|
|
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
.into()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn on_close_requested(&self, id: window::Id) -> Option<Self::Message> {
|
|
|
|
|
Some(Message::Closed(id))
|
|
|
|
|
}
|
2024-03-14 18:47:41 +01:00
|
|
|
}
|