Compare commits

...

69 Commits

Author SHA1 Message Date
qhy040404
660d79ce9f self impl segmented
to fix layout cycle
2024-03-06 23:10:41 +08:00
Lightczx
baa4b88622 refine spiralabyss team ui 2024-03-06 17:11:23 +08:00
DismissedLight
de2ce0db63 Merge pull request #1444 from DGP-Studio/feat/refresh_ui_for_team_appearence 2024-03-06 15:47:45 +08:00
Lightczx
6cbf8ca918 code style 2024-03-06 15:43:46 +08:00
DismissedLight
df125904a6 Merge branch 'develop' into feat/refresh_ui_for_team_appearence 2024-03-06 14:20:36 +08:00
Lightczx
1bad9d0928 fix #1449 2024-03-06 14:19:04 +08:00
DismissedLight
a762166db4 Merge branch 'develop' into feat/refresh_ui_for_team_appearence 2024-03-06 14:09:23 +08:00
DismissedLight
f432e5ba42 Merge pull request #1437 from DGP-Studio/activity-city-banner-compat 2024-03-06 14:07:47 +08:00
Lightczx
46dd4db25c fixup endid indexer setter 2024-03-06 14:04:04 +08:00
Lightczx
2e685182e6 fixup endid indexer getter 2024-03-06 14:01:53 +08:00
Lightczx
4f1bbd2e89 typo fix 2024-03-06 13:58:34 +08:00
qhy040404
0f5b6dda16 refresh ui for team appearance 2024-03-06 12:02:56 +08:00
Lightczx
4c38bb528f basic support 2024-03-06 11:57:37 +08:00
Lightczx
44e7f7482c minor adjustment 2024-03-06 11:55:37 +08:00
Lightczx
8a2fa3c701 factory completed 2024-03-06 11:55:37 +08:00
DismissedLight
6e10819609 Merge pull request #1441 from DGP-Studio/dependabot/nuget/src/Snap.Hutao/develop/packages-15667d2017 2024-03-06 09:16:27 +08:00
dependabot[bot]
c1b838bd97 Bump the packages group in /src/Snap.Hutao with 2 updates
Bumps the packages group in /src/Snap.Hutao with 2 updates: [MSTest.TestAdapter](https://github.com/microsoft/testfx) and [MSTest.TestFramework](https://github.com/microsoft/testfx).


Updates `MSTest.TestAdapter` from 3.2.1 to 3.2.2
- [Release notes](https://github.com/microsoft/testfx/releases)
- [Changelog](https://github.com/microsoft/testfx/blob/main/docs/Changelog.md)
- [Commits](https://github.com/microsoft/testfx/compare/v3.2.1...v3.2.2)

Updates `MSTest.TestFramework` from 3.2.1 to 3.2.2
- [Release notes](https://github.com/microsoft/testfx/releases)
- [Changelog](https://github.com/microsoft/testfx/blob/main/docs/Changelog.md)
- [Commits](https://github.com/microsoft/testfx/compare/v3.2.1...v3.2.2)

---
updated-dependencies:
- dependency-name: MSTest.TestAdapter
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: packages
- dependency-name: MSTest.TestFramework
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: packages
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-05 16:03:28 +00:00
DismissedLight
268c4c6c71 Merge pull request #1421 from DGP-Studio/feat/wikisuggestbox 2024-03-05 23:59:39 +08:00
DismissedLight
59dabaa5be code style 2024-03-05 23:59:23 +08:00
DismissedLight
dcac7ac792 Merge pull request #1431 from DGP-Studio/fix/minor 2024-03-05 21:45:00 +08:00
qhy040404
2ad6dad282 minor fix 2024-03-05 17:28:01 +08:00
qhy040404
4185336556 minor ui adjustment 2024-03-05 17:10:31 +08:00
qhy040404
15a627e50a init all tokens first 2024-03-05 16:55:28 +08:00
qhy040404
2ea53fd39d Frozen Dictionaries 2024-03-04 18:02:00 +08:00
qhy040404
6de3cba550 remove unused code 2024-03-04 00:09:38 +08:00
qhy040404
03c1bacfe9 maybe code style 2024-03-04 00:01:52 +08:00
qhy040404
d157476a9a minor fix 2024-03-03 21:37:44 +08:00
qhy040404
e35c03ac90 support or in wiki search 2024-03-03 21:03:28 +08:00
qhy040404
9619835cc2 migrate to TokenizingTextBox 2024-03-03 16:23:25 +08:00
qhy040404
6936a30a74 finish suggestion methods 2024-03-03 16:23:19 +08:00
Lightczx
506d198167 update to was 1.5 2024-03-01 09:30:43 +08:00
DismissedLight
9c2131cb9d Merge pull request #1426 from DGP-Studio/dependabot/nuget/src/Snap.Hutao/develop/packages-dcc47c4d6c 2024-02-27 09:21:58 +08:00
DismissedLight
896e3d49f2 Merge branch 'develop' into dependabot/nuget/src/Snap.Hutao/develop/packages-dcc47c4d6c 2024-02-27 09:21:30 +08:00
DismissedLight
7da24790c4 Merge pull request #1423 from Masterain98/main 2024-02-26 17:02:25 +08:00
DismissedLight
ebf163f200 Merge branch 'develop' into dependabot/nuget/src/Snap.Hutao/develop/packages-dcc47c4d6c 2024-02-26 16:59:23 +08:00
Lightczx
26043a6a73 Add furniture data struct 2024-02-26 16:35:36 +08:00
Masterain
ad67001556 Update LaunchGame.png 2024-02-26 15:28:25 +08:00
dependabot[bot]
428cd35a7f Bump the packages group in /src/Snap.Hutao with 3 updates
Bumps the packages group in /src/Snap.Hutao with 3 updates: [MSTest.TestAdapter](https://github.com/microsoft/testfx), [MSTest.TestFramework](https://github.com/microsoft/testfx) and [coverlet.collector](https://github.com/coverlet-coverage/coverlet).


Updates `MSTest.TestAdapter` from 3.2.1 to 3.2.2
- [Release notes](https://github.com/microsoft/testfx/releases)
- [Changelog](https://github.com/microsoft/testfx/blob/main/docs/Changelog.md)
- [Commits](https://github.com/microsoft/testfx/compare/v3.2.1...v3.2.2)

Updates `MSTest.TestFramework` from 3.2.1 to 3.2.2
- [Release notes](https://github.com/microsoft/testfx/releases)
- [Changelog](https://github.com/microsoft/testfx/blob/main/docs/Changelog.md)
- [Commits](https://github.com/microsoft/testfx/compare/v3.2.1...v3.2.2)

Updates `coverlet.collector` from 6.0.0 to 6.0.1
- [Release notes](https://github.com/coverlet-coverage/coverlet/releases)
- [Commits](https://github.com/coverlet-coverage/coverlet/compare/v6.0.0...v6.0.1)

---
updated-dependencies:
- dependency-name: MSTest.TestAdapter
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: packages
- dependency-name: MSTest.TestFramework
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: packages
- dependency-name: coverlet.collector
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: packages
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-26 07:16:52 +00:00
qhy040404
1cf3450264 fix static resource download 2024-02-25 23:29:45 +08:00
DismissedLight
1849ac16da adjust regex 2024-02-25 16:23:45 +08:00
DismissedLight
4e9d87af50 fix ja-jp announcement 2024-02-25 15:53:09 +08:00
DismissedLight
eb125e547f add mask when no backdrop 2024-02-24 20:24:46 +08:00
Lightczx
c17798a8c9 code style 2024-02-23 10:22:24 +08:00
DismissedLight
fa9b39d134 Merge pull request #1412 from DGP-Studio/fix/hotkey_notify 2024-02-23 09:42:10 +08:00
qhy040404
8dc85c7a25 prompt if hotkey not registered 2024-02-23 09:39:50 +08:00
DismissedLight
5845c718e1 Merge pull request #1419 from DGP-Studio/feat/real_time_bg 2024-02-23 09:36:25 +08:00
Lightczx
0c093851ed code style 2024-02-23 09:33:55 +08:00
qhy040404
04c187aa16 real time refresh background 2024-02-22 22:34:12 +08:00
Lightczx
25cf9f5356 code style 2024-02-22 10:26:43 +08:00
DismissedLight
c30c8b114a re-enable backdrop 2024-02-21 23:38:57 +08:00
DismissedLight
41f5a04e5a Merge branch 'main' into develop 2024-02-21 20:35:38 +08:00
DismissedLight
4f26d6efc2 update package version 2024-02-21 20:33:18 +08:00
Masterain
0d6b5e595e New Crowdin updates (#1413) 2024-02-21 20:29:41 +08:00
DismissedLight
e30bebd1a3 Update StatisticsCard.xaml 2024-02-21 20:25:56 +08:00
Masterain
ffb34c0e93 New Crowdin updates (#1411) 2024-02-21 20:25:12 +08:00
Lightczx
35b35ab649 minor fix 5 2024-02-21 16:29:30 +08:00
Lightczx
f067350cd8 minor fix 2024-02-21 15:28:05 +08:00
Lightczx
14648dafe8 adjust language order 2024-02-21 14:04:24 +08:00
Lightczx
7a7d81cfee add static resource hint for more situation 2024-02-21 13:55:07 +08:00
Lightczx
30c055e7ba impl #1373 #1389 2024-02-21 11:57:41 +08:00
qhy040404
6a922d9db6 更新 alpha.yml 2024-02-21 09:52:20 +08:00
DismissedLight
02a6e64a8c Merge pull request #1408 from DGP-Studio/fix/launch 2024-02-20 14:40:57 +08:00
qhy040404
85bcf37b1b Update alpha.yml 2024-02-20 10:43:27 +08:00
qhy040404
465c7035c0 Update alpha.yml 2024-02-20 10:31:23 +08:00
qhy040404
6241bbc997 Setup dependabot for actions 2024-02-19 17:57:17 +08:00
qhy040404
afbd8ec6ad Revert "Update alpha.yml"
This reverts commit 288cc841ac.
2024-02-19 17:41:32 +08:00
qhy040404
288cc841ac Update alpha.yml 2024-02-19 17:26:00 +08:00
qhy040404
e1784e2078 Update issue templates 2024-02-18 21:16:44 +08:00
qhy040404
635b52980b Update .gitlab-ci.yml 2024-02-07 21:13:59 +08:00
164 changed files with 3746 additions and 1021 deletions

View File

@@ -18,7 +18,7 @@ body:
options:
- label: 我已阅读 Snap Hutao 文档中的[常见问题](https://hut.ao/advanced/FAQ.html)和[常见程序异常](https://hut.ao/advanced/exceptions.html),我的问题没有在文档中得到解答
required: true
- label: 我知道文档站的导航栏中有**搜索功能**,且已经搜索过相关关键词
required: true
@@ -29,12 +29,12 @@ body:
id: winver
attributes:
label: Windows 版本
description: |
description: |
`Win+R` 输入 `winver` 回车后在打开的窗口第二行可以找到
placeholder: 22000.556
validations:
required: true
- type: input
id: shver
attributes:
@@ -48,10 +48,10 @@ body:
id: deviceid
attributes:
label: 设备 ID
description: |
description: |
在胡桃工具箱的反馈中心界面,你可以找到并复制你的设备 ID
如果你的问题涉及程序崩溃,请填写该项,这将有助于我们定位问题
如果你的程序已经无法启动,请下载并运行[诊断工具](https://github.com/DGP-Automation/ISSUE_TEMPLATES/releases/download/diagnosis_tools/Snap.Hutao.DiagTools.exe),它将显示你的设备 ID
如果你的程序已经无法启动,请下载并运行[诊断工具](https://github.com/DGP-Automation/ISSUE_TEMPLATES/releases/download/diagnosis_tools/Snap.Hutao.Diagnostic.Tooling.exe),它将显示你的设备 ID
validations:
required: false
@@ -79,7 +79,7 @@ body:
- 公告
- 其它
validations:
required: true
required: true
- type: textarea
id: what-happened
@@ -107,4 +107,3 @@ body:
options:
- label: 我认为上述的描述已经足以详细,以允许开发人员能复现该问题
required: true

View File

@@ -18,7 +18,7 @@ body:
options:
- label: I have read [FAQ page](https://hut.ao/advanced/FAQ.html) and [Exception page](https://hut.ao/advanced/exceptions.html) in Snap Hutao document, and my issue is not answered
required: true
- label: I and tried **search feature** in Snap Hutao document site, and no associated article
required: true
@@ -29,12 +29,12 @@ body:
id: winver
attributes:
label: Windows Version
description: |
description: |
Use `Win+R` and input `winver`, Windows build version is usually at the second line
placeholder: e.g. 22000.556
validations:
required: true
- type: input
id: shver
attributes:
@@ -48,10 +48,10 @@ body:
id: deviceid
attributes:
label: Device ID
description: |
description: |
In Snap Hutao's Feedback Center, you can find and copy your device ID
If your issue is about program crash, please fill this so we can dump the log and locate the source easier
If your program cannot startup, please download and run [Diagnosis Tool](https://github.com/DGP-Automation/ISSUE_TEMPLATES/releases/download/diagnosis_tools/Snap.Hutao.DiagTools.exe), it will shows your device ID.
If your program cannot startup, please download and run [Diagnostic Tooling](https://github.com/DGP-Automation/ISSUE_TEMPLATES/releases/download/diagnosis_tools/Snap.Hutao.Diagnostic.Tooling.exe), it will shows your device ID.
validations:
required: false
@@ -74,12 +74,12 @@ body:
- User Interface
- Snap Hutao Cloud
- Snap Hutao Account
- Checkin
- Checkin
- Wiki
- Announcement
- Other
validations:
required: true
required: true
- type: textarea
id: what-happened
@@ -107,4 +107,3 @@ body:
options:
- label: I believe the description above is detail enough to allow developers to reproduce the issue
required: true

View File

@@ -13,4 +13,8 @@ updates:
groups:
packages:
patterns:
- "*"
- "*"
- package-ecosystem: "github-actions"
directory: "/.github/workflows" # GitHub Workflows
schedule:
interval: "weekly"

View File

@@ -14,6 +14,8 @@ on:
- 'LICENSE'
- '**.yml'
pull_request:
branches:
- develop
paths-ignore:
- '.gitattributes'
- '.github/**'
@@ -22,16 +24,17 @@ on:
- '**.md'
- 'LICENSE'
- '**.yml'
- '**.resx'
jobs:
build:
runs-on: self-hosted
steps:
- name: Checkout
uses: actions/checkout@v4.1.1
uses: actions/checkout@v4
- name: Setup .NET
uses: actions/setup-dotnet@v4.0.0
uses: actions/setup-dotnet@v4
with:
dotnet-version: 8.0

View File

@@ -74,3 +74,4 @@ Refresh:
script:
- apt-get install -y curl
- curl -X PATCH "$PURGE_URL"
- curl -X POST -o /dev/null "$UPLOAD_OSS_URL"

View File

@@ -14,8 +14,8 @@
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.9.0" />
<PackageReference Include="MSTest.TestAdapter" Version="3.2.1" />
<PackageReference Include="MSTest.TestFramework" Version="3.2.1" />
<PackageReference Include="coverlet.collector" Version="6.0.0">
<PackageReference Include="MSTest.TestFramework" Version="3.2.2" />
<PackageReference Include="coverlet.collector" Version="6.0.1">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>

View File

@@ -6,8 +6,11 @@
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<XamlControlsResources/>
<ResourceDictionary Source="ms-appx:///CommunityToolkit.WinUI.Controls.TokenizingTextBox/TokenizingTextBox.xaml"/>
<ResourceDictionary Source="ms-appx:///Control/Loading.xaml"/>
<ResourceDictionary Source="ms-appx:///Control/Image/CachedImage.xaml"/>
<ResourceDictionary Source="ms-appx:///Control/Segmented/Segmented.xaml"/>
<ResourceDictionary Source="ms-appx:///Control/Segmented/SegmentedItem.xaml"/>
<ResourceDictionary Source="ms-appx:///Control/Theme/Card.xaml"/>
<ResourceDictionary Source="ms-appx:///Control/Theme/Color.xaml"/>
<ResourceDictionary Source="ms-appx:///Control/Theme/ComboBox.xaml"/>

View File

@@ -23,10 +23,12 @@ internal static class ControlAnimationConstants
/// <summary>
/// 图像淡入
/// </summary>
public static readonly TimeSpan ImageFadeIn = TimeSpan.FromSeconds(0.3);
public static readonly TimeSpan ImageScaleFadeIn = TimeSpan.FromSeconds(0.3);
/// <summary>
/// 图像淡出
/// </summary>
public static readonly TimeSpan ImageFadeOut = TimeSpan.FromSeconds(0.2);
public static readonly TimeSpan ImageScaleFadeOut = TimeSpan.FromSeconds(0.2);
public static readonly TimeSpan ImageOpacityFadeInOut = TimeSpan.FromSeconds(1);
}

View File

@@ -0,0 +1,67 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using CommunityToolkit.WinUI;
using CommunityToolkit.WinUI.Controls;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Controls.Primitives;
using Snap.Hutao.Control.Extension;
namespace Snap.Hutao.Control.AutoSuggestBox;
[DependencyProperty("FilterCommand", typeof(ICommand))]
[DependencyProperty("FilterCommandParameter", typeof(object))]
[DependencyProperty("AvailableTokens", typeof(IReadOnlyDictionary<string, SearchToken>))]
internal sealed partial class AutoSuggestTokenBox : TokenizingTextBox
{
public AutoSuggestTokenBox()
{
DefaultStyleKey = typeof(TokenizingTextBox);
TextChanged += OnFilterSuggestionRequested;
QuerySubmitted += OnQuerySubmitted;
TokenItemAdding += OnTokenItemAdding;
TokenItemAdded += OnTokenItemModified;
TokenItemRemoved += OnTokenItemModified;
}
private void OnFilterSuggestionRequested(Microsoft.UI.Xaml.Controls.AutoSuggestBox sender, AutoSuggestBoxTextChangedEventArgs args)
{
if (string.IsNullOrWhiteSpace(Text))
{
return;
}
if (args.Reason == AutoSuggestionBoxTextChangeReason.UserInput)
{
sender.ItemsSource = AvailableTokens.Values.Where(q => q.Value.Contains(Text, StringComparison.OrdinalIgnoreCase));
// TODO: CornerRadius
// Popup? popup = this.FindDescendant("SuggestionsPopup") as Popup;
}
}
private void OnQuerySubmitted(Microsoft.UI.Xaml.Controls.AutoSuggestBox sender, AutoSuggestBoxQuerySubmittedEventArgs args)
{
if (args.ChosenSuggestion is not null)
{
return;
}
CommandExtension.TryExecute(FilterCommand, FilterCommandParameter);
}
private void OnTokenItemAdding(TokenizingTextBox sender, TokenItemAddingEventArgs args)
{
if (string.IsNullOrWhiteSpace(args.TokenText))
{
return;
}
args.Item = AvailableTokens.GetValueOrDefault(args.TokenText) ?? new SearchToken(SearchTokenKind.None, args.TokenText);
}
private void OnTokenItemModified(TokenizingTextBox sender, object args)
{
CommandExtension.TryExecute(FilterCommand, FilterCommandParameter);
}
}

View File

@@ -0,0 +1,33 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Windows.UI;
namespace Snap.Hutao.Control.AutoSuggestBox;
internal sealed class SearchToken
{
public SearchToken(SearchTokenKind kind, string value, Uri? iconUri = null, Uri? sideIconUri = null, Color? quality = null)
{
Value = value;
Kind = kind;
IconUri = iconUri;
SideIconUri = sideIconUri;
Quality = quality;
}
public SearchTokenKind Kind { get; }
public string Value { get; set; } = default!;
public Uri? IconUri { get; }
public Uri? SideIconUri { get; }
public Color? Quality { get; }
public override string ToString()
{
return Value;
}
}

View File

@@ -0,0 +1,17 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Control.AutoSuggestBox;
internal enum SearchTokenKind
{
None,
AssociationType,
Avatar,
BodyType,
ElementName,
FightProperty,
ItemQuality,
Weapon,
WeaponType,
}

View File

@@ -3,6 +3,7 @@
using CommunityToolkit.WinUI.Behaviors;
using Microsoft.UI.Xaml;
using Snap.Hutao.Control.Extension;
namespace Snap.Hutao.Control.Behavior;
@@ -45,10 +46,6 @@ internal sealed partial class InvokeCommandOnLoadedBehavior : BehaviorBase<UIEle
return;
}
if (Command is not null && Command.CanExecute(CommandParameter))
{
Command.Execute(CommandParameter);
executed = true;
}
executed = Command.TryExecute(CommandParameter);
}
}

View File

@@ -0,0 +1,87 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using CommunityToolkit.WinUI.Behaviors;
using Microsoft.UI.Xaml;
using Snap.Hutao.Control.Extension;
namespace Snap.Hutao.Control.Behavior;
[DependencyProperty("Period", typeof(TimeSpan))]
[DependencyProperty("Command", typeof(ICommand))]
[DependencyProperty("CommandParameter", typeof(object))]
internal sealed partial class PeriodicInvokeCommandOrOnActualThemeChangedBehavior : BehaviorBase<FrameworkElement>, IDisposable
{
private TaskCompletionSource acutalThemeChangedTaskCompletionSource = new();
private CancellationTokenSource periodicTimerCancellationTokenSource = new();
public void Dispose()
{
periodicTimerCancellationTokenSource.Dispose();
}
protected override bool Initialize()
{
AssociatedObject.ActualThemeChanged += OnActualThemeChanged;
return true;
}
protected override void OnAssociatedObjectLoaded()
{
RunCoreAsync().SafeForget();
}
protected override bool Uninitialize()
{
AssociatedObject.ActualThemeChanged -= OnActualThemeChanged;
return true;
}
private void OnActualThemeChanged(FrameworkElement sender, object args)
{
acutalThemeChangedTaskCompletionSource.TrySetResult();
periodicTimerCancellationTokenSource.Cancel();
}
private void TryExecuteCommand()
{
if (AssociatedObject is null)
{
return;
}
Command.TryExecute(CommandParameter);
}
private async ValueTask RunCoreAsync()
{
using (PeriodicTimer timer = new(Period))
{
do
{
if (!IsAttached)
{
break;
}
ITaskContext taskContext = Ioc.Default.GetRequiredService<ITaskContext>();
await taskContext.SwitchToMainThreadAsync();
TryExecuteCommand();
await taskContext.SwitchToBackgroundAsync();
try
{
Task nextTickTask = timer.WaitForNextTickAsync(periodicTimerCancellationTokenSource.Token).AsTask();
await Task.WhenAny(nextTickTask, acutalThemeChangedTaskCompletionSource.Task).ConfigureAwait(false);
}
catch (OperationCanceledException)
{
}
acutalThemeChangedTaskCompletionSource = new();
periodicTimerCancellationTokenSource = new();
}
while (true);
}
}
}

View File

@@ -5,7 +5,6 @@ using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Media;
using Microsoft.UI.Xaml.Shapes;
using System.Collections.Specialized;
using System.Runtime.InteropServices;
namespace Snap.Hutao.Control.Brush;

View File

@@ -4,7 +4,6 @@
using CommunityToolkit.WinUI.Collections;
using CommunityToolkit.WinUI.Helpers;
using Microsoft.UI.Xaml.Data;
using Snap.Hutao.Core.ExceptionService;
using System.Collections;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
@@ -208,13 +207,11 @@ internal sealed class AdvancedCollectionView<T> : IAdvancedCollectionView<T>, IN
public void Add(T item)
{
ThrowHelper.NotSupportedIf(IsReadOnly, "Collection is read-only.");
source.Add(item);
}
public void Clear()
{
ThrowHelper.NotSupportedIf(IsReadOnly, "Collection is read-only.");
source.Clear();
}
@@ -230,7 +227,6 @@ internal sealed class AdvancedCollectionView<T> : IAdvancedCollectionView<T>, IN
public bool Remove(T item)
{
ThrowHelper.NotSupportedIf(IsReadOnly, "Collection is read-only.");
source.Remove(item);
return true;
}
@@ -243,7 +239,6 @@ internal sealed class AdvancedCollectionView<T> : IAdvancedCollectionView<T>, IN
public void Insert(int index, T item)
{
ThrowHelper.NotSupportedIf(IsReadOnly, "Collection is read-only.");
source.Insert(index, item);
}

View File

@@ -0,0 +1,18 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Control.Extension;
internal static class CommandExtension
{
public static bool TryExecute(this ICommand? command, object? parameter = null)
{
if (command is not null && command.CanExecute(parameter))
{
command.Execute(parameter);
return true;
}
return false;
}
}

View File

@@ -0,0 +1,24 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Microsoft.UI.Xaml;
namespace Snap.Hutao.Control.Helper;
[SuppressMessage("", "SH001")]
[DependencyProperty("VisibilityObject", typeof(object), null, nameof(OnVisibilityObjectChanged), IsAttached = true, AttachedType = typeof(UIElement))]
[DependencyProperty("OpacityObject", typeof(object), null, nameof(OnOpacityObjectChanged), IsAttached = true, AttachedType = typeof(UIElement))]
public sealed partial class UIElementHelper
{
private static void OnVisibilityObjectChanged(DependencyObject dp, DependencyPropertyChangedEventArgs e)
{
UIElement element = (UIElement)dp;
element.Visibility = e.NewValue is null ? Visibility.Collapsed : Visibility.Visible;
}
private static void OnOpacityObjectChanged(DependencyObject dp, DependencyPropertyChangedEventArgs e)
{
UIElement element = (UIElement)dp;
element.Opacity = e.NewValue is null ? 0D : 1D;
}
}

View File

@@ -0,0 +1,6 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Control;
internal interface IXamlElementAccessor;

View File

@@ -192,7 +192,7 @@ internal abstract partial class CompositionImage : Microsoft.UI.Xaml.Controls.Co
{
await AnimationBuilder
.Create()
.Opacity(from: 0D, to: 1D, duration: ControlAnimationConstants.ImageFadeIn)
.Opacity(from: 0D, to: 1D, duration: ControlAnimationConstants.ImageScaleFadeIn)
.StartAsync(this, token)
.ConfigureAwait(true);
}
@@ -213,7 +213,7 @@ internal abstract partial class CompositionImage : Microsoft.UI.Xaml.Controls.Co
{
await AnimationBuilder
.Create()
.Opacity(from: 1D, to: 0D, duration: ControlAnimationConstants.ImageFadeOut)
.Opacity(from: 1D, to: 0D, duration: ControlAnimationConstants.ImageScaleFadeOut)
.StartAsync(this, token)
.ConfigureAwait(true);
}

View File

@@ -1,20 +1,21 @@
<cwc:Segmented
<shcs:Segmented
x:Class="Snap.Hutao.Control.Panel.PanelSelector"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:cwc="using:CommunityToolkit.WinUI.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:shcm="using:Snap.Hutao.Control.Markup"
xmlns:shcs="using:Snap.Hutao.Control.Segmented"
Style="{StaticResource DefaultSegmentedStyle}"
mc:Ignorable="d">
<cwc:SegmentedItem
<shcs:SegmentedItem
Icon="{shcm:FontIcon Glyph={StaticResource FontIconContentBulletedList}}"
Tag="List"
ToolTipService.ToolTip="{shcm:ResourceString Name=ControlPanelPanelSelectorDropdownListName}"/>
<cwc:SegmentedItem
<shcs:SegmentedItem
Icon="{shcm:FontIcon Glyph={StaticResource FontIconContentGridView}}"
Tag="Grid"
ToolTipService.ToolTip="{shcm:ResourceString Name=ControlPanelPanelSelectorDropdownGridName}"/>
</cwc:Segmented>
</shcs:Segmented>

View File

@@ -1,9 +1,10 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using CommunityToolkit.WinUI.Controls;
using Microsoft.UI.Xaml;
using Snap.Hutao.Control.Segmented;
using Snap.Hutao.Core.Setting;
using System.Collections.Frozen;
namespace Snap.Hutao.Control.Panel;
@@ -14,16 +15,16 @@ namespace Snap.Hutao.Control.Panel;
[DependencyProperty("Current", typeof(string), List)]
[DependencyProperty("LocalSettingKeySuffixForCurrent", typeof(string))]
[DependencyProperty("LocalSettingKeyExtraForCurrent", typeof(string), "")]
internal sealed partial class PanelSelector : Segmented
internal sealed partial class PanelSelector : Segmented.Segmented
{
public const string List = nameof(List);
public const string Grid = nameof(Grid);
private static readonly Dictionary<int, string> IndexTypeMap = new()
{
[0] = List,
[1] = Grid,
};
private static readonly FrozenDictionary<int, string> IndexTypeMap = FrozenDictionary.ToFrozenDictionary(
[
KeyValuePair.Create(0, List),
KeyValuePair.Create(1, Grid),
]);
private readonly RoutedEventHandler loadedEventHandler;
private readonly RoutedEventHandler unloadedEventHandler;

View File

@@ -0,0 +1,89 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Microsoft.UI.Xaml;
using System.Data;
using Windows.Foundation;
namespace Snap.Hutao.Control.Segmented;
[DependencyProperty("Spacing", typeof(double), default(double), nameof(OnSpacingChanged))]
internal partial class EqualPanel : Microsoft.UI.Xaml.Controls.Panel
{
private double maxItemWidth;
private double maxItemHeight;
private int visibleItemsCount;
public EqualPanel()
{
RegisterPropertyChangedCallback(HorizontalAlignmentProperty, OnHorizontalAlignmentChanged);
}
protected override Size MeasureOverride(Size availableSize)
{
maxItemWidth = 0;
maxItemHeight = 0;
IEnumerable<UIElement> elements = Children.Where(static e => e.Visibility == Visibility.Visible);
visibleItemsCount = elements.Count();
foreach (UIElement child in elements)
{
child.Measure(availableSize);
maxItemWidth = Math.Max(maxItemWidth, child.DesiredSize.Width);
maxItemHeight = Math.Max(maxItemHeight, child.DesiredSize.Height);
}
if (visibleItemsCount > 0)
{
// Return equal widths based on the widest item
// In very specific edge cases the AvailableWidth might be infinite resulting in a crash.
if (HorizontalAlignment != HorizontalAlignment.Stretch || double.IsInfinity(availableSize.Width))
{
return new Size((maxItemWidth * visibleItemsCount) + (Spacing * (visibleItemsCount - 1)), maxItemHeight);
}
else
{
// Equal columns based on the available width, adjust for spacing
double totalWidth = availableSize.Width - (Spacing * (visibleItemsCount - 1));
maxItemWidth = totalWidth / visibleItemsCount;
return new Size(availableSize.Width, maxItemHeight);
}
}
else
{
return new Size(0, 0);
}
}
protected override Size ArrangeOverride(Size finalSize)
{
double x = 0;
// Check if there's more (little) width available - if so, set max item width to the maximum possible as we have an almost perfect height.
if (finalSize.Width > (visibleItemsCount * maxItemWidth) + (Spacing * (visibleItemsCount - 1)))
{
maxItemWidth = (finalSize.Width - (Spacing * (visibleItemsCount - 1))) / visibleItemsCount;
}
IEnumerable<UIElement> elements = Children.Where(static e => e.Visibility == Visibility.Visible);
foreach (UIElement child in elements)
{
child.Arrange(new Rect(x, 0, maxItemWidth, maxItemHeight));
x += maxItemWidth + Spacing;
}
return finalSize;
}
private static void OnSpacingChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
EqualPanel panel = (EqualPanel)d;
panel.InvalidateMeasure();
}
private void OnHorizontalAlignmentChanged(DependencyObject sender, DependencyProperty dp)
{
InvalidateMeasure();
}
}

View File

@@ -0,0 +1,127 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Input;
using Windows.System;
namespace Snap.Hutao.Control.Segmented;
internal partial class Segmented : ListViewBase
{
private int correctSelectedIndex = -1;
public Segmented()
{
DefaultStyleKey = typeof(Segmented);
RegisterPropertyChangedCallback(SelectedIndexProperty, OnSelectedIndexChanged);
}
protected override DependencyObject GetContainerForItemOverride() => new SegmentedItem();
protected override bool IsItemItsOwnContainerOverride(object item)
{
return item is SegmentedItem;
}
protected override void OnApplyTemplate()
{
base.OnApplyTemplate();
if (!IsLoaded)
{
SelectedIndex = correctSelectedIndex;
}
PreviewKeyDown -= OnPreviewKeyDown;
PreviewKeyDown += OnPreviewKeyDown;
}
protected override void PrepareContainerForItemOverride(DependencyObject element, object item)
{
base.PrepareContainerForItemOverride(element, item);
if (element is SegmentedItem segmentedItem)
{
segmentedItem.Loaded += OnLoaded;
}
}
protected override void OnItemsChanged(object e)
{
base.OnItemsChanged(e);
}
private void OnPreviewKeyDown(object sender, KeyRoutedEventArgs e)
{
switch (e.Key)
{
case VirtualKey.Left:
e.Handled = MoveFocus(true);
break;
case VirtualKey.Right:
e.Handled = MoveFocus(false);
break;
}
}
private void OnLoaded(object sender, RoutedEventArgs e)
{
if (sender is SegmentedItem segmentedItem)
{
segmentedItem.Loaded -= OnLoaded;
}
}
private bool MoveFocus(bool reverse)
{
SegmentedItem? currentContainerItem = GetCurrentContainerItem();
if (currentContainerItem is null)
{
return false;
}
int previousIndex = Items.IndexOf(ItemFromContainer(currentContainerItem));
if (reverse)
{
if (previousIndex > 0 && ContainerFromIndex(previousIndex - 1) is SegmentedItem newItem)
{
newItem.Focus(FocusState.Keyboard);
return true;
}
}
else
{
if (previousIndex < Items.Count - 1 && ContainerFromIndex(previousIndex + 1) is SegmentedItem newItem)
{
newItem.Focus(FocusState.Keyboard);
return true;
}
}
return false;
}
private SegmentedItem? GetCurrentContainerItem()
{
if (XamlRoot is not null)
{
return (SegmentedItem)FocusManager.GetFocusedElement(XamlRoot);
}
else
{
return (SegmentedItem)FocusManager.GetFocusedElement();
}
}
private void OnSelectedIndexChanged(DependencyObject sender, DependencyProperty dp)
{
// https://github.com/microsoft/microsoft-ui-xaml/issues/8257
if (correctSelectedIndex == -1 && SelectedIndex > -1)
{
correctSelectedIndex = SelectedIndex;
}
}
}

View File

@@ -0,0 +1,114 @@
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:cw="using:CommunityToolkit.WinUI"
xmlns:shcs="using:Snap.Hutao.Control.Segmented"
xmlns:win="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="ms-appx:///Control/Segmented/SegmentedItem.xaml"/>
</ResourceDictionary.MergedDictionaries>
<ResourceDictionary.ThemeDictionaries>
<ResourceDictionary x:Key="Default">
<StaticResource x:Key="SegmentedBackground" ResourceKey="ControlAltFillColorSecondaryBrush"/>
<StaticResource x:Key="SegmentedBorderBrush" ResourceKey="ControlStrokeColorDefaultBrush"/>
<Thickness x:Key="SegmentedBorderThickness">1</Thickness>
</ResourceDictionary>
<ResourceDictionary x:Key="Light">
<StaticResource x:Key="SegmentedBackground" ResourceKey="ControlAltFillColorSecondaryBrush"/>
<StaticResource x:Key="SegmentedBorderBrush" ResourceKey="ControlStrokeColorDefaultBrush"/>
<Thickness x:Key="SegmentedBorderThickness">1</Thickness>
</ResourceDictionary>
<ResourceDictionary x:Key="HighContrast">
<StaticResource x:Key="SegmentedBackground" ResourceKey="SystemColorButtonFaceColor"/>
<StaticResource x:Key="SegmentedBorderBrush" ResourceKey="SystemColorHighlightColorBrush"/>
<Thickness x:Key="SegmentedBorderThickness">1</Thickness>
</ResourceDictionary>
</ResourceDictionary.ThemeDictionaries>
<x:Double x:Key="SegmentedItemSpacing">1</x:Double>
<x:Double x:Key="ButtonItemSpacing">2</x:Double>
<Style BasedOn="{StaticResource DefaultSegmentedStyle}" TargetType="shcs:Segmented"/>
<Style x:Key="DefaultSegmentedStyle" TargetType="shcs:Segmented">
<Style.Setters>
<Setter Property="CornerRadius" Value="{ThemeResource ControlCornerRadius}"/>
<Setter Property="Background" Value="{ThemeResource SegmentedBackground}"/>
<Setter Property="BorderBrush" Value="{ThemeResource SegmentedBorderBrush}"/>
<Setter Property="BorderThickness" Value="{ThemeResource SegmentedBorderThickness}"/>
<Setter Property="HorizontalAlignment" Value="Left"/>
<Setter Property="VerticalAlignment" Value="Center"/>
<Setter Property="SelectionMode" Value="Single"/>
<Setter Property="IsItemClickEnabled" Value="False"/>
<win:Setter Property="SingleSelectionFollowsFocus" Value="False"/>
<Setter Property="IsTabStop" Value="False"/>
<Setter Property="TabNavigation" Value="Once"/>
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<shcs:EqualPanel
HorizontalAlignment="{Binding (cw:FrameworkElementExtensions.Ancestor).HorizontalAlignment, RelativeSource={RelativeSource Self}}"
cw:FrameworkElementExtensions.AncestorType="shcs:Segmented"
Spacing="{ThemeResource SegmentedItemSpacing}"/>
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="shcs:Segmented">
<Grid>
<Border
VerticalAlignment="Stretch"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="{TemplateBinding CornerRadius}"/>
<ItemsPresenter Margin="{TemplateBinding Padding}"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style.Setters>
</Style>
<Style
x:Key="PivotSegmentedStyle"
BasedOn="{StaticResource DefaultSegmentedStyle}"
TargetType="shcs:Segmented">
<Style.Setters>
<Setter Property="Background" Value="Transparent"/>
<Setter Property="BorderBrush" Value="Transparent"/>
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="Padding" Value="0"/>
<Setter Property="ItemContainerStyle" Value="{StaticResource PivotSegmentedItemStyle}"/>
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" Spacing="{ThemeResource SegmentedItemSpacing}"/>
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
</Style.Setters>
</Style>
<Style
x:Key="ButtonSegmentedStyle"
BasedOn="{StaticResource DefaultSegmentedStyle}"
TargetType="shcs:Segmented">
<Style.Setters>
<Setter Property="Background" Value="Transparent"/>
<Setter Property="BorderBrush" Value="Transparent"/>
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="Padding" Value="0"/>
<Setter Property="ItemContainerStyle" Value="{StaticResource ButtonSegmentedItemStyle}"/>
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" Spacing="{ThemeResource ButtonItemSpacing}"/>
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
</Style.Setters>
</Style>
</ResourceDictionary>

View File

@@ -0,0 +1,62 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
namespace Snap.Hutao.Control.Segmented;
[DependencyProperty("Icon", typeof(IconElement), null, nameof(OnIconPropertyChanged))]
internal partial class SegmentedItem : ListViewItem
{
private const string IconLeftState = "IconLeft";
private const string IconOnlyState = "IconOnly";
private const string ContentOnlyState = "ContentOnly";
public SegmentedItem()
{
DefaultStyleKey = typeof(SegmentedItem);
}
protected override void OnApplyTemplate()
{
base.OnApplyTemplate();
OnIconChanged();
ContentChanged();
}
protected override void OnContentChanged(object oldContent, object newContent)
{
base.OnContentChanged(oldContent, newContent);
ContentChanged();
}
private static void OnIconPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((SegmentedItem)d).OnIconChanged();
}
private void ContentChanged()
{
if (Content is not null)
{
VisualStateManager.GoToState(this, IconLeftState, true);
}
else
{
VisualStateManager.GoToState(this, IconOnlyState, true);
}
}
private void OnIconChanged()
{
if (Icon is not null)
{
VisualStateManager.GoToState(this, IconLeftState, true);
}
else
{
VisualStateManager.GoToState(this, ContentOnlyState, true);
}
}
}

View File

@@ -0,0 +1,875 @@
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:shcs="using:Snap.Hutao.Control.Segmented"
xmlns:win="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<ResourceDictionary.ThemeDictionaries>
<ResourceDictionary x:Key="Default">
<!-- Background -->
<StaticResource x:Key="SegmentedItemBackground" ResourceKey="ControlFillColorTransparentBrush"/>
<StaticResource x:Key="SegmentedItemBackgroundPointerOver" ResourceKey="SubtleFillColorSecondaryBrush"/>
<StaticResource x:Key="SegmentedItemBackgroundSelected" ResourceKey="ControlFillColorDefaultBrush"/>
<StaticResource x:Key="SegmentedItemBackgroundPressed" ResourceKey="SubtleFillColorTertiaryBrush"/>
<StaticResource x:Key="SegmentedItemBackgroundDisabled" ResourceKey="ControlFillColorDisabledBrush"/>
<!-- BorderBrush -->
<StaticResource x:Key="SegmentedItemBorderBrush" ResourceKey="ControlFillColorTransparentBrush"/>
<StaticResource x:Key="SegmentedItemBorderBrushPointerOver" ResourceKey="SubtleFillColorSecondaryBrush"/>
<StaticResource x:Key="SegmentedItemBorderBrushSelected" ResourceKey="ControlElevationBorderBrush"/>
<StaticResource x:Key="SegmentedItemBorderBrushPressed" ResourceKey="SubtleFillColorTertiaryBrush"/>
<StaticResource x:Key="SegmentedItemBorderBrushDisabled" ResourceKey="ControlAltFillColorSecondaryBrush"/>
<!-- Foreground -->
<StaticResource x:Key="SegmentedItemForeground" ResourceKey="TextFillColorPrimaryBrush"/>
<StaticResource x:Key="SegmentedItemForegroundPointerOver" ResourceKey="TextFillColorPrimaryBrush"/>
<StaticResource x:Key="SegmentedItemForegroundSelected" ResourceKey="TextFillColorPrimaryBrush"/>
<StaticResource x:Key="SegmentedItemForegroundPressed" ResourceKey="TextFillColorSecondaryBrush"/>
<StaticResource x:Key="SegmentedItemForegroundDisabled" ResourceKey="TextFillColorDisabledBrush"/>
<!-- Pill -->
<StaticResource x:Key="SegmentedPillBackground" ResourceKey="AccentFillColorDefaultBrush"/>
<StaticResource x:Key="SegmentedPillBackgroundPointerOver" ResourceKey="AccentFillColorSecondaryBrush"/>
<StaticResource x:Key="SegmentedPillBackgroundPressed" ResourceKey="AccentFillColorTertiaryBrush"/>
<StaticResource x:Key="SegmentedPillBackgroundDisabled" ResourceKey="AccentFillColorDisabledBrush"/>
<Thickness x:Key="SegmentedItemBorderThickness">1</Thickness>
<x:Double x:Key="SegmentedItemDisabledOpacity">0.55</x:Double>
<!-- PillSegmentedStyle -->
<!-- Background -->
<StaticResource x:Key="PivotItemBackground" ResourceKey="ControlFillColorTransparentBrush"/>
<StaticResource x:Key="PivotItemBackgroundPointerOver" ResourceKey="ControlFillColorTransparentBrush"/>
<StaticResource x:Key="PivotItemBackgroundSelected" ResourceKey="ControlFillColorTransparentBrush"/>
<StaticResource x:Key="PivotItemBackgroundPressed" ResourceKey="ControlFillColorTransparentBrush"/>
<StaticResource x:Key="PivotItemBackgroundDisabled" ResourceKey="ControlFillColorTransparentBrush"/>
<!-- Foreground -->
<StaticResource x:Key="PivotItemForeground" ResourceKey="TextFillColorPrimaryBrush"/>
<StaticResource x:Key="PivotItemForegroundPointerOver" ResourceKey="TextFillColorSecondaryBrush"/>
<StaticResource x:Key="PivotItemForegroundPressed" ResourceKey="TextFillColorTertiaryBrush"/>
<StaticResource x:Key="PivotItemForegroundSelected" ResourceKey="TextFillColorPrimaryBrush"/>
<StaticResource x:Key="PivotItemForegroundDisabled" ResourceKey="TextFillColorDisabledBrush"/>
<!-- Pill -->
<StaticResource x:Key="PivotItemPillBackground" ResourceKey="AccentFillColorDefaultBrush"/>
<StaticResource x:Key="PivotItemPillBackgroundPointerOver" ResourceKey="AccentFillColorSecondaryBrush"/>
<StaticResource x:Key="PivotItemPillBackgroundPressed" ResourceKey="AccentFillColorTertiaryBrush"/>
<StaticResource x:Key="PivotItemPillBackgroundDisabled" ResourceKey="AccentFillColorDefaultBrush"/>
<!-- ButtonSegmentedStyle -->
<!-- Background -->
<StaticResource x:Key="ButtonItemBackground" ResourceKey="ControlFillColorTransparentBrush"/>
<StaticResource x:Key="ButtonItemBackgroundPointerOver" ResourceKey="SubtleFillColorSecondaryBrush"/>
<StaticResource x:Key="ButtonItemBackgroundPressed" ResourceKey="SubtleFillColorTertiary"/>
<StaticResource x:Key="ButtonItemBackgroundSelected" ResourceKey="AccentFillColorDefaultBrush"/>
<StaticResource x:Key="ButtonItemBackgroundSelectedPointerOver" ResourceKey="AccentFillColorSecondaryBrush"/>
<StaticResource x:Key="ButtonItemBackgroundSelectedPressed" ResourceKey="AccentFillColorTertiaryBrush"/>
<StaticResource x:Key="ButtonItemBackgroundDisabled" ResourceKey="ControlFillColorTransparentBrush"/>
<!-- Foreground -->
<StaticResource x:Key="ButtonItemForeground" ResourceKey="TextFillColorPrimaryBrush"/>
<StaticResource x:Key="ButtonItemForegroundPointerOver" ResourceKey="TextFillColorPrimaryBrush"/>
<StaticResource x:Key="ButtonItemForegroundPressed" ResourceKey="TextFillColorSecondaryBrush"/>
<StaticResource x:Key="ButtonItemForegroundSelected" ResourceKey="TextOnAccentFillColorPrimaryBrush"/>
<StaticResource x:Key="ButtonItemForegroundSelectedPointerOver" ResourceKey="TextOnAccentFillColorPrimaryBrush"/>
<StaticResource x:Key="ButtonItemForegroundSelectedPressed" ResourceKey="TextOnAccentFillColorSecondaryBrush"/>
<StaticResource x:Key="ButtonItemForegroundDisabled" ResourceKey="TextFillColorDisabledBrush"/>
</ResourceDictionary>
<ResourceDictionary x:Key="Light">
<!-- Background -->
<StaticResource x:Key="SegmentedItemBackground" ResourceKey="ControlFillColorTransparentBrush"/>
<StaticResource x:Key="SegmentedItemBackgroundPointerOver" ResourceKey="SubtleFillColorSecondaryBrush"/>
<StaticResource x:Key="SegmentedItemBackgroundSelected" ResourceKey="ControlFillColorDefaultBrush"/>
<StaticResource x:Key="SegmentedItemBackgroundPressed" ResourceKey="SubtleFillColorTertiaryBrush"/>
<StaticResource x:Key="SegmentedItemBackgroundDisabled" ResourceKey="ControlFillColorDisabledBrush"/>
<!-- BorderBrush -->
<StaticResource x:Key="SegmentedItemBorderBrush" ResourceKey="ControlFillColorTransparentBrush"/>
<StaticResource x:Key="SegmentedItemBorderBrushPointerOver" ResourceKey="SubtleFillColorSecondaryBrush"/>
<StaticResource x:Key="SegmentedItemBorderBrushSelected" ResourceKey="ControlElevationBorderBrush"/>
<StaticResource x:Key="SegmentedItemBorderBrushPressed" ResourceKey="SubtleFillColorTertiaryBrush"/>
<StaticResource x:Key="SegmentedItemBorderBrushDisabled" ResourceKey="ControlAltFillColorSecondaryBrush"/>
<!-- Foreground -->
<StaticResource x:Key="SegmentedItemForeground" ResourceKey="TextFillColorPrimaryBrush"/>
<StaticResource x:Key="SegmentedItemForegroundPointerOver" ResourceKey="TextFillColorPrimaryBrush"/>
<StaticResource x:Key="SegmentedItemForegroundSelected" ResourceKey="TextFillColorPrimaryBrush"/>
<StaticResource x:Key="SegmentedItemForegroundPressed" ResourceKey="TextFillColorSecondaryBrush"/>
<StaticResource x:Key="SegmentedItemForegroundDisabled" ResourceKey="TextFillColorDisabledBrush"/>
<!-- Pill -->
<StaticResource x:Key="SegmentedPillBackground" ResourceKey="AccentFillColorDefaultBrush"/>
<StaticResource x:Key="SegmentedPillBackgroundPointerOver" ResourceKey="AccentFillColorSecondaryBrush"/>
<StaticResource x:Key="SegmentedPillBackgroundPressed" ResourceKey="AccentFillColorTertiaryBrush"/>
<StaticResource x:Key="SegmentedPillBackgroundDisabled" ResourceKey="AccentFillColorDisabledBrush"/>
<Thickness x:Key="SegmentedItemBorderThickness">1</Thickness>
<x:Double x:Key="SegmentedItemDisabledOpacity">0.55</x:Double>
<!-- PillSegmentedStyle -->
<!-- Background -->
<StaticResource x:Key="PivotItemBackground" ResourceKey="ControlFillColorTransparentBrush"/>
<StaticResource x:Key="PivotItemBackgroundPointerOver" ResourceKey="ControlFillColorTransparentBrush"/>
<StaticResource x:Key="PivotItemBackgroundSelected" ResourceKey="ControlFillColorTransparentBrush"/>
<StaticResource x:Key="PivotItemBackgroundPressed" ResourceKey="ControlFillColorTransparentBrush"/>
<StaticResource x:Key="PivotItemBackgroundDisabled" ResourceKey="ControlFillColorTransparentBrush"/>
<!-- Foreground -->
<StaticResource x:Key="PivotItemForeground" ResourceKey="TextFillColorPrimaryBrush"/>
<StaticResource x:Key="PivotItemForegroundPointerOver" ResourceKey="TextFillColorSecondaryBrush"/>
<StaticResource x:Key="PivotItemForegroundPressed" ResourceKey="TextFillColorTertiaryBrush"/>
<StaticResource x:Key="PivotItemForegroundSelected" ResourceKey="TextFillColorPrimaryBrush"/>
<StaticResource x:Key="PivotItemForegroundDisabled" ResourceKey="TextFillColorDisabledBrush"/>
<!-- Pill -->
<StaticResource x:Key="PivotItemPillBackground" ResourceKey="AccentFillColorDefaultBrush"/>
<StaticResource x:Key="PivotItemPillBackgroundPointerOver" ResourceKey="AccentFillColorSecondaryBrush"/>
<StaticResource x:Key="PivotItemPillBackgroundPressed" ResourceKey="AccentFillColorTertiaryBrush"/>
<StaticResource x:Key="PivotItemPillBackgroundDisabled" ResourceKey="AccentFillColorDefaultBrush"/>
<!-- ButtonSegmentedStyle -->
<!-- Background -->
<StaticResource x:Key="ButtonItemBackground" ResourceKey="ControlFillColorTransparentBrush"/>
<StaticResource x:Key="ButtonItemBackgroundPointerOver" ResourceKey="SubtleFillColorSecondaryBrush"/>
<StaticResource x:Key="ButtonItemBackgroundPressed" ResourceKey="SubtleFillColorTertiary"/>
<StaticResource x:Key="ButtonItemBackgroundSelected" ResourceKey="AccentFillColorDefaultBrush"/>
<StaticResource x:Key="ButtonItemBackgroundSelectedPointerOver" ResourceKey="AccentFillColorSecondaryBrush"/>
<StaticResource x:Key="ButtonItemBackgroundSelectedPressed" ResourceKey="AccentFillColorTertiaryBrush"/>
<StaticResource x:Key="ButtonItemBackgroundDisabled" ResourceKey="ControlFillColorTransparentBrush"/>
<!-- Foreground -->
<StaticResource x:Key="ButtonItemForeground" ResourceKey="TextFillColorPrimaryBrush"/>
<StaticResource x:Key="ButtonItemForegroundPointerOver" ResourceKey="TextFillColorPrimaryBrush"/>
<StaticResource x:Key="ButtonItemForegroundPressed" ResourceKey="TextFillColorSecondaryBrush"/>
<StaticResource x:Key="ButtonItemForegroundSelected" ResourceKey="TextOnAccentFillColorPrimaryBrush"/>
<StaticResource x:Key="ButtonItemForegroundSelectedPointerOver" ResourceKey="TextOnAccentFillColorPrimaryBrush"/>
<StaticResource x:Key="ButtonItemForegroundSelectedPressed" ResourceKey="TextOnAccentFillColorSecondaryBrush"/>
<StaticResource x:Key="ButtonItemForegroundDisabled" ResourceKey="TextFillColorDisabledBrush"/>
</ResourceDictionary>
<ResourceDictionary x:Key="HighContrast">
<!-- Background -->
<StaticResource x:Key="SegmentedItemBackground" ResourceKey="SystemColorButtonFaceColor"/>
<StaticResource x:Key="SegmentedItemBackgroundPointerOver" ResourceKey="SystemColorHighlightColor"/>
<StaticResource x:Key="SegmentedItemBackgroundSelected" ResourceKey="SystemColorHighlightColor"/>
<StaticResource x:Key="SegmentedItemBackgroundPressed" ResourceKey="SystemColorHighlightColor"/>
<StaticResource x:Key="SegmentedItemBackgroundDisabled" ResourceKey="SystemControlTransparentBrush"/>
<!-- BorderBrush -->
<StaticResource x:Key="SegmentedItemBorderBrush" ResourceKey="ControlAltFillColorSecondaryBrush"/>
<StaticResource x:Key="SegmentedItemBorderBrushPointerOver" ResourceKey="ControlAltFillColorSecondaryBrush"/>
<StaticResource x:Key="SegmentedItemBorderBrushSelected" ResourceKey="ControlElevationBorderBrush"/>
<StaticResource x:Key="SegmentedItemBorderBrushPressed" ResourceKey="ControlAltFillColorSecondaryBrush"/>
<StaticResource x:Key="SegmentedItemBorderBrushDisabled" ResourceKey="ControlAltFillColorSecondaryBrush"/>
<!-- Foreground -->
<StaticResource x:Key="SegmentedItemForeground" ResourceKey="SystemColorButtonTextColor"/>
<StaticResource x:Key="SegmentedItemForegroundPointerOver" ResourceKey="SystemColorHighlightTextColor"/>
<StaticResource x:Key="SegmentedItemForegroundSelected" ResourceKey="SystemColorHighlightTextColor"/>
<StaticResource x:Key="SegmentedItemForegroundPressed" ResourceKey="SystemColorHighlightTextColor"/>
<StaticResource x:Key="SegmentedItemForegroundDisabled" ResourceKey="SystemColorGrayTextColor"/>
<!-- Pill -->
<StaticResource x:Key="SegmentedPillBackground" ResourceKey="AccentFillColorDefaultBrush"/>
<StaticResource x:Key="SegmentedPillBackgroundPointerOver" ResourceKey="AccentFillColorSecondaryBrush"/>
<StaticResource x:Key="SegmentedPillBackgroundPressed" ResourceKey="AccentFillColorTertiaryBrush"/>
<StaticResource x:Key="SegmentedPillBackgroundDisabled" ResourceKey="AccentFillColorDisabledBrush"/>
<Thickness x:Key="SegmentedItemBorderThickness">1</Thickness>
<x:Double x:Key="SegmentedItemDisabledOpacity">0.55</x:Double>
<!-- PillSegmentedStyle -->
<!-- Background -->
<StaticResource x:Key="PivotItemBackground" ResourceKey="SystemColorButtonFaceColor"/>
<StaticResource x:Key="PivotItemBackgroundPointerOver" ResourceKey="SystemColorHighlightColor"/>
<StaticResource x:Key="PivotItemBackgroundSelected" ResourceKey="SystemColorHighlightColor"/>
<StaticResource x:Key="PivotItemBackgroundPressed" ResourceKey="SystemColorHighlightColor"/>
<StaticResource x:Key="PivotItemBackgroundDisabled" ResourceKey="SystemColorButtonTextColor"/>
<!-- Pill -->
<StaticResource x:Key="PivotItemPillBackground" ResourceKey="AccentFillColorDefaultBrush"/>
<StaticResource x:Key="PivotItemPillBackgroundPointerOver" ResourceKey="AccentFillColorSecondaryBrush"/>
<StaticResource x:Key="PivotItemPillBackgroundPressed" ResourceKey="AccentFillColorTertiaryBrush"/>
<StaticResource x:Key="PivotItemPillBackgroundDisabled" ResourceKey="AccentFillColorDefaultBrush"/>
<!-- Foreground -->
<StaticResource x:Key="PivotItemForeground" ResourceKey="SystemColorButtonTextColor"/>
<StaticResource x:Key="PivotItemForegroundPointerOver" ResourceKey="SystemColorHighlightTextColor"/>
<StaticResource x:Key="PivotItemForegroundSelected" ResourceKey="SystemColorHighlightTextColor"/>
<StaticResource x:Key="PivotItemForegroundPressed" ResourceKey="SystemColorHighlightTextColor"/>
<StaticResource x:Key="PivotItemForegroundDisabled" ResourceKey="SystemColorGrayTextColor"/>
<!-- ButtonSegmentedStyle -->
<!-- Background -->
<StaticResource x:Key="ButtonItemBackground" ResourceKey="ControlFillColorTransparentBrush"/>
<StaticResource x:Key="ButtonItemBackgroundPointerOver" ResourceKey="SystemColorHighlightTextColorBrush"/>
<StaticResource x:Key="ButtonItemBackgroundPressed" ResourceKey="SystemColorHighlightTextColorBrush"/>
<StaticResource x:Key="ButtonItemBackgroundSelected" ResourceKey="SystemControlHighlightAccentBrush"/>
<StaticResource x:Key="ButtonItemBackgroundSelectedPointerOver" ResourceKey="SystemColorButtonTextColorBrush"/>
<StaticResource x:Key="ButtonItemBackgroundSelectedPressed" ResourceKey="SystemColorHighlightTextColorBrush"/>
<StaticResource x:Key="ButtonItemBackgroundDisabled" ResourceKey="SystemControlBackgroundBaseLowBrush"/>
<!-- Foreground -->
<StaticResource x:Key="ButtonItemForeground" ResourceKey="SystemColorButtonTextColorBrush"/>
<StaticResource x:Key="ButtonItemForegroundPointerOver" ResourceKey="SystemControlHighlightBaseHighBrush"/>
<StaticResource x:Key="ButtonItemForegroundPressed" ResourceKey="SystemControlHighlightBaseHighBrush"/>
<StaticResource x:Key="ButtonItemForegroundSelected" ResourceKey="SystemControlHighlightAltChromeWhiteBrush"/>
<StaticResource x:Key="ButtonItemForegroundSelectedPointerOver" ResourceKey="SystemColorButtonFaceColorBrush"/>
<StaticResource x:Key="ButtonItemForegroundSelectedPressed" ResourceKey="SystemColorHighlightColorBrush"/>
<StaticResource x:Key="ButtonItemForegroundDisabled" ResourceKey="SystemControlDisabledBaseMediumLowBrush"/>
</ResourceDictionary>
</ResourceDictionary.ThemeDictionaries>
<x:String x:Key="SegmentedItemScaleAnimationDuration">00:00:00.167</x:String>
<Style BasedOn="{StaticResource DefaultSegmentedItemStyle}" TargetType="shcs:SegmentedItem"/>
<shcs:SegmentedMarginConverter
x:Name="MarginConverter"
LeftItemMargin="{StaticResource LeftItemHoverMargin}"
MiddleItemMargin="{StaticResource MiddleItemHoverMargin}"
RightItemMargin="{StaticResource RightItemHoverMargin}"/>
<Thickness x:Key="LeftItemHoverMargin">3, 3, 1, 3</Thickness>
<Thickness x:Key="MiddleItemHoverMargin">1, 3, 1, 3</Thickness>
<Thickness x:Key="RightItemHoverMargin">1, 3, 3, 3</Thickness>
<Thickness x:Key="ButtonItemPadding">11</Thickness>
<Style x:Key="DefaultSegmentedItemStyle" TargetType="shcs:SegmentedItem">
<Style.Setters>
<Setter Property="CornerRadius" Value="{ThemeResource ControlCornerRadius}"/>
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
<Setter Property="FontFamily" Value="{ThemeResource ContentControlThemeFontFamily}"/>
<Setter Property="Background" Value="{ThemeResource SegmentedItemBackground}"/>
<Setter Property="BorderBrush" Value="{ThemeResource SegmentedItemBorderBrush}"/>
<Setter Property="BorderThickness" Value="{ThemeResource SegmentedItemBorderThickness}"/>
<Setter Property="Foreground" Value="{ThemeResource SegmentedItemForeground}"/>
<Setter Property="FontWeight" Value="Normal"/>
<Setter Property="TabNavigation" Value="Local"/>
<Setter Property="UseSystemFocusVisuals" Value="True"/>
<Setter Property="FocusVisualMargin" Value="-3"/>
<Setter Property="BackgroundSizing" Value="InnerBorderEdge"/>
<Setter Property="FontSize" Value="{ThemeResource ControlContentThemeFontSize}"/>
<Setter Property="VerticalAlignment" Value="Stretch"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="shcs:SegmentedItem">
<Grid
x:Name="PART_Root"
VerticalAlignment="{TemplateBinding VerticalAlignment}"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Control.IsTemplateFocusTarget="True"
CornerRadius="{TemplateBinding CornerRadius}">
<win:Grid.BackgroundTransition>
<win:BrushTransition Duration="0:0:0.083"/>
</win:Grid.BackgroundTransition>
<Border
x:Name="PART_Hover"
Margin="{Binding RelativeSource={RelativeSource Mode=TemplatedParent}, Converter={StaticResource MarginConverter}}"
Background="Transparent"
CornerRadius="2"
RenderTransformOrigin="0.5, 0.5">
<win:Border.BackgroundTransition>
<win:BrushTransition Duration="0:0:0.083"/>
</win:Border.BackgroundTransition>
<Border.RenderTransform>
<CompositeTransform/>
</Border.RenderTransform>
</Border>
<!-- Content -->
<Grid
x:Name="ContentHolder"
Margin="11,0,11,0"
HorizontalAlignment="Center"
VerticalAlignment="Stretch"
ColumnSpacing="8">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Viewbox
x:Name="PART_IconBox"
Width="16"
Margin="0,7,0,7"
VerticalAlignment="Center">
<ContentPresenter
x:Name="PART_IconPresenter"
win:HighContrastAdjustment="None"
Content="{TemplateBinding Icon}"
Foreground="{TemplateBinding Foreground}"/>
</Viewbox>
<ContentPresenter
x:Name="PART_ContentPresenter"
Grid.Column="1"
Margin="0,5,0,6"
VerticalAlignment="Center"
win:HighContrastAdjustment="None"
win:OpticalMarginAlignment="TrimSideBearings"
BackgroundSizing="{TemplateBinding BackgroundSizing}"
Content="{TemplateBinding Content}"
ContentTransitions="{TemplateBinding ContentTransitions}"
FontWeight="{TemplateBinding FontWeight}"
Foreground="{TemplateBinding Foreground}"/>
<Rectangle
x:Name="PART_Pill"
Grid.Column="1"
Width="4"
Height="3"
HorizontalAlignment="Center"
VerticalAlignment="Bottom"
Fill="{ThemeResource SegmentedPillBackground}"
Opacity="0"
RadiusX="0.5"
RadiusY="1"
RenderTransformOrigin="0.5, 0.5">
<Rectangle.RenderTransform>
<CompositeTransform x:Name="PillTransform" ScaleX="1"/>
</Rectangle.RenderTransform>
</Rectangle>
</Grid>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="SegmentedIconPositionStates">
<VisualState x:Name="IconOnLeft"/>
<VisualState x:Name="IconOnly">
<VisualState.Setters>
<Setter Target="PART_ContentPresenter.Visibility" Value="Collapsed"/>
<Setter Target="PART_Pill.(Grid.Column)" Value="0"/>
<Setter Target="ContentHolder.ColumnSpacing" Value="0"/>
</VisualState.Setters>
</VisualState>
<VisualState x:Name="ContentOnly">
<VisualState.Setters>
<Setter Target="PART_IconBox.Visibility" Value="Collapsed"/>
<Setter Target="ContentHolder.ColumnSpacing" Value="0"/>
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal"/>
<VisualState x:Name="PointerOver">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="PART_Hover" Storyboard.TargetProperty="Background">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SegmentedItemBackgroundPointerOver}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="PART_ContentPresenter" Storyboard.TargetProperty="Foreground">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SegmentedItemForegroundPointerOver}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="PART_IconPresenter" Storyboard.TargetProperty="Foreground">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SegmentedItemForegroundPointerOver}"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="Pressed">
<Storyboard>
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="PART_Hover" Storyboard.TargetProperty="(UIElement.RenderTransform).(CompositeTransform.ScaleX)">
<SplineDoubleKeyFrame
KeySpline="0,0,0,1"
KeyTime="{ThemeResource SegmentedItemScaleAnimationDuration}"
Value="0.96"/>
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="PART_Hover" Storyboard.TargetProperty="(UIElement.RenderTransform).(CompositeTransform.ScaleY)">
<SplineDoubleKeyFrame
KeySpline="0,0,0,1"
KeyTime="{ThemeResource SegmentedItemScaleAnimationDuration}"
Value="0.96"/>
</DoubleAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="PART_Hover" Storyboard.TargetProperty="Background">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SegmentedItemBackgroundPressed}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="PART_ContentPresenter" Storyboard.TargetProperty="Foreground">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SegmentedItemForegroundPressed}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="PART_IconPresenter" Storyboard.TargetProperty="Foreground">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SegmentedItemForegroundPressed}"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="Selected">
<Storyboard>
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="PART_Pill" Storyboard.TargetProperty="(UIElement.RenderTransform).(CompositeTransform.ScaleX)">
<SplineDoubleKeyFrame
KeySpline="0,0,0,1"
KeyTime="{ThemeResource SegmentedItemScaleAnimationDuration}"
Value="4"/>
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="PART_Pill" Storyboard.TargetProperty="Opacity">
<SplineDoubleKeyFrame
KeySpline="0,0,0,1"
KeyTime="{ThemeResource SegmentedItemScaleAnimationDuration}"
Value="1"/>
</DoubleAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="PART_Pill" Storyboard.TargetProperty="Fill">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SegmentedPillBackground}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="PART_Root" Storyboard.TargetProperty="Background">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SegmentedItemBackgroundSelected}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="PART_Root" Storyboard.TargetProperty="BorderBrush">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SegmentedItemBorderBrushSelected}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="PART_ContentPresenter" Storyboard.TargetProperty="Foreground">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SegmentedItemForegroundSelected}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="PART_IconPresenter" Storyboard.TargetProperty="Foreground">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SegmentedItemForegroundSelected}"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="PointerOverSelected">
<Storyboard>
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="PART_Pill" Storyboard.TargetProperty="(UIElement.RenderTransform).(CompositeTransform.ScaleX)">
<SplineDoubleKeyFrame
KeySpline="0,0,0,1"
KeyTime="{ThemeResource SegmentedItemScaleAnimationDuration}"
Value="4"/>
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="PART_Pill" Storyboard.TargetProperty="Opacity">
<SplineDoubleKeyFrame
KeySpline="0,0,0,1"
KeyTime="{ThemeResource SegmentedItemScaleAnimationDuration}"
Value="1"/>
</DoubleAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="PART_Pill" Storyboard.TargetProperty="Fill">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SegmentedPillBackgroundPointerOver}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="PART_Root" Storyboard.TargetProperty="Background">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SegmentedItemBackgroundSelected}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="PART_Root" Storyboard.TargetProperty="BorderBrush">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SegmentedItemBorderBrushSelected}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="PART_ContentPresenter" Storyboard.TargetProperty="Foreground">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SegmentedItemForegroundSelected}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="PART_IconPresenter" Storyboard.TargetProperty="Foreground">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SegmentedItemForegroundSelected}"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="PressedSelected">
<Storyboard>
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="PART_Pill" Storyboard.TargetProperty="(UIElement.RenderTransform).(CompositeTransform.ScaleX)">
<SplineDoubleKeyFrame
KeySpline="0,0,0,1"
KeyTime="{ThemeResource SegmentedItemScaleAnimationDuration}"
Value="2"/>
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="PART_Pill" Storyboard.TargetProperty="Opacity">
<SplineDoubleKeyFrame
KeySpline="0,0,0,1"
KeyTime="{ThemeResource SegmentedItemScaleAnimationDuration}"
Value="1"/>
</DoubleAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="PART_Pill" Storyboard.TargetProperty="Fill">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SegmentedPillBackgroundPressed}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="PART_Root" Storyboard.TargetProperty="Background">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SegmentedItemBackgroundSelected}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="PART_Root" Storyboard.TargetProperty="BorderBrush">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SegmentedItemBorderBrushSelected}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="PART_ContentPresenter" Storyboard.TargetProperty="Foreground">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SegmentedItemForegroundSelected}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="PART_IconPresenter" Storyboard.TargetProperty="Foreground">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SegmentedItemForegroundSelected}"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
<VisualStateGroup x:Name="DisabledStates">
<VisualState x:Name="Enabled"/>
<VisualState x:Name="Disabled">
<Storyboard>
<DoubleAnimation
Storyboard.TargetName="PART_Root"
Storyboard.TargetProperty="Opacity"
To="{ThemeResource SegmentedItemDisabledOpacity}"
Duration="0:0:0.083"/>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style.Setters>
</Style>
<Style
x:Key="PivotSegmentedItemStyle"
BasedOn="{StaticResource DefaultSegmentedItemStyle}"
TargetType="shcs:SegmentedItem">
<Style.Setters>
<Setter Property="Background" Value="{ThemeResource PivotItemBackground}"/>
<Setter Property="Foreground" Value="{ThemeResource PivotItemForeground}"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="shcs:SegmentedItem">
<Grid
x:Name="PART_Root"
Background="{TemplateBinding Background}"
Control.IsTemplateFocusTarget="True"
CornerRadius="{TemplateBinding CornerRadius}">
<!-- Content -->
<Grid
x:Name="ContentHolder"
Margin="12,0,12,0"
HorizontalAlignment="Center"
VerticalAlignment="Stretch"
ColumnSpacing="8">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Viewbox
x:Name="PART_IconBox"
Width="16"
Margin="0,11,0,11"
VerticalAlignment="Center">
<ContentPresenter
x:Name="PART_IconPresenter"
win:HighContrastAdjustment="None"
Content="{TemplateBinding Icon}"
Foreground="{TemplateBinding Foreground}"/>
</Viewbox>
<ContentPresenter
x:Name="PART_ContentPresenter"
Grid.Column="1"
Margin="0,9,0,10"
VerticalAlignment="Center"
win:HighContrastAdjustment="None"
win:OpticalMarginAlignment="TrimSideBearings"
BackgroundSizing="{TemplateBinding BackgroundSizing}"
Content="{TemplateBinding Content}"
ContentTransitions="{TemplateBinding ContentTransitions}"
FontWeight="{TemplateBinding FontWeight}"
Foreground="{TemplateBinding Foreground}"/>
<Rectangle
x:Name="PART_Pill"
Grid.Column="1"
Width="4"
Height="3"
HorizontalAlignment="Center"
VerticalAlignment="Bottom"
Fill="{ThemeResource PivotItemPillBackground}"
Opacity="0"
RadiusX="0.5"
RadiusY="1"
RenderTransformOrigin="0.5, 0.5">
<Rectangle.RenderTransform>
<CompositeTransform x:Name="PillTransform" ScaleX="1"/>
</Rectangle.RenderTransform>
</Rectangle>
</Grid>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="SegmentedIconPositionStates">
<VisualState x:Name="IconOnLeft"/>
<VisualState x:Name="IconOnly">
<VisualState.Setters>
<Setter Target="PART_ContentPresenter.Visibility" Value="Collapsed"/>
<Setter Target="PART_Pill.(Grid.Column)" Value="0"/>
<Setter Target="ContentHolder.ColumnSpacing" Value="0"/>
</VisualState.Setters>
</VisualState>
<VisualState x:Name="ContentOnly">
<VisualState.Setters>
<Setter Target="PART_IconBox.Visibility" Value="Collapsed"/>
<Setter Target="ContentHolder.ColumnSpacing" Value="0"/>
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal"/>
<VisualState x:Name="PointerOver">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="PART_Root" Storyboard.TargetProperty="Background">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource PivotItemBackgroundPointerOver}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="PART_ContentPresenter" Storyboard.TargetProperty="Foreground">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource PivotItemForegroundPointerOver}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="PART_IconPresenter" Storyboard.TargetProperty="Foreground">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource PivotItemForegroundPointerOver}"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="Pressed">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="PART_Root" Storyboard.TargetProperty="Background">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource PivotItemBackgroundPressed}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="PART_ContentPresenter" Storyboard.TargetProperty="Foreground">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource PivotItemForegroundPressed}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="PART_IconPresenter" Storyboard.TargetProperty="Foreground">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource PivotItemForegroundPressed}"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="Selected">
<Storyboard>
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="PART_Pill" Storyboard.TargetProperty="(UIElement.RenderTransform).(CompositeTransform.ScaleX)">
<SplineDoubleKeyFrame
KeySpline="0,0,0,1"
KeyTime="{ThemeResource SegmentedItemScaleAnimationDuration}"
Value="4"/>
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="PART_Pill" Storyboard.TargetProperty="Opacity">
<SplineDoubleKeyFrame
KeySpline="0,0,0,1"
KeyTime="{ThemeResource SegmentedItemScaleAnimationDuration}"
Value="1"/>
</DoubleAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="PART_Root" Storyboard.TargetProperty="Background">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource PivotItemBackgroundSelected}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="PART_ContentPresenter" Storyboard.TargetProperty="Foreground">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource PivotItemForegroundSelected}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="PART_IconPresenter" Storyboard.TargetProperty="Foreground">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource PivotItemForegroundSelected}"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="PointerOverSelected">
<Storyboard>
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="PART_Pill" Storyboard.TargetProperty="(UIElement.RenderTransform).(CompositeTransform.ScaleX)">
<SplineDoubleKeyFrame
KeySpline="0,0,0,1"
KeyTime="{ThemeResource SegmentedItemScaleAnimationDuration}"
Value="4"/>
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="PART_Pill" Storyboard.TargetProperty="Opacity">
<SplineDoubleKeyFrame
KeySpline="0,0,0,1"
KeyTime="{ThemeResource SegmentedItemScaleAnimationDuration}"
Value="1"/>
</DoubleAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="PART_Root" Storyboard.TargetProperty="Background">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource PivotItemBackgroundSelected}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="PART_ContentPresenter" Storyboard.TargetProperty="Foreground">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource PivotItemForegroundPointerOver}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="PART_IconPresenter" Storyboard.TargetProperty="Foreground">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource PivotItemForegroundPointerOver}"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="PressedSelected">
<Storyboard>
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="PART_Pill" Storyboard.TargetProperty="(UIElement.RenderTransform).(CompositeTransform.ScaleX)">
<SplineDoubleKeyFrame
KeySpline="0,0,0,1"
KeyTime="{ThemeResource SegmentedItemScaleAnimationDuration}"
Value="2"/>
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="PART_Pill" Storyboard.TargetProperty="Opacity">
<SplineDoubleKeyFrame
KeySpline="0,0,0,1"
KeyTime="{ThemeResource SegmentedItemScaleAnimationDuration}"
Value="1"/>
</DoubleAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="PART_Root" Storyboard.TargetProperty="Background">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource PivotItemBackgroundSelected}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="PART_ContentPresenter" Storyboard.TargetProperty="Foreground">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource PivotItemForegroundPressed}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="PART_IconPresenter" Storyboard.TargetProperty="Foreground">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource PivotItemForegroundPressed}"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
<VisualStateGroup x:Name="DisabledStates">
<VisualState x:Name="Enabled"/>
<VisualState x:Name="Disabled">
<Storyboard>
<DoubleAnimation
Storyboard.TargetName="PART_Root"
Storyboard.TargetProperty="Opacity"
To="{ThemeResource SegmentedItemDisabledOpacity}"
Duration="0:0:0.083"/>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style.Setters>
</Style>
<Style
x:Key="ButtonSegmentedItemStyle"
BasedOn="{StaticResource DefaultSegmentedItemStyle}"
TargetType="shcs:SegmentedItem">
<Style.Setters>
<Setter Property="Background" Value="{ThemeResource ButtonItemBackground}"/>
<Setter Property="Foreground" Value="{ThemeResource ButtonItemForeground}"/>
<Setter Property="Padding" Value="{ThemeResource ButtonItemPadding}"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="shcs:SegmentedItem">
<Grid
x:Name="PART_Root"
Background="{TemplateBinding Background}"
BackgroundSizing="{TemplateBinding BackgroundSizing}"
Control.IsTemplateFocusTarget="True"
CornerRadius="{TemplateBinding CornerRadius}">
<win:Grid.BackgroundTransition>
<win:BrushTransition Duration="0:0:0.083"/>
</win:Grid.BackgroundTransition>
<!-- Content -->
<Grid
x:Name="ContentHolder"
Margin="{TemplateBinding Padding}"
HorizontalAlignment="Center"
VerticalAlignment="Stretch"
ColumnSpacing="8">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Viewbox
x:Name="PART_IconBox"
Width="16"
VerticalAlignment="Center">
<ContentPresenter
x:Name="PART_IconPresenter"
win:HighContrastAdjustment="None"
Content="{TemplateBinding Icon}"
Foreground="{TemplateBinding Foreground}"/>
</Viewbox>
<ContentPresenter
x:Name="PART_ContentPresenter"
Grid.Column="1"
VerticalAlignment="Center"
win:HighContrastAdjustment="None"
win:OpticalMarginAlignment="TrimSideBearings"
BackgroundSizing="{TemplateBinding BackgroundSizing}"
Content="{TemplateBinding Content}"
ContentTransitions="{TemplateBinding ContentTransitions}"
FontWeight="{TemplateBinding FontWeight}"
Foreground="{TemplateBinding Foreground}"/>
</Grid>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="SegmentedIconPositionStates">
<VisualState x:Name="IconOnLeft"/>
<VisualState x:Name="IconOnly">
<VisualState.Setters>
<Setter Target="PART_ContentPresenter.Visibility" Value="Collapsed"/>
<Setter Target="ContentHolder.ColumnSpacing" Value="0"/>
</VisualState.Setters>
</VisualState>
<VisualState x:Name="ContentOnly">
<VisualState.Setters>
<Setter Target="PART_IconBox.Visibility" Value="Collapsed"/>
<Setter Target="ContentHolder.ColumnSpacing" Value="0"/>
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal"/>
<VisualState x:Name="PointerOver">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="PART_Root" Storyboard.TargetProperty="Background">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ButtonItemBackgroundPointerOver}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="PART_ContentPresenter" Storyboard.TargetProperty="Foreground">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ButtonItemForegroundPointerOver}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="PART_IconPresenter" Storyboard.TargetProperty="Foreground">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ButtonItemForegroundPointerOver}"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="Pressed">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="PART_Root" Storyboard.TargetProperty="Background">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ButtonItemBackgroundPressed}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="PART_ContentPresenter" Storyboard.TargetProperty="Foreground">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ButtonItemForegroundPressed}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="PART_IconPresenter" Storyboard.TargetProperty="Foreground">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ButtonItemForegroundPressed}"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="Selected">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="PART_Root" Storyboard.TargetProperty="Background">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ButtonItemBackgroundSelected}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="PART_ContentPresenter" Storyboard.TargetProperty="Foreground">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ButtonItemForegroundSelected}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="PART_IconPresenter" Storyboard.TargetProperty="Foreground">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ButtonItemForegroundSelected}"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="PointerOverSelected">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="PART_Root" Storyboard.TargetProperty="Background">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ButtonItemBackgroundSelectedPointerOver}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="PART_ContentPresenter" Storyboard.TargetProperty="Foreground">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ButtonItemForegroundSelectedPointerOver}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="PART_IconPresenter" Storyboard.TargetProperty="Foreground">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ButtonItemForegroundSelectedPointerOver}"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="PressedSelected">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="PART_Root" Storyboard.TargetProperty="Background">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ButtonItemBackgroundSelectedPressed}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="PART_ContentPresenter" Storyboard.TargetProperty="Foreground">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ButtonItemForegroundSelectedPressed}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="PART_IconPresenter" Storyboard.TargetProperty="Foreground">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ButtonItemForegroundSelectedPressed}"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
<VisualStateGroup x:Name="DisabledStates">
<VisualState x:Name="Enabled"/>
<VisualState x:Name="Disabled">
<Storyboard>
<DoubleAnimation
Storyboard.TargetName="PART_Root"
Storyboard.TargetProperty="Opacity"
To="{ThemeResource SegmentedItemDisabledOpacity}"
Duration="0:0:0.083"/>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style.Setters>
</Style>
</ResourceDictionary>

View File

@@ -0,0 +1,40 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Data;
namespace Snap.Hutao.Control.Segmented;
[DependencyProperty("LeftItemMargin", typeof(Thickness))]
[DependencyProperty("MiddleItemMargin", typeof(Thickness))]
[DependencyProperty("RightItemMargin", typeof(Thickness))]
internal partial class SegmentedMarginConverter : DependencyObject, IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
SegmentedItem segmentedItem = (SegmentedItem)value;
ItemsControl listView = ItemsControl.ItemsControlFromItemContainer(segmentedItem);
int index = listView.IndexFromContainer(segmentedItem);
if (index == 0)
{
return LeftItemMargin;
}
else if (index == listView.Items.Count - 1)
{
return RightItemMargin;
}
else
{
return MiddleItemMargin;
}
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
return value;
}
}

View File

@@ -17,6 +17,9 @@
<ItemsPanelTemplate x:Key="HorizontalStackPanelSpacing2Template">
<StackPanel Orientation="Horizontal" Spacing="2"/>
</ItemsPanelTemplate>
<ItemsPanelTemplate x:Key="HorizontalStackPanelSpacing4Template">
<StackPanel Orientation="Horizontal" Spacing="4"/>
</ItemsPanelTemplate>
<ItemsPanelTemplate x:Key="StackPanelSpacing4Template">
<StackPanel Spacing="4"/>
</ItemsPanelTemplate>

