components: Add widget for in-app-notification

This commit is contained in:
Julian Sparber 2021-05-21 18:07:05 +02:00
parent 88b5c0c984
commit b439abe55c
7 changed files with 232 additions and 1 deletions

View file

@ -20,6 +20,7 @@
<file compressed="true" preprocess="xml-stripblanks" alias="context-menu-bin.ui">ui/context-menu-bin.ui</file>
<file compressed="true" preprocess="xml-stripblanks" alias="pill.ui">ui/pill.ui</file>
<file compressed="true" preprocess="xml-stripblanks" alias="spinner-button.ui">ui/spinner-button.ui</file>
<file compressed="true" preprocess="xml-stripblanks" alias="in-app-notification.ui">ui/in-app-notification.ui</file>
<file compressed="true">style.css</file>
<file preprocess="xml-stripblanks">icons/scalable/actions/send-symbolic.svg</file>
<file preprocess="xml-stripblanks">icons/scalable/status/welcome.svg</file>

View file

@ -15,7 +15,7 @@
}
.app-notification .pill {
background-color: alpha(@theme_fg_color, 0.35);
background-color: alpha(@theme_bg_color, 0.2);
}
/* Login */
@ -153,3 +153,8 @@ headerbar.flat {
.invite-room-name {
font-size: 24px;
}
.app-notification {
border-radius: 9999px;
padding-left: 24px;
}

View file

@ -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>

View file

@ -16,6 +16,7 @@ data/resources/ui/content-state-row.ui
data/resources/ui/content.ui
data/resources/ui/context-menu-bin.ui
data/resources/ui/login.ui
data/resources/ui/in-app-notification.ui
data/resources/ui/session.ui
data/resources/ui/shortcuts.ui
data/resources/ui/sidebar-category-row.ui
@ -30,6 +31,7 @@ data/resources/ui/window.ui
src/application.rs
src/components/context_menu_bin.rs
src/components/label_with_widgets.rs
src/components/in_app_notification.rs
src/components/mod.rs
src/components/spinner_button.rs
src/components/pill.rs

View file

@ -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(&current_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);
}
}
}

View file

@ -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;

View file

@ -24,6 +24,7 @@ sources = files(
'components/label_with_widgets.rs',
'components/mod.rs',
'components/pill.rs',
'components/in_app_notification.rs',
'components/spinner_button.rs',
'config.rs',
'error.rs',