code: Use new glib::clone! syntax

This commit is contained in:
Sophie Herold 2024-06-18 00:37:38 +02:00
parent 44bd845be4
commit e9dc3d810c
16 changed files with 801 additions and 464 deletions

View file

@ -99,11 +99,13 @@ impl LpFileModel {
directory.monitor_directory(gio::FileMonitorFlags::WATCH_MOVES, gio::Cancellable::NONE); directory.monitor_directory(gio::FileMonitorFlags::WATCH_MOVES, gio::Cancellable::NONE);
match monitor { match monitor {
Ok(monitor) => { Ok(monitor) => {
monitor.connect_changed( monitor.connect_changed(glib::clone!(
glib::clone!(@weak self as obj => move |_monitor, file_a, file_b, event| { #[weak(rename_to = obj)]
self,
move |_monitor, file_a, file_b, event| {
obj.file_monitor_cb(event, file_a, file_b); obj.file_monitor_cb(event, file_a, file_b);
}), }
); ));
self.imp().monitor.set(monitor).unwrap(); self.imp().monitor.set(monitor).unwrap();
} }
Err(err) => { Err(err) => {

View file

@ -98,12 +98,14 @@ mod imp {
} }
pub fn set_drop_target(&self, drop_target: gtk::DropTarget) { pub fn set_drop_target(&self, drop_target: gtk::DropTarget) {
drop_target.connect_current_drop_notify( drop_target.connect_current_drop_notify(glib::clone!(
glib::clone!(@weak self.revealer as revealer => move |target| { #[weak(rename_to = revealer)]
self.revealer,
move |target| {
let reveal = target.current_drop().is_some(); let reveal = target.current_drop().is_some();
revealer.set_reveal_child(reveal); revealer.set_reveal_child(reveal);
}), }
); ));
self.drop_target.set(drop_target).unwrap(); self.drop_target.set(drop_target).unwrap();

View file

@ -70,23 +70,29 @@ impl LpErrorDetails {
let imp = obj.imp(); let imp = obj.imp();
imp.message.buffer().set_text(text); imp.message.buffer().set_text(text);
imp.copy imp.copy.connect_clicked(glib::clone!(
.connect_clicked(glib::clone!(@weak obj => move |_| { #[weak]
obj,
move |_| {
let buffer = obj.imp().message.buffer(); let buffer = obj.imp().message.buffer();
let (start, end) = buffer.bounds(); let (start, end) = buffer.bounds();
obj.display() obj.display()
.clipboard() .clipboard()
.set_text(buffer.text(&start, &end, true).as_str()) .set_text(buffer.text(&start, &end, true).as_str())
})); }
));
imp.report imp.report.connect_clicked(glib::clone!(
.connect_clicked(glib::clone!(@weak obj => move |_| { #[weak]
obj,
move |_| {
gtk::UriLauncher::new("https://gitlab.gnome.org/sophie-h/glycin/-/issues").launch( gtk::UriLauncher::new("https://gitlab.gnome.org/sophie-h/glycin/-/issues").launch(
obj.root().and_downcast_ref::<gtk::Window>(), obj.root().and_downcast_ref::<gtk::Window>(),
gio::Cancellable::NONE, gio::Cancellable::NONE,
|_| {}, |_| {},
); );
})); }
));
obj.present(Some(root)); obj.present(Some(root));
obj obj

View file

@ -289,31 +289,47 @@ mod imp {
surface_signals.connect_notify_local( surface_signals.connect_notify_local(
Some("scale"), Some("scale"),
glib::clone!(@weak obj => move |_, _| { glib::clone!(
log::debug!("Scale changed signal"); #[weak]
obj.queue_resize(); obj,
}), move |_, _| {
log::debug!("Scale changed signal");
obj.queue_resize();
}
),
); );
obj.connect_realize(glib::clone!(@weak surface_signals => move |obj| { obj.connect_realize(glib::clone!(
surface_signals.set_target(obj.native().and_then(|x| x.surface()).as_ref()); #[weak]
})); surface_signals,
move |obj| {
surface_signals.set_target(obj.native().and_then(|x| x.surface()).as_ref());
}
));
obj.connect_unrealize(glib::clone!(@weak surface_signals => move |_| { obj.connect_unrealize(glib::clone!(
surface_signals.set_target(gdk::Surface::NONE); #[weak]
})); surface_signals,
move |_| {
surface_signals.set_target(gdk::Surface::NONE);
}
));
adw::StyleManager::default().connect_dark_notify(glib::clone!(@weak obj => move |_| { adw::StyleManager::default().connect_dark_notify(glib::clone!(
glib::spawn_future_local(async move { #[weak]
let imp = obj.imp(); obj,
move |_| {
glib::spawn_future_local(async move {
let imp = obj.imp();
let color = imp.background_color_guess().await; let color = imp.background_color_guess().await;
imp.set_background_color(color); imp.set_background_color(color);
if obj.is_mapped() { if obj.is_mapped() {
obj.queue_draw(); obj.queue_draw();
} }
}); });
})); }
));
} }
fn dispose(&self) { fn dispose(&self) {

View file

@ -29,75 +29,102 @@ impl imp::LpImage {
// Needed for having the current cursor position available // Needed for having the current cursor position available
let motion_controller = gtk::EventControllerMotion::new(); let motion_controller = gtk::EventControllerMotion::new();
motion_controller.connect_enter(glib::clone!(@weak obj => move |_, x, y| { motion_controller.connect_enter(glib::clone!(
obj.imp().pointer_position.set(Some((x, y))); #[weak]
})); obj,
motion_controller.connect_motion(glib::clone!(@weak obj => move |_, x, y| { move |_, x, y| {
obj.imp().pointer_position.set(Some((x, y))); obj.imp().pointer_position.set(Some((x, y)));
})); }
motion_controller.connect_leave(glib::clone!(@weak obj => move |_| { ));
obj.imp().pointer_position.set(None); motion_controller.connect_motion(glib::clone!(
})); #[weak]
obj,
move |_, x, y| {
obj.imp().pointer_position.set(Some((x, y)));
}
));
motion_controller.connect_leave(glib::clone!(
#[weak]
obj,
move |_| {
obj.imp().pointer_position.set(None);
}
));
obj.add_controller(motion_controller); obj.add_controller(motion_controller);
// Zoom via scroll wheels etc // Zoom via scroll wheels etc
let scroll_controller = let scroll_controller =
gtk::EventControllerScroll::new(gtk::EventControllerScrollFlags::BOTH_AXES); gtk::EventControllerScroll::new(gtk::EventControllerScrollFlags::BOTH_AXES);
scroll_controller.connect_scroll_end(glib::clone!(@weak obj => move |event| { scroll_controller.connect_scroll_end(glib::clone!(
// Avoid kinetc scrolling in scrolled window after zooming #[weak]
if event.current_event_state().contains(gdk::ModifierType::CONTROL_MASK) { obj,
obj.imp().cancel_deceleration(); move |event| {
} // Avoid kinetc scrolling in scrolled window after zooming
})); if event
.current_event_state()
scroll_controller.connect_scroll(glib::clone!(@weak obj => @default-return glib::Propagation::Proceed, move |event, _, y| { .contains(gdk::ModifierType::CONTROL_MASK)
let imp = obj.imp();
let state = event.current_event_state();
if event.current_event_device().map(|x| x.source()) == Some(gdk::InputSource::Touchpad)
{
// Touchpads do zoom via gestures, expect when Ctrl key is pressed
if !state.contains(gdk::ModifierType::CONTROL_MASK) {
// propagate event to scrolled window
return glib::Propagation::Proceed;
}
} else {
// use Ctrl key as modifier for vertical scrolling
if state.contains(gdk::ModifierType::CONTROL_MASK)
|| state.contains(gdk::ModifierType::SHIFT_MASK)
{ {
// propagate event to scrolled window obj.imp().cancel_deceleration();
return glib::Propagation::Proceed;
} }
} }
));
// Use exponential scaling since zoom is always multiplicative with the existing value scroll_controller.connect_scroll(glib::clone!(
// This is the right thing since `exp(n/2)^2 == exp(n)` (two small steps are the same as one larger step) #[weak]
let (zoom_factor, animated) = match event.unit() { obj,
gdk::ScrollUnit::Wheel => ( #[upgrade_or_else]
f64::exp(-y * f64::ln(ZOOM_FACTOR_SCROLL_WHEEL)), || glib::Propagation::Proceed,
y.abs() >= 1., move |event, _, y| {
), let imp = obj.imp();
gdk::ScrollUnit::Surface => { let state = event.current_event_state();
(f64::exp(-y * f64::ln(ZOOM_FACTOR_SCROLL_SURFACE)), false) if event.current_event_device().map(|x| x.source())
== Some(gdk::InputSource::Touchpad)
{
// Touchpads do zoom via gestures, expect when Ctrl key is pressed
if !state.contains(gdk::ModifierType::CONTROL_MASK) {
// propagate event to scrolled window
return glib::Propagation::Proceed;
}
} else {
// use Ctrl key as modifier for vertical scrolling
if state.contains(gdk::ModifierType::CONTROL_MASK)
|| state.contains(gdk::ModifierType::SHIFT_MASK)
{
// propagate event to scrolled window
return glib::Propagation::Proceed;
}
} }
unknown_unit => {
log::warn!("Ignoring unknown scroll unit: {unknown_unit:?}"); // Use exponential scaling since zoom is always multiplicative with the existing
(1., false) // value This is the right thing since `exp(n/2)^2 == exp(n)`
// (two small steps are the same as one larger step)
let (zoom_factor, animated) = match event.unit() {
gdk::ScrollUnit::Wheel => (
f64::exp(-y * f64::ln(ZOOM_FACTOR_SCROLL_WHEEL)),
y.abs() >= 1.,
),
gdk::ScrollUnit::Surface => {
(f64::exp(-y * f64::ln(ZOOM_FACTOR_SCROLL_SURFACE)), false)
}
unknown_unit => {
log::warn!("Ignoring unknown scroll unit: {unknown_unit:?}");
(1., false)
}
};
let zoom = imp.zoom_target.get() * zoom_factor;
if animated {
obj.zoom_to(zoom);
} else {
imp.zoom_to_full(zoom, false, false, false);
} }
};
let zoom = imp.zoom_target.get() * zoom_factor; // do not propagate event to scrolled window
glib::Propagation::Stop
if animated {
obj.zoom_to(zoom);
} else {
imp.zoom_to_full(zoom, false, false, false);
} }
));
// do not propagate event to scrolled window
glib::Propagation::Stop
}));
obj.add_controller(scroll_controller); obj.add_controller(scroll_controller);
} }
@ -108,8 +135,10 @@ impl imp::LpImage {
// Double click for fullscreen (mouse/touchpad) or zoom (touch screen) // Double click for fullscreen (mouse/touchpad) or zoom (touch screen)
let left_click_gesture = gtk::GestureClick::builder().button(1).build(); let left_click_gesture = gtk::GestureClick::builder().button(1).build();
obj.add_controller(left_click_gesture.clone()); obj.add_controller(left_click_gesture.clone());
left_click_gesture.connect_pressed( left_click_gesture.connect_pressed(glib::clone!(
glib::clone!(@weak obj => move |gesture, n_press, x, y| { #[weak]
obj,
move |gesture, n_press, x, y| {
// only handle double clicks // only handle double clicks
if n_press != 2 { if n_press != 2 {
return; return;
@ -129,150 +158,184 @@ impl imp::LpImage {
// fullscreen // fullscreen
obj.activate_action("win.toggle-fullscreen", None).unwrap(); obj.activate_action("win.toggle-fullscreen", None).unwrap();
} }
}), }
); ));
// Drag for moving image around // Drag for moving image around
let drag_gesture = gtk::GestureDrag::builder().button(0).build(); let drag_gesture = gtk::GestureDrag::builder().button(0).build();
obj.add_controller(drag_gesture.clone()); obj.add_controller(drag_gesture.clone());
drag_gesture.connect_drag_begin(glib::clone!(@weak obj => move |gesture, _, _| { drag_gesture.connect_drag_begin(glib::clone!(
let imp = obj.imp(); #[weak]
obj,
move |gesture, _, _| {
let imp = obj.imp();
// Allow only left and middle button // Allow only left and middle button
if ![1, 2].contains(&gesture.current_button()) if ![1, 2].contains(&gesture.current_button())
// Drag gesture for touchscreens is handled by ScrolledWindow // Drag gesture for touchscreens is handled by ScrolledWindow
|| gesture.device().map(|x| x.source()) == Some(gdk::InputSource::Touchscreen) || gesture.device().map(|x| x.source()) == Some(gdk::InputSource::Touchscreen)
{ {
gesture.set_state(gtk::EventSequenceState::Denied); gesture.set_state(gtk::EventSequenceState::Denied);
return; return;
}
if obj.is_hscrollable() || obj.is_vscrollable() {
imp.cancel_deceleration();
obj.set_cursor(gdk::Cursor::from_name("grabbing", None).as_ref());
imp.last_drag_value.set(Some((0., 0.)));
} else {
// let drag and drop handle the events when not scrollable
gesture.set_state(gtk::EventSequenceState::Denied);
}
} }
));
if obj.is_hscrollable() || obj.is_vscrollable() { drag_gesture.connect_drag_update(glib::clone!(
imp.cancel_deceleration(); #[weak]
obj.set_cursor(gdk::Cursor::from_name("grabbing", None).as_ref()); obj,
imp.last_drag_value.set(Some((0., 0.))); move |_, x1, y1| {
} else { let imp = obj.imp();
// let drag and drop handle the events when not scrollable if let Some((x0, y0)) = obj.imp().last_drag_value.get() {
gesture.set_state(gtk::EventSequenceState::Denied); imp.set_hadj_value(imp.hadj_value() - x1 + x0);
imp.set_vadj_value(imp.vadj_value() - y1 + y0);
}
obj.imp().last_drag_value.set(Some((x1, y1)));
} }
})); ));
drag_gesture.connect_drag_update(glib::clone!(@weak obj => move |_, x1, y1| { drag_gesture.connect_drag_end(glib::clone!(
let imp = obj.imp(); #[weak]
if let Some((x0, y0)) = obj.imp().last_drag_value.get() { obj,
imp.set_hadj_value(imp.hadj_value() - x1 + x0); move |_, _, _| {
imp.set_vadj_value(imp.vadj_value() - y1 + y0); obj.set_cursor(None);
obj.imp().last_drag_value.set(None);
} }
));
obj.imp().last_drag_value.set(Some((x1, y1)));
}));
drag_gesture.connect_drag_end(glib::clone!(@weak obj => move |_, _, _| {
obj.set_cursor(None);
obj.imp().last_drag_value.set(None);
}));
// Rotate // Rotate
let rotation_gesture = gtk::GestureRotate::new(); let rotation_gesture = gtk::GestureRotate::new();
obj.add_controller(rotation_gesture.clone()); obj.add_controller(rotation_gesture.clone());
rotation_gesture rotation_gesture.connect_begin(glib::clone!(
.connect_begin(glib::clone!(@weak obj => move |_, _| obj.imp().cancel_deceleration())); #[weak]
obj,
move |_, _| obj.imp().cancel_deceleration()
));
rotation_gesture.connect_angle_changed(glib::clone!(@weak obj => move |gesture, _, _| { rotation_gesture.connect_angle_changed(glib::clone!(
let angle = gesture.angle_delta().to_degrees(); #[weak]
obj,
move |gesture, _, _| {
let angle = gesture.angle_delta().to_degrees();
// Only reset rotation if scale gesture is locked in // Only reset rotation if scale gesture is locked in
if let Some(Gesture::Scale) = obj.imp().locked_gestured.get() { if let Some(Gesture::Scale) = obj.imp().locked_gestured.get() {
obj.imp().rotation.set(obj.imp().rotation_target.get()); obj.imp().rotation.set(obj.imp().rotation_target.get());
return;
}
// Correct angle by the the angle at the moment of passing the threshold.
// This stops the rotation from suddenly jumping when passing the threshold.
let correction =
if let Some(Gesture::Rotate(correction)) = obj.imp().locked_gestured.get() {
correction
} else if angle.abs() > ROTATE_GESTURE_LOCK_THRESHOLD {
let correction = angle.signum() * ROTATE_GESTURE_LOCK_THRESHOLD;
obj.imp()
.locked_gestured
.set(Some(Gesture::Rotate(correction)));
correction
} else {
return; return;
}; }
obj.set_rotation(obj.imp().rotation_target.get() + angle - correction); // Correct angle by the the angle at the moment of passing the threshold.
})); // This stops the rotation from suddenly jumping when passing the threshold.
let correction =
if let Some(Gesture::Rotate(correction)) = obj.imp().locked_gestured.get() {
correction
} else if angle.abs() > ROTATE_GESTURE_LOCK_THRESHOLD {
let correction = angle.signum() * ROTATE_GESTURE_LOCK_THRESHOLD;
obj.imp()
.locked_gestured
.set(Some(Gesture::Rotate(correction)));
correction
} else {
return;
};
rotation_gesture.connect_end(glib::clone!(@weak obj => move |_, _| { obj.set_rotation(obj.imp().rotation_target.get() + angle - correction);
log::debug!("Rotate gesture ended"); }
));
let angle = (obj.rotation() / 90.).round() * 90. - obj.imp().rotation_target.get(); rotation_gesture.connect_end(glib::clone!(
obj.rotate_by(angle); #[weak]
obj.imp().locked_gestured.set(None); obj,
})); move |_, _| {
log::debug!("Rotate gesture ended");
let angle = (obj.rotation() / 90.).round() * 90. - obj.imp().rotation_target.get();
obj.rotate_by(angle);
obj.imp().locked_gestured.set(None);
}
));
// Zoom // Zoom
let zoom_gesture = gtk::GestureZoom::new(); let zoom_gesture = gtk::GestureZoom::new();
obj.add_controller(zoom_gesture.clone()); obj.add_controller(zoom_gesture.clone());
zoom_gesture.connect_begin(glib::clone!(@weak obj => move |gesture, _| { zoom_gesture.connect_begin(glib::clone!(
let imp = obj.imp(); #[weak]
imp.cancel_deceleration(); obj,
imp.zoom_gesture_center.set(gesture.bounding_box_center()); move |gesture, _| {
})); let imp = obj.imp();
imp.cancel_deceleration();
imp.zoom_gesture_center.set(gesture.bounding_box_center());
}
));
zoom_gesture.connect_scale_changed(glib::clone!(@weak obj => move |gesture, scale| { zoom_gesture.connect_scale_changed(glib::clone!(
let imp = obj.imp(); #[weak]
obj,
move |gesture, scale| {
let imp = obj.imp();
let zoom = imp.zoom_target.get() * scale; let zoom = imp.zoom_target.get() * scale;
// Move image with fingers on touchscreens // Move image with fingers on touchscreens
if gesture.device().map(|x| x.source()) == Some(gdk::InputSource::Touchscreen) { if gesture.device().map(|x| x.source()) == Some(gdk::InputSource::Touchscreen) {
if let p1 @ Some((x1, y1)) = gesture.bounding_box_center() { if let p1 @ Some((x1, y1)) = gesture.bounding_box_center() {
if let Some((x0, y0)) = imp.zoom_gesture_center.get() {
imp.set_hadj_value(imp.hadj_value() + x0 - x1);
imp.set_vadj_value(imp.vadj_value() + y0 - y1);
} else {
log::warn!("Zoom bounding box center: No previous value");
}
if let Some((x0, y0)) = imp.zoom_gesture_center.get() { imp.zoom_gesture_center.set(p1);
imp.set_hadj_value(imp.hadj_value() + x0 - x1);
imp.set_vadj_value(imp.vadj_value()+ y0 - y1);
} else {
log::warn!("Zoom bounding box center: No previous value");
} }
imp.zoom_gesture_center.set(p1);
} }
let zoom_out_threshold = 1. / ZOOM_GESTURE_LOCK_THRESHOLD;
let zoom_in_threshold = ZOOM_GESTURE_LOCK_THRESHOLD;
if let Some(Gesture::Rotate(_)) = imp.locked_gestured.get() {
// Do not zoom when rotate is locked in
return;
} else if !(zoom_out_threshold..zoom_in_threshold).contains(&scale) {
// Lock in scale when leaving the scale threshold
imp.locked_gestured.set(Some(Gesture::Scale));
}
imp.set_zoom_aiming(zoom, imp.zoom_gesture_center.get());
} }
));
let zoom_out_threshold = 1. / ZOOM_GESTURE_LOCK_THRESHOLD; zoom_gesture.connect_end(glib::clone!(
let zoom_in_threshold = ZOOM_GESTURE_LOCK_THRESHOLD; #[weak]
obj,
move |_, _| {
let imp = obj.imp();
log::debug!("Zoom gesture ended");
if let Some(Gesture::Rotate(_)) = imp.locked_gestured.get() { let rotation_target = (obj.rotation() / 90.).round() * 90.;
// Do not zoom when rotate is locked in if obj.zoom() < imp.zoom_level_best_fit_for_rotation(rotation_target) {
return; obj.zoom_to(imp.zoom_level_best_fit_for_rotation(rotation_target));
} else if !(zoom_out_threshold..zoom_in_threshold).contains(&scale) { } else {
// Lock in scale when leaving the scale threshold // rubberband if over highest zoom level and sets `zoom_target`
imp.locked_gestured.set(Some(Gesture::Scale)); obj.zoom_to(obj.zoom());
};
imp.locked_gestured.set(None);
} }
));
imp.set_zoom_aiming(zoom, imp.zoom_gesture_center.get());
}));
zoom_gesture.connect_end(glib::clone!(@weak obj => move |_, _| {
let imp = obj.imp();
log::debug!("Zoom gesture ended");
let rotation_target = (obj.rotation() / 90.).round() * 90.;
if obj.zoom() < imp.zoom_level_best_fit_for_rotation(rotation_target) {
obj.zoom_to(imp.zoom_level_best_fit_for_rotation(rotation_target));
} else {
// rubberband if over highest zoom level and sets `zoom_target`
obj.zoom_to(obj.zoom());
};
imp.locked_gestured.set(None);
}));
zoom_gesture.group_with(&rotation_gesture); zoom_gesture.group_with(&rotation_gesture);
} }

View file

@ -52,9 +52,13 @@ impl imp::LpImage {
let monitor = let monitor =
file.monitor_file(gio::FileMonitorFlags::WATCH_MOVES, gio::Cancellable::NONE); file.monitor_file(gio::FileMonitorFlags::WATCH_MOVES, gio::Cancellable::NONE);
if let Ok(m) = &monitor { if let Ok(m) = &monitor {
m.connect_changed(glib::clone!(@weak obj => move |_, file_a, file_b, event| { m.connect_changed(glib::clone!(
obj.imp().file_changed(event, file_a, file_b); #[weak]
})); obj,
move |_, file_a, file_b, event| {
obj.imp().file_changed(event, file_a, file_b);
}
));
} }
self.file_monitor.replace(monitor.ok()); self.file_monitor.replace(monitor.ok());
@ -90,15 +94,19 @@ impl imp::LpImage {
new_tiles new_tiles
}); });
if self.background_color.borrow().is_none() { if self.background_color.borrow().is_none() {
glib::spawn_future_local(glib::clone!(@weak obj => async move { glib::spawn_future_local(glib::clone!(
let imp = obj.imp(); #[weak]
obj,
async move {
let imp = obj.imp();
let color = imp.background_color_guess().await; let color = imp.background_color_guess().await;
imp.set_background_color(color); imp.set_background_color(color);
if obj.is_mapped() { if obj.is_mapped() {
obj.queue_draw(); obj.queue_draw();
}
} }
})); ));
} }
} }
DecoderUpdate::Error(err) => { DecoderUpdate::Error(err) => {
@ -109,8 +117,13 @@ impl imp::LpImage {
// Just load the first frame // Just load the first frame
self.frame_buffer.next_frame(); self.frame_buffer.next_frame();
} else { } else {
let callback_id = obj let callback_id = obj.add_tick_callback(glib::clone!(
.add_tick_callback(glib::clone!(@weak self as obj => @default-return glib::ControlFlow::Break, move |_, clock| obj.tick_callback(clock))); #[weak(rename_to = obj)]
self,
#[upgrade_or_else]
|| glib::ControlFlow::Break,
move |_, clock| obj.tick_callback(clock)
));
self.tick_callback.replace(Some(callback_id)); self.tick_callback.replace(Some(callback_id));
} }
} }

