diff --git a/readme.md b/readme.md
index afca0a6..7231d3d 100644
--- a/readme.md
+++ b/readme.md
@@ -20,6 +20,7 @@ An unofficial GUI for setting wallpapers with multiple backends, built with GTK4
- **Cli args** - Hyprwall supports command line arguments, to view these type **`hyprwall --help`**, **--restore** is one of them, if you wish you can restore your last used wallpaper in the gui with this argument.
- **GIF support** - Hyprwall supports GIFs, but only if the **swww** backend is used.
- **Search functionality** - Hyprwall has search functionality that can filter through your wallpapers in real time.
+- **Previewable images** - Hyprwall can preview images at a much larger scale via right clicking on an image: it will bring up a preview window.
- **Supports swaybg, swww, wallutils, feh, and hyprpaper** - Hyprwall supports a variety of wallpaper backends, so you can use it with your preferred wallpaper tool.
diff --git a/src/gui.rs b/src/gui.rs
index 64590df..d4af251 100644
--- a/src/gui.rs
+++ b/src/gui.rs
@@ -438,6 +438,16 @@ fn load_images(
});
button.add_controller(motion_controller);
+ let gesture = gtk::GestureClick::new();
+ gesture.set_button(3);
+ let path_clone_preview = path_clone.clone();
+ gesture.connect_released(move |gesture, _, _, _| {
+ if let Some(widget) = gesture.widget() {
+ show_preview_window(&path_clone_preview, &widget);
+ }
+ });
+ button.add_controller(gesture);
+
let file_name = Path::new(&path_clone)
.file_name()
.and_then(|name| name.to_str())
@@ -687,3 +697,83 @@ fn filter_wallpapers(flowbox: &Rc>, search_text: impl AsRef) {
+ let path = path.to_string();
+ let parent = parent_widget.root().and_downcast::();
+
+ glib::spawn_future_local(async move {
+ let window = gtk::Window::new();
+ window.set_title(Some("Preview"));
+ window.set_default_size(800, 600);
+ window.set_modal(true);
+ window.set_transient_for(parent.as_ref());
+
+ let spinner = gtk::Spinner::new();
+ spinner.set_hexpand(true);
+ spinner.set_vexpand(true);
+ spinner.start();
+ window.set_child(Some(&spinner));
+
+ let window_weak = window.downgrade();
+ let key_controller = gtk::EventControllerKey::new();
+ key_controller.connect_key_pressed(move |_, key, _, _| {
+ if key == gdk::Key::Escape {
+ if let Some(window) = window_weak.upgrade() {
+ window.close();
+ }
+ glib::Propagation::Stop
+ } else {
+ glib::Propagation::Proceed
+ }
+ });
+ window.add_controller(key_controller);
+
+ window.present();
+
+ let path_buf = PathBuf::from(&path);
+ let window_weak = window.downgrade();
+
+ let (sender, receiver) = crossbeam_channel::unbounded::>();
+
+ std::thread::spawn(move || {
+ let file = gio::File::for_path(&path_buf);
+ match Texture::from_file(&file) {
+ Ok(texture) => {
+ let _ = sender.send(Ok(texture));
+ }
+ Err(e) => {
+ let _ = sender.send(Err(e.to_string()));
+ }
+ }
+ });
+
+ glib::source::idle_add_local(move || match receiver.try_recv() {
+ Ok(result) => {
+ if let Some(window) = window_weak.upgrade() {
+ match result {
+ Ok(texture) => {
+ let picture = gtk::Picture::for_paintable(&texture);
+ picture.set_can_shrink(true);
+ picture.set_keep_aspect_ratio(true);
+ picture.set_hexpand(true);
+ picture.set_vexpand(true);
+ window.set_child(Some(&picture));
+ }
+ Err(error) => {
+ let error_label =
+ gtk::Label::new(Some(&format!("Failed to load image: {}", error)));
+ error_label.set_wrap(true);
+ error_label.set_margin_start(10);
+ error_label.set_margin_end(10);
+ window.set_child(Some(&error_label));
+ }
+ }
+ }
+ ControlFlow::Break
+ }
+ Err(crossbeam_channel::TryRecvError::Empty) => ControlFlow::Continue,
+ Err(crossbeam_channel::TryRecvError::Disconnected) => ControlFlow::Break,
+ });
+ });
+}