diff --git a/CollapseLauncher/App.xaml b/CollapseLauncher/App.xaml
index 066e3cad2..d98d833d1 100644
--- a/CollapseLauncher/App.xaml
+++ b/CollapseLauncher/App.xaml
@@ -31,6 +31,14 @@
+
+
+
+
+
+
+
+
diff --git a/CollapseLauncher/Classes/AnimatedVisuals/Lottie/BindableThemeChangeAnimation.cs b/CollapseLauncher/Classes/AnimatedVisuals/Lottie/BindableThemeChangeAnimation.cs
new file mode 100644
index 000000000..f225737a6
--- /dev/null
+++ b/CollapseLauncher/Classes/AnimatedVisuals/Lottie/BindableThemeChangeAnimation.cs
@@ -0,0 +1,28 @@
+using Microsoft.UI.Xaml;
+using Microsoft.UI.Xaml.Media;
+
+#pragma warning disable IDE0130
+
+#nullable enable
+namespace CollapseLauncher.AnimatedVisuals.Lottie;
+
+public abstract class BindableThemeChangeAnimation : DependencyObject
+{
+ public Brush Foreground
+ {
+ get => (Brush)GetValue(ForegroundProperty);
+ set => SetValue(ForegroundProperty, value);
+ }
+
+ ///
+ /// Dependency property for Foreground.
+ ///
+ public static readonly DependencyProperty ForegroundProperty =
+ DependencyProperty.Register(nameof(Foreground), typeof(Brush), typeof(BindableThemeChangeAnimation),
+ new PropertyMetadata(null!, OnForegroundChanged));
+
+ protected static void OnForegroundChanged(DependencyObject d, DependencyPropertyChangedEventArgs args)
+ => ((BindableThemeChangeAnimation)d).OnForegroundChanged(args.NewValue as Brush);
+
+ protected abstract void OnForegroundChanged(Brush? brush);
+}
diff --git a/CollapseLauncher/Classes/AnimatedVisuals/Lottie/DownloadIcon.cs b/CollapseLauncher/Classes/AnimatedVisuals/Lottie/DownloadIcon.cs
index 59d0c86ba..67b789ebb 100644
--- a/CollapseLauncher/Classes/AnimatedVisuals/Lottie/DownloadIcon.cs
+++ b/CollapseLauncher/Classes/AnimatedVisuals/Lottie/DownloadIcon.cs
@@ -3,13 +3,13 @@
// This code was generated by a tool.
//
// LottieGen version:
-// 8.0.280225.1+7cd366a738
+// 8.2.250604.1+b02a3ee244
//
// Command:
-// LottieGen -Language CSharp -Namespace CollapseLauncher.AnimatedVisuals.Lottie -Public -WinUIVersion 3.0 -InputFile DownloadIcon.lottie
+// LottieGen -GenerateColorBindings -GenerateDependencyObject -Language CSharp -Namespace CollapseLauncher.AnimatedVisuals.Lottie -Public -WinUIVersion 3.0 -InputFile DownloadIcon.json
//
// Input file:
-// DownloadIcon.lottie (1623 bytes created 23:20+07:00 Jun 1 2024)
+// DownloadIcon.json (7800 bytes created 16:59+07:00 Jan 4 2026)
//
// LottieGen source:
// http://aka.ms/Lottie
@@ -21,16 +21,16 @@
// ____________________________________
// | Object stats | Count |
// |__________________________|_______|
-// | All CompositionObjects | 118 |
+// | All CompositionObjects | 121 |
// |--------------------------+-------|
-// | Expression animators | 8 |
+// | Expression animators | 9 |
// | KeyFrame animators | 15 |
-// | Reference parameters | 8 |
-// | Expression operations | 6 |
+// | Reference parameters | 9 |
+// | Expression operations | 10 |
// |--------------------------+-------|
-// | Animated brushes | - |
+// | Animated brushes | 1 |
// | Animated gradient stops | - |
-// | ExpressionAnimations | 8 |
+// | ExpressionAnimations | 9 |
// | PathKeyFrameAnimations | 1 |
// |--------------------------+-------|
// | ContainerVisuals | 5 |
@@ -48,6 +48,8 @@
using Microsoft.Graphics.Canvas.Effects;
using Microsoft.Graphics.Canvas.Geometry;
using Microsoft.UI.Composition;
+using Microsoft.UI.Xaml;
+using Microsoft.UI.Xaml.Media;
using System;
using System.Collections.Generic;
using System.Numerics;
@@ -59,13 +61,67 @@ namespace CollapseLauncher.AnimatedVisuals.Lottie
// Frame rate: 60 fps
// Frame count: 300
// Duration: 5000.0 mS
- sealed partial class DownloadIcon
- : Microsoft.UI.Xaml.Controls.IAnimatedVisualSource
+ public sealed partial class DownloadIcon
+ : BindableThemeChangeAnimation
+ , Microsoft.UI.Xaml.Controls.IAnimatedVisualSource
, Microsoft.UI.Xaml.Controls.IAnimatedVisualSource2
{
+ protected override void OnForegroundChanged(Brush brush)
+ {
+ if (brush is SolidColorBrush colorBrush)
+ {
+ Color_FFFFFF = colorBrush.Color;
+ return;
+ }
+
+ if (brush is AcrylicBrush acrylicBrush)
+ {
+ Color_FFFFFF = acrylicBrush.TintColor;
+ return;
+ }
+ }
+
+ private static Color GetForegroundColor(bool isLightTheme) =>
+ isLightTheme
+ ? Color.FromArgb(0xFF, 0x00, 0x00, 0x00)
+ : Color.FromArgb(0xFF, 0xFF, 0xFF, 0xFF);
+
// Animation duration: 5.000 seconds.
internal const long c_durationTicks = 50000000;
+ CompositionPropertySet _themeProperties;
+
+ ///
+ /// Dependency property for Color_FFFFFF.
+ ///
+ public static readonly DependencyProperty Color_FFFFFFProperty =
+ DependencyProperty.Register("Color_FFFFFF", typeof(Color), typeof(DownloadIcon),
+ new PropertyMetadata(GetForegroundColor(InnerLauncherConfig.IsAppThemeLight), OnColor_FFFFFFChanged));
+
+ // Theme properties.
+ public Color Color_FFFFFF
+ {
+ get => (Color)GetValue(Color_FFFFFFProperty);
+ set => SetValue(Color_FFFFFFProperty, value);
+ }
+
+ static Vector4 ColorAsVector4(Color color) => new Vector4(color.R, color.G, color.B, color.A);
+
+ static void OnColor_FFFFFFChanged(DependencyObject d, DependencyPropertyChangedEventArgs args)
+ {
+ ((DownloadIcon)d)._themeProperties?.InsertVector4("Color_FFFFFF", ColorAsVector4((Color)(Color)args.NewValue));
+ }
+
+ CompositionPropertySet EnsureThemeProperties(Compositor compositor)
+ {
+ if (_themeProperties == null)
+ {
+ _themeProperties = compositor.CreatePropertySet();
+ _themeProperties.InsertVector4("Color_FFFFFF", ColorAsVector4((Color)Color_FFFFFF));
+ }
+ return _themeProperties;
+ }
+
public Microsoft.UI.Xaml.Controls.IAnimatedVisual TryCreateAnimatedVisual(Compositor compositor)
{
object ignored = null;
@@ -75,10 +131,12 @@ public Microsoft.UI.Xaml.Controls.IAnimatedVisual TryCreateAnimatedVisual(Compos
public Microsoft.UI.Xaml.Controls.IAnimatedVisual TryCreateAnimatedVisual(Compositor compositor, out object diagnostics)
{
diagnostics = null;
+ EnsureThemeProperties(compositor);
var res =
new DownloadIcon_AnimatedVisual(
- compositor
+ compositor,
+ _themeProperties
);
res.CreateAnimations();
return res;
@@ -122,6 +180,19 @@ public double FrameToProgress(double frameNumber)
///
public void SetColorProperty(string propertyName, Color value)
{
+ if (propertyName == "Color_FFFFFF")
+ {
+ Color_FFFFFF = value;
+ }
+ else
+ {
+ return;
+ }
+
+ if (_themeProperties != null)
+ {
+ _themeProperties.InsertVector4(propertyName, ColorAsVector4(value));
+ }
}
///
@@ -139,8 +210,9 @@ sealed partial class DownloadIcon_AnimatedVisual
const long c_durationTicks = 50000000;
readonly Compositor _c;
readonly ExpressionAnimation _reusableExpressionAnimation;
+ readonly CompositionPropertySet _themeProperties;
AnimationController _animationController_0;
- CompositionColorBrush _colorBrush_White;
+ CompositionColorBrush _themeColor_Color_FFFFFF;
CompositionContainerShape _containerShape_0;
CompositionContainerShape _containerShape_1;
CompositionContainerShape _containerShape_2;
@@ -155,7 +227,7 @@ sealed partial class DownloadIcon_AnimatedVisual
CubicBezierEasingFunction _cubicBezierEasingFunction_0;
CubicBezierEasingFunction _cubicBezierEasingFunction_1;
ScalarKeyFrameAnimation _opacityScalarAnimation_1_to_0;
- ScalarKeyFrameAnimation _positionYScalarAnimation_1025_to_2350;
+ ScalarKeyFrameAnimation _positionYScalarAnimation_128p125_to_293p75;
ScalarKeyFrameAnimation _rotationAngleInDegreesScalarAnimation_0_to_0_0;
ShapeVisual _shapeVisual_2;
StepEasingFunction _holdThenStepEasingFunction;
@@ -245,11 +317,11 @@ CanvasGeometry Geometry_2()
CanvasGeometry result;
using (var builder = new CanvasPathBuilder(null))
{
- builder.BeginFigure(new Vector2(2316F, -196F));
- builder.AddCubicBezier(new Vector2(2316F, -196F), new Vector2(-336F, -196F), new Vector2(-336F, -196F));
- builder.AddCubicBezier(new Vector2(-336F, -196F), new Vector2(-336F, 2132F), new Vector2(-336F, 2132F));
- builder.AddCubicBezier(new Vector2(-336F, 2132F), new Vector2(2316F, 2132F), new Vector2(2316F, 2132F));
- builder.AddCubicBezier(new Vector2(2316F, 2132F), new Vector2(2316F, -196F), new Vector2(2316F, -196F));
+ builder.BeginFigure(new Vector2(289.5F, -24.5F));
+ builder.AddCubicBezier(new Vector2(289.5F, -24.5F), new Vector2(-42F, -24.5F), new Vector2(-42F, -24.5F));
+ builder.AddCubicBezier(new Vector2(-42F, -24.5F), new Vector2(-42F, 266.5F), new Vector2(-42F, 266.5F));
+ builder.AddCubicBezier(new Vector2(-42F, 266.5F), new Vector2(289.5F, 266.5F), new Vector2(289.5F, 266.5F));
+ builder.AddCubicBezier(new Vector2(289.5F, 266.5F), new Vector2(289.5F, -24.5F), new Vector2(289.5F, -24.5F));
builder.EndFigure(CanvasFigureLoop.Closed);
result = CanvasGeometry.CreatePath(builder);
}
@@ -264,11 +336,11 @@ CanvasGeometry Geometry_3()
CanvasGeometry result;
using (var builder = new CanvasPathBuilder(null))
{
- builder.BeginFigure(new Vector2(2316F, -196F));
- builder.AddCubicBezier(new Vector2(2316F, -196F), new Vector2(-336F, -196F), new Vector2(-336F, -196F));
- builder.AddCubicBezier(new Vector2(-336F, -196F), new Vector2(-336F, 1532F), new Vector2(-336F, 1532F));
- builder.AddCubicBezier(new Vector2(-336F, 1532F), new Vector2(2316F, 1532F), new Vector2(2316F, 1532F));
- builder.AddCubicBezier(new Vector2(2316F, 1532F), new Vector2(2316F, -196F), new Vector2(2316F, -196F));
+ builder.BeginFigure(new Vector2(289.5F, -24.5F));
+ builder.AddCubicBezier(new Vector2(289.5F, -24.5F), new Vector2(-42F, -24.5F), new Vector2(-42F, -24.5F));
+ builder.AddCubicBezier(new Vector2(-42F, -24.5F), new Vector2(-42F, 191.5F), new Vector2(-42F, 191.5F));
+ builder.AddCubicBezier(new Vector2(-42F, 191.5F), new Vector2(289.5F, 191.5F), new Vector2(289.5F, 191.5F));
+ builder.AddCubicBezier(new Vector2(289.5F, 191.5F), new Vector2(289.5F, -24.5F), new Vector2(289.5F, -24.5F));
builder.EndFigure(CanvasFigureLoop.Closed);
result = CanvasGeometry.CreatePath(builder);
}
@@ -283,11 +355,11 @@ CanvasGeometry Geometry_4()
CanvasGeometry result;
using (var builder = new CanvasPathBuilder(null))
{
- builder.BeginFigure(new Vector2(2316F, -196F));
- builder.AddCubicBezier(new Vector2(2316F, -196F), new Vector2(-336F, -196F), new Vector2(-336F, -196F));
- builder.AddCubicBezier(new Vector2(-336F, -196F), new Vector2(-336F, 1432F), new Vector2(-336F, 1432F));
- builder.AddCubicBezier(new Vector2(-336F, 1432F), new Vector2(2316F, 1432F), new Vector2(2316F, 1432F));
- builder.AddCubicBezier(new Vector2(2316F, 1432F), new Vector2(2316F, -196F), new Vector2(2316F, -196F));
+ builder.BeginFigure(new Vector2(289.5F, -24.5F));
+ builder.AddCubicBezier(new Vector2(289.5F, -24.5F), new Vector2(-42F, -24.5F), new Vector2(-42F, -24.5F));
+ builder.AddCubicBezier(new Vector2(-42F, -24.5F), new Vector2(-42F, 179F), new Vector2(-42F, 179F));
+ builder.AddCubicBezier(new Vector2(-42F, 179F), new Vector2(289.5F, 179F), new Vector2(289.5F, 179F));
+ builder.AddCubicBezier(new Vector2(289.5F, 179F), new Vector2(289.5F, -24.5F), new Vector2(289.5F, -24.5F));
builder.EndFigure(CanvasFigureLoop.Closed);
result = CanvasGeometry.CreatePath(builder);
}
@@ -314,11 +386,13 @@ CompositionColorBrush ColorBrush_Black()
return _c.CreateColorBrush(Color.FromArgb(0xFF, 0x00, 0x00, 0x00));
}
- CompositionColorBrush ColorBrush_White()
+ // Color bound to theme property value: Color_FFFFFF
+ CompositionColorBrush ThemeColor_Color_FFFFFF()
{
- return (_colorBrush_White == null)
- ? _colorBrush_White = _c.CreateColorBrush(InnerLauncherConfig.IsAppThemeLight ? Color.FromArgb(0xFF, 0xFF, 0xFF, 0xFF) : Color.FromArgb(0xFF, 0x00, 0x00, 0x00))
- : _colorBrush_White;
+ if (_themeColor_Color_FFFFFF != null) { return _themeColor_Color_FFFFFF; }
+ var result = _themeColor_Color_FFFFFF = _c.CreateColorBrush();
+ BindProperty(_themeColor_Color_FFFFFF, "Color", "ColorRGB(_theme.Color_FFFFFF.W,_theme.Color_FFFFFF.X,_theme.Color_FFFFFF.Y,_theme.Color_FFFFFF.Z)", "_theme", _themeProperties);
+ return result;
}
CompositionContainerShape ContainerShape_0()
@@ -326,7 +400,9 @@ CompositionContainerShape ContainerShape_0()
if (_containerShape_0 != null) { return _containerShape_0; }
var result = _containerShape_0 = _c.CreateContainerShape();
var propertySet = result.Properties;
- propertySet.InsertVector2("Position", new Vector2(1024F, 1025F));
+ propertySet.InsertVector2("Position", new Vector2(128F, 128.125F));
+ result.CenterPoint = new Vector2(50F, 50F);
+ result.Scale = new Vector2(0.125F, 0.125F);
// Transforms: Shape Layer 1 Offset:<50, 50>
result.Shapes.Add(SpriteShape_0());
BindProperty(_containerShape_0, "Offset", "Vector2(my.Position.X-50,my.Position.Y-50)", "my", _containerShape_0);
@@ -338,7 +414,9 @@ CompositionContainerShape ContainerShape_1()
if (_containerShape_1 != null) { return _containerShape_1; }
var result = _containerShape_1 = _c.CreateContainerShape();
var propertySet = result.Properties;
- propertySet.InsertVector2("Position", new Vector2(1024F, 1025F));
+ propertySet.InsertVector2("Position", new Vector2(128F, 128.125F));
+ result.CenterPoint = new Vector2(50F, 50F);
+ result.Scale = new Vector2(0.125F, 0.125F);
// Transforms: Shape Layer 2 Offset:<50, 50>
result.Shapes.Add(SpriteShape_1());
BindProperty(_containerShape_1, "Offset", "Vector2(my.Position.X-50,my.Position.Y-50)", "my", _containerShape_1);
@@ -350,8 +428,9 @@ CompositionContainerShape ContainerShape_2()
if (_containerShape_2 != null) { return _containerShape_2; }
var result = _containerShape_2 = _c.CreateContainerShape();
var propertySet = result.Properties;
- propertySet.InsertVector2("Position", new Vector2(1024F, 1264F));
+ propertySet.InsertVector2("Position", new Vector2(128F, 179.125F));
result.CenterPoint = new Vector2(4F, 344F);
+ result.Scale = new Vector2(0.125F, 0.125F);
// ShapeGroup: Shape 1
result.Shapes.Add(SpriteShape_3());
BindProperty(_containerShape_2, "Offset", "Vector2(my.Position.X-4,my.Position.Y-344)", "my", _containerShape_2);
@@ -433,7 +512,7 @@ CompositionSpriteShape SpriteShape_0()
{
// Offset:<50, 50>
var result = CreateSpriteShape(PathGeometry_0(), new Matrix3x2(1F, 0F, 0F, 1F, 50F, 50F));;
- result.StrokeBrush = ColorBrush_White();
+ result.StrokeBrush = ThemeColor_Color_FFFFFF();
result.StrokeDashCap = CompositionStrokeCap.Round;
result.StrokeStartCap = CompositionStrokeCap.Round;
result.StrokeEndCap = CompositionStrokeCap.Round;
@@ -448,7 +527,7 @@ CompositionSpriteShape SpriteShape_1()
{
// Offset:<50, 50>
var result = CreateSpriteShape(PathGeometry_1(), new Matrix3x2(1F, 0F, 0F, 1F, 50F, 50F));;
- result.StrokeBrush = ColorBrush_White();
+ result.StrokeBrush = ThemeColor_Color_FFFFFF();
result.StrokeDashCap = CompositionStrokeCap.Round;
result.StrokeStartCap = CompositionStrokeCap.Round;
result.StrokeEndCap = CompositionStrokeCap.Round;
@@ -464,7 +543,7 @@ CompositionSpriteShape SpriteShape_2()
{
if (_spriteShape_2 != null) { return _spriteShape_2; }
var result = _spriteShape_2 = _c.CreateSpriteShape(PathGeometry_2());
- result.CenterPoint = new Vector2(1024F, 1024F);
+ result.CenterPoint = new Vector2(128F, 128F);
result.FillBrush = ColorBrush_Black();
return result;
}
@@ -473,7 +552,7 @@ CompositionSpriteShape SpriteShape_2()
CompositionSpriteShape SpriteShape_3()
{
var result = _c.CreateSpriteShape(PathGeometry_3());
- result.StrokeBrush = ColorBrush_White();
+ result.StrokeBrush = ThemeColor_Color_FFFFFF();
result.StrokeDashCap = CompositionStrokeCap.Round;
result.StrokeStartCap = CompositionStrokeCap.Round;
result.StrokeEndCap = CompositionStrokeCap.Round;
@@ -500,7 +579,7 @@ CompositionVisualSurface VisualSurface_0()
{
var result = _c.CreateVisualSurface();
result.SourceVisual = ContainerVisual_0();
- result.SourceSize = new Vector2(2048F, 2048F);
+ result.SourceSize = new Vector2(256F, 256F);
return result;
}
@@ -509,7 +588,7 @@ CompositionVisualSurface VisualSurface_1()
{
var result = _c.CreateVisualSurface();
result.SourceVisual = ContainerVisual_3();
- result.SourceSize = new Vector2(2048F, 2048F);
+ result.SourceSize = new Vector2(256F, 256F);
return result;
}
@@ -529,7 +608,7 @@ ContainerVisual ContainerVisual_1()
{
if (_containerVisual_1 != null) { return _containerVisual_1; }
var result = _containerVisual_1 = _c.CreateContainerVisual();
- result.CenterPoint = new Vector3(1024F, 1024F, 0F);
+ result.CenterPoint = new Vector3(128F, 128F, 0F);
result.Scale = new Vector3(1F, 1F, 0F);
// Transforms for DownloadIcon
result.Children.InsertAtTop(ContainerVisual_2());
@@ -543,7 +622,7 @@ ContainerVisual ContainerVisual_2()
{
var result = _c.CreateContainerVisual();
result.Clip = InsetClip_0();
- result.Size = new Vector2(2048F, 2048F);
+ result.Size = new Vector2(256F, 256F);
// Layer aggregator
result.Children.InsertAtTop(ShapeVisual_0());
return result;
@@ -627,43 +706,43 @@ ScalarKeyFrameAnimation OpacityScalarAnimation_1_to_0()
}
// Position.Y
- ScalarKeyFrameAnimation PositionYScalarAnimation_1025_to_2350()
+ ScalarKeyFrameAnimation PositionYScalarAnimation_128p125_to_293p75()
{
// Frame 0.
- if (_positionYScalarAnimation_1025_to_2350 != null) { return _positionYScalarAnimation_1025_to_2350; }
- var result = _positionYScalarAnimation_1025_to_2350 = CreateScalarKeyFrameAnimation(0F, 1025F, StepThenHoldEasingFunction());
+ if (_positionYScalarAnimation_128p125_to_293p75 != null) { return _positionYScalarAnimation_128p125_to_293p75; }
+ var result = _positionYScalarAnimation_128p125_to_293p75 = CreateScalarKeyFrameAnimation(0F, 128.125F, StepThenHoldEasingFunction());
// Frame 50.
- result.InsertKeyFrame(0.166666672F, 1025F, HoldThenStepEasingFunction());
+ result.InsertKeyFrame(0.166666672F, 128.125F, HoldThenStepEasingFunction());
// Frame 90.
- result.InsertKeyFrame(0.300000012F, 1009F, CubicBezierEasingFunction_0());
+ result.InsertKeyFrame(0.300000012F, 126.125F, CubicBezierEasingFunction_0());
// Frame 165.
- result.InsertKeyFrame(0.550000012F, 1009F, CubicBezierEasingFunction_0());
+ result.InsertKeyFrame(0.550000012F, 126.125F, CubicBezierEasingFunction_0());
// Frame 200.
- result.InsertKeyFrame(0.666666687F, 876F, CubicBezierEasingFunction_0());
+ result.InsertKeyFrame(0.666666687F, 109.5F, CubicBezierEasingFunction_0());
// Frame 210.
- result.InsertKeyFrame(0.699999988F, 876F, _c.CreateCubicBezierEasingFunction(new Vector2(0.333000004F, 0F), new Vector2(0.167999998F, 1F)));
+ result.InsertKeyFrame(0.699999988F, 109.5F, _c.CreateCubicBezierEasingFunction(new Vector2(0.333000004F, 0F), new Vector2(0.167999998F, 1F)));
// Frame 255.
- result.InsertKeyFrame(0.850000024F, 2350F, CubicBezierEasingFunction_0());
+ result.InsertKeyFrame(0.850000024F, 293.75F, CubicBezierEasingFunction_0());
return result;
}
// Position.Y
- ScalarKeyFrameAnimation PositionYScalarAnimation_1264_to_1433()
+ ScalarKeyFrameAnimation PositionYScalarAnimation_179p125_to_179p125()
{
// Frame 0.
- var result = CreateScalarKeyFrameAnimation(0F, 1264F, StepThenHoldEasingFunction());
+ var result = CreateScalarKeyFrameAnimation(0F, 179.125F, StepThenHoldEasingFunction());
// Frame 30.
- result.InsertKeyFrame(0.100000001F, 1264F, HoldThenStepEasingFunction());
+ result.InsertKeyFrame(0.100000001F, 179.125F, HoldThenStepEasingFunction());
// Frame 70.
- result.InsertKeyFrame(0.233333334F, 1433F, CubicBezierEasingFunction_0());
+ result.InsertKeyFrame(0.233333334F, 179.125F, CubicBezierEasingFunction_0());
// Frame 167.
- result.InsertKeyFrame(0.556666672F, 1433F, _c.CreateCubicBezierEasingFunction(new Vector2(0.157000005F, 0F), new Vector2(0.0299999993F, 1F)));
+ result.InsertKeyFrame(0.556666672F, 179.125F, _c.CreateCubicBezierEasingFunction(new Vector2(0.157000005F, 0F), new Vector2(0.0299999993F, 1F)));
// Frame 188.
- result.InsertKeyFrame(0.626666665F, 1577F, CubicBezierEasingFunction_0());
+ result.InsertKeyFrame(0.626666665F, 197.125F, CubicBezierEasingFunction_0());
// Frame 210.
- result.InsertKeyFrame(0.699999988F, 1577F, CubicBezierEasingFunction_0());
+ result.InsertKeyFrame(0.699999988F, 197.125F, CubicBezierEasingFunction_0());
// Frame 240.
- result.InsertKeyFrame(0.800000012F, 1433F, CubicBezierEasingFunction_0());
+ result.InsertKeyFrame(0.800000012F, 179.125F, CubicBezierEasingFunction_0());
return result;
}
@@ -785,7 +864,7 @@ ScalarKeyFrameAnimation TStartScalarAnimation_0p855_to_0()
ShapeVisual ShapeVisual_0()
{
var result = _c.CreateShapeVisual();
- result.Size = new Vector2(2048F, 2048F);
+ result.Size = new Vector2(256F, 256F);
var shapes = result.Shapes;
shapes.Add(ContainerShape_0());
shapes.Add(ContainerShape_1());
@@ -797,7 +876,7 @@ ShapeVisual ShapeVisual_0()
ShapeVisual ShapeVisual_1()
{
var result = _c.CreateShapeVisual();
- result.Size = new Vector2(2048F, 2048F);
+ result.Size = new Vector2(256F, 256F);
result.Shapes.Add(SpriteShape_2());
return result;
}
@@ -807,7 +886,7 @@ ShapeVisual ShapeVisual_2()
{
if (_shapeVisual_2 != null) { return _shapeVisual_2; }
var result = _shapeVisual_2 = _c.CreateShapeVisual();
- result.Size = new Vector2(2048F, 2048F);
+ result.Size = new Vector2(256F, 256F);
result.Shapes.Add(ContainerShape_2());
return result;
}
@@ -816,7 +895,7 @@ ShapeVisual ShapeVisual_2()
SpriteVisual SpriteVisual_0()
{
var result = _c.CreateSpriteVisual();
- result.Size = new Vector2(2048F, 2048F);
+ result.Size = new Vector2(256F, 256F);
result.Brush = EffectBrush();
return result;
}
@@ -838,25 +917,27 @@ StepEasingFunction StepThenHoldEasingFunction()
}
internal DownloadIcon_AnimatedVisual(
- Compositor compositor
+ Compositor compositor,
+ CompositionPropertySet themeProperties
)
{
_c = compositor;
+ _themeProperties = themeProperties;
_reusableExpressionAnimation = compositor.CreateExpressionAnimation();
Root();
}
public Visual RootVisual => _root;
public TimeSpan Duration => TimeSpan.FromTicks(c_durationTicks);
- public Vector2 Size => new Vector2(2048F, 2048F);
+ public Vector2 Size => new Vector2(256F, 256F);
void IDisposable.Dispose() => _root?.Dispose();
public void CreateAnimations()
{
- _containerShape_0.Properties.StartAnimation("Position.Y", PositionYScalarAnimation_1025_to_2350(), AnimationController_0());
- _containerShape_1.Properties.StartAnimation("Position.Y", PositionYScalarAnimation_1025_to_2350(), AnimationController_0());
+ _containerShape_0.Properties.StartAnimation("Position.Y", PositionYScalarAnimation_128p125_to_293p75(), AnimationController_0());
+ _containerShape_1.Properties.StartAnimation("Position.Y", PositionYScalarAnimation_128p125_to_293p75(), AnimationController_0());
_containerShape_2.StartAnimation("RotationAngleInDegrees", RotationAngleInDegreesScalarAnimation_0_to_0_1(), AnimationController_0());
- _containerShape_2.Properties.StartAnimation("Position.Y", PositionYScalarAnimation_1264_to_1433(), AnimationController_0());
+ _containerShape_2.Properties.StartAnimation("Position.Y", PositionYScalarAnimation_179p125_to_179p125(), AnimationController_0());
_pathGeometry_0.StartAnimation("TStart", TStartScalarAnimation_0p855_to_0(), AnimationController_0());
_pathGeometry_0.StartAnimation("TEnd", TEndScalarAnimation_0p855_to_0p1(), AnimationController_0());
_pathGeometry_1.StartAnimation("TrimStart", TrimStartScalarAnimation_0p585_to_0(), AnimationController_0());
diff --git a/CollapseLauncher/Classes/AnimatedVisuals/Lottie/DownloadIcon.json b/CollapseLauncher/Classes/AnimatedVisuals/Lottie/DownloadIcon.json
new file mode 100644
index 000000000..2810c9899
--- /dev/null
+++ b/CollapseLauncher/Classes/AnimatedVisuals/Lottie/DownloadIcon.json
@@ -0,0 +1 @@
+{"v":"5.12.2","fr":60,"ip":0,"op":300,"w":256,"h":256,"nm":"DownloadIconMasterComp","ddd":0,"assets":[{"id":"comp_0","nm":"DownloadIcon","fr":60,"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Shape Layer 2","parent":3,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[50,50,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[360.517,-180.5],[0,180.017],[-360.517,-180.5]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":128,"ix":5},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":53,"s":[58.5]},{"t":83,"s":[0]}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":53,"s":[58.5]},{"t":83,"s":[58.5]}],"ix":2},"o":{"a":1,"k":[{"i":{"x":[0],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":53,"s":[-101.6]},{"t":83,"s":[0]}],"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":0,"op":300,"st":-7,"ct":1,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Shape Layer 1","parent":3,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[50,50,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,-459.501],[-459.501,0],[0,459.501],[0,0]],"o":[[0,0],[-459.501,0],[0,459.501],[459.501,0],[0,-459.501],[0,0]],"v":[[0,116],[0,-832],[-832,0],[0,832],[832,0],[515,-688]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":128,"ix":5},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":0,"s":[85.5]},{"t":60,"s":[0]}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[85.5]},{"t":80,"s":[10]}],"ix":2},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":30,"s":[0]},{"t":60,"s":[0]}],"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":0,"op":300,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":3,"ty":3,"nm":"Null 1","sr":1,"ks":{"o":{"a":0,"k":0,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"s":true,"x":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":50,"s":[128]},{"t":90,"s":[128]}],"ix":3},"y":{"a":1,"k":[{"i":{"x":[0],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":50,"s":[128.125]},{"i":{"x":[0],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":90,"s":[126.125]},{"i":{"x":[0],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":165,"s":[126.125]},{"i":{"x":[0.168],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":200,"s":[109.5]},{"i":{"x":[0],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":210,"s":[109.5]},{"t":255,"s":[293.75]}],"ix":4}},"a":{"a":0,"k":[50,50,0],"ix":1,"l":2},"s":{"a":0,"k":[12.5,12.5,100],"ix":6,"l":2}},"ao":0,"ip":0,"op":300,"st":-10,"bm":0}]}],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Shape Layer 3","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":260,"s":[100]},{"t":280,"s":[0]}],"ix":11},"r":{"a":1,"k":[{"i":{"x":[0],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":140,"s":[0]},{"i":{"x":[0],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":150,"s":[8]},{"i":{"x":[0],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":167,"s":[-8]},{"t":184,"s":[0]}],"ix":10},"p":{"s":true,"x":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":30,"s":[128]},{"t":70,"s":[128]}],"ix":3},"y":{"a":1,"k":[{"i":{"x":[0],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":30,"s":[179.125]},{"i":{"x":[0.03],"y":[1]},"o":{"x":[0.157],"y":[0]},"t":70,"s":[179.125]},{"i":{"x":[0],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":167,"s":[179.125]},{"i":{"x":[0],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":188,"s":[197.125]},{"i":{"x":[0],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":210,"s":[197.125]},{"t":240,"s":[179.125]}],"ix":4}},"a":{"a":0,"k":[4,344,0],"ix":1,"l":2},"s":{"a":0,"k":[12.5,12.5,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-460,344],[468,344]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":128,"ix":5},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":30,"s":[0]},{"i":{"x":[0.586],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":70,"s":[0]},{"i":{"x":[0],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":260,"s":[0]},{"t":280,"s":[100]}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":30,"s":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":70,"s":[100]},{"i":{"x":[0],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":260,"s":[100]},{"t":280,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":2,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":0,"op":300,"st":-15,"ct":1,"bm":0},{"ddd":0,"ind":2,"ty":0,"nm":"DownloadIcon","refId":"comp_0","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":260,"s":[100]},{"t":280,"s":[0]}],"ix":11},"r":{"a":1,"k":[{"i":{"x":[0],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":140,"s":[0]},{"i":{"x":[0],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":150,"s":[-9]},{"i":{"x":[0],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":160,"s":[11]},{"t":179,"s":[0]}],"ix":10},"p":{"s":true,"x":{"a":0,"k":128,"ix":3},"y":{"a":0,"k":128,"ix":4}},"a":{"a":0,"k":[128,128,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"hasMask":true,"masksProperties":[{"inv":false,"mode":"a","pt":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":150,"s":[{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[289.5,-24.5],[-42,-24.5],[-42,266.5],[289.5,266.5]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":189,"s":[{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[289.5,-24.5],[-42,-24.5],[-42,191.5],[289.5,191.5]],"c":true}]},{"t":230,"s":[{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[289.5,-24.5],[-42,-24.5],[-42,179],[289.5,179]],"c":true}]}],"ix":1},"o":{"a":0,"k":100,"ix":3},"x":{"a":0,"k":0,"ix":4},"nm":"Mask 1"}],"w":256,"h":256,"ip":0,"op":300,"st":0,"bm":0}],"markers":[],"props":{}}
\ No newline at end of file
diff --git a/CollapseLauncher/Classes/AnimatedVisuals/Lottie/DownloadIcon.lottie b/CollapseLauncher/Classes/AnimatedVisuals/Lottie/DownloadIcon.lottie
deleted file mode 100644
index d40ef8c19..000000000
Binary files a/CollapseLauncher/Classes/AnimatedVisuals/Lottie/DownloadIcon.lottie and /dev/null differ
diff --git a/CollapseLauncher/Classes/AnimatedVisuals/Lottie/LoadingSprite.cs b/CollapseLauncher/Classes/AnimatedVisuals/Lottie/LoadingSprite.cs
index 3d1e74178..45c1ba614 100644
--- a/CollapseLauncher/Classes/AnimatedVisuals/Lottie/LoadingSprite.cs
+++ b/CollapseLauncher/Classes/AnimatedVisuals/Lottie/LoadingSprite.cs
@@ -3,13 +3,13 @@
// This code was generated by a tool.
//
// LottieGen version:
-// 8.0.280225.1+7cd366a738
+// 8.2.250604.1+b02a3ee244
//
// Command:
-// LottieGen -Language CSharp -Namespace CollapseLauncher.AnimatedVisuals.Lottie -Public -WinUIVersion 3.0 -InputFile LoadingSprite.lottie
+// LottieGen -GenerateColorBindings -GenerateDependencyObject -Language CSharp -Namespace CollapseLauncher.AnimatedVisuals.Lottie -Public -WinUIVersion 3.0 -InputFile LoadingSprite.json
//
// Input file:
-// LoadingSprite.lottie (1342 bytes created 20:17+07:00 May 23 2024)
+// LoadingSprite.json (5570 bytes created 16:59+07:00 Jan 4 2026)
//
// LottieGen source:
// http://aka.ms/Lottie
@@ -21,16 +21,16 @@
// ____________________________________
// | Object stats | Count |
// |__________________________|_______|
-// | All CompositionObjects | 92 |
+// | All CompositionObjects | 97 |
// |--------------------------+-------|
-// | Expression animators | 6 |
+// | Expression animators | 8 |
// | KeyFrame animators | 17 |
-// | Reference parameters | 6 |
-// | Expression operations | 0 |
+// | Reference parameters | 8 |
+// | Expression operations | 8 |
// |--------------------------+-------|
-// | Animated brushes | - |
+// | Animated brushes | 2 |
// | Animated gradient stops | - |
-// | ExpressionAnimations | 6 |
+// | ExpressionAnimations | 8 |
// | PathKeyFrameAnimations | - |
// |--------------------------+-------|
// | ContainerVisuals | 1 |
@@ -44,6 +44,7 @@
// | CompositionVisualSurface | - |
// ------------------------------------
using Microsoft.UI.Composition;
+using Microsoft.UI.Xaml;
using System;
using System.Collections.Generic;
using System.Numerics;
@@ -55,13 +56,72 @@ namespace CollapseLauncher.AnimatedVisuals.Lottie
// Frame rate: 60 fps
// Frame count: 180
// Duration: 3000.0 mS
- sealed partial class LoadingSprite
- : Microsoft.UI.Xaml.Controls.IAnimatedVisualSource
+ public sealed partial class LoadingSprite :
+ DependencyObject
+ , Microsoft.UI.Xaml.Controls.IAnimatedVisualSource
, Microsoft.UI.Xaml.Controls.IAnimatedVisualSource2
{
// Animation duration: 3.000 seconds.
internal const long c_durationTicks = 30000000;
+ // Theme property: Color_000000.
+ internal static readonly Color c_themeColor_000000 = Color.FromArgb(0xFF, 0x00, 0x00, 0x00);
+
+ // Theme property: Color_FFFFFF.
+ internal static readonly Color c_themeColor_FFFFFF = Color.FromArgb(0xFF, 0xFF, 0xFF, 0xFF);
+
+ CompositionPropertySet _themeProperties;
+
+ ///
+ /// Dependency property for Color_000000.
+ ///
+ public static readonly DependencyProperty Color_000000Property =
+ DependencyProperty.Register("Color_000000", typeof(Color), typeof(LoadingSprite),
+ new PropertyMetadata(Color.FromArgb(0xFF, 0x00, 0x00, 0x00), OnColor_000000Changed));
+
+ ///
+ /// Dependency property for Color_FFFFFF.
+ ///
+ public static readonly DependencyProperty Color_FFFFFFProperty =
+ DependencyProperty.Register("Color_FFFFFF", typeof(Color), typeof(LoadingSprite),
+ new PropertyMetadata(Color.FromArgb(0xFF, 0xFF, 0xFF, 0xFF), OnColor_FFFFFFChanged));
+
+ // Theme properties.
+ public Color Color_000000
+ {
+ get => (Color)GetValue(Color_000000Property);
+ set => SetValue(Color_000000Property, value);
+ }
+
+ public Color Color_FFFFFF
+ {
+ get => (Color)GetValue(Color_FFFFFFProperty);
+ set => SetValue(Color_FFFFFFProperty, value);
+ }
+
+ static Vector4 ColorAsVector4(Color color) => new Vector4(color.R, color.G, color.B, color.A);
+
+ static void OnColor_000000Changed(DependencyObject d, DependencyPropertyChangedEventArgs args)
+ {
+ ((LoadingSprite)d)._themeProperties?.InsertVector4("Color_000000", ColorAsVector4((Color)(Color)args.NewValue));
+ }
+
+ static void OnColor_FFFFFFChanged(DependencyObject d, DependencyPropertyChangedEventArgs args)
+ {
+ ((LoadingSprite)d)._themeProperties?.InsertVector4("Color_FFFFFF", ColorAsVector4((Color)(Color)args.NewValue));
+ }
+
+ CompositionPropertySet EnsureThemeProperties(Compositor compositor)
+ {
+ if (_themeProperties == null)
+ {
+ _themeProperties = compositor.CreatePropertySet();
+ _themeProperties.InsertVector4("Color_000000", ColorAsVector4((Color)Color_000000));
+ _themeProperties.InsertVector4("Color_FFFFFF", ColorAsVector4((Color)Color_FFFFFF));
+ }
+ return _themeProperties;
+ }
+
public Microsoft.UI.Xaml.Controls.IAnimatedVisual TryCreateAnimatedVisual(Compositor compositor)
{
object ignored = null;
@@ -71,10 +131,12 @@ public Microsoft.UI.Xaml.Controls.IAnimatedVisual TryCreateAnimatedVisual(Compos
public Microsoft.UI.Xaml.Controls.IAnimatedVisual TryCreateAnimatedVisual(Compositor compositor, out object diagnostics)
{
diagnostics = null;
+ EnsureThemeProperties(compositor);
var res =
new LoadingSprite_AnimatedVisual(
- compositor
+ compositor,
+ _themeProperties
);
res.CreateAnimations();
return res;
@@ -118,6 +180,23 @@ public double FrameToProgress(double frameNumber)
///
public void SetColorProperty(string propertyName, Color value)
{
+ if (propertyName == "Color_000000")
+ {
+ Color_000000 = value;
+ }
+ else if (propertyName == "Color_FFFFFF")
+ {
+ Color_FFFFFF = value;
+ }
+ else
+ {
+ return;
+ }
+
+ if (_themeProperties != null)
+ {
+ _themeProperties.InsertVector4(propertyName, ColorAsVector4(value));
+ }
}
///
@@ -135,8 +214,10 @@ sealed partial class LoadingSprite_AnimatedVisual
const long c_durationTicks = 30000000;
readonly Compositor _c;
readonly ExpressionAnimation _reusableExpressionAnimation;
+ readonly CompositionPropertySet _themeProperties;
AnimationController _animationController_0;
- CompositionColorBrush _colorBrush_White;
+ CompositionColorBrush _themeColor_Color_000000;
+ CompositionColorBrush _themeColor_Color_FFFFFF;
CompositionContainerShape _containerShape_0;
CompositionContainerShape _containerShape_1;
CompositionContainerShape _containerShape_2;
@@ -150,9 +231,9 @@ sealed partial class LoadingSprite_AnimatedVisual
CubicBezierEasingFunction _cubicBezierEasingFunction_2;
CubicBezierEasingFunction _cubicBezierEasingFunction_3;
CubicBezierEasingFunction _cubicBezierEasingFunction_4;
- ScalarKeyFrameAnimation _scalarAnimation_0p85_to_1;
- ScalarKeyFrameAnimation _scalarAnimation_1_to_1_0;
- ScalarKeyFrameAnimation _scalarAnimation_1_to_1_1;
+ ScalarKeyFrameAnimation _scalarAnimation_0p2_to_0p2_0;
+ ScalarKeyFrameAnimation _scalarAnimation_0p2_to_0p2_1;
+ ScalarKeyFrameAnimation _scalarAnimation_0p17_to_0p2;
StepEasingFunction _holdThenStepEasingFunction;
StepEasingFunction _stepThenHoldEasingFunction;
@@ -211,16 +292,22 @@ AnimationController AnimationController_0()
// - - Layer aggregator
// ShapeGroup: Rectangle 1 Offset:<-2.5, -4>
- CompositionColorBrush ColorBrush_SemiTransparentBlack()
+ // Color bound to theme property value: Color_000000
+ CompositionColorBrush ThemeColor_Color_000000()
{
- return _c.CreateColorBrush(Color.FromArgb(0x4C, 0x00, 0x00, 0x00));
+ if (_themeColor_Color_000000 != null) { return _themeColor_Color_000000; }
+ var result = _themeColor_Color_000000 = _c.CreateColorBrush();
+ BindProperty(_themeColor_Color_000000, "Color", "ColorRGB(_theme.Color_000000.W*0.3,_theme.Color_000000.X,_theme.Color_000000.Y,_theme.Color_000000.Z)", "_theme", _themeProperties);
+ return result;
}
- CompositionColorBrush ColorBrush_White()
+ // Color bound to theme property value: Color_FFFFFF
+ CompositionColorBrush ThemeColor_Color_FFFFFF()
{
- return (_colorBrush_White == null)
- ? _colorBrush_White = _c.CreateColorBrush(Color.FromArgb(0xFF, 0xFF, 0xFF, 0xFF))
- : _colorBrush_White;
+ if (_themeColor_Color_FFFFFF != null) { return _themeColor_Color_FFFFFF; }
+ var result = _themeColor_Color_FFFFFF = _c.CreateColorBrush();
+ BindProperty(_themeColor_Color_FFFFFF, "Color", "ColorRGB(_theme.Color_FFFFFF.W,_theme.Color_FFFFFF.X,_theme.Color_FFFFFF.Y,_theme.Color_FFFFFF.Z)", "_theme", _themeProperties);
+ return result;
}
// Layer aggregator
@@ -229,7 +316,7 @@ CompositionContainerShape ContainerShape_0()
if (_containerShape_0 != null) { return _containerShape_0; }
var result = _containerShape_0 = _c.CreateContainerShape();
result.CenterPoint = new Vector2(-2.5F, -4F);
- result.Offset = new Vector2(322.5F, 324F);
+ result.Offset = new Vector2(66.5F, 68F);
// ShapeGroup: Rectangle 1 Offset:<-2.5, -4>
result.Shapes.Add(SpriteShape_0());
return result;
@@ -241,7 +328,7 @@ CompositionContainerShape ContainerShape_1()
if (_containerShape_1 != null) { return _containerShape_1; }
var result = _containerShape_1 = _c.CreateContainerShape();
result.CenterPoint = new Vector2(15.75F, 4.75F);
- result.Offset = new Vector2(304.25F, 315.25F);
+ result.Offset = new Vector2(48.25F, 59.25F);
// ShapeGroup: Ellipse 1 Offset:<15.75, 4.75>
result.Shapes.Add(SpriteShape_1());
return result;
@@ -265,7 +352,7 @@ CompositionContainerShape ContainerShape_3()
if (_containerShape_3 != null) { return _containerShape_3; }
var result = _containerShape_3 = _c.CreateContainerShape();
result.CenterPoint = new Vector2(15.75F, 4.75F);
- result.Offset = new Vector2(304.25F, 315.25F);
+ result.Offset = new Vector2(48.25F, 59.25F);
// ShapeGroup: Ellipse 1 Offset:<15.75, 4.75>
result.Shapes.Add(SpriteShape_2());
return result;
@@ -319,7 +406,7 @@ CompositionSpriteShape SpriteShape_0()
{
// Offset:<-2.5, -4>
var geometry = RoundedRectangle_291();
- var result = CreateSpriteShape(geometry, new Matrix3x2(1F, 0F, 0F, 1F, -2.5F, -4F), ColorBrush_SemiTransparentBlack());;
+ var result = CreateSpriteShape(geometry, new Matrix3x2(1F, 0F, 0F, 1F, -2.5F, -4F), ThemeColor_Color_000000());;
return result;
}
@@ -329,7 +416,7 @@ CompositionSpriteShape SpriteShape_1()
{
// Offset:<15.75, 4.75>
var result = CreateSpriteShape(Ellipse_84p25_0(), new Matrix3x2(1F, 0F, 0F, 1F, 15.75F, 4.75F));;
- result.StrokeBrush = ColorBrush_White();
+ result.StrokeBrush = ThemeColor_Color_FFFFFF();
result.StrokeDashCap = CompositionStrokeCap.Round;
result.StrokeStartCap = CompositionStrokeCap.Round;
result.StrokeEndCap = CompositionStrokeCap.Round;
@@ -344,7 +431,7 @@ CompositionSpriteShape SpriteShape_2()
{
// Offset:<15.75, 4.75>
var result = CreateSpriteShape(Ellipse_84p25_1(), new Matrix3x2(1F, 0F, 0F, 1F, 15.75F, 4.75F));;
- result.StrokeBrush = ColorBrush_White();
+ result.StrokeBrush = ThemeColor_Color_FFFFFF();
result.StrokeDashCap = CompositionStrokeCap.Round;
result.StrokeStartCap = CompositionStrokeCap.Round;
result.StrokeEndCap = CompositionStrokeCap.Round;
@@ -455,47 +542,47 @@ ScalarKeyFrameAnimation RoundnessScalarAnimation_25_to_25()
}
// Scale
- ScalarKeyFrameAnimation ScalarAnimation_0p85_to_1()
+ ScalarKeyFrameAnimation ScalarAnimation_0p2_to_0p2_0()
{
// Frame 0.
- if (_scalarAnimation_0p85_to_1 != null) { return _scalarAnimation_0p85_to_1; }
- var result = _scalarAnimation_0p85_to_1 = CreateScalarKeyFrameAnimation(0F, 0.850000024F, StepThenHoldEasingFunction());
- // Frame 100.
- result.InsertKeyFrame(0.555555582F, 0.850000024F, HoldThenStepEasingFunction());
- // Frame 120.
- result.InsertKeyFrame(0.666666687F, 1F, CubicBezierEasingFunction_0());
+ if (_scalarAnimation_0p2_to_0p2_0 != null) { return _scalarAnimation_0p2_to_0p2_0; }
+ var result = _scalarAnimation_0p2_to_0p2_0 = CreateScalarKeyFrameAnimation(0F, 0.200000003F, StepThenHoldEasingFunction());
+ // Frame 10.
+ result.InsertKeyFrame(0.055555556F, 0.200000003F, HoldThenStepEasingFunction());
+ // Frame 30.
+ result.InsertKeyFrame(0.166666672F, 0.150000006F, CubicBezierEasingFunction_0());
+ // Frame 90.
+ result.InsertKeyFrame(0.5F, 0.150000006F, _c.CreateCubicBezierEasingFunction(new Vector2(0.166999996F, 0F), new Vector2(0.666999996F, 1F)));
+ // Frame 110.
+ result.InsertKeyFrame(0.611111104F, 0.200000003F, _c.CreateCubicBezierEasingFunction(new Vector2(0.333000004F, 0F), new Vector2(0.666999996F, 1F)));
return result;
}
// Scale
- ScalarKeyFrameAnimation ScalarAnimation_1_to_1_0()
+ ScalarKeyFrameAnimation ScalarAnimation_0p2_to_0p2_1()
{
// Frame 0.
- if (_scalarAnimation_1_to_1_0 != null) { return _scalarAnimation_1_to_1_0; }
- var result = _scalarAnimation_1_to_1_0 = CreateScalarKeyFrameAnimation(0F, 1F, StepThenHoldEasingFunction());
- // Frame 10.
- result.InsertKeyFrame(0.055555556F, 1F, HoldThenStepEasingFunction());
- // Frame 30.
- result.InsertKeyFrame(0.166666672F, 0.75F, CubicBezierEasingFunction_0());
+ if (_scalarAnimation_0p2_to_0p2_1 != null) { return _scalarAnimation_0p2_to_0p2_1; }
+ var result = _scalarAnimation_0p2_to_0p2_1 = CreateScalarKeyFrameAnimation(0F, 0.200000003F, HoldThenStepEasingFunction());
+ // Frame 20.
+ result.InsertKeyFrame(0.111111112F, 0.170000002F, _c.CreateCubicBezierEasingFunction(new Vector2(0.333000004F, 0F), new Vector2(0.833000004F, 0.833000004F)));
// Frame 90.
- result.InsertKeyFrame(0.5F, 0.75F, _c.CreateCubicBezierEasingFunction(new Vector2(0.166999996F, 0F), new Vector2(0.666999996F, 1F)));
+ result.InsertKeyFrame(0.5F, 0.170000002F, _c.CreateCubicBezierEasingFunction(new Vector2(0.166999996F, 0F), new Vector2(0.460000008F, 1F)));
// Frame 110.
- result.InsertKeyFrame(0.611111104F, 1F, _c.CreateCubicBezierEasingFunction(new Vector2(0.333000004F, 0F), new Vector2(0.666999996F, 1F)));
+ result.InsertKeyFrame(0.611111104F, 0.200000003F, CubicBezierEasingFunction_0());
return result;
}
// Scale
- ScalarKeyFrameAnimation ScalarAnimation_1_to_1_1()
+ ScalarKeyFrameAnimation ScalarAnimation_0p17_to_0p2()
{
// Frame 0.
- if (_scalarAnimation_1_to_1_1 != null) { return _scalarAnimation_1_to_1_1; }
- var result = _scalarAnimation_1_to_1_1 = CreateScalarKeyFrameAnimation(0F, 1F, HoldThenStepEasingFunction());
- // Frame 20.
- result.InsertKeyFrame(0.111111112F, 0.850000024F, _c.CreateCubicBezierEasingFunction(new Vector2(0.333000004F, 0F), new Vector2(0.833000004F, 0.833000004F)));
- // Frame 90.
- result.InsertKeyFrame(0.5F, 0.850000024F, _c.CreateCubicBezierEasingFunction(new Vector2(0.166999996F, 0F), new Vector2(0.460000008F, 1F)));
- // Frame 110.
- result.InsertKeyFrame(0.611111104F, 1F, CubicBezierEasingFunction_0());
+ if (_scalarAnimation_0p17_to_0p2 != null) { return _scalarAnimation_0p17_to_0p2; }
+ var result = _scalarAnimation_0p17_to_0p2 = CreateScalarKeyFrameAnimation(0F, 0.170000002F, StepThenHoldEasingFunction());
+ // Frame 100.
+ result.InsertKeyFrame(0.555555582F, 0.170000002F, HoldThenStepEasingFunction());
+ // Frame 120.
+ result.InsertKeyFrame(0.666666687F, 0.200000003F, CubicBezierEasingFunction_0());
return result;
}
@@ -587,7 +674,7 @@ ScalarKeyFrameAnimation TStartScalarAnimation_0_to_0p5_1()
ShapeVisual ShapeVisual_0()
{
var result = _c.CreateShapeVisual();
- result.Size = new Vector2(640F, 640F);
+ result.Size = new Vector2(128F, 128F);
var shapes = result.Shapes;
shapes.Add(ContainerShape_0());
shapes.Add(ContainerShape_1());
@@ -624,31 +711,33 @@ Vector2KeyFrameAnimation ShapeVisibilityAnimation()
}
internal LoadingSprite_AnimatedVisual(
- Compositor compositor
+ Compositor compositor,
+ CompositionPropertySet themeProperties
)
{
_c = compositor;
+ _themeProperties = themeProperties;
_reusableExpressionAnimation = compositor.CreateExpressionAnimation();
Root();
}
public Visual RootVisual => _root;
public TimeSpan Duration => TimeSpan.FromTicks(c_durationTicks);
- public Vector2 Size => new Vector2(640F, 640F);
+ public Vector2 Size => new Vector2(128F, 128F);
void IDisposable.Dispose() => _root?.Dispose();
public void CreateAnimations()
{
_containerShape_0.StartAnimation("RotationAngleInDegrees", RotationAngleInDegreesScalarAnimation_45_to_135(), AnimationController_0());
- _containerShape_0.StartAnimation("Scale.X", ScalarAnimation_1_to_1_0(), AnimationController_0());
- _containerShape_0.StartAnimation("Scale.Y", ScalarAnimation_1_to_1_0(), AnimationController_0());
+ _containerShape_0.StartAnimation("Scale.X", ScalarAnimation_0p2_to_0p2_0(), AnimationController_0());
+ _containerShape_0.StartAnimation("Scale.Y", ScalarAnimation_0p2_to_0p2_0(), AnimationController_0());
_containerShape_1.StartAnimation("RotationAngleInDegrees", RotationAngleInDegreesScalarAnimation_180_to_900_0(), AnimationController_0());
- _containerShape_1.StartAnimation("Scale.X", ScalarAnimation_1_to_1_1(), AnimationController_0());
- _containerShape_1.StartAnimation("Scale.Y", ScalarAnimation_1_to_1_1(), AnimationController_0());
+ _containerShape_1.StartAnimation("Scale.X", ScalarAnimation_0p2_to_0p2_1(), AnimationController_0());
+ _containerShape_1.StartAnimation("Scale.Y", ScalarAnimation_0p2_to_0p2_1(), AnimationController_0());
_containerShape_2.StartAnimation("Scale", ShapeVisibilityAnimation(), AnimationController_0());
_containerShape_3.StartAnimation("RotationAngleInDegrees", RotationAngleInDegreesScalarAnimation_180_to_900_1(), AnimationController_0());
- _containerShape_3.StartAnimation("Scale.X", ScalarAnimation_0p85_to_1(), AnimationController_0());
- _containerShape_3.StartAnimation("Scale.Y", ScalarAnimation_0p85_to_1(), AnimationController_0());
+ _containerShape_3.StartAnimation("Scale.X", ScalarAnimation_0p17_to_0p2(), AnimationController_0());
+ _containerShape_3.StartAnimation("Scale.Y", ScalarAnimation_0p17_to_0p2(), AnimationController_0());
_ellipse_84p25_0.StartAnimation("TStart", TStartScalarAnimation_0_to_0p5_0(), AnimationController_0());
_ellipse_84p25_0.StartAnimation("TEnd", TEndScalarAnimation_0_to_0p5_0(), AnimationController_0());
_ellipse_84p25_0.StartAnimation("TrimOffset", TrimOffsetScalarAnimation_0_to_0p5_0(), AnimationController_0());
diff --git a/CollapseLauncher/Classes/AnimatedVisuals/Lottie/LoadingSprite.json b/CollapseLauncher/Classes/AnimatedVisuals/Lottie/LoadingSprite.json
new file mode 100644
index 000000000..066b5ce05
--- /dev/null
+++ b/CollapseLauncher/Classes/AnimatedVisuals/Lottie/LoadingSprite.json
@@ -0,0 +1 @@
+{"v":"5.12.2","fr":60,"ip":0,"op":180,"w":128,"h":128,"nm":"LoadingSprite","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Shape Layer 4","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":90,"s":[180]},{"t":180,"s":[900]}],"ix":10},"p":{"a":0,"k":[64,64,0],"ix":2,"l":2},"a":{"a":0,"k":[15.75,4.75,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0,0,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":100,"s":[17,17,100]},{"t":120,"s":[20,20,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[168.5,168.5],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":26,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[15.75,4.75],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":90,"s":[0]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":120,"s":[50]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":150,"s":[50]},{"t":180,"s":[50]}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":150,"s":[0]},{"t":180,"s":[50]}],"ix":2},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":120,"s":[0]},{"t":150,"s":[180]}],"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":90,"op":271,"st":90,"ct":1,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Shape Layer 2","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[180]},{"t":90,"s":[900]}],"ix":10},"p":{"a":0,"k":[64,64,0],"ix":2,"l":2},"a":{"a":0,"k":[15.75,4.75,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":0,"s":[20,20,100]},{"i":{"x":[0.46,0.46,0.667],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":20,"s":[17,17,100]},{"i":{"x":[0,0,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":90,"s":[17,17,100]},{"t":110,"s":[20,20,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[168.5,168.5],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":26,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[15.75,4.75],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":0,"s":[0]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":30,"s":[50]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":60,"s":[50]},{"t":90,"s":[50]}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":60,"s":[0]},{"t":90,"s":[50]}],"ix":2},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":30,"s":[0]},{"t":60,"s":[180]}],"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":0,"op":181,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"Shape Layer 3","sr":1,"ks":{"o":{"a":0,"k":30,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":0,"s":[45]},{"i":{"x":[0],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":60,"s":[90]},{"i":{"x":[0],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":90,"s":[90]},{"t":150,"s":[135]}],"ix":10},"p":{"a":0,"k":[64,64,0],"ix":2,"l":2},"a":{"a":0,"k":[-2.5,-4,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0,0,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":10,"s":[20,20,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":30,"s":[15,15,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":90,"s":[15,15,100]},{"t":110,"s":[20,20,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[291,291],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":1,"k":[{"i":{"x":[0],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":0,"s":[25]},{"i":{"x":[0],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":60,"s":[125]},{"i":{"x":[0],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":90,"s":[125]},{"t":150,"s":[25]}],"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-2.5,-4],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":182,"st":0,"ct":1,"bm":0}],"markers":[],"props":{}}
\ No newline at end of file
diff --git a/CollapseLauncher/Classes/AnimatedVisuals/Lottie/LoadingSprite.lottie b/CollapseLauncher/Classes/AnimatedVisuals/Lottie/LoadingSprite.lottie
deleted file mode 100644
index 357515fa6..000000000
Binary files a/CollapseLauncher/Classes/AnimatedVisuals/Lottie/LoadingSprite.lottie and /dev/null differ
diff --git a/CollapseLauncher/Classes/AnimatedVisuals/Lottie/StartGameIcon.cs b/CollapseLauncher/Classes/AnimatedVisuals/Lottie/StartGameIcon.cs
index 8478138ac..fdbcf99b2 100644
--- a/CollapseLauncher/Classes/AnimatedVisuals/Lottie/StartGameIcon.cs
+++ b/CollapseLauncher/Classes/AnimatedVisuals/Lottie/StartGameIcon.cs
@@ -3,13 +3,13 @@
// This code was generated by a tool.
//
// LottieGen version:
-// 8.0.280225.1+7cd366a738
+// 8.2.250604.1+b02a3ee244
//
// Command:
-// LottieGen -Language CSharp -Namespace CollapseLauncher.AnimatedVisuals.Lottie -Public -WinUIVersion 3.0 -InputFile StartGameIcon.lottie
+// LottieGen -GenerateColorBindings -GenerateDependencyObject -Language CSharp -Namespace CollapseLauncher.AnimatedVisuals.Lottie -Public -WinUIVersion 3.0 -InputFile StartGameIcon.json
//
// Input file:
-// StartGameIcon.lottie (1259 bytes created 0:25+07:00 Jun 2 2024)
+// StartGameIcon.json (8234 bytes created 16:59+07:00 Jan 4 2026)
//
// LottieGen source:
// http://aka.ms/Lottie
@@ -21,16 +21,16 @@
// ____________________________________
// | Object stats | Count |
// |__________________________|_______|
-// | All CompositionObjects | 70 |
+// | All CompositionObjects | 77 |
// |--------------------------+-------|
-// | Expression animators | 9 |
+// | Expression animators | 12 |
// | KeyFrame animators | 6 |
-// | Reference parameters | 9 |
-// | Expression operations | 6 |
+// | Reference parameters | 15 |
+// | Expression operations | 18 |
// |--------------------------+-------|
// | Animated brushes | 3 |
// | Animated gradient stops | - |
-// | ExpressionAnimations | 9 |
+// | ExpressionAnimations | 12 |
// | PathKeyFrameAnimations | - |
// |--------------------------+-------|
// | ContainerVisuals | 1 |
@@ -44,6 +44,8 @@
// | CompositionVisualSurface | - |
// ------------------------------------
using Microsoft.UI.Composition;
+using Microsoft.UI.Xaml;
+using Microsoft.UI.Xaml.Media;
using System;
using System.Collections.Generic;
using System.Numerics;
@@ -55,13 +57,67 @@ namespace CollapseLauncher.AnimatedVisuals.Lottie
// Frame rate: 60 fps
// Frame count: 40
// Duration: 666.7 mS
- sealed partial class StartGameIcon
- : Microsoft.UI.Xaml.Controls.IAnimatedVisualSource
+ public sealed partial class StartGameIcon
+ : BindableThemeChangeAnimation
+ , Microsoft.UI.Xaml.Controls.IAnimatedVisualSource
, Microsoft.UI.Xaml.Controls.IAnimatedVisualSource2
{
+ protected override void OnForegroundChanged(Brush brush)
+ {
+ if (brush is SolidColorBrush colorBrush)
+ {
+ Color_FFFFFF = colorBrush.Color;
+ return;
+ }
+
+ if (brush is AcrylicBrush acrylicBrush)
+ {
+ Color_FFFFFF = acrylicBrush.TintColor;
+ return;
+ }
+ }
+
+ private static Color GetForegroundColor(bool isLightTheme) =>
+ isLightTheme
+ ? Color.FromArgb(0xFF, 0x00, 0x00, 0x00)
+ : Color.FromArgb(0xFF, 0xFF, 0xFF, 0xFF);
+
// Animation duration: 0.667 seconds.
internal const long c_durationTicks = 6666666;
+ CompositionPropertySet _themeProperties;
+
+ ///
+ /// Dependency property for Color_FFFFFF.
+ ///
+ public static readonly DependencyProperty Color_FFFFFFProperty =
+ DependencyProperty.Register("Color_FFFFFF", typeof(Color), typeof(StartGameIcon),
+ new PropertyMetadata(GetForegroundColor(InnerLauncherConfig.IsAppThemeLight), OnColor_FFFFFFChanged));
+
+ // Theme properties.
+ public Color Color_FFFFFF
+ {
+ get => (Color)GetValue(Color_FFFFFFProperty);
+ set => SetValue(Color_FFFFFFProperty, value);
+ }
+
+ static Vector4 ColorAsVector4(Color color) => new Vector4(color.R, color.G, color.B, color.A);
+
+ static void OnColor_FFFFFFChanged(DependencyObject d, DependencyPropertyChangedEventArgs args)
+ {
+ ((StartGameIcon)d)._themeProperties?.InsertVector4("Color_FFFFFF", ColorAsVector4((Color)(Color)args.NewValue));
+ }
+
+ CompositionPropertySet EnsureThemeProperties(Compositor compositor)
+ {
+ if (_themeProperties == null)
+ {
+ _themeProperties = compositor.CreatePropertySet();
+ _themeProperties.InsertVector4("Color_FFFFFF", ColorAsVector4((Color)Color_FFFFFF));
+ }
+ return _themeProperties;
+ }
+
public Microsoft.UI.Xaml.Controls.IAnimatedVisual TryCreateAnimatedVisual(Compositor compositor)
{
object ignored = null;
@@ -71,10 +127,12 @@ public Microsoft.UI.Xaml.Controls.IAnimatedVisual TryCreateAnimatedVisual(Compos
public Microsoft.UI.Xaml.Controls.IAnimatedVisual TryCreateAnimatedVisual(Compositor compositor, out object diagnostics)
{
diagnostics = null;
+ EnsureThemeProperties(compositor);
var res =
new StartGameIcon_AnimatedVisual(
- compositor
+ compositor,
+ _themeProperties
);
res.CreateAnimations();
return res;
@@ -118,6 +176,19 @@ public double FrameToProgress(double frameNumber)
///
public void SetColorProperty(string propertyName, Color value)
{
+ if (propertyName == "Color_FFFFFF")
+ {
+ Color_FFFFFF = value;
+ }
+ else
+ {
+ return;
+ }
+
+ if (_themeProperties != null)
+ {
+ _themeProperties.InsertVector4(propertyName, ColorAsVector4(value));
+ }
}
///
@@ -133,19 +204,18 @@ sealed partial class StartGameIcon_AnimatedVisual
, Microsoft.UI.Xaml.Controls.IAnimatedVisual2
{
const long c_durationTicks = 6666666;
- readonly Color _color = InnerLauncherConfig.IsAppThemeLight ? Color.FromArgb(0xFF, 0xFF, 0xFF, 0xFF) : Color.FromArgb(0xFF, 0x00, 0x00, 0x00);
- readonly Color _colorTransparent = InnerLauncherConfig.IsAppThemeLight ? Color.FromArgb(0x00, 0xFF, 0xFF, 0xFF) : Color.FromArgb(0x00, 0x00, 0x00, 0x00);
readonly Compositor _c;
readonly ExpressionAnimation _reusableExpressionAnimation;
+ readonly CompositionPropertySet _themeProperties;
AnimationController _animationController_0;
AnimationController _animationController_1;
AnimationController _animationController_2;
AnimationController _animationController_3;
AnimationController _animationController_4;
AnimationController _animationController_5;
- CompositionColorBrush _animatedColorBrush_TransparentWhite_to_White;
- CompositionColorBrush _animatedColorBrush_White_to_TransparentWhite;
- CompositionColorBrush _animatedColorBrush_White_to_White;
+ CompositionColorBrush _themeColor_Color_FFFFFF_0;
+ CompositionColorBrush _themeColor_Color_FFFFFF_1;
+ CompositionColorBrush _themeColor_Color_FFFFFF_2;
CompositionContainerShape _containerShape_0;
CompositionContainerShape _containerShape_1;
CompositionContainerShape _containerShape_2;
@@ -153,7 +223,7 @@ sealed partial class StartGameIcon_AnimatedVisual
ContainerVisual _root;
CubicBezierEasingFunction _cubicBezierEasingFunction_0;
CubicBezierEasingFunction _cubicBezierEasingFunction_1;
- ScalarKeyFrameAnimation _positionXScalarAnimation_m92_to_993;
+ ScalarKeyFrameAnimation _positionXScalarAnimation_m11p5_to_124p125;
StepEasingFunction _holdThenStepEasingFunction;
void BindProperty(
@@ -169,13 +239,20 @@ void BindProperty(
target.StartAnimation(animatedPropertyName, _reusableExpressionAnimation);
}
- ColorKeyFrameAnimation CreateColorKeyFrameAnimation(float initialProgress, Color initialValue, CompositionEasingFunction initialEasingFunction)
+ void BindProperty2(
+ CompositionObject target,
+ string animatedPropertyName,
+ string expression,
+ string referenceParameterName0,
+ CompositionObject referencedObject0,
+ string referenceParameterName1,
+ CompositionObject referencedObject1)
{
- var result = _c.CreateColorKeyFrameAnimation();
- result.Duration = TimeSpan.FromTicks(c_durationTicks);
- result.InterpolationColorSpace = CompositionColorSpace.Rgb;
- result.InsertKeyFrame(initialProgress, initialValue, initialEasingFunction);
- return result;
+ _reusableExpressionAnimation.ClearAllParameters();
+ _reusableExpressionAnimation.Expression = expression;
+ _reusableExpressionAnimation.SetReferenceParameter(referenceParameterName0, referencedObject0);
+ _reusableExpressionAnimation.SetReferenceParameter(referenceParameterName1, referencedObject1);
+ target.StartAnimation(animatedPropertyName, _reusableExpressionAnimation);
}
ScalarKeyFrameAnimation CreateScalarKeyFrameAnimation(float initialProgress, float initialValue, CompositionEasingFunction initialEasingFunction)
@@ -193,7 +270,6 @@ CompositionSpriteShape CreateSpriteShape(CompositionGeometry geometry, Matrix3x2
return result;
}
- // - ShapeGroup: Rectangle 1 Offset:<30, 198>
AnimationController AnimationController_0()
{
if (_animationController_0 != null) { return _animationController_0; }
@@ -212,7 +288,6 @@ AnimationController AnimationController_1()
return result;
}
- // - ShapeGroup: Rectangle 1 Offset:<30, 198>
AnimationController AnimationController_2()
{
if (_animationController_2 != null) { return _animationController_2; }
@@ -231,7 +306,6 @@ AnimationController AnimationController_3()
return result;
}
- // - ShapeGroup: Rectangle 1 Offset:<30, 198>
AnimationController AnimationController_4()
{
if (_animationController_4 != null) { return _animationController_4; }
@@ -250,69 +324,39 @@ AnimationController AnimationController_5()
return result;
}
- // - ShapeGroup: Rectangle 1 Offset:<30, 198>
- // Color
- ColorKeyFrameAnimation ColorAnimation_TransparentWhite_to_White()
- {
- // Frame 0.
- var result = CreateColorKeyFrameAnimation(0F, _colorTransparent, HoldThenStepEasingFunction());
- // Frame 8.91.
- // White
- result.InsertKeyFrame(0.222662598F, _color, CubicBezierEasingFunction_0());
- // Frame 40.
- // White
- result.InsertKeyFrame(1F, _color, CubicBezierEasingFunction_1());
- return result;
- }
-
- // - ShapeGroup: Rectangle 1 Offset:<30, 198>
- // Color
- ColorKeyFrameAnimation ColorAnimation_White_to_TransparentWhite()
- {
- // Frame 0.
- var result = CreateColorKeyFrameAnimation(0F, _color, CubicBezierEasingFunction_0());
- // Frame 27.93.
- // White
- result.InsertKeyFrame(0.698220015F, _color, CubicBezierEasingFunction_1());
- // Frame 40.
- // TransparentWhite
- result.InsertKeyFrame(0.99998045F, _colorTransparent, CubicBezierEasingFunction_0());
- return result;
- }
-
- // - ShapeGroup: Rectangle 1 Offset:<30, 198>
- // Color
- ColorKeyFrameAnimation ColorAnimation_White_to_White()
- {
- // Frame 0.
- var result = CreateColorKeyFrameAnimation(0F, _color, CubicBezierEasingFunction_0());
- // Frame 40.
- // White
- result.InsertKeyFrame(1F, _color, CubicBezierEasingFunction_1());
- return result;
- }
-
// ShapeGroup: Rectangle 1 Offset:<30, 198>
- CompositionColorBrush AnimatedColorBrush_TransparentWhite_to_White()
+ // Color bound to theme property value: Color_FFFFFF
+ CompositionColorBrush ThemeColor_Color_FFFFFF_0()
{
- if (_animatedColorBrush_TransparentWhite_to_White != null) { return _animatedColorBrush_TransparentWhite_to_White; }
- var result = _animatedColorBrush_TransparentWhite_to_White = _c.CreateColorBrush();
+ if (_themeColor_Color_FFFFFF_0 != null) { return _themeColor_Color_FFFFFF_0; }
+ var result = _themeColor_Color_FFFFFF_0 = _c.CreateColorBrush();
+ var propertySet = result.Properties;
+ propertySet.InsertScalar("Opacity0", 1F);
+ BindProperty2(_themeColor_Color_FFFFFF_0, "Color", "ColorRGB(_theme.Color_FFFFFF.W*my.Opacity0,_theme.Color_FFFFFF.X,_theme.Color_FFFFFF.Y,_theme.Color_FFFFFF.Z)", "_theme", _themeProperties, "my", propertySet);
return result;
}
// ShapeGroup: Rectangle 1 Offset:<30, 198>
- CompositionColorBrush AnimatedColorBrush_White_to_TransparentWhite()
+ // Color bound to theme property value: Color_FFFFFF
+ CompositionColorBrush ThemeColor_Color_FFFFFF_1()
{
- if (_animatedColorBrush_White_to_TransparentWhite != null) { return _animatedColorBrush_White_to_TransparentWhite; }
- var result = _animatedColorBrush_White_to_TransparentWhite = _c.CreateColorBrush();
+ if (_themeColor_Color_FFFFFF_1 != null) { return _themeColor_Color_FFFFFF_1; }
+ var result = _themeColor_Color_FFFFFF_1 = _c.CreateColorBrush();
+ var propertySet = result.Properties;
+ propertySet.InsertScalar("Opacity0", 1F);
+ BindProperty2(_themeColor_Color_FFFFFF_1, "Color", "ColorRGB(_theme.Color_FFFFFF.W*my.Opacity0,_theme.Color_FFFFFF.X,_theme.Color_FFFFFF.Y,_theme.Color_FFFFFF.Z)", "_theme", _themeProperties, "my", propertySet);
return result;
}
// ShapeGroup: Rectangle 1 Offset:<30, 198>
- CompositionColorBrush AnimatedColorBrush_White_to_White()
+ // Color bound to theme property value: Color_FFFFFF
+ CompositionColorBrush ThemeColor_Color_FFFFFF_2()
{
- if (_animatedColorBrush_White_to_White != null) { return _animatedColorBrush_White_to_White; }
- var result = _animatedColorBrush_White_to_White = _c.CreateColorBrush();
+ if (_themeColor_Color_FFFFFF_2 != null) { return _themeColor_Color_FFFFFF_2; }
+ var result = _themeColor_Color_FFFFFF_2 = _c.CreateColorBrush();
+ var propertySet = result.Properties;
+ propertySet.InsertScalar("Opacity0", 0F);
+ BindProperty2(_themeColor_Color_FFFFFF_2, "Color", "ColorRGB(_theme.Color_FFFFFF.W*my.Opacity0,_theme.Color_FFFFFF.X,_theme.Color_FFFFFF.Y,_theme.Color_FFFFFF.Z)", "_theme", _themeProperties, "my", propertySet);
return result;
}
@@ -321,9 +365,10 @@ CompositionContainerShape ContainerShape_0()
if (_containerShape_0 != null) { return _containerShape_0; }
var result = _containerShape_0 = _c.CreateContainerShape();
var propertySet = result.Properties;
- propertySet.InsertVector2("Position", new Vector2(-92F, 1024F));
+ propertySet.InsertVector2("Position", new Vector2(-11.5F, 128F));
result.CenterPoint = new Vector2(30F, 198F);
result.RotationAngleInDegrees = 45F;
+ result.Scale = new Vector2(0.125F, 0.125F);
// ShapeGroup: Rectangle 1 Offset:<30, 198>
result.Shapes.Add(SpriteShape_0());
BindProperty(_containerShape_0, "Offset", "Vector2(my.Position.X-30,my.Position.Y-198)", "my", _containerShape_0);
@@ -335,9 +380,10 @@ CompositionContainerShape ContainerShape_1()
if (_containerShape_1 != null) { return _containerShape_1; }
var result = _containerShape_1 = _c.CreateContainerShape();
var propertySet = result.Properties;
- propertySet.InsertVector2("Position", new Vector2(-92F, 1024F));
+ propertySet.InsertVector2("Position", new Vector2(-11.5F, 128F));
result.CenterPoint = new Vector2(30F, 198F);
result.RotationAngleInDegrees = 45F;
+ result.Scale = new Vector2(0.125F, 0.125F);
// ShapeGroup: Rectangle 1 Offset:<30, 198>
result.Shapes.Add(SpriteShape_1());
BindProperty(_containerShape_1, "Offset", "Vector2(my.Position.X-30,my.Position.Y-198)", "my", _containerShape_1);
@@ -349,9 +395,10 @@ CompositionContainerShape ContainerShape_2()
if (_containerShape_2 != null) { return _containerShape_2; }
var result = _containerShape_2 = _c.CreateContainerShape();
var propertySet = result.Properties;
- propertySet.InsertVector2("Position", new Vector2(-92F, 1024F));
+ propertySet.InsertVector2("Position", new Vector2(-11.5F, 128F));
result.CenterPoint = new Vector2(30F, 198F);
result.RotationAngleInDegrees = 45F;
+ result.Scale = new Vector2(0.125F, 0.125F);
// ShapeGroup: Rectangle 1 Offset:<30, 198>
result.Shapes.Add(SpriteShape_2());
BindProperty(_containerShape_2, "Offset", "Vector2(my.Position.X-30,my.Position.Y-198)", "my", _containerShape_2);
@@ -376,7 +423,7 @@ CompositionSpriteShape SpriteShape_0()
{
// Offset:<30, 198>
var result = CreateSpriteShape(Rectangle_1036(), new Matrix3x2(1F, 0F, 0F, 1F, 30F, 198F));;
- result.StrokeBrush = AnimatedColorBrush_White_to_TransparentWhite();
+ result.StrokeBrush = ThemeColor_Color_FFFFFF_0();
result.StrokeDashCap = CompositionStrokeCap.Round;
result.StrokeStartCap = CompositionStrokeCap.Round;
result.StrokeEndCap = CompositionStrokeCap.Round;
@@ -390,7 +437,7 @@ CompositionSpriteShape SpriteShape_1()
{
// Offset:<30, 198>
var result = CreateSpriteShape(Rectangle_1036(), new Matrix3x2(1F, 0F, 0F, 1F, 30F, 198F));;
- result.StrokeBrush = AnimatedColorBrush_White_to_White();
+ result.StrokeBrush = ThemeColor_Color_FFFFFF_1();
result.StrokeDashCap = CompositionStrokeCap.Round;
result.StrokeStartCap = CompositionStrokeCap.Round;
result.StrokeEndCap = CompositionStrokeCap.Round;
@@ -404,7 +451,7 @@ CompositionSpriteShape SpriteShape_2()
{
// Offset:<30, 198>
var result = CreateSpriteShape(Rectangle_1036(), new Matrix3x2(1F, 0F, 0F, 1F, 30F, 198F));;
- result.StrokeBrush = AnimatedColorBrush_TransparentWhite_to_White();
+ result.StrokeBrush = ThemeColor_Color_FFFFFF_2();
result.StrokeDashCap = CompositionStrokeCap.Round;
result.StrokeStartCap = CompositionStrokeCap.Round;
result.StrokeEndCap = CompositionStrokeCap.Round;
@@ -439,14 +486,48 @@ CubicBezierEasingFunction CubicBezierEasingFunction_1()
: _cubicBezierEasingFunction_1;
}
+ // Opacity0
+ ScalarKeyFrameAnimation Opacity0ScalarAnimation_0_to_1()
+ {
+ // Frame 0.
+ var result = CreateScalarKeyFrameAnimation(0F, 0F, HoldThenStepEasingFunction());
+ // Frame 8.91.
+ result.InsertKeyFrame(0.222662598F, 1F, CubicBezierEasingFunction_0());
+ // Frame 40.
+ result.InsertKeyFrame(1F, 1F, CubicBezierEasingFunction_1());
+ return result;
+ }
+
+ // Opacity0
+ ScalarKeyFrameAnimation Opacity0ScalarAnimation_1_to_0()
+ {
+ // Frame 0.
+ var result = CreateScalarKeyFrameAnimation(0F, 1F, CubicBezierEasingFunction_0());
+ // Frame 27.93.
+ result.InsertKeyFrame(0.698220015F, 1F, CubicBezierEasingFunction_1());
+ // Frame 40.
+ result.InsertKeyFrame(0.99998045F, 0F, CubicBezierEasingFunction_0());
+ return result;
+ }
+
+ // Opacity0
+ ScalarKeyFrameAnimation Opacity0ScalarAnimation_1_to_1()
+ {
+ // Frame 0.
+ var result = CreateScalarKeyFrameAnimation(0F, 1F, CubicBezierEasingFunction_0());
+ // Frame 40.
+ result.InsertKeyFrame(1F, 1F, CubicBezierEasingFunction_1());
+ return result;
+ }
+
// Position.X
- ScalarKeyFrameAnimation PositionXScalarAnimation_m92_to_993()
+ ScalarKeyFrameAnimation PositionXScalarAnimation_m11p5_to_124p125()
{
// Frame 0.
- if (_positionXScalarAnimation_m92_to_993 != null) { return _positionXScalarAnimation_m92_to_993; }
- var result = _positionXScalarAnimation_m92_to_993 = CreateScalarKeyFrameAnimation(0F, -92F, HoldThenStepEasingFunction());
+ if (_positionXScalarAnimation_m11p5_to_124p125 != null) { return _positionXScalarAnimation_m11p5_to_124p125; }
+ var result = _positionXScalarAnimation_m11p5_to_124p125 = CreateScalarKeyFrameAnimation(0F, -11.5F, HoldThenStepEasingFunction());
// Frame 40.
- result.InsertKeyFrame(1F, 993F, _c.CreateCubicBezierEasingFunction(new Vector2(0.47299999F, 0.0659999996F), new Vector2(0.521000028F, 1F)));
+ result.InsertKeyFrame(1F, 124.125F, _c.CreateCubicBezierEasingFunction(new Vector2(0.47299999F, 0.523999989F), new Vector2(0.521000028F, 1F)));
return result;
}
@@ -454,7 +535,7 @@ ScalarKeyFrameAnimation PositionXScalarAnimation_m92_to_993()
ShapeVisual ShapeVisual_0()
{
var result = _c.CreateShapeVisual();
- result.Size = new Vector2(2048F, 2048F);
+ result.Size = new Vector2(256F, 256F);
var shapes = result.Shapes;
shapes.Add(ContainerShape_0());
shapes.Add(ContainerShape_1());
@@ -471,34 +552,36 @@ StepEasingFunction HoldThenStepEasingFunction()
}
internal StartGameIcon_AnimatedVisual(
- Compositor compositor
+ Compositor compositor,
+ CompositionPropertySet themeProperties
)
{
_c = compositor;
+ _themeProperties = themeProperties;
_reusableExpressionAnimation = compositor.CreateExpressionAnimation();
Root();
}
public Visual RootVisual => _root;
public TimeSpan Duration => TimeSpan.FromTicks(c_durationTicks);
- public Vector2 Size => new Vector2(2048F, 2048F);
+ public Vector2 Size => new Vector2(256F, 256F);
void IDisposable.Dispose() => _root?.Dispose();
public void CreateAnimations()
{
- _animatedColorBrush_TransparentWhite_to_White.StartAnimation("Color", ColorAnimation_TransparentWhite_to_White(), AnimationController_4());
- _animatedColorBrush_White_to_TransparentWhite.StartAnimation("Color", ColorAnimation_White_to_TransparentWhite(), AnimationController_0());
- _animatedColorBrush_White_to_White.StartAnimation("Color", ColorAnimation_White_to_White(), AnimationController_2());
- _containerShape_0.Properties.StartAnimation("Position.X", PositionXScalarAnimation_m92_to_993(), AnimationController_1());
- _containerShape_1.Properties.StartAnimation("Position.X", PositionXScalarAnimation_m92_to_993(), AnimationController_3());
- _containerShape_2.Properties.StartAnimation("Position.X", PositionXScalarAnimation_m92_to_993(), AnimationController_5());
+ _themeColor_Color_FFFFFF_0.Properties.StartAnimation("Opacity0", Opacity0ScalarAnimation_1_to_0(), AnimationController_0());
+ _themeColor_Color_FFFFFF_1.Properties.StartAnimation("Opacity0", Opacity0ScalarAnimation_1_to_1(), AnimationController_2());
+ _themeColor_Color_FFFFFF_2.Properties.StartAnimation("Opacity0", Opacity0ScalarAnimation_0_to_1(), AnimationController_4());
+ _containerShape_0.Properties.StartAnimation("Position.X", PositionXScalarAnimation_m11p5_to_124p125(), AnimationController_1());
+ _containerShape_1.Properties.StartAnimation("Position.X", PositionXScalarAnimation_m11p5_to_124p125(), AnimationController_3());
+ _containerShape_2.Properties.StartAnimation("Position.X", PositionXScalarAnimation_m11p5_to_124p125(), AnimationController_5());
}
public void DestroyAnimations()
{
- _animatedColorBrush_TransparentWhite_to_White.StopAnimation("Color");
- _animatedColorBrush_White_to_TransparentWhite.StopAnimation("Color");
- _animatedColorBrush_White_to_White.StopAnimation("Color");
+ _themeColor_Color_FFFFFF_0.Properties.StopAnimation("Opacity0");
+ _themeColor_Color_FFFFFF_1.Properties.StopAnimation("Opacity0");
+ _themeColor_Color_FFFFFF_2.Properties.StopAnimation("Opacity0");
_containerShape_0.Properties.StopAnimation("Position.X");
_containerShape_1.Properties.StopAnimation("Position.X");
_containerShape_2.Properties.StopAnimation("Position.X");
diff --git a/CollapseLauncher/Classes/AnimatedVisuals/Lottie/StartGameIcon.json b/CollapseLauncher/Classes/AnimatedVisuals/Lottie/StartGameIcon.json
new file mode 100644
index 000000000..90563bd90
--- /dev/null
+++ b/CollapseLauncher/Classes/AnimatedVisuals/Lottie/StartGameIcon.json
@@ -0,0 +1 @@
+{"v":"5.12.2","fr":60,"ip":0,"op":40,"w":256,"h":256,"nm":"StartGameIcon","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Shape Layer 5","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":79,"s":[0]},{"i":{"x":[0.413],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":99,"s":[100]},{"i":{"x":[0],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":168.822,"s":[100]},{"t":198.998046875,"s":[0]}],"ix":11},"r":{"a":0,"k":45,"ix":10},"p":{"s":true,"x":{"a":1,"k":[{"i":{"x":[0.521],"y":[1]},"o":{"x":[0.473],"y":[0.524]},"t":79,"s":[-11.5]},{"t":199,"s":[124.125]}],"ix":3},"y":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":79,"s":[128]},{"t":99,"s":[128]}],"ix":4}},"a":{"a":0,"k":[30,198,0],"ix":1,"l":2},"s":{"a":0,"k":[12.5,12.5,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[1036,1036],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"tm","s":{"a":0,"k":37,"ix":1},"e":{"a":0,"k":63,"ix":2},"o":{"a":0,"k":180,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":128,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[30,198],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":4,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":79,"op":79.6060606060606,"st":79,"ct":1,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Shape Layer 4","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":40,"s":[0]},{"i":{"x":[0.413],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":60,"s":[100]},{"i":{"x":[0],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":129.822,"s":[100]},{"t":159.998046875,"s":[0]}],"ix":11},"r":{"a":0,"k":45,"ix":10},"p":{"s":true,"x":{"a":1,"k":[{"i":{"x":[0.521],"y":[1]},"o":{"x":[0.473],"y":[0.524]},"t":40,"s":[-11.5]},{"t":160,"s":[124.125]}],"ix":3},"y":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":40,"s":[128]},{"t":60,"s":[128]}],"ix":4}},"a":{"a":0,"k":[30,198,0],"ix":1,"l":2},"s":{"a":0,"k":[12.5,12.5,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[1036,1036],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"tm","s":{"a":0,"k":37,"ix":1},"e":{"a":0,"k":63,"ix":2},"o":{"a":0,"k":180,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":128,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[30,198],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":4,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":40,"op":40.6060606060606,"st":40,"ct":1,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"Shape Layer 3","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":0,"s":[0]},{"i":{"x":[0.413],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":20,"s":[100]},{"i":{"x":[0],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":89.822,"s":[100]},{"t":119.998046875,"s":[0]}],"ix":11},"r":{"a":0,"k":45,"ix":10},"p":{"s":true,"x":{"a":1,"k":[{"i":{"x":[0.521],"y":[1]},"o":{"x":[0.473],"y":[0.524]},"t":0,"s":[-11.5]},{"t":120,"s":[124.125]}],"ix":3},"y":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[128]},{"t":20,"s":[128]}],"ix":4}},"a":{"a":0,"k":[30,198,0],"ix":1,"l":2},"s":{"a":0,"k":[12.5,12.5,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[1036,1036],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"tm","s":{"a":0,"k":37,"ix":1},"e":{"a":0,"k":63,"ix":2},"o":{"a":0,"k":180,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":128,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[30,198],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":4,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":40,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"Shape Layer 2","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":-40,"s":[0]},{"i":{"x":[0.413],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":-20,"s":[100]},{"i":{"x":[0],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":49.822,"s":[100]},{"t":79.998046875,"s":[0]}],"ix":11},"r":{"a":0,"k":45,"ix":10},"p":{"s":true,"x":{"a":1,"k":[{"i":{"x":[0.521],"y":[1]},"o":{"x":[0.473],"y":[0.524]},"t":-40,"s":[-11.5]},{"t":80,"s":[124.125]}],"ix":3},"y":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-40,"s":[128]},{"t":-20,"s":[128]}],"ix":4}},"a":{"a":0,"k":[30,198,0],"ix":1,"l":2},"s":{"a":0,"k":[12.5,12.5,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[1036,1036],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"tm","s":{"a":0,"k":37,"ix":1},"e":{"a":0,"k":63,"ix":2},"o":{"a":0,"k":180,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":128,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[30,198],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":4,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":40,"st":-40,"ct":1,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"Shape Layer 1","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":-80,"s":[0]},{"i":{"x":[0.413],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":-60,"s":[100]},{"i":{"x":[0],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":9.822,"s":[100]},{"t":39.998046875,"s":[0]}],"ix":11},"r":{"a":0,"k":45,"ix":10},"p":{"s":true,"x":{"a":1,"k":[{"i":{"x":[0.521],"y":[1]},"o":{"x":[0.473],"y":[0.524]},"t":-80,"s":[-11.5]},{"t":40,"s":[124.125]}],"ix":3},"y":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-80,"s":[128]},{"t":-60,"s":[128]}],"ix":4}},"a":{"a":0,"k":[30,198,0],"ix":1,"l":2},"s":{"a":0,"k":[12.5,12.5,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[1036,1036],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"tm","s":{"a":0,"k":37,"ix":1},"e":{"a":0,"k":63,"ix":2},"o":{"a":0,"k":180,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":128,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[30,198],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":4,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":40,"st":-80,"ct":1,"bm":0}],"markers":[],"props":{}}
\ No newline at end of file
diff --git a/CollapseLauncher/Classes/AnimatedVisuals/Lottie/StartGameIcon.lottie b/CollapseLauncher/Classes/AnimatedVisuals/Lottie/StartGameIcon.lottie
deleted file mode 100644
index 22323e1b1..000000000
Binary files a/CollapseLauncher/Classes/AnimatedVisuals/Lottie/StartGameIcon.lottie and /dev/null differ
diff --git a/CollapseLauncher/Classes/AnimatedVisuals/Lottie/UpdateIcon.cs b/CollapseLauncher/Classes/AnimatedVisuals/Lottie/UpdateIcon.cs
index f41006052..13ecb8fde 100644
--- a/CollapseLauncher/Classes/AnimatedVisuals/Lottie/UpdateIcon.cs
+++ b/CollapseLauncher/Classes/AnimatedVisuals/Lottie/UpdateIcon.cs
@@ -3,13 +3,13 @@
// This code was generated by a tool.
//
// LottieGen version:
-// 8.0.280225.1+7cd366a738
+// 8.2.250604.1+b02a3ee244
//
// Command:
-// LottieGen -Language CSharp -Namespace CollapseLauncher.AnimatedVisuals.Lottie -Public -WinUIVersion 3.0 -InputFile UpdateIcon.lottie
+// LottieGen -GenerateColorBindings -GenerateDependencyObject -Language CSharp -Namespace CollapseLauncher.AnimatedVisuals.Lottie -Public -WinUIVersion 3.0 -InputFile UpdateIcon.json
//
// Input file:
-// UpdateIcon.lottie (1532 bytes created 20:12+07:00 Jun 2 2024)
+// UpdateIcon.json (5270 bytes created 16:59+07:00 Jan 4 2026)
//
// LottieGen source:
// http://aka.ms/Lottie
@@ -21,16 +21,16 @@
// ____________________________________
// | Object stats | Count |
// |__________________________|_______|
-// | All CompositionObjects | 42 |
+// | All CompositionObjects | 45 |
// |--------------------------+-------|
-// | Expression animators | 1 |
+// | Expression animators | 2 |
// | KeyFrame animators | 3 |
-// | Reference parameters | 1 |
-// | Expression operations | 0 |
+// | Reference parameters | 2 |
+// | Expression operations | 4 |
// |--------------------------+-------|
-// | Animated brushes | - |
+// | Animated brushes | 1 |
// | Animated gradient stops | - |
-// | ExpressionAnimations | 1 |
+// | ExpressionAnimations | 2 |
// | PathKeyFrameAnimations | - |
// |--------------------------+-------|
// | ContainerVisuals | 3 |
@@ -43,8 +43,11 @@
// | Gradient stops | - |
// | CompositionVisualSurface | - |
// ------------------------------------
+using Microsoft.Graphics;
using Microsoft.Graphics.Canvas.Geometry;
using Microsoft.UI.Composition;
+using Microsoft.UI.Xaml;
+using Microsoft.UI.Xaml.Media;
using System;
using System.Collections.Generic;
using System.Numerics;
@@ -56,13 +59,67 @@ namespace CollapseLauncher.AnimatedVisuals.Lottie
// Frame rate: 60 fps
// Frame count: 240
// Duration: 4000.0 mS
- sealed partial class UpdateIcon
- : Microsoft.UI.Xaml.Controls.IAnimatedVisualSource
+ public sealed partial class UpdateIcon
+ : BindableThemeChangeAnimation
+ , Microsoft.UI.Xaml.Controls.IAnimatedVisualSource
, Microsoft.UI.Xaml.Controls.IAnimatedVisualSource2
{
+ protected override void OnForegroundChanged(Brush brush)
+ {
+ if (brush is SolidColorBrush colorBrush)
+ {
+ Color_FFFFFF = colorBrush.Color;
+ return;
+ }
+
+ if (brush is AcrylicBrush acrylicBrush)
+ {
+ Color_FFFFFF = acrylicBrush.TintColor;
+ return;
+ }
+ }
+
+ private static Color GetForegroundColor(bool isLightTheme) =>
+ isLightTheme
+ ? Color.FromArgb(0xFF, 0x00, 0x00, 0x00)
+ : Color.FromArgb(0xFF, 0xFF, 0xFF, 0xFF);
+
// Animation duration: 4.000 seconds.
internal const long c_durationTicks = 40000000;
+ CompositionPropertySet _themeProperties;
+
+ ///
+ /// Dependency property for Color_FFFFFF.
+ ///
+ public static readonly DependencyProperty Color_FFFFFFProperty =
+ DependencyProperty.Register("Color_FFFFFF", typeof(Color), typeof(UpdateIcon),
+ new PropertyMetadata(GetForegroundColor(InnerLauncherConfig.IsAppThemeLight), OnColor_FFFFFFChanged));
+
+ // Theme properties.
+ public Color Color_FFFFFF
+ {
+ get => (Color)GetValue(Color_FFFFFFProperty);
+ set => SetValue(Color_FFFFFFProperty, value);
+ }
+
+ static Vector4 ColorAsVector4(Color color) => new Vector4(color.R, color.G, color.B, color.A);
+
+ static void OnColor_FFFFFFChanged(DependencyObject d, DependencyPropertyChangedEventArgs args)
+ {
+ ((UpdateIcon)d)._themeProperties?.InsertVector4("Color_FFFFFF", ColorAsVector4((Color)(Color)args.NewValue));
+ }
+
+ CompositionPropertySet EnsureThemeProperties(Compositor compositor)
+ {
+ if (_themeProperties == null)
+ {
+ _themeProperties = compositor.CreatePropertySet();
+ _themeProperties.InsertVector4("Color_FFFFFF", ColorAsVector4((Color)Color_FFFFFF));
+ }
+ return _themeProperties;
+ }
+
public Microsoft.UI.Xaml.Controls.IAnimatedVisual TryCreateAnimatedVisual(Compositor compositor)
{
object ignored = null;
@@ -72,10 +129,12 @@ public Microsoft.UI.Xaml.Controls.IAnimatedVisual TryCreateAnimatedVisual(Compos
public Microsoft.UI.Xaml.Controls.IAnimatedVisual TryCreateAnimatedVisual(Compositor compositor, out object diagnostics)
{
diagnostics = null;
+ EnsureThemeProperties(compositor);
var res =
new UpdateIcon_AnimatedVisual(
- compositor
+ compositor,
+ _themeProperties
);
res.CreateAnimations();
return res;
@@ -119,6 +178,19 @@ public double FrameToProgress(double frameNumber)
///
public void SetColorProperty(string propertyName, Color value)
{
+ if (propertyName == "Color_FFFFFF")
+ {
+ Color_FFFFFF = value;
+ }
+ else
+ {
+ return;
+ }
+
+ if (_themeProperties != null)
+ {
+ _themeProperties.InsertVector4(propertyName, ColorAsVector4(value));
+ }
}
///
@@ -134,11 +206,11 @@ sealed partial class UpdateIcon_AnimatedVisual
, Microsoft.UI.Xaml.Controls.IAnimatedVisual2
{
const long c_durationTicks = 40000000;
- readonly Color _color = InnerLauncherConfig.IsAppThemeLight ? Color.FromArgb(0xFF, 0xFF, 0xFF, 0xFF) : Color.FromArgb(0xFF, 0x00, 0x00, 0x00);
readonly Compositor _c;
readonly ExpressionAnimation _reusableExpressionAnimation;
+ readonly CompositionPropertySet _themeProperties;
AnimationController _animationController_0;
- CompositionColorBrush _colorBrush_White;
+ CompositionColorBrush _themeColor_Color_FFFFFF;
CompositionPathGeometry _pathGeometry_2;
CompositionPathGeometry _pathGeometry_3;
ContainerVisual _containerVisual_0;
@@ -186,7 +258,7 @@ AnimationController AnimationController_0()
// - - - - - PreComp layer: UpdateIcon
// - - - Layer aggregator
- // - - Offset:<1543, 561>
+ // - - Scale:0.125,0.125, Offset:<192.875, 70.125>
CanvasGeometry Geometry_0()
{
CanvasGeometry result;
@@ -203,7 +275,7 @@ CanvasGeometry Geometry_0()
// - - - - - PreComp layer: UpdateIcon
// - - - Layer aggregator
- // - - Offset:<505.5, 1488>
+ // - - Scale:0.125,0.125, Offset:<63.188, 186>
CanvasGeometry Geometry_1()
{
CanvasGeometry result;
@@ -220,7 +292,7 @@ CanvasGeometry Geometry_1()
// - - - - - PreComp layer: UpdateIcon
// - - - Layer aggregator
- // - - Offset:<1024, 1024>
+ // - - Scale:0.125,0.125, Offset:<128, 128>
CanvasGeometry Geometry_2()
{
CanvasGeometry result;
@@ -241,7 +313,7 @@ CanvasGeometry Geometry_2()
// - - - - - PreComp layer: UpdateIcon
// - - - Layer aggregator
- // - - Offset:<1024, 1024>
+ // - - Scale:0.125,0.125, Offset:<128, 128>
CanvasGeometry Geometry_3()
{
CanvasGeometry result;
@@ -259,16 +331,18 @@ CanvasGeometry Geometry_3()
return result;
}
- CompositionColorBrush ColorBrush_White()
+ // Color bound to theme property value: Color_FFFFFF
+ CompositionColorBrush ThemeColor_Color_FFFFFF()
{
- return (_colorBrush_White == null)
- ? _colorBrush_White = _c.CreateColorBrush(_color)
- : _colorBrush_White;
+ if (_themeColor_Color_FFFFFF != null) { return _themeColor_Color_FFFFFF; }
+ var result = _themeColor_Color_FFFFFF = _c.CreateColorBrush();
+ BindProperty(_themeColor_Color_FFFFFF, "Color", "ColorRGB(_theme.Color_FFFFFF.W,_theme.Color_FFFFFF.X,_theme.Color_FFFFFF.Y,_theme.Color_FFFFFF.Z)", "_theme", _themeProperties);
+ return result;
}
// - - - PreComp layer: UpdateIcon
// - Layer aggregator
- // Offset:<1543, 561>
+ // Scale:0.125,0.125, Offset:<192.875, 70.125>
CompositionPathGeometry PathGeometry_0()
{
return _c.CreatePathGeometry(new CompositionPath(Geometry_0()));
@@ -276,7 +350,7 @@ CompositionPathGeometry PathGeometry_0()
// - - - PreComp layer: UpdateIcon
// - Layer aggregator
- // Offset:<505.5, 1488>
+ // Scale:0.125,0.125, Offset:<63.188, 186>
CompositionPathGeometry PathGeometry_1()
{
return _c.CreatePathGeometry(new CompositionPath(Geometry_1()));
@@ -284,7 +358,7 @@ CompositionPathGeometry PathGeometry_1()
// - - - PreComp layer: UpdateIcon
// - Layer aggregator
- // Offset:<1024, 1024>
+ // Scale:0.125,0.125, Offset:<128, 128>
CompositionPathGeometry PathGeometry_2()
{
if (_pathGeometry_2 != null) { return _pathGeometry_2; }
@@ -294,7 +368,7 @@ CompositionPathGeometry PathGeometry_2()
// - - - PreComp layer: UpdateIcon
// - Layer aggregator
- // Offset:<1024, 1024>
+ // Scale:0.125,0.125, Offset:<128, 128>
CompositionPathGeometry PathGeometry_3()
{
if (_pathGeometry_3 != null) { return _pathGeometry_3; }
@@ -307,9 +381,9 @@ CompositionPathGeometry PathGeometry_3()
// Path 1
CompositionSpriteShape SpriteShape_0()
{
- // Offset:<1543, 561>
- var result = CreateSpriteShape(PathGeometry_0(), new Matrix3x2(1F, 0F, 0F, 1F, 1543F, 561F));;
- result.StrokeBrush = ColorBrush_White();
+ // Offset:<192.875, 70.125>, Scale:<0.125, 0.125>
+ var result = CreateSpriteShape(PathGeometry_0(), new Matrix3x2(0.125F, 0F, 0F, 0.125F, 192.875F, 70.125F));;
+ result.StrokeBrush = ThemeColor_Color_FFFFFF();
result.StrokeDashCap = CompositionStrokeCap.Round;
result.StrokeStartCap = CompositionStrokeCap.Round;
result.StrokeEndCap = CompositionStrokeCap.Round;
@@ -323,9 +397,9 @@ CompositionSpriteShape SpriteShape_0()
// Path 1
CompositionSpriteShape SpriteShape_1()
{
- // Offset:<505.5, 1488>
- var result = CreateSpriteShape(PathGeometry_1(), new Matrix3x2(1F, 0F, 0F, 1F, 505.5F, 1488F));;
- result.StrokeBrush = ColorBrush_White();
+ // Offset:<63.188, 186>, Scale:<0.125, 0.125>
+ var result = CreateSpriteShape(PathGeometry_1(), new Matrix3x2(0.125F, 0F, 0F, 0.125F, 63.1879997F, 186F));;
+ result.StrokeBrush = ThemeColor_Color_FFFFFF();
result.StrokeDashCap = CompositionStrokeCap.Round;
result.StrokeStartCap = CompositionStrokeCap.Round;
result.StrokeEndCap = CompositionStrokeCap.Round;
@@ -339,9 +413,9 @@ CompositionSpriteShape SpriteShape_1()
// Path 1
CompositionSpriteShape SpriteShape_2()
{
- // Offset:<1024, 1024>
- var result = CreateSpriteShape(PathGeometry_2(), new Matrix3x2(1F, 0F, 0F, 1F, 1024F, 1024F));;
- result.StrokeBrush = ColorBrush_White();
+ // Offset:<128, 128>, Scale:<0.125, 0.125>
+ var result = CreateSpriteShape(PathGeometry_2(), new Matrix3x2(0.125F, 0F, 0F, 0.125F, 128F, 128F));;
+ result.StrokeBrush = ThemeColor_Color_FFFFFF();
result.StrokeDashCap = CompositionStrokeCap.Round;
result.StrokeStartCap = CompositionStrokeCap.Round;
result.StrokeEndCap = CompositionStrokeCap.Round;
@@ -356,9 +430,9 @@ CompositionSpriteShape SpriteShape_2()
// Path 1
CompositionSpriteShape SpriteShape_3()
{
- // Offset:<1024, 1024>
- var result = CreateSpriteShape(PathGeometry_3(), new Matrix3x2(1F, 0F, 0F, 1F, 1024F, 1024F));;
- result.StrokeBrush = ColorBrush_White();
+ // Offset:<128, 128>, Scale:<0.125, 0.125>
+ var result = CreateSpriteShape(PathGeometry_3(), new Matrix3x2(0.125F, 0F, 0F, 0.125F, 128F, 128F));;
+ result.StrokeBrush = ThemeColor_Color_FFFFFF();
result.StrokeDashCap = CompositionStrokeCap.Round;
result.StrokeStartCap = CompositionStrokeCap.Round;
result.StrokeEndCap = CompositionStrokeCap.Round;
@@ -373,8 +447,7 @@ ContainerVisual ContainerVisual_0()
{
if (_containerVisual_0 != null) { return _containerVisual_0; }
var result = _containerVisual_0 = _c.CreateContainerVisual();
- result.CenterPoint = new Vector3(1024F, 1024F, 0F);
- result.Offset = new Vector3(176F, 176F, 0F);
+ result.CenterPoint = new Vector3(128F, 128F, 0F);
result.Scale = new Vector3(1F, 1F, 0F);
result.Children.InsertAtTop(ContainerVisual_1());
return result;
@@ -385,7 +458,7 @@ ContainerVisual ContainerVisual_1()
{
var result = _c.CreateContainerVisual();
result.Clip = InsetClip_0();
- result.Size = new Vector2(2048F, 2048F);
+ result.Size = new Vector2(256F, 256F);
// Layer aggregator
result.Children.InsertAtTop(ShapeVisual_0());
return result;
@@ -448,15 +521,15 @@ ScalarKeyFrameAnimation TrimStartScalarAnimation_0_to_0()
ShapeVisual ShapeVisual_0()
{
var result = _c.CreateShapeVisual();
- result.Size = new Vector2(2048F, 2048F);
+ result.Size = new Vector2(256F, 256F);
var shapes = result.Shapes;
- // Offset:<1543, 561>
+ // Scale:0.125,0.125, Offset:<192.875, 70.125>
shapes.Add(SpriteShape_0());
- // Offset:<505.5, 1488>
+ // Scale:0.125,0.125, Offset:<63.188, 186>
shapes.Add(SpriteShape_1());
- // Offset:<1024, 1024>
+ // Scale:0.125,0.125, Offset:<128, 128>
shapes.Add(SpriteShape_2());
- // Offset:<1024, 1024>
+ // Scale:0.125,0.125, Offset:<128, 128>
shapes.Add(SpriteShape_3());
return result;
}
@@ -470,17 +543,19 @@ StepEasingFunction HoldThenStepEasingFunction()
}
internal UpdateIcon_AnimatedVisual(
- Compositor compositor
+ Compositor compositor,
+ CompositionPropertySet themeProperties
)
{
_c = compositor;
+ _themeProperties = themeProperties;
_reusableExpressionAnimation = compositor.CreateExpressionAnimation();
Root();
}
public Visual RootVisual => _root;
public TimeSpan Duration => TimeSpan.FromTicks(c_durationTicks);
- public Vector2 Size => new Vector2(2400F, 2400F);
+ public Vector2 Size => new Vector2(256F, 256F);
void IDisposable.Dispose() => _root?.Dispose();
public void CreateAnimations()
diff --git a/CollapseLauncher/Classes/AnimatedVisuals/Lottie/UpdateIcon.json b/CollapseLauncher/Classes/AnimatedVisuals/Lottie/UpdateIcon.json
new file mode 100644
index 000000000..e57b78cd8
--- /dev/null
+++ b/CollapseLauncher/Classes/AnimatedVisuals/Lottie/UpdateIcon.json
@@ -0,0 +1 @@
+{"v":"5.12.2","fr":60,"ip":0,"op":240,"w":256,"h":256,"nm":"UpdateIconMasterComp","ddd":0,"assets":[{"id":"comp_0","nm":"UpdateIcon","fr":60,"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Shape Layer 2","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[128,128,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[12.5,12.5,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-14.666,56.266],[-31.132,53.758],[-105.895,61.521],[-128.947,-0.321],[-103.436,-60.448],[-61.24,-107.197]],"o":[[16.051,-61.58],[61.391,-106.008],[103.968,-60.401],[128.408,0.32],[106.564,62.276],[14.259,24.959]],"v":[[-679.411,-177.885],[-608.024,-351.501],[-352.405,-607.463],[1.406,-702.002],[353.538,-606.465],[610.018,-347.459]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":128,"ix":5},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":0,"s":[0]},{"i":{"x":[0],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":30,"s":[50]},{"t":80,"s":[0]}],"ix":1},"e":{"a":0,"k":100,"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":0,"op":242,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Shape Layer 3","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[128,128,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[12.5,12.5,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[11.129,-42.34],[31.671,-54.12],[104.889,-60.982],[129.029,0.321],[102.932,59.646],[61.568,105.903],[11.121,25.826]],"o":[[-16.328,62.123],[-61.282,104.72],[-104.012,60.472],[-127.485,-0.317],[-106.028,-61.44],[-14.021,-24.117],[-3.58,-8.314]],"v":[[679.076,179.264],[606.447,354.255],[352.629,607.348],[-1.38,702.002],[-351.277,607.765],[-607.341,352.059],[-645.109,277.089]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":128,"ix":5},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":0,"s":[0]},{"i":{"x":[0],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":30,"s":[50]},{"t":80,"s":[0]}],"ix":1},"e":{"a":0,"k":100,"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":0,"op":242,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"Shape Layer 4","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[63.188,186,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[12.5,12.5,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-244.242,122.011],[-123.464,-207.693],[206.416,-86.902]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":128,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false}],"ip":0,"op":242,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"Shape Layer 1","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[192.875,70.125,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[12.5,12.5,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[244.229,-121.905],[123.49,207.693],[-206.2,86.799]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":128,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false}],"ip":0,"op":242,"st":0,"ct":1,"bm":0}]}],"layers":[{"ddd":0,"ind":1,"ty":0,"nm":"UpdateIcon","refId":"comp_0","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":0,"s":[0]},{"i":{"x":[0.024],"y":[1]},"o":{"x":[0.158],"y":[0]},"t":30,"s":[-65]},{"t":220,"s":[2160]}],"ix":10},"p":{"a":0,"k":[128,128,0],"ix":2,"l":2},"a":{"a":0,"k":[128,128,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"w":256,"h":256,"ip":0,"op":240,"st":0,"bm":0}],"markers":[],"props":{}}
\ No newline at end of file
diff --git a/CollapseLauncher/Classes/AnimatedVisuals/Lottie/UpdateIcon.lottie b/CollapseLauncher/Classes/AnimatedVisuals/Lottie/UpdateIcon.lottie
deleted file mode 100644
index 23168ac30..000000000
Binary files a/CollapseLauncher/Classes/AnimatedVisuals/Lottie/UpdateIcon.lottie and /dev/null differ
diff --git a/CollapseLauncher/Classes/EventsManagement/EventsHandler.cs b/CollapseLauncher/Classes/EventsManagement/EventsHandler.cs
index 5f891f117..fcaf47cec 100644
--- a/CollapseLauncher/Classes/EventsManagement/EventsHandler.cs
+++ b/CollapseLauncher/Classes/EventsManagement/EventsHandler.cs
@@ -304,50 +304,6 @@ public class NotificationInvokerProp
}
#endregion
- #region BackgroundRegion
- internal static class BackgroundImgChanger
- {
- private static readonly BackgroundImgChangerInvoker Invoker = new();
- public static void ChangeBackground(string imgPath, Action actionAfterLoaded,
- bool isCustom = true, bool isForceRecreateCache = false, bool isRequestInit = false)
- {
- Invoker!.ChangeBackground(imgPath, actionAfterLoaded, isCustom, isForceRecreateCache, isRequestInit);
- }
- public static void ToggleBackground(bool hide) => Invoker!.ToggleBackground(hide);
- }
-
- internal class BackgroundImgChangerInvoker
- {
- public static event EventHandler ImgEvent;
- public static event EventHandler IsImageHide;
-
- public void ChangeBackground(string imgPath, Action actionAfterLoaded,
- bool isCustom, bool isForceRecreateCache = false, bool isRequestInit = false)
- {
- ImgEvent?.Invoke(this, new BackgroundImgProperty(imgPath, isCustom, isForceRecreateCache, isRequestInit, actionAfterLoaded));
- }
-
- public void ToggleBackground(bool hide) => IsImageHide?.Invoke(this, hide);
- }
-
- internal class BackgroundImgProperty
- {
- internal BackgroundImgProperty(string imgPath, bool isCustom, bool isForceRecreateCache, bool isRequestInit, Action actionAfterLoaded)
- {
- ImgPath = imgPath;
- IsCustom = isCustom;
- IsForceRecreateCache = isForceRecreateCache;
- IsRequestInit = isRequestInit;
- ActionAfterLoaded = actionAfterLoaded;
- }
-
- public Action ActionAfterLoaded { get; }
- public bool IsRequestInit { get; }
- public bool IsForceRecreateCache { get; }
- public string ImgPath { get; }
- public bool IsCustom { get; }
- }
- #endregion
#region SpawnWebView2Region
internal static class SpawnWebView2
{
diff --git a/CollapseLauncher/Classes/Extension/DispatcherQueueExtensions.cs b/CollapseLauncher/Classes/Extension/DispatcherQueueExtensions.cs
index 19e705f40..794b7fd71 100644
--- a/CollapseLauncher/Classes/Extension/DispatcherQueueExtensions.cs
+++ b/CollapseLauncher/Classes/Extension/DispatcherQueueExtensions.cs
@@ -1,11 +1,23 @@
using Microsoft.UI.Dispatching;
using System;
+#nullable enable
namespace CollapseLauncher.Extension
{
public static class DispatcherQueueExtensions
{
- public static bool HasThreadAccessSafe(this DispatcherQueue queue)
+ static DispatcherQueueExtensions()
+ {
+ CurrentDispatcherQueue ??= DispatcherQueue.GetForCurrentThread();
+ }
+
+ public static DispatcherQueue CurrentDispatcherQueue
+ {
+ get;
+ set;
+ }
+
+ public static bool HasThreadAccessSafe(this DispatcherQueue? queue)
{
if (queue == null) return false;
@@ -18,5 +30,24 @@ public static bool HasThreadAccessSafe(this DispatcherQueue queue)
return false; // Return false if an exception occurs
}
}
+
+ public static T CreateObjectFromUIThread()
+ where T : class, new()
+ {
+ if (CurrentDispatcherQueue.HasThreadAccessSafe())
+ {
+ return new T();
+ }
+
+ T? obj = null;
+ CurrentDispatcherQueue.TryEnqueue(() => obj = new T());
+
+ if (obj == null)
+ {
+ throw new NullReferenceException("Object unexpectedly cannot be created.");
+ }
+
+ return obj;
+ }
}
}
\ No newline at end of file
diff --git a/CollapseLauncher/Classes/Extension/UIElementExtensions.SetCursor.cs b/CollapseLauncher/Classes/Extension/UIElementExtensions.SetCursor.cs
new file mode 100644
index 000000000..b5acabe70
--- /dev/null
+++ b/CollapseLauncher/Classes/Extension/UIElementExtensions.SetCursor.cs
@@ -0,0 +1,50 @@
+using Microsoft.UI.Input;
+using Microsoft.UI.Xaml;
+using Microsoft.UI.Xaml.Controls;
+using System;
+using System.Runtime.CompilerServices;
+
+#nullable enable
+#pragma warning disable IDE0130
+namespace CollapseLauncher.Extension;
+
+public static partial class UIElementExtensions
+{
+ public static readonly DependencyProperty CursorTypeProperty =
+ DependencyProperty.RegisterAttached("CursorType", typeof(InputSystemCursorShape),
+ typeof(UIElement), new PropertyMetadata(InputSystemCursorShape.Arrow));
+
+ public static InputSystemCursorShape GetCursorType(DependencyObject obj) => (InputSystemCursorShape)obj.GetValue(CursorTypeProperty);
+
+ public static void SetCursorType(DependencyObject obj, InputSystemCursorShape value)
+ {
+ InputSystemCursor? cursor = InputSystemCursor.Create(value);
+ if (cursor is null ||
+ obj is not UIElement asElement)
+ {
+ return;
+ }
+
+ asElement.SetCursor(cursor);
+ asElement.SetValue(CursorTypeProperty, value);
+ }
+
+ extension(Control source)
+ {
+ [UnsafeAccessor(UnsafeAccessorKind.Method, Name = "GetTemplateChild")]
+ private extern DependencyObject GetTemplateChildAccessor(string name);
+
+ internal T GetTemplateChild(string name)
+ where T : class
+ {
+ DependencyObject obj = source.GetTemplateChildAccessor(name);
+ if (obj is not T castObj)
+ {
+ throw new
+ InvalidCastException($"Cannot cast type to: {typeof(T).Name} as the object expects type: {obj.GetType().Name}");
+ }
+
+ return castObj;
+ }
+ }
+}
diff --git a/CollapseLauncher/Classes/Extension/UIElementExtensions.SetIsTranslationEnabled.cs b/CollapseLauncher/Classes/Extension/UIElementExtensions.SetIsTranslationEnabled.cs
new file mode 100644
index 000000000..860ca02da
--- /dev/null
+++ b/CollapseLauncher/Classes/Extension/UIElementExtensions.SetIsTranslationEnabled.cs
@@ -0,0 +1,21 @@
+using Microsoft.UI.Xaml;
+using Microsoft.UI.Xaml.Hosting;
+
+#nullable enable
+#pragma warning disable IDE0130
+namespace CollapseLauncher.Extension;
+
+public static partial class UIElementExtensions
+{
+ public static readonly DependencyProperty IsTranslationEnabledProperty =
+ DependencyProperty.RegisterAttached("IsTranslationEnabled", typeof(bool),
+ typeof(UIElement), new PropertyMetadata(false));
+
+ public static bool GetIsTranslationEnabled(DependencyObject obj) => (bool)((UIElement)obj).GetValue(IsTranslationEnabledProperty);
+
+ public static void SetIsTranslationEnabled(DependencyObject obj, bool value)
+ {
+ ElementCompositionPreview.SetIsTranslationEnabled((UIElement)obj, value);
+ ((UIElement)obj).SetValue(IsTranslationEnabledProperty, value);
+ }
+}
diff --git a/CollapseLauncher/Classes/Extension/UIElementExtensions.ThemeExtensions.cs b/CollapseLauncher/Classes/Extension/UIElementExtensions.ThemeExtensions.cs
index bd33a12e3..f045cc4cb 100644
--- a/CollapseLauncher/Classes/Extension/UIElementExtensions.ThemeExtensions.cs
+++ b/CollapseLauncher/Classes/Extension/UIElementExtensions.ThemeExtensions.cs
@@ -8,7 +8,7 @@
#nullable enable
namespace CollapseLauncher.Extension
{
- internal static partial class UIElementExtensions
+ public static partial class UIElementExtensions
{
private static string CurrentReversedThemeKey => InnerLauncherConfig.IsAppThemeLight ? "Dark" : "Light";
diff --git a/CollapseLauncher/Classes/Extension/UIElementExtensions.UnsafeAccessorExtensions.cs b/CollapseLauncher/Classes/Extension/UIElementExtensions.UnsafeAccessorExtensions.cs
index 40a9d8432..efbeff7e4 100644
--- a/CollapseLauncher/Classes/Extension/UIElementExtensions.UnsafeAccessorExtensions.cs
+++ b/CollapseLauncher/Classes/Extension/UIElementExtensions.UnsafeAccessorExtensions.cs
@@ -5,7 +5,7 @@
#nullable enable
namespace CollapseLauncher.Extension
{
- internal static partial class UIElementExtensions
+ public static partial class UIElementExtensions
{
///
/// Set the cursor for the element.
diff --git a/CollapseLauncher/Classes/Extension/UIElementExtensions.cs b/CollapseLauncher/Classes/Extension/UIElementExtensions.cs
index 08b471d43..d63a6d1a0 100644
--- a/CollapseLauncher/Classes/Extension/UIElementExtensions.cs
+++ b/CollapseLauncher/Classes/Extension/UIElementExtensions.cs
@@ -36,7 +36,7 @@ internal class NavigationViewItemLocaleTextProperty
public string LocalePropertyName { get; init; }
}
- internal static partial class UIElementExtensions
+ public static partial class UIElementExtensions
{
#nullable enable
///
@@ -188,6 +188,20 @@ internal static void ApplyNavigationViewItemLocaleTextBindings(this NavigationVi
navViewControl.UpdateLayout();
}
+ internal static void BindProperty(this FrameworkElement element,
+ object? source,
+ string propertyName,
+ DependencyProperty dependencyProperty,
+ BindingMode bindingMode)
+ {
+ element.SetBinding(dependencyProperty, new Binding
+ {
+ Mode = bindingMode,
+ Source = source,
+ Path = new PropertyPath(propertyName)
+ });
+ }
+
internal static void BindProperty(
this T element,
DependencyProperty dependencyProperty,
@@ -293,11 +307,15 @@ internal static Grid CreateIconTextGrid(string text = null, string iconGlyph = n
return contentPanel;
}
- internal static Grid CreateGrid() => new();
- internal static StackPanel CreateStackPanel(Orientation orientation = Orientation.Vertical) => new() { Orientation = orientation };
+ internal static Grid CreateGrid() =>
+ CreateElementFromUIThread();
+
+ internal static StackPanel CreateStackPanel(Orientation orientation = Orientation.Vertical) =>
+ CreateElementFromUIThread(x => x.Orientation = orientation);
+
+ internal static void AddElementToStackPanel(this Panel stackPanel, params FrameworkElement[] elements) =>
+ AddElementToStackPanel(stackPanel, elements.AsEnumerable());
- internal static void AddElementToStackPanel(this Panel stackPanel, params FrameworkElement[] elements)
- => AddElementToStackPanel(stackPanel, elements.AsEnumerable());
internal static void AddElementToStackPanel(this Panel stackPanel, IEnumerable elements)
{
foreach (FrameworkElement element in elements)
@@ -1039,29 +1057,6 @@ public static nint GetPointerFromDependencyObject(this T? element)
where T : DependencyObject
=> ptr == nint.Zero ? null : MarshalInspectable.FromAbi(ptr);
-
- internal static readonly InputSystemCursor HandCursor = InputSystemCursor.Create(InputSystemCursorShape.Hand);
-
- internal static void AttachHandCursorRecursiveOnLoaded(object sender, RoutedEventArgs e)
- {
- if (sender is not UIElement element)
- {
- return;
- }
-
- element.SetAllControlsCursorRecursive(HandCursor);
- }
-
- internal static void AttachHandCursorOnLoaded(object sender, RoutedEventArgs e)
- {
- if (sender is not UIElement element)
- {
- return;
- }
-
- element.SetCursor(HandCursor);
- }
-
internal static void EnableImplicitAnimationRecursiveOnLoaded(object sender, RoutedEventArgs e)
{
if (sender is not UIElement element)
@@ -1199,5 +1194,22 @@ internal static Grid FindOverlayGrid([NotNull] this XamlRoot? root, bool isAlway
// Return the result (whether if it's not found/as null, or any last grid)
return lastGrid;
}
+
+ internal static T CreateElementFromUIThread(Action? setAttributeDelegate = null)
+ where T : UIElement, new()
+ {
+ T element = DispatcherQueueExtensions.CreateObjectFromUIThread();
+ if (DispatcherQueueExtensions.CurrentDispatcherQueue.HasThreadAccessSafe())
+ {
+ setAttributeDelegate?.Invoke(element);
+ }
+ else
+ {
+ DispatcherQueueExtensions.CurrentDispatcherQueue
+ .TryEnqueue(() => setAttributeDelegate?.Invoke(element));
+ }
+
+ return element;
+ }
}
}
diff --git a/CollapseLauncher/Classes/GameManagement/ImageBackground/ClipboardUtility.cs b/CollapseLauncher/Classes/GameManagement/ImageBackground/ClipboardUtility.cs
new file mode 100644
index 000000000..98af5a9f5
--- /dev/null
+++ b/CollapseLauncher/Classes/GameManagement/ImageBackground/ClipboardUtility.cs
@@ -0,0 +1,277 @@
+using CollapseLauncher.Extension;
+using Hi3Helper;
+using Hi3Helper.Win32.ManagedTools;
+using Hi3Helper.Win32.Native.Enums;
+using Hi3Helper.Win32.Native.LibraryImport;
+using Hi3Helper.Win32.Native.Structs;
+using Microsoft.Extensions.Logging;
+using Microsoft.Graphics.Canvas;
+using Microsoft.UI.Dispatching;
+using System;
+using System.Buffers;
+using System.IO;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Runtime.InteropServices.WindowsRuntime;
+using System.Threading;
+using System.Threading.Tasks;
+using Windows.Graphics.DirectX;
+using Windows.Storage.Streams;
+
+// ReSharper disable CheckNamespace
+
+#nullable enable
+namespace CollapseLauncher.GameManagement.ImageBackground;
+
+public static class ClipboardUtility
+{
+ public static async void CopyCurrentFrameToClipboard(
+ ImageBackgroundManager? instance,
+ FrameToCopyType frameToCopyType)
+ {
+ bool isBackgroundVideo = false;
+
+ try
+ {
+ if (instance == null)
+ {
+ return;
+ }
+
+ // Try to get video frame buffer first (if the background is video).
+ CanvasRenderTarget? backgroundCanvas = frameToCopyType.HasFlag(FrameToCopyType.Background)
+ ? instance.CurrentBackgroundElement?.LockCanvasRenderTarget()
+ : null;
+
+ ICanvasResourceCreator? canvasDevice = backgroundCanvas;
+ canvasDevice ??= CanvasDevice.GetSharedDevice();
+
+ if (backgroundCanvas != null)
+ {
+ // Copy bitmap to avoid arbitrary write to video frame buffer.
+ CanvasRenderTarget oldCanvas = backgroundCanvas;
+ backgroundCanvas = new CanvasRenderTarget(canvasDevice, backgroundCanvas.Size._width, backgroundCanvas.Size._height, 96f);
+ backgroundCanvas.CopyPixelsFromBitmap(oldCanvas);
+
+ isBackgroundVideo = true; // Mark as video so we can unlock the frame later.
+ }
+
+ // If it's still null (which most likely an image), get the canvas.
+ backgroundCanvas ??= frameToCopyType.HasFlag(FrameToCopyType.Background)
+ ? await GetImageCanvasTargetBuffer(canvasDevice,
+ instance.CurrentSelectedBackgroundContext?
+ .OriginBackgroundImagePath)
+ : await GetImageCanvasTargetBuffer(canvasDevice,
+ instance.CurrentSelectedBackgroundContext?
+ .OriginOverlayImagePath);
+
+ // If background canvas is still null, abort.
+ if (backgroundCanvas == null)
+ {
+ return;
+ }
+
+ // Get foreground canvas if available.
+ CanvasRenderTarget? foregroundCanvas =
+ frameToCopyType == FrameToCopyType.Both && frameToCopyType.HasFlag(FrameToCopyType.Foreground)
+ ? await GetImageCanvasTargetBuffer(canvasDevice,
+ instance.CurrentSelectedBackgroundContext?
+ .OriginOverlayImagePath)
+ : null;
+
+ int widthOfForeground = (int)(foregroundCanvas?.SizeInPixels.Width ?? 0u);
+ int heightOfForeground = (int)(foregroundCanvas?.SizeInPixels.Height ?? 0u);
+ if (foregroundCanvas != null &&
+ (backgroundCanvas.SizeInPixels.Width != widthOfForeground ||
+ backgroundCanvas.SizeInPixels.Height != heightOfForeground))
+ {
+ backgroundCanvas = ResizeCanvasToSizeOf(backgroundCanvas, widthOfForeground, heightOfForeground);
+ }
+
+ MergeCanvasTargetWith(backgroundCanvas, foregroundCanvas); // Merge overlay (foreground) to background.
+ await CopyCanvasTargetBufferToClipboard(backgroundCanvas); // Copy to clipboard
+ }
+ catch (Exception ex)
+ {
+ Logger.LogWriteLine($"[ImageBackgroundManager::CopyCurrentFrameToClipboard] An error occurred while trying to copy current background frame to clipboard\r\n{ex}",
+ LogType.Error,
+ true);
+ }
+ finally
+ {
+ if (isBackgroundVideo)
+ {
+ instance?.CurrentBackgroundElement?.UnlockCanvasRenderTarget();
+ }
+ }
+ }
+
+ private static CanvasRenderTarget ResizeCanvasToSizeOf(CanvasRenderTarget canvasToResize, int width, int height)
+ {
+ CanvasRenderTarget resizedCanvas = new(canvasToResize,
+ width,
+ height,
+ canvasToResize.Dpi);
+ using CanvasDrawingSession ds = resizedCanvas.CreateDrawingSession();
+ ds.DrawImage(canvasToResize,
+ new Windows.Foundation.Rect(0, 0, width, height),
+ new Windows.Foundation.Rect(0, 0,
+ canvasToResize.SizeInPixels.Width,
+ canvasToResize.SizeInPixels.Height),
+ 1f,
+ CanvasImageInterpolation.HighQualityCubic);
+
+ canvasToResize.Dispose();
+ return resizedCanvas;
+ }
+
+ private static async Task GetImageCanvasTargetBuffer(ICanvasResourceCreator canvasDevice, string? path)
+ {
+ if (string.IsNullOrEmpty(path))
+ {
+ return null; // Ignore if path is null or empty.
+ }
+
+ // Get source image as native or decoded image (if it requires external codec decoding).
+ Uri sourceUri = new(path);
+ (sourceUri, _) = await ImageBackgroundManager
+ .GetNativeOrDecodedImagePath(sourceUri, CancellationToken.None);
+
+ using CanvasBitmap bitmap = sourceUri.IsFile ?
+ await CanvasBitmap.LoadAsync(canvasDevice,
+ sourceUri.LocalPath,
+ 96f, CanvasAlphaMode.Premultiplied) :
+ await CanvasBitmap.LoadAsync(canvasDevice,
+ sourceUri,
+ 96f, CanvasAlphaMode.Premultiplied);
+
+ // Load to render target
+ return await Task.Run(() =>
+ {
+ CanvasRenderTarget renderTarget = new(canvasDevice,
+ bitmap.SizeInPixels.Width,
+ bitmap.SizeInPixels.Height, bitmap.Dpi);
+ using CanvasDrawingSession ds = renderTarget.CreateDrawingSession();
+ ds.DrawImage(bitmap);
+ return renderTarget;
+ });
+ }
+
+ private static void MergeCanvasTargetWith(CanvasRenderTarget sourceCanvasTarget,
+ CanvasRenderTarget? withCanvasTarget)
+ {
+ if (withCanvasTarget == null)
+ {
+ return; // Abort
+ }
+
+ CanvasDrawingSession? ds = sourceCanvasTarget.CreateDrawingSession();
+ ds.DrawImage(withCanvasTarget);
+
+ // Dispose the overlay canvas
+ withCanvasTarget.Dispose();
+
+ if (DispatcherQueueExtensions.CurrentDispatcherQueue.HasThreadAccess)
+ {
+ ds.Dispose();
+ return;
+ }
+
+ DispatcherQueueExtensions.CurrentDispatcherQueue.TryEnqueue(DispatcherQueuePriority.High, () => ds.Dispose());
+ }
+
+ private static async Task CopyCanvasTargetBufferToClipboard(CanvasRenderTarget? sourceCanvasTarget)
+ {
+ const uint biBitfields = 3;
+ if (sourceCanvasTarget == null)
+ {
+ return;
+ }
+
+ // BGRA
+ // TODO: Add a way to assign the color size and channels based on CanvasRenderTarget.Format
+ const int colorSizePerChannel = 1; // 8-bit / 1 byte per color
+ const int channels = 4;
+
+ DirectXPixelFormat format = sourceCanvasTarget.Format;
+ CanvasAlphaMode alphaMode = sourceCanvasTarget.AlphaMode;
+
+ int width = (int)sourceCanvasTarget.SizeInPixels.Width;
+ int height = (int)sourceCanvasTarget.SizeInPixels.Height;
+ int stride = width * height * channels * colorSizePerChannel;
+
+ bool isClipboardOpened = false;
+
+ int sizeOfHeader = Marshal.SizeOf();
+ byte[] buffer = ArrayPool.Shared.Rent(stride + sizeOfHeader);
+ byte[] bufferPng = ArrayPool.Shared.Rent(stride + sizeOfHeader);
+ try
+ {
+ IBuffer windowsBuffer = buffer.AsBuffer(sizeOfHeader, stride, buffer.Length - sizeOfHeader);
+ sourceCanvasTarget.GetPixelBytes(windowsBuffer);
+
+ using MemoryStream pngBufferStream = new(bufferPng);
+ using IRandomAccessStream pngBufferRandomStream = pngBufferStream.AsRandomAccessStream();
+ await sourceCanvasTarget.SaveAsync(pngBufferRandomStream, CanvasBitmapFileFormat.Png);
+
+ // Dispose the source canvas
+ sourceCanvasTarget.Dispose();
+
+ // Prepare BITMAPV5HEADER
+ Span headerSpan = buffer.AsSpan(0, sizeOfHeader);
+ headerSpan.Clear();
+ ref BITMAPV5HEADER header = ref AsRef(headerSpan);
+
+ header.bV5Size = (uint)sizeOfHeader;
+ header.bV5Width = width;
+ header.bV5Height = -height;
+ header.bV5Planes = 1;
+ header.bV5BitCount = 32;
+ header.bV5Compression = biBitfields;
+ header.bV5SizeImage = (uint)stride;
+ header.bV5RedMask = 0x00FF0000;
+ header.bV5GreenMask = 0x0000FF00;
+ header.bV5BlueMask = 0x000000FF;
+ header.bV5AlphaMask = 0xFF000000;
+ header.bV5CSType = 0x73524742; // 'sRGB'
+
+ // Try open the Clipboard
+ if (!PInvoke.OpenClipboard(nint.Zero))
+ {
+ Logger.LogWriteLine($"[ClipboardUtility::CopyCanvasTargetBufferToClipboard] Cannot open the clipboard! Error: {Win32Error.GetLastWin32ErrorMessage()}",
+ LogType.Error,
+ true);
+ return;
+ }
+
+ // Make sure to empty the clipboard before setting and allocate the content
+ if (!PInvoke.EmptyClipboard())
+ {
+ Logger.LogWriteLine($"[ClipboardUtility::CopyCanvasTargetBufferToClipboard] Cannot clear the previous clipboard! Error: {Win32Error.GetLastWin32ErrorMessage()}",
+ LogType.Error,
+ true);
+ return;
+ }
+
+ isClipboardOpened = true;
+
+ // Load the "load" ( ͡° ͜ʖ ͡°)
+ ILogger logger = ILoggerHelper.GetILogger("ClipboardUtility::CopyCanvasTargetBufferToClipboard");
+ Clipboard.CopyDataToClipboard(buffer.AsSpan(0, sizeOfHeader + stride), StandardClipboardFormats.CF_DIBV5, logger); // Copy as BMP for fallback
+ Clipboard.CopyDataToClipboard(bufferPng.AsSpan(0, (int)pngBufferStream.Position), StandardClipboardFormats.CF_PNG, logger); // Copy as PNG
+ }
+ finally
+ {
+ ArrayPool.Shared.Return(buffer);
+ ArrayPool.Shared.Return(bufferPng);
+
+ if (isClipboardOpened)
+ {
+ // Close the clipboard
+ PInvoke.CloseClipboard();
+ }
+ }
+ }
+
+ private static unsafe ref T AsRef(Span span) => ref Unsafe.AsRef(Unsafe.AsPointer(ref MemoryMarshal.AsRef(span)));
+}
diff --git a/CollapseLauncher/Classes/GameManagement/ImageBackground/ImageBackgroundManager.FormatDetection.cs b/CollapseLauncher/Classes/GameManagement/ImageBackground/ImageBackgroundManager.FormatDetection.cs
new file mode 100644
index 000000000..11ed51b0b
--- /dev/null
+++ b/CollapseLauncher/Classes/GameManagement/ImageBackground/ImageBackgroundManager.FormatDetection.cs
@@ -0,0 +1,172 @@
+using CollapseLauncher.XAMLs.Theme.CustomControls.LayeredBackgroundImage;
+using System;
+using System.Buffers;
+using System.IO;
+using System.Runtime.InteropServices;
+using System.Threading;
+using System.Threading.Tasks;
+// ReSharper disable StringLiteralTypo
+// ReSharper disable CommentTypo
+// ReSharper disable CheckNamespace
+
+#pragma warning disable IDE0130
+#nullable enable
+namespace CollapseLauncher.GameManagement.ImageBackground;
+
+public partial class ImageBackgroundManager
+{
+ private static async ValueTask TryGetImageCodecType(string filePath, CancellationToken token)
+ {
+ ReadOnlySpan fileExt = Path.GetExtension(filePath);
+ // Try to check if extension is a video file, then ignore and return default.
+ if (LayeredBackgroundImage.SupportedVideoExtensionsLookup
+ .Contains(fileExt))
+ {
+ return ImageExternalCodecType.Default;
+ }
+
+ // Try to determine from file extension
+ if (LayeredBackgroundImage.SupportedImageBitmapExtensionsLookup
+ .Contains(fileExt))
+ {
+ return ImageExternalCodecType.Default;
+ }
+
+ if (fileExt.EndsWith("webp", StringComparison.OrdinalIgnoreCase))
+ {
+ return ImageExternalCodecType.Webp;
+ }
+
+ if (fileExt.EndsWith("jxr", StringComparison.OrdinalIgnoreCase))
+ {
+ return ImageExternalCodecType.Jxr;
+ }
+
+ if (fileExt.EndsWith("heif", StringComparison.OrdinalIgnoreCase) ||
+ fileExt.EndsWith("heic", StringComparison.OrdinalIgnoreCase))
+ {
+ return ImageExternalCodecType.Heic;
+ }
+
+ if (fileExt.EndsWith("avif", StringComparison.OrdinalIgnoreCase))
+ {
+ return ImageExternalCodecType.Avif;
+ }
+
+ const int maxBuffer = 1 << 10;
+ byte[] tempBuffer = ArrayPool.Shared.Rent(maxBuffer);
+ try
+ {
+ await using Stream stream = await OpenStreamFromFileOrUrl(filePath, token);
+ int read = await stream.ReadAsync(tempBuffer, token);
+
+ scoped Span buffer = tempBuffer.AsSpan(0, read);
+ if (IsHeaderInternalCodecImage(buffer) is var codecType &&
+ codecType != ImageExternalCodecType.NotSupported)
+ {
+ return codecType;
+ }
+
+ return IsHeaderExternalCodecImage(buffer);
+ }
+ finally
+ {
+ ArrayPool.Shared.Return(tempBuffer);
+ }
+ }
+
+ private static ImageExternalCodecType IsHeaderInternalCodecImage(scoped Span buffer)
+ {
+ // Checks for JPEG
+ ReadOnlySpan jpegSig1 = [0xFF, 0xD8, 0xFF];
+ var jpegSigJfif = "JFIF"u8;
+ var jpegSigExif = "Exif"u8;
+
+ bool isJpeg = buffer.StartsWith(jpegSig1) && (buffer[6..].StartsWith(jpegSigJfif) || buffer[6..].StartsWith(jpegSigExif));
+ if (isJpeg)
+ {
+ return ImageExternalCodecType.Default;
+ }
+
+ // Checks for PNG
+ ReadOnlySpan pngSig = [0x89, 0x50, 0x4E, 0x47];
+
+ bool isPng = buffer.StartsWith(pngSig);
+ if (isPng)
+ {
+ return ImageExternalCodecType.Default;
+ }
+
+ // Checks for BMP
+ var bmpSig = "BM"u8;
+
+ bool isBmp = buffer.StartsWith(bmpSig);
+ if (isBmp)
+ {
+ return ImageExternalCodecType.Default;
+ }
+
+ // Checks for GIF
+ var gifSig1 = "GIF87a"u8;
+ var gifSig2 = "GIF89a"u8;
+
+ bool isGif = buffer.StartsWith(gifSig1) || buffer.StartsWith(gifSig2);
+ if (isGif)
+ {
+ return ImageExternalCodecType.Default;
+ }
+
+ // Checks for TIFF
+ var tiffSig1 = "II*\0"u8; // Little-endian
+ var tiffSig2 = "MM\0*"u8; // Big-endian
+
+ bool isTiff = buffer.StartsWith(tiffSig1) || buffer.StartsWith(tiffSig2);
+ if (isTiff)
+ {
+ return ImageExternalCodecType.Default;
+ }
+
+ // Checks for ICO
+ bool isIco = MemoryMarshal.Read(buffer) == 0 && // Reserved must be 0
+ MemoryMarshal.Read(buffer[2..]) is 1 or 2 && // Make sure if value is 1 or 2 (1 = ICO, 2 = CUR)
+ MemoryMarshal.Read(buffer[4..]) > 0; // Check for count always be > 0
+ if (isIco)
+ {
+ return ImageExternalCodecType.Default;
+ }
+
+ return ImageExternalCodecType.NotSupported;
+ }
+
+ private static ImageExternalCodecType IsHeaderExternalCodecImage(scoped Span buffer)
+ {
+ // Checks for VP8
+ if (buffer.StartsWith("RIFF"u8) &&
+ buffer[8..].StartsWith("WEBPVP8"u8))
+ {
+ return ImageExternalCodecType.Webp;
+ }
+
+ // Checks for AVIF
+ if (buffer[4..].StartsWith("ftypavif"u8))
+ {
+ return ImageExternalCodecType.Avif;
+ }
+
+ // Checks for HEIF/HEIC
+ if (buffer[4..].StartsWith("ftyphe"u8))
+ {
+ return ImageExternalCodecType.Heic;
+ }
+
+ // Checks for JXR
+ ReadOnlySpan jxrSig = [0x49, 0x49, 0xBC, 0x01, 0x08, 0x00, 0x00, 0x00];
+ if (buffer.StartsWith(jxrSig))
+ {
+ return ImageExternalCodecType.Jxr;
+ }
+
+ return ImageExternalCodecType.NotSupported;
+ }
+
+}
diff --git a/CollapseLauncher/Classes/GameManagement/ImageBackground/ImageBackgroundManager.FrameToCopyType.cs b/CollapseLauncher/Classes/GameManagement/ImageBackground/ImageBackgroundManager.FrameToCopyType.cs
new file mode 100644
index 000000000..b65aed6f3
--- /dev/null
+++ b/CollapseLauncher/Classes/GameManagement/ImageBackground/ImageBackgroundManager.FrameToCopyType.cs
@@ -0,0 +1,15 @@
+using System;
+
+// ReSharper disable CheckNamespace
+
+#nullable enable
+namespace CollapseLauncher.GameManagement.ImageBackground;
+
+[Flags]
+public enum FrameToCopyType
+{
+ Foreground = 1,
+ Background = 2,
+
+ Both = Foreground | Background
+}
diff --git a/CollapseLauncher/Classes/GameManagement/ImageBackground/ImageBackgroundManager.ImageCropperAndConvert.cs b/CollapseLauncher/Classes/GameManagement/ImageBackground/ImageBackgroundManager.ImageCropperAndConvert.cs
new file mode 100644
index 000000000..d11fff923
--- /dev/null
+++ b/CollapseLauncher/Classes/GameManagement/ImageBackground/ImageBackgroundManager.ImageCropperAndConvert.cs
@@ -0,0 +1,376 @@
+using CollapseLauncher.CustomControls;
+using CollapseLauncher.Dialogs;
+using CollapseLauncher.Extension;
+using CollapseLauncher.Helper;
+using CollapseLauncher.Helper.StreamUtility;
+using CollapseLauncher.XAMLs.Theme.CustomControls.LayeredBackgroundImage;
+using CommunityToolkit.WinUI.Animations;
+using CommunityToolkit.WinUI.Media;
+using Hi3Helper;
+using Hi3Helper.CommunityToolkit.WinUI.Controls;
+using Hi3Helper.SentryHelper;
+using Hi3Helper.Shared.Region;
+using Microsoft.UI.Text;
+using Microsoft.UI.Xaml;
+using Microsoft.UI.Xaml.Controls;
+using Microsoft.UI.Xaml.Media;
+using Microsoft.UI.Xaml.Media.Animation;
+using Microsoft.UI.Xaml.Media.Imaging;
+using PhotoSauce.MagicScaler;
+using System;
+using System.Drawing;
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+using Windows.Foundation;
+using Windows.Storage.Streams;
+using Orientation = Microsoft.UI.Xaml.Controls.Orientation;
+using UIElementExtensions = CollapseLauncher.Extension.UIElementExtensions;
+
+#pragma warning disable IDE0130
+#nullable enable
+namespace CollapseLauncher.GameManagement.ImageBackground;
+
+public partial class ImageBackgroundManager
+{
+ ///
+ /// Get local path of the resized/cropped custom image. The return values are cropped image path.
+ /// If the input path is a video, then it will return the input path.
+ ///
+ /// To perform cropping request, you must set to .
+ /// Otherwise, it will return the input paths.
+ ///
+ private static async Task<(string? ProcessedOverlayPath, string? ProcessedBackgroundPath, bool IsCancelResize)>
+ GetCroppedCustomImage(string? overlayUrlOrPath,
+ string? backgroundUrlOrPath,
+ bool performCropRequest,
+ CancellationToken token)
+ {
+ if ((string.IsNullOrEmpty(overlayUrlOrPath) &&
+ string.IsNullOrEmpty(backgroundUrlOrPath)) ||
+ string.IsNullOrEmpty(backgroundUrlOrPath))
+ {
+ return (null, null, false); // Skip from processing
+ }
+
+ // -- Check if the file is video or image
+ if (LayeredBackgroundImage.SupportedVideoExtensionsLookup
+ .Contains(Path.GetExtension(backgroundUrlOrPath)))
+ {
+ return (overlayUrlOrPath, backgroundUrlOrPath, false);
+ }
+
+ // -- Try to get existing image file path
+ if (TryGetCroppedImageFilePath(backgroundUrlOrPath, out string croppedBackgroundPath) &&
+ !performCropRequest)
+ {
+ return (null, croppedBackgroundPath, false);
+ }
+
+ try
+ {
+ // -- Try to get original or decoded image paths.
+ (string? decodedOverlayPath, _) = string.IsNullOrEmpty(overlayUrlOrPath)
+ ? (null, default)
+ : await GetNativeOrDecodedImagePath(overlayUrlOrPath, token);
+
+ (string decodedBackgroundPath, _) =
+ await GetNativeOrDecodedImagePath(backgroundUrlOrPath, token);
+
+ // -- Create path of image to Uri
+ Uri overlayImageUri = !string.IsNullOrEmpty(decodedOverlayPath)
+ ? new Uri(decodedOverlayPath)
+ : new Uri(Path.Combine(LauncherConfig.AppExecutableDir, @"Assets\Images\ImageCropperOverlay",
+ LauncherConfig.GetAppConfigValue("WindowSizeProfile") == "Small" ? "small.png" : "normal.png"));
+
+ Uri backgroundImageUri = new(decodedBackgroundPath);
+
+ // -- Create elements, load image and spawn the dialog box.
+ Grid parentGrid = UIElementExtensions
+ .CreateElementFromUIThread(SetParentGridProperties);
+
+ ContentDialogOverlay dialog = UIElementExtensions
+ .CreateElementFromUIThread(SetDialogProperties);
+
+ ImageCropper cropper = UIElementExtensions
+ .CreateElementFromUIThread(SetImageCropperProperties);
+
+ // -- Register to close dialog if cancellation is triggered outside the event.
+ token.Register(() => dialog.Hide());
+ if (token.IsCancellationRequested)
+ {
+ return (null, null, true);
+ }
+
+ DispatcherQueueExtensions
+ .CurrentDispatcherQueue
+ .TryEnqueue(() => LoadImageCropperDetached(backgroundImageUri,
+ cropper,
+ parentGrid,
+ dialog,
+ token));
+
+ ContentDialogResult dialogResult = await dialog.QueueAndSpawnDialog();
+ if (dialogResult is ContentDialogResult.Secondary or ContentDialogResult.None)
+ {
+ return (null, null, true);
+ }
+
+ // -- Save cropped image
+ await SaveCroppedImageToFilePath(backgroundUrlOrPath, croppedBackgroundPath, cropper, token);
+
+ return (null, croppedBackgroundPath, false);
+
+ void SetDialogProperties(ContentDialogOverlay element)
+ {
+ element.Title = Locale.Lang._Misc.ImageCropperTitle;
+ element.Content = parentGrid;
+ element.SecondaryButtonText = Locale.Lang._Misc.Cancel;
+ element.PrimaryButtonText = Locale.Lang._Misc.OkayHappy;
+ element.DefaultButton = ContentDialogButton.Primary;
+ element.IsPrimaryButtonEnabled = false;
+
+ element.XamlRoot = (WindowUtility.CurrentWindow as MainWindow)?.Content.XamlRoot;
+ }
+
+ static void SetParentGridProperties(Grid element)
+ {
+ element.HorizontalAlignment = HorizontalAlignment.Stretch;
+ element.VerticalAlignment = VerticalAlignment.Stretch;
+ element.CornerRadius = new CornerRadius(12);
+ }
+
+ void SetImageCropperProperties(ImageCropper element)
+ {
+ element.AspectRatio = 16d / 9d;
+ element.CropShape = CropShape.Rectangular;
+ element.ThumbPlacement = ThumbPlacement.Corners;
+ element.HorizontalAlignment = HorizontalAlignment.Stretch;
+ element.VerticalAlignment = VerticalAlignment.Stretch;
+ element.Opacity = 0;
+
+ // Why not use ImageBrush?
+ // https://github.com/microsoft/microsoft-ui-xaml/issues/7809
+ element.Overlay = new Hi3Helper.CommunityToolkit.WinUI.Media.ImageBlendBrush
+ {
+ Opacity = 0.5,
+ Stretch = Stretch.Fill,
+ Mode = ImageBlendMode.Multiply,
+ SourceUri = overlayImageUri
+ };
+ }
+ }
+ catch
+ {
+ return (null, null, true);
+ }
+ }
+
+ ///
+ /// Load the image to instance in asynchronous detached manner.
+ ///
+ internal static async void LoadImageCropperDetached(Uri filePath,
+ ImageCropper imageCropper,
+ Grid parentGrid,
+ ContentDialogOverlay dialogOverlay,
+ CancellationToken token = default)
+ {
+ try
+ {
+ StackPanel loadingMsgPanel =
+ UIElementExtensions
+ .CreateElementFromUIThread(x =>
+ {
+ x.Orientation = Orientation.Horizontal;
+ x.HorizontalAlignment = HorizontalAlignment.Center;
+ x.VerticalAlignment = VerticalAlignment.Center;
+ x.Opacity = 1d;
+ });
+
+ DispatcherQueueExtensions
+ .CurrentDispatcherQueue
+ .TryEnqueue(() =>
+ {
+ loadingMsgPanel.AddElementToStackPanel(new ProgressRing
+ {
+ IsIndeterminate = true,
+ Width = 16,
+ Height = 16,
+ VerticalAlignment = VerticalAlignment.Center,
+ Margin = new Thickness(0, 0, 8, 0)
+ });
+ loadingMsgPanel.AddElementToStackPanel(new TextBlock
+ {
+ Text = "Loading the Image",
+ FontWeight = FontWeights.SemiBold
+ });
+
+ parentGrid.AddElementToGridRowColumn(imageCropper);
+ parentGrid.AddElementToGridRowColumn(loadingMsgPanel);
+ });
+
+ WriteableBitmap bitmap = new(1, 1);
+ await using (Stream fileStream = await OpenStreamFromFileOrUrl(filePath, token))
+ {
+ using (IRandomAccessStream randomStream = fileStream.AsRandomAccessStream())
+ {
+ await bitmap.SetSourceAsync(randomStream);
+ }
+ }
+
+ DispatcherQueueExtensions
+ .CurrentDispatcherQueue
+ .TryEnqueue(() =>
+ {
+ imageCropper.Source = bitmap;
+
+ GC.WaitForPendingFinalizers();
+ GC.WaitForFullGCComplete();
+
+ dialogOverlay.IsPrimaryButtonEnabled = true;
+
+ Storyboard storyboardAnim = new();
+ DoubleAnimation loadingMsgPanelAnim = loadingMsgPanel.CreateDoubleAnimation("Opacity", 0,
+ 1, null,
+ TimeSpan.FromMilliseconds(500), EasingType.Cubic.ToEasingFunction());
+ DoubleAnimation imageCropperAnim = imageCropper.CreateDoubleAnimation("Opacity", 1, 0,
+ null,
+ TimeSpan.FromMilliseconds(500), EasingType.Cubic.ToEasingFunction());
+ storyboardAnim.Children.Add(loadingMsgPanelAnim);
+ storyboardAnim.Children.Add(imageCropperAnim);
+ storyboardAnim.Begin();
+ });
+ }
+ catch (Exception ex)
+ {
+ SentryHelper.ExceptionHandler(ex);
+ DispatcherQueueExtensions
+ .CurrentDispatcherQueue
+ .TryEnqueue(dialogOverlay.Hide);
+ }
+ }
+
+ ///
+ /// Save the cropped image from instance.
+ ///
+ internal static async Task SaveCroppedImageToFilePath(
+ string imageSourceFilePath,
+ string imageTargetFilePath,
+ ImageCropper imageCropper,
+ CancellationToken token)
+ {
+ await using Stream imageSourceStream = await OpenStreamFromFileOrUrl(imageSourceFilePath, token);
+ (Rect cropArea, _) = imageCropper.GetCropArea();
+
+ FileInfo imageTargetFileInfo = new(imageTargetFilePath);
+ imageTargetFileInfo.Directory?.Create();
+
+ Stream imageTargetStream = imageTargetFileInfo.Create();
+ bool isError = false;
+ try
+ {
+ await Task.Run(() =>
+ {
+ ProcessImageSettings settings = new()
+ {
+ Crop = new Rectangle((int)cropArea.X, (int)cropArea.Y, (int)cropArea.Width, (int)cropArea.Height),
+ EncoderOptions = new PngEncoderOptions()
+ };
+ MagicImageProcessor.ProcessImage(imageSourceStream, imageTargetStream, settings);
+ }, token);
+ }
+ catch
+ {
+ isError = true;
+ throw;
+ }
+ finally
+ {
+ await imageTargetStream.DisposeAsync();
+ imageTargetFileInfo.Refresh();
+ if (isError)
+ {
+ imageTargetFileInfo.TryDeleteFile();
+ }
+
+ GC.WaitForPendingFinalizers();
+ GC.WaitForFullGCComplete();
+ }
+ }
+
+ ///
+ /// Converts any (seekable) image stream to PNG.
+ ///
+ private static Task ConvertImageToPngFromStream(Stream sourceStream, string targetFilePath, CancellationToken token)
+ {
+ return Task.Factory.StartNew(Impl, (sourceStream, targetFilePath), token);
+
+ static void Impl(object? state)
+ {
+ (Stream innerSourceStream, string innerTargetFilePath) = (ValueTuple)state!;
+
+ FileInfo innerTargetFileInfo = new FileInfo(innerTargetFilePath)
+ .EnsureCreationOfDirectory()
+ .EnsureNoReadOnly()
+ .StripAlternateDataStream();
+ using FileStream innerTargetFileStream = innerTargetFileInfo.Open(FileMode.Create,
+ FileAccess.Write,
+ FileShare.ReadWrite);
+
+ // We won't do any post-processing and just do a straight conversion to PNG.
+ // The post-processing (like resizing with Waifu2X) will be done on image loading process.
+ ProcessImageSettings settings = new()
+ {
+ DpiX = 96,
+ DpiY = 96,
+ ColorProfileMode = ColorProfileMode.Preserve,
+ BlendingMode = GammaMode.Linear
+ };
+ settings.TrySetEncoderFormat(ImageMimeTypes.Png);
+ MagicImageProcessor.ProcessImage(innerSourceStream, innerTargetFileStream, settings);
+ }
+ }
+
+ ///
+ /// Get native or redirected decoded image path.
+ ///
+ /// If the codec of the image is not supported.
+ internal static async Task<(string, ImageExternalCodecType)> GetNativeOrDecodedImagePath(string filePath, CancellationToken token)
+ {
+ // Check the extension type. If the type is native or a video file, then just return the original path.
+ if (await TryGetImageCodecType(filePath, token) is var codecType &&
+ codecType == ImageExternalCodecType.Default)
+ {
+ return (filePath, codecType);
+ }
+
+ // Return null if the codec type is not supported.
+ if (codecType == ImageExternalCodecType.NotSupported)
+ {
+ throw new NotSupportedException($"The format of the image: {filePath} is not supported!");
+ }
+
+ // Try to get decoded temporary file. If it exists, return the file path.
+ if (TryGetDecodedTemporaryFile(filePath, out string decodedFilePath))
+ {
+ return (decodedFilePath, ImageExternalCodecType.Default);
+ }
+
+ // Otherwise, try to convert the image to png and write it to given decoded file path.
+ await using Stream sourceStream = await OpenStreamFromFileOrUrl(filePath, token);
+ await ConvertImageToPngFromStream(sourceStream, decodedFilePath, token);
+
+ return (decodedFilePath, codecType);
+ }
+
+ ///
+ /// Get native or redirected decoded image path.
+ ///
+ /// If the codec of the image is not supported.
+ internal static async Task<(Uri, ImageExternalCodecType)> GetNativeOrDecodedImagePath(
+ Uri filePath, CancellationToken token)
+ {
+ (string, ImageExternalCodecType) result = await GetNativeOrDecodedImagePath(filePath.ToString(), token);
+ return (new Uri(result.Item1), result.Item2);
+ }
+}
diff --git a/CollapseLauncher/Classes/GameManagement/ImageBackground/ImageBackgroundManager.Loaders.cs b/CollapseLauncher/Classes/GameManagement/ImageBackground/ImageBackgroundManager.Loaders.cs
new file mode 100644
index 000000000..49ffe809d
--- /dev/null
+++ b/CollapseLauncher/Classes/GameManagement/ImageBackground/ImageBackgroundManager.Loaders.cs
@@ -0,0 +1,220 @@
+using CollapseLauncher.Extension;
+using CollapseLauncher.Helper.Image;
+using CollapseLauncher.Helper.StreamUtility;
+using CollapseLauncher.XAMLs.Theme.CustomControls.LayeredBackgroundImage;
+using Hi3Helper;
+using Hi3Helper.SentryHelper;
+using Hi3Helper.Shared.Region;
+using Microsoft.UI.Xaml;
+using Microsoft.UI.Xaml.Data;
+using Microsoft.UI.Xaml.Media.Animation;
+using PhotoSauce.MagicScaler;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+
+// ReSharper disable CheckNamespace
+
+#nullable enable
+namespace CollapseLauncher.GameManagement.ImageBackground;
+
+public partial class ImageBackgroundManager
+{
+ #region Fields
+
+ private CancellationTokenSource? _imageLoadingTokenSource;
+
+ #endregion
+
+ private void LoadImageAtIndex(int index, CancellationToken token) =>
+ new Thread(() => LoadImageAtIndexCore(index, token))
+ {
+ IsBackground = true
+ }.Start();
+
+ private async void LoadImageAtIndexCore(int index, CancellationToken token)
+ {
+ try
+ {
+ if (ImageContextSources.Count <= index ||
+ index < 0)
+ {
+ return;
+ }
+
+ // -- Notify changes on context menu properties
+ DispatcherQueueExtensions.CurrentDispatcherQueue.TryEnqueue(() =>
+ OnPropertyChanged(nameof(
+ CurrentSelectedBackgroundHasOverlayImage)));
+
+ // -- Get context and invalidate previous CTS
+ LayeredImageBackgroundContext context = ImageContextSources[index];
+ if (Interlocked.Exchange(ref _imageLoadingTokenSource,
+ CancellationTokenSource.CreateLinkedTokenSource(token)) is { } lastCts)
+ {
+ await lastCts.CancelAsync();
+ lastCts.Dispose();
+ }
+
+ token = _imageLoadingTokenSource.Token;
+
+ Uri? downloadedOverlayUri = null;
+ // -- Download overlay and background files.
+ if (Uri.TryCreate(context.OverlayImagePath, UriKind.Absolute, out Uri? overlayImageUri))
+ {
+ downloadedOverlayUri = await GetLocalOrDownloadedFilePath(overlayImageUri, token);
+ (downloadedOverlayUri, _) = await GetNativeOrDecodedImagePath(downloadedOverlayUri, token);
+ }
+
+ if (!Uri.TryCreate(context.BackgroundImagePath, UriKind.Absolute, out Uri? backgroundImageUri))
+ {
+ throw new InvalidOperationException($"URI/Path of the background image is malformed! {context.BackgroundImagePath}");
+ }
+
+ Uri? downloadedBackgroundUri = await GetLocalOrDownloadedFilePath(backgroundImageUri, token);
+ (downloadedBackgroundUri, _) = await GetNativeOrDecodedImagePath(downloadedBackgroundUri, token);
+
+ // -- Get upscaled image file if Waifu2X is enabled
+ if (GlobalIsWaifu2XEnabled)
+ {
+ downloadedOverlayUri = await TryGetScaledWaifu2XImagePath(downloadedOverlayUri, token);
+ downloadedBackgroundUri = await TryGetScaledWaifu2XImagePath(downloadedBackgroundUri, token);
+ }
+
+ token.ThrowIfCancellationRequested();
+
+ // -- Use UI thread and load image layer
+ DispatcherQueueExtensions.CurrentDispatcherQueue.TryEnqueue(() => SpawnImageLayer(downloadedOverlayUri, downloadedBackgroundUri, context));
+ }
+ catch (Exception ex)
+ {
+ _ = SentryHelper.ExceptionHandlerAsync(ex);
+ Logger.LogWriteLine($"[ImageBackgroundManager::LoadImageAtIndex] {ex}",
+ LogType.Error,
+ true);
+ }
+ }
+
+ private void SpawnImageLayer(Uri? overlayFilePath, Uri? backgroundFilePath, LayeredImageBackgroundContext context)
+ {
+ LayeredBackgroundImage layerElement = new()
+ {
+ BackgroundSource = backgroundFilePath,
+ ForegroundSource = overlayFilePath,
+ Tag = context,
+ ParallaxResetOnUnfocused = false
+ };
+
+ layerElement.BindProperty(this,
+ nameof(GlobalParallaxHoverSource),
+ LayeredBackgroundImage.ParallaxHoverSourceProperty,
+ BindingMode.OneWay);
+
+ layerElement.BindProperty(this,
+ nameof(GlobalIsBackgroundParallaxEffectEnabled),
+ LayeredBackgroundImage.IsParallaxEnabledProperty,
+ BindingMode.OneWay);
+
+ layerElement.BindProperty(this,
+ nameof(GlobalBackgroundParallaxPixelShift),
+ LayeredBackgroundImage.ParallaxHorizontalShiftProperty,
+ BindingMode.OneWay);
+
+ layerElement.BindProperty(this,
+ nameof(GlobalBackgroundParallaxPixelShift),
+ LayeredBackgroundImage.ParallaxVerticalShiftProperty,
+ BindingMode.OneWay);
+
+ layerElement.BindProperty(this,
+ nameof(GlobalBackgroundAudioEnabled),
+ LayeredBackgroundImage.IsAudioEnabledProperty,
+ BindingMode.OneWay);
+
+ layerElement.BindProperty(this,
+ nameof(GlobalBackgroundAudioVolume),
+ LayeredBackgroundImage.AudioVolumeProperty,
+ BindingMode.OneWay);
+
+ layerElement.Transitions.Add(new PopupThemeTransition());
+ layerElement.ImageLoaded += LayerElementOnLoaded;
+ PresenterGrid?.Children.Add(layerElement);
+
+ // Notify current displayed element change
+ OnPropertyChanged(nameof(CurrentBackgroundElement));
+ }
+
+ private void LayerElementOnLoaded(LayeredBackgroundImage layerElement)
+ {
+ List lastElements = PresenterGrid?.Children.ToList() ?? [];
+ foreach (UIElement element in lastElements.Where(element => element != layerElement))
+ {
+ PresenterGrid?.Children.Remove(element);
+ }
+
+ layerElement.ImageLoaded -= LayerElementOnLoaded;
+ }
+
+ private static async Task TryGetScaledWaifu2XImagePath(Uri? uri, CancellationToken token)
+ {
+ if (uri == null)
+ {
+ return null;
+ }
+
+ if (!uri.IsFile)
+ {
+ throw new InvalidOperationException("You must provide a local file path!");
+ }
+
+ // Ignore if the input file is a video
+ string inputFilePath = uri.LocalPath;
+ if (LayeredBackgroundImage.SupportedVideoExtensionsLookup
+ .Contains(Path.GetExtension(inputFilePath)))
+ {
+ return uri;
+ }
+
+ string outputFilePath = Path.Combine(LauncherConfig.AppGameImgCachedFolder,
+ $"scaled_{Path.GetFileNameWithoutExtension(inputFilePath)}.png");
+
+ FileInfo outputFileInfo = new(outputFilePath);
+ if (outputFileInfo.Exists)
+ {
+ return new Uri(outputFileInfo.FullName);
+ }
+
+ outputFileInfo.Directory?.Create();
+
+ await Task.Run(Impl, token);
+ outputFileInfo.Refresh();
+
+ try
+ {
+ token.ThrowIfCancellationRequested();
+ return new Uri(outputFileInfo.FullName);
+ }
+ catch
+ {
+ outputFileInfo.TryDeleteFile();
+ throw;
+ }
+
+ void Impl()
+ {
+ using ProcessingPipeline pipeline =
+ MagicImageProcessor
+ .BuildPipeline(inputFilePath,
+ ProcessImageSettings.Default);
+
+ using FileStream outputFileStream = outputFileInfo.Open(FileMode.Create,
+ FileAccess.Write,
+ FileShare.ReadWrite);
+
+ pipeline.AddTransform(new Waifu2XTransform(ImageLoaderHelper._waifu2X));
+ pipeline.WriteOutput(outputFileStream);
+ }
+ }
+}
diff --git a/CollapseLauncher/Classes/GameManagement/ImageBackground/ImageBackgroundManager.StreamAndPath.cs b/CollapseLauncher/Classes/GameManagement/ImageBackground/ImageBackgroundManager.StreamAndPath.cs
new file mode 100644
index 000000000..6f89b4362
--- /dev/null
+++ b/CollapseLauncher/Classes/GameManagement/ImageBackground/ImageBackgroundManager.StreamAndPath.cs
@@ -0,0 +1,150 @@
+using CollapseLauncher.Helper.Metadata;
+using CollapseLauncher.Helper.StreamUtility;
+using Hi3Helper.EncTool;
+using Hi3Helper.Shared.Region;
+using System;
+using System.IO;
+using System.Net.Http;
+using System.Threading;
+using System.Threading.Tasks;
+// ReSharper disable StringLiteralTypo
+#pragma warning disable IDE0130
+
+#nullable enable
+namespace CollapseLauncher.GameManagement.ImageBackground;
+
+public partial class ImageBackgroundManager
+{
+ #region Fields
+
+ private static readonly string[] PlaceholderBackgroundImageRelPath = [
+ @"Assets\Images\PageBackground\default_genshin.webp",
+ @"Assets\Images\PageBackground\default_honkai.webp",
+ @"Assets\Images\PageBackground\default_starrail.webp",
+ @"Assets\Images\PageBackground\default_zzz.webp",
+ ];
+
+ private static readonly string[] PlaceholderBackgroundImagePngRelPath = [
+ @"Assets\Images\GamePoster\poster_genshin.png",
+ @"Assets\Images\GamePoster\poster_honkai.png",
+ @"Assets\Images\GamePoster\poster_starrail.png",
+ @"Assets\Images\GamePoster\poster_zzz.png",
+ ];
+
+ #endregion
+
+ private static Task OpenStreamFromFileOrUrl(string? filePath, CancellationToken token)
+ {
+ if (!Uri.TryCreate(filePath, UriKind.Absolute, out Uri? uri))
+ {
+ throw new InvalidOperationException($"File path or URL is misformed! {filePath}");
+ }
+
+ return OpenStreamFromFileOrUrl(uri, token);
+ }
+
+ private static async Task OpenStreamFromFileOrUrl(Uri uri, CancellationToken token)
+ {
+ if (uri.IsFile)
+ {
+ string localPath = uri.LocalPath;
+ return !File.Exists(localPath)
+ ? throw new FileNotFoundException($"File: {localPath} does not exist!")
+ : File.Open(localPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
+ }
+
+ HttpClient sharedClient = FallbackCDNUtil.GetGlobalHttpClient(true);
+ UrlStatus status = await sharedClient.GetCachedUrlStatus(uri, token);
+ status.EnsureSuccessStatusCode();
+
+ if (status.FileSize == 0)
+ {
+ throw new HttpRequestException($"File URL is reachable but it returns 0 bytes. {uri}");
+ }
+
+ if (TryGetDownloadedFile(uri, out FileInfo downloadedFilePath) &&
+ downloadedFilePath.Length == status.FileSize)
+ {
+ return downloadedFilePath.Open(FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
+ }
+
+ downloadedFilePath = downloadedFilePath
+ .EnsureCreationOfDirectory()
+ .EnsureNoReadOnly()
+ .StripAlternateDataStream();
+
+ FileStream downloadedFileStream =
+ downloadedFilePath.Open(FileMode.Create, FileAccess.ReadWrite, FileShare.ReadWrite);
+
+ using HttpResponseMessage responseMessage =
+ await sharedClient.GetAsync(uri, HttpCompletionOption.ResponseHeadersRead, token);
+ await using Stream responseStream = await responseMessage.Content.ReadAsStreamAsync(token);
+ await responseStream.CopyToAsync(downloadedFileStream, token);
+
+ downloadedFileStream.Position = 0;
+ return downloadedFileStream;
+ }
+
+ private static async Task GetLocalOrDownloadedFilePath(Uri uri, CancellationToken token)
+ {
+ await using FileStream stream = await OpenStreamFromFileOrUrl(uri, token);
+ return new Uri(stream.Name);
+ }
+
+ private static bool TryGetCroppedImageFilePath(string filePath,
+ out string croppedFilePath)
+ {
+ string imageDirPath = LauncherConfig.AppGameImgFolder;
+ string fileName = $"cropped_{Path.GetFileNameWithoutExtension(filePath)}.png";
+
+ croppedFilePath = Path.Combine(imageDirPath, fileName);
+ return File.Exists(croppedFilePath);
+ }
+
+ private static bool TryGetDecodedTemporaryFile(string filePath,
+ out string decodedFilePath)
+ {
+ string imageDirPath = LauncherConfig.AppGameImgFolder;
+ string fileName = $"decoded_{Path.GetFileNameWithoutExtension(filePath)}.png";
+
+ decodedFilePath = Path.Combine(imageDirPath, fileName);
+ return File.Exists(decodedFilePath);
+ }
+
+ private static bool TryGetDownloadedFile(Uri filePath,
+ out FileInfo downloadedFilePath)
+ {
+ string imageDirPath = LauncherConfig.AppGameImgFolder;
+ string fileHashedName = $"{Path.GetFileName(filePath.AbsolutePath)}";
+
+ downloadedFilePath = new FileInfo(Path.Combine(imageDirPath, fileHashedName));
+ return downloadedFilePath.Exists;
+ }
+
+ internal static string GetPlaceholderBackgroundImageFrom(PresetConfig? presetConfig, bool usePngVersion = false)
+ {
+ string[] source = usePngVersion
+ ? PlaceholderBackgroundImagePngRelPath
+ : PlaceholderBackgroundImageRelPath;
+
+ string relPath = presetConfig?.GameName switch
+ {
+ "Genshin Impact" => source[0],
+ "Honkai Impact 3rd" => source[1],
+ "Honkai: Star Rail" => source[2],
+ "Zenless Zone Zero" => source[3],
+ _ => GetRandomPlaceholderImage(usePngVersion)
+ };
+
+ return Path.Combine(LauncherConfig.AppExecutableDir, relPath);
+ }
+
+ public static string GetRandomPlaceholderImage(bool usePngVersion = false)
+ {
+ string[] source = usePngVersion
+ ? PlaceholderBackgroundImagePngRelPath
+ : PlaceholderBackgroundImageRelPath;
+
+ return source[Random.Shared.Next(0, source.Length - 1)];
+ }
+}
diff --git a/CollapseLauncher/Classes/GameManagement/ImageBackground/ImageBackgroundManager.WindowEvents.cs b/CollapseLauncher/Classes/GameManagement/ImageBackground/ImageBackgroundManager.WindowEvents.cs
new file mode 100644
index 000000000..64c90194d
--- /dev/null
+++ b/CollapseLauncher/Classes/GameManagement/ImageBackground/ImageBackgroundManager.WindowEvents.cs
@@ -0,0 +1,19 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+#nullable enable
+namespace CollapseLauncher.GameManagement.ImageBackground;
+
+public partial class ImageBackgroundManager
+{
+ public void SetWindowFocusedEvent()
+ {
+
+ }
+
+ public void SetWindowUnfocusedEvent()
+ {
+
+ }
+}
diff --git a/CollapseLauncher/Classes/GameManagement/ImageBackground/ImageBackgroundManager.cs b/CollapseLauncher/Classes/GameManagement/ImageBackground/ImageBackgroundManager.cs
new file mode 100644
index 000000000..4947fcb7e
--- /dev/null
+++ b/CollapseLauncher/Classes/GameManagement/ImageBackground/ImageBackgroundManager.cs
@@ -0,0 +1,523 @@
+using CollapseLauncher.Extension;
+using CollapseLauncher.Helper.Image;
+using CollapseLauncher.Helper.LauncherApiLoader.HoYoPlay;
+using CollapseLauncher.Helper.Metadata;
+using CollapseLauncher.Interfaces.Class;
+using CollapseLauncher.XAMLs.Theme.CustomControls.LayeredBackgroundImage;
+using Hi3Helper.SentryHelper;
+using Hi3Helper.Shared.Region;
+using Microsoft.UI.Xaml;
+using Microsoft.UI.Xaml.Controls;
+using Microsoft.UI.Xaml.Data;
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Linq;
+using System.Runtime.InteropServices;
+using System.Threading;
+using System.Threading.Tasks;
+using WinRT;
+// ReSharper disable CheckNamespace
+#pragma warning disable IDE0130
+
+#nullable enable
+namespace CollapseLauncher.GameManagement.ImageBackground;
+
+[GeneratedBindableCustomProperty]
+public partial class ImageBackgroundManager
+ : NotifyPropertyChanged
+{
+ internal static ImageBackgroundManager Shared
+ {
+ get;
+ } = new();
+
+ #region Shared/Static Properties and Fields
+
+ private const string GlobalCustomBackgroundConfigKey = "globalBg";
+ private const string GlobalIsEnableCustomImageConfigKey = "globalIsCustomImageEnabled";
+ private const string GlobalIsBackgroundParallaxEffectEnabledConfigKey = "globalIsBackgroundParallaxEffectEnabled";
+ private const string GlobalBackgroundParallaxPixelShiftConfigKey = "globalBackgroundParallaxPixelShift";
+ private const string GlobalBackgroundAudioVolumeConfigKey = "globalBackgroundAudioVolume";
+ private const string GlobalBackgroundAudioEnabledConfigKey = "globalBackgroundAudioEnabled";
+
+ public string? GlobalCustomBackgroundImagePath
+ {
+ get => LauncherConfig.GetAppConfigValue(GlobalCustomBackgroundConfigKey);
+ set
+ {
+ LauncherConfig.SetAndSaveConfigValue(GlobalCustomBackgroundConfigKey, value);
+ OnPropertyChanged();
+ }
+ }
+
+ public bool GlobalIsEnableCustomImage
+ {
+ get => LauncherConfig.GetAppConfigValue(GlobalIsEnableCustomImageConfigKey);
+ set
+ {
+ LauncherConfig.SetAndSaveConfigValue(GlobalIsEnableCustomImageConfigKey, value);
+ OnPropertyChanged();
+ InitializeCore();
+ }
+ }
+
+ public bool GlobalIsWaifu2XEnabled
+ {
+ get => ImageLoaderHelper.IsWaifu2XEnabled && ImageLoaderHelper.IsWaifu2XUsable;
+ set
+ {
+ ImageLoaderHelper.IsWaifu2XEnabled = value;
+ OnPropertyChanged();
+ }
+ }
+
+ public FrameworkElement? GlobalParallaxHoverSource
+ {
+ get;
+ set
+ {
+ field = value;
+ OnPropertyChanged();
+ }
+ }
+
+ public bool GlobalIsBackgroundParallaxEffectEnabled
+ {
+ get => LauncherConfig.GetAppConfigValue(GlobalIsBackgroundParallaxEffectEnabledConfigKey);
+ set
+ {
+ LauncherConfig.SetAndSaveConfigValue(GlobalIsBackgroundParallaxEffectEnabledConfigKey, value);
+ OnPropertyChanged();
+ }
+ }
+
+ public double GlobalBackgroundParallaxPixelShift
+ {
+ get => LauncherConfig.GetAppConfigValue(GlobalBackgroundParallaxPixelShiftConfigKey);
+ set
+ {
+ LauncherConfig.SetAndSaveConfigValue(GlobalBackgroundParallaxPixelShiftConfigKey, value);
+ OnPropertyChanged();
+ }
+ }
+
+ public double GlobalBackgroundAudioVolume
+ {
+ get => LauncherConfig.GetAppConfigValue(GlobalBackgroundAudioVolumeConfigKey);
+ set
+ {
+ LauncherConfig.SetAndSaveConfigValue(GlobalBackgroundAudioVolumeConfigKey, value);
+ OnPropertyChanged();
+ }
+ }
+
+ public bool GlobalBackgroundAudioEnabled
+ {
+ get => LauncherConfig.GetAppConfigValue(GlobalBackgroundAudioEnabledConfigKey);
+ set
+ {
+ LauncherConfig.SetAndSaveConfigValue(GlobalBackgroundAudioEnabledConfigKey, value);
+ OnPropertyChanged();
+ }
+ }
+
+ #endregion
+
+ #region This Instance Properties
+
+ private string CurrentCustomBackgroundConfigKey { get; set; } = "";
+ private string CurrentIsEnableCustomImageConfigKey { get; set; } = "";
+ private string CurrentSelectedBackgroundIndexKey { get; set; } = "";
+ private HypLauncherBackgroundApi? CurrentBackgroundApi { get; set; }
+ private Grid? PresenterGrid { get; set; }
+ private PresetConfig? PresetConfig { get; set; }
+
+ public string? CurrentCustomBackgroundImagePath
+ {
+ get => LauncherConfig.GetAppConfigValue(CurrentCustomBackgroundConfigKey);
+ set
+ {
+ LauncherConfig.SetAndSaveConfigValue(CurrentCustomBackgroundConfigKey, value);
+ OnPropertyChanged();
+ }
+ }
+
+ public bool CurrentIsEnableCustomImage
+ {
+ get => LauncherConfig.GetAppConfigValue(CurrentIsEnableCustomImageConfigKey);
+ set
+ {
+ LauncherConfig.SetAndSaveConfigValue(CurrentIsEnableCustomImageConfigKey, value);
+ OnPropertyChanged();
+ InitializeCore();
+ }
+ }
+
+ public int CurrentSelectedBackgroundIndex
+ {
+ get
+ {
+ int value = LauncherConfig.GetAppConfigValue(CurrentSelectedBackgroundIndexKey);
+ if (value > ImageContextSources.Count - 1 ||
+ value < 0)
+ {
+ return 0;
+ }
+
+ return value;
+ }
+ set
+ {
+ if (value > ImageContextSources.Count - 1 ||
+ value < 0)
+ {
+ return;
+ }
+
+ LauncherConfig.SetAndSaveConfigValue(CurrentSelectedBackgroundIndexKey, value);
+ OnPropertyChanged();
+ LoadImageAtIndex(value, CancellationToken.None);
+ }
+ }
+
+ public LayeredImageBackgroundContext? CurrentSelectedBackgroundContext
+ {
+ get
+ {
+ int index = CurrentSelectedBackgroundIndex;
+ return index > ImageContextSources.Count - 1 ||
+ index < 0
+ ? null
+ : ImageContextSources[CurrentSelectedBackgroundIndex];
+ }
+ }
+
+ public LayeredBackgroundImage? CurrentBackgroundElement
+ {
+ get => PresenterGrid?.Children.LastOrDefault() as LayeredBackgroundImage;
+ }
+
+ public bool CurrentSelectedBackgroundHasOverlayImage => !string.IsNullOrEmpty(CurrentSelectedBackgroundContext?.OriginOverlayImagePath);
+
+ public int CurrentBackgroundCount
+ {
+ get => ImageContextSources.Count;
+ }
+
+ ///
+ /// The collection of image context sources.
+ /// Notes to Dev:
+ /// This collection SHOULDN'T BE SET outside this instance.
+ /// To set the ItemsSource to any other ItemsTemplate's element, please use to make sure the changes always be tracked!
+ /// Also, the use of is intended and REQUIRED to make sure the changes are tracked properly.
+ ///
+ public ObservableCollection ImageContextSources
+ {
+ get;
+ } = [];
+
+ #endregion
+
+ ///
+ /// Initialize background images of for a region. This method MUST be called everytime the region is loaded.
+ ///
+ /// The preset config of the current game region.
+ /// The background API implementation of the current game region.
+ /// Presenter Grid which the element of the background will be shown on.
+ /// Cancellation token to cancel asynchronous operations.
+ public void Initialize(PresetConfig? presetConfig,
+ HypLauncherBackgroundApi? backgroundApi,
+ Grid? presenterGrid,
+ CancellationToken token = default)
+ {
+ ArgumentNullException.ThrowIfNull(presetConfig);
+ ArgumentNullException.ThrowIfNull(presenterGrid);
+
+ CurrentCustomBackgroundConfigKey = $"lastCustomBg-{presetConfig.GameName}-{presetConfig.ZoneName}";
+ CurrentSelectedBackgroundIndexKey = $"lastCustomBgIndex-{presetConfig.GameName}-{presetConfig.ZoneName}";
+ CurrentIsEnableCustomImageConfigKey = $"lastIsCustomImageEnabled-{presetConfig.GameName}-{presetConfig.ZoneName}";
+
+ PresetConfig = presetConfig;
+ PresenterGrid = presenterGrid;
+ CurrentBackgroundApi = backgroundApi;
+
+ InitializeCore(token);
+ }
+
+ private void InitializeCore(CancellationToken token = default)
+ {
+ new Thread(Impl)
+ {
+ IsBackground = true,
+ }.Start();
+
+ return;
+
+ async void Impl()
+ {
+ try
+ {
+ if (PresetConfig == null)
+ {
+ throw new InvalidOperationException($"{nameof(PresetConfig)} is uninitialized!");
+ }
+
+ // -- Try to initialize custom image first
+ // -- A. Check for per-region custom background
+ if (CurrentIsEnableCustomImage &&
+ await SetCurrentCustomBackground(CurrentCustomBackgroundImagePath, false, false, token))
+ {
+ return;
+ }
+
+ // -- B. Check for global custom background
+ if (!CurrentIsEnableCustomImage &&
+ GlobalIsEnableCustomImage &&
+ await SetGlobalCustomBackground(GlobalCustomBackgroundImagePath, false, false, token))
+ {
+ return;
+ }
+
+ // -- If no custom background is used, then fallback to background provided by API or fallback background.
+ List imageContexts = [];
+
+ try
+ {
+ // -- Check 1: Add placeholder ones if the API is not implemented.
+ if (CurrentBackgroundApi?.Data is not { GameContentList: { Count: > 0 } contextList })
+ {
+ string bgPlaceholderPath = GetPlaceholderBackgroundImageFrom(PresetConfig);
+ imageContexts.Add(new LayeredImageBackgroundContext
+ {
+ OriginBackgroundImagePath = bgPlaceholderPath,
+ BackgroundImagePath = bgPlaceholderPath
+ });
+ return;
+ }
+
+ // -- Check 2: Use ones provided by the API
+ // ReSharper disable once LoopCanBeConvertedToQuery
+ foreach (HypLauncherBackgroundContentKindData contextEntry in contextList.SelectMany(x => x.Backgrounds))
+ {
+ string? overlayImagePath = contextEntry.BackgroundOverlay?.ImageUrl;
+ string? backgroundImagePath = contextEntry.BackgroundVideo?.ImageUrl ??
+ contextEntry.BackgroundImage?.ImageUrl;
+
+ imageContexts.Add(new LayeredImageBackgroundContext
+ {
+ OriginOverlayImagePath = overlayImagePath,
+ OriginBackgroundImagePath = backgroundImagePath,
+ OverlayImagePath = overlayImagePath,
+ BackgroundImagePath = backgroundImagePath
+ });
+ }
+ }
+ finally
+ {
+ UpdateContextListCore(token, false, imageContexts);
+ }
+ }
+ catch (Exception ex)
+ {
+ SentryHelper.ExceptionHandler(ex);
+ // ignore
+ }
+ }
+ }
+
+ public Task SetGlobalCustomBackground(string? imagePath, bool performCropRequest = true, bool skipPreviousContextCheck = true, CancellationToken token = default)
+ {
+ GlobalCustomBackgroundImagePath = imagePath;
+ // For global custom background, pass CurrentIsEnableCustomImage status so when it's set to true,
+ // this code will only perform cropping but not applying it.
+ return SetCustomBackgroundCore(imagePath, performCropRequest, !CurrentIsEnableCustomImage, skipPreviousContextCheck, token);
+ }
+
+ public Task SetCurrentCustomBackground(string? imagePath, bool performCropRequest = true, bool skipPreviousContextCheck = true, CancellationToken token = default)
+ {
+ CurrentCustomBackgroundImagePath = imagePath;
+ // For current custom background, since we put the priority at the top.
+ // So if it's done performing cropping, apply the change no matter what's the status for global background.
+ return SetCustomBackgroundCore(imagePath, performCropRequest, true, skipPreviousContextCheck, token);
+ }
+
+ private async Task SetCustomBackgroundCore(string? imagePath,
+ bool performCropRequest,
+ bool applyChanges,
+ bool skipPreviousContextCheck,
+ CancellationToken token)
+ {
+ try
+ {
+ // -- Return as cancelled if path is null
+ if (string.IsNullOrEmpty(imagePath))
+ {
+ return false;
+ }
+
+ // -- Try to perform cropped or pass through processing
+ (_, string? resultBackgroundPath, bool isCancelProcess) =
+ await GetCroppedCustomImage(null,
+ imagePath,
+ performCropRequest,
+ token);
+
+ // -- Do not process custom background if cancelled.
+ if (isCancelProcess)
+ {
+ return false;
+ }
+
+ // -- If the change does not require to be applied, then
+ // just ignore it. (Only perform cropping)
+ if (!applyChanges)
+ {
+ return true;
+ }
+
+ // -- Apply background
+ LayeredImageBackgroundContext context = new()
+ {
+ OriginBackgroundImagePath = imagePath,
+ BackgroundImagePath = resultBackgroundPath
+ };
+
+ UpdateContextListCore(token, skipPreviousContextCheck, context);
+ return true;
+ }
+ catch (Exception ex)
+ {
+ SentryHelper.ExceptionHandler(ex);
+ // Yeet! we won't do any processing for this custom background.
+
+ return false;
+ }
+ }
+
+#pragma warning disable CA1068
+ private void UpdateContextListCore(
+ CancellationToken token,
+ bool skipPreviousContextCheck,
+ IEnumerable imageContexts)
+ {
+ if (imageContexts is List asList)
+ {
+ UpdateContextListCore(token, skipPreviousContextCheck, CollectionsMarshal.AsSpan(asList));
+ return;
+ }
+
+ UpdateContextListCore(token, skipPreviousContextCheck, imageContexts.ToArray());
+ }
+
+ private void UpdateContextListCore(
+ CancellationToken token,
+ bool skipPreviousContextCheck,
+ params ReadOnlySpan imageContexts)
+ {
+ // Do not update if the previous contexts are equal
+ if (!skipPreviousContextCheck &&
+ IsContextEqual(imageContexts))
+ {
+ return;
+ }
+
+ ImageContextSources.Clear(); // Flush list
+ ref IList? backedList =
+ ref ObservableCollectionExtension
+ .GetBackedCollectionList(ImageContextSources);
+
+ // If backed list is List, then use .AddRange
+ if (backedList is List backedListAsList)
+ {
+ backedListAsList.AddRange(imageContexts);
+
+ // Raise collection event.
+ ObservableCollectionExtension.RefreshAllEvents(ImageContextSources);
+ }
+ // Otherwise, add item one-by-one (might cause flicker on the UI Element that bind to it).
+ else
+ {
+ foreach (LayeredImageBackgroundContext imageContext in imageContexts)
+ {
+ ImageContextSources.Add(imageContext);
+ }
+ }
+
+ // Update index based on current image context list content.
+ OnPropertyChanged(nameof(CurrentSelectedBackgroundIndex));
+ OnPropertyChanged(nameof(CurrentBackgroundCount));
+ LoadImageAtIndex(CurrentSelectedBackgroundIndex, token);
+ }
+#pragma warning restore CA1068
+
+ private bool IsContextEqual(ReadOnlySpan imageContexts)
+ {
+ if (ImageContextSources.Count != imageContexts.Length)
+ {
+ return false;
+ }
+
+ HashSet currentContextHashes =
+ ImageContextSources
+ .Select(x => x.GetHashCode())
+ .ToHashSet();
+ foreach (LayeredImageBackgroundContext imageContext in imageContexts)
+ {
+ if (!currentContextHashes.Contains(imageContext.GetHashCode()))
+ {
+ return false;
+ }
+ }
+
+ return true;
+ }
+}
+
+[GeneratedBindableCustomProperty]
+public partial class LayeredImageBackgroundContext : NotifyPropertyChanged, IEquatable
+{
+ public string? OriginOverlayImagePath
+ {
+ get;
+ init;
+ }
+
+ public string? OriginBackgroundImagePath
+ {
+ get;
+ init;
+ }
+
+ public string? OverlayImagePath
+ {
+ get;
+ init;
+ }
+
+ public string? BackgroundImagePath
+ {
+ get;
+ init;
+ }
+
+ public bool Equals(LayeredImageBackgroundContext? other) => other?.GetHashCode() == GetHashCode();
+
+ public override bool Equals(object? obj)
+ {
+ if (ReferenceEquals(obj, this))
+ {
+ return true;
+ }
+
+ if (obj is not LayeredImageBackgroundContext other)
+ {
+ return false;
+ }
+
+ return Equals(other);
+ }
+
+ public override int GetHashCode() =>
+ HashCode.Combine(OriginOverlayImagePath, OriginBackgroundImagePath, OverlayImagePath, BackgroundImagePath);
+}
\ No newline at end of file
diff --git a/CollapseLauncher/Classes/GameManagement/ImageBackground/ImageExternalCodecType.cs b/CollapseLauncher/Classes/GameManagement/ImageBackground/ImageExternalCodecType.cs
new file mode 100644
index 000000000..006fbf20c
--- /dev/null
+++ b/CollapseLauncher/Classes/GameManagement/ImageBackground/ImageExternalCodecType.cs
@@ -0,0 +1,14 @@
+// ReSharper disable IdentifierTypo
+#pragma warning disable IDE0130
+
+namespace CollapseLauncher.GameManagement.ImageBackground;
+
+public enum ImageExternalCodecType
+{
+ Webp = 1,
+ Jxr,
+ Avif,
+ Heic,
+ Default = 0,
+ NotSupported = -1
+}
diff --git a/CollapseLauncher/Classes/Helper/Background/BackgroundMediaUtility.cs b/CollapseLauncher/Classes/Helper/Background/BackgroundMediaUtility.cs
deleted file mode 100644
index 7dad9ff75..000000000
--- a/CollapseLauncher/Classes/Helper/Background/BackgroundMediaUtility.cs
+++ /dev/null
@@ -1,546 +0,0 @@
-using CollapseLauncher.Extension;
-using CollapseLauncher.Helper.Background.Loaders;
-using CollapseLauncher.Helper.Metadata;
-using CollapseLauncher.Statics;
-using Hi3Helper.SentryHelper;
-using Hi3Helper.Shared.Region;
-using Microsoft.UI.Xaml;
-using Microsoft.UI.Xaml.Controls;
-using Microsoft.UI.Xaml.Media;
-using Microsoft.UI.Xaml.Media.Imaging;
-using System;
-using System.Diagnostics.CodeAnalysis;
-using System.IO;
-using System.Linq;
-using System.Threading;
-using System.Threading.Channels;
-using System.Threading.Tasks;
-using ImageUI = Microsoft.UI.Xaml.Controls.Image;
-// ReSharper disable PartialTypeWithSinglePart
-// ReSharper disable AsyncVoidMethod
-// ReSharper disable GrammarMistakeInComment
-// ReSharper disable SwitchExpressionHandlesSomeKnownEnumValuesWithExceptionInDefault
-// ReSharper disable SwitchStatementMissingSomeEnumCasesNoDefault
-// ReSharper disable UnusedMember.Global
-// ReSharper disable CommentTypo
-// ReSharper disable StringLiteralTypo
-
-#nullable enable
-namespace CollapseLauncher.Helper.Background
-{
- [SuppressMessage("ReSharper", "IdentifierTypo")]
- [SuppressMessage("ReSharper", "PossibleNullReferenceException")]
- [SuppressMessage("ReSharper", "AssignNullToNotNullAttribute")]
- internal sealed partial class BackgroundMediaUtility : IDisposable
- {
- internal enum MediaType
- {
- Media,
- SequenceImage,
- StillImage,
- Unknown
- }
-
- internal const double TransitionDuration = 0.25d;
- internal const double TransitionDurationSlow = 0.5d;
-
- internal static readonly string[] SupportedImageExt =
- [".jpg", ".jpeg", ".jfif", ".png", ".bmp", ".tiff", ".tif", ".webp"];
-
- internal static readonly string[] SupportedMediaPlayerExt =
- [".mp4", ".mov", ".mkv", ".webm", ".avi", ".gif"];
-
- private static FrameworkElement? _parentUI;
- private ImageUI? _bgImageBackground;
- private ImageUI? _bgImageBackgroundLast;
- private MediaPlayerElement? _bgMediaPlayerBackground;
-
- private Grid? _bgAcrylicMask;
- private Grid? _bgOverlayTitleBar;
-
- private Grid? _parentBgImageBackgroundGrid;
- private Grid? _parentBgMediaPlayerBackgroundGrid;
-
- internal static string? CurrentAppliedMediaPath;
- internal static MediaType CurrentAppliedMediaType = MediaType.Unknown;
-
- private CancellationTokenSourceWrapper? _cancellationToken;
- private StillImageLoader? _loaderStillImage;
- private MediaPlayerLoader? _loaderMediaPlayer;
-
- private bool _isCurrentRegistered;
-
- private static MemoryStream? _alternativeImageStream;
-
- private delegate ValueTask AssignDefaultAction(T element) where T : class;
- internal delegate void ThrowExceptionAction(Exception element);
-
- private static readonly Channel QueuedTaskChannel;
-
- static BackgroundMediaUtility()
- {
- QueuedTaskChannel = Channel.CreateBounded(new BoundedChannelOptions(1)
- {
- AllowSynchronousContinuations = true,
- FullMode = BoundedChannelFullMode.Wait,
- SingleReader = true,
- SingleWriter = true
- });
-
- _ = RunAsyncTaskFromChannel();
- }
-
- private static async Task RunAsyncTaskFromChannel()
- {
- while (await QueuedTaskChannel.Reader.ReadAsync() is { } asTask)
- {
- await asTask;
- }
- }
-
- internal static async void RunQueuedTask(Task task)
- {
- await QueuedTaskChannel.Writer.WaitToWriteAsync();
- await QueuedTaskChannel.Writer.WriteAsync(task);
- }
-
- ///
- /// Attach and register the of the page to be assigned with background utility.
- /// The must be empty or have existing instances of previously registered .
- ///
- /// The parent UI to be assigned for the media elements.
- /// The acrylic mask for Background Image.
- /// The title bar shadow over Background Image.
- /// The parent for Background Image.
- /// The parent for Background Media Player
- internal static async Task CreateInstanceAsync(FrameworkElement? parentUI, Grid bgAcrylicMask,
- Grid bgOverlayTitleBar, Grid bgImageGridBackground,
- Grid bgMediaPlayerGrid)
- {
- CurrentAppliedMediaPath = null;
- CurrentAppliedMediaType = MediaType.Unknown;
- if (_alternativeImageStream != null)
- {
- await _alternativeImageStream.DisposeAsync();
- _alternativeImageStream = null;
- }
-
- // Set the parent UI
- FrameworkElement? ui = parentUI;
-
- // Initialize the background instances
- var (bgImageBackground, bgImageBackgroundLast) =
- await InitializeElementGrid(bgImageGridBackground, "ImageBackground", AssignDefaultImage);
- var bgMediaPlayerBackground =
- (await TryGetFirstGridElement(bgMediaPlayerGrid.WithOpacity(0), "MediaPlayer"))
- .WithHorizontalAlignment(HorizontalAlignment.Center)
- .WithVerticalAlignment(VerticalAlignment.Center)
- .WithStretch(Stretch.UniformToFill);
-
- return new BackgroundMediaUtility(ui, bgAcrylicMask, bgOverlayTitleBar,
- bgImageGridBackground, bgMediaPlayerGrid, bgImageBackground,
- bgImageBackgroundLast, bgMediaPlayerBackground);
- }
-
- private BackgroundMediaUtility(FrameworkElement? parentUI, Grid bgAcrylicMask,
- Grid bgOverlayTitleBar, Grid bgImageGridBackground,
- Grid bgMediaPlayerGrid, ImageUI? bgImageBackground,
- ImageUI? bgImageBackgroundLast, MediaPlayerElement? mediaPlayerElement)
- {
- _parentUI = parentUI;
- _bgAcrylicMask = bgAcrylicMask;
- _bgOverlayTitleBar = bgOverlayTitleBar;
- _parentBgImageBackgroundGrid = bgImageGridBackground;
- _parentBgMediaPlayerBackgroundGrid = bgMediaPlayerGrid;
-
- _bgImageBackground = bgImageBackground;
- _bgImageBackgroundLast = bgImageBackgroundLast;
- _bgMediaPlayerBackground = mediaPlayerElement;
-
- // Set that the current page has been registered
- _isCurrentRegistered = true;
- }
-
- ~BackgroundMediaUtility() => Dispose();
-
- ///
- /// Detach and dispose the current background utility from the previously attached .
- ///
- public void Dispose()
- {
- if (_cancellationToken is { IsCancellationRequested: false })
- {
- _cancellationToken.Cancel();
- }
- _cancellationToken?.Dispose();
-
- _bgImageBackground = null;
- _bgImageBackgroundLast = null;
- _bgMediaPlayerBackground = null;
-
- _parentBgImageBackgroundGrid = null;
- _parentBgMediaPlayerBackgroundGrid = null;
- _bgAcrylicMask = null;
- _bgOverlayTitleBar = null;
-
- _loaderMediaPlayer?.Dispose();
- _loaderMediaPlayer = null;
- _loaderStillImage?.Dispose();
- _loaderStillImage = null;
-
- _alternativeImageStream?.Dispose();
- _alternativeImageStream = null;
-
- _isCurrentRegistered = false;
- GC.SuppressFinalize(this);
- }
-
- ///
- /// Initialize or to find an existing element by its base tag and type.
- ///
- ///
- /// The type of the element need to be created (where TElement is a member of
- /// FrameworkElement)
- ///
- /// The parent Grid where the element is going to be or has stored.
- /// The base tag to determine the element type.
- /// The delegate to perform action after the new element is created.
- /// The tuple of the new "current and last" instance of the element.
- private static async ValueTask<(TElement?, TElement?)> InitializeElementGrid(Grid elementGrid,
- string baseTagType, AssignDefaultAction? defaultAssignAction = null)
- where TElement : FrameworkElement, new()
- {
- // Get the type name of the element
- string typeName = typeof(TElement).Name + '_';
-
- // Find or create the element from/to the parent grid.
- TElement? elementCurrent =
- (await TryGetFirstGridElement(elementGrid, typeName + baseTagType + "_Current", defaultAssignAction))
- .WithHorizontalAlignment(HorizontalAlignment.Center)
- .WithVerticalAlignment(VerticalAlignment.Bottom)
- .WithStretch(Stretch.UniformToFill);
- TElement? elementLast =
- (await TryGetFirstGridElement(elementGrid, typeName + baseTagType + "_Last", defaultAssignAction))
- .WithHorizontalAlignment(HorizontalAlignment.Center)
- .WithVerticalAlignment(VerticalAlignment.Bottom)
- .WithStretch(Stretch.UniformToFill);
-
- // Return the current and last element
- return (elementCurrent, elementLast);
- }
-
- ///
- /// Try to get the first element or create a new one from the parent Grid based on its tag type.
- ///
- /// The type of the element to get (where T is a member of FrameworkElement)
- /// The parent Grid to get the element from.
- /// The exact tag to determine the element type.
- /// The delegate to perform action after the new element is created.
- /// Returns null if the Grid is null. Returns the new or existing element from the Grid.
- private static async ValueTask TryGetFirstGridElement(Grid? elementGrid, string tagType,
- AssignDefaultAction? defaultAssignAction = null)
- where T : FrameworkElement, new()
- {
- // If the parent grid is null, then return null
- if (elementGrid == null) return null;
-
- // Try to find the existing element at the first pos of the
- // parent's grid with the corresponding tag. If not found,
- // assign it as null instead
- T? targetElement = elementGrid.Children
- .OfType()
- .FirstOrDefault(x => x.Tag is string tagString && tagString == tagType);
-
- // If the existing element is not found, then starts
- // create a new one
- if (targetElement != null) return targetElement; // Return an existing instance from the parent grid
-
- // Create a new instance of the element and add it into parent grid
- T newElement = elementGrid.AddElementToGridRowColumn(new T());
- newElement.SetTag(tagType); // Set element tag
-
- // If an "assign action" delegate is defined, then execute the delegate
- if (defaultAssignAction != null)
- {
- await defaultAssignAction(newElement);
- }
-
- // Return a new instance of the element
- return newElement;
- }
-
- ///
- /// Assign the default.png image to the new Image instance.
- ///
- /// Type of Image.
- /// Instance of an Image.
- internal static async ValueTask AssignDefaultImage(TElement element)
- {
- // If the element type is an "Image" type, then proceed
- if (element is ImageUI image)
- {
- // Get the default.png path and check if it exists
- string filePath = GetDefaultRegionBackgroundPath();
- if (!File.Exists(filePath))
- {
- return;
- }
-
- // Read the default.png image and load it to
- // the image element.
- await using FileStream fileStream =
- new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read, 64 << 10, true);
- BitmapImage imageSource = new BitmapImage();
- await imageSource.SetSourceAsync(fileStream.AsRandomAccessStream());
- image.Source = imageSource;
- }
- }
-
- ///
- /// Ensure that the instance is already initialized
- ///
- /// Throw if instance is not registered
- private void EnsureCurrentImageRegistered()
- {
- if (_bgImageBackground == null || _bgImageBackgroundLast == null)
- {
- throw new NullReferenceException("bgImageGridBackground instance is null");
- }
- }
-
- ///
- /// Ensure that the instance is already initialized
- ///
- /// Throw if instance is not registered
- private void EnsureCurrentMediaPlayerRegistered()
- {
- if (_bgMediaPlayerBackground == null)
- {
- throw new NullReferenceException("bgMediaPlayerGrid instance is null");
- }
- }
-
- ///
- /// Load Still Image or Video as a background.
- ///
- /// Path of the background file
- /// Request an initialization before processing the background file
- /// Request a cache recreation if the background file properties have been cached
- /// Action to do after exception occurred
- /// Action to do after background is loaded
- /// Throws if the background file is not supported
- /// Throws if some instances aren't yet initialized
- internal void LoadBackground(string mediaPath,
- bool isRequestInit = false,
- bool isForceRecreateCache = false,
- ThrowExceptionAction? throwAction = null,
- Action? actionAfterLoaded = null)
- => RunQueuedTask(LoadBackgroundInner(mediaPath, isRequestInit, isForceRecreateCache, throwAction, actionAfterLoaded));
-
- private async Task LoadBackgroundInner(string mediaPath, bool isRequestInit = false,
- bool isForceRecreateCache = false, ThrowExceptionAction? throwAction = null,
- Action? actionAfterLoaded = null)
- {
- if (mediaPath.Equals(CurrentAppliedMediaPath, StringComparison.OrdinalIgnoreCase))
- return;
-
- Interlocked.Exchange(ref CurrentAppliedMediaPath, mediaPath);
-
- try
- {
- while (!_isCurrentRegistered)
- {
- await Task.Delay(250, _cancellationToken?.Token ?? CancellationToken.None);
- }
-
- EnsureCurrentImageRegistered();
- EnsureCurrentMediaPlayerRegistered();
-
- _loaderMediaPlayer ??= new MediaPlayerLoader(_parentUI!,
- _bgAcrylicMask!, _bgOverlayTitleBar!,
- _parentBgMediaPlayerBackgroundGrid!,
- _bgMediaPlayerBackground);
-
- _loaderStillImage ??= new StillImageLoader(_parentUI!,
- _bgAcrylicMask!, _bgOverlayTitleBar!,
- _parentBgImageBackgroundGrid!,
- _bgImageBackground, _bgImageBackgroundLast);
-
- MediaType mediaType = GetMediaType(mediaPath);
-
- if (_cancellationToken is { IsDisposed: false })
- {
- if (!_cancellationToken.IsCancelled)
- {
- await _cancellationToken.CancelAsync();
- }
-
- _cancellationToken.Dispose();
- }
-
- _cancellationToken = new CancellationTokenSourceWrapper();
- await (mediaType switch
- {
- MediaType.Media => _loaderMediaPlayer,
- MediaType.StillImage => _loaderStillImage as IBackgroundMediaLoader,
- _ => throw new InvalidCastException()
- }).LoadAsync(mediaPath, isForceRecreateCache, isRequestInit, _cancellationToken.Token);
-
- switch (mediaType)
- {
- case MediaType.Media:
- _loaderStillImage?.Hide();
- _loaderMediaPlayer?.Show();
- break;
- case MediaType.StillImage:
- _loaderStillImage?.Show(CurrentAppliedMediaType == MediaType.Media
- || InnerLauncherConfig.m_appCurrentFrameName != "HomePage");
- _loaderMediaPlayer?.Hide();
- break;
- }
-
- if (InnerLauncherConfig.m_appCurrentFrameName != "HomePage")
- {
- if (_loaderMediaPlayer != null) _loaderMediaPlayer.IsBackgroundDimm = true;
- if (_loaderStillImage != null) _loaderStillImage.IsBackgroundDimm = true;
- }
-
- CurrentAppliedMediaType = mediaType;
- actionAfterLoaded?.Invoke();
- }
- catch (Exception ex)
- {
- await SentryHelper.ExceptionHandlerAsync(ex, SentryHelper.ExceptionType.UnhandledOther);
- throwAction?.Invoke(ex);
- }
- }
-
- ///
- /// Dimming the current loaded background
- ///
- internal void Dimm()
- {
- _loaderMediaPlayer?.Dimm();
- _loaderStillImage?.Dimm();
- }
-
- ///
- /// Undimming the current loaded background
- ///
- internal void Undimm()
- {
- _loaderMediaPlayer?.Undimm();
- _loaderStillImage?.Undimm();
- }
-
- ///
- /// Mute the audio of the currently loaded background
- ///
- internal void Mute()
- {
- _loaderMediaPlayer?.Mute();
- _loaderStillImage?.Mute();
- }
-
- ///
- /// Unmute the audio of the currently loaded background
- ///
- internal void Unmute()
- {
- _loaderMediaPlayer?.Unmute();
- _loaderStillImage?.Unmute();
- }
-
- ///
- /// Set the volume of the audio from the currently loaded background
- ///
- internal void SetVolume(double value)
- {
- _loaderMediaPlayer?.SetVolume(value);
- _loaderStillImage?.SetVolume(value);
- }
-
- ///
- /// Trigger the unfocused window event to the currently loaded background
- ///
- internal void WindowUnfocused()
- {
- _loaderMediaPlayer?.WindowUnfocused();
- _loaderStillImage?.WindowUnfocused();
- }
-
- ///
- /// Trigger the focused window event to the currently loaded background
- ///
- internal void WindowFocused()
- {
- _loaderMediaPlayer?.WindowFocused();
- _loaderStillImage?.WindowFocused();
- }
-
- ///
- /// Play/Resume the currently loaded background
- ///
- internal void Play()
- {
- _loaderMediaPlayer?.Play();
- _loaderStillImage?.Play();
- }
-
- ///
- /// Pause the currently loaded background
- ///
- internal void Pause()
- {
- _loaderMediaPlayer?.Pause();
- _loaderStillImage?.Pause();
- }
-
- public static MemoryStream? GetAlternativeImageStream()
- {
- MemoryStream? returnStream = _alternativeImageStream;
- _alternativeImageStream = null;
- return returnStream;
- }
-
- public static void SetAlternativeImageStream(MemoryStream stream)
- {
- _alternativeImageStream?.Dispose();
- _alternativeImageStream = stream;
- }
-
- public static MediaType GetMediaType(string mediaPath)
- {
- string extension = Path.GetExtension(mediaPath);
- if (SupportedImageExt.Contains(extension, StringComparer.OrdinalIgnoreCase))
- {
- return MediaType.StillImage;
- }
-
- return SupportedMediaPlayerExt.Contains(extension, StringComparer.OrdinalIgnoreCase) ? MediaType.Media : MediaType.Unknown;
- }
-
- public static string GetDefaultRegionBackgroundPath()
- {
- GameNameType currentGameType = GameNameType.Unknown;
- try
- {
- GamePresetProperty currentGameProperty = GamePropertyVault.GetCurrentGameProperty();
- currentGameType = currentGameProperty.GamePreset.GameType;
- }
- catch
- {
- // ignored
- }
-
- return currentGameType switch
- {
- GameNameType.Honkai => Path.Combine(LauncherConfig.AppExecutableDir, @"Assets\Images\PageBackground\default_honkai.webp"),
- GameNameType.StarRail => Path.Combine(LauncherConfig.AppExecutableDir, @"Assets\Images\PageBackground\default_starrail.webp"),
- GameNameType.Zenless => Path.Combine(LauncherConfig.AppExecutableDir, @"Assets\Images\PageBackground\default_zzz.webp"),
- GameNameType.Genshin => Path.Combine(LauncherConfig.AppExecutableDir, @"Assets\Images\PageBackground\default_genshin.webp"),
- _ => Path.Combine(LauncherConfig.AppExecutableDir, @"Assets\Images\PageBackground\default.png")
- };
- }
- }
-}
diff --git a/CollapseLauncher/Classes/Helper/Background/Loaders/IBackgroundMediaLoader.cs b/CollapseLauncher/Classes/Helper/Background/Loaders/IBackgroundMediaLoader.cs
deleted file mode 100644
index 8ec20c8bf..000000000
--- a/CollapseLauncher/Classes/Helper/Background/Loaders/IBackgroundMediaLoader.cs
+++ /dev/null
@@ -1,30 +0,0 @@
-using System;
-using System.Diagnostics.CodeAnalysis;
-using System.Threading;
-using System.Threading.Tasks;
-// ReSharper disable UnusedMemberInSuper.Global
-
-#nullable enable
-namespace CollapseLauncher.Helper.Background.Loaders
-{
- [SuppressMessage("ReSharper", "IdentifierTypo")]
- [SuppressMessage("ReSharper", "PossibleNullReferenceException")]
- [SuppressMessage("ReSharper", "AssignNullToNotNullAttribute")]
- internal interface IBackgroundMediaLoader : IDisposable
- {
- bool IsBackgroundDimm { get; set; }
-
- Task LoadAsync(string filePath, bool isForceRecreateCache = false, bool isRequestInit = false, CancellationToken token = default);
- void Dimm();
- void Undimm();
- void Show(bool isForceShow = false);
- void Hide();
- void Mute();
- void Unmute();
- void SetVolume(double value);
- void WindowFocused();
- void WindowUnfocused();
- void Play();
- void Pause();
- }
-}
diff --git a/CollapseLauncher/Classes/Helper/Background/Loaders/MediaPlayerLoader.cs b/CollapseLauncher/Classes/Helper/Background/Loaders/MediaPlayerLoader.cs
deleted file mode 100644
index ce6e35fd0..000000000
--- a/CollapseLauncher/Classes/Helper/Background/Loaders/MediaPlayerLoader.cs
+++ /dev/null
@@ -1,591 +0,0 @@
-using CollapseLauncher.Extension;
-using CollapseLauncher.Helper.Animation;
-using CollapseLauncher.Helper.StreamUtility;
-using CommunityToolkit.WinUI.Animations;
-#if USEFFMPEGFORVIDEOBG
-using FFmpegInteropX;
-#endif
-using Hi3Helper;
-using Hi3Helper.SentryHelper;
-using Hi3Helper.Shared.Region;
-using Microsoft.Graphics.Canvas;
-using Microsoft.Graphics.Canvas.UI.Xaml;
-using Microsoft.UI.Composition;
-using Microsoft.UI.Dispatching;
-using Microsoft.UI.Xaml;
-using Microsoft.UI.Xaml.Controls;
-using Microsoft.UI.Xaml.Media;
-using System;
-using System.Buffers;
-using System.Diagnostics.CodeAnalysis;
-using System.IO;
-using System.Threading;
-using System.Threading.Tasks;
-using Windows.Foundation;
-using Windows.Media.Playback;
-using Windows.Storage;
-using Windows.Storage.FileProperties;
-using Windows.UI;
-using ImageUI = Microsoft.UI.Xaml.Controls.Image;
-using static Hi3Helper.Logger;
-// ReSharper disable PartialTypeWithSinglePart
-// ReSharper disable StringLiteralTypo
-// ReSharper disable AsyncVoidMethod
-// ReSharper disable BadControlBracesIndent
-
-#nullable enable
-namespace CollapseLauncher.Helper.Background.Loaders
-{
- [SuppressMessage("ReSharper", "IdentifierTypo")]
- [SuppressMessage("ReSharper", "PossibleNullReferenceException")]
- [SuppressMessage("ReSharper", "AssignNullToNotNullAttribute")]
- internal sealed partial class MediaPlayerLoader : IBackgroundMediaLoader
- {
- private readonly Color _currentDefaultColor = Color.FromArgb(0, 0, 0, 0);
-
- private FrameworkElement ParentUI { get; }
- private Compositor CurrentCompositor { get; }
- private DispatcherQueue CurrentDispatcherQueue { get; }
- private static bool IsUseVideoBgDynamicColorUpdate
- {
- get => LauncherConfig.IsUseVideoBGDynamicColorUpdate && LauncherConfig.EnableAcrylicEffect;
- }
-
- private Grid AcrylicMask { get; }
- private Grid OverlayTitleBar { get; }
- public bool IsBackgroundDimm { get; set; }
-
- private MemoryStream? _currentMediaStream;
- private MediaPlayer? _currentMediaPlayer;
-#if USEFFMPEGFORVIDEOBG
- private FFmpegMediaSource? _currentFFmpegMediaSource;
-#endif
-
- private const float CanvasBaseDpi = 96f;
-
- private CanvasVirtualImageSource? _currentCanvasVirtualImageSource;
- private CanvasBitmap? _currentCanvasBitmap;
- private CanvasDevice? _currentCanvasDevice;
- private volatile CanvasDrawingSession? _currentCanvasDrawingSession;
- private volatile float _currentCanvasWidth;
- private volatile float _currentCanvasHeight;
- private Rect _currentCanvasDrawArea;
- private readonly MediaPlayerElement? _currentMediaPlayerFrame;
- private readonly Grid _currentMediaPlayerFrameParentGrid;
- private readonly ImageUI _currentImage;
- private readonly Lock _currentLock = new();
-
- internal MediaPlayerLoader(
- FrameworkElement parentUI,
- Grid acrylicMask, Grid overlayTitleBar,
- Grid mediaPlayerParentGrid, MediaPlayerElement? mediaPlayerCurrent)
- {
- ParentUI = parentUI;
- CurrentCompositor = parentUI.GetElementCompositor();
- CurrentDispatcherQueue = parentUI.DispatcherQueue;
-
- AcrylicMask = acrylicMask;
- OverlayTitleBar = overlayTitleBar;
-
- _currentMediaPlayerFrameParentGrid = mediaPlayerParentGrid;
- _currentMediaPlayerFrameParentGrid.SizeChanged += UpdateCanvasOnSizeChangeEvent;
- _currentMediaPlayerFrame = mediaPlayerCurrent;
-
- float actualWidth = (float)_currentMediaPlayerFrameParentGrid.ActualWidth;
- float actualHeight = (float)_currentMediaPlayerFrameParentGrid.ActualHeight;
- float scalingFactor = (float)WindowUtility.CurrentWindowMonitorScaleFactor;
-
- _currentCanvasWidth = actualWidth * scalingFactor;
- _currentCanvasHeight = actualHeight * scalingFactor;
- _currentCanvasDrawArea = new Rect(0f, 0f, _currentCanvasWidth, _currentCanvasHeight);
-
- _currentImage = mediaPlayerParentGrid.AddElementToGridRowColumn(new ImageUI
- {
- HorizontalAlignment = HorizontalAlignment.Center,
- VerticalAlignment = VerticalAlignment.Center,
- Stretch = Stretch.UniformToFill
- });
- }
-
- ~MediaPlayerLoader()
- {
- LogWriteLine("[~MediaPlayerLoader()] MediaPlayerLoader Destructor has been called!", LogType.Warning, true);
- Dispose();
- }
-
- public void Dispose()
- {
- try
- {
- _currentMediaPlayerFrameParentGrid.SizeChanged -= UpdateCanvasOnSizeChangeEvent;
- DisposeMediaModules();
- }
- catch (Exception ex)
- {
- LogWriteLine($"Error disposing Media Modules: {ex.Message}", LogType.Error, true);
- SentryHelper.ExceptionHandler(ex, SentryHelper.ExceptionType.UnhandledOther);
- }
-
- GC.SuppressFinalize(this);
- }
-
- public async Task LoadAsync(string filePath, bool isImageLoadForFirstTime,
- bool isRequestInit, CancellationToken token)
- {
- try
- {
- DisposeMediaModules();
- _currentMediaPlayer ??= new MediaPlayer();
-
- if (IsUseVideoBgDynamicColorUpdate)
- {
- _currentCanvasDevice ??= CanvasDevice.GetSharedDevice();
- CreateAndAssignCanvasVirtualImageSource();
- CreateCanvasBitmap();
-
- _currentImage.Visibility = Visibility.Visible;
- App.ToggleBlurBackdrop();
- }
- else
- {
- _currentImage.Visibility = Visibility.Collapsed;
- }
-
- await GetPreviewAsColorPalette(filePath);
-
- if (_currentMediaStream == null)
- {
- var altStream = BackgroundMediaUtility.GetAlternativeImageStream();
- if (altStream != null)
- {
- _currentMediaStream = altStream;
- }
- else
- {
- await using var fileStream = File.Open(filePath, StreamExtension.FileStreamOpenReadOpt);
- var memoryStream = new MemoryStream();
- await fileStream.CopyToAsync(memoryStream, token);
- memoryStream.Position = 0;
- _currentMediaStream = memoryStream;
- }
- }
-
-#if !USEFFMPEGFORVIDEOBG
- EnsureIfFormatIsDashOrUnsupported(_currentMediaStream);
- _currentMediaPlayer ??= new MediaPlayer();
-#endif
-
- if (WindowUtility.IsCurrentWindowInFocus())
- {
- _currentMediaPlayer.AutoPlay = true;
- }
-
- bool isAudioMute = LauncherConfig.GetAppConfigValue("BackgroundAudioIsMute").ToBool();
- double lastAudioVolume = LauncherConfig.GetAppConfigValue("BackgroundAudioVolume").ToDouble();
-
- _currentMediaPlayer.IsMuted = isAudioMute;
- _currentMediaPlayer.Volume = lastAudioVolume;
- _currentMediaPlayer.IsLoopingEnabled = true;
-
-#if !USEFFMPEGFORVIDEOBG
- _currentMediaPlayer.SetStreamSource(_currentMediaStream.AsRandomAccessStream());
-#else
- _currentFFmpegMediaSource ??= await FFmpegMediaSource.CreateFromStreamAsync(_currentMediaStream.AsRandomAccessStream());
-
- await _currentFFmpegMediaSource.OpenWithMediaPlayerAsync(_currentMediaPlayer);
- const string mediaInfoStrFormat = """
- Playing background video with FFmpeg!
- Media Duration: {0}
- Video Resolution: {9}x{10} px
- Video Codec: {1}
- Video Codec Decoding Method: {3}
- Video Decoder Engine: {11}
- Video Bitrate: {2} bps
- Video Bitdepth: {11} Bits
- Audio Codec: {4}
- Audio Bitrate: {5} bps
- Audio Channel: {6}
- Audio Sample: {7}Hz
- Audio Bitwide: {8} Bits
- """;
- LogWriteLine(
- string.Format(mediaInfoStrFormat,
- _currentFFmpegMediaSource.Duration.ToString("c"), // 0
- _currentFFmpegMediaSource.CurrentVideoStream?.CodecName ?? "No Video Stream", // 1
- _currentFFmpegMediaSource.CurrentVideoStream?.Bitrate ?? 0, // 2
- _currentFFmpegMediaSource.CurrentVideoStream?.DecoderEngine // 3
- == DecoderEngine.FFmpegD3D11HardwareDecoder ? "Hardware" : "Software",
- _currentFFmpegMediaSource.CurrentAudioStream?.CodecName ?? "No Audio Stream", // 4
- _currentFFmpegMediaSource.CurrentAudioStream?.Bitrate ?? 0, // 5
- _currentFFmpegMediaSource.CurrentAudioStream?.Channels ?? 0, // 6
- _currentFFmpegMediaSource.CurrentAudioStream?.SampleRate ?? 0, // 7
- _currentFFmpegMediaSource.CurrentAudioStream?.BitsPerSample ?? 0, // 8
- _currentFFmpegMediaSource.CurrentVideoStream?.PixelWidth ?? 0, // 9
- _currentFFmpegMediaSource.CurrentVideoStream?.PixelHeight ?? 0, // 10
- _currentFFmpegMediaSource.CurrentVideoStream?.BitsPerSample ?? 0 // 11
- ), LogType.Debug, true);
-#endif
- _currentMediaPlayer.IsVideoFrameServerEnabled = IsUseVideoBgDynamicColorUpdate;
- if (IsUseVideoBgDynamicColorUpdate)
- {
- _currentMediaPlayer.VideoFrameAvailable += FrameGrabberEvent;
- }
-
- _currentMediaPlayerFrame?.SetMediaPlayer(_currentMediaPlayer);
- _currentMediaPlayer.Play();
- }
- catch
- {
- DisposeMediaModules();
- await BackgroundMediaUtility.AssignDefaultImage(_currentImage);
- throw;
- }
- finally
- {
- GC.Collect();
- GC.WaitForPendingFinalizers();
- }
- }
-
- private void UpdateCanvasOnSizeChangeEvent(object sender, SizeChangedEventArgs e)
- {
- using (_currentLock.EnterScope())
- {
- float scalingFactor = (float)WindowUtility.CurrentWindowMonitorScaleFactor;
- float newWidth = (float)(e.NewSize.Width * scalingFactor);
- float newHeight = (float)(e.NewSize.Height * scalingFactor);
-
- LogWriteLine($"Updating video canvas size from: {_currentCanvasWidth}x{_currentCanvasHeight} to {newWidth}x{newHeight}", LogType.Debug, true);
-
- _currentCanvasWidth = newWidth;
- _currentCanvasHeight = newHeight;
- _currentCanvasDrawArea = new Rect(0, 0, _currentCanvasWidth, _currentCanvasHeight);
-
- _currentCanvasBitmap?.Dispose();
- _currentCanvasBitmap = null;
- _currentCanvasVirtualImageSource = null;
-
- _currentCanvasDevice ??= CanvasDevice.GetSharedDevice();
- CreateAndAssignCanvasVirtualImageSource();
- CreateCanvasBitmap();
- }
- }
-
- private void CreateAndAssignCanvasVirtualImageSource()
- {
- _currentCanvasVirtualImageSource ??= new CanvasVirtualImageSource(_currentCanvasDevice,
- _currentCanvasWidth,
- _currentCanvasHeight,
- CanvasBaseDpi);
-
- _currentImage.Source = _currentCanvasVirtualImageSource.Source;
- }
-
- private void CreateCanvasBitmap()
- {
- int widthInt = (int)_currentCanvasWidth;
- int heightInt = (int)_currentCanvasHeight;
-
- byte[] temporaryBuffer = ArrayPool.Shared.Rent(widthInt * heightInt * 4);
- try
- {
- _currentCanvasBitmap ??= CanvasBitmap
- .CreateFromBytes(_currentCanvasDevice,
- temporaryBuffer,
- widthInt,
- heightInt,
- Windows.Graphics.DirectX.DirectXPixelFormat.B8G8R8A8UIntNormalized,
- CanvasBaseDpi,
- CanvasAlphaMode.Ignore);
- }
- finally
- {
- ArrayPool.Shared.Return(temporaryBuffer);
- }
- }
-
- private void DisposeMediaModules()
- {
-#if !USEFFMPEGFORVIDEOBG
- if (_currentMediaPlayer != null)
- {
- _currentMediaPlayer.VideoFrameAvailable -= FrameGrabberEvent;
- _currentMediaPlayer.Dispose();
- Interlocked.Exchange(ref _currentMediaPlayer, null);
- }
-#endif
-
- if (IsUseVideoBgDynamicColorUpdate)
- {
- using (_currentLock.EnterScope())
- {
- _currentCanvasDrawingSession?.Dispose();
- Interlocked.Exchange(ref _currentCanvasDrawingSession, null);
- }
- }
-
- if (_currentCanvasVirtualImageSource != null)
- {
- Interlocked.Exchange(ref _currentCanvasVirtualImageSource, null);
- }
-
- if (_currentCanvasBitmap != null)
- {
- _currentCanvasBitmap.Dispose();
- Interlocked.Exchange(ref _currentCanvasBitmap, null);
- }
-
- if (_currentCanvasDevice != null)
- {
- _currentCanvasDevice.Dispose();
- Interlocked.Exchange(ref _currentCanvasDevice, null);
- }
-
-#if USEFFMPEGFORVIDEOBG
- _currentFFmpegMediaSource?.Dispose();
- _currentFFmpegMediaSource = null;
-#endif
- _currentMediaStream?.Dispose();
- _currentMediaStream = null;
- }
-
-#if !USEFFMPEGFORVIDEOBG
- private static void EnsureIfFormatIsDashOrUnsupported(Stream stream)
- {
- ReadOnlySpan dashSignature = "ftypdash"u8;
-
- Span buffer = stackalloc byte[64];
- stream.ReadExactly(buffer);
-
- try
- {
- if (buffer.StartsWith(dashSignature))
- throw new FormatException("The video format is in \"MPEG-DASH\" format, which is unsupported.");
- }
- finally
- {
- stream.Position = 0;
- }
- }
-#endif
-
- private async Task GetPreviewAsColorPalette(string file)
- {
- StorageFile storageFile = await GetFileAsStorageFile(file);
- using StorageItemThumbnail thumbnail = await storageFile.GetThumbnailAsync(ThumbnailMode.VideosView);
-
- await ColorPaletteUtility
- .ApplyAccentColor(ParentUI,
- thumbnail,
- string.Empty,
- false,
- true);
- }
-
- private static async Task GetFileAsStorageFile(string filePath)
- => await StorageFile.GetFileFromPathAsync(filePath);
-
- #pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously
- private async void FrameGrabberEvent(MediaPlayer mediaPlayer, object args)
- #pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously
- {
- using (_currentLock.EnterScope())
- {
- if (_currentCanvasVirtualImageSource is null)
- {
- return;
- }
-
- if (_currentCanvasDrawingSession is not null)
- {
-#if DEBUG
- LogWriteLine($@"[FrameGrabberEvent] Frame skipped at: {mediaPlayer.Position:hh\:mm\:ss\.ffffff}", LogType.Debug, true);
-#endif
- return;
- }
- }
-
- try
- {
- using (_currentLock.EnterScope())
- {
- mediaPlayer.CopyFrameToVideoSurface(_currentCanvasBitmap);
- _currentCanvasDrawingSession = _currentCanvasVirtualImageSource
- .CreateDrawingSession(_currentDefaultColor,
- _currentCanvasDrawArea);
- _currentCanvasDrawingSession.DrawImage(_currentCanvasBitmap);
- }
- }
- catch
-#if DEBUG
- (Exception e)
- {
- LogWriteLine($"[FrameGrabberEvent] Error while drawing frame to bitmap.\r\n{e}", LogType.Warning, true);
- }
-#else
- {
- // ignored
- }
-#endif
- finally
- {
- using (_currentLock.EnterScope())
- {
- CurrentDispatcherQueue.TryEnqueue(() =>
- {
- _currentCanvasDrawingSession?.Dispose();
- _currentCanvasDrawingSession = null;
- });
- }
- }
- }
-
- public void Dimm() => BackgroundMediaUtility.RunQueuedTask(ToggleImageVisibility(true));
-
- public void Undimm() => BackgroundMediaUtility.RunQueuedTask(ToggleImageVisibility(false));
-
- private Task ToggleImageVisibility(bool hideImage)
- {
- if (IsBackgroundDimm == hideImage) return Task.CompletedTask;
- IsBackgroundDimm = hideImage;
-
- TimeSpan duration = TimeSpan.FromSeconds(hideImage
- ? BackgroundMediaUtility.TransitionDuration
- : BackgroundMediaUtility.TransitionDurationSlow);
- return Task.WhenAll(
- AcrylicMask.StartAnimation(
- duration,
- CurrentCompositor.CreateScalarKeyFrameAnimation("Opacity",
- hideImage ? 1f : 0f,
- hideImage ? 0f : 1f)
- ),
- OverlayTitleBar.StartAnimation(
- duration,
- CurrentCompositor.CreateScalarKeyFrameAnimation("Opacity",
- hideImage ? 0f : 1f,
- hideImage ? 1f : 0f)
- )
- );
- }
-
- public void Show(bool isForceShow = false) => BackgroundMediaUtility.RunQueuedTask(ShowInner());
-
- private Task ShowInner()
- {
- if (_currentMediaPlayerFrameParentGrid.Opacity > 0f) return Task.CompletedTask;
-
- if (!IsUseVideoBgDynamicColorUpdate)
- {
- App.ToggleBlurBackdrop(false);
- }
- TimeSpan duration = TimeSpan.FromSeconds(BackgroundMediaUtility.TransitionDuration);
-
- return _currentMediaPlayerFrameParentGrid
- .StartAnimation(duration,
- CurrentCompositor
- .CreateScalarKeyFrameAnimation("Opacity", 1f, 0f)
- );
- }
-
- public void Hide() => BackgroundMediaUtility.RunQueuedTask(HideInner());
-
- private async Task HideInner()
- {
- bool isLastAcrylicEnabled = LauncherConfig.GetAppConfigValue("EnableAcrylicEffect").ToBool();
-
- if (!IsUseVideoBgDynamicColorUpdate)
- {
- App.ToggleBlurBackdrop(isLastAcrylicEnabled);
- }
-
- if (_currentMediaPlayerFrameParentGrid.Opacity < 1f) return;
- TimeSpan duration = TimeSpan.FromSeconds(BackgroundMediaUtility.TransitionDuration);
-
- await _currentMediaPlayerFrameParentGrid
- .StartAnimation(duration,
- CurrentCompositor
- .CreateScalarKeyFrameAnimation("Opacity", 0f,
- (float)_currentMediaPlayerFrameParentGrid
- .Opacity)
- );
-
- DisposeMediaModules();
- }
-
- public void WindowUnfocused() => BackgroundMediaUtility.RunQueuedTask(WindowUnfocusedInner());
-
- private async Task WindowUnfocusedInner()
- {
- double currentAudioVolume = _currentMediaPlayer?.Volume ?? 0;
- await InterpolateVolumeChange((float)currentAudioVolume, 0f, true);
- Pause();
- }
-
- public void WindowFocused() => BackgroundMediaUtility.RunQueuedTask(WindowFocusedInner());
-
- private async Task WindowFocusedInner()
- {
- double currentAudioVolume = LauncherConfig.GetAppConfigValue("BackgroundAudioVolume")
- .ToDouble();
- Play();
- await InterpolateVolumeChange(0f, (float)currentAudioVolume, false);
- }
-
- public void Mute()
- {
- if (_currentMediaPlayer == null) return;
- _currentMediaPlayer.IsMuted = true;
- LauncherConfig.SetAndSaveConfigValue("BackgroundAudioIsMute", true);
- }
-
- public void Unmute()
- {
- if (_currentMediaPlayer == null) return;
-
- _currentMediaPlayer.IsMuted = false;
- LauncherConfig.SetAndSaveConfigValue("BackgroundAudioIsMute", false);
- }
-
- private async Task InterpolateVolumeChange(float from, float to, bool isMute)
- {
- double tFrom = from;
- double tTo = to;
-
- double current = tFrom;
- double inc = isMute ? -0.05 : 0.05;
-
- Loops:
- if (_currentMediaPlayer == null) return;
-
- current += inc;
- _currentMediaPlayer.Volume = current;
-
- await Task.Delay(10);
- switch (isMute)
- {
- case true when current > tTo - inc:
- case false when current < tTo - inc:
- goto Loops;
- }
-
- _currentMediaPlayer.Volume = tTo;
- }
-
- public void SetVolume(double value)
- {
- if (_currentMediaPlayer != null)
- _currentMediaPlayer.Volume = value;
- LauncherConfig.SetAndSaveConfigValue("BackgroundAudioVolume", value);
- }
-
- public void Play()
- {
- _currentMediaPlayer?.Play();
- }
-
- public void Pause()
- {
- _currentMediaPlayer?.Pause();
- }
- }
-}
diff --git a/CollapseLauncher/Classes/Helper/Background/Loaders/StillImageLoader.cs b/CollapseLauncher/Classes/Helper/Background/Loaders/StillImageLoader.cs
deleted file mode 100644
index 917a14a16..000000000
--- a/CollapseLauncher/Classes/Helper/Background/Loaders/StillImageLoader.cs
+++ /dev/null
@@ -1,254 +0,0 @@
-using CollapseLauncher.Extension;
-using CollapseLauncher.Helper.Animation;
-using CollapseLauncher.Helper.Image;
-using CommunityToolkit.WinUI.Animations;
-using Hi3Helper.Shared.Region;
-using Microsoft.UI.Composition;
-using Microsoft.UI.Xaml;
-using Microsoft.UI.Xaml.Controls;
-using Microsoft.UI.Xaml.Media.Imaging;
-using System;
-using System.Diagnostics.CodeAnalysis;
-using System.IO;
-using System.Numerics;
-using System.Runtime.InteropServices;
-using System.Threading;
-using System.Threading.Tasks;
-using ImageUI = Microsoft.UI.Xaml.Controls.Image;
-// ReSharper disable PartialTypeWithSinglePart
-#pragma warning disable IDE0130
-
-#nullable enable
-namespace CollapseLauncher.Helper.Background.Loaders
-{
- [SuppressMessage("ReSharper", "IdentifierTypo")]
- [SuppressMessage("ReSharper", "PossibleNullReferenceException")]
- [SuppressMessage("ReSharper", "AssignNullToNotNullAttribute")]
- internal sealed partial class StillImageLoader : IBackgroundMediaLoader
- {
- private FrameworkElement ParentUI { get; }
- private Compositor CurrentCompositor { get; }
- private ImageUI? ImageBackCurrent { get; }
- private ImageUI? ImageBackLast { get; }
- private Grid? ImageBackParentGrid { get; }
- private Grid AcrylicMask { get; }
- private Grid OverlayTitleBar { get; }
- private double AnimationDuration { get; }
-
- public bool IsBackgroundDimm
- {
- get;
- set;
- }
-
- internal StillImageLoader(
- FrameworkElement parentUI,
- Grid acrylicMask, Grid overlayTitleBar,
- Grid imageBackParentGrid,
- ImageUI? imageBackCurrent, ImageUI? imageBackLast,
- double animationDuration = BackgroundMediaUtility.TransitionDuration)
- {
- GC.SuppressFinalize(this);
- ParentUI = parentUI;
- CurrentCompositor = parentUI.GetElementCompositor();
-
- AcrylicMask = acrylicMask;
- OverlayTitleBar = overlayTitleBar;
-
- ImageBackCurrent = imageBackCurrent;
- ImageBackLast = imageBackLast;
- ImageBackParentGrid = imageBackParentGrid;
-
- AnimationDuration = animationDuration;
- }
-
- ~StillImageLoader() => Dispose();
-
- public void Dispose()
- {
- GC.Collect();
- GC.SuppressFinalize(this);
- }
-
- public async Task LoadAsync(string filePath,
- bool isImageLoadForFirstTime,
- bool isRequestInit,
- CancellationToken token)
- {
- // Get the image stream
- token.ThrowIfCancellationRequested();
- await using MemoryStream? imageStream = BackgroundMediaUtility.GetAlternativeImageStream() ??
- await ImageLoaderHelper.LoadImage(filePath, false,
- isImageLoadForFirstTime);
- // Return if the stream is null due to cancellation or an error.
- if (imageStream == null)
- {
- return;
- }
-
- // Load and decode to WriteableBitmap
- WriteableBitmap wBitmap = new(1, 1);
- await wBitmap.SetSourceAsync(imageStream.AsRandomAccessStream());
-
- // Run image switch task
- nint wBitmapBufferP = wBitmap.GetBufferPointer(out uint colorChannels);
- if (wBitmapBufferP == nint.Zero)
- {
- throw new COMException("Cannot obtain buffer pointer from WriteableBitmap");
- }
-
- Task applyColorTask = ColorPaletteUtility
- .ApplyAccentColor(ParentUI,
- new BitmapInputStruct
- {
- Buffer = wBitmapBufferP,
- Channel = (int)colorChannels,
- Width = wBitmap.PixelWidth,
- Height = wBitmap.PixelHeight
- },
- filePath,
- isImageLoadForFirstTime);
- Task applyAndSwitchImageTask = ApplyAndSwitchImage(AnimationDuration, wBitmap);
-
- _ = Task.WhenAll(applyAndSwitchImageTask, applyColorTask).ContinueWith(_ => GC.Collect(), token);
- }
-
- private Task ApplyAndSwitchImage(double duration, BitmapSource imageToApply)
- {
- TimeSpan timeSpan = TimeSpan.FromSeconds(duration);
-
- ImageBackLast!.Source = ImageBackCurrent!.Source;
-
- ImageBackCurrent!.Opacity = 0;
-
- ImageBackLast!.Opacity = 1;
- ImageBackCurrent!.Source = imageToApply;
-
- return Task.WhenAll(
- ImageBackCurrent.StartAnimation(timeSpan,
- CurrentCompositor
- .CreateScalarKeyFrameAnimation("Opacity",
- 1, 0)),
- ImageBackLast.StartAnimation(timeSpan,
- CurrentCompositor
- .CreateScalarKeyFrameAnimation("Opacity",
- 0, 1, timeSpan * 0.5))
- );
- }
-
- public void Dimm() => BackgroundMediaUtility.RunQueuedTask(ToggleImageVisibility(true));
-
- public void Undimm() => BackgroundMediaUtility.RunQueuedTask(ToggleImageVisibility(false));
-
- private Task ToggleImageVisibility(bool hideImage, bool completeInvisible = false, bool isForceShow = false)
- {
- if (isForceShow)
- {
- hideImage = false;
- completeInvisible = false;
- }
- else
- {
- if (IsBackgroundDimm == hideImage) return Task.CompletedTask;
- IsBackgroundDimm = hideImage;
- }
-
- TimeSpan duration = TimeSpan.FromSeconds(hideImage
- ? BackgroundMediaUtility.TransitionDuration
- : BackgroundMediaUtility.TransitionDurationSlow);
-
- const float fromScale = 1f;
- Vector3 fromTranslate =
- new Vector3(-((float)(ImageBackParentGrid?.ActualWidth ?? 0) * (fromScale - 1f) / 2),
- -((float)(ImageBackParentGrid?.ActualHeight ?? 0) * (fromScale - 1f) / 2), 0);
- const float toScale = 1.07f;
- Vector3 toTranslate = new Vector3(-((float)(ImageBackParentGrid?.ActualWidth ?? 0) * (toScale - 1f) / 2),
- -((float)(ImageBackParentGrid?.ActualHeight ?? 0) * (toScale - 1f) / 2), 0);
-
- if (isForceShow)
- {
- return Task.WhenAll(
- AcrylicMask.StartAnimation(
- duration,
- CurrentCompositor.CreateScalarKeyFrameAnimation("Opacity", hideImage ? 1f : 0f, hideImage ? 0f : 1f)
- ),
- OverlayTitleBar.StartAnimation(
- duration,
- CurrentCompositor.CreateScalarKeyFrameAnimation("Opacity", hideImage ? 0f : 1f, hideImage ? 1f : 0f)
- ),
- ImageBackParentGrid.StartAnimation(
- duration,
- CurrentCompositor.CreateVector3KeyFrameAnimation("Scale", new Vector3(hideImage ? toScale : fromScale), new Vector3(!hideImage ? toScale : fromScale)),
- CurrentCompositor.CreateVector3KeyFrameAnimation("Translation", hideImage ? toTranslate : fromTranslate, !hideImage ? toTranslate : fromTranslate),
- CurrentCompositor.CreateScalarKeyFrameAnimation("Opacity", 1f, 0f)
- )
- );
- }
-
- if (completeInvisible)
- {
- return Task.WhenAll(
- ImageBackParentGrid.StartAnimation(
- duration,
- CurrentCompositor.CreateScalarKeyFrameAnimation("Opacity", hideImage ? completeInvisible ? 0f : 0.4f : 1f, hideImage ? 1f : 0f)
- )
- );
- }
-
- return Task.WhenAll(AcrylicMask.StartAnimation(duration,
- CurrentCompositor.CreateScalarKeyFrameAnimation("Opacity", hideImage ? 1f : 0f, hideImage ? 0f : 1f)
- ),
- OverlayTitleBar.StartAnimation(duration,
- CurrentCompositor.CreateScalarKeyFrameAnimation("Opacity", hideImage ? 0f : 1f, hideImage ? 1f : 0f)
- ),
- ImageBackParentGrid.StartAnimation(duration,
- CurrentCompositor.CreateVector3KeyFrameAnimation("Scale", new Vector3(hideImage ? toScale : fromScale), new Vector3(!hideImage ? toScale : fromScale)),
- CurrentCompositor.CreateVector3KeyFrameAnimation("Translation", hideImage ? toTranslate : fromTranslate, !hideImage ? toTranslate : fromTranslate)
- )
- );
- }
-
- public void Show(bool isForceShow = false)
- {
- if (ImageBackParentGrid?.Opacity > 0f) return;
- BackgroundMediaUtility.RunQueuedTask(ToggleImageVisibility(false, true, isForceShow));
- }
-
- public void Hide()
- {
- if (ImageBackParentGrid?.Opacity < 1f) return;
- BackgroundMediaUtility.RunQueuedTask(ToggleImageVisibility(true, true));
- }
-
- public void Mute()
- {
- LauncherConfig.SetAndSaveConfigValue("BackgroundAudioIsMute", true);
- }
-
- public void Unmute()
- {
- LauncherConfig.SetAndSaveConfigValue("BackgroundAudioIsMute", false);
- }
-
- public void SetVolume(double value)
- {
- LauncherConfig.SetAndSaveConfigValue("BackgroundAudioVolume", value);
- }
-
- public void WindowFocused()
- {
- }
-
- public void WindowUnfocused()
- {
- }
-
- public void Play()
- {
- }
-
- public void Pause()
- {
- }
- }
-}
diff --git a/CollapseLauncher/Classes/Helper/Image/ImageLoaderHelper.cs b/CollapseLauncher/Classes/Helper/Image/ImageLoaderHelper.cs
index cf5b8d5fc..038d6cba8 100644
--- a/CollapseLauncher/Classes/Helper/Image/ImageLoaderHelper.cs
+++ b/CollapseLauncher/Classes/Helper/Image/ImageLoaderHelper.cs
@@ -1,21 +1,18 @@
using CollapseLauncher.CustomControls;
using CollapseLauncher.Dialogs;
-using CollapseLauncher.Extension;
-using CollapseLauncher.Helper.Background;
+using CollapseLauncher.GameManagement.ImageBackground;
using CollapseLauncher.Helper.StreamUtility;
using CollapseLauncher.Plugins;
-using CommunityToolkit.WinUI.Animations;
+using CollapseLauncher.XAMLs.Theme.CustomControls.LayeredBackgroundImage;
using CommunityToolkit.WinUI.Media;
using Hi3Helper;
using Hi3Helper.Data;
using Hi3Helper.EncTool;
using Hi3Helper.EncTool.Hashes;
using Hi3Helper.SentryHelper;
-using Microsoft.UI.Text;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Media;
-using Microsoft.UI.Xaml.Media.Animation;
using Microsoft.UI.Xaml.Media.Imaging;
using PhotoSauce.MagicScaler;
using System;
@@ -34,15 +31,12 @@
using System.Text;
using System.Threading;
using System.Threading.Tasks;
-using Windows.Storage;
using Windows.Storage.Streams;
using static CollapseLauncher.Helper.Image.Waifu2X;
using static Hi3Helper.Shared.Region.LauncherConfig;
-using BitmapFileFormat = Hi3Helper.CommunityToolkit.WinUI.Controls.BitmapFileFormat;
using CropShape = Hi3Helper.CommunityToolkit.WinUI.Controls.CropShape;
using ImageBlendBrush = Hi3Helper.CommunityToolkit.WinUI.Media.ImageBlendBrush;
using ImageCropper = Hi3Helper.CommunityToolkit.WinUI.Controls.ImageCropper;
-using Orientation = Microsoft.UI.Xaml.Controls.Orientation;
using ThumbPlacement = Hi3Helper.CommunityToolkit.WinUI.Controls.ThumbPlacement;
// ReSharper disable IdentifierTypo
// ReSharper disable StringLiteralTypo
@@ -54,15 +48,23 @@ namespace CollapseLauncher.Helper.Image
{
internal static class ImageLoaderHelper
{
- internal static readonly Dictionary SupportedImageFormats =
+ internal static readonly string SupportedImageFormats =
+ string.Join(";", LayeredBackgroundImage.SupportedImageBitmapExtensions.Select(x => $"*{x}")) + ";" +
+ string.Join(";", LayeredBackgroundImage.SupportedImageBitmapExternalCodecExtensions.Select(x => $"*{x}")) + ";" +
+ string.Join(";", LayeredBackgroundImage.SupportedImageVectorExtensions.Select(x => $"*{x}"));
+
+ internal static readonly string SupportedVideoFormats =
+ string.Join(";", LayeredBackgroundImage.SupportedVideoExtensions.Select(x => $"*{x}"));
+
+ internal static readonly Dictionary SupportedBackgroundFormats =
new() {
- { "All supported formats", string.Join(';', BackgroundMediaUtility.SupportedImageExt.Select(x => $"*{x}")) + ';' + string.Join(';', BackgroundMediaUtility.SupportedMediaPlayerExt.Select(x => $"*{x}")) },
- { "Image formats", string.Join(';', BackgroundMediaUtility.SupportedImageExt.Select(x => $"*{x}")) },
- { "Video formats", string.Join(';', BackgroundMediaUtility.SupportedMediaPlayerExt.Select(x => $"*{x}")) }
+ { "All supported formats", SupportedImageFormats + ';' + SupportedVideoFormats },
+ { "Image formats", SupportedImageFormats },
+ { "Video formats", SupportedVideoFormats }
};
#region Waifu2X
- private static Waifu2X _waifu2X;
+ public static Waifu2X _waifu2X;
private static Waifu2XStatus _cachedStatus = Waifu2XStatus.NotInitialized;
public static Waifu2XStatus Waifu2XStatus => _cachedStatus;
@@ -133,7 +135,7 @@ internal static async Task LoadImage(string path, bool isUseImageC
try
{
- FileInfo inputFileInfo = new FileInfo(path);
+ FileInfo inputFileInfo = new(path);
FileInfo resizedFileInfo = GetCacheFileInfo(inputFileInfo.FullName + inputFileInfo.Length);
if (resizedFileInfo!.Exists && resizedFileInfo.Length > 1 << 15 && !overwriteCachedImage)
{
@@ -180,12 +182,11 @@ private static async Task SpawnImageCropperDialog(string filePath, s
Grid parentGrid = new()
{
HorizontalAlignment = HorizontalAlignment.Stretch,
- VerticalAlignment = VerticalAlignment.Stretch,
- CornerRadius = new CornerRadius(12)
- // Margin = new Thickness(-23, -19, -23, -19)
+ VerticalAlignment = VerticalAlignment.Stretch,
+ CornerRadius = new CornerRadius(12)
};
- ImageCropper imageCropper = new ImageCropper
+ ImageCropper imageCropper = new()
{
AspectRatio = 16d / 9d,
CropShape = CropShape.Rectangular,
@@ -196,47 +197,41 @@ private static async Task SpawnImageCropperDialog(string filePath, s
};
// Path of image
- Uri overlayImageUri = new Uri(Path.Combine(AppExecutableDir, @"Assets\Images\ImageCropperOverlay",
- GetAppConfigValue("WindowSizeProfile").ToString() == "Small" ? "small.png" : "normal.png"));
+ Uri overlayImageUri = new(Path.Combine(AppExecutableDir, @"Assets\Images\ImageCropperOverlay",
+ GetAppConfigValue("WindowSizeProfile").ToString() == "Small" ? "small.png" : "normal.png"));
// Why not use ImageBrush?
// https://github.com/microsoft/microsoft-ui-xaml/issues/7809
imageCropper.Overlay = new ImageBlendBrush
{
- Opacity = 0.5,
- Stretch = Stretch.Fill,
- Mode = ImageBlendMode.Multiply,
+ Opacity = 0.5,
+ Stretch = Stretch.Fill,
+ Mode = ImageBlendMode.Multiply,
SourceUri = overlayImageUri
};
- ContentDialogOverlay dialogOverlay = new ContentDialogOverlay(ContentDialogTheme.Informational)
+ ContentDialogOverlay dialogOverlay = new(ContentDialogTheme.Informational)
{
- Title = Locale.Lang!._Misc!.ImageCropperTitle,
- Content = parentGrid,
- SecondaryButtonText = Locale.Lang._Misc.Cancel,
- PrimaryButtonText = Locale.Lang._Misc.OkayHappy,
- DefaultButton = ContentDialogButton.Primary,
+ Title = Locale.Lang._Misc.ImageCropperTitle,
+ Content = parentGrid,
+ SecondaryButtonText = Locale.Lang._Misc.Cancel,
+ PrimaryButtonText = Locale.Lang._Misc.OkayHappy,
+ DefaultButton = ContentDialogButton.Primary,
IsPrimaryButtonEnabled = false,
- XamlRoot = (WindowUtility.CurrentWindow as MainWindow)?.Content!.XamlRoot
+ XamlRoot = (WindowUtility.CurrentWindow as MainWindow)?.Content.XamlRoot
};
- LoadImageCropperDetached(filePath, imageCropper, parentGrid, dialogOverlay);
+ ImageBackgroundManager.LoadImageCropperDetached(new Uri(filePath), imageCropper, parentGrid, dialogOverlay);
ContentDialogResult dialogResult = await dialogOverlay.QueueAndSpawnDialog();
if (dialogResult == ContentDialogResult.Secondary) return null;
try
{
- await using (FileStream cachedFileStream =
- new FileStream(cachedFilePath!, StreamExtension.FileStreamCreateReadWriteOpt))
- {
- dialogOverlay.IsPrimaryButtonEnabled = false;
- dialogOverlay.IsSecondaryButtonEnabled = false;
- await imageCropper.SaveAsync(cachedFileStream.AsRandomAccessStream()!, BitmapFileFormat.Png);
- }
-
- GC.WaitForPendingFinalizers();
- GC.WaitForFullGCComplete();
+ await ImageBackgroundManager.SaveCroppedImageToFilePath(filePath,
+ cachedFilePath,
+ imageCropper,
+ CancellationToken.None);
}
catch (Exception ex)
{
@@ -244,74 +239,21 @@ private static async Task SpawnImageCropperDialog(string filePath, s
await SentryHelper.ExceptionHandlerAsync(ex, SentryHelper.ExceptionType.UnhandledOther);
}
- FileInfo cachedFileInfo = new FileInfo(cachedFilePath);
+ FileInfo cachedFileInfo = new(cachedFilePath);
return await GenerateCachedStream(cachedFileInfo, toWidth, toHeight, true);
}
- private static async void LoadImageCropperDetached(string filePath, ImageCropper imageCropper,
- Grid parentGrid, ContentDialogOverlay dialogOverlay)
- {
- try
- {
- StackPanel loadingMsgPanel = new()
- {
- Orientation = Orientation.Horizontal,
- HorizontalAlignment = HorizontalAlignment.Center,
- VerticalAlignment = VerticalAlignment.Center,
- Opacity = 1d
- };
-
- loadingMsgPanel.AddElementToStackPanel(new ProgressRing
- {
- IsIndeterminate = true,
- Width = 16,
- Height = 16,
- VerticalAlignment = VerticalAlignment.Center,
- Margin = new Thickness(0, 0, 8, 0)
- });
- loadingMsgPanel.AddElementToStackPanel(new TextBlock
- {
- Text = "Loading the Image",
- FontWeight = FontWeights.SemiBold
- });
-
- parentGrid.AddElementToGridRowColumn(imageCropper);
- parentGrid.AddElementToGridRowColumn(loadingMsgPanel);
-
- StorageFile file = await StorageFile.GetFileFromPathAsync(filePath);
- await imageCropper!.LoadImageFromFile(file!);
-
- GC.WaitForPendingFinalizers();
- GC.WaitForFullGCComplete();
-
- Storyboard storyboardAnim = new();
- DoubleAnimation loadingMsgPanelAnim = loadingMsgPanel.CreateDoubleAnimation("Opacity", 0, 1, null,
- TimeSpan.FromMilliseconds(500), EasingType.Cubic.ToEasingFunction());
- DoubleAnimation imageCropperAnim = imageCropper.CreateDoubleAnimation("Opacity", 1, 0, null,
- TimeSpan.FromMilliseconds(500), EasingType.Cubic.ToEasingFunction());
- storyboardAnim.Children!.Add(loadingMsgPanelAnim);
- storyboardAnim.Children!.Add(imageCropperAnim);
- storyboardAnim.Begin();
-
- dialogOverlay!.IsPrimaryButtonEnabled = true;
- }
- catch
- {
- // ignored
- }
- }
-
private static async Task GenerateCachedStream(FileInfo inputFileInfo,
uint toWidth, uint toHeight,
bool isFromCropProcess = false)
{
if (isFromCropProcess)
{
- string inputFileName = inputFileInfo!.FullName;
+ string inputFileName = inputFileInfo.FullName;
try
{
inputFileInfo.MoveTo(inputFileInfo.FullName + "_old", true);
- FileInfo newCachedFileInfo = new FileInfo(inputFileName);
+ FileInfo newCachedFileInfo = new(inputFileName);
await using (FileStream newCachedFileStream = newCachedFileInfo.Create())
await using (FileStream oldInputFileStream = inputFileInfo.Open(FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.ReadWrite))
await ResizeImageStream(oldInputFileStream, newCachedFileStream, toWidth, toHeight);
diff --git a/CollapseLauncher/Classes/Helper/WindowUtility.cs b/CollapseLauncher/Classes/Helper/WindowUtility.cs
index 77b43dc04..4caefb197 100644
--- a/CollapseLauncher/Classes/Helper/WindowUtility.cs
+++ b/CollapseLauncher/Classes/Helper/WindowUtility.cs
@@ -1,5 +1,6 @@
#nullable enable
using CollapseLauncher.Extension;
+using CollapseLauncher.GameManagement.ImageBackground;
using H.NotifyIcon.Core;
using Hi3Helper;
using Hi3Helper.SentryHelper;
@@ -468,10 +469,10 @@ private static IntPtr MainWndProc(IntPtr hwnd, uint msg, UIntPtr wParam, IntPtr
switch (wParam)
{
case 1 when lParam == 0:
- MainPage.CurrentBackgroundHandler?.WindowFocused();
+ ImageBackgroundManager.Shared.SetWindowFocusedEvent();
break;
case 0 when lParam == 0:
- MainPage.CurrentBackgroundHandler?.WindowUnfocused();
+ ImageBackgroundManager.Shared.SetWindowUnfocusedEvent();
break;
}
@@ -510,14 +511,14 @@ private static IntPtr MainWndProc(IntPtr hwnd, uint msg, UIntPtr wParam, IntPtr
}
case SC_MINIMIZE:
{
- MainPage.CurrentBackgroundHandler?.WindowUnfocused();
- InnerLauncherConfig.m_homePage?.CarouselStopScroll();
+ ImageBackgroundManager.Shared.SetWindowUnfocusedEvent();
+ InnerLauncherConfig.m_homePage?.StopCarouselSlideshow();
break;
}
case SC_RESTORE:
{
- MainPage.CurrentBackgroundHandler?.WindowFocused();
- InnerLauncherConfig.m_homePage?.CarouselRestartScroll();
+ ImageBackgroundManager.Shared.SetWindowFocusedEvent();
+ InnerLauncherConfig.m_homePage?.StartCarouselSlideshow();
break;
}
}
@@ -528,12 +529,12 @@ private static IntPtr MainWndProc(IntPtr hwnd, uint msg, UIntPtr wParam, IntPtr
{
if (wParam == 0)
{
- InnerLauncherConfig.m_homePage?.CarouselStopScroll();
+ InnerLauncherConfig.m_homePage?.StopCarouselSlideshow();
}
else
{
- MainPage.CurrentBackgroundHandler?.WindowFocused();
- InnerLauncherConfig.m_homePage?.CarouselRestartScroll();
+ ImageBackgroundManager.Shared.SetWindowFocusedEvent();
+ InnerLauncherConfig.m_homePage?.StartCarouselSlideshow();
}
break;
}
diff --git a/CollapseLauncher/Classes/InstallManagement/Base/InstallManagerBase.cs b/CollapseLauncher/Classes/InstallManagement/Base/InstallManagerBase.cs
index e8a960e7c..dbb4ede96 100644
--- a/CollapseLauncher/Classes/InstallManagement/Base/InstallManagerBase.cs
+++ b/CollapseLauncher/Classes/InstallManagement/Base/InstallManagerBase.cs
@@ -128,7 +128,7 @@ internal partial class InstallManagerBase : ProgressBase, IG
protected List _gameDeltaPatchPreReqList { get; } = [];
protected bool _forceIgnoreDeltaPatch;
- protected GameInstallFileInfo? _gameInstallFileInfo { get; set; }
+ protected GameInstallFileInfo _gameInstallFileInfo { get; set; }
#endregion
#region Public Properties
diff --git a/CollapseLauncher/Classes/Interfaces/Class/NotifyPropertyChanged.cs b/CollapseLauncher/Classes/Interfaces/Class/NotifyPropertyChanged.cs
index 6b1001701..f5411bf01 100644
--- a/CollapseLauncher/Classes/Interfaces/Class/NotifyPropertyChanged.cs
+++ b/CollapseLauncher/Classes/Interfaces/Class/NotifyPropertyChanged.cs
@@ -1,5 +1,4 @@
-using System;
-using System.ComponentModel;
+using System.ComponentModel;
using System.Runtime.CompilerServices;
// ReSharper disable CheckNamespace
#pragma warning disable IDE0130
diff --git a/CollapseLauncher/Classes/RegionManagement/RegionManagement.cs b/CollapseLauncher/Classes/RegionManagement/RegionManagement.cs
index a408dce14..67de79a42 100644
--- a/CollapseLauncher/Classes/RegionManagement/RegionManagement.cs
+++ b/CollapseLauncher/Classes/RegionManagement/RegionManagement.cs
@@ -1,5 +1,6 @@
using CollapseLauncher.Dialogs;
using CollapseLauncher.Extension;
+using CollapseLauncher.GameManagement.ImageBackground;
using CollapseLauncher.Helper.Background;
using CollapseLauncher.Helper.Image;
using CollapseLauncher.Helper.Loading;
@@ -33,14 +34,17 @@ namespace CollapseLauncher
[SuppressMessage("ReSharper", "AssignNullToNotNullAttribute")]
public sealed partial class MainPage
{
- // ReSharper disable once UnusedAutoPropertyAccessor.Local
private GamePresetProperty CurrentGameProperty { get; set; }
private bool IsLoadRegionComplete;
- private static string RegionToChangeName { get => $"{GetGameTitleRegionTranslationString(LauncherMetadataHelper.CurrentMetadataConfigGameName, Lang._GameClientTitles)} - {GetGameTitleRegionTranslationString(LauncherMetadataHelper.CurrentMetadataConfigGameRegion, Lang._GameClientRegions)}"; }
- private List