scaffold: Epic 1 — project scaffold

Add the full Rust + GTK4 + libadwaita project skeleton:
- Cargo.toml with all dependencies (gtk4 0.11, libadwaita 0.9,
  webkit6 0.6, reqwest, serde, tokio, libsecret)
- build.rs that compiles Blueprint .blp files and bundles a GResource
- data/ui/window.blp — AdwApplicationWindow with AdwNavigationSplitView,
  sidebar with refresh button/spinner and primary menu,
  content page with article menu
- data/resources.gresource.xml bundling UI, HTML, and CSS
- data/net.jeena.FeedTheMonkey.gschema.xml with all GSettings keys
- html/content.html and html/content.css (minimal placeholders)
- src/main.rs, src/app.rs — AdwApplication with APP_ID net.jeena.FeedTheMonkey
- src/window.rs — AdwApplicationWindow GObject subclass loading the
  Blueprint template and persisting window size in GSettings
- COPYING (GPL-3.0) restored from master

The app compiles and the binary is ready to open a blank window.
This commit is contained in:
Jeena 2026-03-20 11:22:19 +00:00
parent 3196988c98
commit 3339bb5ec8
15 changed files with 3761 additions and 2 deletions

60
src/app.rs Normal file
View file

@ -0,0 +1,60 @@
use gtk4::prelude::*;
use crate::window::FeedTheMonkeyWindow;
const APP_ID: &str = "net.jeena.FeedTheMonkey";
glib::wrapper! {
pub struct FeedTheMonkeyApp(ObjectSubclass<imp::FeedTheMonkeyApp>)
@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);
let window = FeedTheMonkeyWindow::new(app.upcast_ref());
window.present();
}
}
impl GtkApplicationImpl for FeedTheMonkeyApp {}
impl AdwApplicationImpl for FeedTheMonkeyApp {}
}

7
src/main.rs Normal file
View file

@ -0,0 +1,7 @@
mod app;
mod window;
fn main() -> glib::ExitCode {
let app = app::FeedTheMonkeyApp::new();
app.run()
}

96
src/window.rs Normal file
View file

@ -0,0 +1,96 @@
use gtk4::prelude::*;
glib::wrapper! {
pub struct FeedTheMonkeyWindow(ObjectSubclass<imp::FeedTheMonkeyWindow>)
@extends libadwaita::ApplicationWindow, gtk4::ApplicationWindow, gtk4::Window, gtk4::Widget,
@implements gio::ActionGroup, gio::ActionMap, gtk4::Accessible, gtk4::Buildable,
gtk4::ConstraintTarget, gtk4::Native, gtk4::Root, gtk4::ShortcutManager;
}
impl FeedTheMonkeyWindow {
pub fn new(app: &libadwaita::Application) -> Self {
glib::Object::builder()
.property("application", app)
.build()
}
}
mod imp {
use super::*;
use gtk4::CompositeTemplate;
use libadwaita::subclass::prelude::*;
#[derive(CompositeTemplate, Default)]
#[template(resource = "/net/jeena/FeedTheMonkey/ui/window.ui")]
pub struct FeedTheMonkeyWindow {
#[template_child]
pub toast_overlay: TemplateChild<libadwaita::ToastOverlay>,
#[template_child]
pub split_view: TemplateChild<libadwaita::NavigationSplitView>,
#[template_child]
pub refresh_stack: TemplateChild<gtk4::Stack>,
#[template_child]
pub refresh_button: TemplateChild<gtk4::Button>,
#[template_child]
pub content_page: TemplateChild<libadwaita::NavigationPage>,
#[template_child]
pub article_menu_button: TemplateChild<gtk4::MenuButton>,
}
#[glib::object_subclass]
impl ObjectSubclass for FeedTheMonkeyWindow {
const NAME: &'static str = "FeedTheMonkeyWindow";
type Type = super::FeedTheMonkeyWindow;
type ParentType = libadwaita::ApplicationWindow;
fn class_init(klass: &mut Self::Class) {
klass.bind_template();
}
fn instance_init(obj: &glib::subclass::InitializingObject<Self>) {
obj.init_template();
}
}
impl ObjectImpl for FeedTheMonkeyWindow {
fn constructed(&self) {
self.parent_constructed();
self.restore_window_state();
}
}
impl FeedTheMonkeyWindow {
fn restore_window_state(&self) {
let settings = gio::Settings::new("net.jeena.FeedTheMonkey");
let window = self.obj();
let width = settings.int("window-width");
let height = settings.int("window-height");
let maximized = settings.boolean("window-maximized");
window.set_default_size(width, height);
if maximized {
window.maximize();
}
// Save state when the window closes
let settings_clone = settings.clone();
window.connect_close_request(move |win| {
if !win.is_maximized() {
let (w, h) = (win.width(), win.height());
settings_clone.set_int("window-width", w).ok();
settings_clone.set_int("window-height", h).ok();
}
settings_clone
.set_boolean("window-maximized", win.is_maximized())
.ok();
glib::Propagation::Proceed
});
}
}
impl WidgetImpl for FeedTheMonkeyWindow {}
impl WindowImpl for FeedTheMonkeyWindow {}
impl ApplicationWindowImpl for FeedTheMonkeyWindow {}
impl AdwApplicationWindowImpl for FeedTheMonkeyWindow {}
}