island: cover gradient background, unmirrored pill levels
- the pill, the card and the fade veils share one 150-degree gradient from the cover's palette (most common, second, background), each darkened to a fixed brightness so white text stays readable; veils span the whole shape so their gradient lines up with the backdrop's - measured on the desktop: card open 3.1 ms gpu (solid fill was ~3), pill 1.7 ms (was 1.0), open/close animation unchanged (1 and 0 slow frames) - pill levels: lows left, highs right, no mirroring; every bar still grows up and down from the middle line
This commit is contained in:
+54
-31
@@ -3,7 +3,7 @@ use std::thread;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use amane::{
|
||||
Animation, Arc, Argument, Bus, Canvas, Cap, Center, Color, Column, Easing, End, Full,
|
||||
Animation, Arc, Argument, Bus, Canvas, Cap, Center, Color, Column, Easing, End, Full, Gradient,
|
||||
Image, InputArea, Layer, LayerWindow, Line, Media, MediaPlayer, Padding, Palette, Parent, Path,
|
||||
Pointer, Rectangle, Row, Scroll, ScrollArea, Service, Shadow, Shape, SpaceBetween, Stack,
|
||||
Start, Text, Vertical, Weight, Widget, Zone, children, shapes,
|
||||
@@ -92,7 +92,6 @@ const CHIP: Color = Color::rgba(255, 255, 255, 18);
|
||||
const CHIP_OPEN: Color = Color::rgba(255, 255, 255, 34);
|
||||
const TRACK: Color = Color::rgba(255, 255, 255, 24);
|
||||
const EDGE: Color = Color::rgba(255, 255, 255, 26);
|
||||
const SURFACE_TOP: Color = Color::rgb(30, 21, 38);
|
||||
const SURFACE_BOTTOM: Color = Color::rgb(13, 10, 18);
|
||||
const MENU_BG: Color = Color::rgb(35, 26, 44);
|
||||
|
||||
@@ -185,9 +184,10 @@ pub fn view() -> LayerWindow {
|
||||
|
||||
let width = mix(COMPACT_W, WIDE_W, grow);
|
||||
let height = mix(COMPACT_H, WIDE_H, grow);
|
||||
let radius = mix(COMPACT_H / 2.0, WIDE_RADIUS, grow);
|
||||
|
||||
// solid, tinted toward the cover: gradients were this iGPU's most expensive fill
|
||||
let surface = tint(palette.dominant());
|
||||
// a gradient of the cover's own colours, for the pill and the card alike
|
||||
let colors = backdrop_colors(&palette);
|
||||
|
||||
// at most one face at a time, each faded by a veil instead of a gpu layer
|
||||
let face = if reveal > 0.0 {
|
||||
@@ -200,9 +200,9 @@ pub fn view() -> LayerWindow {
|
||||
levels: &spectrum.levels,
|
||||
};
|
||||
|
||||
veiled(card(&scene), WIDE_W, WIDE_H, WIDE_RADIUS, reveal, surface)
|
||||
veiled(card(&scene), width, height, radius, reveal, &colors)
|
||||
} else if pill > 0.0 {
|
||||
veiled(compact(player, &art.path, &spectrum.levels), COMPACT_W, COMPACT_H, COMPACT_H / 2.0, pill, surface)
|
||||
veiled(compact(player, &art.path, &spectrum.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)
|
||||
@@ -211,8 +211,8 @@ pub fn view() -> LayerWindow {
|
||||
let shape = Rectangle::new()
|
||||
.width(width)
|
||||
.height(height)
|
||||
.radius(mix(COMPACT_H / 2.0, WIDE_RADIUS, grow))
|
||||
.fill(surface)
|
||||
.radius(radius)
|
||||
.fill(backdrop(&colors, 255))
|
||||
.border(1.0, EDGE);
|
||||
|
||||
/*
|
||||
@@ -377,11 +377,12 @@ fn phases(timeline: f32) -> (f32, f32, f32) {
|
||||
}
|
||||
|
||||
/*
|
||||
* fades a face in over the island's solid surface by laying that surface over it,
|
||||
* fades a face in over the island's backdrop by laying the same gradient over it,
|
||||
* thinning as `shown` rises: the same picture as .opacity(shown), without a gpu layer.
|
||||
* inset by the 1 px rim, so the border stays visible
|
||||
* the veil spans the whole shape (minus the 1 px rim), so its gradient lines up with
|
||||
* the backdrop's exactly; amane places gradients by each rectangle's own bounds
|
||||
*/
|
||||
fn veiled(face: Rectangle, width: f32, height: f32, radius: f32, shown: f32, surface: Color) -> Rectangle {
|
||||
fn veiled(face: Rectangle, width: f32, height: f32, radius: f32, shown: f32, colors: &[Color; 3]) -> Rectangle {
|
||||
if shown >= 1.0 {
|
||||
return face;
|
||||
}
|
||||
@@ -390,12 +391,42 @@ fn veiled(face: Rectangle, width: f32, height: f32, radius: f32, shown: f32, sur
|
||||
.width(width - 2.0)
|
||||
.height(height - 2.0)
|
||||
.radius(radius - 1.0)
|
||||
.fill(faded(surface, ((1.0 - shown) * 255.0).round() as u8))
|
||||
.fill(backdrop(colors, ((1.0 - shown) * 255.0).round() as u8))
|
||||
.translate(1.0, 1.0);
|
||||
|
||||
let face = Rectangle::new().width(width).height(height).align_child(Center, Start).child(face);
|
||||
|
||||
Rectangle::new().width(width).height(height).child(Stack::new(children![face, veil]).width(width).height(height))
|
||||
}
|
||||
|
||||
// the cover's colours for the backdrop: most common, second most common, darkest
|
||||
fn backdrop_colors(palette: &Palette) -> [Color; 3] {
|
||||
let first = palette.dominant();
|
||||
let second = palette.colors().get(1).copied().unwrap_or(first);
|
||||
|
||||
// darker toward the bottom right, where the equalizer and presets sit
|
||||
[deepen(first, 0.22), deepen(second, 0.15), deepen(palette.background(), 0.07)]
|
||||
}
|
||||
|
||||
/*
|
||||
* one gradient for the pill, the card and the veils; 150 degrees runs from the
|
||||
* top left to the bottom right, like the reference's light
|
||||
*/
|
||||
fn backdrop(colors: &[Color; 3], alpha: u8) -> Gradient {
|
||||
Gradient::linear(150.0, [(0.0, faded(colors[0], alpha)), (0.55, faded(colors[1], alpha)), (1.0, faded(colors[2], alpha))])
|
||||
}
|
||||
|
||||
// scaled down, keeping its hue, until its brightness is at most `max` (0 to 1), so white text reads on it
|
||||
fn deepen(color: Color, max: f32) -> Color {
|
||||
let (red, green, blue) = (f32::from(color.red()), f32::from(color.green()), f32::from(color.blue()));
|
||||
|
||||
let brightness = (0.2126 * red + 0.7152 * green + 0.0722 * blue) / 255.0;
|
||||
|
||||
let scale = if brightness > max { max / brightness } else { 1.0 };
|
||||
|
||||
Color::rgb((red * scale) as u8, (green * scale) as u8, (blue * scale) as u8)
|
||||
}
|
||||
|
||||
fn faded(color: Color, alpha: u8) -> Color {
|
||||
Color::rgba(color.red(), color.green(), color.blue(), alpha)
|
||||
}
|
||||
@@ -431,13 +462,6 @@ fn card(scene: &Scene) -> Rectangle {
|
||||
Rectangle::new().width(WIDE_W).height(WIDE_H).child(Stack::new(layers).width(WIDE_W).height(WIDE_H))
|
||||
}
|
||||
|
||||
// the surface colour leaning a little toward the cover's
|
||||
fn tint(color: Color) -> Color {
|
||||
let lean = |surface: u8, cover: u8| (f32::from(surface) * 0.7 + f32::from(cover) * 0.3) as u8;
|
||||
|
||||
Color::rgb(lean(SURFACE_TOP.red(), color.red()), lean(SURFACE_TOP.green(), color.green()), lean(SURFACE_TOP.blue(), color.blue()))
|
||||
}
|
||||
|
||||
fn content(scene: &Scene) -> Rectangle {
|
||||
Rectangle::new().width(WIDE_W).height(WIDE_H).padding(PAD).child(
|
||||
Column::new(children![
|
||||
@@ -1064,9 +1088,9 @@ fn cover(art: &str, size: f32, rim: f32, hole: bool) -> Rectangle {
|
||||
}
|
||||
|
||||
/*
|
||||
* the pill's visualiser: seven bars mirrored around the bass in the middle, from the
|
||||
* shown player's own sound, glowing when loud; a pause sign while paused, and still
|
||||
* bars while the capture starts or the player has no stream to listen to
|
||||
* the pill's visualiser: seven bars from the shown player's own sound, glowing when
|
||||
* loud; a pause sign while paused, and still bars while the capture starts or the
|
||||
* player has no stream to listen to
|
||||
*/
|
||||
fn pill_levels(playing: bool, levels: &[f32]) -> Canvas {
|
||||
const BARS: usize = 7;
|
||||
@@ -1077,32 +1101,31 @@ fn pill_levels(playing: bool, levels: &[f32]) -> Canvas {
|
||||
return icon(Glyph::Pause, 13.0, DIM);
|
||||
}
|
||||
|
||||
let middle = BARS / 2;
|
||||
// lows on the left, highs on the right, no mirroring; every bar grows up and down from the middle line
|
||||
let middle = HEIGHT / 2.0;
|
||||
|
||||
let mut glow = Path::new();
|
||||
let mut body = Path::new();
|
||||
|
||||
for index in 0..BARS {
|
||||
let distance = index.abs_diff(middle);
|
||||
|
||||
let level = if levels.is_empty() {
|
||||
[0.85, 0.55, 0.7, 0.35][distance]
|
||||
[0.55, 0.85, 0.6, 0.75, 0.45, 0.6, 0.35][index]
|
||||
} else {
|
||||
/*
|
||||
* the loudest band of this bar's quarter of the spectrum, linear with a little
|
||||
* the loudest band of this bar's seventh of the spectrum, linear with a little
|
||||
* gain: typical music sits at 0.15 to 0.35 here, and a lifting curve made every
|
||||
* bar look nearly full
|
||||
*/
|
||||
(band(levels, distance, middle + 1) * 1.4).min(1.0)
|
||||
(band(levels, index, BARS) * 1.4).min(1.0)
|
||||
};
|
||||
|
||||
let half = 1.5 + level * (HEIGHT / 2.0 - 3.0);
|
||||
let half = 1.5 + level * (middle - 3.0);
|
||||
let x = 3.0 + index as f32 * (WIDTH - 6.0) / (BARS - 1) as f32;
|
||||
|
||||
body = body.move_to(x, HEIGHT / 2.0 - half).line_to(x, HEIGHT / 2.0 + half);
|
||||
body = body.move_to(x, middle - half).line_to(x, middle + half);
|
||||
|
||||
if level > 0.3 {
|
||||
glow = glow.move_to(x, HEIGHT / 2.0 - half).line_to(x, HEIGHT / 2.0 + half);
|
||||
glow = glow.move_to(x, middle - half).line_to(x, middle + half);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user