View File

@@ -11,4 +11,6 @@ internal static class KnownColors
public static readonly Color Orange = StructMarshal.Color(0xFFBC6932);
public static readonly Color Purple = StructMarshal.Color(0xFFA156E0);
public static readonly Color Blue = StructMarshal.Color(0xFF5180CB);
public static readonly Color Green = StructMarshal.Color(0xFF2A8F72);
public static readonly Color White = StructMarshal.Color(0xFF72778B);
}

View File

@@ -1,7 +1,6 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Core.DependencyInjection.Abstraction;
using Snap.Hutao.Core.IO;
namespace Snap.Hutao.Core.Caching;
@@ -10,7 +9,7 @@ namespace Snap.Hutao.Core.Caching;
/// 为图像缓存提供抽象
/// </summary>
[HighQuality]
internal interface IImageCache : ICastService
internal interface IImageCache
{
/// <summary>
/// Gets the file path containing cached item for given Uri

View File

@@ -26,12 +26,12 @@ internal sealed partial class ImageCache : IImageCache, IImageCacheFilePathOpera
{
private const string CacheFolderName = nameof(ImageCache);
private readonly FrozenDictionary<int, TimeSpan> retryCountToDelay = new Dictionary<int, TimeSpan>()
{
[0] = TimeSpan.FromSeconds(4),
[1] = TimeSpan.FromSeconds(16),
[2] = TimeSpan.FromSeconds(64),
}.ToFrozenDictionary();
private readonly FrozenDictionary<int, TimeSpan> retryCountToDelay = FrozenDictionary.ToFrozenDictionary(
[
KeyValuePair.Create(0, TimeSpan.FromSeconds(4)),
KeyValuePair.Create(1, TimeSpan.FromSeconds(16)),
KeyValuePair.Create(2, TimeSpan.FromSeconds(64)),
]);
private readonly ConcurrentDictionary<string, Task> concurrentTasks = new();

View File

@@ -25,6 +25,7 @@ internal static partial class IocHttpClientConfiguration
.ConfigurePrimaryHttpMessageHandler((handler, provider) =>
{
HttpClientHandler clientHandler = (HttpClientHandler)handler;
clientHandler.AllowAutoRedirect = true;
clientHandler.UseProxy = true;
clientHandler.Proxy = provider.GetRequiredService<DynamicHttpProxy>();
});

View File

@@ -23,8 +23,16 @@ internal sealed partial class ExceptionRecorder
public void Record(Application app)
{
app.UnhandledException += OnAppUnhandledException;
app.DebugSettings.FailFastOnErrors = false;
app.DebugSettings.IsBindingTracingEnabled = true;
app.DebugSettings.BindingFailed += OnXamlBindingFailed;
app.DebugSettings.IsXamlResourceReferenceTracingEnabled = true;
app.DebugSettings.XamlResourceReferenceFailed += OnXamlResourceReferenceFailed;
app.DebugSettings.LayoutCycleTracingLevel = LayoutCycleTracingLevel.High;
app.DebugSettings.LayoutCycleDebugBreakLevel = LayoutCycleDebugBreakLevel.High;
}
[SuppressMessage("", "CA2012")]

View File

@@ -1,6 +1,8 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using System.Numerics;
namespace Snap.Hutao.Core.ExceptionService;
internal sealed class HutaoException : Exception
@@ -18,6 +20,12 @@ internal sealed class HutaoException : Exception
public HutaoExceptionKind Kind { get; private set; }
[DoesNotReturn]
public static HutaoException Throw(HutaoExceptionKind kind, string message, Exception? innerException = default)
{
throw new HutaoException(kind, message, innerException);
}
public static void ThrowIf(bool condition, HutaoExceptionKind kind, string message, Exception? innerException = default)
{
if (condition)
@@ -25,4 +33,16 @@ internal sealed class HutaoException : Exception
throw new HutaoException(kind, message, innerException);
}
}
public static HutaoException ServiceTypeCastFailed<TFrom, TTo>(string name, Exception? innerException = default)
{
string message = $"This instance of '{typeof(TFrom).FullName}' '{name}' doesn't implement '{typeof(TTo).FullName}'";
throw new HutaoException(HutaoExceptionKind.ServiceTypeCastFailed, message, innerException);
}
public static HutaoException GachaStatisticsInvalidItemId(uint id, Exception? innerException = default)
{
string message = SH.FormatServiceGachaStatisticsFactoryItemIdInvalid(id);
throw new HutaoException(HutaoExceptionKind.GachaStatisticsInvalidItemId, message, innerException);
}
}

View File

@@ -6,4 +6,8 @@ namespace Snap.Hutao.Core.ExceptionService;
internal enum HutaoExceptionKind
{
None,
ServiceTypeCastFailed,
FileSystemCreateFileInsufficientPermissions,
PrivateNamedPipeContentHashIncorrect,
GachaStatisticsInvalidItemId,
}

View File

@@ -29,7 +29,7 @@ internal readonly struct TempFile : IDisposable
}
catch (UnauthorizedAccessException ex)
{
ThrowHelper.RuntimeEnvironment(SH.CoreIOTempFileCreateFail, ex);
HutaoException.Throw(HutaoExceptionKind.FileSystemCreateFileInsufficientPermissions, SH.CoreIOTempFileCreateFail, ex);
}
if (delete)

View File

@@ -148,17 +148,15 @@ internal sealed partial class Activation : IActivation
await taskContext.SwitchToBackgroundAsync();
serviceProvider
.GetRequiredService<IMetadataService>()
.As<IMetadataServiceInitialization>()?
.InitializeInternalAsync()
.SafeForget();
if (serviceProvider.GetRequiredService<IMetadataService>() is IMetadataServiceInitialization metadataServiceInitialization)
{
metadataServiceInitialization.InitializeInternalAsync().SafeForget();
}
serviceProvider
.GetRequiredService<IHutaoUserService>()
.As<IHutaoUserServiceInitialization>()?
.InitializeInternalAsync()
.SafeForget();
if (serviceProvider.GetRequiredService<IHutaoUserService>() is IHutaoUserServiceInitialization hutaoUserServiceInitialization)
{
hutaoUserServiceInitialization.InitializeInternalAsync().SafeForget();
}
serviceProvider
.GetRequiredService<IDiscordService>()

View File

@@ -49,7 +49,7 @@ internal sealed partial class PrivateNamedPipeServer : IDisposable
{
byte[] content = new byte[header->ContentLength];
serverStream.ReadAtLeast(content, header->ContentLength, false);
ThrowHelper.InvalidDataIf(XxHash64.HashToUInt64(content) != header->Checksum, "PipePacket Content Hash incorrect");
HutaoException.ThrowIf(XxHash64.HashToUInt64(content) != header->Checksum, HutaoExceptionKind.PrivateNamedPipeContentHashIncorrect, "PipePacket Content Hash incorrect");
return content;
}

View File

@@ -1,6 +1,7 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Collections.Frozen;
using System.Text;
namespace Snap.Hutao.Core;
@@ -14,25 +15,25 @@ internal static class TypeNameHelper
{
private const char DefaultNestedTypeDelimiter = '+';
private static readonly Dictionary<Type, string> BuiltInTypeNames = new()
{
{ typeof(void), "void" },
{ typeof(bool), "bool" },
{ typeof(byte), "byte" },
{ typeof(char), "char" },
{ typeof(decimal), "decimal" },
{ typeof(double), "double" },
{ typeof(float), "float" },
{ typeof(int), "int" },
{ typeof(long), "long" },
{ typeof(object), "object" },
{ typeof(sbyte), "sbyte" },
{ typeof(short), "short" },
{ typeof(string), "string" },
{ typeof(uint), "uint" },
{ typeof(ulong), "ulong" },
{ typeof(ushort), "ushort" },
};
private static readonly FrozenDictionary<Type, string> BuiltInTypeNames = FrozenDictionary.ToFrozenDictionary(
[
KeyValuePair.Create(typeof(void), "void"),
KeyValuePair.Create(typeof(bool), "bool"),
KeyValuePair.Create(typeof(byte), "byte"),
KeyValuePair.Create(typeof(char), "char"),
KeyValuePair.Create(typeof(decimal), "decimal"),
KeyValuePair.Create(typeof(double), "double"),
KeyValuePair.Create(typeof(float), "float"),
KeyValuePair.Create(typeof(int), "int"),
KeyValuePair.Create(typeof(long), "long"),
KeyValuePair.Create(typeof(object), "object"),
KeyValuePair.Create(typeof(sbyte), "sbyte"),
KeyValuePair.Create(typeof(short), "short"),
KeyValuePair.Create(typeof(string), "string"),
KeyValuePair.Create(typeof(uint), "uint"),
KeyValuePair.Create(typeof(ulong), "ulong"),
KeyValuePair.Create(typeof(ushort), "ushort"),
]);
/// <summary>
/// 获取对象类型的显示名称

View File

@@ -5,6 +5,7 @@ using CommunityToolkit.Mvvm.ComponentModel;
using Snap.Hutao.Core.LifeCycle;
using Snap.Hutao.Core.Setting;
using Snap.Hutao.Model;
using Snap.Hutao.Service.Notification;
using Snap.Hutao.Win32.Foundation;
using Snap.Hutao.Win32.UI.Input.KeyboardAndMouse;
using System.Text;
@@ -17,6 +18,7 @@ namespace Snap.Hutao.Core.Windowing.HotKey;
internal sealed class HotKeyCombination : ObservableObject
{
private readonly ICurrentWindowReference currentWindowReference;
private readonly IInfoBarService infoBarService;
private readonly RuntimeOptions runtimeOptions;
private readonly string settingKey;
@@ -37,6 +39,7 @@ internal sealed class HotKeyCombination : ObservableObject
public HotKeyCombination(IServiceProvider serviceProvider, string settingKey, int hotKeyId, HOT_KEY_MODIFIERS defaultModifiers, VirtualKey defaultKey)
{
currentWindowReference = serviceProvider.GetRequiredService<ICurrentWindowReference>();
infoBarService = serviceProvider.GetRequiredService<IInfoBarService>();
runtimeOptions = serviceProvider.GetRequiredService<RuntimeOptions>();
this.settingKey = settingKey;
@@ -186,6 +189,12 @@ internal sealed class HotKeyCombination : ObservableObject
HWND hwnd = currentWindowReference.GetWindowHandle();
BOOL result = RegisterHotKey(hwnd, hotKeyId, Modifiers, (uint)Key);
registered = result;
if (!result)
{
infoBarService.Warning(SH.FormatCoreWindowHotkeyCombinationRegisterFailed(SH.ViewPageSettingKeyShortcutAutoClickingHeader, DisplayName));
}
return result;
}

View File

@@ -18,20 +18,11 @@ internal static class DateTimeOffsetExtension
return defaultValue;
}
try
return value switch
{
return DateTimeOffset.FromUnixTimeSeconds(value);
}
catch (ArgumentOutOfRangeException)
{
try
{
return DateTimeOffset.FromUnixTimeMilliseconds(value);
}
catch (ArgumentOutOfRangeException)
{
return defaultValue;
}
}
>= -62135596800 and <= 253402300799 => DateTimeOffset.FromUnixTimeSeconds(value),
>= -62135596800000 and <= 253402300799999 => DateTimeOffset.FromUnixTimeMilliseconds(value),
_ => defaultValue,
};
}
}

View File

