fix(library): proper All list rows, NVIDIA seam, close-to-tray crash, Go-to-album
- Rework the "All" list view to the aligned column layout of the mixtape detail list: artwork + hover play, title + clickable-artist subtitle, a TYPE column, a dedicated SOURCE column (SourceGlyph), the row QualityBadgeFull in a Quality column (was the uneven grid badge), favorite/follow indicator, and the per-kind context menu — no longer bare and out of tune. - Fix the NVIDIA "wallpaper join" background seam: replace the float hash2 in ambient.wgsl + plasma.wgsl with a bit-exact integer (u32) lattice hash so the same cell corner hashes identically from adjacent cells on NVIDIA. - Fix the close-to-tray "RefCell already mutably borrowed" panic introduced by the teardown texture-clear: defer the set via upgrade_in_event_loop so it runs after the window adapter releases its borrow. - Add "Go to album" to the TrackCard context menu and the list-row menu (adds album-id to the feed model). cc
This commit is contained in:
7 files changed
+194
-82
No files matched your search
@@ -202,8 +202,8 @@ export component TrackCard inherits Rectangle {
|
||||
x: 26px;
|
||||
y: 56px;
|
||||
width: 196px;
|
||||
// Open-artist? Play / Play-next / Queue + favorite = 4-5 items.
|
||||
height: 5 * 33px + 10px;
|
||||
// Play / Play-next / Queue / Go-to-artist / Go-to-album / favorite.
|
||||
height: 6 * 33px + 10px;
|
||||
ContextMenu {
|
||||
ContextMenuItem {
|
||||
icon: @image-url("../assets/icons/play.svg");
|
||||
@@ -233,6 +233,13 @@ export component TrackCard inherits Rectangle {
|
||||
root.media-action("artist", root.track.artist-id, "open");
|
||||
}
|
||||
}
|
||||
if root.track.album-id != "": ContextMenuItem {
|
||||
icon: @image-url("../assets/icons/disc.svg");
|
||||
label: @tr("Go to album");
|
||||
clicked => {
|
||||
root.media-action("album", root.track.album-id, "open");
|
||||
}
|
||||
}
|
||||
if root.show-favorite: ContextMenuItem {
|
||||
icon: root.track.is-favorite
|
||||
? @image-url("../assets/icons/heart-filled.svg")
|
||||
|
||||
@@ -40,6 +40,8 @@ import { ToggleButton } from "../primitives/ToggleButton.slint";
|
||||
import { QbzSelect } from "../primitives/QbzSelect.slint";
|
||||
import { QbzIcon } from "../primitives/QbzIcon.slint";
|
||||
import { QualityBadge } from "../primitives/QualityBadge.slint";
|
||||
import { QualityBadgeFull } from "../primitives/QualityBadgeFull.slint";
|
||||
import { SourceGlyph } from "../primitives/SourceGlyph.slint";
|
||||
import { ContextMenu } from "../primitives/ContextMenu.slint";
|
||||
import { ContextMenuItem } from "../primitives/ContextMenuItem.slint";
|
||||
import { LoadingSpinner } from "../primitives/LoadingSpinner.slint";
|
||||
@@ -966,6 +968,7 @@ export component FavoritesView inherits Rectangle {
|
||||
artist: item.artist,
|
||||
artist-id: item.artist-id,
|
||||
album: item.album,
|
||||
album-id: item.album-id,
|
||||
artwork: item.image,
|
||||
quality-tier: item.quality-tier,
|
||||
quality-detail: item.quality-detail,
|
||||
@@ -975,6 +978,8 @@ export component FavoritesView inherits Rectangle {
|
||||
media-action(kind, id, action) => {
|
||||
if (kind == "artist") {
|
||||
FavoritesActions.open-artist(id);
|
||||
} else if (kind == "album") {
|
||||
FavoritesActions.open-album(id);
|
||||
} else {
|
||||
FavoritesActions.track-action(id, action);
|
||||
}
|
||||
@@ -1080,10 +1085,10 @@ export component FavoritesView inherits Rectangle {
|
||||
}
|
||||
}
|
||||
HorizontalLayout {
|
||||
padding-left: 8px;
|
||||
padding-right: 8px;
|
||||
padding-left: 12px;
|
||||
padding-right: 12px;
|
||||
spacing: 12px;
|
||||
// Cover (round for artists, square otherwise).
|
||||
// Col — artwork (round for artists) + hover play.
|
||||
VerticalLayout {
|
||||
alignment: center;
|
||||
Rectangle {
|
||||
@@ -1098,65 +1103,128 @@ export component FavoritesView inherits Rectangle {
|
||||
source: item.image;
|
||||
image-fit: item.kind == "label" ? contain : cover;
|
||||
}
|
||||
if item.kind == "track" || item.kind == "album" || item.kind == "playlist": Rectangle {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: #000000a6;
|
||||
opacity: play-ta.has-hover ? 1.0 : 0.0;
|
||||
animate opacity { duration: 150ms; }
|
||||
QbzIcon {
|
||||
source: @image-url("../assets/icons/play-fill.svg");
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
x: Math.round((parent.width - self.width) / 2 / 1px) * 1px;
|
||||
y: Math.round((parent.height - self.height) / 2 / 1px) * 1px;
|
||||
tint: #ffffff;
|
||||
}
|
||||
play-ta := TouchArea {
|
||||
mouse-cursor: pointer;
|
||||
clicked => {
|
||||
if (item.kind == "track") { FavoritesActions.track-action(item.id, "play"); }
|
||||
else if (item.kind == "album") { FavoritesActions.album-action(item.id, "play"); }
|
||||
else { FavoritesActions.playlist-action(item.id, "play"); }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Title + type-aware subtitle.
|
||||
// Col — title + subtitle (artist link for track/album).
|
||||
VerticalLayout {
|
||||
alignment: center;
|
||||
horizontal-stretch: 1;
|
||||
spacing: 2px;
|
||||
Text {
|
||||
text: item.title;
|
||||
color: Theme.text-primary;
|
||||
font-size: Typography.body;
|
||||
font-weight: Typography.medium;
|
||||
overflow: elide;
|
||||
title-ta := TouchArea {
|
||||
height: 18px;
|
||||
mouse-cursor: pointer;
|
||||
clicked => {
|
||||
if (item.kind == "track") { FavoritesActions.track-action(item.id, "play"); }
|
||||
else if (item.kind == "album") { FavoritesActions.open-album(item.id); }
|
||||
else if (item.kind == "artist") { FavoritesActions.open-artist(item.id); }
|
||||
else if (item.kind == "playlist") { FavoritesActions.open-playlist(item.id); }
|
||||
else { FavoritesActions.open-label(item.id, item.title); }
|
||||
}
|
||||
Text {
|
||||
width: 100%;
|
||||
text: item.title;
|
||||
color: title-ta.has-hover ? Theme.accent : Theme.text-primary;
|
||||
font-size: 14px;
|
||||
font-weight: Typography.medium;
|
||||
overflow: elide;
|
||||
vertical-alignment: center;
|
||||
}
|
||||
}
|
||||
Text {
|
||||
text: item.kind == "artist" ? @tr("Artist")
|
||||
: item.kind == "album"
|
||||
? (item.artist != "" ? @tr("Album") + " • " + item.artist : @tr("Album"))
|
||||
: item.kind == "track"
|
||||
? (item.artist != "" ? @tr("Track") + " • " + item.artist : @tr("Track"))
|
||||
: item.kind == "playlist"
|
||||
? (item.subtitle != "" ? @tr("Playlist") + " • " + item.subtitle : @tr("Playlist"))
|
||||
: (item.subtitle != "" ? @tr("Label") + " • " + item.subtitle : @tr("Label"));
|
||||
color: Theme.text-muted;
|
||||
font-size: Typography.legal;
|
||||
overflow: elide;
|
||||
}
|
||||
}
|
||||
// Quality badge (albums + tracks).
|
||||
if item.kind == "album" || item.kind == "track": VerticalLayout {
|
||||
alignment: center;
|
||||
QualityBadge {
|
||||
tier: item.quality-tier;
|
||||
label: item.quality-detail;
|
||||
detail: item.quality-detail;
|
||||
}
|
||||
}
|
||||
// Source badge (local / Plex).
|
||||
if item.source == "local" || item.source == "plex": VerticalLayout {
|
||||
alignment: center;
|
||||
Rectangle {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border-radius: 4px;
|
||||
background: #000000b3;
|
||||
Image {
|
||||
source: item.source == "plex"
|
||||
? @image-url("../assets/icons/plex-logo.svg")
|
||||
: @image-url("../assets/icons/hard-drive.svg");
|
||||
colorize: item.source == "local" ? #ffffff : transparent;
|
||||
width: item.source == "plex" ? 15px : 14px;
|
||||
height: self.width;
|
||||
x: Math.round((parent.width - self.width) / 2 / 1px) * 1px;
|
||||
y: Math.round((parent.height - self.height) / 2 / 1px) * 1px;
|
||||
image-fit: contain;
|
||||
if item.subtitle != "": sub-ta := TouchArea {
|
||||
height: 16px;
|
||||
mouse-cursor: (item.artist-id != "" && (item.kind == "track" || item.kind == "album")) ? pointer : default;
|
||||
clicked => {
|
||||
if (item.artist-id != "" && (item.kind == "track" || item.kind == "album")) {
|
||||
FavoritesActions.open-artist(item.artist-id);
|
||||
}
|
||||
}
|
||||
Text {
|
||||
width: 100%;
|
||||
text: item.subtitle;
|
||||
color: (item.artist-id != "" && (item.kind == "track" || item.kind == "album") && sub-ta.has-hover)
|
||||
? Theme.accent : Theme.text-muted;
|
||||
font-size: 12px;
|
||||
overflow: elide;
|
||||
vertical-alignment: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Favorite (heart) / follow (check) indicator.
|
||||
// Col — type (icon + label).
|
||||
Rectangle {
|
||||
width: 116px;
|
||||
HorizontalLayout {
|
||||
spacing: 6px;
|
||||
alignment: start;
|
||||
VerticalLayout {
|
||||
alignment: center;
|
||||
QbzIcon {
|
||||
source: item.kind == "track" ? @image-url("../assets/icons/music.svg")
|
||||
: item.kind == "playlist" ? @image-url("../assets/icons/list-music.svg")
|
||||
: item.kind == "artist" ? @image-url("../assets/icons/user.svg")
|
||||
: item.kind == "label" ? @image-url("../assets/icons/disc-3.svg")
|
||||
: @image-url("../assets/icons/disc.svg");
|
||||
width: 13px;
|
||||
height: 13px;
|
||||
tint: Theme.text-muted;
|
||||
}
|
||||
}
|
||||
Text {
|
||||
text: item.kind == "track" ? @tr("Track")
|
||||
: item.kind == "album" ? @tr("Album")
|
||||
: item.kind == "artist" ? @tr("Artist")
|
||||
: item.kind == "playlist" ? @tr("Playlist")
|
||||
: @tr("Label");
|
||||
color: Theme.text-muted;
|
||||
font-size: 10px;
|
||||
font-weight: Typography.semibold;
|
||||
letter-spacing: 1.2px;
|
||||
vertical-alignment: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Col — source glyph (its own column).
|
||||
Rectangle {
|
||||
width: 44px;
|
||||
if item.source != "": SourceGlyph {
|
||||
kind: item.source;
|
||||
x: 0;
|
||||
y: Math.round((parent.height - self.height) / 2 / 1px) * 1px;
|
||||
}
|
||||
}
|
||||
// Col — quality (albums + tracks; the row badge).
|
||||
Rectangle {
|
||||
width: 150px;
|
||||
if (item.kind == "album" || item.kind == "track") && item.quality-tier != "": QualityBadgeFull {
|
||||
tier: item.quality-tier;
|
||||
detail: item.quality-detail;
|
||||
x: 0;
|
||||
y: Math.round((parent.height - self.height) / 2 / 1px) * 1px;
|
||||
}
|
||||
}
|
||||
// Col — favorite (heart) / follow (person) indicator.
|
||||
VerticalLayout {
|
||||
alignment: center;
|
||||
QbzIcon {
|
||||
@@ -1170,7 +1238,7 @@ export component FavoritesView inherits Rectangle {
|
||||
tint: Theme.accent;
|
||||
}
|
||||
}
|
||||
// Context-menu button.
|
||||
// Col — context-menu button.
|
||||
VerticalLayout {
|
||||
alignment: center;
|
||||
menu-btn := Rectangle {
|
||||
@@ -1245,6 +1313,11 @@ export component FavoritesView inherits Rectangle {
|
||||
label: @tr("Go to artist");
|
||||
clicked => { FavoritesActions.open-artist(item.artist-id); }
|
||||
}
|
||||
if item.kind == "track" && item.album-id != "": ContextMenuItem {
|
||||
icon: @image-url("../assets/icons/disc.svg");
|
||||
label: @tr("Go to album");
|
||||
clicked => { FavoritesActions.open-album(item.album-id); }
|
||||
}
|
||||
if item.kind == "artist": ContextMenuItem {
|
||||
icon: @image-url("../assets/icons/user.svg");
|
||||
label: @tr("Go to artist");
|
||||
|
||||
@@ -52,15 +52,20 @@ fn vs_main(@builtin(vertex_index) vid: u32) -> VsOut {
|
||||
// Cheap smooth value noise (hash + bilinear), a couple of octaves. No loops over
|
||||
// large ranges — this is a background that must stay near-free on an integrated
|
||||
// GPU.
|
||||
// Sin-FREE hash (Dave Hoskins). The old `fract(sin(dot(p,k))*43758)` breaks on
|
||||
// some GPUs (NVIDIA): as the noise coords grow with time, sin's argument gets
|
||||
// huge and the driver's range-reduction precision diverges from Intel's,
|
||||
// degenerating the noise into big blocks. This integer-style fract hash is
|
||||
// stable across GPUs regardless of coordinate magnitude.
|
||||
// INTEGER lattice hash (bit-exact). vnoise only ever hashes floor() outputs, so
|
||||
// the i32 conversion is exact. Float hashes (the old sin-based one AND the
|
||||
// fract-based Hoskins one) leave the result to f32 rounding: NVIDIA may
|
||||
// contract/round the same expression differently at different inline sites, so
|
||||
// the SAME lattice corner hashed from two adjacent cells can disagree (worst
|
||||
// case fract() flips by ~1.0), drawing the cell border as a straight "wallpaper
|
||||
// join" seam through the warp field. u32 arithmetic has no rounding: same input,
|
||||
// same output, at every call site, on every GPU, at any coordinate magnitude.
|
||||
fn hash2(p: vec2<f32>) -> f32 {
|
||||
var p3 = fract(p.xyx * 0.1031);
|
||||
p3 = p3 + dot(p3, p3.yzx + 33.33);
|
||||
return fract((p3.x + p3.y) * p3.z);
|
||||
var h = bitcast<u32>(i32(p.x)) * 0x8da6b343u + bitcast<u32>(i32(p.y)) * 0xd8163841u;
|
||||
h = (h ^ (h >> 15u)) * 0x2c1b3c6du;
|
||||
h = (h ^ (h >> 12u)) * 0x297a2d39u;
|
||||
h = h ^ (h >> 15u);
|
||||
return f32(h >> 8u) * (1.0 / 16777216.0);
|
||||
}
|
||||
|
||||
fn vnoise(p: vec2<f32>) -> f32 {
|
||||
|
||||
@@ -52,14 +52,18 @@ fn vs_main(@builtin(vertex_index) vid: u32) -> VsOut {
|
||||
return out;
|
||||
}
|
||||
|
||||
// Sin-FREE hash (Dave Hoskins). The old `fract(sin(dot(p,k))*43758)` breaks on
|
||||
// NVIDIA: as the noise coords grow with time, sin's argument gets huge and the
|
||||
// driver's range-reduction precision diverges from Intel's, degenerating the
|
||||
// noise into big blocks. This integer-style fract hash is stable across GPUs.
|
||||
// INTEGER lattice hash (bit-exact) — vnoise only hashes floor() outputs, so the
|
||||
// i32 conversion is exact. A float hash leaves the result to f32 rounding, which
|
||||
// NVIDIA can contract/round differently per inline call site, so the SAME
|
||||
// lattice corner hashed from two adjacent cells disagrees and draws the cell
|
||||
// border as a straight "wallpaper join" seam. u32 arithmetic is bit-exact: same
|
||||
// input, same output, every call site, every GPU, any coordinate magnitude.
|
||||
fn hash2(p: vec2<f32>) -> f32 {
|
||||
var p3 = fract(p.xyx * 0.1031);
|
||||
p3 = p3 + dot(p3, p3.yzx + 33.33);
|
||||
return fract((p3.x + p3.y) * p3.z);
|
||||
var h = bitcast<u32>(i32(p.x)) * 0x8da6b343u + bitcast<u32>(i32(p.y)) * 0xd8163841u;
|
||||
h = (h ^ (h >> 15u)) * 0x2c1b3c6du;
|
||||
h = (h ^ (h >> 12u)) * 0x297a2d39u;
|
||||
h = h ^ (h >> 15u);
|
||||
return f32(h >> 8u) * (1.0 / 16777216.0);
|
||||
}
|
||||
|
||||
fn vnoise(p: vec2<f32>) -> f32 {
|
||||
|
||||
@@ -4978,6 +4978,7 @@ export struct LibraryFeedItem {
|
||||
artist: string,
|
||||
artist-id: string,
|
||||
album: string,
|
||||
album-id: string, // for "Go to album" (tracks)
|
||||
image-url: string,
|
||||
image: image, // decoded cover (empty until the pipeline resolves it)
|
||||
quality-tier: string, // "hires" | "cd" | ""
|
||||
|
||||
@@ -31,6 +31,7 @@ pub struct Feed {
|
||||
pub artist: String,
|
||||
pub artist_id: String,
|
||||
pub album: String,
|
||||
pub album_id: String,
|
||||
pub image_url: String,
|
||||
pub quality_tier: String,
|
||||
pub quality_detail: String,
|
||||
@@ -69,6 +70,7 @@ pub async fn load_library_all(runtime: &Runtime) -> Result<Vec<Feed>, String> {
|
||||
artist: t.artist,
|
||||
artist_id: t.artist_id,
|
||||
album: t.album,
|
||||
album_id: t.album_id,
|
||||
image_url: t.artwork_url,
|
||||
quality_tier: t.quality_tier,
|
||||
quality_detail: t.quality_detail,
|
||||
@@ -92,6 +94,7 @@ pub async fn load_library_all(runtime: &Runtime) -> Result<Vec<Feed>, String> {
|
||||
artist: a.artist,
|
||||
artist_id: a.artist_id,
|
||||
album: String::new(),
|
||||
album_id: String::new(),
|
||||
image_url: a.artwork_url,
|
||||
quality_tier: a.quality_tier,
|
||||
quality_detail: a.quality_detail,
|
||||
@@ -117,6 +120,7 @@ pub async fn load_library_all(runtime: &Runtime) -> Result<Vec<Feed>, String> {
|
||||
artist: String::new(),
|
||||
artist_id: ar.id.clone(),
|
||||
album: String::new(),
|
||||
album_id: String::new(),
|
||||
image_url: ar.image_url,
|
||||
quality_tier: String::new(),
|
||||
quality_detail: String::new(),
|
||||
@@ -140,6 +144,7 @@ pub async fn load_library_all(runtime: &Runtime) -> Result<Vec<Feed>, String> {
|
||||
artist: String::new(),
|
||||
artist_id: String::new(),
|
||||
album: String::new(),
|
||||
album_id: String::new(),
|
||||
image_url: l.image_url,
|
||||
quality_tier: String::new(),
|
||||
quality_detail: String::new(),
|
||||
@@ -168,6 +173,7 @@ pub async fn load_library_all(runtime: &Runtime) -> Result<Vec<Feed>, String> {
|
||||
artist: String::new(),
|
||||
artist_id: String::new(),
|
||||
album: String::new(),
|
||||
album_id: String::new(),
|
||||
image_url,
|
||||
quality_tier: String::new(),
|
||||
quality_detail: String::new(),
|
||||
@@ -188,6 +194,7 @@ pub async fn load_library_all(runtime: &Runtime) -> Result<Vec<Feed>, String> {
|
||||
artist: String::new(),
|
||||
artist_id: String::new(),
|
||||
album: String::new(),
|
||||
album_id: String::new(),
|
||||
image_url,
|
||||
quality_tier: String::new(),
|
||||
quality_detail: String::new(),
|
||||
@@ -214,6 +221,7 @@ pub async fn load_library_all(runtime: &Runtime) -> Result<Vec<Feed>, String> {
|
||||
artist: a.artist.name,
|
||||
artist_id: a.artist.id.to_string(),
|
||||
album: String::new(),
|
||||
album_id: String::new(),
|
||||
image_url,
|
||||
quality_tier: tier.into(),
|
||||
quality_detail: String::new(),
|
||||
@@ -225,14 +233,20 @@ pub async fn load_library_all(runtime: &Runtime) -> Result<Vec<Feed>, String> {
|
||||
}
|
||||
let n = tracks.len();
|
||||
for (i, t) in tracks.into_iter().enumerate() {
|
||||
let (artist, image_url, album) = {
|
||||
let (artist, image_url, album, album_id) = {
|
||||
let artist = t.performer.name.clone();
|
||||
let (img, alb) = t
|
||||
let (img, alb, aid) = t
|
||||
.album
|
||||
.as_ref()
|
||||
.map(|a| (a.image.best().cloned().unwrap_or_default(), a.title.clone()))
|
||||
.map(|a| {
|
||||
(
|
||||
a.image.best().cloned().unwrap_or_default(),
|
||||
a.title.clone(),
|
||||
a.id.clone(),
|
||||
)
|
||||
})
|
||||
.unwrap_or_default();
|
||||
(artist, img, alb)
|
||||
(artist, img, alb, aid)
|
||||
};
|
||||
let tier = if t.hires { "hires" } else { "cd" };
|
||||
feed.push(Feed {
|
||||
@@ -243,6 +257,7 @@ pub async fn load_library_all(runtime: &Runtime) -> Result<Vec<Feed>, String> {
|
||||
artist,
|
||||
artist_id: t.performer.id.to_string(),
|
||||
album,
|
||||
album_id,
|
||||
image_url,
|
||||
quality_tier: tier.into(),
|
||||
quality_detail: String::new(),
|
||||
@@ -268,6 +283,7 @@ pub async fn load_library_all(runtime: &Runtime) -> Result<Vec<Feed>, String> {
|
||||
artist: lf.artist.clone(),
|
||||
artist_id: String::new(),
|
||||
album: String::new(),
|
||||
album_id: String::new(),
|
||||
image_url: lf.artwork_url,
|
||||
quality_tier: String::new(),
|
||||
quality_detail: String::new(),
|
||||
@@ -299,6 +315,7 @@ fn to_item(f: &Feed) -> LibraryFeedItem {
|
||||
artist: f.artist.clone().into(),
|
||||
artist_id: f.artist_id.clone().into(),
|
||||
album: f.album.clone().into(),
|
||||
album_id: f.album_id.clone().into(),
|
||||
image_url: f.image_url.clone().into(),
|
||||
image: slint::Image::default(),
|
||||
quality_tier: f.quality_tier.clone().into(),
|
||||
|
||||
+13
-8
@@ -7858,16 +7858,21 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
crate::shader_underlay::teardown();
|
||||
// The Image in ImmersiveState.shader-texture wraps a wgpu
|
||||
// texture from the instance/device being destroyed. The
|
||||
// property survives a Wayland hide (tray restore); the
|
||||
// first frame after the next RenderingSetup would hand that
|
||||
// stale texture to the NEW femtovg canvas — a cross-instance
|
||||
// id lookup that panics in wgpu-core ("TextureView … is no
|
||||
// longer alive"). Clear it now; the 30 fps drain repopulates
|
||||
// it on the new device within a tick.
|
||||
if let Some(w) = weak_for_underlay.upgrade() {
|
||||
// property survives a Wayland hide (tray); the first frame
|
||||
// after the next RenderingSetup would hand that stale
|
||||
// texture to the NEW femtovg canvas — a cross-instance id
|
||||
// lookup that panics in wgpu-core ("TextureView … is no
|
||||
// longer alive"). Clear it — but DEFERRED via
|
||||
// upgrade_in_event_loop, NOT synchronously here: the
|
||||
// notifier runs while the winit window adapter holds its
|
||||
// RefCell borrowed, and setting a property re-borrows it
|
||||
// ("RefCell already mutably borrowed" panic on close-to-
|
||||
// tray). The queued closure runs on the next loop tick,
|
||||
// after the borrow is released and long before restore.
|
||||
let _ = weak_for_underlay.upgrade_in_event_loop(|w| {
|
||||
w.global::<ImmersiveState>()
|
||||
.set_shader_texture(slint::Image::default());
|
||||
}
|
||||
});
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user