components: Add widget for in-app-notification
This commit is contained in:
parent
88b5c0c984
commit
b439abe55c
7 changed files with 232 additions and 1 deletions
|
@ -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>
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
35
data/resources/ui/in-app-notification.ui
Normal file
35
data/resources/ui/in-app-notification.ui
Normal 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>
|
||||
|
|
@ -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
|
||||
|
|
185
src/components/in_app_notification.rs
Normal file
185
src/components/in_app_notification.rs
Normal 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(¤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;
|
||||
|
|
|
@ -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',
|
||||
|
|
Loading…
Reference in a new issue