@@ -15,6 +15,12 @@ namespace Snap.Hutao.Extension;
[HighQuality]
internal static partial class EnumerableExtension
{
public static void Deconstruct<TKey, TElement>(this IGrouping<TKey, TElement> grouping, out TKey key, out IEnumerable<TElement> elements)
{
key = grouping.Key;
elements = grouping;
}
public static TElement? ElementAtOrLastOrDefault<TElement>(this IEnumerable<TElement> source, int index)
{
return source.ElementAtOrDefault(index) ?? source.LastOrDefault();
@@ -46,34 +52,6 @@ internal static partial class EnumerableExtension
return first;
}
public static string JoinToString<T>(this IEnumerable<T> source, char separator, Action<StringBuilder, T> selector)
{
StringBuilder resultBuilder = new();
IEnumerator<T> enumerator = source.GetEnumerator();
if (!enumerator.MoveNext())
{
return string.Empty;
}
T first = enumerator.Current;
selector(resultBuilder, first);
if (!enumerator.MoveNext())
{
return resultBuilder.ToString();
}
do
{
resultBuilder.Append(separator);
selector(resultBuilder, enumerator.Current);
}
while (enumerator.MoveNext());
return resultBuilder.ToString();
}
public static string JoinToString<TKey, TValue>(this IEnumerable<KeyValuePair<TKey, TValue>> source, char separator, Action<StringBuilder, TKey, TValue> selector)
{
StringBuilder resultBuilder = new();

View File

@@ -41,6 +41,30 @@
"Equatable": true,
"EqualityOperators": true
},
{
"Name": "FurnitureId",
"Documentation": "家具 Id",
"Equatable": true,
"EqualityOperators": true
},
{
"Name": "FurnitureMakeId",
"Documentation": "家具配方 Id",
"Equatable": true,
"EqualityOperators": true
},
{
"Name": "FurnitureSuiteId",
"Documentation": "家具套装 Id",
"Equatable": true,
"EqualityOperators": true
},
{
"Name": "FurnitureTypeId",
"Documentation": "家具分类 Id",
"Equatable": true,
"EqualityOperators": true
},
{
"Name": "Level",
"Documentation": "等级 1 - 90",

View File

@@ -27,7 +27,7 @@ internal sealed partial class MainWindow : Window, IWindowOptionsSource, IMinMax
public MainWindow(IServiceProvider serviceProvider)
{
InitializeComponent();
windowOptions = new(this, TitleBarView.DragArea, new(1200, 741), true, false);
windowOptions = new(this, TitleBarView.DragArea, new(1200, 741), true);
this.InitializeController(serviceProvider);
}

View File

@@ -0,0 +1,6 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Message;
internal sealed class BackgroundImageTypeChangedMessage;

View File

@@ -11,10 +11,16 @@ internal static class CollectionsNameValue
return [.. Enum.GetValues<TEnum>().Select(x => new NameValue<TEnum>(x.ToString(), x))];
}
public static List<NameValue<TEnum>> FromEnum<TEnum>(Func<TEnum, bool> codiction)
public static List<NameValue<TEnum>> FromEnum<TEnum>(Func<TEnum, bool> condition)
where TEnum : struct, Enum
{
return [.. Enum.GetValues<TEnum>().Where(codiction).Select(x => new NameValue<TEnum>(x.ToString(), x))];
return [.. Enum.GetValues<TEnum>().Where(condition).Select(x => new NameValue<TEnum>(x.ToString(), x))];
}
public static List<NameValue<TEnum>> FromEnum<TEnum>(Func<TEnum, string> nameSelector)
where TEnum : struct, Enum
{
return [.. Enum.GetValues<TEnum>().Select(x => new NameValue<TEnum>(nameSelector(x), x))];
}
public static List<NameValue<TSource>> From<TSource>(IEnumerable<TSource> sources, Func<TSource, string> nameSelector)

View File

@@ -42,14 +42,14 @@ internal sealed partial class GachaItem
/// <summary>
/// 祈愿记录分类
/// </summary>
public GachaConfigType GachaType { get; set; }
public GachaType GachaType { get; set; }
/// <summary>
/// 祈愿记录查询分类
/// 合并保底的卡池使用此属性
/// 仅4种不含400
/// </summary>
public GachaConfigType QueryType { get; set; }
public GachaType QueryType { get; set; }
/// <summary>
/// 物品Id

View File

@@ -20,7 +20,7 @@ internal sealed class UIGFItem : GachaLogItem, IMappingFrom<UIGFItem, GachaItem,
/// </summary>
[JsonPropertyName("uigf_gacha_type")]
[JsonEnum(JsonSerializeType.NumberString)]
public GachaConfigType UIGFGachaType { get; set; } = default!;
public GachaType UIGFGachaType { get; set; } = default!;
public static UIGFItem From(GachaItem item, INameQuality nameQuality)
{

View File

@@ -0,0 +1,25 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Model.Intrinsic;
internal enum FurnitureDeploySurfaceType
{
Ground = 0,
Wall = 1,
Ceil = 2,
StackObjPlane = 3,
Door = 4,
Chandelier = 5,
Floor = 6,
WallBody = 7,
Carpet = 8,
LegoRockery = 9,
Stair = 10,
NPC = 11,
Animal = 12,
Apartment = 13,
FurnitureSuite = 14,
Road = 15,
Terrain = 16,
}

View File

@@ -0,0 +1,13 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Model.Intrinsic;
internal enum FurnitureDeployType
{
Interior,
Exterior,
InteriorRoom,
InteriorHall,
Skybox,
}

View File

@@ -0,0 +1,14 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Model.Intrinsic;
internal enum GroupRecordType
{
None,
Racing,
Balloon,
Stake,
Seek,
Explosion,
}

View File

@@ -0,0 +1,26 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Model.Intrinsic;
internal enum SpecialFurnitureType
{
NormalFurniture,
BlockDependent,
FarmField,
TeleportPoint,
Fishpond,
Npc,
Apartment,
FurnitureSuite,
Paimon,
Fish,
CustomBaseFurniture,
CustomNodeFurniture,
VirtualFurniture,
GroupFurniture,
CoopPictureFrame,
ChangeBgmFurniture,
ServerGadget,
Fishtank,
}

View File

@@ -8,15 +8,4 @@ namespace Snap.Hutao.Model.Metadata.Achievement;
/// <summary>
/// 奖励
/// </summary>
internal sealed class Reward
{
/// <summary>
/// Id
/// </summary>
public MaterialId Id { get; set; }
/// <summary>
/// 数量
/// </summary>
public uint Count { get; set; }
}
internal sealed class Reward : IdCount;

View File

@@ -0,0 +1,55 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Control;
using Snap.Hutao.Model.Intrinsic;
using System.Collections.Frozen;
namespace Snap.Hutao.Model.Metadata.Converter;
internal sealed class AssociationTypeIconConverter : ValueConverter<AssociationType, Uri?>
{
private static readonly FrozenDictionary<string, AssociationType> LocalizedNameToAssociationType = FrozenDictionary.ToFrozenDictionary(
[
KeyValuePair.Create(SH.ModelIntrinsicAssociationTypeMondstadt, AssociationType.ASSOC_TYPE_MONDSTADT),
KeyValuePair.Create(SH.ModelIntrinsicAssociationTypeLiyue, AssociationType.ASSOC_TYPE_LIYUE),
KeyValuePair.Create(SH.ModelIntrinsicAssociationTypeFatui, AssociationType.ASSOC_TYPE_FATUI),
KeyValuePair.Create(SH.ModelIntrinsicAssociationTypeInazuma, AssociationType.ASSOC_TYPE_INAZUMA),
KeyValuePair.Create(SH.ModelIntrinsicAssociationTypeRanger, AssociationType.ASSOC_TYPE_RANGER),
KeyValuePair.Create(SH.ModelIntrinsicAssociationTypeSumeru, AssociationType.ASSOC_TYPE_SUMERU),
KeyValuePair.Create(SH.ModelIntrinsicAssociationTypeFontaine, AssociationType.ASSOC_TYPE_FONTAINE),
KeyValuePair.Create(SH.ModelIntrinsicAssociationTypeNatlan, AssociationType.ASSOC_TYPE_NATLAN),
KeyValuePair.Create(SH.ModelIntrinsicAssociationTypeSnezhnaya, AssociationType.ASSOC_TYPE_SNEZHNAYA),
]);
public static Uri? AssociationTypeNameToIconUri(string associationTypeName)
{
return AssociationTypeToIconUri(LocalizedNameToAssociationType.GetValueOrDefault(associationTypeName));
}
public static Uri? AssociationTypeToIconUri(AssociationType type)
{
string? association = type switch
{
AssociationType.ASSOC_TYPE_MONDSTADT => "Mengde",
AssociationType.ASSOC_TYPE_LIYUE => "Liyue",
AssociationType.ASSOC_TYPE_FATUI => default,
AssociationType.ASSOC_TYPE_INAZUMA => "Inazuma",
AssociationType.ASSOC_TYPE_RANGER => default,
AssociationType.ASSOC_TYPE_SUMERU => "Sumeru",
AssociationType.ASSOC_TYPE_FONTAINE => "Fontaine",
AssociationType.ASSOC_TYPE_NATLAN => default,
AssociationType.ASSOC_TYPE_SNEZHNAYA => default,
_ => throw Must.NeverHappen(),
};
return association is null
? default
: Web.HutaoEndpoints.StaticRaw("ChapterIcon", $"UI_ChapterIcon_{association}.png").ToUri();
}
public override Uri? Convert(AssociationType from)
{
return AssociationTypeToIconUri(from);
}
}

View File

@@ -3,6 +3,7 @@
using Snap.Hutao.Control;
using Snap.Hutao.Model.Intrinsic;
using System.Collections.Frozen;
namespace Snap.Hutao.Model.Metadata.Converter;
@@ -12,27 +13,27 @@ namespace Snap.Hutao.Model.Metadata.Converter;
[HighQuality]
internal sealed class ElementNameIconConverter : ValueConverter<string, Uri>
{
private static readonly Dictionary<string, string> LocalizedNameToElementIconName = new()
{
[SH.ModelIntrinsicElementNameElec] = "Electric",
[SH.ModelIntrinsicElementNameFire] = "Fire",
[SH.ModelIntrinsicElementNameGrass] = "Grass",
[SH.ModelIntrinsicElementNameIce] = "Ice",
[SH.ModelIntrinsicElementNameRock] = "Rock",
[SH.ModelIntrinsicElementNameWater] = "Water",
[SH.ModelIntrinsicElementNameWind] = "Wind",
};
private static readonly FrozenDictionary<string, string> LocalizedNameToElementIconName = FrozenDictionary.ToFrozenDictionary(
[
KeyValuePair.Create(SH.ModelIntrinsicElementNameElec, "Electric"),
KeyValuePair.Create(SH.ModelIntrinsicElementNameFire, "Fire"),
KeyValuePair.Create(SH.ModelIntrinsicElementNameGrass, "Grass"),
KeyValuePair.Create(SH.ModelIntrinsicElementNameIce, "Ice"),
KeyValuePair.Create(SH.ModelIntrinsicElementNameRock, "Rock"),
KeyValuePair.Create(SH.ModelIntrinsicElementNameWater, "Water"),
KeyValuePair.Create(SH.ModelIntrinsicElementNameWind, "Wind"),
]);
private static readonly Dictionary<string, ElementType> LocalizedNameToElementType = new()
{
[SH.ModelIntrinsicElementNameElec] = ElementType.Electric,
[SH.ModelIntrinsicElementNameFire] = ElementType.Fire,
[SH.ModelIntrinsicElementNameGrass] = ElementType.Grass,
[SH.ModelIntrinsicElementNameIce] = ElementType.Ice,
[SH.ModelIntrinsicElementNameRock] = ElementType.Rock,
[SH.ModelIntrinsicElementNameWater] = ElementType.Water,
[SH.ModelIntrinsicElementNameWind] = ElementType.Wind,
};
private static readonly FrozenDictionary<string, ElementType> LocalizedNameToElementType = FrozenDictionary.ToFrozenDictionary(
[
KeyValuePair.Create(SH.ModelIntrinsicElementNameElec, ElementType.Electric),
KeyValuePair.Create(SH.ModelIntrinsicElementNameFire, ElementType.Fire),
KeyValuePair.Create(SH.ModelIntrinsicElementNameGrass, ElementType.Grass),
KeyValuePair.Create(SH.ModelIntrinsicElementNameIce, ElementType.Ice),
KeyValuePair.Create(SH.ModelIntrinsicElementNameRock, ElementType.Rock),
KeyValuePair.Create(SH.ModelIntrinsicElementNameWater, ElementType.Water),
KeyValuePair.Create(SH.ModelIntrinsicElementNameWind, ElementType.Wind),
]);
/// <summary>
/// 将中文元素名称转换为图标链接

View File

@@ -3,8 +3,9 @@
using Microsoft.UI;
using Snap.Hutao.Control;
using Snap.Hutao.Control.Theme;
using Snap.Hutao.Model.Intrinsic;
using Snap.Hutao.Win32;
using System.Collections.Frozen;
using Windows.UI;
namespace Snap.Hutao.Model.Metadata.Converter;
@@ -15,17 +16,39 @@ namespace Snap.Hutao.Model.Metadata.Converter;
[HighQuality]
internal sealed class QualityColorConverter : ValueConverter<QualityType, Color>
{
private static readonly FrozenDictionary<string, QualityType> LocalizedNameToQualityType = FrozenDictionary.ToFrozenDictionary(
[
KeyValuePair.Create(SH.ModelIntrinsicItemQualityWhite, QualityType.QUALITY_WHITE),
KeyValuePair.Create(SH.ModelIntrinsicItemQualityGreen, QualityType.QUALITY_GREEN),
KeyValuePair.Create(SH.ModelIntrinsicItemQualityBlue, QualityType.QUALITY_BLUE),
KeyValuePair.Create(SH.ModelIntrinsicItemQualityPurple, QualityType.QUALITY_PURPLE),
KeyValuePair.Create(SH.ModelIntrinsicItemQualityOrange, QualityType.QUALITY_ORANGE),
KeyValuePair.Create(SH.ModelIntrinsicItemQualityRed, QualityType.QUALITY_ORANGE_SP),
]);
private static readonly FrozenDictionary<QualityType, Color> QualityTypeToColor = FrozenDictionary.ToFrozenDictionary(
[
KeyValuePair.Create(QualityType.QUALITY_WHITE, KnownColors.White),
KeyValuePair.Create(QualityType.QUALITY_GREEN, KnownColors.Green),
KeyValuePair.Create(QualityType.QUALITY_BLUE, KnownColors.Blue),
KeyValuePair.Create(QualityType.QUALITY_PURPLE, KnownColors.Purple),
KeyValuePair.Create(QualityType.QUALITY_ORANGE, KnownColors.Orange),
KeyValuePair.Create(QualityType.QUALITY_ORANGE_SP, KnownColors.Orange),
]);
public static Color QualityNameToColor(string qualityName)
{
return QualityToColor(LocalizedNameToQualityType.GetValueOrDefault(qualityName));
}
public static Color QualityToColor(QualityType quality)
{
return QualityTypeToColor.GetValueOrDefault(quality, Colors.Transparent);
}
/// <inheritdoc/>
public override Color Convert(QualityType from)
{
return from switch
{
QualityType.QUALITY_WHITE => StructMarshal.Color(0xFF72778B),
QualityType.QUALITY_GREEN => StructMarshal.Color(0xFF2A8F72),
QualityType.QUALITY_BLUE => StructMarshal.Color(0xFF5180CB),
QualityType.QUALITY_PURPLE => StructMarshal.Color(0xFFA156E0),
QualityType.QUALITY_ORANGE or QualityType.QUALITY_ORANGE_SP => StructMarshal.Color(0xFFBC6932),
_ => Colors.Transparent,
};
return QualityToColor(from);
}
}

View File

@@ -3,6 +3,7 @@
using Snap.Hutao.Control;
using Snap.Hutao.Model.Intrinsic;
using System.Collections.Frozen;
namespace Snap.Hutao.Model.Metadata.Converter;
@@ -12,6 +13,20 @@ namespace Snap.Hutao.Model.Metadata.Converter;
[HighQuality]
internal sealed class WeaponTypeIconConverter : ValueConverter<WeaponType, Uri>
{
private static readonly FrozenDictionary<string, WeaponType> LocalizedNameToWeaponType = FrozenDictionary.ToFrozenDictionary(
[
KeyValuePair.Create(SH.ModelIntrinsicWeaponTypeSwordOneHand, WeaponType.WEAPON_SWORD_ONE_HAND),
KeyValuePair.Create(SH.ModelIntrinsicWeaponTypeBow, WeaponType.WEAPON_BOW),
KeyValuePair.Create(SH.ModelIntrinsicWeaponTypePole, WeaponType.WEAPON_POLE),
KeyValuePair.Create(SH.ModelIntrinsicWeaponTypeClaymore, WeaponType.WEAPON_CLAYMORE),
KeyValuePair.Create(SH.ModelIntrinsicWeaponTypeCatalyst, WeaponType.WEAPON_CATALYST),
]);
public static Uri WeaponTypeNameToIconUri(string weaponTypeName)
{
return WeaponTypeToIconUri(LocalizedNameToWeaponType.GetValueOrDefault(weaponTypeName));
}
/// <summary>
/// 将武器类型转换为图标链接
/// </summary>

View File

@@ -0,0 +1,50 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Model.Intrinsic;
using Snap.Hutao.Model.Primitive;
namespace Snap.Hutao.Model.Metadata.Furniture;
internal sealed class Furniture
{
public List<FurnitureTypeId> Types { get; set; } = default!;
public FurnitureDeploySurfaceType SurfaceType { get; set; }
public bool IsSpecial { get; set; }
public SpecialFurnitureType SpecialType { get; set; }
public uint Comfort { get; set; }
public uint Cost { get; set; }
public uint DiscountCost { get; set; }
public bool CanFloat { get; set; }
public bool IsUnique { get; set; }
public string? ItemIcon { get; set; }
public string? EffectIcon { get; set; }
public QualityType RankLevel { get; set; }
public List<FurnitureId> GruopUnits { get; set; } = default!;
public GroupRecordType GroupRecordType { get; set; }
public List<string> SourceTexts { get; set; } = default!;
public FurnitureId Id { get; set; }
public string Name { get; set; } = default!;
public string Description { get; set; } = default!;
public string? Icon { get; set; }
public uint Rank { get; set; }
}

View File

@@ -0,0 +1,18 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Model.Intrinsic;
using Snap.Hutao.Model.Primitive;
namespace Snap.Hutao.Model.Metadata.Furniture;
internal sealed class FurnitureMake
{
public FurnitureMakeId Id { get; set; }
public FurnitureId ItemId { get; set; }
public uint Experience { get; set; }
public List<IdCount> Materials { get; set; } = default!;
}

View File

@@ -0,0 +1,26 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Model.Intrinsic;
using Snap.Hutao.Model.Primitive;
namespace Snap.Hutao.Model.Metadata.Furniture;
internal sealed class FurnitureSuite
{
public FurnitureSuiteId Id { get; set; }
public List<FurnitureTypeId> Types { get; set; } = default!;
public string Name { get; set; } = default!;
public string Description { get; set; } = default!;
public string ItemIcon { get; set; } = default!;
public string? MapIcon { get; set; }
public List<AvatarId>? FavoriteNpcs { get; set; }
public List<FurnitureId> Units { get; set; } = default!;
}

View File

@@ -0,0 +1,28 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Model.Intrinsic;
using Snap.Hutao.Model.Primitive;
namespace Snap.Hutao.Model.Metadata.Furniture;
internal sealed class FurnitureType
{
public FurnitureTypeId Id { get; set; }
public uint Category { get; set; }
public string Name { get; set; } = default!;
public string Name2 { get; set; } = default!;
public string TabIcon { get; set; } = default!;
public FurnitureDeployType SceneType { get; set; }
public bool BagPageOnly { get; set; }
public bool IsShowInBag { get; set; }
public uint Sort { get; set; }
}

View File

@@ -49,7 +49,7 @@ internal sealed class GachaEvent
/// <summary>
/// 卡池类型
/// </summary>
public GachaConfigType Type { get; set; }
public GachaType Type { get; set; }
/// <summary>
/// 五星列表

View File

@@ -0,0 +1,20 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Model.Intrinsic;
using Snap.Hutao.Model.Primitive;
namespace Snap.Hutao.Model.Metadata;
internal class IdCount
{
/// <summary>
/// Id
/// </summary>
public MaterialId Id { get; set; }
/// <summary>
/// 数量
/// </summary>
public uint Count { get; set; }
}

View File

@@ -13,7 +13,7 @@
<Identity
Name="60568DGPStudio.SnapHutao"
Publisher="CN=35C8E923-85DF-49A7-9172-B39DC6312C52"
Version="1.9.6.0" />
Version="1.9.7.0" />
<Properties>
<DisplayName>Snap Hutao</DisplayName>

View File

@@ -13,7 +13,7 @@
<Identity
Name="60568DGPStudio.SnapHutaoDev"
Publisher="CN=35C8E923-85DF-49A7-9172-B39DC6312C52"
Version="1.9.6.0" />
Version="1.9.7.0" />
<Properties>
<DisplayName>Snap Hutao Dev</DisplayName>

View File

@@ -2,7 +2,7 @@
"profiles": {
"Snap.Hutao": {
"commandName": "MsixPackage",
"nativeDebugging": false,
"nativeDebugging": true,
"doNotLaunchApp": false,
"allowLocalNetworkLoopbackProperty": true
},

View File

@@ -145,7 +145,7 @@
<value>Save</value>
</data>
<data name="ControlImageCachedImageInvalidResourceUri" xml:space="preserve">
<value>Invalid URL</value>
<value>Invalid Uri</value>
</data>
<data name="ControlImageCompositionImageHttpRequest" xml:space="preserve">
<value>HTTP GET {0}</value>
@@ -285,7 +285,7 @@
<comment>Need EXACT same string in game</comment>
</data>
<data name="ModelIntrinsicAssociationTypeInazuma" xml:space="preserve">
<value>Inadzuma</value>
<value>Inazuma</value>
<comment>Need EXACT same string in game</comment>
</data>
<data name="ModelIntrinsicAssociationTypeLiyue" xml:space="preserve">
@@ -546,7 +546,7 @@
<value>Uploaded {1} wish records of UID: {0}, stored {2}</value>
</data>
<data name="ServerPassportLoginRequired" xml:space="preserve">
<value>Please login or register Hutao account first</value>
<value>Please login or register Snap Hutao account first</value>
</data>
<data name="ServerPassportLoginSucceed" xml:space="preserve">
<value>Login successfully</value>
@@ -558,7 +558,7 @@
<value>Password has been set successfully</value>
</data>
<data name="ServerPassportServiceEmailHasNotRegistered" xml:space="preserve">
<value>Current email adress is not registered</value>
<value>Current email address is not registered</value>
</data>
<data name="ServerPassportServiceEmailHasRegistered" xml:space="preserve">
<value>Current emaill address is registered</value>
@@ -767,6 +767,21 @@
<data name="ServiceAvatarInfoSummaryShowcaseRefreshTimeFormat" xml:space="preserve">
<value>Character Showcase: {0:MM-dd HH:mm}</value>
</data>
<data name="ServiceBackgroundImageTypeBing" xml:space="preserve">
<value>Bing Daily Wallpaper</value>
</data>
<data name="ServiceBackgroundImageTypeDaily" xml:space="preserve">
<value>Hutao Daily Wallpaper</value>
</data>
<data name="ServiceBackgroundImageTypeLauncher" xml:space="preserve">
<value>Genshin Official Launcher Wallpaper</value>
</data>
<data name="ServiceBackgroundImageTypeLocalFolder" xml:space="preserve">
<value>Local Random Image</value>
</data>
<data name="ServiceBackgroundImageTypeNone" xml:space="preserve">
<value>No Wallpaper</value>
</data>
<data name="ServiceCultivationProjectCurrentUserdataCourrpted" xml:space="preserve">
<value>Failed to save development plan status</value>
</data>
@@ -824,6 +839,9 @@
<data name="ServiceDailyNoteNotifierTransformerHint" xml:space="preserve">
<value>Parametric Transformer is ready</value>
</data>
<data name="ServiceDiscordActivityElevationRequiredHint" xml:space="preserve">
<value>Missing permission, unable to set your Discord Activity.</value>
</data>
<data name="ServiceDiscordGameActivityDetails" xml:space="preserve">
<value>Exploring in Teyvat</value>
</data>
@@ -1391,6 +1409,9 @@
<data name="ViewLaunchGameHeader" xml:space="preserve">
<value>Game Launcher</value>
</data>
<data name="ViewListViewDragElevatedHint" xml:space="preserve">
<value>Cannot reorder in Admin Mode</value>
</data>
<data name="ViewModelAchievementArchiveAdded" xml:space="preserve">
<value>Archive [{0}] added successfully</value>
</data>
@@ -1670,6 +1691,9 @@
<data name="ViewModelWelcomeDownloadSummaryComplete" xml:space="preserve">
<value>Completed</value>
</data>
<data name="ViewModelWelcomeDownloadSummaryContentTypeNotMatch" xml:space="preserve">
<value>Response stream does not contain valid content type</value>
</data>
<data name="ViewModelWelcomeDownloadSummaryDefault" xml:space="preserve">
<value>Queued</value>
</data>
@@ -2186,6 +2210,12 @@
<data name="ViewPageLaunchGameArgumentsHeader" xml:space="preserve">
<value>Launch Parameters</value>
</data>
<data name="ViewPageLaunchGameBetterGIDescription" xml:space="preserve">
<value>Auto start Better GUI for automation tasks after game launched</value>
</data>
<data name="ViewPageLaunchGameBetterGIHeader" xml:space="preserve">
<value>Better GI</value>
</data>
<data name="ViewPageLaunchGameCommonHeader" xml:space="preserve">
<value>General</value>
</data>
@@ -2327,6 +2357,15 @@
<data name="ViewPageSettingBackdropMaterialHeader" xml:space="preserve">
<value>Backdrop Material</value>
</data>
<data name="ViewPageSettingBackgroundImageCopyrightHeader" xml:space="preserve">
<value>Image Copyright Information</value>
</data>
<data name="ViewPageSettingBackgroundImageDescription" xml:space="preserve">
<value>Change the source of wallpaper, restart Snap Hutao to apply the change</value>
</data>
<data name="ViewPageSettingBackgroundImageHeader" xml:space="preserve">
<value>Wallpaper Image</value>
</data>
<data name="ViewPageSettingCacheFolderDescription" xml:space="preserve">
<value>Images cache are saved here</value>
</data>
@@ -2544,10 +2583,10 @@
<value>Reset Image Resource</value>
</data>
<data name="ViewPageSettingsAdvancedOptionsLaunchUnlockFpsDescription" xml:space="preserve">
<value>在启动游戏页面的进程部分加入解锁帧率限制选项</value>
<value>Add Unlock Frame Rate Limit Option in Game Launcher Process Section</value>
</data>
<data name="ViewPageSettingsAdvancedOptionsLaunchUnlockFpsHeader" xml:space="preserve">
<value>启动游戏-解锁帧率限制</value>
<value>Game Launcher - Unlock Frame Rate Limit</value>
</data>
<data name="ViewPageSettingSetDataFolderDescription" xml:space="preserve">
<value>You need to move data in the directory manually, otherwise new user data will be created.</value>

View File

@@ -767,6 +767,21 @@
<data name="ServiceAvatarInfoSummaryShowcaseRefreshTimeFormat" xml:space="preserve">
<value>Pameran Karakter: {0:MM-dd HH:mm}</value>
</data>
<data name="ServiceBackgroundImageTypeBing" xml:space="preserve">
<value>必应每日一图</value>
</data>
<data name="ServiceBackgroundImageTypeDaily" xml:space="preserve">
<value>胡桃每日一图</value>
</data>
<data name="ServiceBackgroundImageTypeLauncher" xml:space="preserve">
<value>官方启动器壁纸</value>
</data>
<data name="ServiceBackgroundImageTypeLocalFolder" xml:space="preserve">
<value>本地随机图片</value>
</data>
<data name="ServiceBackgroundImageTypeNone" xml:space="preserve">
<value>无背景图片</value>
</data>
<data name="ServiceCultivationProjectCurrentUserdataCourrpted" xml:space="preserve">
<value>Gagal menyimpan status rencana pengembangan</value>
</data>
@@ -824,6 +839,9 @@
<data name="ServiceDailyNoteNotifierTransformerHint" xml:space="preserve">
<value>Parametric Transformer telah siap</value>
</data>
<data name="ServiceDiscordActivityElevationRequiredHint" xml:space="preserve">
<value>权限不足,将无法为您设置 Discord Activity 状态</value>
</data>
<data name="ServiceDiscordGameActivityDetails" xml:space="preserve">
<value>Menjelajahi di Teyvat</value>
</data>
@@ -1391,6 +1409,9 @@
<data name="ViewLaunchGameHeader" xml:space="preserve">
<value>Game Launcher</value>
</data>
<data name="ViewListViewDragElevatedHint" xml:space="preserve">
<value>管理员模式下无法拖动排序</value>
</data>
<data name="ViewModelAchievementArchiveAdded" xml:space="preserve">
<value>Arsip [{0}] berhasil ditambahkan</value>
</data>
@@ -1670,6 +1691,9 @@
<data name="ViewModelWelcomeDownloadSummaryComplete" xml:space="preserve">
<value>Selesai</value>
</data>
<data name="ViewModelWelcomeDownloadSummaryContentTypeNotMatch" xml:space="preserve">
<value>响应内容不是有效的文件字节流</value>
</data>
<data name="ViewModelWelcomeDownloadSummaryDefault" xml:space="preserve">
<value>Mengantre</value>
</data>
@@ -2010,7 +2034,7 @@
<value>Pratinjau</value>
</data>
<data name="ViewPageGahcaLogPivotStatistics" xml:space="preserve">
<value>全球祈愿统计</value>
<value>Statistik</value>
</data>
<data name="ViewPageGahcaLogPivotWeapon" xml:space="preserve">
<value>Senjata</value>
@@ -2186,6 +2210,12 @@
<data name="ViewPageLaunchGameArgumentsHeader" xml:space="preserve">
<value>Argumen Awalan</value>
</data>
<data name="ViewPageLaunchGameBetterGIDescription" xml:space="preserve">
<value>在游戏启动后尝试启动并使用 Better GI 进行自动化任务</value>
</data>
<data name="ViewPageLaunchGameBetterGIHeader" xml:space="preserve">
<value>Better GI</value>
</data>
<data name="ViewPageLaunchGameCommonHeader" xml:space="preserve">
<value>Umum</value>
</data>
@@ -2327,6 +2357,15 @@
<data name="ViewPageSettingBackdropMaterialHeader" xml:space="preserve">
<value>Backdrop Material</value>
</data>
<data name="ViewPageSettingBackgroundImageCopyrightHeader" xml:space="preserve">
<value>图片版权信息</value>
</data>
<data name="ViewPageSettingBackgroundImageDescription" xml:space="preserve">
<value>更改窗体的背景图片来源,重启胡桃以尽快生效</value>
</data>
<data name="ViewPageSettingBackgroundImageHeader" xml:space="preserve">
<value>背景图片</value>
</data>
<data name="ViewPageSettingCacheFolderDescription" xml:space="preserve">
<value>Cache gambar disimpan di sini</value>
</data>

View File

@@ -767,6 +767,21 @@
<data name="ServiceAvatarInfoSummaryShowcaseRefreshTimeFormat" xml:space="preserve">
<value>キャラクターラインナップ:{0:MM-dd HH:mm}</value>
</data>
<data name="ServiceBackgroundImageTypeBing" xml:space="preserve">
<value>必应每日一图</value>
</data>
<data name="ServiceBackgroundImageTypeDaily" xml:space="preserve">
<value>胡桃每日一图</value>
</data>
<data name="ServiceBackgroundImageTypeLauncher" xml:space="preserve">
<value>官方启动器壁纸</value>
</data>
<data name="ServiceBackgroundImageTypeLocalFolder" xml:space="preserve">
<value>本地随机图片</value>
</data>
<data name="ServiceBackgroundImageTypeNone" xml:space="preserve">
<value>无背景图片</value>
</data>
<data name="ServiceCultivationProjectCurrentUserdataCourrpted" xml:space="preserve">
<value>育成計画のステータスを保存できません</value>
</data>
@@ -824,6 +839,9 @@
<data name="ServiceDailyNoteNotifierTransformerHint" xml:space="preserve">
<value>参量物質変化器は使用可能</value>
</data>
<data name="ServiceDiscordActivityElevationRequiredHint" xml:space="preserve">
<value>权限不足,将无法为您设置 Discord Activity 状态</value>
</data>
<data name="ServiceDiscordGameActivityDetails" xml:space="preserve">
<value>テイワット大陸を探索中</value>
</data>
@@ -1391,6 +1409,9 @@
<data name="ViewLaunchGameHeader" xml:space="preserve">
<value>ゲームランチャー</value>
</data>
<data name="ViewListViewDragElevatedHint" xml:space="preserve">
<value>管理员模式下无法拖动排序</value>
</data>
<data name="ViewModelAchievementArchiveAdded" xml:space="preserve">
<value>アーカイブ [{0}] を作成しました</value>
</data>
@@ -1670,6 +1691,9 @@
<data name="ViewModelWelcomeDownloadSummaryComplete" xml:space="preserve">
<value>完了</value>
</data>
<data name="ViewModelWelcomeDownloadSummaryContentTypeNotMatch" xml:space="preserve">
<value>响应内容不是有效的文件字节流</value>
</data>
<data name="ViewModelWelcomeDownloadSummaryDefault" xml:space="preserve">
<value>待機中</value>
</data>
@@ -2010,7 +2034,7 @@
<value>一覧</value>
</data>
<data name="ViewPageGahcaLogPivotStatistics" xml:space="preserve">
<value>全球祈愿统计</value>
<value>統計</value>
</data>
<data name="ViewPageGahcaLogPivotWeapon" xml:space="preserve">
<value>武器</value>
@@ -2186,6 +2210,12 @@
<data name="ViewPageLaunchGameArgumentsHeader" xml:space="preserve">
<value>コマンドラインパラメーター</value>
</data>
<data name="ViewPageLaunchGameBetterGIDescription" xml:space="preserve">
<value>在游戏启动后尝试启动并使用 Better GI 进行自动化任务</value>
</data>
<data name="ViewPageLaunchGameBetterGIHeader" xml:space="preserve">
<value>Better GI</value>
</data>
<data name="ViewPageLaunchGameCommonHeader" xml:space="preserve">
<value>一般</value>
</data>
@@ -2327,6 +2357,15 @@
<data name="ViewPageSettingBackdropMaterialHeader" xml:space="preserve">
<value>テーマ</value>
</data>
<data name="ViewPageSettingBackgroundImageCopyrightHeader" xml:space="preserve">
<value>图片版权信息</value>
</data>
<data name="ViewPageSettingBackgroundImageDescription" xml:space="preserve">
<value>更改窗体的背景图片来源,重启胡桃以尽快生效</value>
</data>
<data name="ViewPageSettingBackgroundImageHeader" xml:space="preserve">
<value>背景图片</value>
</data>
<data name="ViewPageSettingCacheFolderDescription" xml:space="preserve">
<value>イメージキャッシュはここに格納されます</value>
</data>

View File

@@ -767,6 +767,21 @@
<data name="ServiceAvatarInfoSummaryShowcaseRefreshTimeFormat" xml:space="preserve">
<value>角色橱窗:{0:MM-dd HH:mm}</value>
</data>
<data name="ServiceBackgroundImageTypeBing" xml:space="preserve">
<value>必应每日一图</value>
</data>
<data name="ServiceBackgroundImageTypeDaily" xml:space="preserve">
<value>胡桃每日一图</value>
</data>
<data name="ServiceBackgroundImageTypeLauncher" xml:space="preserve">
<value>官方启动器壁纸</value>
</data>
<data name="ServiceBackgroundImageTypeLocalFolder" xml:space="preserve">
<value>本地随机图片</value>
</data>
<data name="ServiceBackgroundImageTypeNone" xml:space="preserve">
<value>无背景图片</value>
</data>
<data name="ServiceCultivationProjectCurrentUserdataCourrpted" xml:space="preserve">
<value>육성 계획 상태를 저장하지 못했습니다</value>
</data>
@@ -824,6 +839,9 @@
<data name="ServiceDailyNoteNotifierTransformerHint" xml:space="preserve">
<value>매개 변수 변환기가 준비되었습니다</value>
</data>
<data name="ServiceDiscordActivityElevationRequiredHint" xml:space="preserve">
<value>权限不足,将无法为您设置 Discord Activity 状态</value>
</data>
<data name="ServiceDiscordGameActivityDetails" xml:space="preserve">
<value>正在提瓦特大陆中探索</value>
</data>
@@ -1391,6 +1409,9 @@
<data name="ViewLaunchGameHeader" xml:space="preserve">
<value>게임 시작</value>
</data>
<data name="ViewListViewDragElevatedHint" xml:space="preserve">
<value>管理员模式下无法拖动排序</value>
</data>
<data name="ViewModelAchievementArchiveAdded" xml:space="preserve">
<value>아카이브 [{0}]가 추가되었습니다</value>
</data>
@@ -1670,6 +1691,9 @@
<data name="ViewModelWelcomeDownloadSummaryComplete" xml:space="preserve">
<value>완료</value>
</data>
<data name="ViewModelWelcomeDownloadSummaryContentTypeNotMatch" xml:space="preserve">
<value>响应内容不是有效的文件字节流</value>
</data>
<data name="ViewModelWelcomeDownloadSummaryDefault" xml:space="preserve">
<value>대기 중</value>
</data>
@@ -2010,7 +2034,7 @@
<value>개요</value>
</data>
<data name="ViewPageGahcaLogPivotStatistics" xml:space="preserve">
<value>全球祈愿统计</value>
<value>统计</value>
</data>
<data name="ViewPageGahcaLogPivotWeapon" xml:space="preserve">
<value>무기</value>
@@ -2186,6 +2210,12 @@
<data name="ViewPageLaunchGameArgumentsHeader" xml:space="preserve">
<value>启动参数</value>
</data>
<data name="ViewPageLaunchGameBetterGIDescription" xml:space="preserve">
<value>在游戏启动后尝试启动并使用 Better GI 进行自动化任务</value>
</data>
<data name="ViewPageLaunchGameBetterGIHeader" xml:space="preserve">
<value>Better GI</value>
</data>
<data name="ViewPageLaunchGameCommonHeader" xml:space="preserve">
<value>보통</value>
</data>
@@ -2327,6 +2357,15 @@
<data name="ViewPageSettingBackdropMaterialHeader" xml:space="preserve">
<value>배경 테마</value>
</data>
<data name="ViewPageSettingBackgroundImageCopyrightHeader" xml:space="preserve">
<value>图片版权信息</value>
</data>
<data name="ViewPageSettingBackgroundImageDescription" xml:space="preserve">
<value>更改窗体的背景图片来源,重启胡桃以尽快生效</value>
</data>
<data name="ViewPageSettingBackgroundImageHeader" xml:space="preserve">
<value>背景图片</value>
</data>
<data name="ViewPageSettingCacheFolderDescription" xml:space="preserve">
<value>여기에 저장된 이미지 캐시</value>
</data>

View File

@@ -767,6 +767,21 @@
<data name="ServiceAvatarInfoSummaryShowcaseRefreshTimeFormat" xml:space="preserve">
<value>Exibição de personagens: {0:MM-dd HH:mm}</value>
</data>
<data name="ServiceBackgroundImageTypeBing" xml:space="preserve">
<value>必应每日一图</value>
</data>
<data name="ServiceBackgroundImageTypeDaily" xml:space="preserve">
<value>胡桃每日一图</value>
</data>
<data name="ServiceBackgroundImageTypeLauncher" xml:space="preserve">
<value>官方启动器壁纸</value>
</data>
<data name="ServiceBackgroundImageTypeLocalFolder" xml:space="preserve">
<value>本地随机图片</value>
</data>
<data name="ServiceBackgroundImageTypeNone" xml:space="preserve">
<value>无背景图片</value>
</data>
<data name="ServiceCultivationProjectCurrentUserdataCourrpted" xml:space="preserve">
<value>Falha ao salvar o status do planejamento</value>
</data>
@@ -824,6 +839,9 @@
<data name="ServiceDailyNoteNotifierTransformerHint" xml:space="preserve">
<value>O Transformador Paramétrico está pronto</value>
</data>
<data name="ServiceDiscordActivityElevationRequiredHint" xml:space="preserve">
<value>权限不足,将无法为您设置 Discord Activity 状态</value>
</data>
<data name="ServiceDiscordGameActivityDetails" xml:space="preserve">
<value>Explorando em Teyvat</value>
</data>
@@ -1391,6 +1409,9 @@
<data name="ViewLaunchGameHeader" xml:space="preserve">
<value>Inicializador</value>
</data>
<data name="ViewListViewDragElevatedHint" xml:space="preserve">
<value>管理员模式下无法拖动排序</value>
</data>
<data name="ViewModelAchievementArchiveAdded" xml:space="preserve">
<value>Arquivo [{0}] adicionado com sucesso</value>
</data>
@@ -1670,6 +1691,9 @@
<data name="ViewModelWelcomeDownloadSummaryComplete" xml:space="preserve">
<value>Concluído</value>
</data>
<data name="ViewModelWelcomeDownloadSummaryContentTypeNotMatch" xml:space="preserve">
<value>响应内容不是有效的文件字节流</value>
</data>
<data name="ViewModelWelcomeDownloadSummaryDefault" xml:space="preserve">
<value>Em fila</value>
</data>
@@ -2186,6 +2210,12 @@
<data name="ViewPageLaunchGameArgumentsHeader" xml:space="preserve">
<value>Argumentos de inicialização</value>
</data>
<data name="ViewPageLaunchGameBetterGIDescription" xml:space="preserve">
<value>在游戏启动后尝试启动并使用 Better GI 进行自动化任务</value>
</data>
<data name="ViewPageLaunchGameBetterGIHeader" xml:space="preserve">
<value>Better GI</value>
</data>
<data name="ViewPageLaunchGameCommonHeader" xml:space="preserve">
<value>Geral</value>
</data>
@@ -2327,6 +2357,15 @@
<data name="ViewPageSettingBackdropMaterialHeader" xml:space="preserve">
<value>Material de pano de fundo</value>
</data>
<data name="ViewPageSettingBackgroundImageCopyrightHeader" xml:space="preserve">
<value>图片版权信息</value>
</data>
<data name="ViewPageSettingBackgroundImageDescription" xml:space="preserve">
<value>更改窗体的背景图片来源,重启胡桃以尽快生效</value>
</data>
<data name="ViewPageSettingBackgroundImageHeader" xml:space="preserve">
<value>背景图片</value>
</data>
<data name="ViewPageSettingCacheFolderDescription" xml:space="preserve">
<value>O cache de imagens é salvo aqui</value>
</data>
@@ -2532,7 +2571,7 @@
<value>自定义背景图片,支持 bmp / gif / ico / jpg / jpeg / png / tiff / webp 格式</value>
</data>
<data name="ViewPageSettingOpenBackgroundImageFolderHeader" xml:space="preserve">
<value>打开背景图片文件夹</value>
<value>Abrir pasta do fundo</value>
</data>
<data name="ViewPageSettingResetAction" xml:space="preserve">
<value>Resetar</value>

View File

@@ -186,6 +186,9 @@
<data name="CoreWebView2HelperVersionUndetected" xml:space="preserve">
<value>未检测到 WebView2 运行时</value>
</data>
<data name="CoreWindowHotkeyCombinationRegisterFailed" xml:space="preserve">
<value>[{0}] 热键 [{1}] 注册失败</value>
</data>
<data name="FilePickerExportCommit" xml:space="preserve">
<value>导出</value>
</data>
@@ -767,6 +770,21 @@
<data name="ServiceAvatarInfoSummaryShowcaseRefreshTimeFormat" xml:space="preserve">
<value>角色橱窗:{0:MM-dd HH:mm}</value>
</data>
<data name="ServiceBackgroundImageTypeBing" xml:space="preserve">
<value>必应每日一图</value>
</data>
<data name="ServiceBackgroundImageTypeDaily" xml:space="preserve">
<value>胡桃每日一图</value>
</data>
<data name="ServiceBackgroundImageTypeLauncher" xml:space="preserve">
<value>官方启动器壁纸</value>
</data>
<data name="ServiceBackgroundImageTypeLocalFolder" xml:space="preserve">
<value>本地随机图片</value>
</data>
<data name="ServiceBackgroundImageTypeNone" xml:space="preserve">
<value>无背景图片</value>
</data>
<data name="ServiceCultivationProjectCurrentUserdataCourrpted" xml:space="preserve">
<value>保存养成计划状态失败</value>
</data>
@@ -842,6 +860,9 @@
<data name="ServiceGachaLogFactoryAvatarWishName" xml:space="preserve">
<value>角色活动</value>
</data>
<data name="ServiceGachaLogFactoryChronicledWishName" xml:space="preserve">
<value>集录祈愿</value>
</data>
<data name="ServiceGachaLogFactoryPermanentWishName" xml:space="preserve">
<value>奔行世间</value>
</data>
@@ -1676,6 +1697,9 @@
<data name="ViewModelWelcomeDownloadSummaryComplete" xml:space="preserve">
<value>完成</value>
</data>
<data name="ViewModelWelcomeDownloadSummaryContentTypeNotMatch" xml:space="preserve">
<value>响应内容不是有效的文件字节流</value>
</data>
<data name="ViewModelWelcomeDownloadSummaryDefault" xml:space="preserve">
<value>等待中</value>
</data>
@@ -2339,6 +2363,15 @@
<data name="ViewPageSettingBackdropMaterialHeader" xml:space="preserve">
<value>背景材质</value>
</data>
<data name="ViewPageSettingBackgroundImageCopyrightHeader" xml:space="preserve">
<value>图片版权信息</value>
</data>
<data name="ViewPageSettingBackgroundImageDescription" xml:space="preserve">
<value>更改窗体的背景图片来源,重启胡桃以尽快生效</value>
</data>
<data name="ViewPageSettingBackgroundImageHeader" xml:space="preserve">
<value>背景图片</value>
</data>
<data name="ViewPageSettingCacheFolderDescription" xml:space="preserve">
<value>图片缓存 在此处存放</value>
</data>
@@ -2603,6 +2636,12 @@
<data name="ViewPageSettingWebview2Header" xml:space="preserve">
<value>Webview2 运行时</value>
</data>
<data name="ViewPageSpiralAbyssTeamAppearanceDownHeader" xml:space="preserve">
<value>下半</value>
</data>
<data name="ViewPageSpiralAbyssTeamAppearanceUpHeader" xml:space="preserve">
<value>上半</value>
</data>
<data name="ViewPageWiKiAvatarArtifactSetCombinationHeader" xml:space="preserve">
<value>搭配圣遗物</value>
</data>
@@ -2865,7 +2904,7 @@
<value>〓活动时间〓.*?\d\.\d版本期间持续开放</value>
</data>
<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 name="WebAnnouncementMatchVersionUpdateTime" xml:space="preserve">
<value>〓更新时间〓.+?&amp;lt;t class=\"t_(?:gl|lc)\".*?&amp;gt;(.*?)&amp;lt;/t&amp;gt;</value>
@@ -3017,6 +3056,9 @@
<data name="WebGachaConfigTypeAvatarEventWish2" xml:space="preserve">
<value>角色活动祈愿-2</value>
</data>
<data name="WebGachaConfigTypeChronicledWish" xml:space="preserve">
<value>集录祈愿</value>
</data>
<data name="WebGachaConfigTypeNoviceWish" xml:space="preserve">
<value>新手祈愿</value>
</data>

View File

@@ -767,6 +767,21 @@
<data name="ServiceAvatarInfoSummaryShowcaseRefreshTimeFormat" xml:space="preserve">
<value>Демонстрация персонажей: {0:MM-dd HH:mm}</value>
</data>
<data name="ServiceBackgroundImageTypeBing" xml:space="preserve">
<value>必应每日一图</value>
</data>
<data name="ServiceBackgroundImageTypeDaily" xml:space="preserve">
<value>胡桃每日一图</value>
</data>
<data name="ServiceBackgroundImageTypeLauncher" xml:space="preserve">
<value>官方启动器壁纸</value>
</data>
<data name="ServiceBackgroundImageTypeLocalFolder" xml:space="preserve">
<value>本地随机图片</value>
</data>
<data name="ServiceBackgroundImageTypeNone" xml:space="preserve">
<value>无背景图片</value>
</data>
<data name="ServiceCultivationProjectCurrentUserdataCourrpted" xml:space="preserve">
<value>Не удалось сохранить статус плана разработки.</value>
</data>
@@ -824,6 +839,9 @@
<data name="ServiceDailyNoteNotifierTransformerHint" xml:space="preserve">
<value>Преобразователь готов</value>
</data>
<data name="ServiceDiscordActivityElevationRequiredHint" xml:space="preserve">
<value>权限不足,将无法为您设置 Discord Activity 状态</value>
</data>
<data name="ServiceDiscordGameActivityDetails" xml:space="preserve">
<value>Исследование Тейвата</value>
</data>
@@ -1391,6 +1409,9 @@
<data name="ViewLaunchGameHeader" xml:space="preserve">
<value>Game Launcher</value>
</data>
<data name="ViewListViewDragElevatedHint" xml:space="preserve">
<value>管理员模式下无法拖动排序</value>
</data>
<data name="ViewModelAchievementArchiveAdded" xml:space="preserve">
<value>存档 [{0}] 添加成功</value>
</data>
@@ -1670,6 +1691,9 @@
<data name="ViewModelWelcomeDownloadSummaryComplete" xml:space="preserve">
<value>Завершено</value>
</data>
<data name="ViewModelWelcomeDownloadSummaryContentTypeNotMatch" xml:space="preserve">
<value>响应内容不是有效的文件字节流</value>
</data>
<data name="ViewModelWelcomeDownloadSummaryDefault" xml:space="preserve">
<value>В процессе</value>
</data>
@@ -2010,7 +2034,7 @@
<value>总览</value>
</data>
<data name="ViewPageGahcaLogPivotStatistics" xml:space="preserve">
<value>全球祈愿统计</value>
<value>统计</value>
</data>
<data name="ViewPageGahcaLogPivotWeapon" xml:space="preserve">
<value>Оружие</value>
@@ -2186,6 +2210,12 @@
<data name="ViewPageLaunchGameArgumentsHeader" xml:space="preserve">
<value>启动参数</value>
</data>
<data name="ViewPageLaunchGameBetterGIDescription" xml:space="preserve">
<value>在游戏启动后尝试启动并使用 Better GI 进行自动化任务</value>
</data>
<data name="ViewPageLaunchGameBetterGIHeader" xml:space="preserve">
<value>Better GI</value>
</data>
<data name="ViewPageLaunchGameCommonHeader" xml:space="preserve">
<value>常规</value>
</data>
@@ -2327,6 +2357,15 @@
<data name="ViewPageSettingBackdropMaterialHeader" xml:space="preserve">
<value>背景材质</value>
</data>
<data name="ViewPageSettingBackgroundImageCopyrightHeader" xml:space="preserve">
<value>图片版权信息</value>
</data>
<data name="ViewPageSettingBackgroundImageDescription" xml:space="preserve">
<value>更改窗体的背景图片来源,重启胡桃以尽快生效</value>
</data>
<data name="ViewPageSettingBackgroundImageHeader" xml:space="preserve">
<value>背景图片</value>
</data>
<data name="ViewPageSettingCacheFolderDescription" xml:space="preserve">
<value>图片缓存 在此处存放</value>
</data>

View File

@@ -767,6 +767,21 @@
<data name="ServiceAvatarInfoSummaryShowcaseRefreshTimeFormat" xml:space="preserve">
<value>角色櫥窗:{0:MM-dd HH:mm}</value>
</data>
<data name="ServiceBackgroundImageTypeBing" xml:space="preserve">
<value>必应每日一图</value>
</data>
<data name="ServiceBackgroundImageTypeDaily" xml:space="preserve">
<value>胡桃每日一图</value>
</data>
<data name="ServiceBackgroundImageTypeLauncher" xml:space="preserve">
<value>官方启动器壁纸</value>
</data>
<data name="ServiceBackgroundImageTypeLocalFolder" xml:space="preserve">
<value>本地随机图片</value>
</data>
<data name="ServiceBackgroundImageTypeNone" xml:space="preserve">
<value>无背景图片</value>
</data>
<data name="ServiceCultivationProjectCurrentUserdataCourrpted" xml:space="preserve">
<value>保存養成計劃狀態失敗</value>
</data>
@@ -824,6 +839,9 @@
<data name="ServiceDailyNoteNotifierTransformerHint" xml:space="preserve">
<value>參量質變儀已準備完成</value>
</data>
<data name="ServiceDiscordActivityElevationRequiredHint" xml:space="preserve">
<value>权限不足,将无法为您设置 Discord Activity 状态</value>
</data>
<data name="ServiceDiscordGameActivityDetails" xml:space="preserve">
<value>正在提瓦特大陸中探索</value>
</data>
@@ -1391,6 +1409,9 @@
<data name="ViewLaunchGameHeader" xml:space="preserve">
<value>啟動遊戲</value>
</data>
<data name="ViewListViewDragElevatedHint" xml:space="preserve">
<value>管理员模式下无法拖动排序</value>
</data>
<data name="ViewModelAchievementArchiveAdded" xml:space="preserve">
<value>存檔 [{0}] 添加成功</value>
</data>
@@ -1670,6 +1691,9 @@
<data name="ViewModelWelcomeDownloadSummaryComplete" xml:space="preserve">
<value>完成</value>
</data>
<data name="ViewModelWelcomeDownloadSummaryContentTypeNotMatch" xml:space="preserve">
<value>响应内容不是有效的文件字节流</value>
</data>
<data name="ViewModelWelcomeDownloadSummaryDefault" xml:space="preserve">
<value>待處理</value>
</data>
@@ -2010,7 +2034,7 @@
<value>總覽</value>
</data>
<data name="ViewPageGahcaLogPivotStatistics" xml:space="preserve">
<value>全球祈愿统计</value>
<value>統計</value>
</data>
<data name="ViewPageGahcaLogPivotWeapon" xml:space="preserve">
<value>武器</value>
@@ -2186,6 +2210,12 @@
<data name="ViewPageLaunchGameArgumentsHeader" xml:space="preserve">
<value>啟動參數</value>
</data>
<data name="ViewPageLaunchGameBetterGIDescription" xml:space="preserve">
<value>在游戏启动后尝试启动并使用 Better GI 进行自动化任务</value>
</data>
<data name="ViewPageLaunchGameBetterGIHeader" xml:space="preserve">
<value>Better GI</value>
</data>
<data name="ViewPageLaunchGameCommonHeader" xml:space="preserve">
<value>一般</value>
</data>
@@ -2327,6 +2357,15 @@
<data name="ViewPageSettingBackdropMaterialHeader" xml:space="preserve">
<value>背景材質</value>
</data>
<data name="ViewPageSettingBackgroundImageCopyrightHeader" xml:space="preserve">
<value>图片版权信息</value>
</data>
<data name="ViewPageSettingBackgroundImageDescription" xml:space="preserve">
<value>更改窗体的背景图片来源,重启胡桃以尽快生效</value>
</data>
<data name="ViewPageSettingBackgroundImageHeader" xml:space="preserve">
<value>背景图片</value>
</data>
<data name="ViewPageSettingCacheFolderDescription" xml:space="preserve">
<value>圖片暫存存放在此</value>
</data>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

After

Width:  |  Height:  |  Size: 8.0 KiB

View File

@@ -85,8 +85,13 @@ internal sealed partial class AnnouncementService : IAnnouncementService
{
foreach (ref readonly Announcement item in CollectionsMarshal.AsSpan(listWrapper.List))
{
item.Subtitle = new StringBuilder(item.Subtitle).Replace("\r<br>", string.Empty).ToString();
item.Content = AnnouncementRegex.XmlTimeTagRegex.Replace(item.Content, x => x.Groups[1].Value);
item.Subtitle = new StringBuilder(item.Subtitle)
.Replace("\r<br>", string.Empty)
.Replace("<br />", string.Empty)
.ToString();
item.Content = AnnouncementRegex
.XmlTimeTagRegex()
.Replace(item.Content, x => x.Groups[1].Value);
}
}
}
@@ -133,7 +138,7 @@ internal sealed partial class AnnouncementService : IAnnouncementService
continue;
}
MatchCollection matches = AnnouncementRegex.XmlTimeTagRegex.Matches(announcement.Content);
MatchCollection matches = AnnouncementRegex.XmlTimeTagRegex().Matches(announcement.Content);
if (matches.Count < 2)
{
continue;

View File

@@ -34,9 +34,11 @@ internal sealed partial class AppOptions : DbStoreOptions
set => SetOption(ref backdropType, SettingEntry.SystemBackdropType, value, EnumToStringOrEmpty);
}
public List<NameValue<BackgroundImageType>> BackgroundImageTypes { get; } = CollectionsNameValue.FromEnum<BackgroundImageType>(type => type.GetLocalizedDescription());
public BackgroundImageType BackgroundImageType
{
get => GetOption(ref backgroundImageType, SettingEntry.BackgroundImageType, EnumParse<BackgroundImageType>, BackgroundImageType.HutaoOfficialLauncher).Value;
get => GetOption(ref backgroundImageType, SettingEntry.BackgroundImageType, EnumParse<BackgroundImageType>, BackgroundImageType.None).Value;
set => SetOption(ref backgroundImageType, SettingEntry.BackgroundImageType, value, EnumToStringOrEmpty);
}

View File

@@ -2,11 +2,6 @@
// Licensed under the MIT license.
using Microsoft.UI.Xaml.Media.Imaging;
using Snap.Hutao.Control.Media;
using Snap.Hutao.Core;
using System.IO;
using System.Runtime.InteropServices;
using Windows.Graphics.Imaging;
using Windows.UI;
namespace Snap.Hutao.Service.BackgroundImage;

View File

@@ -0,0 +1,15 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using CommunityToolkit.Mvvm.ComponentModel;
using Snap.Hutao.Web.Hutao.Wallpaper;
namespace Snap.Hutao.Service.BackgroundImage;
[Injection(InjectAs.Singleton)]
internal sealed partial class BackgroundImageOptions : ObservableObject
{
private Wallpaper? wallpaper;
public Wallpaper? Wallpaper { get => wallpaper; set => SetProperty(ref wallpaper, value); }
}

View File

@@ -5,11 +5,11 @@ using Snap.Hutao.Control.Media;
using Snap.Hutao.Core;
using Snap.Hutao.Core.Caching;
using Snap.Hutao.Core.IO;
using Snap.Hutao.Service.Game.Scheme;
using Snap.Hutao.Web.Hoyolab.SdkStatic.Hk4e.Launcher;
using Snap.Hutao.Web.Hoyolab.SdkStatic.Hk4e.Launcher.Content;
using Snap.Hutao.Web.Hutao.Wallpaper;
using Snap.Hutao.Web.Response;
using Snap.Hutao.Win32.Foundation;
using System.IO;
using System.Runtime.InteropServices;
using Windows.Graphics.Imaging;
namespace Snap.Hutao.Service.BackgroundImage;
@@ -20,22 +20,24 @@ internal sealed partial class BackgroundImageService : IBackgroundImageService
{
private static readonly HashSet<string> AllowedFormats = [".bmp", ".gif", ".ico", ".jpg", ".jpeg", ".png", ".tiff", ".webp"];
private readonly BackgroundImageOptions backgroundImageOptions;
private readonly IServiceProvider serviceProvider;
private readonly RuntimeOptions runtimeOptions;
private readonly ITaskContext taskContext;
private readonly AppOptions appOptions;
private HashSet<string> backgroundPathSet;
private HashSet<string> currentBackgroundPathSet;
public async ValueTask<ValueResult<bool, BackgroundImage>> GetNextBackgroundImageAsync(BackgroundImage? previous)
public async ValueTask<ValueResult<bool, BackgroundImage?>> GetNextBackgroundImageAsync(BackgroundImage? previous)
{
HashSet<string> backgroundSet = await SkipOrInitBackgroundAsync().ConfigureAwait(false);
if (backgroundSet.Count <= 0)
{
return new(false, default!);
return new(true, default!);
}
string path = System.Random.Shared.GetItems(backgroundSet.ToArray(), 1)[0];
string path = System.Random.Shared.GetItems([..backgroundSet], 1)[0];
backgroundSet.Remove(path);
if (string.Equals(path, previous?.Path, StringComparison.OrdinalIgnoreCase))
@@ -45,7 +47,21 @@ internal sealed partial class BackgroundImageService : IBackgroundImageService
using (FileStream fileStream = File.OpenRead(path))
{
BitmapDecoder decoder = await BitmapDecoder.CreateAsync(fileStream.AsRandomAccessStream());
BitmapDecoder decoder;
try
{
decoder = await BitmapDecoder.CreateAsync(fileStream.AsRandomAccessStream());
}
catch (COMException comException)
{
if (comException.HResult != HRESULT.E_FAIL)
{
throw;
}
return new(false, default!);
}
SoftwareBitmap softwareBitmap = await decoder.GetSoftwareBitmapAsync(BitmapPixelFormat.Bgra8, BitmapAlphaMode.Straight);
Bgra32 accentColor = softwareBitmap.GetAccentColor();
@@ -65,32 +81,58 @@ internal sealed partial class BackgroundImageService : IBackgroundImageService
private async ValueTask<HashSet<string>> SkipOrInitBackgroundAsync()
{
if (backgroundPathSet is not { Count: > 0 })
switch (appOptions.BackgroundImageType)
{
string backgroundFolder = runtimeOptions.GetDataFolderBackgroundFolder();
Directory.CreateDirectory(backgroundFolder);
backgroundPathSet = Directory
.GetFiles(backgroundFolder, "*.*", SearchOption.AllDirectories)
.Where(path => AllowedFormats.Contains(Path.GetExtension(path)))
.ToHashSet();
// No image found
if (backgroundPathSet.Count <= 0)
{
ResourceClient resourceClient = serviceProvider.GetRequiredService<ResourceClient>();
string launguageCode = serviceProvider.GetRequiredService<CultureOptions>().LanguageCode;
LaunchScheme scheme = launguageCode is "zh-cn"
? KnownLaunchSchemes.Get().First(scheme => !scheme.IsOversea && scheme.IsNotCompatOnly)
: KnownLaunchSchemes.Get().First(scheme => scheme.IsOversea && scheme.IsNotCompatOnly);
Response<GameContent> response = await resourceClient.GetContentAsync(scheme, launguageCode).ConfigureAwait(false);
if (response is { Data.Advertisement.Background: string url })
case BackgroundImageType.LocalFolder:
{
ValueFile file = await serviceProvider.GetRequiredService<IImageCache>().GetFileFromCacheAsync(url.ToUri()).ConfigureAwait(false);
backgroundPathSet = [file];
if (currentBackgroundPathSet is not { Count: > 0 })
{
string backgroundFolder = runtimeOptions.GetDataFolderBackgroundFolder();
Directory.CreateDirectory(backgroundFolder);
currentBackgroundPathSet = Directory
.GetFiles(backgroundFolder, "*.*", SearchOption.AllDirectories)
.Where(path => AllowedFormats.Contains(Path.GetExtension(path)))
.ToHashSet();
}
backgroundImageOptions.Wallpaper = default;
break;
}
case BackgroundImageType.HutaoBing:
await SetCurrentBackgroundPathSetAsync(client => client.GetBingWallpaperAsync()).ConfigureAwait(false);
break;
case BackgroundImageType.HutaoDaily:
await SetCurrentBackgroundPathSetAsync(client => client.GetTodayWallpaperAsync()).ConfigureAwait(false);
break;
case BackgroundImageType.HutaoOfficialLauncher:
await SetCurrentBackgroundPathSetAsync(client => client.GetLauncherWallpaperAsync()).ConfigureAwait(false);
break;
default:
currentBackgroundPathSet = [];
break;
}
currentBackgroundPathSet ??= [];
return currentBackgroundPathSet;
async Task SetCurrentBackgroundPathSetAsync(Func<HutaoWallpaperClient, ValueTask<Response<Wallpaper>>> responseFactory)
{
HutaoWallpaperClient wallpaperClient = serviceProvider.GetRequiredService<HutaoWallpaperClient>();
Response<Wallpaper> response = await responseFactory(wallpaperClient).ConfigureAwait(false);
if (response is { Data: Wallpaper wallpaper })
{
await taskContext.SwitchToMainThreadAsync();
backgroundImageOptions.Wallpaper = wallpaper;
await taskContext.SwitchToBackgroundAsync();
if (wallpaper.Url is { } url)
{
ValueFile file = await serviceProvider.GetRequiredService<IImageCache>().GetFileFromCacheAsync(url).ConfigureAwait(false);
currentBackgroundPathSet = [file];
}
}
}
return backgroundPathSet;
}
}

View File

@@ -3,11 +3,21 @@
namespace Snap.Hutao.Service.BackgroundImage;
[Localization]
internal enum BackgroundImageType
{
[LocalizationKey(nameof(SH.ServiceBackgroundImageTypeNone))]
None,
[LocalizationKey(nameof(SH.ServiceBackgroundImageTypeLocalFolder))]
LocalFolder,
[LocalizationKey(nameof(SH.ServiceBackgroundImageTypeBing))]
HutaoBing,
[LocalizationKey(nameof(SH.ServiceBackgroundImageTypeDaily))]
HutaoDaily,
[LocalizationKey(nameof(SH.ServiceBackgroundImageTypeLauncher))]
HutaoOfficialLauncher,
}

View File

@@ -5,5 +5,5 @@ namespace Snap.Hutao.Service.BackgroundImage;
internal interface IBackgroundImageService
{
ValueTask<ValueResult<bool, BackgroundImage>> GetNextBackgroundImageAsync(BackgroundImage? previous);
ValueTask<ValueResult<bool, BackgroundImage?>> GetNextBackgroundImageAsync(BackgroundImage? previous);
}

View File

@@ -39,7 +39,7 @@ internal sealed partial class CultivationService : ICultivationService
List<InventoryItem> entities = cultivationDbService.GetInventoryItemListByProjectId(projectId);
List<InventoryItemView> results = [];
foreach (Material meta in context.EnumerateInventroyMaterial())
foreach (Material meta in context.EnumerateInventoryMaterial())
{
InventoryItem entity = entities.SingleOrDefault(e => e.ItemId == meta.Id) ?? InventoryItem.From(projectId, meta.Id);
results.Add(new(entity, meta, saveCommand));

View File

@@ -41,39 +41,33 @@ internal sealed partial class DiscordService : IDiscordService, IDisposable
private bool IsSupported()
{
try
{
// Actually requires a discord client to be running on Windows platform.
// If not, discord core creation code will throw.
Process[] discordProcesses = Process.GetProcessesByName("Discord");
// Actually requires a discord client to be running on Windows platform.
// If not, discord core creation code will throw.
Process[] discordProcesses = Process.GetProcessesByName("Discord");
if (discordProcesses.Length <= 0)
if (discordProcesses.Length <= 0)
{
return false;
}
foreach (Process process in discordProcesses)
{
try
{
_ = process.Handle;
}
catch (Exception)
{
if (!isInitialized)
{
isInitialized = true;
infoBarService.Warning(SH.ServiceDiscordActivityElevationRequiredHint);
}
return false;
}
foreach (Process process in discordProcesses)
{
try
{
_ = process.Handle;
}
catch (Exception)
{
if (!isInitialized)
{
infoBarService.Warning(SH.ServiceDiscordActivityElevationRequiredHint);
}
return false;
}
}
return true;
}
finally
{
isInitialized = true;
}
return true;
}
}

View File

@@ -1,41 +0,0 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Web.Hoyolab.Hk4e.Event.GachaInfo;
using System.Collections.Frozen;
using System.Runtime.CompilerServices;
namespace Snap.Hutao.Service.GachaLog.Factory;
/// <summary>
/// 祈愿配置类型比较器
/// </summary>
internal sealed class GachaConfigTypeComparer : IComparer<GachaConfigType>
{
private static readonly Lazy<GachaConfigTypeComparer> LazyShared = new(() => new());
private static readonly FrozenDictionary<GachaConfigType, int> OrderMap = new Dictionary<GachaConfigType, int>()
{
[GachaConfigType.AvatarEventWish] = 0,
[GachaConfigType.AvatarEventWish2] = 1,
[GachaConfigType.WeaponEventWish] = 2,
[GachaConfigType.StandardWish] = 3,
[GachaConfigType.NoviceWish] = 4,
}.ToFrozenDictionary();
/// <summary>
/// 共享的比较器
/// </summary>
public static GachaConfigTypeComparer Shared { get => LazyShared.Value; }
/// <inheritdoc/>
public int Compare(GachaConfigType x, GachaConfigType y)
{
return OrderOf(x) - OrderOf(y);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int OrderOf(GachaConfigType type)
{
return OrderMap.GetValueOrDefault(type, 0);
}
}

View File

@@ -3,6 +3,7 @@
using Snap.Hutao.Model.Metadata.Abstraction;
using Snap.Hutao.ViewModel.GachaLog;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using System.Text;
using Windows.UI;
@@ -25,7 +26,7 @@ internal static class GachaStatisticsExtension
bool isPreviousUp = true;
// mark the IsGuarantee
foreach (SummaryItem item in summaryItems)
foreach (ref readonly SummaryItem item in CollectionsMarshal.AsSpan(summaryItems))
{
if (item.IsUp && (!isPreviousUp))
{
@@ -62,4 +63,4 @@ internal static class GachaStatisticsExtension
ReadOnlySpan<byte> codes = MD5.HashData(Encoding.UTF8.GetBytes(name));
return Color.FromArgb(255, codes.Slice(0, 5).Average(), codes.Slice(5, 5).Average(), codes.Slice(10, 5).Average());
}
}
}

View File

@@ -7,6 +7,7 @@ using Snap.Hutao.Model.Metadata;
using Snap.Hutao.Model.Metadata.Avatar;
using Snap.Hutao.Model.Metadata.Weapon;
using Snap.Hutao.Service.Metadata;
using Snap.Hutao.Service.Metadata.ContextAbstraction;
using Snap.Hutao.ViewModel.GachaLog;
using Snap.Hutao.Web.Hoyolab.Hk4e.Event.GachaInfo;
using Snap.Hutao.Web.Hutao.GachaLog;
@@ -31,9 +32,8 @@ internal sealed partial class GachaStatisticsFactory : IGachaStatisticsFactory
public async ValueTask<GachaStatistics> CreateAsync(List<Model.Entity.GachaItem> items, GachaLogServiceMetadataContext context)
{
await taskContext.SwitchToBackgroundAsync();
List<GachaEvent> gachaEvents = await metadataService.GetGachaEventListAsync().ConfigureAwait(false);
List<HistoryWishBuilder> historyWishBuilders = gachaEvents.SelectList(gachaEvent => new HistoryWishBuilder(gachaEvent, context));
List<HistoryWishBuilder> historyWishBuilders = context.GachaEvents.SelectList(gachaEvent => new HistoryWishBuilder(gachaEvent, context));
return CreateCore(taskContext, homaGachaLogClient, items, historyWishBuilders, context, options.IsEmptyHistoryWishVisible);
}
@@ -54,6 +54,9 @@ internal sealed partial class GachaStatisticsFactory : IGachaStatisticsFactory
TypedWishSummaryBuilderContext weaponContext = TypedWishSummaryBuilderContext.WeaponEventWish(taskContext, gachaLogClient);
TypedWishSummaryBuilder weaponWishBuilder = new(weaponContext);
TypedWishSummaryBuilderContext chronicledContext = TypedWishSummaryBuilderContext.ChronicledWish(taskContext, gachaLogClient);
TypedWishSummaryBuilder chronicledWishBuilder = new(chronicledContext);
Dictionary<Avatar, int> orangeAvatarCounter = [];
Dictionary<Avatar, int> purpleAvatarCounter = [];
Dictionary<Weapon, int> orangeWeaponCounter = [];
@@ -61,24 +64,25 @@ internal sealed partial class GachaStatisticsFactory : IGachaStatisticsFactory
Dictionary<Weapon, int> blueWeaponCounter = [];
// Pre group builders
Dictionary<GachaConfigType, List<HistoryWishBuilder>> historyWishBuilderMap = historyWishBuilders
Dictionary<GachaType, List<HistoryWishBuilder>> historyWishBuilderMap = historyWishBuilders
.GroupBy(b => b.ConfigType)
.ToDictionary(g => g.Key, g => g.ToList().SortBy(b => b.From));
// Items are ordered by precise time, first is oldest
// 'ref' is not allowed here because we have lambda below
foreach (Model.Entity.GachaItem item in CollectionsMarshal.AsSpan(items))
foreach (ref readonly Model.Entity.GachaItem item in CollectionsMarshal.AsSpan(items))
{
// Find target history wish to operate. // w.From <= item.Time <= w.To
HistoryWishBuilder? targetHistoryWishBuilder = item.GachaType is not (GachaConfigType.StandardWish or GachaConfigType.NoviceWish)
? historyWishBuilderMap[item.GachaType].BinarySearch(w => item.Time < w.From ? -1 : item.Time > w.To ? 1 : 0)
// Find target history wish to operate. // banner.From <= item.Time <= banner.To
Model.Entity.GachaItem pinned = item;
HistoryWishBuilder? targetHistoryWishBuilder = item.GachaType is not (GachaType.Standard or GachaType.NewBie)
? historyWishBuilderMap[item.GachaType].BinarySearch(banner => pinned.Time < banner.From ? -1 : pinned.Time > banner.To ? 1 : 0)
: default;
switch (item.ItemId.StringLength())
{
case 8U:
{
Avatar avatar = context.IdAvatarMap[item.ItemId];
Avatar avatar = context.GetAvatar(item.ItemId);
bool isUp = false;
switch (avatar.Quality)
@@ -98,6 +102,7 @@ internal sealed partial class GachaStatisticsFactory : IGachaStatisticsFactory
standardWishBuilder.Track(item, avatar, isUp);
avatarWishBuilder.Track(item, avatar, isUp);
weaponWishBuilder.Track(item, avatar, isUp);
chronicledWishBuilder.Track(item, avatar, isUp);
break;
}
@@ -127,17 +132,18 @@ internal sealed partial class GachaStatisticsFactory : IGachaStatisticsFactory
standardWishBuilder.Track(item, weapon, isUp);
avatarWishBuilder.Track(item, weapon, isUp);
weaponWishBuilder.Track(item, weapon, isUp);
chronicledWishBuilder.Track(item, weapon, isUp);
break;
}
default:
// ItemId string length not correct.
ThrowHelper.UserdataCorrupted(SH.FormatServiceGachaStatisticsFactoryItemIdInvalid(item.ItemId), default!);
HutaoException.GachaStatisticsInvalidItemId(item.ItemId);
break;
}
}
AsyncBarrier barrier = new(3);
AsyncBarrier barrier = new(4);
return new()
{
@@ -145,7 +151,7 @@ internal sealed partial class GachaStatisticsFactory : IGachaStatisticsFactory
HistoryWishes = historyWishBuilders
.Where(b => isEmptyHistoryWishVisible || (!b.IsEmpty))
.OrderByDescending(builder => builder.From)
.ThenBy(builder => builder.ConfigType, GachaConfigTypeComparer.Shared)
.ThenBy(builder => builder.ConfigType, GachaTypeComparer.Shared)
.Select(builder => builder.ToHistoryWish())
.ToList(),
@@ -162,6 +168,7 @@ internal sealed partial class GachaStatisticsFactory : IGachaStatisticsFactory
StandardWish = standardWishBuilder.ToTypedWishSummary(barrier),
AvatarWish = avatarWishBuilder.ToTypedWishSummary(barrier),
WeaponWish = weaponWishBuilder.ToTypedWishSummary(barrier),
ChronicledWish = chronicledWishBuilder.ToTypedWishSummary(barrier),
};
}
}

View File

@@ -62,22 +62,29 @@ internal sealed partial class GachaStatisticsSlimFactory : IGachaStatisticsSlimF
int weaponPurpleTracker = 0;
TypedWishSummarySlim weaponWish = new(SH.ServiceGachaLogFactoryWeaponWishName, 80, 10);
int chronicledOrangeTracker = 0;
int chronicledPurpleTracker = 0;
TypedWishSummarySlim chronicledWish = new(SH.ServiceGachaLogFactoryChronicledWishName, 90, 10);
// O(n) operation
foreach (ref readonly GachaItem item in CollectionsMarshal.AsSpan(items))
{
INameQuality nameQuality = context.GetNameQualityByItemId(item.ItemId);
switch (item.QueryType)
{
case GachaConfigType.StandardWish:
case GachaType.Standard:
Track(nameQuality, ref standardOrangeTracker, ref standardPurpleTracker);
break;
case GachaConfigType.AvatarEventWish:
case GachaConfigType.AvatarEventWish2:
case GachaType.ActivityAvatar:
case GachaType.SpecialActivityAvatar:
Track(nameQuality, ref avatarOrangeTracker, ref avatarPurpleTracker);
break;
case GachaConfigType.WeaponEventWish:
case GachaType.ActivityWeapon:
Track(nameQuality, ref weaponOrangeTracker, ref weaponPurpleTracker);
break;
case GachaType.ActivityCity:
Track(nameQuality, ref chronicledOrangeTracker, ref chronicledPurpleTracker);
break;
default:
break;
}
@@ -85,11 +92,16 @@ internal sealed partial class GachaStatisticsSlimFactory : IGachaStatisticsSlimF
standardWish.LastOrangePull = standardOrangeTracker;
standardWish.LastPurplePull = standardPurpleTracker;
avatarWish.LastOrangePull = avatarOrangeTracker;
avatarWish.LastPurplePull = avatarPurpleTracker;
weaponWish.LastOrangePull = weaponOrangeTracker;
weaponWish.LastPurplePull = weaponPurpleTracker;
chronicledWish.LastOrangePull = chronicledOrangeTracker;
chronicledWish.LastPurplePull = chronicledPurpleTracker;
return new()
{
Uid = uid,

View File

@@ -0,0 +1,42 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Web.Hoyolab.Hk4e.Event.GachaInfo;
using System.Collections.Frozen;
using System.Runtime.CompilerServices;
namespace Snap.Hutao.Service.GachaLog.Factory;
/// <summary>
/// 祈愿配置类型比较器
/// </summary>
internal sealed class GachaTypeComparer : IComparer<GachaType>
{
private static readonly Lazy<GachaTypeComparer> LazyShared = new(() => new());
private static readonly FrozenDictionary<GachaType, int> OrderMap = FrozenDictionary.ToFrozenDictionary(
[
KeyValuePair.Create(GachaType.ActivityAvatar, 0),
KeyValuePair.Create(GachaType.SpecialActivityAvatar, 1),
KeyValuePair.Create(GachaType.ActivityWeapon, 2),
KeyValuePair.Create(GachaType.Standard, 3),
KeyValuePair.Create(GachaType.NewBie, 4),
KeyValuePair.Create(GachaType.ActivityCity, 5),
]);
/// <summary>
/// 共享的比较器
/// </summary>
public static GachaTypeComparer Shared { get => LazyShared.Value; }
/// <inheritdoc/>
public int Compare(GachaType x, GachaType y)
{
return OrderOf(x) - OrderOf(y);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int OrderOf(GachaType type)
{
return OrderMap.GetValueOrDefault(type, 0);
}
}

View File

@@ -29,7 +29,6 @@ internal sealed class HistoryWishBuilder
/// </summary>
/// <param name="gachaEvent">卡池配置</param>
/// <param name="context">祈愿记录上下文</param>
[SuppressMessage("", "SH002")]
public HistoryWishBuilder(GachaEvent gachaEvent, GachaLogServiceMetadataContext context)
{
this.gachaEvent = gachaEvent;
@@ -37,21 +36,27 @@ internal sealed class HistoryWishBuilder
switch (ConfigType)
{
case GachaConfigType.AvatarEventWish or GachaConfigType.AvatarEventWish2:
case GachaType.ActivityAvatar or GachaType.SpecialActivityAvatar:
orangeUpCounter = gachaEvent.UpOrangeList.Select(id => context.IdAvatarMap[id]).ToDictionary(a => (IStatisticsItemSource)a, a => 0);
purpleUpCounter = gachaEvent.UpPurpleList.Select(id => context.IdAvatarMap[id]).ToDictionary(a => (IStatisticsItemSource)a, a => 0);
break;
case GachaConfigType.WeaponEventWish:
case GachaType.ActivityWeapon:
orangeUpCounter = gachaEvent.UpOrangeList.Select(id => context.IdWeaponMap[id]).ToDictionary(w => (IStatisticsItemSource)w, w => 0);
purpleUpCounter = gachaEvent.UpPurpleList.Select(id => context.IdWeaponMap[id]).ToDictionary(w => (IStatisticsItemSource)w, w => 0);
break;
case GachaType.ActivityCity:
// Avatars are less than weapons, so we try to get the value from avatar map first
orangeUpCounter = gachaEvent.UpOrangeList.Select(id => (IStatisticsItemSource?)context.IdAvatarMap.GetValueOrDefault(id) ?? context.IdWeaponMap[id]).ToDictionary(c => c, c => 0);
purpleUpCounter = gachaEvent.UpPurpleList.Select(id => (IStatisticsItemSource?)context.IdAvatarMap.GetValueOrDefault(id) ?? context.IdWeaponMap[id]).ToDictionary(c => c, c => 0);
break;
}
}
/// <summary>
/// 祈愿配置类型
/// </summary>
public GachaConfigType ConfigType { get; }
public GachaType ConfigType { get; }
/// <inheritdoc cref="GachaEvent.From"/>
public DateTimeOffset From { get => gachaEvent.From; }
@@ -106,13 +111,13 @@ internal sealed class HistoryWishBuilder
{
HistoryWish historyWish = new()
{
// base
// Base
Name = gachaEvent.Name,
From = gachaEvent.From,
To = gachaEvent.To,
TotalCount = totalCountTracker,
// fill
// Fill
Version = gachaEvent.Version,
BannerImage = gachaEvent.Banner,
OrangeUpList = orangeUpCounter.ToStatisticsList(),

View File

@@ -5,6 +5,7 @@ using Snap.Hutao.Core.ExceptionService;
using Snap.Hutao.Model.Intrinsic;
using Snap.Hutao.Model.Metadata;
using Snap.Hutao.Model.Metadata.Abstraction;
using Snap.Hutao.Service.Metadata.ContextAbstraction;
using Snap.Hutao.ViewModel.GachaLog;
using Snap.Hutao.Web.Hoyolab.Hk4e.Event.GachaInfo;
using Snap.Hutao.Web.Hutao.GachaLog;
@@ -18,18 +19,20 @@ internal sealed class HutaoStatisticsFactory
private readonly GachaEvent avatarEvent;
private readonly GachaEvent avatarEvent2;
private readonly GachaEvent weaponEvent;
private readonly GachaEvent chronicledEvent;
public HutaoStatisticsFactory(in HutaoStatisticsFactoryMetadataContext context)
{
this.context = context;
// TODO: when in new verion
// when in new verion
// due to lack of newer metadata
// this can crash
DateTimeOffset now = DateTimeOffset.UtcNow;
avatarEvent = context.GachaEvents.Single(g => g.From < now && g.To > now && g.Type == GachaConfigType.AvatarEventWish);
avatarEvent2 = context.GachaEvents.Single(g => g.From < now && g.To > now && g.Type == GachaConfigType.AvatarEventWish2);
weaponEvent = context.GachaEvents.Single(g => g.From < now && g.To > now && g.Type == GachaConfigType.WeaponEventWish);
avatarEvent = context.GachaEvents.Single(g => g.From < now && g.To > now && g.Type == GachaType.ActivityAvatar);
avatarEvent2 = context.GachaEvents.Single(g => g.From < now && g.To > now && g.Type == GachaType.SpecialActivityAvatar);
weaponEvent = context.GachaEvents.Single(g => g.From < now && g.To > now && g.Type == GachaType.ActivityWeapon);
chronicledEvent = context.GachaEvents.Single(g => g.From < now && g.To > now && g.Type == GachaType.ActivityCity);
}
public HutaoStatistics Create(GachaEventStatistics raw)
@@ -38,7 +41,8 @@ internal sealed class HutaoStatisticsFactory
{
AvatarEvent = CreateWishSummary(avatarEvent, raw.AvatarEvent),
AvatarEvent2 = CreateWishSummary(avatarEvent2, raw.AvatarEvent2),
WeaponWish = CreateWishSummary(weaponEvent, raw.WeaponEvent),
WeaponEvent = CreateWishSummary(weaponEvent, raw.WeaponEvent),
Chronicled = CreateWishSummary(chronicledEvent, raw.Chronicled),
};
}
@@ -53,12 +57,13 @@ internal sealed class HutaoStatisticsFactory
{
IStatisticsItemSource source = item.Item.StringLength() switch
{
8U => context.IdAvatarMap[item.Item],
5U => context.IdWeaponMap[item.Item],
_ => throw ThrowHelper.UserdataCorrupted(SH.FormatServiceGachaStatisticsFactoryItemIdInvalid(item.Item), default!),
8U => context.GetAvatar(item.Item),
5U => context.GetWeapon(item.Item),
_ => throw HutaoException.GachaStatisticsInvalidItemId(item.Item),
};
StatisticsItem statisticsItem = source.ToStatisticsItem(unchecked((int)item.Count));
// Put UP items to a separate list
if (gachaEvent.UpOrangeList.Contains(item.Item) || gachaEvent.UpPurpleList.Contains(item.Item))
{
upItems.Add(statisticsItem);

View File

@@ -5,19 +5,18 @@ using Snap.Hutao.Model.Metadata;
using Snap.Hutao.Model.Metadata.Avatar;
using Snap.Hutao.Model.Metadata.Weapon;
using Snap.Hutao.Model.Primitive;
using Snap.Hutao.Service.Metadata.ContextAbstraction;
namespace Snap.Hutao.Service.GachaLog.Factory;
internal readonly struct HutaoStatisticsFactoryMetadataContext
internal sealed class HutaoStatisticsFactoryMetadataContext : IMetadataContext,
IMetadataDictionaryIdAvatarSource,
IMetadataDictionaryIdWeaponSource,
IMetadataListGachaEventSource
{
public readonly Dictionary<AvatarId, Avatar> IdAvatarMap;
public readonly Dictionary<WeaponId, Weapon> IdWeaponMap;
public readonly List<GachaEvent> GachaEvents;
public Dictionary<AvatarId, Avatar> IdAvatarMap { get; set; } = default!;
public HutaoStatisticsFactoryMetadataContext(Dictionary<AvatarId, Avatar> idAvatarMap, Dictionary<WeaponId, Weapon> idWeaponMap, List<GachaEvent> gachaEvents)
{
IdAvatarMap = idAvatarMap;
IdWeaponMap = idWeaponMap;
GachaEvents = gachaEvents;
}
public Dictionary<WeaponId, Weapon> IdWeaponMap { get; set; } = default!;
public List<GachaEvent> GachaEvents { get; set; } = default!;
}

View File

@@ -15,21 +15,6 @@ namespace Snap.Hutao.Service.GachaLog.Factory;
[HighQuality]
internal sealed class TypedWishSummaryBuilder
{
/// <summary>
/// 常驻祈愿
/// </summary>
public static readonly Func<GachaConfigType, bool> IsStandardWish = type => type is GachaConfigType.StandardWish;
/// <summary>
/// 角色活动
/// </summary>
public static readonly Func<GachaConfigType, bool> IsAvatarEventWish = type => type is GachaConfigType.AvatarEventWish or GachaConfigType.AvatarEventWish2;
/// <summary>
/// 武器活动
/// </summary>
public static readonly Func<GachaConfigType, bool> IsWeaponEventWish = type => type is GachaConfigType.WeaponEventWish;
private readonly TypedWishSummaryBuilderContext context;
private readonly List<int> averageOrangePullTracker = [];
@@ -62,52 +47,54 @@ internal sealed class TypedWishSummaryBuilder
/// <param name="isUp">是否为Up物品</param>
public void Track(GachaItem item, ISummaryItemSource source, bool isUp)
{
if (context.TypeEvaluator(item.GachaType))
if (!context.TypeEvaluator(item.GachaType))
{
++lastOrangePullTracker;
++lastPurplePullTracker;
++lastUpOrangePullTracker;
return;
}
// track total pulls
++totalCountTracker;
TrackFromToTime(item.Time);
++lastOrangePullTracker;
++lastPurplePullTracker;
++lastUpOrangePullTracker;
switch (source.Quality)
{
case QualityType.QUALITY_ORANGE:
// track total pulls
++totalCountTracker;
TrackFromToTime(item.Time);
switch (source.Quality)
{
case QualityType.QUALITY_ORANGE:
{
TrackMinMaxOrangePull(lastOrangePullTracker);
averageOrangePullTracker.Add(lastOrangePullTracker);
if (isUp)
{
TrackMinMaxOrangePull(lastOrangePullTracker);
averageOrangePullTracker.Add(lastOrangePullTracker);
if (isUp)
{
averageUpOrangePullTracker.Add(lastUpOrangePullTracker);
lastUpOrangePullTracker = 0;
}
summaryItems.Add(source.ToSummaryItem(lastOrangePullTracker, item.Time, isUp));
lastOrangePullTracker = 0;
++totalOrangePullTracker;
break;
averageUpOrangePullTracker.Add(lastUpOrangePullTracker);
lastUpOrangePullTracker = 0;
}
case QualityType.QUALITY_PURPLE:
{
lastPurplePullTracker = 0;
++totalPurplePullTracker;
break;
}
summaryItems.Add(source.ToSummaryItem(lastOrangePullTracker, item.Time, isUp));
case QualityType.QUALITY_BLUE:
{
++totalBluePullTracker;
break;
}
default:
lastOrangePullTracker = 0;
++totalOrangePullTracker;
break;
}
}
case QualityType.QUALITY_PURPLE:
{
lastPurplePullTracker = 0;
++totalPurplePullTracker;
break;
}
case QualityType.QUALITY_BLUE:
{
++totalBluePullTracker;
break;
}
default:
break;
}
}

View File

@@ -14,12 +14,13 @@ internal readonly struct TypedWishSummaryBuilderContext
public readonly string Name;
public readonly int GuaranteeOrangeThreshold;
public readonly int GuaranteePurpleThreshold;
public readonly Func<GachaConfigType, bool> TypeEvaluator;
public readonly Func<GachaType, bool> TypeEvaluator;
public readonly GachaDistributionType DistributionType;
private static readonly Func<GachaConfigType, bool> IsStandardWish = type => type is GachaConfigType.StandardWish;
private static readonly Func<GachaConfigType, bool> IsAvatarEventWish = type => type is GachaConfigType.AvatarEventWish or GachaConfigType.AvatarEventWish2;
private static readonly Func<GachaConfigType, bool> IsWeaponEventWish = type => type is GachaConfigType.WeaponEventWish;
private static readonly Func<GachaType, bool> IsStandardWish = type => type is GachaType.Standard;
private static readonly Func<GachaType, bool> IsAvatarEventWish = type => type is GachaType.ActivityAvatar or GachaType.SpecialActivityAvatar;
private static readonly Func<GachaType, bool> IsWeaponEventWish = type => type is GachaType.ActivityWeapon;
private static readonly Func<GachaType, bool> IsChronicledWish = type => type is GachaType.ActivityCity;
public TypedWishSummaryBuilderContext(
ITaskContext taskContext,
@@ -27,7 +28,7 @@ internal readonly struct TypedWishSummaryBuilderContext
string name,
int guaranteeOrangeThreshold,
int guaranteePurpleThreshold,
Func<GachaConfigType, bool> typeEvaluator,
Func<GachaType, bool> typeEvaluator,
GachaDistributionType distributionType)
{
TaskContext = taskContext;
@@ -54,6 +55,11 @@ internal readonly struct TypedWishSummaryBuilderContext
return new(taskContext, gachaLogClient, SH.ServiceGachaLogFactoryWeaponWishName, 80, 10, IsWeaponEventWish, GachaDistributionType.WeaponEvent);
}
public static TypedWishSummaryBuilderContext ChronicledWish(ITaskContext taskContext, HomaGachaLogClient gachaLogClient)
{
return new(taskContext, gachaLogClient, SH.ServiceGachaLogFactoryChronicledWishName, 90, 10, IsChronicledWish, GachaDistributionType.Chronicled);
}
public ValueTask<HutaoResponse<GachaDistribution>> GetGachaDistributionAsync()
{
return GachaLogClient.GetGachaDistributionAsync(DistributionType);

View File

@@ -15,12 +15,14 @@ internal static class GachaArchiveOperation
{
archive = archives.SingleOrDefault(a => a.Uid == uid);
if (archive is null)
if (archive is not null)
{
GachaArchive created = GachaArchive.From(uid);
gachaLogDbService.AddGachaArchive(created);
taskContext.InvokeOnMainThread(() => archives.Add(created));
archive = created;
return;
}
GachaArchive created = GachaArchive.From(uid);
gachaLogDbService.AddGachaArchive(created);
taskContext.InvokeOnMainThread(() => archives.Add(created));
archive = created;
}
}

View File

@@ -1,58 +0,0 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Model.Entity;
using Snap.Hutao.Web.Hoyolab.Hk4e.Event.GachaInfo;
namespace Snap.Hutao.Service.GachaLog;
/// <summary>
/// 祈愿物品保存上下文
/// </summary>
internal readonly struct GachaItemSaveContext
{
/// <summary>
/// 待添加物品
/// </summary>
public readonly List<GachaItem> ItemsToAdd;
/// <summary>
/// 是否懒惰
/// </summary>
public readonly bool IsLazy;
public readonly GachaConfigType QueryType;
/// <summary>
/// 结尾 Id
/// </summary>
public readonly long EndId;
/// <summary>
/// 数据集
/// </summary>
public readonly IGachaLogDbService GachaLogDbService;
public GachaItemSaveContext(List<GachaItem> itemsToAdd, bool isLazy, GachaConfigType queryType, long endId, IGachaLogDbService gachaLogDbService)
{
ItemsToAdd = itemsToAdd;
IsLazy = isLazy;
QueryType = queryType;
EndId = endId;
GachaLogDbService = gachaLogDbService;
}
public void SaveItems(GachaArchive archive)
{
if (ItemsToAdd.Count > 0)
{
// 全量刷新
if (!IsLazy)
{
GachaLogDbService.RemoveNewerGachaItemRangeByArchiveIdQueryTypeAndEndId(archive.InnerId, QueryType, EndId);
}
GachaLogDbService.AddGachaItemRange(ItemsToAdd);
}
}
}

View File

@@ -14,11 +14,12 @@ internal static class GachaLog
/// <summary>
/// 查询类型
/// </summary>
public static readonly FrozenSet<GachaConfigType> QueryTypes = FrozenSet.ToFrozenSet(
public static readonly FrozenSet<GachaType> QueryTypes = FrozenSet.ToFrozenSet(
[
GachaConfigType.NoviceWish,
GachaConfigType.StandardWish,
GachaConfigType.AvatarEventWish,
GachaConfigType.WeaponEventWish,
GachaType.NewBie,
GachaType.Standard,
GachaType.ActivityAvatar,
GachaType.ActivityWeapon,
GachaType.ActivityCity,
]);
}

View File

@@ -73,7 +73,7 @@ internal sealed partial class GachaLogDbService : IGachaLogDbService
}
}
public async ValueTask<long> GetNewestGachaItemIdByArchiveIdAndQueryTypeAsync(Guid archiveId, GachaConfigType queryType, CancellationToken token)
public async ValueTask<long> GetNewestGachaItemIdByArchiveIdAndQueryTypeAsync(Guid archiveId, GachaType queryType, CancellationToken token)
{
GachaItem? item = null;
@@ -103,7 +103,7 @@ internal sealed partial class GachaLogDbService : IGachaLogDbService
return item?.Id ?? 0L;
}
public long GetNewestGachaItemIdByArchiveIdAndQueryType(Guid archiveId, GachaConfigType queryType)
public long GetNewestGachaItemIdByArchiveIdAndQueryType(Guid archiveId, GachaType queryType)
{
GachaItem? item = null;
@@ -132,7 +132,7 @@ internal sealed partial class GachaLogDbService : IGachaLogDbService
return item?.Id ?? 0L;
}
public async ValueTask<long> GetNewestGachaItemIdByArchiveIdAndQueryTypeAsync(Guid archiveId, GachaConfigType queryType)
public async ValueTask<long> GetNewestGachaItemIdByArchiveIdAndQueryTypeAsync(Guid archiveId, GachaType queryType)
{
GachaItem? item = null;
@@ -205,7 +205,7 @@ internal sealed partial class GachaLogDbService : IGachaLogDbService
return item?.Id ?? long.MaxValue;
}
public long GetOldestGachaItemIdByArchiveIdAndQueryType(Guid archiveId, GachaConfigType queryType)
public long GetOldestGachaItemIdByArchiveIdAndQueryType(Guid archiveId, GachaType queryType)
{
GachaItem? item = null;
@@ -226,7 +226,7 @@ internal sealed partial class GachaLogDbService : IGachaLogDbService
return item?.Id ?? long.MaxValue;
}
public async ValueTask<long> GetOldestGachaItemIdByArchiveIdAndQueryTypeAsync(Guid archiveId, GachaConfigType queryType, CancellationToken token)
public async ValueTask<long> GetOldestGachaItemIdByArchiveIdAndQueryTypeAsync(Guid archiveId, GachaType queryType, CancellationToken token)
{
GachaItem? item = null;
@@ -266,7 +266,7 @@ internal sealed partial class GachaLogDbService : IGachaLogDbService
}
}
public List<Web.Hutao.GachaLog.GachaItem> GetHutaoGachaItemList(Guid archiveId, GachaConfigType queryType, long endId)
public List<Web.Hutao.GachaLog.GachaItem> GetHutaoGachaItemList(Guid archiveId, GachaType queryType, long endId)
{
using (IServiceScope scope = serviceProvider.CreateScope())
{
@@ -291,7 +291,7 @@ internal sealed partial class GachaLogDbService : IGachaLogDbService
}
}
public async ValueTask<List<Web.Hutao.GachaLog.GachaItem>> GetHutaoGachaItemListAsync(Guid archiveId, GachaConfigType queryType, long endId)
public async ValueTask<List<Web.Hutao.GachaLog.GachaItem>> GetHutaoGachaItemListAsync(Guid archiveId, GachaType queryType, long endId)
{
using (IServiceScope scope = serviceProvider.CreateScope())
{
@@ -368,7 +368,7 @@ internal sealed partial class GachaLogDbService : IGachaLogDbService
}
}
public void RemoveNewerGachaItemRangeByArchiveIdQueryTypeAndEndId(Guid archiveId, GachaConfigType queryType, long endId)
public void RemoveNewerGachaItemRangeByArchiveIdQueryTypeAndEndId(Guid archiveId, GachaType queryType, long endId)
{
using (IServiceScope scope = serviceProvider.CreateScope())
{
@@ -381,7 +381,7 @@ internal sealed partial class GachaLogDbService : IGachaLogDbService
}
}
public async ValueTask RemoveNewerGachaItemRangeByArchiveIdQueryTypeAndEndIdAsync(Guid archiveId, GachaConfigType queryType, long endId)
public async ValueTask RemoveNewerGachaItemRangeByArchiveIdQueryTypeAndEndIdAsync(Guid archiveId, GachaType queryType, long endId)
{
using (IServiceScope scope = serviceProvider.CreateScope())
{

View File

@@ -46,14 +46,14 @@ internal struct GachaLogFetchContext
/// <summary>
/// 当前类型
/// </summary>
public GachaConfigType CurrentType;
public GachaType CurrentType;
private readonly GachaLogServiceMetadataContext serviceContext;
private readonly IGachaLogDbService gachaLogDbService;
private readonly ITaskContext taskContext;
private readonly bool isLazy;
public GachaLogFetchContext(IGachaLogDbService gachaLogDbService, ITaskContext taskContext, in GachaLogServiceMetadataContext serviceContext, bool isLazy)
public GachaLogFetchContext(IGachaLogDbService gachaLogDbService, ITaskContext taskContext, GachaLogServiceMetadataContext serviceContext, bool isLazy)
{
this.gachaLogDbService = gachaLogDbService;
this.taskContext = taskContext;
@@ -66,7 +66,7 @@ internal struct GachaLogFetchContext
/// </summary>
/// <param name="configType">卡池类型</param>
/// <param name="query">查询</param>
public void ResetForProcessingType(GachaConfigType configType, in GachaLogQuery query)
public void ResetForProcessingType(GachaType configType, in GachaLogQuery query)
{
DbEndId = null;
CurrentType = configType;
@@ -140,8 +140,18 @@ internal struct GachaLogFetchContext
// While no item is fetched, archive can be null.
if (TargetArchive is not null)
{
GachaItemSaveContext saveContext = new(ItemsToAdd, isLazy, QueryOptions.Type, QueryOptions.EndId, gachaLogDbService);
saveContext.SaveItems(TargetArchive);
if (ItemsToAdd.Count <= 0)
{
return;
}
// 全量刷新
if (!isLazy)
{
gachaLogDbService.RemoveNewerGachaItemRangeByArchiveIdQueryTypeAndEndId(TargetArchive.InnerId, QueryOptions.Type, QueryOptions.EndId);
}
gachaLogDbService.AddGachaItemRange(ItemsToAdd);
}
}

View File

@@ -6,33 +6,17 @@ using Snap.Hutao.Web.Hoyolab.Hk4e.Event.GachaInfo;
namespace Snap.Hutao.Service.GachaLog;
/// <summary>
/// 祈愿记录获取状态
/// </summary>
internal sealed class GachaLogFetchStatus
{
/// <summary>
/// 构造一个新的祈愿记录获取状态
/// </summary>
/// <param name="configType">卡池类型</param>
public GachaLogFetchStatus(GachaConfigType configType)
public GachaLogFetchStatus(GachaType configType)
{
ConfigType = configType;
}
/// <summary>
/// 验证密钥是否过期
/// </summary>
public bool AuthKeyTimeout { get; set; }
/// <summary>
/// 卡池类型
/// </summary>
public GachaConfigType ConfigType { get; set; }
public GachaType ConfigType { get; set; }
/// <summary>
/// 当前获取的物品
/// </summary>
public List<Item> Items { get; set; } = new(20);
public string Header

Some files were not shown because too many files have changed in this diff Show More