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
+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));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user