Skip to main content

gtk4/
widget.rs

1// Take a look at the license at the top of the repository in the LICENSE file.
2
3use glib::{ControlFlow, WeakRef, subclass::SignalId, translate::*};
4
5use crate::{
6    AccessibleRole, Shortcut, Widget, ffi, prelude::*, subclass::widget::WidgetActionIter,
7};
8
9// rustdoc-stripper-ignore-next
10/// Trait containing manually implemented methods of [`Widget`](crate::Widget).
11pub trait WidgetExtManual: IsA<Widget> + 'static {
12    /// Queues an animation frame update and adds a callback to be called
13    /// before each frame.
14    ///
15    /// Until the tick callback is removed, it will be called frequently
16    /// (usually at the frame rate of the output device or as quickly as
17    /// the application can be repainted, whichever is slower). For this
18    /// reason, is most suitable for handling graphics that change every
19    /// frame or every few frames.
20    ///
21    /// The tick callback does not automatically imply a relayout or repaint.
22    /// If you want a repaint or relayout, and aren’t changing widget properties
23    /// that would trigger that (for example, changing the text of a label),
24    /// then you will have to call [`WidgetExt::queue_resize()`][crate::prelude::WidgetExt::queue_resize()] or
25    /// [`WidgetExt::queue_draw()`][crate::prelude::WidgetExt::queue_draw()] yourself.
26    ///
27    /// [`FrameClock::frame_time()`][crate::gdk::FrameClock::frame_time()] should generally be used
28    /// for timing continuous animations and
29    /// `Gdk::FrameTimings::get_predicted_presentation_time()` should be
30    /// used if you are trying to display isolated frames at particular times.
31    ///
32    /// This is a more convenient alternative to connecting directly to the
33    /// [`update`][struct@crate::gdk::FrameClock#update] signal of the frame clock, since you
34    /// don't have to worry about when a frame clock is assigned to a widget.
35    ///
36    /// To remove a tick callback, pass the ID that is returned by this function
37    /// to [`WidgetExtManual::remove()`][crate::prelude::WidgetExtManual::remove()]. Tick callbacks will be
38    /// removed automatically when the widget is destroyed, so you do not have
39    /// to remove it yourself.
40    /// ## `callback`
41    /// function
42    ///   to call for updating animations
43    ///
44    /// # Returns
45    ///
46    /// an ID for this callback
47    #[doc(alias = "gtk_widget_add_tick_callback")]
48    fn add_tick_callback<P: Fn(&Self, &gdk::FrameClock) -> ControlFlow + 'static>(
49        &self,
50        callback: P,
51    ) -> TickCallbackId {
52        let callback_data: Box<P> = Box::new(callback);
53
54        unsafe extern "C" fn callback_func<
55            O: IsA<Widget>,
56            P: Fn(&O, &gdk::FrameClock) -> ControlFlow + 'static,
57        >(
58            widget: *mut ffi::GtkWidget,
59            frame_clock: *mut gdk::ffi::GdkFrameClock,
60            user_data: glib::ffi::gpointer,
61        ) -> glib::ffi::gboolean {
62            unsafe {
63                let widget: Borrowed<Widget> = from_glib_borrow(widget);
64                let frame_clock = from_glib_borrow(frame_clock);
65                let callback: &P = &*(user_data as *mut _);
66                let res = (*callback)(widget.unsafe_cast_ref(), &frame_clock);
67                res.into_glib()
68            }
69        }
70        let callback = Some(callback_func::<Self, P> as _);
71
72        unsafe extern "C" fn notify_func<
73            O: IsA<Widget>,
74            P: Fn(&O, &gdk::FrameClock) -> ControlFlow + 'static,
75        >(
76            data: glib::ffi::gpointer,
77        ) {
78            unsafe {
79                let _callback: Box<P> = Box::from_raw(data as *mut _);
80            }
81        }
82        let destroy_call = Some(notify_func::<Self, P> as _);
83
84        let id = unsafe {
85            ffi::gtk_widget_add_tick_callback(
86                self.as_ref().to_glib_none().0,
87                callback,
88                Box::into_raw(callback_data) as *mut _,
89                destroy_call,
90            )
91        };
92        TickCallbackId {
93            id,
94            widget: self.upcast_ref().downgrade(),
95        }
96    }
97}
98
99impl<O: IsA<Widget>> WidgetExtManual for O {}
100
101#[derive(Debug)]
102pub struct TickCallbackId {
103    id: u32,
104    widget: WeakRef<Widget>,
105}
106
107impl PartialEq for TickCallbackId {
108    #[inline]
109    fn eq(&self, other: &Self) -> bool {
110        self.id == other.id
111    }
112}
113
114impl TickCallbackId {
115    /// Removes a tick callback previously registered with
116    /// [`WidgetExtManual::add_tick_callback()`][crate::prelude::WidgetExtManual::add_tick_callback()].
117    /// ## `id`
118    /// an ID returned by [`WidgetExtManual::add_tick_callback()`][crate::prelude::WidgetExtManual::add_tick_callback()]
119    #[doc(alias = "gtk_widget_remove_tick_callback")]
120    #[doc(alias = "remove_tick_callback")]
121    pub fn remove(self) {
122        if let Some(widget) = self.widget.upgrade() {
123            unsafe {
124                ffi::gtk_widget_remove_tick_callback(widget.to_glib_none().0, self.id);
125            }
126        }
127    }
128}
129
130// rustdoc-stripper-ignore-next
131/// Trait containing widget class methods that can be called on any widget type
132/// at runtime, without requiring a subclass.
133///
134/// This trait is implemented for `glib::Class<T>` for any `T: IsA<Widget>`
135/// (e.g., `Class<Widget>`, `Class<TextView>`, `Class<Button>`).
136///
137/// # Example
138///
139/// ```no_run
140/// # use gtk4 as gtk;
141/// use gtk::prelude::*;
142///
143/// let class = glib::Class::<gtk::TextView>::from_type(gtk::TextView::static_type()).unwrap();
144/// let trigger = gtk::ShortcutTrigger::parse_string("<Meta>c").unwrap();
145/// let shortcut = gtk::Shortcut::new(Some(trigger), Some(gtk::NamedAction::new("clipboard.copy")));
146/// class.add_shortcut(&shortcut);
147/// ```
148pub trait WidgetClassManualExt {
149    #[doc(alias = "gtk_widget_class_add_shortcut")]
150    fn add_shortcut(&self, shortcut: &Shortcut);
151
152    #[doc(alias = "gtk_widget_class_add_binding_action")]
153    fn add_binding_action(&self, keyval: gdk::Key, mods: gdk::ModifierType, action_name: &str);
154
155    #[doc(alias = "gtk_widget_class_install_property_action")]
156    fn install_property_action(&self, action_name: &str, property_name: &str);
157
158    #[doc(alias = "gtk_widget_class_query_action")]
159    fn query_action(&self) -> WidgetActionIter;
160
161    #[doc(alias = "gtk_widget_class_get_activate_signal")]
162    #[doc(alias = "get_activate_signal")]
163    fn activate_signal(&self) -> Option<SignalId>;
164
165    #[doc(alias = "gtk_widget_class_get_layout_manager_type")]
166    #[doc(alias = "get_layout_manager_type")]
167    fn layout_manager_type(&self) -> glib::Type;
168
169    #[doc(alias = "gtk_widget_class_get_css_name")]
170    #[doc(alias = "get_css_name")]
171    fn css_name(&self) -> glib::GString;
172
173    #[doc(alias = "gtk_widget_class_get_accessible_role")]
174    #[doc(alias = "get_accessible_role")]
175    fn accessible_role(&self) -> AccessibleRole;
176}
177
178impl<T: IsA<Widget> + glib::object::IsClass> WidgetClassManualExt for glib::Class<T> {
179    fn add_shortcut(&self, shortcut: &Shortcut) {
180        unsafe {
181            let widget_class = self as *const Self as *mut ffi::GtkWidgetClass;
182            ffi::gtk_widget_class_add_shortcut(widget_class, shortcut.to_glib_none().0);
183        }
184    }
185
186    fn add_binding_action(&self, keyval: gdk::Key, mods: gdk::ModifierType, action_name: &str) {
187        let shortcut = Shortcut::new(
188            Some(crate::KeyvalTrigger::new(keyval, mods)),
189            Some(crate::NamedAction::new(action_name)),
190        );
191        self.add_shortcut(&shortcut);
192    }
193
194    fn install_property_action(&self, action_name: &str, property_name: &str) {
195        unsafe {
196            let widget_class = self as *const Self as *mut ffi::GtkWidgetClass;
197            ffi::gtk_widget_class_install_property_action(
198                widget_class,
199                action_name.to_glib_none().0,
200                property_name.to_glib_none().0,
201            );
202        }
203    }
204
205    fn query_action(&self) -> WidgetActionIter {
206        let widget_class = self as *const Self as *mut ffi::GtkWidgetClass;
207        WidgetActionIter::new(widget_class)
208    }
209
210    fn activate_signal(&self) -> Option<SignalId> {
211        unsafe {
212            let widget_class = self as *const Self as *mut ffi::GtkWidgetClass;
213            let signal_id = ffi::gtk_widget_class_get_activate_signal(widget_class);
214            if signal_id == 0 {
215                None
216            } else {
217                Some(from_glib(signal_id))
218            }
219        }
220    }
221
222    fn layout_manager_type(&self) -> glib::Type {
223        unsafe {
224            let widget_class = self as *const Self as *mut ffi::GtkWidgetClass;
225            from_glib(ffi::gtk_widget_class_get_layout_manager_type(widget_class))
226        }
227    }
228
229    fn css_name(&self) -> glib::GString {
230        unsafe {
231            let widget_class = self as *const Self as *mut ffi::GtkWidgetClass;
232            from_glib_none(ffi::gtk_widget_class_get_css_name(widget_class))
233        }
234    }
235
236    fn accessible_role(&self) -> AccessibleRole {
237        unsafe {
238            let widget_class = self as *const Self as *mut ffi::GtkWidgetClass;
239            from_glib(ffi::gtk_widget_class_get_accessible_role(widget_class))
240        }
241    }
242}