Compare commits

..

20 Commits

Author SHA1 Message Date
qhy040404
48991c2167 impl #1487 2024-03-16 18:31:05 +08:00
ema
d50a6df14c upgrade SH.ja.resx translation (#1491) 2024-03-16 17:06:34 +08:00
DismissedLight
4886904530 refactor controls 2024-03-16 16:59:19 +08:00
DismissedLight
ac34376c13 Merge pull request #1484 from DGP-Studio/feat/spinwait_game_window 2024-03-16 16:57:17 +08:00
qhy040404
3bcee3d149 Apply suggestion 2024-03-16 16:05:18 +08:00
qhy040404
3d3b03851e Use SpinWait to wait game window instead of WaitForInputIdle 2024-03-16 15:59:38 +08:00
DismissedLight
dc64302424 Merge pull request #1483 from DGP-Studio/fix/theme 2024-03-16 14:57:54 +08:00
qhy040404
a2586b0ef2 fix different theme between window and dialog 2024-03-15 20:32:16 +08:00
DismissedLight
eee84a338e significantly reduce uniformstaggeredlayout resize lag 2024-03-15 20:24:32 +08:00
Lightczx
be30362b52 uniformstaggeredlayout optimization 2024-03-15 17:18:25 +08:00
Lightczx
38f36bbb82 fix gachalog page closing crash 2024-03-15 14:21:38 +08:00
Lightczx
704866b16a update dependency 2024-03-15 13:43:55 +08:00
Lightczx
ca9783bc1b corner radius on auto suggest tokenbox 2024-03-15 10:54:06 +08:00
DismissedLight
6e8e151fff fix unlocking fps 2024-03-14 20:32:06 +08:00
DismissedLight
b98dc9f5d3 fix unlocking fps 2024-03-14 19:58:01 +08:00
Masterain
206100d8ef New Crowdin updates (#1477) 2024-03-14 14:41:52 +08:00
Lightczx
1a74c7ca96 fix #1476 2024-03-14 11:24:55 +08:00
DismissedLight
88528fa28d hide zero count section 2024-03-13 21:18:11 +08:00
DismissedLight
263cea9225 Merge pull request #1475 from DGP-Studio/fix/token_ui 2024-03-13 21:09:13 +08:00
Lightczx
2879bd653a add chronicled wish cloud statistics 2024-03-13 17:25:42 +08:00
44 changed files with 263 additions and 401 deletions

View File

@@ -29,6 +29,7 @@
<ResourceDictionary Source="ms-appx:///Control/Theme/TransitionCollection.xaml"/> <ResourceDictionary Source="ms-appx:///Control/Theme/TransitionCollection.xaml"/>
<ResourceDictionary Source="ms-appx:///Control/Theme/Uri.xaml"/> <ResourceDictionary Source="ms-appx:///Control/Theme/Uri.xaml"/>
<ResourceDictionary Source="ms-appx:///Control/Theme/WindowOverride.xaml"/> <ResourceDictionary Source="ms-appx:///Control/Theme/WindowOverride.xaml"/>
<ResourceDictionary Source="ms-appx:///View/Card/Primitive/CardProgressBar.xaml"/>
</ResourceDictionary.MergedDictionaries> </ResourceDictionary.MergedDictionaries>
<Style <Style

View File

@@ -1,8 +1,11 @@
// Copyright (c) DGP Studio. All rights reserved. // Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license. // Licensed under the MIT license.
using CommunityToolkit.WinUI;
using CommunityToolkit.WinUI.Controls; using CommunityToolkit.WinUI.Controls;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls; using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Controls.Primitives;
using Snap.Hutao.Control.Extension; using Snap.Hutao.Control.Extension;
namespace Snap.Hutao.Control.AutoSuggestBox; namespace Snap.Hutao.Control.AutoSuggestBox;
@@ -20,6 +23,42 @@ internal sealed partial class AutoSuggestTokenBox : TokenizingTextBox
TokenItemAdding += OnTokenItemAdding; TokenItemAdding += OnTokenItemAdding;
TokenItemAdded += OnTokenItemModified; TokenItemAdded += OnTokenItemModified;
TokenItemRemoved += OnTokenItemModified; TokenItemRemoved += OnTokenItemModified;
Loaded += OnLoaded;
}
private void OnLoaded(object sender, RoutedEventArgs e)
{
if (this.FindDescendant("SuggestionsPopup") is Popup { Child: Border { Child: ListView listView } border })
{
IAppResourceProvider appResourceProvider = Ioc.Default.GetRequiredService<IAppResourceProvider>();
listView.Background = null;
listView.Margin = appResourceProvider.GetResource<Thickness>("AutoSuggestListPadding");
border.Background = appResourceProvider.GetResource<Microsoft.UI.Xaml.Media.Brush>("AutoSuggestBoxSuggestionsListBackground");
border.CornerRadius = new(0, 0, 8, 8);
}
if (this.FindDescendant("PART_AutoSuggestBox") is Microsoft.UI.Xaml.Controls.AutoSuggestBox autoSuggestBox)
{
autoSuggestBox.GotFocus += OnSuggestBoxFocusGot;
autoSuggestBox.LosingFocus += OnSuggestBoxFocusLosing;
}
}
private void OnSuggestBoxFocusGot(object sender, RoutedEventArgs e)
{
if (sender is Microsoft.UI.Xaml.Controls.AutoSuggestBox autoSuggestBox)
{
autoSuggestBox.ItemsSource = AvailableTokens.Values;
}
}
private void OnSuggestBoxFocusLosing(object sender, RoutedEventArgs e)
{
if (sender is Microsoft.UI.Xaml.Controls.AutoSuggestBox autoSuggestBox)
{
autoSuggestBox.ItemsSource = null;
}
} }
private void OnFilterSuggestionRequested(Microsoft.UI.Xaml.Controls.AutoSuggestBox sender, AutoSuggestBoxTextChangedEventArgs args) private void OnFilterSuggestionRequested(Microsoft.UI.Xaml.Controls.AutoSuggestBox sender, AutoSuggestBoxTextChangedEventArgs args)
@@ -32,9 +71,6 @@ internal sealed partial class AutoSuggestTokenBox : TokenizingTextBox
if (args.Reason == AutoSuggestionBoxTextChangeReason.UserInput) if (args.Reason == AutoSuggestionBoxTextChangeReason.UserInput)
{ {
sender.ItemsSource = AvailableTokens.Values.Where(q => q.Value.Contains(Text, StringComparison.OrdinalIgnoreCase)); sender.ItemsSource = AvailableTokens.Values.Where(q => q.Value.Contains(Text, StringComparison.OrdinalIgnoreCase));
// TODO: CornerRadius
// Popup? popup = this.FindDescendant("SuggestionsPopup") as Popup;
} }
} }
@@ -45,7 +81,7 @@ internal sealed partial class AutoSuggestTokenBox : TokenizingTextBox
return; return;
} }
CommandExtension.TryExecute(FilterCommand, FilterCommandParameter); CommandInvocation.TryExecute(FilterCommand, FilterCommandParameter);
} }
private void OnTokenItemAdding(TokenizingTextBox sender, TokenItemAddingEventArgs args) private void OnTokenItemAdding(TokenizingTextBox sender, TokenItemAddingEventArgs args)
@@ -60,6 +96,6 @@ internal sealed partial class AutoSuggestTokenBox : TokenizingTextBox
private void OnTokenItemModified(TokenizingTextBox sender, object args) private void OnTokenItemModified(TokenizingTextBox sender, object args)
{ {
CommandExtension.TryExecute(FilterCommand, FilterCommandParameter); CommandInvocation.TryExecute(FilterCommand, FilterCommandParameter);
} }
} }

View File

