| 1 |
|
| 2 |
|
| 3 |
use egui; |
| 4 |
|
| 5 |
use crate::state::{BrowserState, ImportMode}; |
| 6 |
use crate::ui::{detail, edit_panel, export_screens, file_list, filter_panel, footer, forge_panel, import_screens, instrument_panel, overlays, sidebar, theme, toolbar}; |
| 7 |
use audiofiles_core::vfs::NodeType; |
| 8 |
|
| 9 |
|
| 10 |
|
| 11 |
|
| 12 |
pub fn draw_browser( |
| 13 |
ui: &mut egui::Ui, |
| 14 |
state: &mut BrowserState, |
| 15 |
sync_manager: Option<&audiofiles_sync::SyncManager>, |
| 16 |
) { |
| 17 |
let ctx = ui.ctx().clone(); |
| 18 |
let ctx = &ctx; |
| 19 |
theme::apply_theme(ctx); |
| 20 |
|
| 21 |
match &state.import_mode { |
| 22 |
ImportMode::None => { |
| 23 |
|
| 24 |
if state.poll_workers() { |
| 25 |
ctx.request_repaint(); |
| 26 |
} |
| 27 |
handle_keyboard(ctx, state); |
| 28 |
draw_normal_browser(ui, state, sync_manager); |
| 29 |
} |
| 30 |
ImportMode::ConfigureImport { .. } => { |
| 31 |
import_screens::draw_configure_import(ui, state); |
| 32 |
} |
| 33 |
ImportMode::Importing { .. } |
| 34 |
| ImportMode::Analyzing { .. } |
| 35 |
| ImportMode::Exporting { .. } |
| 36 |
| ImportMode::Cleaning { .. } => { |
| 37 |
if state.poll_workers() { |
| 38 |
ctx.request_repaint(); |
| 39 |
} |
| 40 |
match &state.import_mode { |
| 41 |
ImportMode::Importing { .. } => { |
| 42 |
import_screens::draw_import_progress(ui, state); |
| 43 |
} |
| 44 |
ImportMode::Analyzing { .. } => { |
| 45 |
import_screens::draw_analysis_progress(ui, state); |
| 46 |
} |
| 47 |
ImportMode::Exporting { .. } => { |
| 48 |
export_screens::draw_export_progress(ui, state); |
| 49 |
} |
| 50 |
ImportMode::Cleaning { .. } => { |
| 51 |
import_screens::draw_cleanup_progress(ui, state); |
| 52 |
} |
| 53 |
|
| 54 |
ImportMode::TagFolders { .. } => { |
| 55 |
import_screens::draw_tag_folders(ui, state); |
| 56 |
} |
| 57 |
ImportMode::ConfigureAnalysis { .. } => { |
| 58 |
import_screens::draw_configure_analysis(ui, state); |
| 59 |
} |
| 60 |
ImportMode::ReviewSuggestions { .. } => { |
| 61 |
import_screens::draw_review_suggestions(ui, state); |
| 62 |
} |
| 63 |
ImportMode::ExportComplete { .. } => { |
| 64 |
export_screens::draw_export_complete(ui, state); |
| 65 |
} |
| 66 |
ImportMode::ReviewErrors => { |
| 67 |
import_screens::draw_review_errors(ui, state); |
| 68 |
} |
| 69 |
_ => {} |
| 70 |
} |
| 71 |
} |
| 72 |
ImportMode::TagFolders { .. } => { |
| 73 |
import_screens::draw_tag_folders(ui, state); |
| 74 |
} |
| 75 |
ImportMode::ConfigureAnalysis { .. } => { |
| 76 |
import_screens::draw_configure_analysis(ui, state); |
| 77 |
} |
| 78 |
ImportMode::ReviewSuggestions { .. } => { |
| 79 |
import_screens::draw_review_suggestions(ui, state); |
| 80 |
} |
| 81 |
ImportMode::ConfigureExport { .. } => { |
| 82 |
export_screens::draw_configure_export(ui, state); |
| 83 |
} |
| 84 |
ImportMode::ExportComplete { .. } => { |
| 85 |
export_screens::draw_export_complete(ui, state); |
| 86 |
} |
| 87 |
ImportMode::ReviewErrors => { |
| 88 |
import_screens::draw_review_errors(ui, state); |
| 89 |
} |
| 90 |
ImportMode::OperationCancelled { .. } => { |
| 91 |
import_screens::draw_operation_cancelled(ui, state); |
| 92 |
} |
| 93 |
} |
| 94 |
|
| 95 |
|
| 96 |
|
| 97 |
|
| 98 |
let modal_active = state.pending_confirm.is_some() |
| 99 |
|| state.bulk_modal.is_some() |
| 100 |
|| state.show_help |
| 101 |
|| state.show_vfs_create |
| 102 |
|| state.vfs_rename_target.is_some() |
| 103 |
|| state.show_dir_create |
| 104 |
|| state.dir_rename_target.is_some() |
| 105 |
|| state.show_loose_files_warning |
| 106 |
|| state.pending_import_preflight.is_some(); |
| 107 |
if modal_active { |
| 108 |
crate::ui::widgets::modal_scrim(ctx); |
| 109 |
} |
| 110 |
|
| 111 |
|
| 112 |
if state.pending_confirm.is_some() { |
| 113 |
overlays::draw_confirm_dialog(ctx, state); |
| 114 |
} |
| 115 |
if state.bulk_modal.is_some() { |
| 116 |
overlays::draw_bulk_modal(ctx, state); |
| 117 |
} |
| 118 |
if state.show_help { |
| 119 |
overlays::draw_help_overlay(ctx, state); |
| 120 |
} |
| 121 |
if state.show_vfs_create { |
| 122 |
overlays::draw_vfs_create_modal(ctx, state); |
| 123 |
} |
| 124 |
if state.vfs_rename_target.is_some() { |
| 125 |
overlays::draw_vfs_rename_modal(ctx, state); |
| 126 |
} |
| 127 |
if state.show_dir_create { |
| 128 |
overlays::draw_dir_create_modal(ctx, state); |
| 129 |
} |
| 130 |
if state.dir_rename_target.is_some() { |
| 131 |
overlays::draw_dir_rename_modal(ctx, state); |
| 132 |
} |
| 133 |
if state.show_loose_files_warning { |
| 134 |
overlays::draw_loose_files_warning(ctx, state); |
| 135 |
} |
| 136 |
if state.pending_import_preflight.is_some() { |
| 137 |
overlays::draw_import_preflight(ctx, state); |
| 138 |
} |
| 139 |
|
| 140 |
|
| 141 |
if state.settings.show_manager { |
| 142 |
crate::ui::settings_panel::draw_settings_panel(ctx, state); |
| 143 |
} |
| 144 |
|
| 145 |
|
| 146 |
if state.sync.show_panel { |
| 147 |
if let Some(sync) = sync_manager { |
| 148 |
crate::ui::sync_panel::draw_sync_panel(ctx, state, sync); |
| 149 |
} else { |
| 150 |
crate::ui::sync_panel::draw_sync_not_configured(ctx, state); |
| 151 |
} |
| 152 |
} |
| 153 |
|
| 154 |
|
| 155 |
if state.edit.show_window { |
| 156 |
edit_panel::draw_edit_window(ctx, state); |
| 157 |
} |
| 158 |
|
| 159 |
|
| 160 |
if state.forge.show_window { |
| 161 |
forge_panel::draw_forge_window(ctx, state); |
| 162 |
} |
| 163 |
} |
| 164 |
|
| 165 |
|
| 166 |
fn draw_normal_browser( |
| 167 |
ui: &mut egui::Ui, |
| 168 |
state: &mut BrowserState, |
| 169 |
sync_manager: Option<&audiofiles_sync::SyncManager>, |
| 170 |
) { |
| 171 |
let ctx = ui.ctx().clone(); |
| 172 |
|
| 173 |
|
| 174 |
egui::Panel::top("toolbar") |
| 175 |
.exact_size(56.0) |
| 176 |
.show_inside(ui, |ui| { |
| 177 |
toolbar::draw_toolbar(ui, state, sync_manager); |
| 178 |
}); |
| 179 |
|
| 180 |
|
| 181 |
egui::Panel::bottom("footer").show_inside(ui, |ui| { |
| 182 |
let ctx = ui.ctx().clone(); |
| 183 |
footer::draw_footer(ui, &ctx, state); |
| 184 |
}); |
| 185 |
|
| 186 |
|
| 187 |
if state.show_midi_window { |
| 188 |
instrument_panel::draw_midi_window(&ctx, state); |
| 189 |
} |
| 190 |
|
| 191 |
|
| 192 |
if state.filter_panel_open { |
| 193 |
egui::Panel::left("filter_panel") |
| 194 |
.default_size(200.0) |
| 195 |
.size_range(160.0..=300.0) |
| 196 |
.show_inside(ui, |ui| { |
| 197 |
egui::ScrollArea::vertical().show(ui, |ui| { |
| 198 |
filter_panel::draw_filter_panel(ui, state); |
| 199 |
}); |
| 200 |
}); |
| 201 |
} else if state.sidebar_visible { |
| 202 |
egui::Panel::left("sidebar") |
| 203 |
.default_size(180.0) |
| 204 |
.size_range(120.0..=280.0) |
| 205 |
.show_inside(ui, |ui| { |
| 206 |
sidebar::draw_sidebar(ui, state); |
| 207 |
}); |
| 208 |
} |
| 209 |
|
| 210 |
|
| 211 |
|
| 212 |
|
| 213 |
if state.detail_visible && ctx.content_rect().width() >= 700.0 { |
| 214 |
egui::Panel::right("detail") |
| 215 |
.default_size(250.0) |
| 216 |
.size_range(200.0..=400.0) |
| 217 |
.show_inside(ui, |ui| { |
| 218 |
egui::ScrollArea::vertical().show(ui, |ui| { |
| 219 |
detail::draw_detail(ui, state); |
| 220 |
}); |
| 221 |
}); |
| 222 |
} |
| 223 |
|
| 224 |
|
| 225 |
egui::CentralPanel::default().show_inside(ui, |ui| { |
| 226 |
file_list::draw_file_list(ui, state, sync_manager); |
| 227 |
}); |
| 228 |
} |
| 229 |
|
| 230 |
|
| 231 |
fn handle_keyboard(ctx: &egui::Context, state: &mut BrowserState) { |
| 232 |
|
| 233 |
if ctx.memory(|m| m.focused().is_some()) { |
| 234 |
|
| 235 |
ctx.input(|input| { |
| 236 |
if input.key_pressed(egui::Key::Escape) |
| 237 |
&& !state.search_query.is_empty() |
| 238 |
{ |
| 239 |
state.search_query.clear(); |
| 240 |
state.apply_search(); |
| 241 |
} |
| 242 |
}); |
| 243 |
return; |
| 244 |
} |
| 245 |
|
| 246 |
ctx.input(|input| { |
| 247 |
|
| 248 |
if input.key_pressed(egui::Key::Escape) { |
| 249 |
if state.settings.show_manager { |
| 250 |
state.settings.show_manager = false; |
| 251 |
} else if state.forge.show_window { |
| 252 |
state.close_forge_window(); |
| 253 |
} else if state.edit.show_window { |
| 254 |
state.close_edit_window(); |
| 255 |
} else if state.sync.show_panel { |
| 256 |
state.sync.show_panel = false; |
| 257 |
} else if state.bulk_modal.is_some() { |
| 258 |
state.close_bulk_modal(); |
| 259 |
} else if state.pending_import_preflight.is_some() { |
| 260 |
state.cancel_import_preflight(); |
| 261 |
} else if state.pending_confirm.is_some() { |
| 262 |
state.dismiss_confirm(); |
| 263 |
} else if state.show_help { |
| 264 |
state.show_help = false; |
| 265 |
} else if matches!( |
| 266 |
state.import_mode, |
| 267 |
ImportMode::ConfigureImport { .. } |
| 268 |
| ImportMode::TagFolders { .. } |
| 269 |
| ImportMode::ConfigureAnalysis { .. } |
| 270 |
| ImportMode::ReviewSuggestions { .. } |
| 271 |
| ImportMode::ConfigureExport { .. } |
| 272 |
| ImportMode::ReviewErrors |
| 273 |
) { |
| 274 |
|
| 275 |
|
| 276 |
|
| 277 |
|
| 278 |
state.cancel_import(); |
| 279 |
} else if matches!(state.import_mode, ImportMode::OperationCancelled { .. }) { |
| 280 |
|
| 281 |
|
| 282 |
state.import_mode = ImportMode::None; |
| 283 |
} else if !state.search_query.is_empty() { |
| 284 |
state.search_query.clear(); |
| 285 |
state.apply_search(); |
| 286 |
} |
| 287 |
return; |
| 288 |
} |
| 289 |
|
| 290 |
if input.key_pressed(egui::Key::F1) { |
| 291 |
state.show_help = !state.show_help; |
| 292 |
} |
| 293 |
|
| 294 |
if input.key_pressed(egui::Key::F2) |
| 295 |
&& state.selection.count() > 1 |
| 296 |
{ |
| 297 |
state.open_bulk_rename_modal(); |
| 298 |
if state.bulk_modal.is_some() { |
| 299 |
state.update_rename_previews(); |
| 300 |
} |
| 301 |
} |
| 302 |
|
| 303 |
if input.key_pressed(egui::Key::Delete) { |
| 304 |
state.confirm_delete_selected(); |
| 305 |
} |
| 306 |
|
| 307 |
|
| 308 |
|
| 309 |
if input.modifiers.command && input.key_pressed(egui::Key::A) && !input.modifiers.shift { |
| 310 |
let len = state.visible_len(); |
| 311 |
let start = if state.current_dir.is_some() { 1 } else { 0 }; |
| 312 |
state.selection.select_all_from(start, len); |
| 313 |
return; |
| 314 |
} |
| 315 |
|
| 316 |
|
| 317 |
if input.modifiers.command && input.modifiers.shift && input.key_pressed(egui::Key::I) { |
| 318 |
state.invert_selection(); |
| 319 |
return; |
| 320 |
} |
| 321 |
|
| 322 |
|
| 323 |
if input.key_pressed(egui::Key::Tab) && !input.modifiers.shift { |
| 324 |
if !state.detail_visible { |
| 325 |
state.set_detail_visible(true); |
| 326 |
} |
| 327 |
state.focus_tag_input = true; |
| 328 |
return; |
| 329 |
} |
| 330 |
|
| 331 |
|
| 332 |
if input.modifiers.command && input.key_pressed(egui::Key::Z) { |
| 333 |
state.undo(); |
| 334 |
return; |
| 335 |
} |
| 336 |
|
| 337 |
|
| 338 |
if input.modifiers.command && input.key_pressed(egui::Key::T) { |
| 339 |
if state.selection.count() > 1 { |
| 340 |
state.open_bulk_tag_modal(); |
| 341 |
} |
| 342 |
return; |
| 343 |
} |
| 344 |
|
| 345 |
let shift = input.modifiers.shift; |
| 346 |
|
| 347 |
if input.key_pressed(egui::Key::ArrowDown) || input.key_pressed(egui::Key::J) { |
| 348 |
if shift { |
| 349 |
state.selection.extend_down(state.visible_len()); |
| 350 |
state.scroll_to_row = Some(state.selection.focus); |
| 351 |
} else { |
| 352 |
state.select_next(); |
| 353 |
state.autoplay_current(); |
| 354 |
} |
| 355 |
} |
| 356 |
if input.key_pressed(egui::Key::ArrowUp) || input.key_pressed(egui::Key::K) { |
| 357 |
if shift { |
| 358 |
state.selection.extend_up(); |
| 359 |
state.scroll_to_row = Some(state.selection.focus); |
| 360 |
} else { |
| 361 |
state.select_prev(); |
| 362 |
state.autoplay_current(); |
| 363 |
} |
| 364 |
} |
| 365 |
if input.key_pressed(egui::Key::Enter) || input.key_pressed(egui::Key::ArrowRight) { |
| 366 |
if let Some(node) = state.selected_node() { |
| 367 |
match node.node.node_type { |
| 368 |
NodeType::Directory => state.enter_directory(), |
| 369 |
NodeType::Sample => { |
| 370 |
if let Some(hash) = &node.node.sample_hash { |
| 371 |
let hash = hash.clone(); |
| 372 |
state.trigger_preview(&hash); |
| 373 |
} |
| 374 |
} |
| 375 |
} |
| 376 |
} else if state.current_dir.is_some() && state.selection.focus == 0 { |
| 377 |
state.go_up(); |
| 378 |
} |
| 379 |
} |
| 380 |
if input.key_pressed(egui::Key::Backspace) || input.key_pressed(egui::Key::ArrowLeft) { |
| 381 |
|
| 382 |
|
| 383 |
|
| 384 |
|
| 385 |
if state.similarity_search_hash.is_some() { |
| 386 |
state.clear_similarity_search(); |
| 387 |
} else { |
| 388 |
state.go_up(); |
| 389 |
} |
| 390 |
} |
| 391 |
if input.key_pressed(egui::Key::Space) { |
| 392 |
state.toggle_preview(); |
| 393 |
} |
| 394 |
|
| 395 |
if input.key_pressed(egui::Key::Slash) { |
| 396 |
state.focus_search = true; |
| 397 |
} |
| 398 |
|
| 399 |
if input.key_pressed(egui::Key::I) { |
| 400 |
state.show_midi_window = !state.show_midi_window; |
| 401 |
} |
| 402 |
|
| 403 |
if input.key_pressed(egui::Key::E) { |
| 404 |
if state.edit.show_window { |
| 405 |
state.close_edit_window(); |
| 406 |
} else if let Some(node) = state.selected_node() |
| 407 |
&& let Some(hash) = &node.node.sample_hash { |
| 408 |
let hash = hash.clone(); |
| 409 |
state.open_edit_window(&hash); |
| 410 |
} |
| 411 |
} |
| 412 |
|
| 413 |
if input.key_pressed(egui::Key::F) && !shift { |
| 414 |
if state.forge.show_window { |
| 415 |
state.close_forge_window(); |
| 416 |
} else if let Some(node) = state.selected_node() |
| 417 |
&& let Some(hash) = &node.node.sample_hash { |
| 418 |
let hash = hash.clone(); |
| 419 |
state.open_forge_window(&hash); |
| 420 |
} |
| 421 |
} |
| 422 |
|
| 423 |
if input.key_pressed(egui::Key::L) { |
| 424 |
state.toggle_loop(); |
| 425 |
} |
| 426 |
|
| 427 |
if input.key_pressed(egui::Key::S) { |
| 428 |
state.toggle_sidebar(); |
| 429 |
} |
| 430 |
|
| 431 |
if input.key_pressed(egui::Key::D) && !shift { |
| 432 |
state.toggle_detail(); |
| 433 |
} |
| 434 |
|
| 435 |
if shift && input.key_pressed(egui::Key::F) |
| 436 |
&& let Some(node) = state.selected_node() |
| 437 |
&& let Some(hash) = &node.node.sample_hash { |
| 438 |
let hash = hash.clone(); |
| 439 |
state.find_similar(&hash); |
| 440 |
} |
| 441 |
|
| 442 |
if shift && input.key_pressed(egui::Key::D) |
| 443 |
&& let Some(node) = state.selected_node() |
| 444 |
&& let Some(hash) = &node.node.sample_hash { |
| 445 |
let hash = hash.clone(); |
| 446 |
state.find_near_duplicates(&hash); |
| 447 |
} |
| 448 |
|
| 449 |
if input.modifiers.command && input.modifiers.shift && input.key_pressed(egui::Key::M) |
| 450 |
&& state.selection.count() > 1 { |
| 451 |
state.open_bulk_move_modal(); |
| 452 |
} |
| 453 |
}); |
| 454 |
} |
| 455 |
|