View file

@ -28,10 +28,14 @@ impl imp::LpImage {
let adjustment = hadjustment.unwrap_or_default(); let adjustment = hadjustment.unwrap_or_default();
adjustment.connect_value_changed(glib::clone!(@weak obj => move |_| { adjustment.connect_value_changed(glib::clone!(
obj.imp().request_tiles(); #[weak]
obj.queue_draw(); obj,
})); move |_| {
obj.imp().request_tiles();
obj.queue_draw();
}
));
self.hadjustment.replace(adjustment); self.hadjustment.replace(adjustment);
self.configure_adjustments(); self.configure_adjustments();
@ -42,10 +46,14 @@ impl imp::LpImage {
let adjustment = vadjustment.unwrap_or_default(); let adjustment = vadjustment.unwrap_or_default();
adjustment.connect_value_changed(glib::clone!(@weak obj => move |_| { adjustment.connect_value_changed(glib::clone!(
obj.imp().request_tiles(); #[weak]
obj.queue_draw(); obj,
})); move |_| {
obj.imp().request_tiles();
obj.queue_draw();
}
));
self.vadjustment.replace(adjustment); self.vadjustment.replace(adjustment);
self.configure_adjustments(); self.configure_adjustments();

View file

@ -264,11 +264,13 @@ impl imp::LpImage {
let animation = self.zoom_animation(); let animation = self.zoom_animation();
if force_cursor_center { if force_cursor_center {
animation.set_target(&adw::CallbackAnimationTarget::new( animation.set_target(&adw::CallbackAnimationTarget::new(glib::clone!(
glib::clone!(@weak obj => move |zoom| { #[weak]
obj,
move |zoom| {
obj.imp().set_zoom_aiming(zoom, None); obj.imp().set_zoom_aiming(zoom, None);
}), }
)); )));
} else { } else {
// Set new point in image that should remain under the cursor while zooming if // Set new point in image that should remain under the cursor while zooming if
// there isn't one already // there isn't one already
@ -304,11 +306,15 @@ impl imp::LpImage {
.target(&adw::CallbackAnimationTarget::new(|_| {})) .target(&adw::CallbackAnimationTarget::new(|_| {}))
.build(); .build();
animation.connect_done(glib::clone!(@weak obj => move |_| { animation.connect_done(glib::clone!(
let imp = obj.imp(); #[weak]
imp.zoom_cursor_target.set(None); obj,
imp.set_zoom_target(obj.imp().zoom_target.get()); move |_| {
})); let imp = obj.imp();
imp.zoom_cursor_target.set(None);
imp.set_zoom_target(obj.imp().zoom_target.get());
}
));
animation animation
}) })

View file

@ -94,42 +94,57 @@ mod imp {
self.parent_constructed(); self.parent_constructed();
self.right_click_gesture self.right_click_gesture.connect_pressed(clone!(
.connect_pressed(clone!(@weak obj => move |gesture, _, x, y| { #[weak]
obj,
move |gesture, _, x, y| {
obj.show_popover_at(x, y); obj.show_popover_at(x, y);
gesture.set_state(gtk::EventSequenceState::Claimed); gesture.set_state(gtk::EventSequenceState::Claimed);
})); }
));
self.press_gesture self.press_gesture.connect_pressed(clone!(
.connect_pressed(clone!(@weak obj => move |gesture, x, y| { #[weak]
obj,
move |gesture, x, y| {
log::debug!("Long press triggered"); log::debug!("Long press triggered");
obj.show_popover_at(x, y); obj.show_popover_at(x, y);
gesture.set_state(gtk::EventSequenceState::Claimed); gesture.set_state(gtk::EventSequenceState::Claimed);
})); }
));
self.stack self.stack.connect_visible_child_notify(clone!(
.connect_visible_child_notify(clone!(@weak obj => move |stack| { #[weak]
obj,
move |stack| {
let imp = obj.imp(); let imp = obj.imp();
let spinner_visible = let spinner_visible =
stack.visible_child().as_ref() == Some(imp.spinner_page.upcast_ref()); stack.visible_child().as_ref() == Some(imp.spinner_page.upcast_ref());
// Hide spinner for crossfading into other content since it does not look great // Hide spinner for crossfading into other content since it does not look great
imp.spinner.set_visible(spinner_visible); imp.spinner.set_visible(spinner_visible);
})); }
));
obj.image() obj.image().connect_is_loaded_notify(clone!(
.connect_is_loaded_notify(clone!(@weak obj => move |_| { #[weak]
obj,
move |_| {
if obj.image().is_loaded() { if obj.image().is_loaded() {
log::debug!("Showing image"); log::debug!("Showing image");
obj.imp() obj.imp()
.stack .stack
.set_visible_child(&*obj.imp().image_stack_page); .set_visible_child(&*obj.imp().image_stack_page);
} }
})); }
));
obj.image() obj.image().connect_error_notify(clone!(
.connect_error_notify(clone!(@weak obj => move |_| { #[weak]
obj,
move |_| {
glib::spawn_future_local(async move { obj.show_error().await }); glib::spawn_future_local(async move { obj.show_error().await });
})); }
));
// Do not waste CPU on spinner if it is not visible // Do not waste CPU on spinner if it is not visible
self.spinner.connect_map(|s| s.start()); self.spinner.connect_map(|s| s.start());
@ -159,10 +174,16 @@ impl LpImagePage {
obj.imp().image.init(file); obj.imp().image.init(file);
glib::spawn_future_local(clone!(@weak obj, @strong file => async move { glib::spawn_future_local(clone!(
let imp = obj.imp(); #[weak]
imp.image.load(&file).await; obj,
})); #[strong]
file,
async move {
let imp = obj.imp();
imp.image.load(&file).await;
}
));
obj obj
} }
@ -225,10 +246,13 @@ impl LpImagePage {
imp.stack.set_visible_child(&*imp.error_page); imp.stack.set_visible_child(&*imp.error_page);
} else if let Some(err) = image.error() { } else if let Some(err) = image.error() {
imp.error_more_info imp.error_more_info.connect_clicked(glib::clone!(
.connect_clicked(glib::clone!(@weak self as obj => move |_| { #[weak(rename_to = obj)]
self,
move |_| {
LpErrorDetails::new(&obj.root().unwrap(), &err); LpErrorDetails::new(&obj.root().unwrap(), &err);
})); }
));
imp.error_page.set_description(Some(&gettext( imp.error_page.set_description(Some(&gettext(
"Either the image file is corrupted or it contains unsupported elements.", "Either the image file is corrupted or it contains unsupported elements.",

View file

@ -136,45 +136,65 @@ mod imp {
// Manually manage widget layout, see `WidgetImpl` for details // Manually manage widget layout, see `WidgetImpl` for details
obj.set_layout_manager(None::<gtk::LayoutManager>); obj.set_layout_manager(None::<gtk::LayoutManager>);
self.sliding_view.connect_current_page_notify( self.sliding_view.connect_current_page_notify(glib::clone!(
glib::clone!(@weak obj => move |_| obj.page_changed()), #[weak]
); obj,
move |_| obj.page_changed()
));
self.sliding_view.connect_target_page_reached( self.sliding_view.connect_target_page_reached(glib::clone!(
glib::clone!(@weak obj => move || obj.target_page_reached()), #[weak]
); obj,
move || obj.target_page_reached()
));
let signal_group = glib::SignalGroup::new::<LpImage>(); let signal_group = glib::SignalGroup::new::<LpImage>();
obj.connect_notify_local( obj.connect_notify_local(
Some("current-page"), Some("current-page"),
clone!(@weak signal_group => move |obj, _| { clone!(
signal_group.set_target(obj.current_image().as_ref()); #[weak]
}), signal_group,
move |obj, _| {
signal_group.set_target(obj.current_image().as_ref());
}
),
); );
let view = &*obj; let view = &*obj;
signal_group.connect_closure( signal_group.connect_closure(
"notify::file", "notify::file",
true, true,
glib::closure_local!(@watch view => move |_: LpImage, _: glib::ParamSpec| { glib::closure_local!(
view.current_image_path_changed(); #[watch]
}), view,
move |_: LpImage, _: glib::ParamSpec| {
view.current_image_path_changed();
}
),
); );
signal_group.connect_closure( signal_group.connect_closure(
"notify::is-deleted", "notify::is-deleted",
true, true,
glib::closure_local!(@watch view => move |_: LpImage, _: glib::ParamSpec| { glib::closure_local!(
view.current_image_path_changed(); #[watch]
}), view,
move |_: LpImage, _: glib::ParamSpec| {
view.current_image_path_changed();
}
),
); );
self.current_image_signals.set(signal_group).unwrap(); self.current_image_signals.set(signal_group).unwrap();
self.drag_source.set_exclusive(true); self.drag_source.set_exclusive(true);
self.drag_source.connect_prepare( self.drag_source.connect_prepare(glib::clone!(
glib::clone!(@weak obj => @default-return None, move |gesture, _, _| { #[weak]
obj,
#[upgrade_or_else]
|| None,
move |gesture, _, _| {
let is_scrollable = obj let is_scrollable = obj
.current_page() .current_page()
.map(|p| p.image().is_hscrollable() || p.image().is_vscrollable()); .map(|p| p.image().is_hscrollable() || p.image().is_vscrollable());
@ -190,22 +210,30 @@ mod imp {
gesture.set_state(gtk::EventSequenceState::Claimed); gesture.set_state(gtk::EventSequenceState::Claimed);
obj.current_page().and_then(|p| p.content_provider()) obj.current_page().and_then(|p| p.content_provider())
} }
}), }
); ));
self.drag_source.connect_drag_begin(glib::clone!(@weak obj => move |source, _| { self.drag_source.connect_drag_begin(glib::clone!(
if let Some(paintable) = obj.current_image().and_then(|p| p.thumbnail()) { #[weak]
// -6 for cursor width, +16 for margin in .drag-icon obj,
source.set_icon(Some(&paintable), paintable.intrinsic_width() / 2 - 6 + 16, -12); move |source, _| {
if let Some(drag) = source.drag() { if let Some(paintable) = obj.current_image().and_then(|p| p.thumbnail()) {
let drag_icon = gtk::DragIcon::for_drag(&drag); // -6 for cursor width, +16 for margin in .drag-icon
// Rounds corners, adds outline and shadow source.set_icon(
drag_icon.add_css_class("drag-icon"); Some(&paintable),
// Make border-radius clip the image paintable.intrinsic_width() / 2 - 6 + 16,
drag_icon.set_overflow(gtk::Overflow::Hidden); -12,
} );
}; if let Some(drag) = source.drag() {
})); let drag_icon = gtk::DragIcon::for_drag(&drag);
// Rounds corners, adds outline and shadow
drag_icon.add_css_class("drag-icon");
// Make border-radius clip the image
drag_icon.set_overflow(gtk::Overflow::Hidden);
}
};
}
));
obj.add_controller(self.drag_source.clone()); obj.add_controller(self.drag_source.clone());
} }
@ -340,16 +368,29 @@ impl LpImageView {
// List other files in directory // List other files in directory
if let Some(directory) = directory { if let Some(directory) = directory {
glib::spawn_future_local(glib::clone!(@weak self as obj => async move { glib::spawn_future_local(glib::clone!(
if let Err(err) = obj.model().load_directory(directory.clone()).await { #[weak(rename_to = obj)]
log::warn!("Failed to load directory: {}", err.root_cause()); self,
obj.activate_action("win.show-toast", Some(&(format!("{} {}", err, err.root_cause()), adw::ToastPriority::High.into_glib()).to_variant())) async move {
if let Err(err) = obj.model().load_directory(directory.clone()).await {
log::warn!("Failed to load directory: {}", err.root_cause());
obj.activate_action(
"win.show-toast",
Some(
&(
format!("{} {}", err, err.root_cause()),
adw::ToastPriority::High.into_glib(),
)
.to_variant(),
),
)
.unwrap(); .unwrap();
return; return;
} }
obj.update_sliding_view(&file); obj.update_sliding_view(&file);
})); }
));
} }
} }
@ -498,33 +539,39 @@ impl LpImageView {
page.image().connect_notify_local( page.image().connect_notify_local(
Some("is-unsupported"), Some("is-unsupported"),
glib::clone!(@weak self as obj => move |image, _| { glib::clone!(
if image.is_unsupported() { #[weak(rename_to = obj)]
if obj.current_image().as_ref() == Some(image) { self,
log::debug!( move |image, _| {
"Image format unsupported but not removing since current image" if image.is_unsupported() {
); if obj.current_image().as_ref() == Some(image) {
return; log::debug!(
} "Image format unsupported but not removing since current image"
);
return;
}
if let Some(file) = image.file() { if let Some(file) = image.file() {
log::debug!("Removing image with unsupported format {:?}", file.uri()); log::debug!("Removing image with unsupported format {:?}", file.uri());
obj.model().remove(&file); obj.model().remove(&file);
if let Some(current_file) = obj.current_file() { if let Some(current_file) = obj.current_file() {
obj.update_sliding_view(&current_file); obj.update_sliding_view(&current_file);
}
} }
} }
} }
}), ),
); );
page page
} }
fn set_model(&self, model: LpFileModel) { fn set_model(&self, model: LpFileModel) {
model.connect_changed( model.connect_changed(glib::clone!(
glib::clone!(@weak self as obj => move || obj.model_content_changed_cb()), #[weak(rename_to = obj)]
); self,
move || obj.model_content_changed_cb()
));
self.imp().model.replace(model); self.imp().model.replace(model);
} }

View file

@ -294,8 +294,11 @@ mod imp {
obj.set_modal(true); obj.set_modal(true);
// Page alignment (center, left, ...) // Page alignment (center, left, ...)
self.alignment self.alignment.connect_selected_notify(glib::clone!(
.connect_selected_notify(glib::clone!(@weak obj => move |_| obj.draw_preview())); #[weak]
obj,
move |_| obj.draw_preview()
));
self.alignment self.alignment
.set_expression(Some(gtk::ClosureExpression::new::<glib::GString>( .set_expression(Some(gtk::ClosureExpression::new::<glib::GString>(
@ -306,27 +309,45 @@ mod imp {
))); )));
// Margin signals // Margin signals
self.margin_unit.connect_selected_notify( self.margin_unit.connect_selected_notify(glib::clone!(
glib::clone!(@weak obj => move |_| obj.on_margin_unit_changed()), #[weak]
); obj,
move |_| obj.on_margin_unit_changed()
));
self.margin_unit.set_expression(Some(Unit::expression())); self.margin_unit.set_expression(Some(Unit::expression()));
self.margin_horizontal self.margin_horizontal.connect_value_notify(glib::clone!(
.connect_value_notify(glib::clone!(@weak obj => move |_| obj.on_margin_changed())); #[weak]
self.margin_vertical obj,
.connect_value_notify(glib::clone!(@weak obj => move |_| obj.on_margin_changed())); move |_| obj.on_margin_changed()
));
self.margin_vertical.connect_value_notify(glib::clone!(
#[weak]
obj,
move |_| obj.on_margin_changed()
));
// Size signals // Size signals
self.size_unit.connect_selected_notify( self.size_unit.connect_selected_notify(glib::clone!(
glib::clone!(@weak obj => move |_| obj.on_size_unit_changed()), #[weak]
); obj,
move |_| obj.on_size_unit_changed()
));
self.size_unit.set_expression(Some(Unit::expression())); self.size_unit.set_expression(Some(Unit::expression()));
self.fill_space.connect_active_notify( self.fill_space.connect_active_notify(glib::clone!(
glib::clone!(@weak obj => move |_| obj.on_fill_space_changed()), #[weak]
); obj,
self.width move |_| obj.on_fill_space_changed()
.connect_value_notify(glib::clone!(@weak obj => move |_| obj.on_width_changed())); ));
self.height self.width.connect_value_notify(glib::clone!(
.connect_value_notify(glib::clone!(@weak obj => move |_| obj.on_height_changed())); #[weak]
obj,
move |_| obj.on_width_changed()
));
self.height.connect_value_notify(glib::clone!(
#[weak]
obj,
move |_| obj.on_height_changed()
));
let imp = self; let imp = self;

View file

@ -116,13 +116,26 @@ mod imp {
image_signals.connect_notify_local( image_signals.connect_notify_local(
Some("image-size-available"), Some("image-size-available"),
glib::clone!(@weak obj => move |_, _| obj.imp().update()), glib::clone!(
#[weak]
obj,
move |_, _| obj.imp().update()
),
); );
image_signals.connect_local( image_signals.connect_local(
"metadata-changed", "metadata-changed",
false, false,
glib::clone!(@weak obj => @default-return None, move |_| { obj.imp().update(); None }), glib::clone!(
#[weak]
obj,
#[upgrade_or_else]
|| None,
move |_| {
obj.imp().update();
None
}
),
); );
self.update(); self.update();

View file

@ -17,7 +17,6 @@
<child> <child>
<object class="AdwActionRow" id="folder"> <object class="AdwActionRow" id="folder">
<property name="title" translatable="yes">Folder</property> <property name="title" translatable="yes">Folder</property>
<property name="activatable-widget">folder_button</property>
<property name="use-markup">False</property> <property name="use-markup">False</property>
<child type="suffix"> <child type="suffix">
<object class="GtkButton" id="folder_button"> <object class="GtkButton" id="folder_button">

View file

@ -159,20 +159,32 @@ mod imp {
.upper_overshoot(true) .upper_overshoot(true)
.build(); .build();
swipe_tracker.connect_begin_swipe(glib::clone!(@weak obj => move |_| { swipe_tracker.connect_begin_swipe(glib::clone!(
obj.scroll_animation().pause(); #[weak]
obj.step_animation().pause(); obj,
})); move |_| {
obj.scroll_animation().pause();
swipe_tracker.connect_update_swipe(glib::clone!(@weak obj => move |_, position| { obj.step_animation().pause();
obj.set_position(position);
}));
swipe_tracker.connect_end_swipe(glib::clone!(@weak obj => move |_, velocity, to| {
if let Some(page) = obj.page_at(to) {
obj.scroll_to(&page, velocity);
} }
})); ));
swipe_tracker.connect_update_swipe(glib::clone!(
#[weak]
obj,
move |_, position| {
obj.set_position(position);
}
));
swipe_tracker.connect_end_swipe(glib::clone!(
#[weak]
obj,
move |_, velocity, to| {
if let Some(page) = obj.page_at(to) {
obj.scroll_to(&page, velocity);
}
}
));
self.swipe_tracker.set(swipe_tracker).unwrap(); self.swipe_tracker.set(swipe_tracker).unwrap();
@ -180,8 +192,12 @@ mod imp {
let scroll_controller = let scroll_controller =
gtk::EventControllerScroll::new(gtk::EventControllerScrollFlags::HORIZONTAL); gtk::EventControllerScroll::new(gtk::EventControllerScrollFlags::HORIZONTAL);
scroll_controller.connect_scroll( scroll_controller.connect_scroll(glib::clone!(
glib::clone!(@weak obj => @default-return glib::Propagation::Proceed, move |_, x, _| { #[weak]
obj,
#[upgrade_or_else]
|| glib::Propagation::Proceed,
move |_, x, _| {
let direction_sign = if obj.imp().is_rtl() { -1. } else { 1. }; let direction_sign = if obj.imp().is_rtl() { -1. } else { 1. };
if x * direction_sign > 0. { if x * direction_sign > 0. {
@ -207,8 +223,8 @@ mod imp {
glib::Propagation::Stop glib::Propagation::Stop
} }
} }
}), }
); ));
obj.add_controller(scroll_controller); obj.add_controller(scroll_controller);
} }
@ -712,9 +728,11 @@ impl LpSlidingView {
fn scroll_animation(&self) -> &adw::SpringAnimation { fn scroll_animation(&self) -> &adw::SpringAnimation {
self.imp().scroll_animation.get_or_init(|| { self.imp().scroll_animation.get_or_init(|| {
let target = adw::CallbackAnimationTarget::new( let target = adw::CallbackAnimationTarget::new(glib::clone!(
glib::clone!(@weak self as obj => move |position| obj.set_position(position)), #[weak(rename_to = obj)]
); self,
move |position| obj.set_position(position)
));
let animation = adw::SpringAnimation::builder() let animation = adw::SpringAnimation::builder()
.spring_params(&adw::SpringParams::new( .spring_params(&adw::SpringParams::new(
@ -726,9 +744,11 @@ impl LpSlidingView {
.target(&target) .target(&target)
.build(); .build();
animation.connect_done(glib::clone!(@weak self as obj => move |_| { animation.connect_done(glib::clone!(
obj.emit_target_page_reached() #[weak(rename_to = obj)]
})); self,
move |_| obj.emit_target_page_reached()
));
animation animation
}) })
@ -736,12 +756,18 @@ impl LpSlidingView {
fn step_animation(&self) -> &adw::TimedAnimation { fn step_animation(&self) -> &adw::TimedAnimation {
self.imp().step_animation.get_or_init(|| { self.imp().step_animation.get_or_init(|| {
let target: adw::CallbackAnimationTarget = adw::CallbackAnimationTarget::new( let target: adw::CallbackAnimationTarget =
glib::clone!(@weak self as obj => move |progress| { adw::CallbackAnimationTarget::new(glib::clone!(
obj.imp().position_tracking.borrow_mut().set_progress(progress); #[weak(rename_to = obj)]
obj.queue_allocate(); self,
}), move |progress| {
); obj.imp()
.position_tracking
.borrow_mut()
.set_progress(progress);
obj.queue_allocate();
}
));
let animation = adw::TimedAnimation::builder() let animation = adw::TimedAnimation::builder()
.duration(STEP_DURATION) .duration(STEP_DURATION)
@ -751,17 +777,23 @@ impl LpSlidingView {
.target(&target) .target(&target)
.build(); .build();
animation.connect_done(glib::clone!(@weak self as obj => move |_| { animation.connect_done(glib::clone!(
if let Some(current_index) = obj.current_index() { #[weak(rename_to = obj)]
let imp = obj.imp(); self,
imp.position_tracking.borrow_mut().set_progress(1.); move |_| {
imp.position_tracking.borrow_mut().set_position(current_index as f64 - obj.position_shift()); if let Some(current_index) = obj.current_index() {
obj.queue_allocate(); let imp = obj.imp();
} else { imp.position_tracking.borrow_mut().set_progress(1.);
error!("No current page at end of animation"); imp.position_tracking
.borrow_mut()
.set_position(current_index as f64 - obj.position_shift());
obj.queue_allocate();
} else {
error!("No current page at end of animation");
}
obj.emit_target_page_reached();
} }
obj.emit_target_page_reached(); ));
}));
animation animation
}) })

View file

@ -172,18 +172,27 @@ mod imp {
let obj = self.obj(); let obj = self.obj();
self.forward_click_gesture self.forward_click_gesture.connect_pressed(clone!(
.connect_pressed(clone!(@weak obj => move |_,_,_,_| { #[weak]
obj,
move |_, _, _, _| {
obj.image_view().navigate(Direction::Forward, false); obj.image_view().navigate(Direction::Forward, false);
})); }
self.backward_click_gesture ));
.connect_pressed(clone!(@weak obj => move |_,_,_,_| { self.backward_click_gesture.connect_pressed(clone!(
#[weak]
obj,
move |_, _, _, _| {
obj.image_view().navigate(Direction::Back, false); obj.image_view().navigate(Direction::Back, false);
})); }
self.properties_button ));
.connect_toggled(clone!(@weak obj => move |_| { self.properties_button.connect_toggled(clone!(
#[weak]
obj,
move |_| {
obj.on_properties_button_toggled(); obj.on_properties_button_toggled();
})); }
));
if config::PROFILE == ".Devel" { if config::PROFILE == ".Devel" {
obj.add_css_class("devel"); obj.add_css_class("devel");
@ -195,37 +204,58 @@ mod imp {
glib::timeout_add_local_once( glib::timeout_add_local_once(
std::time::Duration::from_millis(SHOW_WINDOW_AFTER), std::time::Duration::from_millis(SHOW_WINDOW_AFTER),
glib::clone!(@weak obj => move || if !obj.is_visible() { obj.present() }), glib::clone!(
#[weak]
obj,
move || if !obj.is_visible() {
obj.present()
}
),
); );
obj.on_current_page_changed(); obj.on_current_page_changed();
obj.on_fullscreen_changed(); obj.on_fullscreen_changed();
self.image_view.connect_current_page_notify( self.image_view.connect_current_page_notify(glib::clone!(
glib::clone!(@weak obj => move |_| obj.on_current_page_changed()), #[weak]
); obj,
move |_| obj.on_current_page_changed()
));
obj.connect_fullscreened_notify( obj.connect_fullscreened_notify(glib::clone!(
glib::clone!(@weak obj => move |_| obj.on_fullscreen_changed()), #[weak]
); obj,
move |_| obj.on_fullscreen_changed()
));
obj.connect_map(|win| { obj.connect_map(|win| {
win.resize_default(); win.resize_default();
}); });
self.properties_button.connect_active_notify( self.properties_button.connect_active_notify(glib::clone!(
glib::clone!(@weak obj => move |_| obj.update_headerbar_style()), #[weak]
); obj,
move |_| obj.update_headerbar_style()
));
let gesture_click = gtk::GestureClick::new(); let gesture_click = gtk::GestureClick::new();
gesture_click gesture_click.connect_pressed(glib::clone!(
.connect_pressed(glib::clone!(@weak obj => move |_, _, _, _| obj.on_click())); #[weak]
obj,
move |_, _, _, _| obj.on_click()
));
obj.add_controller(gesture_click); obj.add_controller(gesture_click);
self.motion_controller self.motion_controller.connect_motion(glib::clone!(
.connect_motion(glib::clone!(@weak obj => move |_, x, y| obj.on_motion((x,y)))); #[weak]
self.motion_controller obj,
.connect_enter(glib::clone!(@weak obj => move |_, x, y| obj.on_motion((x,y)))); move |_, x, y| obj.on_motion((x, y))
));
self.motion_controller.connect_enter(glib::clone!(
#[weak]
obj,
move |_, x, y| obj.on_motion((x, y))
));
obj.add_controller(self.motion_controller.clone()); obj.add_controller(self.motion_controller.clone());
let current_image_signals = self.image_view.current_image_signals(); let current_image_signals = self.image_view.current_image_signals();
@ -237,17 +267,25 @@ mod imp {
// Object alive, pass as @weak. Otherwise, pass // Object alive, pass as @weak. Otherwise, pass
// as @strong. Most of the time you will want // as @strong. Most of the time you will want
// to use @weak. // to use @weak.
current_image_signals.connect_bind_local(glib::clone!(@weak obj => move |_, _| { current_image_signals.connect_bind_local(glib::clone!(
obj.on_zoom_status_changed() #[weak]
})); obj,
move |_, _| obj.on_zoom_status_changed()
));
current_image_signals.connect_local( current_image_signals.connect_local(
"metadata-changed", "metadata-changed",
true, true,
glib::clone!(@weak obj => @default-return None, move |_| { glib::clone!(
obj.update_title(); #[weak]
None obj,
}), #[upgrade_or_else]
|| None,
move |_| {
obj.update_title();
None
}
),
); );
current_image_signals.connect_closure( current_image_signals.connect_closure(
@ -256,57 +294,82 @@ mod imp {
// `closure_local!` is similar to `clone`, but you use `@watch` instead of clone. // `closure_local!` is similar to `clone`, but you use `@watch` instead of clone.
// `@watch` means that this signal will be disconnected when the watched object // `@watch` means that this signal will be disconnected when the watched object
// is dropped. // is dropped.
glib::closure_local!(@watch obj => move |_: &LpImage, _: &glib::ParamSpec| { glib::closure_local!(
obj.on_zoom_status_changed(); #[watch]
}), obj,
move |_: &LpImage, _: &glib::ParamSpec| {
obj.on_zoom_status_changed();
}
),
); );
current_image_signals.connect_closure( current_image_signals.connect_closure(
"notify::is-max-zoom", "notify::is-max-zoom",
false, false,
glib::closure_local!(@watch obj => move |_: &LpImage, _: &glib::ParamSpec| { glib::closure_local!(
obj.on_zoom_status_changed(); #[watch]
}), obj,
move |_: &LpImage, _: &glib::ParamSpec| {
obj.on_zoom_status_changed();
}
),
); );
current_image_signals.connect_closure( current_image_signals.connect_closure(
"notify::image-size-available", "notify::image-size-available",
false, false,
glib::closure_local!(@watch obj => move |_: &LpImage, _: &glib::ParamSpec| { glib::closure_local!(
obj.image_size_available(); #[watch]
}), obj,
move |_: &LpImage, _: &glib::ParamSpec| {
obj.image_size_available();
}
),
); );
current_image_signals.connect_closure( current_image_signals.connect_closure(
"notify::error", "notify::error",
false, false,
glib::closure_local!(@watch obj => move |_: &LpImage, _: &glib::ParamSpec| { glib::closure_local!(
obj.image_error(); #[watch]
}), obj,
move |_: &LpImage, _: &glib::ParamSpec| {
obj.image_error();
}
),
); );
self.toolbar_view.connect_top_bar_style_notify( self.toolbar_view.connect_top_bar_style_notify(glib::clone!(
glib::clone!(@weak obj => move |_| obj.on_top_bar_style_notify()), #[weak]
); obj,
move |_| obj.on_top_bar_style_notify()
));
// action win.previous status // action win.previous status
self.image_view.connect_is_previous_available_notify( self.image_view
glib::clone!(@weak obj => move |_| { .connect_is_previous_available_notify(glib::clone!(
obj.action_set_enabled( #[weak]
"win.previous", obj,
obj.imp().image_view.is_previous_available(), move |_| {
); obj.action_set_enabled(
}), "win.previous",
); obj.imp().image_view.is_previous_available(),
);
}
));
// action win.next status // action win.next status
self.image_view self.image_view
.connect_is_next_available_notify(glib::clone!(@weak obj => move |_| { .connect_is_next_available_notify(glib::clone!(
obj.action_set_enabled( #[weak]
"win.next", obj,
obj.imp().image_view.is_next_available(), move |_| {
); obj.action_set_enabled(
})); "win.next",
obj.imp().image_view.is_next_available(),
);
}
));
// Make widgets visible when the focus moves // Make widgets visible when the focus moves
obj.connect_move_focus(|obj, _| { obj.connect_move_focus(|obj, _| {
@ -323,31 +386,41 @@ mod imp {
self.drop_target.set_types(&[gdk::FileList::static_type()]); self.drop_target.set_types(&[gdk::FileList::static_type()]);
self.drop_target.connect_accept( self.drop_target.connect_accept(clone!(
clone!(@weak obj => @default-return false, move |_drop_target, drop| { #[weak]
obj,
#[upgrade_or_else]
|| false,
move |_drop_target, drop| {
// Only accept drops from external sources or different windows // Only accept drops from external sources or different windows
let different_source = drop.drag().is_none() || drop.drag() != obj.image_view().drag_source().drag(); let different_source = drop.drag().is_none()
|| drop.drag() != obj.image_view().drag_source().drag();
// We have to do this manually since we are overwriting the default handler // We have to do this manually since we are overwriting the default handler
let correct_format = drop.formats().contains_type(gdk::FileList::static_type()); let correct_format = drop.formats().contains_type(gdk::FileList::static_type());
different_source && correct_format different_source && correct_format
}), }
); ));
// For callbacks, you will want to reference the GTK docs on // For callbacks, you will want to reference the GTK docs on
// the relevant signal to see which parameters you need. // the relevant signal to see which parameters you need.
// In this case, we need only need the GValue, // In this case, we need only need the GValue,
// so we name it `value` then use `_` for the other spots. // so we name it `value` then use `_` for the other spots.
self.drop_target.connect_drop( self.drop_target.connect_drop(clone!(
clone!(@weak obj => @default-return false, move |_, value, _, _| { #[weak]
obj,
// Here we use a GValue, which is a dynamic object that can hold different types, #[upgrade_or_else]
// e.g. strings, numbers, or in this case objects. In order to get the GdkFileList || false,
// from the GValue, we need to use the `get()` method. move |_, value, _, _| {
// Here we use a GValue, which is a dynamic object that can hold different
// types, e.g. strings, numbers, or in this case objects. In
// order to get the GdkFileList from the GValue, we need to
// use the `get()` method.
// //
// We've added type annotations here, and written it as `let list: gdk::FileList = ...`, // We've added type annotations here, and written it as `let list: gdk::FileList
// but you might also see places where type arguments are used. // = ...`, but you might also see places where type
// This line could have been written as `let list = value.get::<gdk::FileList>().unwrap()`. // arguments are used. This line could have been written as
// `let list = value.get::<gdk::FileList>().unwrap()`.
let files = match value.get::<gdk::FileList>() { let files = match value.get::<gdk::FileList>() {
Ok(list) => list.files(), Ok(list) => list.files(),
Err(err) => { Err(err) => {
@ -367,8 +440,8 @@ mod imp {
obj.present(); obj.present();
true true
}), }
); ));
} }
} }
@ -563,21 +636,25 @@ impl LpWindow {
.button_label(gettext("Undo")) .button_label(gettext("Undo"))
.priority(adw::ToastPriority::High) .priority(adw::ToastPriority::High)
.build(); .build();
toast.connect_button_clicked(glib::clone!(@weak self as win => move |_| { toast.connect_button_clicked(glib::clone!(
let path = path.clone(); #[weak(rename_to = win)]
glib::spawn_future_local(async move { self,
win.image_view() move |_| {
.set_trash_restore(Some(gio::File::for_path(&path))); let path = path.clone();
let result = crate::util::untrash(&path).await; glib::spawn_future_local(async move {
if let Err(err) = result { win.image_view()
log::error!("Failed to untrash {path:?}: {err}"); .set_trash_restore(Some(gio::File::for_path(&path)));
win.show_toast( let result = crate::util::untrash(&path).await;
gettext("Failed to restore image from trash"), if let Err(err) = result {
adw::ToastPriority::High, log::error!("Failed to untrash {path:?}: {err}");
); win.show_toast(
} gettext("Failed to restore image from trash"),
}); adw::ToastPriority::High,
})); );
}
});
}
));
self.imp().toast_overlay.add_toast(toast); self.imp().toast_overlay.add_toast(toast);
} }
Err(err) => { Err(err) => {

View file

@ -50,7 +50,9 @@ impl LpWindow {
fn show_controls_animation(&self) -> &adw::TimedAnimation { fn show_controls_animation(&self) -> &adw::TimedAnimation {
self.imp().show_controls_animation.get_or_init(|| { self.imp().show_controls_animation.get_or_init(|| {
let target = adw::CallbackAnimationTarget::new(glib::clone!( let target = adw::CallbackAnimationTarget::new(glib::clone!(
@weak self as obj => move |opacity| obj.set_control_opacity(opacity, false) #[weak(rename_to = obj)]
self,
move |opacity| obj.set_control_opacity(opacity, false)
)); ));
adw::TimedAnimation::builder() adw::TimedAnimation::builder()
@ -66,7 +68,9 @@ impl LpWindow {
fn hide_controls_animation(&self) -> &adw::TimedAnimation { fn hide_controls_animation(&self) -> &adw::TimedAnimation {
self.imp().hide_controls_animation.get_or_init(|| { self.imp().hide_controls_animation.get_or_init(|| {
let target = adw::CallbackAnimationTarget::new(glib::clone!( let target = adw::CallbackAnimationTarget::new(glib::clone!(
@weak self as obj => move |opacity| obj.set_control_opacity(opacity, true) #[weak(rename_to = obj)]
self,
move |opacity| obj.set_control_opacity(opacity, true)
)); ));
adw::TimedAnimation::builder() adw::TimedAnimation::builder()
@ -85,10 +89,14 @@ impl LpWindow {
let new_timeout = glib::timeout_add_local_once( let new_timeout = glib::timeout_add_local_once(
std::time::Duration::from_millis(HIDE_CONTROLS_IDLE_TIMEOUT), std::time::Duration::from_millis(HIDE_CONTROLS_IDLE_TIMEOUT),
glib::clone!(@weak self as win => move || { glib::clone!(
win.imp().hide_controls_timeout.take(); #[weak(rename_to = win)]
win.hide_controls(); self,
}), move || {
win.imp().hide_controls_timeout.take();
win.hide_controls();
}
),
); );
self.imp().hide_controls_timeout.replace(Some(new_timeout)); self.imp().hide_controls_timeout.replace(Some(new_timeout));