fractal/components/rows/
entry_add_row.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
use adw::{prelude::*, subclass::prelude::*};
use gtk::{glib, glib::closure_local, CompositeTemplate};

use crate::components::LoadingButton;

mod imp {
    use std::{cell::Cell, marker::PhantomData, sync::LazyLock};

    use glib::subclass::{InitializingObject, Signal};

    use super::*;

    #[derive(Debug, Default, CompositeTemplate, glib::Properties)]
    #[template(resource = "/org/gnome/Fractal/ui/components/rows/entry_add_row.ui")]
    #[properties(wrapper_type = super::EntryAddRow)]
    pub struct EntryAddRow {
        #[template_child]
        add_button: TemplateChild<LoadingButton>,
        /// The tooltip text of the add button.
        #[property(get = Self::add_button_tooltip_text, set = Self::set_add_button_tooltip_text, explicit_notify, nullable)]
        add_button_tooltip_text: PhantomData<Option<glib::GString>>,
        /// Whether to prevent the add button from being activated.
        #[property(get, set = Self::set_inhibit_add, explicit_notify)]
        inhibit_add: Cell<bool>,
        /// Whether this row is loading.
        #[property(get = Self::is_loading, set = Self::set_is_loading, explicit_notify)]
        is_loading: PhantomData<bool>,
    }

    #[glib::object_subclass]
    impl ObjectSubclass for EntryAddRow {
        const NAME: &'static str = "EntryAddRow";
        type Type = super::EntryAddRow;
        type ParentType = adw::EntryRow;

        fn class_init(klass: &mut Self::Class) {
            Self::bind_template(klass);
            Self::bind_template_callbacks(klass);
        }

        fn instance_init(obj: &InitializingObject<Self>) {
            obj.init_template();
        }
    }

    #[glib::derived_properties]
    impl ObjectImpl for EntryAddRow {
        fn signals() -> &'static [Signal] {
            static SIGNALS: LazyLock<Vec<Signal>> =
                LazyLock::new(|| vec![Signal::builder("add").build()]);
            SIGNALS.as_ref()
        }
    }

    impl WidgetImpl for EntryAddRow {}
    impl ListBoxRowImpl for EntryAddRow {}
    impl PreferencesRowImpl for EntryAddRow {}
    impl ActionRowImpl for EntryAddRow {}
    impl EntryRowImpl for EntryAddRow {}

    #[gtk::template_callbacks]
    impl EntryAddRow {
        /// The tooltip text of the add button.
        fn add_button_tooltip_text(&self) -> Option<glib::GString> {
            self.add_button.tooltip_text()
        }

        /// Set the tooltip text of the add button.
        fn set_add_button_tooltip_text(&self, tooltip_text: Option<&str>) {
            if self.add_button_tooltip_text().as_deref() == tooltip_text {
                return;
            }

            self.add_button.set_tooltip_text(tooltip_text);
            self.obj().notify_add_button_tooltip_text();
        }

        /// Set whether to prevent the add button from being activated.
        fn set_inhibit_add(&self, inhibit: bool) {
            if self.inhibit_add.get() == inhibit {
                return;
            }

            self.inhibit_add.set(inhibit);

            self.update_add_button();
            self.obj().notify_inhibit_add();
        }

        /// Whether this row is loading.
        fn is_loading(&self) -> bool {
            self.add_button.is_loading()
        }

        /// Set whether this row is loading.
        fn set_is_loading(&self, is_loading: bool) {
            if self.is_loading() == is_loading {
                return;
            }

            self.add_button.set_is_loading(is_loading);

            let obj = self.obj();
            obj.set_sensitive(!is_loading);
            obj.notify_is_loading();
        }

        /// Whether the add button can be activated.
        fn can_add(&self) -> bool {
            !self.inhibit_add.get() && !self.obj().text().is_empty()
        }

        /// Update the state of the add button.
        #[template_callback]
        fn update_add_button(&self) {
            self.add_button.set_sensitive(self.can_add());
        }

        /// Emit the `add` signal.
        #[template_callback]
        fn add(&self) {
            if !self.can_add() {
                return;
            }

            self.obj().emit_by_name::<()>("add", &[]);
        }
    }
}

glib::wrapper! {
    /// An `AdwEntryRow` with an "Add" button.
    pub struct EntryAddRow(ObjectSubclass<imp::EntryAddRow>)
        @extends gtk::Widget, gtk::ListBoxRow, adw::PreferencesRow, adw::ActionRow, adw::EntryRow,
        @implements gtk::Actionable, gtk::Editable, gtk::Accessible;
}

impl EntryAddRow {
    pub fn new() -> Self {
        glib::Object::new()
    }

    /// Connect to the signal emitted when the "Add" button is activated.
    pub fn connect_add<F: Fn(&Self) + 'static>(&self, f: F) -> glib::SignalHandlerId {
        self.connect_closure(
            "add",
            true,
            closure_local!(move |obj: Self| {
                f(&obj);
            }),
        )
    }
}