Skip to content

Commit 3f19425

Browse files
committed
Push local changes (2)
+ Change focus-unfocus behaviour. Now the video can be still playing when window is unfocused, but visible. An option is added to the context menu to toggle this behavior. This option is disabled by default. + Make parallax effect disabled by default + Fix choppy transition on audio fade-in/out when the same event is executed in near time. + Use by-game default image when opening the launcher for the first time.
1 parent 9795755 commit 3f19425

18 files changed

Lines changed: 454 additions & 201 deletions

CollapseLauncher/Classes/GameManagement/ImageBackground/ImageBackgroundManager.Control.cs

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,4 @@
1-
using System;
2-
using System.Collections.Generic;
3-
using System.Text;
4-
using System.Threading;
1+
using System.Threading;
52
#pragma warning disable IDE0130
63

74
#nullable enable
@@ -13,14 +10,32 @@ public partial class ImageBackgroundManager
1310

1411
public void SetWindowFocusedEvent()
1512
{
16-
Play(false);
13+
if (GlobalKeepPlayVideoWhenWindowUnfocused)
14+
{
15+
CurrentBackgroundElement?.FadeInAudio();
16+
}
17+
else
18+
{
19+
Play(false);
20+
}
1721
}
1822

1923
public void SetWindowUnfocusedEvent()
2024
{
21-
Pause(false);
25+
if (GlobalKeepPlayVideoWhenWindowUnfocused)
26+
{
27+
CurrentBackgroundElement?.FadeOutAudio();
28+
}
29+
else
30+
{
31+
Pause(false);
32+
}
2233
}
2334

35+
public void SetWindowMinimizeEvent() => Pause(false);
36+
37+
public void SetWindowRestoreEvent() => Play(false);
38+
2439
public void Play(bool isUserRequest = true)
2540
{
2641
// Block play request for window event if paused by user

CollapseLauncher/Classes/GameManagement/ImageBackground/ImageBackgroundManager.StreamAndPath.cs

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -127,14 +127,15 @@ internal static string GetPlaceholderBackgroundImageFrom(PresetConfig? presetCon
127127
? PlaceholderBackgroundImagePngRelPath
128128
: PlaceholderBackgroundImageRelPath;
129129

130-
string relPath = presetConfig?.GameName switch
131-
{
132-
"Genshin Impact" => source[0],
133-
"Honkai Impact 3rd" => source[1],
134-
"Honkai: Star Rail" => source[2],
135-
"Zenless Zone Zero" => source[3],
136-
_ => GetRandomPlaceholderImage(usePngVersion)
137-
};
130+
string relPath = presetConfig?.GameName ??
131+
LauncherConfig.GetAppConfigValue("GameCategory").Value switch
132+
{
133+
"Genshin Impact" => source[0],
134+
"Honkai Impact 3rd" => source[1],
135+
"Honkai: Star Rail" => source[2],
136+
"Zenless Zone Zero" => source[3],
137+
_ => GetRandomPlaceholderImage(usePngVersion)
138+
};
138139

139140
return Path.Combine(LauncherConfig.AppExecutableDir, relPath);
140141
}

CollapseLauncher/Classes/GameManagement/ImageBackground/ImageBackgroundManager.cs

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,12 @@ static ImageBackgroundManager()
7676

7777
#endregion
7878

79+
#region Shared/Static Events
80+
81+
internal event Action<Color>? ColorAccentChanged;
82+
83+
#endregion
84+
7985
#region Shared/Static Properties and Fields
8086

8187
private const string GlobalCustomBackgroundConfigKey = "GlobalBg";
@@ -85,16 +91,14 @@ static ImageBackgroundManager()
8591
private const string GlobalBackgroundAudioVolumeConfigKey = "GlobalBackgroundAudioVolume";
8692
private const string GlobalBackgroundAudioEnabledConfigKey = "GlobalBackgroundAudioEnabled";
8793
private const string GlobalFfmpegCustomPathConfigKey = "GlobalFfmpegCustomPath";
94+
private const string GlobalKeepPlayVideoWhenWindowUnfocusedConfigKey = "GlobalKeepPlayVideoWhenWindowUnfocused";
8895

8996
public static bool IsUseFfmpeg => IsFfmpegAvailable(Directory.GetCurrentDirectory(), out _);
9097

9198
public static string? CustomFfmpegPath
9299
{
93100
get => LauncherConfig.GetAppConfigValue(GlobalFfmpegCustomPathConfigKey);
94-
set
95-
{
96-
LauncherConfig.SetAndSaveConfigValue(GlobalFfmpegCustomPathConfigKey, value);
97-
}
101+
set => LauncherConfig.SetAndSaveConfigValue(GlobalFfmpegCustomPathConfigKey, value);
98102
}
99103

100104
public string? GlobalCustomBackgroundImagePath
@@ -178,6 +182,16 @@ public bool GlobalBackgroundAudioEnabled
178182
}
179183
}
180184

