6 changed files with 263 additions and 76 deletions
@ -0,0 +1,172 @@
|
||||
use adw::{prelude::*, subclass::prelude::*}; |
||||
use gtk::{glib, graphene, gsk}; |
||||
|
||||
mod imp { |
||||
use std::cell::{Cell, RefCell}; |
||||
|
||||
use super::*; |
||||
|
||||
#[derive(Debug, Default, glib::Properties)] |
||||
#[properties(wrapper_type = super::CropCircle)] |
||||
pub struct CropCircle { |
||||
/// The child widget to crop.
|
||||
#[property(get, set = Self::set_child, explicit_notify, nullable)] |
||||
pub child: RefCell<Option<gtk::Widget>>, |
||||
/// Whether the child should be cropped.
|
||||
#[property(get, set = Self::set_is_cropped, explicit_notify)] |
||||
pub is_cropped: Cell<bool>, |
||||
/// The width that should be cropped.
|
||||
///
|
||||
/// This is the number of pixels from the right edge of the child
|
||||
/// widget.
|
||||
#[property(get, set = Self::set_cropped_width, explicit_notify)] |
||||
pub cropped_width: Cell<u32>, |
||||
mask: adw::Bin, |
||||
} |
||||
|
||||
#[glib::object_subclass] |
||||
impl ObjectSubclass for CropCircle { |
||||
const NAME: &'static str = "CropCircle"; |
||||
type Type = super::CropCircle; |
||||
type ParentType = gtk::Widget; |
||||
|
||||
fn class_init(klass: &mut Self::Class) { |
||||
klass.set_css_name("crop-circle"); |
||||
} |
||||
} |
||||
|
||||
#[glib::derived_properties] |
||||
impl ObjectImpl for CropCircle { |
||||
fn constructed(&self) { |
||||
self.parent_constructed(); |
||||
|
||||
self.mask.set_parent(&*self.obj()); |
||||
self.mask.add_css_class("mask"); |
||||
self.mask |
||||
.set_accessible_role(gtk::AccessibleRole::Presentation); |
||||
} |
||||
|
||||
fn dispose(&self) { |
||||
if let Some(child) = self.child.take() { |
||||
child.unparent(); |
||||
} |
||||
|
||||
self.mask.unparent(); |
||||
} |
||||
} |
||||
|
||||
impl WidgetImpl for CropCircle { |
||||
fn measure(&self, orientation: gtk::Orientation, for_size: i32) -> (i32, i32, i32, i32) { |
||||
if let Some(child) = self.child.borrow().as_ref() { |
||||
return child.measure(orientation, for_size); |
||||
} |
||||
|
||||
(0, 0, -1, -1) |
||||
} |
||||
|
||||
fn size_allocate(&self, width: i32, height: i32, baseline: i32) { |
||||
let Some(child) = self.child.borrow().clone() else { |
||||
return; |
||||
}; |
||||
|
||||
child.allocate(width, height, baseline, None); |
||||
|
||||
let (_, child_size) = child.preferred_size(); |
||||
|
||||
// The x position at the right edge of the child.
|
||||
let mut x = (width + child_size.width()) / 2; |
||||
|
||||
if self.is_cropped.get() { |
||||
x = x.saturating_sub(self.cropped_width.get() as i32); |
||||
} |
||||
|
||||
let transform = gsk::Transform::new().translate(&graphene::Point::new(x as f32, 0.0)); |
||||
self.mask.allocate(width, height, baseline, Some(transform)); |
||||
} |
||||
|
||||
fn snapshot(&self, snapshot: >k::Snapshot) { |
||||
let borrow = self.child.borrow(); |
||||
let Some(child) = borrow.as_ref() else { |
||||
return; |
||||
}; |
||||
|
||||
let obj = self.obj(); |
||||
|
||||
if !self.is_cropped.get() || self.cropped_width.get() == 0 { |
||||
obj.snapshot_child(child, snapshot); |
||||
return; |
||||
} |
||||
|
||||
snapshot.push_mask(gsk::MaskMode::InvertedAlpha); |
||||
|
||||
obj.snapshot_child(&self.mask, snapshot); |
||||
snapshot.pop(); |
||||
|
||||
obj.snapshot_child(child, snapshot); |
||||
snapshot.pop(); |
||||
} |
||||
} |
||||
|
||||
impl CropCircle { |
||||
/// Set the child widget to crop.
|
||||
fn set_child(&self, child: Option<gtk::Widget>) { |
||||
let prev_child = self.child.borrow().clone(); |
||||
|
||||
if prev_child == child { |
||||
return; |
||||
} |
||||
let obj = self.obj(); |
||||
|
||||
if let Some(child) = prev_child { |
||||
child.unparent(); |
||||
} |
||||
|
||||
if let Some(child) = &child { |
||||
child.set_parent(&*obj); |
||||
} |
||||
|
||||
self.child.replace(child); |
||||
|
||||
obj.queue_resize(); |
||||
obj.notify_child(); |
||||
} |
||||
|
||||
/// Set whether the child widget should be cropped.
|
||||
fn set_is_cropped(&self, is_cropped: bool) { |
||||
if self.is_cropped.get() == is_cropped { |
||||
return; |
||||
} |
||||
let obj = self.obj(); |
||||
|
||||
self.is_cropped.set(is_cropped); |
||||
|
||||
obj.queue_allocate(); |
||||
obj.notify_is_cropped(); |
||||
} |
||||
|
||||
/// Set the width that should be cropped.
|
||||
fn set_cropped_width(&self, width: u32) { |
||||
if self.cropped_width.get() == width { |
||||
return; |
||||
} |
||||
let obj = self.obj(); |
||||
|
||||
self.cropped_width.set(width); |
||||
|
||||
obj.queue_allocate(); |
||||
obj.notify_cropped_width(); |
||||
} |
||||
} |
||||
} |
||||
|
||||
glib::wrapper! { |
||||
/// A widget that crops its child with a circle.
|
||||
pub struct CropCircle(ObjectSubclass<imp::CropCircle>) |
||||
@extends gtk::Widget, @implements gtk::Accessible; |
||||
} |
||||
|
||||
impl CropCircle { |
||||
pub fn new() -> Self { |
||||
glib::Object::new() |
||||
} |
||||
} |
||||
Loading…
Reference in new issue