Skip to main content

fractal/components/avatar/
data.rs

1use gtk::{gdk, glib, prelude::*, subclass::prelude::*};
2use tracing::warn;
3
4use super::AvatarImage;
5use crate::{
6    application::Application,
7    utils::notifications::{paintable_as_notification_icon, string_as_notification_icon},
8};
9
10mod imp {
11    use std::cell::RefCell;
12
13    use super::*;
14
15    #[derive(Debug, Default, glib::Properties)]
16    #[properties(wrapper_type = super::AvatarData)]
17    pub struct AvatarData {
18        /// The data of the user-defined image.
19        #[property(get, set = Self::set_image, explicit_notify, nullable)]
20        image: RefCell<Option<AvatarImage>>,
21        /// The display name used as a fallback for this avatar.
22        #[property(get, set = Self::set_display_name, explicit_notify)]
23        display_name: RefCell<String>,
24    }
25
26    #[glib::object_subclass]
27    impl ObjectSubclass for AvatarData {
28        const NAME: &'static str = "AvatarData";
29        type Type = super::AvatarData;
30    }
31
32    #[glib::derived_properties]
33    impl ObjectImpl for AvatarData {}
34
35    impl AvatarData {
36        /// Set the data of the user-defined image.
37        fn set_image(&self, image: Option<AvatarImage>) {
38            if *self.image.borrow() == image {
39                return;
40            }
41
42            self.image.replace(image);
43            self.obj().notify_image();
44        }
45
46        /// Set the display name used as a fallback for this avatar.
47        fn set_display_name(&self, display_name: String) {
48            if *self.display_name.borrow() == display_name {
49                return;
50            }
51
52            self.display_name.replace(display_name);
53            self.obj().notify_display_name();
54        }
55    }
56}
57
58glib::wrapper! {
59    /// Data about a User’s or Room’s avatar.
60    pub struct AvatarData(ObjectSubclass<imp::AvatarData>);
61}
62
63impl AvatarData {
64    /// Construct a new empty `AvatarData`.
65    pub fn new() -> Self {
66        glib::Object::new()
67    }
68
69    /// Get this avatar as a notification icon.
70    ///
71    /// If `inhibit_image` is set, the image of the avatar will not be used.
72    ///
73    /// Returns `None` if an error occurred while generating the icon.
74    pub(crate) async fn as_notification_icon(&self, inhibit_image: bool) -> Option<gdk::Texture> {
75        let Some(window) = Application::default().active_window() else {
76            warn!("Could not generate icon for notification: no active window");
77            return None;
78        };
79        let Some(renderer) = window.renderer() else {
80            warn!("Could not generate icon for notification: no renderer");
81            return None;
82        };
83        let scale_factor = window.scale_factor();
84
85        if !inhibit_image && let Some(image) = self.image() {
86            match image.load_small_paintable().await {
87                Ok(Some(paintable)) => {
88                    let texture = paintable_as_notification_icon(
89                        paintable.upcast_ref(),
90                        scale_factor,
91                        &renderer,
92                    );
93                    return Some(texture);
94                }
95                // No paintable, we will try to generate the fallback.
96                Ok(None) => {}
97                // Could not get the paintable, we will try to generate the fallback.
98                Err(error) => {
99                    warn!("Could not generate icon for notification: {error}");
100                }
101            }
102        }
103
104        let texture = string_as_notification_icon(
105            &self.display_name(),
106            scale_factor,
107            &window.create_pango_layout(None),
108            &renderer,
109        );
110        Some(texture)
111    }
112}
113
114impl Default for AvatarData {
115    fn default() -> Self {
116        Self::new()
117    }
118}