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

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 {}
}