@@ -3,7 +3,7 @@
namespace Snap.Hutao.Control.Extension; namespace Snap.Hutao.Control.Extension;
internal static class CommandExtension internal static class CommandInvocation
{ {
public static bool TryExecute(this ICommand? command, object? parameter = null) public static bool TryExecute(this ICommand? command, object? parameter = null)
{ {

View File

@@ -2,11 +2,13 @@
// Licensed under the MIT license. // Licensed under the MIT license.
using Microsoft.UI.Xaml; using Microsoft.UI.Xaml;
using System.Runtime.CompilerServices;
namespace Snap.Hutao.Control.Extension; namespace Snap.Hutao.Control.Extension;
internal static class DependencyObjectExtension internal static class DependencyObjectExtension
{ {
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static IServiceProvider ServiceProvider(this DependencyObject obj) public static IServiceProvider ServiceProvider(this DependencyObject obj)
{ {
return Ioc.Default; return Ioc.Default;

View File

@@ -3,7 +3,9 @@
using Microsoft.UI.Xaml.Media; using Microsoft.UI.Xaml.Media;
using Microsoft.UI.Xaml.Media.Imaging; using Microsoft.UI.Xaml.Media.Imaging;
using Snap.Hutao.Control.Extension;
using Snap.Hutao.Core.Caching; using Snap.Hutao.Core.Caching;
using Snap.Hutao.Core.ExceptionService;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
namespace Snap.Hutao.Control.Image; namespace Snap.Hutao.Control.Image;
@@ -26,12 +28,11 @@ internal sealed class CachedImage : Implementation.ImageEx
/// <inheritdoc/> /// <inheritdoc/>
protected override async Task<ImageSource?> ProvideCachedResourceAsync(Uri imageUri, CancellationToken token) protected override async Task<ImageSource?> ProvideCachedResourceAsync(Uri imageUri, CancellationToken token)
{ {
// We can only use Ioc to retrieve IImageCache, no IServiceProvider is available. IImageCache imageCache = this.ServiceProvider().GetRequiredService<IImageCache>();
IImageCache imageCache = Ioc.Default.GetRequiredService<IImageCache>();
try try
{ {
Verify.Operation(!string.IsNullOrEmpty(imageUri.Host), SH.ControlImageCachedImageInvalidResourceUri); HutaoException.ThrowIf(string.IsNullOrEmpty(imageUri.Host), HutaoExceptionKind.ImageCacheInvalidUri, SH.ControlImageCachedImageInvalidResourceUri);
string file = await imageCache.GetFileFromCacheAsync(imageUri).ConfigureAwait(true); // BitmapImage need to be created by main thread. string file = await imageCache.GetFileFromCacheAsync(imageUri).ConfigureAwait(true); // BitmapImage need to be created by main thread.
token.ThrowIfCancellationRequested(); // check token state to determine whether the operation should be canceled. token.ThrowIfCancellationRequested(); // check token state to determine whether the operation should be canceled.
return new BitmapImage(file.ToUri()); // BitmapImage initialize with a uri will increase image quality and loading speed. return new BitmapImage(file.ToUri()); // BitmapImage initialize with a uri will increase image quality and loading speed.

View File

@@ -7,21 +7,14 @@ using Windows.Media.Casting;
namespace Snap.Hutao.Control.Image.Implementation; namespace Snap.Hutao.Control.Image.Implementation;
internal class ImageEx : ImageExBase [DependencyProperty("NineGrid", typeof(Thickness))]
internal partial class ImageEx : ImageExBase
{ {
private static readonly DependencyProperty NineGridProperty = DependencyProperty.Register(nameof(NineGrid), typeof(Thickness), typeof(ImageEx), new PropertyMetadata(default(Thickness)));
public ImageEx() public ImageEx()
: base() : base()
{ {
} }
public Thickness NineGrid
{
get => (Thickness)GetValue(NineGridProperty);
set => SetValue(NineGridProperty, value);
}
public override CompositionBrush GetAlphaMask() public override CompositionBrush GetAlphaMask()
{ {
if (IsInitialized && Image is Microsoft.UI.Xaml.Controls.Image image) if (IsInitialized && Image is Microsoft.UI.Xaml.Controls.Image image)

View File

@@ -6,6 +6,7 @@ using Microsoft.UI.Composition;
using Microsoft.UI.Xaml; using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Media; using Microsoft.UI.Xaml.Media;
using Microsoft.UI.Xaml.Media.Imaging; using Microsoft.UI.Xaml.Media.Imaging;
using Snap.Hutao.Win32;
using System.IO; using System.IO;
using Windows.Foundation; using Windows.Foundation;
@@ -158,7 +159,6 @@ internal abstract partial class ImageExBase : Microsoft.UI.Xaml.Controls.Control
if (value) if (value)
{ {
control.LayoutUpdated += control.OnImageExBaseLayoutUpdated; control.LayoutUpdated += control.OnImageExBaseLayoutUpdated;
control.InvalidateLazyLoading(); control.InvalidateLazyLoading();
} }
else else
@@ -169,7 +169,7 @@ internal abstract partial class ImageExBase : Microsoft.UI.Xaml.Controls.Control
private static void LazyLoadingThresholdChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) private static void LazyLoadingThresholdChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{ {
if (d is ImageExBase control && control.EnableLazyLoading) if (d is ImageExBase { EnableLazyLoading: true } control)
{ {
control.InvalidateLazyLoading(); control.InvalidateLazyLoading();
} }
@@ -229,9 +229,6 @@ internal abstract partial class ImageExBase : Microsoft.UI.Xaml.Controls.Control
private void AttachPlaceholderSource(ImageSource? source) private void AttachPlaceholderSource(ImageSource? source)
{ {
// Setting the source at this point should call ImageExOpened/VisualStateManager.GoToState
// as we register to both the ImageOpened/ImageFailed events of the underlying control.
// We only need to call those methods if we fail in other cases before we get here.
if (PlaceholderImage is Microsoft.UI.Xaml.Controls.Image image) if (PlaceholderImage is Microsoft.UI.Xaml.Controls.Image image)
{ {
image.Source = source; image.Source = source;
@@ -240,6 +237,15 @@ internal abstract partial class ImageExBase : Microsoft.UI.Xaml.Controls.Control
{ {
brush.ImageSource = source; brush.ImageSource = source;
} }
if (source is null)
{
VisualStateManager.GoToState(this, UnloadedState, true);
}
else if (source is BitmapSource { PixelHeight: > 0, PixelWidth: > 0 })
{
VisualStateManager.GoToState(this, LoadedState, true);
}
} }
private async void SetSource(object? source) private async void SetSource(object? source)
@@ -311,8 +317,7 @@ internal abstract partial class ImageExBase : Microsoft.UI.Xaml.Controls.Control
} }
tokenSource?.Cancel(); tokenSource?.Cancel();
tokenSource = new();
tokenSource = new CancellationTokenSource();
AttachPlaceholderSource(null); AttachPlaceholderSource(null);
@@ -443,9 +448,10 @@ internal abstract partial class ImageExBase : Microsoft.UI.Xaml.Controls.Control
return; return;
} }
Rect controlRect = TransformToVisual(hostElement) Rect controlRect = TransformToVisual(hostElement).TransformBounds(StructMarshal.Rect(ActualSize));
.TransformBounds(new Rect(0, 0, ActualWidth, ActualHeight));
double lazyLoadingThreshold = LazyLoadingThreshold; double lazyLoadingThreshold = LazyLoadingThreshold;
// Left/Top 1 Threshold, Right/Bottom 2 Threshold
Rect hostRect = new( Rect hostRect = new(
0 - lazyLoadingThreshold, 0 - lazyLoadingThreshold,
0 - lazyLoadingThreshold, 0 - lazyLoadingThreshold,

View File

@@ -1,151 +0,0 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Microsoft.UI.Composition;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Hosting;
using System.Numerics;
using Windows.Foundation;
namespace Snap.Hutao.Control.Layout;
internal sealed class DefaultItemCollectionTransitionProvider : ItemCollectionTransitionProvider
{
private const double DefaultAnimationDurationInMs = 300.0;
static DefaultItemCollectionTransitionProvider()
{
AnimationSlowdownFactor = 1.0;
}
public static double AnimationSlowdownFactor { get; set; }
protected override bool ShouldAnimateCore(ItemCollectionTransition transition)
{
return true;
}
protected override void StartTransitions(IList<ItemCollectionTransition> transitions)
{
List<ItemCollectionTransition> addTransitions = [];
List<ItemCollectionTransition> removeTransitions = [];
List<ItemCollectionTransition> moveTransitions = [];
foreach (ItemCollectionTransition transition in addTransitions)
{
switch (transition.Operation)
{
case ItemCollectionTransitionOperation.Add:
addTransitions.Add(transition);
break;
case ItemCollectionTransitionOperation.Remove:
removeTransitions.Add(transition);
break;
case ItemCollectionTransitionOperation.Move:
moveTransitions.Add(transition);
break;
}
}
StartAddTransitions(addTransitions, removeTransitions.Count > 0, moveTransitions.Count > 0);
StartRemoveTransitions(removeTransitions);
StartMoveTransitions(moveTransitions, removeTransitions.Count > 0);
}
private static void StartAddTransitions(IList<ItemCollectionTransition> transitions, bool hasRemoveTransitions, bool hasMoveTransitions)
{
foreach (ItemCollectionTransition transition in transitions)
{
ItemCollectionTransitionProgress progress = transition.Start();
Visual visual = ElementCompositionPreview.GetElementVisual(progress.Element);
Compositor compositor = visual.Compositor;
ScalarKeyFrameAnimation fadeInAnimation = compositor.CreateScalarKeyFrameAnimation();
fadeInAnimation.InsertKeyFrame(0.0f, 0.0f);
if (hasMoveTransitions && hasRemoveTransitions)
{
fadeInAnimation.InsertKeyFrame(0.66f, 0.0f);
}
else if (hasMoveTransitions || hasRemoveTransitions)
{
fadeInAnimation.InsertKeyFrame(0.5f, 0.0f);
}
fadeInAnimation.InsertKeyFrame(1.0f, 1.0f);
fadeInAnimation.Duration = TimeSpan.FromMilliseconds(
DefaultAnimationDurationInMs * ((hasRemoveTransitions ? 1 : 0) + (hasMoveTransitions ? 1 : 0) + 1) * AnimationSlowdownFactor);
CompositionScopedBatch batch = compositor.CreateScopedBatch(CompositionBatchTypes.Animation);
visual.StartAnimation("Opacity", fadeInAnimation);
batch.End();
batch.Completed += (_, _) => progress.Complete();
}
}
private static void StartRemoveTransitions(IList<ItemCollectionTransition> transitions)
{
foreach (ItemCollectionTransition transition in transitions)
{
ItemCollectionTransitionProgress progress = transition.Start();
Visual visual = ElementCompositionPreview.GetElementVisual(progress.Element);
Compositor compositor = visual.Compositor;
ScalarKeyFrameAnimation fadeOutAnimation = compositor.CreateScalarKeyFrameAnimation();
fadeOutAnimation.InsertExpressionKeyFrame(0.0f, "this.CurrentValue");
fadeOutAnimation.InsertKeyFrame(1.0f, 0.0f);
fadeOutAnimation.Duration = TimeSpan.FromMilliseconds(DefaultAnimationDurationInMs * AnimationSlowdownFactor);
CompositionScopedBatch batch = compositor.CreateScopedBatch(CompositionBatchTypes.Animation);
visual.StartAnimation(nameof(Visual.Opacity), fadeOutAnimation);
batch.End();
batch.Completed += (_, _) =>
{
visual.Opacity = 1.0f;
progress.Complete();
};
}
}
private static void StartMoveTransitions(IList<ItemCollectionTransition> transitions, bool hasRemoveAnimations)
{
foreach (ItemCollectionTransition transition in transitions)
{
ItemCollectionTransitionProgress progress = transition.Start();
Visual visual = ElementCompositionPreview.GetElementVisual(progress.Element);
Compositor compositor = visual.Compositor;
CompositionScopedBatch batch = compositor.CreateScopedBatch(CompositionBatchTypes.Animation);
// Animate offset.
if (transition.OldBounds.X != transition.NewBounds.X ||
transition.OldBounds.Y != transition.NewBounds.Y)
{
AnimateOffset(visual, compositor, transition.OldBounds, transition.NewBounds, hasRemoveAnimations);
}
batch.End();
batch.Completed += (_, _) => progress.Complete();
}
}
private static void AnimateOffset(Visual visual, Compositor compositor, Rect oldBounds, Rect newBounds, bool hasRemoveAnimations)
{
Vector2KeyFrameAnimation offsetAnimation = compositor.CreateVector2KeyFrameAnimation();
offsetAnimation.SetVector2Parameter("delta", new Vector2(
(float)(oldBounds.X - newBounds.X),
(float)(oldBounds.Y - newBounds.Y)));
offsetAnimation.SetVector2Parameter("final", default);
offsetAnimation.InsertExpressionKeyFrame(0.0f, "this.CurrentValue + delta");
if (hasRemoveAnimations)
{
offsetAnimation.InsertExpressionKeyFrame(0.5f, "delta");
}
offsetAnimation.InsertExpressionKeyFrame(1.0f, "final");
offsetAnimation.Duration = TimeSpan.FromMilliseconds(
DefaultAnimationDurationInMs * ((hasRemoveAnimations ? 1 : 0) + 1) * AnimationSlowdownFactor);
visual.StartAnimation("TransformMatrix._41_42", offsetAnimation);
}
}

View File

@@ -4,6 +4,7 @@
using Microsoft.UI.Xaml; using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls; using Microsoft.UI.Xaml.Controls;
using System.Collections.Specialized; using System.Collections.Specialized;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using Windows.Foundation; using Windows.Foundation;
@@ -18,14 +19,12 @@ internal sealed partial class UniformStaggeredLayout : VirtualizingLayout
protected override void InitializeForContextCore(VirtualizingLayoutContext context) protected override void InitializeForContextCore(VirtualizingLayoutContext context)
{ {
context.LayoutState = new UniformStaggeredLayoutState(context); context.LayoutState = new UniformStaggeredLayoutState(context);
base.InitializeForContextCore(context);
} }
/// <inheritdoc/> /// <inheritdoc/>
protected override void UninitializeForContextCore(VirtualizingLayoutContext context) protected override void UninitializeForContextCore(VirtualizingLayoutContext context)
{ {
context.LayoutState = null; context.LayoutState = null;
base.UninitializeForContextCore(context);
} }
/// <inheritdoc/> /// <inheritdoc/>
@@ -82,16 +81,10 @@ internal sealed partial class UniformStaggeredLayout : VirtualizingLayout
(int numberOfColumns, double columnWidth) = GetNumberOfColumnsAndWidth(availableWidth, MinItemWidth, MinColumnSpacing); (int numberOfColumns, double columnWidth) = GetNumberOfColumnsAndWidth(availableWidth, MinItemWidth, MinColumnSpacing);
if (columnWidth != state.ColumnWidth)
{
// The items will need to be remeasured
state.Clear();
}
state.ColumnWidth = columnWidth; state.ColumnWidth = columnWidth;
// adjust for column spacing on all columns expect the first double totalWidth = ((state.ColumnWidth + MinColumnSpacing) * numberOfColumns) - MinColumnSpacing;
double totalWidth = state.ColumnWidth + ((numberOfColumns - 1) * (state.ColumnWidth + MinColumnSpacing));
if (totalWidth > availableWidth) if (totalWidth > availableWidth)
{ {
numberOfColumns--; numberOfColumns--;
@@ -103,7 +96,6 @@ internal sealed partial class UniformStaggeredLayout : VirtualizingLayout
if (numberOfColumns != state.NumberOfColumns) if (numberOfColumns != state.NumberOfColumns)
{ {
// The items will not need to be remeasured, but they will need to go into new columns
state.ClearColumns(); state.ClearColumns();
} }
@@ -170,7 +162,7 @@ internal sealed partial class UniformStaggeredLayout : VirtualizingLayout
item.Element.Measure(new Size(state.ColumnWidth, availableHeight)); item.Element.Measure(new Size(state.ColumnWidth, availableHeight));
if (item.Height != item.Element.DesiredSize.Height) if (item.Height != item.Element.DesiredSize.Height)
{ {
// this item changed size; we need to recalculate layout for everything after this // this item changed size; we need to recalculate layout for everything after this item
state.RemoveFromIndex(i + 1); state.RemoveFromIndex(i + 1);
item.Height = item.Element.DesiredSize.Height; item.Height = item.Element.DesiredSize.Height;
columnHeights[columnIndex] = item.Top + item.Height; columnHeights[columnIndex] = item.Top + item.Height;
@@ -201,16 +193,16 @@ internal sealed partial class UniformStaggeredLayout : VirtualizingLayout
// Cycle through each column and arrange the items that are within the realization bounds // Cycle through each column and arrange the items that are within the realization bounds
for (int columnIndex = 0; columnIndex < state.NumberOfColumns; columnIndex++) for (int columnIndex = 0; columnIndex < state.NumberOfColumns; columnIndex++)
{ {
UniformStaggeredColumnLayout layout = state.GetColumnLayout(columnIndex); foreach (ref readonly UniformStaggeredItem item in CollectionsMarshal.AsSpan(state.GetColumnLayout(columnIndex)))
foreach (ref readonly UniformStaggeredItem item in CollectionsMarshal.AsSpan(layout))
{ {
double bottom = item.Top + item.Height; double bottom = item.Top + item.Height;
if (bottom < context.RealizationRect.Top) if (bottom < context.RealizationRect.Top)
{ {
// element is above the realization bounds // Element is above the realization bounds
continue; continue;
} }
// Partial or fully in the view
if (item.Top <= context.RealizationRect.Bottom) if (item.Top <= context.RealizationRect.Bottom)
{ {
double itemHorizontalOffset = (state.ColumnWidth * columnIndex) + (MinColumnSpacing * columnIndex); double itemHorizontalOffset = (state.ColumnWidth * columnIndex) + (MinColumnSpacing * columnIndex);
@@ -229,21 +221,22 @@ internal sealed partial class UniformStaggeredLayout : VirtualizingLayout
return finalSize; return finalSize;
} }
private static (int NumberOfColumns, double ColumnWidth) GetNumberOfColumnsAndWidth(double availableWidth, double minItemWidth, double minColumnSpacing) private static (int NumberOfColumns, double ColumnWidth) GetNumberOfColumnsAndWidth(double availableWidth, double minItemWidth, double columnSpacing)
{ {
// test if the width can fit in 2 items // test if the width can fit in 2 items
if ((2 * minItemWidth) + minColumnSpacing > availableWidth) if ((2 * minItemWidth) + columnSpacing > availableWidth)
{ {
return (1, availableWidth); return (1, availableWidth);
} }
int columnCount = Math.Max(1, (int)((availableWidth + minColumnSpacing) / (minItemWidth + minColumnSpacing))); int columnCount = Math.Max(1, (int)((availableWidth + columnSpacing) / (minItemWidth + columnSpacing)));
double columnWidthAddSpacing = (availableWidth + minColumnSpacing) / columnCount; double columnWidthWithSpacing = (availableWidth + columnSpacing) / columnCount;
return (columnCount, columnWidthAddSpacing - minColumnSpacing); return (columnCount, columnWidthWithSpacing - columnSpacing);
} }
private static int GetLowestColumnIndex(in ReadOnlySpan<double> columnHeights) private static int GetLowestColumnIndex(in ReadOnlySpan<double> columnHeights)
{ {
// We want to find the leftest column with the lowest height
int columnIndex = 0; int columnIndex = 0;
double height = columnHeights[0]; double height = columnHeights[0];
for (int j = 1; j < columnHeights.Length; j++) for (int j = 1; j < columnHeights.Length; j++)
@@ -260,13 +253,11 @@ internal sealed partial class UniformStaggeredLayout : VirtualizingLayout
private static void OnMinItemWidthChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) private static void OnMinItemWidthChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{ {
UniformStaggeredLayout panel = (UniformStaggeredLayout)d; ((UniformStaggeredLayout)d).InvalidateMeasure();
panel.InvalidateMeasure();
} }
private static void OnSpacingChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) private static void OnSpacingChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{ {
UniformStaggeredLayout panel = (UniformStaggeredLayout)d; ((UniformStaggeredLayout)d).InvalidateMeasure();
panel.InvalidateMeasure();
} }
} }

View File

@@ -1,7 +1,6 @@
// Copyright (c) DGP Studio. All rights reserved. // Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license. // Licensed under the MIT license.
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls; using Microsoft.UI.Xaml.Controls;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
@@ -67,46 +66,6 @@ internal sealed class UniformStaggeredLayoutState
return columnLayout[columnIndex]; return columnLayout[columnIndex];
} }
/// <summary>
/// Clear everything that has been calculated.
/// </summary>
internal void Clear()
{
// https://github.com/DGP-Studio/Snap.Hutao/issues/1079
// The first element must be force refreshed otherwise
// it will use the old one realized
// https://github.com/DGP-Studio/Snap.Hutao/issues/1099
// Now we need to refresh the first element of each column
// https://github.com/DGP-Studio/Snap.Hutao/issues/1099
// Finally we need to refresh the whole layout when we reset
if (context.ItemCount > 0)
{
for (int i = 0; i < context.ItemCount; i++)
{
RecycleElementAt(i);
}
}
columnLayout.Clear();
items.Clear();
}
/// <summary>
/// Clear the layout columns so they will be recalculated.
/// </summary>
internal void ClearColumns()
{
columnLayout.Clear();
}
/// <summary>
/// Gets the estimated height of the layout.
/// </summary>
/// <returns>The estimated height of the layout.</returns>
/// <remarks>
/// If all of the items have been calculated then the actual height will be returned.
/// If all of the items have not been calculated then an estimated height will be calculated based on the average height of the items.
/// </remarks>
internal double GetHeight() internal double GetHeight()
{ {
double desiredHeight = columnLayout.Values.Max(c => c.Height); double desiredHeight = columnLayout.Values.Max(c => c.Height);
@@ -139,10 +98,37 @@ internal sealed class UniformStaggeredLayoutState
return desiredHeight; return desiredHeight;
} }
internal void Clear()
{
RecycleElements();
ClearColumns();
ClearItems();
}
internal void ClearColumns()
{
columnLayout.Clear();
}
internal void ClearItems()
{
items.Clear();
}
internal void RecycleElements()
{
if (context.ItemCount > 0)
{
for (int i = 0; i < items.Count; i++)
{
RecycleElementAt(i);
}
}
}
internal void RecycleElementAt(int index) internal void RecycleElementAt(int index)
{ {
UIElement element = context.GetOrCreateElementAt(index); context.RecycleElement(context.GetOrCreateElementAt(index));
context.RecycleElement(element);
} }
internal void RemoveFromIndex(int index) internal void RemoveFromIndex(int index)
@@ -175,7 +161,7 @@ internal sealed class UniformStaggeredLayoutState
{ {
for (int i = startIndex; i <= endIndex; i++) for (int i = startIndex; i <= endIndex; i++)
{ {
if (i > items.Count) if (i >= items.Count)
{ {
break; break;
} }
@@ -184,7 +170,7 @@ internal sealed class UniformStaggeredLayoutState
item.Height = 0; item.Height = 0;
item.Top = 0; item.Top = 0;
// We must recycle all elements to ensure that it gets the correct context // We must recycle all removed elements to ensure that it gets the correct context
RecycleElementAt(i); RecycleElementAt(i);
} }

View File

@@ -12,7 +12,6 @@ internal sealed class UInt32Extension : MarkupExtension
protected override object ProvideValue() protected override object ProvideValue()
{ {
_ = uint.TryParse(Value, out uint result); return XamlBindingHelper.ConvertValue(typeof(uint), Value);
return result;
} }
} }

View File

@@ -39,24 +39,6 @@ internal static class SoftwareBitmapExtension
} }
} }
public static unsafe double Luminance(this SoftwareBitmap softwareBitmap)
{
using (BitmapBuffer buffer = softwareBitmap.LockBuffer(BitmapBufferAccessMode.Read))
{
using (IMemoryBufferReference reference = buffer.CreateReference())
{
reference.As<IMemoryBufferByteAccess>().GetBuffer(out Span<Bgra32> bytes);
double sum = 0;
foreach (ref readonly Bgra32 pixel in bytes)
{
sum += pixel.Luminance;
}
return sum / bytes.Length;
}
}
}
public static unsafe Bgra32 GetAccentColor(this SoftwareBitmap softwareBitmap) public static unsafe Bgra32 GetAccentColor(this SoftwareBitmap softwareBitmap)
{ {
using (BitmapBuffer buffer = softwareBitmap.LockBuffer(BitmapBufferAccessMode.Read)) using (BitmapBuffer buffer = softwareBitmap.LockBuffer(BitmapBufferAccessMode.Read))

View File

@@ -82,8 +82,8 @@ internal partial class EqualPanel : Microsoft.UI.Xaml.Controls.Panel
(d as EqualPanel)?.InvalidateMeasure(); (d as EqualPanel)?.InvalidateMeasure();
} }
private void OnHorizontalAlignmentChanged(DependencyObject sender, DependencyProperty dp) private static void OnHorizontalAlignmentChanged(DependencyObject d, DependencyProperty dp)
{ {
InvalidateMeasure(); (d as EqualPanel)?.InvalidateMeasure();
} }
} }

View File

@@ -33,8 +33,8 @@ internal partial class HorizontalEqualPanel : Microsoft.UI.Xaml.Controls.Panel
protected override Size ArrangeOverride(Size finalSize) protected override Size ArrangeOverride(Size finalSize)
{ {
int itemCount = Children.Count; int itemCount = Children.Count;
double availableWidthPerItem = (finalSize.Width - (Spacing * (itemCount - 1))) / itemCount; double availableItemWidth = (finalSize.Width - (Spacing * (itemCount - 1))) / itemCount;
double actualItemWidth = Math.Max(MinItemWidth, availableWidthPerItem); double actualItemWidth = Math.Max(MinItemWidth, availableItemWidth);
double offset = 0; double offset = 0;
foreach (UIElement child in Children) foreach (UIElement child in Children)
@@ -46,13 +46,14 @@ internal partial class HorizontalEqualPanel : Microsoft.UI.Xaml.Controls.Panel
return finalSize; return finalSize;
} }
private void OnLoaded(object sender, RoutedEventArgs e) private static void OnLoaded(object sender, RoutedEventArgs e)
{ {
MinWidth = (MinItemWidth * Children.Count) + (Spacing * (Children.Count - 1)); HorizontalEqualPanel panel = (HorizontalEqualPanel)sender;
panel.MinWidth = (panel.MinItemWidth * panel.Children.Count) + (panel.Spacing * (panel.Children.Count - 1));
} }
private void OnSizeChanged(object sender, SizeChangedEventArgs e) private static void OnSizeChanged(object sender, SizeChangedEventArgs e)
{ {
InvalidateMeasure(); ((HorizontalEqualPanel)sender).InvalidateMeasure();
} }
} }

View File

@@ -72,7 +72,11 @@ internal class ScopedPage : Page
DisposeViewModel(); DisposeViewModel();
} }
DataContext = null; if (this.IsDisposed())
{
return;
}
Unloaded -= unloadEventHandler; Unloaded -= unloadEventHandler;
} }

View File

@@ -9,6 +9,7 @@ internal enum HutaoExceptionKind
// Foundation // Foundation
ServiceTypeCastFailed, ServiceTypeCastFailed,
ImageCacheInvalidUri,
// IO // IO
FileSystemCreateFileInsufficientPermissions, FileSystemCreateFileInsufficientPermissions,

View File

@@ -3,6 +3,7 @@
using Microsoft.UI.Xaml.Controls; using Microsoft.UI.Xaml.Controls;
using Snap.Hutao.Core.LifeCycle; using Snap.Hutao.Core.LifeCycle;
using Snap.Hutao.Service;
namespace Snap.Hutao.Factory.ContentDialog; namespace Snap.Hutao.Factory.ContentDialog;
@@ -15,6 +16,7 @@ internal sealed partial class ContentDialogFactory : IContentDialogFactory
private readonly ICurrentWindowReference currentWindowReference; private readonly ICurrentWindowReference currentWindowReference;
private readonly IServiceProvider serviceProvider; private readonly IServiceProvider serviceProvider;
private readonly ITaskContext taskContext; private readonly ITaskContext taskContext;
private readonly AppOptions appOptions;
/// <inheritdoc/> /// <inheritdoc/>
public async ValueTask<ContentDialogResult> CreateForConfirmAsync(string title, string content) public async ValueTask<ContentDialogResult> CreateForConfirmAsync(string title, string content)
@@ -27,6 +29,7 @@ internal sealed partial class ContentDialogFactory : IContentDialogFactory
Content = content, Content = content,
DefaultButton = ContentDialogButton.Primary, DefaultButton = ContentDialogButton.Primary,
PrimaryButtonText = SH.ContentDialogConfirmPrimaryButtonText, PrimaryButtonText = SH.ContentDialogConfirmPrimaryButtonText,
RequestedTheme = appOptions.ElementTheme,
}; };
return await dialog.ShowAsync(); return await dialog.ShowAsync();
@@ -44,6 +47,7 @@ internal sealed partial class ContentDialogFactory : IContentDialogFactory
DefaultButton = defaultButton, DefaultButton = defaultButton,
PrimaryButtonText = SH.ContentDialogConfirmPrimaryButtonText, PrimaryButtonText = SH.ContentDialogConfirmPrimaryButtonText,
CloseButtonText = SH.ContentDialogCancelCloseButtonText, CloseButtonText = SH.ContentDialogCancelCloseButtonText,
RequestedTheme = appOptions.ElementTheme,
}; };
return await dialog.ShowAsync(); return await dialog.ShowAsync();
@@ -58,6 +62,7 @@ internal sealed partial class ContentDialogFactory : IContentDialogFactory
XamlRoot = currentWindowReference.GetXamlRoot(), XamlRoot = currentWindowReference.GetXamlRoot(),
Title = title, Title = title,
Content = new ProgressBar() { IsIndeterminate = true }, Content = new ProgressBar() { IsIndeterminate = true },
RequestedTheme = appOptions.ElementTheme,
}; };
return dialog; return dialog;
@@ -69,6 +74,7 @@ internal sealed partial class ContentDialogFactory : IContentDialogFactory
await taskContext.SwitchToMainThreadAsync(); await taskContext.SwitchToMainThreadAsync();
TContentDialog contentDialog = serviceProvider.CreateInstance<TContentDialog>(parameters); TContentDialog contentDialog = serviceProvider.CreateInstance<TContentDialog>(parameters);
contentDialog.XamlRoot = currentWindowReference.GetXamlRoot(); contentDialog.XamlRoot = currentWindowReference.GetXamlRoot();
contentDialog.RequestedTheme = appOptions.ElementTheme;
return contentDialog; return contentDialog;
} }
@@ -77,6 +83,7 @@ internal sealed partial class ContentDialogFactory : IContentDialogFactory
{ {
TContentDialog contentDialog = serviceProvider.CreateInstance<TContentDialog>(parameters); TContentDialog contentDialog = serviceProvider.CreateInstance<TContentDialog>(parameters);
contentDialog.XamlRoot = currentWindowReference.GetXamlRoot(); contentDialog.XamlRoot = currentWindowReference.GetXamlRoot();
contentDialog.RequestedTheme = appOptions.ElementTheme;
return contentDialog; return contentDialog;
} }
} }

