From 6dc517bf01ee282a8257a6517eebf0333d2489c4 Mon Sep 17 00:00:00 2001 From: shatyuka Date: Wed, 22 Jul 2026 23:51:26 +0800 Subject: [PATCH 1/7] Cache background URLs for instant background on game switch --- .../UIElementExtensions.ThemeExtensions.cs | 31 +- .../Classes/Extension/UIElementExtensions.cs | 3 - .../ImageBackgroundManager.Control.cs | 4 +- .../ImageBackgroundManager.Loaders.cs | 278 ++++++++++++++++-- .../ImageBackgroundManager.StreamAndPath.cs | 37 +++ .../ImageBackground/ImageBackgroundManager.cs | 28 +- .../RegionManagement/RegionManagement.cs | 9 + CollapseLauncher/XAMLs/MainApp/MainPage.xaml | 4 +- .../XAMLs/MainApp/MainPage.xaml.cs | 10 +- .../LayeredBackgroundImage.Events.Loaders.cs | 49 ++- 10 files changed, 382 insertions(+), 71 deletions(-) diff --git a/CollapseLauncher/Classes/Extension/UIElementExtensions.ThemeExtensions.cs b/CollapseLauncher/Classes/Extension/UIElementExtensions.ThemeExtensions.cs index aadf1951f7..7655aef1ee 100644 --- a/CollapseLauncher/Classes/Extension/UIElementExtensions.ThemeExtensions.cs +++ b/CollapseLauncher/Classes/Extension/UIElementExtensions.ThemeExtensions.cs @@ -15,6 +15,13 @@ public static partial class UIElementExtensions public static void ChangeAccentColor(this FrameworkElement element, Color baseColor) { + if (Application.Current.Resources.TryGetValue("AccentColor", out object? existing) && + existing is SolidColorBrush existingBrush && + existingBrush.Color.Equals(baseColor)) + { + return; + } + Color accentColorMask = baseColor; Color accentColorTransparent = baseColor; @@ -55,30 +62,28 @@ public static void ChangeAccentColor(this FrameworkElement element, Color baseCo internal static void ReloadPageTheme(this FrameworkElement page) { - ElementTheme theme = InnerLauncherConfig.CurrentAppTheme switch + ElementTheme target = InnerLauncherConfig.CurrentAppTheme switch { AppThemeMode.Dark => ElementTheme.Dark, AppThemeMode.Light => ElementTheme.Light, _ => ElementTheme.Default }; + // Force the opposite theme first to ensure ThemeResource re-evaluation. + ElementTheme opposite = target switch + { + ElementTheme.Dark => ElementTheme.Light, + ElementTheme.Light => ElementTheme.Dark, + _ => ElementTheme.Light + }; + bool isComplete = false; while (!isComplete) { try { - page.RequestedTheme = page.RequestedTheme switch - { - ElementTheme.Dark => ElementTheme.Light, - ElementTheme.Light => ElementTheme.Default, - ElementTheme.Default => ElementTheme.Dark, - _ => page.RequestedTheme - }; - - if (page.RequestedTheme != theme) - { - ReloadPageTheme(page); - } + page.RequestedTheme = opposite; + page.RequestedTheme = target; isComplete = true; } diff --git a/CollapseLauncher/Classes/Extension/UIElementExtensions.cs b/CollapseLauncher/Classes/Extension/UIElementExtensions.cs index 0ca8328779..5fe490be70 100644 --- a/CollapseLauncher/Classes/Extension/UIElementExtensions.cs +++ b/CollapseLauncher/Classes/Extension/UIElementExtensions.cs @@ -543,9 +543,6 @@ internal static ref TReturnType GetApplicationResourceRef(string re internal static void SetApplicationResource(string resourceKey, object value) { - if (!CurrentResourceDictionary.ContainsKey(resourceKey)) - throw new KeyNotFoundException($"Application resource with key: {resourceKey} does not exist!"); - CurrentResourceDictionary[resourceKey] = value; } diff --git a/CollapseLauncher/Classes/GameManagement/ImageBackground/ImageBackgroundManager.Control.cs b/CollapseLauncher/Classes/GameManagement/ImageBackground/ImageBackgroundManager.Control.cs index 00ddef2048..58f6c6ab97 100644 --- a/CollapseLauncher/Classes/GameManagement/ImageBackground/ImageBackgroundManager.Control.cs +++ b/CollapseLauncher/Classes/GameManagement/ImageBackground/ImageBackgroundManager.Control.cs @@ -58,7 +58,7 @@ public void Play(bool isUserRequest = true) bool hasStaticImage = !string.IsNullOrEmpty(context?.BackgroundImageStaticPath); if (isUserRequest && hasStaticImage && context != null) { - LoadImageAtIndex(CurrentSelectedBackgroundIndex, false, CancellationToken.None); + LoadImageAtIndex(CurrentSelectedBackgroundIndex, false, CancellationToken.None, true); } // Force to restore autoplay status to true. @@ -81,7 +81,7 @@ public void Pause(bool isUserRequest = true) bool hasStaticImage = !string.IsNullOrEmpty(context?.BackgroundImageStaticPath); if (isUserRequest && hasStaticImage && context != null) { - LoadImageAtIndex(CurrentSelectedBackgroundIndex, true, CancellationToken.None); + LoadImageAtIndex(CurrentSelectedBackgroundIndex, true, CancellationToken.None, true); return; } diff --git a/CollapseLauncher/Classes/GameManagement/ImageBackground/ImageBackgroundManager.Loaders.cs b/CollapseLauncher/Classes/GameManagement/ImageBackground/ImageBackgroundManager.Loaders.cs index 1d4acca988..fe167987b9 100644 --- a/CollapseLauncher/Classes/GameManagement/ImageBackground/ImageBackgroundManager.Loaders.cs +++ b/CollapseLauncher/Classes/GameManagement/ImageBackground/ImageBackgroundManager.Loaders.cs @@ -2,6 +2,7 @@ using CollapseLauncher.Helper; using CollapseLauncher.Helper.Background; using CollapseLauncher.Helper.Image; +using CollapseLauncher.Helper.Metadata; using CollapseLauncher.Helper.StreamUtility; using CollapseLauncher.Pages; using CollapseLauncher.XAMLs.Theme.CustomControls; @@ -9,13 +10,18 @@ using Hi3Helper.SentryHelper; using Hi3Helper.Shared.Region; using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; using Microsoft.UI.Xaml.Data; +using Microsoft.UI.Xaml.Media; using Microsoft.UI.Xaml.Media.Animation; +using Microsoft.UI.Xaml.Media.Imaging; using PhotoSauce.MagicScaler; using System; using System.Collections.Generic; using System.Diagnostics; +using System.Globalization; using System.IO; +using System.Runtime.InteropServices; using System.Linq; using System.Runtime.CompilerServices; using System.Threading; @@ -35,7 +41,7 @@ public partial class ImageBackgroundManager #endregion - private void LoadImageAtIndex(int index, bool forceLoadToStatic, CancellationToken token) + private void LoadImageAtIndex(int index, bool forceLoadToStatic, CancellationToken token, bool forceReload = false) { if (ImageContextSources.Count <= index || index < 0) @@ -48,12 +54,16 @@ private void LoadImageAtIndex(int index, bool forceLoadToStatic, CancellationTok CancelCurrentBackgroundLoad(); } - IsBackgroundLoading = true; + // Only show loading indicator when a download is required. + if (!CheckIsContextCached(ImageContextSources[index])) + { + IsBackgroundLoading = true; + } new Thread(async void () => { try { - await LoadImageAtIndexCore(index, forceLoadToStatic, token).ConfigureAwait(false); + await LoadImageAtIndexCore(index, forceLoadToStatic, token, forceReload).ConfigureAwait(false); } catch (Exception e) { @@ -76,7 +86,103 @@ private void CancelCurrentBackgroundLoad() IsBackgroundLoading = false; } - private async Task LoadImageAtIndexCore(int index, bool forceLoadToStatic, CancellationToken token) + public void PreloadBackground(PresetConfig? preset, Grid? presenterGrid) + { + if (preset == null || presenterGrid == null) return; + + CancelCurrentBackgroundLoad(); + Interlocked.Increment(ref _loadGeneration); + + PresenterGrid = presenterGrid; + string autoPlayKey = $"LastIsEnableBackgroundAutoPlay-{preset.GameName}-{preset.ZoneName}"; + CurrentIsEnableBackgroundAutoPlayKey = autoPlayKey; + + string cachedBgKey = $"CachedBg-{preset.GameName}-{preset.ZoneName}"; + string? backgroundUrl = LauncherConfig.GetAppConfigValue($"{cachedBgKey}-Background").ToString(); + string? staticBgUrl = LauncherConfig.GetAppConfigValue($"{cachedBgKey}-StaticBackground").ToString(); + string? overlayUrl = LauncherConfig.GetAppConfigValue($"{cachedBgKey}-Overlay").ToString(); + + if (string.IsNullOrEmpty(backgroundUrl) && string.IsNullOrEmpty(staticBgUrl)) return; + + string? primaryBg = backgroundUrl ?? staticBgUrl; + + LayeredImageBackgroundContext context = new() + { + OriginOverlayImagePath = overlayUrl, + OriginBackgroundImagePath = backgroundUrl, + OriginBackgroundStaticImagePath = staticBgUrl, + OverlayImagePath = overlayUrl, + BackgroundImagePath = primaryBg, + BackgroundImageStaticPath = staticBgUrl, + IsVideo = IsVideoMediaFileExtensionSupported(primaryBg ?? staticBgUrl!), + IsCustom = false + }; + + if (CheckIsContextCached(context)) + { + string? localOverlay = TryGetCachedLocalPath(overlayUrl); + string? localBackground = TryGetCachedLocalPath(primaryBg); + string? localStatic = TryGetCachedLocalPath(staticBgUrl); + + bool overlayRequired = !string.IsNullOrEmpty(overlayUrl); + bool staticRequired = !string.IsNullOrEmpty(staticBgUrl); + + if (localBackground != null && + (!overlayRequired || localOverlay != null) && + (!staticRequired || localStatic != null)) + { + bool willDisplayStatic = !IsVideoMediaFileExtensionSupported(primaryBg ?? staticBgUrl!) || + (!CurrentIsEnableCustomImage && + !GlobalIsEnableCustomImage && + !CurrentIsEnableBackgroundAutoPlay); + Uri accentPreviewUri = willDisplayStatic ? new Uri(localStatic) : new Uri(localBackground); + + RestoreSavedAccent(cachedBgKey, accentPreviewUri); + + ImageContextSources.Clear(); + ImageContextSources.Add(context); + OnPropertyChanged(nameof(CurrentSelectedBackgroundContext)); + OnPropertyChanged(nameof(CurrentBackgroundCount)); + + CurrentBackgroundElement = CreateLayerElement(localOverlay != null ? new Uri(localOverlay) : null, + new Uri(localBackground), + localStatic != null ? new Uri(localStatic) : null, + context, + IsVideoMediaFileExtensionSupported(primaryBg ?? staticBgUrl!)); + + bool isUseFFmpeg = GlobalIsUseFFmpeg && GlobalIsFFmpegAvailable; + new Thread(async void (ctx) => + { + try + { + await GetMediaAccentColor(ctx).ConfigureAwait(false); + } + catch (Exception e) + { + Logger.LogWriteLine($"{e}", LogType.Error, true); + } + }) + { + IsBackground = true + }.UnsafeStart((accentPreviewUri, isUseFFmpeg, cachedBgKey)); + return; + } + } + + // Show preset placeholder. + string placeholderPath = GetPlaceholderBackgroundImageFrom(null); + PresenterGrid.Background = new ImageBrush + { + ImageSource = new BitmapImage(new Uri(placeholderPath)), + Stretch = Stretch.UniformToFill + }; + PresenterGrid.Children.Clear(); + CurrentBackgroundElement = null; + _displayedContext = null; + IsBackgroundLoading = true; + } + + private async Task LoadImageAtIndexCore(int index, bool forceLoadToStatic, CancellationToken token, bool forceReload = false) { Stopwatch? stopwatch = null; try @@ -126,8 +232,13 @@ private async Task LoadImageAtIndexCore(int index, bool forceLoadToStatic, Cance // Try to use static bg URL if normal bg is not available. downloadedBackgroundUri ??= downloadedBackgroundStaticUri; - if (downloadedBackgroundUri == null) // If no background file is available, return. + if (downloadedBackgroundUri == null) { + if (!context.IsCustom) + { + NeedFallbackPlaceholder = false; + OnBackgroundLoadFailed?.Invoke(); + } return; } @@ -148,7 +259,23 @@ private async Task LoadImageAtIndexCore(int index, bool forceLoadToStatic, Cance return; } - // -- Read Color Accent information from current background context. + // Try to force loading static image if requested. + if (forceLoadToStatic && downloadedBackgroundStaticUri != null) + { + isVideo = false; + downloadedBackgroundUri = downloadedBackgroundStaticUri; + downloadedBackgroundStaticUri = null; + } + + // Read accent color from the source that is actually displayed. + bool willDisplayStatic = !isVideo || + (!CurrentIsEnableCustomImage && + !GlobalIsEnableCustomImage && + !CurrentIsEnableBackgroundAutoPlay); + Uri? accentSourceUri = willDisplayStatic + ? downloadedBackgroundStaticUri ?? downloadedBackgroundUri + : downloadedBackgroundUri; + new Thread(async void (ctx) => { try @@ -162,39 +289,50 @@ private async Task LoadImageAtIndexCore(int index, bool forceLoadToStatic, Cance }) { IsBackground = true - }.UnsafeStart((downloadedBackgroundUri, isUseFFmpeg)); + }.UnsafeStart((accentSourceUri, isUseFFmpeg, CurrentCachedBgKey)); - // Try to force loading static image if requested. - if (forceLoadToStatic && downloadedBackgroundStaticUri != null) + if (!context.IsCustom && + !string.IsNullOrEmpty(CurrentCachedBgKey)) { - isVideo = false; - downloadedBackgroundUri = downloadedBackgroundStaticUri; - downloadedBackgroundStaticUri = null; + LauncherConfig.SetAndSaveConfigValue($"{CurrentCachedBgKey}-Background", context.OriginBackgroundImagePath ?? ""); + LauncherConfig.SetAndSaveConfigValue($"{CurrentCachedBgKey}-StaticBackground", context.OriginBackgroundStaticImagePath ?? ""); + LauncherConfig.SetAndSaveConfigValue($"{CurrentCachedBgKey}-Overlay", context.OriginOverlayImagePath ?? ""); } // -- Use UI thread and load image layer + int captureGen = _loadGeneration; + bool captureForceReload = forceReload; DispatcherQueueExtensions .CurrentDispatcherQueue .TryEnqueue(() => SpawnImageLayer(downloadedOverlayUri, - downloadedBackgroundUri, - downloadedBackgroundStaticUri, - isVideo, - context)); + downloadedBackgroundUri, + downloadedBackgroundStaticUri, + isVideo, + context, + captureGen, + forceReload: captureForceReload)); GlobalIsFFmpegCurrentlyUsed = isVideo && isUseFFmpeg; } - catch (Exception ex) + catch (Exception ex) when (ex is not OperationCanceledException) { _ = SentryHelper.ExceptionHandlerAsync(ex); Logger.LogWriteLine($"[ImageBackgroundManager::LoadImageAtIndex] {ex}", LogType.Error, true); } + catch (OperationCanceledException) + { + // Suppress: cancellation is expected on game switch or rapid load cycles. + } finally { stopwatch?.Stop(); Logger.LogWriteLine($"Background image loading took: {stopwatch?.Elapsed.TotalSeconds} second(s)"); - IsBackgroundLoading = false; + if (!token.IsCancellationRequested) + { + IsBackgroundLoading = false; + } } } @@ -202,14 +340,75 @@ private void SpawnImageLayer(Uri? overlayFilePath, Uri? backgroundFilePath, Uri? backgroundStaticFilePath, bool isVideo, - LayeredImageBackgroundContext context) + LayeredImageBackgroundContext context, + int loadGeneration, + bool forceReload = false) { - if (CurrentSelectedBackgroundContext != null && - !context.Equals(CurrentSelectedBackgroundContext)) + if (loadGeneration != _loadGeneration) { return; } + if (!forceReload && + CurrentBackgroundElement != null && + _displayedContext != null && + context.Equals(_displayedContext)) + { + return; + } + + CurrentBackgroundElement = CreateLayerElement(overlayFilePath, backgroundFilePath, backgroundStaticFilePath, context, isVideo); + } + + private void RestoreSavedAccent(string cachedBgKey, Uri? fallbackSourceUri = null) + { + string? savedHex = LauncherConfig.GetAppConfigValue($"{cachedBgKey}-AccentColor").ToString(); + if (!string.IsNullOrEmpty(savedHex) && savedHex!.Length >= 6 && ThemeRootElement != null) + { + if (TryParseHexColor(savedHex, out Color accentColor)) + { + ThemeRootElement.ChangeAccentColor(accentColor); + return; + } + } + + if (fallbackSourceUri != null && ThemeRootElement != null) + { + try + { + Color syncColor = Task.Run(async () => + await ColorPaletteUtility.GetMediaAccentColorFromAsync(fallbackSourceUri, false) + ).GetAwaiter().GetResult(); + ThemeRootElement.ChangeAccentColor(syncColor); + LauncherConfig.SetAndSaveConfigValue($"{cachedBgKey}-AccentColor", + $"{syncColor.R:X2}{syncColor.G:X2}{syncColor.B:X2}"); + } + catch (Exception ex) + { + Logger.LogWriteLine($"RestoreSavedAccent failed: {ex}", LogType.Error, true); + } + } + } + + private static bool TryParseHexColor(string hex, out Color color) + { + color = default; + if (hex.Length < 6) return false; + if (hex[0] == '#') + hex = hex[1..]; + if (!uint.TryParse(hex, NumberStyles.HexNumber, null, out uint argb)) return false; + if (hex.Length <= 6) + argb = 0xFF_000000 | argb; + color = Color.FromArgb((byte)(argb >> 24), (byte)(argb >> 16), (byte)(argb >> 8), (byte)argb); + return true; + } + + private LayeredBackgroundImage CreateLayerElement(Uri? overlayFilePath, + Uri? backgroundFilePath, + Uri? backgroundStaticFilePath, + LayeredImageBackgroundContext context, + bool isVideo) + { LayeredBackgroundImage layerElement = new() { BackgroundSource = backgroundFilePath, @@ -221,8 +420,6 @@ private void SpawnImageLayer(Uri? overlayFilePath, BackgroundElevationPixels = 64d }; - CurrentBackgroundElement = layerElement; - if (!CurrentIsEnableCustomImage && !GlobalIsEnableCustomImage) { @@ -293,10 +490,12 @@ private void SpawnImageLayer(Uri? overlayFilePath, bindingMode: BindingMode.OneWay, converter: StaticConverter.Shared); - layerElement.ImageLoaded += LayerElementOnLoaded; + layerElement.ImageLoaded += LayerElementOnLoaded; PresenterGrid?.Children.Add(layerElement); layerElement.Tag = isVideo; + _displayedContext = context; + return layerElement; } private void LayerElementOnLoaded(LayeredBackgroundImage layerElement) @@ -304,11 +503,17 @@ private void LayerElementOnLoaded(LayeredBackgroundImage layerElement) layerElement.ImageLoaded -= LayerElementOnLoaded; layerElement.Transitions.Add(new PopupThemeTransition()); - UIElement? lastElement = PresenterGrid?.Children.LastOrDefault(); - List elementToRemove = PresenterGrid?.Children.Where(element => element != lastElement).ToList() ?? []; - foreach (UIElement element in elementToRemove) + if (PresenterGrid != null) { - PresenterGrid?.Children.Remove(element); + List toRemove = PresenterGrid.Children.Where(element => element != layerElement).ToList(); + foreach (UIElement oldElement in toRemove) + { + if (oldElement is LayeredBackgroundImage oldLayer) + oldLayer.ImageLoaded -= LayerElementOnLoaded; + PresenterGrid.Children.Remove(oldElement); + } + + PresenterGrid.Background = null; } if (CurrentIsEnableBackgroundAutoPlay && WindowUtility.CurrentWindowIsVisible) @@ -393,20 +598,29 @@ private async Task GetMediaAccentColor(object? context) { try { - if (context is not (Uri asUri, bool useFfmpegForVideo)) + if (context is not (Uri asUri, bool useFfmpegForVideo, string configKey)) { return; } Color color = await ColorPaletteUtility.GetMediaAccentColorFromAsync(asUri, useFfmpegForVideo) .ConfigureAwait(false); + + if (color == default(Color)) return; + + string hex = $"{color.R:X2}{color.G:X2}{color.B:X2}"; + if (!string.IsNullOrEmpty(configKey)) + { + string? savedHex = LauncherConfig.GetAppConfigValue($"{configKey}-AccentColor").ToString(); + if (savedHex == hex) return; + LauncherConfig.SetAndSaveConfigValue($"{configKey}-AccentColor", hex); + } + ColorAccentChanged?.Invoke(color); } catch (Exception ex) { - Logger.LogWriteLine($"An error has occurred while trying to get media accent color! {ex}", - LogType.Error, - true); + Logger.LogWriteLine($"GetMediaAccentColor failed: {ex}", LogType.Error, true); SentryHelper.ExceptionHandler(ex); } } diff --git a/CollapseLauncher/Classes/GameManagement/ImageBackground/ImageBackgroundManager.StreamAndPath.cs b/CollapseLauncher/Classes/GameManagement/ImageBackground/ImageBackgroundManager.StreamAndPath.cs index 77df844cd9..ec50a11353 100644 --- a/CollapseLauncher/Classes/GameManagement/ImageBackground/ImageBackgroundManager.StreamAndPath.cs +++ b/CollapseLauncher/Classes/GameManagement/ImageBackground/ImageBackgroundManager.StreamAndPath.cs @@ -207,4 +207,41 @@ public static string GetRandomPlaceholderImage(bool usePngVersion = false) return source[Random.Shared.Next(0, source.Length - 1)]; } + + internal static bool CheckIsContextCached(LayeredImageBackgroundContext ctx) + { + bool backgroundCached = false; + if (!string.IsNullOrEmpty(ctx.BackgroundImagePath) && + TryGetDownloadedFile(new Uri(ctx.BackgroundImagePath), out _, out _, out _)) + { + backgroundCached = true; + } + else if (!string.IsNullOrEmpty(ctx.BackgroundImageStaticPath) && + TryGetDownloadedFile(new Uri(ctx.BackgroundImageStaticPath), out _, out _, out _)) + { + backgroundCached = true; + } + + return backgroundCached; + } + + internal static string? TryGetCachedLocalPath(string? urlOrPath) + { + if (string.IsNullOrEmpty(urlOrPath)) + { + return null; + } + + if (TryGetDecodedTemporaryFile(urlOrPath, out string decodedFilePath)) + { + return decodedFilePath; + } + + if (TryGetDownloadedFile(new Uri(urlOrPath), out FileInfo downloadedFile, out _, out _)) + { + return downloadedFile.FullName; + } + + return null; + } } diff --git a/CollapseLauncher/Classes/GameManagement/ImageBackground/ImageBackgroundManager.cs b/CollapseLauncher/Classes/GameManagement/ImageBackground/ImageBackgroundManager.cs index 781a07e407..0af623e028 100644 --- a/CollapseLauncher/Classes/GameManagement/ImageBackground/ImageBackgroundManager.cs +++ b/CollapseLauncher/Classes/GameManagement/ImageBackground/ImageBackgroundManager.cs @@ -191,6 +191,7 @@ public double SmokeOpacity #region This Instance Properties private string CurrentCustomBackgroundConfigKey { get; set; } = ""; + private string CurrentCachedBgKey { get; set; } = ""; private string CurrentIsEnableCustomImageConfigKey { get; set; } = ""; private string CurrentSelectedBackgroundIndexKey { get; set; } = ""; private string CurrentIsEnableBackgroundAutoPlayKey { get; set; } = ""; @@ -198,6 +199,10 @@ public double SmokeOpacity private Grid? PresenterGrid { get; set; } private PresetConfig? PresetConfig { get; set; } + internal bool NeedFallbackPlaceholder { get; set; } + + public Action? OnBackgroundLoadFailed { get; set; } + public string? CurrentCustomBackgroundImagePath { get => LauncherConfig.GetAppConfigValue(CurrentCustomBackgroundConfigKey); @@ -252,7 +257,7 @@ public int CurrentSelectedBackgroundIndex LauncherConfig.SetAndSaveConfigValue(CurrentSelectedBackgroundIndexKey, value); OnPropertyChanged(); - LoadImageAtIndex(value, false, CancellationToken.None); + LoadImageAtIndex(value, false, CancellationToken.None, true); } } @@ -316,6 +321,12 @@ public ObservableCollection ImageContextSources private int _previousContextHash; + private volatile int _loadGeneration; + + private LayeredImageBackgroundContext? _displayedContext; + + internal FrameworkElement? ThemeRootElement { get; set; } + #endregion /// @@ -337,6 +348,7 @@ public void Initialize(PresetConfig? presetConfig, CurrentSelectedBackgroundIndexKey = $"LastCustomBgIndex-{presetConfig.GameName}-{presetConfig.ZoneName}"; CurrentIsEnableCustomImageConfigKey = $"LastIsCustomImageEnabled-{presetConfig.GameName}-{presetConfig.ZoneName}"; CurrentIsEnableBackgroundAutoPlayKey = $"LastIsEnableBackgroundAutoPlay-{presetConfig.GameName}-{presetConfig.ZoneName}"; + CurrentCachedBgKey = $"CachedBg-{presetConfig.GameName}-{presetConfig.ZoneName}"; PresetConfig = presetConfig; CurrentBackgroundApi = backgroundApi; @@ -533,6 +545,15 @@ private void UpdateContextListCore( CancellationToken token, bool skipPreviousContextCheck, params ReadOnlySpan imageContexts) + { + UpdateContextListCore(token, skipPreviousContextCheck, false, imageContexts); + } + + private void UpdateContextListCore( + CancellationToken token, + bool skipPreviousContextCheck, + bool forceLoadToStatic, + params ReadOnlySpan imageContexts) { // Do not update if the previous contexts are equal if ((!skipPreviousContextCheck && @@ -542,6 +563,8 @@ private void UpdateContextListCore( return; } + Interlocked.Increment(ref _loadGeneration); + ImageContextSources.Clear(); // Flush list ref IList? backedList = ref ObservableCollectionExtension @@ -568,7 +591,7 @@ ref ObservableCollectionExtension OnPropertyChanged(nameof(CurrentSelectedBackgroundIndex)); OnPropertyChanged(nameof(CurrentSelectedBackgroundContext)); OnPropertyChanged(nameof(CurrentBackgroundCount)); - LoadImageAtIndex(CurrentSelectedBackgroundIndex, false, token); + LoadImageAtIndex(CurrentSelectedBackgroundIndex, forceLoadToStatic, token); } #pragma warning restore CA1068 @@ -593,6 +616,7 @@ public void ResetContexts() { Interlocked.Exchange(ref _previousContextHash, 0); ImageContextSources.Clear(); + NeedFallbackPlaceholder = false; } void INotifyAllPropertyChanged.NotifyAllChanged() diff --git a/CollapseLauncher/Classes/RegionManagement/RegionManagement.cs b/CollapseLauncher/Classes/RegionManagement/RegionManagement.cs index 014add3925..5d61f80c5e 100644 --- a/CollapseLauncher/Classes/RegionManagement/RegionManagement.cs +++ b/CollapseLauncher/Classes/RegionManagement/RegionManagement.cs @@ -11,6 +11,7 @@ using Hi3Helper.Plugin.Core.Management; using Hi3Helper.SentryHelper; using Hi3Helper.Shared.ClassStruct; +using Hi3Helper.Shared.Region; using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; using System; @@ -167,6 +168,14 @@ Task BeforeLoadRoutine(CancellationToken token) LogWriteLine($"Initializing game: {regionToChangeName}...", LogType.Scheme, true); ClearMainPageState(); + + bool isCustom = LauncherConfig.GetAppConfigValue($"LastIsCustomImageEnabled-{preset.GameName}-{preset.ZoneName}").ToBool(); + if (!isCustom) + { + DispatcherQueue.TryEnqueue(() => + ImageBackgroundManager.Shared.PreloadBackground(preset, BackgroundPresenterGrid)); + } + _ = ShowAsyncLoadingTimedOutPill(token); } catch (Exception ex) diff --git a/CollapseLauncher/XAMLs/MainApp/MainPage.xaml b/CollapseLauncher/XAMLs/MainApp/MainPage.xaml index 4d892a3817..927aac7dca 100644 --- a/CollapseLauncher/XAMLs/MainApp/MainPage.xaml +++ b/CollapseLauncher/XAMLs/MainApp/MainPage.xaml @@ -32,7 +32,9 @@ Background="{ThemeResource WindowBackground}"> - diff --git a/CollapseLauncher/XAMLs/MainApp/MainPage.xaml.cs b/CollapseLauncher/XAMLs/MainApp/MainPage.xaml.cs index 928d488b8a..a83e60b809 100644 --- a/CollapseLauncher/XAMLs/MainApp/MainPage.xaml.cs +++ b/CollapseLauncher/XAMLs/MainApp/MainPage.xaml.cs @@ -94,6 +94,8 @@ public MainPage() ImageBackgroundManager.Shared.GlobalParallaxHoverSource = MainPageGrid; ImageBackgroundManager.Shared.ColorAccentChanged += SharedOnColorAccentChanged; + ImageBackgroundManager.Shared.OnBackgroundLoadFailed += SharedOnBackgroundLoadFailed; + ImageBackgroundManager.Shared.ThemeRootElement = this; // Enable implicit animation on certain elements AnimationHelper.EnableImplicitAnimation(true, null, GridBGRegionGrid, GridBGNotifBtn, NotificationPanelClearAllGrid); @@ -108,8 +110,11 @@ public MainPage() private void SharedOnColorAccentChanged(Color color) { DispatcherQueue.TryEnqueue(() => this.ChangeAccentColor(color)); - DispatcherQueue.TryEnqueue(() => this.ChangeAccentColor(color)); - DispatcherQueue.TryEnqueue(() => this.ChangeAccentColor(color)); + } + + private void SharedOnBackgroundLoadFailed() + { + DispatcherQueue.TryEnqueue(() => PlaceholderBackgroundLayer.Visibility = Visibility.Visible); } private void Page_Unloaded(object sender, RoutedEventArgs e) @@ -419,6 +424,7 @@ private void UnsubscribeEvents() KeyboardShortcuts.KeyboardShortcutsEvent -= SettingsPage_KeyboardShortcutsEvent; GridBGRegionGrid.SizeChanged -= GridBG_RegionGrid_SizeChanged; MainPageGrid.SizeChanged -= MainPageGrid_SizeChanged; + ImageBackgroundManager.Shared.OnBackgroundLoadFailed -= SharedOnBackgroundLoadFailed; } #endregion diff --git a/CollapseLauncher/XAMLs/Theme/CustomControls/LayeredBackgroundImage.Events.Loaders.cs b/CollapseLauncher/XAMLs/Theme/CustomControls/LayeredBackgroundImage.Events.Loaders.cs index fd13a34dc6..9d67ba4245 100644 --- a/CollapseLauncher/XAMLs/Theme/CustomControls/LayeredBackgroundImage.Events.Loaders.cs +++ b/CollapseLauncher/XAMLs/Theme/CustomControls/LayeredBackgroundImage.Events.Loaders.cs @@ -108,13 +108,17 @@ private static void PlaceholderSource_OnChange(DependencyObject d, DependencyPro } Grid grid = element._placeholderGrid; - element.LoadFromSourceAsyncDetached(PlaceholderSourceProperty, - nameof(PlaceholderStretch), - nameof(PlaceholderHorizontalAlignment), - nameof(PlaceholderVerticalAlignment), - grid, - false, - ref element._lastPlaceholderSourceType); + if (!IsSourceKindEquals(element._lastPlaceholderSource, element.PlaceholderSource)) + { + element.LoadFromSourceAsyncDetached(PlaceholderSourceProperty, + nameof(PlaceholderStretch), + nameof(PlaceholderHorizontalAlignment), + nameof(PlaceholderVerticalAlignment), + grid, + false, + ref element._lastPlaceholderSourceType); + element._lastPlaceholderSource = element.PlaceholderSource; + } } private static void BackgroundSource_OnChange(DependencyObject d, DependencyPropertyChangedEventArgs e) @@ -129,11 +133,19 @@ private static void BackgroundSource_OnChange(DependencyObject d, DependencyProp // then load static background source. if (element is { CanUseStaticBackground: true, IsVideoPlay: true } or { CanUseStaticBackground: true, IsUseStaticBackgroundUsed: true }) { - BackgroundSource_UseStatic(element); + if (!IsSourceKindEquals(element._lastBackgroundStaticSource, element.BackgroundStaticSource)) + { + BackgroundSource_UseStatic(element); + element._lastBackgroundStaticSource = element.BackgroundStaticSource; + } return; } - BackgroundSource_UseNormal(element); + if (!IsSourceKindEquals(element._lastBackgroundSource, element.BackgroundSource)) + { + BackgroundSource_UseNormal(element); + element._lastBackgroundSource = element.BackgroundSource; + } } private static void BackgroundSource_UseStatic(LayeredBackgroundImage element) @@ -174,13 +186,17 @@ private static void ForegroundSource_OnChange(DependencyObject d, DependencyProp } Grid grid = element._foregroundGrid; - element.LoadFromSourceAsyncDetached(ForegroundSourceProperty, - nameof(ForegroundStretch), - nameof(ForegroundHorizontalAlignment), - nameof(ForegroundVerticalAlignment), - grid, - false, - ref element._lastForegroundSourceType); + if (!IsSourceKindEquals(element._lastForegroundSource, element.ForegroundSource)) + { + element.LoadFromSourceAsyncDetached(ForegroundSourceProperty, + nameof(ForegroundStretch), + nameof(ForegroundHorizontalAlignment), + nameof(ForegroundVerticalAlignment), + grid, + false, + ref element._lastForegroundSourceType); + element._lastForegroundSource = element.ForegroundSource; + } } private void LoadFromSourceAsyncDetached( @@ -299,6 +315,7 @@ private static async ValueTask LoadImageFromSourceAsync( image.BindProperty(instance, horizontalAlignmentProperty, HorizontalAlignmentProperty, BindingMode.OneWay); image.BindProperty(instance, verticalAlignmentProperty, VerticalAlignmentProperty, BindingMode.OneWay); + ClearMediaGrid(grid); grid.Children.Add(image); image.Tag = (grid, instance); From a74c3f88f166d405bb77e3165d992dd96f6ae78c Mon Sep 17 00:00:00 2001 From: shatyuka Date: Thu, 23 Jul 2026 21:37:03 +0800 Subject: [PATCH 2/7] Fix potential ArgumentNullException in PreloadBackground when staticBgUrl is null --- .../ImageBackground/ImageBackgroundManager.Loaders.cs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/CollapseLauncher/Classes/GameManagement/ImageBackground/ImageBackgroundManager.Loaders.cs b/CollapseLauncher/Classes/GameManagement/ImageBackground/ImageBackgroundManager.Loaders.cs index fe167987b9..ba5fbe1f29 100644 --- a/CollapseLauncher/Classes/GameManagement/ImageBackground/ImageBackgroundManager.Loaders.cs +++ b/CollapseLauncher/Classes/GameManagement/ImageBackground/ImageBackgroundManager.Loaders.cs @@ -131,11 +131,12 @@ public void PreloadBackground(PresetConfig? preset, Grid? presenterGrid) (!overlayRequired || localOverlay != null) && (!staticRequired || localStatic != null)) { - bool willDisplayStatic = !IsVideoMediaFileExtensionSupported(primaryBg ?? staticBgUrl!) || - (!CurrentIsEnableCustomImage && - !GlobalIsEnableCustomImage && - !CurrentIsEnableBackgroundAutoPlay); - Uri accentPreviewUri = willDisplayStatic ? new Uri(localStatic) : new Uri(localBackground); + bool willDisplayStatic = (!IsVideoMediaFileExtensionSupported(primaryBg ?? staticBgUrl!) || + (!CurrentIsEnableCustomImage && + !GlobalIsEnableCustomImage && + !CurrentIsEnableBackgroundAutoPlay)) + && localStatic != null; + Uri accentPreviewUri = willDisplayStatic ? new Uri(localStatic!) : new Uri(localBackground); RestoreSavedAccent(cachedBgKey, accentPreviewUri); From 0ede2381233e97d55593dc3c7bff0e2b5512f87c Mon Sep 17 00:00:00 2001 From: shatyuka Date: Thu, 23 Jul 2026 21:46:30 +0800 Subject: [PATCH 3/7] Fix potential deadlock in RestoreSavedAccent when extracting accent from SVG --- .../ImageBackground/ImageBackgroundManager.Loaders.cs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/CollapseLauncher/Classes/GameManagement/ImageBackground/ImageBackgroundManager.Loaders.cs b/CollapseLauncher/Classes/GameManagement/ImageBackground/ImageBackgroundManager.Loaders.cs index ba5fbe1f29..1e8ecb3eef 100644 --- a/CollapseLauncher/Classes/GameManagement/ImageBackground/ImageBackgroundManager.Loaders.cs +++ b/CollapseLauncher/Classes/GameManagement/ImageBackground/ImageBackgroundManager.Loaders.cs @@ -138,7 +138,7 @@ public void PreloadBackground(PresetConfig? preset, Grid? presenterGrid) && localStatic != null; Uri accentPreviewUri = willDisplayStatic ? new Uri(localStatic!) : new Uri(localBackground); - RestoreSavedAccent(cachedBgKey, accentPreviewUri); + _ = RestoreSavedAccent(cachedBgKey, accentPreviewUri); ImageContextSources.Clear(); ImageContextSources.Add(context); @@ -361,7 +361,7 @@ private void SpawnImageLayer(Uri? overlayFilePath, CurrentBackgroundElement = CreateLayerElement(overlayFilePath, backgroundFilePath, backgroundStaticFilePath, context, isVideo); } - private void RestoreSavedAccent(string cachedBgKey, Uri? fallbackSourceUri = null) + private async Task RestoreSavedAccent(string cachedBgKey, Uri? fallbackSourceUri = null) { string? savedHex = LauncherConfig.GetAppConfigValue($"{cachedBgKey}-AccentColor").ToString(); if (!string.IsNullOrEmpty(savedHex) && savedHex!.Length >= 6 && ThemeRootElement != null) @@ -377,9 +377,7 @@ private void RestoreSavedAccent(string cachedBgKey, Uri? fallbackSourceUri = nul { try { - Color syncColor = Task.Run(async () => - await ColorPaletteUtility.GetMediaAccentColorFromAsync(fallbackSourceUri, false) - ).GetAwaiter().GetResult(); + Color syncColor = await ColorPaletteUtility.GetMediaAccentColorFromAsync(fallbackSourceUri, false); ThemeRootElement.ChangeAccentColor(syncColor); LauncherConfig.SetAndSaveConfigValue($"{cachedBgKey}-AccentColor", $"{syncColor.R:X2}{syncColor.G:X2}{syncColor.B:X2}"); From f970b8b245d9ab59da0aa0a6e5c7630379871426 Mon Sep 17 00:00:00 2001 From: shatyuka Date: Thu, 23 Jul 2026 23:16:31 +0800 Subject: [PATCH 4/7] Fix preset background not showing during region load --- .../ImageBackgroundManager.Loaders.cs | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/CollapseLauncher/Classes/GameManagement/ImageBackground/ImageBackgroundManager.Loaders.cs b/CollapseLauncher/Classes/GameManagement/ImageBackground/ImageBackgroundManager.Loaders.cs index 1e8ecb3eef..db5dba2e24 100644 --- a/CollapseLauncher/Classes/GameManagement/ImageBackground/ImageBackgroundManager.Loaders.cs +++ b/CollapseLauncher/Classes/GameManagement/ImageBackground/ImageBackgroundManager.Loaders.cs @@ -102,7 +102,11 @@ public void PreloadBackground(PresetConfig? preset, Grid? presenterGrid) string? staticBgUrl = LauncherConfig.GetAppConfigValue($"{cachedBgKey}-StaticBackground").ToString(); string? overlayUrl = LauncherConfig.GetAppConfigValue($"{cachedBgKey}-Overlay").ToString(); - if (string.IsNullOrEmpty(backgroundUrl) && string.IsNullOrEmpty(staticBgUrl)) return; + if (string.IsNullOrEmpty(backgroundUrl) && string.IsNullOrEmpty(staticBgUrl)) + { + IsBackgroundLoading = true; + return; + } string? primaryBg = backgroundUrl ?? staticBgUrl; @@ -508,8 +512,14 @@ private void LayerElementOnLoaded(LayeredBackgroundImage layerElement) foreach (UIElement oldElement in toRemove) { if (oldElement is LayeredBackgroundImage oldLayer) + { oldLayer.ImageLoaded -= LayerElementOnLoaded; - PresenterGrid.Children.Remove(oldElement); + oldLayer.Visibility = Visibility.Collapsed; + } + else + { + PresenterGrid.Children.Remove(oldElement); + } } PresenterGrid.Background = null; From d72d2225a94e0af8cb20d77a220aa2db3f81176c Mon Sep 17 00:00:00 2001 From: shatyuka Date: Thu, 23 Jul 2026 23:24:05 +0800 Subject: [PATCH 5/7] Fix PreloadBackground clearing PlaceholderBackgroundLayer from visual tree --- .../ImageBackground/ImageBackgroundManager.Loaders.cs | 1 - CollapseLauncher/XAMLs/MainApp/MainPage.xaml | 1 - 2 files changed, 2 deletions(-) diff --git a/CollapseLauncher/Classes/GameManagement/ImageBackground/ImageBackgroundManager.Loaders.cs b/CollapseLauncher/Classes/GameManagement/ImageBackground/ImageBackgroundManager.Loaders.cs index db5dba2e24..5554177c88 100644 --- a/CollapseLauncher/Classes/GameManagement/ImageBackground/ImageBackgroundManager.Loaders.cs +++ b/CollapseLauncher/Classes/GameManagement/ImageBackground/ImageBackgroundManager.Loaders.cs @@ -181,7 +181,6 @@ public void PreloadBackground(PresetConfig? preset, Grid? presenterGrid) ImageSource = new BitmapImage(new Uri(placeholderPath)), Stretch = Stretch.UniformToFill }; - PresenterGrid.Children.Clear(); CurrentBackgroundElement = null; _displayedContext = null; IsBackgroundLoading = true; diff --git a/CollapseLauncher/XAMLs/MainApp/MainPage.xaml b/CollapseLauncher/XAMLs/MainApp/MainPage.xaml index 927aac7dca..b09f1a0a91 100644 --- a/CollapseLauncher/XAMLs/MainApp/MainPage.xaml +++ b/CollapseLauncher/XAMLs/MainApp/MainPage.xaml @@ -33,7 +33,6 @@ From 5ba59c531b2153baac52a8befc9c12b7c6671c88 Mon Sep 17 00:00:00 2001 From: shatyuka Date: Thu, 23 Jul 2026 23:38:16 +0800 Subject: [PATCH 6/7] Fix background transition animation lost after switching games --- .../ImageBackground/ImageBackgroundManager.Loaders.cs | 8 +------- CollapseLauncher/XAMLs/MainApp/MainPage.xaml.cs | 7 ++++++- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/CollapseLauncher/Classes/GameManagement/ImageBackground/ImageBackgroundManager.Loaders.cs b/CollapseLauncher/Classes/GameManagement/ImageBackground/ImageBackgroundManager.Loaders.cs index 5554177c88..95088f0ac6 100644 --- a/CollapseLauncher/Classes/GameManagement/ImageBackground/ImageBackgroundManager.Loaders.cs +++ b/CollapseLauncher/Classes/GameManagement/ImageBackground/ImageBackgroundManager.Loaders.cs @@ -511,14 +511,8 @@ private void LayerElementOnLoaded(LayeredBackgroundImage layerElement) foreach (UIElement oldElement in toRemove) { if (oldElement is LayeredBackgroundImage oldLayer) - { oldLayer.ImageLoaded -= LayerElementOnLoaded; - oldLayer.Visibility = Visibility.Collapsed; - } - else - { - PresenterGrid.Children.Remove(oldElement); - } + PresenterGrid.Children.Remove(oldElement); } PresenterGrid.Background = null; diff --git a/CollapseLauncher/XAMLs/MainApp/MainPage.xaml.cs b/CollapseLauncher/XAMLs/MainApp/MainPage.xaml.cs index a83e60b809..d1743d3976 100644 --- a/CollapseLauncher/XAMLs/MainApp/MainPage.xaml.cs +++ b/CollapseLauncher/XAMLs/MainApp/MainPage.xaml.cs @@ -114,7 +114,12 @@ private void SharedOnColorAccentChanged(Color color) private void SharedOnBackgroundLoadFailed() { - DispatcherQueue.TryEnqueue(() => PlaceholderBackgroundLayer.Visibility = Visibility.Visible); + DispatcherQueue.TryEnqueue(() => + { + if (!BackgroundPresenterGrid.Children.Contains(PlaceholderBackgroundLayer)) + BackgroundPresenterGrid.Children.Insert(0, PlaceholderBackgroundLayer); + PlaceholderBackgroundLayer.Visibility = Visibility.Visible; + }); } private void Page_Unloaded(object sender, RoutedEventArgs e) From a6340420517fb819fe2fb501493b60c4a01f40bb Mon Sep 17 00:00:00 2001 From: shatyuka Date: Fri, 24 Jul 2026 00:29:10 +0800 Subject: [PATCH 7/7] Fix occasional flicker --- .../ImageBackgroundManager.Loaders.cs | 44 +++++++++++++++++-- CollapseLauncher/XAMLs/MainApp/MainPage.xaml | 3 ++ .../XAMLs/MainApp/MainPage.xaml.cs | 2 +- 3 files changed, 44 insertions(+), 5 deletions(-) diff --git a/CollapseLauncher/Classes/GameManagement/ImageBackground/ImageBackgroundManager.Loaders.cs b/CollapseLauncher/Classes/GameManagement/ImageBackground/ImageBackgroundManager.Loaders.cs index 95088f0ac6..3441f50acd 100644 --- a/CollapseLauncher/Classes/GameManagement/ImageBackground/ImageBackgroundManager.Loaders.cs +++ b/CollapseLauncher/Classes/GameManagement/ImageBackground/ImageBackgroundManager.Loaders.cs @@ -355,15 +355,37 @@ private void SpawnImageLayer(Uri? overlayFilePath, if (!forceReload && CurrentBackgroundElement != null && - _displayedContext != null && - context.Equals(_displayedContext)) + _displayedContext != null) { - return; + if (context.Equals(_displayedContext)) + { + return; + } + + if (CurrentBackgroundElement is LayeredBackgroundImage existingLayer && + IsSameLocalFile(existingLayer.BackgroundSource, backgroundFilePath) && + IsSameLocalFile(existingLayer.BackgroundStaticSource, backgroundStaticFilePath)) + { + return; + } } CurrentBackgroundElement = CreateLayerElement(overlayFilePath, backgroundFilePath, backgroundStaticFilePath, context, isVideo); } + private static bool IsSameLocalFile(object? currentSource, Uri? newFilePath) + { + if (newFilePath == null) return currentSource == null; + string? newPath = newFilePath.IsFile ? newFilePath.LocalPath : newFilePath.OriginalString; + string? currentPath = currentSource switch + { + Uri uri => uri.IsFile ? uri.LocalPath : uri.OriginalString, + string str => str, + _ => null + }; + return string.Equals(currentPath, newPath, StringComparison.OrdinalIgnoreCase); + } + private async Task RestoreSavedAccent(string cachedBgKey, Uri? fallbackSourceUri = null) { string? savedHex = LauncherConfig.GetAppConfigValue($"{cachedBgKey}-AccentColor").ToString(); @@ -511,8 +533,22 @@ private void LayerElementOnLoaded(LayeredBackgroundImage layerElement) foreach (UIElement oldElement in toRemove) { if (oldElement is LayeredBackgroundImage oldLayer) + { oldLayer.ImageLoaded -= LayerElementOnLoaded; - PresenterGrid.Children.Remove(oldElement); + + if (oldLayer.Tag == null) + { + oldLayer.Opacity = 0; + } + else + { + PresenterGrid.Children.Remove(oldElement); + } + } + else + { + PresenterGrid.Children.Remove(oldElement); + } } PresenterGrid.Background = null; diff --git a/CollapseLauncher/XAMLs/MainApp/MainPage.xaml b/CollapseLauncher/XAMLs/MainApp/MainPage.xaml index b09f1a0a91..12d2dcafd4 100644 --- a/CollapseLauncher/XAMLs/MainApp/MainPage.xaml +++ b/CollapseLauncher/XAMLs/MainApp/MainPage.xaml @@ -35,6 +35,9 @@ + + + diff --git a/CollapseLauncher/XAMLs/MainApp/MainPage.xaml.cs b/CollapseLauncher/XAMLs/MainApp/MainPage.xaml.cs index d1743d3976..979c2f79d8 100644 --- a/CollapseLauncher/XAMLs/MainApp/MainPage.xaml.cs +++ b/CollapseLauncher/XAMLs/MainApp/MainPage.xaml.cs @@ -118,7 +118,7 @@ private void SharedOnBackgroundLoadFailed() { if (!BackgroundPresenterGrid.Children.Contains(PlaceholderBackgroundLayer)) BackgroundPresenterGrid.Children.Insert(0, PlaceholderBackgroundLayer); - PlaceholderBackgroundLayer.Visibility = Visibility.Visible; + PlaceholderBackgroundLayer.Opacity = 1; }); }