[fractal/fractal-next] components: Add button with loading spinner



commit a180118a8eb28ac8b9848e6296363af69f8bb48d
Author: Julian Sparber <julian sparber net>
Date:   Fri May 21 19:50:37 2021 +0200

    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(+)
---
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 @@
     <file compressed="true" preprocess="xml-stripblanks" alias="window.ui">ui/window.ui</file>
     <file compressed="true" preprocess="xml-stripblanks" 
alias="context-menu-bin.ui">ui/context-menu-bin.ui</file>
     <file compressed="true" preprocess="xml-stripblanks" alias="user-pill.ui">ui/user-pill.ui</file>
+    <file compressed="true" preprocess="xml-stripblanks" 
alias="spinner-button.ui">ui/spinner-button.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>
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 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <template class="SpinnerButton" parent="GtkButton">
+    <property name="child">
+      <object class="GtkStack" id="stack">
+        <child>
+          <object class="GtkLabel" id="label">
+            <property name="use_underline">True</property>
+            <property name="mnemonic_widget">SpinnerButton</property>
+            <style>
+              <class name="text-button"/>
+            </style>
+          </object>
+        </child>
+        <child>
+          <object class="GtkSpinner" id="spinner">
+            <property name="spinning">True</property>
+            <property name="valign">center</property>
+            <property name="halign">center</property>
+          </object>
+        </child>
+      </object>
+    </property>
+  </template>
+</interface>
+
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<gtk::Stack>,
+        #[template_child]
+        pub label: TemplateChild<gtk::Label>,
+        #[template_child]
+        pub spinner: TemplateChild<gtk::Spinner>,
+    }
+
+    #[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<Self>) {
+            obj.init_template();
+        }
+    }
+
+    impl ObjectImpl for SpinnerButton {
+        fn properties() -> &'static [glib::ParamSpec] {
+            use once_cell::sync::Lazy;
+            static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = 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<imp::SpinnerButton>)
+        @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',


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]