island: glowing card, hover on eq bands and time bar, bold transport
- the open card keeps a faint pink glow: the pill's shadow blends from a dark drop shadow to a centred pink glow (opacity 0.3, blur 22, inside the window margin); measured 2.95 ms gpu with the card open, as before - eq bands: hovered, the track widens 4 px and brightens, the knob grows 25% with a full pink ring, the frequency label turns pink - time bar: hovered, the track thickens 4 px and brightens, the knob grows - previous/play-pause/next: no halo, circle or growth any more; they go bold instead (an outline of the glyph's own colour, 0 to 2 px; pause bars widen), and glowing() is gone
This commit is contained in:
+62
-68
@@ -28,6 +28,9 @@ const SHAPE_DONE: f32 = 0.6;
|
||||
const HOVER_MS: u64 = 220;
|
||||
const PILL_HOVER_GROW: f32 = 10.0;
|
||||
|
||||
// how much thicker, in px, the transport icons get when hovered
|
||||
const BOLD: f32 = 2.0;
|
||||
|
||||
// sizes, in pixels
|
||||
const COMPACT_W: f32 = 220.0;
|
||||
const COMPACT_H: f32 = 38.0;
|
||||
@@ -121,6 +124,8 @@ enum Spot {
|
||||
Next,
|
||||
Preset(&'static str),
|
||||
Row(usize),
|
||||
Band(usize),
|
||||
Timeline,
|
||||
}
|
||||
|
||||
pub struct Island {
|
||||
@@ -197,17 +202,6 @@ fn hovering(spot: Spot) -> impl Fn(bool) + 'static {
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* a soft pink halo that grows with `lit`; nothing at rest, since every shadow costs
|
||||
* two gpu layers each frame
|
||||
*/
|
||||
fn glowing(rectangle: Rectangle, lit: f32, strength: f32) -> Rectangle {
|
||||
if lit < 0.01 {
|
||||
return rectangle;
|
||||
}
|
||||
|
||||
rectangle.shadow(Shadow::drop(PINK).opacity(strength * lit).blur(6.0 + 12.0 * lit))
|
||||
}
|
||||
|
||||
// the picked player while it's still open, else the one Media calls active
|
||||
pub fn shown<'a>(media: &'a Media, selected: Option<&str>) -> Option<&'a MediaPlayer> {
|
||||
@@ -296,24 +290,19 @@ pub fn view() -> LayerWindow {
|
||||
.border(1.0, Color::blend(EDGE, faded(PINK, 110), lift));
|
||||
|
||||
/*
|
||||
* amane redraws the whole window every frame, so big static effects are paid
|
||||
* 60 times a second: the shadow belongs to the pill and the grow, and fades out
|
||||
* with the card coming in. no clip anywhere: neither face ever overflows the shape
|
||||
* one shadow, three looks: a dark drop shadow under the pill, a pink centred glow
|
||||
* when the pill is hovered, and a faint pink glow around the open card. blur 22
|
||||
* fades out within ~28 px (amane blurs with a deviation of blur / 2), inside the
|
||||
* window's SHADOW margin. no clip anywhere: neither face ever overflows the shape
|
||||
*/
|
||||
let shape = if reveal < 1.0 {
|
||||
/*
|
||||
* hovered, the pill's own drop shadow turns into a glow: pink, centred, a little
|
||||
* stronger, so it costs nothing the pill wasn't already paying
|
||||
*/
|
||||
let shadow = Shadow::drop(Color::blend(Color::BLACK, PINK, lift))
|
||||
.opacity(mix(0.55, 0.7, lift) * (1.0 - reveal))
|
||||
.blur(mix(24.0, 20.0, lift))
|
||||
.offset(0.0, mix(10.0, 0.0, lift));
|
||||
let glow = lift.max(reveal);
|
||||
|
||||
shape.shadow(shadow)
|
||||
} else {
|
||||
shape
|
||||
};
|
||||
let shape = shape.shadow(
|
||||
Shadow::drop(Color::blend(Color::BLACK, PINK, glow))
|
||||
.opacity(mix(mix(0.55, 0.7, lift), 0.3, reveal))
|
||||
.blur(mix(mix(24.0, 20.0, lift), 22.0, reveal))
|
||||
.offset(0.0, mix(10.0, 0.0, glow)),
|
||||
);
|
||||
|
||||
let shape = shape
|
||||
.align_child(Center, Start)
|
||||
@@ -755,14 +744,18 @@ fn timeline(scene: &Scene) -> Column {
|
||||
// a live stream has no length to scale by, so it just counts up
|
||||
let shown = if length > 0.0 { fraction * length } else { position };
|
||||
|
||||
// hovered, the track thickens and brightens and the knob grows
|
||||
let lit = island.lit(Spot::Timeline);
|
||||
let bar_h = BAR_H + 4.0 * lit;
|
||||
|
||||
let bar = Rectangle::new().width(SIDE_W).height(KNOB).align_child(Start, Center).child(
|
||||
Stack::new(children![
|
||||
Rectangle::new().width(SIDE_W).height(BAR_H).radius(Full).fill(TRACK).child(
|
||||
Rectangle::new().width(SIDE_W).height(bar_h).radius(Full).fill(Color::blend(TRACK, CHIP_HOVER, lit)).child(
|
||||
Rectangle::new()
|
||||
.width((SIDE_W * fraction).max(BAR_H))
|
||||
.height(BAR_H)
|
||||
.width((SIDE_W * fraction).max(bar_h))
|
||||
.height(bar_h)
|
||||
.radius(Full)
|
||||
.fill(PINK),
|
||||
.fill(Color::blend(PINK, FLAME, 0.4 * lit)),
|
||||
),
|
||||
Rectangle::new()
|
||||
.width(KNOB)
|
||||
@@ -770,10 +763,11 @@ fn timeline(scene: &Scene) -> Column {
|
||||
.radius(Full)
|
||||
.fill(Color::WHITE)
|
||||
.shadow(Shadow::drop("#000000").opacity(0.4).blur(6.0))
|
||||
.translate(SIDE_W * fraction - KNOB / 2.0, (BAR_H - KNOB) / 2.0),
|
||||
.translate(SIDE_W * fraction - KNOB / 2.0, (bar_h - KNOB) / 2.0)
|
||||
.scale(1.0 + 0.25 * lit),
|
||||
])
|
||||
.width(SIDE_W)
|
||||
.height(BAR_H),
|
||||
.height(bar_h),
|
||||
);
|
||||
|
||||
// live streams have no length, so there is nowhere to seek to
|
||||
@@ -781,7 +775,10 @@ fn timeline(scene: &Scene) -> Column {
|
||||
let name = player.name().to_string();
|
||||
|
||||
bar.cursor(Pointer)
|
||||
.on_hover(leave_bar)
|
||||
.on_hover(|inside| {
|
||||
hovering(Spot::Timeline)(inside);
|
||||
leave_bar(inside);
|
||||
})
|
||||
.on_drag(|point| {
|
||||
let mut island = Island::write();
|
||||
|
||||
@@ -859,32 +856,22 @@ fn controls(player: &MediaPlayer, island: &Island) -> Row {
|
||||
let toggle = player.clone();
|
||||
let next = player.clone();
|
||||
|
||||
/*
|
||||
* hovered, play/pause swells, gains a halo and its pink warms toward white;
|
||||
* previous and next do the same smaller, their icons turning pink
|
||||
*/
|
||||
// hovered, the transport icons go bold: thicker strokes, nothing else
|
||||
let lit = island.lit(Spot::PlayPause);
|
||||
let ink = Color::blend(PINK, FLAME, 0.55 * lit);
|
||||
|
||||
let middle = Rectangle::new()
|
||||
.width(CONTROLS_H)
|
||||
.height(CONTROLS_H)
|
||||
.radius(Full)
|
||||
.fill(Color::blend(faded(PINK, 0), faded(PINK, 34), lit))
|
||||
.align_child(Center, Center)
|
||||
.scale(1.0 + 0.1 * lit);
|
||||
let middle = Rectangle::new().width(CONTROLS_H).height(CONTROLS_H).align_child(Center, Center);
|
||||
|
||||
let middle = if player.playing() {
|
||||
let bar = || Rectangle::new().width(10.0).height(30.0).radius(3.0).fill(ink);
|
||||
let bar = || Rectangle::new().width(10.0 + BOLD * lit).height(30.0 + BOLD * lit).radius(3.0).fill(PINK);
|
||||
|
||||
middle.child(Row::new(children![bar(), bar()]).gap(7.0))
|
||||
middle.child(Row::new(children![bar(), bar()]).gap(7.0 - BOLD * lit))
|
||||
} else {
|
||||
middle.child(icon(Glyph::Play, 30.0, ink))
|
||||
middle.child(bold_icon(Glyph::Play, 30.0, PINK, BOLD * lit))
|
||||
};
|
||||
|
||||
Row::new(children![
|
||||
button(Glyph::Previous, island.lit(Spot::Previous)).on_hover(hovering(Spot::Previous)).on_click(move |_| previous.previous()),
|
||||
glowing(middle, lit, 0.55).cursor(Pointer).on_hover(hovering(Spot::PlayPause)).on_click(move |_| toggle.play_pause()),
|
||||
middle.cursor(Pointer).on_hover(hovering(Spot::PlayPause)).on_click(move |_| toggle.play_pause()),
|
||||
button(Glyph::Next, island.lit(Spot::Next)).on_hover(hovering(Spot::Next)).on_click(move |_| next.next()),
|
||||
])
|
||||
.gap(26.0)
|
||||
@@ -894,19 +881,14 @@ fn controls(player: &MediaPlayer, island: &Island) -> Row {
|
||||
.height(CONTROLS_H)
|
||||
}
|
||||
|
||||
// a round icon button: hovered, it swells, its icon turns pink and a soft pink halo grows around it
|
||||
// a transport icon button, going bold as `lit` rises
|
||||
fn button(glyph: Glyph, lit: f32) -> Rectangle {
|
||||
let circle = Rectangle::new()
|
||||
Rectangle::new()
|
||||
.width(40.0)
|
||||
.height(40.0)
|
||||
.radius(Full)
|
||||
.fill(Color::blend(faded(PINK, 0), faded(PINK, 26), lit))
|
||||
.align_child(Center, Center)
|
||||
.scale(1.0 + 0.12 * lit)
|
||||
.cursor(Pointer)
|
||||
.child(icon(glyph, 18.0, Color::blend(TEXT, PINK, lit)));
|
||||
|
||||
glowing(circle, lit, 0.4)
|
||||
.child(bold_icon(glyph, 18.0, TEXT, BOLD * lit))
|
||||
}
|
||||
|
||||
fn eq_header(scene: &Scene) -> Row {
|
||||
@@ -982,21 +964,25 @@ fn equalizer(scene: &Scene) -> Column {
|
||||
for (band, (frequency, gain)) in BANDS.iter().zip(gains).enumerate() {
|
||||
let y = knob_y(gain);
|
||||
|
||||
// hovered, a band's track widens and brightens, its knob grows and its label turns pink
|
||||
let lit = scene.island.lit(Spot::Band(band));
|
||||
let track_w = TRACK_W + 4.0 * lit;
|
||||
|
||||
tracks.push(Box::new(
|
||||
Rectangle::new().width(column).height(SLIDER_H).align_child(Center, Start).child(
|
||||
Rectangle::new()
|
||||
.width(TRACK_W)
|
||||
.width(track_w)
|
||||
.height(SLIDER_H)
|
||||
.radius(TRACK_W / 2.0)
|
||||
.fill(TRACK)
|
||||
.radius(track_w / 2.0)
|
||||
.fill(Color::blend(TRACK, CHIP_HOVER, lit))
|
||||
.align_child(Center, End)
|
||||
.child(
|
||||
// rounded itself rather than clipped by the track: clips are gpu layers
|
||||
Rectangle::new()
|
||||
.width(TRACK_W)
|
||||
.width(track_w)
|
||||
.height(SLIDER_H - y)
|
||||
.radius(TRACK_W / 2.0)
|
||||
.fill(PINK),
|
||||
.radius(track_w / 2.0)
|
||||
.fill(Color::blend(PINK, FLAME, 0.45 * lit)),
|
||||
),
|
||||
),
|
||||
));
|
||||
@@ -1010,6 +996,7 @@ fn equalizer(scene: &Scene) -> Column {
|
||||
.height(SLIDER_H)
|
||||
.align_child(Center, Start)
|
||||
.cursor(Pointer)
|
||||
.on_hover(hovering(Spot::Band(band)))
|
||||
.on_drag(move |point| Sound::set_band(&player, &identity, band, gain_at(point.y)))
|
||||
.child(
|
||||
// a pink ring instead of a blurred shadow: ten blurs per frame cost the gpu more than the rest of the card
|
||||
@@ -1018,15 +1005,16 @@ fn equalizer(scene: &Scene) -> Column {
|
||||
.height(KNOB)
|
||||
.radius(Full)
|
||||
.fill(Color::WHITE)
|
||||
.border(2.0, faded(PINK, 150))
|
||||
.translate(0.0, y - KNOB / 2.0),
|
||||
.border(2.0, Color::blend(faded(PINK, 150), PINK, lit))
|
||||
.translate(0.0, y - KNOB / 2.0)
|
||||
.scale(1.0 + 0.25 * lit),
|
||||
),
|
||||
));
|
||||
|
||||
let label = if *frequency >= 1000 { format!("{}k", frequency / 1000) } else { frequency.to_string() };
|
||||
|
||||
labels.push(Box::new(
|
||||
Rectangle::new().width(column).height(LABEL_H).align_child(Center, Center).child(Text::new(label).size(10.0).color(DIM)),
|
||||
Rectangle::new().width(column).height(LABEL_H).align_child(Center, Center).child(Text::new(label).size(10.0).color(Color::blend(DIM, PINK, lit))),
|
||||
));
|
||||
}
|
||||
|
||||
@@ -1299,10 +1287,15 @@ enum Glyph {
|
||||
|
||||
// drawn, not typed, so the island looks the same whatever fonts are installed
|
||||
fn icon(glyph: Glyph, size: f32, color: Color) -> Canvas {
|
||||
bold_icon(glyph, size, color, 0.0)
|
||||
}
|
||||
|
||||
// play, pause, previous and next thickened by an outline `weight` px wide in their own colour; 0 draws none
|
||||
fn bold_icon(glyph: Glyph, size: f32, color: Color, weight: f32) -> Canvas {
|
||||
let s = size;
|
||||
|
||||
let triangle = |x0: f32, x1: f32| {
|
||||
Path::new().move_to(x0, 0.14 * s).line_to(x1, 0.5 * s).line_to(x0, 0.86 * s).close().fill(color)
|
||||
Path::new().move_to(x0, 0.14 * s).line_to(x1, 0.5 * s).line_to(x0, 0.86 * s).close().fill(color).stroke(weight, color)
|
||||
};
|
||||
|
||||
let bar = |x0: f32, x1: f32| {
|
||||
@@ -1313,6 +1306,7 @@ fn icon(glyph: Glyph, size: f32, color: Color) -> Canvas {
|
||||
.line_to(x0, 0.86 * s)
|
||||
.close()
|
||||
.fill(color)
|
||||
.stroke(weight, color)
|
||||
};
|
||||
|
||||
let speaker = || {
|
||||
|
||||
Reference in New Issue
Block a user