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 LastMenuNavigationItem; - private List LastFooterNavigationItem; - internal static string PreviousTag = string.Empty; + private static string RegionToChangeName + { + get => $"{GetGameTitleRegionTranslationString(LauncherMetadataHelper.CurrentMetadataConfigGameName, Lang._GameClientTitles)} - {GetGameTitleRegionTranslationString(LauncherMetadataHelper.CurrentMetadataConfigGameRegion, Lang._GameClientRegions)}"; + } + + private List LastMenuNavigationItem; + private List LastFooterNavigationItem; + internal static string PreviousTag = string.Empty; private readonly Dictionary<(string, string), bool> RegionLoadingStatus = new(); @@ -53,7 +57,7 @@ private async Task LoadRegionFromCurrentConfigV2(PresetConfig preset, stri } RegionLoadingStatus.Add((gameName, gameRegion), false); - CancellationTokenSourceWrapper tokenSource = new CancellationTokenSourceWrapper(); + CancellationTokenSourceWrapper tokenSource = new(); string regionToChangeName = $"{preset.GameLauncherApi.GameNameTranslation} - {preset.GameLauncherApi.GameRegionTranslation}"; bool runResult = await preset.GameLauncherApi @@ -97,8 +101,7 @@ void CancelLoadEvent(object sender, RoutedEventArgs args) NavigationViewControl.IsSettingsVisible = true; LastMenuNavigationItem.Clear(); LastFooterNavigationItem.Clear(); - if (m_arguments.StartGame != null) - m_arguments.StartGame.Play = false; + m_arguments.StartGame?.Play = false; ChangeRegionConfirmProgressBar.Visibility = Visibility.Collapsed; ChangeRegionConfirmBtn.IsEnabled = true; @@ -117,16 +120,19 @@ async ValueTask AfterLoadRoutine(CancellationToken token) _ = SentryHelper.ExceptionHandlerAsync(new Exception("Double region loading detected!")); return; } - + LogWriteLine($"Game: {regionToChangeName} has been completely initialized!", LogType.Scheme, true); await FinalizeLoadRegion(gameName, gameRegion, token); - _ = ChangeBackgroundImageAsRegionAsync(); Interlocked.Exchange(ref IsLoadRegionComplete, true); LoadingMessageHelper.HideActionButton(); LoadingMessageHelper.HideLoadingFrame(); KeyboardShortcuts.CannotUseKbShortcuts = false; // Re-enable keyboard shortcuts after loading region + ImageBackgroundManager.Shared.Initialize(preset, + preset.GameLauncherApi.LauncherGameBackground, + presenterGrid: BackgroundPresenterGrid, + token: token); } catch (Exception ex) { @@ -183,86 +189,6 @@ private void ClearMainPageState() }); } - private async Task DownloadBackgroundImage(CancellationToken token) - { - var currentProperty = GamePropertyVault.GetCurrentGameProperty(); - // Get and set the current path of the image - string backgroundFolder = Path.Combine(AppGameImgFolder, "bg"); - string backgroundFileName = Path.GetFileName(LauncherMetadataHelper.CurrentMetadataConfig.GameLauncherApi.GameBackgroundImg); - LauncherMetadataHelper.CurrentMetadataConfig.GameLauncherApi.GameBackgroundImgLocal = Path.Combine(backgroundFolder, backgroundFileName); - SetAndSaveConfigValue("CurrentBackground", LauncherMetadataHelper.CurrentMetadataConfig.GameLauncherApi.GameBackgroundImgLocal); - await DownloadNonPluginBackgroundImage(backgroundFolder, currentProperty, token); - } - - private async Task DownloadNonPluginBackgroundImage(string backgroundFolder, - GamePresetProperty currentProperty, - CancellationToken token) - { - // Check if the background folder exist - if (!Directory.Exists(backgroundFolder)) - Directory.CreateDirectory(backgroundFolder); - - var imgFileInfo = - new FileInfo(LauncherMetadataHelper.CurrentMetadataConfig.GameLauncherApi.GameBackgroundImgLocal); - - // Start downloading the background image - var isDownloaded = await ImageLoaderHelper.IsFileCompletelyDownloadedAsync(imgFileInfo, true); - if (isDownloaded) - { - BackgroundImgChanger.ChangeBackground(imgFileInfo.FullName, - this.ReloadPageTheme, - false, - false, - true); - return; - } - - #nullable enable - string? tempImage = null; - var lastBgCfg = "lastBg-" + LauncherMetadataHelper.CurrentMetadataConfig.GameLauncherApi.GameName + - "-" + LauncherMetadataHelper.CurrentMetadataConfig.GameLauncherApi.GameRegion; - - // Check if the last background image exist, then use that temporarily instead - var lastGameBackground = GetAppConfigValue(lastBgCfg).ToString(); - if (!string.IsNullOrEmpty(lastGameBackground)) - { - if (File.Exists(lastGameBackground)) - { - tempImage = lastGameBackground; - } - } - - // If the file is not downloaded, use template image first, then download the image - GameNameType? currentGameType = currentProperty.GameVersion?.GameType; - tempImage ??= currentGameType switch - { - GameNameType.Honkai => Path.Combine(AppExecutableDir, @"Assets\Images\GameBackground\honkai.webp"), - GameNameType.Genshin => Path.Combine(AppExecutableDir, @"Assets\Images\GameBackground\genshin.webp"), - GameNameType.StarRail => Path.Combine(AppExecutableDir, @"Assets\Images\GameBackground\starrail.webp"), - GameNameType.Zenless => Path.Combine(AppExecutableDir, @"Assets\Images\GameBackground\zzz.webp"), - _ => BackgroundMediaUtility.GetDefaultRegionBackgroundPath() - }; - BackgroundImgChanger.ChangeBackground(tempImage, - this.ReloadPageTheme, - false, - false, - true); - if (await ImageLoaderHelper.TryDownloadToCompletenessAsync(LauncherMetadataHelper.CurrentMetadataConfig.GameLauncherApi.GameBackgroundImg, - LauncherMetadataHelper.CurrentMetadataConfig.GameLauncherApi.ApiResourceHttpClient, - imgFileInfo, - false, - token)) - { - BackgroundImgChanger.ChangeBackground(imgFileInfo.FullName, - this.ReloadPageTheme, - false, - true, - true); - SetAndSaveConfigValue(lastBgCfg, imgFileInfo.FullName); - } - #nullable restore - } - private async Task FinalizeLoadRegion(string gameName, string gameRegion, CancellationToken token) { PresetConfig preset = LauncherMetadataHelper.LauncherMetadataConfig[gameName][gameRegion]; diff --git a/CollapseLauncher/CollapseLauncher.csproj b/CollapseLauncher/CollapseLauncher.csproj index b1b61b826..ffb46279b 100644 --- a/CollapseLauncher/CollapseLauncher.csproj +++ b/CollapseLauncher/CollapseLauncher.csproj @@ -293,6 +293,8 @@ + + diff --git a/CollapseLauncher/Program.cs b/CollapseLauncher/Program.cs index 5e3b354e7..ad16d1f42 100644 --- a/CollapseLauncher/Program.cs +++ b/CollapseLauncher/Program.cs @@ -118,8 +118,9 @@ public static void Main(params string[] args) Application.Start(pContext => { DispatcherQueue dispatcherQueue = DispatcherQueue.GetForCurrentThread(); + DispatcherQueueExtensions.CurrentDispatcherQueue = dispatcherQueue; - DispatcherQueueSynchronizationContext context = new DispatcherQueueSynchronizationContext(dispatcherQueue); + DispatcherQueueSynchronizationContext context = new(dispatcherQueue); SynchronizationContext.SetSynchronizationContext(context); // ReSharper disable once ObjectCreationAsStatement diff --git a/CollapseLauncher/XAMLs/MainApp/MainPage.Background.cs b/CollapseLauncher/XAMLs/MainApp/MainPage.Background.cs deleted file mode 100644 index e0f8e7218..000000000 --- a/CollapseLauncher/XAMLs/MainApp/MainPage.Background.cs +++ /dev/null @@ -1,229 +0,0 @@ -using CollapseLauncher.Extension; -using CollapseLauncher.Helper.Background; -using CollapseLauncher.Helper.Image; -using CollapseLauncher.Helper.Metadata; -using Hi3Helper; -using Microsoft.UI.Xaml; -using Microsoft.UI.Xaml.Controls; -using System; -using System.Collections.Generic; -using System.IO; -using System.Threading; -using System.Threading.Tasks; -using static CollapseLauncher.InnerLauncherConfig; -using static CollapseLauncher.Statics.GamePropertyVault; -using static Hi3Helper.Logger; -using static Hi3Helper.Shared.Region.LauncherConfig; - -// ReSharper disable CheckNamespace -// ReSharper disable RedundantExtendsListEntry -// ReSharper disable InconsistentNaming -// ReSharper disable UnusedMember.Local -// ReSharper disable SwitchStatementHandlesSomeKnownEnumValuesWithDefault -// ReSharper disable IdentifierTypo -// ReSharper disable AsyncVoidMethod -// ReSharper disable StringLiteralTypo -// ReSharper disable CommentTypo -// ReSharper disable SwitchStatementMissingSomeEnumCasesNoDefault - -namespace CollapseLauncher; - -public partial class MainPage : Page -{ - private static void BackgroundImg_IsImageHideEvent(object sender, bool e) - { - if (e) CurrentBackgroundHandler?.Dimm(); - else CurrentBackgroundHandler?.Undimm(); - } - - private readonly HashSet _processingBackground = []; - private async void CustomBackgroundChanger_Event(object sender, BackgroundImgProperty e) - { - if (_processingBackground.Contains(e.ImgPath)) - { - LogWriteLine($"Background {e.ImgPath} is already being processed!", LogType.Warning, true); - return; - } - - try - { - _processingBackground.Add(e.ImgPath); - var gameLauncherApi = LauncherMetadataHelper.CurrentMetadataConfig?.GameLauncherApi; - if (gameLauncherApi == null) - { - return; - } - - gameLauncherApi.GameBackgroundImgLocal = e.ImgPath; - IsCustomBG = e.IsCustom; - - if (!File.Exists(gameLauncherApi.GameBackgroundImgLocal)) - { - if (IsCustomBG) - { - var customBGPath = e.ImgPath; - var warningMsgTag = ""; - - if (string.IsNullOrWhiteSpace(customBGPath)) - { - // Check if using regional custom BG - if (GetCurrentGameProperty().GameSettings?.SettingsCollapseMisc.UseCustomRegionBG ?? false) - { - customBGPath = GetCurrentGameProperty().GameSettings?.SettingsCollapseMisc?.CustomRegionBGPath; - warningMsgTag = Locale.Lang._UnhandledExceptionPage.CustomBackground_RegionalTag; - } - // check if using global custom BG - else - { - customBGPath = GetAppConfigValue("CustomBGPath").ToString(); - warningMsgTag = Locale.Lang._UnhandledExceptionPage.CustomBackground_GlobalTag; - } - } - - var missingImageEx = - new FileNotFoundException($"[{warningMsgTag}] {Locale.Lang._UnhandledExceptionPage.CustomBackground_NotFound}", - customBGPath); - DispatcherQueue.TryEnqueue(() => - { - try - { - ErrorSender.SendWarning(missingImageEx); - } - catch - { - // ignored - } - }); - } - - LogWriteLine($"Custom background file {e.ImgPath} is missing!", LogType.Warning, true); - gameLauncherApi.GameBackgroundImgLocal = BackgroundMediaUtility.GetDefaultRegionBackgroundPath(); - } - - var mType = BackgroundMediaUtility.GetMediaType(gameLauncherApi.GameBackgroundImgLocal); - switch (mType) - { - case BackgroundMediaUtility.MediaType.Media: - BackgroundNewMediaPlayerGrid.Visibility = Visibility.Visible; - BackgroundNewBackGrid.Visibility = Visibility.Collapsed; - break; - case BackgroundMediaUtility.MediaType.StillImage: - var imgStream = await ImageLoaderHelper.LoadImage(gameLauncherApi.GameBackgroundImgLocal); - BackgroundMediaUtility.SetAlternativeImageStream(imgStream); - BackgroundNewMediaPlayerGrid.Visibility = Visibility.Collapsed; - BackgroundNewBackGrid.Visibility = Visibility.Visible; - break; - case BackgroundMediaUtility.MediaType.Unknown: - default: - throw new InvalidCastException(); - } - - await InitBackgroundHandler(); - CurrentBackgroundHandler?.LoadBackground(gameLauncherApi.GameBackgroundImgLocal, e.IsRequestInit, - e.IsForceRecreateCache, ex => - { - gameLauncherApi.GameBackgroundImgLocal = - BackgroundMediaUtility.GetDefaultRegionBackgroundPath(); - LogWriteLine($"An error occured while loading background {e.ImgPath}\r\n{ex}", - LogType.Error, true); - ErrorSender.SendException(ex); - }, e.ActionAfterLoaded); - } - catch (Exception ex) - { - LogWriteLine($"An error occured while loading background {e.ImgPath}\r\n{ex}", - LogType.Error, true); - ErrorSender.SendException(new Exception($"An error occured while loading background {e.ImgPath}", ex)); - } - finally - { - _processingBackground.Remove(e.ImgPath); - } - } - - internal async Task ChangeBackgroundImageAsRegionAsync(bool ShowLoadingMsg = false) - { - var gameLauncherApi = LauncherMetadataHelper.CurrentMetadataConfig?.GameLauncherApi; - if (gameLauncherApi == null) - { - return; - } - - GamePresetProperty currentGameProperty = GetCurrentGameProperty(); - bool isUseCustomPerRegionBg = currentGameProperty.GameSettings?.SettingsCollapseMisc?.UseCustomRegionBG ?? false; - - IsCustomBG = GetAppConfigValue("UseCustomBG").ToBool(); - bool isAPIBackgroundAvailable = - !string.IsNullOrEmpty(gameLauncherApi.GameBackgroundImg); - - var posterBg = currentGameProperty.GameVersion?.GameType switch - { - GameNameType.Honkai => Path.Combine(AppExecutableDir, - @"Assets\Images\GameBackground\honkai.webp"), - GameNameType.Genshin => Path.Combine(AppExecutableDir, - @"Assets\Images\GameBackground\genshin.webp"), - GameNameType.StarRail => Path.Combine(AppExecutableDir, - @"Assets\Images\GameBackground\starrail.webp"), - GameNameType.Zenless => Path.Combine(AppExecutableDir, - @"Assets\Images\GameBackground\zzz.webp"), - _ => BackgroundMediaUtility.GetDefaultRegionBackgroundPath() - }; - - // Check if Regional Custom BG is enabled and available - if (isUseCustomPerRegionBg) - { - var regionBgPath = currentGameProperty.GameSettings?.SettingsCollapseMisc?.CustomRegionBGPath; - if (!string.IsNullOrEmpty(regionBgPath) && File.Exists(regionBgPath)) - { - if (BackgroundMediaUtility.GetMediaType(regionBgPath) == BackgroundMediaUtility.MediaType.StillImage) - { - var imgStream = await ImageLoaderHelper.LoadImage(regionBgPath); - BackgroundMediaUtility.SetAlternativeImageStream(imgStream); - } - - gameLauncherApi.GameBackgroundImgLocal = regionBgPath; - } - } - // If not, then check for global Custom BG - else - { - var BGPath = IsCustomBG ? GetAppConfigValue("CustomBGPath").ToString() : null; - if (!string.IsNullOrEmpty(BGPath)) - { - gameLauncherApi.GameBackgroundImgLocal = BGPath; - } - // If it's still not, then check if API gives any background. Anyway, also ignore if the provided API is the plugin ones. - // ReSharper disable once ConvertIfStatementToSwitchStatement - else if (!gameLauncherApi.IsPlugin && isAPIBackgroundAvailable) - { - try - { - await DownloadBackgroundImage(CancellationToken.None); - return; // Return after successfully loading - } - catch (Exception ex) - { - ErrorSender.SendException(ex); - LogWriteLine($"Failed while downloading default background image!\r\n{ex}", LogType.Error, true); - gameLauncherApi.GameBackgroundImgLocal = BackgroundMediaUtility.GetDefaultRegionBackgroundPath(); - } - } - // IF ITS STILL NOT THERE, then use fallback game poster, IF ITS STILL NOT THEREEEE!! use paimon cute deadge pic :) - else if (!gameLauncherApi.IsPlugin) - { - gameLauncherApi.GameBackgroundImgLocal = posterBg; - } - } - - // Use default background if the API background is empty (in-case HoYo did something catchy) - if (!isAPIBackgroundAvailable && !IsCustomBG && LauncherMetadataHelper.CurrentMetadataConfig is { GameLauncherApi: not null }) - gameLauncherApi.GameBackgroundImgLocal ??= posterBg; - - // If the custom per region is enabled, then execute below - BackgroundImgChanger.ChangeBackground(gameLauncherApi.GameBackgroundImgLocal, - this.ReloadPageTheme, - IsCustomBG || isUseCustomPerRegionBg, - true, - true); - } -} diff --git a/CollapseLauncher/XAMLs/MainApp/MainPage.xaml b/CollapseLauncher/XAMLs/MainApp/MainPage.xaml index 06951e6a0..fb6afbdda 100644 --- a/CollapseLauncher/XAMLs/MainApp/MainPage.xaml +++ b/CollapseLauncher/XAMLs/MainApp/MainPage.xaml @@ -7,6 +7,7 @@ xmlns:extension="using:CollapseLauncher.Extension" xmlns:helper="using:Hi3Helper" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:layeredBackgroundImage="using:CollapseLauncher.XAMLs.Theme.CustomControls.LayeredBackgroundImage" Unloaded="Page_Unloaded" mc:Ignorable="d"> @@ -17,16 +18,13 @@ - - - + + + Background="{ThemeResource BackgroundImageMaskAcrylicBrush}" + Opacity="0" /> PreviousTagString = []; -#nullable enable - internal static BackgroundMediaUtility? CurrentBackgroundHandler; - private BackgroundMediaUtility? _localBackgroundHandler; -#nullable restore + internal Uri PlaceholderBackgroundImage => new(ImageBackgroundManager.GetPlaceholderBackgroundImageFrom(CurrentGameProperty?.GamePreset, true)); + #endregion #region Main Routine @@ -86,6 +85,8 @@ public MainPage() WebView2Frame.Navigate(typeof(BlankPage)); Loaded += StartRoutine; + ImageBackgroundManager.Shared.GlobalParallaxHoverSource = MainPageGrid; + // Enable implicit animation on certain elements AnimationHelper.EnableImplicitAnimation(true, null, GridBG_RegionGrid, GridBG_NotifBtn, NotificationPanelClearAllGrid); } @@ -103,10 +104,6 @@ private void Page_Unloaded(object sender, RoutedEventArgs e) AppDiscordPresence?.Dispose(); #endif ImageLoaderHelper.DestroyWaifu2X(); - _localBackgroundHandler?.Dispose(); - CurrentBackgroundHandler = null; - _localBackgroundHandler = null; - Interlocked.Exchange(ref m_mainPage, null); } @@ -160,9 +157,6 @@ private async void StartRoutine(object sender, RoutedEventArgs e) private async Task InitializeStartup() { - // Initialize the background image utility - await InitBackgroundHandler(); - Type Page = typeof(HomePage); bool isCacheUpdaterMode = m_appMode == AppMode.Hi3CacheUpdater; @@ -203,12 +197,6 @@ private async Task InitializeStartup() // invoking notifications _ = RunBackgroundCheck(); } - - private async Task InitBackgroundHandler() - { - CurrentBackgroundHandler ??= await BackgroundMediaUtility.CreateInstanceAsync(this, BackgroundAcrylicMask, BackgroundOverlayTitleBar, BackgroundNewBackGrid, BackgroundNewMediaPlayerGrid); - _localBackgroundHandler = CurrentBackgroundHandler; - } #endregion #region Invokers @@ -241,7 +229,6 @@ private void UpdateBindingsEvent(object sender, EventArgs e) private static void ShowLoadingPageInvoker_PageEvent(object sender, ShowLoadingPageProperty e) { - BackgroundImgChanger.ToggleBackground(e.Hide); InvokeLoadingRegionPopup(!e.Hide, e.Title, e.Subtitle); } @@ -418,8 +405,6 @@ private void SubscribeEvents() MainFrameChangerInvoker.FrameEvent += MainFrameChangerInvoker_FrameEvent; MainFrameChangerInvoker.FrameGoBackEvent += MainFrameChangerInvoker_FrameGoBackEvent; NotificationInvoker.EventInvoker += NotificationInvoker_EventInvoker; - BackgroundImgChangerInvoker.ImgEvent += CustomBackgroundChanger_Event; - BackgroundImgChangerInvoker.IsImageHide += BackgroundImg_IsImageHideEvent; SpawnWebView2Invoker.SpawnEvent += SpawnWebView2Invoker_SpawnEvent; ShowLoadingPageInvoker.PageEvent += ShowLoadingPageInvoker_PageEvent; SettingsPage.KeyboardShortcutsEvent += SettingsPage_KeyboardShortcutsEvent; @@ -434,8 +419,6 @@ private void UnsubscribeEvents() ErrorSenderInvoker.ExceptionEvent -= ErrorSenderInvoker_ExceptionEvent; MainFrameChangerInvoker.FrameEvent -= MainFrameChangerInvoker_FrameEvent; NotificationInvoker.EventInvoker -= NotificationInvoker_EventInvoker; - BackgroundImgChangerInvoker.ImgEvent -= CustomBackgroundChanger_Event; - BackgroundImgChangerInvoker.IsImageHide -= BackgroundImg_IsImageHideEvent; SpawnWebView2Invoker.SpawnEvent -= SpawnWebView2Invoker_SpawnEvent; ShowLoadingPageInvoker.PageEvent -= ShowLoadingPageInvoker_PageEvent; SettingsPage.KeyboardShortcutsEvent -= SettingsPage_KeyboardShortcutsEvent; diff --git a/CollapseLauncher/XAMLs/MainApp/Pages/CachesPage.xaml b/CollapseLauncher/XAMLs/MainApp/Pages/CachesPage.xaml index 06a6761b4..fd02eeac0 100644 --- a/CollapseLauncher/XAMLs/MainApp/Pages/CachesPage.xaml +++ b/CollapseLauncher/XAMLs/MainApp/Pages/CachesPage.xaml @@ -5,6 +5,7 @@ xmlns:control="using:CommunityToolkit.WinUI.Controls" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:datatable="using:CommunityToolkit.WinUI.Controls.Labs.DataTable" + xmlns:extension="using:CollapseLauncher.Extension" xmlns:helper="using:Hi3Helper" xmlns:interactivity="using:Microsoft.Xaml.Interactivity" xmlns:local="using:CollapseLauncher" @@ -169,6 +170,7 @@ + + + + + + + + + + + + + + + - - + + + + + - - + + + + + + + + + + + + + + + + + @@ -1029,8 +1327,8 @@ - + - + @@ -2479,8 +2764,8 @@ Grid.Row="0" Grid.Column="0" HorizontalAlignment="Stretch" + extension:UIElementExtensions.CursorType="Hand" CornerRadius="{x:Bind extension:UIElementExtensions.AttachRoundedKindCornerRadius(LauncherBtn)}" - Loaded="SetHandCursor" PointerEntered="ElementScaleOutHoveredPointerEntered" PointerExited="ElementScaleInHoveredPointerExited" Shadow="{ThemeResource SharedShadow}" @@ -2511,7 +2796,7 @@ CornerRadius="{x:Bind extension:UIElementExtensions.AttachRoundedKindCornerRadius(InstallGameBtnAnimBackground)}"> - + @@ -2521,9 +2806,9 @@ HorizontalAlignment="Stretch" VerticalAlignment="Stretch" HorizontalContentAlignment="Stretch" + extension:UIElementExtensions.CursorType="Hand" Click="UpdateGameDialog" CornerRadius="{x:Bind extension:UIElementExtensions.AttachRoundedKindCornerRadius(UpdateGameBtn)}" - Loaded="SetHandCursor" Style="{ThemeResource NewAccentButtonStyle}" Visibility="Collapsed"> @@ -2546,7 +2831,7 @@ CornerRadius="{x:Bind extension:UIElementExtensions.AttachRoundedKindCornerRadius(UpdateGameBtnAnimBackground)}"> - + @@ -2556,9 +2841,9 @@ HorizontalAlignment="Stretch" VerticalAlignment="Stretch" HorizontalContentAlignment="Stretch" + extension:UIElementExtensions.CursorType="Hand" Click="StartGame" CornerRadius="{x:Bind extension:UIElementExtensions.AttachRoundedKindCornerRadius(StartGameBtn)}" - Loaded="SetHandCursor" Style="{ThemeResource NewAccentButtonStyle}" Visibility="Collapsed"> @@ -2580,7 +2865,7 @@ CornerRadius="{x:Bind extension:UIElementExtensions.AttachRoundedKindCornerRadius(StartGameBtnAnimBackground)}"> - + @@ -2622,8 +2907,8 @@ HorizontalAlignment="Stretch" VerticalAlignment="Stretch" HorizontalContentAlignment="Stretch" + extension:UIElementExtensions.CursorType="Hand" CornerRadius="{x:Bind extension:UIElementExtensions.AttachRoundedKindCornerRadius(ResumeDownloadBtn)}" - Loaded="SetHandCursor" Style="{ThemeResource NewAccentButtonStyle}" Visibility="Collapsed"> diff --git a/CollapseLauncher/XAMLs/MainApp/Pages/HomePage.xaml.cs b/CollapseLauncher/XAMLs/MainApp/Pages/HomePage.xaml.cs index 44054d716..0e7ec3caa 100644 --- a/CollapseLauncher/XAMLs/MainApp/Pages/HomePage.xaml.cs +++ b/CollapseLauncher/XAMLs/MainApp/Pages/HomePage.xaml.cs @@ -1,5 +1,5 @@ -using CollapseLauncher.DiscordPresence; using CollapseLauncher.CustomControls; +using CollapseLauncher.DiscordPresence; using CollapseLauncher.Extension; using CollapseLauncher.Helper; using CollapseLauncher.Helper.Animation; @@ -34,7 +34,6 @@ using System.Numerics; using System.Threading.Tasks; using WinRT; - using static CollapseLauncher.Dialogs.SimpleDialogs; using static CollapseLauncher.InnerLauncherConfig; using static Hi3Helper.Data.ConverterTool; @@ -74,7 +73,6 @@ public sealed partial class HomePage #region Properties private GamePresetProperty CurrentGameProperty { get; set; } private CancellationTokenSourceWrapper PageToken { get; set; } - private CancellationTokenSourceWrapper CarouselToken { get; set; } private int barWidth; private int consoleWidth; @@ -158,12 +156,9 @@ private async void StartLoadedRoutine(object sender, RoutedEventArgs e) // But first, let it initialize its properties. CurrentGameProperty = GamePropertyVault.GetCurrentGameProperty(); PageToken = new CancellationTokenSourceWrapper(); - CarouselToken = new CancellationTokenSourceWrapper(); InitializeComponent(); - BackgroundImgChanger.ToggleBackground(false); - await GetCurrentGameState(); if (!GetAppConfigValue("ShowSocialMediaPanel").ToBool()) @@ -188,8 +183,8 @@ private async void StartLoadedRoutine(object sender, RoutedEventArgs e) if (IsCarouselPanelAvailable || IsNewsPanelAvailable) { - ImageCarousel.SelectedIndex = 0; - ImageCarousel.Visibility = Visibility.Visible; + // ImageCarousel.SelectedIndex = 0; + // ImageCarousel.Visibility = Visibility.Visible; ImageCarouselPipsPager.Visibility = Visibility.Visible; ShowEventsPanelToggle.IsEnabled = true; @@ -217,8 +212,6 @@ private async void StartLoadedRoutine(object sender, RoutedEventArgs e) UpdatePlaytime(null, CurrentGameProperty.GamePlaytime.CollapsePlaytime); } - _ = StartCarouselAutoScroll(); - #if !DISABLEDISCORD AppDiscordPresence?.SetActivity(ActivityType.Idle); #endif @@ -343,7 +336,6 @@ private void Page_Unloaded(object sender, RoutedEventArgs e) } if (!PageToken.IsDisposed && !PageToken.IsCancelled) PageToken.Cancel(); - if (!CarouselToken.IsDisposed && !CarouselToken.IsCancelled) CarouselToken.Cancel(); } #endregion @@ -438,85 +430,17 @@ await ImageLoaderHelper.GetConvertedImageAsPng(outStream, #endregion #region Carousel - private bool _isCarouselInitialized = false; - - private async Task StartCarouselAutoScroll(int delaySeconds = 5) - { - if (!IsCarouselPanelAvailable) return; - if (delaySeconds < 5) delaySeconds = 5; - - try - { - CarouselToken ??= new CancellationTokenSourceWrapper(); - while (true) - { - await Task.Delay(TimeSpan.FromSeconds(delaySeconds), CarouselToken.Token); - _isCarouselInitialized = true; - if (!IsCarouselPanelAvailable) return; - if (ImageCarousel.SelectedIndex != GameCarouselData?.Count - 1 - && ImageCarousel.SelectedIndex < ImageCarousel.Items.Count - 1) - ImageCarousel.SelectedIndex++; - else - for (int i = GameCarouselData?.Count ?? 0; i > 0; i--) - { - while (!WindowUtility.IsCurrentWindowInFocus()) - { - await Task.Delay(RefreshRate, CarouselToken.Token); - } - if (i - 1 >= 0 && i - 1 < ImageCarousel.Items.Count) - { - ImageCarousel.SelectedIndex = i - 1; - } - if (CarouselToken is { IsDisposed: false, IsCancellationRequested: false }) - { - await Task.Delay(100, CarouselToken.Token); - } - else break; - } - break; - } - } - catch (TaskCanceledException) - { - // Ignore - } - catch (Exception ex) - { - LogWriteLine($"[HomePage::StartCarouselAutoScroll] Task returns error!\r\n{ex}", LogType.Error, true); - _ = CarouselRestartScroll(); - } - } - - private async void CarouselPointerExited(object sender = null, PointerRoutedEventArgs e = null) => - await CarouselRestartScroll(); + public void StartCarouselSlideshow() + => ImageCarouselEventSlideshow.ResumeSlideshow(); - private async void CarouselPointerEntered(object sender = null, PointerRoutedEventArgs e = null) => - await CarouselStopScroll(); + public void StopCarouselSlideshow() + => ImageCarouselEventSlideshow.PauseSlideshow(); - public async Task CarouselRestartScroll(int delaySeconds = 5) - { - // Don't restart carousel if game is running and LoPrio is on - if (_cachedIsGameRunning && GetAppConfigValue("LowerCollapsePrioOnGameLaunch").ToBool()) return; - await CarouselStopScroll(); + private void CarouselPointerExited(object sender = null, PointerRoutedEventArgs e = null) + => ImageCarouselPipsPager.Opacity = .5d; - CarouselToken = new CancellationTokenSourceWrapper(); - _ = StartCarouselAutoScroll(delaySeconds); - } - - public async ValueTask CarouselStopScroll() - { - // Wait until Carousel is fully initialized to invoke the cts cancellation - while (!_isCarouselInitialized) - { - await Task.Delay(500); - } - - if (CarouselToken is { IsCancellationRequested: false, IsDisposed: false, IsCancelled: false }) - { - await CarouselToken.CancelAsync(); - CarouselToken.Dispose(); - } - } + private void CarouselPointerEntered(object sender = null, PointerRoutedEventArgs e = null) + => ImageCarouselPipsPager.Opacity = 1; private async void HideImageCarousel(bool hide) { @@ -1039,11 +963,6 @@ private async void CleanupFilesButton_Click(object sender, RoutedEventArgs e) } #endregion - #region Set Hand Cursor - private void SetHandCursor(object sender, RoutedEventArgs e = null) => - (sender as UIElement)?.SetCursor(InputSystemCursor.Create(InputSystemCursorShape.Hand)); - #endregion - #region Hyper Link Color private void HyperLink_OnPointerEntered(object sender, PointerRoutedEventArgs e) { @@ -1133,7 +1052,6 @@ private async void SidePanelScaleOutHoveredPointerEntered(object sender, Pointer Storyboard.SetTargetProperty(scaleYAnim, "ScaleY"); storyboard.Children.Add(scaleYAnim); - MainPage.CurrentBackgroundHandler?.Dimm(); HideImageEventImg(true); IsSidePanelCurrentlyScaledOut = true; @@ -1152,7 +1070,6 @@ private async void SidePanelScaleInHoveredPointerExited(object sender, PointerRo if (!IsSidePanelCurrentlyScaledOut) return; - MainPage.CurrentBackgroundHandler?.Undimm(); HideImageEventImg(false); var storyboard = new Storyboard(); @@ -1252,7 +1169,7 @@ await element.StartAnimation(TimeSpan.FromSeconds(0.25), compositor.CreateVector3KeyFrameAnimation("Scale", new Vector3(1.0f)) ); } - + #endregion } } diff --git a/CollapseLauncher/XAMLs/MainApp/Pages/NotInstalledPage.xaml.cs b/CollapseLauncher/XAMLs/MainApp/Pages/NotInstalledPage.xaml.cs index e1c1fe7fa..d25b57795 100644 --- a/CollapseLauncher/XAMLs/MainApp/Pages/NotInstalledPage.xaml.cs +++ b/CollapseLauncher/XAMLs/MainApp/Pages/NotInstalledPage.xaml.cs @@ -4,7 +4,6 @@ public sealed partial class NotInstalledPage { public NotInstalledPage() { - BackgroundImgChanger.ToggleBackground(true); InitializeComponent(); } } diff --git a/CollapseLauncher/XAMLs/MainApp/Pages/OOBE/OOBESelectGameBG.xaml.cs b/CollapseLauncher/XAMLs/MainApp/Pages/OOBE/OOBESelectGameBG.xaml.cs index f1b8ed1a5..81b3af70e 100644 --- a/CollapseLauncher/XAMLs/MainApp/Pages/OOBE/OOBESelectGameBG.xaml.cs +++ b/CollapseLauncher/XAMLs/MainApp/Pages/OOBE/OOBESelectGameBG.xaml.cs @@ -1,4 +1,4 @@ -using CollapseLauncher.Helper.Background; +using CollapseLauncher.GameManagement.ImageBackground; using CollapseLauncher.Helper.Image; using CollapseLauncher.Helper.Metadata; using CollapseLauncher.Plugins; @@ -110,9 +110,7 @@ internal static async Task TryLoadGameDetails(PresetConfig config = null) GameNameType.Genshin => Path.Combine(AppExecutableDir, @"Assets\Images\GamePoster\poster_genshin.png"), GameNameType.StarRail => Path.Combine(AppExecutableDir, @"Assets\Images\GamePoster\poster_starrail.png"), GameNameType.Zenless => Path.Combine(AppExecutableDir, @"Assets\Images\GamePoster\poster_zzz.png"), - _ => isPlugin ? - await ImageLoaderHelper.GetCachedSpritesAsync(FallbackCDNUtil.TryGetAbsoluteToRelativeCDNURL(config.ZonePosterURL, "metadata/"), true, CancellationToken.None) : - BackgroundMediaUtility.GetDefaultRegionBackgroundPath() + _ => ImageBackgroundManager.GetRandomPlaceholderImage() }; // TODO: Use FallbackCDNUtil to get the sprites diff --git a/CollapseLauncher/XAMLs/MainApp/Pages/OOBE/OOBEStartUpMenu.xaml.cs b/CollapseLauncher/XAMLs/MainApp/Pages/OOBE/OOBEStartUpMenu.xaml.cs index c5a0eed4a..270638a22 100644 --- a/CollapseLauncher/XAMLs/MainApp/Pages/OOBE/OOBEStartUpMenu.xaml.cs +++ b/CollapseLauncher/XAMLs/MainApp/Pages/OOBE/OOBEStartUpMenu.xaml.cs @@ -2,6 +2,7 @@ using CollapseLauncher.Dialogs; using CollapseLauncher.Extension; using CollapseLauncher.FileDialogCOM; +using CollapseLauncher.GameManagement.ImageBackground; using CollapseLauncher.Helper; using CollapseLauncher.Helper.Animation; using CollapseLauncher.Helper.Background; @@ -484,60 +485,18 @@ private async void CustomBackgroundCheckedOpen(object sender, RoutedEventArgs e) CheckBox? senderSource = sender as CheckBox; if (senderSource == null) return; - string selectedPath = await FileDialogNative.GetFilePicker(ImageLoaderHelper.SupportedImageFormats); - string fileExt = Path.GetExtension(selectedPath); - if (BackgroundMediaUtility.SupportedMediaPlayerExt.Contains(fileExt, StringComparer.OrdinalIgnoreCase)) - { - await SimpleDialogs.Dialog_OOBEVideoBackgroundPreviewUnavailable(); - - SetAppConfigValue("UseCustomBG", true); - SetAppConfigValue("CustomBGPath", selectedPath); - return; - } - - senderSource.IsEnabled = false; + string selectedPath = await FileDialogNative.GetFilePicker(ImageLoaderHelper.SupportedBackgroundFormats); if (string.IsNullOrEmpty(selectedPath)) { - senderSource.IsChecked = false; - await ReplaceBackgroundImage(Path.Combine(AppImagesFolder, "PageBackground", "StartupBackground2.png"), - IsAppThemeLight ? 0.2f : 0.175f, 0.5f); - SetAppConfigValue("UseCustomBG", false); - SetAppConfigValue("CustomBGPath", ""); - - senderSource.IsEnabled = true; + ImageBackgroundManager.Shared.GlobalCustomBackgroundImagePath = null; + ImageBackgroundManager.Shared.GlobalIsEnableCustomImage = false; return; } - LoadingMessageHelper.ShowLoadingFrame(); - LoadingMessageHelper.SetProgressBarState(); - LoadingMessageHelper.SetMessage(Lang._OOBEStartUpMenu.LoadingBackgroundImageTitle, - Lang._OOBEStartUpMenu.LoadingBackgroundImageSubtitle); + await SimpleDialogs.Dialog_OOBEVideoBackgroundPreviewUnavailable(); - try - { - var imageStream = await ImageLoaderHelper.LoadImage(selectedPath, true, true); - if (imageStream != null) - { - await ReplaceBackgroundImage(imageStream, 0.5f, IsAppThemeLight ? 0.2f : 0.175f); - SetAppConfigValue("UseCustomBG", true); - SetAppConfigValue("CustomBGPath", selectedPath); - await imageStream.DisposeAsync(); - } - else - { - senderSource.IsChecked = false; - } - } - catch (Exception ex) - { - LogWriteLine($"Error has occurred while loading the image!\r\n{ex}", LogType.Error, true); - ErrorSender.SendException(ex); - } - finally - { - senderSource.IsEnabled = true; - LoadingMessageHelper.HideLoadingFrame(); - } + ImageBackgroundManager.Shared.GlobalCustomBackgroundImagePath = selectedPath; + ImageBackgroundManager.Shared.GlobalIsEnableCustomImage = true; } private async void CustomBackgroundCheckedClose(object? sender, RoutedEventArgs e) diff --git a/CollapseLauncher/XAMLs/MainApp/Pages/PluginManagerPage.xaml b/CollapseLauncher/XAMLs/MainApp/Pages/PluginManagerPage.xaml index 89fe76d28..1e337091a 100644 --- a/CollapseLauncher/XAMLs/MainApp/Pages/PluginManagerPage.xaml +++ b/CollapseLauncher/XAMLs/MainApp/Pages/PluginManagerPage.xaml @@ -112,13 +112,13 @@ + IsEnabled="{x:Bind pages:PluginManagerPage.Context.IsUpdateCheckOnProgress, Converter={StaticResource InverseBooleanConverter}, Mode=OneWay}"> - @@ -175,37 +175,37 @@ Loaded="{x:Bind extension:UIElementExtensions.EnableImplicitAnimationRecursiveOnLoaded}"> - - - - @@ -216,17 +216,17 @@ - - @@ -234,17 +234,17 @@ - - @@ -254,27 +254,27 @@ - - - @@ -293,12 +293,12 @@ Padding="0" HorizontalAlignment="Stretch" HorizontalContentAlignment="Stretch" + extension:UIElementExtensions.CursorType="Hand" HeaderIcon="{x:Bind Converter={StaticResource GamePluginIconConverter}}" - Loaded="{x:Bind extension:UIElementExtensions.AttachHandCursorOnLoaded}" Visibility="Visible"> - + @@ -548,8 +548,8 @@ Padding="4" HorizontalAlignment="Right" VerticalAlignment="Bottom" + extension:UIElementExtensions.CursorType="Hand" Click="{x:Bind pages:SettingsPage.CopyLoadedPluginInformationClick}" - Loaded="{x:Bind extension:UIElementExtensions.AttachHandCursorOnLoaded}" Style="{ThemeResource TransparentDefaultButtonStyle}" Tag="{x:Bind}">