View File

@@ -21,6 +21,7 @@ internal static class MonsterRelationship
5112U => 511U, // 历经百战的浊水喷吐幻灵 5112U => 511U, // 历经百战的浊水喷吐幻灵
30605U => 30603U, // 历经百战的霜剑律从 30605U => 30603U, // 历经百战的霜剑律从
30606U => 30604U, // 历经百战的幽风铃兰 30606U => 30604U, // 历经百战的幽风铃兰
40632U => 40613U, // 自律超算型场力发生装置
60402U => 60401U, // (火)岩龙蜥 60402U => 60401U, // (火)岩龙蜥
60403U => 60401U, // (冰)岩龙蜥 60403U => 60401U, // (冰)岩龙蜥
60404U => 60401U, // (雷)岩龙蜥 60404U => 60401U, // (雷)岩龙蜥

View File

@@ -2922,7 +2922,7 @@
<value>〓Event Duration〓.*?\d\.\d Available throughout the entirety of Version</value> <value>〓Event Duration〓.*?\d\.\d Available throughout the entirety of Version</value>
</data> </data>
<data name="WebAnnouncementMatchTransientActivityTime" xml:space="preserve"> <data name="WebAnnouncementMatchTransientActivityTime" xml:space="preserve">
<value>(?:〓Event Duration〓|Event Wish Duration|【Availability Duration】).*?(\d\.\dAfter the Version update).*?~.*?&amp;lt;t class="t_(?:gl|lc)".*?&amp;gt;(.*?)&amp;lt;/t&amp;gt;</value> <value>(?:〓Event Duration〓|Event Wish Duration|【Availability Duration】|〓Discount Period〓).*?(\d\.\dAfter the Version update).*?~.*?&amp;lt;t class="t_(?:gl|lc)".*?&amp;gt;(.*?)&amp;lt;/t&amp;gt;</value>
</data> </data>
<data name="WebAnnouncementMatchVersionUpdateTime" xml:space="preserve"> <data name="WebAnnouncementMatchVersionUpdateTime" xml:space="preserve">
<value>〓Update Maintenance Duration〓.+?&amp;lt;t class=\"t_(?:gl|lc)\".*?&amp;gt;(.*?)&amp;lt;/t&amp;gt;</value> <value>〓Update Maintenance Duration〓.+?&amp;lt;t class=\"t_(?:gl|lc)\".*?&amp;gt;(.*?)&amp;lt;/t&amp;gt;</value>

View File

@@ -2247,7 +2247,7 @@
<value>Berkas</value> <value>Berkas</value>
</data> </data>
<data name="ViewPageLaunchGameInterProcessHeader" xml:space="preserve"> <data name="ViewPageLaunchGameInterProcessHeader" xml:space="preserve">
<value>进程联动</value> <value>InterProcess</value>
</data> </data>
<data name="ViewPageLaunchGameMonitorsDescription" xml:space="preserve"> <data name="ViewPageLaunchGameMonitorsDescription" xml:space="preserve">
<value>Menjalankan perangkat lunak pada layar yang dipilih</value> <value>Menjalankan perangkat lunak pada layar yang dipilih</value>

View File

@@ -1368,7 +1368,7 @@
<value>クッキーを設定</value> <value>クッキーを設定</value>
</data> </data>
<data name="ViewFeedbackHeader" xml:space="preserve"> <data name="ViewFeedbackHeader" xml:space="preserve">
<value>フィードバック センター</value> <value>フィードバックセンター</value>
</data> </data>
<data name="ViewGachaLogHeader" xml:space="preserve"> <data name="ViewGachaLogHeader" xml:space="preserve">
<value>祈願履歴</value> <value>祈願履歴</value>
@@ -2247,7 +2247,7 @@
<value>ファイル</value> <value>ファイル</value>
</data> </data>
<data name="ViewPageLaunchGameInterProcessHeader" xml:space="preserve"> <data name="ViewPageLaunchGameInterProcessHeader" xml:space="preserve">
<value>进程联动</value> <value>インタープロセス</value>
</data> </data>
<data name="ViewPageLaunchGameMonitorsDescription" xml:space="preserve"> <data name="ViewPageLaunchGameMonitorsDescription" xml:space="preserve">
<value>指定したディスプレイで実行</value> <value>指定したディスプレイで実行</value>
@@ -2505,7 +2505,7 @@
<value>アチーブメント</value> <value>アチーブメント</value>
</data> </data>
<data name="ViewpageSettingHomeCardItemDailyNoteHeader" xml:space="preserve"> <data name="ViewpageSettingHomeCardItemDailyNoteHeader" xml:space="preserve">
<value>リアルタイムノート</value> <value>リアルタイムノート</value>
</data> </data>
<data name="ViewpageSettingHomeCardItemgachaStatisticsHeader" xml:space="preserve"> <data name="ViewpageSettingHomeCardItemgachaStatisticsHeader" xml:space="preserve">
<value>祈願履歴</value> <value>祈願履歴</value>
@@ -2859,7 +2859,7 @@
<value>QRコードでログイン</value> <value>QRコードでログイン</value>
</data> </data>
<data name="ViewUserCookieOperationManualInputAction" xml:space="preserve"> <data name="ViewUserCookieOperationManualInputAction" xml:space="preserve">
<value>手入力</value> <value>手入力</value>
</data> </data>
<data name="ViewUserCookieOperationRefreshCookieAction" xml:space="preserve"> <data name="ViewUserCookieOperationRefreshCookieAction" xml:space="preserve">
<value>Cookie 再取得</value> <value>Cookie 再取得</value>
@@ -2922,7 +2922,7 @@
<value>〓イベント期間〓.*?\d\.\d当バージョン期間オープン</value> <value>〓イベント期間〓.*?\d\.\d当バージョン期間オープン</value>
</data> </data>
<data name="WebAnnouncementMatchTransientActivityTime" xml:space="preserve"> <data name="WebAnnouncementMatchTransientActivityTime" xml:space="preserve">
<value>(?:〓活动时间〓|祈愿时间|【上架时间】|〓折扣时间〓).*?(\d\.\d版本更新后).*?~.*?&amp;lt;t class="t_(?:gl|lc)".*?&amp;gt;(.*?)&amp;lt;/t&amp;gt;</value> <value>(?:〓イベント期間〓|祈願期間|【開始日時】).*?(\d\.\dバージョンアップ完了後).*?~.*?&amp;lt;t class="t_(?:gl|lc)".*?&amp;gt;(.*?)&amp;lt;/t&amp;gt;</value>
</data> </data>
<data name="WebAnnouncementMatchVersionUpdateTime" xml:space="preserve"> <data name="WebAnnouncementMatchVersionUpdateTime" xml:space="preserve">
<value>〓メンテナンス時間〓.+?&amp;lt;t class=\"t_(?:gl|lc)\".*?&amp;gt;(.*?)&amp;lt;/t&amp;gt;</value> <value>〓メンテナンス時間〓.+?&amp;lt;t class=\"t_(?:gl|lc)\".*?&amp;gt;(.*?)&amp;lt;/t&amp;gt;</value>

