max / audiofiles
46 files changed,
+293 insertions,
-371 deletions
| @@ -396,8 +396,8 @@ mod tests { | |||
| 396 | 396 | #[test] | |
| 397 | 397 | fn clamp_prevents_overflow() { | |
| 398 | 398 | // Simulate very loud mixed audio | |
| 399 | - | let buf = vec![1.5f32, -1.5, 0.5, -0.5]; | |
| 400 | - | let mut data = vec![0.0f32; 4]; | |
| 399 | + | let buf = [1.5f32, -1.5, 0.5, -0.5]; | |
| 400 | + | let mut data = [0.0f32; 4]; | |
| 401 | 401 | for (out, &mix) in data.iter_mut().zip(buf.iter()) { | |
| 402 | 402 | *out = mix.clamp(-1.0, 1.0); | |
| 403 | 403 | } |
| @@ -292,14 +292,12 @@ pub fn trial_days_remaining(trial: &TrialState) -> i64 { | |||
| 292 | 292 | let now = chrono::Utc::now(); | |
| 293 | 293 | ||
| 294 | 294 | // Clock rollback detection: if now is before last_seen_date, expire immediately | |
| 295 | - | if let Some(ref last) = trial.last_seen_date { | |
| 296 | - | if let Ok(last_seen) = chrono::DateTime::parse_from_rfc3339(last) { | |
| 297 | - | if now.signed_duration_since(last_seen).num_hours() < -1 { | |
| 295 | + | if let Some(ref last) = trial.last_seen_date | |
| 296 | + | && let Ok(last_seen) = chrono::DateTime::parse_from_rfc3339(last) | |
| 297 | + | && now.signed_duration_since(last_seen).num_hours() < -1 { | |
| 298 | 298 | // Allow up to 1 hour of drift (DST, NTP correction) | |
| 299 | 299 | return 0; | |
| 300 | 300 | } | |
| 301 | - | } | |
| 302 | - | } | |
| 303 | 301 | ||
| 304 | 302 | let elapsed = now.signed_duration_since(first); | |
| 305 | 303 | 30 - elapsed.num_days() |
| @@ -164,8 +164,7 @@ fn load_api_key(data_dir: &Path) -> Option<String> { | |||
| 164 | 164 | return Some(key); | |
| 165 | 165 | } | |
| 166 | 166 | // Fall back to bundled synckit.toml | |
| 167 | - | parse_synckit_toml_key().map(String::from) | |
| 168 | - | } | |
| 167 | + | parse_synckit_toml_key()} | |
| 169 | 168 | ||
| 170 | 169 | /// Save an API key to the data directory for future launches. | |
| 171 | 170 | #[cfg(test)] | |
| @@ -526,8 +525,8 @@ impl AudioFilesApp { | |||
| 526 | 525 | let ctx = ui.ctx().clone(); | |
| 527 | 526 | let ctx = &ctx; | |
| 528 | 527 | // Poll tray menu events | |
| 529 | - | if let Some(ref tray) = self.tray { | |
| 530 | - | if let Some(action) = tray.poll() { | |
| 528 | + | if let Some(ref tray) = self.tray | |
| 529 | + | && let Some(action) = tray.poll() { | |
| 531 | 530 | match action { | |
| 532 | 531 | tray::TrayAction::ShowWindow => { | |
| 533 | 532 | ctx.send_viewport_cmd(ViewportCommand::Focus); | |
| @@ -542,11 +541,10 @@ impl AudioFilesApp { | |||
| 542 | 541 | } | |
| 543 | 542 | } | |
| 544 | 543 | } | |
| 545 | - | } | |
| 546 | 544 | ||
| 547 | 545 | // Update tray tooltip based on playback state | |
| 548 | - | if let Some(ref tray) = self.tray { | |
| 549 | - | if let Some(ref browser) = self.browser { | |
| 546 | + | if let Some(ref tray) = self.tray | |
| 547 | + | && let Some(ref browser) = self.browser { | |
| 550 | 548 | let playing = browser.shared.preview.lock().playing; | |
| 551 | 549 | if playing { | |
| 552 | 550 | tray.set_tooltip(&browser.status); | |
| @@ -554,23 +552,21 @@ impl AudioFilesApp { | |||
| 554 | 552 | tray.set_tooltip("audiofiles"); | |
| 555 | 553 | } | |
| 556 | 554 | } | |
| 557 | - | } | |
| 558 | 555 | ||
| 559 | 556 | // Check if sync pulled remote changes → refresh browser contents | |
| 560 | - | if let Some(ref sync) = self.sync_manager { | |
| 561 | - | if sync.status().needs_refresh { | |
| 557 | + | if let Some(ref sync) = self.sync_manager | |
| 558 | + | && sync.status().needs_refresh { | |
| 562 | 559 | if let Some(ref mut browser) = self.browser { | |
| 563 | 560 | browser.refresh_vfs_list(); | |
| 564 | 561 | browser.refresh_contents(); | |
| 565 | 562 | } | |
| 566 | 563 | sync.clear_needs_refresh(); | |
| 567 | 564 | } | |
| 568 | - | } | |
| 569 | 565 | ||
| 570 | 566 | // ── Sync setup actions (before draw, so UI sees results this frame) ── | |
| 571 | 567 | // ── Vault actions ── | |
| 572 | - | if let Some(ref mut browser) = self.browser { | |
| 573 | - | if let Some(action) = browser.settings.pending_action.take() { | |
| 568 | + | if let Some(ref mut browser) = self.browser | |
| 569 | + | && let Some(action) = browser.settings.pending_action.take() { | |
| 574 | 570 | use audiofiles_browser::state::VaultAction; | |
| 575 | 571 | match action { | |
| 576 | 572 | VaultAction::SwitchVault(path) => { | |
| @@ -581,12 +577,11 @@ impl AudioFilesApp { | |||
| 581 | 577 | let switch_path = path.clone(); | |
| 582 | 578 | if self.with_vault_registry(|reg| vault::create_vault(reg, &name, &path)) { | |
| 583 | 579 | self.switch_vault(switch_path); | |
| 584 | - | if loose_files { | |
| 585 | - | if let Some(ref mut browser) = self.browser { | |
| 580 | + | if loose_files | |
| 581 | + | && let Some(ref mut browser) = self.browser { | |
| 586 | 582 | let _ = browser.backend.set_config("loose_files", "1"); | |
| 587 | 583 | browser.settings.is_loose_files = true; | |
| 588 | 584 | } | |
| 589 | - | } | |
| 590 | 585 | return; | |
| 591 | 586 | } | |
| 592 | 587 | } | |
| @@ -637,7 +632,6 @@ impl AudioFilesApp { | |||
| 637 | 632 | } | |
| 638 | 633 | } | |
| 639 | 634 | } | |
| 640 | - | } | |
| 641 | 635 | ||
| 642 | 636 | // ── VFS Mirror: sync if dirty ── | |
| 643 | 637 | if let Some(ref mut browser) = self.browser { | |
| @@ -960,7 +954,7 @@ mod tests { | |||
| 960 | 954 | // Empty file → falls through to bundled synckit.toml key | |
| 961 | 955 | if std::env::var("AF_SYNC_API_KEY").is_err() { | |
| 962 | 956 | let key = load_api_key(dir.path()); | |
| 963 | - | assert_eq!(key, parse_synckit_toml_key().map(String::from)); | |
| 957 | + | assert_eq!(key, parse_synckit_toml_key()); | |
| 964 | 958 | } | |
| 965 | 959 | } | |
| 966 | 960 | ||
| @@ -970,7 +964,7 @@ mod tests { | |||
| 970 | 964 | std::fs::write(dir.path().join("sync_api_key"), " \n ").unwrap(); | |
| 971 | 965 | if std::env::var("AF_SYNC_API_KEY").is_err() { | |
| 972 | 966 | let key = load_api_key(dir.path()); | |
| 973 | - | assert_eq!(key, parse_synckit_toml_key().map(String::from)); | |
| 967 | + | assert_eq!(key, parse_synckit_toml_key()); | |
| 974 | 968 | } | |
| 975 | 969 | } | |
| 976 | 970 | ||
| @@ -979,7 +973,7 @@ mod tests { | |||
| 979 | 973 | let dir = tempfile::tempdir().unwrap(); | |
| 980 | 974 | if std::env::var("AF_SYNC_API_KEY").is_err() { | |
| 981 | 975 | let key = load_api_key(dir.path()); | |
| 982 | - | assert_eq!(key, parse_synckit_toml_key().map(String::from)); | |
| 976 | + | assert_eq!(key, parse_synckit_toml_key()); | |
| 983 | 977 | } | |
| 984 | 978 | } | |
| 985 | 979 |
| @@ -137,7 +137,8 @@ mod tests { | |||
| 137 | 137 | ||
| 138 | 138 | assert_eq!(rgba.len(), SIZE * SIZE * 4); | |
| 139 | 139 | // Middle bar (index 2) is full height — pixel at (9, 0) should be coloured | |
| 140 | - | let mid_offset = (0 * SIZE + 9) * 4; | |
| 140 | + | // (row 0, col 9) -> offset (0 * SIZE + 9) * 4 | |
| 141 | + | let mid_offset = 9 * 4; | |
| 141 | 142 | assert_eq!(&rgba[mid_offset..mid_offset + 4], &bar_colour); | |
| 142 | 143 | // Corner (0,0) should be transparent | |
| 143 | 144 | assert_eq!(&rgba[0..4], &[0, 0, 0, 0]); |
| @@ -187,8 +187,8 @@ async fn check_once(status: &Arc<Mutex<UpdateStatus>>) { | |||
| 187 | 187 | Ok(resp) if resp.status().is_success() => { | |
| 188 | 188 | match resp.json::<UpdateResponse>().await { | |
| 189 | 189 | Ok(update) => { | |
| 190 | - | if let Ok(remote) = Version::parse(&update.version) { | |
| 191 | - | if remote > current && is_trusted_download_url(&update.url) { | |
| 190 | + | if let Ok(remote) = Version::parse(&update.version) | |
| 191 | + | && remote > current && is_trusted_download_url(&update.url) { | |
| 192 | 192 | tracing::info!("Update available: v{}", update.version); | |
| 193 | 193 | let mut s = status.lock(); | |
| 194 | 194 | s.available = true; | |
| @@ -196,7 +196,6 @@ async fn check_once(status: &Arc<Mutex<UpdateStatus>>) { | |||
| 196 | 196 | s.notes = update.notes; | |
| 197 | 197 | s.download_url = update.url; | |
| 198 | 198 | } | |
| 199 | - | } | |
| 200 | 199 | } | |
| 201 | 200 | Err(e) => { | |
| 202 | 201 | tracing::warn!("Failed to parse update response: {e}"); |
| @@ -74,11 +74,10 @@ impl AudioFilesApp { | |||
| 74 | 74 | }); | |
| 75 | 75 | ui.add_space(theme::space::SM); | |
| 76 | 76 | ui.horizontal(|ui| { | |
| 77 | - | if ui.button("Choose different location...").clicked() { | |
| 78 | - | if let Some(path) = rfd::FileDialog::new().pick_folder() { | |
| 77 | + | if ui.button("Choose different location...").clicked() | |
| 78 | + | && let Some(path) = rfd::FileDialog::new().pick_folder() { | |
| 79 | 79 | self.vault_setup_path = Some(path); | |
| 80 | 80 | } | |
| 81 | - | } | |
| 82 | 81 | if is_custom && ui.button("Use default").clicked() { | |
| 83 | 82 | self.vault_setup_path = None; | |
| 84 | 83 | } |
| @@ -112,11 +112,10 @@ impl DirectBackend { | |||
| 112 | 112 | let profile = &plugin.profile; | |
| 113 | 113 | ||
| 114 | 114 | // Format: if Original, set to profile's first supported format | |
| 115 | - | if config.format == ExportFormat::Original { | |
| 116 | - | if let Some(fmt) = profile.audio.formats.first() { | |
| 115 | + | if config.format == ExportFormat::Original | |
| 116 | + | && let Some(fmt) = profile.audio.formats.first() { | |
| 117 | 117 | config.format = fmt.clone(); | |
| 118 | 118 | } | |
| 119 | - | } | |
| 120 | 119 | ||
| 121 | 120 | // Sample rate: if not set, use profile's first rate | |
| 122 | 121 | if config.sample_rate.is_none() { |
| @@ -149,7 +149,8 @@ fn remove_orphans( | |||
| 149 | 149 | WHERE vn.id IS NULL AND s.deleted_at IS NULL", | |
| 150 | 150 | ) { | |
| 151 | 151 | Ok(mut stmt) => { | |
| 152 | - | let rows = stmt | |
| 152 | + | ||
| 153 | + | stmt | |
| 153 | 154 | .query_map([], |row| { | |
| 154 | 155 | Ok(( | |
| 155 | 156 | row.get::<_, String>(0)?, | |
| @@ -159,8 +160,7 @@ fn remove_orphans( | |||
| 159 | 160 | }) | |
| 160 | 161 | .ok() | |
| 161 | 162 | .map(|iter| iter.flatten().collect::<Vec<_>>()) | |
| 162 | - | .unwrap_or_default(); | |
| 163 | - | rows | |
| 163 | + | .unwrap_or_default() | |
| 164 | 164 | } | |
| 165 | 165 | Err(e) => { | |
| 166 | 166 | error!("Cleanup worker failed to query orphans: {e}"); | |
| @@ -206,11 +206,10 @@ fn remove_orphans( | |||
| 206 | 206 | ) { | |
| 207 | 207 | Ok(_) => { | |
| 208 | 208 | // Delete from disk | |
| 209 | - | if let Ok(path) = store.sample_path(hash, ext) { | |
| 210 | - | if path.exists() { | |
| 209 | + | if let Ok(path) = store.sample_path(hash, ext) | |
| 210 | + | && path.exists() { | |
| 211 | 211 | let _ = std::fs::remove_file(&path); | |
| 212 | 212 | } | |
| 213 | - | } | |
| 214 | 213 | removed += 1; | |
| 215 | 214 | } | |
| 216 | 215 | Err(e) => { |
| @@ -403,23 +403,21 @@ fn handle_keyboard(ctx: &egui::Context, state: &mut BrowserState) { | |||
| 403 | 403 | if input.key_pressed(egui::Key::E) { | |
| 404 | 404 | if state.edit.show_window { | |
| 405 | 405 | state.close_edit_window(); | |
| 406 | - | } else if let Some(node) = state.selected_node() { | |
| 407 | - | if let Some(hash) = &node.node.sample_hash { | |
| 406 | + | } else if let Some(node) = state.selected_node() | |
| 407 | + | && let Some(hash) = &node.node.sample_hash { | |
| 408 | 408 | let hash = hash.clone(); | |
| 409 | 409 | state.open_edit_window(&hash); | |
| 410 | 410 | } | |
| 411 | - | } | |
| 412 | 411 | } | |
| 413 | 412 | // "F" toggles the floating Sample Forge window for the selected sample | |
| 414 | 413 | if input.key_pressed(egui::Key::F) && !shift { | |
| 415 | 414 | if state.forge.show_window { | |
| 416 | 415 | state.close_forge_window(); | |
| 417 | - | } else if let Some(node) = state.selected_node() { | |
| 418 | - | if let Some(hash) = &node.node.sample_hash { | |
| 416 | + | } else if let Some(node) = state.selected_node() | |
| 417 | + | && let Some(hash) = &node.node.sample_hash { | |
| 419 | 418 | let hash = hash.clone(); | |
| 420 | 419 | state.open_forge_window(&hash); | |
| 421 | 420 | } | |
| 422 | - | } | |
| 423 | 421 | } | |
| 424 | 422 | // "L" toggles loop | |
| 425 | 423 | if input.key_pressed(egui::Key::L) { | |
| @@ -434,28 +432,23 @@ fn handle_keyboard(ctx: &egui::Context, state: &mut BrowserState) { | |||
| 434 | 432 | state.toggle_detail(); | |
| 435 | 433 | } | |
| 436 | 434 | // Shift+F: find similar | |
| 437 | - | if shift && input.key_pressed(egui::Key::F) { | |
| 438 | - | if let Some(node) = state.selected_node() { | |
| 439 | - | if let Some(hash) = &node.node.sample_hash { | |
| 435 | + | if shift && input.key_pressed(egui::Key::F) | |
| 436 | + | && let Some(node) = state.selected_node() | |
| 437 | + | && let Some(hash) = &node.node.sample_hash { | |
| 440 | 438 | let hash = hash.clone(); | |
| 441 | 439 | state.find_similar(&hash); | |
| 442 | 440 | } | |
| 443 | - | } | |
| 444 | - | } | |
| 445 | 441 | // Shift+D: find duplicates | |
| 446 | - | if shift && input.key_pressed(egui::Key::D) { | |
| 447 | - | if let Some(node) = state.selected_node() { | |
| 448 | - | if let Some(hash) = &node.node.sample_hash { | |
| 442 | + | if shift && input.key_pressed(egui::Key::D) | |
| 443 | + | && let Some(node) = state.selected_node() | |
| 444 | + | && let Some(hash) = &node.node.sample_hash { | |
| 449 | 445 | let hash = hash.clone(); | |
| 450 | 446 | state.find_near_duplicates(&hash); | |
| 451 | 447 | } | |
| 452 | - | } | |
| 453 | - | } | |
| 454 | 448 | // Cmd+Shift+M: bulk move (Cmd+M alone conflicts with macOS minimize) | |
| 455 | - | if input.modifiers.command && input.modifiers.shift && input.key_pressed(egui::Key::M) { | |
| 456 | - | if state.selection.count() > 1 { | |
| 449 | + | if input.modifiers.command && input.modifiers.shift && input.key_pressed(egui::Key::M) | |
| 450 | + | && state.selection.count() > 1 { | |
| 457 | 451 | state.open_bulk_move_modal(); | |
| 458 | 452 | } | |
| 459 | - | } | |
| 460 | 453 | }); | |
| 461 | 454 | } |
| @@ -216,11 +216,10 @@ fn import_single_file( | |||
| 216 | 216 | Ok(_) => Ok(ImportFileResult::Imported(hash, ext)), | |
| 217 | 217 | Err(CoreError::NameConflict(_)) => Ok(ImportFileResult::Duplicate), | |
| 218 | 218 | Err(e) => { | |
| 219 | - | if let CoreError::Db(ref sqlite_err) = e { | |
| 220 | - | if sqlite_err.to_string().contains("UNIQUE") { | |
| 219 | + | if let CoreError::Db(ref sqlite_err) = e | |
| 220 | + | && sqlite_err.to_string().contains("UNIQUE") { | |
| 221 | 221 | return Ok(ImportFileResult::Duplicate); | |
| 222 | 222 | } | |
| 223 | - | } | |
| 224 | 223 | Err(e) | |
| 225 | 224 | } | |
| 226 | 225 | } |
| @@ -18,11 +18,10 @@ impl BrowserState { | |||
| 18 | 18 | let analysis = self.backend.get_analysis(hash).ok().flatten(); | |
| 19 | 19 | let source_rate = analysis.as_ref().map(|a| a.sample_rate).unwrap_or(44100); | |
| 20 | 20 | // Seed BPM from analysis when present so BPM-grid chop is ready to go. | |
| 21 | - | if let Some(bpm) = analysis.as_ref().and_then(|a| a.bpm) { | |
| 22 | - | if bpm > 0.0 { | |
| 21 | + | if let Some(bpm) = analysis.as_ref().and_then(|a| a.bpm) | |
| 22 | + | && bpm > 0.0 { | |
| 23 | 23 | self.forge.bpm = bpm; | |
| 24 | 24 | } | |
| 25 | - | } | |
| 26 | 25 | let ext = self.backend.sample_extension(hash).unwrap_or_else(|_| "wav".to_string()); | |
| 27 | 26 | let name = self | |
| 28 | 27 | .selected_node() |
| @@ -704,11 +704,10 @@ impl BrowserState { | |||
| 704 | 704 | pub fn retry_analysis(&mut self) { | |
| 705 | 705 | self.cancel_analysis(); | |
| 706 | 706 | let hashes = std::mem::take(&mut self.last_analysis_hashes); | |
| 707 | - | if let Some(config) = self.last_analysis_config.take() { | |
| 708 | - | if !hashes.is_empty() { | |
| 707 | + | if let Some(config) = self.last_analysis_config.take() | |
| 708 | + | && !hashes.is_empty() { | |
| 709 | 709 | self.run_analysis(hashes, config); | |
| 710 | 710 | } | |
| 711 | - | } | |
| 712 | 711 | } | |
| 713 | 712 | ||
| 714 | 713 | /// Apply user-entered tags to each imported folder's samples, then start analysis. |
| @@ -302,11 +302,10 @@ impl BrowserState { | |||
| 302 | 302 | ||
| 303 | 303 | /// Load column config from the user_config table. | |
| 304 | 304 | pub fn load_column_config(&mut self) { | |
| 305 | - | if let Ok(Some(json)) = self.backend.get_config("column_config") { | |
| 306 | - | if let Ok(parsed) = serde_json::from_str::<ColumnConfig>(&json) { | |
| 305 | + | if let Ok(Some(json)) = self.backend.get_config("column_config") | |
| 306 | + | && let Ok(parsed) = serde_json::from_str::<ColumnConfig>(&json) { | |
| 307 | 307 | self.column_config = parsed; | |
| 308 | 308 | } | |
| 309 | - | } | |
| 310 | 309 | } | |
| 311 | 310 | ||
| 312 | 311 | /// Save the current theme ID to the user_config table. |
| @@ -224,6 +224,7 @@ pub struct BrowserState { | |||
| 224 | 224 | /// Back button on the ConfigureAnalysis screen can rehydrate the previous | |
| 225 | 225 | /// state (C-1). Tags themselves are `INSERT OR IGNORE` so re-applying after | |
| 226 | 226 | /// a Back is a no-op for the backend. | |
| 227 | + | #[allow(clippy::type_complexity)] // a snapshot tuple for Back-button rehydration; a named alias would not earn its keep | |
| 227 | 228 | pub last_folder_tags: Option<(Vec<crate::state::FolderTagEntry>, Vec<(String, String)>)>, | |
| 228 | 229 | /// Rolling progress samples for the current long-running operation | |
| 229 | 230 | /// (import / analysis / export). Drives the rate + ETA readout (M-11). |
| @@ -118,14 +118,13 @@ impl BrowserState { | |||
| 118 | 118 | /// Reload the tag list for the currently focused sample (shown in the detail panel). | |
| 119 | 119 | pub fn refresh_selected_tags(&mut self) { | |
| 120 | 120 | self.selected_tags = Arc::new(Vec::new()); | |
| 121 | - | if let Some(node) = self.selected_node() { | |
| 122 | - | if let Some(hash) = &node.node.sample_hash { | |
| 121 | + | if let Some(node) = self.selected_node() | |
| 122 | + | && let Some(hash) = &node.node.sample_hash { | |
| 123 | 123 | self.selected_tags = Arc::new(self.backend.get_sample_tags(hash).unwrap_or_else(|e| { | |
| 124 | 124 | warn!("Failed to load tags: {e}"); | |
| 125 | 125 | Vec::new() | |
| 126 | 126 | })); | |
| 127 | 127 | } | |
| 128 | - | } | |
| 129 | 128 | } | |
| 130 | 129 | ||
| 131 | 130 | /// Refresh the detail panel (analysis + waveform) for the currently selected sample. | |
| @@ -133,14 +132,13 @@ impl BrowserState { | |||
| 133 | 132 | self.selected_analysis = None; | |
| 134 | 133 | self.selected_waveform = None; | |
| 135 | 134 | ||
| 136 | - | if let Some(node) = self.selected_node() { | |
| 137 | - | if let Some(hash) = &node.node.sample_hash { | |
| 135 | + | if let Some(node) = self.selected_node() | |
| 136 | + | && let Some(hash) = &node.node.sample_hash { | |
| 138 | 137 | self.selected_analysis = self.backend.get_analysis(hash) | |
| 139 | 138 | .unwrap_or(None); | |
| 140 | 139 | self.selected_waveform = self.backend.get_waveform(hash) | |
| 141 | 140 | .unwrap_or(None); | |
| 142 | 141 | } | |
| 143 | - | } | |
| 144 | 142 | } | |
| 145 | 143 | ||
| 146 | 144 | /// Whether the file list currently shows a ".." parent-directory entry. | |
| @@ -199,8 +197,8 @@ impl BrowserState { | |||
| 199 | 197 | return; | |
| 200 | 198 | } | |
| 201 | 199 | ||
| 202 | - | if let Some(node) = self.selected_node() { | |
| 203 | - | if node.node.node_type == NodeType::Directory { | |
| 200 | + | if let Some(node) = self.selected_node() | |
| 201 | + | && node.node.node_type == NodeType::Directory { | |
| 204 | 202 | self.current_dir = Some(node.node.id); | |
| 205 | 203 | self.breadcrumb = self.backend.get_breadcrumb(node.node.id).unwrap_or_else(|e| { | |
| 206 | 204 | warn!("Breadcrumb failed: {e}"); | |
| @@ -209,7 +207,6 @@ impl BrowserState { | |||
| 209 | 207 | self.selection.clear(); | |
| 210 | 208 | self.refresh_contents(); | |
| 211 | 209 | } | |
| 212 | - | } | |
| 213 | 210 | } | |
| 214 | 211 | ||
| 215 | 212 | /// Navigate to the parent directory, or do nothing if already at root. |
| @@ -99,12 +99,11 @@ impl BrowserState { | |||
| 99 | 99 | pub fn toggle_preview(&mut self) { | |
| 100 | 100 | if self.shared.preview.lock().playing { | |
| 101 | 101 | self.stop_preview(); | |
| 102 | - | } else if let Some(node) = self.selected_node() { | |
| 103 | - | if let Some(hash) = &node.node.sample_hash { | |
| 102 | + | } else if let Some(node) = self.selected_node() | |
| 103 | + | && let Some(hash) = &node.node.sample_hash { | |
| 104 | 104 | let hash = hash.clone(); | |
| 105 | 105 | self.trigger_preview(&hash); | |
| 106 | 106 | } | |
| 107 | - | } | |
| 108 | 107 | } | |
| 109 | 108 | ||
| 110 | 109 | /// If autoplay is enabled and the focused node is a sample, preview it. | |
| @@ -112,12 +111,11 @@ impl BrowserState { | |||
| 112 | 111 | if !self.autoplay { | |
| 113 | 112 | return; | |
| 114 | 113 | } | |
| 115 | - | if let Some(node) = self.selected_node() { | |
| 116 | - | if let Some(hash) = &node.node.sample_hash { | |
| 114 | + | if let Some(node) = self.selected_node() | |
| 115 | + | && let Some(hash) = &node.node.sample_hash { | |
| 117 | 116 | let hash = hash.clone(); | |
| 118 | 117 | self.trigger_preview(&hash); | |
| 119 | 118 | } | |
| 120 | - | } | |
| 121 | 119 | } | |
| 122 | 120 | ||
| 123 | 121 | /// Toggle loop mode and persist the setting. |
| @@ -56,8 +56,8 @@ pub fn draw_detail(ui: &mut egui::Ui, state: &mut BrowserState) { | |||
| 56 | 56 | // Hover indicator: paint a vertical accent_blue line at the cursor X | |
| 57 | 57 | // and a time label above it so the user can see where a click-to-seek | |
| 58 | 58 | // would land before committing. | |
| 59 | - | if resp.hovered() { | |
| 60 | - | if let Some(pos) = resp.hover_pos() { | |
| 59 | + | if resp.hovered() | |
| 60 | + | && let Some(pos) = resp.hover_pos() { | |
| 61 | 61 | let rect = resp.rect; | |
| 62 | 62 | let normalized = ((pos.x - rect.left()) / rect.width()).clamp(0.0, 1.0); | |
| 63 | 63 | let total_secs = waveform_data.duration as f32; | |
| @@ -82,15 +82,14 @@ pub fn draw_detail(ui: &mut egui::Ui, state: &mut BrowserState) { | |||
| 82 | 82 | theme::text_secondary(), | |
| 83 | 83 | ); | |
| 84 | 84 | } | |
| 85 | - | } | |
| 86 | 85 | // Click-to-seek: map the click's X position to a 0.0–1.0 fraction | |
| 87 | 86 | // within the waveform rect, then set the playback cursor to that frame. | |
| 88 | - | if resp.clicked() { | |
| 89 | - | if let Some(pos) = resp.interact_pointer_pos() { | |
| 87 | + | if resp.clicked() | |
| 88 | + | && let Some(pos) = resp.interact_pointer_pos() { | |
| 90 | 89 | let rect = resp.rect; | |
| 91 | 90 | let normalized = ((pos.x - rect.left()) / rect.width()).clamp(0.0, 1.0); | |
| 92 | - | if let Some(hash) = &node.node.sample_hash { | |
| 93 | - | if state.previewing_hash.as_deref() == Some(hash) { | |
| 91 | + | if let Some(hash) = &node.node.sample_hash | |
| 92 | + | && state.previewing_hash.as_deref() == Some(hash) { | |
| 94 | 93 | let mut playback = state.shared.preview.lock(); | |
| 95 | 94 | if let Some(ref buf) = playback.buffer { | |
| 96 | 95 | let total_frames = if playback.streaming { | |
| @@ -102,9 +101,7 @@ pub fn draw_detail(ui: &mut egui::Ui, state: &mut BrowserState) { | |||
| 102 | 101 | .min((playback.decoded_frames.max(1) - 1) as f64); | |
| 103 | 102 | } | |
| 104 | 103 | } | |
| 105 | - | } | |
| 106 | 104 | } | |
| 107 | - | } | |
| 108 | 105 | ||
| 109 | 106 | ui.add_space(theme::section_spacing()); | |
| 110 | 107 | } | |
| @@ -226,8 +223,8 @@ pub fn draw_detail(ui: &mut egui::Ui, state: &mut BrowserState) { | |||
| 226 | 223 | || ui.small_button("+").on_hover_text("Add tag").clicked() | |
| 227 | 224 | { | |
| 228 | 225 | let tag = state.tag_input.trim().to_string(); | |
| 229 | - | if !tag.is_empty() { | |
| 230 | - | if let Some(ref hash) = node.node.sample_hash { | |
| 226 | + | if !tag.is_empty() | |
| 227 | + | && let Some(ref hash) = node.node.sample_hash { | |
| 231 | 228 | if audiofiles_core::tags::validate_tag(&tag).is_ok() { | |
| 232 | 229 | let _ = state.backend.add_tag(hash, &tag); | |
| 233 | 230 | state.tag_input.clear(); | |
| @@ -236,15 +233,14 @@ pub fn draw_detail(ui: &mut egui::Ui, state: &mut BrowserState) { | |||
| 236 | 233 | state.status = format!("Invalid tag: {tag}"); | |
| 237 | 234 | } | |
| 238 | 235 | } | |
| 239 | - | } | |
| 240 | 236 | } | |
| 241 | 237 | }); | |
| 242 | 238 | ||
| 243 | 239 | // Tag suggestions based on classification. Per-classification dismissals | |
| 244 | 240 | // let the user say "I never tag kicks with `percussion`" once and have | |
| 245 | 241 | // the suggestion stop appearing on every future kick. | |
| 246 | - | if let Some(ref analysis) = state.selected_analysis { | |
| 247 | - | if let Some(ref class) = analysis.classification { | |
| 242 | + | if let Some(ref analysis) = state.selected_analysis | |
| 243 | + | && let Some(ref class) = analysis.classification { | |
| 248 | 244 | let class_str = class.to_string(); | |
| 249 | 245 | let dismissed_for_class = state | |
| 250 | 246 | .dismissed_suggestions | |
| @@ -273,12 +269,10 @@ pub fn draw_detail(ui: &mut egui::Ui, state: &mut BrowserState) { | |||
| 273 | 269 | ) | |
| 274 | 270 | .on_hover_text(format!("Add tag: {sug}")) | |
| 275 | 271 | .clicked() | |
| 276 | - | { | |
| 277 | - | if let Some(ref hash) = node.node.sample_hash { | |
| 272 | + | && let Some(ref hash) = node.node.sample_hash { | |
| 278 | 273 | let _ = state.backend.add_tag(hash, sug); | |
| 279 | 274 | state.refresh_selected_tags(); | |
| 280 | 275 | } | |
| 281 | - | } | |
| 282 | 276 | // Painted X (two crossed line_segments) — matches the | |
| 283 | 277 | // Phase 4 M-8 X-icon precedent in instrument_panel.rs | |
| 284 | 278 | // rather than a literal "x" glyph. Muted stroke since | |
| @@ -350,7 +344,6 @@ pub fn draw_detail(ui: &mut egui::Ui, state: &mut BrowserState) { | |||
| 350 | 344 | ui.ctx().request_repaint(); | |
| 351 | 345 | } | |
| 352 | 346 | } | |
| 353 | - | } | |
| 354 | 347 | ||
| 355 | 348 | }); // end of Tags CollapsingHeader | |
| 356 | 349 | ||
| @@ -359,12 +352,11 @@ pub fn draw_detail(ui: &mut egui::Ui, state: &mut BrowserState) { | |||
| 359 | 352 | .default_open(true) | |
| 360 | 353 | .show(ui, |ui| { | |
| 361 | 354 | ui.horizontal(|ui| { | |
| 362 | - | if ui.button("Copy Path").on_hover_text("Copy file path to clipboard").clicked() { | |
| 363 | - | if let Some(path) = state.selected_sample_path() { | |
| 355 | + | if ui.button("Copy Path").on_hover_text("Copy file path to clipboard").clicked() | |
| 356 | + | && let Some(path) = state.selected_sample_path() { | |
| 364 | 357 | state.status = format!("Copied: {path}"); | |
| 365 | 358 | ui.ctx().copy_text(path); | |
| 366 | 359 | } | |
| 367 | - | } | |
| 368 | 360 | if let Some(hash) = &node.node.sample_hash { | |
| 369 | 361 | let hash = hash.clone(); | |
| 370 | 362 | if ui.button("Edit").on_hover_text("Open sample editor (E)").clicked() { |
| @@ -148,8 +148,8 @@ fn draw_waveform_section(ui: &mut egui::Ui, state: &mut BrowserState, hash: &str | |||
| 148 | 148 | // first so a click in the editor auditions from the clicked point — | |
| 149 | 149 | // previously the click was a silent no-op until preview was started | |
| 150 | 150 | // elsewhere. | |
| 151 | - | if resp.clicked() { | |
| 152 | - | if let Some(pos) = resp.interact_pointer_pos() { | |
| 151 | + | if resp.clicked() | |
| 152 | + | && let Some(pos) = resp.interact_pointer_pos() { | |
| 153 | 153 | let rect = resp.rect; | |
| 154 | 154 | let normalized = ((pos.x - rect.left()) / rect.width()).clamp(0.0, 1.0); | |
| 155 | 155 | if state.previewing_hash.as_deref() != Some(hash) { | |
| @@ -166,7 +166,6 @@ fn draw_waveform_section(ui: &mut egui::Ui, state: &mut BrowserState, hash: &str | |||
| 166 | 166 | .min((playback.decoded_frames.max(1) - 1) as f64); | |
| 167 | 167 | } | |
| 168 | 168 | } | |
| 169 | - | } | |
| 170 | 169 | } | |
| 171 | 170 | } | |
| 172 | 171 | ||
| @@ -209,11 +208,10 @@ fn trim_handle(ui: &mut egui::Ui, rect: egui::Rect, frac: f32, id_salt: &str) -> | |||
| 209 | 208 | ui.ctx().set_cursor_icon(egui::CursorIcon::ResizeHorizontal); | |
| 210 | 209 | } | |
| 211 | 210 | let mut new_frac = frac; | |
| 212 | - | if resp.dragged() { | |
| 213 | - | if let Some(p) = resp.interact_pointer_pos() { | |
| 211 | + | if resp.dragged() | |
| 212 | + | && let Some(p) = resp.interact_pointer_pos() { | |
| 214 | 213 | new_frac = ((p.x - rect.left()) / rect.width()).clamp(0.0, 1.0); | |
| 215 | 214 | } | |
| 216 | - | } | |
| 217 | 215 | let width = if active { 2.5 } else { 1.0 }; | |
| 218 | 216 | ui.painter_at(rect).line_segment( | |
| 219 | 217 | [egui::pos2(x, rect.top()), egui::pos2(x, rect.bottom())], | |
| @@ -534,16 +532,14 @@ fn draw_result_section(ui: &mut egui::Ui, state: &mut BrowserState) { | |||
| 534 | 532 | ||
| 535 | 533 | let mut mode = state.edit.result_mode; | |
| 536 | 534 | ui.horizontal(|ui| { | |
| 537 | - | if ui.radio_value(&mut mode, Some(EditResultMode::Replace), "Replace original").changed() { | |
| 538 | - | if let Some(m) = mode { | |
| 535 | + | if ui.radio_value(&mut mode, Some(EditResultMode::Replace), "Replace original").changed() | |
| 536 | + | && let Some(m) = mode { | |
| 539 | 537 | state.set_edit_result_mode(m); | |
| 540 | 538 | } | |
| 541 | - | } | |
| 542 | - | if ui.radio_value(&mut mode, Some(EditResultMode::Sibling), "Create sibling").changed() { | |
| 543 | - | if let Some(m) = mode { | |
| 539 | + | if ui.radio_value(&mut mode, Some(EditResultMode::Sibling), "Create sibling").changed() | |
| 540 | + | && let Some(m) = mode { | |
| 544 | 541 | state.set_edit_result_mode(m); | |
| 545 | 542 | } | |
| 546 | - | } | |
| 547 | 543 | }); | |
| 548 | 544 | // Replace mode advisory. The previous "no in-app undo" copy was retired | |
| 549 | 545 | // when the inline Undo affordance landed (C-1 part 2). The hint still |
| @@ -115,9 +115,9 @@ pub fn draw_configure_export(ui: &mut egui::Ui, state: &mut BrowserState) { | |||
| 115 | 115 | } | |
| 116 | 116 | ||
| 117 | 117 | // Device file size limit warning | |
| 118 | - | if let Some(ref profile_name) = config.device_profile { | |
| 119 | - | if let Some(profile) = available_profiles.iter().find(|p| &p.name == profile_name) { | |
| 120 | - | if let Some(max_bytes) = profile.max_file_size_bytes { | |
| 118 | + | if let Some(ref profile_name) = config.device_profile | |
| 119 | + | && let Some(profile) = available_profiles.iter().find(|p| &p.name == profile_name) | |
| 120 | + | && let Some(max_bytes) = profile.max_file_size_bytes { | |
| 121 | 121 | // Estimate: duration * sample_rate * channels * bytes_per_sample | |
| 122 | 122 | // Use worst case: stereo 24-bit at 48kHz = 288000 bytes/sec | |
| 123 | 123 | let bytes_per_sec: f64 = 288_000.0; | |
| @@ -148,8 +148,6 @@ pub fn draw_configure_export(ui: &mut egui::Ui, state: &mut BrowserState) { | |||
| 148 | 148 | ); | |
| 149 | 149 | } | |
| 150 | 150 | } | |
| 151 | - | } | |
| 152 | - | } | |
| 153 | 151 | ||
| 154 | 152 | // Disk space check (M-3): estimate from actual per-item durations | |
| 155 | 153 | // and the current encoding config rather than a fixed 10 MB/item | |
| @@ -181,15 +179,14 @@ pub fn draw_configure_export(ui: &mut egui::Ui, state: &mut BrowserState) { | |||
| 181 | 179 | if ui.button("Cancel").clicked() { | |
| 182 | 180 | state.import_mode = ImportMode::None; | |
| 183 | 181 | } | |
| 184 | - | if ui.button("Export").clicked() { | |
| 185 | - | if let ImportMode::ConfigureExport { ref items, ref config, .. } = | |
| 182 | + | if ui.button("Export").clicked() | |
| 183 | + | && let ImportMode::ConfigureExport { ref items, ref config, .. } = | |
| 186 | 184 | state.import_mode | |
| 187 | 185 | { | |
| 188 | 186 | let items = items.clone(); | |
| 189 | 187 | let config = config.clone(); | |
| 190 | 188 | state.run_export(items, config); | |
| 191 | 189 | } | |
| 192 | - | } | |
| 193 | 190 | }); | |
| 194 | 191 | ui.add_space(theme::space::XS); | |
| 195 | 192 | }); | |
| @@ -258,8 +255,8 @@ pub fn draw_configure_export(ui: &mut egui::Ui, state: &mut BrowserState) { | |||
| 258 | 255 | // device's supported formats / rates / depths / channels | |
| 259 | 256 | // so the user knows what the lock is hiding, not just | |
| 260 | 257 | // that something is hidden. | |
| 261 | - | if let Some(ref name) = config.device_profile { | |
| 262 | - | if let Some(profile) = | |
| 258 | + | if let Some(ref name) = config.device_profile | |
| 259 | + | && let Some(profile) = | |
| 263 | 260 | available_profiles.iter().find(|p| &p.name == name) | |
| 264 | 261 | { | |
| 265 | 262 | ui.label( | |
| @@ -289,7 +286,6 @@ pub fn draw_configure_export(ui: &mut egui::Ui, state: &mut BrowserState) { | |||
| 289 | 286 | ); | |
| 290 | 287 | } | |
| 291 | 288 | } | |
| 292 | - | } | |
| 293 | 289 | } | |
| 294 | 290 | ui.add_space(theme::space::MD); | |
| 295 | 291 | } | |
| @@ -351,8 +347,8 @@ pub fn draw_configure_export(ui: &mut egui::Ui, state: &mut BrowserState) { | |||
| 351 | 347 | } | |
| 352 | 348 | ); | |
| 353 | 349 | ||
| 354 | - | if needs_encoding_options { | |
| 355 | - | if let ImportMode::ConfigureExport { ref mut config, .. } = state.import_mode { | |
| 350 | + | if needs_encoding_options | |
| 351 | + | && let ImportMode::ConfigureExport { ref mut config, .. } = state.import_mode { | |
| 356 | 352 | // Sample rate | |
| 357 | 353 | ui.label(egui::RichText::new("Sample Rate").strong()); | |
| 358 | 354 | let rates: [(Option<u32>, &str); 4] = [ | |
| @@ -382,7 +378,6 @@ pub fn draw_configure_export(ui: &mut egui::Ui, state: &mut BrowserState) { | |||
| 382 | 378 | } | |
| 383 | 379 | ui.add_space(theme::space::MD); | |
| 384 | 380 | } | |
| 385 | - | } | |
| 386 | 381 | ||
| 387 | 382 | // --- Channels --- | |
| 388 | 383 | ui.label(egui::RichText::new("Channels").strong()); | |
| @@ -431,8 +426,8 @@ pub fn draw_configure_export(ui: &mut egui::Ui, state: &mut BrowserState) { | |||
| 431 | 426 | ui.add_space(theme::space::MD); | |
| 432 | 427 | ||
| 433 | 428 | // --- Naming pattern (when flattened) --- | |
| 434 | - | if let ImportMode::ConfigureExport { ref mut config, ref items, .. } = state.import_mode { | |
| 435 | - | if config.flatten { | |
| 429 | + | if let ImportMode::ConfigureExport { ref mut config, ref items, .. } = state.import_mode | |
| 430 | + | && config.flatten { | |
| 436 | 431 | ui.label(egui::RichText::new("Naming Pattern").strong()); | |
| 437 | 432 | let mut pattern = config.naming_pattern.clone().unwrap_or_default(); | |
| 438 | 433 | ||
| @@ -509,7 +504,6 @@ pub fn draw_configure_export(ui: &mut egui::Ui, state: &mut BrowserState) { | |||
| 509 | 504 | } | |
| 510 | 505 | ui.add_space(theme::space::MD); | |
| 511 | 506 | } | |
| 512 | - | } | |
| 513 | 507 | ||
| 514 | 508 | // --- Destination --- | |
| 515 | 509 | ui.label(egui::RichText::new("Destination").strong()); | |
| @@ -517,15 +511,14 @@ pub fn draw_configure_export(ui: &mut egui::Ui, state: &mut BrowserState) { | |||
| 517 | 511 | ui.horizontal(|ui| { | |
| 518 | 512 | let dest_display = config.destination.display().to_string(); | |
| 519 | 513 | ui.label(&dest_display); | |
| 520 | - | if ui.button("Browse...").clicked() { | |
| 521 | - | if let Some(path) = rfd::FileDialog::new() | |
| 514 | + | if ui.button("Browse...").clicked() | |
| 515 | + | && let Some(path) = rfd::FileDialog::new() | |
| 522 | 516 | .set_title("Export Destination") | |
| 523 | 517 | .set_directory(&config.destination) | |
| 524 | 518 | .pick_folder() | |
| 525 | 519 | { | |
| 526 | 520 | config.destination = path; | |
| 527 | 521 | } | |
| 528 | - | } | |
| 529 | 522 | }); | |
| 530 | 523 | } | |
| 531 | 524 | }); | |
| @@ -631,8 +624,8 @@ pub fn draw_export_complete(ui: &mut egui::Ui, state: &mut BrowserState) { | |||
| 631 | 624 | // without navigating Finder/Explorer themselves. Suppressed when | |
| 632 | 625 | // the destination wasn't stashed (e.g. export driven from outside | |
| 633 | 626 | // the wizard's run_export path). | |
| 634 | - | if let Some(dest) = state.last_export_destination.clone() { | |
| 635 | - | if ui.button("Open destination folder").clicked() { | |
| 627 | + | if let Some(dest) = state.last_export_destination.clone() | |
| 628 | + | && ui.button("Open destination folder").clicked() { | |
| 636 | 629 | #[cfg(target_os = "macos")] | |
| 637 | 630 | let _ = std::process::Command::new("open").arg(&dest).spawn(); | |
| 638 | 631 | #[cfg(target_os = "linux")] | |
| @@ -642,7 +635,6 @@ pub fn draw_export_complete(ui: &mut egui::Ui, state: &mut BrowserState) { | |||
| 642 | 635 | .args(["/c", "start", "", &dest.display().to_string()]) | |
| 643 | 636 | .spawn(); | |
| 644 | 637 | } | |
| 645 | - | } | |
| 646 | 638 | }); | |
| 647 | 639 | }); | |
| 648 | 640 | } |
| @@ -59,14 +59,13 @@ pub fn draw_file_list( | |||
| 59 | 59 | ui.horizontal(|ui| { | |
| 60 | 60 | widgets::step_number(ui, 1); | |
| 61 | 61 | ui.label("Drop a folder of samples onto this window, or click "); | |
| 62 | - | if ui.link("Import").clicked() { | |
| 63 | - | if let Some(path) = rfd::FileDialog::new() | |
| 62 | + | if ui.link("Import").clicked() | |
| 63 | + | && let Some(path) = rfd::FileDialog::new() | |
| 64 | 64 | .set_title("Quick Import Folder") | |
| 65 | 65 | .pick_folder() | |
| 66 | 66 | { | |
| 67 | 67 | state.quick_import_folder(path); | |
| 68 | 68 | } | |
| 69 | - | } | |
| 70 | 69 | }); | |
| 71 | 70 | ui.add_space(theme::space::SM); | |
| 72 | 71 | ||
| @@ -112,14 +111,13 @@ pub fn draw_file_list( | |||
| 112 | 111 | tooltip: Some("Choose a folder of samples to import"), | |
| 113 | 112 | }), | |
| 114 | 113 | ); | |
| 115 | - | if clicked { | |
| 116 | - | if let Some(path) = rfd::FileDialog::new() | |
| 114 | + | if clicked | |
| 115 | + | && let Some(path) = rfd::FileDialog::new() | |
| 117 | 116 | .set_title("Import folder") | |
| 118 | 117 | .pick_folder() | |
| 119 | 118 | { | |
| 120 | 119 | state.quick_import_folder(path); | |
| 121 | 120 | } | |
| 122 | - | } | |
| 123 | 121 | // Quiet link to bring the welcome screen back if the user dismissed it. | |
| 124 | 122 | ui.vertical_centered(|ui| { | |
| 125 | 123 | ui.add_space(theme::space::LG); | |
| @@ -350,8 +348,8 @@ pub fn draw_file_list( | |||
| 350 | 348 | } | |
| 351 | 349 | let Some(hash) = node.node.sample_hash.as_ref() else { return; }; | |
| 352 | 350 | if node.cloud_only { | |
| 353 | - | if let Some(sync) = sync_manager { | |
| 354 | - | if ui | |
| 351 | + | if let Some(sync) = sync_manager | |
| 352 | + | && ui | |
| 355 | 353 | .button("Download") | |
| 356 | 354 | .on_hover_text("Fetch this sample from the cloud") | |
| 357 | 355 | .clicked() | |
| @@ -367,7 +365,6 @@ pub fn draw_file_list( | |||
| 367 | 365 | "Sync not ready — open the Sync panel first".to_string(); | |
| 368 | 366 | } | |
| 369 | 367 | } | |
| 370 | - | } | |
| 371 | 368 | } else { | |
| 372 | 369 | let is_playing = state.previewing_hash.as_deref() == Some(hash) | |
| 373 | 370 | && state.shared.preview.lock().playing; | |
| @@ -495,25 +492,23 @@ fn draw_name_column( | |||
| 495 | 492 | state.enter_directory(); | |
| 496 | 493 | } | |
| 497 | 494 | NodeType::Sample => { | |
| 498 | - | if !node.cloud_only { | |
| 499 | - | if let Some(hash) = &node.node.sample_hash { | |
| 495 | + | if !node.cloud_only | |
| 496 | + | && let Some(hash) = &node.node.sample_hash { | |
| 500 | 497 | let hash = hash.clone(); | |
| 501 | 498 | state.trigger_preview(&hash); | |
| 502 | 499 | } | |
| 503 | - | } | |
| 504 | 500 | } | |
| 505 | 501 | } | |
| 506 | 502 | } | |
| 507 | 503 | ||
| 508 | 504 | // Drag source for instrument zone assignment (not for cloud-only) | |
| 509 | - | if state.instrument_visible && node.node.node_type == NodeType::Sample && !node.cloud_only { | |
| 510 | - | if let Some(hash) = &node.node.sample_hash { | |
| 505 | + | if state.instrument_visible && node.node.node_type == NodeType::Sample && !node.cloud_only | |
| 506 | + | && let Some(hash) = &node.node.sample_hash { | |
| 511 | 507 | resp.dnd_set_drag_payload(DragPayload { | |
| 512 | 508 | hash: hash.to_string(), | |
| 513 | 509 | name: node.node.name.clone(), | |
| 514 | 510 | }); | |
| 515 | 511 | } | |
| 516 | - | } | |
| 517 | 512 | ||
| 518 | 513 | // Native OS drag-out to Finder/DAW (only when instrument panel is closed) | |
| 519 | 514 | #[cfg(any(target_os = "macos", target_os = "windows", target_os = "linux"))] | |
| @@ -564,6 +559,7 @@ fn draw_name_column( | |||
| 564 | 559 | ||
| 565 | 560 | /// Draw the analysis data columns (duration, classification, BPM, key, peak dB, tags) | |
| 566 | 561 | /// for a single file-list row. Each visible column emits a `row.col()` call. | |
| 562 | + | #[allow(clippy::too_many_arguments)] // one column-renderer; the args map 1:1 to visible columns | |
| 567 | 563 | fn draw_analysis_columns( | |
| 568 | 564 | row: &mut egui_extras::TableRow, | |
| 569 | 565 | node: &VfsNodeWithAnalysis, |