Iced port of status area applet
This is based on the GTK version of the status area applet that was previously in this repository. This exposes app indicators found over dbus. As used in applications like nm-applet and steam.
This commit is contained in:
parent
29a2dea760
commit
6a64486163
16 changed files with 1313 additions and 1 deletions
215
cosmic-applet-status-area/src/components/app.rs
Normal file
215
cosmic-applet-status-area/src/components/app.rs
Normal file
|
|
@ -0,0 +1,215 @@
|
|||
use cosmic::{
|
||||
app::{self, Command},
|
||||
iced::{
|
||||
self,
|
||||
wayland::{
|
||||
popup::{destroy_popup, get_popup},
|
||||
window::resize_window,
|
||||
},
|
||||
window, Subscription,
|
||||
},
|
||||
iced_style::application,
|
||||
Theme,
|
||||
};
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use crate::{components::status_menu, subscriptions::status_notifier_watcher};
|
||||
|
||||
// XXX copied from libcosmic
|
||||
const APPLET_PADDING: u32 = 8;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum Msg {
|
||||
Closed(window::Id),
|
||||
// XXX don't use index (unique window id? or I guess that's created and destroyed)
|
||||
StatusMenu((usize, status_menu::Msg)),
|
||||
StatusNotifier(status_notifier_watcher::Event),
|
||||
TogglePopup(usize),
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct App {
|
||||
core: app::Core,
|
||||
connection: Option<zbus::Connection>,
|
||||
menus: BTreeMap<usize, status_menu::State>,
|
||||
open_menu: Option<usize>,
|
||||
max_menu_id: usize,
|
||||
max_popup_id: u128,
|
||||
popup: Option<window::Id>,
|
||||
}
|
||||
|
||||
impl App {
|
||||
fn next_menu_id(&mut self) -> usize {
|
||||
self.max_menu_id += 1;
|
||||
self.max_menu_id
|
||||
}
|
||||
|
||||
fn next_popup_id(&mut self) -> window::Id {
|
||||
self.max_popup_id += 1;
|
||||
window::Id(self.max_popup_id)
|
||||
}
|
||||
|
||||
fn resize_window(&self) -> Command<Msg> {
|
||||
let icon_size = self.core.applet_helper.suggested_size().0 as u32 + APPLET_PADDING * 2;
|
||||
let n = self.menus.len() as u32;
|
||||
resize_window(window::Id(0), 1.max(icon_size * n), icon_size)
|
||||
}
|
||||
}
|
||||
|
||||
impl cosmic::Application for App {
|
||||
type Message = Msg;
|
||||
type Executor = iced::executor::Default;
|
||||
type Flags = ();
|
||||
const APP_ID: &'static str = "com.system76.CosmicAppletStatusArea";
|
||||
|
||||
fn init(core: app::Core, _flags: ()) -> (Self, app::Command<Msg>) {
|
||||
(
|
||||
Self {
|
||||
core,
|
||||
..Self::default()
|
||||
},
|
||||
Command::none(),
|
||||
)
|
||||
}
|
||||
|
||||
fn core(&self) -> &app::Core {
|
||||
&self.core
|
||||
}
|
||||
|
||||
fn core_mut(&mut self) -> &mut app::Core {
|
||||
&mut self.core
|
||||
}
|
||||
|
||||
fn style(&self) -> Option<<Theme as application::StyleSheet>::Style> {
|
||||
Some(app::applet::style())
|
||||
}
|
||||
|
||||
fn update(&mut self, message: Msg) -> Command<Msg> {
|
||||
match message {
|
||||
Msg::Closed(surface) => {
|
||||
if self.popup == Some(surface) {
|
||||
self.popup = None;
|
||||
self.open_menu = None;
|
||||
}
|
||||
Command::none()
|
||||
}
|
||||
Msg::StatusMenu((id, msg)) => match self.menus.get_mut(&id) {
|
||||
Some(state) => state
|
||||
.update(msg)
|
||||
.map(move |msg| app::message::app(Msg::StatusMenu((id, msg)))),
|
||||
None => Command::none(),
|
||||
},
|
||||
Msg::StatusNotifier(event) => match event {
|
||||
status_notifier_watcher::Event::Connected(connection) => {
|
||||
self.connection = Some(connection);
|
||||
Command::none()
|
||||
}
|
||||
status_notifier_watcher::Event::Registered(name) => {
|
||||
let (state, cmd) = status_menu::State::new(name);
|
||||
let id = self.next_menu_id();
|
||||
self.menus.insert(id, state);
|
||||
Command::batch([
|
||||
self.resize_window(),
|
||||
cmd.map(move |msg| app::message::app(Msg::StatusMenu((id, msg)))),
|
||||
])
|
||||
}
|
||||
status_notifier_watcher::Event::Unregistered(name) => {
|
||||
if let Some((id, _)) =
|
||||
self.menus.iter().find(|(_id, menu)| menu.name() == &name)
|
||||
{
|
||||
let id = *id;
|
||||
self.menus.remove(&id);
|
||||
if self.open_menu == Some(id) {
|
||||
self.open_menu = None;
|
||||
if let Some(popup_id) = self.popup {
|
||||
return destroy_popup(popup_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
self.resize_window()
|
||||
}
|
||||
status_notifier_watcher::Event::Error(err) => {
|
||||
eprintln!("Status notifier error: {}", err);
|
||||
Command::none()
|
||||
}
|
||||
},
|
||||
Msg::TogglePopup(id) => {
|
||||
self.open_menu = if self.open_menu != Some(id) {
|
||||
Some(id)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
// Reuse popup if a different menu is opened.
|
||||
// Had issue creating new one. Does it make a difference?
|
||||
if self.open_menu.is_some() {
|
||||
if self.popup.is_none() {
|
||||
let id = self.next_popup_id();
|
||||
let popup_settings = self.core.applet_helper.get_popup_settings(
|
||||
window::Id(0),
|
||||
id,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
);
|
||||
self.popup = Some(id);
|
||||
return get_popup(popup_settings);
|
||||
}
|
||||
} else if let Some(id) = self.popup {
|
||||
return destroy_popup(id);
|
||||
}
|
||||
Command::none()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn subscription(&self) -> Subscription<Msg> {
|
||||
let mut subscriptions = Vec::new();
|
||||
|
||||
subscriptions.push(status_notifier_watcher::subscription().map(Msg::StatusNotifier));
|
||||
|
||||
for (id, menu) in self.menus.iter() {
|
||||
subscriptions.push(menu.subscription().with(*id).map(Msg::StatusMenu));
|
||||
}
|
||||
|
||||
iced::Subscription::batch(subscriptions)
|
||||
}
|
||||
|
||||
fn view(&self) -> cosmic::Element<'_, Msg> {
|
||||
// XXX connect open event
|
||||
iced::widget::row(
|
||||
self.menus
|
||||
.iter()
|
||||
.map(|(id, menu)| {
|
||||
self.core
|
||||
.applet_helper
|
||||
.icon_button(menu.icon_name())
|
||||
.on_press(Msg::TogglePopup(*id))
|
||||
.into()
|
||||
})
|
||||
.collect(),
|
||||
)
|
||||
.into()
|
||||
}
|
||||
|
||||
fn view_window(&self, _surface: window::Id) -> cosmic::Element<'_, Msg> {
|
||||
match self.open_menu {
|
||||
Some(id) => match self.menus.get(&id) {
|
||||
Some(menu) => self
|
||||
.core
|
||||
.applet_helper
|
||||
.popup_container(menu.popup_view().map(move |msg| Msg::StatusMenu((id, msg))))
|
||||
.into(),
|
||||
None => unreachable!(),
|
||||
},
|
||||
None => iced::widget::text("").into(),
|
||||
}
|
||||
}
|
||||
|
||||
fn on_close_requested(&self, id: window::Id) -> Option<Msg> {
|
||||
Some(Msg::Closed(id))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn main() -> iced::Result {
|
||||
app::applet::run::<App>(true, ())
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue