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',