use gtk::prelude::{BoxExt, ButtonExt, GtkWindowExt, OrientableExt, WidgetExt};
use once_cell::sync::Lazy;
use relm4::{Component, ComponentParts, ComponentSender, RelmWidgetExt, gtk};
const LIBADWAITA_ENABLED: bool = cfg!(feature = "libadwaita");
const COMPONENT_CSS: &str = include_str!("style.css");
const MESSAGE_AREA_CSS: &str = "message-area";
const RESPONSE_BUTTONS_CSS: &str = "response-buttons";
static INITIALIZE_CSS: Lazy<()> = Lazy::new(|| {
relm4::set_global_css_with_priority(COMPONENT_CSS, gtk::STYLE_PROVIDER_PRIORITY_APPLICATION);
});
#[derive(Debug)]
pub struct AlertSettings {
pub text: Option<String>,
pub secondary_text: Option<String>,
pub is_modal: bool,
pub destructive_accept: bool,
pub confirm_label: Option<String>,
pub cancel_label: Option<String>,
pub option_label: Option<String>,
pub extra_child: Option<gtk::Widget>,
}
impl Default for AlertSettings {
fn default() -> Self {
Self {
text: Some("Alert".into()),
secondary_text: None,
is_modal: true,
destructive_accept: false,
confirm_label: None,
cancel_label: None,
option_label: None,
extra_child: None,
}
}
}
#[derive(Debug)]
pub struct Alert {
pub settings: AlertSettings,
is_active: bool,
current_child: Option<gtk::Widget>,
}
#[derive(Debug)]
pub enum AlertMsg {
Show,
Hide,
#[doc(hidden)]
Response(AlertResponse),
}
#[derive(Debug)]
pub enum AlertResponse {
Confirm,
Cancel,
Option,
}
#[relm4::component(pub)]
impl Component for Alert {
type Init = AlertSettings;
type Input = AlertMsg;
type Output = AlertResponse;
type CommandOutput = ();
view! {
gtk::Window {
#[watch]
set_visible: model.is_active,
set_modal: model.settings.is_modal,
add_css_class: "relm4-alert",
#[wrap(Some)]
set_titlebar = >k::Box {
set_visible: false,
},
gtk::Box {
set_orientation: gtk::Orientation::Vertical,
#[name(message_area)]
gtk::Box {
set_orientation: gtk::Orientation::Vertical,
set_spacing: 8,
set_vexpand: true,
add_css_class: MESSAGE_AREA_CSS,
gtk::Label {
#[watch]
set_text: model.settings.text.as_deref().unwrap_or_default(),
#[watch]
set_visible: model.settings.text.is_some(),
set_valign: gtk::Align::Start,
set_justify: gtk::Justification::Center,
add_css_class: relm4::css::TITLE_2,
set_wrap: true,
set_max_width_chars: 20,
},
gtk::Label {
#[watch]
set_text: model.settings.secondary_text.as_deref().unwrap_or_default(),
set_vexpand: true,
set_valign: gtk::Align::Fill,
set_justify: gtk::Justification::Center,
set_wrap: true,
set_max_width_chars: 40,
},
},
gtk::Box {
add_css_class: RESPONSE_BUTTONS_CSS,
set_orientation: gtk::Orientation::Vertical,
set_vexpand_set: true,
set_valign: gtk::Align::End,
gtk::Separator {},
gtk::Box {
set_homogeneous: true,
set_vexpand: true,
set_valign: gtk::Align::End,
#[name(confirm_label)]
gtk::Button {
#[watch]
set_visible: model.settings.confirm_label.is_some(),
#[watch]
set_class_active: (relm4::css::DESTRUCTIVE_ACTION, !LIBADWAITA_ENABLED && model.settings.destructive_accept),
#[watch]
set_class_active: (relm4::css::FLAT, LIBADWAITA_ENABLED || !model.settings.destructive_accept),
set_hexpand: true,
connect_clicked => AlertMsg::Response(AlertResponse::Confirm),
gtk::Label {
#[watch]
set_label: model.settings.confirm_label.as_deref().unwrap_or_default(),
#[watch]
set_class_active: (relm4::css::ERROR, LIBADWAITA_ENABLED && model.settings.destructive_accept),
}
},
gtk::Box {
#[watch]
set_visible: model.settings.cancel_label.is_some(),
gtk::Separator {},
#[name(cancel_label)]
gtk::Button {
#[watch]
set_label: model.settings.cancel_label.as_deref().unwrap_or_default(),
add_css_class: relm4::css::FLAT,
set_hexpand: true,
connect_clicked => AlertMsg::Response(AlertResponse::Cancel)
}
},
gtk::Box {
#[watch]
set_visible: model.settings.option_label.is_some(),
gtk::Separator {},
#[name(option_label)]
gtk::Button {
#[watch]
set_label: model.settings.option_label.as_deref().unwrap_or_default(),
add_css_class: relm4::css::FLAT,
set_hexpand: true,
connect_clicked => AlertMsg::Response(AlertResponse::Option)
}
}
}
}
}
}
}
fn init(
settings: AlertSettings,
root: Self::Root,
sender: ComponentSender<Self>,
) -> ComponentParts<Self> {
#[allow(clippy::no_effect)] *INITIALIZE_CSS;
let current_child = settings.extra_child.clone();
let model = Alert {
settings,
is_active: false,
current_child,
};
let widgets = view_output!();
ComponentParts { model, widgets }
}
fn update_with_view(
&mut self,
widgets: &mut Self::Widgets,
input: AlertMsg,
sender: ComponentSender<Self>,
_root: &Self::Root,
) {
if let Some(widget) = self.current_child.take() {
widgets.message_area.remove(&widget);
}
if let Some(extra_child) = self.settings.extra_child.clone() {
widgets.message_area.append(&extra_child);
self.current_child = Some(extra_child);
}
match input {
AlertMsg::Show => self.is_active = true,
AlertMsg::Hide => self.is_active = false,
AlertMsg::Response(resp) => {
self.is_active = false;
sender.output(resp).unwrap();
}
}
self.update_view(widgets, sender);
}
}