185+
public bool GlobalKeepPlayVideoWhenWindowUnfocused
186+
{
187+
get => LauncherConfig.GetAppConfigValue(GlobalKeepPlayVideoWhenWindowUnfocusedConfigKey);
188+
set
189+
{
190+
LauncherConfig.SetAndSaveConfigValue(GlobalKeepPlayVideoWhenWindowUnfocusedConfigKey, value);
191+
OnPropertyChanged();
192+
}
193+
}
194+
181195
public bool IsBackgroundElevated
182196
{
183197
get;
@@ -210,12 +224,6 @@ public double SmokeOpacity
210224

211225
#endregion
212226

213-
#region Shared/Static Events
214-
215-
internal event Action<Color>? ColorAccentChanged;
216-
217-
#endregion
218-
219227
#region This Instance Properties
220228

221229
private string CurrentCustomBackgroundConfigKey { get; set; } = "";

CollapseLauncher/Classes/Helper/WindowUtility.cs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -538,13 +538,13 @@ private static IntPtr MainWndProc(IntPtr hwnd, uint msg, UIntPtr wParam, IntPtr
538538
}
539539
case SC_MINIMIZE:
540540
{
541-
ImageBackgroundManager.Shared.SetWindowUnfocusedEvent();
541+
ImageBackgroundManager.Shared.SetWindowMinimizeEvent();
542542
InnerLauncherConfig.m_homePage?.StopCarouselSlideshow();
543543
break;
544544
}
545545
case SC_RESTORE:
546546
{
547-
ImageBackgroundManager.Shared.SetWindowFocusedEvent();
547+
ImageBackgroundManager.Shared.SetWindowRestoreEvent();
548548
InnerLauncherConfig.m_homePage?.StartCarouselSlideshow();
549549
break;
550550
}
@@ -560,7 +560,6 @@ private static IntPtr MainWndProc(IntPtr hwnd, uint msg, UIntPtr wParam, IntPtr
560560
}
561561
else
562562
{
563-
ImageBackgroundManager.Shared.SetWindowFocusedEvent();
564563
InnerLauncherConfig.m_homePage?.StartCarouselSlideshow();
565564
}
566565
break;

CollapseLauncher/XAMLs/MainApp/MainPage.xaml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@
2020
Background="{ThemeResource WindowBackground}">
2121
<Grid x:Name="BackgroundPresenterGrid"
2222
x:FieldModifier="internal">
23-
<layeredBackgroundImage:LayeredBackgroundImage BackgroundSource="{x:Bind PlaceholderBackgroundImage}"/>
23+
<layeredBackgroundImage:LayeredBackgroundImage BackgroundSource="{x:Bind PlaceholderBackgroundImage}"
24+
MediaSourceCacheDir="{x:Bind PlaceholderDecodedCacheDir}"/>
2425
</Grid>
2526
<Border x:Name="NavViewPaneBackground"
2627
Width="48"

CollapseLauncher/XAMLs/MainApp/MainPage.xaml.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
using Hi3Helper.Plugin.Core.Update;
1717
using Hi3Helper.SentryHelper;
1818
using Hi3Helper.Shared.ClassStruct;
19+
using Hi3Helper.Shared.Region;
1920
using Microsoft.UI.Text;
2021
using Microsoft.UI.Xaml;
2122
using Microsoft.UI.Xaml.Controls;
@@ -69,7 +70,8 @@ public partial class MainPage : Page
6970

7071
internal static readonly List<string> PreviousTagString = [];
7172

72-
internal Uri PlaceholderBackgroundImage => new(ImageBackgroundManager.GetPlaceholderBackgroundImageFrom(CurrentGameProperty?.GamePreset, true));
73+
internal Uri PlaceholderBackgroundImage => new(ImageBackgroundManager.GetPlaceholderBackgroundImageFrom(CurrentGameProperty?.GamePreset));
74+
internal string PlaceholderDecodedCacheDir => AppGameImgFolder;
7375

7476
#endregion
7577

CollapseLauncher/XAMLs/MainApp/Pages/HomePage.xaml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,15 @@
119119
<FontIcon Glyph="&#xE8D6;"/>
120120
</ToggleMenuFlyoutItem.Icon>
121121
</ToggleMenuFlyoutItem>
122+
<ToggleMenuFlyoutItem DataContext="{x:Bind CurrentBackgroundManager, Mode=OneWay}"
123+
IsChecked="{Binding GlobalKeepPlayVideoWhenWindowUnfocused, Mode=TwoWay}"
124+
Text="{x:Bind helper:Locale.Lang._HomePage.BgContextMenu_KeepPlayVideoOnUnfocusText, Mode=OneWay}"
125+
ToolTipService.ToolTip="{x:Bind helper:Locale.Lang._HomePage.BgContextMenu_KeepPlayVideoOnUnfocusTooltip, Mode=OneWay}"
126+
ToolTipService.Placement="Left">
127+
<ToggleMenuFlyoutItem.Icon>
128+
<FontIcon Glyph="&#xE786;"/>
129+
</ToggleMenuFlyoutItem.Icon>
130+
</ToggleMenuFlyoutItem>
122131
<ToggleMenuFlyoutItem DataContext="{x:Bind CurrentBackgroundManager, Mode=OneWay}"
123132
IsChecked="{Binding GlobalIsBackgroundParallaxEffectEnabled, Mode=TwoWay}"
124133
Text="{x:Bind helper:Locale.Lang._HomePage.BgContextMenu_EnableParallaxText, Mode=OneWay}"

CollapseLauncher/XAMLs/Theme/CustomControls/LayeredBackgroundImage/LayeredBackgroundImage.Events.Control.cs

Lines changed: 180 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
1-
using Hi3Helper;
1+
using CollapseLauncher.Extension;
2+
using Hi3Helper;
23
using Microsoft.UI.Xaml;
34
using System;
5+
using System.Diagnostics.CodeAnalysis;
6+
using System.Runtime.CompilerServices;
47
using System.Threading;
58

69
#nullable enable
@@ -50,6 +53,182 @@ public void Pause()
5053

5154
#endregion
5255

56+
#region Fade In/Out Audio Control
57+
58+
public void FadeInAudio(double volumeFadeDurationMs = 500d,
59+
double volumeFadeResolutionMs = 10d,
60+
CancellationToken coopToken = default)
61+
{
62+
if (_videoPlayer == null!)
63+
{
64+
return;
65+
}
66+
67+
StartVideoPlayerVolumeFade(Math.Max(0, _videoPlayer.Volume * 100d),
68+
AudioVolume,
69+
volumeFadeDurationMs,
70+
volumeFadeResolutionMs,
71+
coopToken: coopToken);
72+
}
73+
74+
public void FadeOutAudio(Action? actionAfterPause = null,
75+
double volumeFadeDurationMs = 500d,
76+
double volumeFadeResolutionMs = 10d,
77+
CancellationToken coopToken = default)
78+
{
79+
if (_videoPlayer == null!)
80+
{
81+
return;
82+
}
83+
84+
StartVideoPlayerVolumeFade(Math.Min(AudioVolume, _videoPlayer.Volume * 100d),
85+
0,
86+
volumeFadeDurationMs,
87+
volumeFadeResolutionMs,
88+
true,
89+
onAfterAction: actionAfterPause,
90+
coopToken: coopToken);
91+
}
92+
93+
private void StartVideoPlayerVolumeFade(double fromValue,
94+
double toValue,
95+
double durationMs,
96+
double resolutionMs,
97+
bool fadeOut = false,
98+
Action? onAfterAction = null,
99+
CancellationToken coopToken = default)
100+
{
101+
new Thread(() =>
102+
StartVideoPlayerVolumeFadeCore(fromValue,
103+
toValue,
104+
durationMs,
105+
resolutionMs,
106+
fadeOut,
107+
onAfterAction,
108+
coopToken))
109+
{
110+
IsBackground = true
111+
}.Start();
112+
}
113+
114+
public double ActualVolume
115+
{
116+
get => (double)GetValue(ActualVolumeProperty);
117+
set => SetValue(ActualVolumeProperty, value);
118+
}
119+
120+
public static readonly DependencyProperty ActualVolumeProperty =
121+
DependencyProperty.Register(nameof(ActualVolume),
122+
typeof(double),
123+
typeof(LayeredBackgroundImage),
124+
new PropertyMetadata(0d, SetAudioVolumeBridge));
125+
126+
private static void SetAudioVolumeBridge(DependencyObject d, DependencyPropertyChangedEventArgs e)
127+
{
128+
LayeredBackgroundImage instance = (LayeredBackgroundImage)d;
129+
if (instance._videoPlayer == null!)
130+
{
131+
return;
132+
}
133+
134+
UIElementExtensions.RunFunctionFromUIThread(() => instance._videoPlayer.Volume = (double)e.NewValue);
135+
}
136+
137+
private void StartVideoPlayerVolumeFadeCore(double fromValue,
138+
double toValue,
139+
double durationMs,
140+
double resolutionMs,
141+
bool fadeOut = false,
142+
Action? onAfterAction = null,
143+
CancellationToken coopToken = default)
144+
{
145+
try
146+
{
147+
CancellationTokenSource currentCts = CancellationTokenSource.CreateLinkedTokenSource(coopToken);
148+
CancellationTokenSource? prevCts = Interlocked.Exchange(ref _videoPlayerFadeCts, currentCts);
149+
prevCts?.Cancel();
150+
prevCts?.Dispose();
151+
152+
CancellationToken currentToken = currentCts.Token;
153+
154+
fromValue /= 100d;
155+
toValue /= 100d;
156+
157+
double timeDelta = resolutionMs / durationMs;
158+
double step = Math.Max(fromValue, toValue) * timeDelta;
159+
step = fadeOut ? -step : step;
160+
161+
Unsafe.SkipInit(out Timer timer); // HACK: Ignore Error CS0165
162+
timer = new Timer(Impl, null, TimeSpan.Zero, TimeSpan.FromMilliseconds(resolutionMs));
163+
164+
return;
165+
166+
[SuppressMessage("ReSharper", "AccessToModifiedClosure")]
167+
void Impl(object? state)
168+
{
169+
try
170+
{
171+
// ReSharper disable once AccessToModifiedClosure
172+
if (timer == null! ||
173+
currentToken.IsCancellationRequested)
174+
{
175+
timer?.Dispose();
176+
onAfterAction?.Invoke();
177+
return;
178+
}
179+
180+
durationMs -= resolutionMs;
181+
fromValue += step;
182+
if (durationMs < 0)
183+
{
184+
if (_videoPlayer != null!)
185+
{
186+
TrySetVolume(toValue);
187+
}
188+
timer.Dispose();
189+
onAfterAction?.Invoke();
190+
return;
191+
}
192+
193+
if (_videoPlayer != null!)
194+
{
195+
TrySetVolume(fromValue);
196+
}
197+
}
198+
catch (Exception e)
199+
{
200+
timer.Dispose();
201+
onAfterAction?.Invoke();
202+
Logger.LogWriteLine($"[LayeredBackgroundImage::StartVideoPlayerVolumeFadeCore::Impl] {e}",
203+
LogType.Error,
204+
true);
205+
}
206+
}
207+
208+
void TrySetVolume(double value)
209+
{
210+
try
211+
{
212+
_videoPlayer.Volume = value;
213+
}
214+
catch (Exception e)
215+
{
216+
Logger.LogWriteLine($"[LayeredBackgroundImage::StartVideoPlayerVolumeFadeCore::TrySetVolume] {e}",
217+
LogType.Error,
218+
true);
219+
}
220+
}
221+
}
222+
catch (Exception e)
223+
{
224+
Logger.LogWriteLine($"[LayeredBackgroundImage::StartVideoPlayerVolumeFadeCore] {e}",
225+
LogType.Error,
226+
true);
227+
}
228+
}
229+
230+
#endregion
231+
53232
#region Duration Position Control
54233

55234
private static void MediaDurationPosition_OnChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)

0 commit comments

Comments
 (0)