fractal/components/avatar/
crop_circle.rs1use adw::{prelude::*, subclass::prelude::*};
2use gtk::{glib, graphene, gsk};
3
4mod imp {
5 use std::cell::{Cell, RefCell};
6
7 use super::*;
8
9 #[derive(Debug, Default, glib::Properties)]
10 #[properties(wrapper_type = super::CropCircle)]
11 pub struct CropCircle {
12 #[property(get, set = Self::set_child, explicit_notify, nullable)]
14 child: RefCell<Option<gtk::Widget>>,
15 #[property(get, set = Self::set_is_cropped, explicit_notify)]
17 is_cropped: Cell<bool>,
18 #[property(get, set = Self::set_cropped_width, explicit_notify)]
23 cropped_width: Cell<u32>,
24 mask: adw::Bin,
25 }
26
27 #[glib::object_subclass]
28 impl ObjectSubclass for CropCircle {
29 const NAME: &'static str = "CropCircle";
30 type Type = super::CropCircle;
31 type ParentType = gtk::Widget;
32
33 fn class_init(klass: &mut Self::Class) {
34 klass.set_css_name("crop-circle");
35 }
36 }
37
38 #[glib::derived_properties]
39 impl ObjectImpl for CropCircle {
40 fn constructed(&self) {
41 self.parent_constructed();
42
43 self.mask.set_parent(&*self.obj());
44 self.mask.add_css_class("mask");
45 self.mask
46 .set_accessible_role(gtk::AccessibleRole::Presentation);
47 }
48
49 fn dispose(&self) {
50 if let Some(child) = self.child.take() {
51 child.unparent();
52 }
53
54 self.mask.unparent();
55 }
56 }
57
58 impl WidgetImpl for CropCircle {
59 fn measure(&self, orientation: gtk::Orientation, for_size: i32) -> (i32, i32, i32, i32) {
60 if let Some(child) = self.child.borrow().as_ref() {
61 return child.measure(orientation, for_size);
62 }
63
64 (0, 0, -1, -1)
65 }
66
67 fn size_allocate(&self, width: i32, height: i32, baseline: i32) {
68 let Some(child) = self.child.borrow().clone() else {
69 return;
70 };
71
72 child.allocate(width, height, baseline, None);
73
74 let (_, child_size) = child.preferred_size();
75
76 let mut x = width.midpoint(child_size.width());
78
79 if self.is_cropped.get() {
80 let cropped_width = self
81 .cropped_width
82 .get()
83 .try_into()
84 .expect("width fits into an i32");
85 x = x.saturating_sub(cropped_width);
86 }
87
88 let transform = gsk::Transform::new().translate(&graphene::Point::new(x as f32, 0.0));
89 self.mask.allocate(width, height, baseline, Some(transform));
90 }
91
92 fn snapshot(&self, snapshot: >k::Snapshot) {
93 let borrow = self.child.borrow();
94 let Some(child) = borrow.as_ref() else {
95 return;
96 };
97
98 let obj = self.obj();
99
100 if !self.is_cropped.get() || self.cropped_width.get() == 0 {
101 obj.snapshot_child(child, snapshot);
102 return;
103 }
104
105 snapshot.push_mask(gsk::MaskMode::InvertedAlpha);
106
107 obj.snapshot_child(&self.mask, snapshot);
108 snapshot.pop();
109
110 obj.snapshot_child(child, snapshot);
111 snapshot.pop();
112 }
113 }
114
115 impl CropCircle {
116 fn set_child(&self, child: Option<gtk::Widget>) {
118 let prev_child = self.child.borrow().clone();
119
120 if prev_child == child {
121 return;
122 }
123 let obj = self.obj();
124
125 if let Some(child) = prev_child {
126 child.unparent();
127 }
128
129 if let Some(child) = &child {
130 child.set_parent(&*obj);
131 }
132
133 self.child.replace(child);
134
135 obj.queue_resize();
136 obj.notify_child();
137 }
138
139 fn set_is_cropped(&self, is_cropped: bool) {
141 if self.is_cropped.get() == is_cropped {
142 return;
143 }
144 let obj = self.obj();
145
146 self.is_cropped.set(is_cropped);
147
148 obj.queue_allocate();
149 obj.notify_is_cropped();
150 }
151
152 fn set_cropped_width(&self, width: u32) {
154 if self.cropped_width.get() == width {
155 return;
156 }
157 let obj = self.obj();
158
159 self.cropped_width.set(width);
160
161 obj.queue_allocate();
162 obj.notify_cropped_width();
163 }
164 }
165}
166
167glib::wrapper! {
168 pub struct CropCircle(ObjectSubclass<imp::CropCircle>)
170 @extends gtk::Widget,
171 @implements gtk::Accessible, gtk::Buildable, gtk::ConstraintTarget;
172}
173
174impl CropCircle {
175 pub fn new() -> Self {
176 glib::Object::new()
177 }
178}