From 32b47eea023feb76ab0d541d9544aeb727a01e6f Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Tue, 23 Nov 2021 18:09:35 -0500 Subject: [PATCH] feat: basic list store implementation --- Cargo.toml | 3 +- examples/launcher/application_object/imp.rs | 199 ++++++++++++++++++++ examples/launcher/application_object/mod.rs | 34 ++++ examples/launcher/main.rs | 143 +++++++++----- 4 files changed, 329 insertions(+), 50 deletions(-) create mode 100644 examples/launcher/application_object/imp.rs create mode 100644 examples/launcher/application_object/mod.rs diff --git a/Cargo.toml b/Cargo.toml index 0bcdd55d..aebdb0d4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,5 +11,6 @@ gio = "0.14.8" gtk4 = "0.3.1" x11 = { version = "2", features = ["xlib"] } # examples/launcher -pop-launcher = "1.0.3" +pop-launcher = "=1.0.3" serde_json = "1.0.70" +once_cell = "1.8.0" diff --git a/examples/launcher/application_object/imp.rs b/examples/launcher/application_object/imp.rs new file mode 100644 index 00000000..9a267181 --- /dev/null +++ b/examples/launcher/application_object/imp.rs @@ -0,0 +1,199 @@ +use glib::{ + types::Type, FromVariant, ParamFlags, ParamSpec, ParamSpecObject, ToVariant, Value, Variant, + VariantTy, +}; +use gtk4::glib; +use gtk4::prelude::*; +use gtk4::subclass::prelude::*; +use once_cell::sync::Lazy; + +use std::cell::RefCell; +use std::rc::Rc; + +use super::ApplicationData; + +// Object holding the state +#[derive(Default)] +pub struct ApplicationObject { + data: Rc>, +} + +// The central trait for subclassing a GObject +#[glib::object_subclass] +impl ObjectSubclass for ApplicationObject { + const NAME: &'static str = "LibCosmicLauncherApplicationObject"; + type Type = super::ApplicationObject; + type ParentType = glib::Object; +} + +// Trait shared by all GObjects +impl ObjectImpl for ApplicationObject { + fn properties() -> &'static [ParamSpec] { + static PROPERTIES: Lazy> = Lazy::new(|| { + vec![ + ParamSpec::new_string( + // Name + "name", + // Nickname + "name", + // Short description + "Name of application in launcher search result", + // Default value + Some(""), + // The property can be read and written to + ParamFlags::READWRITE, + ), + ParamSpec::new_string( + // Name + "description", + // Nickname + "description", + // Short description + "Description of application in launcher search result", + // Default value + Some(""), + // The property can be read and written to + ParamFlags::READWRITE, + ), + ParamSpec::new_variant( + // Name + "icon", + // Nickname + "icon", + // Short description + "Icon of application in launcher search result", + VariantTy::new("(is)").expect("Oops invalid string for VariantTy tuple."), + // Default value + None, + // The property can be read and written to + ParamFlags::READWRITE, + ), + ParamSpec::new_variant( + // Name + "categoryicon", + // Nickname + "categoryicon", + // Short description + "Category icon of application in launcher search result", + VariantTy::new("(is)").expect("Oops invalid string for VariantTy tuple."), + // Default value + None, + // The property can be read and written to + ParamFlags::READWRITE, + ), + ParamSpec::new_variant( + // Name + "window", + // Nickname + "window", + // Short description + "Window of application in launcher search result", + // type (tuple of two uint32) + VariantTy::new("(uu)").expect("Oops invalid string for VariantTy tuple."), + // Default value + None, + // 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() { + "name" => { + let name = value + .get() + .expect("The value needs to be of type `String`."); + self.data.borrow_mut().0.name = name; + } + "description" => { + let description = value + .get() + .expect("The description needs to be of type `String`"); + self.data.borrow_mut().0.description = description; + } + "icon" => { + let icon = >::from_variant( + &value + .get::() + .expect("The icon needs to be a Variant"), + ) + .expect("The icon variant needs to be an Option<(i32, String)>"); + if let Some(icon) = icon { + self.data.borrow_mut().0.icon = match icon { + (i_type, name) if i_type == pop_launcher::IconSource::Name as i32 => { + Some(pop_launcher::IconSource::Name(name.into())) + } + (i_type, name) if i_type == pop_launcher::IconSource::Mime as i32 => { + Some(pop_launcher::IconSource::Mime(name.into())) + } + _ => None, + }; + } else { + self.data.borrow_mut().0.icon = None; + } + } + "categoryicon" => { + let icon = >::from_variant( + &value + .get::() + .expect("The icon needs to be a Variant"), + ) + .expect("The icon variant needs to be an Option<(i32, String)>"); + if let Some(icon) = icon { + self.data.borrow_mut().0.icon = match icon { + (i_type, name) if i_type == pop_launcher::IconSource::Name as i32 => { + Some(pop_launcher::IconSource::Name(name.into())) + } + (i_type, name) if i_type == pop_launcher::IconSource::Mime as i32 => { + Some(pop_launcher::IconSource::Mime(name.into())) + } + _ => None, + }; + } else { + self.data.borrow_mut().0.icon = None; + } + } + "window" => { + unimplemented!() + } + _ => unimplemented!(), + } + } + + fn property(&self, _obj: &Self::Type, _id: usize, pspec: &ParamSpec) -> Value { + match pspec.name() { + "name" => self.data.borrow().0.name.to_value(), + "description" => self.data.borrow().0.name.to_value(), + "icon" => match &self.data.borrow().0.icon { + Some(pop_launcher::IconSource::Name(icon_name)) => { + (pop_launcher::IconSource::Name as i32, icon_name.to_string()) + .to_variant() + .to_value() + } + Some(pop_launcher::IconSource::Mime(icon_name)) => { + (pop_launcher::IconSource::Mime as i32, icon_name.to_string()) + .to_variant() + .to_value() + } + _ => None::.to_value(), + }, + "categoryicon" => match &self.data.borrow().0.category_icon { + Some(pop_launcher::IconSource::Name(icon_name)) => { + (pop_launcher::IconSource::Name as i32, icon_name.to_string()) + .to_variant() + .to_value() + } + Some(pop_launcher::IconSource::Mime(icon_name)) => { + (pop_launcher::IconSource::Mime as i32, icon_name.to_string()) + .to_variant() + .to_value() + } + _ => None::.to_value(), + }, + _ => unimplemented!(), + } + } +} diff --git a/examples/launcher/application_object/mod.rs b/examples/launcher/application_object/mod.rs new file mode 100644 index 00000000..98d9d03c --- /dev/null +++ b/examples/launcher/application_object/mod.rs @@ -0,0 +1,34 @@ +mod imp; + +use gdk4::glib::Object; +use gtk4::glib; +use std::cell::RefCell; +use std::rc::Rc; + +glib::wrapper! { + pub struct ApplicationObject(ObjectSubclass); +} + +impl ApplicationObject { + pub fn new(application_search_result: &pop_launcher::SearchResult) -> Self { + Object::new(&[("name", &application_search_result.name)]) + .expect("Failed to create `ApplicationObject`.") + } +} + +// Object holding the state +pub struct ApplicationData(pop_launcher::SearchResult); + +impl Default for ApplicationData { + fn default() -> Self { + let default_application = pop_launcher::SearchResult { + id: 0, + name: String::default(), + description: String::default(), + icon: None, + category_icon: None, + window: None, + }; + Self(default_application) + } +} diff --git a/examples/launcher/main.rs b/examples/launcher/main.rs index f46d4a7d..e06c8755 100644 --- a/examples/launcher/main.rs +++ b/examples/launcher/main.rs @@ -1,12 +1,16 @@ +mod application_object; use gtk4 as gtk; +use gtk::gio; use gtk::prelude::*; -use libcosmic::x; -use std::{ - cell::RefCell, - rc::Rc, +use gtk::{ + glib, Application, ApplicationWindow, Label, ListView, PolicyType, ScrolledWindow, + SignalListItemFactory, SingleSelection, }; +use libcosmic::x; +use std::{cell::RefCell, rc::Rc}; +use self::application_object::ApplicationObject; use self::ipc::LauncherIpc; mod ipc; @@ -14,12 +18,10 @@ fn icon_source(icon: >k::Image, source: &Option) { match source { Some(pop_launcher::IconSource::Name(name)) => { icon.set_from_icon_name(Some(name)); - }, + } Some(pop_launcher::IconSource::Mime(content_type)) => { - icon.set_from_gicon( - &gio::content_type_get_icon(content_type) - ); - }, + icon.set_from_gicon(&gio::content_type_get_icon(content_type)); + } _ => { icon.set_from_icon_name(None); } @@ -28,7 +30,7 @@ fn icon_source(icon: >k::Image, source: &Option) { fn main() { let launcher = Rc::new(RefCell::new( - LauncherIpc::new().expect("failed to connect to launcher service") + LauncherIpc::new().expect("failed to connect to launcher service"), )); let app = gtk::Application::builder() @@ -56,63 +58,106 @@ fn main() { vbox.append(&search); let listbox = gtk::ListBox::new(); - vbox.append(&listbox); + //vbox.append(&listbox); + + let model = gio::ListStore::new(ApplicationObject::static_type()); + let factory = SignalListItemFactory::new(); + factory.connect_setup(move |_, list_item| { + let label = Label::new(None); + list_item.set_child(Some(&label)) + }); + 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 `ApplicationObject`"); + let name = application_object + .property("name") + .expect("Property name of the wrong type or does not exist!") + .get::() + .expect("Property name needs to be a String."); + let label = list_item + .child() + .expect("The child has to exist.") + .downcast::