Lists: a selectable list's cursor starts on the owner's pick; the waveform's fit is tested
SelectableList keeps the owner's last selection and the one it last sent. A selection the owner makes, on first paint or later, moves the cursor to its first row; the echo of the list's own pick leaves the cursor where the pick put it. The playlist's keys start from the song under way. The waveform's bar count and inset move into `fit`, tested beside `resample`.
This commit is contained in:
1 parent
04f88812dc
commit
cb6c78b90c
6 files changed
+102
-5
No files matched your search
+16
-3
@@ -32,6 +32,13 @@ pub(crate) fn resample(peaks: &[f32], count: usize) -> Vec<f32> {
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// How many bars `bar` wide with `gap` between fit across `width`, at least one, and the inset that centers them.
|
||||
pub(crate) fn fit(width: f32, bar: f32, gap: f32) -> (usize, f32) {
|
||||
let count = ((width + gap) / (bar + gap)).floor().max(1.0) as usize;
|
||||
let used = (bar + gap) * count as f32 - gap;
|
||||
(count, ((width - used) / 2.0).max(0.0))
|
||||
}
|
||||
|
||||
/// A seeking drag on a waveform, marked with its waveform.
|
||||
struct Seek(EntityId);
|
||||
|
||||
@@ -114,8 +121,7 @@ impl RenderOnce for AudioWaveform {
|
||||
move |bounds, _, window, _| {
|
||||
let width = f32::from(bounds.size.width);
|
||||
let pitch = f32::from(bar + gap);
|
||||
let count = ((width + f32::from(gap)) / pitch).floor().max(1.0) as usize;
|
||||
let inset = (width - (pitch * count as f32 - f32::from(gap))) / 2.0;
|
||||
let (count, inset) = fit(width, f32::from(bar), f32::from(gap));
|
||||
for (ix, peak) in resample(&peaks, count).into_iter().enumerate() {
|
||||
let left = inset + pitch * ix as f32;
|
||||
let tall = (bounds.size.height * peak).max(bar);
|
||||
@@ -242,7 +248,14 @@ impl RenderOnce for AudioSpectrum {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::resample;
|
||||
use super::{fit, resample};
|
||||
|
||||
#[test]
|
||||
fn the_bars_that_fit_sit_centered() {
|
||||
assert_eq!(fit(100.0, 3.0, 2.0), (20, 1.0));
|
||||
assert_eq!(fit(98.0, 3.0, 2.0), (20, 0.0));
|
||||
assert_eq!(fit(2.0, 3.0, 2.0), (1, 0.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn peaks_gather_into_bars_by_their_loudest() {
|
||||
|
||||
@@ -304,6 +304,42 @@ fn down_and_enter_play_the_next_song(cx: &mut TestAppContext) {
|
||||
assert_eq!(view.read_with(cx, |view, _| view.played.clone()), ["b"]);
|
||||
}
|
||||
|
||||
/// Four songs with the third under way, and the songs it was told to play.
|
||||
struct Midway {
|
||||
played: Vec<SharedString>,
|
||||
}
|
||||
|
||||
impl Render for Midway {
|
||||
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let owner = cx.entity();
|
||||
let songs = [
|
||||
track("a", "One"),
|
||||
track("b", "Two"),
|
||||
track("c", "Three"),
|
||||
track("d", "Four"),
|
||||
];
|
||||
div().w(px(320.0)).child(
|
||||
Playlist::new("list", songs)
|
||||
.current("c", true)
|
||||
.on_play(move |key, _, cx| {
|
||||
owner.update(cx, |view, _| view.played.push(key.clone()))
|
||||
}),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn the_keys_start_from_the_song_under_way(cx: &mut TestAppContext) {
|
||||
setup(cx);
|
||||
let (view, cx) = cx.add_window_view(|_, _| Midway { played: Vec::new() });
|
||||
cx.update(|window, _| window.activate_window());
|
||||
settle(cx);
|
||||
tab_to(1, cx);
|
||||
press("down", cx);
|
||||
press("enter", cx);
|
||||
assert_eq!(view.read_with(cx, |view, _| view.played.clone()), ["d"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(expected = "no track c")]
|
||||
fn the_song_under_way_is_one_of_the_list() {
|
||||
|
||||
Reference in new issue
Block a user