use gtk4::prelude::*; use libadwaita::prelude::*; use crate::window::FeedTheMonkeyWindow; const APP_ID: &str = "net.jeena.FeedTheMonkey"; glib::wrapper! { pub struct FeedTheMonkeyApp(ObjectSubclass) @extends libadwaita::Application, gtk4::Application, gio::Application, @implements gio::ActionGroup, gio::ActionMap; } impl FeedTheMonkeyApp { pub fn new() -> Self { glib::Object::builder() .property("application-id", APP_ID) .property("flags", gio::ApplicationFlags::empty()) .build() } pub fn run(&self) -> glib::ExitCode { ApplicationExtManual::run(self) } } mod imp { use super::*; use libadwaita::subclass::prelude::*; #[derive(Default)] pub struct FeedTheMonkeyApp; #[glib::object_subclass] impl ObjectSubclass for FeedTheMonkeyApp { const NAME: &'static str = "FeedTheMonkeyApp"; type Type = super::FeedTheMonkeyApp; type ParentType = libadwaita::Application; } impl ObjectImpl for FeedTheMonkeyApp {} impl ApplicationImpl for FeedTheMonkeyApp { fn activate(&self) { self.parent_activate(); let app = self.obj(); // Register GResource let resource_bytes = glib::Bytes::from_static(include_bytes!(env!("GRESOURCE_FILE"))); let resource = gio::Resource::from_data(&resource_bytes) .expect("failed to load GResource"); gio::resources_register(&resource); // Apply application-level CSS tweaks let css = gtk4::CssProvider::new(); css.load_from_string( "paned > :first-child .top-bar headerbar { background-color: @headerbar_bg_color; box-shadow: none; } paned > :first-child > toolbarview > .content { padding-top: 0; margin-top: 0; } .sidebar-content row:not(:selected) { background-color: alpha(@window_fg_color, 0.07); } .sidebar-content row:not(:selected):hover { background-color: alpha(@window_fg_color, 0.14); } .sidebar-content row:selected { background-color: alpha(@window_fg_color, 0.22); }" ); gtk4::style_context_add_provider_for_display( >k4::gdk::Display::default().unwrap(), &css, gtk4::STYLE_PROVIDER_PRIORITY_APPLICATION, ); let window = FeedTheMonkeyWindow::new(app.upcast_ref()); // Shortcuts overlay let builder = gtk4::Builder::from_resource( "/net/jeena/FeedTheMonkey/ui/shortcuts.ui", ); let overlay: gtk4::ShortcutsWindow = builder.object("help_overlay").unwrap(); window.set_help_overlay(Some(&overlay)); setup_shortcuts(&window); // About action on app let app_weak = app.downgrade(); let about_action = gio::SimpleAction::new("about", None); about_action.connect_activate(move |_, _| { if let Some(app) = app_weak.upgrade() { let win = app.active_window(); let dialog = libadwaita::AboutDialog::builder() .application_name("FeedTheMonkey") .application_icon("feedthemonkey") .version("3.0.0") .copyright("© Jeena Paradies") .license_type(gtk4::License::Gpl30) .website("https://git.jeena.net/jeena/FeedTheMonkey") .developer_name("Jeena Paradies") .build(); dialog.present(win.as_ref().map(|w| w.upcast_ref::())); } }); app.add_action(&about_action); // Quit action let app_weak = app.downgrade(); let quit_action = gio::SimpleAction::new("quit", None); quit_action.connect_activate(move |_, _| { if let Some(app) = app_weak.upgrade() { app.quit(); } }); app.add_action(&quit_action); window.present(); } } impl GtkApplicationImpl for FeedTheMonkeyApp {} impl AdwApplicationImpl for FeedTheMonkeyApp {} } fn setup_shortcuts(window: &FeedTheMonkeyWindow) { use gtk4::gdk::{Key, ModifierType}; let controller = gtk4::ShortcutController::new(); controller.set_scope(gtk4::ShortcutScope::Global); let add = |controller: >k4::ShortcutController, key: Key, mods: ModifierType, action_name: &str| { let trigger = gtk4::KeyvalTrigger::new(key, mods); let action = gtk4::NamedAction::new(action_name); let shortcut = gtk4::Shortcut::new(Some(trigger), Some(action)); controller.add_shortcut(shortcut); }; // j/k/Left/Right are handled by a capture-phase key controller in window.rs // so they work regardless of which widget has focus. add(&controller, Key::r, ModifierType::empty(), "win.reload"); add(&controller, Key::u, ModifierType::empty(), "win.mark-unread"); add(&controller, Key::Return, ModifierType::empty(), "win.open-in-browser"); add(&controller, Key::n, ModifierType::empty(), "win.open-in-browser"); add(&controller, Key::plus, ModifierType::CONTROL_MASK, "win.zoom-in"); add(&controller, Key::equal, ModifierType::CONTROL_MASK, "win.zoom-in"); add(&controller, Key::minus, ModifierType::CONTROL_MASK, "win.zoom-out"); add(&controller, Key::_0, ModifierType::CONTROL_MASK, "win.zoom-reset"); add(&controller, Key::F9, ModifierType::empty(), "win.toggle-sidebar"); add(&controller, Key::F11, ModifierType::empty(), "win.toggle-fullscreen"); add(&controller, Key::w, ModifierType::CONTROL_MASK, "window.close"); add(&controller, Key::q, ModifierType::CONTROL_MASK, "app.quit"); add(&controller, Key::F1, ModifierType::empty(), "win.show-help-overlay"); window.add_controller(controller); }