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
//! Reusable and easily configurable alert component.
//!
//! **[Example implementation](https://github.com/AaronErhardt/relm4/blob/main/relm4-examples/examples/alert.rs)**

use gtk::prelude::{DialogExt, GtkWindowExt, WidgetExt};
use relm4::{gtk, ComponentParts, ComponentSender, SimpleComponent};

/// Configuration for the alert dialog component
#[derive(Debug)]
pub struct AlertSettings {
    /// Large text
    pub text: String,
    /// Optional secondary, smaller text
    pub secondary_text: Option<String>,
    /// Modal dialogs freeze other windows as long they are visible
    pub is_modal: bool,
    /// Sets color of the accept button to red if the theme supports it
    pub destructive_accept: bool,
    /// Text for confirm button
    pub confirm_label: String,
    /// Text for cancel button
    pub cancel_label: String,
    /// Text for third option button. If [`None`] the third button won't be created.
    pub option_label: Option<String>,
}

/// Alert dialog component.
#[derive(Debug)]
pub struct Alert {
    settings: AlertSettings,
    is_active: bool,
}

/// Messages that can be sent to the alert dialog component
#[derive(Debug)]
pub enum AlertMsg {
    /// Message sent by the parent to view the dialog
    Show,

    #[doc(hidden)]
    Response(gtk::ResponseType),
}

/// User action performed on the alert dialog.
#[derive(Debug)]
pub enum AlertResponse {
    /// User clicked confirm button.
    Confirm,

    /// User clicked cancel button.
    Cancel,

    /// User clicked user-supplied option.
    Option,
}

/// Widgets of the alert dialog component.
#[relm4::component(pub)]
impl SimpleComponent for Alert {
    type Init = AlertSettings;
    type Input = AlertMsg;
    type Output = AlertResponse;

    view! {
        #[name(dialog)]
        gtk::MessageDialog {
            set_message_type: gtk::MessageType::Question,
            #[watch]
            set_visible: model.is_active,
            connect_response[sender] => move |_, response| {
                sender.input(AlertMsg::Response(response));
            },

            // Apply configuration
            set_text: Some(&model.settings.text),
            set_secondary_text: model.settings.secondary_text.as_deref(),
            set_modal: model.settings.is_modal,
            add_button: (&model.settings.confirm_label, gtk::ResponseType::Accept),
            add_button: (&model.settings.cancel_label, gtk::ResponseType::Cancel),
        }
    }

    fn init(
        settings: AlertSettings,
        root: &Self::Root,
        sender: ComponentSender<Self>,
    ) -> ComponentParts<Self> {
        let model = Alert {
            settings,
            is_active: false,
        };

        let widgets = view_output!();

        if let Some(option_label) = &model.settings.option_label {
            widgets
                .dialog
                .add_button(option_label, gtk::ResponseType::Other(0));
        }

        if model.settings.destructive_accept {
            let accept_widget = widgets
                .dialog
                .widget_for_response(gtk::ResponseType::Accept)
                .expect("No button for accept response set");
            accept_widget.add_css_class("destructive-action");
        }

        ComponentParts { model, widgets }
    }

    fn update(&mut self, input: AlertMsg, sender: ComponentSender<Self>) {
        match input {
            AlertMsg::Show => {
                self.is_active = true;
            }
            AlertMsg::Response(ty) => {
                self.is_active = false;
                sender
                    .output(match ty {
                        gtk::ResponseType::Accept => AlertResponse::Confirm,
                        gtk::ResponseType::Other(_) => AlertResponse::Option,
                        _ => AlertResponse::Cancel,
                    })
                    .unwrap();
            }
        }
    }
}