mini player + dark glass: eight shapes run by the hidden main as the one brain, dissolving covers and accents, drag from anywhere, and the space between the panes turned to real acrylic - with the webview flags that keep rest frames honest
This commit is contained in:
1 parent
9b84b30aef
commit
dc7ea16b22
33 files changed
+2432
-5
No files matched your search
@@ -3,7 +3,8 @@
|
||||
"identifier": "default",
|
||||
"description": "enables the default permissions",
|
||||
"windows": [
|
||||
"main"
|
||||
"main",
|
||||
"mini"
|
||||
],
|
||||
"permissions": [
|
||||
"core:default",
|
||||
@@ -16,6 +17,8 @@
|
||||
"core:window:allow-is-maximized",
|
||||
"core:window:allow-show",
|
||||
"core:window:allow-set-focus",
|
||||
"core:window:allow-set-size",
|
||||
"core:window:allow-set-always-on-top",
|
||||
"dialog:allow-open"
|
||||
]
|
||||
}
|
||||
+112
-1
@@ -1,4 +1,4 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tauri::{Emitter, Manager, State};
|
||||
|
||||
use soulseek_client::{Client, Config, DownloadState, DownloadUpdate, Event};
|
||||
@@ -78,6 +78,8 @@ pub struct Settings {
|
||||
pub volume_normalization: bool,
|
||||
/// Word-by-word lyric fill — experimental, default off.
|
||||
pub word_timing: bool,
|
||||
/// Dark-glass acrylic backdrop, default off.
|
||||
pub glass_mode: bool,
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
@@ -117,6 +119,7 @@ pub async fn get_settings(
|
||||
download_source_prefs: cfg.download_source_prefs,
|
||||
volume_normalization: config::volume_normalization(&cfg),
|
||||
word_timing: config::word_timing(&cfg),
|
||||
glass_mode: config::glass_mode(&cfg),
|
||||
username: cfg.username,
|
||||
})
|
||||
}
|
||||
@@ -145,6 +148,7 @@ pub async fn set_settings(
|
||||
download_source_prefs: Option<config::SourcePrefs>,
|
||||
volume_normalization: Option<bool>,
|
||||
word_timing: Option<bool>,
|
||||
glass_mode: Option<bool>,
|
||||
) -> Result<(), String> {
|
||||
let mut cfg = config::load(&app);
|
||||
let library_folder_moved = download_dir
|
||||
@@ -216,6 +220,9 @@ pub async fn set_settings(
|
||||
if word_timing.is_some() {
|
||||
cfg.word_timing = word_timing;
|
||||
}
|
||||
if glass_mode.is_some() {
|
||||
cfg.glass_mode = glass_mode;
|
||||
}
|
||||
config::save(&app, &cfg);
|
||||
|
||||
// A new library folder is a new library. Re-base immediately rather than
|
||||
@@ -4857,6 +4864,108 @@ pub fn player_control(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Enter mini mode: show the pre-built mini window (declared in
|
||||
/// tauri.conf.json, created hidden at startup — runtime window creation
|
||||
/// from a command half-initialises on Windows) and hide the main one. The
|
||||
/// main window is HIDDEN, never closed: its frontend stays alive as the one
|
||||
/// brain the mini remote-controls (see src/lib/miniBridge.svelte.js).
|
||||
#[tauri::command]
|
||||
pub async fn mini_open(app: tauri::AppHandle, width: f64, height: f64) -> Result<(), String> {
|
||||
let mini = app
|
||||
.get_webview_window("mini")
|
||||
.ok_or_else(|| "mini window missing from config".to_string())?;
|
||||
let _ = mini.set_size(tauri::LogicalSize::new(width, height));
|
||||
mini.show().map_err(|e| e.to_string())?;
|
||||
let _ = mini.set_focus();
|
||||
if let Some(main) = app.get_webview_window("main") {
|
||||
let _ = main.hide();
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Leave mini mode: bring the full app back and tuck the mini away warm
|
||||
/// (hidden, not closed — reopening is instant and its mirror stays hot).
|
||||
#[tauri::command]
|
||||
pub async fn mini_close(app: tauri::AppHandle) -> Result<(), String> {
|
||||
if let Some(main) = app.get_webview_window("main") {
|
||||
let _ = main.show();
|
||||
let _ = main.set_focus();
|
||||
}
|
||||
if let Some(mini) = app.get_webview_window("mini") {
|
||||
let _ = mini.hide();
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// The OS half of dark-glass mode: acrylic blur behind the calling window
|
||||
/// (the window itself is transparent; the CSS half — :root.glass — thins the
|
||||
/// window canvas so the blur shows through).
|
||||
///
|
||||
/// NOT tauri's Effect::Acrylic: on Win11 22H2+ that picks the DWMSBT system
|
||||
/// backdrop, which Windows SUSPENDS whenever the maximized webview gets
|
||||
/// promoted to a hardware overlay — every hover animation or compositor
|
||||
/// scroll blinked the glass dark for a beat (Kyle, 2026-08-26). The legacy
|
||||
/// accent-policy acrylic below is composed with the window itself and holds
|
||||
/// through interaction.
|
||||
#[tauri::command]
|
||||
pub fn window_glass(window: tauri::WebviewWindow, on: bool) -> Result<(), String> {
|
||||
// Clear any system backdrop a previous toggle/build may have applied.
|
||||
let _ = window.set_effects(None::<tauri::utils::config::WindowEffectsConfig>);
|
||||
let hwnd = window.hwnd().map_err(|e| e.to_string())?.0 as *mut std::ffi::c_void;
|
||||
|
||||
#[repr(C)]
|
||||
struct AccentPolicy {
|
||||
state: u32,
|
||||
flags: u32,
|
||||
gradient: u32, // 0xAABBGGRR
|
||||
animation: u32,
|
||||
}
|
||||
#[repr(C)]
|
||||
struct AttribData {
|
||||
attrib: u32,
|
||||
data: *mut std::ffi::c_void,
|
||||
size: usize,
|
||||
}
|
||||
#[allow(non_snake_case)]
|
||||
#[link(name = "kernel32")]
|
||||
extern "system" {
|
||||
fn GetModuleHandleA(name: *const u8) -> *mut std::ffi::c_void;
|
||||
fn GetProcAddress(
|
||||
module: *mut std::ffi::c_void,
|
||||
name: *const u8,
|
||||
) -> *mut std::ffi::c_void;
|
||||
}
|
||||
type Swca = unsafe extern "system" fn(*mut std::ffi::c_void, *mut AttribData) -> i32;
|
||||
|
||||
unsafe {
|
||||
let user32 = GetModuleHandleA(b"user32.dll\0".as_ptr());
|
||||
let f = if user32.is_null() {
|
||||
std::ptr::null_mut()
|
||||
} else {
|
||||
GetProcAddress(user32, b"SetWindowCompositionAttribute\0".as_ptr())
|
||||
};
|
||||
if f.is_null() {
|
||||
return Err("SetWindowCompositionAttribute unavailable".into());
|
||||
}
|
||||
let f: Swca = std::mem::transmute(f);
|
||||
let mut policy = AccentPolicy {
|
||||
state: if on { 4 } else { 0 }, // ACCENT_ENABLE_ACRYLICBLURBEHIND / ACCENT_DISABLED
|
||||
flags: 0,
|
||||
// A whisper of black in the OS layer (acrylic treats alpha 0 as
|
||||
// "don't draw"); the real darkening is the CSS canvas tint.
|
||||
gradient: if on { 0x2E00_0000 } else { 0 },
|
||||
animation: 0,
|
||||
};
|
||||
let mut data = AttribData {
|
||||
attrib: 0x13, // WCA_ACCENT_POLICY
|
||||
data: &mut policy as *mut _ as _,
|
||||
size: std::mem::size_of::<AccentPolicy>(),
|
||||
};
|
||||
f(hwnd, &mut data);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod hero_processing_tests {
|
||||
use super::*;
|
||||
@@ -4937,3 +5046,5 @@ mod hero_processing_tests {
|
||||
assert_eq!(trimmed.dimensions(), (256, 120));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -140,6 +140,10 @@ pub struct AppConfig {
|
||||
#[serde(default)]
|
||||
pub volume_normalization: Option<bool>,
|
||||
pub word_timing: Option<bool>,
|
||||
/// Dark-glass mode: acrylic blur behind a transparent window, with the
|
||||
/// base surfaces thinned in CSS so the desktop glows through. None → false.
|
||||
#[serde(default)]
|
||||
pub glass_mode: Option<bool>,
|
||||
}
|
||||
|
||||
/// Default longest edge for embedded covers when the user hasn't chosen.
|
||||
@@ -185,6 +189,10 @@ pub fn volume_normalization(cfg: &AppConfig) -> bool {
|
||||
/// default: generated word spans still drift on dense or echo-heavy mixes,
|
||||
/// and the solid line-level sync must be what users live with until the
|
||||
/// feature earns its keep (Kyle, 2026-08-19).
|
||||
pub fn glass_mode(cfg: &AppConfig) -> bool {
|
||||
cfg.glass_mode.unwrap_or(false)
|
||||
}
|
||||
|
||||
pub fn word_timing(cfg: &AppConfig) -> bool {
|
||||
cfg.word_timing.unwrap_or(false)
|
||||
}
|
||||
|
||||
@@ -27,6 +27,39 @@ mod ytdlp;
|
||||
|
||||
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||
pub fn run() {
|
||||
// Two WebView2 presentation fixes, both delivered by env var because
|
||||
// tauri.conf's additionalBrowserArgs never reaches the browser process
|
||||
// (verified via its command line). Merge, don't clobber — dev launches
|
||||
// carry the CDP port here. Both found 2026-08-26 via the dark-glass work:
|
||||
//
|
||||
// 1. NO delegated compositing: with it, DWM composes the webview's layers
|
||||
// at rest and blends semi-transparent darks in the wrong colorspace —
|
||||
// the app rested washed-out LIGHT and looked correct only while an
|
||||
// animation forced Chromium to compose in-process.
|
||||
// 2. Force the sRGB color profile: on an HDR display the MAXIMIZED
|
||||
// window's rest path takes a different SDR-white tone mapping than
|
||||
// the animated path — covers blew out near-white at rest and snapped
|
||||
// correct on hover. One profile, one mapping, both paths agree.
|
||||
#[cfg(windows)]
|
||||
{
|
||||
const KEY: &str = "WEBVIEW2_ADDITIONAL_BROWSER_ARGUMENTS";
|
||||
let mut args = std::env::var(KEY).unwrap_or_default();
|
||||
let mut ensure = |flag: &str, key: &str| {
|
||||
if !args.contains(key) {
|
||||
if !args.is_empty() {
|
||||
args.push(' ');
|
||||
}
|
||||
args.push_str(flag);
|
||||
}
|
||||
};
|
||||
ensure(
|
||||
"--disable-features=msWebOOUI,msPdfOOUI,msSmartScreenProtection,DelegatedCompositing",
|
||||
"DelegatedCompositing",
|
||||
);
|
||||
ensure("--force-color-profile=srgb", "force-color-profile");
|
||||
std::env::set_var(KEY, args);
|
||||
}
|
||||
|
||||
tauri::Builder::default()
|
||||
.manage(state::AppState::default())
|
||||
.setup(|app| {
|
||||
@@ -108,6 +141,9 @@ pub fn run() {
|
||||
commands::unfollow_artist,
|
||||
commands::home_artist_news,
|
||||
commands::home_warm,
|
||||
commands::mini_open,
|
||||
commands::mini_close,
|
||||
commands::window_glass,
|
||||
playlists::list_playlists,
|
||||
playlists::create_playlist,
|
||||
playlists::rename_playlist,
|
||||
|
||||
@@ -19,8 +19,23 @@
|
||||
"height": 820,
|
||||
"minWidth": 960,
|
||||
"minHeight": 600,
|
||||
"backgroundColor": "#121212",
|
||||
"transparent": true,
|
||||
"decorations": false
|
||||
},
|
||||
{
|
||||
"label": "mini",
|
||||
"title": "SoulStream Mini",
|
||||
"url": "mini.html",
|
||||
"width": 484,
|
||||
"height": 96,
|
||||
"visible": false,
|
||||
"decorations": false,
|
||||
"resizable": false,
|
||||
"maximizable": false,
|
||||
"minimizable": false,
|
||||
"alwaysOnTop": true,
|
||||
"shadow": true,
|
||||
"backgroundColor": "#181818"
|
||||
}
|
||||
],
|
||||
"security": {
|
||||
|
||||
Reference in new issue
Block a user