fractal/components/loading/
button.rs1use adw::{prelude::*, subclass::prelude::*};
2use gtk::{glib, pango};
3
4use super::LoadingBin;
5use crate::prelude::*;
6
7mod imp {
8 use std::marker::PhantomData;
9
10 use super::*;
11
12 #[derive(Debug, Default, glib::Properties)]
13 #[properties(wrapper_type = super::LoadingButton)]
14 pub struct LoadingButton {
15 loading_bin: LoadingBin,
16 #[property(get = Self::content_label, set = Self::set_content_label, explicit_notify)]
20 content_label: PhantomData<Option<glib::GString>>,
21 #[property(get = Self::content_icon_name, set = Self::set_content_icon_name, explicit_notify)]
25 content_icon_name: PhantomData<Option<glib::GString>>,
26 #[property(get = Self::is_loading, set = Self::set_is_loading, explicit_notify)]
30 is_loading: PhantomData<bool>,
31 }
32
33 #[glib::object_subclass]
34 impl ObjectSubclass for LoadingButton {
35 const NAME: &'static str = "LoadingButton";
36 type Type = super::LoadingButton;
37 type ParentType = gtk::Button;
38 }
39
40 #[glib::derived_properties]
41 impl ObjectImpl for LoadingButton {
42 fn constructed(&self) {
43 self.parent_constructed();
44
45 self.obj().set_child(Some(&self.loading_bin));
46 }
47 }
48
49 impl WidgetImpl for LoadingButton {}
50 impl ButtonImpl for LoadingButton {}
51
52 impl LoadingButton {
53 fn content_label(&self) -> Option<glib::GString> {
55 self.loading_bin
56 .child()
57 .and_downcast::<gtk::Label>()
58 .map(|l| l.label())
59 .filter(|s| !s.is_empty())
60 }
61
62 fn set_content_label(&self, label: &str) {
64 if self.content_label().as_deref() == Some(label) {
65 return;
66 }
67 let obj = self.obj();
68
69 let child_label = self.loading_bin.child_or_else::<gtk::Label>(|| {
70 let child_label = gtk::Label::builder()
71 .ellipsize(pango::EllipsizeMode::End)
72 .use_underline(true)
73 .mnemonic_widget(&*obj)
74 .css_classes(["text-button"])
75 .build();
76
77 obj.remove_css_class("image-button");
79 obj.update_relation(&[gtk::accessible::Relation::LabelledBy(&[
80 child_label.upcast_ref()
81 ])]);
82
83 child_label
84 });
85
86 child_label.set_label(label);
87
88 obj.notify_content_label();
89 }
90
91 fn content_icon_name(&self) -> Option<glib::GString> {
93 self.loading_bin
94 .child()
95 .and_downcast::<gtk::Image>()
96 .and_then(|i| i.icon_name())
97 }
98
99 fn set_content_icon_name(&self, icon_name: &str) {
101 if self.content_icon_name().as_deref() == Some(icon_name) {
102 return;
103 }
104 let obj = self.obj();
105
106 let child_image = self.loading_bin.child_or_else::<gtk::Image>(|| {
107 obj.add_css_class("image-button");
108
109 gtk::Image::builder()
110 .accessible_role(gtk::AccessibleRole::Presentation)
111 .build()
112 });
113
114 child_image.set_icon_name(Some(icon_name));
115
116 obj.notify_content_icon_name();
117 }
118
119 fn is_loading(&self) -> bool {
123 self.loading_bin.is_loading()
124 }
125
126 fn set_is_loading(&self, is_loading: bool) {
128 if self.is_loading() == is_loading {
129 return;
130 }
131 let obj = self.obj();
132
133 if obj.action_name().is_none() {
136 obj.set_sensitive(!is_loading);
137 }
138
139 self.loading_bin.set_is_loading(is_loading);
140
141 obj.notify_is_loading();
142 }
143 }
144}
145
146glib::wrapper! {
147 pub struct LoadingButton(ObjectSubclass<imp::LoadingButton>)
152 @extends gtk::Widget, gtk::Button,
153 @implements gtk::Accessible, gtk::Buildable, gtk::ConstraintTarget, gtk::Actionable;
154}
155
156impl LoadingButton {
157 pub fn new() -> Self {
158 glib::Object::new()
159 }
160}
161
162impl Default for LoadingButton {
163 fn default() -> Self {
164 Self::new()
165 }
166}