initial support for app library

This commit is contained in:
Ashley Wulber 2022-01-19 16:00:02 -05:00
parent 6ed9fdc9cd
commit 9273ea91e8
8 changed files with 154 additions and 171 deletions

View file

@ -7,7 +7,7 @@ edition = "2021"
cascade = "1.0.0" cascade = "1.0.0"
derivative = "2.2.0" derivative = "2.2.0"
gdk4 = "0.4.4" gdk4 = "0.4.4"
gdk4-wayland = { version = "0.4.2", optional = true } gdk4-wayland = { version = "0.4.2", features = [ "wayland_crate" ], optional = true }
gdk4-x11 = { version = "0.4.2", features = [ "xlib" ] } gdk4-x11 = { version = "0.4.2", features = [ "xlib" ] }
gio = "0.15.2" gio = "0.15.2"
gobject-sys = "0.15.1" gobject-sys = "0.15.1"

View file

@ -131,25 +131,19 @@ impl AppGrid {
let app_grid_view = &imp.app_grid_view.get().unwrap(); let app_grid_view = &imp.app_grid_view.get().unwrap();
app_grid_view.connect_activate(move |list_view, i| { app_grid_view.connect_activate(move |list_view, i| {
let window = list_view
.root()
.unwrap()
.downcast::<gtk4::Window>()
.unwrap();
// on activation change the group filter model to use the app names, and category // on activation change the group filter model to use the app names, and category
println!("selected app {}", i); println!("selected app {}", i);
// Launch the application when an item of the list is activated // Launch the application when an item of the list is activated
let model = list_view.model().unwrap(); let model = list_view.model().unwrap();
if let Some(item) = model.item(i) { if let Some(item) = model.item(i) {
let app_info = item.downcast::<gio::DesktopAppInfo>().unwrap(); let app_info = item.downcast::<gio::DesktopAppInfo>().unwrap();
let context = window.display().app_launch_context(); let context = list_view.display().app_launch_context();
if let Err(err) = app_info.launch(&[], Some(&context)) { if let Err(err) = app_info.launch(&[], Some(&context)) {
gtk4::MessageDialog::builder() gtk4::MessageDialog::builder()
.text(&format!("Failed to start {}", app_info.name())) .text(&format!("Failed to start {}", app_info.name()))
.secondary_text(&err.to_string()) .secondary_text(&err.to_string())
.message_type(gtk4::MessageType::Error) .message_type(gtk4::MessageType::Error)
.modal(true) .modal(true)
.transient_for(&window)
.build() .build()
.show(); .show();
} }

View file

@ -150,7 +150,7 @@ impl GroupGrid {
let self_clone = self.clone(); let self_clone = self.clone();
group_grid_view.connect_activate(move |group_grid_view, i| { group_grid_view.connect_activate(move |group_grid_view, i| {
// on activation change the group filter model to use the app names, and category // on activation change the group filter model to use the app names, and category
let window = group_grid_view.root().unwrap().downcast::<Window>().unwrap(); // let window = group_grid_view.root().unwrap().downcast::<Window>().unwrap();
println!("grid view activated. {}", i); println!("grid view activated. {}", i);
let group_model = group_grid_view.model().unwrap().downcast::<gtk4::SingleSelection>().unwrap() let group_model = group_grid_view.model().unwrap().downcast::<gtk4::SingleSelection>().unwrap()
.model() .model()
@ -180,15 +180,15 @@ impl GroupGrid {
.resizable(false) .resizable(false)
.use_header_bar(true.into()) .use_header_bar(true.into())
.destroy_with_parent(true) .destroy_with_parent(true)
.transient_for(&window) // .transient_for(&window)
.title("New App Group") .title("New App Group")
.child(&vbox) .child(&vbox)
.build(); .build();
let app = window // let app = window
.application() // .application()
.expect("could not get application from window"); // .expect("could not get application from window");
dialog.set_application(Some(&app)); // dialog.set_application(Some(&app));
dialog.add_buttons(&[ dialog.add_buttons(&[
("Apply", gtk4::ResponseType::Apply), ("Apply", gtk4::ResponseType::Apply),
("Cancel", gtk4::ResponseType::Cancel), ("Cancel", gtk4::ResponseType::Cancel),
@ -215,20 +215,20 @@ impl GroupGrid {
dialog.emit_close(); dialog.emit_close();
}), }),
); );
dialog.connect_is_active_notify(move |win| { // dialog.connect_is_active_notify(move |win| {
let app = win // let app = win
.application() // .application()
.expect("could not get application from window"); // .expect("could not get application from window");
let active_window = app // let active_window = app
.active_window() // .active_window()
.expect("no active window available, closing app library."); // .expect("no active window available, closing app library.");
dbg!(&active_window); // dbg!(&active_window);
if win == &active_window && !win.is_active() { // if win == &active_window && !win.is_active() {
println!("no focus"); // println!("no focus");
// close top level window // // close top level window
window.close(); // window.close();
} // }
}); // });
dialog.show(); dialog.show();
return; return;
}; };
@ -257,7 +257,7 @@ impl GroupGrid {
} }
}); });
self_clone self_clone
.emit_by_name::<CustomFilter>("group-changed", &[&new_filter]); .emit_by_name::<()>("group-changed", &[&new_filter]);
}); });
} }

