From 6fef03ed2ff8c87dcb2e83216e40cbd92de501fa Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Tue, 13 Dec 2022 22:46:30 -0500 Subject: [PATCH] feat: config methods --- applets/cosmic-app-list/src/app.rs | 2 +- .../cosmic-app-list/src/apps_container/imp.rs | 26 - .../cosmic-app-list/src/apps_container/mod.rs | 92 --- .../cosmic-app-list/src/apps_window/imp.rs | 32 - .../cosmic-app-list/src/apps_window/mod.rs | 62 -- applets/cosmic-app-list/src/config.ron | 4 + applets/cosmic-app-list/src/config.rs | 27 +- applets/cosmic-app-list/src/dock_item/imp.rs | 41 -- applets/cosmic-app-list/src/dock_item/mod.rs | 195 ------ applets/cosmic-app-list/src/dock_list/imp.rs | 40 -- applets/cosmic-app-list/src/dock_list/mod.rs | 553 ------------------ .../cosmic-app-list/src/dock_object/imp.rs | 113 ---- .../cosmic-app-list/src/dock_object/mod.rs | 135 ----- .../cosmic-app-list/src/dock_popover/imp.rs | 45 -- .../cosmic-app-list/src/dock_popover/mod.rs | 234 -------- applets/cosmic-app-list/src/main.rs | 2 +- 16 files changed, 25 insertions(+), 1578 deletions(-) delete mode 100644 applets/cosmic-app-list/src/apps_container/imp.rs delete mode 100644 applets/cosmic-app-list/src/apps_container/mod.rs delete mode 100644 applets/cosmic-app-list/src/apps_window/imp.rs delete mode 100644 applets/cosmic-app-list/src/apps_window/mod.rs create mode 100644 applets/cosmic-app-list/src/config.ron delete mode 100644 applets/cosmic-app-list/src/dock_item/imp.rs delete mode 100644 applets/cosmic-app-list/src/dock_item/mod.rs delete mode 100644 applets/cosmic-app-list/src/dock_list/imp.rs delete mode 100644 applets/cosmic-app-list/src/dock_list/mod.rs delete mode 100644 applets/cosmic-app-list/src/dock_object/imp.rs delete mode 100644 applets/cosmic-app-list/src/dock_object/mod.rs delete mode 100644 applets/cosmic-app-list/src/dock_popover/imp.rs delete mode 100644 applets/cosmic-app-list/src/dock_popover/mod.rs diff --git a/applets/cosmic-app-list/src/app.rs b/applets/cosmic-app-list/src/app.rs index 2a071b2e..0c4a6482 100644 --- a/applets/cosmic-app-list/src/app.rs +++ b/applets/cosmic-app-list/src/app.rs @@ -329,7 +329,7 @@ impl Application for CosmicAppList { .height(Length::Units(self.applet_helper.suggested_icon_size())) .into() }; - let dot_radius = (self.applet_helper.suggested_icon_size() / 8).max(2); + let dot_radius = 2; let dots = (0..toplevels.len()) .into_iter() .map(|_| { diff --git a/applets/cosmic-app-list/src/apps_container/imp.rs b/applets/cosmic-app-list/src/apps_container/imp.rs deleted file mode 100644 index 58093e15..00000000 --- a/applets/cosmic-app-list/src/apps_container/imp.rs +++ /dev/null @@ -1,26 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0-only -use gtk4::glib; -use gtk4::subclass::prelude::*; -use once_cell::sync::OnceCell; - -use crate::dock_list::DockList; - -#[derive(Default)] -pub struct AppsContainer { - pub saved_list: OnceCell, - pub active_list: OnceCell, -} - -#[glib::object_subclass] -impl ObjectSubclass for AppsContainer { - // `NAME` needs to match `class` attribute of template - const NAME: &'static str = "AppsContainer"; - type Type = super::AppsContainer; - type ParentType = gtk4::Box; -} - -impl ObjectImpl for AppsContainer {} - -impl WidgetImpl for AppsContainer {} - -impl BoxImpl for AppsContainer {} diff --git a/applets/cosmic-app-list/src/apps_container/mod.rs b/applets/cosmic-app-list/src/apps_container/mod.rs deleted file mode 100644 index 766e25f4..00000000 --- a/applets/cosmic-app-list/src/apps_container/mod.rs +++ /dev/null @@ -1,92 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0-only -use crate::dock_list::DockList; -use crate::dock_list::DockListType; -use cascade::cascade; -use cosmic_panel_config::{PanelAnchor}; -use gtk4::prelude::*; -use gtk4::subclass::prelude::*; -use gtk4::Orientation; -use gtk4::Separator; -use gtk4::{gio, glib}; - -mod imp; - -glib::wrapper! { - pub struct AppsContainer(ObjectSubclass) - @extends gtk4::Widget, gtk4::Box, - @implements gtk4::Accessible, gtk4::Buildable, gtk4::ConstraintTarget, gtk4::Orientable; -} - -impl AppsContainer { - pub fn new() -> Self { - let self_: Self = glib::Object::new(&[]).expect("Failed to create AppsContainer"); - let imp = imp::AppsContainer::from_instance(&self_); - - cascade! { - &self_; - ..set_orientation(Orientation::Horizontal); - ..add_css_class("transparent"); - // ..add_css_class("dock_container"); - }; - - let saved_app_list_view = DockList::new(DockListType::Saved); - self_.append(&saved_app_list_view); - - let separator_container = cascade! { - gtk4::Box::new(Orientation::Vertical, 0); - ..set_margin_top(8); - ..set_margin_bottom(8); - ..set_vexpand(true); - }; - self_.append(&separator_container); - let separator = cascade! { - Separator::new(Orientation::Vertical); - ..set_margin_start(8); - ..set_margin_end(8); - ..set_vexpand(true); - ..add_css_class("dock_separator"); - }; - separator_container.append(&separator); - let active_app_list_view = DockList::new(DockListType::Active); - self_.append(&active_app_list_view); - - imp.saved_list.set(saved_app_list_view).unwrap(); - imp.active_list.set(active_app_list_view).unwrap(); - // Setup - self_.setup_callbacks(); - let anchor = std::env::var("COSMIC_PANEL_ANCHOR") - .ok() - .and_then(|anchor| anchor.parse::().ok()) - .unwrap_or_default(); - self_.set_position(anchor); - - Self::setup_callbacks(&self_); - - self_ - } - - pub fn model(&self, type_: DockListType) -> &gio::ListStore { - // Get state - let imp = imp::AppsContainer::from_instance(self); - match type_ { - DockListType::Active => imp.active_list.get().unwrap().model(), - DockListType::Saved => imp.saved_list.get().unwrap().model(), - } - } - - pub fn set_position(&self, position: PanelAnchor) { - self.set_orientation(position.into()); - let imp = imp::AppsContainer::from_instance(self); - imp.saved_list.get().unwrap().set_position(position); - imp.active_list.get().unwrap().set_position(position); - } - - fn setup_callbacks(&self) { - // Get state - let imp = imp::AppsContainer::from_instance(self); - let drop_controller = imp.saved_list.get().unwrap().drop_controller(); - - // hack to prevent hiding window when dnd from other apps - drop_controller.connect_enter(move |_self, _x, _y| gtk4::gdk::DragAction::COPY); - } -} diff --git a/applets/cosmic-app-list/src/apps_window/imp.rs b/applets/cosmic-app-list/src/apps_window/imp.rs deleted file mode 100644 index 5aed55f1..00000000 --- a/applets/cosmic-app-list/src/apps_window/imp.rs +++ /dev/null @@ -1,32 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0-only - -use crate::apps_container::AppsContainer; -use gtk4::{glib, subclass::prelude::*}; -use once_cell::sync::OnceCell; -// Object holding the state -#[derive(Default)] - -pub struct CosmicAppListWindow { - pub(super) inner: OnceCell, -} - -// The central trait for subclassing a GObject -#[glib::object_subclass] -impl ObjectSubclass for CosmicAppListWindow { - // `NAME` needs to match `class` attribute of template - const NAME: &'static str = "CosmicAppListWindow"; - type Type = super::CosmicAppListWindow; - type ParentType = gtk4::ApplicationWindow; -} - -// Trait shared by all GObjects -impl ObjectImpl for CosmicAppListWindow {} - -// Trait shared by all widgets -impl WidgetImpl for CosmicAppListWindow {} - -// Trait shared by all windows -impl WindowImpl for CosmicAppListWindow {} - -// Trait shared by all application -impl ApplicationWindowImpl for CosmicAppListWindow {} diff --git a/applets/cosmic-app-list/src/apps_window/mod.rs b/applets/cosmic-app-list/src/apps_window/mod.rs deleted file mode 100644 index 0db1afe0..00000000 --- a/applets/cosmic-app-list/src/apps_window/mod.rs +++ /dev/null @@ -1,62 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0-only - -use crate::{apps_container::AppsContainer, fl, AppListEvent}; -use cascade::cascade; -use gtk4::{ - gio, - glib::{self, Object}, - prelude::*, - subclass::prelude::*, -}; - -mod imp; - -glib::wrapper! { - pub struct CosmicAppListWindow(ObjectSubclass) - @extends gtk4::ApplicationWindow, gtk4::Window, gtk4::Widget, - @implements gio::ActionGroup, gio::ActionMap, gtk4::Accessible, gtk4::Buildable, - gtk4::ConstraintTarget, gtk4::Native, gtk4::Root, gtk4::ShortcutManager; -} - -impl CosmicAppListWindow { - pub fn new(app: >k4::Application) -> Self { - let self_: Self = - Object::new(&[("application", app)]).expect("Failed to create `CosmicAppListWindow`."); - let imp = imp::CosmicAppListWindow::from_instance(&self_); - - cascade! { - &self_; - ..set_width_request(1); - ..set_height_request(1); - ..set_decorated(false); - ..set_resizable(false); - ..set_title(Some(&fl!("cosmic-app-list"))); - ..add_css_class("transparent"); - }; - let app_list = AppsContainer::new(); - self_.set_child(Some(&app_list)); - imp.inner.set(app_list).unwrap(); - - self_.setup_shortcuts(); - - self_ - } - - pub fn apps_container(&self) -> &AppsContainer { - imp::CosmicAppListWindow::from_instance(&self) - .inner - .get() - .unwrap() - } - - fn setup_shortcuts(&self) { - let window = self.clone().upcast::(); - let action_quit = gio::SimpleAction::new("quit", None); - action_quit.connect_activate(glib::clone!(@weak window => move |_, _| { - window.close(); - if let Some(a) = window.application() { a.quit() } - std::process::exit(0); - })); - self.add_action(&action_quit); - } -} diff --git a/applets/cosmic-app-list/src/config.ron b/applets/cosmic-app-list/src/config.ron new file mode 100644 index 00000000..49ec9ff5 --- /dev/null +++ b/applets/cosmic-app-list/src/config.ron @@ -0,0 +1,4 @@ +( + filter_top_levels: None, + favorites: [], +) \ No newline at end of file diff --git a/applets/cosmic-app-list/src/config.rs b/applets/cosmic-app-list/src/config.rs index 17a67829..adb84c51 100644 --- a/applets/cosmic-app-list/src/config.rs +++ b/applets/cosmic-app-list/src/config.rs @@ -1,31 +1,35 @@ use anyhow::anyhow; -use serde::Deserialize; +use serde::{Serialize, Deserialize}; use std::fmt::Debug; use std::fs::File; +use std::path::PathBuf; use xdg::BaseDirectories; pub const APP_ID: &str = "com.system76.CosmicAppList"; pub const VERSION: &str = "0.1.0"; -#[derive(Debug, Clone, Deserialize, Default)] +#[derive(Debug, Clone, Deserialize, Serialize, Default)] pub enum TopLevelFilter { #[default] ActiveWorkspace, ConfiguredOutput, } -#[derive(Debug, Clone, Default, Deserialize)] +#[derive(Debug, Clone, Default, Deserialize, Serialize)] pub struct AppListConfig { pub filter_top_levels: Option, pub favorites: Vec, } impl AppListConfig { + // TODO async? /// load config with the provided name pub fn load() -> anyhow::Result { + let mut relative_path = PathBuf::from(APP_ID); + relative_path.push("config.ron"); let file = match BaseDirectories::new() .ok() - .and_then(|dirs| dirs.find_config_file(format!("{APP_ID}/config.ron"))) + .and_then(|dirs| dirs.find_config_file(relative_path)) .and_then(|p| File::open(p).ok()) { Some(path) => path, @@ -42,15 +46,22 @@ impl AppListConfig { if !self.favorites.contains(&id) { self.favorites.push(id); } - todo!() + self.save() } pub fn remove_favorite(&mut self, id: String) -> anyhow::Result<()> { self.favorites.retain(|e| e != &id); - todo!() + self.save() } - pub fn save() -> anyhow::Result<()> { - todo!() + // TODO async? + pub fn save(&self) -> anyhow::Result<()> { + let bd = BaseDirectories::new()?; + let mut relative_path = PathBuf::from(APP_ID); + relative_path.push("config.ron"); + let config_path = bd.place_config_file(relative_path)?; + let f = File::create(config_path)?; + ron::ser::to_writer_pretty(f, self, Default::default())?; + Ok(()) } } diff --git a/applets/cosmic-app-list/src/dock_item/imp.rs b/applets/cosmic-app-list/src/dock_item/imp.rs deleted file mode 100644 index 2d922b29..00000000 --- a/applets/cosmic-app-list/src/dock_item/imp.rs +++ /dev/null @@ -1,41 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0-only - -use gtk4::{ - glib::{self, subclass::Signal}, - prelude::*, - subclass::prelude::*, -}; -use once_cell::sync::Lazy; -use std::cell::{Cell, RefCell}; -use std::rc::Rc; - -use crate::dock_popover::DockPopover; - -#[derive(Debug, Default)] -pub struct DockItem { - pub image: Rc>>, - pub dots: Rc>, - pub item_box: Rc>, - pub popover: Rc>, - pub popover_menu: Rc>>, - pub icon_size: Rc>, -} - -#[glib::object_subclass] -impl ObjectSubclass for DockItem { - const NAME: &'static str = "DockItem"; - type Type = super::DockItem; - type ParentType = gtk4::Button; -} - -impl ObjectImpl for DockItem { - fn signals() -> &'static [Signal] { - static SIGNALS: Lazy> = - Lazy::new(|| vec![Signal::builder("popover-closed").build()]); - SIGNALS.as_ref() - } -} - -impl WidgetImpl for DockItem {} - -impl ButtonImpl for DockItem {} diff --git a/applets/cosmic-app-list/src/dock_item/mod.rs b/applets/cosmic-app-list/src/dock_item/mod.rs deleted file mode 100644 index a4c66b82..00000000 --- a/applets/cosmic-app-list/src/dock_item/mod.rs +++ /dev/null @@ -1,195 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0-only - -use crate::dock_object::DockObject; -use crate::dock_popover::DockPopover; -use crate::utils::AppListEvent; -use crate::utils::BoxedWindowList; -use cascade::cascade; -use cosmic_panel_config::PanelAnchor; -use gtk4::glib; -use gtk4::prelude::*; -use gtk4::subclass::prelude::*; -use gtk4::Box; -use gtk4::Image; -use gtk4::Orientation; -use gtk4::Popover; -use gtk4::{Align, PositionType}; - -mod imp; - -glib::wrapper! { - pub struct DockItem(ObjectSubclass) - @extends gtk4::Button, gtk4::Widget, - @implements gtk4::Accessible, gtk4::Actionable, gtk4::Buildable, gtk4::ConstraintTarget; -} - -impl DockItem { - pub fn new(icon_size: u32) -> Self { - let self_: DockItem = glib::Object::new(&[]).expect("Failed to create DockItem"); - - let item_box = Box::new(Orientation::Vertical, 0); - item_box.add_css_class("transparent"); - cascade! { - &self_; - ..set_child(Some(&item_box)); - ..add_css_class("dock_item"); - }; - - let image = cascade! { - Image::new(); - ..set_hexpand(true); - ..set_halign(Align::Center); - ..set_pixel_size(icon_size.try_into().unwrap()); - ..add_css_class("dock"); - }; - let dots = cascade! { - Box::new(Orientation::Horizontal, 4); - ..set_hexpand(true); - ..set_halign(Align::Center); - ..set_valign(Align::Center); - ..add_css_class("transparent"); - }; - - item_box.append(&image); - item_box.append(&dots); - let popover = cascade! { - Popover::new(); - ..set_autohide(true); - ..add_css_class("dock"); - ..set_has_arrow(false); - }; - item_box.append(&popover); - let self_clone = self_.clone(); - popover.connect_closed(move |_| { - let _ = self_clone.emit_by_name::<()>("popover-closed", &[]); - }); - - let popover_menu = cascade! { - DockPopover::new(); - ..add_css_class("popover_menu"); - }; - popover.set_child(Some(&popover_menu)); - popover_menu.connect_local( - "menu-hide", - false, - glib::clone!(@weak popover, @weak popover_menu => @default-return None, move |_| { - popover.popdown(); - popover_menu.reset_menu(); - None - }), - ); - - let imp = imp::DockItem::from_instance(&self_); - imp.icon_size.set(icon_size); - imp.image.replace(Some(image)); - imp.dots.replace(dots); - imp.item_box.replace(item_box); - imp.popover.replace(popover); - imp.popover_menu.replace(Some(popover_menu)); - self_ - } - - // refactor to emit event for removing the item? - pub fn set_dock_object(&self, dock_object: &DockObject) { - let imp = imp::DockItem::from_instance(self); - let image = cascade! { - dock_object.get_image(); - ..set_hexpand(true); - ..set_halign(Align::Center); - ..set_pixel_size(imp.icon_size.get().try_into().unwrap()); - ..set_tooltip_text(dock_object.get_name().as_deref()); - }; - let old_image = imp.image.replace(None); - if let Some(old_image) = old_image { - imp.item_box.borrow().remove(&old_image); - imp.item_box.borrow().prepend(&image); - imp.image.replace(Some(image)); - } - let active = dock_object.property::("active"); - let dots = imp.dots.borrow(); - while let Some(c) = dots.first_child() { - dots.remove(&c); - } - - for _ in active.0 { - dots.append(&cascade! { - Box::new(Orientation::Horizontal, 0); - ..set_halign(Align::Center); - ..set_valign(Align::Center); - ..add_css_class("dock_dots"); - }); - } - - let popover = dock_object.property::("popover"); - // dbg!(popover); - // dbg!(dock_object); - if popover { - self.add_popover(dock_object); - } else { - self.clear_popover(); - } - } - - pub fn set_position(&self, position: PanelAnchor) { - let imp = imp::DockItem::from_instance(self); - let item_box = imp.item_box.borrow(); - let dots = imp.dots.borrow(); - if let Some(image) = imp.image.borrow().as_ref() { - match position { - PanelAnchor::Left => { - item_box.set_orientation(Orientation::Horizontal); - dots.set_orientation(Orientation::Vertical); - dots.set_margin_bottom(4); - dots.set_margin_top(4); - item_box.reorder_child_after(&image.clone(), Some(&dots.clone())); - } - PanelAnchor::Right => { - item_box.set_orientation(Orientation::Horizontal); - dots.set_orientation(Orientation::Vertical); - dots.set_margin_bottom(4); - dots.set_margin_top(4); - item_box.reorder_child_after(&dots.clone(), Some(&image.clone())); - } - PanelAnchor::Top => { - item_box.set_orientation(Orientation::Vertical); - dots.set_orientation(Orientation::Horizontal); - dots.set_margin_start(4); - dots.set_margin_end(4); - item_box.reorder_child_after(&image.clone(), Some(&dots.clone())); - } - PanelAnchor::Bottom => { - item_box.set_orientation(Orientation::Vertical); - dots.set_orientation(Orientation::Horizontal); - dots.set_margin_start(4); - dots.set_margin_end(4); - item_box.reorder_child_after(&dots.clone(), Some(&image.clone())); - } - }; - } - let popover = imp.popover.borrow(); - popover.set_position(match position { - PanelAnchor::Left => PositionType::Right, - PanelAnchor::Right => PositionType::Left, - PanelAnchor::Top => PositionType::Bottom, - PanelAnchor::Bottom => PositionType::Top, - }); - } - - pub fn add_popover(&self, obj: &DockObject) { - let imp = imp::DockItem::from_instance(self); - let popover = imp.popover.borrow(); - if let Some(popover_menu) = imp.popover_menu.borrow().as_ref() { - popover_menu.set_dock_object(obj, true); - popover.popup(); - } - } - - pub fn clear_popover(&self) { - let imp = imp::DockItem::from_instance(self); - let popover = imp.popover.borrow(); - if let Some(popover_menu) = imp.popover_menu.borrow().as_ref() { - popover.popdown(); - popover_menu.reset_menu(); - } - } -} diff --git a/applets/cosmic-app-list/src/dock_list/imp.rs b/applets/cosmic-app-list/src/dock_list/imp.rs deleted file mode 100644 index d11e6dce..00000000 --- a/applets/cosmic-app-list/src/dock_list/imp.rs +++ /dev/null @@ -1,40 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0-only - -use cosmic_panel_config::{CosmicPanelConfig, PanelAnchor}; -use glib::SignalHandlerId; -use gtk4::subclass::prelude::*; -use gtk4::{gio, glib}; -use gtk4::{Box, DragSource, DropTarget, GestureClick, ListView}; -use once_cell::sync::OnceCell; -use std::cell::{Cell, RefCell}; -use std::rc::Rc; - -use crate::utils::AppListEvent; - -#[derive(Debug, Default)] -pub struct DockList { - pub list_view: OnceCell, - pub type_: OnceCell, - pub model: OnceCell, - pub click_controller: OnceCell, - pub drop_controller: OnceCell, - pub drag_source: OnceCell, - pub drag_end_signal: Rc>>, - pub drag_cancel_signal: Rc>>, - pub popover_menu_index: Rc>>, - pub position: Rc>, - pub config: OnceCell, -} - -#[glib::object_subclass] -impl ObjectSubclass for DockList { - const NAME: &'static str = "DockList"; - type Type = super::DockList; - type ParentType = Box; -} - -impl ObjectImpl for DockList {} - -impl WidgetImpl for DockList {} - -impl BoxImpl for DockList {} diff --git a/applets/cosmic-app-list/src/dock_list/mod.rs b/applets/cosmic-app-list/src/dock_list/mod.rs deleted file mode 100644 index 42ad4c21..00000000 --- a/applets/cosmic-app-list/src/dock_list/mod.rs +++ /dev/null @@ -1,553 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0-only - -use crate::{ - dock_item::DockItem, - dock_object::DockObject, - utils::{data_path, AppListEvent, BoxedWindowList}, - wayland::{Toplevel, ToplevelEvent}, - {TX, WAYLAND_TX}, -}; -use cascade::cascade; -use cosmic_panel_config::{CosmicPanelConfig, PanelAnchor, PanelSize}; - -use gio::traits::AppLaunchContextExt; -use gtk4::{ - gdk::{self, ContentProvider, Display, ModifierType}, - gio::{self, DesktopAppInfo, Icon}, - glib::{self, Object, Type}, - prelude::ListModelExt, - prelude::*, - subclass::prelude::*, - DropTarget, IconTheme, ListView, Orientation, SignalListItemFactory, - {DragSource, GestureClick}, -}; -use std::{fs::File, path::Path}; - -mod imp; - -glib::wrapper! { - pub struct DockList(ObjectSubclass) - @extends gtk4::Widget, gtk4::Box, - @implements gtk4::Accessible, gtk4::Buildable, gtk4::ConstraintTarget, gtk4::Orientable; -} - -#[derive(Debug, PartialEq, Copy, Clone)] -pub enum DockListType { - Saved, - Active, -} - -impl Default for DockListType { - fn default() -> Self { - DockListType::Active - } -} - -impl DockList { - pub fn new(type_: DockListType) -> Self { - let self_: DockList = glib::Object::new(&[]).expect("Failed to create DockList"); - let imp = imp::DockList::from_instance(&self_); - imp.type_.set(type_).unwrap(); - self_.layout(); - //dnd behavior is different for each type, as well as the data in the model - self_.setup_model(); - self_.setup_click_controller(); - self_.setup_drag(); - self_.setup_drop_target(); - self_.setup_factory(); - self_ - } - - pub fn model(&self) -> &gio::ListStore { - // Get state - let imp = imp::DockList::from_instance(self); - imp.model.get().expect("Could not get model") - } - - pub fn drop_controller(&self) -> &DropTarget { - // Get state - let imp = imp::DockList::from_instance(self); - imp.drop_controller.get().expect("Could not get model") - } - - pub fn popover_index(&self) -> Option { - // Get state - let imp = imp::DockList::from_instance(self); - imp.popover_menu_index.get() - } - - fn restore_data(&self) { - // TODO use IDs instead of names - if let Ok(file) = File::open(data_path()) { - if let Ok(data) = serde_json::from_reader::<_, Vec>(file) { - // dbg!(&data); - let dock_objects: Vec = data - .into_iter() - .filter_map(|d| { - DockObject::from_app_info_path(&d) - .map(|dockobject| dockobject.upcast::()) - }) - .collect(); - // dbg!(&dock_objects); - - let model = self.model(); - model.splice(model.n_items(), 0, &dock_objects); - } - } else { - eprintln!("Error loading saved apps!"); - let model = &self.model(); - xdg::BaseDirectories::new() - .expect("could not access XDG Base directory") - .get_data_dirs() - .iter_mut() - .for_each(|xdg_data_path| { - let defaults = ["Firefox Web Browser", "Files", "Terminal", "Pop!_Shop"]; - xdg_data_path.push("applications"); - // dbg!(&xdg_data_path); - if let Ok(dir_iter) = std::fs::read_dir(xdg_data_path) { - dir_iter.for_each(|dir_entry| { - if let Ok(dir_entry) = dir_entry { - if let Some(path) = dir_entry.path().file_name() { - if let Some(path) = path.to_str() { - if let Some(app_info) = gio::DesktopAppInfo::new(path) { - if app_info.should_show() - && defaults.contains(&app_info.name().as_str()) - { - model.append(&DockObject::new(app_info)); - } else { - // println!("Ignoring {}", path); - } - } else { - // println!("error loading {}", path); - } - } - } - } - }) - } - }); - } - } - - fn store_data(model: &gio::ListStore) { - // Store todo data in vector - let mut backup_data = Vec::new(); - let mut i = 0; - while let Some(item) = model.item(i) { - // Get `AppGroup` from `glib::Object` - let dock_object = item - .downcast_ref::() - .expect("The object needs to be of type `AppGroupData`."); - // Add todo data to vector and increase position - if let Some(app_info) = dock_object.property::>("appinfo") { - if let Some(f) = app_info.filename() { - backup_data.push(f); - } - } - i += 1; - } - // dbg!(&backup_data); - // Save state in file - let file = File::create(data_path()).expect("Could not create json file."); - serde_json::to_writer_pretty(file, &backup_data) - .expect("Could not write data to json file"); - // TODO save plugins here for now examples are hardcoded and don't need to be saved - } - - fn layout(&self) { - let imp = imp::DockList::from_instance(self); - let list_view = cascade! { - ListView::default(); - ..set_orientation(Orientation::Horizontal); - ..add_css_class("transparent"); - }; - if imp.type_.get().unwrap() == &DockListType::Saved { - list_view.set_width_request(64); - } - self.append(&list_view); - imp.list_view.set(list_view).unwrap(); - } - - pub fn set_position(&self, position: PanelAnchor) { - let imp = imp::DockList::from_instance(self); - let model = imp.model.get().unwrap(); - imp.position.replace(position); - model.items_changed(0, model.n_items(), model.n_items()); - - let imp = imp::DockList::from_instance(self); - imp.list_view - .get() - .unwrap() - .set_orientation(position.into()); - } - - fn setup_model(&self) { - let imp = imp::DockList::from_instance(self); - let model = gio::ListStore::new(DockObject::static_type()); - - let selection_model = gtk4::NoSelection::new(Some(&model)); - - // Wrap model with selection and pass it to the list view - let list_view = imp.list_view.get().unwrap(); - list_view.set_model(Some(&selection_model)); - imp.model.set(model).expect("Could not set model"); - - if imp.type_.get().unwrap() == &DockListType::Saved { - let model = self.model(); - self.restore_data(); - model.connect_items_changed(|model, _, _removed, _added| { - Self::store_data(model); - }); - } - } - - fn setup_click_controller(&self) { - let imp = imp::DockList::from_instance(self); - let controller = GestureClick::builder() - .button(0) - .propagation_limit(gtk4::PropagationLimit::None) - .propagation_phase(gtk4::PropagationPhase::Capture) - .build(); - self.add_controller(&controller); - - let model = self.model(); - let list_view = &imp.list_view.get().unwrap(); - let popover_menu_index = &imp.popover_menu_index; - controller.connect_released(glib::clone!(@weak model, @weak list_view, @weak popover_menu_index => move |self_, _, x, y| { - let max_x = list_view.allocated_width(); - let max_y = list_view.allocated_height(); - let (indexing_dim, indexing_length, other_dim, other_length) = match list_view.orientation() { - Orientation::Horizontal => (x, max_x, y, max_y), - Orientation::Vertical => (y, max_y, x, max_x), - _ => return, - }; - // dbg!(max_y); - // dbg!(y); - let n_buckets = model.n_items(); - let index = (indexing_dim * n_buckets as f64 / (indexing_length as f64 + 0.1)) as u32; - // dbg!(self_.current_button()); - // dbg!(self_.last_event(self_.current_sequence().as_ref())); - let click_modifier = self_.last_event(self_.current_sequence().as_ref()).map(|event| event.modifier_state()); - // dbg!(click_modifier); - // Launch the application when an item of the list is activated - - // TODO use seat eventually - // let wl_seat = self_.device().map(|d| d.seat().downcast::().unwrap().wl_seat()).unwrap(); - let focus_window = move |first_focused_item: &Toplevel| { - let toplevel_handle = first_focused_item.toplevel_handle.clone(); - let tx = WAYLAND_TX.get().unwrap().clone(); - let _ = tx.clone().send(ToplevelEvent::Activate(toplevel_handle)); - }; - - let old_index = popover_menu_index.get(); - if let Some(old_index) = old_index { - if let Some(old_item) = model.item(old_index) { - if let Ok(old_dock_object) = old_item.downcast::() { - old_dock_object.set_popover(false); - popover_menu_index.replace(None); - model.items_changed(old_index, 0, 0); - //TODO signal dock to check if it should hide - } - } - return; - } - if other_dim > f64::from(other_length) || y < 0.0 || indexing_dim > f64::from(indexing_length) || indexing_dim < 0.0 { - // println!("out of bounds click..."); - return; - } - - if let Some(item) = model.item(index) { - if let Ok(dock_object) = item.downcast::() { - let active = dock_object.property::("active"); - let app_info = dock_object.property::>("appinfo"); - match (self_.current_button(), click_modifier, active.0.get(0), app_info) { - (click, Some(click_modifier), Some(first_focused_item), _) if click == 1 && !click_modifier.contains(ModifierType::CONTROL_MASK) => focus_window(first_focused_item), - (click, None, Some(first_focused_item), _) if click == 1 => focus_window(first_focused_item), - (click, _, _, Some(app_info)) | (click, _, None, Some(app_info)) if click != 3 => { - let context = gdk::Display::default().unwrap().app_launch_context(); - if let Err(err) = app_info.launch(&[], Some(&context)) { - dbg!(err); - } - - } - (click, _, _, _) if click == 3 => { - // println!("handling right click"); - if let Some(old_index) = popover_menu_index.get() { - if let Some(item) = model.item(old_index) { - if let Ok(dock_object) = item.downcast::() { - dock_object.set_popover(false); - popover_menu_index.replace(Some(index)); - model.items_changed(old_index, 0, 0); - } - } - } - dock_object.set_popover(true); - popover_menu_index.replace(Some(index)); - model.items_changed(index, 0, 0); - } - _ => eprintln!("Failed to process click.") - } - } - } - })); - imp.click_controller.set(controller).unwrap(); - } - - fn setup_drop_target(&self) { - let imp = imp::DockList::from_instance(self); - if imp.type_.get().unwrap() != &DockListType::Saved { - return; - } - - let drop_target_widget = &imp.list_view.get().unwrap(); - let mut drop_actions = gdk::DragAction::COPY; - drop_actions.insert(gdk::DragAction::MOVE); - let drop_format = gdk::ContentFormats::for_type(Type::STRING); - let drop_format = drop_format.union(&gdk::ContentFormats::for_type(Type::U32)); - let drop_controller = DropTarget::builder() - .preload(true) - .actions(drop_actions) - .formats(&drop_format) - .build(); - drop_target_widget.add_controller(&drop_controller); - - let model = self.model(); - let list_view = &imp.list_view.get().unwrap(); - let drag_end = &imp.drag_end_signal; - let drag_source = &imp.drag_source.get().unwrap(); - drop_controller.connect_drop( - glib::clone!(@weak model, @weak list_view, @weak drag_end, @weak drag_source => @default-return true, move |_self, drop_value, x, y| { - //calculate insertion location - let max_x = list_view.allocated_width(); - let max_y = list_view.allocated_height(); - let n_buckets = model.n_items() * 2; - - let (indexing_dim, indexing_length, _other_dim, _other_length) = match list_view.orientation() { - Orientation::Horizontal => (x, max_x, y, max_y), - Orientation::Vertical => (y, max_y, x, max_x), - _ => (x, max_x, y, max_y), - }; - - let drop_bucket = (indexing_dim * n_buckets as f64 / (indexing_length as f64 + 0.1)) as u32; - let index = if drop_bucket == 0 { - 0 - } else if drop_bucket == n_buckets - 1 { - model.n_items() - } else { - (drop_bucket + 1) / 2 - }; - - if let Ok(Some(path_str)) = drop_value.get::>() { - let desktop_path = &Path::new(&path_str); - if let Some(pathbase) = desktop_path.file_name() { - if let Some(app_info) = gio::DesktopAppInfo::new(&pathbase.to_string_lossy()) { - // remove item if already exists - let mut i: u32 = 0; - let mut index_of_existing_app: Option = None; - while let Some(item) = model.item(i) { - if let Ok(cur_app_info) = item.downcast::() { - if let Some(cur_app_info) = cur_app_info.property::>("appinfo") { - if cur_app_info.filename() == Some(Path::new(&path_str).to_path_buf()) { - index_of_existing_app = Some(i); - } - } - } - i += 1; - } - if let Some(index_of_existing_app) = index_of_existing_app { - // remove existing entry - model.remove(index_of_existing_app); - if let Some(old_handle) = drag_end.replace(None) { - glib::signal_handler_disconnect(&drag_source, old_handle); - } - } - model.insert(index, &DockObject::new(app_info)); - } - } - } - else if let Ok(old_index) = drop_value.get::() { - if let Some(item) = model.item(old_index) { - if let Ok(dock_object) = item.downcast::() { - model.remove(old_index); - model.insert(index, &dock_object); - if let Some(old_handle) = drag_end.replace(None) { - glib::signal_handler_disconnect(&drag_source, old_handle); - } - } - } - } - else { - // dbg!("rejecting drop"); - _self.reject(); - } - let tx = TX.get().unwrap().clone(); - let _ = tx.send(AppListEvent::Refresh); - true - }), - ); - - imp.drop_controller - .set(drop_controller) - .expect("Could not set dock dnd drop controller"); - } - - fn setup_drag(&self) { - let imp = imp::DockList::from_instance(self); - let type_ = imp.type_.get().unwrap(); - - let actions = match *type_ { - DockListType::Saved => gdk::DragAction::MOVE, - DockListType::Active => gdk::DragAction::COPY, - }; - let drag_source = DragSource::builder() - .name("dock drag source") - .actions(actions) - .build(); - - let model = self.model(); - let list_view = imp.list_view.get().unwrap(); - let drag_end = &imp.drag_end_signal; - let drag_cancel = &imp.drag_cancel_signal; - let type_ = *type_; - list_view.add_controller(&drag_source); - drag_source.connect_prepare(glib::clone!(@weak model, @weak list_view, @weak drag_end, @weak drag_cancel => @default-return None, move |self_, x, _y| { - let max_x = list_view.allocated_width(); - // dbg!(max_x); - // dbg!(max_y); - let n_buckets = model.n_items(); - - let index = (x * n_buckets as f64 / (max_x as f64 + 0.1)) as u32; - if let Some(item) = model.item(index) { - if type_ == DockListType::Saved { - if let Some(old_handle) = drag_end.replace(Some(self_.connect_drag_end( - glib::clone!(@weak model => move |_self, _drag, _delete_data| { - if _delete_data { - model.remove(index); - let tx = TX.get().unwrap().clone(); - let _ = tx.send(AppListEvent::Refresh); - - }; - }), - ))) { - glib::signal_handler_disconnect(self_, old_handle); - } - - if let Some(old_handle) = drag_cancel.replace(Some(self_.connect_drag_cancel( - glib::clone!(@weak model => @default-return false, move |_self, _drag, cancel_reason| { - if cancel_reason != gdk::DragCancelReason::UserCancelled { - model.remove(index); - let tx = TX.get().unwrap().clone(); - let _ = tx.send(AppListEvent::Refresh); - true - } else { - false - } - }), - ))) { - glib::signal_handler_disconnect(self_, old_handle); - } - } - if let Ok(dock_object) = item.downcast::() { - if let Some(app_info) = dock_object.property::>("appinfo") { - let icon = app_info - .icon() - .unwrap_or_else(|| Icon::for_string("image-missing").expect("Failed to set default icon")); - - if let Some(default_display) = &Display::default() { - let icon_theme = IconTheme::for_display(default_display); - let paintable_icon = icon_theme.lookup_by_gicon( - &icon, - 64, - 1, - gtk4::TextDirection::None, - gtk4::IconLookupFlags::empty(), - ); - self_.set_icon(Some(&paintable_icon), 32, 32); - } - - // saved app list provides index - return match type_ { - DockListType::Saved => Some(ContentProvider::for_value(&index.to_value())), - DockListType::Active => app_info.filename().map(|file| ContentProvider::for_value(&file.to_string_lossy().to_value())) - } - } - } - } - None - })); - - // TODO investigate why drop does not finish when dropping on some surfaces - // for now this is a fix that will cancel the drop after 100 ms and not completing. - drag_source.connect_drag_begin(|_self, drag| { - drag.connect_drop_performed(|_self| { - glib::timeout_add_local_once( - std::time::Duration::from_millis(100), - glib::clone!(@weak _self => move || { - _self.drop_done(false); - }), - ); - }); - }); - - imp.drag_source - .set(drag_source) - .expect("Could not set saved drag source"); - } - - fn setup_factory(&self) { - let imp = imp::DockList::from_instance(self); - let popover_menu_index = &imp.popover_menu_index; - let factory = SignalListItemFactory::new(); - let model = imp.model.get().expect("Failed to get saved app model."); - - let icon_size = std::env::var("COSMIC_PANEL_SIZE") - .ok() - .and_then(|size| match size.parse::() { - Ok(PanelSize::XL) => Some(64), - Ok(PanelSize::L) => Some(48), - Ok(PanelSize::M) => Some(36), - Ok(PanelSize::S) => Some(24), - Ok(PanelSize::XS) => Some(18), - Err(_) => Some(36), - }) - .unwrap_or(36); - - factory.connect_setup( - glib::clone!(@weak popover_menu_index, @weak model => move |_, list_item| { - let dock_item = DockItem::new(icon_size); - dock_item - .connect_local("popover-closed", false, move |_| { - if let Some(old_index) = popover_menu_index.replace(None) { - if let Some(item) = model.item(old_index) { - if let Ok(dock_object) = item.downcast::() { - dock_object.set_popover(false); - model.items_changed(old_index, 0, 0); - } - } - } - - None - }); - list_item.set_child(Some(&dock_item)); - }), - ); - factory.connect_bind( - glib::clone!(@weak imp.position as position => move |_, list_item| { - let dock_object = list_item - .item() - .expect("The item has to exist.") - .downcast::() - .expect("The item has to be a `DockObject`"); - let dock_item = list_item - .child() - .expect("The list item child needs to exist.") - .downcast::() - .expect("The list item type needs to be `DockItem`"); - dock_item.set_dock_object(&dock_object); - dock_item.set_position(position.get()); - }), - ); - // Set the factory of the list view - imp.list_view.get().unwrap().set_factory(Some(&factory)); - } -} diff --git a/applets/cosmic-app-list/src/dock_object/imp.rs b/applets/cosmic-app-list/src/dock_object/imp.rs deleted file mode 100644 index 7c13076c..00000000 --- a/applets/cosmic-app-list/src/dock_object/imp.rs +++ /dev/null @@ -1,113 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0-only - -use std::cell::Cell; -use std::cell::RefCell; - -use crate::utils::BoxedWindowList; -use gio::DesktopAppInfo; -use glib::{ParamFlags, ParamSpec, Value}; -use gtk4::gdk::glib::ParamSpecBoolean; -use gtk4::gdk::glib::ParamSpecBoxed; -use gtk4::gdk::glib::ParamSpecObject; -use gtk4::glib; -use gtk4::prelude::*; -use gtk4::subclass::prelude::*; -use once_cell::sync::Lazy; - -// Object holding the state -#[derive(Default)] -pub struct DockObject { - pub(super) appinfo: RefCell>, - pub(super) active: RefCell, - pub(super) saved: Cell, - pub(super) popover: Cell, -} - -// The central trait for subclassing a GObject -#[glib::object_subclass] -impl ObjectSubclass for DockObject { - const NAME: &'static str = "DockObject"; - type Type = super::DockObject; - type ParentType = glib::Object; -} - -// Trait shared by all GObjects -impl ObjectImpl for DockObject { - fn properties() -> &'static [ParamSpec] { - static PROPERTIES: Lazy> = Lazy::new(|| { - vec![ - ParamSpecObject::new( - // Name - "appinfo", - // Nickname - "appinfo", - // Short description - "app info", - DesktopAppInfo::static_type(), - // The property can be read and written to - ParamFlags::READWRITE, - ), - ParamSpecBoxed::new( - // Name - "active", - // Nickname - "active", - // Short description - "active", - BoxedWindowList::static_type(), - // The property can be read and written to - ParamFlags::READWRITE, - ), - ParamSpecBoolean::new( - "saved", - "saved", - "Indicates whether app is saved to the dock", - false, - ParamFlags::READWRITE, - ), - ParamSpecBoolean::new( - "popover", - "popover", - "Indicates whether there is a popover menu displayed for this object", - false, - ParamFlags::READWRITE, - ), - ] - }); - PROPERTIES.as_ref() - } - - fn set_property(&self, _obj: &Self::Type, _id: usize, value: &Value, pspec: &ParamSpec) { - match pspec.name() { - "appinfo" => { - let appinfo = value - .get() - .expect("Value needs to be Option"); - self.appinfo.replace(appinfo); - } - "active" => { - let active = value.get().expect("Value needs to be BoxedWindowList"); - self.active.replace(active); - } - "saved" => { - self.saved - .replace(value.get().expect("Value needs to be a boolean")); - } - "popover" => { - self.popover - .replace(value.get().expect("Value needs to be a boolean")); - } - _ => unimplemented!(), - } - } - - fn property(&self, _obj: &Self::Type, _id: usize, pspec: &ParamSpec) -> Value { - match pspec.name() { - "appinfo" => self.appinfo.borrow().to_value(), - "active" => self.active.borrow().to_value(), - "saved" => self.saved.get().to_value(), - "popover" => self.popover.get().to_value(), - _ => unimplemented!(), - } - } -} diff --git a/applets/cosmic-app-list/src/dock_object/mod.rs b/applets/cosmic-app-list/src/dock_object/mod.rs deleted file mode 100644 index df0974ed..00000000 --- a/applets/cosmic-app-list/src/dock_object/mod.rs +++ /dev/null @@ -1,135 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0-only - -use crate::utils::BoxedWindowList; -use gio::{DesktopAppInfo, Icon}; -use gtk4::gdk::glib::Object; -use gtk4::gdk::subclass::prelude::ObjectSubclassExt; -use gtk4::prelude::*; -use gtk4::{glib, Image}; -use std::path::Path; - -mod imp; - -glib::wrapper! { - pub struct DockObject(ObjectSubclass); -} - -impl DockObject { - pub fn new(appinfo: DesktopAppInfo) -> Self { - Object::new(&[("appinfo", &Some(appinfo)), ("saved", &true)]) - .expect("Failed to create `DockObject`.") - } - - pub fn from_app_info_path(path: &str) -> Option { - if let Some(path) = Path::new(path).file_name() { - if let Some(path) = path.to_str() { - if let Some(appinfo) = gio::DesktopAppInfo::new(path) { - if appinfo.should_show() { - return Some( - Object::new(&[("appinfo", &Some(appinfo)), ("saved", &true)]) - .expect("Failed to create `DockObject`."), - ); - } - } - } - } - None - } - - pub fn get_path(&self) -> Option { - let imp = imp::DockObject::from_instance(self); - if let Some(app_info) = imp.appinfo.borrow().as_ref() { - app_info - .filename() - .map(|name| name.to_string_lossy().into()) - } else { - None - } - } - - pub fn get_name(&self) -> Option { - let imp = imp::DockObject::from_instance(self); - imp.appinfo - .borrow() - .as_ref() - .map(|app_info| app_info.name().to_string()) - } - - pub fn get_image(&self) -> gtk4::Image { - let imp = imp::DockObject::from_instance(self); - if let Some(app_info) = imp.appinfo.borrow().as_ref() { - let image = Image::new(); - let icon = app_info.icon().unwrap_or_else(|| { - Icon::for_string("image-missing").expect("Failed to set default icon") - }); - image.set_from_gicon(&icon); - image.set_tooltip_text(None); - image - } else { - eprintln!("failed to load image"); - let image = Image::new(); - image.set_tooltip_text(None); - image - } - } - - pub fn set_saved(&self, is_saved: bool) { - let imp = imp::DockObject::from_instance(self); - imp.saved.replace(is_saved); - } - - pub fn from_window_list(results: BoxedWindowList) -> Option { - if let Some(first) = results.0.get(0) { - return xdg::BaseDirectories::new() - .expect("could not access XDG Base directory") - .get_data_dirs() - .iter_mut() - .filter_map(|xdg_data_path| { - xdg_data_path.push("applications"); - std::fs::read_dir(xdg_data_path).ok() - }) - .flatten() - .filter_map(|dir_entry| { - if let Ok(dir_entry) = dir_entry { - if let Some(path) = dir_entry.path().file_name() { - if let Some(path) = path.to_str() { - if let Some(app_info) = gio::DesktopAppInfo::new(path) { - if app_info.should_show() - && Some(&first.app_id) - == app_info - .filename() - .and_then(|p| { - p.file_stem().and_then(|s| { - s.to_str().map(|s| s.to_string()) - }) - }) - .as_ref() - { - return Some( - Object::new(&[ - ("appinfo", &app_info), - ("active", &results), - ]) - .expect("Failed to create `DockObject`."), - ); - } - } - } - } - } - None - }) - .next(); - } - None - } - - pub fn set_popover(&self, b: bool) { - let imp = imp::DockObject::from_instance(self); - imp.popover.replace(b); - } -} - -#[derive(Clone, Debug, Default, glib::Boxed)] -#[boxed_type(name = "BoxedDockObject")] -pub struct BoxedDockObject(pub Option); diff --git a/applets/cosmic-app-list/src/dock_popover/imp.rs b/applets/cosmic-app-list/src/dock_popover/imp.rs deleted file mode 100644 index d1782e1d..00000000 --- a/applets/cosmic-app-list/src/dock_popover/imp.rs +++ /dev/null @@ -1,45 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0-only - -use std::cell::RefCell; -use std::rc::Rc; - -use glib::subclass::Signal; -use gtk4::glib; -use gtk4::prelude::*; -use gtk4::subclass::prelude::*; -use gtk4::{Box, Button, ListBox, Revealer}; -use once_cell::sync::Lazy; - -use crate::dock_object::DockObject; - -#[derive(Debug, Default)] -pub struct DockPopover { - pub menu_handle: Rc>, - pub all_windows_item_revealer: Rc>, - pub all_windows_item_header: Rc>, - pub window_list: Rc>, - pub launch_new_item: Rc>, - pub favorite_item: Rc>, - pub quit_all_item: Rc>, - //TODO figure out how to use lifetimes with glib::wrapper! macro - pub dock_object: Rc>>, -} - -#[glib::object_subclass] -impl ObjectSubclass for DockPopover { - const NAME: &'static str = "DockPopover"; - type Type = super::DockPopover; - type ParentType = Box; -} - -impl ObjectImpl for DockPopover { - fn signals() -> &'static [Signal] { - static SIGNALS: Lazy> = - Lazy::new(|| vec![Signal::builder("menu-hide").build()]); - SIGNALS.as_ref() - } -} - -impl WidgetImpl for DockPopover {} - -impl BoxImpl for DockPopover {} diff --git a/applets/cosmic-app-list/src/dock_popover/mod.rs b/applets/cosmic-app-list/src/dock_popover/mod.rs deleted file mode 100644 index 33944847..00000000 --- a/applets/cosmic-app-list/src/dock_popover/mod.rs +++ /dev/null @@ -1,234 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0-only - -use cascade::cascade; -use gio::DesktopAppInfo; -use gtk4::gdk::pango::EllipsizeMode; -use gtk4::subclass::prelude::*; -use gtk4::{gdk, gio, glib}; -use gtk4::{prelude::*, Label}; -use gtk4::{Box, Button, ListBox, Orientation}; - -use crate::dock_object::DockObject; -use crate::utils::AppListEvent; -use crate::utils::BoxedWindowList; -use crate::wayland::ToplevelEvent; -use crate::{TX, WAYLAND_TX}; - -mod imp; - -glib::wrapper! { - pub struct DockPopover(ObjectSubclass) - @extends gtk4::Widget, gtk4::Box, - @implements gtk4::Accessible, gtk4::Buildable, gtk4::ConstraintTarget, gtk4::Orientable; -} - -impl DockPopover { - pub fn new() -> Self { - let self_: DockPopover = glib::Object::new(&[]).expect("Failed to create DockList"); - self_.layout(); - //dnd behavior is different for each type, as well as the data in the model - self_ - } - - pub fn set_dock_object(&self, dock_object: &DockObject, update_layout: bool) { - let imp = imp::DockPopover::from_instance(self); - imp.dock_object.replace(Some(dock_object.clone())); - if update_layout { - self.update_layout(); - } - } - - pub fn update_layout(&self) { - self.reset_menu(); - cascade! { - &self; - ..set_orientation(Orientation::Vertical); - ..set_hexpand(true); - }; - - // build menu - let imp = imp::DockPopover::from_instance(self); - let dock_object = imp.dock_object.borrow(); - let menu_handle = imp.menu_handle.borrow(); - if let Some(dock_object) = dock_object.as_ref() { - let all_windows_item_container = cascade! { - Box::new(Orientation::Vertical, 4); - }; - menu_handle.append(&all_windows_item_container); - let window_list = dock_object.property::("active"); - if window_list.0.is_empty() { - all_windows_item_container.hide(); - } else { - let window_listbox = cascade! { - ListBox::new(); - ..set_activate_on_single_click(true); - ..add_css_class("popover_menu"); - }; - all_windows_item_container.append(&window_listbox); - for w in window_list.0 { - let window_box = cascade! { - Box::new(Orientation::Vertical, 4); - ..add_css_class("dock_item"); - }; - window_listbox.append(&window_box); - - let window_title = cascade! { - Label::new(Some(w.name.as_str())); - ..set_margin_start(4); - ..set_margin_end(4); - ..set_margin_top(4); - ..set_margin_bottom(4); - ..set_wrap(true); - ..set_max_width_chars(20); - ..set_ellipsize(EllipsizeMode::End); - ..add_css_class("title-4"); - ..add_css_class("dock_popover_title"); - }; - //TODO fill with image of window - // let window_image = cascade! { - // Image::from_pixbuf(None); - // }; - // window_box.append(&window_image); - window_box.append(&window_title); - } - // imp.all_windows_item_revealer.replace(window_list_revealer); - imp.window_list.replace(window_listbox); - } - - let launch_item_container = cascade! { - Box::new(Orientation::Vertical, 4); - ..set_hexpand(true); - ..add_css_class("popover_menu"); - }; - menu_handle.append(&launch_item_container); - - let launch_new_item = cascade! { - Button::with_label("New Window"); - ..add_css_class("popover_menu"); - }; - launch_item_container.append(&launch_new_item); - imp.launch_new_item.replace(launch_new_item); - - let favorite_item = cascade! { - Button::with_label(if dock_object.property::("saved") {"Remove from Favorites"} else {"Add to Favorites"}); - ..add_css_class("popover_menu"); - }; - menu_handle.append(&favorite_item); - imp.favorite_item.replace(favorite_item); - - let window_list = dock_object.property::("active"); - - if window_list.0.len() > 1 { - let quit_all_item = cascade! { - Button::with_label(format!("Quit {} Windows", window_list.0.len()).as_str()); - ..add_css_class("popover_menu"); - }; - menu_handle.append(&quit_all_item); - imp.quit_all_item.replace(quit_all_item); - } else { - let quit_all_item = cascade! { - Button::with_label("Quit"); - ..add_css_class("popover_menu"); - }; - menu_handle.append(&quit_all_item); - if window_list.0.is_empty() { - quit_all_item.hide(); - } - imp.quit_all_item.replace(quit_all_item); - } - - self.setup_handlers(); - } - } - - fn layout(&self) { - let imp = imp::DockPopover::from_instance(self); - let menu_handle = cascade! { - Box::new(Orientation::Vertical, 4); - ..add_css_class("popover_menu"); - }; - self.append(&menu_handle); - imp.menu_handle.replace(menu_handle); - } - - fn emit_hide(&self) { - self.emit_by_name::<()>("menu-hide", &[]); - } - - pub fn reset_menu(&self) { - // reset menu - let menu_handle = cascade! { - Box::new(Orientation::Vertical, 4); - }; - self.append(&menu_handle); - - let imp = imp::DockPopover::from_instance(self); - let old_menu_handle = imp.menu_handle.replace(menu_handle); - self.remove(&old_menu_handle); - } - - fn setup_handlers(&self) { - let imp = imp::DockPopover::from_instance(self); - let dock_object = imp.dock_object.borrow(); - let launch_new_item = imp.launch_new_item.borrow(); - let favorite_item = imp.favorite_item.borrow(); - let quit_all_item = imp.quit_all_item.borrow(); - let window_listbox = imp.window_list.borrow(); - // let all_windows_header = imp.all_windows_item_header.borrow(); - // let revealer = &imp.all_windows_item_revealer; - - if let Some(dock_object) = dock_object.as_ref() { - // println!("setting up popover menu handlers"); - let self_ = self.clone(); - launch_new_item.connect_clicked(glib::clone!(@weak dock_object, => move |_| { - let app_info = dock_object.property::>("appinfo").expect("Failed to convert value to DesktopAppInfo"); - - let context = gdk::Display::default().unwrap().app_launch_context(); - if let Err(err) = app_info.launch(&[], Some(&context)) { - eprintln!("{}", err); - } - self_.emit_hide(); - })); - - let self_ = self.clone(); - quit_all_item.connect_clicked(glib::clone!(@weak dock_object => move |_| { - let active = dock_object.property::("active").0; - for w in active { - let t = w.toplevel_handle.clone(); - let tx = WAYLAND_TX.get().unwrap().clone(); - let _ = tx.clone().send(ToplevelEvent::Close(t)); - } - self_.emit_hide(); - })); - - let self_ = self.clone(); - favorite_item.connect_clicked(glib::clone!(@weak dock_object => move |_| { - let saved = dock_object.property::("saved"); - if let Some(name) = dock_object.get_name() { - let tx = TX.get().unwrap().clone(); - let _ = tx.clone().send(AppListEvent::Favorite((name, !saved))); - } - self_.emit_hide(); - })); - - // all_windows_header.connect_clicked( - // glib::clone!(@weak dock_object, @weak revealer => move |self_| { - // // dbg!(dock_object); - // let revealer = revealer.borrow(); - // revealer.set_reveal_child(!revealer.reveals_child()) - // }), - // ); - - let self_ = self.clone(); - window_listbox.connect_row_activated( - glib::clone!(@weak dock_object => move |_, item| { - let active = dock_object.property::("active").0; - let t = active[usize::try_from(item.index()).unwrap()].toplevel_handle.clone(); - let tx = WAYLAND_TX.get().unwrap().clone(); - let _ = tx.send(ToplevelEvent::Activate(t)); - self_.emit_hide(); - }), - ); - } - } -} diff --git a/applets/cosmic-app-list/src/main.rs b/applets/cosmic-app-list/src/main.rs index d5473778..9431be12 100644 --- a/applets/cosmic-app-list/src/main.rs +++ b/applets/cosmic-app-list/src/main.rs @@ -16,7 +16,7 @@ fn main() -> cosmic::iced::Result { pretty_env_logger::init(); info!("Iced Workspaces Applet ({})", APP_ID); info!("Version: {}", VERSION); - + config::AppListConfig::default().save().unwrap(); // Prepare i18n localize();