diff --git a/src/island.rs b/src/island.rs index 7c6b1a3..d5ebfd5 100644 --- a/src/island.rs +++ b/src/island.rs @@ -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 = 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( diff --git a/src/pods.rs b/src/pods.rs index 555da3f..dae86f0 100644 --- a/src/pods.rs +++ b/src/pods.rs @@ -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, 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 = 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;