View File

@@ -2247,7 +2247,7 @@
<value>文件</value> <value>文件</value>
</data> </data>
<data name="ViewPageLaunchGameInterProcessHeader" xml:space="preserve"> <data name="ViewPageLaunchGameInterProcessHeader" xml:space="preserve">
<value>进程联动</value> <value>进程</value>
</data> </data>
<data name="ViewPageLaunchGameMonitorsDescription" xml:space="preserve"> <data name="ViewPageLaunchGameMonitorsDescription" xml:space="preserve">
<value>지정한 모니터에서 실행</value> <value>지정한 모니터에서 실행</value>
@@ -2922,7 +2922,7 @@
<value>〓活动时间〓.*?\d\.\d版本期间持续开放</value> <value>〓活动时间〓.*?\d\.\d版本期间持续开放</value>
</data> </data>
<data name="WebAnnouncementMatchTransientActivityTime" xml:space="preserve"> <data name="WebAnnouncementMatchTransientActivityTime" xml:space="preserve">
<value>(?:〓活动时间〓|祈愿时间|【上架时间】|〓折扣时间〓).*?(\d\.\d版本更新后).*?~.*?&amp;lt;t class="t_(?:gl|lc)".*?&amp;gt;(.*?)&amp;lt;/t&amp;gt;</value> <value>(?:〓活动时间〓|祈愿时间|【上架时间】).*?(\d\.\d版本更新后).*?~.*?&amp;lt;t class="t_(?:gl|lc)".*?&amp;gt;(.*?)&amp;lt;/t&amp;gt;</value>
</data> </data>
<data name="WebAnnouncementMatchVersionUpdateTime" xml:space="preserve"> <data name="WebAnnouncementMatchVersionUpdateTime" xml:space="preserve">
<value>〓更新时间〓.+?&amp;lt;t class=\"t_(?:gl|lc)\".*?&amp;gt;(.*?)&amp;lt;/t&amp;gt;</value> <value>〓更新时间〓.+?&amp;lt;t class=\"t_(?:gl|lc)\".*?&amp;gt;(.*?)&amp;lt;/t&amp;gt;</value>

View File

@@ -2247,7 +2247,7 @@
<value>Arquivo</value> <value>Arquivo</value>
</data> </data>
<data name="ViewPageLaunchGameInterProcessHeader" xml:space="preserve"> <data name="ViewPageLaunchGameInterProcessHeader" xml:space="preserve">
<value>进程联动</value> <value>Comunicações entre processos</value>
</data> </data>
<data name="ViewPageLaunchGameMonitorsDescription" xml:space="preserve"> <data name="ViewPageLaunchGameMonitorsDescription" xml:space="preserve">
<value>Executar o software na tela selecionada</value> <value>Executar o software na tela selecionada</value>
@@ -2922,7 +2922,7 @@
<value>〓Duração do evento〓.*?\d\.\d Disponível em toda as versões</value> <value>〓Duração do evento〓.*?\d\.\d Disponível em toda as versões</value>
</data> </data>
<data name="WebAnnouncementMatchTransientActivityTime" xml:space="preserve"> <data name="WebAnnouncementMatchTransientActivityTime" xml:space="preserve">
<value>(?:〓活动时间〓|祈愿时间|【上架时间】|〓折扣时间〓).*?(\d\.\d版本更新后).*?~.*?&amp;lt;t class="t_(?:gl|lc)".*?&amp;gt;(.*?)&amp;lt;/t&amp;gt;</value> <value>(?:〓Duração do evento〓|Duração da oração do evento|【Duração da disponibilidade】).*?(\d\.\dApós a atualização da versão).*?~.*?&amp;lt;t class="t_(?:gl|lc)".*?&amp;gt;(.*?)&amp;lt;/t&amp;gt;</value>
</data> </data>
<data name="WebAnnouncementMatchVersionUpdateTime" xml:space="preserve"> <data name="WebAnnouncementMatchVersionUpdateTime" xml:space="preserve">
<value>〓Duração da manutenção da atualização.+?&amp;lt;t class=\"t_(?:gl|lc)\".*?&amp;gt;(.*?)&amp;lt;/t&amp;gt;</value> <value>〓Duração da manutenção da atualização.+?&amp;lt;t class=\"t_(?:gl|lc)\".*?&amp;gt;(.*?)&amp;lt;/t&amp;gt;</value>

View File

@@ -2247,7 +2247,7 @@
<value>文件</value> <value>文件</value>
</data> </data>
<data name="ViewPageLaunchGameInterProcessHeader" xml:space="preserve"> <data name="ViewPageLaunchGameInterProcessHeader" xml:space="preserve">
<value>进程联动</value> <value>进程</value>
</data> </data>
<data name="ViewPageLaunchGameMonitorsDescription" xml:space="preserve"> <data name="ViewPageLaunchGameMonitorsDescription" xml:space="preserve">
<value>在指定的显示器上运行</value> <value>在指定的显示器上运行</value>
@@ -2922,7 +2922,7 @@
<value>〓活动时间〓.*?\d\.\d版本期间持续开放</value> <value>〓活动时间〓.*?\d\.\d版本期间持续开放</value>
</data> </data>
<data name="WebAnnouncementMatchTransientActivityTime" xml:space="preserve"> <data name="WebAnnouncementMatchTransientActivityTime" xml:space="preserve">
<value>(?:〓活动时间〓|祈愿时间|【上架时间】|〓折扣时间〓).*?(\d\.\d版本更新后).*?~.*?&amp;lt;t class="t_(?:gl|lc)".*?&amp;gt;(.*?)&amp;lt;/t&amp;gt;</value> <value>(?:〓活动时间〓|祈愿时间|【上架时间】).*?(\d\.\d版本更新后).*?~.*?&amp;lt;t class="t_(?:gl|lc)".*?&amp;gt;(.*?)&amp;lt;/t&amp;gt;</value>
</data> </data>
<data name="WebAnnouncementMatchVersionUpdateTime" xml:space="preserve"> <data name="WebAnnouncementMatchVersionUpdateTime" xml:space="preserve">
<value>〓更新时间〓.+?&amp;lt;t class=\"t_(?:gl|lc)\".*?&amp;gt;(.*?)&amp;lt;/t&amp;gt;</value> <value>〓更新时间〓.+?&amp;lt;t class=\"t_(?:gl|lc)\".*?&amp;gt;(.*?)&amp;lt;/t&amp;gt;</value>

