From a180118a8eb28ac8b9848e6296363af69f8bb48d Mon Sep 17 00:00:00 2001 From: Julian Sparber Date: Fri, 21 May 2021 19:50:37 +0200 Subject: [PATCH] components: Add button with loading spinner --- data/resources/resources.gresource.xml | 1 + data/resources/style.css | 5 + data/resources/ui/spinner-button.ui | 26 +++++ po/POTFILES.in | 2 + src/components/mod.rs | 2 + src/components/spinner_button.rs | 140 +++++++++++++++++++++++++ src/meson.build | 1 + 7 files changed, 177 insertions(+) create mode 100644 data/resources/ui/spinner-button.ui create mode 100644 src/components/spinner_button.rs diff --git a/data/resources/resources.gresource.xml b/data/resources/resources.gresource.xml index f83e2946..0d47f3e8 100644 --- a/data/resources/resources.gresource.xml +++ b/data/resources/resources.gresource.xml @@ -18,6 +18,7 @@ ui/window.ui ui/context-menu-bin.ui ui/user-pill.ui + ui/spinner-button.ui style.css icons/scalable/actions/send-symbolic.svg icons/scalable/status/welcome.svg diff --git a/data/resources/style.css b/data/resources/style.css index 5d46ab2c..3f8b404e 100644 --- a/data/resources/style.css +++ b/data/resources/style.css @@ -3,6 +3,11 @@ font-weight: bold; } +.pill-button { + border-radius: 9999px; + padding: 12px 40px; +} + .user-pill { border-radius: 9999px; background-color: alpha(@theme_text_color, 0.1); diff --git a/data/resources/ui/spinner-button.ui b/data/resources/ui/spinner-button.ui new file mode 100644 index 00000000..582af4bf --- /dev/null +++ b/data/resources/ui/spinner-button.ui @@ -0,0 +1,26 @@ + + + + + diff --git a/po/POTFILES.in b/po/POTFILES.in index 28385a3c..1e51db6c 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -21,6 +21,7 @@ data/resources/ui/sidebar-category-row.ui data/resources/ui/sidebar-item.ui data/resources/ui/sidebar-room-row.ui data/resources/ui/sidebar.ui +data/resources/ui/spinner-button.ui data/resources/ui/user-pill.ui data/resources/ui/window.ui @@ -28,6 +29,7 @@ data/resources/ui/window.ui src/application.rs src/components/context_menu_bin.rs src/components/mod.rs +src/components/spinner_button.rs src/components/user_pill.rs src/login.rs src/main.rs diff --git a/src/components/mod.rs b/src/components/mod.rs index 05615c7f..d5fdf081 100644 --- a/src/components/mod.rs +++ b/src/components/mod.rs @@ -1,5 +1,7 @@ mod context_menu_bin; +mod spinner_button; mod user_pill; pub use self::context_menu_bin::{ContextMenuBin, ContextMenuBinImpl}; +pub use self::spinner_button::SpinnerButton; pub use self::user_pill::UserPill; diff --git a/src/components/spinner_button.rs b/src/components/spinner_button.rs new file mode 100644 index 00000000..92dcab40 --- /dev/null +++ b/src/components/spinner_button.rs @@ -0,0 +1,140 @@ +use adw::subclass::prelude::*; +use gtk::prelude::*; +use gtk::subclass::prelude::*; +use gtk::{glib, CompositeTemplate}; + +mod imp { + use super::*; + use glib::object::ObjectClass; + use glib::subclass::InitializingObject; + + #[derive(Debug, Default, CompositeTemplate)] + #[template(resource = "/org/gnome/FractalNext/spinner-button.ui")] + pub struct SpinnerButton { + #[template_child] + pub stack: TemplateChild, + #[template_child] + pub label: TemplateChild, + #[template_child] + pub spinner: TemplateChild, + } + + #[glib::object_subclass] + impl ObjectSubclass for SpinnerButton { + const NAME: &'static str = "SpinnerButton"; + type Type = super::SpinnerButton; + type ParentType = gtk::Button; + + fn class_init(klass: &mut Self::Class) { + Self::bind_template(klass); + } + + fn instance_init(obj: &InitializingObject) { + obj.init_template(); + } + } + + impl ObjectImpl for SpinnerButton { + fn properties() -> &'static [glib::ParamSpec] { + use once_cell::sync::Lazy; + static PROPERTIES: Lazy> = Lazy::new(|| { + vec![ + glib::ParamSpec::new_override( + "label", + &ObjectClass::from_type(gtk::Button::static_type()) + .unwrap() + .find_property("label") + .unwrap(), + ), + glib::ParamSpec::new_boolean( + "loading", + "Loading", + "Wheter to display the loading spinner or the content", + false, + glib::ParamFlags::READWRITE | glib::ParamFlags::EXPLICIT_NOTIFY, + ), + ] + }); + + PROPERTIES.as_ref() + } + + fn set_property( + &self, + obj: &Self::Type, + _id: usize, + value: &glib::Value, + pspec: &glib::ParamSpec, + ) { + match pspec.name() { + "label" => obj.set_label(value.get().unwrap()), + "loading" => obj.set_loading(value.get().unwrap()), + _ => unimplemented!(), + } + } + + fn property(&self, obj: &Self::Type, _id: usize, pspec: &glib::ParamSpec) -> glib::Value { + match pspec.name() { + "label" => obj.label().to_value(), + "loading" => obj.loading().to_value(), + _ => unimplemented!(), + } + } + } + + impl WidgetImpl for SpinnerButton {} + + impl ButtonImpl for SpinnerButton {} +} + +glib::wrapper! { + pub struct SpinnerButton(ObjectSubclass) + @extends gtk::Widget, gtk::Button, @implements gtk::Accessible; +} + +/// A widget displaying a `User` +impl SpinnerButton { + pub fn new() -> Self { + glib::Object::new(&[]).expect("Failed to create SpinnerButton") + } + + pub fn set_label(&self, label: &str) { + let priv_ = imp::SpinnerButton::from_instance(self); + + if priv_.label.label().as_str() == label { + return; + } + + priv_.label.set_label(label); + + self.notify("label"); + } + + pub fn label(&self) -> glib::GString { + let priv_ = imp::SpinnerButton::from_instance(self); + priv_.label.label() + } + + pub fn set_loading(&self, loading: bool) { + let priv_ = imp::SpinnerButton::from_instance(self); + + if self.loading() == loading { + return; + } + + self.set_sensitive(!loading); + + if loading { + priv_.stack.set_visible_child(&*priv_.spinner); + } else { + priv_.stack.set_visible_child(&*priv_.label); + } + + self.notify("loading"); + } + + pub fn loading(&self) -> bool { + let priv_ = imp::SpinnerButton::from_instance(self); + priv_.stack.visible_child().as_ref() == Some(priv_.spinner.upcast_ref()) + } +} diff --git a/src/meson.build b/src/meson.build index d91b6343..5a53a121 100644 --- a/src/meson.build +++ b/src/meson.build @@ -23,6 +23,7 @@ sources = files( 'components/context_menu_bin.rs', 'components/mod.rs', 'components/user_pill.rs', + 'components/spinner_button.rs', 'config.rs', 'main.rs', 'window.rs',