diff --git a/Cargo.toml b/Cargo.toml index 7982f27..f5e6210 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,7 +5,7 @@ edition = "2021" [dependencies] cascade = "1.0.0" -derivative = "2.2.0" +gtk4 = { version = "0.4.4", features = ["v4_4"] } gdk4 = "0.4.4" gdk4-wayland = { version = "0.4.2", features = [ "wayland_crate" ], optional = true } gdk4-x11 = { version = "0.4.2", features = [ "xlib" ] } @@ -14,30 +14,7 @@ gobject-sys = "0.15.1" wayland-client = { version = "0.29.4", optional = true } wayland-protocols = { version = "0.29.4", features = [ "client", "unstable_protocols" ], optional = true } x11 = { version = "2.19.1", features = ["xlib"] } - -# examples -gtk4 = { version = "0.4.4", features = ["v4_4"] } -gtk4-sys = "0.4.2" -glib-sys = "0.15.1" -relm4-macros = { git = "https://github.com/AaronErhardt/Relm4" } -pop-launcher-service = { git = "https://github.com/wash2/launcher.git" } -pop-launcher = { git = "https://github.com/wash2/launcher.git" } -serde = "1.0.134" -serde_json = "1.0.75" -tokio = { version = "1.15.0", features = ["sync"] } -futures = "0.3.19" -futures-util = "0.3.19" once_cell = "1.9.0" -xdg = "2.4.0" -# zbus -zbus = "2.0.1" -zvariant = "3.1.0" -zvariant_derive = "3.1.0" -libloading = "0.7.3" - -[profile.release] -incremental = true -debug = 1 [features] layer-shell = ["gdk4-wayland", "wayland-client", "wayland-protocols"] diff --git a/examples/app_library/README.md b/examples/app_library/README.md deleted file mode 100644 index 00596fd..0000000 --- a/examples/app_library/README.md +++ /dev/null @@ -1,2 +0,0 @@ -# Applications Library - diff --git a/examples/app_library/app_grid/imp.rs b/examples/app_library/app_grid/imp.rs deleted file mode 100644 index bcd1c6f..0000000 --- a/examples/app_library/app_grid/imp.rs +++ /dev/null @@ -1,26 +0,0 @@ -use gtk4::subclass::prelude::*; -use gtk4::{gio, glib, GridView}; -use once_cell::sync::OnceCell; - -#[derive(Default)] -pub struct AppGrid { - pub app_grid_view: OnceCell, - pub app_model: OnceCell, - pub app_sort_model: OnceCell, - pub search_filter_model: OnceCell, - pub group_filter_model: OnceCell, -} - -#[glib::object_subclass] -impl ObjectSubclass for AppGrid { - // `NAME` needs to match `class` attribute of template - const NAME: &'static str = "AppGrid"; - type Type = super::AppGrid; - type ParentType = gtk4::Box; -} - -impl ObjectImpl for AppGrid {} - -impl WidgetImpl for AppGrid {} - -impl BoxImpl for AppGrid {} diff --git a/examples/app_library/app_grid/mod.rs b/examples/app_library/app_grid/mod.rs deleted file mode 100644 index 5a51e78..0000000 --- a/examples/app_library/app_grid/mod.rs +++ /dev/null @@ -1,196 +0,0 @@ -use cascade::cascade; -use gtk4::prelude::*; -use gtk4::subclass::prelude::*; -use gtk4::{gio, glib, GridView, PolicyType, ScrolledWindow, SignalListItemFactory}; - -use crate::grid_item::GridItem; - -mod imp; - -glib::wrapper! { - pub struct AppGrid(ObjectSubclass) - @extends gtk4::Widget, gtk4::Box, - @implements gtk4::Accessible, gtk4::Buildable, gtk4::ConstraintTarget, gtk4::Orientable; -} - -impl Default for AppGrid { - fn default() -> Self { - Self::new() - } -} - -impl AppGrid { - pub fn new() -> Self { - let self_: Self = glib::Object::new(&[]).expect("Failed to create AppGrid"); - let imp = imp::AppGrid::from_instance(&self_); - - let library_window = cascade! { - ScrolledWindow::new(); - ..set_hscrollbar_policy(PolicyType::Never); - ..set_min_content_height(520); - ..set_hexpand(true); - ..set_margin_top(12); - }; - self_.append(&library_window); - - let library_grid = cascade! { - GridView::default(); - ..set_min_columns(7); - ..set_max_columns(7); - ..set_single_click_activate(true); - }; - library_window.set_child(Some(&library_grid)); - - imp.app_grid_view.set(library_grid).unwrap(); - - // Setup - self_.setup_model(); - self_.setup_callbacks(); - self_.setup_factory(); - - self_ - } - - fn setup_model(&self) { - // Create new model - let app_model = gio::ListStore::new(gio::DesktopAppInfo::static_type()); - // Get state and set model - let imp = imp::AppGrid::from_instance(self); - - // A sorter used to sort AppInfo in the model by their name - xdg::BaseDirectories::new() - .expect("could not access XDG Base directory") - .get_data_dirs() - .iter_mut() - .for_each(|xdg_data_path| { - 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() { - app_model.append(&app_info) - } else { - // println!("Ignoring {}", path); - } - } else { - // println!("error loading {}", path); - } - } - } - } - }) - } - }); - - let sorter = gtk4::CustomSorter::new(move |obj1, obj2| { - let app_info1 = obj1.downcast_ref::().unwrap(); - let app_info2 = obj2.downcast_ref::().unwrap(); - - app_info1 - .name() - .to_lowercase() - .cmp(&app_info2.name().to_lowercase()) - .into() - }); - let filter = gtk4::CustomFilter::new(|_obj| true); - - let search_filter_model = - gtk4::FilterListModel::new(Some(&app_model), Some(filter).as_ref()); - let filter = gtk4::CustomFilter::new(|_obj| true); - let group_filter_model = - gtk4::FilterListModel::new(Some(&search_filter_model), Some(filter).as_ref()); - let sorted_model = gtk4::SortListModel::new(Some(&group_filter_model), Some(&sorter)); - - let selection_model = gtk4::SingleSelection::builder() - .model(&sorted_model) - .autoselect(false) - .can_unselect(true) - .selected(gtk4::INVALID_LIST_POSITION) - .build(); - - // Wrap model with selection and pass it to the list view - imp.app_model - .set(app_model.clone()) - .expect("Could not set model"); - imp.app_sort_model.set(sorted_model).unwrap(); - imp.search_filter_model.set(search_filter_model).unwrap(); - imp.group_filter_model.set(group_filter_model).unwrap(); - imp.app_grid_view - .get() - .unwrap() - .set_model(Some(&selection_model)); - selection_model.unselect_all(); - } - - fn setup_callbacks(&self) { - let imp = imp::AppGrid::from_instance(self); - let app_grid_view = &imp.app_grid_view.get().unwrap(); - - app_grid_view.connect_activate(move |list_view, i| { - // on activation change the group filter model to use the app names, and category - // println!("selected app {}", i); - // Launch the application when an item of the list is activated - let model = list_view.model().unwrap(); - if let Some(item) = model.item(i) { - let app_info = item.downcast::().unwrap(); - let context = list_view.display().app_launch_context(); - if let Err(err) = app_info.launch(&[], Some(&context)) { - gtk4::MessageDialog::builder() - .text(&format!("Failed to start {}", app_info.name())) - .secondary_text(&err.to_string()) - .message_type(gtk4::MessageType::Error) - .modal(true) - .build() - .show(); - } - } - }); - } - - fn setup_factory(&self) { - let app_factory = SignalListItemFactory::new(); - app_factory.connect_setup(move |_factory, item| { - let row = GridItem::new(); - item.set_child(Some(&row)); - }); - - let imp = imp::AppGrid::from_instance(self); - // the bind stage is used for "binding" the data to the created widgets on the "setup" stage - let app_grid_view = &imp.app_grid_view.get().unwrap(); - app_factory.connect_bind( - glib::clone!(@weak app_grid_view => move |_factory, grid_item| { - let app_info = grid_item - .item() - .unwrap() - .downcast::() - .unwrap(); - let child = grid_item.child().unwrap().downcast::().unwrap(); - child.set_app_info(&app_info); - }), - ); - // Set the factory of the list view - app_grid_view.set_factory(Some(&app_factory)); - } - - pub fn set_app_sorter(&self, sorter: >k4::CustomSorter) { - let imp = imp::AppGrid::from_instance(&self); - let sort_model = imp.app_sort_model.get().unwrap(); - sort_model.set_sorter(Some(sorter)); - } - - pub fn set_search_filter(&self, filter: >k4::CustomFilter) { - let imp = imp::AppGrid::from_instance(&self); - let filter_model = imp.search_filter_model.get().unwrap(); - filter_model.set_filter(Some(filter)); - } - - pub fn set_group_filter(&self, filter: >k4::CustomFilter) { - let imp = imp::AppGrid::from_instance(&self); - let filter_model = imp.group_filter_model.get().unwrap(); - filter_model.set_filter(Some(filter)); - } -} diff --git a/examples/app_library/app_group/imp.rs b/examples/app_library/app_group/imp.rs deleted file mode 100644 index b982a29..0000000 --- a/examples/app_library/app_group/imp.rs +++ /dev/null @@ -1,62 +0,0 @@ -use std::cell::RefCell; -use std::rc::Rc; - -use gdk4::glib::ParamSpecBoxed; -use glib::{ParamFlags, ParamSpec, Value}; -use gtk4::glib; -use gtk4::prelude::*; -use gtk4::subclass::prelude::*; -use once_cell::sync::Lazy; - -use super::BoxedAppGroupType; - -// Object holding the state -#[derive(Default)] -pub struct AppGroup { - pub inner: Rc>, -} - -// The central trait for subclassing a GObject -#[glib::object_subclass] -impl ObjectSubclass for AppGroup { - const NAME: &'static str = "AppGroup"; - type Type = super::AppGroup; - type ParentType = glib::Object; -} - -// Trait shared by all GObjects -impl ObjectImpl for AppGroup { - fn properties() -> &'static [ParamSpec] { - static PROPERTIES: Lazy> = Lazy::new(|| { - vec![ParamSpecBoxed::new( - // Name - "inner", - // Nickname - "inner", - // Short description - "inner", - BoxedAppGroupType::static_type(), - // The property can be read and written to - ParamFlags::READWRITE, - )] - }); - PROPERTIES.as_ref() - } - - fn set_property(&self, _obj: &Self::Type, _id: usize, value: &Value, pspec: &ParamSpec) { - match pspec.name() { - "inner" => { - let inner = value.get().expect("The value needs to be of type `u32`."); - self.inner.replace(inner); - } - _ => unimplemented!(), - } - } - - fn property(&self, _obj: &Self::Type, _id: usize, pspec: &ParamSpec) -> Value { - match pspec.name() { - "inner" => self.inner.borrow().to_value(), - _ => unimplemented!(), - } - } -} diff --git a/examples/app_library/app_group/mod.rs b/examples/app_library/app_group/mod.rs deleted file mode 100644 index f167c8c..0000000 --- a/examples/app_library/app_group/mod.rs +++ /dev/null @@ -1,88 +0,0 @@ -use glib::Object; -use gtk4::glib; -use gtk4::subclass::prelude::*; -use serde::{Deserialize, Serialize}; - -mod imp; - -glib::wrapper! { - pub struct AppGroup(ObjectSubclass); -} - -impl AppGroup { - pub fn new(data: BoxedAppGroupType) -> Self { - let self_: Self = - Object::new(&[("inner", &data)]).expect("Failed to create `ApplicationObject`."); - self_ - } - - pub fn popup(&self) { - let imp = imp::AppGroup::from_instance(self); - let inner = imp.inner.borrow().clone(); - match inner { - BoxedAppGroupType::Group(d) => { - // d.popup = true; - imp.inner.replace(BoxedAppGroupType::Group(d)); - } - BoxedAppGroupType::NewGroup(_) => { - imp.inner.replace(BoxedAppGroupType::NewGroup(true)); - } - }; - } - - pub fn popdown(&self) { - let imp = imp::AppGroup::from_instance(self); - let inner = imp.inner.borrow().clone(); - match inner { - BoxedAppGroupType::Group(d) => { - // d.popup = false; - imp.inner.replace(BoxedAppGroupType::Group(d)); - } - BoxedAppGroupType::NewGroup(_) => { - imp.inner.replace(BoxedAppGroupType::NewGroup(false)); - } - }; - } - - pub fn is_popup_active(&self) -> bool { - let imp = imp::AppGroup::from_instance(self); - match imp.inner.borrow().clone() { - BoxedAppGroupType::Group(_d) => false, - BoxedAppGroupType::NewGroup(is_active) => is_active, - } - } - - pub fn group_data(&self) -> Option { - let imp = imp::AppGroup::from_instance(self); - let inner = imp.inner.borrow().clone(); - match inner { - BoxedAppGroupType::Group(d) => Some(d), - _ => None, - } - } -} - -#[derive(Serialize, Deserialize, Clone, glib::Boxed)] -#[boxed_type(name = "BoxedAppGroupType")] -pub enum BoxedAppGroupType { - Group(AppGroupData), - NewGroup(bool), -} - -impl Default for BoxedAppGroupType { - fn default() -> Self { - Self::NewGroup(false) - } -} - -// Object holding the state -#[derive(Default, Serialize, Deserialize, Clone)] -pub struct AppGroupData { - pub id: u32, - pub name: String, - pub icon: String, - pub mutable: bool, - pub app_names: Vec, - pub category: String, - // pub popup: bool, -} diff --git a/examples/app_library/grid_item/imp.rs b/examples/app_library/grid_item/imp.rs deleted file mode 100644 index f274473..0000000 --- a/examples/app_library/grid_item/imp.rs +++ /dev/null @@ -1,55 +0,0 @@ -use glib::subclass::Signal; -use gtk4::subclass::prelude::*; -use once_cell::sync::Lazy; -use std::cell::Cell; -use std::cell::RefCell; -use std::rc::Rc; - -use gtk4::{glib, prelude::*, Popover}; - -#[derive(Debug, Default)] -pub struct GridItem { - pub(super) name: Rc>, - pub(super) image: Rc>, - pub(super) index: Cell, - pub(super) popover: Rc>>, -} - -#[glib::object_subclass] -impl ObjectSubclass for GridItem { - const NAME: &'static str = "GridItem"; - type Type = super::GridItem; - type ParentType = gtk4::Box; -} - -impl ObjectImpl for GridItem { - fn signals() -> &'static [Signal] { - static SIGNALS: Lazy> = Lazy::new(|| { - vec![ - Signal::builder( - // Signal name - "new-group", - // Types of the values which will be sent to the signal handler - &[String::static_type().into()], - // Type of the value the signal handler sends back - <()>::static_type().into(), - ) - .build(), - Signal::builder( - // Signal name - "popover-closed", - // Types of the values which will be sent to the signal handler - &[], - // Type of the value the signal handler sends back - <()>::static_type().into(), - ) - .build(), - ] - }); - SIGNALS.as_ref() - } -} - -impl WidgetImpl for GridItem {} - -impl BoxImpl for GridItem {} diff --git a/examples/app_library/grid_item/mod.rs b/examples/app_library/grid_item/mod.rs deleted file mode 100644 index 7c9df9b..0000000 --- a/examples/app_library/grid_item/mod.rs +++ /dev/null @@ -1,199 +0,0 @@ -use cascade::cascade; -use gdk4::ContentProvider; -use gdk4::Display; -use gio::File; -use gio::Icon; -use gtk4::pango::EllipsizeMode; -use gtk4::prelude::*; -use gtk4::subclass::prelude::*; -use gtk4::traits::WidgetExt; -use gtk4::Align; -use gtk4::Button; -use gtk4::DragSource; -use gtk4::IconTheme; -use gtk4::Image; -use gtk4::Label; -use gtk4::Orientation; -use gtk4::{gio, glib}; - -use crate::app_group::AppGroup; -use crate::app_group::BoxedAppGroupType; - -mod imp; - -glib::wrapper! { -pub struct GridItem(ObjectSubclass) - @extends gtk4::Widget, gtk4::Box, - @implements gtk4::Accessible, gtk4::Buildable, gtk4::ConstraintTarget, gtk4::Orientable; -} - -impl Default for GridItem { - fn default() -> Self { - Self::new() - } -} - -impl GridItem { - pub fn new() -> Self { - let self_ = glib::Object::new(&[]).expect("Failed to create GridItem"); - let imp = imp::GridItem::from_instance(&self_); - - cascade! { - &self_; - ..set_orientation(Orientation::Vertical); - ..set_halign(Align::Center); - ..set_hexpand(true); - ..set_margin_top(4); - ..set_margin_bottom(4); - ..set_margin_end(4); - ..set_margin_start(4); - }; - - let image = cascade! { - Image::new(); - ..set_margin_top(4); - ..set_margin_bottom(4); - ..set_pixel_size(64); - }; - self_.append(&image); - - let name = cascade! { - Label::new(None); - ..set_halign(Align::Center); - ..set_hexpand(true); - ..set_ellipsize(EllipsizeMode::End); - ..add_css_class("title-5"); - }; - self_.append(&name); - - imp.name.replace(name); - imp.image.replace(image); - self_ - } - - pub fn set_app_info(&self, app_info: &gio::DesktopAppInfo) { - let self_ = imp::GridItem::from_instance(self); - self_.name.borrow().set_text(&app_info.name()); - - let drag_controller = DragSource::builder() - .name("application library drag source") - .actions(gdk4::DragAction::COPY) - // .content() - .build(); - self.add_controller(&drag_controller); - if let Some(file) = app_info.filename() { - let file = File::for_path(file); - let provider = ContentProvider::for_value(&file.to_value()); - drag_controller.set_content(Some(&provider)); - } - let icon = app_info - .icon() - .unwrap_or(Icon::for_string("image-missing").expect("Failed to set default icon")); - self_.image.borrow().set_from_gicon(&icon); - drag_controller.connect_drag_begin(glib::clone!(@weak icon, => move |_self, drag| { - drag.set_selected_action(gdk4::DragAction::MOVE); - // set drag source icon if possible... - // gio Icon is not easily converted to a Paintable, but this seems to be the correct method - 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); - } - })); - } - - pub fn set_group_info(&self, app_group: AppGroup) { - // if data type set name and icon to values in data - let imp = imp::GridItem::from_instance(self); - match app_group.property::("inner") { - BoxedAppGroupType::Group(data) => { - imp.name.borrow().set_text(&data.name); - imp.image.borrow().set_from_icon_name(Some(&data.icon)); - } - BoxedAppGroupType::NewGroup(popover_active) => { - // else must be add group - imp.name.borrow().set_text("New Group"); - imp.image.borrow().set_from_icon_name(Some("folder-new")); - - let popover_menu = gtk4::Box::builder() - .spacing(12) - .hexpand(true) - .orientation(gtk4::Orientation::Vertical) - .margin_top(12) - .margin_bottom(12) - .margin_end(12) - .margin_start(12) - .build(); - - // build menu - let dialog_entry = gtk4::Entry::new(); - let label = cascade! { - Label::new(Some("Name")); - ..set_justify(gtk4::Justification::Left); - ..set_xalign(0.0); - }; - popover_menu.append(&label); - popover_menu.append(&dialog_entry); - let btn_container = cascade! { - gtk4::Box::new(Orientation::Horizontal, 8); - }; - let ok_btn = cascade! { - Button::with_label("Ok"); - }; - let cancel_btn = cascade! { - Button::with_label("Cancel"); - }; - btn_container.append(&ok_btn); - btn_container.append(&cancel_btn); - popover_menu.append(&btn_container); - let popover = cascade! { - gtk4::Popover::new(); - ..set_autohide(true); - ..set_child(Some(&popover_menu)); - }; - self.append(&popover); - - popover.connect_closed( - glib::clone!(@weak self as self_, @weak dialog_entry => move |_| { - dialog_entry.set_text(""); - self_.emit_by_name::<()>("popover-closed", &[]); - }), - ); - ok_btn.connect_clicked( - glib::clone!(@weak self as self_, @weak dialog_entry, @weak popover => move |_| { - let new_name = dialog_entry.text().to_string(); - popover.popdown(); - glib::idle_add_local_once(glib::clone!(@weak self_ => move || { - self_.emit_by_name::<()>("new-group", &[&new_name]); - })); - }), - ); - cancel_btn.connect_clicked(glib::clone!(@weak popover => move |_| { - popover.popdown(); - })); - if popover_active { - popover.popup(); - } - - imp.popover.replace(Some(popover)); - } - } - } - - pub fn set_index(&self, index: u32) { - imp::GridItem::from_instance(self).index.set(index); - } - - pub fn popup(&self) { - let imp = imp::GridItem::from_instance(self); - if let Some(popover) = imp.popover.borrow().as_ref() { - popover.popup(); - } - } -} diff --git a/examples/app_library/group_grid/imp.rs b/examples/app_library/group_grid/imp.rs deleted file mode 100644 index 426a2f5..0000000 --- a/examples/app_library/group_grid/imp.rs +++ /dev/null @@ -1,41 +0,0 @@ -use glib::subclass::Signal; -use gtk4::subclass::prelude::*; -use gtk4::{gio, glib, GridView, ScrolledWindow}; -use gtk4::{prelude::*, CustomFilter}; -use once_cell::sync::{Lazy, OnceCell}; - -#[derive(Default)] -pub struct GroupGrid { - pub group_grid_view: OnceCell, - pub group_scroll_window: OnceCell, - pub group_model: OnceCell, -} - -#[glib::object_subclass] -impl ObjectSubclass for GroupGrid { - // `NAME` needs to match `class` attribute of template - const NAME: &'static str = "GroupGrid"; - type Type = super::GroupGrid; - type ParentType = gtk4::Box; -} - -impl ObjectImpl for GroupGrid { - fn signals() -> &'static [Signal] { - static SIGNALS: Lazy> = Lazy::new(|| { - vec![Signal::builder( - // Signal name - "group-changed", - // Types of the values which will be sent to the signal handler - &[CustomFilter::static_type().into()], - // Type of the value the signal handler sends back - <()>::static_type().into(), - ) - .build()] - }); - SIGNALS.as_ref() - } -} - -impl WidgetImpl for GroupGrid {} - -impl BoxImpl for GroupGrid {} diff --git a/examples/app_library/group_grid/mod.rs b/examples/app_library/group_grid/mod.rs deleted file mode 100644 index 769afd2..0000000 --- a/examples/app_library/group_grid/mod.rs +++ /dev/null @@ -1,287 +0,0 @@ -use cascade::cascade; -use glib::Object; -use gtk4::prelude::*; -use gtk4::subclass::prelude::*; -use gtk4::{gio, glib, GridView, PolicyType, ScrolledWindow, SignalListItemFactory}; -use std::fs::File; - -use crate::app_group::{AppGroup, AppGroupData, BoxedAppGroupType}; -use crate::grid_item::GridItem; -use crate::utils::data_path; -use crate::utils::set_group_scroll_policy; - -mod imp; - -glib::wrapper! { - pub struct GroupGrid(ObjectSubclass) - @extends gtk4::Widget, gtk4::Box, - @implements gtk4::Accessible, gtk4::Buildable, gtk4::ConstraintTarget, gtk4::Orientable; -} - -impl Default for GroupGrid { - fn default() -> Self { - Self::new() - } -} - -impl GroupGrid { - pub fn new() -> Self { - let self_: Self = glib::Object::new(&[]).expect("Failed to create GroupGrid"); - let imp = imp::GroupGrid::from_instance(&self_); - - let group_window = cascade! { - ScrolledWindow::new(); - ..set_hscrollbar_policy(PolicyType::Never); - ..set_vscrollbar_policy(PolicyType::Never); - ..set_propagate_natural_height(true); - ..set_min_content_height(150); - ..set_max_content_height(300); - ..set_hexpand(true); - }; - self_.append(&group_window); - - let group_grid_view = cascade! { - GridView::default(); - ..set_min_columns(8); - ..set_max_columns(8); - }; - group_window.set_child(Some(&group_grid_view)); - - imp.group_grid_view.set(group_grid_view).unwrap(); - imp.group_scroll_window.set(group_window).unwrap(); - - // Setup - // Setup - self_.setup_model(); - self_.restore_data(); - self_.setup_callbacks(); - self_.setup_factory(); - - self_ - } - - fn setup_model(&self) { - let imp = imp::GroupGrid::from_instance(&self); - let group_model = gio::ListStore::new(AppGroup::static_type()); - imp.group_model - .set(group_model.clone()) - .expect("Could not set group model"); - vec![ - AppGroup::new(BoxedAppGroupType::Group(AppGroupData { - id: 0, - name: "Library Home".to_string(), - icon: "user-home".to_string(), - mutable: false, - app_names: Vec::new(), - category: "".to_string(), - })), - AppGroup::new(BoxedAppGroupType::Group(AppGroupData { - id: 0, - name: "System".to_string(), - icon: "folder".to_string(), - mutable: false, - app_names: Vec::new(), - category: "System".to_string(), - })), - AppGroup::new(BoxedAppGroupType::Group(AppGroupData { - id: 0, - name: "Utilities".to_string(), - icon: "folder".to_string(), - mutable: false, - app_names: Vec::new(), - category: "Utility".to_string(), - })), - // Example of group with app name - // AppGroup::new(AppGroupData { - // id: 0, - // name: "Custom Web".to_string(), - // icon: "folder".to_string(), - // mutable: true, - // app_names: vec!["Firefox Web Browser".to_string()], - // category: "".to_string(), - // }), - AppGroup::new(BoxedAppGroupType::NewGroup(false)), - ] - .iter() - .for_each(|group| { - group_model.append(group); - }); - let group_selection = gtk4::SingleSelection::new(Some(&group_model)); - imp.group_grid_view - .get() - .unwrap() - .set_model(Some(&group_selection)); - } - - fn group_model(&self) -> &gio::ListStore { - // Get state - let imp = imp::GroupGrid::from_instance(self); - imp.group_model.get().expect("Could not get model") - } - - fn setup_callbacks(&self) { - let imp = imp::GroupGrid::from_instance(self); - let group_grid_view = &imp.group_grid_view.get().unwrap(); - - let scroll_window = &imp.group_scroll_window.get().unwrap(); - // dynamically set scroll method - self.group_model().connect_items_changed( - glib::clone!(@weak scroll_window => move |scroll_list_model, _i, _rmv_cnt, _add_cnt| { - set_group_scroll_policy(&scroll_window, scroll_list_model.n_items()); - }), - ); - - let self_clone = self.clone(); - group_grid_view.connect_activate(move |group_grid_view, i| { - // on activation change the group filter model to use the app names, and category - println!("grid view activated. {}", i); - let group_model = group_grid_view - .model() - .unwrap() - .downcast::() - .unwrap() - .model() - .downcast::() - .expect("could not downcast app group view selection model to list store model"); - // update the application filter - if let Some(data) = group_model - .item(i) - .unwrap() - .downcast::() - .unwrap() - .group_data() - { - let category = data.category.to_lowercase(); - - let new_filter: gtk4::CustomFilter = gtk4::CustomFilter::new(move |obj| { - let app = obj - .downcast_ref::() - .expect("The Object needs to be of type AppInfo"); - if data.app_names.len() > 0 { - return data.app_names.contains(&String::from(app.name().as_str())); - } - match app.categories() { - Some(categories) => { - categories.to_string().to_lowercase().contains(&category) - } - None => false, - } - }); - self_clone.emit_by_name::<()>("group-changed", &[&new_filter]); - } else { - // don't change filter, instead show dialog for adding new group! - let item = group_model.item(i).unwrap().downcast::().unwrap(); - item.popup(); - group_model.items_changed(i, 0, 0); - } - }); - } - - pub fn is_popup_active(&self) -> bool { - let model = self.group_model(); - for i in 0..model.n_items() { - let item = model.item(i).unwrap().downcast::().unwrap(); - if item.is_popup_active() { - return true; - } - } - return false; - } - - fn setup_factory(&self) { - let imp = imp::GroupGrid::from_instance(&self); - let group_factory = SignalListItemFactory::new(); - group_factory.connect_setup(glib::clone!(@weak self as self_ => move |_factory, item| { - let obj = GridItem::new(); - item.set_child(Some(&obj)); - obj - .connect_local("new-group", false, glib::clone!(@weak self_ => @default-return None, move |args| { - let m = self_.group_model(); - match args[1].get::() { - Ok(name) => { - let new_group = AppGroup::new(BoxedAppGroupType::Group(AppGroupData { - id: 0, - name: name, - icon: "folder".to_string(), - mutable: false, - app_names: Vec::new(), - category: "".to_string(), - })).upcast::(); - - m.insert(m.n_items() - 1, &new_group); - self_.store_data(); - } - _ => unimplemented!(), - }; - None - })); - obj - .connect_local("popover-closed", false, glib::clone!(@weak self_ => @default-return None, move |_| { - let m = self_.group_model(); - let group = m.item(m.n_items() - 1).unwrap().downcast::().unwrap(); - glib::idle_add_local_once(move || { - group.popdown(); - }); - None - })); - })); - - // the bind stage is used for "binding" the data to the created widgets on the "setup" stage - group_factory.connect_bind(move |_factory, grid_item| { - let group_info = grid_item.item().unwrap().downcast::().unwrap(); - - let child = grid_item.child().unwrap().downcast::().unwrap(); - child.set_group_info(group_info); - }); - // Set the factory of the list view - imp.group_grid_view - .get() - .unwrap() - .set_factory(Some(&group_factory)); - } - - fn restore_data(&self) { - if let Ok(file) = File::open(data_path()) { - // Deserialize data from file to vector - let backup_data: Vec = - serde_json::from_reader(file).expect("Could not get backup data from json file."); - - let app_group_objects: Vec = backup_data - .into_iter() - .map(|data| AppGroup::new(BoxedAppGroupType::Group(data)).upcast::()) - .collect(); - let scroll_window = &imp::GroupGrid::from_instance(self) - .group_scroll_window - .get() - .unwrap(); - - // Insert restored objects into model - self.group_model().splice(3, 0, &app_group_objects); - set_group_scroll_policy(&scroll_window, self.group_model().n_items()); - } else { - println!("Backup file does not exist yet {:?}", data_path()); - } - } - pub fn store_data(&self) { - let mut backup_data = Vec::new(); - let mut position = 3; - while let Some(item) = self.group_model().item(position) { - if position == self.group_model().n_items() - 1 { - break; - } - // Get `AppGroup` from `glib::Object` - let group_data = item - .downcast_ref::() - .expect("The object needs to be of type `AppGroupData`.") - .group_data(); - // Add data to vector and increase position - backup_data.push(group_data); - position += 1; - } - - // 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"); - } -} diff --git a/examples/app_library/main.rs b/examples/app_library/main.rs deleted file mode 100644 index e6ff850..0000000 --- a/examples/app_library/main.rs +++ /dev/null @@ -1,49 +0,0 @@ -use gtk4::gdk::Display; -use gtk4::prelude::*; -use gtk4::CssProvider; -use gtk4::StyleContext; - -use window::AppLibraryWindow; - -mod app_grid; -mod app_group; -mod grid_item; -mod group_grid; -mod utils; -mod window; -mod window_inner; - -fn main() { - let app = gtk4::Application::new(Some("com.cosmic.app_library"), Default::default()); - app.connect_startup(|_app| { - load_css(); - }); - - app.connect_activate(|app| { - build_ui(app); - }); - - app.run(); -} - -fn load_css() { - // Load the css file and add it to the provider - let provider = CssProvider::new(); - provider.load_from_data(include_bytes!("style.css")); - - // Add the provider to the default screen - StyleContext::add_provider_for_display( - &Display::default().expect("Error initializing GTK CSS provider."), - &provider, - gtk4::STYLE_PROVIDER_PRIORITY_APPLICATION, - ); -} - -fn build_ui(app: >k4::Application) { - // Create a new custom window and show it - let display = Display::default().unwrap(); - window::create(app, display.monitors().item(0).unwrap().downcast().unwrap()); - // let window = AppLibraryWindow::new(app); - - // window.show(); -} diff --git a/examples/app_library/screenshot.png b/examples/app_library/screenshot.png deleted file mode 100644 index 8ffcefa..0000000 Binary files a/examples/app_library/screenshot.png and /dev/null differ diff --git a/examples/app_library/style.css b/examples/app_library/style.css deleted file mode 100644 index e924a7f..0000000 --- a/examples/app_library/style.css +++ /dev/null @@ -1,23 +0,0 @@ -child:selected { - transition: 100ms; - background: #AAAAAA; -} - -gridview child { - transition: 100ms; - border-radius: 5px; -} - -gridview { - background: #333333; -} - -box.app_library_container { - background: #333333; - padding: 12px; - border-radius: 12px; -} - -window.root_window { - background: rgba(50, 50, 50, 0.0); -} diff --git a/examples/app_library/utils.rs b/examples/app_library/utils.rs deleted file mode 100644 index db4d2e2..0000000 --- a/examples/app_library/utils.rs +++ /dev/null @@ -1,20 +0,0 @@ -use std::path::PathBuf; - -use gtk4::glib; -use gtk4::ScrolledWindow; - -pub fn data_path() -> PathBuf { - let mut path = glib::user_data_dir(); - path.push("com.cosmic.app_library"); - std::fs::create_dir_all(&path).expect("Could not create directory."); - path.push("data.json"); - path -} - -pub fn set_group_scroll_policy(scroll_window: &ScrolledWindow, group_cnt: u32) { - if scroll_window.policy().1 == gtk4::PolicyType::Never && group_cnt > 16 { - scroll_window.set_policy(gtk4::PolicyType::Never, gtk4::PolicyType::Automatic); - } else if scroll_window.policy().1 == gtk4::PolicyType::Automatic && group_cnt <= 16 { - scroll_window.set_policy(gtk4::PolicyType::Never, gtk4::PolicyType::Never); - } -} diff --git a/examples/app_library/window/imp.rs b/examples/app_library/window/imp.rs deleted file mode 100644 index 657e12c..0000000 --- a/examples/app_library/window/imp.rs +++ /dev/null @@ -1,32 +0,0 @@ -use crate::window_inner::AppLibraryWindowInner; -use gtk4::glib; -use gtk4::subclass::prelude::*; -use once_cell::sync::OnceCell; - -// Object holding the state -#[derive(Default)] - -pub struct AppLibraryWindow { - pub(super) inner: OnceCell, -} - -// The central trait for subclassing a GObject -#[glib::object_subclass] -impl ObjectSubclass for AppLibraryWindow { - // `NAME` needs to match `class` attribute of template - const NAME: &'static str = "AppLibraryWindow"; - type Type = super::AppLibraryWindow; - type ParentType = gtk4::ApplicationWindow; -} - -// Trait shared by all GObjects -impl ObjectImpl for AppLibraryWindow {} - -// Trait shared by all widgets -impl WidgetImpl for AppLibraryWindow {} - -// Trait shared by all windows -impl WindowImpl for AppLibraryWindow {} - -// Trait shared by all application -impl ApplicationWindowImpl for AppLibraryWindow {} diff --git a/examples/app_library/window/mod.rs b/examples/app_library/window/mod.rs deleted file mode 100644 index 89817fc..0000000 --- a/examples/app_library/window/mod.rs +++ /dev/null @@ -1,171 +0,0 @@ -use crate::window_inner::AppLibraryWindowInner; -use cascade::cascade; -use gdk4::subclass::prelude::ObjectSubclassExt; -use gdk4_x11::X11Display; -use glib::Object; -use gtk4::prelude::*; -use gtk4::Application; -use gtk4::{gdk, gio, glib}; -use libcosmic::x; - -pub fn create(app: &Application, monitor: gdk::Monitor) { - //quit shortcut - app.set_accels_for_action("app.quit", &["W", "Escape"]); - setup_shortcuts(app); - - #[cfg(feature = "layer-shell")] - if let Some(wayland_monitor) = monitor.downcast_ref() { - wayland_create(&app, wayland_monitor); - return; - } - - cascade! { - AppLibraryWindow::new(&app); - ..show(); - }; -} - -fn setup_shortcuts(app: &Application) { - let action_quit = gio::SimpleAction::new("quit", None); - action_quit.connect_activate(glib::clone!(@weak app => move |_, _| { - app.quit(); - })); - app.add_action(&action_quit); -} - -#[cfg(feature = "layer-shell")] -fn wayland_create(app: &Application, monitor: &gdk4_wayland::WaylandMonitor) { - use libcosmic::wayland::{Anchor, KeyboardInteractivity, Layer, LayerShellWindow}; - - let window = cascade! { - LayerShellWindow::new(Some(monitor), Layer::Top, ""); - ..set_width_request(800); - ..set_height_request(600); - // ..set_title(Some("Cosmic App Library")); - // ..set_decorated(false); - ..set_keyboard_interactivity(KeyboardInteractivity::OnDemand); - ..add_css_class("root_window"); - ..set_anchor(Anchor::empty()); - ..show(); - }; - - let app_library = AppLibraryWindowInner::new(); - window.set_child(Some(&app_library)); - dbg!(&window); - window.connect_is_active_notify(glib::clone!(@weak app => move |w| { - if !w.is_active() { - app.quit(); - } - })); - window.show(); - - // setup_shortcuts(window.clone().upcast::()); - // XXX - unsafe { window.set_data("cosmic-app-hold", app.hold()) }; -} - -mod imp; - -glib::wrapper! { - pub struct AppLibraryWindow(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 AppLibraryWindow { - pub fn new(app: &Application) -> Self { - let self_: Self = - Object::new(&[("application", app)]).expect("Failed to create `AppLibraryWindow`."); - let imp = imp::AppLibraryWindow::from_instance(&self_); - - cascade! { - &self_; - ..set_width_request(1200); - ..set_title(Some("Cosmic App Library")); - ..set_decorated(false); - ..add_css_class("root_window"); - }; - let app_library = AppLibraryWindowInner::new(); - self_.set_child(Some(&app_library)); - imp.inner.set(app_library).unwrap(); - - Self::setup_callbacks(&self_); - - self_ - } - - fn setup_callbacks(&self) { - // Get state - let window = self.clone().upcast::(); - - window.connect_realize(move |window| { - println!("gtk window setup"); - if let Some((display, surface)) = x::get_window_x11(window) { - // ignore all x11 errors... - let xdisplay = display - .clone() - .downcast::() - .expect("Failed to downgrade X11 Display."); - xdisplay.error_trap_push(); - unsafe { - x::change_property( - &display, - &surface, - "_NET_WM_WINDOW_TYPE", - x::PropMode::Replace, - &[x::Atom::new(&display, "_NET_WM_WINDOW_TYPE_DIALOG").unwrap()], - ); - } - let resize = glib::clone!(@weak window => move || { - let height = window.height(); - let width = window.width(); - - if let Some((display, _surface)) = x::get_window_x11(&window) { - let geom = display - .primary_monitor().geometry(); - let monitor_x = geom.x(); - let monitor_y = geom.y(); - let monitor_width = geom.width(); - let monitor_height = geom.height(); - // dbg!(monitor_width); - // dbg!(monitor_height); - // dbg!(width); - // dbg!(height); - unsafe { x::set_position(&display, &surface, - monitor_x + monitor_width / 2 - width / 2, - monitor_y + monitor_height / 2 - height / 2)}; - } - }); - let s = window.surface(); - let resize_height = resize.clone(); - s.connect_height_notify(move |_s| { - glib::source::idle_add_local_once(resize_height.clone()); - }); - let resize_width = resize.clone(); - s.connect_width_notify(move |_s| { - glib::source::idle_add_local_once(resize_width.clone()); - }); - s.connect_scale_factor_notify(move |_s| { - glib::source::idle_add_local_once(resize.clone()); - }); - } else { - println!("failed to get X11 window"); - } - }); - - let imp = imp::AppLibraryWindow::from_instance(&self); - let inner = imp.inner.get().unwrap(); - window.connect_is_active_notify(glib::clone!(@weak inner => move |win| { - let app = win - .application() - .expect("could not get application from window"); - let active_window = app - .active_window() - .expect("no active window available, closing app library."); - if win == &active_window && !win.is_active() && !inner.is_popup_active() { - win.close(); - } - })); - } -} diff --git a/examples/app_library/window_inner/imp.rs b/examples/app_library/window_inner/imp.rs deleted file mode 100644 index 757dc92..0000000 --- a/examples/app_library/window_inner/imp.rs +++ /dev/null @@ -1,28 +0,0 @@ -use gtk4::glib; -use gtk4::subclass::prelude::*; -use gtk4::SearchEntry; -use once_cell::sync::OnceCell; - -use crate::app_grid::AppGrid; -use crate::group_grid::GroupGrid; - -#[derive(Default)] -pub struct AppLibraryWindowInner { - pub entry: OnceCell, - pub app_grid: OnceCell, - pub group_grid: OnceCell, -} - -#[glib::object_subclass] -impl ObjectSubclass for AppLibraryWindowInner { - // `NAME` needs to match `class` attribute of template - const NAME: &'static str = "AppLibraryWindowInner"; - type Type = super::AppLibraryWindowInner; - type ParentType = gtk4::Box; -} - -impl ObjectImpl for AppLibraryWindowInner {} - -impl WidgetImpl for AppLibraryWindowInner {} - -impl BoxImpl for AppLibraryWindowInner {} diff --git a/examples/app_library/window_inner/mod.rs b/examples/app_library/window_inner/mod.rs deleted file mode 100644 index cc41423..0000000 --- a/examples/app_library/window_inner/mod.rs +++ /dev/null @@ -1,136 +0,0 @@ -use cascade::cascade; -use gtk4::prelude::*; -use gtk4::subclass::prelude::*; -use gtk4::{gio, glib, Align, CustomFilter, Orientation, SearchEntry, Separator}; - -use crate::app_grid::AppGrid; -use crate::group_grid::GroupGrid; - -mod imp; - -glib::wrapper! { - pub struct AppLibraryWindowInner(ObjectSubclass) - @extends gtk4::Widget, gtk4::Box, - @implements gtk4::Accessible, gtk4::Buildable, gtk4::ConstraintTarget, gtk4::Orientable; -} - -impl Default for AppLibraryWindowInner { - fn default() -> Self { - Self::new() - } -} - -impl AppLibraryWindowInner { - pub fn new() -> Self { - let self_: Self = glib::Object::new(&[]).expect("Failed to create AppLibraryWindowInner"); - let imp = imp::AppLibraryWindowInner::from_instance(&self_); - - cascade! { - &self_; - ..set_orientation(Orientation::Vertical); - ..add_css_class("app_library_container"); - }; - - let entry = cascade! { - SearchEntry::new(); - ..set_width_request(300); - ..set_halign(Align::Center); - ..set_margin_top(12); - ..set_margin_bottom(12); - ..set_placeholder_text(Some(" Type to search")); - }; - self_.append(&entry); - - let app_grid = AppGrid::new(); - self_.append(&app_grid); - - let separator = cascade! { - Separator::new(Orientation::Horizontal); - ..set_hexpand(true); - ..set_margin_bottom(12); - ..set_margin_top(12); - }; - self_.append(&separator); - - let group_grid = GroupGrid::new(); - self_.append(&group_grid); - - imp.entry.set(entry).unwrap(); - imp.app_grid.set(app_grid).unwrap(); - imp.group_grid.set(group_grid).unwrap(); - - Self::setup_callbacks(&self_); - - self_ - } - - pub fn group_grid(&self) -> Option<&GroupGrid> { - let imp = imp::AppLibraryWindowInner::from_instance(self); - imp.group_grid.get() - } - - pub fn is_popup_active(&self) -> bool { - if let Some(group_grid) = self.group_grid() { - group_grid.is_popup_active() - } else { - false - } - } - - fn setup_callbacks(&self) { - // Get state - let imp = imp::AppLibraryWindowInner::from_instance(self); - let app_grid = &imp.app_grid.get().unwrap(); - let group_grid = &imp.group_grid.get().unwrap(); - - let entry = &imp.entry.get().unwrap(); - - group_grid.connect_local( - "group-changed", - false, - glib::clone!(@weak app_grid => @default-return None, move |args| { - let new_filter = args[1].get::().unwrap(); - app_grid.set_group_filter(&new_filter); - None - }), - ); - - entry.connect_changed( - glib::clone!(@weak app_grid => move |search: >k4::SearchEntry| { - let search_text = search.text().to_string().to_lowercase(); - let new_filter: gtk4::CustomFilter = gtk4::CustomFilter::new(move |obj| { - let search_res = obj.downcast_ref::() - .expect("The Object needs to be of type AppInfo"); - search_res.name().to_string().to_lowercase().contains(&search_text) - }); - let search_text = search.text().to_string().to_lowercase(); - let new_sorter: gtk4::CustomSorter = gtk4::CustomSorter::new(move |obj1, obj2| { - let app_info1 = obj1.downcast_ref::().unwrap(); - let app_info2 = obj2.downcast_ref::().unwrap(); - if search_text == "" { - return app_info1 - .name() - .to_lowercase() - .cmp(&app_info2.name().to_lowercase()) - .into(); - } - - let i_1 = app_info1.name().to_lowercase().find(&search_text); - let i_2 = app_info2.name().to_lowercase().find(&search_text); - match (i_1, i_2) { - (Some(i_1), Some(i_2)) => i_1.cmp(&i_2).into(), - (Some(_), None) => std::cmp::Ordering::Less.into(), - (None, Some(_)) => std::cmp::Ordering::Greater.into(), - _ => app_info1 - .name() - .to_lowercase() - .cmp(&app_info2.name().to_lowercase()) - .into() - } - }); - app_grid.set_search_filter(&new_filter); - app_grid.set_app_sorter(&new_sorter); - }), - ); - } -} diff --git a/examples/dock/dock_item/imp.rs b/examples/dock/dock_item/imp.rs deleted file mode 100644 index 7649c74..0000000 --- a/examples/dock/dock_item/imp.rs +++ /dev/null @@ -1,46 +0,0 @@ -use glib::subclass::Signal; -use gtk4::glib; -use gtk4::prelude::*; -use gtk4::subclass::prelude::*; -use once_cell::sync::Lazy; -use std::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>, -} - -#[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( - // Signal name - "popover-closed", - // Types of the values which will be sent to the signal handler - &[], - // Type of the value the signal handler sends back - <()>::static_type().into(), - ) - .build()] - }); - SIGNALS.as_ref() - } -} - -impl WidgetImpl for DockItem {} - -impl ButtonImpl for DockItem {} diff --git a/examples/dock/dock_item/mod.rs b/examples/dock/dock_item/mod.rs deleted file mode 100644 index 9b2e03b..0000000 --- a/examples/dock/dock_item/mod.rs +++ /dev/null @@ -1,137 +0,0 @@ -use cascade::cascade; -use gtk4::glib; -use gtk4::prelude::*; -use gtk4::subclass::prelude::*; -use gtk4::Align; -use gtk4::Box; -use gtk4::Image; -use gtk4::Label; -use gtk4::Orientation; -use gtk4::Popover; - -use crate::dock_object::DockObject; -use crate::dock_popover::DockPopover; -use crate::utils::BoxedWindowList; - -mod imp; - -glib::wrapper! { - pub struct DockItem(ObjectSubclass) - @extends gtk4::Button, gtk4::Widget, - @implements gtk4::Accessible, gtk4::Actionable, gtk4::Buildable, gtk4::ConstraintTarget; -} - -impl Default for DockItem { - fn default() -> Self { - Self::new() - } -} - -impl DockItem { - pub fn new() -> Self { - let self_: DockItem = glib::Object::new(&[]).expect("Failed to create DockItem"); - - let item_box = Box::new(Orientation::Vertical, 0); - 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(64); - }; - let dots = cascade! { - Label::new(Some("")); - ..set_hexpand(true); - ..set_halign(Align::Center); - }; - item_box.append(&image); - item_box.append(&dots); - let popover = cascade! { - Popover::new(); - ..set_autohide(true); - }; - 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(); - }; - 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.image.replace(Some(image)); - imp.dots.replace(dots); - imp.item_box.replace(item_box); - imp.popover.replace(popover); - imp.popover_menu.replace(popover_menu); - - self_ - } - - // refactor to emit event for removing the item? - pub fn set_dock_object(&self, dock_object: &DockObject) { - let self_ = imp::DockItem::from_instance(self); - let image = cascade! { - dock_object.get_image(); - ..set_hexpand(true); - ..set_halign(Align::Center); - ..set_pixel_size(64); - ..set_tooltip_text(dock_object.get_name().as_deref()); - }; - let old_image = self_.image.replace(None); - if let Some(old_image) = old_image { - self_.item_box.borrow().remove(&old_image); - self_.item_box.borrow().prepend(&image); - self_.image.replace(Some(image)); - } - let active = dock_object.property::("active"); - let dots = self_.dots.borrow(); - dots.set_text(""); - for _ in active.0 { - dots.set_text(format!("{}{}", dots.text(), " ยท ").as_str()); - } - - let popover = dock_object.property::("popover"); - // dbg!(popover); - // dbg!(dock_object); - if popover { - self.add_popover(dock_object); - } else { - self.clear_popover(); - } - } - - pub fn add_popover(&self, item: &DockObject) { - let imp = imp::DockItem::from_instance(self); - let popover = imp.popover.borrow(); - let popover_menu = imp.popover_menu.borrow(); - - popover_menu.set_dock_object(item, true); - popover.popup(); - } - - pub fn clear_popover(&self) { - let imp = imp::DockItem::from_instance(self); - let popover = imp.popover.borrow(); - let popover_menu = imp.popover_menu.borrow(); - popover.popdown(); - popover_menu.reset_menu(); - } -} diff --git a/examples/dock/dock_list/imp.rs b/examples/dock/dock_list/imp.rs deleted file mode 100644 index 0bfc14e..0000000 --- a/examples/dock/dock_list/imp.rs +++ /dev/null @@ -1,34 +0,0 @@ -use std::cell::{Cell, RefCell}; -use std::rc::Rc; - -use glib::SignalHandlerId; -use gtk4::subclass::prelude::*; -use gtk4::{gio, glib}; -use gtk4::{Box, DragSource, DropTarget, GestureClick, ListView}; -use once_cell::sync::OnceCell; - -#[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>>, -} - -#[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/examples/dock/dock_list/mod.rs b/examples/dock/dock_list/mod.rs deleted file mode 100644 index 9a1e194..0000000 --- a/examples/dock/dock_list/mod.rs +++ /dev/null @@ -1,627 +0,0 @@ -use crate::dock_item::DockItem; -use crate::dock_object::DockObject; -use crate::plugin; -use crate::utils::data_path; -use crate::BoxedWindowList; -use crate::Event; -use crate::Item; -use crate::PLUGINS; -use crate::TX; -use cascade::cascade; -use gdk4::ContentProvider; -use gdk4::Display; -use gdk4::ModifierType; -use gio::DesktopAppInfo; -use gio::Icon; -use glib::Object; -use glib::Type; -use gtk4::glib; -use gtk4::prelude::ListModelExt; -use gtk4::prelude::*; -use gtk4::subclass::prelude::*; -use gtk4::DropTarget; -use gtk4::IconTheme; -use gtk4::ListView; -use gtk4::Orientation; -use gtk4::SignalListItemFactory; -use gtk4::Window; -use gtk4::{DragSource, GestureClick}; -use std::ffi::CStr; -use std::fs::File; -use std::io::BufReader; -use std::io::Read; -use std::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) { - 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); - } - } - } - } - }) - } - }); - } - // TODO load saved plugins here... for now, load the hardcoded example. - // TODO unload plugin library before the dynamic library is changed, otherwise, it will crash after segfault - // TODO unload plugin on removal from model - // TODO dnd for plugin? I think they should either be at the start or end of the dock and not draggable - // TODO call plugin click handler on click or if it is not provided by the library, open the popover menu instead - let mut path_dir = glib::user_data_dir(); - path_dir.push(crate::ID); - std::fs::create_dir_all(&path_dir).expect("Could not create directory."); - path_dir.push("plugins"); - std::fs::create_dir_all(&path_dir).expect("Could not create directory."); - let mut path = path_dir.clone(); - path.push("dock_plugin_uwu.so"); - let mut path_css = path_dir.clone(); - path_css.push("dock_plugin_uwu.css"); - let provider = gtk4::CssProvider::new(); - if path.exists() { - let path = path - .as_os_str() - .to_str() - .expect("plugin path needs to be a valid string"); - - if let Ok(f) = File::open(path_css) { - let mut reader = BufReader::new(f); - let mut buffer = Vec::new(); - - if reader.read_to_end(&mut buffer).is_ok() { - provider.load_from_data(&buffer); - // Add the provider to the default screen - gtk4::StyleContext::add_provider_for_display( - &gdk4::Display::default().expect("Error initializing GTK CSS provider."), - &provider, - gtk4::STYLE_PROVIDER_PRIORITY_APPLICATION, - ); - } else { - eprintln!("loading plugin css failed"); - } - } else { - eprintln!("loading plugin css failed"); - } - - let (popover_menu, image, name, lib) = unsafe { - let lib = libloading::Library::new(path).unwrap(); - // store library until unloading the plugin - let image_func: libloading::Symbol< - unsafe extern "C" fn() -> *mut gtk4_sys::GtkWidget, - > = lib.get(b"dock_plugin_image").unwrap(); - let popover_func: libloading::Symbol< - unsafe extern "C" fn() -> *mut gtk4_sys::GtkWidget, - > = lib.get(b"dock_plugin_popover_menu").unwrap(); - let name_func: libloading::Symbol< - unsafe extern "C" fn() -> *const std::os::raw::c_char, - > = lib.get(b"dock_plugin_name").unwrap(); - // click handler is optional - - (popover_func(), image_func(), name_func(), lib) - }; - if let Ok(ref mut mutex) = PLUGINS.try_lock() { - mutex.insert(String::from(path), lib); - } - let name = if !name.is_null() { - unsafe { String::from(CStr::from_ptr(name).to_str().unwrap_or_default()) } - } else { - String::new() - }; - let image = if !image.is_null() { - unsafe { - gtk4::glib::translate::from_glib_none::<_, gtk4::Widget>(image).unsafe_cast() - } - } else { - gtk4::Image::new() - }; - let popover_menu = if !popover_menu.is_null() { - unsafe { - gtk4::glib::translate::from_glib_none::<_, gtk4::Widget>(popover_menu) - .unsafe_cast() - } - } else { - gtk4::Box::new(Orientation::Vertical, 4) - }; - let boxed_plugin = plugin::BoxedDockPlugin { - path: String::from(path), - name, - image, - popover_menu, - }; - let model = self.model(); - model.append(&DockObject::from_plugin(boxed_plugin).upcast::()); - } - } - - 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("docklist"); - }; - if imp.type_.get().unwrap() == &DockListType::Saved { - list_view.set_width_request(64); - } - self.append(&list_view); - imp.list_view.set(list_view).unwrap(); - } - - 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 window = list_view.root().unwrap().downcast::().unwrap(); - let max_x = list_view.allocated_width(); - let max_y = list_view.allocated_height(); - // dbg!(max_y); - // dbg!(y); - let n_buckets = model.n_items(); - let index = (x * n_buckets as f64 / (max_x as f64 + 0.1)) as u32; - // dbg!(self_.current_button()); - // dbg!(self_.last_event(self_.current_sequence().as_ref())); - let click_modifier = if let Some(event) = self_.last_event(self_.current_sequence().as_ref()) { - // dbg!(&event); - Some(event.modifier_state()) - } - else { - None - }; - // dbg!(click_modifier); - // Launch the application when an item of the list is activated - let focus_window = move |first_focused_item: &Item| { - let entity = first_focused_item.entity.clone(); - glib::MainContext::default().spawn_local(async move { - if let Some(tx) = TX.get() { - let _ = tx.send(Event::Activate(entity)).await; - } - }); - }; - 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 y > f64::from(max_y) || y < 0.0 || x > f64::from(max_x) || x < 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.iter().next(), 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 = window.display().app_launch_context(); - if let Err(err) = app_info.launch(&[], Some(&context)) { - gtk4::MessageDialog::builder() - .text(&format!("Failed to start {}", app_info.name())) - .secondary_text(&err.to_string()) - .message_type(gtk4::MessageType::Error) - .modal(true) - .transient_for(&window) - .build() - .show(); - } - - } - (click, _, _, _) if click == 3 => { - // println!("handling right click"); - if let Some(old_index) = popover_menu_index.get().clone() { - 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 = gdk4::DragAction::COPY; - drop_actions.insert(gdk4::DragAction::MOVE); - let drop_format = gdk4::ContentFormats::for_type(Type::STRING); - let drop_format = drop_format.union(&gdk4::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 n_buckets = model.n_items() * 2; - - let drop_bucket = (x * n_buckets as f64 / (max_x 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(); - } - glib::MainContext::default().spawn_local(async move { - let _ = TX.get().unwrap().send(Event::RefreshFromCache).await; - }); - 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 => gdk4::DragAction::MOVE, - &DockListType::Active => gdk4::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_.clone(); - 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); - glib::MainContext::default().spawn_local(async move { - if let Some(tx) = TX.get() { - let _ = tx.send(Event::RefreshFromCache).await; - } - }); - }; - }), - ))) { - 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 != gdk4::DragCancelReason::UserCancelled { - model.remove(index); - glib::MainContext::default().spawn_local(async move { - if let Some(tx) = TX.get() { - let _ = tx.send(Event::RefreshFromCache).await; - } - }); - 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(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."); - factory.connect_setup( - glib::clone!(@weak popover_menu_index, @weak model => move |_, list_item| { - let dock_item = DockItem::new(); - 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(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); - }); - // Set the factory of the list view - imp.list_view.get().unwrap().set_factory(Some(&factory)); - } -} diff --git a/examples/dock/dock_object/imp.rs b/examples/dock/dock_object/imp.rs deleted file mode 100644 index 3202799..0000000 --- a/examples/dock/dock_object/imp.rs +++ /dev/null @@ -1,114 +0,0 @@ -use std::cell::Cell; -use std::cell::RefCell; - -use gdk4::glib::ParamSpecBoolean; -use gdk4::glib::ParamSpecBoxed; -use gdk4::glib::ParamSpecObject; -use gio::DesktopAppInfo; -use glib::{ParamFlags, ParamSpec, Value}; -use gtk4::glib; -use gtk4::prelude::*; -use gtk4::subclass::prelude::*; -use once_cell::sync::Lazy; - -use crate::plugin::BoxedDockPlugin; -use crate::utils::BoxedWindowList; - -// Object holding the state -#[derive(Default)] -pub struct DockObject { - pub(super) appinfo: RefCell>, - pub(super) active: RefCell, - pub(super) plugin: 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/examples/dock/dock_object/mod.rs b/examples/dock/dock_object/mod.rs deleted file mode 100644 index 5bc6c9e..0000000 --- a/examples/dock/dock_object/mod.rs +++ /dev/null @@ -1,141 +0,0 @@ -use std::path::Path; - -use crate::plugin; -use crate::utils::BoxedWindowList; -use gdk4::glib::Object; -use gdk4::subclass::prelude::ObjectSubclassExt; -use gio::{DesktopAppInfo, Icon}; -use gtk4::prelude::*; -use gtk4::{glib, Image}; - -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 from_plugin(plugin: plugin::BoxedDockPlugin) -> Self { - let self_ = Object::new(&[("saved", &true)]).expect("Failed to create `DockObject`."); - let imp = imp::DockObject::from_instance(&self_); - imp.plugin.replace(Some(plugin)); - self_ - } - - 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 if let Some(plugin) = imp.plugin.borrow().as_ref() { - Some(plugin.path.clone()) - } else { - None - } - } - - pub fn get_name(&self) -> Option { - let imp = imp::DockObject::from_instance(&self); - if let Some(app_info) = imp.appinfo.borrow().as_ref() { - Some(app_info.name().to_string()) - } else if let Some(plugin) = imp.plugin.borrow().as_ref() { - Some(plugin.name.clone()) - } else { - None - } - } - - pub fn get_popover_menu(&self) -> Option { - let imp = imp::DockObject::from_instance(&self); - if let Some(plugin) = imp.plugin.borrow().as_ref() { - Some(plugin.popover_menu.clone()) - } else { - None - } - } - - 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(Icon::for_string("image-missing").expect("Failed to set default icon")); - image.set_from_gicon(&icon); - image - } else if let Some(plugin) = imp.plugin.borrow().as_ref() { - plugin.image.clone() - } else { - println!("failed to load image"); - Image::new() - } - } - - pub fn set_saved(&self, is_saved: bool) { - let imp = imp::DockObject::from_instance(&self); - imp.saved.replace(is_saved); - } - - pub fn from_search_results(results: BoxedWindowList) -> Self { - let appinfo = if let Some(first) = results.0.iter().next() { - 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() - && first.description.as_str() == app_info.name().as_str() - { - return Some(app_info); - } - } - } - } - } - None - }) - .next() - } else { - None - }; - // dbg!(&appinfo); - Object::new(&[("appinfo", &appinfo), ("active", &results)]) - .expect("Failed to create `DockObject`.") - } - - pub fn set_popover(&self, b: bool) { - let imp = imp::DockObject::from_instance(self); - imp.popover.replace(b); - } -} diff --git a/examples/dock/dock_popover/imp.rs b/examples/dock/dock_popover/imp.rs deleted file mode 100644 index ce349f6..0000000 --- a/examples/dock/dock_popover/imp.rs +++ /dev/null @@ -1,52 +0,0 @@ -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( - // Signal name - "menu-hide", - // Types of the values which will be sent to the signal handler - &[], - // Type of the value the signal handler sends back - <()>::static_type().into(), - ) - .build()] - }); - SIGNALS.as_ref() - } -} - -impl WidgetImpl for DockPopover {} - -impl BoxImpl for DockPopover {} diff --git a/examples/dock/dock_popover/mod.rs b/examples/dock/dock_popover/mod.rs deleted file mode 100644 index c4a02cf..0000000 --- a/examples/dock/dock_popover/mod.rs +++ /dev/null @@ -1,253 +0,0 @@ -use cascade::cascade; -use gdk4::pango::EllipsizeMode; -use gio::DesktopAppInfo; -use gtk4::subclass::prelude::*; -use gtk4::{gio, glib}; -use gtk4::{prelude::*, Label}; -use gtk4::{Box, Button, Image, ListBox, Orientation, Window}; - -use crate::dock_object::DockObject; -use crate::utils::BoxedWindowList; -use crate::Event; -use crate::TX; - -mod imp; - -glib::wrapper! { - pub struct DockPopover(ObjectSubclass) - @extends gtk4::Widget, gtk4::Box, - @implements gtk4::Accessible, gtk4::Buildable, gtk4::ConstraintTarget, gtk4::Orientable; -} - -impl Default for DockPopover { - fn default() -> Self { - Self::new() - } -} - -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_spacing(4); - ..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() { - if let Some(menu) = dock_object.get_popover_menu() { - menu_handle.append(&menu); - } else { - 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.len() == 0 { - all_windows_item_container.hide(); - } else { - let window_listbox = cascade! { - ListBox::new(); - ..set_activate_on_single_click(true); - }; - all_windows_item_container.append(&window_listbox); - for w in window_list.0 { - let window_box = cascade! { - Box::new(Orientation::Vertical, 4); - }; - 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("window_title"); - }; - - let window_image = cascade! { - //TODO fill with image of window - 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); - }; - menu_handle.append(&launch_item_container); - - let launch_new_item = cascade! { - Button::with_label("New Window"); - }; - 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"}); - }; - 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()); - }; - menu_handle.append(&quit_all_item); - imp.quit_all_item.replace(quit_all_item); - } else { - let quit_all_item = cascade! { - Button::with_label("Quit"); - }; - menu_handle.append(&quit_all_item); - if window_list.0.len() == 0 { - 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); - }; - 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 window = self_.root().unwrap().downcast::().unwrap(); - let context = window.display().app_launch_context(); - if let Err(err) = app_info.launch(&[], Some(&context)) { - gtk4::MessageDialog::builder() - .text(&format!("Failed to start {}", app_info.name())) - .secondary_text(&err.to_string()) - .message_type(gtk4::MessageType::Error) - .modal(true) - .transient_for(&window) - .build() - .show(); - } - 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 entity = w.entity.clone(); - glib::MainContext::default().spawn_local(async move { - if let Some(tx) = TX.get() { - let _ = tx.send(Event::Close(entity)).await; - } - }); - } - self_.emit_hide(); - })); - - let self_ = self.clone(); - favorite_item.connect_clicked(glib::clone!(@weak dock_object => move |_| { - let saved = dock_object.property::("saved"); - - glib::MainContext::default().spawn_local(async move { - if let Some(tx) = TX.get() { - if let Some(name) = dock_object.get_name() { - let _ = tx.send(Event::Favorite((name.into(), !saved))).await; - } - } - }); - 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 entity = active[usize::try_from(item.index()).unwrap()].entity.clone(); - glib::MainContext::default().spawn_local(async move { - if let Some(tx) = TX.get() { - let _ = tx.send(Event::Activate(entity)).await; - } - }); - self_.emit_hide(); - }), - ); - } - } -} diff --git a/examples/dock/main.rs b/examples/dock/main.rs deleted file mode 100644 index bf03fb3..0000000 --- a/examples/dock/main.rs +++ /dev/null @@ -1,349 +0,0 @@ -use std::collections::BTreeMap; -use std::sync::Mutex; -use std::time::Duration; - -use crate::dock_list::DockListType; -use crate::utils::{block_on, BoxedWindowList}; -use gdk4::Display; -use gio::DesktopAppInfo; -use gtk4::glib; -use gtk4::prelude::*; -use gtk4::Application; -use gtk4::CssProvider; -use gtk4::StyleContext; -use once_cell::sync::{Lazy, OnceCell}; -use serde::{Deserialize, Serialize}; -use std::collections::HashMap; -use tokio::sync::mpsc; -use zbus::Connection; -use zvariant_derive::Type; - -use self::dock_object::DockObject; -use self::window::Window; - -mod dock_item; -mod dock_list; -mod dock_object; -mod dock_popover; -mod plugin; -mod utils; -mod window; - -const ID: &str = "com.cosmic.dock"; -const DEST: &str = "com.System76.PopShell"; -const PATH: &str = "/com/System76/PopShell"; - -static TX: OnceCell> = OnceCell::new(); -static PLUGINS: Lazy>> = - Lazy::new(|| Mutex::new(HashMap::new())); - -pub enum Event { - WindowList(Vec), - Activate((u32, u32)), - Close((u32, u32)), - Favorite((String, bool)), - RefreshFromCache, -} - -#[derive(Debug, Deserialize, Serialize, Type, Clone, PartialEq, Eq)] -pub struct Item { - entity: (u32, u32), - name: String, - description: String, - desktop_entry: String, -} - -fn spawn_zbus(tx: mpsc::Sender) -> Connection { - let connection = block_on(Connection::session()).unwrap(); - - let sender = tx.clone(); - let conn = connection.clone(); - let _ = std::thread::spawn(move || { - let cached_results: Vec = vec![]; - block_on(async move { - futures::pin_mut!(cached_results); - loop { - let m = conn - .call_method(Some(DEST), PATH, Some(DEST), "WindowList", &()) - .await; - if let Ok(m) = m { - if let Ok(mut reply) = m.body::>() { - let mut cached_results = cached_results.as_mut(); - reply.sort_by(|a, b| a.name.cmp(&b.name)); - - if cached_results.len() != reply.len() - || !reply.iter().zip(cached_results.iter()).fold( - 0, - |acc, z: (&Item, &Item)| { - let (a, b) = z; - if a.name == b.name { - acc + 1 - } else { - acc - } - }, - ) == cached_results.len() - { - cached_results.splice(.., reply.clone()); - let _ = sender.send(Event::WindowList(reply)).await; - } - } - glib::timeout_future(Duration::from_millis(100)).await; - } - } - }) - }); - - connection -} - -fn _setup_shortcuts(_app: &Application) {} - -fn load_css() { - // Load the css file and add it to the provider - let provider = CssProvider::new(); - provider.load_from_data(include_bytes!("style.css")); - - // Add the provider to the default screen - StyleContext::add_provider_for_display( - &Display::default().expect("Error initializing GTK CSS provider."), - &provider, - gtk4::STYLE_PROVIDER_PRIORITY_APPLICATION, - ); -} - -fn main() { - assert!(utils::BoxedWindowList::static_type().is_valid()); - assert!(plugin::BoxedDockPlugin::static_type().is_valid()); - let app = gtk4::Application::builder().application_id(ID).build(); - - app.connect_startup(|_app| { - // setup_shortcuts(app); - load_css() - }); - - app.connect_activate(move |app| { - let (tx, mut rx) = mpsc::channel(100); - - let zbus_conn = spawn_zbus(tx.clone()); - if TX.set(tx).is_err() { - eprintln!("failed to set global Sender. Exiting"); - std::process::exit(1); - }; - - let window = Window::new(app); - window.show(); - - let cached_results: Vec = vec![]; - glib::MainContext::default().spawn_local(async move { - futures::pin_mut!(cached_results); - // let rx = RX.get().unwrap().clone(); - while let Some(event) = rx.recv().await { - match event { - Event::Activate(e) => { - let _activate_window = zbus_conn - .call_method(Some(DEST), PATH, Some(DEST), "WindowFocus", &((e,))) - .await - .expect("Failed to focus selected window"); - } - Event::Close(e) => { - let _activate_window = zbus_conn - .call_method(Some(DEST), PATH, Some(DEST), "WindowQuit", &((e,))) - .await - .expect("Failed to close selected window"); - } - Event::Favorite((name, should_favorite)) => { - dbg!(&name); - dbg!(should_favorite); - let saved_app_model = window.model(DockListType::Saved); - let active_app_model = window.model(DockListType::Active); - if should_favorite { - let mut cur: u32 = 0; - let mut index: Option = None; - while let Some(item) = active_app_model.item(cur) { - if let Ok(cur_dock_object) = item.downcast::() { - if cur_dock_object.get_path() == Some(name.clone()) { - cur_dock_object.set_saved(true); - index = Some(cur); - } - } - cur += 1; - } - if let Some(index) = index { - let object = active_app_model.item(index).unwrap(); - active_app_model.remove(index); - saved_app_model.append(&object); - } - } else { - let mut cur: u32 = 0; - let mut index: Option = None; - while let Some(item) = saved_app_model.item(cur) { - if let Ok(cur_dock_object) = item.downcast::() { - if cur_dock_object.get_path() == Some(name.clone()) { - cur_dock_object.set_saved(false); - index = Some(cur); - } - } - cur += 1; - } - if let Some(index) = index { - let object = saved_app_model.item(index).unwrap(); - saved_app_model.remove(index); - active_app_model.append(&object); - } - } - let _ = TX.get().unwrap().send(Event::RefreshFromCache).await; - } - Event::RefreshFromCache => { - // println!("refreshing model from cache"); - let cached_results = cached_results.as_ref(); - let stack_active = cached_results.iter().fold( - BTreeMap::new(), - |mut acc: BTreeMap, elem| { - if let Some(v) = acc.get_mut(&elem.description) { - v.0.push(elem.clone()); - } else { - acc.insert( - elem.description.clone(), - BoxedWindowList(vec![elem.clone()]), - ); - } - acc - }, - ); - let mut stack_active: Vec = - stack_active.into_values().collect(); - - // update active app stacks for saved apps into the saved app model - // then put the rest in the active app model (which doesn't include saved apps) - let saved_app_model = window.model(DockListType::Saved); - - let mut saved_i: u32 = 0; - while let Some(item) = saved_app_model.item(saved_i) { - if let Ok(dock_obj) = item.downcast::() { - if let Some(cur_app_info) = - dock_obj.property::>("appinfo") - { - if let Some((i, _s)) = stack_active - .iter() - .enumerate() - .find(|(_i, s)| s.0[0].description == cur_app_info.name()) - { - // println!( - // "found active saved app {} at {}", - // _s.0[0].name, i - // ); - let active = stack_active.remove(i); - dock_obj.set_property("active", active.to_value()); - saved_app_model.items_changed( - saved_i.try_into().unwrap(), - 0, - 0, - ); - } else if let Some(_) = cached_results - .iter() - .find(|s| s.description == cur_app_info.name()) - { - dock_obj.set_property( - "active", - BoxedWindowList(Vec::new()).to_value(), - ); - saved_app_model.items_changed( - saved_i.try_into().unwrap(), - 0, - 0, - ); - } - } - } - saved_i += 1; - } - - let active_app_model = window.model(DockListType::Active); - let model_len = active_app_model.n_items(); - let new_results: Vec = stack_active - .into_iter() - .map(|v| DockObject::from_search_results(v).upcast()) - .collect(); - active_app_model.splice(0, model_len, &new_results[..]); - } - Event::WindowList(results) => { - // sort to make comparison with cache easier - let mut cached_results = cached_results.as_mut(); - - // build active app stacks for each app - let stack_active = results.iter().fold( - BTreeMap::new(), - |mut acc: BTreeMap, elem| { - if let Some(v) = acc.get_mut(&elem.description) { - v.0.push(elem.clone()); - } else { - acc.insert( - elem.description.clone(), - BoxedWindowList(vec![elem.clone()]), - ); - } - acc - }, - ); - let mut stack_active: Vec = - stack_active.into_values().collect(); - - // update active app stacks for saved apps into the saved app model - // then put the rest in the active app model (which doesn't include saved apps) - let saved_app_model = window.model(DockListType::Saved); - - let mut saved_i: u32 = 0; - while let Some(item) = saved_app_model.item(saved_i) { - if let Ok(dock_obj) = item.downcast::() { - if let Some(cur_app_info) = - dock_obj.property::>("appinfo") - { - if let Some((i, _s)) = stack_active - .iter() - .enumerate() - .find(|(_i, s)| s.0[0].description == cur_app_info.name()) - { - // println!("found active saved app {} at {}", s.0[0].name, i); - let active = stack_active.remove(i); - dock_obj.set_property("active", active.to_value()); - saved_app_model.items_changed( - saved_i.try_into().unwrap(), - 0, - 0, - ); - } else if let Some(_) = cached_results - .iter() - .find(|s| s.description == cur_app_info.name()) - { - dock_obj.set_property( - "active", - BoxedWindowList(Vec::new()).to_value(), - ); - saved_app_model.items_changed( - saved_i.try_into().unwrap(), - 0, - 0, - ); - } - } - } - saved_i += 1; - } - - let active_app_model = window.model(DockListType::Active); - let model_len = active_app_model.n_items(); - let new_results: Vec = stack_active - .into_iter() - .map(|v| DockObject::from_search_results(v).upcast()) - .collect(); - active_app_model.splice(0, model_len, &new_results[..]); - cached_results.splice(.., results); - } - } - } - }); - }); - - app.run(); -} diff --git a/examples/dock/plugin.rs b/examples/dock/plugin.rs deleted file mode 100644 index 3cd6432..0000000 --- a/examples/dock/plugin.rs +++ /dev/null @@ -1,10 +0,0 @@ -use gtk4::glib; - -#[derive(Clone, Debug, Default, gtk4::glib::Boxed)] -#[boxed_type(name = "BoxedDockPlugin")] -pub struct BoxedDockPlugin { - pub path: String, - pub name: String, - pub image: gtk4::Image, - pub popover_menu: gtk4::Box, -} diff --git a/examples/dock/style.css b/examples/dock/style.css deleted file mode 100644 index 60afd21..0000000 --- a/examples/dock/style.css +++ /dev/null @@ -1,69 +0,0 @@ -listview.docklist { - border-radius: 12px; - background: transparent; - transition: 100ms; -} - -listview.docklist row:hover { - background: #888888CC; - border-radius: 12px; - padding: 0px; -} - -listview.docklist row { - transition: 100ms; - background: transparent; - border-radius: 12px; - padding: 0px; -} - -button:hover { - transition: 300ms; - border-width: 0px; - background: #888888CC; - border-radius: 12px; -} - -button { - transition: 300ms; - background: transparent; - border-radius: 12px; - border-width: 0px; -} - -list { - border-radius: 12px; - background: #333333CC; -} - -label.window_title { - color: white; - margin: 4px; -} - -popover contents { - border-width: 0px; - border-radius: 12px; - padding: 12px; - background: #333333DD; -} - -popover arrow { - border-width: 0px; - border-radius: 12px; - padding: 12px; - background: #333333DD; -} - -box.dock { - border-radius: 12px; - background: #333333CC; -} - -image.dock { - border-radius: 12px; -} - -window.root_window { - background: transparent; -} diff --git a/examples/dock/utils.rs b/examples/dock/utils.rs deleted file mode 100644 index c43c4e5..0000000 --- a/examples/dock/utils.rs +++ /dev/null @@ -1,38 +0,0 @@ -use std::path::PathBuf; - -use gtk4::glib; -use std::future::Future; - -use crate::DockObject; -use crate::Item; - -#[derive(Clone, Debug, Default, glib::Boxed)] -#[boxed_type(name = "BoxedWindowList")] -pub struct BoxedWindowList(pub Vec); - -#[derive(Clone, Debug, Default, glib::Boxed)] -#[boxed_type(name = "BoxedDockObject")] -pub struct BoxedDockObject(pub Option); - -pub fn data_path() -> PathBuf { - let mut path = glib::user_data_dir(); - path.push(crate::ID); - std::fs::create_dir_all(&path).expect("Could not create directory."); - path.push("data.json"); - path -} - -pub fn thread_context() -> glib::MainContext { - glib::MainContext::thread_default().unwrap_or_else(|| { - let ctx = glib::MainContext::new(); - ctx - }) -} - -pub fn block_on(future: F) -> F::Output -where - F: Future, -{ - let ctx = thread_context(); - ctx.with_thread_default(|| ctx.block_on(future)).unwrap() -} diff --git a/examples/dock/window/imp.rs b/examples/dock/window/imp.rs deleted file mode 100644 index 5fdf47f..0000000 --- a/examples/dock/window/imp.rs +++ /dev/null @@ -1,41 +0,0 @@ -use gtk4::glib; -use gtk4::subclass::prelude::*; -use gtk4::Box; -use gtk4::DropTarget; -use gtk4::EventControllerMotion; -use gtk4::Revealer; -use once_cell::sync::OnceCell; - -use crate::dock_list::DockList; - -// Object holding the state -#[derive(Default)] -pub struct Window { - pub revealer: OnceCell, - pub cursor_handle: OnceCell, - pub cursor_motion_controller: OnceCell, - pub window_drop_controller: OnceCell, - pub saved_list: OnceCell, - pub active_list: OnceCell, -} - -// The central trait for subclassing a GObject -#[glib::object_subclass] -impl ObjectSubclass for Window { - // `NAME` needs to match `class` attribute of template - const NAME: &'static str = "DockWindow"; - type Type = super::Window; - type ParentType = gtk4::ApplicationWindow; -} - -// Trait shared by all GObjects -impl ObjectImpl for Window {} - -// Trait shared by all widgets -impl WidgetImpl for Window {} - -// Trait shared by all windows -impl WindowImpl for Window {} - -// Trait shared by all application -impl ApplicationWindowImpl for Window {} diff --git a/examples/dock/window/mod.rs b/examples/dock/window/mod.rs deleted file mode 100644 index 81666fe..0000000 --- a/examples/dock/window/mod.rs +++ /dev/null @@ -1,252 +0,0 @@ -use cascade::cascade; -use gdk4_x11::X11Display; -use glib::Object; -use glib::Type; -use gtk4::prelude::*; -use gtk4::subclass::prelude::*; -use gtk4::Align; -use gtk4::Application; -use gtk4::Box; -use gtk4::DropTarget; -use gtk4::EventControllerMotion; -use gtk4::Orientation; -use gtk4::Revealer; -use gtk4::RevealerTransitionType; -use gtk4::Separator; -use gtk4::{gio, glib}; - -use libcosmic::x; - -use crate::dock_list::DockList; -use crate::dock_list::DockListType; - -mod imp; - -glib::wrapper! { - pub struct Window(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 Window { - pub fn new(app: &Application) -> Self { - let self_: Self = Object::new(&[("application", app)]).expect("Failed to create `Window`."); - let imp = imp::Window::from_instance(&self_); - cascade! { - &self_; - ..set_height_request(100); - ..set_width_request(128); - ..set_title(Some("Cosmic Dock")); - ..set_decorated(false); - ..set_resizable(false); - ..add_css_class("root_window"); - }; - let cursor_handle = Box::new(Orientation::Vertical, 0); - self_.set_child(Some(&cursor_handle)); - - let window_filler = cascade! { - Box::new(Orientation::Vertical, 0); - ..set_height_request(0); // shrinks to nothing when revealer is shown - ..set_vexpand(true); // expands to fill window when revealer is hidden, preventingb window from changing size so much... - }; - cursor_handle.append(&window_filler); - - let revealer = cascade! { - Revealer::new(); - ..set_reveal_child(true); - ..set_valign(Align::Baseline); - ..set_transition_duration(150); - ..set_transition_type(RevealerTransitionType::SwingUp); - }; - cursor_handle.append(&revealer); - - let dock = cascade! { - Box::new(Orientation::Horizontal, 4); - ..set_margin_start(4); - ..set_margin_end(4); - ..set_margin_bottom(4); - }; - dock.add_css_class("dock"); - revealer.set_child(Some(&dock)); - - let saved_app_list_view = DockList::new(DockListType::Saved); - dock.append(&saved_app_list_view); - - let separator = cascade! { - Separator::new(Orientation::Vertical); - ..set_margin_start(8); - ..set_margin_end(8); - }; - dock.append(&separator); - - let active_app_list_view = DockList::new(DockListType::Active); - dock.append(&active_app_list_view); - - imp.cursor_handle.set(cursor_handle).unwrap(); - imp.revealer.set(revealer).unwrap(); - imp.saved_list.set(saved_app_list_view).unwrap(); - imp.active_list.set(active_app_list_view).unwrap(); - // Setup - self_.setup_motion_controller(); - self_.setup_drop_target(); - self_.setup_callbacks(); - - self_ - } - - pub fn model(&self, type_: DockListType) -> &gio::ListStore { - // Get state - let imp = imp::Window::from_instance(self); - match type_ { - DockListType::Active => imp.active_list.get().unwrap().model(), - DockListType::Saved => imp.saved_list.get().unwrap().model(), - } - } - - fn setup_callbacks(&self) { - // Get state - let imp = imp::Window::from_instance(self); - let window = self.clone().upcast::(); - let cursor_event_controller = &imp.cursor_motion_controller.get().unwrap(); - // let drop_controller = &imp.drop_controller.get().unwrap(); - let window_drop_controller = &imp.window_drop_controller.get().unwrap(); - let revealer = &imp.revealer.get().unwrap(); - window.connect_show( - glib::clone!(@weak revealer, @weak cursor_event_controller => move |_| { - // dbg!(!cursor_event_controller.contains_pointer()); - if !cursor_event_controller.contains_pointer() { - revealer.set_reveal_child(false); - } - }), - ); - window.connect_realize(glib::clone!(@weak revealer, @weak window_drop_controller, @weak cursor_event_controller => move |window| { - if let Some((display, surface)) = x::get_window_x11(window) { - // ignore all x11 errors... - let xdisplay = display.clone().downcast::().expect("Failed to downgrade X11 Display."); - xdisplay.error_trap_push(); - unsafe { - x::change_property( - &display, - &surface, - "_NET_WM_WINDOW_TYPE", - x::PropMode::Replace, - &[x::Atom::new(&display, "_NET_WM_WINDOW_TYPE_DOCK").unwrap()], - ); - } - let resize = glib::clone!(@weak window, @weak revealer => move || { - let height = if revealer.reveals_child() { window.height() } else { 4 }; - let width = window.width(); - - if let Some((display, _surface)) = x::get_window_x11(&window) { - let geom = display - .primary_monitor().geometry(); - let monitor_x = geom.x(); - let monitor_y = geom.y(); - let monitor_width = geom.width(); - let monitor_height = geom.height(); - // dbg!(monitor_x); - // dbg!(monitor_y); - // dbg!(monitor_width); - // dbg!(monitor_height); - // dbg!(width); - // dbg!(height); - unsafe { x::set_position(&display, &surface, - (monitor_x + monitor_width / 2 - width / 2).clamp(0, monitor_x + monitor_width - 1), - (monitor_y + monitor_height - height).clamp(0, monitor_y + monitor_height - 1));} - } - }); - - let resize_drop = resize.clone(); - window_drop_controller.connect_enter(glib::clone!(@weak revealer, @weak window => @default-return gdk4::DragAction::COPY, move |_self, _x, _y| { - glib::source::idle_add_local_once(resize_drop.clone()); - revealer.set_reveal_child(true); - gdk4::DragAction::COPY - })); - - let resize_cursor = resize.clone(); - cursor_event_controller.connect_enter(glib::clone!(@weak revealer, @weak window => move |_evc, _x, _y| { - // dbg!("hello, mouse entered me :)"); - revealer.set_reveal_child(true); - glib::source::idle_add_local_once(resize_cursor.clone()); - })); - - let resize_revealed = resize.clone(); - revealer.connect_child_revealed_notify(glib::clone!(@weak window => move |r| { - if !r.is_child_revealed() { - glib::source::idle_add_local_once(resize_revealed.clone()); - } - })); - - let s = window.surface(); - let resize_height = resize.clone(); - s.connect_height_notify(move |_s| { - glib::source::idle_add_local_once(resize_height.clone()); - }); - let resize_width = resize.clone(); - s.connect_width_notify(move |_s| { - glib::source::idle_add_local_once(resize_width.clone()); - }); - s.connect_scale_factor_notify(move |_s| { - glib::source::idle_add_local_once(resize.clone()); - }); - } else { - println!("failed to get X11 window"); - } - })); - - let drop_controller = imp.saved_list.get().unwrap().drop_controller(); - cursor_event_controller.connect_leave( - glib::clone!(@weak revealer, @weak drop_controller => move |_evc| { - // only hide if DnD is not happening - if drop_controller.current_drop().is_none() { - // dbg!("hello, mouse left me :)"); - revealer.set_reveal_child(false); - } - }), - ); - - // hack to prevent hiding window when dnd from other apps - drop_controller.connect_enter(glib::clone!(@weak revealer => @default-return gdk4::DragAction::COPY, move |_self, _x, _y| { - - revealer.set_reveal_child(true); - gdk4::DragAction::COPY - })); - window_drop_controller.connect_drop(|_, _, _, _| { - println!("dropping into window"); - false - }); - } - - fn setup_motion_controller(&self) { - let imp = imp::Window::from_instance(self); - let handle = &imp.cursor_handle.get().unwrap(); - let ev = EventControllerMotion::builder() - .propagation_limit(gtk4::PropagationLimit::None) - .propagation_phase(gtk4::PropagationPhase::Capture) - .build(); - handle.add_controller(&ev); - - imp.cursor_motion_controller - .set(ev) - .expect("Could not set event controller"); - } - fn setup_drop_target(&self) { - // hack for revealing hidden dock when drag enters dock window - let imp = imp::Window::from_instance(self); - let mut drop_actions = gdk4::DragAction::COPY; - drop_actions.insert(gdk4::DragAction::MOVE); - let drop_format = gdk4::ContentFormats::for_type(Type::STRING); - let drop_format = drop_format.union(&gdk4::ContentFormats::for_type(Type::U32)); - - let window_drop_target_controller = DropTarget::builder() - .actions(drop_actions) - .formats(&drop_format) - .build(); - - let enter_handle = &imp.cursor_handle.get().unwrap(); - enter_handle.add_controller(&window_drop_target_controller); - imp.window_drop_controller - .set(window_drop_target_controller) - .expect("Could not set dock dnd drop controller"); - } -} diff --git a/examples/launcher/main.rs b/examples/launcher/main.rs deleted file mode 100644 index 8351eec..0000000 --- a/examples/launcher/main.rs +++ /dev/null @@ -1,128 +0,0 @@ -use gdk4::Display; -use gio::DesktopAppInfo; -use gtk4::gio; -use gtk4::glib; -use gtk4::prelude::*; -use gtk4::Application; -use gtk4::CssProvider; -use gtk4::StyleContext; -use once_cell::sync::OnceCell; -use pop_launcher_service::IpcClient; -use tokio::sync::mpsc; - -use crate::utils::BoxedSearchResult; - -use self::search_result_object::SearchResultObject; -use self::window::Window; - -mod search_result_object; -mod search_result_row; -mod utils; -mod window; - -const NUM_LAUNCHER_ITEMS: u8 = 10; -static TX: OnceCell> = OnceCell::new(); - -pub enum Event { - Response(pop_launcher::Response), - Search(String), - Activate(u32), -} - -fn spawn_launcher(tx: mpsc::Sender) -> IpcClient { - let (launcher, responses) = - pop_launcher_service::IpcClient::new().expect("failed to connect to launcher service"); - - glib::MainContext::default().spawn_local(async move { - use futures::StreamExt; - futures::pin_mut!(responses); - while let Some(event) = responses.next().await { - let _ = tx.send(Event::Response(event)).await; - } - }); - - launcher -} - -fn setup_shortcuts(app: &Application) { - //quit shortcut - app.set_accels_for_action("win.quit", &["W", "Escape"]); - //launch shortcuts - for i in 1..NUM_LAUNCHER_ITEMS { - app.set_accels_for_action(&format!("win.launch{}", i), &[&format!("{}", i)]); - } -} - -fn load_css() { - // Load the css file and add it to the provider - let provider = CssProvider::new(); - provider.load_from_data(include_bytes!("style.css")); - - // Add the provider to the default screen - StyleContext::add_provider_for_display( - &Display::default().expect("Error initializing GTK CSS provider."), - &provider, - gtk4::STYLE_PROVIDER_PRIORITY_APPLICATION, - ); -} - -fn main() { - let app = gtk4::Application::builder() - .application_id("com.cosmic.Launcher") - .build(); - - app.connect_startup(|app| { - setup_shortcuts(app); - load_css() - }); - app.connect_activate(move |app| { - let (tx, mut rx) = mpsc::channel(100); - let mut launcher = spawn_launcher(tx.clone()); - if TX.set(tx).is_err() { - println!("failed to set global Sender. Exiting"); - std::process::exit(1); - }; - - let window = Window::new(app); - window.show(); - - glib::MainContext::default().spawn_local(async move { - while let Some(event) = rx.recv().await { - match event { - Event::Search(search) => { - let _ = launcher.send(pop_launcher::Request::Search(search)).await; - } - Event::Activate(index) => { - let _ = launcher.send(pop_launcher::Request::Activate(index)).await; - } - - Event::Response(event) => { - if let pop_launcher::Response::Update(results) = event { - let model = window.model(); - let model_len = model.n_items(); - dbg!(&results); - let new_results: Vec = results - // [0..std::cmp::min(results.len(), NUM_LAUNCHER_ITEMS.into())] - .into_iter() - .map(|result| SearchResultObject::new(&BoxedSearchResult(Some(result))).upcast()) - .collect(); - model.splice(0, model_len, &new_results[..]); - } else if let pop_launcher::Response::DesktopEntry { - path, - gpu_preference: _gpu_preference, // TODO use GPU preference when launching app - } = event - { - let app_info = - DesktopAppInfo::new(&path.file_name().expect("desktop entry path needs to be a valid filename").to_string_lossy()) - .expect("failed to create a Desktop App info for launching the application."); - app_info - .launch(&[], Some(&window.display().app_launch_context())).expect("failed to launch the application."); - } - } - } - } - }); - }); - - app.run(); -} diff --git a/examples/launcher/search_result_object/imp.rs b/examples/launcher/search_result_object/imp.rs deleted file mode 100644 index cfe117e..0000000 --- a/examples/launcher/search_result_object/imp.rs +++ /dev/null @@ -1,61 +0,0 @@ -use std::cell::RefCell; -use std::rc::Rc; - -use glib::{ParamFlags, ParamSpec, ParamSpecBoxed, Value}; -use gtk4::glib; -use gtk4::prelude::*; -use gtk4::subclass::prelude::*; -use once_cell::sync::Lazy; - -use crate::utils::BoxedSearchResult; - -// Object holding the state -#[derive(Default)] -pub struct SearchResultObject { - data: Rc>, -} - -// The central trait for subclassing a GObject -#[glib::object_subclass] -impl ObjectSubclass for SearchResultObject { - const NAME: &'static str = "SearchResultObject"; - type Type = super::SearchResultObject; - type ParentType = glib::Object; -} - -// Trait shared by all GObjects -impl ObjectImpl for SearchResultObject { - fn properties() -> &'static [ParamSpec] { - static PROPERTIES: Lazy> = Lazy::new(|| { - vec![ParamSpecBoxed::new( - // Name - "data", - // Nickname - "data", - // Short description - "data", - BoxedSearchResult::static_type(), - // The property can be read and written to - ParamFlags::READWRITE, - )] - }); - PROPERTIES.as_ref() - } - - fn set_property(&self, _obj: &Self::Type, _id: usize, value: &Value, pspec: &ParamSpec) { - match pspec.name() { - "data" => { - let data = value.get().expect("Value needs to be BoxedSearchResult"); - self.data.replace(data); - } - _ => unimplemented!(), - } - } - - fn property(&self, _obj: &Self::Type, _id: usize, pspec: &ParamSpec) -> Value { - match pspec.name() { - "data" => self.data.borrow().to_value(), - _ => unimplemented!(), - } - } -} diff --git a/examples/launcher/search_result_object/mod.rs b/examples/launcher/search_result_object/mod.rs deleted file mode 100644 index 8c2589f..0000000 --- a/examples/launcher/search_result_object/mod.rs +++ /dev/null @@ -1,20 +0,0 @@ -use crate::utils::BoxedSearchResult; -use gtk4::glib; -use gtk4::prelude::*; - -mod imp; - -glib::wrapper! { - pub struct SearchResultObject(ObjectSubclass); -} - -impl SearchResultObject { - pub fn new(search_result: &BoxedSearchResult) -> Self { - glib::Object::new(&[("data", search_result)]).expect("Failed to create Application Object") - } - - pub fn data(&self) -> Option { - let search_result = self.property::("data"); - return search_result.0; - } -} diff --git a/examples/launcher/search_result_row/imp.rs b/examples/launcher/search_result_row/imp.rs deleted file mode 100644 index 0afa5bc..0000000 --- a/examples/launcher/search_result_row/imp.rs +++ /dev/null @@ -1,26 +0,0 @@ -use gtk4::glib; -use gtk4::subclass::prelude::*; -use std::cell::RefCell; -use std::rc::Rc; - -#[derive(Debug, Default)] -pub struct SearchResultRow { - pub name: Rc>, - pub description: Rc>, - pub shortcut: Rc>, - pub image: Rc>, - pub category_image: Rc>, -} - -#[glib::object_subclass] -impl ObjectSubclass for SearchResultRow { - const NAME: &'static str = "SearchResultRow"; - type Type = super::SearchResultRow; - type ParentType = gtk4::Box; -} - -impl ObjectImpl for SearchResultRow {} - -impl WidgetImpl for SearchResultRow {} - -impl BoxImpl for SearchResultRow {} diff --git a/examples/launcher/search_result_row/mod.rs b/examples/launcher/search_result_row/mod.rs deleted file mode 100644 index 0f3a5d7..0000000 --- a/examples/launcher/search_result_row/mod.rs +++ /dev/null @@ -1,124 +0,0 @@ -use cascade::cascade; -use gtk4::glib; -use gtk4::pango::EllipsizeMode; -use gtk4::prelude::*; -use gtk4::subclass::prelude::*; -use gtk4::Align; -use gtk4::Box; -use gtk4::Image; -use gtk4::Label; -use gtk4::Orientation; - -use crate::utils::icon_source; -use crate::BoxedSearchResult; -use crate::SearchResultObject; - -mod imp; - -glib::wrapper! { - pub struct SearchResultRow(ObjectSubclass) - @extends gtk4::Widget, gtk4::Box, - @implements gtk4::Accessible, gtk4::Buildable, gtk4::ConstraintTarget, gtk4::Orientable; -} - -impl Default for SearchResultRow { - fn default() -> Self { - Self::new() - } -} - -impl SearchResultRow { - pub fn new() -> Self { - let self_ = glib::Object::new(&[]).expect("Failed to create SearchResultRow"); - let imp = imp::SearchResultRow::from_instance(&self_); - - cascade! { - &self_; - ..set_orientation(Orientation::Horizontal); - ..set_spacing(12); - ..set_margin_start(4); - ..set_margin_end(4); - ..set_hexpand(true); - }; - - let category_image = cascade! { - Image::new(); - ..set_pixel_size(24); - }; - self_.append(&category_image); - - let image = cascade! { - Image::new(); - ..set_margin_top(4); - ..set_margin_bottom(4); - ..set_pixel_size(40); - }; - self_.append(&image); - - let text_container = cascade! { - Box::new(Orientation::Vertical, 0); - ..set_halign(Align::Fill); - ..set_hexpand(true); - ..set_margin_top(4); - ..set_margin_end(4); - ..set_margin_bottom(4); - }; - self_.append(&text_container); - - let shortcut = cascade! { - Label::new(None); - ..set_halign(Align::End); - ..set_wrap(false); - ..add_css_class("body"); - }; - self_.append(&shortcut); - - let name = cascade! { - Label::new(None); - ..set_halign(Align::Start); - ..set_ellipsize(EllipsizeMode::End); - ..set_max_width_chars(40); - ..add_css_class("title-4"); - }; - text_container.append(&name); - - let description = cascade! { - Label::new(None); - ..set_halign(Align::Start); - ..set_ellipsize(EllipsizeMode::End); - ..set_max_width_chars(50); - ..add_css_class("body"); - }; - text_container.append(&description); - - imp.category_image.replace(category_image); - imp.image.replace(image); - imp.name.replace(name); - imp.description.replace(description); - imp.shortcut.replace(shortcut); - - self_ - } - - pub fn set_search_result(&self, search_obj: SearchResultObject) { - let self_ = imp::SearchResultRow::from_instance(self); - let search_result = search_obj.property::("data"); - if let Some(search_result) = search_result.0 { - self_.name.borrow().set_text(&search_result.name); - self_ - .description - .borrow() - .set_text(&search_result.description); - icon_source(&self_.image, &search_result.icon); - icon_source(&self_.category_image, &search_result.category_icon); - } - } - - pub fn set_shortcut(&self, indx: u32) { - let self_ = imp::SearchResultRow::from_instance(self); - self_ - .shortcut - .borrow() - .set_text(&format!("Ctrl + {}", indx)); - } -} diff --git a/examples/launcher/style.css b/examples/launcher/style.css deleted file mode 100644 index d1c017b..0000000 --- a/examples/launcher/style.css +++ /dev/null @@ -1,25 +0,0 @@ -listview row:selected { - transition: 100ms; - background: #888888; - border-radius: 8px; -} - -listview row { - transition: 100ms; - background: #333333; - border-radius: 8px; -} - -listview { - background: #333333; -} - -box.container { - background: #333333; - padding: 12px; - border-radius: 12px; -} - -window.root_window { - background: rgba(50, 50, 50, 0.0); -} diff --git a/examples/launcher/utils.rs b/examples/launcher/utils.rs deleted file mode 100644 index 742639e..0000000 --- a/examples/launcher/utils.rs +++ /dev/null @@ -1,22 +0,0 @@ -use gtk4::glib; -use std::cell::RefCell; -use std::rc::Rc; - -#[derive(Clone, Debug, Default, glib::Boxed)] -#[boxed_type(name = "BoxedSearchResult")] -pub struct BoxedSearchResult(pub Option); - -pub fn icon_source(icon: &Rc>, source: &Option) { - match source { - Some(pop_launcher::IconSource::Name(name)) => { - icon.borrow().set_from_icon_name(Some(name)); - } - Some(pop_launcher::IconSource::Mime(content_type)) => { - icon.borrow() - .set_from_gicon(&gio::content_type_get_icon(content_type)); - } - _ => { - icon.borrow().set_from_icon_name(None); - } - } -} diff --git a/examples/launcher/window/imp.rs b/examples/launcher/window/imp.rs deleted file mode 100644 index 9aaa76f..0000000 --- a/examples/launcher/window/imp.rs +++ /dev/null @@ -1,33 +0,0 @@ -use gtk4::subclass::prelude::*; -use gtk4::{gio, glib}; -use gtk4::{Entry, ListView}; -use once_cell::sync::OnceCell; - -// Object holding the state -#[derive(Default)] -pub struct Window { - pub entry: OnceCell, - pub list_view: OnceCell, - pub model: OnceCell, -} - -// The central trait for subclassing a GObject -#[glib::object_subclass] -impl ObjectSubclass for Window { - // `NAME` needs to match `class` attribute of template - const NAME: &'static str = "LauncherWindow"; - type Type = super::Window; - type ParentType = gtk4::ApplicationWindow; -} - -// Trait shared by all GObjects -impl ObjectImpl for Window {} - -// Trait shared by all widgets -impl WidgetImpl for Window {} - -// Trait shared by all windows -impl WindowImpl for Window {} - -// Trait shared by all application -impl ApplicationWindowImpl for Window {} diff --git a/examples/launcher/window/mod.rs b/examples/launcher/window/mod.rs deleted file mode 100644 index 79de783..0000000 --- a/examples/launcher/window/mod.rs +++ /dev/null @@ -1,271 +0,0 @@ -use cascade::cascade; -use gdk4_x11::X11Display; -use glib::Object; -use gtk4::prelude::*; -use gtk4::subclass::prelude::*; -use gtk4::Box; -use gtk4::Entry; -use gtk4::ListView; -use gtk4::Orientation; -use gtk4::{gio, glib}; -use gtk4::{Application, SignalListItemFactory}; - -use libcosmic::x; - -use crate::search_result_row::SearchResultRow; -use crate::SearchResultObject; -use crate::TX; - -mod imp; - -glib::wrapper! { - pub struct Window(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; -} - -const NUM_LAUNCHER_ITEMS: u8 = 9; - -impl Window { - pub fn new(app: &Application) -> Self { - let self_: Self = Object::new(&[("application", app)]).expect("Failed to create `Window`."); - let imp = imp::Window::from_instance(&self_); - - cascade! { - &self_; - ..set_width_request(600); - ..set_title(Some("Cosmic Launcher")); - ..set_decorated(false); - ..set_resizable(false); - ..add_css_class("root_window"); - }; - - let container = cascade! { - Box::new(Orientation::Vertical, 0); - ..add_css_class("container"); - }; - self_.set_child(Some(&container)); - - let entry = cascade! { - Entry::new(); - ..set_margin_bottom(12); - }; - container.append(&entry); - - let list_view = cascade! { - ListView::default(); - ..set_orientation(Orientation::Vertical); - ..set_single_click_activate(true); - }; - container.append(&list_view); - - imp.entry.set(entry).unwrap(); - imp.list_view.set(list_view).unwrap(); - - // Setup - self_.setup_model(); - self_.setup_callbacks(); - self_.setup_factory(); - self_ - } - - pub fn model(&self) -> &gio::ListStore { - // Get state - let imp = imp::Window::from_instance(self); - imp.model.get().expect("Could not get model") - } - - fn setup_model(&self) { - // Get state and set model - let imp = imp::Window::from_instance(self); - let model = gio::ListStore::new(SearchResultObject::static_type()); - - let slice_model = gtk4::SliceListModel::new(Some(&model), 0, NUM_LAUNCHER_ITEMS.into()); - let selection_model = gtk4::SingleSelection::builder() - .model(&slice_model) - .autoselect(false) - .can_unselect(true) - .selected(gtk4::INVALID_LIST_POSITION) - .build(); - - imp.model.set(model).expect("Could not set model"); - // Wrap model with selection and pass it to the list view - imp.list_view - .get() - .unwrap() - .set_model(Some(&selection_model)); - } - - fn setup_callbacks(&self) { - // Get state - let imp = imp::Window::from_instance(self); - let window = self.clone().upcast::(); - let list_view = &imp.list_view; - let entry = &imp.entry.get().unwrap(); - let lv = list_view.get().unwrap(); - for i in 1..10 { - let action_launchi = gio::SimpleAction::new(&format!("launch{}", i), None); - self.add_action(&action_launchi); - action_launchi.connect_activate(glib::clone!(@weak lv => move |_action, _parameter| { - let i = i - 1; - println!("activating... {}", i); - let model = lv.model().unwrap(); - let obj = match model.item(i) { - Some(obj) => obj.downcast::().unwrap(), - None => { - dbg!(model.item(i)); - return; - }, - }; - if let Some(search_result) = obj.data() { - println!("activating... {}", i + 1); - glib::MainContext::default().spawn_local(async move { - if let Some(tx) = TX.get() { - let _ = tx.send(crate::Event::Activate(search_result.id)).await; - } - }); - } - })); - } - - lv.connect_activate(glib::clone!(@weak window => move |list_view, i| { - dbg!(i); - let model = list_view.model() - .expect("List view missing selection model") - .downcast::() - .expect("could not downcast listview model to no selection model"); - - if i >= model.n_items() { - dbg!("index out of range"); - return; - } - let obj = match model.item(i) { - Some(obj) => obj.downcast::().unwrap(), - None => { - dbg!(model.item(i)); - return; - }, - }; - if let Some(search_result) = obj.data() { - println!("activating... {}", i + 1); - glib::MainContext::default().spawn_local(async move { - if let Some(tx) = TX.get() { - let _ = tx.send(crate::Event::Activate(search_result.id)).await; - } - }); - } - })); - - entry.connect_changed(glib::clone!(@weak lv => move |search: >k4::Entry| { - let search = search.text().to_string(); - - glib::MainContext::default().spawn_local(async move { - if let Some(tx) = TX.get() { - let _ = tx.send(crate::Event::Search(search)).await; - } - }); - })); - - entry.connect_realize(glib::clone!(@weak lv => move |search: >k4::Entry| { - let search = search.text().to_string(); - - glib::MainContext::default().spawn_local(async move { - if let Some(tx) = TX.get() { - let _ = tx.send(crate::Event::Search(search)).await; - } - }); - })); - - window.connect_realize(move |window| { - if let Some((display, surface)) = x::get_window_x11(window) { - // ignore all x11 errors... - let xdisplay = display.clone().downcast::().expect("Failed to downgrade X11 Display."); - xdisplay.error_trap_push(); - unsafe { - x::change_property( - &display, - &surface, - "_NET_WM_WINDOW_TYPE", - x::PropMode::Replace, - &[x::Atom::new(&display, "_NET_WM_WINDOW_TYPE_DIALOG").unwrap()], - ); - } - let resize = glib::clone!(@weak window => move || { - let height = window.height(); - let width = window.width(); - - if let Some((display, _surface)) = x::get_window_x11(&window) { - let geom = display - .primary_monitor().geometry(); - let monitor_x = geom.x(); - let monitor_y = geom.y(); - let monitor_width = geom.width(); - let monitor_height = geom.height(); - // dbg!(monitor_width); - // dbg!(monitor_height); - // dbg!(width); - // dbg!(height); - unsafe { x::set_position(&display, &surface, - (monitor_x + monitor_width / 2 - width / 2).clamp(0, monitor_x + monitor_width - 1), - (monitor_y + monitor_height / 2 - height / 2).clamp(0, monitor_y + monitor_height - 1))}; - } - }); - let s = window.surface(); - let resize_height = resize.clone(); - s.connect_height_notify(move |_s| { - glib::source::idle_add_local_once(resize_height.clone()); - }); - let resize_width = resize.clone(); - s.connect_width_notify(move |_s| { - glib::source::idle_add_local_once(resize_width.clone()); - }); - s.connect_scale_factor_notify(move |_s| { - glib::source::idle_add_local_once(resize.clone()); - }); - } else { - println!("failed to get X11 window"); - } - }); - - let action_quit = gio::SimpleAction::new("quit", None); - action_quit.connect_activate(glib::clone!(@weak window => move |_, _| { - window.close(); - })); - self.add_action(&action_quit); - - window.connect_is_active_notify(|win| { - if !win.is_active() { - win.close(); - } - }); - } - - fn setup_factory(&self) { - let factory = SignalListItemFactory::new(); - factory.connect_setup(move |_, list_item| { - let row = SearchResultRow::new(); - list_item.set_child(Some(&row)) - }); - factory.connect_bind(move |_, list_item| { - let application_object = list_item - .item() - .expect("The item has to exist.") - .downcast::() - .expect("The item has to be an `SearchResultObject`"); - let row = list_item - .child() - .expect("The list item child needs to exist.") - .downcast::() - .expect("The list item type needs to be `SearchResultRow`"); - if list_item.position() < 9 { - row.set_shortcut(list_item.position() + 1); - } - - row.set_search_result(application_object); - }); - // Set the factory of the list view - let imp = imp::Window::from_instance(self); - imp.list_view.get().unwrap().set_factory(Some(&factory)); - } -}