diff --git a/Cargo.lock b/Cargo.lock index 4725ed1..150e953 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -221,6 +221,7 @@ dependencies = [ "bindgen", "bytes", "chrono", + "color-thief", "event-listener", "futures", "gettext-rs", @@ -334,6 +335,12 @@ version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" +[[package]] +name = "bytemuck" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8334215b81e418a0a7bdb8ef0849474f40bb10c8b71f1c4ed315cff49f32494d" + [[package]] name = "byteorder" version = "1.5.0" @@ -455,6 +462,15 @@ dependencies = [ "libloading", ] +[[package]] +name = "color-thief" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6460d760cf38ce67c9e0318f896538820acc54f2d0a3bfc5b2c557211066c98" +dependencies = [ + "rgb", +] + [[package]] name = "concurrent-queue" version = "2.5.0" @@ -2081,6 +2097,15 @@ dependencies = [ "windows-registry", ] +[[package]] +name = "rgb" +version = "0.8.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57397d16646700483b67d2dd6511d79318f9d057fdbd21a4066aeac8b41d310a" +dependencies = [ + "bytemuck", +] + [[package]] name = "ring" version = "0.17.8" diff --git a/Cargo.toml b/Cargo.toml index ccdb9ec..999de5e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,6 +8,7 @@ adw = { version = "0.7.0", package = "libadwaita", features = ["v1_6"] } async-channel = "2.3.1" bytes = "1.8.0" chrono = { version = "0.4.38", features = ["serde"] } +color-thief = "0.2.2" event-listener = "5.3.1" futures = "0.3.31" gettext-rs = { version = "0.7.2", features = ["gettext-system"] } diff --git a/resources/style.css b/resources/style.css index a81ecfb..3877351 100644 --- a/resources/style.css +++ b/resources/style.css @@ -1,3 +1,13 @@ +window.background > * { + /* from amberol */ + background: linear-gradient(127deg, color-mix(in srgb, var(--background-color-0) 55%, transparent), color-mix(in srgb, var(--background-color-0) 0%, transparent) 70.71%), + linear-gradient(217deg, color-mix(in srgb, var(--background-color-1) 55%, transparent), color-mix(in srgb, var(--background-color-1) 0%, transparent) 70.71%), + linear-gradient(336deg, color-mix(in srgb, var(--background-color-2) 55%, transparent), color-mix(in srgb, var(--background-color-2) 0%, transparent) 70.71%); + transition-property: background; + transition-duration: 250ms; + transition-timing-function: ease; +} + #seek-scale slider { margin: 0px; opacity: 0%; diff --git a/src/ui/window.rs b/src/ui/window.rs index a1f9d76..27e94e8 100644 --- a/src/ui/window.rs +++ b/src/ui/window.rs @@ -87,6 +87,8 @@ mod imp { #[property(get, set)] refreshing_albums: Cell, + + css_provider: gtk::CssProvider, } impl Default for Window { @@ -146,6 +148,8 @@ mod imp { mpris_player: Default::default(), refreshing_albums: Cell::new(true), + + css_provider: gtk::CssProvider::new(), } } } @@ -171,6 +175,10 @@ mod imp { fn constructed(&self) { self.parent_constructed(); + if let Some(display) = gdk::Display::default() { + gtk::style_context_add_provider_for_display(&display, &self.css_provider, 420); + } + use gio::ActionEntry; let action_seek_backward = ActionEntry::builder("seek-backward") @@ -671,32 +679,41 @@ mod imp { match window.song() { Some(song) if song.id() == song_id => { - let texture = gdk::Texture::from_bytes(&glib::Bytes::from_owned(bytes)) - .expect("could not create texture from cover art for {song_id}"); + let loader = gtk::gdk_pixbuf::PixbufLoader::new(); // TODO: pass mime type? + loader.write(&bytes).unwrap(); + loader.close().unwrap(); + let pixbuf = loader.pixbuf().unwrap(); + + let texture = gdk::Texture::for_pixbuf(&pixbuf); window.set_playing_cover_art(Some(&texture.clone().into())); - // from g4music - let snapshot = gtk::Snapshot::new(); - snapshot.push_blur(512.0 * 0.1); - snapshot.push_opacity(0.5); - texture.snapshot(&snapshot, 512.0, 512.0); - snapshot.pop(); - snapshot.pop(); + // from amberol + let palette = color_thief::get_palette( + pixbuf.pixel_bytes().unwrap().as_ref(), + if pixbuf.has_alpha() { + color_thief::ColorFormat::Rgba + } else { + color_thief::ColorFormat::Rgb + }, + 5, + 4, + ) + .unwrap(); - use gtk::graphene; - let rect = graphene::Rect::new(0.0, 0.0, 512.0, 512.0); - if let Some(node) = snapshot.clone().to_node() { - window.set_background(Some( - window - .native() - .unwrap() - .renderer() - .unwrap() - .render_texture(node, Some(&rect)), - )); - } else { - todo!(); + let mut css = String::new(); + css.push_str(":root {"); + let n_colors = palette.len(); + for (i, color) in palette.into_iter().enumerate() { + css.push_str(&format!("--background-color-{i}: {color}; ")); } + for i in n_colors..3 { + css.push_str(&format!( + "--background-color-{i}: var(--window-bg-color); " + )); + } + css.push_str("}"); + + window.imp().css_provider.load_from_string(&css); } _ => { event!( @@ -762,6 +779,7 @@ mod imp { match self.state.get() { State::Active => {} State::FileLoading => {} + State::FileLoaded => {} State::Seeking => {} other => panic!("invalid state transition: EndFile from {other:?}"),