glass-base: a glass opacity setting and one place for the surface look
- shell.conf glass (0.05 to 1, default 1 = solid, as before): the surfaces' opacity; read live with the theme - surface.rs: shell() draws the island, the idle pill and the workspace pill (fill, rim, shadow), so a design is one file; the default is the look so far - see-through surfaces can't hide content under a veil, so with glass below 1 the open/close fades use .opacity() while they run - README: the Hyprland layer rule that frosts what's behind the shell (namespace match is a full match: ^magic-.*$; ignore_alpha keeps the transparent margin around the island sharp) - docs/design: the solid look and glass 0.5 with the rule, for review
This commit is contained in:
+21
-27
@@ -14,6 +14,7 @@ use crate::art::Art;
|
||||
use crate::parse::{self, BANDS, Sink};
|
||||
use crate::sound::{EQ_PRESETS, MAX_GAIN, Sound};
|
||||
use crate::spectrum::Spectrum;
|
||||
use crate::surface;
|
||||
use crate::theme;
|
||||
|
||||
/*
|
||||
@@ -331,26 +332,16 @@ pub fn view(monitor: &Monitor) -> LayerWindow {
|
||||
Rectangle::new().width(0.0).height(0.0)
|
||||
};
|
||||
|
||||
let shape = Rectangle::new()
|
||||
.width(width)
|
||||
.height(height)
|
||||
.radius(radius)
|
||||
.fill(backdrop(&colors, 255))
|
||||
.border(1.0, Color::blend(theme::now().outline, faded(theme::now().accent, 110), lift));
|
||||
// the face centred along the top: while the shape grows it is narrower than the shape
|
||||
let face = Rectangle::new().width(width).height(height).align_child(Center, Start).child(face);
|
||||
|
||||
/*
|
||||
* one faint pink glow around the island, pill and card alike, stronger while the
|
||||
* pill is hovered. 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 = shape.shadow(Shadow::drop(theme::now().accent).opacity(mix(0.3, 0.7, lift)).blur(22.0));
|
||||
// the surface (fill, rim, glow) is surface.rs's; no clip anywhere: neither face ever overflows the shape
|
||||
let shape = surface::shell(width, height, radius, &colors, lift, face);
|
||||
|
||||
let tapped = monitor.name.clone();
|
||||
let hovered = monitor.name.clone();
|
||||
|
||||
let shape = shape
|
||||
.align_child(Center, Start)
|
||||
.on_click(move |_| tap(&tapped))
|
||||
.on_hover(move |inside| {
|
||||
if inside {
|
||||
@@ -365,8 +356,7 @@ pub fn view(monitor: &Monitor) -> LayerWindow {
|
||||
if !inside && card_here {
|
||||
collapse();
|
||||
}
|
||||
})
|
||||
.child(face);
|
||||
});
|
||||
|
||||
let shape = if island.expanded && opened_here { shape } else { shape.cursor(Pointer) };
|
||||
|
||||
@@ -551,13 +541,19 @@ fn phases(timeline: f32) -> (f32, f32, f32) {
|
||||
* 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.
|
||||
* 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
|
||||
* the backdrop's exactly; amane places gradients by each rectangle's own bounds.
|
||||
* a see-through surface can't hide anything under a veil, so glass fades with
|
||||
* .opacity(), a gpu layer only while the fade runs
|
||||
*/
|
||||
fn veiled(face: Rectangle, width: f32, height: f32, radius: f32, shown: f32, colors: &[Color; 3]) -> Rectangle {
|
||||
if shown >= 1.0 {
|
||||
return face;
|
||||
}
|
||||
|
||||
if theme::glass() < 1.0 {
|
||||
return Rectangle::new().width(width).height(height).align_child(Center, Start).child(face).opacity(shown);
|
||||
}
|
||||
|
||||
let veil = Rectangle::new()
|
||||
.width(width - 2.0)
|
||||
.height(height - 2.0)
|
||||
@@ -602,17 +598,15 @@ pub fn faded(color: Color, alpha: u8) -> Color {
|
||||
Color::rgba(color.red(), color.green(), color.blue(), alpha)
|
||||
}
|
||||
|
||||
// the island while no player is open: a music note and "Nothing playing", glowing like the pill
|
||||
// the island while no player is open: a music note and "Nothing playing"
|
||||
fn idle(colors: &[Color; 3]) -> Rectangle {
|
||||
Rectangle::new()
|
||||
.width(COMPACT_W)
|
||||
.height(COMPACT_H)
|
||||
.radius(Full)
|
||||
.fill(backdrop(colors, 255))
|
||||
.border(1.0, theme::now().outline)
|
||||
.shadow(Shadow::drop(theme::now().accent).opacity(0.3).blur(22.0))
|
||||
.align_child(Center, Center)
|
||||
.child(Row::new(children![icon(Glyph::Note, 15.0, theme::now().accent), Text::new("Nothing playing").size(12.0).color(theme::now().dim)]).gap(8.0).align(Center))
|
||||
let content = Rectangle::new().width(COMPACT_W).height(COMPACT_H).align_child(Center, Center).child(
|
||||
Row::new(children![icon(Glyph::Note, 15.0, theme::now().accent), Text::new("Nothing playing").size(12.0).color(theme::now().dim)])
|
||||
.gap(8.0)
|
||||
.align(Center),
|
||||
);
|
||||
|
||||
surface::shell(COMPACT_W, COMPACT_H, COMPACT_H / 2.0, colors, 0.0, content)
|
||||
}
|
||||
|
||||
// the pill: cover, title, the player's live levels
|
||||
|
||||
@@ -3,6 +3,7 @@ mod island;
|
||||
mod parse;
|
||||
mod sound;
|
||||
mod spectrum;
|
||||
mod surface;
|
||||
mod theme;
|
||||
mod workspaces;
|
||||
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
use amane::{Blend, Color, Rectangle, Shadow};
|
||||
|
||||
use crate::island::{backdrop, faded};
|
||||
use crate::theme;
|
||||
|
||||
/*
|
||||
* what the island, the idle pill and the workspace pill are made of: one place, so
|
||||
* a design is this file. `shell` wraps `content` (already width x height, with its
|
||||
* own alignment and padding) in the surface. `lift` (0 to 1) is how hovered it is.
|
||||
* the default: the gradient of `colors`, a 1 px rim, a soft glow in the accent
|
||||
*/
|
||||
pub fn shell(width: f32, height: f32, radius: f32, colors: &[Color; 3], lift: f32, content: Rectangle) -> Rectangle {
|
||||
let theme = theme::now();
|
||||
|
||||
Rectangle::new()
|
||||
.width(width)
|
||||
.height(height)
|
||||
.radius(radius)
|
||||
.fill(backdrop(colors, alpha(theme::glass())))
|
||||
.border(1.0, Color::blend(theme.outline, faded(theme.accent, 110), lift))
|
||||
.shadow(Shadow::drop(theme.accent).opacity(0.3 + 0.4 * lift).blur(22.0))
|
||||
.child(content)
|
||||
}
|
||||
|
||||
// an opacity, 0 to 1, as a colour's alpha
|
||||
pub fn alpha(opacity: f32) -> u8 {
|
||||
(opacity.clamp(0.0, 1.0) * 255.0).round() as u8
|
||||
}
|
||||
+13
-1
@@ -73,6 +73,9 @@ pub struct Theme {
|
||||
|
||||
// the open media card and the equalizer take the cover's colours as their background
|
||||
pub cover_colors: bool,
|
||||
|
||||
// how opaque the shell's surfaces are, 0.05 to 1; below 1 the desktop shows through
|
||||
glass: f32,
|
||||
}
|
||||
|
||||
impl Service for Theme {
|
||||
@@ -80,6 +83,7 @@ impl Service for Theme {
|
||||
Self {
|
||||
colors: Animation::new(PINK_ON_PURPLE),
|
||||
cover_colors: false,
|
||||
glass: 1.0,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -174,6 +178,7 @@ fn apply(theme: &str, config: &str, startup: bool) {
|
||||
let mut theme = Theme::write();
|
||||
|
||||
theme.cover_colors = setting("cover_colors") == Some("true");
|
||||
theme.glass = setting("glass").and_then(|glass| glass.parse::<f32>().ok()).map_or(1.0, |glass| glass.clamp(0.05, 1.0));
|
||||
|
||||
// a change in the middle of a fade starts from the colours on screen now
|
||||
let now = theme.colors.value();
|
||||
@@ -182,9 +187,10 @@ fn apply(theme: &str, config: &str, startup: bool) {
|
||||
theme.colors.to(colors);
|
||||
}
|
||||
|
||||
// the colours of the frame being drawn, set by the view that draws it
|
||||
// the colours and glass of the frame being drawn, set by the view that draws it
|
||||
thread_local! {
|
||||
static NOW: Cell<Colors> = const { Cell::new(PINK_ON_PURPLE) };
|
||||
static GLASS: Cell<f32> = const { Cell::new(1.0) };
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -195,6 +201,7 @@ pub fn begin() -> bool {
|
||||
let theme = Theme::read();
|
||||
|
||||
NOW.set(theme.colors.value());
|
||||
GLASS.set(theme.glass);
|
||||
|
||||
theme.cover_colors
|
||||
}
|
||||
@@ -202,3 +209,8 @@ pub fn begin() -> bool {
|
||||
pub fn now() -> Colors {
|
||||
NOW.get()
|
||||
}
|
||||
|
||||
// the surfaces' opacity this frame, 0.05 to 1
|
||||
pub fn glass() -> f32 {
|
||||
GLASS.get()
|
||||
}
|
||||
|
||||
+5
-13
@@ -5,10 +5,11 @@ use std::time::Duration;
|
||||
|
||||
use amane::{
|
||||
Animation, Blend, Center, Color, Full, Horizontal, InputArea, Layer, LayerWindow, Monitor, Padding, Parent, Pointer,
|
||||
Rectangle, Row, Service, Shadow, Start, Text, Vertical, Weight, Widget, Workspace, Workspaces, Zone,
|
||||
Rectangle, Row, Service, Start, Text, Vertical, Weight, Widget, Workspace, Workspaces, Zone,
|
||||
};
|
||||
|
||||
use crate::island::{CHIP_HOVER, CLEAR, COMPACT_H, HOVER_MS, SHADOW, TOP_GAP, backdrop, faded};
|
||||
use crate::island::{CHIP_HOVER, CLEAR, COMPACT_H, HOVER_MS, SHADOW, TOP_GAP, faded};
|
||||
use crate::surface;
|
||||
use crate::theme;
|
||||
|
||||
// one round slot per workspace, inside the pill's inset; the pill sits LEFT px from the monitor's edge
|
||||
@@ -107,18 +108,9 @@ pub fn view(monitor: &Monitor) -> LayerWindow {
|
||||
));
|
||||
}
|
||||
|
||||
let theme = theme::now();
|
||||
let content = Rectangle::new().width(width).height(COMPACT_H).padding(INSET).align_child(Start, Center).child(Row::new(slots).gap(SLOT_GAP).align(Center));
|
||||
|
||||
let pill = Rectangle::new()
|
||||
.width(width)
|
||||
.height(COMPACT_H)
|
||||
.radius(Full)
|
||||
.fill(backdrop(&theme.surfaces(), 255))
|
||||
.border(1.0, theme.outline)
|
||||
.shadow(Shadow::drop(theme.accent).opacity(0.3).blur(22.0))
|
||||
.padding(INSET)
|
||||
.align_child(Start, Center)
|
||||
.child(Row::new(slots).gap(SLOT_GAP).align(Center));
|
||||
let pill = surface::shell(width, COMPACT_H, COMPACT_H / 2.0, &theme::now().surfaces(), 0.0, content);
|
||||
|
||||
// clicks next to the pill reach the windows underneath
|
||||
let region = InputArea {
|
||||
|
||||
Reference in New Issue
Block a user