island: card on one monitor, live bars only on the focused one
- the card opens on the monitor that was clicked (keybinds: the focused monitor); the others keep their pill. moving the card to another monitor restarts its open animation there - only the focused monitor's window (and the one with the card) reads Spectrum, so the other pills show still bars and rest - hover lights only the pill under the pointer - measured with music on 3 monitors: pill 10.7% of a core (was 27.5%), open card 20.7% (was 58%), ~62 frames a second in total, 0-1 slow frames per open/close (was 7-14), back to one island's cost
This commit is contained in:
+76
-19
@@ -5,9 +5,9 @@ use std::time::{Duration, Instant};
|
||||
|
||||
use amane::{
|
||||
Animation, Arc, Argument, Blend, Bus, Canvas, Cap, Center, Color, Column, Easing, End, Full, Gradient,
|
||||
Image, InputArea, Layer, LayerWindow, Line, Media, MediaPlayer, Padding, Palette, Parent, Path,
|
||||
Image, InputArea, Layer, LayerWindow, Line, Media, MediaPlayer, Monitor, Padding, Palette, Parent, Path,
|
||||
Pointer, Rectangle, Row, Scroll, ScrollArea, Service, Shadow, Shape, SpaceBetween, Stack,
|
||||
Start, Text, Vertical, Weight, Widget, Zone, children, shapes,
|
||||
Start, Text, Vertical, Weight, Widget, Workspaces, Zone, children, shapes,
|
||||
};
|
||||
|
||||
use crate::art::Art;
|
||||
@@ -144,18 +144,28 @@ pub struct Island {
|
||||
|
||||
// each hovered spot's highlight, fading between 0 and 1
|
||||
hover: HashMap<Spot, Animation>,
|
||||
|
||||
/*
|
||||
* every monitor has an island, but the card opens on one: the monitor it was last
|
||||
* opened on (none: every monitor, without a workspace list), and the monitor whose
|
||||
* pill the pointer was last on, so only that pill lights up
|
||||
*/
|
||||
opened_on: Option<String>,
|
||||
pointer_on: Option<String>,
|
||||
}
|
||||
|
||||
impl Service for Island {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
open: Animation::new(0.0).duration(Duration::from_millis(OPEN_MS)).easing(Easing::Linear),
|
||||
open: opening(),
|
||||
expanded: false,
|
||||
selected: None,
|
||||
menu: None,
|
||||
scrub: None,
|
||||
seek_shown_until: None,
|
||||
hover: HashMap::new(),
|
||||
opened_on: None,
|
||||
pointer_on: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -220,13 +230,27 @@ struct Scene<'a> {
|
||||
levels: &'a [f32],
|
||||
}
|
||||
|
||||
pub fn view() -> LayerWindow {
|
||||
pub fn view(monitor: &Monitor) -> LayerWindow {
|
||||
let media = Media::read();
|
||||
let island = Island::read();
|
||||
let sound = Sound::read();
|
||||
let art = Art::read();
|
||||
let palette = Palette::read();
|
||||
let spectrum = Spectrum::read();
|
||||
|
||||
let here = monitor.name.as_str();
|
||||
let focused = focused_monitor();
|
||||
|
||||
// the other monitors keep their pill while the card is open here
|
||||
let opened_here = island.opened_on.as_deref().is_none_or(|name| name == here);
|
||||
|
||||
/*
|
||||
* only the focused monitor (and the one with the open card) shows live levels:
|
||||
* reading Spectrum is what redraws a window for every cava frame, so the other
|
||||
* windows skip it, rest, and show still bars
|
||||
*/
|
||||
let live = focused.as_deref().is_none_or(|name| name == here) || (opened_here && island.expanded);
|
||||
let spectrum = live.then(Spectrum::read);
|
||||
let levels: &[f32] = spectrum.as_deref().map_or(&[], |spectrum| spectrum.levels.as_slice());
|
||||
|
||||
let window_w = WIDE_W + 2.0 * SHADOW;
|
||||
|
||||
@@ -243,16 +267,17 @@ pub fn view() -> LayerWindow {
|
||||
return window.visible(false);
|
||||
};
|
||||
|
||||
let (grow, pill, reveal) = phases(island.open.value());
|
||||
let (grow, pill, reveal) = phases(if opened_here { island.open.value() } else { 0.0 });
|
||||
|
||||
// the pill under the pointer widens a little and glows pink; the card doesn't
|
||||
let lift = island.lit(Spot::Island) * (1.0 - grow);
|
||||
let hovered_here = island.pointer_on.as_deref() == Some(here);
|
||||
let lift = if hovered_here { island.lit(Spot::Island) } else { 0.0 } * (1.0 - grow);
|
||||
|
||||
/*
|
||||
* live levels keep frames coming the way an animation does: a service change
|
||||
* alone asks with an empty commit, which hyprland only answers on its next repaint
|
||||
*/
|
||||
if !spectrum.levels.is_empty() {
|
||||
if !levels.is_empty() {
|
||||
amane::request_frame();
|
||||
}
|
||||
|
||||
@@ -271,12 +296,12 @@ pub fn view() -> LayerWindow {
|
||||
island: &island,
|
||||
sound: &sound,
|
||||
art: &art.path,
|
||||
levels: &spectrum.levels,
|
||||
levels,
|
||||
};
|
||||
|
||||
veiled(card(&scene), width, height, radius, reveal, &colors)
|
||||
} else if pill > 0.0 {
|
||||
veiled(compact(player, &art.path, &spectrum.levels), width, height, radius, pill, &colors)
|
||||
veiled(compact(player, &art.path, levels), 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)
|
||||
@@ -297,19 +322,29 @@ pub fn view() -> LayerWindow {
|
||||
*/
|
||||
let shape = shape.shadow(Shadow::drop(PINK).opacity(mix(0.3, 0.7, lift)).blur(22.0));
|
||||
|
||||
let tapped = monitor.name.clone();
|
||||
let hovered = monitor.name.clone();
|
||||
|
||||
let shape = shape
|
||||
.align_child(Center, Start)
|
||||
.on_click(|_| tap())
|
||||
.on_hover(|inside| {
|
||||
.on_click(move |_| tap(&tapped))
|
||||
.on_hover(move |inside| {
|
||||
if inside {
|
||||
Island::write().pointer_on = Some(hovered.clone());
|
||||
}
|
||||
|
||||
hovering(Spot::Island)(inside);
|
||||
|
||||
if !inside {
|
||||
// leaving a pill on another monitor leaves the open card alone
|
||||
let card_here = Island::read().opened_on.as_deref().is_none_or(|name| name == hovered);
|
||||
|
||||
if !inside && card_here {
|
||||
collapse();
|
||||
}
|
||||
})
|
||||
.child(face);
|
||||
|
||||
let shape = if island.expanded { shape } else { shape.cursor(Pointer) };
|
||||
let shape = if island.expanded && opened_here { shape } else { shape.cursor(Pointer) };
|
||||
|
||||
// clicks outside the island reach the windows underneath
|
||||
let region = InputArea {
|
||||
@@ -329,23 +364,45 @@ pub fn view() -> LayerWindow {
|
||||
)
|
||||
}
|
||||
|
||||
// a click on the island itself: closes an open dropdown, else opens the card
|
||||
fn tap() {
|
||||
// a click on the island of `monitor`: closes an open dropdown, else opens the card there
|
||||
fn tap(monitor: &str) {
|
||||
let mut island = Island::write();
|
||||
|
||||
if island.menu.take().is_none() {
|
||||
island.expanded = true;
|
||||
island.open.to(1.0);
|
||||
open_on(&mut island, Some(monitor.to_string()));
|
||||
}
|
||||
}
|
||||
|
||||
// keybinds open the card on the focused monitor
|
||||
fn expand() {
|
||||
let mut island = Island::write();
|
||||
let focused = focused_monitor();
|
||||
|
||||
open_on(&mut Island::write(), focused);
|
||||
}
|
||||
|
||||
fn open_on(island: &mut Island, monitor: Option<String>) {
|
||||
// open on another monitor: that one grows from its pill, this one shows its pill again
|
||||
if island.opened_on != monitor {
|
||||
island.open = opening();
|
||||
island.opened_on = monitor;
|
||||
}
|
||||
|
||||
island.expanded = true;
|
||||
island.open.to(1.0);
|
||||
}
|
||||
|
||||
// the open timeline, at rest on the pill
|
||||
fn opening() -> Animation {
|
||||
Animation::new(0.0).duration(Duration::from_millis(OPEN_MS)).easing(Easing::Linear)
|
||||
}
|
||||
|
||||
// the monitor of the focused workspace, which on hyprland follows the mouse; none without a workspace list
|
||||
fn focused_monitor() -> Option<String> {
|
||||
let workspaces = Workspaces::read();
|
||||
|
||||
workspaces.list().iter().find(|workspace| workspace.focused()).and_then(|workspace| workspace.output()).map(String::from)
|
||||
}
|
||||
|
||||
fn collapse() {
|
||||
let mut island = Island::write();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user