feat(gui): now directories can be reordered using drag and drop

This commit is contained in:
2026-01-28 02:10:36 +03:00
parent d385e5356e
commit 4499b1d3aa
7 changed files with 97 additions and 15 deletions
Generated
+61 -1
View File
@@ -182,7 +182,7 @@ dependencies = [
"bitflags 2.10.0", "bitflags 2.10.0",
"cexpr", "cexpr",
"clang-sys", "clang-sys",
"itertools", "itertools 0.13.0",
"proc-macro2", "proc-macro2",
"quote", "quote",
"regex", "regex",
@@ -461,6 +461,16 @@ dependencies = [
"memchr", "memchr",
] ]
[[package]]
name = "concat-idents"
version = "1.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f76990911f2267d837d9d0ad060aa63aaad170af40904b29461734c339030d4d"
dependencies = [
"quote",
"syn",
]
[[package]] [[package]]
name = "concurrent-queue" name = "concurrent-queue"
version = "2.5.0" version = "2.5.0"
@@ -810,6 +820,29 @@ dependencies = [
"winit", "winit",
] ]
[[package]]
name = "egui_animation"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a3db554dd3784f469d804f7dc25d1b14a2e00f1608d7af60218ccbced720a6e8"
dependencies = [
"egui",
"hello_egui_utils",
"simple-easing",
]
[[package]]
name = "egui_dnd"
version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f535b8df7ca89f781954feaa505899c8955b550074ecafcaa3c040f67aec3d46"
dependencies = [
"egui",
"egui_animation",
"simple-easing",
"web-time",
]
[[package]] [[package]]
name = "egui_glow" name = "egui_glow"
version = "0.33.3" version = "0.33.3"
@@ -1180,6 +1213,16 @@ version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]]
name = "hello_egui_utils"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f5d09c2c7f3aa61624b1bec320be9029f30e06769f92e39ac99a6d6e01024ae8"
dependencies = [
"concat-idents",
"egui",
]
[[package]] [[package]]
name = "hermit-abi" name = "hermit-abi"
version = "0.5.2" version = "0.5.2"
@@ -1327,6 +1370,15 @@ dependencies = [
"either", "either",
] ]
[[package]]
name = "itertools"
version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285"
dependencies = [
"either",
]
[[package]] [[package]]
name = "itoa" name = "itoa"
version = "1.0.17" version = "1.0.17"
@@ -2247,7 +2299,9 @@ dependencies = [
"dirs", "dirs",
"eframe", "eframe",
"egui", "egui",
"egui_dnd",
"egui_material_icons", "egui_material_icons",
"itertools 0.14.0",
"pipewire", "pipewire",
"rfd", "rfd",
"rodio", "rodio",
@@ -2569,6 +2623,12 @@ version = "0.3.8"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2"
[[package]]
name = "simple-easing"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "832ddd7df0d98d6fd93b973c330b7c8e0742d5cb8f1afc7dea89dba4d2531aa1"
[[package]] [[package]]
name = "slab" name = "slab"
version = "0.4.11" version = "0.4.11"
+3 -1
View File
@@ -20,6 +20,7 @@ serde_json = "1.0.149"
clap = { version = "4.5.54", default-features = false, features = ["std", "suggestions", "help", "usage", "error-context", "derive"] } clap = { version = "4.5.54", default-features = false, features = ["std", "suggestions", "help", "usage", "error-context", "derive"] }
dirs = "6.0.0" dirs = "6.0.0"
itertools = "0.14.0"
rodio = { version = "0.21.1", default-features = false, features = ["symphonia-all", "playback"] } rodio = { version = "0.21.1", default-features = false, features = ["symphonia-all", "playback"] }
pipewire = "0.9.2" pipewire = "0.9.2"
@@ -28,6 +29,7 @@ rfd = { version = "0.17.2", default-features = false, features = ["xdg-portal"]}
egui = { version = "0.33.3", default-features = false, features = ["default_fonts", "rayon"] } egui = { version = "0.33.3", default-features = false, features = ["default_fonts", "rayon"] }
eframe = { version = "0.33.3", default-features = false, features = ["default_fonts", "glow", "x11", "wayland"] } eframe = { version = "0.33.3", default-features = false, features = ["default_fonts", "glow", "x11", "wayland"] }
egui_material_icons = "0.5.0" egui_material_icons = "0.5.0"
egui_dnd = "0.14.0"
[[bin]] [[bin]]
name = "pwsp-daemon" name = "pwsp-daemon"
@@ -56,4 +58,4 @@ assets = [
["assets/pwsp-gui.desktop", "usr/share/applications/pwsp.desktop", "644"], ["assets/pwsp-gui.desktop", "usr/share/applications/pwsp.desktop", "644"],
["assets/icon.png", "usr/share/icons/hicolor/256x256/apps/pwsp.png", "644"], ["assets/icon.png", "usr/share/icons/hicolor/256x256/apps/pwsp.png", "644"],
["assets/pwsp-daemon.service", "usr/lib/systemd/user/pwsp-daemon.service", "644"], ["assets/pwsp-daemon.service", "usr/lib/systemd/user/pwsp-daemon.service", "644"],
] ]
+13 -7
View File
@@ -3,10 +3,11 @@ use egui::{
Align, AtomExt, Button, CollapsingHeader, Color32, ComboBox, CursorIcon, FontFamily, Label, Align, AtomExt, Button, CollapsingHeader, Color32, ComboBox, CursorIcon, FontFamily, Label,
Layout, RichText, ScrollArea, Sense, Slider, TextEdit, Ui, Vec2, Layout, RichText, ScrollArea, Sense, Slider, TextEdit, Ui, Vec2,
}; };
use egui_dnd::dnd;
use egui_material_icons::icons; use egui_material_icons::icons;
use pwsp::types::audio_player::TrackInfo; use pwsp::types::audio_player::TrackInfo;
use pwsp::utils::gui::format_time_pair; use pwsp::utils::gui::format_time_pair;
use std::{error::Error, path::PathBuf, time::Instant}; use std::{error::Error, time::Instant};
use pwsp::types::gui::AppState; use pwsp::types::gui::AppState;
@@ -301,10 +302,14 @@ impl SoundpadGui {
ScrollArea::vertical().id_salt(0).show(ui, |ui| { ScrollArea::vertical().id_salt(0).show(ui, |ui| {
ui.set_min_width(area_size.x); ui.set_min_width(area_size.x);
let mut dirs: Vec<PathBuf> = self.app_state.dirs.iter().cloned().collect(); let mut dirs = self.app_state.dirs.clone();
dirs.sort();
for path in dirs.iter() { dnd(ui, "dnd_directories").show_vec(&mut dirs, |ui, item, handle, _state| {
let path = item.clone();
ui.horizontal(|ui| { ui.horizontal(|ui| {
handle.ui(ui, |ui| {
ui.label(icons::ICON_DRAG_INDICATOR);
});
let name = path let name = path
.file_name() .file_name()
.map(|s| s.to_string_lossy().to_string()) .map(|s| s.to_string_lossy().to_string())
@@ -312,7 +317,7 @@ impl SoundpadGui {
let mut dir_button_text = RichText::new(name.clone()); let mut dir_button_text = RichText::new(name.clone());
if let Some(current_dir) = &self.app_state.current_dir { if let Some(current_dir) = &self.app_state.current_dir {
if current_dir.eq(path) { if current_dir.eq(&path) {
dir_button_text = dir_button_text.color(Color32::WHITE); dir_button_text = dir_button_text.color(Color32::WHITE);
} }
} }
@@ -322,7 +327,7 @@ impl SoundpadGui {
let dir_button_response = ui.add(dir_button); let dir_button_response = ui.add(dir_button);
if dir_button_response.clicked() { if dir_button_response.clicked() {
self.open_dir(path); self.open_dir(&path);
} }
let delete_dir_button = Button::new(icons::ICON_DELETE).frame(false); let delete_dir_button = Button::new(icons::ICON_DELETE).frame(false);
@@ -332,7 +337,8 @@ impl SoundpadGui {
self.remove_dir(&path.clone()); self.remove_dir(&path.clone());
} }
}); });
} });
self.app_state.dirs = dirs;
ui.horizontal(|ui| { ui.horizontal(|ui| {
let add_dirs_button = Button::new(icons::ICON_ADD).frame(false); let add_dirs_button = Button::new(icons::ICON_ADD).frame(false);
+4 -2
View File
@@ -4,6 +4,7 @@ mod update;
use eframe::{HardwareAcceleration, NativeOptions, icon_data::from_png_bytes, run_native}; use eframe::{HardwareAcceleration, NativeOptions, icon_data::from_png_bytes, run_native};
use egui::{Context, Vec2, ViewportBuilder}; use egui::{Context, Vec2, ViewportBuilder};
use itertools::Itertools;
use pwsp::{ use pwsp::{
types::{ types::{
audio_player::PlayerState, audio_player::PlayerState,
@@ -87,15 +88,16 @@ impl SoundpadGui {
let file_dialog = FileDialog::new(); let file_dialog = FileDialog::new();
if let Some(paths) = file_dialog.pick_folders() { if let Some(paths) = file_dialog.pick_folders() {
for path in paths { for path in paths {
self.app_state.dirs.insert(path); self.app_state.dirs.push(path);
} }
self.app_state.dirs = self.app_state.dirs.iter().unique().cloned().collect();
self.config.dirs = self.app_state.dirs.clone(); self.config.dirs = self.app_state.dirs.clone();
self.config.save_to_file().ok(); self.config.save_to_file().ok();
} }
} }
pub fn remove_dir(&mut self, path: &PathBuf) { pub fn remove_dir(&mut self, path: &PathBuf) {
self.app_state.dirs.remove(path); self.app_state.dirs.retain(|x| x != path);
if let Some(current_dir) = &self.app_state.current_dir if let Some(current_dir) = &self.app_state.current_dir
&& current_dir == path && current_dir == path
{ {
+12
View File
@@ -9,6 +9,13 @@ use std::time::{Duration, Instant};
impl App for SoundpadGui { impl App for SoundpadGui {
fn update(&mut self, ctx: &Context, _frame: &mut EFrame) { fn update(&mut self, ctx: &Context, _frame: &mut EFrame) {
// Save directories if changed
if !self.config.dirs.eq(&self.app_state.dirs) {
self.config.dirs = self.app_state.dirs.clone();
self.config.save_to_file().ok();
}
// Seek and volume requests
let mut seek_requests = vec![]; let mut seek_requests = vec![];
let mut volume_requests = vec![]; let mut volume_requests = vec![];
@@ -57,11 +64,13 @@ impl App for SoundpadGui {
} }
} }
// Sync audio player state
{ {
let guard = self.audio_player_state_shared.lock().unwrap(); let guard = self.audio_player_state_shared.lock().unwrap();
self.audio_player_state = guard.clone(); self.audio_player_state = guard.clone();
} }
// Handle scale factor changes
let old_scale_factor = self.config.scale_factor; let old_scale_factor = self.config.scale_factor;
let new_scale_factor = ctx.zoom_factor().clamp(0.5, 2.0); let new_scale_factor = ctx.zoom_factor().clamp(0.5, 2.0);
@@ -72,8 +81,10 @@ impl App for SoundpadGui {
self.config.save_to_file().ok(); self.config.save_to_file().ok();
} }
// Handle input
self.handle_input(ctx); self.handle_input(ctx);
// Draw UI
CentralPanel::default().show(ctx, |ui| { CentralPanel::default().show(ctx, |ui| {
if !self.audio_player_state.is_daemon_running { if !self.audio_player_state.is_daemon_running {
self.draw_waiting_for_daemon(ui); self.draw_waiting_for_daemon(ui);
@@ -88,6 +99,7 @@ impl App for SoundpadGui {
self.draw(ui).ok(); self.draw(ui).ok();
}); });
// Request repaint
ctx.request_repaint_after_secs(1.0 / 60.0); ctx.request_repaint_after_secs(1.0 / 60.0);
} }
} }
+3 -3
View File
@@ -1,6 +1,6 @@
use crate::utils::config::get_config_path; use crate::utils::config::get_config_path;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::{collections::HashSet, error::Error, fs, path::PathBuf}; use std::{error::Error, fs, path::PathBuf};
#[derive(Default, Clone, Serialize, Deserialize)] #[derive(Default, Clone, Serialize, Deserialize)]
#[serde(default)] #[serde(default)]
@@ -41,7 +41,7 @@ pub struct GuiConfig {
pub save_scale_factor: bool, pub save_scale_factor: bool,
pub pause_on_exit: bool, pub pause_on_exit: bool,
pub dirs: HashSet<PathBuf>, pub dirs: Vec<PathBuf>,
} }
impl Default for GuiConfig { impl Default for GuiConfig {
@@ -55,7 +55,7 @@ impl Default for GuiConfig {
save_scale_factor: false, save_scale_factor: false,
pause_on_exit: false, pause_on_exit: false,
dirs: HashSet::default(), dirs: vec![],
} }
} }
} }
+1 -1
View File
@@ -37,7 +37,7 @@ pub struct AppState {
pub ignore_volume_update_until: Option<Instant>, pub ignore_volume_update_until: Option<Instant>,
pub current_dir: Option<PathBuf>, pub current_dir: Option<PathBuf>,
pub dirs: HashSet<PathBuf>, pub dirs: Vec<PathBuf>,
pub selected_file: Option<PathBuf>, pub selected_file: Option<PathBuf>,
pub files: HashSet<PathBuf>, pub files: HashSet<PathBuf>,