max / audiofiles
- Co-Authored-By
- Claude Opus 5 (1M context) <noreply@anthropic.com>
- Claude-Session
- https://claude.ai/code/session_0153yKAYUmoC9bZz2eV6cA88
6 files changed,
+178 insertions,
-55 deletions
| @@ -3074,9 +3074,9 @@ | |||
| 3074 | 3074 | ||
| 3075 | 3075 | [[package]] | |
| 3076 | 3076 | name = "makeover-immediate" | |
| 3077 | - | version = "0.39.0" | |
| 3077 | + | version = "0.40.0" | |
| 3078 | 3078 | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 3079 | - | checksum = "628f2e478a4bd00a305cc7b43ee1bdc229ccd8dd9c7077ba7d7cc1f85ba6994c" | |
| 3079 | + | checksum = "ebf95b374f071aac2094c22d1d90ee565ae920adb9950195c072352ee4da2f01" | |
| 3080 | 3080 | dependencies = [ | |
| 3081 | 3081 | "egui", | |
| 3082 | 3082 | "egui_extras", | |
| @@ -3086,9 +3086,9 @@ | |||
| 3086 | 3086 | ||
| 3087 | 3087 | [[package]] | |
| 3088 | 3088 | name = "makeover-layout" | |
| 3089 | - | version = "0.38.0" | |
| 3089 | + | version = "0.39.0" | |
| 3090 | 3090 | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 3091 | - | checksum = "73ad4a03ed87470b838bd84be586739682f0a1210db1d7ad4a0146cb9ef7734b" | |
| 3091 | + | checksum = "253ab4630d39ec39233020b81024773da52e93b15544217034ce6ca083ba9227" | |
| 3092 | 3092 | ||
| 3093 | 3093 | [[package]] | |
| 3094 | 3094 | name = "makeover-timing" | |
| @@ -4256,7 +4256,7 @@ | |||
| 4256 | 4256 | ||
| 4257 | 4257 | [[package]] | |
| 4258 | 4258 | name = "quasi-immediate" | |
| 4259 | - | version = "0.77.0" | |
| 4259 | + | version = "0.78.0" | |
| 4260 | 4260 | dependencies = [ | |
| 4261 | 4261 | "docengine", | |
| 4262 | 4262 | "egui", | |
| @@ -4267,7 +4267,7 @@ | |||
| 4267 | 4267 | ||
| 4268 | 4268 | [[package]] | |
| 4269 | 4269 | name = "quasi-router" | |
| 4270 | - | version = "0.77.0" | |
| 4270 | + | version = "0.78.0" | |
| 4271 | 4271 | dependencies = [ | |
| 4272 | 4272 | "makeover-layout", | |
| 4273 | 4273 | ] | |
| @@ -7556,21 +7556,25 @@ | |||
| 7556 | 7556 | "winnow 1.0.4", | |
| 7557 | 7557 | ] | |
| 7558 | 7558 | ||
| 7559 | + | [[patch.unused]] | |
| 7560 | + | name = "quasi-type" | |
| 7561 | + | version = "0.1.2" | |
| 7562 | + | ||
| 7559 | 7563 | [[patch.unused]] | |
| 7560 | 7564 | name = "quasi-axum" | |
| 7561 | - | version = "0.77.0" | |
| 7565 | + | version = "0.78.0" | |
| 7562 | 7566 | ||
| 7563 | 7567 | [[patch.unused]] | |
| 7564 | 7568 | name = "quasi-basics" | |
| 7565 | - | version = "0.77.0" | |
| 7569 | + | version = "0.78.0" | |
| 7566 | 7570 | ||
| 7567 | 7571 | [[patch.unused]] | |
| 7568 | 7572 | name = "quasi-http" | |
| 7569 | - | version = "0.77.0" | |
| 7573 | + | version = "0.78.0" | |
| 7570 | 7574 | ||
| 7571 | 7575 | [[patch.unused]] | |
| 7572 | 7576 | name = "quasi-notifs" | |
| 7573 | - | version = "0.77.0" | |
| 7577 | + | version = "0.78.0" | |
| 7574 | 7578 | ||
| 7575 | 7579 | [[patch.unused]] | |
| 7576 | 7580 | name = "quasi-store" | |
| @@ -7578,15 +7582,11 @@ | |||
| 7578 | 7582 | ||
| 7579 | 7583 | [[patch.unused]] | |
| 7580 | 7584 | name = "quasi-tauri" | |
| 7581 | - | version = "0.77.0" | |
| 7585 | + | version = "0.78.0" | |
| 7582 | 7586 | ||
| 7583 | 7587 | [[patch.unused]] | |
| 7584 | 7588 | name = "quasi-webview" | |
| 7585 | - | version = "0.77.0" | |
| 7586 | - | ||
| 7587 | - | [[patch.unused]] | |
| 7588 | - | name = "quasi-type" | |
| 7589 | - | version = "0.1.2" | |
| 7589 | + | version = "0.78.0" | |
| 7590 | 7590 | ||
| 7591 | 7591 | [[patch.unused]] | |
| 7592 | 7592 | name = "kberg" |
| @@ -17,8 +17,8 @@ | |||
| 17 | 17 | # to compile against an API added in a later one. The two move together -- | |
| 18 | 18 | # makeover-immediate re-exports nothing, so the `Column` the app describes and | |
| 19 | 19 | # the `Column` the renderer matches on have to be the same type. | |
| 20 | - | makeover-layout = "0.38.0" | |
| 21 | - | makeover-immediate = "0.39.0" | |
| 20 | + | makeover-layout = "0.39.0" | |
| 21 | + | makeover-immediate = "0.40.0" | |
| 22 | 22 | # The time axis. Minor rather than exact patch: it re-exports nothing and no | |
| 23 | 23 | # type of its crosses a renderer boundary, so it is under the same rule as | |
| 24 | 24 | # makeover-geometry above rather than the layout/immediate pair below. | |
| @@ -26,8 +26,8 @@ | |||
| 26 | 26 | # The described screens, which are audiofiles-browser's screens. By git URL with | |
| 27 | 27 | # a version requirement, per the tree's rule for cross-repo deps. They were | |
| 28 | 28 | # behind a `quasi` feature until 2026-08-25, when the last flip landed. | |
| 29 | - | quasi-router = { git = "https://makenot.work/git/max/quasi.git", version = "0.77" } | |
| 30 | - | quasi-immediate = { git = "https://makenot.work/git/max/quasi.git", version = "0.77" } | |
| 29 | + | quasi-router = { git = "https://makenot.work/git/max/quasi.git", version = "0.78" } | |
| 30 | + | quasi-immediate = { git = "https://makenot.work/git/max/quasi.git", version = "0.78" } | |
| 31 | 31 | egui = { version = "0.35", default-features = false, features = ["default_fonts"] } | |
| 32 | 32 | egui_extras = { version = "0.35", default-features = false } | |
| 33 | 33 | eframe = { version = "0.35", default-features = false, features = ["default_fonts", "glow"] } |
| @@ -91,34 +91,42 @@ | |||
| 91 | 91 | //! the subject, and the two menus turn out to differ in both. | |
| 92 | 92 | //! | |
| 93 | 93 | //! - **The selection menu hangs off the row the press landed on**, as | |
| 94 | - | //! `chosen_menu` below. quasi 0.77 ships | |
| 95 | - | //! [`Anchor::Selection`](quasi_router::Anchor::Selection) for exactly this and | |
| 96 | - | //! it does not fit here: that member resolves against the *description's* | |
| 97 | - | //! selection — `Screen::selection` named, rows ticked — and this list's | |
| 98 | - | //! selection is the app's own, live and unticked, for the reason `row` below | |
| 99 | - | //! records. So the anchor would resolve to nothing and the menu would open | |
| 100 | - | //! away from its subject. Ruled by Max 2026-08-29 on the measurement; the row | |
| 101 | - | //! the press landed on is the anchor, which is what the shipped list did. | |
| 94 | + | //! `chosen_menu` below. Ruled by Max 2026-08-29: that is where every file | |
| 95 | + | //! manager opens it, and it opens at the pointer rather than at a corner of | |
| 96 | + | //! the list. [`Anchor::Selection`](quasi_router::Anchor::Selection) exists and | |
| 97 | + | //! now resolves here — quasi 0.78 taught it to read a live selection and not | |
| 98 | + | //! only a staged one — so this is a choice about where a context menu belongs | |
| 99 | + | //! rather than a limit being worked around. | |
| 102 | 100 | //! - **The empty-space menu is anchored at the region**, as `background_menu`, | |
| 103 | 101 | //! because a press on nothing has no row. It is the app's first | |
| 104 | 102 | //! [`Outcome::Anchored`](quasi_router::Outcome::Anchored), and its gesture is | |
| 105 | 103 | //! the host's — `panel::menued` — since the absence of a row is not a | |
| 106 | 104 | //! described thing for a description to answer about. | |
| 107 | 105 | //! | |
| 108 | - | //! What is still owed, and it is the reason the first of those went the way it | |
| 109 | - | //! did: **a live multi-selection has no described shape.** `Cells` marks the | |
| 110 | - | //! current row and holds a tick, a tick is a staged set awaiting a commit | |
| 111 | - | //! control, and this list's selection is neither — so a five-hundred-row Cmd+A | |
| 112 | - | //! draws the same as a one-row click, and [`Sample::selected`] is read by the | |
| 113 | - | //! menu fork and shown nowhere. Measured 2026-08-29 and filed against | |
| 114 | - | //! quasicoherent. | |
| 106 | + | //! # The live selection, which had no described shape until 2026-08-29 | |
| 107 | + | //! | |
| 108 | + | //! `1894e95d`, and it was worse than the menus: `Cells` could mark the current | |
| 109 | + | //! row and hold a staged tick, and this list's selection is neither, so nothing | |
| 110 | + | //! drew it — a five-hundred-row Cmd+A looked exactly like a click on one — and | |
| 111 | + | //! nothing could say a press was ctrl-held, so the only multi-select left was | |
| 112 | + | //! the keyboard. Picking eleven kicks out of four hundred by hand, which is | |
| 113 | + | //! close to what this app is for, could not be done at all. | |
| 114 | + | //! | |
| 115 | + | //! quasi 0.78 is the answer and it is two members. [`Cells::chosen`] is the | |
| 116 | + | //! third state beside `current` and `selected`, three-valued for the reason the | |
| 117 | + | //! tick is: a ctrl-click lands on an **unchosen** row, so a renderer has to know | |
| 118 | + | //! that row can join a set before anything is in one. | |
| 119 | + | //! [`Choosing`] is what a press meant — `Only`, `Also`, `Through` — said by the | |
| 120 | + | //! renderer because the idiom is the host's and carried under | |
| 121 | + | //! [`Node::CHOOSING`]. The app still owns the set; what it gets told is what the | |
| 122 | + | //! reader asked for. | |
| 115 | 123 | //! - **Virtual scrolling.** Recorded in the findings note as renderer policy | |
| 116 | 124 | //! from the start: windowing rows the app already holds is a performance | |
| 117 | 125 | //! technique, not a described fact. | |
| 118 | 126 | ||
| 119 | 127 | use quasi_router::layout::{Priority, Sort, Tone, Width}; | |
| 120 | 128 | use quasi_router::{ | |
| 121 | - | Act, Action, Cell, Cells, Choice, Column, Field, Node, RegionKind, Request, Response, | |
| 129 | + | Act, Action, Cell, Cells, Choice, Choosing, Column, Field, Node, RegionKind, Request, Response, | |
| 122 | 130 | RouteError, Router, Screen, Slot, Tag, | |
| 123 | 131 | }; | |
| 124 | 132 | ||
| @@ -192,9 +200,19 @@ | |||
| 192 | 200 | } | |
| 193 | 201 | ||
| 194 | 202 | /// `POST /files/{id}/open` | |
| 203 | + | /// | |
| 204 | + | /// A press on a row, which in a file list is how a selection is built as well as | |
| 205 | + | /// how one row is picked. What the press *meant* is the renderer's answer and | |
| 206 | + | /// arrives in the payload; `1894e95d` and [`Choosing`] carry the reasoning. | |
| 207 | + | /// | |
| 208 | + | /// A request that names none -- a hand-typed one, or a renderer that has not | |
| 209 | + | /// learned the member -- reads as the plain press, which is what this route did | |
| 210 | + | /// before the member existed. | |
| 195 | 211 | fn open(state: &Panels<'_>, request: Request) -> Result<Response, RouteError> { | |
| 196 | 212 | let id = id_of(&request)?; | |
| 197 | - | state.files.open(id); | |
| 213 | + | state | |
| 214 | + | .files | |
| 215 | + | .choose(id, Choosing::read(request.payload.get(Node::CHOOSING))); | |
| 198 | 216 | Ok(screen(state).into()) | |
| 199 | 217 | } | |
| 200 | 218 | ||
| @@ -751,17 +769,20 @@ | |||
| 751 | 769 | )]) | |
| 752 | 770 | }); | |
| 753 | 771 | ||
| 754 | - | // Named, so the renderer can answer which row the pointer is over with the | |
| 755 | - | // sample rather than with a position. `identified` and not `ticking`: this | |
| 756 | - | // list's selection is the app's -- `state.nav.selection` -- so there is no | |
| 757 | - | // tick to set, and the two were only ever written together because every | |
| 758 | - | // list that wanted an identity before this one also wanted a checkbox. | |
| 772 | + | // Named and chosen, which is one call because they are one fact: the id the | |
| 773 | + | // renderer answers a gesture with, and this row's standing in the selection | |
| 774 | + | // that gesture is building. `choosing` and not `ticking` -- a tick is a | |
| 775 | + | // staged set awaiting a commit control, and this list's selection is the | |
| 776 | + | // app's own and already in force. `1894e95d`. | |
| 759 | 777 | // | |
| 760 | - | // What it closes: `panel::dragging_out` used to have to match `row_at`'s | |
| 761 | - | // index back against the sample ordering this description was built from, | |
| 762 | - | // which is the same row identified twice in two places, agreeing by luck. | |
| 778 | + | // What the name closes: `panel::dragging_out` used to have to match | |
| 779 | + | // `row_at`'s index back against the sample ordering this description was | |
| 780 | + | // built from, which is the same row identified twice in two places, agreeing | |
| 781 | + | // by luck. What the chosen half closes is bigger -- until it landed nothing | |
| 782 | + | // drew a multi-selection at all, so Cmd+A over five hundred samples looked | |
| 783 | + | // exactly like a click on one. | |
| 763 | 784 | let mut row = Cells::new(values) | |
| 764 | - | .identified(sample.id.to_string()) | |
| 785 | + | .choosing(sample.id.to_string(), sample.selected) | |
| 765 | 786 | .activate(Action::post(format!("/files/{}/open", sample.id))); | |
| 766 | 787 | row.current = listing.current == Some(sample.id); | |
| 767 | 788 | // Which menu a row offers is which subject the press is about. A row inside |
| @@ -611,12 +611,12 @@ | |||
| 611 | 611 | /// unchosen one. What reads it is `files::menu`, which offers a row inside a | |
| 612 | 612 | /// multi-selection what can be done to the *set* rather than to the row. | |
| 613 | 613 | /// | |
| 614 | - | /// Worth knowing where this stops: nothing **shows** it. `Cells` marks the | |
| 615 | - | /// current row and holds a tick, and a tick is a staged set awaiting a | |
| 616 | - | /// commit control rather than a selection that is already in force, so the | |
| 617 | - | /// list draws a five-hundred-row Cmd+A the same as a one-row click. Measured | |
| 618 | - | /// 2026-08-29 and filed; it is why `Anchor::Selection` resolves against | |
| 619 | - | /// nothing here and why this menu hangs off the row instead. | |
| 614 | + | /// It is also what the list *draws*. Until quasi 0.78 nothing could: `Cells` | |
| 615 | + | /// marked the current row and held a tick, a tick is a staged set awaiting a | |
| 616 | + | /// commit control rather than a selection already in force, and a | |
| 617 | + | /// five-hundred-row Cmd+A therefore looked exactly like a one-row click. | |
| 618 | + | /// `Cells::chosen` is the third state that closes it, and `Cells::choosing` | |
| 619 | + | /// is what `files::row` writes this into. | |
| 620 | 620 | pub selected: bool, | |
| 621 | 621 | /// Whether this sample is in the basket -- one of the samples being looked | |
| 622 | 622 | /// *for*, rather than one of the answers. | |
| @@ -668,9 +668,24 @@ | |||
| 668 | 668 | /// The row the app is pointing at. | |
| 669 | 669 | fn current(&self) -> Option<i64>; | |
| 670 | 670 | ||
| 671 | - | /// Select this row. | |
| 671 | + | /// Select this row, and nothing else. | |
| 672 | + | /// | |
| 673 | + | /// What the routes that borrow a capability call before they borrow it: the | |
| 674 | + | /// detail handle acts on the sample in focus, so a row menu selects the row | |
| 675 | + | /// first. Not what a *press* on a row calls -- that is | |
| 676 | + | /// [`choose`](Self::choose), which carries what the press meant. | |
| 672 | 677 | fn open(&self, id: i64); | |
| 673 | 678 | ||
| 679 | + | /// Answer a press on this row, which may be building a selection. | |
| 680 | + | /// | |
| 681 | + | /// `1894e95d`. The file list's selection is live: click chooses one row, | |
| 682 | + | /// ctrl-click adds or removes one, shift-click takes the run. The | |
| 683 | + | /// description cannot say which of those a press was, because that is the | |
| 684 | + | /// host's own idiom -- so the renderer says, under | |
| 685 | + | /// [`Choosing`](quasi_router::Choosing), and this carries it to the app that | |
| 686 | + | /// owns the set. | |
| 687 | + | fn choose(&self, id: i64, meant: quasi_router::Choosing); | |
| 688 | + | ||
| 674 | 689 | /// Preview this row. | |
| 675 | 690 | fn play(&self, id: i64); | |
| 676 | 691 | ||
| @@ -780,8 +795,11 @@ | |||
| 780 | 795 | /// submitted, and a gain is an `f64`. | |
| 781 | 796 | #[derive(Debug, Clone, PartialEq)] | |
| 782 | 797 | pub enum Intent { | |
| 783 | - | /// Select a row. | |
| 798 | + | /// Select a row, and nothing else. | |
| 784 | 799 | Open(i64), | |
| 800 | + | /// Answer a press on a row, which may be adding to the selection or taking | |
| 801 | + | /// a run. | |
| 802 | + | Choose(i64, quasi_router::Choosing), | |
| 785 | 803 | /// Preview a row. | |
| 786 | 804 | Play(i64), | |
| 787 | 805 | /// Go into a folder. | |
| @@ -1345,6 +1363,10 @@ | |||
| 1345 | 1363 | self.intents.borrow_mut().push(Intent::Open(id)); | |
| 1346 | 1364 | } | |
| 1347 | 1365 | ||
| 1366 | + | fn choose(&self, id: i64, meant: quasi_router::Choosing) { | |
| 1367 | + | self.intents.borrow_mut().push(Intent::Choose(id, meant)); | |
| 1368 | + | } | |
| 1369 | + | ||
| 1348 | 1370 | fn play(&self, id: i64) { | |
| 1349 | 1371 | self.intents.borrow_mut().push(Intent::Play(id)); | |
| 1350 | 1372 | } |
| @@ -579,6 +579,30 @@ | |||
| 579 | 579 | state.refresh_selected_detail(); | |
| 580 | 580 | } | |
| 581 | 581 | } | |
| 582 | + | // A press on a row, which in a file list is how a selection is | |
| 583 | + | // built. `1894e95d`, and it is `ui/file_list.rs::handle_click` | |
| 584 | + | // restored: the deleted list read the modifiers itself, and what | |
| 585 | + | // replaces that reading is `Choosing` -- said by the renderer, | |
| 586 | + | // carried by the description, applied here. | |
| 587 | + | // | |
| 588 | + | // The three writes are the app's own and were never gone; what went | |
| 589 | + | // with the flip was any way to reach two of them. `extend_to` takes | |
| 590 | + | // `visible_len` rather than the row count because the run it walks | |
| 591 | + | // is the rows the *list* shows, `..` included. | |
| 592 | + | Intent::Choose(id, meant) => { | |
| 593 | + | if let Some(at) = index_of(state, id) { | |
| 594 | + | match meant { | |
| 595 | + | quasi_router::Choosing::Only => state.nav.selection.set_single(at), | |
| 596 | + | quasi_router::Choosing::Also => state.nav.selection.toggle(at), | |
| 597 | + | quasi_router::Choosing::Through => { | |
| 598 | + | let len = state.visible_len(); | |
| 599 | + | state.nav.selection.extend_to(at, len); | |
| 600 | + | } | |
| 601 | + | } | |
| 602 | + | state.refresh_selected_tags(); | |
| 603 | + | state.refresh_selected_detail(); | |
| 604 | + | } | |
| 605 | + | } | |
| 582 | 606 | Intent::Play(id) => { | |
| 583 | 607 | if let Some(at) = index_of(state, id) { | |
| 584 | 608 | state.nav.selection.set_single(at); |
| @@ -117,6 +117,11 @@ | |||
| 117 | 117 | fn open(&self, id: i64) { | |
| 118 | 118 | self.asked.borrow_mut().push(format!("open:{id}")); | |
| 119 | 119 | } | |
| 120 | + | fn choose(&self, id: i64, meant: quasi_router::Choosing) { | |
| 121 | + | self.asked | |
| 122 | + | .borrow_mut() | |
| 123 | + | .push(format!("choose:{id}:{}", meant.as_str())); | |
| 124 | + | } | |
| 120 | 125 | fn play(&self, id: i64) { | |
| 121 | 126 | self.asked.borrow_mut().push(format!("play:{id}")); | |
| 122 | 127 | } | |
| @@ -1963,7 +1968,58 @@ | |||
| 1963 | 1968 | listing(&files, Request::post("/files/7/open")).expect("answered"); | |
| 1964 | 1969 | listing(&files, Request::post("/files/7/play")).expect("answered"); | |
| 1965 | 1970 | listing(&files, Request::post("/files/sort/BPM")).expect("answered"); | |
| 1966 | - | assert_eq!(files.asked(), ["open:7", "play:7", "sort:BPM"]); | |
| 1971 | + | // A press with nothing said about it is the plain one, which is what this | |
| 1972 | + | // route did before it could be told. See the test below. | |
| 1973 | + | assert_eq!(files.asked(), ["choose:7:only", "play:7", "sort:BPM"]); | |
| 1974 | + | } | |
| 1975 | + | ||
| 1976 | + | /// `1894e95d`. Ctrl-click and shift-click, which the described list lost in | |
| 1977 | + | /// `49b7429` and which are how anyone builds a selection in a file manager. | |
| 1978 | + | /// | |
| 1979 | + | /// The description cannot say which of the three a press was -- that is the | |
| 1980 | + | /// host's own idiom, and a terminal's is not a keyboard modifier -- so the | |
| 1981 | + | /// renderer says under `Node::CHOOSING` and this route carries it through. | |
| 1982 | + | #[test] | |
| 1983 | + | fn a_press_says_whether_it_is_building_a_selection() { | |
| 1984 | + | let files = FakeFiles::with(vec![sample(7, "kick.wav"), sample(9, "snare.wav")]); | |
| 1985 | + | for meant in ["only", "also", "through"] { | |
| 1986 | + | let request = Request::post("/files/9/open") | |
| 1987 | + | .sending(Params::new().with(quasi_router::Node::CHOOSING.to_owned(), meant.to_owned())); | |
| 1988 | + | listing(&files, request).expect("answered"); | |
| 1989 | + | } | |
| 1990 | + | // A spelling this version does not know is the plain press rather than a | |
| 1991 | + | // refusal: it is still a press on a row, and the safe reading chooses the | |
| 1992 | + | // row that was pressed and nothing else. | |
| 1993 | + | let odd = Request::post("/files/9/open") | |
| 1994 | + | .sending(Params::new().with(quasi_router::Node::CHOOSING.to_owned(), "lasso".to_owned())); | |
| 1995 | + | listing(&files, odd).expect("answered"); | |
| 1996 | + | ||
| 1997 | + | assert_eq!( | |
| 1998 | + | files.asked(), | |
| 1999 | + | [ | |
| 2000 | + | "choose:9:only", | |
| 2001 | + | "choose:9:also", | |
| 2002 | + | "choose:9:through", | |
| 2003 | + | "choose:9:only", | |
| 2004 | + | ] | |
| 2005 | + | ); | |
| 2006 | + | } | |
| 2007 | + | ||
| 2008 | + | /// The rows say which of them are chosen, which is what lets a renderer draw a | |
| 2009 | + | /// selection at all -- and what `chosen_menu` forks on. | |
| 2010 | + | #[test] | |
| 2011 | + | fn the_rows_say_which_of_them_the_reader_has_chosen() { | |
| 2012 | + | let files = FakeFiles::with(vec![chosen(7, "kick.wav"), sample(9, "snare.wav")]); | |
| 2013 | + | let response = listing(&files, Request::get("/files")).expect("answered"); | |
| 2014 | + | let (_, rows) = table_of(screen_of(&response)); | |
| 2015 | + | ||
| 2016 | + | // Three states, not two: the second row can be chosen and is not, which is | |
| 2017 | + | // what a ctrl-click on it depends on the renderer knowing. | |
| 2018 | + | assert_eq!(rows[0].chosen, Some(true)); | |
| 2019 | + | assert_eq!(rows[1].chosen, Some(false)); | |
| 2020 | + | // And the value the app reads the press back by is the sample's own id, the | |
| 2021 | + | // same one the drag reads off `row_at`. | |
| 2022 | + | assert_eq!(rows[0].value.as_deref(), Some("7")); | |
| 1967 | 2023 | } | |
| 1968 | 2024 | ||
| 1969 | 2025 | #[test] |