max / audiofiles
6 files changed,
+140 insertions,
-31 deletions
| @@ -2990,9 +2990,9 @@ | |||
| 2990 | 2990 | ||
| 2991 | 2991 | [[package]] | |
| 2992 | 2992 | name = "makeover-immediate" | |
| 2993 | - | version = "0.5.0" | |
| 2993 | + | version = "0.5.2" | |
| 2994 | 2994 | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2995 | - | checksum = "84f4962445845b0f436ef8f60035dbf0748bddda84f1e060183e82b3e2cafc7b" | |
| 2995 | + | checksum = "66eefff77a8f3b3c36afc479c193c46df37c21d924451b47e35a3f08469a5355" | |
| 2996 | 2996 | dependencies = [ | |
| 2997 | 2997 | "egui", | |
| 2998 | 2998 | "makeover-layout", | |
| @@ -3000,9 +3000,9 @@ | |||
| 3000 | 3000 | ||
| 3001 | 3001 | [[package]] | |
| 3002 | 3002 | name = "makeover-layout" | |
| 3003 | - | version = "0.8.0" | |
| 3003 | + | version = "0.8.2" | |
| 3004 | 3004 | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 3005 | - | checksum = "ab87cfbb9bb3278d647159ee2089ed4bcabc66d77a6433e65563ae784943ce02" | |
| 3005 | + | checksum = "9883c75a9d26fce10be2b979c01a74f8c07513800a70ec6bf598de1e7d411f1b" | |
| 3006 | 3006 | ||
| 3007 | 3007 | [[package]] | |
| 3008 | 3008 | name = "matchers" | |
| @@ -7297,10 +7297,6 @@ | |||
| 7297 | 7297 | "winnow 1.0.4", | |
| 7298 | 7298 | ] | |
| 7299 | 7299 | ||
| 7300 | - | [[patch.unused]] | |
| 7301 | - | name = "supernote-push" | |
| 7302 | - | version = "0.1.0" | |
| 7303 | - | ||
| 7304 | 7300 | [[patch.unused]] | |
| 7305 | 7301 | name = "kberg" | |
| 7306 | 7302 | version = "0.1.0" | |
| @@ -7312,3 +7308,7 @@ | |||
| 7312 | 7308 | [[patch.unused]] | |
| 7313 | 7309 | name = "docengine" | |
| 7314 | 7310 | version = "0.4.0" | |
| 7311 | + | ||
| 7312 | + | [[patch.unused]] | |
| 7313 | + | name = "supernote-push" | |
| 7314 | + | version = "0.1.0" |
| @@ -12,8 +12,8 @@ | |||
| 12 | 12 | audiofiles-browser = { path = "crates/audiofiles-browser" } | |
| 13 | 13 | audiofiles-sync = { path = "crates/audiofiles-sync" } | |
| 14 | 14 | audiofiles-rhai = { path = "crates/audiofiles-rhai" } | |
| 15 | - | makeover-layout = "0.8.0" | |
| 16 | - | makeover-immediate = "0.5.0" | |
| 15 | + | makeover-layout = "0.8.2" | |
| 16 | + | makeover-immediate = "0.5.2" | |
| 17 | 17 | egui = { version = "0.35", default-features = false, features = ["default_fonts"] } | |
| 18 | 18 | egui_extras = { version = "0.35", default-features = false } | |
| 19 | 19 | eframe = { version = "0.35", default-features = false, features = ["default_fonts", "glow"] } |
| @@ -194,10 +194,7 @@ | |||
| 194 | 194 | /// to apply (on the GUI thread, with `&mut BrowserState`). | |
| 195 | 195 | pub fn take_completed(&self) -> Option<(DialogHandler, Vec<PathBuf>)> { | |
| 196 | 196 | let mut slot = self.pending.lock(); | |
| 197 | - | let result = match slot.as_ref() { | |
| 198 | - | Some(p) => p.rx.try_recv(), | |
| 199 | - | None => return None, | |
| 200 | - | }; | |
| 197 | + | let result = slot.as_ref()?.rx.try_recv(); | |
| 201 | 198 | match result { | |
| 202 | 199 | Ok(paths) => { | |
| 203 | 200 | let pending = slot.take().expect("pending checked above"); |
| @@ -375,10 +375,34 @@ | |||
| 375 | 375 | ||
| 376 | 376 | // Create new library | |
| 377 | 377 | ui.label(egui::RichText::new("Add Library").strong()); | |
| 378 | - | ui.horizontal(|ui| { | |
| 379 | - | ui.label("Name:"); | |
| 380 | - | ui.text_edit_singleline(&mut state.settings.create_name); | |
| 381 | - | }); | |
| 378 | + | // The name is the one input in this form the description can say. | |
| 379 | + | // The folder picker is a button and a path, and the storage style | |
| 380 | + | // is a radio pair; `FieldKind` has neither, so both stay | |
| 381 | + | // hand-rolled below and `makeover_immediate::group` cannot be used | |
| 382 | + | // for a form with one describable field out of three. Filed on | |
| 383 | + | // GoingsOn `bebfd112` rather than worked around here. | |
| 384 | + | // | |
| 385 | + | // The error is the state the old form left unexplained: a folder | |
| 386 | + | // chosen and no name, where "Create New" disables itself and says | |
| 387 | + | // nothing about why. | |
| 388 | + | let name_missing = state.settings.create_path.is_some() | |
| 389 | + | && state.settings.create_name.trim().is_empty(); | |
| 390 | + | let name_field = makeover_layout::Field { | |
| 391 | + | required: true, | |
| 392 | + | error: name_missing.then_some("A library needs a name."), | |
| 393 | + | ..makeover_layout::Field::new( | |
| 394 | + | makeover_layout::FieldKind::Text, | |
| 395 | + | "create_name", | |
| 396 | + | "Name", | |
| 397 | + | ) | |
| 398 | + | }; | |
| 399 | + | widgets::field( | |
| 400 | + | ui, | |
| 401 | + | &name_field, | |
| 402 | + | makeover_immediate::Filling::Text(&mut state.settings.create_name), | |
| 403 | + | None, | |
| 404 | + | ); | |
| 405 | + | ui.add_space(theme::space::bound()); | |
| 382 | 406 | ui.horizontal(|ui| { | |
| 383 | 407 | if ui.button("Choose folder...").clicked() { | |
| 384 | 408 | state.dialogs.pick_folder("Choose folder", |s, p| { | |
| @@ -396,19 +420,54 @@ | |||
| 396 | 420 | // Storage style is a significant choice (copy vs reference in | |
| 397 | 421 | // place), promote it from a buried checkbox to an explicit radio | |
| 398 | 422 | // choice so users opt into loose-files mode deliberately. | |
| 399 | - | ui.label(egui::RichText::new("Storage style:").small().color(theme::content_secondary())); | |
| 400 | - | let mut style = state.settings.create_loose_files; | |
| 401 | - | if ui.radio_value(&mut style, false, "Copy samples into library (recommended)") | |
| 402 | - | .on_hover_text("Samples are duplicated into the library's content-addressed store. Originals can be moved or deleted safely.") | |
| 403 | - | .changed() | |
| 423 | + | // | |
| 424 | + | // A described field now, and specifically a `Radio` rather than a | |
| 425 | + | // `Select`: this is the call site `FieldKind::Radio` was added for | |
| 426 | + | // in makeover-layout 0.8.1. The reason the comment above gives is | |
| 427 | + | // the reason the kind exists — the alternatives to an irreversible | |
| 428 | + | // choice have to be readable without opening anything — and it was | |
| 429 | + | // being enforced by hand here because the description could not say | |
| 430 | + | // it. | |
| 431 | + | const STORAGE_STYLES: [makeover_layout::Choice<'static>; 2] = [ | |
| 432 | + | makeover_layout::Choice { | |
| 433 | + | value: "copy", | |
| 434 | + | label: "Copy samples into library (recommended)", | |
| 435 | + | }, | |
| 436 | + | makeover_layout::Choice { | |
| 437 | + | value: "reference", | |
| 438 | + | label: "Reference samples in place (loose-files mode)", | |
| 439 | + | }, | |
| 440 | + | ]; | |
| 441 | + | // The description names its options by string and this app holds a | |
| 442 | + | // bool, so the two are marshalled here rather than by reshaping the | |
| 443 | + | // state. "Which of a fixed set" is the question; the bool is this | |
| 444 | + | // app's private encoding of the answer, and it stays private. | |
| 445 | + | let mut storage = String::from(if state.settings.create_loose_files { | |
| 446 | + | "reference" | |
| 447 | + | } else { | |
| 448 | + | "copy" | |
| 449 | + | }); | |
| 450 | + | let storage_field = makeover_layout::Field { | |
| 451 | + | // Was the tail of one option's hover text. It is the one fact | |
| 452 | + | // that applies to the whole question rather than to either | |
| 453 | + | // answer, and standing help is shown without being hunted for, | |
| 454 | + | // which an irreversible choice deserves. | |
| 455 | + | hint: Some("Cannot be changed after the library is created."), | |
| 456 | + | ..makeover_layout::Field::radio( | |
| 457 | + | "create_storage_style", | |
| 458 | + | "Storage style", | |
| 459 | + | &STORAGE_STYLES, | |
| 460 | + | ) | |
| 461 | + | }; | |
| 462 | + | if widgets::field( | |
| 463 | + | ui, | |
| 464 | + | &storage_field, | |
| 465 | + | makeover_immediate::Filling::Text(&mut storage), | |
| 466 | + | None, | |
| 467 | + | ) | |
| 468 | + | .is_some_and(|response| response.changed()) | |
| 404 | 469 | { | |
| 405 | - | state.settings.create_loose_files = style; | |
| 406 | - | } | |
| 407 | - | if ui.radio_value(&mut style, true, "Reference samples in place (loose-files mode)") | |
| 408 | - | .on_hover_text("Reference files in place instead of duplicating. Saves disk space but samples break if originals are moved or deleted. Cannot be changed later.") | |
| 409 | - | .changed() | |
| 410 | - | { | |
| 411 | - | state.settings.create_loose_files = style; | |
| 470 | + | state.settings.create_loose_files = storage == "reference"; | |
| 412 | 471 | } | |
| 413 | 472 | if state.settings.create_loose_files { | |
| 414 | 473 | ui.label( |
| @@ -862,7 +862,17 @@ | |||
| 862 | 862 | ||
| 863 | 863 | visuals.panel_fill = t.surface_overlay; | |
| 864 | 864 | visuals.window_fill = t.surface_overlay; | |
| 865 | - | visuals.extreme_bg_color = t.surface_page; | |
| 865 | + | // egui's fill for the things you look *into*, text edits above all, so it | |
| 866 | + | // is the well and not the page. It said page until makeover 2.3.0 derived | |
| 867 | + | // `surface-well`, which is the same substitution `palette()` documents | |
| 868 | + | // having outgrown: the well was the page because there was no well token, | |
| 869 | + | // and this is the last place that stayed behind after there was one. | |
| 870 | + | // | |
| 871 | + | // Load-bearing now rather than cosmetic. `makeover_immediate::field` fills | |
| 872 | + | // its well from `palette().well`, so leaving this on the page would put a | |
| 873 | + | // described field and a hand-built one at two different colours, which is | |
| 874 | + | // exactly the inconsistency `widgets::text_field` was written to prevent. | |
| 875 | + | visuals.extreme_bg_color = t.surface_well; | |
| 866 | 876 | visuals.faint_bg_color = lerp_color(t.surface_page, t.surface_overlay, 0.3); | |
| 867 | 877 | ||
| 868 | 878 | visuals.selection.bg_fill = lerp_color(t.surface_page, t.action, 0.3); |
| @@ -408,6 +408,49 @@ | |||
| 408 | 408 | ) | |
| 409 | 409 | } | |
| 410 | 410 | ||
| 411 | + | // --- Form fields ------------------------------------------------------------- | |
| 412 | + | ||
| 413 | + | /// The geometry a described form field is drawn with. | |
| 414 | + | /// | |
| 415 | + | /// The well is [`inset_well`]'s frame at field size: same radius, same | |
| 416 | + | /// one-point margin holding the text off the bevel, same stroke. That sameness | |
| 417 | + | /// is the point, and it is the rule [`text_field`] was written to keep — a | |
| 418 | + | /// field is a well at a different size, so it looks like one. | |
| 419 | + | /// | |
| 420 | + | /// The two gaps are named relationships, not numbers. `gap` separates a label | |
| 421 | + | /// from its own control, which is `space::bound` by definition; `group_gap` | |
| 422 | + | /// separates one field from the next, which is `space::peer`. | |
| 423 | + | fn field_style() -> makeover_immediate::FieldStyle { | |
| 424 | + | makeover_immediate::FieldStyle { | |
| 425 | + | frame: makeover_immediate::FrameStyle { | |
| 426 | + | radius: theme::radius_container(), | |
| 427 | + | margin: egui::Margin::same(theme::stroke::DEFAULT as i8), | |
| 428 | + | stroke: theme::stroke::DEFAULT, | |
| 429 | + | }, | |
| 430 | + | gap: theme::space::bound(), | |
| 431 | + | group_gap: theme::space::peer(), | |
| 432 | + | required_marker: "*", | |
| 433 | + | } | |
| 434 | + | } | |
| 435 | + | ||
| 436 | + | /// One described form field, drawn with this app's palette and geometry. | |
| 437 | + | /// | |
| 438 | + | /// The thin wrapper [`raised_frame`] and [`inset_well`] are, for the same | |
| 439 | + | /// reason: `makeover-immediate` takes the palette and the geometry as values, | |
| 440 | + | /// and every call site would otherwise repeat both. | |
| 441 | + | /// | |
| 442 | + | /// What it deliberately does not do is hold the value. That stays with the | |
| 443 | + | /// caller and arrives borrowed as a `Filling`, which is the division the | |
| 444 | + | /// description draws: the question is described, the answer is not. | |
| 445 | + | pub fn field( | |
| 446 | + | ui: &mut egui::Ui, | |
| 447 | + | field: &makeover_layout::Field<'_>, | |
| 448 | + | filling: makeover_immediate::Filling<'_>, | |
| 449 | + | state: Option<makeover_layout::State>, | |
| 450 | + | ) -> Option<egui::Response> { | |
| 451 | + | makeover_immediate::field(ui, field, filling, state, &theme::palette(), &field_style()) | |
| 452 | + | } | |
| 453 | + | ||
| 411 | 454 | /// Inline informational banner: raised card, body text in `content_secondary`. | |
| 412 | 455 | /// Used for one-time tips and unobtrusive panel notices. | |
| 413 | 456 | pub fn info_banner(ui: &mut egui::Ui, body: &str) { |