Carousel: always two items on each side of an open card, in the row's order going round (items changing sides fade instead of crossing); the island shrinks to its cover, or its bars
This commit is contained in:
+53
-19
@@ -315,7 +315,7 @@ pub fn view(monitor: &Monitor) -> LayerWindow {
|
||||
|
||||
veiled(card(&scene), width, height, radius, reveal, &colors)
|
||||
} else if pill > 0.0 {
|
||||
veiled(compact(player, playing(player, &island), &art.path, levels), width, height, radius, pill, &colors)
|
||||
veiled(compact(player, playing(player, &island), &art.path, levels, c::pill::width()), width, height, radius, pill, &colors)
|
||||
} else {
|
||||
// mid-grow: the empty shape alone, the cheapest frames of the animation
|
||||
Rectangle::new().width(0.0).height(0.0)
|
||||
@@ -376,18 +376,21 @@ pub fn view(monitor: &Monitor) -> LayerWindow {
|
||||
/*
|
||||
* the island's pill as the side pills' carousel draws it while one of their cards is
|
||||
* open in the middle (the island's own window leaves it to them meanwhile, see
|
||||
* pods::holds_island): the same pill with its live levels, or the quiet one
|
||||
* pods::holds_island). `small` shrinks it from the pill (0) to its least (1): the song's
|
||||
* cover, or its live bars when there's no cover, or a note with nothing playing. the
|
||||
* pill's face fades out over the first half, the small one in over the second
|
||||
*/
|
||||
pub fn pill_face(monitor: &str) -> Rectangle {
|
||||
pub fn pill_face(monitor: &str, small: f32) -> Rectangle {
|
||||
let media = Media::read();
|
||||
let island = Island::read();
|
||||
let colors = theme::now().surfaces();
|
||||
let art = Art::read();
|
||||
let h = c::pill::height();
|
||||
|
||||
let Some(player) = shown(&media, island.selected.as_deref()) else {
|
||||
return idle(&colors);
|
||||
};
|
||||
let player = shown(&media, island.selected.as_deref());
|
||||
let width = mix(c::pill::width(), small_width_for(player.is_some(), &art.path), small);
|
||||
|
||||
let live = focused_monitor().as_deref().is_none_or(|name| name == monitor);
|
||||
let live = player.is_some() && focused_monitor().as_deref().is_none_or(|name| name == monitor);
|
||||
let spectrum = live.then(Spectrum::read);
|
||||
let levels: &[f32] = spectrum.as_deref().map_or(&[], |spectrum| spectrum.levels.as_slice());
|
||||
|
||||
@@ -395,9 +398,38 @@ pub fn pill_face(monitor: &str) -> Rectangle {
|
||||
amane::request_frame();
|
||||
}
|
||||
|
||||
let face = compact(player, playing(player, &island), &Art::read().path, levels);
|
||||
let face = if small < 0.5 {
|
||||
let big = match player {
|
||||
Some(player) => compact(player, playing(player, &island), &art.path, levels, width),
|
||||
None => idle_face(width),
|
||||
};
|
||||
|
||||
surface::shell(c::pill::width(), c::pill::height(), c::pill::height() / 2.0, &colors, 0.0, face)
|
||||
veiled(big, width, h, h / 2.0, 1.0 - 2.0 * small, &colors)
|
||||
} else {
|
||||
let little: Box<dyn Widget> = match player {
|
||||
Some(_) if !art.path.is_empty() => Box::new(cover(&art.path, h - 10.0, 1.5, false)),
|
||||
Some(player) => Box::new(pill_levels(playing(player, &island), levels)),
|
||||
None => Box::new(icon(Glyph::Note, 15.0, theme::now().accent)),
|
||||
};
|
||||
|
||||
veiled(Rectangle::new().width(width).height(h).align_child(Center, Center).child(Stack::new(vec![little])), width, h, h / 2.0, 2.0 * small - 1.0, &colors)
|
||||
};
|
||||
|
||||
surface::shell(width, h, h / 2.0, &colors, 0.0, face)
|
||||
}
|
||||
|
||||
// the island's pill at its least in the carousel: as wide as tall with a cover or nothing playing, else room for its bars
|
||||
pub fn small_width() -> f32 {
|
||||
let media = Media::read();
|
||||
let island = Island::read();
|
||||
|
||||
small_width_for(shown(&media, island.selected.as_deref()).is_some(), &Art::read().path)
|
||||
}
|
||||
|
||||
fn small_width_for(playing_something: bool, art: &str) -> f32 {
|
||||
let h = c::pill::height();
|
||||
|
||||
if !playing_something || !art.is_empty() { h } else { (c::pill::bars_width() + 24.0).max(h) }
|
||||
}
|
||||
|
||||
// a click on the island of `monitor`: closes an open dropdown, else opens the card there
|
||||
@@ -633,19 +665,21 @@ pub fn faded(color: Color, alpha: u8) -> Color {
|
||||
|
||||
// the island while no player is open: a music note and "Nothing playing", on the pill's surface
|
||||
fn idle(colors: &[Color; 3]) -> Rectangle {
|
||||
let content = Rectangle::new()
|
||||
.width(c::pill::width())
|
||||
.height(c::pill::height())
|
||||
.align_child(Center, Center)
|
||||
.child(Row::new(children![icon(Glyph::Note, 15.0, theme::now().accent), Text::new("Nothing playing").size(text::body()).color(theme::now().dim)]).gap(8.0).align(Center));
|
||||
|
||||
surface::shell(c::pill::width(), c::pill::height(), c::pill::height() / 2.0, colors, 0.0, content)
|
||||
surface::shell(c::pill::width(), c::pill::height(), c::pill::height() / 2.0, colors, 0.0, idle_face(c::pill::width()))
|
||||
}
|
||||
|
||||
// the pill: cover, title, the player's live levels
|
||||
fn compact(player: &MediaPlayer, playing: bool, art: &str, levels: &[f32]) -> Rectangle {
|
||||
fn idle_face(width: f32) -> Rectangle {
|
||||
Rectangle::new()
|
||||
.width(c::pill::width())
|
||||
.width(width)
|
||||
.height(c::pill::height())
|
||||
.align_child(Center, Center)
|
||||
.child(Row::new(children![icon(Glyph::Note, 15.0, theme::now().accent), Text::new("Nothing playing").size(text::body()).color(theme::now().dim).elide()]).gap(8.0).align(Center))
|
||||
}
|
||||
|
||||
// the pill, `width` wide: cover, title, the player's live levels
|
||||
fn compact(player: &MediaPlayer, playing: bool, art: &str, levels: &[f32], width: f32) -> Rectangle {
|
||||
Rectangle::new()
|
||||
.width(width)
|
||||
.height(c::pill::height())
|
||||
.padding(Padding { top: 0.0, right: 12.0, bottom: 0.0, left: 6.0 })
|
||||
.child(
|
||||
|
||||
+89
-31
@@ -276,10 +276,12 @@ fn island_tapped(monitor: String) {
|
||||
});
|
||||
}
|
||||
|
||||
// an item's width in the carousel
|
||||
fn item_width(item: Item) -> f32 {
|
||||
match item {
|
||||
Item::Pod(_) => island_pill::height(),
|
||||
Item::Island => island_pill::width(),
|
||||
// beside an open card the island's pill shrinks to its cover or its bars
|
||||
Item::Island => island::small_width(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -296,25 +298,65 @@ fn closed_x(item: Item, middle: f32, island_w: f32) -> f32 {
|
||||
}
|
||||
}
|
||||
|
||||
// an item's left edge in the carousel around `open`'s card, `card_w` wide, in the middle
|
||||
fn carousel_x(item: Item, open: Pod, card_w: f32, middle: f32) -> f32 {
|
||||
let at = |wanted: Item| ROW.iter().position(|other| *other == wanted).unwrap_or(0);
|
||||
let (index, centre) = (at(item), at(Item::Pod(open)));
|
||||
let (spacing, gap) = (layout::spacing(), layout::gap());
|
||||
fn index(item: Item) -> usize {
|
||||
ROW.iter().position(|other| *other == item).unwrap_or(0)
|
||||
}
|
||||
|
||||
if index == centre {
|
||||
return middle - card_w / 2.0;
|
||||
/*
|
||||
* the two items on each side of `open`'s card, always: the row's order going round, so
|
||||
* the ones past an end of the row come in on the other side (around Home: System and the
|
||||
* island on its left, the bell and AI on its right)
|
||||
*/
|
||||
fn beside(open: Pod) -> ([Item; 2], [Item; 2]) {
|
||||
let (count, at) = (ROW.len(), index(Item::Pod(open)));
|
||||
let step = |by: usize| ROW[(at + by) % count];
|
||||
|
||||
([step(count - 2), step(count - 1)], [step(1), step(2)])
|
||||
}
|
||||
|
||||
// an item's left edge in the carousel around `open`'s card, `card_w` wide, in the middle, and its side of the card (-1, 0 for the card's own, 1)
|
||||
fn carousel(item: Item, open: Pod, card_w: f32, middle: f32) -> (f32, i8) {
|
||||
// the card's own pill, while it fades into the card or out of it, sits in the card's middle
|
||||
if item == Item::Pod(open) {
|
||||
return (middle - item_width(item) / 2.0, 0);
|
||||
}
|
||||
|
||||
if index < centre {
|
||||
// leftwards from the card, past the items between
|
||||
let between: f32 = ROW[index + 1..centre].iter().map(|other| item_width(*other) + spacing).sum();
|
||||
let (spacing, gap) = (layout::spacing(), layout::gap());
|
||||
let (left, right) = beside(open);
|
||||
|
||||
middle - card_w / 2.0 - gap - between - item_width(item)
|
||||
match left.iter().position(|other| *other == item) {
|
||||
// leftwards from the card, past the one between
|
||||
Some(k) => {
|
||||
let between: f32 = left[k + 1..].iter().map(|other| item_width(*other) + spacing).sum();
|
||||
|
||||
(middle - card_w / 2.0 - gap - between - item_width(item), -1)
|
||||
}
|
||||
None => {
|
||||
let k = right.iter().position(|other| *other == item).unwrap_or(0);
|
||||
let between: f32 = right[..k].iter().map(|other| item_width(*other) + spacing).sum();
|
||||
|
||||
(middle + card_w / 2.0 + gap + between, 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// an item's side of `open` in the closed row
|
||||
fn closed_side(item: Item, open: Pod) -> i8 {
|
||||
(index(item) as i8 - index(Item::Pod(open)) as i8).signum()
|
||||
}
|
||||
|
||||
/*
|
||||
* from one place to another by `k`, with how much it shows: sliding when it stays on its
|
||||
* side of the card; changing sides it fades out where it is over the first half and in at
|
||||
* its new place over the second, so it never crosses the card
|
||||
*/
|
||||
fn travel(from: (f32, i8), to: (f32, i8), k: f32) -> (f32, f32) {
|
||||
if from.1 == to.1 || from.1 == 0 || to.1 == 0 {
|
||||
(mix(from.0, to.0, k), 1.0)
|
||||
} else if k < 0.5 {
|
||||
(from.0, 1.0 - 2.0 * k)
|
||||
} else {
|
||||
let between: f32 = ROW[centre + 1..index].iter().map(|other| item_width(*other) + spacing).sum();
|
||||
|
||||
middle + card_w / 2.0 + gap + between
|
||||
(to.0, 2.0 * k - 1.0)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -329,8 +371,8 @@ struct Scene {
|
||||
previous: Option<Pod>,
|
||||
middle: f32,
|
||||
island_w: f32,
|
||||
// the island's pill starts moving only after the hand-over (see holds_island), so it never shows twice
|
||||
island_grow: f32,
|
||||
// the row's move into the carousel: it starts only after the hand-over (see holds_island), so the island's pill never shows twice
|
||||
row_grow: f32,
|
||||
}
|
||||
|
||||
fn scene(state: &Pods, monitor: &Monitor) -> Scene {
|
||||
@@ -342,25 +384,33 @@ fn scene(state: &Pods, monitor: &Monitor) -> Scene {
|
||||
let switch = if card.is_some() { state.switch.value() } else { 1.0 };
|
||||
let previous = state.previous.filter(|_| switch < 1.0 && card.is_some());
|
||||
|
||||
let island_grow = card.map_or(0.0, |(_, t)| phases(((t - 2.0 * HAND_OVER) / (1.0 - 2.0 * HAND_OVER)).max(0.0)).0);
|
||||
let row_grow = card.map_or(0.0, |(_, t)| phases(((t - 2.0 * HAND_OVER) / (1.0 - 2.0 * HAND_OVER)).max(0.0)).0);
|
||||
|
||||
Scene { card, grow, pill_shown, reveal, switch, previous, middle: monitor.width as f32 / 2.0, island_w: island::width_on(here), island_grow }
|
||||
Scene { card, grow, pill_shown, reveal, switch, previous, middle: monitor.width as f32 / 2.0, island_w: island::width_on(here), row_grow }
|
||||
}
|
||||
|
||||
impl Scene {
|
||||
// an item's left edge on the monitor: between the closed row and the carousel by `grow`, between two carousels by `switch`
|
||||
fn place(&self, item: Item) -> f32 {
|
||||
// an item's left edge on the monitor and how much it shows: between the closed row and the carousel by `row_grow`, between two carousels by `switch`
|
||||
fn place(&self, item: Item) -> (f32, f32) {
|
||||
let closed = closed_x(item, self.middle, self.island_w);
|
||||
|
||||
let Some((open, _)) = self.card else { return closed };
|
||||
let Some((open, _)) = self.card else { return (closed, 1.0) };
|
||||
|
||||
let now = carousel_x(item, open, open.size().0, self.middle);
|
||||
let open_x = match self.previous {
|
||||
Some(before) => mix(carousel_x(item, before, before.size().0, self.middle), now, self.switch),
|
||||
None => now,
|
||||
let now = carousel(item, open, open.size().0, self.middle);
|
||||
|
||||
let (open_x, open_shown, open_side) = match self.previous {
|
||||
Some(before) => {
|
||||
let from = carousel(item, before, before.size().0, self.middle);
|
||||
let (x, shown) = travel(from, now, self.switch);
|
||||
|
||||
(x, shown, if self.switch < 0.5 { from.1 } else { now.1 })
|
||||
}
|
||||
None => (now.0, 1.0, now.1),
|
||||
};
|
||||
|
||||
mix(closed, open_x, if item == Item::Island { self.island_grow } else { self.grow })
|
||||
let (x, shown) = travel((closed, closed_side(item, open)), (open_x, open_side), self.row_grow);
|
||||
|
||||
(x, shown * open_shown)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -403,7 +453,11 @@ pub fn row_view(monitor: &Monitor) -> LayerWindow {
|
||||
let mut regions: Vec<InputArea> = Vec::new();
|
||||
|
||||
for item in ROW {
|
||||
let x = scene.place(item);
|
||||
let (x, seen) = scene.place(item);
|
||||
|
||||
if seen <= 0.0 {
|
||||
continue;
|
||||
}
|
||||
|
||||
match item {
|
||||
Item::Island => {
|
||||
@@ -412,10 +466,14 @@ pub fn row_view(monitor: &Monitor) -> LayerWindow {
|
||||
continue;
|
||||
}
|
||||
|
||||
// it shrinks to its cover (or its bars) as it moves aside
|
||||
let small = scene.row_grow;
|
||||
let width = mix(island_pill::width(), island::small_width(), small);
|
||||
let on = here.to_string();
|
||||
let pill = island::pill_face(here).cursor(Pointer).on_hover(edge::over_bar).on_click(move |_| island_tapped(on.clone()));
|
||||
let pill = island::pill_face(here, small).cursor(Pointer).on_hover(edge::over_bar).on_click(move |_| island_tapped(on.clone()));
|
||||
let pill = if seen < 1.0 { pill.opacity(seen) } else { pill };
|
||||
|
||||
regions.push(InputArea { x: x as i32, y: top as i32, width: island_pill::width().ceil() as i32, height: h.ceil() as i32 });
|
||||
regions.push(InputArea { x: x as i32, y: top as i32, width: width.ceil() as i32, height: h.ceil() as i32 });
|
||||
layers.push(Box::new(pill.translate(x, top)));
|
||||
}
|
||||
Item::Pod(pod) => {
|
||||
@@ -427,7 +485,7 @@ pub fn row_view(monitor: &Monitor) -> LayerWindow {
|
||||
_ if card.is_some_and(|(open, t)| open == pod && t > 2.0 * HAND_OVER) => continue,
|
||||
Some(before) if before == pod => switch,
|
||||
_ => 1.0,
|
||||
};
|
||||
} * seen;
|
||||
|
||||
if shown <= 0.0 {
|
||||
continue;
|
||||
|
||||
Reference in New Issue
Block a user