1use std::hash::Hasher;
2
3use djb_hash::{x33a_u32::X33aU32, HasherU32};
4use gtk::{gdk, graphene, gsk, pango, prelude::*};
5
6const NOTIFICATION_ICON_SIZE: i32 = 48;
8
9const AVATAR_COLOR_LIST: [(&str, &str, &str); 14] = [
11 ("#cfe1f5", "#83b6ec", "#337fdc"), ("#caeaf2", "#7ad9f1", "#0f9ac8"), ("#cef8d8", "#8de6b1", "#29ae74"), ("#e6f9d7", "#b5e98a", "#6ab85b"), ("#f9f4e1", "#f8e359", "#d29d09"), ("#ffead1", "#ffcb62", "#d68400"), ("#ffe5c5", "#ffa95a", "#ed5b00"), ("#f8d2ce", "#f78773", "#e62d42"), ("#fac7de", "#e973ab", "#e33b6a"), ("#e7c2e8", "#cb78d4", "#9945b5"), ("#d5d2f5", "#9e91e8", "#7a59ca"), ("#f2eade", "#e3cf9c", "#b08952"), ("#e5d6ca", "#be916d", "#785336"), ("#d8d7d3", "#c0bfbc", "#6e6d71"), ];
26
27pub(crate) fn paintable_as_notification_icon(
29 paintable: &gdk::Paintable,
30 scale_factor: i32,
31 renderer: &gsk::Renderer,
32) -> gdk::Texture {
33 let img_width = f64::from(paintable.intrinsic_width());
34 let img_height = f64::from(paintable.intrinsic_height());
35
36 let mut icon_size = f64::from(NOTIFICATION_ICON_SIZE * scale_factor);
37 let mut snap_width = img_width;
38 let mut snap_height = img_height;
39 let mut x_pos = 0.0;
40 let mut y_pos = 0.0;
41
42 if img_width > img_height {
43 if img_height > icon_size {
46 snap_height = icon_size;
47 snap_width = img_width * icon_size / img_height;
48 } else {
49 icon_size = img_height;
50 }
51
52 if snap_width > icon_size {
54 x_pos = ((snap_width - icon_size) / 2.0) as f32;
55 }
56 } else {
57 if img_width > icon_size {
60 snap_width = icon_size;
61 snap_height = img_height * icon_size / img_width;
62 } else {
63 icon_size = img_width;
64 }
65
66 if snap_height > icon_size {
68 y_pos = ((snap_height - icon_size) / 2.0) as f32;
69 }
70 }
71
72 let icon_size = icon_size as f32;
73 let snapshot = gtk::Snapshot::new();
74
75 let bounds = gsk::RoundedRect::from_rect(
77 graphene::Rect::new(x_pos, y_pos, icon_size, icon_size),
78 icon_size / 2.0,
79 );
80 snapshot.push_rounded_clip(&bounds);
81
82 paintable.snapshot(&snapshot, snap_width, snap_height);
83
84 snapshot.pop();
85
86 let node = snapshot.to_node().unwrap();
88 renderer.render_texture(node, None)
89}
90
91pub(crate) fn string_as_notification_icon(
95 string: &str,
96 scale_factor: i32,
97 layout: &pango::Layout,
98 renderer: &gsk::Renderer,
99) -> gdk::Texture {
100 let mut hasher = X33aU32::new();
102 hasher.write(string.as_bytes());
103 let color_nb = hasher.finish_u32() as usize % AVATAR_COLOR_LIST.len();
104 let colors = AVATAR_COLOR_LIST[color_nb];
105
106 let icon_size = (NOTIFICATION_ICON_SIZE * scale_factor) as f32;
107 let snapshot = gtk::Snapshot::new();
108
109 let bounds = gsk::RoundedRect::from_rect(
111 graphene::Rect::new(0.0, 0.0, icon_size, icon_size),
112 icon_size / 2.0,
113 );
114 snapshot.push_rounded_clip(&bounds);
115
116 snapshot.append_linear_gradient(
118 &graphene::Rect::new(0.0, 0.0, icon_size, icon_size),
119 &graphene::Point::new(0.0, 0.0),
120 &graphene::Point::new(0.0, icon_size),
121 &[
122 gsk::ColorStop::new(0.0, gdk::RGBA::parse(colors.1).unwrap()),
123 gsk::ColorStop::new(1.0, gdk::RGBA::parse(colors.2).unwrap()),
124 ],
125 );
126
127 snapshot.pop();
128
129 let initials = string
131 .split(char::is_whitespace)
132 .filter_map(|s| s.chars().next())
133 .collect::<String>();
134 layout.set_text(&initials);
135
136 if let Some(mut font_description) = layout
138 .font_description()
139 .or_else(|| layout.context().font_description())
140 {
141 font_description.set_weight(pango::Weight::Bold);
142 font_description.set_size(18 * scale_factor * pango::SCALE);
143 layout.set_font_description(Some(&font_description));
144 }
145
146 layout.set_width(icon_size as i32 * pango::SCALE);
148 layout.set_alignment(pango::Alignment::Center);
149
150 let (_, lay_height) = layout.pixel_size();
152 let lay_baseline = layout.baseline() / pango::SCALE;
153 let lay_padding = lay_height - lay_baseline;
156 let pos_y = (icon_size - lay_height as f32 - lay_padding as f32) / 2.0;
157 snapshot.translate(&graphene::Point::new(0.0, pos_y));
158
159 snapshot.append_layout(layout, &gdk::RGBA::parse(colors.0).unwrap());
160
161 let node = snapshot.to_node().unwrap();
163 renderer.render_texture(node, None)
164}