View File

@@ -17,7 +17,7 @@ internal sealed class LaunchStatus
public string Description { get; set; } public string Description { get; set; }
public static LaunchStatus FromUnlockState(GameFpsUnlockerState unlockerState) public static LaunchStatus FromUnlockerContext(GameFpsUnlockerContext unlockerState)
{ {
if (unlockerState.FindModuleResult == FindModuleResult.Ok) if (unlockerState.FindModuleResult == FindModuleResult.Ok)
{ {

View File

@@ -9,7 +9,7 @@ internal sealed class LaunchExecutionBetterGenshinImpactAutomationHandlder : ILa
{ {
public async ValueTask OnExecutionAsync(LaunchExecutionContext context, LaunchExecutionDelegate next) public async ValueTask OnExecutionAsync(LaunchExecutionContext context, LaunchExecutionDelegate next)
{ {
if (context.Options.UseBetterGenshinImpactAutomation) if (!context.Process.HasExited && context.Options.UseBetterGenshinImpactAutomation)
{ {
context.Logger.LogInformation("Using BetterGI to automate gameplay"); context.Logger.LogInformation("Using BetterGI to automate gameplay");
await LaunchBetterGenshinImpactAsync(context).ConfigureAwait(false); await LaunchBetterGenshinImpactAsync(context).ConfigureAwait(false);
@@ -26,11 +26,12 @@ internal sealed class LaunchExecutionBetterGenshinImpactAutomationHandlder : ILa
try try
{ {
context.Logger.LogInformation("Waiting game window to be ready"); context.Logger.LogInformation("Waiting game window to be ready");
context.Process.WaitForInputIdle();
SpinWait.SpinUntil(() => context.Process.MainWindowHandle != IntPtr.Zero);
} }
catch (InvalidOperationException) catch (InvalidOperationException)
{ {
context.Logger.LogInformation("Failed to wait Input idle waiting"); context.Logger.LogInformation("Failed to get game window handle");
return; return;
} }

View File

@@ -9,7 +9,7 @@ internal sealed class LaunchExecutionStarwardPlayTimeStatisticsHandler : ILaunch
{ {
public async ValueTask OnExecutionAsync(LaunchExecutionContext context, LaunchExecutionDelegate next) public async ValueTask OnExecutionAsync(LaunchExecutionContext context, LaunchExecutionDelegate next)
{ {
if (context.Options.UseStarwardPlayTimeStatistics) if (!context.Process.HasExited && context.Options.UseStarwardPlayTimeStatistics)
{ {
context.Logger.LogInformation("Using Starward to count game time"); context.Logger.LogInformation("Using Starward to count game time");
await LaunchStarwardForPlayTimeStatisticsAsync(context).ConfigureAwait(false); await LaunchStarwardForPlayTimeStatisticsAsync(context).ConfigureAwait(false);

View File

@@ -18,15 +18,17 @@ internal sealed class LaunchExecutionUnlockFpsHandler : ILaunchExecutionDelegate
context.Progress.Report(new(LaunchPhase.UnlockingFps, SH.ServiceGameLaunchPhaseUnlockingFps)); context.Progress.Report(new(LaunchPhase.UnlockingFps, SH.ServiceGameLaunchPhaseUnlockingFps));
IProgressFactory progressFactory = context.ServiceProvider.GetRequiredService<IProgressFactory>(); IProgressFactory progressFactory = context.ServiceProvider.GetRequiredService<IProgressFactory>();
IProgress<GameFpsUnlockerState> progress = progressFactory.CreateForMainThread<GameFpsUnlockerState>(status => context.Progress.Report(LaunchStatus.FromUnlockState(status))); IProgress<GameFpsUnlockerContext> progress = progressFactory.CreateForMainThread<GameFpsUnlockerContext>(c => context.Progress.Report(LaunchStatus.FromUnlockerContext(c)));
GameFpsUnlocker unlocker = new(context.ServiceProvider, context.Process, new(100, 20000, 3000), progress); GameFpsUnlocker unlocker = new(context.ServiceProvider, context.Process, new(100, 20000, 3000), progress);
try try
{ {
await unlocker.UnlockAsync(context.CancellationToken).ConfigureAwait(false); if (await unlocker.UnlockAsync(context.CancellationToken).ConfigureAwait(false))
unlocker.PostUnlockAsync(context.CancellationToken).SafeForget(); {
unlocker.PostUnlockAsync(context.CancellationToken).SafeForget();
}
} }
catch (InvalidOperationException ex) catch (Exception ex)
{ {
context.Logger.LogCritical(ex, "Unlocking FPS failed"); context.Logger.LogCritical(ex, "Unlocking FPS failed");

View File

@@ -16,7 +16,7 @@ internal static class GameFpsAddress
private const byte ASM_JMP = 0xE9; private const byte ASM_JMP = 0xE9;
#pragma warning restore SA1310 #pragma warning restore SA1310
public static unsafe void UnsafeFindFpsAddress(GameFpsUnlockerState state, in RequiredGameModule requiredGameModule) public static unsafe void UnsafeFindFpsAddress(GameFpsUnlockerContext state, in RequiredGameModule requiredGameModule)
{ {
bool readOk = UnsafeReadModulesMemory(state.GameProcess, requiredGameModule, out VirtualMemory localMemory); bool readOk = UnsafeReadModulesMemory(state.GameProcess, requiredGameModule, out VirtualMemory localMemory);
HutaoException.ThrowIfNot(readOk, HutaoExceptionKind.GameFpsUnlockingFailed, SH.ServiceGameUnlockerReadModuleMemoryCopyVirtualMemoryFailed); HutaoException.ThrowIfNot(readOk, HutaoExceptionKind.GameFpsUnlockingFailed, SH.ServiceGameUnlockerReadModuleMemoryCopyVirtualMemoryFailed);

View File

@@ -16,45 +16,46 @@ namespace Snap.Hutao.Service.Game.Unlocker;
internal sealed class GameFpsUnlocker : IGameFpsUnlocker internal sealed class GameFpsUnlocker : IGameFpsUnlocker
{ {
private readonly LaunchOptions launchOptions; private readonly LaunchOptions launchOptions;
private readonly GameFpsUnlockerState state = new(); private readonly GameFpsUnlockerContext context = new();
public GameFpsUnlocker(IServiceProvider serviceProvider, Process gameProcess, in UnlockTimingOptions options, IProgress<GameFpsUnlockerState> progress) public GameFpsUnlocker(IServiceProvider serviceProvider, Process gameProcess, in UnlockTimingOptions options, IProgress<GameFpsUnlockerContext> progress)
{ {
launchOptions = serviceProvider.GetRequiredService<LaunchOptions>(); launchOptions = serviceProvider.GetRequiredService<LaunchOptions>();
state.GameProcess = gameProcess; context.GameProcess = gameProcess;
state.TimingOptions = options; context.TimingOptions = options;
state.Progress = progress; context.Progress = progress;
} }
/// <inheritdoc/> /// <inheritdoc/>
public async ValueTask UnlockAsync(CancellationToken token = default) public async ValueTask<bool> UnlockAsync(CancellationToken token = default)
{ {
HutaoException.ThrowIfNot(state.IsUnlockerValid, HutaoExceptionKind.GameFpsUnlockingFailed, "This Unlocker is invalid"); HutaoException.ThrowIfNot(context.IsUnlockerValid, HutaoExceptionKind.GameFpsUnlockingFailed, "This Unlocker is invalid");
(FindModuleResult result, RequiredGameModule gameModule) = await GameProcessModule.FindModuleAsync(state).ConfigureAwait(false); (FindModuleResult result, RequiredGameModule gameModule) = await GameProcessModule.FindModuleAsync(context).ConfigureAwait(false);
HutaoException.ThrowIfNot(result != FindModuleResult.TimeLimitExeeded, HutaoExceptionKind.GameFpsUnlockingFailed, SH.ServiceGameUnlockerFindModuleTimeLimitExeeded); HutaoException.ThrowIfNot(result != FindModuleResult.TimeLimitExeeded, HutaoExceptionKind.GameFpsUnlockingFailed, SH.ServiceGameUnlockerFindModuleTimeLimitExeeded);
HutaoException.ThrowIfNot(result != FindModuleResult.NoModuleFound, HutaoExceptionKind.GameFpsUnlockingFailed, SH.ServiceGameUnlockerFindModuleNoModuleFound); HutaoException.ThrowIfNot(result != FindModuleResult.NoModuleFound, HutaoExceptionKind.GameFpsUnlockingFailed, SH.ServiceGameUnlockerFindModuleNoModuleFound);
GameFpsAddress.UnsafeFindFpsAddress(state, gameModule); GameFpsAddress.UnsafeFindFpsAddress(context, gameModule);
state.Report(); context.Report();
return context.FpsAddress != 0U;
} }
public async ValueTask PostUnlockAsync(CancellationToken token = default) public async ValueTask PostUnlockAsync(CancellationToken token = default)
{ {
using (PeriodicTimer timer = new(state.TimingOptions.AdjustFpsDelay)) using (PeriodicTimer timer = new(context.TimingOptions.AdjustFpsDelay))
{ {
while (await timer.WaitForNextTickAsync(token).ConfigureAwait(false)) while (await timer.WaitForNextTickAsync(token).ConfigureAwait(false))
{ {
if (!state.GameProcess.HasExited && state.FpsAddress != 0U) if (!context.GameProcess.HasExited && context.FpsAddress != 0U)
{ {
UnsafeWriteProcessMemory(state.GameProcess, state.FpsAddress, launchOptions.TargetFps); UnsafeWriteProcessMemory(context.GameProcess, context.FpsAddress, launchOptions.TargetFps);
state.Report(); context.Report();
} }
else else
{ {
state.IsUnlockerValid = false; context.IsUnlockerValid = false;
state.FpsAddress = 0; context.FpsAddress = 0;
state.Report(); context.Report();
return; return;
} }
} }

View File

@@ -8,7 +8,7 @@ namespace Snap.Hutao.Service.Game.Unlocker;
/// <summary> /// <summary>
/// 解锁状态 /// 解锁状态
/// </summary> /// </summary>
internal sealed class GameFpsUnlockerState internal sealed class GameFpsUnlockerContext
{ {
public string Description { get; set; } = default!; public string Description { get; set; } = default!;
@@ -22,7 +22,7 @@ internal sealed class GameFpsUnlockerState
public Process GameProcess { get; set; } = default!; public Process GameProcess { get; set; } = default!;
public IProgress<GameFpsUnlockerState> Progress { get; set; } = default!; public IProgress<GameFpsUnlockerContext> Progress { get; set; } = default!;
public void Report() public void Report()
{ {

View File

@@ -13,7 +13,7 @@ namespace Snap.Hutao.Service.Game.Unlocker;
internal static class GameProcessModule internal static class GameProcessModule
{ {
public static async ValueTask<ValueResult<FindModuleResult, RequiredGameModule>> FindModuleAsync(GameFpsUnlockerState state) public static async ValueTask<ValueResult<FindModuleResult, RequiredGameModule>> FindModuleAsync(GameFpsUnlockerContext state)
{ {
ValueStopwatch watch = ValueStopwatch.StartNew(); ValueStopwatch watch = ValueStopwatch.StartNew();
using (PeriodicTimer timer = new(state.TimingOptions.FindModuleDelay)) using (PeriodicTimer timer = new(state.TimingOptions.FindModuleDelay))

View File

@@ -11,5 +11,5 @@ internal interface IGameFpsUnlocker
{ {
ValueTask PostUnlockAsync(CancellationToken token = default); ValueTask PostUnlockAsync(CancellationToken token = default);
ValueTask UnlockAsync(CancellationToken token = default); ValueTask<bool> UnlockAsync(CancellationToken token = default);
} }

View File

@@ -16,7 +16,7 @@ namespace Snap.Hutao.Service.Hutao;
[Injection(InjectAs.Scoped, typeof(IHutaoSpiralAbyssService))] [Injection(InjectAs.Scoped, typeof(IHutaoSpiralAbyssService))]
internal sealed partial class HutaoSpiralAbyssService : IHutaoSpiralAbyssService internal sealed partial class HutaoSpiralAbyssService : IHutaoSpiralAbyssService
{ {
private readonly TimeSpan cacheExpireTime = TimeSpan.FromHours(4); private readonly TimeSpan cacheExpireTime = TimeSpan.FromHours(1);
private readonly IObjectCacheDbService objectCacheDbService; private readonly IObjectCacheDbService objectCacheDbService;
private readonly HutaoSpiralAbyssClient homaClient; private readonly HutaoSpiralAbyssClient homaClient;

View File

@@ -302,8 +302,8 @@
<PackageReference Include="CommunityToolkit.WinUI.Controls.TokenizingTextBox" Version="8.0.240109" /> <PackageReference Include="CommunityToolkit.WinUI.Controls.TokenizingTextBox" Version="8.0.240109" />
<PackageReference Include="CommunityToolkit.WinUI.Media" Version="8.0.240109" /> <PackageReference Include="CommunityToolkit.WinUI.Media" Version="8.0.240109" />
<PackageReference Include="CommunityToolkit.WinUI.Notifications" Version="7.1.2" /> <PackageReference Include="CommunityToolkit.WinUI.Notifications" Version="7.1.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.2" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.3" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.2"> <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.3">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
@@ -311,14 +311,14 @@
<PackageReference Include="Microsoft.Extensions.Http" Version="8.0.0" /> <PackageReference Include="Microsoft.Extensions.Http" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="8.0.0" /> <PackageReference Include="Microsoft.Extensions.Logging.Console" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="8.0.0" /> <PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="8.0.0" />
<PackageReference Include="Microsoft.Graphics.Win2D" Version="1.1.1" /> <PackageReference Include="Microsoft.Graphics.Win2D" Version="1.2.0" />
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="8.0.0"> <PackageReference Include="Microsoft.SourceLink.GitHub" Version="8.0.0">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
<PackageReference Include="Microsoft.VisualStudio.Validation" Version="17.8.8" /> <PackageReference Include="Microsoft.VisualStudio.Validation" Version="17.8.8" />
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.22621.3233" /> <PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.22621.3233" />
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.5.240227000" /> <PackageReference Include="Microsoft.WindowsAppSDK" Version="1.5.240311000" />
<PackageReference Include="QRCoder" Version="1.4.3" /> <PackageReference Include="QRCoder" Version="1.4.3" />
<PackageReference Include="Snap.Discord.GameSDK" Version="1.6.0" /> <PackageReference Include="Snap.Discord.GameSDK" Version="1.6.0" />
<PackageReference Include="Snap.Hutao.Deployment.Runtime" Version="1.15.3"> <PackageReference Include="Snap.Hutao.Deployment.Runtime" Version="1.15.3">

View File

@@ -70,7 +70,7 @@
Maximum="{Binding GuaranteeOrangeThreshold}" Maximum="{Binding GuaranteeOrangeThreshold}"
ProgressForeground="{StaticResource OrangeColorBrush}" ProgressForeground="{StaticResource OrangeColorBrush}"
TextForeground="{StaticResource OrangeColorBrush}" TextForeground="{StaticResource OrangeColorBrush}"
Value="{Binding LastOrangePull}"/> Value="{Binding LastOrangePull, Mode=OneWay}"/>
<shvcp:CardProgressBar <shvcp:CardProgressBar
Grid.Column="0" Grid.Column="0"
Description="{Binding LastPurplePull}" Description="{Binding LastPurplePull}"

View File

@@ -13,12 +13,9 @@ namespace Snap.Hutao.View.Card.Primitive;
[DependencyProperty("Value", typeof(double))] [DependencyProperty("Value", typeof(double))]
[DependencyProperty("Header", typeof(string))] [DependencyProperty("Header", typeof(string))]
[DependencyProperty("Description", typeof(string))] [DependencyProperty("Description", typeof(string))]
internal sealed partial class CardProgressBar : Grid internal sealed partial class CardProgressBar : ContentControl
{ {
public CardProgressBar() public CardProgressBar()
{ {
IAppResourceProvider appResourceProvider = Ioc.Default.GetRequiredService<IAppResourceProvider>();
TextForeground = appResourceProvider.GetResource<Brush>("TextFillColorPrimaryBrush");
InitializeComponent();
} }
} }

View File

@@ -1,39 +1,49 @@
<Grid <ResourceDictionary
x:Class="Snap.Hutao.View.Card.Primitive.CardProgressBar"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:shvcp="using:Snap.Hutao.View.Card.Primitive">
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
MinHeight="40"
Style="{ThemeResource GridCardStyle}"
mc:Ignorable="d">
<Grid.ColumnDefinitions> <Style BasedOn="{StaticResource DefaultCardProgressBarStyle}" TargetType="shvcp:CardProgressBar"/>
<ColumnDefinition Width="auto"/>
<ColumnDefinition/> <Style x:Key="DefaultCardProgressBarStyle" TargetType="shvcp:CardProgressBar">
</Grid.ColumnDefinitions> <Setter Property="TextForeground" Value="{ThemeResource TextFillColorPrimaryBrush}"/>
<ProgressBar <Setter Property="MinHeight" Value="40"/>
Grid.ColumnSpan="2" <Setter Property="Template">
MinHeight="{x:Bind MinHeight, Mode=OneWay}" <Setter.Value>
Background="Transparent" <ControlTemplate>
CornerRadius="{StaticResource ControlCornerRadius}" <Grid MinHeight="{TemplateBinding MinHeight}" Style="{ThemeResource GridCardStyle}">
Foreground="{x:Bind ProgressForeground, Mode=OneWay}" <Grid.ColumnDefinitions>
Maximum="{x:Bind Maximum, Mode=OneWay}" <ColumnDefinition Width="auto"/>
Opacity="{StaticResource LargeBackgroundProgressBarOpacity}" <ColumnDefinition/>
Value="{x:Bind Value, Mode=OneWay}"/> </Grid.ColumnDefinitions>
<TextBlock <ProgressBar
Grid.Column="0" Grid.ColumnSpan="2"
Margin="6,0" MinHeight="{TemplateBinding MinHeight}"
HorizontalAlignment="Center" Background="Transparent"
VerticalAlignment="Center" CornerRadius="{StaticResource ControlCornerRadius}"
Foreground="{x:Bind TextForeground, Mode=OneWay}" Foreground="{Binding ProgressForeground, Mode=OneWay, RelativeSource={RelativeSource TemplatedParent}}"
Style="{StaticResource CaptionTextBlockStyle}" Maximum="{Binding Maximum, Mode=OneWay, RelativeSource={RelativeSource TemplatedParent}}"
Text="{x:Bind Header, Mode=OneWay}"/> Opacity="{StaticResource LargeBackgroundProgressBarOpacity}"
<TextBlock Value="{Binding Value, Mode=OneWay, RelativeSource={RelativeSource TemplatedParent}}"/>
Grid.Column="1" <TextBlock
HorizontalAlignment="Center" Grid.Column="0"
VerticalAlignment="Center" Margin="6,0"
Foreground="{x:Bind TextForeground, Mode=OneWay}" HorizontalAlignment="Center"
Style="{StaticResource CaptionTextBlockStyle}" VerticalAlignment="Center"
Text="{x:Bind Description, Mode=OneWay}"/> Foreground="{Binding TextForeground, Mode=OneWay, RelativeSource={RelativeSource TemplatedParent}}"
</Grid> Style="{StaticResource CaptionTextBlockStyle}"
Text="{Binding Header, Mode=OneWay, RelativeSource={RelativeSource TemplatedParent}}"/>
<TextBlock
Grid.Column="1"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Foreground="{Binding TextForeground, Mode=OneWay, RelativeSource={RelativeSource TemplatedParent}}"
Style="{StaticResource CaptionTextBlockStyle}"
Text="{Binding Description, Mode=OneWay, RelativeSource={RelativeSource TemplatedParent}}"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>

View File

@@ -52,41 +52,49 @@
Margin="0,16,0,8" Margin="0,16,0,8"
Foreground="{ThemeResource UpPullColorBrush}" Foreground="{ThemeResource UpPullColorBrush}"
Style="{StaticResource BaseTextBlockStyle}" Style="{StaticResource BaseTextBlockStyle}"
Text="{shcm:ResourceString Name=ViewControlStatisticsCardUpText}"/> Text="{shcm:ResourceString Name=ViewControlStatisticsCardUpText}"
Visibility="{Binding UpItems.Count, Converter={StaticResource Int32ToVisibilityConverter}}"/>
<ItemsControl <ItemsControl
ItemTemplate="{StaticResource GridTemplate}" ItemTemplate="{StaticResource GridTemplate}"
ItemsPanel="{StaticResource WrapPanelSpacing4Template}" ItemsPanel="{StaticResource WrapPanelSpacing4Template}"
ItemsSource="{Binding UpItems}"/> ItemsSource="{Binding UpItems}"
Visibility="{Binding UpItems.Count, Converter={StaticResource Int32ToVisibilityConverter}}"/>
<TextBlock <TextBlock
Margin="0,16,0,8" Margin="0,16,0,8"
Foreground="{ThemeResource OrangeColorBrush}" Foreground="{ThemeResource OrangeColorBrush}"
Style="{StaticResource BaseTextBlockStyle}" Style="{StaticResource BaseTextBlockStyle}"
Text="{shcm:ResourceString Name=ViewControlStatisticsCardOrangeText}"/> Text="{shcm:ResourceString Name=ViewControlStatisticsCardOrangeText}"
Visibility="{Binding OrangeItems.Count, Converter={StaticResource Int32ToVisibilityConverter}}"/>
<ItemsControl <ItemsControl
ItemTemplate="{StaticResource GridTemplate}" ItemTemplate="{StaticResource GridTemplate}"
ItemsPanel="{StaticResource WrapPanelSpacing4Template}" ItemsPanel="{StaticResource WrapPanelSpacing4Template}"
ItemsSource="{Binding OrangeItems}"/> ItemsSource="{Binding OrangeItems}"
Visibility="{Binding OrangeItems.Count, Converter={StaticResource Int32ToVisibilityConverter}}"/>
<TextBlock <TextBlock
Margin="0,16,0,8" Margin="0,16,0,8"
Foreground="{ThemeResource PurpleColorBrush}" Foreground="{ThemeResource PurpleColorBrush}"
Style="{StaticResource BaseTextBlockStyle}" Style="{StaticResource BaseTextBlockStyle}"
Text="{shcm:ResourceString Name=ViewControlStatisticsCardPurpleText}"/> Text="{shcm:ResourceString Name=ViewControlStatisticsCardPurpleText}"
Visibility="{Binding PurpleItems.Count, Converter={StaticResource Int32ToVisibilityConverter}}"/>
<ItemsControl <ItemsControl
ItemTemplate="{StaticResource GridTemplate}" ItemTemplate="{StaticResource GridTemplate}"
ItemsPanel="{StaticResource WrapPanelSpacing4Template}" ItemsPanel="{StaticResource WrapPanelSpacing4Template}"
ItemsSource="{Binding PurpleItems}"/> ItemsSource="{Binding PurpleItems}"
Visibility="{Binding PurpleItems.Count, Converter={StaticResource Int32ToVisibilityConverter}}"/>
<TextBlock <TextBlock
Margin="0,16,0,8" Margin="0,16,0,8"
Foreground="{ThemeResource BlueColorBrush}" Foreground="{ThemeResource BlueColorBrush}"
Style="{StaticResource BaseTextBlockStyle}" Style="{StaticResource BaseTextBlockStyle}"
Text="{shcm:ResourceString Name=ViewControlStatisticsCardBlueText}"/> Text="{shcm:ResourceString Name=ViewControlStatisticsCardBlueText}"
Visibility="{Binding BlueItems.Count, Converter={StaticResource Int32ToVisibilityConverter}}"/>
<ItemsControl <ItemsControl
ItemTemplate="{StaticResource GridTemplate}" ItemTemplate="{StaticResource GridTemplate}"
ItemsPanel="{StaticResource WrapPanelSpacing4Template}" ItemsPanel="{StaticResource WrapPanelSpacing4Template}"
ItemsSource="{Binding BlueItems}"/> ItemsSource="{Binding BlueItems}"
Visibility="{Binding BlueItems.Count, Converter={StaticResource Int32ToVisibilityConverter}}"/>
</StackPanel> </StackPanel>
</ScrollViewer> </ScrollViewer>
</Grid> </Grid>

View File

@@ -479,10 +479,12 @@
<ColumnDefinition/> <ColumnDefinition/>
<ColumnDefinition/> <ColumnDefinition/>
<ColumnDefinition/> <ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<shvc:HutaoStatisticsCard Grid.Column="0" DataContext="{Binding HutaoCloudStatisticsViewModel.Statistics.AvatarEvent}"/> <shvc:HutaoStatisticsCard Grid.Column="0" DataContext="{Binding HutaoCloudStatisticsViewModel.Statistics.AvatarEvent}"/>
<shvc:HutaoStatisticsCard Grid.Column="1" DataContext="{Binding HutaoCloudStatisticsViewModel.Statistics.AvatarEvent2}"/> <shvc:HutaoStatisticsCard Grid.Column="1" DataContext="{Binding HutaoCloudStatisticsViewModel.Statistics.AvatarEvent2}"/>
<shvc:HutaoStatisticsCard Grid.Column="2" DataContext="{Binding HutaoCloudStatisticsViewModel.Statistics.WeaponEvent}"/> <shvc:HutaoStatisticsCard Grid.Column="2" DataContext="{Binding HutaoCloudStatisticsViewModel.Statistics.WeaponEvent}"/>
<shvc:HutaoStatisticsCard Grid.Column="3" DataContext="{Binding HutaoCloudStatisticsViewModel.Statistics.Chronicled}"/>
</Grid> </Grid>
</Grid> </Grid>
</PivotItem> </PivotItem>

View File

@@ -317,7 +317,6 @@
QueryIcon="{cw:FontIconSource Glyph=&#xE721;}" QueryIcon="{cw:FontIconSource Glyph=&#xE721;}"
Style="{StaticResource DefaultTokenizingTextBoxStyle}" Style="{StaticResource DefaultTokenizingTextBoxStyle}"
SuggestedItemTemplate="{StaticResource TokenTemplate}" SuggestedItemTemplate="{StaticResource TokenTemplate}"
SuggestedItemsSource="{Binding AvailableTokens.Values}"
Text="{Binding FilterToken, Mode=TwoWay}" Text="{Binding FilterToken, Mode=TwoWay}"
TokenItemTemplate="{StaticResource TokenTemplate}"> TokenItemTemplate="{StaticResource TokenTemplate}">
<shca:AutoSuggestTokenBox.ItemsPanel> <shca:AutoSuggestTokenBox.ItemsPanel>

View File

@@ -185,7 +185,6 @@
PlaceholderText="{shcm:ResourceString Name=ViewPageWiKiWeaponAutoSuggestBoxPlaceHolder}" PlaceholderText="{shcm:ResourceString Name=ViewPageWiKiWeaponAutoSuggestBoxPlaceHolder}"
QueryIcon="{cw:FontIconSource Glyph=&#xE721;}" QueryIcon="{cw:FontIconSource Glyph=&#xE721;}"
SuggestedItemTemplate="{StaticResource TokenTemplate}" SuggestedItemTemplate="{StaticResource TokenTemplate}"
SuggestedItemsSource="{Binding AvailableTokens.Values}"
Text="{Binding FilterToken, Mode=TwoWay}" Text="{Binding FilterToken, Mode=TwoWay}"
TokenItemTemplate="{StaticResource TokenTemplate}"> TokenItemTemplate="{StaticResource TokenTemplate}">
<shca:AutoSuggestTokenBox.ItemsPanel> <shca:AutoSuggestTokenBox.ItemsPanel>

View File

@@ -1,6 +1,7 @@
// Copyright (c) DGP Studio. All rights reserved. // Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license. // Licensed under the MIT license.
using System.IO;
using System.Text; using System.Text;
namespace Snap.Hutao.Web.Hoyolab.SdkStatic.Hk4e.Launcher.Resource; namespace Snap.Hutao.Web.Hoyolab.SdkStatic.Hk4e.Launcher.Resource;
@@ -16,6 +17,6 @@ internal static class LatestPackageExtension
} }
latest.Path = pathBuilder.ToStringTrimEndReturn(); latest.Path = pathBuilder.ToStringTrimEndReturn();
latest.Name = latest.Segments[0].Path[..^4]; // .00X latest.Name = Path.GetFileName(latest.Segments[0].Path[..^4]); // .00X
} }
} }

View File

@@ -4,6 +4,7 @@
using System.Buffers.Binary; using System.Buffers.Binary;
using System.Numerics; using System.Numerics;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using Windows.Foundation;
using Windows.Graphics; using Windows.Graphics;
namespace Snap.Hutao.Win32; namespace Snap.Hutao.Win32;
@@ -26,45 +27,26 @@ internal static class StructMarshal
return color; return color;
} }
/// <summary> public static Rect Rect(Vector2 size)
/// 从 0,0 点构造一个新的<see cref="Windows.Graphics.RectInt32"/> {
/// </summary> return new(0, 0, size.X, size.Y);
/// <param name="size">尺寸</param> }
/// <returns>新的实例</returns>
public static RectInt32 RectInt32(SizeInt32 size) public static RectInt32 RectInt32(SizeInt32 size)
{ {
return new(0, 0, size.Width, size.Height); return new(0, 0, size.Width, size.Height);
} }
/// <summary>
/// 构造一个新的<see cref="Windows.Graphics.RectInt32"/>
/// </summary>
/// <param name="point">左上角位置</param>
/// <param name="size">尺寸</param>
/// <returns>新的实例</returns>
public static RectInt32 RectInt32(PointInt32 point, Vector2 size) public static RectInt32 RectInt32(PointInt32 point, Vector2 size)
{ {
return RectInt32(point.X, point.Y, size); return RectInt32(point.X, point.Y, size);
} }
/// <summary>
/// 构造一个新的<see cref="Windows.Graphics.RectInt32"/>
/// </summary>
/// <param name="x">X</param>
/// <param name="y">Y</param>
/// <param name="size">尺寸</param>
/// <returns>新的实例</returns>
public static RectInt32 RectInt32(int x, int y, Vector2 size) public static RectInt32 RectInt32(int x, int y, Vector2 size)
{ {
return new(x, y, (int)size.X, (int)size.Y); return new(x, y, (int)size.X, (int)size.Y);
} }
/// <summary>
/// 构造一个新的<see cref="Windows.Graphics.RectInt32"/>
/// </summary>
/// <param name="point">左上角位置</param>
/// <param name="size">尺寸</param>
/// <returns>新的实例</returns>
public static RectInt32 RectInt32(PointInt32 point, SizeInt32 size) public static RectInt32 RectInt32(PointInt32 point, SizeInt32 size)
{ {
return new(point.X, point.Y, size.Width, size.Height); return new(point.X, point.Y, size.Width, size.Height);