fractal/components/camera/
qrcode_scanner.rsuse adw::{prelude::*, subclass::prelude::*};
use gettextrs::gettext;
use gtk::{
glib,
glib::{clone, closure_local},
};
use matrix_sdk::encryption::verification::QrVerificationData;
use super::{Camera, CameraExt, CameraViewfinder, CameraViewfinderExt, CameraViewfinderState};
use crate::utils::BoundConstructOnlyObject;
#[derive(Clone, Debug, PartialEq, Eq, glib::Boxed)]
#[boxed_type(name = "QrVerificationDataBoxed")]
pub(super) struct QrVerificationDataBoxed(pub(super) QrVerificationData);
mod imp {
use std::sync::LazyLock;
use glib::subclass::{InitializingObject, Signal};
use gtk::CompositeTemplate;
use super::*;
#[derive(Debug, CompositeTemplate, Default, glib::Properties)]
#[template(resource = "/org/gnome/Fractal/ui/components/camera/qrcode_scanner.ui")]
#[properties(wrapper_type = super::QrCodeScanner)]
pub struct QrCodeScanner {
#[template_child]
stack: TemplateChild<gtk::Stack>,
#[property(get, set = Self::set_viewfinder, construct_only)]
viewfinder: BoundConstructOnlyObject<CameraViewfinder>,
}
#[glib::object_subclass]
impl ObjectSubclass for QrCodeScanner {
const NAME: &'static str = "QrCodeScanner";
type Type = super::QrCodeScanner;
type ParentType = adw::Bin;
fn class_init(klass: &mut Self::Class) {
Self::bind_template(klass);
}
fn instance_init(obj: &InitializingObject<Self>) {
obj.init_template();
}
}
#[glib::derived_properties]
impl ObjectImpl for QrCodeScanner {
fn signals() -> &'static [Signal] {
static SIGNALS: LazyLock<Vec<Signal>> = LazyLock::new(|| {
vec![Signal::builder("qrcode-detected")
.param_types([QrVerificationDataBoxed::static_type()])
.run_first()
.build()]
});
SIGNALS.as_ref()
}
}
impl WidgetImpl for QrCodeScanner {}
impl BinImpl for QrCodeScanner {}
impl QrCodeScanner {
fn set_viewfinder(&self, viewfinder: CameraViewfinder) {
let obj = self.obj();
let state_handler = viewfinder.connect_state_notify(clone!(
#[weak(rename_to = imp)]
self,
move |_| {
imp.update_visible_page();
}
));
let qrcode_detected_handler = viewfinder.connect_qrcode_detected(clone!(
#[weak]
obj,
move |_, data| {
obj.emit_by_name::<()>("qrcode-detected", &[&QrVerificationDataBoxed(data)]);
}
));
viewfinder.set_overflow(gtk::Overflow::Hidden);
viewfinder.add_css_class("card");
self.stack
.add_titled(&viewfinder, Some("camera"), &gettext("Camera"));
self.viewfinder
.set(viewfinder, vec![state_handler, qrcode_detected_handler]);
self.update_visible_page();
}
fn update_visible_page(&self) {
let name = match self.viewfinder.obj().state() {
CameraViewfinderState::Loading => "loading",
CameraViewfinderState::Ready => "camera",
CameraViewfinderState::NoCameras | CameraViewfinderState::Error => "no-camera",
};
self.stack.set_visible_child_name(name);
}
}
}
glib::wrapper! {
pub struct QrCodeScanner(ObjectSubclass<imp::QrCodeScanner>)
@extends gtk::Widget, adw::Bin;
}
impl QrCodeScanner {
pub async fn new() -> Option<Self> {
let viewfinder = Camera::viewfinder().await?;
let obj = glib::Object::builder()
.property("viewfinder", viewfinder)
.build();
Some(obj)
}
pub fn connect_qrcode_detected<F: Fn(&Self, QrVerificationData) + 'static>(
&self,
f: F,
) -> glib::SignalHandlerId {
self.connect_closure(
"qrcode-detected",
true,
closure_local!(move |obj: Self, data: QrVerificationDataBoxed| {
f(&obj, data.0);
}),
)
}
}