use gtk4::glib; use gtk4::prelude::*; use gtk4::subclass::prelude::*; use std::cell::RefCell; #[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)] pub struct Article { pub id: String, pub title: String, pub feed_title: String, pub author: String, pub link: String, pub published: i64, pub content: String, pub excerpt: String, pub unread: bool, } // ── GObject wrapper ────────────────────────────────────────────────────────── glib::wrapper! { pub struct ArticleObject(ObjectSubclass); } impl ArticleObject { pub fn new(article: Article) -> Self { let obj: Self = glib::Object::new(); *obj.imp().article.borrow_mut() = article; obj } pub fn article(&self) -> std::cell::Ref<'_, Article> { self.imp().article.borrow() } pub fn set_unread(&self, unread: bool) { self.imp().article.borrow_mut().unread = unread; self.notify("unread"); } } mod imp { use super::*; #[derive(Default)] pub struct ArticleObject { pub article: RefCell
, } #[glib::object_subclass] impl ObjectSubclass for ArticleObject { const NAME: &'static str = "ArticleObject"; type Type = super::ArticleObject; } impl ObjectImpl for ArticleObject { fn properties() -> &'static [glib::ParamSpec] { use std::sync::OnceLock; static PROPS: OnceLock> = OnceLock::new(); PROPS.get_or_init(|| { vec![ glib::ParamSpecBoolean::builder("unread") .read_only() .build(), ] }) } fn property(&self, _id: usize, pspec: &glib::ParamSpec) -> glib::Value { match pspec.name() { "unread" => self.article.borrow().unread.to_value(), _ => unimplemented!(), } } } }