| @@ -198,6 +198,8 @@ enum Item { |
| 198 | 198 | | NewPreset, |
| 199 | 199 | | /// "Rename", on a custom EQ preset's card. |
| 200 | 200 | | Rename(u8), |
| 201 | + | /// "Delete", on a custom EQ preset's card. |
| 202 | + | Delete(u8), |
| 201 | 203 | | } |
| 202 | 204 | | |
| 203 | 205 | | /// The dialog that asks for a preset's name. |
| @@ -297,6 +299,10 @@ enum Message { |
| 297 | 299 | | NameInput(String), |
| 298 | 300 | | SubmitName, |
| 299 | 301 | | CancelName, |
| 302 | + | /// Delete a custom preset (by its id), after a confirmation. |
| 303 | + | DeletePreset(u8), |
| 304 | + | /// The answer to "Delete the preset?". |
| 305 | + | ConfirmDelete(bool), |
| 300 | 306 | | ShowHelp, |
| 301 | 307 | | /// Read the buds again. |
| 302 | 308 | | Refresh, |
| @@ -348,6 +354,8 @@ struct App { |
| 348 | 354 | | palette_problem: Option<String>, |
| 349 | 355 | | /// The preset-name dialog, when it is open. |
| 350 | 356 | | prompt: Option<NamePrompt>, |
| 357 | + | /// "Delete the preset?" is open, for this preset. |
| 358 | + | confirm_delete: Option<u8>, |
| 351 | 359 | | } |
| 352 | 360 | | |
| 353 | 361 | | impl App { |
| @@ -386,6 +394,7 @@ impl App { |
| 386 | 394 | | confirm_find: false, |
| 387 | 395 | | palette_problem: None, |
| 388 | 396 | | prompt: None, |
| 397 | + | confirm_delete: None, |
| 389 | 398 | | }; |
| 390 | 399 | | app.refresh_palette(); |
| 391 | 400 | | app |
| @@ -441,6 +450,16 @@ impl App { |
| 441 | 450 | | } |
| 442 | 451 | | Message::SubmitName => self.submit_name(), |
| 443 | 452 | | Message::CancelName => self.prompt = None, |
| 453 | + | Message::DeletePreset(id) => self.confirm_delete = Some(id), |
| 454 | + | Message::ConfirmDelete(yes) => { |
| 455 | + | if let (true, Some(id)) = (yes, self.confirm_delete.take()) |
| 456 | + | && let Some(preset) = self.buds.preset(id) |
| 457 | + | { |
| 458 | + | let command = Command::DeleteEq(buds::key(preset)); |
| 459 | + | self.write(Item::Preset(id), command); |
| 460 | + | } |
| 461 | + | self.confirm_delete = None; |
| 462 | + | } |
| 444 | 463 | | Message::Refresh => self.refresh(), |
| 445 | 464 | | Message::Worker(event) => self.answer(event), |
| 446 | 465 | | Message::Focused => { |
| @@ -520,6 +539,16 @@ impl App { |
| 520 | 539 | | }); |
| 521 | 540 | | } |
| 522 | 541 | | } |
| 542 | + | Command::DeleteEq(_) => { |
| 543 | + | let id = item_preset(item); |
| 544 | + | if let Some(eq) = &mut self.buds.eq { |
| 545 | + | eq.presets.retain(|p| !(p.custom && p.id == id)); |
| 546 | + | } |
| 547 | + | if self.buds.status.eq.as_deref() == Some(&format!("custom-{id}")) { |
| 548 | + | self.buds.set_eq("pure-raw".to_owned()); |
| 549 | + | } |
| 550 | + | self.draft = None; |
| 551 | + | } |
| 523 | 552 | | Command::RenameEq(_, name) => { |
| 524 | 553 | | if let Some(preset) = self.buds.preset(item_preset(item)) { |
| 525 | 554 | | let preset = EqPreset { name, ..preset.clone() }; |
| @@ -893,6 +922,11 @@ impl App { |
| 893 | 922 | | } |
| 894 | 923 | | |
| 895 | 924 | | fn key(&mut self, key: &Key, modifiers: Modifiers) -> Task<Message> { |
| 925 | + | if self.confirm_delete.is_some() { |
| 926 | + | // Only Enter deletes; any other key says no. |
| 927 | + | let yes = matches!(key, Key::Named(keyboard::key::Named::Enter)); |
| 928 | + | return self.update(Message::ConfirmDelete(yes)); |
| 929 | + | } |
| 896 | 930 | | if self.prompt.is_some() { |
| 897 | 931 | | // The name field takes the keys, and Enter in it; only Esc is ours. |
| 898 | 932 | | if matches!(key, Key::Named(keyboard::key::Named::Escape)) { |
| @@ -1058,6 +1092,7 @@ impl App { |
| 1058 | 1092 | | Item::Find => (action != Action::Left).then_some(Message::Find), |
| 1059 | 1093 | | Item::NewPreset => (action != Action::Left).then_some(Message::NewPreset), |
| 1060 | 1094 | | Item::Rename(id) => (action != Action::Left).then_some(Message::RenamePreset(id)), |
| 1095 | + | Item::Delete(id) => (action != Action::Left).then_some(Message::DeletePreset(id)), |
| 1061 | 1096 | | Item::Bass => { |
| 1062 | 1097 | | let (low, high) = self.buds.bass_range?; |
| 1063 | 1098 | | let now = self.bass_draft.or(self.buds.bass); |
| @@ -1207,6 +1242,8 @@ impl App { |
| 1207 | 1242 | | stack![window, self.confirm_find()].into() |
| 1208 | 1243 | | } else if let Some(prompt) = &self.prompt { |
| 1209 | 1244 | | stack![window, self.name_prompt(prompt)].into() |
| 1245 | + | } else if let Some(id) = self.confirm_delete { |
| 1246 | + | stack![window, self.confirm_delete(id)].into() |
| 1210 | 1247 | | } else { |
| 1211 | 1248 | | window.into() |
| 1212 | 1249 | | } |
| @@ -1357,6 +1394,37 @@ impl App { |
| 1357 | 1394 | | opaque(backdrop) |
| 1358 | 1395 | | } |
| 1359 | 1396 | | |
| 1397 | + | /// "Delete the preset?", over a dimmed window: its bands are lost. |
| 1398 | + | fn confirm_delete(&self, id: u8) -> Element<'_, Message> { |
| 1399 | + | let look = self.look; |
| 1400 | + | let p = look.p; |
| 1401 | + | let name = self |
| 1402 | + | .buds |
| 1403 | + | .preset(id) |
| 1404 | + | .map_or("this preset", |preset| preset.name.as_str()); |
| 1405 | + | let words = column![ |
| 1406 | + | text(format!("Delete {name}?")).size(22).font(look.bold()).color(p.fg), |
| 1407 | + | text("Its bands are lost. The buds keep their other presets.") |
| 1408 | + | .size(14) |
| 1409 | + | .color(p.dim), |
| 1410 | + | ] |
| 1411 | + | .spacing(8); |
| 1412 | + | let buttons = row![ |
| 1413 | + | space::horizontal(), |
| 1414 | + | ui::pill(look, "Cancel", false, Message::ConfirmDelete(false)), |
| 1415 | + | ui::pill(look, "Delete", true, Message::ConfirmDelete(true)), |
| 1416 | + | ] |
| 1417 | + | .spacing(10); |
| 1418 | + | let dialog = ui::panel(look, column![words, buttons].spacing(22).into()); |
| 1419 | + | let backdrop = container(container(dialog).width(460)) |
| 1420 | + | .center(Length::Fill) |
| 1421 | + | .style(|_| container::Style { |
| 1422 | + | background: Some(Color { a: 0.6, ..Color::BLACK }.into()), |
| 1423 | + | ..container::Style::default() |
| 1424 | + | }); |
| 1425 | + | opaque(backdrop) |
| 1426 | + | } |
| 1427 | + | |
| 1360 | 1428 | | /// The dialog that asks for a preset's name, over a dimmed window. |
| 1361 | 1429 | | fn name_prompt<'a>(&'a self, prompt: &'a NamePrompt) -> Element<'a, Message> { |
| 1362 | 1430 | | let look = self.look; |
| @@ -1787,11 +1855,12 @@ mod tests { |
| 1787 | 1855 | | press(&mut app, "j l"); |
| 1788 | 1856 | | assert_eq!(app.buds.status.eq.as_deref(), Some("pure-raw")); |
| 1789 | 1857 | | assert_eq!(app.buds.eq_name(), "Pure raw"); |
| 1858 | + | // Back to Custom1, the fifth preset: its bands come back. |
| 1859 | + | press(&mut app, "j j j j l"); |
| 1860 | + | assert_eq!(app.buds.status.eq.as_deref(), Some("custom-4")); |
| 1790 | 1861 | | press(&mut app, "G"); |
| 1791 | 1862 | | assert_eq!(app.focus(), Some(Item::Band(4, 16000))); |
| 1792 | | − | // Moving a band saves the custom preset, which also selects it. |
| 1793 | 1863 | | press(&mut app, "l"); |
| 1794 | | − | assert_eq!(app.buds.status.eq.as_deref(), Some("custom-4")); |
| 1795 | 1864 | | let last = |app: &App| app.buds.preset(4).unwrap().bands.last().unwrap().gain; |
| 1796 | 1865 | | assert_eq!(last(&app), 3); |
| 1797 | 1866 | | press(&mut app, "l l l l l"); |
| @@ -2022,6 +2091,36 @@ mod tests { |
| 2022 | 2091 | | )); |
| 2023 | 2092 | | } |
| 2024 | 2093 | | |
| 2094 | + | #[test] |
| 2095 | + | fn presets_are_deleted_after_a_yes() { |
| 2096 | + | let mut app = app(); |
| 2097 | + | let _ = app.update(Message::DeletePreset(4)); |
| 2098 | + | assert_eq!(app.confirm_delete, Some(4)); |
| 2099 | + | press(&mut app, "j"); |
| 2100 | + | assert_eq!(app.confirm_delete, None, "any other key says no"); |
| 2101 | + | assert!(app.buds.preset(4).is_some()); |
| 2102 | + | let _ = app.update(Message::DeletePreset(4)); |
| 2103 | + | press(&mut app, "Enter"); |
| 2104 | + | assert!(app.buds.preset(4).is_none()); |
| 2105 | + | assert_eq!(app.buds.status.eq.as_deref(), Some("pure-raw")); |
| 2106 | + | |
| 2107 | + | let (mut app, commands) = live(); |
| 2108 | + | let _ = commands.try_recv(); |
| 2109 | + | let _ = app.update(Message::Worker(Event::Read(Box::new(reading())))); |
| 2110 | + | let _ = app.update(Message::DeletePreset(4)); |
| 2111 | + | let _ = app.update(Message::ConfirmDelete(true)); |
| 2112 | + | assert!(matches!(commands.try_recv(), Ok(Command::DeleteEq(preset)) if preset == "custom-4")); |
| 2113 | + | assert_eq!(app.pending(), [Item::Preset(4)]); |
| 2114 | + | } |
| 2115 | + | |
| 2116 | + | #[test] |
| 2117 | + | fn only_the_preset_in_use_shows_its_bands() { |
| 2118 | + | let mut app = app(); |
| 2119 | + | press(&mut app, "J j l"); |
| 2120 | + | assert_eq!(app.buds.status.eq.as_deref(), Some("pure-raw")); |
| 2121 | + | assert!(!app.items().iter().any(|item| matches!(item, Item::Band(..)))); |
| 2122 | + | } |
| 2123 | + | |
| 2025 | 2124 | | #[test] |
| 2026 | 2125 | | fn steps() { |
| 2027 | 2126 | | let abc = ['a', 'b', 'c']; |
| @@ -2086,6 +2185,10 @@ mod tests { |
| 2086 | 2185 | | let _ = naming.update(Message::NameInput("Custom1 ".to_owned())); |
| 2087 | 2186 | | let _ = naming.update(Message::SubmitName); |
| 2088 | 2187 | | shots.push(("name-prompt".to_owned(), 800.0, naming)); |
| 2188 | + | shots.push(("equalizer-builtin".to_owned(), 1000.0, on(Page::Equalizer, "j l"))); |
| 2189 | + | let mut deleting = on(Page::Equalizer, ""); |
| 2190 | + | let _ = deleting.update(Message::DeletePreset(4)); |
| 2191 | + | shots.push(("delete-confirm".to_owned(), 800.0, deleting)); |
| 2089 | 2192 | | let mut connecting = App::new(palette.clone(), font, None, false); |
| 2090 | 2193 | | connecting.link = Link::Connecting; |
| 2091 | 2194 | | shots.push(("connecting".to_owned(), 800.0, connecting)); |