diff --git a/Dalamud/Interface/ImGuiNotification/NotificationUtilities.cs b/Dalamud/Interface/ImGuiNotification/NotificationUtilities.cs index 631263f95..04f275e43 100644 --- a/Dalamud/Interface/ImGuiNotification/NotificationUtilities.cs +++ b/Dalamud/Interface/ImGuiNotification/NotificationUtilities.cs @@ -129,7 +129,7 @@ public static class NotificationUtilities plugin, plugin.Manifest, plugin.IsThirdParty, - out var texture) || texture is null) + out var texture, out _) || texture is null) { texture = dam.GetDalamudTextureWrap(DalamudAsset.DefaultIcon); } diff --git a/Dalamud/Interface/Internal/Windows/PluginImageCache.cs b/Dalamud/Interface/Internal/Windows/PluginImageCache.cs index 97744b1a7..644165044 100644 --- a/Dalamud/Interface/Internal/Windows/PluginImageCache.cs +++ b/Dalamud/Interface/Internal/Windows/PluginImageCache.cs @@ -55,7 +55,9 @@ internal class PluginImageCache : IInternalDisposableService private readonly Task downloadTask; private readonly Task loadTask; - private readonly ConcurrentDictionary pluginIconMap = new(); + private record LoadedIcon(IDalamudTextureWrap Texture, DateTime LoadedSince); + + private readonly ConcurrentDictionary pluginIconMap = new(); private readonly ConcurrentDictionary pluginImagesMap = new(); private readonly DalamudAssetManager dalamudAssetManager; @@ -153,7 +155,7 @@ internal class PluginImageCache : IInternalDisposableService foreach (var icon in this.pluginIconMap.Values) { - icon?.Dispose(); + icon?.Texture.Dispose(); } foreach (var images in this.pluginImagesMap.Values) @@ -185,10 +187,12 @@ internal class PluginImageCache : IInternalDisposableService /// The plugin manifest. /// If the plugin was third party sourced. /// Cached image textures, or an empty array. + /// The time the icon was successfully downloaded. /// True if an entry exists, may be null if currently downloading. - public bool TryGetIcon(LocalPlugin? plugin, IPluginManifest manifest, bool isThirdParty, out IDalamudTextureWrap? iconTexture) + public bool TryGetIcon(LocalPlugin? plugin, IPluginManifest manifest, bool isThirdParty, out IDalamudTextureWrap? iconTexture, out DateTime? loadedSince) { iconTexture = null; + loadedSince = null; if (manifest == null || manifest.InternalName == null) { @@ -198,7 +202,13 @@ internal class PluginImageCache : IInternalDisposableService if (!this.pluginIconMap.TryAdd(manifest.InternalName, null)) { - iconTexture = this.pluginIconMap[manifest.InternalName]; + var loaded = this.pluginIconMap[manifest.InternalName]; + if (loaded != null) + { + iconTexture = loaded.Texture; + loadedSince = loaded.LoadedSince; + } + return true; } @@ -207,8 +217,9 @@ internal class PluginImageCache : IInternalDisposableService { try { - this.pluginIconMap[manifest.InternalName] = - await this.DownloadPluginIconAsync(plugin, manifest, isThirdParty, requestedFrame); + var texture = await this.DownloadPluginIconAsync(plugin, manifest, isThirdParty, requestedFrame); + if (texture != null) + this.pluginIconMap[manifest.InternalName] = new LoadedIcon(texture, DateTime.Now); } catch (Exception ex) { diff --git a/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginInstallerWindow.cs b/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginInstallerWindow.cs index a9b692127..1fd9d0a31 100644 --- a/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginInstallerWindow.cs +++ b/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginInstallerWindow.cs @@ -1814,23 +1814,27 @@ internal class PluginInstallerWindow : Window, IDisposable if (ImGui.IsRectVisible(rectOffset + cursorBeforeImage, rectOffset + cursorBeforeImage + iconSize)) { var iconTex = this.imageCache.DefaultIcon; - var hasIcon = this.imageCache.TryGetIcon(plugin, manifest, isThirdParty, out var cachedIconTex); + var hasIcon = this.imageCache.TryGetIcon(plugin, manifest, isThirdParty, out var cachedIconTex, out var loadedSince); if (hasIcon && cachedIconTex != null) { iconTex = cachedIconTex; } + + const float fadeTime = 0.3f; + var iconAlpha = 1f; - if (pluginDisabled || installableOutdated) + if (loadedSince.HasValue) { - ImGui.PushStyleVar(ImGuiStyleVar.Alpha, 0.4f); + float EaseOutCubic(float t) => 1 - MathF.Pow(1 - t, 3); + + var secondsSinceLoad = (float)DateTime.Now.Subtract(loadedSince.Value).TotalSeconds; + var fadeTo = pluginDisabled || installableOutdated ? 0.4f : 1f; + iconAlpha = Math.Clamp(EaseOutCubic(Math.Min(secondsSinceLoad, fadeTime) / fadeTime) * fadeTo, 0, 1); } + ImGui.PushStyleVar(ImGuiStyleVar.Alpha, iconAlpha); ImGui.Image(iconTex.ImGuiHandle, iconSize); - - if (pluginDisabled || installableOutdated) - { - ImGui.PopStyleVar(); - } + ImGui.PopStyleVar(); ImGui.SameLine(); ImGui.SetCursorPos(cursorBeforeImage); @@ -2019,7 +2023,7 @@ internal class PluginInstallerWindow : Window, IDisposable if (log is PluginChangelogEntry pluginLog) { icon = this.imageCache.DefaultIcon; - var hasIcon = this.imageCache.TryGetIcon(pluginLog.Plugin, pluginLog.Plugin.Manifest, pluginLog.Plugin.IsThirdParty, out var cachedIconTex); + var hasIcon = this.imageCache.TryGetIcon(pluginLog.Plugin, pluginLog.Plugin.Manifest, pluginLog.Plugin.IsThirdParty, out var cachedIconTex, out _); if (hasIcon && cachedIconTex != null) { icon = cachedIconTex; diff --git a/Dalamud/Interface/Internal/Windows/PluginInstaller/ProfileManagerWidget.cs b/Dalamud/Interface/Internal/Windows/PluginInstaller/ProfileManagerWidget.cs index 857002771..9f3196928 100644 --- a/Dalamud/Interface/Internal/Windows/PluginInstaller/ProfileManagerWidget.cs +++ b/Dalamud/Interface/Internal/Windows/PluginInstaller/ProfileManagerWidget.cs @@ -437,7 +437,7 @@ internal class ProfileManagerWidget if (pmPlugin != null) { var cursorBeforeIcon = ImGui.GetCursorPos(); - pic.TryGetIcon(pmPlugin, pmPlugin.Manifest, pmPlugin.IsThirdParty, out var icon); + pic.TryGetIcon(pmPlugin, pmPlugin.Manifest, pmPlugin.IsThirdParty, out var icon, out _); icon ??= pic.DefaultIcon; ImGui.Image(icon.ImGuiHandle, new Vector2(pluginLineHeight));