View file

@ -11,6 +11,7 @@ mod grid_item;
mod group_grid; mod group_grid;
mod utils; mod utils;
mod window; mod window;
mod window_inner;
fn main() { fn main() {
let app = gtk4::Application::new(Some("com.cosmic.app_library"), Default::default()); let app = gtk4::Application::new(Some("com.cosmic.app_library"), Default::default());
@ -40,7 +41,9 @@ fn load_css() {
fn build_ui(app: &gtk4::Application) { fn build_ui(app: &gtk4::Application) {
// Create a new custom window and show it // Create a new custom window and show it
let window = AppLibraryWindow::new(app); let display = Display::default().unwrap();
window::create(app, display.monitors().item(0).unwrap().downcast().unwrap());
// let window = AppLibraryWindow::new(app);
window.show(); // window.show();
} }

View file

@ -1,18 +1,13 @@
use glib::signal::Inhibit; use crate::window_inner::AppLibraryWindowInner;
use gtk4::glib; use gtk4::glib;
use gtk4::subclass::prelude::*; use gtk4::subclass::prelude::*;
use gtk4::SearchEntry;
use once_cell::sync::OnceCell; use once_cell::sync::OnceCell;
use crate::app_grid::AppGrid;
use crate::group_grid::GroupGrid;
// Object holding the state // Object holding the state
#[derive(Default)] #[derive(Default)]
pub struct AppLibraryWindow { pub struct AppLibraryWindow {
pub entry: OnceCell<SearchEntry>, pub(super) inner: OnceCell<AppLibraryWindowInner>,
pub app_grid: OnceCell<AppGrid>,
pub group_grid: OnceCell<GroupGrid>,
} }
// The central trait for subclassing a GObject // The central trait for subclassing a GObject
@ -31,14 +26,7 @@ impl ObjectImpl for AppLibraryWindow {}
impl WidgetImpl for AppLibraryWindow {} impl WidgetImpl for AppLibraryWindow {}
// Trait shared by all windows // Trait shared by all windows
impl WindowImpl for AppLibraryWindow { impl WindowImpl for AppLibraryWindow {}
fn close_request(&self, window: &Self::Type) -> Inhibit {
let imp = AppLibraryWindow::from_instance(window);
imp.group_grid.get().unwrap().store_data();
// Pass close request on to the parent
self.parent_close_request(window)
}
}
// Trait shared by all application // Trait shared by all application
impl ApplicationWindowImpl for AppLibraryWindow {} impl ApplicationWindowImpl for AppLibraryWindow {}

View file

@ -1,19 +1,91 @@
use std::rc::Rc;
use crate::app_grid::AppGrid; use crate::app_grid::AppGrid;
use crate::group_grid::GroupGrid; use crate::group_grid::GroupGrid;
use crate::window_inner::AppLibraryWindowInner;
use cascade::cascade; use cascade::cascade;
use gdk4::subclass::prelude::ObjectSubclassExt;
use gdk4_x11::X11Display; use gdk4_x11::X11Display;
use glib::Object; use glib::Object;
use gtk4::prelude::*; use gtk4::prelude::*;
use gtk4::subclass::prelude::*;
use gtk4::Align; use gtk4::Align;
use gtk4::Application; use gtk4::Application;
use gtk4::ApplicationWindow;
use gtk4::Box; use gtk4::Box;
use gtk4::CustomFilter; use gtk4::CustomFilter;
use gtk4::Inhibit;
use gtk4::Orientation; use gtk4::Orientation;
use gtk4::SearchEntry; use gtk4::SearchEntry;
use gtk4::Separator; use gtk4::Separator;
use gtk4::{gio, glib}; use gtk4::{gdk, gio, glib};
use libcosmic::x; use libcosmic::x;
use once_cell::sync::OnceCell;
pub fn create(app: &Application, monitor: gdk::Monitor) {
//quit shortcut
app.set_accels_for_action("win.quit", &["<primary>W", "Escape"]);
#[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(window: &ApplicationWindow) {
let action_quit = gio::SimpleAction::new("quit", None);
action_quit.connect_activate(glib::clone!(@weak window => move |_, _| {
window.close();
}));
window.add_action(&action_quit);
window.connect_is_active_notify(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.");
dbg!(&active_window);
if win == &active_window && !win.is_active() {
win.close();
}
});
}
#[cfg(feature = "layer-shell")]
fn wayland_create(app: &Application, monitor: &gdk4_wayland::WaylandMonitor) {
use libcosmic::wayland::{Anchor, Layer, LayerShellWindow};
let window = cascade! {
LayerShellWindow::new(Some(monitor), Layer::Top, "");
..set_width_request(1200);
..set_height_request(800);
// ..set_title(Some("Cosmic App Library"));
// ..set_decorated(false);
..add_css_class("root_window");
..set_anchor(Anchor::empty());
..show();
};
let app_library = AppLibraryWindowInner::new();
window.set_child(Some(&app_library));
dbg!(&window);
window.show();
// window.connect_close_request(
// glib::clone!(@strong app_library => @default-return Inhibit(false), move |_| {
// app_library.group_grid().unwrap().store_data();
// Inhibit(false)
// }),
// );
// setup_shortcuts(window.clone().upcast::<gtk4::ApplicationWindow>());
// XXX
unsafe { window.set_data("cosmic-app-hold", app.hold()) };
}
mod imp; mod imp;
@ -26,12 +98,6 @@ glib::wrapper! {
impl AppLibraryWindow { impl AppLibraryWindow {
pub fn new(app: &Application) -> Self { pub fn new(app: &Application) -> Self {
//quit shortcut
app.set_accels_for_action("win.quit", &["<primary>W", "Escape"]);
//launch shortcuts
for i in 1..10 {
app.set_accels_for_action(&format!("win.launch{}", i), &[&format!("<primary>{}", i)]);
}
let self_: Self = let self_: Self =
Object::new(&[("application", app)]).expect("Failed to create `AppLibraryWindow`."); Object::new(&[("application", app)]).expect("Failed to create `AppLibraryWindow`.");
let imp = imp::AppLibraryWindow::from_instance(&self_); let imp = imp::AppLibraryWindow::from_instance(&self_);
@ -43,42 +109,46 @@ impl AppLibraryWindow {
..set_decorated(false); ..set_decorated(false);
..add_css_class("root_window"); ..add_css_class("root_window");
}; };
let app_library = AppLibraryWindowInner::new();
let app_library = cascade! {
Box::new(Orientation::Vertical, 0);
..add_css_class("app_library_container");
};
self_.set_child(Some(&app_library)); self_.set_child(Some(&app_library));
imp.inner.set(app_library).unwrap();
// let app_library = cascade! {
// Box::new(Orientation::Vertical, 0);
// ..add_css_class("app_library_container");
// };
// self_.set_child(Some(&app_library));
let entry = cascade! { // let entry = cascade! {
SearchEntry::new(); // SearchEntry::new();
..set_width_request(300); // ..set_width_request(300);
..set_halign(Align::Center); // ..set_halign(Align::Center);
..set_margin_top(12); // ..set_margin_top(12);
..set_margin_bottom(12); // ..set_margin_bottom(12);
..set_placeholder_text(Some(" Type to search")); // ..set_placeholder_text(Some(" Type to search"));
}; // };
app_library.append(&entry); // app_library.append(&entry);
let app_grid = AppGrid::new(); // let app_grid = AppGrid::new();
app_library.append(&app_grid); // app_library.append(&app_grid);
let separator = cascade! { // let separator = cascade! {
Separator::new(Orientation::Horizontal); // Separator::new(Orientation::Horizontal);
..set_hexpand(true); // ..set_hexpand(true);
..set_margin_bottom(12); // ..set_margin_bottom(12);
..set_margin_top(12); // ..set_margin_top(12);
}; // };
app_library.append(&separator); // app_library.append(&separator);
let group_grid = GroupGrid::new(); // let group_grid = GroupGrid::new();
app_library.append(&group_grid); // app_library.append(&group_grid);
imp.entry.set(entry).unwrap(); // imp.entry.set(entry).unwrap();
imp.app_grid.set(app_grid).unwrap(); // imp.app_grid.set(app_grid).unwrap();
imp.group_grid.set(group_grid).unwrap(); // imp.group_grid.set(group_grid).unwrap();
Self::setup_callbacks(&self_); Self::setup_callbacks(&self_);
setup_shortcuts(&self_.clone().upcast::<gtk4::ApplicationWindow>());
self_ self_
} }
@ -86,58 +156,6 @@ impl AppLibraryWindow {
// Get state // Get state
let imp = imp::AppLibraryWindow::from_instance(self); let imp = imp::AppLibraryWindow::from_instance(self);
let window = self.clone().upcast::<gtk4::Window>(); let window = self.clone().upcast::<gtk4::Window>();
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::<CustomFilter>().unwrap();
app_grid.set_group_filter(&new_filter);
None
}),
);
entry.connect_changed(
glib::clone!(@weak app_grid => move |search: &gtk4::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::<gio::DesktopAppInfo>()
.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::<gio::DesktopAppInfo>().unwrap();
let app_info2 = obj2.downcast_ref::<gio::DesktopAppInfo>().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);
}),
);
window.connect_realize(move |window| { window.connect_realize(move |window| {
if let Some((display, surface)) = x::get_window_x11(window) { if let Some((display, surface)) = x::get_window_x11(window) {
@ -192,23 +210,5 @@ impl AppLibraryWindow {
println!("failed to get X11 window"); 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(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.");
dbg!(&active_window);
if win == &active_window && !win.is_active() {
win.close();
}
});
} }
} }

View file

@ -77,17 +77,15 @@ impl AppLibraryWindowInner {
let entry = &imp.entry.get().unwrap(); let entry = &imp.entry.get().unwrap();
group_grid group_grid.connect_local(
.connect_local( "group-changed",
"group-changed", false,
false, glib::clone!(@weak app_grid => @default-return None, move |args| {
glib::clone!(@weak app_grid => @default-return None, move |args| { let new_filter = args[1].get::<CustomFilter>().unwrap();
let new_filter = args[1].get::<CustomFilter>().unwrap(); app_grid.set_group_filter(&new_filter);
app_grid.set_group_filter(&new_filter); None
None }),
}), );
)
.unwrap();
entry.connect_changed( entry.connect_changed(
glib::clone!(@weak app_grid => move |search: &gtk4::SearchEntry| { glib::clone!(@weak app_grid => move |search: &gtk4::SearchEntry| {

View file

@ -5,7 +5,7 @@ use gdk4_wayland::prelude::*;
use gtk4::{ use gtk4::{
cairo, gdk, cairo, gdk,
glib::{self, clone, subclass::prelude::*, translate::*}, glib::{self, clone, subclass::prelude::*, translate::*},
gsk::{self, traits::RendererExt}, gsk::{self, traits::GskRendererExt},
prelude::*, prelude::*,
subclass::prelude::*, subclass::prelude::*,
}; };
@ -497,7 +497,7 @@ pub struct GtkRootInterface {
unsafe extern "C" fn get_surface(native: *mut gtk4::ffi::GtkNative) -> *mut gdk::ffi::GdkSurface { unsafe extern "C" fn get_surface(native: *mut gtk4::ffi::GtkNative) -> *mut gdk::ffi::GdkSurface {
let instance = &*(native as *mut <LayerShellWindowInner as ObjectSubclass>::Instance); let instance = &*(native as *mut <LayerShellWindowInner as ObjectSubclass>::Instance);
let imp = instance.impl_(); let imp = instance.imp();
imp.surface.borrow().as_ref().map_or(ptr::null_mut(), |x| { imp.surface.borrow().as_ref().map_or(ptr::null_mut(), |x| {
x.upcast_ref::<gdk::Surface>().to_glib_none().0 x.upcast_ref::<gdk::Surface>().to_glib_none().0
}) })
@ -505,7 +505,7 @@ unsafe extern "C" fn get_surface(native: *mut gtk4::ffi::GtkNative) -> *mut gdk:
unsafe extern "C" fn get_renderer(native: *mut gtk4::ffi::GtkNative) -> *mut gsk::ffi::GskRenderer { unsafe extern "C" fn get_renderer(native: *mut gtk4::ffi::GtkNative) -> *mut gsk::ffi::GskRenderer {
let instance = &*(native as *mut <LayerShellWindowInner as ObjectSubclass>::Instance); let instance = &*(native as *mut <LayerShellWindowInner as ObjectSubclass>::Instance);
let imp = instance.impl_(); let imp = instance.imp();
imp.renderer imp.renderer
.borrow() .borrow()
.as_ref() .as_ref()
@ -530,7 +530,7 @@ unsafe extern "C" fn layout(native: *mut gtk4::ffi::GtkNative, width: c_int, hei
unsafe extern "C" fn get_display(root: *mut gtk4::ffi::GtkRoot) -> *mut gdk::ffi::GdkDisplay { unsafe extern "C" fn get_display(root: *mut gtk4::ffi::GtkRoot) -> *mut gdk::ffi::GdkDisplay {
let instance = &*(root as *mut <LayerShellWindowInner as ObjectSubclass>::Instance); let instance = &*(root as *mut <LayerShellWindowInner as ObjectSubclass>::Instance);
let imp = instance.impl_(); let imp = instance.imp();
imp.display.upcast_ref::<gdk::Display>().to_glib_none().0 imp.display.upcast_ref::<gdk::Display>().to_glib_none().0
} }
@ -538,13 +538,13 @@ unsafe extern "C" fn get_constraint_solver(
root: *mut gtk4::ffi::GtkRoot, root: *mut gtk4::ffi::GtkRoot,
) -> *mut GtkConstraintSolver { ) -> *mut GtkConstraintSolver {
let instance = &*(root as *mut <LayerShellWindowInner as ObjectSubclass>::Instance); let instance = &*(root as *mut <LayerShellWindowInner as ObjectSubclass>::Instance);
let imp = instance.impl_(); let imp = instance.imp();
imp.constraint_solver.to_glib_none().0 imp.constraint_solver.to_glib_none().0
} }
unsafe extern "C" fn get_focus(root: *mut gtk4::ffi::GtkRoot) -> *mut gtk4::ffi::GtkWidget { unsafe extern "C" fn get_focus(root: *mut gtk4::ffi::GtkRoot) -> *mut gtk4::ffi::GtkWidget {
let instance = &*(root as *mut <LayerShellWindowInner as ObjectSubclass>::Instance); let instance = &*(root as *mut <LayerShellWindowInner as ObjectSubclass>::Instance);
let imp = instance.impl_(); let imp = instance.imp();
imp.focus_widget imp.focus_widget
.borrow() .borrow()
.as_ref() .as_ref()
@ -554,7 +554,7 @@ unsafe extern "C" fn get_focus(root: *mut gtk4::ffi::GtkRoot) -> *mut gtk4::ffi:
unsafe extern "C" fn set_focus(root: *mut gtk4::ffi::GtkRoot, focus: *mut gtk4::ffi::GtkWidget) { unsafe extern "C" fn set_focus(root: *mut gtk4::ffi::GtkRoot, focus: *mut gtk4::ffi::GtkWidget) {
// TODO: `GtkWindow` does more here // TODO: `GtkWindow` does more here
let instance = &*(root as *mut <LayerShellWindowInner as ObjectSubclass>::Instance); let instance = &*(root as *mut <LayerShellWindowInner as ObjectSubclass>::Instance);
let imp = instance.impl_(); let imp = instance.imp();
*imp.focus_widget.borrow_mut() = if focus.is_null() { *imp.focus_widget.borrow_mut() = if focus.is_null() {
None None
} else { } else {