2022-12-15 14:35:31 -05:00
|
|
|
use std::collections::HashMap;
|
2022-12-27 18:35:06 -05:00
|
|
|
use std::path::Path;
|
2022-12-12 19:48:31 -05:00
|
|
|
use std::path::PathBuf;
|
|
|
|
|
|
|
|
|
|
use crate::config;
|
|
|
|
|
use crate::config::AppListConfig;
|
2022-12-15 14:35:31 -05:00
|
|
|
use crate::fl;
|
2022-12-12 19:48:31 -05:00
|
|
|
use crate::toplevel_subscription::toplevel_subscription;
|
|
|
|
|
use crate::toplevel_subscription::ToplevelRequest;
|
|
|
|
|
use crate::toplevel_subscription::ToplevelUpdate;
|
|
|
|
|
use calloop::channel::Sender;
|
|
|
|
|
use cctk::toplevel_info::ToplevelInfo;
|
2022-12-13 19:58:00 -05:00
|
|
|
use cctk::wayland_client::protocol::wl_seat::WlSeat;
|
2022-12-21 17:06:53 -05:00
|
|
|
use cosmic::applet::cosmic_panel_config::PanelAnchor;
|
2022-12-12 19:48:31 -05:00
|
|
|
use cosmic::applet::CosmicAppletHelper;
|
2022-12-13 19:58:00 -05:00
|
|
|
use cosmic::iced;
|
2023-01-20 11:25:53 -05:00
|
|
|
use cosmic::iced::wayland::actions::window::SctkWindowSettings;
|
2022-12-12 19:48:31 -05:00
|
|
|
use cosmic::iced::wayland::popup::destroy_popup;
|
|
|
|
|
use cosmic::iced::wayland::popup::get_popup;
|
2022-12-20 13:19:23 -05:00
|
|
|
use cosmic::iced::widget::mouse_listener;
|
2022-12-12 19:48:31 -05:00
|
|
|
use cosmic::iced::widget::{column, row};
|
2023-01-20 11:25:53 -05:00
|
|
|
use cosmic::iced::Settings;
|
2023-01-18 16:51:30 -08:00
|
|
|
use cosmic::iced::{window, Application, Command, Subscription};
|
2022-12-15 15:18:48 -05:00
|
|
|
use cosmic::iced_native::alignment::Horizontal;
|
2022-12-13 19:58:00 -05:00
|
|
|
use cosmic::iced_native::subscription::events_with;
|
2022-12-27 18:35:06 -05:00
|
|
|
use cosmic::iced_native::widget::vertical_space;
|
2023-01-20 11:25:53 -05:00
|
|
|
use cosmic::iced_sctk::layout::Limits;
|
|
|
|
|
use cosmic::iced_sctk::settings::InitialSurface;
|
2023-01-31 17:24:11 -05:00
|
|
|
use cosmic::iced_sctk::widget::vertical_rule;
|
2022-12-12 19:48:31 -05:00
|
|
|
use cosmic::iced_style::application::{self, Appearance};
|
|
|
|
|
use cosmic::iced_style::Color;
|
|
|
|
|
use cosmic::theme::Button;
|
2023-01-31 17:24:11 -05:00
|
|
|
use cosmic::widget::divider;
|
2022-12-15 14:35:31 -05:00
|
|
|
use cosmic::widget::rectangle_tracker::rectangle_tracker_subscription;
|
|
|
|
|
use cosmic::widget::rectangle_tracker::RectangleTracker;
|
|
|
|
|
use cosmic::widget::rectangle_tracker::RectangleUpdate;
|
2022-12-12 19:48:31 -05:00
|
|
|
use cosmic::{Element, Theme};
|
|
|
|
|
use cosmic_protocols::toplevel_info::v1::client::zcosmic_toplevel_handle_v1::ZcosmicToplevelHandleV1;
|
|
|
|
|
use freedesktop_desktop_entry::DesktopEntry;
|
|
|
|
|
use iced::widget::container;
|
2022-12-13 19:58:00 -05:00
|
|
|
use iced::Alignment;
|
|
|
|
|
use iced::Background;
|
2022-12-12 19:48:31 -05:00
|
|
|
use iced::Length;
|
|
|
|
|
use itertools::Itertools;
|
|
|
|
|
|
|
|
|
|
pub fn run() -> cosmic::iced::Result {
|
|
|
|
|
let helper = CosmicAppletHelper::default();
|
2023-01-20 11:25:53 -05:00
|
|
|
let pixel_size = helper.suggested_size().0;
|
|
|
|
|
let padding = 8;
|
|
|
|
|
let dot_size = 4;
|
|
|
|
|
let spacing = 4;
|
|
|
|
|
let thickness = (pixel_size + 2 * padding + dot_size + spacing) as u32;
|
|
|
|
|
let (w, h) = match helper.anchor {
|
|
|
|
|
PanelAnchor::Top | PanelAnchor::Bottom => (2000, thickness),
|
|
|
|
|
PanelAnchor::Left | PanelAnchor::Right => (thickness, 2000),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
CosmicAppList::run(Settings {
|
|
|
|
|
initial_surface: InitialSurface::XdgWindow(SctkWindowSettings {
|
|
|
|
|
iced_settings: cosmic::iced_native::window::Settings {
|
|
|
|
|
..Default::default()
|
|
|
|
|
},
|
|
|
|
|
autosize: true,
|
|
|
|
|
size_limits: Limits::NONE
|
|
|
|
|
.min_height(1)
|
|
|
|
|
.min_width(1)
|
|
|
|
|
.max_height(h)
|
|
|
|
|
.max_width(w),
|
|
|
|
|
..Default::default()
|
|
|
|
|
}),
|
|
|
|
|
..Default::default()
|
|
|
|
|
})
|
2022-12-12 19:48:31 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, Clone, Default)]
|
|
|
|
|
struct Toplevel {
|
2022-12-15 14:35:31 -05:00
|
|
|
id: u32,
|
2022-12-12 19:48:31 -05:00
|
|
|
toplevels: Vec<(ZcosmicToplevelHandleV1, ToplevelInfo)>,
|
2022-12-14 11:17:05 -05:00
|
|
|
desktop_info: DesktopInfo,
|
2022-12-15 14:35:31 -05:00
|
|
|
popup: Option<window::Id>,
|
2022-12-12 19:48:31 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Clone, Default)]
|
|
|
|
|
struct CosmicAppList {
|
|
|
|
|
theme: Theme,
|
|
|
|
|
popup: Option<window::Id>,
|
2022-12-15 14:35:31 -05:00
|
|
|
surface_id_ctr: u32,
|
2022-12-12 19:48:31 -05:00
|
|
|
subscription_ctr: u32,
|
2022-12-15 14:35:31 -05:00
|
|
|
toplevel_ctr: u32,
|
2022-12-12 19:48:31 -05:00
|
|
|
toplevel_list: Vec<Toplevel>,
|
|
|
|
|
config: AppListConfig,
|
|
|
|
|
toplevel_sender: Option<Sender<ToplevelRequest>>,
|
|
|
|
|
applet_helper: CosmicAppletHelper,
|
2022-12-13 19:58:00 -05:00
|
|
|
seat: Option<WlSeat>,
|
2022-12-15 14:35:31 -05:00
|
|
|
rectangle_tracker: Option<RectangleTracker<u32>>,
|
|
|
|
|
rectangles: HashMap<u32, iced::Rectangle>,
|
|
|
|
|
}
|
|
|
|
|
|
2022-12-12 19:48:31 -05:00
|
|
|
// TODO DnD after sctk merges DnD
|
|
|
|
|
#[derive(Debug, Clone)]
|
|
|
|
|
enum Message {
|
|
|
|
|
Toplevel(ToplevelUpdate),
|
|
|
|
|
Favorite(String),
|
|
|
|
|
UnFavorite(String),
|
2022-12-15 14:35:31 -05:00
|
|
|
Popup(String),
|
2022-12-15 15:49:59 -05:00
|
|
|
ClosePopup,
|
2022-12-14 10:15:04 -05:00
|
|
|
Activate(ZcosmicToplevelHandleV1),
|
|
|
|
|
Exec(String),
|
2022-12-15 14:35:31 -05:00
|
|
|
Quit(String),
|
2022-12-12 19:48:31 -05:00
|
|
|
Errored(String),
|
|
|
|
|
Ignore,
|
2022-12-13 19:58:00 -05:00
|
|
|
NewSeat(WlSeat),
|
|
|
|
|
RemovedSeat(WlSeat),
|
2022-12-15 15:18:48 -05:00
|
|
|
Rectangle(RectangleUpdate<u32>),
|
2022-12-12 19:48:31 -05:00
|
|
|
}
|
2022-12-14 11:17:05 -05:00
|
|
|
|
|
|
|
|
#[derive(Debug, Clone, Default)]
|
|
|
|
|
struct DesktopInfo {
|
|
|
|
|
id: String,
|
|
|
|
|
icon: PathBuf,
|
|
|
|
|
exec: String,
|
|
|
|
|
name: String,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn desktop_info_for_app_ids(mut app_ids: Vec<String>) -> Vec<DesktopInfo> {
|
2022-12-12 19:48:31 -05:00
|
|
|
let mut ret = freedesktop_desktop_entry::Iter::new(freedesktop_desktop_entry::default_paths())
|
|
|
|
|
.filter_map(|path| {
|
|
|
|
|
std::fs::read_to_string(&path).ok().and_then(|input| {
|
|
|
|
|
DesktopEntry::decode(&path, &input).ok().and_then(|de| {
|
2022-12-15 14:35:31 -05:00
|
|
|
if let Some(i) = app_ids
|
|
|
|
|
.iter()
|
|
|
|
|
.position(|s| s == de.appid || s.eq(&de.name(None).unwrap_or_default()))
|
|
|
|
|
{
|
2022-12-12 19:48:31 -05:00
|
|
|
freedesktop_icons::lookup(de.icon().unwrap_or(de.appid))
|
|
|
|
|
.with_size(128)
|
|
|
|
|
.with_cache()
|
|
|
|
|
.find()
|
2022-12-15 14:35:31 -05:00
|
|
|
.map(|buf| DesktopInfo {
|
2023-02-05 22:37:47 +01:00
|
|
|
id: app_ids.remove(i),
|
2022-12-15 14:35:31 -05:00
|
|
|
icon: buf,
|
|
|
|
|
exec: de.exec().unwrap_or_default().to_string(),
|
|
|
|
|
name: de.name(None).unwrap_or_default().to_string(),
|
|
|
|
|
})
|
2022-12-12 19:48:31 -05:00
|
|
|
} else {
|
|
|
|
|
None
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
.collect_vec();
|
|
|
|
|
ret.append(
|
|
|
|
|
&mut app_ids
|
|
|
|
|
.into_iter()
|
2022-12-15 14:35:31 -05:00
|
|
|
.map(|id| DesktopInfo {
|
|
|
|
|
id,
|
|
|
|
|
..Default::default()
|
|
|
|
|
})
|
2022-12-12 19:48:31 -05:00
|
|
|
.collect_vec(),
|
|
|
|
|
);
|
|
|
|
|
ret
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Application for CosmicAppList {
|
|
|
|
|
type Message = Message;
|
|
|
|
|
type Theme = Theme;
|
2023-01-18 16:51:30 -08:00
|
|
|
type Executor = cosmic::SingleThreadExecutor;
|
2022-12-12 19:48:31 -05:00
|
|
|
type Flags = ();
|
|
|
|
|
|
|
|
|
|
fn new(_flags: ()) -> (Self, Command<Message>) {
|
|
|
|
|
let config = config::AppListConfig::load().unwrap_or_default();
|
2022-12-15 14:35:31 -05:00
|
|
|
let mut toplevel_ctr = 0;
|
2022-12-15 15:18:48 -05:00
|
|
|
let self_ = CosmicAppList {
|
|
|
|
|
toplevel_list: desktop_info_for_app_ids(config.favorites.clone())
|
|
|
|
|
.into_iter()
|
|
|
|
|
.map(|e| {
|
|
|
|
|
toplevel_ctr += 1;
|
|
|
|
|
Toplevel {
|
|
|
|
|
id: toplevel_ctr,
|
|
|
|
|
toplevels: Default::default(),
|
|
|
|
|
desktop_info: e,
|
|
|
|
|
popup: None,
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
.collect(),
|
|
|
|
|
config,
|
|
|
|
|
toplevel_ctr,
|
|
|
|
|
..Default::default()
|
|
|
|
|
};
|
2022-12-15 15:49:59 -05:00
|
|
|
|
2023-01-20 11:25:53 -05:00
|
|
|
(self_, Command::none())
|
2022-12-12 19:48:31 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn title(&self) -> String {
|
|
|
|
|
config::APP_ID.to_string()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn update(&mut self, message: Message) -> Command<Message> {
|
|
|
|
|
match message {
|
|
|
|
|
Message::Errored(_) => {
|
|
|
|
|
// TODO log errors
|
|
|
|
|
}
|
2022-12-15 14:35:31 -05:00
|
|
|
Message::Popup(id) => {
|
|
|
|
|
if let Some(toplevel_group) = self
|
|
|
|
|
.toplevel_list
|
|
|
|
|
.iter_mut()
|
|
|
|
|
.find(|t| t.desktop_info.id == id)
|
|
|
|
|
{
|
|
|
|
|
if let Some(p) = self.popup.take() {
|
|
|
|
|
toplevel_group.popup.take();
|
|
|
|
|
return destroy_popup(p);
|
|
|
|
|
}
|
|
|
|
|
let rectangle = match self.rectangles.get(&toplevel_group.id) {
|
|
|
|
|
Some(r) => r,
|
|
|
|
|
None => return Command::none(),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
self.surface_id_ctr += 1;
|
|
|
|
|
let new_id = window::Id::new(self.surface_id_ctr);
|
2022-12-12 19:48:31 -05:00
|
|
|
self.popup.replace(new_id);
|
2022-12-15 14:35:31 -05:00
|
|
|
toplevel_group.popup.replace(new_id);
|
2023-01-05 10:05:19 -08:00
|
|
|
|
2022-12-15 14:35:31 -05:00
|
|
|
let mut popup_settings = self.applet_helper.get_popup_settings(
|
2022-12-12 19:48:31 -05:00
|
|
|
window::Id::new(0),
|
|
|
|
|
new_id,
|
2022-12-27 18:35:06 -05:00
|
|
|
None,
|
2022-12-12 19:48:31 -05:00
|
|
|
None,
|
|
|
|
|
None,
|
|
|
|
|
);
|
2022-12-15 14:35:31 -05:00
|
|
|
let 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,
|
|
|
|
|
};
|
2022-12-12 19:48:31 -05:00
|
|
|
return get_popup(popup_settings);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
Message::Favorite(id) => {
|
|
|
|
|
let _ = self.config.add_favorite(id);
|
|
|
|
|
}
|
|
|
|
|
Message::UnFavorite(id) => {
|
|
|
|
|
let _ = self.config.remove_favorite(id);
|
2022-12-15 14:35:31 -05:00
|
|
|
self.toplevel_list.retain(|t| {
|
|
|
|
|
self.config.favorites.contains(&t.desktop_info.id)
|
|
|
|
|
|| self.config.favorites.contains(&t.desktop_info.name)
|
|
|
|
|
})
|
2022-12-12 19:48:31 -05:00
|
|
|
}
|
|
|
|
|
Message::Activate(handle) => {
|
2022-12-15 14:35:31 -05:00
|
|
|
if let (Some(tx), Some(seat)) = (self.toplevel_sender.as_ref(), self.seat.as_ref())
|
|
|
|
|
{
|
2022-12-13 19:58:00 -05:00
|
|
|
let _ = tx.send(ToplevelRequest::Activate(handle, seat.clone()));
|
2022-12-12 19:48:31 -05:00
|
|
|
}
|
|
|
|
|
}
|
2022-12-15 14:35:31 -05:00
|
|
|
Message::Quit(id) => {
|
|
|
|
|
if let Some(toplevel_group) =
|
|
|
|
|
self.toplevel_list.iter().find(|t| t.desktop_info.id == id)
|
|
|
|
|
{
|
|
|
|
|
for (handle, _) in &toplevel_group.toplevels {
|
|
|
|
|
if let Some(tx) = self.toplevel_sender.as_ref() {
|
|
|
|
|
let _ = tx.send(ToplevelRequest::Quit(handle.clone()));
|
|
|
|
|
}
|
|
|
|
|
}
|
2022-12-12 19:48:31 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
Message::Toplevel(event) => {
|
|
|
|
|
match event {
|
|
|
|
|
ToplevelUpdate::AddToplevel(handle, info) => {
|
2023-01-05 10:05:19 -08:00
|
|
|
if info.app_id.is_empty() {
|
2022-12-14 11:17:05 -05:00
|
|
|
return Command::none();
|
|
|
|
|
}
|
2022-12-15 14:35:31 -05:00
|
|
|
if let Some(i) = self.toplevel_list.iter().position(
|
2023-01-05 10:05:19 -08:00
|
|
|
|Toplevel { desktop_info, .. }| desktop_info.id == info.app_id,
|
2022-12-15 14:35:31 -05:00
|
|
|
) {
|
2022-12-12 19:48:31 -05:00
|
|
|
self.toplevel_list[i].toplevels.push((handle, info));
|
|
|
|
|
} else {
|
2022-12-14 11:17:05 -05:00
|
|
|
let desktop_info =
|
|
|
|
|
desktop_info_for_app_ids(vec![info.app_id.clone()]).remove(0);
|
2022-12-15 14:35:31 -05:00
|
|
|
self.toplevel_ctr += 1;
|
2022-12-12 19:48:31 -05:00
|
|
|
self.toplevel_list.push(Toplevel {
|
2022-12-15 14:35:31 -05:00
|
|
|
id: self.toplevel_ctr,
|
2022-12-12 19:48:31 -05:00
|
|
|
toplevels: vec![(handle, info)],
|
2022-12-15 14:35:31 -05:00
|
|
|
desktop_info,
|
|
|
|
|
popup: None,
|
2022-12-12 19:48:31 -05:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
ToplevelUpdate::Init(tx) => {
|
|
|
|
|
self.toplevel_sender.replace(tx);
|
|
|
|
|
}
|
|
|
|
|
ToplevelUpdate::Finished => {
|
|
|
|
|
self.subscription_ctr += 1;
|
2022-12-13 19:58:00 -05:00
|
|
|
for t in &mut self.toplevel_list {
|
|
|
|
|
t.toplevels.clear();
|
|
|
|
|
}
|
2022-12-12 19:48:31 -05:00
|
|
|
}
|
|
|
|
|
ToplevelUpdate::RemoveToplevel(handle) => {
|
|
|
|
|
if let Some(i) = self.toplevel_list.iter_mut().position(
|
|
|
|
|
|Toplevel {
|
2022-12-15 14:35:31 -05:00
|
|
|
toplevels,
|
|
|
|
|
desktop_info,
|
|
|
|
|
..
|
2022-12-12 19:48:31 -05:00
|
|
|
}| {
|
2023-01-05 10:05:19 -08:00
|
|
|
if let Some(ret) = toplevels.iter().position(|t| t.0 == handle) {
|
2022-12-12 19:48:31 -05:00
|
|
|
toplevels.remove(ret);
|
2022-12-15 14:35:31 -05:00
|
|
|
toplevels.is_empty()
|
|
|
|
|
&& !self.config.favorites.contains(&desktop_info.id)
|
|
|
|
|
&& !self.config.favorites.contains(&desktop_info.name)
|
2022-12-12 19:48:31 -05:00
|
|
|
} else {
|
|
|
|
|
false
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
) {
|
|
|
|
|
self.toplevel_list.remove(i);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
ToplevelUpdate::UpdateToplevel(handle, info) => {
|
2022-12-14 11:17:05 -05:00
|
|
|
// TODO probably want to make sure it is removed
|
2023-01-05 10:05:19 -08:00
|
|
|
if info.app_id.is_empty() {
|
2022-12-14 11:17:05 -05:00
|
|
|
return Command::none();
|
|
|
|
|
}
|
2022-12-12 19:48:31 -05:00
|
|
|
'toplevel_loop: for toplevel_list in &mut self.toplevel_list {
|
|
|
|
|
for (t_handle, t_info) in &mut toplevel_list.toplevels {
|
|
|
|
|
if &handle == t_handle {
|
|
|
|
|
*t_info = info;
|
|
|
|
|
break 'toplevel_loop;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2022-12-13 19:58:00 -05:00
|
|
|
Message::NewSeat(s) => {
|
|
|
|
|
self.seat.replace(s);
|
2022-12-15 14:35:31 -05:00
|
|
|
}
|
2022-12-13 19:58:00 -05:00
|
|
|
Message::RemovedSeat(_) => {
|
|
|
|
|
self.seat.take();
|
2022-12-15 14:35:31 -05:00
|
|
|
}
|
2022-12-14 10:15:04 -05:00
|
|
|
Message::Exec(exec_str) => {
|
|
|
|
|
let mut exec = shlex::Shlex::new(&exec_str);
|
|
|
|
|
let mut cmd = match exec.next() {
|
2023-01-05 10:05:19 -08:00
|
|
|
Some(cmd) if !cmd.contains('=') => tokio::process::Command::new(cmd),
|
2022-12-14 10:15:04 -05:00
|
|
|
_ => return Command::none(),
|
|
|
|
|
};
|
|
|
|
|
for arg in exec {
|
|
|
|
|
// TODO handle "%" args here if necessary?
|
2023-01-05 10:05:19 -08:00
|
|
|
if !arg.starts_with('%') {
|
2022-12-14 10:15:04 -05:00
|
|
|
cmd.arg(arg);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
let _ = cmd.spawn();
|
2022-12-15 14:35:31 -05:00
|
|
|
}
|
|
|
|
|
Message::Rectangle(u) => match u {
|
|
|
|
|
RectangleUpdate::Rectangle(r) => {
|
|
|
|
|
self.rectangles.insert(r.0, r.1);
|
|
|
|
|
}
|
|
|
|
|
RectangleUpdate::Init(tracker) => {
|
|
|
|
|
self.rectangle_tracker.replace(tracker);
|
|
|
|
|
}
|
2022-12-14 10:15:04 -05:00
|
|
|
},
|
2022-12-15 14:35:31 -05:00
|
|
|
Message::Ignore => {}
|
2022-12-15 15:49:59 -05:00
|
|
|
Message::ClosePopup => {
|
|
|
|
|
if let Some(p) = self.popup.take() {
|
|
|
|
|
if let Some(toplevel_group) =
|
|
|
|
|
self.toplevel_list.iter_mut().find(|t| t.popup == Some(p))
|
|
|
|
|
{
|
|
|
|
|
toplevel_group.popup.take();
|
|
|
|
|
}
|
|
|
|
|
return destroy_popup(p);
|
|
|
|
|
}
|
|
|
|
|
}
|
2022-12-12 19:48:31 -05:00
|
|
|
}
|
|
|
|
|
Command::none()
|
|
|
|
|
}
|
|
|
|
|
|
2023-04-05 20:40:22 -04:00
|
|
|
fn view(&self, id: window::Id) -> Element<Message> {
|
|
|
|
|
if let Some(Toplevel {
|
|
|
|
|
toplevels,
|
|
|
|
|
desktop_info,
|
|
|
|
|
..
|
|
|
|
|
}) = self.toplevel_list.iter().find(|t| t.popup == Some(id))
|
|
|
|
|
{
|
|
|
|
|
let is_favorite = self.config.favorites.contains(&desktop_info.id)
|
|
|
|
|
|| self.config.favorites.contains(&desktop_info.name);
|
2023-01-20 11:25:53 -05:00
|
|
|
|
2023-04-05 20:40:22 -04:00
|
|
|
let mut content = column![
|
|
|
|
|
iced::widget::text(&desktop_info.name).horizontal_alignment(Horizontal::Center),
|
|
|
|
|
cosmic::widget::button(Button::Text)
|
|
|
|
|
.custom(vec![iced::widget::text(fl!("new-window")).into()])
|
|
|
|
|
.on_press(Message::Exec(desktop_info.exec.clone())),
|
|
|
|
|
]
|
|
|
|
|
.padding(8)
|
|
|
|
|
.spacing(4)
|
|
|
|
|
.align_items(Alignment::Center);
|
|
|
|
|
if !toplevels.is_empty() {
|
|
|
|
|
let mut list_col = column![];
|
|
|
|
|
for (handle, info) in toplevels {
|
|
|
|
|
let title = if info.title.len() > 20 {
|
|
|
|
|
format!("{:.24}...", &info.title)
|
|
|
|
|
} else {
|
|
|
|
|
info.title.clone()
|
|
|
|
|
};
|
|
|
|
|
list_col = list_col.push(
|
|
|
|
|
cosmic::widget::button(Button::Text)
|
|
|
|
|
.custom(vec![iced::widget::text(title).into()])
|
|
|
|
|
.on_press(Message::Activate(handle.clone())),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
content = content.push(divider::horizontal::light());
|
|
|
|
|
content = content.push(list_col);
|
|
|
|
|
content = content.push(divider::horizontal::light());
|
|
|
|
|
}
|
|
|
|
|
content = content.push(if is_favorite {
|
|
|
|
|
cosmic::widget::button(Button::Text)
|
|
|
|
|
.custom(vec![iced::widget::text(fl!("unfavorite")).into()])
|
|
|
|
|
.on_press(Message::UnFavorite(desktop_info.id.clone()))
|
|
|
|
|
} else {
|
|
|
|
|
cosmic::widget::button(Button::Text)
|
|
|
|
|
.custom(vec![iced::widget::text(fl!("favorite")).into()])
|
|
|
|
|
.on_press(Message::Favorite(desktop_info.id.clone()))
|
|
|
|
|
});
|
2022-12-15 15:49:59 -05:00
|
|
|
|
2023-04-05 20:40:22 -04:00
|
|
|
content = match toplevels.len() {
|
|
|
|
|
0 => content,
|
|
|
|
|
1 => content.push(
|
|
|
|
|
cosmic::widget::button(Button::Text)
|
|
|
|
|
.custom(vec![iced::widget::text(fl!("quit")).into()])
|
|
|
|
|
.on_press(Message::Quit(desktop_info.id.clone())),
|
|
|
|
|
),
|
|
|
|
|
_ => content.push(
|
|
|
|
|
cosmic::widget::button(Button::Text)
|
|
|
|
|
.custom(vec![iced::widget::text(&fl!("quit-all")).into()])
|
|
|
|
|
.on_press(Message::Quit(desktop_info.id.clone())),
|
|
|
|
|
),
|
|
|
|
|
};
|
|
|
|
|
// return Container::new(Container::new(content.width(Length::Shrink).height(Length::Shrink)).style(
|
|
|
|
|
// cosmic::Container::Custom(|theme| container::Appearance {
|
|
|
|
|
// text_color: Some(theme.cosmic().on_bg_color().into()),
|
|
|
|
|
// background: Some(theme.extended_palette().background.base.color.into()),
|
|
|
|
|
// border_radius: 12.0,
|
|
|
|
|
// border_width: 0.0,
|
|
|
|
|
// border_color: Color::TRANSPARENT,
|
|
|
|
|
// }),
|
|
|
|
|
// )).into();
|
|
|
|
|
return self.applet_helper.popup_container(content).into();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let (favorites, running) = self.toplevel_list.iter().fold(
|
|
|
|
|
(Vec::new(), Vec::new()),
|
|
|
|
|
|(mut favorites, mut running),
|
|
|
|
|
Toplevel {
|
|
|
|
|
id,
|
|
|
|
|
toplevels,
|
|
|
|
|
desktop_info,
|
|
|
|
|
..
|
|
|
|
|
}| {
|
|
|
|
|
let cosmic_icon = cosmic::widget::icon(
|
|
|
|
|
Path::new(&desktop_info.icon),
|
|
|
|
|
self.applet_helper.suggested_size().0,
|
2022-12-12 19:48:31 -05:00
|
|
|
);
|
2022-12-15 14:35:31 -05:00
|
|
|
|
2023-04-05 20:40:22 -04:00
|
|
|
let dot_radius = 2;
|
|
|
|
|
let dots = (0..toplevels.len())
|
|
|
|
|
.into_iter()
|
|
|
|
|
.map(|_| {
|
|
|
|
|
container(vertical_space(Length::Units(0)))
|
|
|
|
|
.padding(dot_radius)
|
|
|
|
|
.style(<Self::Theme as container::StyleSheet>::Style::Custom(
|
|
|
|
|
|theme| container::Appearance {
|
|
|
|
|
text_color: Some(Color::TRANSPARENT),
|
|
|
|
|
background: Some(Background::Color(
|
|
|
|
|
theme.cosmic().on_bg_color().into(),
|
|
|
|
|
)),
|
|
|
|
|
border_radius: 4.0,
|
|
|
|
|
border_width: 0.0,
|
|
|
|
|
border_color: Color::TRANSPARENT,
|
|
|
|
|
},
|
|
|
|
|
))
|
|
|
|
|
.into()
|
|
|
|
|
})
|
|
|
|
|
.collect_vec();
|
|
|
|
|
let icon_wrapper = match &self.applet_helper.anchor {
|
|
|
|
|
PanelAnchor::Left => {
|
|
|
|
|
row(vec![column(dots).spacing(4).into(), cosmic_icon.into()])
|
|
|
|
|
.align_items(iced::Alignment::Center)
|
|
|
|
|
.spacing(4)
|
|
|
|
|
.into()
|
|
|
|
|
}
|
|
|
|
|
PanelAnchor::Right => {
|
|
|
|
|
row(vec![cosmic_icon.into(), column(dots).spacing(4).into()])
|
|
|
|
|
.align_items(iced::Alignment::Center)
|
|
|
|
|
.spacing(4)
|
|
|
|
|
.into()
|
|
|
|
|
}
|
|
|
|
|
PanelAnchor::Top => {
|
|
|
|
|
column(vec![row(dots).spacing(4).into(), cosmic_icon.into()])
|
|
|
|
|
.align_items(iced::Alignment::Center)
|
|
|
|
|
.spacing(4)
|
|
|
|
|
.into()
|
|
|
|
|
}
|
|
|
|
|
PanelAnchor::Bottom => {
|
|
|
|
|
column(vec![cosmic_icon.into(), row(dots).spacing(4).into()])
|
|
|
|
|
.align_items(iced::Alignment::Center)
|
|
|
|
|
.spacing(4)
|
|
|
|
|
.into()
|
|
|
|
|
}
|
2023-01-20 11:25:53 -05:00
|
|
|
};
|
2023-04-05 20:40:22 -04:00
|
|
|
let mut icon_button = cosmic::widget::button(Button::Text)
|
|
|
|
|
.custom(vec![icon_wrapper])
|
|
|
|
|
.padding(8);
|
|
|
|
|
if self.popup.is_none() {
|
|
|
|
|
icon_button = icon_button.on_press(
|
|
|
|
|
toplevels
|
|
|
|
|
.first()
|
|
|
|
|
.map(|t| Message::Activate(t.0.clone()))
|
|
|
|
|
.unwrap_or_else(|| Message::Exec(desktop_info.exec.clone())),
|
|
|
|
|
);
|
|
|
|
|
}
|
2023-01-20 11:25:53 -05:00
|
|
|
|
2023-04-05 20:40:22 -04:00
|
|
|
// TODO tooltip on hover
|
|
|
|
|
let icon_button =
|
|
|
|
|
mouse_listener(icon_button.width(Length::Shrink).height(Length::Shrink))
|
|
|
|
|
.on_right_release(Message::Popup(desktop_info.id.clone()));
|
|
|
|
|
let icon_button = if let Some(tracker) = self.rectangle_tracker.as_ref() {
|
|
|
|
|
tracker.container(*id, icon_button).into()
|
|
|
|
|
} else {
|
|
|
|
|
icon_button.into()
|
2022-12-15 15:49:59 -05:00
|
|
|
};
|
2023-04-05 20:40:22 -04:00
|
|
|
if self.config.favorites.contains(&desktop_info.id)
|
|
|
|
|
|| self.config.favorites.contains(&desktop_info.name)
|
|
|
|
|
{
|
|
|
|
|
favorites.push(icon_button)
|
2022-12-15 15:49:59 -05:00
|
|
|
} else {
|
2023-04-05 20:40:22 -04:00
|
|
|
running.push(icon_button);
|
2022-12-12 19:48:31 -05:00
|
|
|
}
|
2023-04-05 20:40:22 -04:00
|
|
|
(favorites, running)
|
|
|
|
|
},
|
|
|
|
|
);
|
2022-12-15 14:35:31 -05:00
|
|
|
|
2023-04-05 20:40:22 -04:00
|
|
|
let (w, h) = match self.applet_helper.anchor {
|
|
|
|
|
PanelAnchor::Top | PanelAnchor::Bottom => (Length::Shrink, Length::Fill),
|
|
|
|
|
PanelAnchor::Left | PanelAnchor::Right => (Length::Fill, Length::Shrink),
|
|
|
|
|
};
|
2022-12-15 14:35:31 -05:00
|
|
|
|
2023-04-05 20:40:22 -04:00
|
|
|
let content = match &self.applet_helper.anchor {
|
|
|
|
|
PanelAnchor::Left | PanelAnchor::Right => container(
|
|
|
|
|
column![
|
|
|
|
|
column(favorites),
|
|
|
|
|
divider::horizontal::light(),
|
|
|
|
|
column(running)
|
|
|
|
|
]
|
|
|
|
|
.spacing(4)
|
|
|
|
|
.align_items(Alignment::Center)
|
|
|
|
|
.height(h)
|
|
|
|
|
.width(w),
|
|
|
|
|
),
|
|
|
|
|
PanelAnchor::Top | PanelAnchor::Bottom => container(
|
|
|
|
|
row![row(favorites), vertical_rule(1), row(running)]
|
|
|
|
|
.spacing(4)
|
|
|
|
|
.align_items(Alignment::Center)
|
|
|
|
|
.height(h)
|
|
|
|
|
.width(w),
|
|
|
|
|
),
|
|
|
|
|
};
|
|
|
|
|
if self.popup.is_some() {
|
|
|
|
|
mouse_listener(content)
|
|
|
|
|
.on_right_press(Message::ClosePopup)
|
|
|
|
|
.on_press(Message::ClosePopup)
|
|
|
|
|
.into()
|
|
|
|
|
} else {
|
|
|
|
|
content.into()
|
2022-12-12 19:48:31 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn subscription(&self) -> Subscription<Message> {
|
|
|
|
|
Subscription::batch(vec![
|
2022-12-13 19:58:00 -05:00
|
|
|
toplevel_subscription(self.subscription_ctr).map(|(_, event)| Message::Toplevel(event)),
|
2022-12-15 14:35:31 -05:00
|
|
|
events_with(|e, _| match e {
|
2022-12-13 19:58:00 -05:00
|
|
|
cosmic::iced_native::Event::PlatformSpecific(
|
|
|
|
|
cosmic::iced_native::event::PlatformSpecific::Wayland(
|
|
|
|
|
cosmic::iced_native::event::wayland::Event::Seat(e, seat),
|
|
|
|
|
),
|
|
|
|
|
) => match e {
|
|
|
|
|
cosmic::iced_native::event::wayland::SeatEvent::Enter => {
|
|
|
|
|
Some(Message::NewSeat(seat))
|
2022-12-15 14:35:31 -05:00
|
|
|
}
|
2022-12-13 19:58:00 -05:00
|
|
|
cosmic::iced_native::event::wayland::SeatEvent::Leave => {
|
|
|
|
|
Some(Message::RemovedSeat(seat))
|
2022-12-15 14:35:31 -05:00
|
|
|
}
|
2022-12-13 19:58:00 -05:00
|
|
|
},
|
2022-12-15 14:35:31 -05:00
|
|
|
_ => None,
|
2022-12-13 19:58:00 -05:00
|
|
|
}),
|
2022-12-15 14:35:31 -05:00
|
|
|
rectangle_tracker_subscription(0).map(|(_, update)| Message::Rectangle(update)),
|
2022-12-12 19:48:31 -05:00
|
|
|
])
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn theme(&self) -> Theme {
|
|
|
|
|
self.theme
|
|
|
|
|
}
|
|
|
|
|
|
2023-04-05 20:40:22 -04:00
|
|
|
fn close_requested(&self, _id: window::Id) -> Self::Message {
|
2022-12-12 19:48:31 -05:00
|
|
|
Message::Ignore
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn style(&self) -> <Self::Theme as application::StyleSheet>::Style {
|
|
|
|
|
<Self::Theme as application::StyleSheet>::Style::Custom(|theme| Appearance {
|
|
|
|
|
background_color: Color::from_rgba(0.0, 0.0, 0.0, 0.0),
|
|
|
|
|
text_color: theme.cosmic().on_bg_color().into(),
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|