relm4/actions/
mod.rs

1//! Action utility.
2
3use gtk::gio;
4use gtk::prelude::{ActionExt, ActionMapExt, FromVariant, StaticVariantType, ToVariant, WidgetExt};
5
6use std::marker::PhantomData;
7
8/// Type safe traits for interacting with actions.
9pub mod traits;
10pub use traits::*;
11
12#[macro_export]
13/// Create a new type that implements [`ActionGroupName`].
14macro_rules! new_action_group {
15    ($vis:vis $ty:ident, $name:expr) => {
16        $vis struct $ty;
17
18        impl relm4::actions::ActionGroupName for $ty {
19            const NAME: &'static str = $name;
20        }
21    };
22}
23
24#[macro_export]
25/// Create a new type that implements [`ActionName`] without state or target type.
26macro_rules! new_stateless_action {
27    ($vis:vis $ty:ident, $group:ty, $name:expr) => {
28        $vis struct $ty;
29
30        impl relm4::actions::ActionName for $ty {
31            type Group = $group;
32            type Target = ();
33            type State = ();
34
35            const NAME: &'static str = $name;
36        }
37    };
38}
39
40#[macro_export]
41/// Create a new type that implements [`ActionName`] with state and target type.
42///
43/// The state stores the state of this action and the target type is passed by callers of the action.
44macro_rules! new_stateful_action {
45    ($vis:vis $ty:ident, $group:ty, $name:expr, $value:ty, $state:ty) => {
46        $vis struct $ty;
47
48        impl relm4::actions::ActionName for $ty {
49            type Group = $group;
50            type Target = $value;
51            type State = $state;
52
53            const NAME: &'static str = $name;
54        }
55    };
56}
57
58/// A type safe action that wraps around [`gio::SimpleAction`].
59pub struct RelmAction<Name: ActionName> {
60    name: PhantomData<Name>,
61    action: gio::SimpleAction,
62}
63
64impl<Name: ActionName> Clone for RelmAction<Name> {
65    fn clone(&self) -> Self {
66        Self {
67            name: self.name,
68            action: self.action.clone(),
69        }
70    }
71}
72
73impl<Name: ActionName> std::fmt::Debug for RelmAction<Name> {
74    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
75        f.debug_struct("RelmAction")
76            .field("name", &self.name)
77            .field("action", &self.action)
78            .finish()
79    }
80}
81
82impl<Name: ActionName> From<RelmAction<Name>> for gio::SimpleAction {
83    fn from(value: RelmAction<Name>) -> Self {
84        value.action
85    }
86}
87
88impl<Name: ActionName> RelmAction<Name>
89where
90    Name::State: ToVariant + FromVariant,
91    Name::Target: ToVariant + FromVariant,
92{
93    /// Create a new stateful action with target value.
94    pub fn new_stateful_with_target_value<
95        Callback: Fn(&gio::SimpleAction, &mut Name::State, Name::Target) + 'static,
96    >(
97        start_value: &Name::State,
98        callback: Callback,
99    ) -> Self {
100        let ty = Name::Target::static_variant_type();
101
102        let action =
103            gio::SimpleAction::new_stateful(Name::NAME, Some(&ty), &start_value.to_variant());
104
105        action.connect_activate(move |action, variant| {
106            let value = variant.unwrap().get().unwrap();
107            let mut state = action.state().unwrap().get().unwrap();
108
109            callback(action, &mut state, value);
110            action.set_state(&state.to_variant());
111        });
112
113        Self {
114            name: PhantomData,
115            action,
116        }
117    }
118}
119
120impl<Name: ActionName> RelmAction<Name>
121where
122    Name::State: ToVariant + FromVariant,
123    Name::Target: EmptyType,
124{
125    /// Create a new stateful action.
126    pub fn new_stateful<Callback: Fn(&gio::SimpleAction, &mut Name::State) + 'static>(
127        start_value: &Name::State,
128        callback: Callback,
129    ) -> Self {
130        let action = gio::SimpleAction::new_stateful(Name::NAME, None, &start_value.to_variant());
131
132        action.connect_activate(move |action, _variant| {
133            let mut state = action.state().unwrap().get().unwrap();
134            callback(action, &mut state);
135            action.set_state(&state.to_variant());
136        });
137
138        Self {
139            name: PhantomData,
140            action,
141        }
142    }
143}
144
145impl<Name: ActionName> RelmAction<Name>
146where
147    Name::State: EmptyType,
148    Name::Target: ToVariant + FromVariant,
149{
150    /// Create a new stateless action with a target value.
151    pub fn new_with_target_value<Callback: Fn(&gio::SimpleAction, Name::Target) + 'static>(
152        callback: Callback,
153    ) -> Self {
154        let ty = Name::Target::static_variant_type();
155
156        let action = gio::SimpleAction::new(Name::NAME, Some(&ty));
157
158        action.connect_activate(move |action, variant| {
159            let value = variant.unwrap().get().unwrap();
160            callback(action, value);
161        });
162
163        Self {
164            name: PhantomData,
165            action,
166        }
167    }
168}
169
170impl<Name: ActionName> RelmAction<Name>
171where
172    Name::Target: EmptyType,
173    Name::State: EmptyType,
174{
175    /// Create a new stateless action.
176    pub fn new_stateless<Callback: Fn(&gio::SimpleAction) + 'static>(callback: Callback) -> Self {
177        let action = gio::SimpleAction::new(Name::NAME, None);
178
179        action.connect_activate(move |action, _variant| {
180            callback(action);
181        });
182
183        Self {
184            name: PhantomData,
185            action,
186        }
187    }
188}
189
190impl<Name: ActionName> RelmAction<Name>
191where
192    Name::Target: ToVariant + FromVariant,
193{
194    /// Create a menu item for this action with the target value sent to the action on activation.
195    pub fn to_menu_item_with_target_value(
196        label: &str,
197        target_value: &Name::Target,
198    ) -> gio::MenuItem {
199        let menu_item = gio::MenuItem::new(Some(label), Some(&Name::action_name()));
200        menu_item.set_action_and_target_value(
201            Some(&Name::action_name()),
202            Some(&target_value.to_variant()),
203        );
204
205        menu_item
206    }
207}
208
209impl<Name: ActionName> RelmAction<Name>
210where
211    Name::Target: EmptyType,
212{
213    /// Create a menu item for this action.
214    #[must_use]
215    pub fn to_menu_item(label: &str) -> gio::MenuItem {
216        gio::MenuItem::new(Some(label), Some(&Name::action_name()))
217    }
218}
219
220impl<Name: ActionName> RelmAction<Name> {
221    /// Sets the action as enabled or disabled.
222    ///
223    /// If disabled, the action cannot be activated anymore.
224    pub fn set_enabled(&self, enabled: bool) {
225        self.action.set_enabled(enabled);
226    }
227
228    /// Returns the inner [`gio::SimpleAction`].
229    ///
230    /// This method is meant for low level control.
231    /// Only use it if you know exactly what you are doing.
232    #[must_use]
233    pub fn gio_action(&self) -> &gio::SimpleAction {
234        &self.action
235    }
236}
237
238#[derive(Debug)]
239/// A type-safe action group that wraps around [`gio::SimpleActionGroup`].
240pub struct RelmActionGroup<GroupName: ActionGroupName> {
241    group_name: PhantomData<GroupName>,
242    actions: Vec<gio::SimpleAction>,
243}
244
245impl<GroupName: ActionGroupName> RelmActionGroup<GroupName> {
246    /// Create a new [`RelmActionGroup`].
247    #[must_use]
248    pub fn new() -> Self {
249        Self::default()
250    }
251
252    /// Add an action to the group.
253    pub fn add_action<Name: ActionName>(&mut self, action: RelmAction<Name>) {
254        self.actions.push(action.action);
255    }
256
257    /// Register the added actions at application level.
258    pub fn register_for_main_application(self) {
259        let app = crate::main_application();
260        for action in self.actions {
261            app.add_action(&action);
262        }
263    }
264
265    /// Register the added actions for a certain widget.
266    pub fn register_for_widget<W>(self, widget: W)
267    where
268        W: AsRef<gtk::Widget>,
269    {
270        let group = self.into_action_group();
271        widget
272            .as_ref()
273            .insert_action_group(GroupName::NAME, Some(&group));
274    }
275
276    /// Convert [`RelmActionGroup`] into a [`gio::SimpleActionGroup`].
277    #[must_use]
278    pub fn into_action_group(self) -> gio::SimpleActionGroup {
279        let group = gio::SimpleActionGroup::new();
280        for action in self.actions {
281            group.add_action(&action);
282        }
283        group
284    }
285}
286
287impl<GroupName, A> FromIterator<A> for RelmActionGroup<GroupName>
288where
289    A: Into<gio::SimpleAction>,
290    GroupName: ActionGroupName,
291{
292    fn from_iter<I>(iter: I) -> Self
293    where
294        I: IntoIterator<Item = A>,
295    {
296        Self {
297            group_name: PhantomData,
298            actions: iter.into_iter().map(Into::into).collect(),
299        }
300    }
301}
302
303impl<GroupName: ActionGroupName> Default for RelmActionGroup<GroupName> {
304    fn default() -> Self {
305        Self {
306            group_name: PhantomData,
307            actions: Vec::new(),
308        }
309    }
310}