7 changed files with 232 additions and 1 deletions
@ -0,0 +1,35 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?> |
||||
<interface> |
||||
<template class="InAppNotification" parent="AdwBin"> |
||||
<property name="valign">end</property> |
||||
<property name="halign">center</property> |
||||
<property name="margin-bottom">100</property> |
||||
<property name="margin-start">24</property> |
||||
<property name="margin-end">24</property> |
||||
<property name="child"> |
||||
<object class="GtkRevealer" id="revealer"> |
||||
<property name="transition-type">crossfade</property> |
||||
<property name="child"> |
||||
<object class="GtkBox" id="box_"> |
||||
<property name="valign">center</property> |
||||
<child> |
||||
<object class="GtkButton"> |
||||
<property name="valign">center</property> |
||||
<property name="icon-name">window-close-symbolic</property> |
||||
<property name="action-name">in-app-notification.close</property> |
||||
<style> |
||||
<class name="flat"/> |
||||
<class name="circular"/> |
||||
</style> |
||||
</object> |
||||
</child> |
||||
<style> |
||||
<class name="app-notification"/> |
||||
</style> |
||||
</object> |
||||
</property> |
||||
</object> |
||||
</property> |
||||
</template> |
||||
</interface> |
||||
|
||||
@ -0,0 +1,185 @@
|
||||
use crate::Error; |
||||
use adw::subclass::prelude::*; |
||||
use gtk::prelude::*; |
||||
use gtk::subclass::prelude::*; |
||||
use gtk::{gio, glib, glib::clone, CompositeTemplate}; |
||||
|
||||
mod imp { |
||||
use super::*; |
||||
use glib::{signal::SignalHandlerId, subclass::InitializingObject}; |
||||
use std::cell::{Cell, RefCell}; |
||||
|
||||
#[derive(Debug, Default, CompositeTemplate)] |
||||
#[template(resource = "/org/gnome/FractalNext/in-app-notification.ui")] |
||||
pub struct InAppNotification { |
||||
pub error_list: RefCell<Option<gio::ListStore>>, |
||||
pub handler: RefCell<Option<SignalHandlerId>>, |
||||
#[template_child] |
||||
pub revealer: TemplateChild<gtk::Revealer>, |
||||
#[template_child] |
||||
pub box_: TemplateChild<gtk::Box>, |
||||
pub current_widget: RefCell<Option<gtk::Widget>>, |
||||
pub shows_error: Cell<bool>, |
||||
} |
||||
|
||||
#[glib::object_subclass] |
||||
impl ObjectSubclass for InAppNotification { |
||||
const NAME: &'static str = "InAppNotification"; |
||||
type Type = super::InAppNotification; |
||||
type ParentType = adw::Bin; |
||||
|
||||
fn class_init(klass: &mut Self::Class) { |
||||
Self::bind_template(klass); |
||||
|
||||
klass.install_action("in-app-notification.close", None, move |widget, _, _| { |
||||
widget.dismiss() |
||||
}); |
||||
} |
||||
|
||||
fn instance_init(obj: &InitializingObject<Self>) { |
||||
obj.init_template(); |
||||
} |
||||
} |
||||
|
||||
impl ObjectImpl for InAppNotification { |
||||
fn properties() -> &'static [glib::ParamSpec] { |
||||
use once_cell::sync::Lazy; |
||||
static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| { |
||||
vec![glib::ParamSpec::new_object( |
||||
"error-list", |
||||
"Error List", |
||||
"The list of errors to display", |
||||
gio::ListStore::static_type(), |
||||
glib::ParamFlags::READWRITE, |
||||
)] |
||||
}); |
||||
|
||||
PROPERTIES.as_ref() |
||||
} |
||||
|
||||
fn set_property( |
||||
&self, |
||||
obj: &Self::Type, |
||||
_id: usize, |
||||
value: &glib::Value, |
||||
pspec: &glib::ParamSpec, |
||||
) { |
||||
match pspec.name() { |
||||
"error-list" => obj.set_error_list(value.get().unwrap()), |
||||
_ => unimplemented!(), |
||||
} |
||||
} |
||||
|
||||
fn property(&self, obj: &Self::Type, _id: usize, pspec: &glib::ParamSpec) -> glib::Value { |
||||
match pspec.name() { |
||||
"error-list" => obj.error_list().to_value(), |
||||
_ => unimplemented!(), |
||||
} |
||||
} |
||||
|
||||
fn constructed(&self, obj: &Self::Type) { |
||||
self.parent_constructed(obj); |
||||
self.revealer |
||||
.connect_child_revealed_notify(clone!(@weak obj => move |revealer| { |
||||
let priv_ = imp::InAppNotification::from_instance(&obj); |
||||
revealer.set_visible(priv_.shows_error.get()); |
||||
})); |
||||
} |
||||
|
||||
fn dispose(&self, _obj: &Self::Type) { |
||||
if let Some(id) = self.handler.take() { |
||||
self.error_list.borrow().as_ref().unwrap().disconnect(id); |
||||
} |
||||
} |
||||
} |
||||
|
||||
impl WidgetImpl for InAppNotification {} |
||||
|
||||
impl BinImpl for InAppNotification {} |
||||
} |
||||
|
||||
glib::wrapper! { |
||||
pub struct InAppNotification(ObjectSubclass<imp::InAppNotification>) |
||||
@extends gtk::Widget, adw::Bin, @implements gtk::Accessible; |
||||
} |
||||
|
||||
impl InAppNotification { |
||||
pub fn new(error_list: &gio::ListStore) -> Self { |
||||
glib::Object::new(&[("error-list", &error_list)]) |
||||
.expect("Failed to create InAppNotification") |
||||
} |
||||
|
||||
pub fn set_error_list(&self, error_list: Option<gio::ListStore>) { |
||||
let priv_ = imp::InAppNotification::from_instance(self); |
||||
if self.error_list() == error_list { |
||||
return; |
||||
} |
||||
|
||||
if let Some(id) = priv_.handler.take() { |
||||
priv_.error_list.borrow().as_ref().unwrap().disconnect(id); |
||||
} |
||||
|
||||
if let Some(ref error_list) = error_list { |
||||
let handler = error_list.connect_items_changed( |
||||
clone!(@weak self as obj => move |_, position, removed, added| { |
||||
let priv_ = imp::InAppNotification::from_instance(&obj); |
||||
// If the first error is removed we need to display the next error
|
||||
if position == 0 && removed > 0 { |
||||
obj.next(); |
||||
} |
||||
|
||||
if added > 0 && !priv_.shows_error.get() { |
||||
obj.next(); |
||||
} |
||||
|
||||
}), |
||||
); |
||||
priv_.handler.replace(Some(handler)); |
||||
} |
||||
priv_.error_list.replace(error_list); |
||||
|
||||
self.next(); |
||||
self.notify("error-list"); |
||||
} |
||||
|
||||
pub fn error_list(&self) -> Option<gio::ListStore> { |
||||
let priv_ = imp::InAppNotification::from_instance(self); |
||||
priv_.error_list.borrow().to_owned() |
||||
} |
||||
|
||||
/// Show the next message in the `error-list`
|
||||
fn next(&self) { |
||||
let priv_ = imp::InAppNotification::from_instance(self); |
||||
|
||||
let shows_error = if let Some(widget) = priv_ |
||||
.error_list |
||||
.borrow() |
||||
.as_ref() |
||||
.and_then(|error_list| error_list.item(0)) |
||||
.and_then(|obj| obj.downcast::<Error>().ok()) |
||||
.and_then(|error| error.widget()) |
||||
{ |
||||
if let Some(current_widget) = priv_.current_widget.take() { |
||||
priv_.box_.remove(¤t_widget); |
||||
} |
||||
priv_.box_.prepend(&widget); |
||||
priv_.current_widget.replace(Some(widget)); |
||||
true |
||||
} else { |
||||
false |
||||
}; |
||||
|
||||
priv_.shows_error.set(shows_error); |
||||
if shows_error { |
||||
priv_.revealer.show(); |
||||
} |
||||
priv_.revealer.set_reveal_child(shows_error); |
||||
} |
||||
|
||||
fn dismiss(&self) { |
||||
let priv_ = imp::InAppNotification::from_instance(self); |
||||
if let Some(error_list) = &*priv_.error_list.borrow() { |
||||
error_list.remove(0); |
||||
} |
||||
} |
||||
} |
||||
@ -1,9 +1,11 @@
|
||||
mod context_menu_bin; |
||||
mod in_app_notification; |
||||
mod label_with_widgets; |
||||
mod pill; |
||||
mod spinner_button; |
||||
|
||||
pub use self::context_menu_bin::{ContextMenuBin, ContextMenuBinImpl}; |
||||
pub use self::in_app_notification::InAppNotification; |
||||
pub use self::label_with_widgets::LabelWithWidgets; |
||||
pub use self::pill::Pill; |
||||
pub use self::spinner_button::SpinnerButton; |
||||
|
||||
Loading…
Reference in new issue