diff --git a/examples/dock/dock_item/imp.rs b/examples/dock/dock_item/imp.rs index ab34712..2a7ad21 100644 --- a/examples/dock/dock_item/imp.rs +++ b/examples/dock/dock_item/imp.rs @@ -1,5 +1,9 @@ +use glib::subclass::Signal; use gtk4::glib; +use gtk4::prelude::*; use gtk4::subclass::prelude::*; +use gtk4::PopoverMenu; +use once_cell::sync::Lazy; use std::cell::RefCell; use std::rc::Rc; @@ -7,6 +11,8 @@ use std::rc::Rc; pub struct DockItem { pub image: Rc>, pub dots: Rc>, + pub item_box: Rc>, + pub popover_menu: Rc>>, } #[glib::object_subclass] @@ -16,7 +22,22 @@ impl ObjectSubclass for DockItem { type ParentType = gtk4::Button; } -impl ObjectImpl for DockItem {} +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 {} diff --git a/examples/dock/dock_item/mod.rs b/examples/dock/dock_item/mod.rs index c9f74a2..86961c7 100644 --- a/examples/dock/dock_item/mod.rs +++ b/examples/dock/dock_item/mod.rs @@ -1,6 +1,8 @@ use cascade::cascade; use gio::DesktopAppInfo; use gio::Icon; +use gio::Menu; +use gio::MenuItem; use gtk4::glib; use gtk4::prelude::*; use gtk4::subclass::prelude::*; @@ -9,6 +11,7 @@ use gtk4::Box; use gtk4::Image; use gtk4::Label; use gtk4::Orientation; +use gtk4::PopoverMenu; use crate::dock_object::DockObject; use crate::utils::BoxedWindowList; @@ -47,11 +50,24 @@ impl DockItem { }; item_box.append(&image); item_box.append(&dots); + let popover_menu = cascade! { + PopoverMenu::from_model_full(&Menu::new(), gtk4::PopoverMenuFlags::NESTED); + ..set_autohide(true); + }; + item_box.append(&popover_menu); + let self_clone = self_.clone(); + popover_menu.connect_closed(move |_| { + self_clone + .emit_by_name::<&str>("popover-closed", &[]) + .unwrap(); + }); let imp = imp::DockItem::from_instance(&self_); imp.image.replace(image); imp.dots.replace(dots); - self_.show(); + imp.item_box.replace(item_box); + imp.popover_menu.replace(Some(popover_menu)); + self_ } @@ -83,5 +99,85 @@ impl DockItem { } } } + if let Ok(popover_value) = dock_object.property("popover") { + if let Ok(popover) = popover_value.get::() { + 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); + if let Some(popover_menu) = imp.popover_menu.borrow().as_ref() { + let menu = if let Some(m) = popover_menu.menu_model() { + m.downcast::().unwrap() + } else { + Menu::new() + }; + menu.remove_all(); + + let appinfo = item + .property("appinfo") + .expect("property appinfo missing from DockObject") + .get::>(); + let window_list = item + .property("active") + .expect("property appinfo missing from DockObject") + .get::(); + + if let Ok(Some(_appinfo)) = appinfo { + let launch_menu = Menu::new(); + let launch_subsection = MenuItem::new_section(None, &launch_menu); + launch_menu.append(Some("New Window"), None); + menu.append_item(&launch_subsection); + } + + let favorites_menu = Menu::new(); + let favorites_subsection = MenuItem::new_section(None, &favorites_menu); + match item.property("saved").unwrap().get::().unwrap() { + true => favorites_menu.append(Some("Remove from Favorites"), None), + false => favorites_menu.append(Some("Add to Favorites"), None), + }; + menu.append_item(&favorites_subsection); + + if let Ok(window_list) = window_list { + let window_list = window_list.0; + if window_list.len() > 0 { + let all_windows_submenu_menu = Menu::new(); + for w in window_list { + all_windows_submenu_menu.append(Some(w.name.as_str()), None); + } + + let all_windows_menu = Menu::new(); + let all_windows_submenu = + MenuItem::new_submenu(Some("All Windows"), &all_windows_submenu_menu); + all_windows_menu.append_item(&all_windows_submenu); + let all_windows_subsection = MenuItem::new_section(None, &all_windows_menu); + + let quit_windows_menu = Menu::new(); + quit_windows_menu.append(Some("Quit All"), None); + let quit_windows_subsection = MenuItem::new_section(None, &quit_windows_menu); + + menu.prepend_item(&all_windows_subsection); + menu.append_item(&quit_windows_subsection); + } + } + popover_menu.popup(); + } + } + + pub fn clear_popover(&self) { + let imp = imp::DockItem::from_instance(self); + let popover_menu = imp.popover_menu.borrow(); + + if let Some(popover) = popover_menu.as_ref() { + popover.popdown(); + } } } diff --git a/examples/dock/dock_list/imp.rs b/examples/dock/dock_list/imp.rs index 8d45227..0bfc14e 100644 --- a/examples/dock/dock_list/imp.rs +++ b/examples/dock/dock_list/imp.rs @@ -1,4 +1,4 @@ -use std::cell::RefCell; +use std::cell::{Cell, RefCell}; use std::rc::Rc; use glib::SignalHandlerId; @@ -17,6 +17,7 @@ pub struct DockList { pub drag_source: OnceCell, pub drag_end_signal: Rc>>, pub drag_cancel_signal: Rc>>, + pub popover_menu_index: Rc>>, } #[glib::object_subclass] diff --git a/examples/dock/dock_list/mod.rs b/examples/dock/dock_list/mod.rs index 23ce411..7feae44 100644 --- a/examples/dock/dock_list/mod.rs +++ b/examples/dock/dock_list/mod.rs @@ -76,6 +76,12 @@ impl DockList { 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) { @@ -198,10 +204,14 @@ impl DockList { self.add_controller(&controller); let model = self.model(); - let list_view = imp.list_view.get().unwrap(); - controller.connect_released(glib::clone!(@weak model, @weak list_view => move |self_, _, x, _y| { + 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()); @@ -223,6 +233,23 @@ impl DockList { } }); }; + 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::() { + println!("removing popup..."); + old_dock_object.set_popover(false); + popover_menu_index.replace(None); + model.items_changed(old_index, 0, 0); + } + } + 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").expect("DockObject must have active property").get::().expect("Failed to convert value to WindowList"); @@ -246,6 +273,18 @@ impl DockList { } (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); } _ => println!("Failed to process click.") } @@ -446,14 +485,31 @@ impl DockList { } fn setup_factory(&self) { - let factory = SignalListItemFactory::new(); - factory.connect_setup(move |_, list_item| { - let dock_item = DockItem::new(); - list_item.set_child(Some(&dock_item)); - }); 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_bind(glib::clone!(@weak model => move |_, list_item| { + 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 + }) + .unwrap(); + list_item.set_child(Some(&dock_item)); + }), + ); + factory.connect_bind(move |_, list_item| { let application_object = list_item .item() .expect("The item has to exist.") @@ -464,9 +520,8 @@ impl DockList { .expect("The list item child needs to exist.") .downcast::() .expect("The list item type needs to be `DockItem`"); - dock_item.set_app_info(&application_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 index 79d6f07..e3e10f3 100644 --- a/examples/dock/dock_object/imp.rs +++ b/examples/dock/dock_object/imp.rs @@ -16,6 +16,7 @@ pub struct DockObject { appinfo: RefCell>, active: RefCell, saved: Cell, + pub(super) popover: Cell, } // The central trait for subclassing a GObject @@ -60,6 +61,13 @@ impl ObjectImpl for DockObject { false, ParamFlags::READWRITE, ), + ParamSpec::new_boolean( + "popover", + "popover", + "Indicates whether there is a popover menu displayed for this object", + false, + ParamFlags::READWRITE, + ), ] }); PROPERTIES.as_ref() @@ -81,6 +89,11 @@ impl ObjectImpl for DockObject { 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!(), } } @@ -90,6 +103,7 @@ impl ObjectImpl for DockObject { "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 index d6e00ea..52cd2e5 100644 --- a/examples/dock/dock_object/mod.rs +++ b/examples/dock/dock_object/mod.rs @@ -1,6 +1,7 @@ use std::path::Path; use gdk4::glib::Object; +use gdk4::subclass::prelude::ObjectSubclassExt; use gio::DesktopAppInfo; use gtk4::glib; use gtk4::prelude::AppInfoExt; @@ -70,4 +71,9 @@ impl DockObject { 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 new file mode 100644 index 0000000..e69de29 diff --git a/examples/dock/dock_popover/mod.rs b/examples/dock/dock_popover/mod.rs new file mode 100644 index 0000000..e69de29 diff --git a/examples/dock/window/.#mod.rs b/examples/dock/window/.#mod.rs deleted file mode 120000 index d8189d5..0000000 --- a/examples/dock/window/.#mod.rs +++ /dev/null @@ -1 +0,0 @@ -ashleywulber@pop-os.394616:1640815731 \ No newline at end of file