mirror of
https://jihulab.com/DGP-Studio/Snap.Hutao.git
synced 2025-11-19 21:02:53 +08:00
Compare commits
1 Commits
readme2024
...
ci/say_goo
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
402d491aff |
13
.github/ISSUE_TEMPLATE/CHS-bug-report.yml
vendored
13
.github/ISSUE_TEMPLATE/CHS-bug-report.yml
vendored
@@ -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.Diagnostic.Tooling.exe),它将显示你的设备 ID
|
||||
如果你的程序已经无法启动,请下载并运行[诊断工具](https://github.com/DGP-Automation/ISSUE_TEMPLATES/releases/download/diagnosis_tools/Snap.Hutao.DiagTools.exe),它将显示你的设备 ID
|
||||
validations:
|
||||
required: false
|
||||
|
||||
@@ -79,7 +79,7 @@ body:
|
||||
- 公告
|
||||
- 其它
|
||||
validations:
|
||||
required: true
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: what-happened
|
||||
@@ -107,3 +107,4 @@ body:
|
||||
options:
|
||||
- label: 我认为上述的描述已经足以详细,以允许开发人员能复现该问题
|
||||
required: true
|
||||
|
||||
|
||||
15
.github/ISSUE_TEMPLATE/ENG-bug-report.yml
vendored
15
.github/ISSUE_TEMPLATE/ENG-bug-report.yml
vendored
@@ -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 [Diagnostic Tooling](https://github.com/DGP-Automation/ISSUE_TEMPLATES/releases/download/diagnosis_tools/Snap.Hutao.Diagnostic.Tooling.exe), it will shows your device ID.
|
||||
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.
|
||||
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,3 +107,4 @@ body:
|
||||
options:
|
||||
- label: I believe the description above is detail enough to allow developers to reproduce the issue
|
||||
required: true
|
||||
|
||||
|
||||
6
.github/dependabot.yml
vendored
6
.github/dependabot.yml
vendored
@@ -13,8 +13,4 @@ updates:
|
||||
groups:
|
||||
packages:
|
||||
patterns:
|
||||
- "*"
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/.github/workflows" # GitHub Workflows
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
- "*"
|
||||
23
.github/workflows/alpha.yml
vendored
23
.github/workflows/alpha.yml
vendored
@@ -13,28 +13,16 @@ on:
|
||||
- '**.md'
|
||||
- 'LICENSE'
|
||||
- '**.yml'
|
||||
pull_request:
|
||||
branches:
|
||||
- develop
|
||||
paths-ignore:
|
||||
- '.gitattributes'
|
||||
- '.github/**'
|
||||
- '.gitignore'
|
||||
- '.gitmodules'
|
||||
- '**.md'
|
||||
- 'LICENSE'
|
||||
- '**.yml'
|
||||
- '**.resx'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: self-hosted
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v4.1.1
|
||||
|
||||
- name: Setup .NET
|
||||
uses: actions/setup-dotnet@v4
|
||||
uses: actions/setup-dotnet@v4.0.0
|
||||
with:
|
||||
dotnet-version: 8.0
|
||||
|
||||
@@ -46,21 +34,20 @@ jobs:
|
||||
VERSION_API_TOKEN: ${{ secrets.VERSION_API_TOKEN }}
|
||||
|
||||
- name: Sign Msix
|
||||
if: success() && github.event_name != 'pull_request'
|
||||
shell: pwsh
|
||||
run: |
|
||||
[System.Convert]::FromBase64String("${{ secrets.CERTIFICATE }}") | Set-Content -AsByteStream temp.pfx
|
||||
signtool.exe sign /debug /v /a /fd SHA256 /f temp.pfx /p ${{ secrets.PW }} ${{ github.workspace }}\src\output\Snap.Hutao.Alpha-${{ steps.cake.outputs.version }}.msix
|
||||
|
||||
- name: Upload signed msix
|
||||
if: success() && github.event_name != 'pull_request'
|
||||
uses: actions/upload-artifact@v4
|
||||
if: success()
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: Snap.Hutao.Alpha-${{ steps.cake.outputs.version }}
|
||||
path: ${{ github.workspace }}/src/output/Snap.Hutao.Alpha-${{ steps.cake.outputs.version }}.msix
|
||||
|
||||
- name: Add summary
|
||||
if: success() && github.event_name != 'pull_request'
|
||||
if: success()
|
||||
shell: pwsh
|
||||
run: |
|
||||
$summary = "
|
||||
|
||||
@@ -74,4 +74,3 @@ Refresh:
|
||||
script:
|
||||
- apt-get install -y curl
|
||||
- curl -X PATCH "$PURGE_URL"
|
||||
- curl -X POST -o /dev/null "$UPLOAD_OSS_URL"
|
||||
|
||||
36
README.md
36
README.md
@@ -1,36 +1,41 @@
|
||||

|
||||

|
||||
|
||||
|
||||
胡桃工具箱是一款以 MIT 协议开源的原神工具箱,专为现代化 Windows 平台设计,旨在改善桌面端玩家的游戏体验。通过将既有的官方资源与开发团队设计的全新功能相结合,提供了一套完整且实用的工具集,且无需依赖任何移动设备。它不对游戏客户端进行任何破坏性修改以确保工具箱的安全性
|
||||
胡桃工具箱是一款以 MIT 协议开源的原神工具箱,专为现代化 Windows 平台设计,旨在改善桌面端玩家的游戏体验。通过将既有的官方资源与开发团队设计的全新 功能相结合,它提供了一套完整且实用的工具集,且无需依赖任何移动设备。它不对游戏客户端进行任何破坏性修改以确保工具箱的安全性
|
||||
|
||||
Snap Hutao is an open-source Genshin Impact toolkit under MIT license, designed for modern Windows platform to improve the gaming experience for desktop players. By combining existing official resources with new features designed by the development team, it provides a complete and useful set of tools without the need to rely on mobile devices. Snap Hutao does not take any destructive modification to the game client to ensure the security of the toolkit.
|
||||
|
||||
## 安装 / Installation
|
||||
## 下载使用 / Download
|
||||
|
||||
 [](https://github.com/DGP-Studio/Snap.Hutao/releases/latest) []()
|
||||
|
||||
---
|
||||
|
||||
你可以按照[快速开始](https://hut.ao/zh/quick-start.html)文档中提供的流程安装并设置 Snap Hutao。
|
||||
#### 使用安装器安装 / Install with Snap.Hutao.Depolyment Installer
|
||||
|
||||
You can follow the instructions in the [Quick Start](https://hut.ao/en/quick-start.html) document to install and set up Snap Hutao.
|
||||
Snap.Hutao.Depolyment 是一个由 DGP-Studio 重新包装的 Windows 应用安装器,适用于缺少专业计算机知识的一般用户,可以在安装时同时解决缺少必要系统环境的问题。
|
||||
|
||||
## 本地化翻译 / Localization
|
||||
Snap.Hutao.Depolyment is a Windows application installer repackaged by DGP-Studio for the users who lacks computer knowledge and can solve the problem of missing necessary system environment at the same time as the installation.
|
||||
|
||||
].data.translationProgress&url=https%3A%2F%2Fbadges.awesome-crowdin.com%2Fstats-15670597-565845.json) ].data.translationProgress&url=https%3A%2F%2Fbadges.awesome-crowdin.com%2Fstats-15670597-565845.json) ].data.translationProgress&url=https%3A%2F%2Fbadges.awesome-crowdin.com%2Fstats-15670597-565845.json) ].data.translationProgress&url=https%3A%2F%2Fbadges.awesome-crowdin.com%2Fstats-15670597-565845.json) ].data.translationProgress&url=https%3A%2F%2Fbadges.awesome-crowdin.com%2Fstats-15670597-565845.json)].data.translationProgress&url=https%3A%2F%2Fbadges.awesome-crowdin.com%2Fstats-15670597-565845.json) ].data.translationProgress&url=https%3A%2F%2Fbadges.awesome-crowdin.com%2Fstats-15670597-565845.json)
|
||||
[从 GitHub 发布页获取 / Download from GitHub release](https://github.com/DGP-Studio/Snap.Hutao.Deployment/releases/latest)
|
||||
|
||||
Snap Hutao 使用 [Crowdin](https://translate.hut.ao/) 作为客户端文本翻译平台,在该平台上你可以为你熟悉的语言提交翻译文本。我们感谢每一个为 Snap Hutao 做出贡献的社区成员,并且欢迎更多的朋友能参与到这个项目中。
|
||||
[从极狐Lab 发布页获取 / Download from Jihu Gitlab release](https://jihulab.com/DGP-Studio/Snap.Hutao.Deployment/-/releases)
|
||||
|
||||
Snap Hutao uses [Crowdin](https://translate.hut.ao/) as a client text translation platform where you can submit translated text for languages you are familiar with. We are grateful to every community member who has contributed to Snap Hutao and welcome more friends to participate in this project.
|
||||
#### 使用 MSIX 包安装 / Install with MSIX Package
|
||||
|
||||
## 社区 / Community
|
||||
直接使用 Snap Hutao MSIX 安装包,使用 Windows 内置的 App Installer 即可安装。如在安装中出现问题,请查阅我们的[常见问题](https://hut.ao/zh/advanced/FAQ.html)文档
|
||||
|
||||
[](https://discord.gg/CcH5XtDtvR) [](https://qm.qq.com/q/WJKykrY9W)
|
||||
Install with Snap Hutao MSIX package, can be installed with Windows built-in App Installer. If you faced any issue, please check our [FAQ](https://hut.ao/en/advanced/FAQ.html) document.
|
||||
|
||||
[从 GitHub 发布页获取 / Download from GitHub release](https://github.com/DGP-Studio/Snap.Hutao/releases/latest)
|
||||
|
||||
[从极狐Lab 发布页获取 / Download from Jihu Gitlab release](https://jihulab.com/DGP-Studio/Snap.Hutao/-/releases)
|
||||
|
||||
## 贡献 / Contribute
|
||||
|
||||
* [向我们提交 PR / Make Pull Requests](https://hut.ao/development/contribute.html)
|
||||
* [为我们更新文档 / Enhance our Document](https://github.com/DGP-Studio/Snap.Hutao.Docs)
|
||||
* [向我们提交 PR / Make Pull Requests](https://github.com/DGP-Studio/Snap.Hutao/pulls)
|
||||
* [在 Crowdin 上进行本地化 / Translate Project on Crowdin](https://translate.hut.ao/)
|
||||
* [为我们更新文档 / Enhance our Document ](https://github.com/DGP-Studio/Snap.Hutao.Docs)
|
||||
|
||||
## 特别感谢 / Special Thanks
|
||||
|
||||
@@ -39,7 +44,9 @@ Snap Hutao uses [Crowdin](https://translate.hut.ao/) as a client text translatio
|
||||
|
||||
### 特定的原神项目 / Specific Genshin-related Projects
|
||||
|
||||
* [Scighost/Starward](https://github.com/Scighost/Starward)
|
||||
* [biuuu/genshin-wish-export](https://github.com/biuuu/genshin-wish-export)
|
||||
* [xunkong/xunkong](https://github.com/xunkong/xunkong)
|
||||
* [YuehaiTeam/cocogoat](https://github.com/YuehaiTeam/cocogoat)
|
||||
|
||||
### 使用的技术栈 / Tech Stack
|
||||
|
||||
@@ -50,6 +57,7 @@ Snap Hutao uses [Crowdin](https://translate.hut.ao/) as a client text translatio
|
||||
* [dotnet/efcore](https://github.com/dotnet/efcore)
|
||||
* [dotnet/runtime](https://github.com/dotnet/runtime)
|
||||
* [DotNetAnalyzers/StyleCopAnalyzers](https://github.com/DotNetAnalyzers/StyleCopAnalyzers)
|
||||
* [microsoft/CsWin32](https://github.com/microsoft/CsWin32)
|
||||
* [microsoft/vs-validation](https://github.com/microsoft/vs-validation)
|
||||
* [microsoft/WindowsAppSDK](https://github.com/microsoft/WindowsAppSDK)
|
||||
* [microsoft/microsoft-ui-xaml](https://github.com/microsoft/microsoft-ui-xaml)
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
|
||||
| Version | Supported |
|
||||
| ------- | ------------------ |
|
||||
| >=1.9.0 | :white_check_mark: |
|
||||
| <1.9.0 | :x: |
|
||||
| >=1.6.0 | :white_check_mark: |
|
||||
| <1.6.0 | :x: |
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
|
||||
60
build.cake
60
build.cake
@@ -1,5 +1,5 @@
|
||||
#tool "nuget:?package=nuget.commandline&version=6.9.1"
|
||||
#addin nuget:?package=Cake.Http&version=4.0.0
|
||||
#tool "nuget:?package=nuget.commandline&version=6.5.0"
|
||||
#addin nuget:?package=Cake.Http&version=3.0.2
|
||||
|
||||
var target = Argument("target", "Build");
|
||||
var configuration = Argument("configuration", "Release");
|
||||
@@ -33,28 +33,18 @@ if (GitHubActions.IsRunningOnGitHubActions)
|
||||
repoDir = GitHubActions.Environment.Workflow.Workspace.FullPath;
|
||||
outputPath = System.IO.Path.Combine(repoDir, "src", "output");
|
||||
|
||||
if (GitHubActions.Environment.PullRequest.IsPullRequest)
|
||||
{
|
||||
version = System.DateTime.Now.ToString("yyyy.M.d.0");
|
||||
|
||||
Information("Is Pull Request. Skip version.");
|
||||
}
|
||||
else
|
||||
{
|
||||
var versionAuth = HasEnvironmentVariable("VERSION_API_TOKEN") ? EnvironmentVariable("VERSION_API_TOKEN") : throw new Exception("Cannot find VERSION_API_TOKEN");
|
||||
version = HttpGet(
|
||||
"https://internal.snapgenshin.cn/BuildIntergration/RequestNewVersion",
|
||||
new HttpSettings
|
||||
{
|
||||
Headers = new Dictionary<string, string>
|
||||
{
|
||||
var versionAuth = HasEnvironmentVariable("VERSION_API_TOKEN") ? EnvironmentVariable("VERSION_API_TOKEN") : throw new Exception("Cannot find VERSION_API_TOKEN");
|
||||
version = HttpGet(
|
||||
"https://internal.snapgenshin.cn/BuildIntergration/RequestNewVersion",
|
||||
new HttpSettings
|
||||
{
|
||||
Headers = new Dictionary<string, string>
|
||||
{
|
||||
{ "Authorization", versionAuth }
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
Information($"Version: {version}");
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
Information($"Version: {version}");
|
||||
|
||||
GitHubActions.Commands.SetOutputParameter("version", version);
|
||||
}
|
||||
@@ -69,15 +59,6 @@ else if (AppVeyor.IsRunningOnAppVeyor)
|
||||
})[..^2];
|
||||
Information($"Version: {version}");
|
||||
}
|
||||
else // Local
|
||||
{
|
||||
repoDir = System.Environment.CurrentDirectory;
|
||||
outputPath = System.IO.Path.Combine(repoDir, "src", "output");
|
||||
|
||||
version = System.DateTime.Now.ToString("yyyy.M.d.") + ((int)((System.DateTime.Now - System.DateTime.Today).TotalSeconds / 86400 * 65535)).ToString();
|
||||
|
||||
Information($"Version: {version}");
|
||||
}
|
||||
|
||||
Task("Build")
|
||||
.IsDependentOn("Build binary package")
|
||||
@@ -121,17 +102,6 @@ Task("Generate AppxManifest")
|
||||
Information("Using Release configuration");
|
||||
content = System.Text.RegularExpressions.Regex.Replace(content, " Publisher=\"([^\"]*)\"", " Publisher=\"CN=SignPath Foundation, O=SignPath Foundation, L=Lewes, S=Delaware, C=US\"");
|
||||
}
|
||||
else
|
||||
{
|
||||
Information("Using Local configuration.");
|
||||
content = content
|
||||
.Replace("Snap Hutao", "Snap Hutao Local")
|
||||
.Replace("胡桃", "胡桃 Local")
|
||||
.Replace("DGP Studio", "DGP Studio CI");
|
||||
content = System.Text.RegularExpressions.Regex.Replace(content, " Name=\"([^\"]*)\"", " Name=\"E8B6E2B3-D2A0-4435-A81D-2A16AAF405C7\"");
|
||||
content = System.Text.RegularExpressions.Regex.Replace(content, " Publisher=\"([^\"]*)\"", " Publisher=\"E=admin@dgp-studio.cn, CN=DGP Studio CI, OU=CI, O=DGP-Studio, L=San Jose, S=CA, C=US\"");
|
||||
content = System.Text.RegularExpressions.Regex.Replace(content, " Version=\"([0-9\\.]+)\"", $" Version=\"{version}\"");
|
||||
}
|
||||
|
||||
System.IO.File.WriteAllText(manifest, content);
|
||||
|
||||
@@ -193,10 +163,6 @@ Task("Build MSIX")
|
||||
{
|
||||
arguments = "pack /d " + binPath + " /p " + System.IO.Path.Combine(outputPath, $"Snap.Hutao-{version}.msix");
|
||||
}
|
||||
else
|
||||
{
|
||||
arguments = "pack /d " + binPath + " /p " + System.IO.Path.Combine(outputPath, $"Snap.Hutao.Local-{version}.msix");
|
||||
}
|
||||
var p = StartProcess(
|
||||
"makeappx.exe",
|
||||
new ProcessSettings
|
||||
|
||||
@@ -160,39 +160,9 @@ public sealed class GeniusInvokationDecoding
|
||||
|
||||
ushort[] testKnownResult =
|
||||
[
|
||||
060,
|
||||
019,
|
||||
001,
|
||||
079,
|
||||
120,
|
||||
120,
|
||||
129,
|
||||
151,
|
||||
151,
|
||||
153,
|
||||
153,
|
||||
181,
|
||||
184,
|
||||
184,
|
||||
185,
|
||||
185,
|
||||
194,
|
||||
194,
|
||||
200,
|
||||
200,
|
||||
201,
|
||||
201,
|
||||
217,
|
||||
217,
|
||||
219,
|
||||
241,
|
||||
241,
|
||||
244,
|
||||
244,
|
||||
245,
|
||||
245,
|
||||
270,
|
||||
270,
|
||||
060, 019, 001, 079, 120, 120, 129, 151, 151, 153, 153,
|
||||
181, 184, 184, 185, 185, 194, 194, 200, 200, 201, 201,
|
||||
217, 217, 219, 241, 241, 244, 244, 245, 245, 270, 270,
|
||||
];
|
||||
|
||||
CollectionAssert.AreEqual(resultArray, testKnownResult);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net8.0</TargetFrameworks>
|
||||
@@ -12,10 +12,10 @@
|
||||
|
||||
<ItemGroup>
|
||||
<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.2" />
|
||||
<PackageReference Include="MSTest.TestFramework" Version="3.2.2" />
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.1">
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
|
||||
<PackageReference Include="MSTest.TestAdapter" Version="3.1.1" />
|
||||
<PackageReference Include="MSTest.TestFramework" Version="3.1.1" />
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.0">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
|
||||
11
src/Snap.Hutao/Snap.Hutao.Win32/NativeMethods.json
Normal file
11
src/Snap.Hutao/Snap.Hutao.Win32/NativeMethods.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"$schema": "https://raw.githubusercontent.com/microsoft/CsWin32/main/src/Microsoft.Windows.CsWin32/settings.schema.json",
|
||||
"allowMarshaling": true,
|
||||
"useSafeHandles": false,
|
||||
"comInterop": {
|
||||
"preserveSigMethods": [
|
||||
"IFileOpenDialog.Show",
|
||||
"IFileSaveDialog.Show"
|
||||
]
|
||||
}
|
||||
}
|
||||
117
src/Snap.Hutao/Snap.Hutao.Win32/NativeMethods.txt
Normal file
117
src/Snap.Hutao/Snap.Hutao.Win32/NativeMethods.txt
Normal file
@@ -0,0 +1,117 @@
|
||||
// ADVAPI32
|
||||
RegCloseKey
|
||||
RegOpenKeyExW
|
||||
RegNotifyChangeKeyValue
|
||||
|
||||
// COMCTL32
|
||||
DefSubclassProc
|
||||
RemoveWindowSubclass
|
||||
SetWindowSubclass
|
||||
|
||||
// DWMAPI
|
||||
DwmSetWindowAttribute
|
||||
|
||||
// GDI32
|
||||
GetDeviceCaps
|
||||
|
||||
// KERNEL32
|
||||
AllocConsole
|
||||
CloseHandle
|
||||
CreateEventW
|
||||
CreateRemoteThread
|
||||
FreeConsole
|
||||
GetConsoleMode
|
||||
GetModuleHandleW
|
||||
GetProcAddress
|
||||
GetStdHandle
|
||||
K32EnumProcessModules
|
||||
K32GetModuleBaseNameW
|
||||
K32GetModuleInformation
|
||||
ReadProcessMemory
|
||||
SetConsoleMode
|
||||
SetConsoleTitle
|
||||
SetEvent
|
||||
VirtualAlloc
|
||||
VirtualAllocEx
|
||||
VirtualFree
|
||||
VirtualFreeEx
|
||||
WaitForSingleObject
|
||||
WriteProcessMemory
|
||||
|
||||
// OLE32
|
||||
CoCreateInstance
|
||||
CoWaitForMultipleObjects
|
||||
|
||||
// SHELL32
|
||||
SHCreateItemFromParsingName
|
||||
|
||||
// USER32
|
||||
AttachThreadInput
|
||||
FindWindowExW
|
||||
GetCursorPos
|
||||
GetDC
|
||||
GetDpiForWindow
|
||||
GetForegroundWindow
|
||||
GetWindowLongPtrW
|
||||
GetWindowPlacement
|
||||
GetWindowThreadProcessId
|
||||
ReleaseDC
|
||||
RegisterHotKey
|
||||
SendInput
|
||||
SetForegroundWindow
|
||||
SetWindowLongPtrW
|
||||
UnregisterHotKey
|
||||
|
||||
// COM
|
||||
FileOpenDialog
|
||||
FileSaveDialog
|
||||
IFileOpenDialog
|
||||
IFileSaveDialog
|
||||
IPersistFile
|
||||
IShellLinkDataList
|
||||
IShellLinkW
|
||||
ShellLink
|
||||
SHELL_LINK_DATA_FLAGS
|
||||
|
||||
// WinRT
|
||||
IMemoryBufferByteAccess
|
||||
|
||||
// Macro
|
||||
HRESULT_FROM_WIN32
|
||||
|
||||
// Const value
|
||||
INFINITE
|
||||
MAX_PATH
|
||||
WM_ERASEBKGND
|
||||
WM_GETMINMAXINFO
|
||||
WM_HOTKEY
|
||||
WM_NCRBUTTONDOWN
|
||||
WM_NCRBUTTONUP
|
||||
WM_NULL
|
||||
|
||||
// HKEY
|
||||
HKEY_CLASSES_ROOT
|
||||
HKEY_CURRENT_CONFIG
|
||||
HKEY_CURRENT_USER
|
||||
HKEY_LOCAL_MACHINE
|
||||
HKEY_USERS
|
||||
|
||||
// HRESULT
|
||||
E_FAIL
|
||||
RPC_E_WRONG_THREAD
|
||||
|
||||
// System.Com
|
||||
CWMO_FLAGS
|
||||
|
||||
// System.Registry
|
||||
REG_NOTIFY_FILTER
|
||||
|
||||
// System.Threading
|
||||
LPTHREAD_START_ROUTINE
|
||||
|
||||
// UI.Shell
|
||||
SLGP_FLAGS
|
||||
|
||||
// UI.WindowsAndMessaging
|
||||
MINMAXINFO
|
||||
WINDOW_EX_STYLE
|
||||
17
src/Snap.Hutao/Snap.Hutao.Win32/PInvoke.cs
Normal file
17
src/Snap.Hutao/Snap.Hutao.Win32/PInvoke.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
using System;
|
||||
using Windows.Win32.Foundation;
|
||||
using Windows.Win32.System.Com;
|
||||
|
||||
namespace Windows.Win32;
|
||||
|
||||
internal static partial class PInvoke
|
||||
{
|
||||
/// <inheritdoc cref="CoCreateInstance(Guid*, object, CLSCTX, Guid*, out object)"/>
|
||||
internal static unsafe HRESULT CoCreateInstance<TClass, TInterface>(object? pUnkOuter, CLSCTX dwClsContext, out TInterface ppv)
|
||||
where TInterface : class
|
||||
{
|
||||
HRESULT hr = CoCreateInstance(typeof(TClass).GUID, pUnkOuter, dwClsContext, typeof(TInterface).GUID, out object o);
|
||||
ppv = (TInterface)o;
|
||||
return hr;
|
||||
}
|
||||
}
|
||||
26
src/Snap.Hutao/Snap.Hutao.Win32/Snap.Hutao.Win32.csproj
Normal file
26
src/Snap.Hutao/Snap.Hutao.Win32/Snap.Hutao.Win32.csproj
Normal file
@@ -0,0 +1,26 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0-windows10.0.22621.0</TargetFramework>
|
||||
<ImplicitUsings>disable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Remove="NativeMethods.json" />
|
||||
<None Remove="NativeMethods.txt" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<AdditionalFiles Include="NativeMethods.json" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Windows.CsWin32" Version="0.3.49-beta">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
24
src/Snap.Hutao/Snap.Hutao.Win32/StructMarshal.cs
Normal file
24
src/Snap.Hutao/Snap.Hutao.Win32/StructMarshal.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using System.Runtime.CompilerServices;
|
||||
using Windows.Win32.UI.WindowsAndMessaging;
|
||||
|
||||
[assembly: InternalsVisibleTo("Snap.Hutao")]
|
||||
|
||||
namespace Snap.Hutao.Win32;
|
||||
|
||||
/// <summary>
|
||||
/// 结构体封送
|
||||
/// </summary>
|
||||
internal static class StructMarshal
|
||||
{
|
||||
/// <summary>
|
||||
/// 构造一个新的 <see cref="Windows.Win32.UI.WindowsAndMessaging.WINDOWPLACEMENT"/>
|
||||
/// </summary>
|
||||
/// <returns>新的实例</returns>
|
||||
public static unsafe WINDOWPLACEMENT WINDOWPLACEMENT()
|
||||
{
|
||||
return new() { length = unchecked((uint)sizeof(WINDOWPLACEMENT)) };
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Snap.Hutao.Test", "Snap.Hutao.Test\Snap.Hutao.Test.csproj", "{D691BA9F-904C-4229-87A5-E14F2EFF2F64}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Snap.Hutao.Win32", "Snap.Hutao.Win32\Snap.Hutao.Win32.csproj", "{0F7ABEB2-5107-4037-B9DC-84D288FB0801}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
@@ -65,16 +67,32 @@ Global
|
||||
{D691BA9F-904C-4229-87A5-E14F2EFF2F64}.Release|x64.Build.0 = Release|Any CPU
|
||||
{D691BA9F-904C-4229-87A5-E14F2EFF2F64}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{D691BA9F-904C-4229-87A5-E14F2EFF2F64}.Release|x86.Build.0 = Release|Any CPU
|
||||
{0F7ABEB2-5107-4037-B9DC-84D288FB0801}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{0F7ABEB2-5107-4037-B9DC-84D288FB0801}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{0F7ABEB2-5107-4037-B9DC-84D288FB0801}.Debug|arm64.ActiveCfg = Debug|Any CPU
|
||||
{0F7ABEB2-5107-4037-B9DC-84D288FB0801}.Debug|arm64.Build.0 = Debug|Any CPU
|
||||
{0F7ABEB2-5107-4037-B9DC-84D288FB0801}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{0F7ABEB2-5107-4037-B9DC-84D288FB0801}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{0F7ABEB2-5107-4037-B9DC-84D288FB0801}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{0F7ABEB2-5107-4037-B9DC-84D288FB0801}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{0F7ABEB2-5107-4037-B9DC-84D288FB0801}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{0F7ABEB2-5107-4037-B9DC-84D288FB0801}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{0F7ABEB2-5107-4037-B9DC-84D288FB0801}.Release|arm64.ActiveCfg = Release|Any CPU
|
||||
{0F7ABEB2-5107-4037-B9DC-84D288FB0801}.Release|arm64.Build.0 = Release|Any CPU
|
||||
{0F7ABEB2-5107-4037-B9DC-84D288FB0801}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{0F7ABEB2-5107-4037-B9DC-84D288FB0801}.Release|x64.Build.0 = Release|Any CPU
|
||||
{0F7ABEB2-5107-4037-B9DC-84D288FB0801}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{0F7ABEB2-5107-4037-B9DC-84D288FB0801}.Release|x86.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
RESX_AutoApplyExistingTranslations = False
|
||||
RESX_NeutralResourcesLanguage = zh-CN
|
||||
SolutionGuid = {E4449B1C-0E6A-4D19-955E-1CA491656ABA}
|
||||
RESX_SortFileContentOnSave = True
|
||||
RESX_ShowErrorsInErrorList = False
|
||||
RESX_Rules = {"EnabledRules":["StringFormat","WhiteSpaceLead","WhiteSpaceTail","PunctuationLead"]}
|
||||
RESX_ShowErrorsInErrorList = False
|
||||
RESX_SortFileContentOnSave = True
|
||||
SolutionGuid = {E4449B1C-0E6A-4D19-955E-1CA491656ABA}
|
||||
RESX_NeutralResourcesLanguage = zh-CN
|
||||
RESX_AutoApplyExistingTranslations = False
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
<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/Theme/Card.xaml"/>
|
||||
@@ -23,9 +22,7 @@
|
||||
<ResourceDictionary Source="ms-appx:///Control/Theme/PageOverride.xaml"/>
|
||||
<ResourceDictionary Source="ms-appx:///Control/Theme/PivotOverride.xaml"/>
|
||||
<ResourceDictionary Source="ms-appx:///Control/Theme/ScrollViewer.xaml"/>
|
||||
<ResourceDictionary Source="ms-appx:///Control/Theme/SegmentedOverride.xaml"/>
|
||||
<ResourceDictionary Source="ms-appx:///Control/Theme/SettingsStyle.xaml"/>
|
||||
<ResourceDictionary Source="ms-appx:///Control/Theme/Thickness.xaml"/>
|
||||
<ResourceDictionary Source="ms-appx:///Control/Theme/TransitionCollection.xaml"/>
|
||||
<ResourceDictionary Source="ms-appx:///Control/Theme/Uri.xaml"/>
|
||||
<ResourceDictionary Source="ms-appx:///Control/Theme/WindowOverride.xaml"/>
|
||||
@@ -35,7 +32,6 @@
|
||||
x:Key="LargeGridViewItemStyle"
|
||||
BasedOn="{StaticResource DefaultGridViewItemStyle}"
|
||||
TargetType="GridViewItem">
|
||||
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
|
||||
<Setter Property="Margin" Value="0,0,12,12"/>
|
||||
</Style>
|
||||
<Style
|
||||
|
||||
@@ -6,7 +6,6 @@ using Microsoft.Windows.AppLifecycle;
|
||||
using Snap.Hutao.Core;
|
||||
using Snap.Hutao.Core.ExceptionService;
|
||||
using Snap.Hutao.Core.LifeCycle;
|
||||
using Snap.Hutao.Core.LifeCycle.InterProcess;
|
||||
using Snap.Hutao.Core.Shell;
|
||||
using System.Diagnostics;
|
||||
|
||||
@@ -37,6 +36,8 @@ public sealed partial class App : Application
|
||||
----------------------------------------------------------------
|
||||
""";
|
||||
|
||||
private const string AppInstanceKey = "main";
|
||||
|
||||
private readonly IServiceProvider serviceProvider;
|
||||
private readonly IActivation activation;
|
||||
private readonly ILogger<App> logger;
|
||||
@@ -49,6 +50,7 @@ public sealed partial class App : Application
|
||||
{
|
||||
// Load app resource
|
||||
InitializeComponent();
|
||||
|
||||
activation = serviceProvider.GetRequiredService<IActivation>();
|
||||
logger = serviceProvider.GetRequiredService<ILogger<App>>();
|
||||
serviceProvider.GetRequiredService<ExceptionRecorder>().Record(this);
|
||||
@@ -62,21 +64,25 @@ public sealed partial class App : Application
|
||||
try
|
||||
{
|
||||
AppActivationArguments activatedEventArgs = AppInstance.GetCurrent().GetActivatedEventArgs();
|
||||
AppInstance firstInstance = AppInstance.FindOrRegisterForKey(AppInstanceKey);
|
||||
|
||||
if (serviceProvider.GetRequiredService<PrivateNamedPipeClient>().TryRedirectActivationTo(activatedEventArgs))
|
||||
if (firstInstance.IsCurrent)
|
||||
{
|
||||
Exit();
|
||||
return;
|
||||
logger.LogInformation(ConsoleBanner);
|
||||
LogDiagnosticInformation();
|
||||
|
||||
// manually invoke
|
||||
activation.NonRedirectToActivate(firstInstance, activatedEventArgs);
|
||||
activation.InitializeWith(firstInstance);
|
||||
|
||||
serviceProvider.GetRequiredService<IJumpListInterop>().ConfigureAsync().SafeForget();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Redirect the activation (and args) to the "main" instance, and exit.
|
||||
firstInstance.RedirectActivationTo(activatedEventArgs);
|
||||
Process.GetCurrentProcess().Kill();
|
||||
}
|
||||
|
||||
logger.LogInformation(ConsoleBanner);
|
||||
LogDiagnosticInformation();
|
||||
|
||||
// manually invoke
|
||||
activation.Activate(HutaoActivationArguments.FromAppActivationArguments(activatedEventArgs));
|
||||
activation.Initialize();
|
||||
|
||||
serviceProvider.GetRequiredService<IJumpListInterop>().ConfigureAsync().SafeForget();
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
||||
@@ -23,12 +23,10 @@ internal static class ControlAnimationConstants
|
||||
/// <summary>
|
||||
/// 图像淡入
|
||||
/// </summary>
|
||||
public static readonly TimeSpan ImageScaleFadeIn = TimeSpan.FromSeconds(0.3);
|
||||
public static readonly TimeSpan ImageFadeIn = TimeSpan.FromSeconds(0.3);
|
||||
|
||||
/// <summary>
|
||||
/// 图像淡出
|
||||
/// </summary>
|
||||
public static readonly TimeSpan ImageScaleFadeOut = TimeSpan.FromSeconds(0.2);
|
||||
|
||||
public static readonly TimeSpan ImageOpacityFadeInOut = TimeSpan.FromSeconds(1);
|
||||
public static readonly TimeSpan ImageFadeOut = TimeSpan.FromSeconds(0.2);
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using CommunityToolkit.WinUI.Controls;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
// 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,
|
||||
}
|
||||
@@ -3,7 +3,6 @@
|
||||
|
||||
using CommunityToolkit.WinUI.Behaviors;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Snap.Hutao.Control.Extension;
|
||||
|
||||
namespace Snap.Hutao.Control.Behavior;
|
||||
|
||||
@@ -46,6 +45,10 @@ internal sealed partial class InvokeCommandOnLoadedBehavior : BehaviorBase<UIEle
|
||||
return;
|
||||
}
|
||||
|
||||
executed = Command.TryExecute(CommandParameter);
|
||||
if (Command is not null && Command.CanExecute(CommandParameter))
|
||||
{
|
||||
Command.Execute(CommandParameter);
|
||||
executed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,87 +0,0 @@
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Control.Brush;
|
||||
|
||||
internal sealed class ColorSegmentCollection : List<IColorSegment>
|
||||
{
|
||||
}
|
||||
@@ -9,5 +9,5 @@ internal interface IColorSegment
|
||||
{
|
||||
Color Color { get; }
|
||||
|
||||
double Value { get; set; }
|
||||
double Value { get; }
|
||||
}
|
||||
@@ -9,48 +9,36 @@ using System.Runtime.InteropServices;
|
||||
|
||||
namespace Snap.Hutao.Control.Brush;
|
||||
|
||||
[DependencyProperty("Source", typeof(ColorSegmentCollection), default!, nameof(OnSourceChanged))]
|
||||
[DependencyProperty("Source", typeof(List<IColorSegment>), default!, nameof(OnSourceChanged))]
|
||||
internal sealed partial class SegmentedBar : ContentControl
|
||||
{
|
||||
private readonly LinearGradientBrush brush = new() { StartPoint = new(0, 0), EndPoint = new(1, 0), };
|
||||
|
||||
public SegmentedBar()
|
||||
{
|
||||
HorizontalContentAlignment = HorizontalAlignment.Stretch;
|
||||
VerticalContentAlignment = VerticalAlignment.Stretch;
|
||||
|
||||
Content = new Rectangle()
|
||||
{
|
||||
Fill = brush,
|
||||
HorizontalAlignment = HorizontalAlignment.Stretch,
|
||||
VerticalAlignment = VerticalAlignment.Stretch,
|
||||
};
|
||||
}
|
||||
|
||||
private static void OnSourceChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
|
||||
{
|
||||
UpdateLinearGradientBrush((SegmentedBar)obj);
|
||||
}
|
||||
SegmentedBar segmentedBar = (SegmentedBar)obj;
|
||||
|
||||
private static void UpdateLinearGradientBrush(SegmentedBar segmentedBar)
|
||||
{
|
||||
GradientStopCollection collection = segmentedBar.brush.GradientStops;
|
||||
collection.Clear();
|
||||
|
||||
ColorSegmentCollection segmentCollection = segmentedBar.Source;
|
||||
|
||||
double total = segmentCollection.Sum(seg => seg.Value);
|
||||
if (total is 0D)
|
||||
if (args.NewValue as List<IColorSegment> is [_, ..] list)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
double offset = 0;
|
||||
foreach (ref readonly IColorSegment segment in CollectionsMarshal.AsSpan(segmentCollection))
|
||||
{
|
||||
collection.Add(new() { Color = segment.Color, Offset = offset, });
|
||||
offset += segment.Value / total;
|
||||
collection.Add(new() { Color = segment.Color, Offset = offset, });
|
||||
double total = list.Sum(seg => seg.Value);
|
||||
double offset = 0;
|
||||
foreach (ref readonly IColorSegment segment in CollectionsMarshal.AsSpan(list))
|
||||
{
|
||||
collection.Add(new() { Color = segment.Color, Offset = offset, });
|
||||
offset += segment.Value / total;
|
||||
collection.Add(new() { Color = segment.Color, Offset = offset, });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,763 +0,0 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using CommunityToolkit.WinUI.Collections;
|
||||
using CommunityToolkit.WinUI.Helpers;
|
||||
using Microsoft.UI.Xaml.Data;
|
||||
using System.Collections;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Specialized;
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Windows.Foundation;
|
||||
using Windows.Foundation.Collections;
|
||||
using NotifyCollectionChangedAction = System.Collections.Specialized.NotifyCollectionChangedAction;
|
||||
|
||||
namespace Snap.Hutao.Control.Collection.AdvancedCollectionView;
|
||||
|
||||
internal sealed class AdvancedCollectionView<T> : IAdvancedCollectionView<T>, INotifyPropertyChanged, ISupportIncrementalLoading, IComparer<object>
|
||||
where T : class
|
||||
{
|
||||
private readonly List<T> view;
|
||||
private readonly ObservableCollection<SortDescription> sortDescriptions;
|
||||
private readonly Dictionary<string, PropertyInfo?> sortProperties;
|
||||
private readonly bool liveShapingEnabled;
|
||||
private readonly HashSet<string?> observedFilterProperties = [];
|
||||
|
||||
private IList<T> source;
|
||||
private Predicate<T>? filter;
|
||||
private int deferCounter;
|
||||
private WeakEventListener<AdvancedCollectionView<T>, object?, NotifyCollectionChangedEventArgs>? sourceWeakEventListener;
|
||||
|
||||
public AdvancedCollectionView()
|
||||
: this(new List<T>(0))
|
||||
{
|
||||
}
|
||||
|
||||
public AdvancedCollectionView(IList<T> source, bool isLiveShaping = false)
|
||||
{
|
||||
liveShapingEnabled = isLiveShaping;
|
||||
view = [];
|
||||
sortDescriptions = [];
|
||||
sortDescriptions.CollectionChanged += SortDescriptionsCollectionChanged;
|
||||
sortProperties = [];
|
||||
Source = source;
|
||||
}
|
||||
|
||||
public event EventHandler<object>? CurrentChanged;
|
||||
|
||||
public event CurrentChangingEventHandler? CurrentChanging;
|
||||
|
||||
public event PropertyChangedEventHandler? PropertyChanged;
|
||||
|
||||
public event VectorChangedEventHandler<object>? VectorChanged;
|
||||
|
||||
public IList<T> Source
|
||||
{
|
||||
get => source;
|
||||
|
||||
[MemberNotNull(nameof(source))]
|
||||
set
|
||||
{
|
||||
if (ReferenceEquals(source, value))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (source is not null)
|
||||
{
|
||||
DetachPropertyChangedHandler(source);
|
||||
}
|
||||
|
||||
source = value;
|
||||
AttachPropertyChangedHandler(source);
|
||||
|
||||
sourceWeakEventListener?.Detach();
|
||||
|
||||
if (source is INotifyCollectionChanged sourceNotifyCollectionChanged)
|
||||
{
|
||||
sourceWeakEventListener = new WeakEventListener<AdvancedCollectionView<T>, object?, NotifyCollectionChangedEventArgs>(this)
|
||||
{
|
||||
// Call the actual collection changed event
|
||||
OnEventAction = (source, changed, arg3) => SourceNotifyCollectionChangedCollectionChanged(source, arg3),
|
||||
|
||||
// The source doesn't exist anymore
|
||||
OnDetachAction = (listener) =>
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(sourceWeakEventListener);
|
||||
sourceNotifyCollectionChanged.CollectionChanged -= sourceWeakEventListener.OnEvent;
|
||||
},
|
||||
};
|
||||
sourceNotifyCollectionChanged.CollectionChanged += sourceWeakEventListener.OnEvent;
|
||||
}
|
||||
|
||||
HandleSourceChanged();
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public int Count
|
||||
{
|
||||
get => view.Count;
|
||||
}
|
||||
|
||||
public bool IsReadOnly
|
||||
{
|
||||
get => source is null || source.IsReadOnly;
|
||||
}
|
||||
|
||||
public IObservableVector<object> CollectionGroups
|
||||
{
|
||||
get => default!;
|
||||
}
|
||||
|
||||
public T? CurrentItem
|
||||
{
|
||||
get => CurrentPosition > -1 && CurrentPosition < view.Count ? view[CurrentPosition] : default;
|
||||
set => MoveCurrentTo(value);
|
||||
}
|
||||
|
||||
public int CurrentPosition { get; private set; }
|
||||
|
||||
public bool HasMoreItems
|
||||
{
|
||||
get => source is ISupportIncrementalLoading { HasMoreItems: true };
|
||||
}
|
||||
|
||||
public bool IsCurrentAfterLast
|
||||
{
|
||||
get => CurrentPosition >= view.Count;
|
||||
}
|
||||
|
||||
public bool IsCurrentBeforeFirst
|
||||
{
|
||||
get => CurrentPosition < 0;
|
||||
}
|
||||
|
||||
public bool CanFilter
|
||||
{
|
||||
get => true;
|
||||
}
|
||||
|
||||
public Predicate<T>? Filter
|
||||
{
|
||||
get => filter;
|
||||
set
|
||||
{
|
||||
if (filter == value)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
filter = value;
|
||||
HandleFilterChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public bool CanSort
|
||||
{
|
||||
get => true;
|
||||
}
|
||||
|
||||
public IList<SortDescription> SortDescriptions
|
||||
{
|
||||
get => sortDescriptions;
|
||||
}
|
||||
|
||||
public IEnumerable<T> SourceCollection
|
||||
{
|
||||
get => source;
|
||||
}
|
||||
|
||||
public IReadOnlyList<T> View
|
||||
{
|
||||
get => view;
|
||||
}
|
||||
|
||||
public T this[int index]
|
||||
{
|
||||
get => view[index];
|
||||
set => view[index] = value;
|
||||
}
|
||||
|
||||
public void Refresh()
|
||||
{
|
||||
HandleSourceChanged();
|
||||
}
|
||||
|
||||
public void RefreshFilter()
|
||||
{
|
||||
HandleFilterChanged();
|
||||
}
|
||||
|
||||
public void RefreshSorting()
|
||||
{
|
||||
HandleSortChanged();
|
||||
}
|
||||
|
||||
public IEnumerator<T> GetEnumerator()
|
||||
{
|
||||
return view.GetEnumerator();
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return view.GetEnumerator();
|
||||
}
|
||||
|
||||
public void Add(T item)
|
||||
{
|
||||
source.Add(item);
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
source.Clear();
|
||||
}
|
||||
|
||||
public bool Contains(T item)
|
||||
{
|
||||
return view.Contains(item);
|
||||
}
|
||||
|
||||
public void CopyTo(T[] array, int arrayIndex)
|
||||
{
|
||||
view.CopyTo(array, arrayIndex);
|
||||
}
|
||||
|
||||
public bool Remove(T item)
|
||||
{
|
||||
source.Remove(item);
|
||||
return true;
|
||||
}
|
||||
|
||||
[SuppressMessage("", "SH007")]
|
||||
public int IndexOf(T? item)
|
||||
{
|
||||
return view.IndexOf(item!);
|
||||
}
|
||||
|
||||
public void Insert(int index, T item)
|
||||
{
|
||||
source.Insert(index, item);
|
||||
}
|
||||
|
||||
public void RemoveAt(int index)
|
||||
{
|
||||
Remove(view[index]);
|
||||
}
|
||||
|
||||
public bool MoveCurrentTo(T? item)
|
||||
{
|
||||
return (item is not null && item.Equals(CurrentItem)) || MoveCurrentToIndex(IndexOf(item));
|
||||
}
|
||||
|
||||
public bool MoveCurrentToPosition(int index)
|
||||
{
|
||||
return MoveCurrentToIndex(index);
|
||||
}
|
||||
|
||||
public bool MoveCurrentToFirst()
|
||||
{
|
||||
return MoveCurrentToIndex(0);
|
||||
}
|
||||
|
||||
public bool MoveCurrentToLast()
|
||||
{
|
||||
return MoveCurrentToIndex(view.Count - 1);
|
||||
}
|
||||
|
||||
public bool MoveCurrentToNext()
|
||||
{
|
||||
return MoveCurrentToIndex(CurrentPosition + 1);
|
||||
}
|
||||
|
||||
public bool MoveCurrentToPrevious()
|
||||
{
|
||||
return MoveCurrentToIndex(CurrentPosition - 1);
|
||||
}
|
||||
|
||||
public IAsyncOperation<LoadMoreItemsResult>? LoadMoreItemsAsync(uint count)
|
||||
{
|
||||
return (source as ISupportIncrementalLoading)?.LoadMoreItemsAsync(count);
|
||||
}
|
||||
|
||||
public void ObserveFilterProperty(string propertyName)
|
||||
{
|
||||
observedFilterProperties.Add(propertyName);
|
||||
}
|
||||
|
||||
public void ClearObservedFilterProperties()
|
||||
{
|
||||
observedFilterProperties.Clear();
|
||||
}
|
||||
|
||||
public IDisposable DeferRefresh()
|
||||
{
|
||||
return new NotificationDeferrer(this);
|
||||
}
|
||||
|
||||
int IComparer<object>.Compare(object? x, object? y)
|
||||
{
|
||||
if (sortProperties.Count <= 0)
|
||||
{
|
||||
Type listType = source.GetType();
|
||||
Type? type;
|
||||
|
||||
if (listType.IsGenericType)
|
||||
{
|
||||
type = listType.GetGenericArguments()[0];
|
||||
}
|
||||
else
|
||||
{
|
||||
type = x?.GetType();
|
||||
}
|
||||
|
||||
foreach (SortDescription sd in sortDescriptions)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(sd.PropertyName))
|
||||
{
|
||||
sortProperties[sd.PropertyName] = type?.GetProperty(sd.PropertyName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (SortDescription sd in sortDescriptions)
|
||||
{
|
||||
object? cx, cy;
|
||||
|
||||
if (string.IsNullOrEmpty(sd.PropertyName))
|
||||
{
|
||||
cx = x;
|
||||
cy = y;
|
||||
}
|
||||
else
|
||||
{
|
||||
PropertyInfo? pi = sortProperties[sd.PropertyName];
|
||||
|
||||
cx = pi?.GetValue(x);
|
||||
cy = pi?.GetValue(y);
|
||||
}
|
||||
|
||||
int cmp = sd.Comparer.Compare(cx, cy);
|
||||
|
||||
if (cmp is not 0)
|
||||
{
|
||||
return sd.Direction is SortDirection.Ascending ? +cmp : -cmp;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
internal void OnPropertyChanged([CallerMemberName] string propertyName = default!)
|
||||
{
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||
}
|
||||
|
||||
private void ItemOnPropertyChanged(object? item, PropertyChangedEventArgs e)
|
||||
{
|
||||
if (!liveShapingEnabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ArgumentNullException.ThrowIfNull(item);
|
||||
T typedItem = (T)item;
|
||||
|
||||
bool? filterResult = filter?.Invoke(typedItem);
|
||||
|
||||
if (filterResult.HasValue && observedFilterProperties.Contains(e.PropertyName))
|
||||
{
|
||||
int viewIndex = view.IndexOf(typedItem);
|
||||
if (viewIndex != -1 && !filterResult.Value)
|
||||
{
|
||||
RemoveFromView(viewIndex, typedItem);
|
||||
}
|
||||
else if (viewIndex == -1 && filterResult.Value)
|
||||
{
|
||||
int index = source.IndexOf(typedItem);
|
||||
HandleItemAdded(index, typedItem);
|
||||
}
|
||||
}
|
||||
|
||||
if ((filterResult ?? true) && SortDescriptions.Any(sd => sd.PropertyName == e.PropertyName))
|
||||
{
|
||||
int oldIndex = view.IndexOf(typedItem);
|
||||
|
||||
// Check if item is in view:
|
||||
if (oldIndex < 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
view.RemoveAt(oldIndex);
|
||||
int targetIndex = view.BinarySearch(typedItem, this);
|
||||
if (targetIndex < 0)
|
||||
{
|
||||
targetIndex = ~targetIndex;
|
||||
}
|
||||
|
||||
// Only trigger expensive UI updates if the index really changed:
|
||||
if (targetIndex != oldIndex)
|
||||
{
|
||||
OnVectorChanged(new VectorChangedEventArgs(CollectionChange.ItemRemoved, oldIndex, typedItem));
|
||||
|
||||
view.Insert(targetIndex, typedItem);
|
||||
|
||||
OnVectorChanged(new VectorChangedEventArgs(CollectionChange.ItemInserted, targetIndex, typedItem));
|
||||
}
|
||||
else
|
||||
{
|
||||
view.Insert(targetIndex, typedItem);
|
||||
}
|
||||
}
|
||||
else if (string.IsNullOrEmpty(e.PropertyName))
|
||||
{
|
||||
HandleSourceChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private void AttachPropertyChangedHandler(IEnumerable items)
|
||||
{
|
||||
if (!liveShapingEnabled || items is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (object item in items)
|
||||
{
|
||||
if (item is INotifyPropertyChanged notifyPropertyChanged)
|
||||
{
|
||||
notifyPropertyChanged.PropertyChanged += ItemOnPropertyChanged;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void DetachPropertyChangedHandler(IEnumerable items)
|
||||
{
|
||||
if (!liveShapingEnabled || items is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (object item in items)
|
||||
{
|
||||
if (item is INotifyPropertyChanged notifyPropertyChanged)
|
||||
{
|
||||
notifyPropertyChanged.PropertyChanged -= ItemOnPropertyChanged;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleSortChanged()
|
||||
{
|
||||
sortProperties.Clear();
|
||||
view.Sort(this);
|
||||
sortProperties.Clear();
|
||||
OnVectorChanged(new VectorChangedEventArgs(CollectionChange.Reset));
|
||||
}
|
||||
|
||||
private void HandleFilterChanged()
|
||||
{
|
||||
if (filter is not null)
|
||||
{
|
||||
for (int index = 0; index < view.Count; index++)
|
||||
{
|
||||
T item = view[index];
|
||||
if (filter(item))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
RemoveFromView(index, item);
|
||||
index--;
|
||||
}
|
||||
}
|
||||
|
||||
HashSet<T> viewHash = new(view);
|
||||
int viewIndex = 0;
|
||||
for (int index = 0; index < source.Count; index++)
|
||||
{
|
||||
T item = source[index];
|
||||
if (viewHash.Contains(item))
|
||||
{
|
||||
viewIndex++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (HandleItemAdded(index, item, viewIndex))
|
||||
{
|
||||
viewIndex++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleSourceChanged()
|
||||
{
|
||||
sortProperties.Clear();
|
||||
T? currentItem = CurrentItem;
|
||||
view.Clear();
|
||||
foreach (T item in Source)
|
||||
{
|
||||
if (filter is not null && !filter(item))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (sortDescriptions.Count > 0)
|
||||
{
|
||||
int targetIndex = view.BinarySearch(item, this);
|
||||
if (targetIndex < 0)
|
||||
{
|
||||
targetIndex = ~targetIndex;
|
||||
}
|
||||
|
||||
view.Insert(targetIndex, item);
|
||||
}
|
||||
else
|
||||
{
|
||||
view.Add(item);
|
||||
}
|
||||
}
|
||||
|
||||
sortProperties.Clear();
|
||||
OnVectorChanged(new VectorChangedEventArgs(CollectionChange.Reset));
|
||||
MoveCurrentTo(currentItem);
|
||||
}
|
||||
|
||||
private void SourceNotifyCollectionChangedCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
switch (e.Action)
|
||||
{
|
||||
case NotifyCollectionChangedAction.Add:
|
||||
ArgumentNullException.ThrowIfNull(e.NewItems);
|
||||
AttachPropertyChangedHandler(e.NewItems);
|
||||
if (deferCounter <= 0)
|
||||
{
|
||||
if (e.NewItems?.Count == 1)
|
||||
{
|
||||
object? newItem = e.NewItems[0];
|
||||
ArgumentNullException.ThrowIfNull(newItem);
|
||||
HandleItemAdded(e.NewStartingIndex, (T)newItem);
|
||||
}
|
||||
else
|
||||
{
|
||||
HandleSourceChanged();
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
case NotifyCollectionChangedAction.Remove:
|
||||
ArgumentNullException.ThrowIfNull(e.OldItems);
|
||||
DetachPropertyChangedHandler(e.OldItems);
|
||||
if (deferCounter <= 0)
|
||||
{
|
||||
if (e.OldItems?.Count == 1)
|
||||
{
|
||||
object? oldItem = e.OldItems[0];
|
||||
ArgumentNullException.ThrowIfNull(oldItem);
|
||||
HandleItemRemoved(e.OldStartingIndex, (T)oldItem);
|
||||
}
|
||||
else
|
||||
{
|
||||
HandleSourceChanged();
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
case NotifyCollectionChangedAction.Move:
|
||||
case NotifyCollectionChangedAction.Replace:
|
||||
case NotifyCollectionChangedAction.Reset:
|
||||
if (deferCounter <= 0)
|
||||
{
|
||||
HandleSourceChanged();
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private bool HandleItemAdded(int newStartingIndex, T newItem, int? viewIndex = null)
|
||||
{
|
||||
if (filter is not null && !filter(newItem))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
int newViewIndex = view.Count;
|
||||
|
||||
if (sortDescriptions.Count > 0)
|
||||
{
|
||||
sortProperties.Clear();
|
||||
newViewIndex = view.BinarySearch(newItem, this);
|
||||
if (newViewIndex < 0)
|
||||
{
|
||||
newViewIndex = ~newViewIndex;
|
||||
}
|
||||
}
|
||||
else if (filter is not null)
|
||||
{
|
||||
if (source is null)
|
||||
{
|
||||
HandleSourceChanged();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (newStartingIndex == 0 || view.Count == 0)
|
||||
{
|
||||
newViewIndex = 0;
|
||||
}
|
||||
else if (newStartingIndex == source.Count - 1)
|
||||
{
|
||||
newViewIndex = view.Count;
|
||||
}
|
||||
else if (viewIndex.HasValue)
|
||||
{
|
||||
newViewIndex = viewIndex.Value;
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0, j = 0; i < source.Count; i++)
|
||||
{
|
||||
if (i == newStartingIndex)
|
||||
{
|
||||
newViewIndex = j;
|
||||
break;
|
||||
}
|
||||
|
||||
if (Equals(view[j], source[i]))
|
||||
{
|
||||
j++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
view.Insert(newViewIndex, newItem);
|
||||
if (newViewIndex <= CurrentPosition)
|
||||
{
|
||||
CurrentPosition++;
|
||||
}
|
||||
|
||||
OnVectorChanged(new VectorChangedEventArgs(CollectionChange.ItemInserted, newViewIndex, newItem));
|
||||
return true;
|
||||
}
|
||||
|
||||
private void HandleItemRemoved(int oldStartingIndex, T oldItem)
|
||||
{
|
||||
if (filter is not null && !filter(oldItem))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (oldStartingIndex < 0 || oldStartingIndex >= view.Count || !Equals(view[oldStartingIndex], oldItem))
|
||||
{
|
||||
oldStartingIndex = view.IndexOf(oldItem);
|
||||
}
|
||||
|
||||
if (oldStartingIndex < 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
RemoveFromView(oldStartingIndex, oldItem);
|
||||
}
|
||||
|
||||
private void RemoveFromView(int itemIndex, T item)
|
||||
{
|
||||
view.RemoveAt(itemIndex);
|
||||
if (itemIndex <= CurrentPosition)
|
||||
{
|
||||
CurrentPosition--;
|
||||
}
|
||||
|
||||
OnVectorChanged(new VectorChangedEventArgs(CollectionChange.ItemRemoved, itemIndex, item));
|
||||
}
|
||||
|
||||
private void SortDescriptionsCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
if (deferCounter > 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
HandleSortChanged();
|
||||
}
|
||||
|
||||
private bool MoveCurrentToIndex(int i)
|
||||
{
|
||||
if (i < -1 || i >= view.Count)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (i == CurrentPosition)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
CurrentChangingEventArgs e = new();
|
||||
OnCurrentChanging(e);
|
||||
if (e.Cancel)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
CurrentPosition = i;
|
||||
OnCurrentChanged(default!);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void OnCurrentChanging(CurrentChangingEventArgs e)
|
||||
{
|
||||
if (deferCounter > 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
CurrentChanging?.Invoke(this, e);
|
||||
}
|
||||
|
||||
private void OnCurrentChanged(object e)
|
||||
{
|
||||
if (deferCounter > 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
CurrentChanged?.Invoke(this, e);
|
||||
OnPropertyChanged(nameof(CurrentItem));
|
||||
}
|
||||
|
||||
private void OnVectorChanged(IVectorChangedEventArgs e)
|
||||
{
|
||||
if (deferCounter > 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
VectorChanged?.Invoke(this, e);
|
||||
OnPropertyChanged(nameof(Count));
|
||||
}
|
||||
|
||||
internal sealed class NotificationDeferrer : IDisposable
|
||||
{
|
||||
private readonly AdvancedCollectionView<T> advancedCollectionView;
|
||||
private readonly T? currentItem;
|
||||
|
||||
public NotificationDeferrer(AdvancedCollectionView<T> acvs)
|
||||
{
|
||||
advancedCollectionView = acvs;
|
||||
currentItem = advancedCollectionView.CurrentItem;
|
||||
advancedCollectionView.deferCounter++;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
advancedCollectionView.MoveCurrentTo(currentItem);
|
||||
advancedCollectionView.deferCounter--;
|
||||
advancedCollectionView.Refresh();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,105 +0,0 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using CommunityToolkit.WinUI.Collections;
|
||||
using Microsoft.UI.Xaml.Data;
|
||||
using System.Collections;
|
||||
|
||||
namespace Snap.Hutao.Control.Collection.AdvancedCollectionView;
|
||||
|
||||
internal interface IAdvancedCollectionView<T> : ICollectionView, IEnumerable
|
||||
where T : class
|
||||
{
|
||||
bool CanFilter { get; }
|
||||
|
||||
bool CanSort { get; }
|
||||
|
||||
object? ICollectionView.CurrentItem
|
||||
{
|
||||
get => CurrentItem;
|
||||
}
|
||||
|
||||
new T? CurrentItem { get; }
|
||||
|
||||
Predicate<T>? Filter { get; set; }
|
||||
|
||||
IList<SortDescription> SortDescriptions { get; }
|
||||
|
||||
IEnumerable<T> SourceCollection { get; }
|
||||
|
||||
object IList<object>.this[int index]
|
||||
{
|
||||
get => this[index];
|
||||
set => this[index] = (T)value;
|
||||
}
|
||||
|
||||
new T this[int index] { get; set; }
|
||||
|
||||
void ICollection<object>.Add(object item)
|
||||
{
|
||||
Add((T)item);
|
||||
}
|
||||
|
||||
void Add(T item);
|
||||
|
||||
void ClearObservedFilterProperties();
|
||||
|
||||
bool ICollection<object>.Contains(object item)
|
||||
{
|
||||
return Contains((T)item);
|
||||
}
|
||||
|
||||
bool Contains(T item);
|
||||
|
||||
void ICollection<object>.CopyTo(object[] array, int arrayIndex)
|
||||
{
|
||||
CopyTo((T[])array, arrayIndex);
|
||||
}
|
||||
|
||||
void CopyTo(T[] array, int arrayIndex);
|
||||
|
||||
IDisposable DeferRefresh();
|
||||
|
||||
IEnumerator<object> IEnumerable<object>.GetEnumerator()
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
|
||||
new IEnumerator<T> GetEnumerator();
|
||||
|
||||
int IList<object>.IndexOf(object item)
|
||||
{
|
||||
return IndexOf((T)item);
|
||||
}
|
||||
|
||||
int IndexOf(T item);
|
||||
|
||||
void IList<object>.Insert(int index, object item)
|
||||
{
|
||||
Insert(index, (T)item);
|
||||
}
|
||||
|
||||
void Insert(int index, T item);
|
||||
|
||||
bool ICollectionView.MoveCurrentTo(object item)
|
||||
{
|
||||
return MoveCurrentTo((T)item);
|
||||
}
|
||||
|
||||
bool MoveCurrentTo(T item);
|
||||
|
||||
void ObserveFilterProperty(string propertyName);
|
||||
|
||||
void Refresh();
|
||||
|
||||
void RefreshFilter();
|
||||
|
||||
void RefreshSorting();
|
||||
|
||||
bool ICollection<object>.Remove(object item)
|
||||
{
|
||||
return Remove((T)item);
|
||||
}
|
||||
|
||||
bool Remove(T item);
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Windows.Foundation.Collections;
|
||||
|
||||
namespace Snap.Hutao.Control.Collection.AdvancedCollectionView;
|
||||
|
||||
internal sealed class VectorChangedEventArgs : IVectorChangedEventArgs
|
||||
{
|
||||
public VectorChangedEventArgs(CollectionChange cc, int index = -1, object item = null!)
|
||||
{
|
||||
CollectionChange = cc;
|
||||
Index = (uint)index;
|
||||
}
|
||||
|
||||
public CollectionChange CollectionChange { get; }
|
||||
|
||||
public uint Index { get; }
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using CommunityToolkit.WinUI;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
|
||||
namespace Snap.Hutao.Control.Helper;
|
||||
|
||||
[SuppressMessage("", "SH001")]
|
||||
[DependencyProperty("PaneCornerRadius", typeof(CornerRadius), default, nameof(OnPaneCornerRadiusChanged), IsAttached = true, AttachedType = typeof(NavigationView))]
|
||||
public sealed partial class NavigationViewHelper
|
||||
{
|
||||
private static void OnPaneCornerRadiusChanged(DependencyObject dp, DependencyPropertyChangedEventArgs args)
|
||||
{
|
||||
NavigationView navigationView = (NavigationView)dp;
|
||||
CornerRadius newValue = (CornerRadius)args.NewValue;
|
||||
|
||||
if (navigationView.IsLoaded)
|
||||
{
|
||||
SetNavigationViewPaneCornerRadius(navigationView, newValue);
|
||||
return;
|
||||
}
|
||||
|
||||
navigationView.Loaded += (s, e) =>
|
||||
{
|
||||
NavigationView loadedNavigationView = (NavigationView)s;
|
||||
SetNavigationViewPaneCornerRadius(loadedNavigationView, newValue);
|
||||
};
|
||||
}
|
||||
|
||||
private static void SetNavigationViewPaneCornerRadius(NavigationView navigationView, CornerRadius value)
|
||||
{
|
||||
if (navigationView.FindDescendant("RootSplitView") is SplitView splitView)
|
||||
{
|
||||
splitView.CornerRadius = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@ using Microsoft.UI.Xaml.Controls;
|
||||
namespace Snap.Hutao.Control.Helper;
|
||||
|
||||
[SuppressMessage("", "SH001")]
|
||||
[DependencyProperty("LeftPanelMaxWidth", typeof(double), IsAttached = true, AttachedType = typeof(ScrollViewer))]
|
||||
[DependencyProperty("RightPanel", typeof(UIElement), IsAttached = true, AttachedType = typeof(ScrollViewer))]
|
||||
public sealed partial class ScrollViewerHelper
|
||||
{
|
||||
|
||||
@@ -20,4 +20,4 @@ public sealed partial class SettingsExpanderHelper
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Control;
|
||||
|
||||
internal interface IXamlElementAccessor;
|
||||
@@ -168,7 +168,6 @@ internal abstract partial class CompositionImage : Microsoft.UI.Xaml.Controls.Co
|
||||
if (surface.DecodedPhysicalSize.Size() <= 0D)
|
||||
{
|
||||
await Task.WhenAny(surfaceLoadTaskCompletionSource.Task, Task.Delay(5000, token)).ConfigureAwait(true);
|
||||
await Task.Delay(50, token).ConfigureAwait(true);
|
||||
}
|
||||
|
||||
LoadImageSurfaceCompleted(surface);
|
||||
@@ -193,7 +192,7 @@ internal abstract partial class CompositionImage : Microsoft.UI.Xaml.Controls.Co
|
||||
{
|
||||
await AnimationBuilder
|
||||
.Create()
|
||||
.Opacity(from: 0D, to: 1D, duration: ControlAnimationConstants.ImageScaleFadeIn)
|
||||
.Opacity(from: 0D, to: 1D, duration: ControlAnimationConstants.ImageFadeIn)
|
||||
.StartAsync(this, token)
|
||||
.ConfigureAwait(true);
|
||||
}
|
||||
@@ -214,7 +213,7 @@ internal abstract partial class CompositionImage : Microsoft.UI.Xaml.Controls.Co
|
||||
{
|
||||
await AnimationBuilder
|
||||
.Create()
|
||||
.Opacity(from: 1D, to: 0D, duration: ControlAnimationConstants.ImageScaleFadeOut)
|
||||
.Opacity(from: 1D, to: 0D, duration: ControlAnimationConstants.ImageFadeOut)
|
||||
.StartAsync(this, token)
|
||||
.ConfigureAwait(true);
|
||||
}
|
||||
|
||||
@@ -33,16 +33,6 @@ internal struct Bgra32
|
||||
/// </summary>
|
||||
public byte A;
|
||||
|
||||
public Bgra32(byte b, byte g, byte r, byte a)
|
||||
{
|
||||
B = b;
|
||||
G = g;
|
||||
R = r;
|
||||
A = a;
|
||||
}
|
||||
|
||||
public readonly double Luminance { get => ((0.299 * R) + (0.587 * G) + (0.114 * B)) / 255; }
|
||||
|
||||
/// <summary>
|
||||
/// 从 Color 转换
|
||||
/// </summary>
|
||||
@@ -54,11 +44,4 @@ internal struct Bgra32
|
||||
*(uint*)&bgra8 = BinaryPrimitives.ReverseEndianness(*(uint*)&color);
|
||||
return bgra8;
|
||||
}
|
||||
|
||||
public static unsafe implicit operator Color(Bgra32 bgra8)
|
||||
{
|
||||
Unsafe.SkipInit(out Color color);
|
||||
*(uint*)&color = BinaryPrimitives.ReverseEndianness(*(uint*)&bgra8);
|
||||
return color;
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,7 @@ namespace Snap.Hutao.Control.Media;
|
||||
/// <summary>
|
||||
/// Defines a color in Hue/Saturation/Lightness (HSL) space.
|
||||
/// </summary>
|
||||
internal struct Hsla32
|
||||
internal struct Hsl32
|
||||
{
|
||||
/// <summary>
|
||||
/// The Hue in 0..360 range.
|
||||
@@ -46,14 +46,13 @@ internal struct Rgba32
|
||||
/// <summary>
|
||||
/// 使用 RGBA 代码初始化新的结构
|
||||
/// </summary>
|
||||
/// <param name="xrgbaCode">RGBA 代码</param>
|
||||
public unsafe Rgba32(uint xrgbaCode)
|
||||
/// <param name="code">RGBA 代码</param>
|
||||
public unsafe Rgba32(uint code)
|
||||
{
|
||||
// uint layout: 0xRRGGBBAA is AABBGGRR
|
||||
// AABBGGRR -> RRGGBBAA
|
||||
// RRGGBBAA -> AABBGGRR
|
||||
fixed (Rgba32* pSelf = &this)
|
||||
{
|
||||
*(uint*)pSelf = BinaryPrimitives.ReverseEndianness(xrgbaCode);
|
||||
*(uint*)pSelf = BinaryPrimitives.ReverseEndianness(code);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,14 +66,14 @@ internal struct Rgba32
|
||||
|
||||
public static unsafe implicit operator Color(Rgba32 hexColor)
|
||||
{
|
||||
// Goal : Rgba32:RRGGBBAA(0xAABBGGRR) -> Color: AARRGGBB(0xBBGGRRAA)
|
||||
// Step1: Rgba32:RRGGBBAA(0xAABBGGRR) -> UInt32:AA000000(0x000000AA)
|
||||
uint a = ((*(uint*)&hexColor) >> 24) & 0x000000FF;
|
||||
// AABBGGRR -> BBGGRRAA
|
||||
// AABBGGRR -> 000000AA
|
||||
uint a = (*(uint*)&hexColor) >> 24;
|
||||
|
||||
// Step2: Rgba32:RRGGBBAA(0xAABBGGRR) -> UInt32:00RRGGBB(0xRRGGBB00)
|
||||
uint rgb = ((*(uint*)&hexColor) << 8) & 0xFFFFFF00;
|
||||
// AABBGGRR -> BBGGRR00
|
||||
uint rgb = (*(uint*)&hexColor) << 8;
|
||||
|
||||
// Step2: UInt32:00RRGGBB(0xRRGGBB00) + UInt32:AA000000(0x000000AA) -> UInt32:AARRGGBB(0xRRGGBBAA)
|
||||
// BBGGRR00 + 000000AA
|
||||
uint rgba = rgb + a;
|
||||
|
||||
return *(Color*)&rgba;
|
||||
@@ -85,7 +84,7 @@ internal struct Rgba32
|
||||
/// </summary>
|
||||
/// <param name="hsl">HSL 颜色</param>
|
||||
/// <returns>RGBA8颜色</returns>
|
||||
public static Rgba32 FromHsl(Hsla32 hsl)
|
||||
public static Rgba32 FromHsl(Hsl32 hsl)
|
||||
{
|
||||
double chroma = (1 - Math.Abs((2 * hsl.L) - 1)) * hsl.S;
|
||||
double h1 = hsl.H / 60;
|
||||
@@ -142,7 +141,7 @@ internal struct Rgba32
|
||||
/// 转换到 HSL 颜色
|
||||
/// </summary>
|
||||
/// <returns>HSL 颜色</returns>
|
||||
public readonly Hsla32 ToHsl()
|
||||
public readonly Hsl32 ToHsl()
|
||||
{
|
||||
const double toDouble = 1.0 / 255;
|
||||
double r = toDouble * R;
|
||||
@@ -175,7 +174,7 @@ internal struct Rgba32
|
||||
double lightness = 0.5 * (max + min);
|
||||
double saturation = chroma == 0 ? 0 : chroma / (1 - Math.Abs((2 * lightness) - 1));
|
||||
|
||||
Hsla32 ret;
|
||||
Hsl32 ret;
|
||||
ret.H = 60 * h1;
|
||||
ret.S = saturation;
|
||||
ret.L = lightness;
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Win32.System.WinRT;
|
||||
using Windows.Foundation;
|
||||
using Windows.Graphics.Imaging;
|
||||
using Windows.Win32;
|
||||
using Windows.Win32.System.WinRT;
|
||||
using WinRT;
|
||||
|
||||
namespace Snap.Hutao.Control.Media;
|
||||
@@ -25,7 +26,8 @@ internal static class SoftwareBitmapExtension
|
||||
{
|
||||
using (IMemoryBufferReference reference = buffer.CreateReference())
|
||||
{
|
||||
reference.As<IMemoryBufferByteAccess>().GetBuffer(out Span<Bgra32> bytes);
|
||||
reference.As<IMemoryBufferByteAccess>().GetBuffer(out byte* data, out uint length);
|
||||
Span<Bgra32> bytes = new(data, unchecked((int)length / sizeof(Bgra32)));
|
||||
foreach (ref Bgra32 pixel in bytes)
|
||||
{
|
||||
byte baseAlpha = pixel.A;
|
||||
@@ -38,43 +40,4 @@ internal static class SoftwareBitmapExtension
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static unsafe double Luminance(this SoftwareBitmap softwareBitmap)
|
||||
{
|
||||
using (BitmapBuffer buffer = softwareBitmap.LockBuffer(BitmapBufferAccessMode.Read))
|
||||
{
|
||||
using (IMemoryBufferReference reference = buffer.CreateReference())
|
||||
{
|
||||
reference.As<IMemoryBufferByteAccess>().GetBuffer(out Span<Bgra32> bytes);
|
||||
double sum = 0;
|
||||
foreach (ref readonly Bgra32 pixel in bytes)
|
||||
{
|
||||
sum += pixel.Luminance;
|
||||
}
|
||||
|
||||
return sum / bytes.Length;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static unsafe Bgra32 GetAccentColor(this SoftwareBitmap softwareBitmap)
|
||||
{
|
||||
using (BitmapBuffer buffer = softwareBitmap.LockBuffer(BitmapBufferAccessMode.Read))
|
||||
{
|
||||
using (IMemoryBufferReference reference = buffer.CreateReference())
|
||||
{
|
||||
reference.As<IMemoryBufferByteAccess>().GetBuffer(out Span<Bgra32> bytes);
|
||||
double b = 0, g = 0, r = 0, a = 0;
|
||||
foreach (ref readonly Bgra32 pixel in bytes)
|
||||
{
|
||||
b += pixel.B;
|
||||
g += pixel.G;
|
||||
r += pixel.R;
|
||||
a += pixel.A;
|
||||
}
|
||||
|
||||
return new((byte)(b / bytes.Length), (byte)(g / bytes.Length), (byte)(r / bytes.Length), (byte)(a / bytes.Length));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,89 +0,0 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.UI.Xaml;
|
||||
using System.Data;
|
||||
using System.Runtime.InteropServices;
|
||||
using Windows.Foundation;
|
||||
|
||||
namespace Snap.Hutao.Control.Panel;
|
||||
|
||||
[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;
|
||||
|
||||
List<UIElement> elements = [.. Children.Where(element => element.Visibility == Visibility.Visible)];
|
||||
visibleItemsCount = elements.Count;
|
||||
|
||||
foreach (ref readonly UIElement child in CollectionsMarshal.AsSpan(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 is not 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)
|
||||
{
|
||||
(d as EqualPanel)?.InvalidateMeasure();
|
||||
}
|
||||
|
||||
private void OnHorizontalAlignmentChanged(DependencyObject sender, DependencyProperty dp)
|
||||
{
|
||||
InvalidateMeasure();
|
||||
}
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.UI.Xaml;
|
||||
using Windows.Foundation;
|
||||
|
||||
namespace Snap.Hutao.Control.Panel;
|
||||
|
||||
[DependencyProperty("MinItemWidth", typeof(double))]
|
||||
[DependencyProperty("Spacing", typeof(double))]
|
||||
internal partial class HorizontalEqualPanel : Microsoft.UI.Xaml.Controls.Panel
|
||||
{
|
||||
public HorizontalEqualPanel()
|
||||
{
|
||||
Loaded += OnLoaded;
|
||||
SizeChanged += OnSizeChanged;
|
||||
}
|
||||
|
||||
protected override Size MeasureOverride(Size availableSize)
|
||||
{
|
||||
foreach (UIElement child in Children)
|
||||
{
|
||||
// ScrollViewer will always return an Infinity Size, we should use ActualWidth for this situation.
|
||||
double availableWidth = double.IsInfinity(availableSize.Width) ? ActualWidth : availableSize.Width;
|
||||
double childAvailableWidth = (availableWidth + Spacing) / Children.Count;
|
||||
double childMaxAvailableWidth = Math.Max(MinItemWidth, childAvailableWidth);
|
||||
child.Measure(new(childMaxAvailableWidth - Spacing, ActualHeight));
|
||||
}
|
||||
|
||||
return base.MeasureOverride(availableSize);
|
||||
}
|
||||
|
||||
protected override Size ArrangeOverride(Size finalSize)
|
||||
{
|
||||
int itemCount = Children.Count;
|
||||
double availableWidthPerItem = (finalSize.Width - (Spacing * (itemCount - 1))) / itemCount;
|
||||
double actualItemWidth = Math.Max(MinItemWidth, availableWidthPerItem);
|
||||
|
||||
double offset = 0;
|
||||
foreach (UIElement child in Children)
|
||||
{
|
||||
child.Arrange(new Rect(offset, 0, actualItemWidth, finalSize.Height));
|
||||
offset += actualItemWidth + Spacing;
|
||||
}
|
||||
|
||||
return finalSize;
|
||||
}
|
||||
|
||||
private void OnLoaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
MinWidth = (MinItemWidth * Children.Count) + (Spacing * (Children.Count - 1));
|
||||
}
|
||||
|
||||
private void OnSizeChanged(object sender, SizeChangedEventArgs e)
|
||||
{
|
||||
InvalidateMeasure();
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,6 @@
|
||||
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"
|
||||
Style="{StaticResource DefaultSegmentedStyle}"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<cwc:SegmentedItem
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
using CommunityToolkit.WinUI.Controls;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Snap.Hutao.Core.Setting;
|
||||
using System.Collections.Frozen;
|
||||
|
||||
namespace Snap.Hutao.Control.Panel;
|
||||
|
||||
@@ -20,11 +19,11 @@ internal sealed partial class PanelSelector : Segmented
|
||||
public const string List = nameof(List);
|
||||
public const string Grid = nameof(Grid);
|
||||
|
||||
private static readonly FrozenDictionary<int, string> IndexTypeMap = FrozenDictionary.ToFrozenDictionary(
|
||||
[
|
||||
KeyValuePair.Create(0, List),
|
||||
KeyValuePair.Create(1, Grid),
|
||||
]);
|
||||
private static readonly Dictionary<int, string> IndexTypeMap = new()
|
||||
{
|
||||
[0] = List,
|
||||
[1] = Grid,
|
||||
};
|
||||
|
||||
private readonly RoutedEventHandler loadedEventHandler;
|
||||
private readonly RoutedEventHandler unloadedEventHandler;
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using CommunityToolkit.WinUI.Controls;
|
||||
|
||||
namespace Snap.Hutao.Control.Panel;
|
||||
|
||||
[DependencyProperty("MinItemWidth", typeof(double))]
|
||||
internal sealed partial class UniformPanel : UniformGrid
|
||||
{
|
||||
public UniformPanel()
|
||||
{
|
||||
Columns = 1;
|
||||
SizeChanged += OnSizeChanged;
|
||||
}
|
||||
|
||||
private void OnSizeChanged(object sender, Microsoft.UI.Xaml.SizeChangedEventArgs e)
|
||||
{
|
||||
Columns = (int)((e.NewSize.Width + ColumnSpacing) / (MinItemWidth + ColumnSpacing));
|
||||
}
|
||||
}
|
||||
@@ -7,9 +7,7 @@ using Microsoft.UI.Xaml.Documents;
|
||||
using Microsoft.UI.Xaml.Media;
|
||||
using Snap.Hutao.Control.Extension;
|
||||
using Snap.Hutao.Control.Media;
|
||||
using Snap.Hutao.Control.Text.Syntax.MiHoYo;
|
||||
using Snap.Hutao.Control.Theme;
|
||||
using Snap.Hutao.Metadata;
|
||||
using Windows.Foundation;
|
||||
using Windows.UI;
|
||||
|
||||
@@ -17,12 +15,20 @@ namespace Snap.Hutao.Control.Text;
|
||||
|
||||
/// <summary>
|
||||
/// 专用于呈现描述文本的文本块
|
||||
/// Some part of this file came from:
|
||||
/// https://github.com/xunkong/desktop/tree/main/src/Desktop/Desktop/Pages/CharacterInfoPage.xaml.cs
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
[DependencyProperty("Description", typeof(string), "", nameof(OnDescriptionChanged))]
|
||||
[DependencyProperty("TextStyle", typeof(Style), default(Style), nameof(OnTextStyleChanged))]
|
||||
internal sealed partial class DescriptionTextBlock : ContentControl
|
||||
{
|
||||
private static readonly int ColorTagFullLength = "<color=#FFFFFFFF></color>".Length;
|
||||
private static readonly int ColorTagLeftLength = "<color=#FFFFFFFF>".Length;
|
||||
|
||||
private static readonly int ItalicTagFullLength = "<i></i>".Length;
|
||||
private static readonly int ItalicTagLeftLength = "<i>".Length;
|
||||
|
||||
private readonly TypedEventHandler<FrameworkElement, object> actualThemeChangedEventHandler;
|
||||
|
||||
/// <summary>
|
||||
@@ -45,15 +51,9 @@ internal sealed partial class DescriptionTextBlock : ContentControl
|
||||
private static void OnDescriptionChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
TextBlock textBlock = (TextBlock)((DescriptionTextBlock)d).Content;
|
||||
ReadOnlySpan<char> description = (string)e.NewValue;
|
||||
|
||||
try
|
||||
{
|
||||
UpdateDescription(textBlock, MiHoYoSyntaxTree.Parse(SpecialNameHandler.Handle((string)e.NewValue)));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_ = ex;
|
||||
}
|
||||
UpdateDescription(textBlock, description);
|
||||
}
|
||||
|
||||
private static void OnTextStyleChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
@@ -62,106 +62,101 @@ internal sealed partial class DescriptionTextBlock : ContentControl
|
||||
textBlock.Style = (Style)e.NewValue;
|
||||
}
|
||||
|
||||
private static void UpdateDescription(TextBlock textBlock, MiHoYoSyntaxTree syntaxTree)
|
||||
private static void UpdateDescription(TextBlock textBlock, in ReadOnlySpan<char> description)
|
||||
{
|
||||
textBlock.Inlines.Clear();
|
||||
AppendNode(textBlock, textBlock.Inlines, syntaxTree.Root);
|
||||
}
|
||||
|
||||
private static void AppendNode(TextBlock textBlock, InlineCollection inlines, MiHoYoSyntaxNode node)
|
||||
{
|
||||
switch (node.Kind)
|
||||
int last = 0;
|
||||
for (int i = 0; i < description.Length;)
|
||||
{
|
||||
case MiHoYoSyntaxKind.Root:
|
||||
foreach (MiHoYoSyntaxNode child in ((MiHoYoRootSyntax)node).Children)
|
||||
{
|
||||
AppendNode(textBlock, inlines, child);
|
||||
}
|
||||
// newline
|
||||
if (description[i..].StartsWith(@"\n"))
|
||||
{
|
||||
AppendText(textBlock, description[last..i]);
|
||||
AppendLineBreak(textBlock);
|
||||
i += 2;
|
||||
last = i;
|
||||
}
|
||||
|
||||
break;
|
||||
case MiHoYoSyntaxKind.PlainText:
|
||||
AppendPlainText(textBlock, inlines, (MiHoYoPlainTextSyntax)node);
|
||||
break;
|
||||
case MiHoYoSyntaxKind.ColorText:
|
||||
AppendColorText(textBlock, inlines, (MiHoYoColorTextSyntax)node);
|
||||
break;
|
||||
case MiHoYoSyntaxKind.ItalicText:
|
||||
AppendItalicText(textBlock, inlines, (MiHoYoItalicTextSyntax)node);
|
||||
break;
|
||||
// color tag
|
||||
else if (description[i..].StartsWith("<c"))
|
||||
{
|
||||
AppendText(textBlock, description[last..i]);
|
||||
Rgba32 color = new(description.Slice(i + 8, 8).ToString());
|
||||
int length = description[(i + ColorTagLeftLength)..].IndexOf('<');
|
||||
AppendColorText(textBlock, description.Slice(i + ColorTagLeftLength, length), color);
|
||||
|
||||
i += length + ColorTagFullLength;
|
||||
last = i;
|
||||
}
|
||||
|
||||
// italic
|
||||
else if (description[i..].StartsWith("<i"))
|
||||
{
|
||||
AppendText(textBlock, description[last..i]);
|
||||
|
||||
int length = description[(i + ItalicTagLeftLength)..].IndexOf('<');
|
||||
AppendItalicText(textBlock, description.Slice(i + ItalicTagLeftLength, length));
|
||||
|
||||
i += length + ItalicTagFullLength;
|
||||
last = i;
|
||||
}
|
||||
else
|
||||
{
|
||||
i += 1;
|
||||
}
|
||||
|
||||
if (i == description.Length - 1)
|
||||
{
|
||||
AppendText(textBlock, description[last..(i + 1)]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void AppendPlainText(TextBlock textBlock, InlineCollection inlines, MiHoYoPlainTextSyntax plainText)
|
||||
private static void AppendText(TextBlock text, in ReadOnlySpan<char> slice)
|
||||
{
|
||||
// PlainText doesn't have children
|
||||
inlines.Add(new Run { Text = plainText.Span.ToString() });
|
||||
text.Inlines.Add(new Run { Text = slice.ToString() });
|
||||
}
|
||||
|
||||
private static void AppendColorText(TextBlock textBlock, InlineCollection inlines, MiHoYoColorTextSyntax colorText)
|
||||
private static void AppendColorText(TextBlock text, in ReadOnlySpan<char> slice, Rgba32 color)
|
||||
{
|
||||
Rgba32 color = new(colorText.ColorSpan.ToString());
|
||||
Color targetColor;
|
||||
if (ThemeHelper.IsDarkMode(textBlock.ActualTheme))
|
||||
if (ThemeHelper.IsDarkMode(text.ActualTheme))
|
||||
{
|
||||
targetColor = color;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Make lighter in light mode
|
||||
Hsla32 hsl = color.ToHsl();
|
||||
Hsl32 hsl = color.ToHsl();
|
||||
hsl.L *= 0.3;
|
||||
targetColor = Rgba32.FromHsl(hsl);
|
||||
}
|
||||
|
||||
if (colorText.Children.Count > 1)
|
||||
text.Inlines.Add(new Run
|
||||
{
|
||||
Span span = new()
|
||||
{
|
||||
Foreground = new SolidColorBrush(targetColor),
|
||||
};
|
||||
|
||||
foreach (MiHoYoSyntaxNode child in colorText.Children)
|
||||
{
|
||||
AppendNode(textBlock, span.Inlines, child);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
inlines.Add(new Run
|
||||
{
|
||||
Text = colorText.ContentSpan.ToString(),
|
||||
Foreground = new SolidColorBrush(targetColor),
|
||||
});
|
||||
}
|
||||
Text = slice.ToString(),
|
||||
Foreground = new SolidColorBrush(targetColor),
|
||||
});
|
||||
}
|
||||
|
||||
private static void AppendItalicText(TextBlock textBlock, InlineCollection inlines, MiHoYoItalicTextSyntax italicText)
|
||||
private static void AppendItalicText(TextBlock text, in ReadOnlySpan<char> slice)
|
||||
{
|
||||
if (italicText.Children.Count > 1)
|
||||
text.Inlines.Add(new Run
|
||||
{
|
||||
Span span = new()
|
||||
{
|
||||
FontStyle = Windows.UI.Text.FontStyle.Italic,
|
||||
};
|
||||
Text = slice.ToString(),
|
||||
FontStyle = Windows.UI.Text.FontStyle.Italic,
|
||||
});
|
||||
}
|
||||
|
||||
foreach (MiHoYoSyntaxNode child in italicText.Children)
|
||||
{
|
||||
AppendNode(textBlock, span.Inlines, child);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
inlines.Add(new Run
|
||||
{
|
||||
Text = italicText.ContentSpan.ToString(),
|
||||
FontStyle = Windows.UI.Text.FontStyle.Italic,
|
||||
});
|
||||
}
|
||||
private static void AppendLineBreak(TextBlock text)
|
||||
{
|
||||
text.Inlines.Add(new LineBreak());
|
||||
}
|
||||
|
||||
private void OnActualThemeChanged(FrameworkElement sender, object args)
|
||||
{
|
||||
// Simply re-apply texts
|
||||
UpdateDescription((TextBlock)Content, MiHoYoSyntaxTree.Parse(SpecialNameHandler.Handle(Description)));
|
||||
UpdateDescription((TextBlock)Content, Description);
|
||||
}
|
||||
}
|
||||
@@ -139,7 +139,7 @@ internal sealed partial class HtmlDescriptionTextBlock : ContentControl
|
||||
else
|
||||
{
|
||||
// Make lighter in light mode
|
||||
Hsla32 hsl = color.ToHsl();
|
||||
Hsl32 hsl = color.ToHsl();
|
||||
hsl.L *= 0.3;
|
||||
targetColor = Rgba32.FromHsl(hsl);
|
||||
}
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Control.Text.Syntax.MiHoYo;
|
||||
|
||||
internal enum MiHoYoColorKind
|
||||
{
|
||||
None,
|
||||
Rgba,
|
||||
Rgb,
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Control.Text.Syntax.MiHoYo;
|
||||
|
||||
internal sealed class MiHoYoColorTextSyntax : MiHoYoXmlElementSyntax
|
||||
{
|
||||
public MiHoYoColorTextSyntax(MiHoYoColorKind colorKind, string text, int start, int end)
|
||||
: base(MiHoYoSyntaxKind.ColorText, text, start, end)
|
||||
{
|
||||
ColorKind = colorKind;
|
||||
}
|
||||
|
||||
public MiHoYoColorTextSyntax(MiHoYoColorKind colorKind, string text, in TextPosition position)
|
||||
: base(MiHoYoSyntaxKind.ColorText, text, position)
|
||||
{
|
||||
ColorKind = colorKind;
|
||||
}
|
||||
|
||||
public MiHoYoColorKind ColorKind { get; }
|
||||
|
||||
public override TextPosition ContentPosition
|
||||
{
|
||||
get
|
||||
{
|
||||
return ColorKind switch
|
||||
{
|
||||
MiHoYoColorKind.Rgba => new(Position.Start + 17, Position.End - 8),
|
||||
MiHoYoColorKind.Rgb => new(Position.Start + 15, Position.End - 8),
|
||||
_ => throw Must.NeverHappen(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public TextPosition ColorPosition
|
||||
{
|
||||
get
|
||||
{
|
||||
return ColorKind switch
|
||||
{
|
||||
MiHoYoColorKind.Rgba => new(Position.Start + 8, Position.Start + 16),
|
||||
MiHoYoColorKind.Rgb => new(Position.Start + 8, Position.Start + 14),
|
||||
_ => throw Must.NeverHappen(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public ReadOnlySpan<char> ColorSpan { get => Text.AsSpan()[ColorPosition.Start..ColorPosition.End]; }
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Control.Text.Syntax.MiHoYo;
|
||||
|
||||
internal sealed class MiHoYoItalicTextSyntax : MiHoYoXmlElementSyntax
|
||||
{
|
||||
public MiHoYoItalicTextSyntax(string text, int start, int end)
|
||||
: base(MiHoYoSyntaxKind.ItalicText, text, start, end)
|
||||
{
|
||||
}
|
||||
|
||||
public MiHoYoItalicTextSyntax(string text, in TextPosition position)
|
||||
: base(MiHoYoSyntaxKind.ItalicText, text, position)
|
||||
{
|
||||
}
|
||||
|
||||
public override TextPosition ContentPosition { get => new(Position.Start + 3, Position.End - 4); }
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Control.Text.Syntax.MiHoYo;
|
||||
|
||||
internal sealed class MiHoYoPlainTextSyntax : MiHoYoSyntaxNode
|
||||
{
|
||||
public MiHoYoPlainTextSyntax(string text, int start, int end)
|
||||
: base(MiHoYoSyntaxKind.PlainText, text, start, end)
|
||||
{
|
||||
}
|
||||
|
||||
public MiHoYoPlainTextSyntax(string text, in TextPosition position)
|
||||
: base(MiHoYoSyntaxKind.PlainText, text, position)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Control.Text.Syntax.MiHoYo;
|
||||
|
||||
internal sealed class MiHoYoRootSyntax : MiHoYoSyntaxNode
|
||||
{
|
||||
public MiHoYoRootSyntax(string text, int start, int end)
|
||||
: base(MiHoYoSyntaxKind.Root, text, start, end)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Control.Text.Syntax.MiHoYo;
|
||||
|
||||
internal enum MiHoYoSyntaxKind
|
||||
{
|
||||
None,
|
||||
Root,
|
||||
PlainText,
|
||||
ColorText,
|
||||
ItalicText,
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Control.Text.Syntax.MiHoYo;
|
||||
|
||||
internal abstract class MiHoYoSyntaxNode : SyntaxNode<MiHoYoSyntaxNode, MiHoYoSyntaxKind>
|
||||
{
|
||||
public MiHoYoSyntaxNode(MiHoYoSyntaxKind kind, string text, int start, int end)
|
||||
: base(kind, text, start, end)
|
||||
{
|
||||
}
|
||||
|
||||
public MiHoYoSyntaxNode(MiHoYoSyntaxKind kind, string text, in TextPosition position)
|
||||
: base(kind, text, position)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -1,146 +0,0 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Control.Text.Syntax.MiHoYo;
|
||||
|
||||
internal sealed class MiHoYoSyntaxTree
|
||||
{
|
||||
public MiHoYoSyntaxNode Root { get; set; } = default!;
|
||||
|
||||
public string Text { get; set; } = default!;
|
||||
|
||||
public static MiHoYoSyntaxTree Parse(string text)
|
||||
{
|
||||
MiHoYoRootSyntax root = new(text, 0, text.Length);
|
||||
ParseComponents(text, root);
|
||||
|
||||
MiHoYoSyntaxTree tree = new()
|
||||
{
|
||||
Text = text,
|
||||
Root = root,
|
||||
};
|
||||
|
||||
return tree;
|
||||
}
|
||||
|
||||
private static void ParseComponents(string text, MiHoYoSyntaxNode syntax)
|
||||
{
|
||||
TextPosition contentPosition = syntax switch
|
||||
{
|
||||
MiHoYoXmlElementSyntax xmlSyntax => xmlSyntax.ContentPosition,
|
||||
_ => syntax.Position,
|
||||
};
|
||||
ReadOnlySpan<char> contentSpan = text.AsSpan().Slice(contentPosition.Start, contentPosition.Length);
|
||||
|
||||
int endOfProcessedAtContent = 0;
|
||||
while (true)
|
||||
{
|
||||
if (endOfProcessedAtContent >= contentSpan.Length)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
int indexOfXmlLeftOpeningAtUnprocessedContent = contentSpan[endOfProcessedAtContent..].IndexOf('<');
|
||||
|
||||
// End of content
|
||||
if (indexOfXmlLeftOpeningAtUnprocessedContent < 0)
|
||||
{
|
||||
TextPosition position = new(contentPosition.Start + endOfProcessedAtContent, contentPosition.End);
|
||||
MiHoYoPlainTextSyntax plainText = new(text, position);
|
||||
syntax.Children.Add(plainText);
|
||||
break;
|
||||
}
|
||||
|
||||
// We have plain text between xml elements
|
||||
if (indexOfXmlLeftOpeningAtUnprocessedContent > 0)
|
||||
{
|
||||
TextPosition position = new(0, indexOfXmlLeftOpeningAtUnprocessedContent);
|
||||
TextPosition positionAtContent = position.RightShift(endOfProcessedAtContent);
|
||||
TextPosition positionAtText = positionAtContent.RightShift(contentPosition.Start);
|
||||
MiHoYoPlainTextSyntax plainText = new(text, positionAtText);
|
||||
syntax.Children.Add(plainText);
|
||||
endOfProcessedAtContent = positionAtContent.End;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Peek the next character after '<'
|
||||
int indexOfXmlLeftOpeningAtContent = endOfProcessedAtContent + indexOfXmlLeftOpeningAtUnprocessedContent;
|
||||
switch (contentSpan[indexOfXmlLeftOpeningAtContent + 1])
|
||||
{
|
||||
case 'c':
|
||||
{
|
||||
int endOfXmlColorRightClosingAtUnprocessedContent = EndOfXmlClosing(contentSpan[indexOfXmlLeftOpeningAtContent..], out int endOfXmlColorLeftClosingAtUnprocessedContent);
|
||||
|
||||
MiHoYoColorKind colorKind = endOfXmlColorLeftClosingAtUnprocessedContent switch
|
||||
{
|
||||
17 => MiHoYoColorKind.Rgba,
|
||||
15 => MiHoYoColorKind.Rgb,
|
||||
_ => throw Must.NeverHappen(),
|
||||
};
|
||||
|
||||
TextPosition position = new(0, endOfXmlColorRightClosingAtUnprocessedContent);
|
||||
TextPosition positionAtContent = position.RightShift(endOfProcessedAtContent);
|
||||
TextPosition positionAtText = positionAtContent.RightShift(contentPosition.Start);
|
||||
|
||||
MiHoYoColorTextSyntax colorText = new(colorKind, text, positionAtText);
|
||||
ParseComponents(text, colorText);
|
||||
syntax.Children.Add(colorText);
|
||||
endOfProcessedAtContent = positionAtContent.End;
|
||||
break;
|
||||
}
|
||||
|
||||
case 'i':
|
||||
{
|
||||
int endOfXmlItalicRightClosingAtUnprocessedContent = EndOfXmlClosing(contentSpan[indexOfXmlLeftOpeningAtContent..], out _);
|
||||
|
||||
TextPosition position = new(0, endOfXmlItalicRightClosingAtUnprocessedContent);
|
||||
TextPosition positionAtContent = position.RightShift(endOfProcessedAtContent);
|
||||
TextPosition positionAtText = positionAtContent.RightShift(contentPosition.Start);
|
||||
|
||||
MiHoYoItalicTextSyntax italicText = new(text, positionAtText);
|
||||
ParseComponents(text, italicText);
|
||||
syntax.Children.Add(italicText);
|
||||
endOfProcessedAtContent = positionAtContent.End;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static int EndOfXmlClosing(in ReadOnlySpan<char> span, out int endOfLeftClosing)
|
||||
{
|
||||
endOfLeftClosing = 0;
|
||||
|
||||
int openingCount = 0;
|
||||
int closingCount = 0;
|
||||
|
||||
int current = 0;
|
||||
|
||||
// Considering <i>text1</i>text2<i>text3</i>
|
||||
// Considering <i>text1<span>text2</span>text3</i>
|
||||
while (true)
|
||||
{
|
||||
int leftMarkIndex = span[current..].IndexOf('<');
|
||||
if (span[current..][leftMarkIndex + 1] is '/')
|
||||
{
|
||||
closingCount++;
|
||||
}
|
||||
else
|
||||
{
|
||||
openingCount++;
|
||||
}
|
||||
|
||||
current += span[current..].IndexOf('>') + 1;
|
||||
|
||||
if (openingCount is 1 && closingCount is 0)
|
||||
{
|
||||
endOfLeftClosing = current;
|
||||
}
|
||||
|
||||
if (openingCount == closingCount)
|
||||
{
|
||||
return current;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Control.Text.Syntax.MiHoYo;
|
||||
|
||||
internal abstract class MiHoYoXmlElementSyntax : MiHoYoSyntaxNode
|
||||
{
|
||||
public MiHoYoXmlElementSyntax(MiHoYoSyntaxKind kind, string text, int start, int end)
|
||||
: base(kind, text, start, end)
|
||||
{
|
||||
}
|
||||
|
||||
public MiHoYoXmlElementSyntax(MiHoYoSyntaxKind kind, string text, in TextPosition position)
|
||||
: base(kind, text, position)
|
||||
{
|
||||
}
|
||||
|
||||
public abstract TextPosition ContentPosition { get; }
|
||||
|
||||
public ReadOnlySpan<char> ContentSpan { get => Text.AsSpan(ContentPosition.Start, ContentPosition.Length); }
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Control.Text.Syntax;
|
||||
|
||||
internal abstract class SyntaxNode<TSelf, TKind>
|
||||
where TSelf : SyntaxNode<TSelf, TKind>
|
||||
where TKind : struct, Enum
|
||||
{
|
||||
public SyntaxNode(TKind kind, string text, int start, int end)
|
||||
{
|
||||
Kind = kind;
|
||||
Text = text;
|
||||
Position = new(start, end);
|
||||
}
|
||||
|
||||
public SyntaxNode(TKind kind, string text, in TextPosition position)
|
||||
{
|
||||
Kind = kind;
|
||||
Text = text;
|
||||
Position = position;
|
||||
}
|
||||
|
||||
public TKind Kind { get; protected set; }
|
||||
|
||||
public List<TSelf> Children { get; } = [];
|
||||
|
||||
public TextPosition Position { get; protected set; }
|
||||
|
||||
public ReadOnlySpan<char> Span { get => Text.AsSpan().Slice(Position.Start, Position.Length); }
|
||||
|
||||
protected string Text { get; set; } = default!;
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Snap.Hutao.Control.Text.Syntax;
|
||||
|
||||
[DebuggerDisplay("[{Start}..{End}]")]
|
||||
internal readonly struct TextPosition
|
||||
{
|
||||
public readonly int Start;
|
||||
public readonly int End;
|
||||
|
||||
public TextPosition(int start, int end)
|
||||
{
|
||||
Start = start;
|
||||
End = end;
|
||||
}
|
||||
|
||||
public readonly int Length
|
||||
{
|
||||
get => End - Start;
|
||||
}
|
||||
|
||||
public TextPosition LeftShift(int offset)
|
||||
{
|
||||
return new(Start - offset, End - offset);
|
||||
}
|
||||
|
||||
public TextPosition RightShift(int offset)
|
||||
{
|
||||
return new(Start + offset, End + offset);
|
||||
}
|
||||
}
|
||||
@@ -4,62 +4,30 @@
|
||||
xmlns:cwm="using:CommunityToolkit.WinUI.Media">
|
||||
<ResourceDictionary.ThemeDictionaries>
|
||||
<ResourceDictionary x:Key="Light">
|
||||
<x:Double x:Key="CompatShadowThemeOpacity">0.14</x:Double>
|
||||
<cwm:AttachedCardShadow
|
||||
x:Key="CompatCardShadow"
|
||||
BlurRadius="8"
|
||||
Opacity="0.14"
|
||||
Offset="0,4,0"/>
|
||||
</ResourceDictionary>
|
||||
<ResourceDictionary x:Key="Dark">
|
||||
<x:Double x:Key="CompatShadowThemeOpacity">0.28</x:Double>
|
||||
<cwm:AttachedCardShadow
|
||||
x:Key="CompatCardShadow"
|
||||
BlurRadius="8"
|
||||
Opacity="0.28"
|
||||
Offset="0,4,0"/>
|
||||
</ResourceDictionary>
|
||||
</ResourceDictionary.ThemeDictionaries>
|
||||
|
||||
<cwm:AttachedCardShadow
|
||||
x:Key="CompatCardShadow"
|
||||
BlurRadius="8"
|
||||
Opacity="{ThemeResource CompatShadowThemeOpacity}"
|
||||
Offset="0,4,0"/>
|
||||
|
||||
<Style x:Key="BorderCardStyle" TargetType="Border">
|
||||
<Setter Property="Background" Value="{ThemeResource CardBackgroundFillColorDefaultBrush}"/>
|
||||
<Setter Property="BorderBrush" Value="{ThemeResource CardStrokeColorDefaultBrush}"/>
|
||||
<Setter Property="BorderThickness" Value="1"/>
|
||||
<Setter Property="CornerRadius" Value="{ThemeResource ControlCornerRadius}"/>
|
||||
</Style>
|
||||
|
||||
<Style
|
||||
x:Key="AcrylicBorderCardStyle"
|
||||
BasedOn="{StaticResource BorderCardStyle}"
|
||||
TargetType="Border">
|
||||
<Setter Property="BorderThickness" Value="0"/>
|
||||
<Setter Property="Background" Value="{ThemeResource SystemControlAcrylicElementBrush}"/>
|
||||
</Style>
|
||||
|
||||
<Style
|
||||
x:Key="AcrylicSecondaryBorderCardStyle"
|
||||
BasedOn="{StaticResource BorderCardStyle}"
|
||||
TargetType="Border">
|
||||
<Setter Property="BorderThickness" Value="0"/>
|
||||
<Setter Property="Background" Value="{ThemeResource SystemControlChromeMediumAcrylicElementMediumBrush}"/>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="GridCardStyle" TargetType="Grid">
|
||||
<Setter Property="Background" Value="{ThemeResource CardBackgroundFillColorDefaultBrush}"/>
|
||||
<Setter Property="BorderBrush" Value="{ThemeResource CardStrokeColorDefaultBrush}"/>
|
||||
<Setter Property="BorderThickness" Value="1"/>
|
||||
<Setter Property="CornerRadius" Value="{ThemeResource ControlCornerRadius}"/>
|
||||
</Style>
|
||||
|
||||
<Style
|
||||
x:Key="AcrylicGridCardStyle"
|
||||
BasedOn="{StaticResource GridCardStyle}"
|
||||
TargetType="Grid">
|
||||
<Setter Property="BorderThickness" Value="0"/>
|
||||
<Setter Property="Background" Value="{ThemeResource SystemControlAcrylicElementBrush}"/>
|
||||
</Style>
|
||||
|
||||
<Style
|
||||
x:Key="AcrylicSecondaryGridCardStyle"
|
||||
BasedOn="{StaticResource GridCardStyle}"
|
||||
TargetType="Grid">
|
||||
<Setter Property="BorderThickness" Value="0"/>
|
||||
<Setter Property="Background" Value="{ThemeResource SystemControlChromeMediumAcrylicElementMediumBrush}"/>
|
||||
</Style>
|
||||
</ResourceDictionary>
|
||||
</ResourceDictionary>
|
||||
|
||||
@@ -2,53 +2,6 @@
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:shch="using:Snap.Hutao.Control.Helper">
|
||||
<ResourceDictionary.ThemeDictionaries>
|
||||
<ResourceDictionary x:Key="Light">
|
||||
<AcrylicBrush
|
||||
x:Key="InfoBarErrorSeverityBackgroundBrush"
|
||||
FallbackColor="#FDE7E9"
|
||||
TintColor="#FDE7E9"
|
||||
TintOpacity="0.6"/>
|
||||
<AcrylicBrush
|
||||
x:Key="InfoBarWarningSeverityBackgroundBrush"
|
||||
FallbackColor="#FFF4CE"
|
||||
TintColor="#FFF4CE"
|
||||
TintOpacity="0.6"/>
|
||||
<AcrylicBrush
|
||||
x:Key="InfoBarSuccessSeverityBackgroundBrush"
|
||||
FallbackColor="#DFF6DD"
|
||||
TintColor="#DFF6DD"
|
||||
TintOpacity="0.6"/>
|
||||
<AcrylicBrush
|
||||
x:Key="InfoBarInformationalSeverityBackgroundBrush"
|
||||
FallbackColor="#80F6F6F6"
|
||||
TintColor="#80F6F6F6"
|
||||
TintOpacity="0.6"/>
|
||||
</ResourceDictionary>
|
||||
<ResourceDictionary x:Key="Dark">
|
||||
<AcrylicBrush
|
||||
x:Key="InfoBarErrorSeverityBackgroundBrush"
|
||||
FallbackColor="#442726"
|
||||
TintColor="#442726"
|
||||
TintOpacity="0.6"/>
|
||||
<AcrylicBrush
|
||||
x:Key="InfoBarWarningSeverityBackgroundBrush"
|
||||
FallbackColor="#433519"
|
||||
TintColor="#433519"
|
||||
TintOpacity="0.6"/>
|
||||
<AcrylicBrush
|
||||
x:Key="InfoBarSuccessSeverityBackgroundBrush"
|
||||
FallbackColor="#393D1B"
|
||||
TintColor="#393D1B"
|
||||
TintOpacity="0.6"/>
|
||||
<AcrylicBrush
|
||||
x:Key="InfoBarInformationalSeverityBackgroundBrush"
|
||||
FallbackColor="#34424d"
|
||||
TintColor="#34424d"
|
||||
TintOpacity="0.6"/>
|
||||
</ResourceDictionary>
|
||||
</ResourceDictionary.ThemeDictionaries>
|
||||
|
||||
<Thickness x:Key="InfoBarIconMargin">19,16,19,16</Thickness>
|
||||
<Thickness x:Key="InfoBarContentRootPadding">0,0,0,0</Thickness>
|
||||
<x:Double x:Key="InfoBarIconFontSize">20</x:Double>
|
||||
|
||||
@@ -8,9 +8,6 @@
|
||||
<ItemsPanelTemplate x:Key="WrapPanelSpacing0Template">
|
||||
<cwcont:WrapPanel/>
|
||||
</ItemsPanelTemplate>
|
||||
<ItemsPanelTemplate x:Key="WrapPanelSpacing2Template">
|
||||
<cwcont:WrapPanel HorizontalSpacing="2" VerticalSpacing="2"/>
|
||||
</ItemsPanelTemplate>
|
||||
<ItemsPanelTemplate x:Key="WrapPanelSpacing4Template">
|
||||
<cwcont:WrapPanel HorizontalSpacing="4" VerticalSpacing="4"/>
|
||||
</ItemsPanelTemplate>
|
||||
@@ -20,9 +17,6 @@
|
||||
<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>
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Win32;
|
||||
using Windows.UI;
|
||||
|
||||
namespace Snap.Hutao.Control.Theme;
|
||||
|
||||
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);
|
||||
}
|
||||
@@ -9,8 +9,6 @@
|
||||
<x:Double x:Key="ContentDialogMinHeight">64</x:Double>
|
||||
<x:Double x:Key="LargeAppBarButtonWidth">100</x:Double>
|
||||
|
||||
<x:Double x:Key="AppBarThemeCompactActualHeight">50</x:Double>
|
||||
|
||||
<!-- ProgressBar -->
|
||||
<x:Double x:Key="LargeBackgroundProgressBarOpacity">0.2</x:Double>
|
||||
</ResourceDictionary>
|
||||
|
||||
@@ -1,758 +1,5 @@
|
||||
<ResourceDictionary
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:cw="using:CommunityToolkit.WinUI">
|
||||
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||
<x:Double x:Key="PivotHeaderItemFontSize">16</x:Double>
|
||||
<Thickness x:Key="PivotHeaderItemMargin">16,0,0,0</Thickness>
|
||||
<Thickness x:Key="PivotItemMargin">0</Thickness>
|
||||
|
||||
<Style x:Key="CardPivotStyle" TargetType="Pivot">
|
||||
<Setter Property="Margin" Value="0"/>
|
||||
<Setter Property="Padding" Value="0"/>
|
||||
<Setter Property="Background" Value="{ThemeResource PivotBackground}"/>
|
||||
<Setter Property="IsTabStop" Value="False"/>
|
||||
<Setter Property="ItemsPanel">
|
||||
<Setter.Value>
|
||||
<ItemsPanelTemplate>
|
||||
<Grid/>
|
||||
</ItemsPanelTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="Pivot">
|
||||
<Grid
|
||||
x:Name="RootElement"
|
||||
HorizontalAlignment="{TemplateBinding HorizontalAlignment}"
|
||||
VerticalAlignment="{TemplateBinding VerticalAlignment}"
|
||||
Background="{TemplateBinding Background}">
|
||||
<Grid.Resources>
|
||||
<Style x:Key="BaseContentControlStyle" TargetType="ContentControl">
|
||||
<Setter Property="FontFamily" Value="XamlAutoFontFamily"/>
|
||||
<Setter Property="FontWeight" Value="SemiBold"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="ContentControl">
|
||||
<ContentPresenter
|
||||
Margin="{TemplateBinding Padding}"
|
||||
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
|
||||
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
|
||||
Content="{TemplateBinding Content}"
|
||||
ContentTemplate="{TemplateBinding ContentTemplate}"
|
||||
ContentTransitions="{TemplateBinding ContentTransitions}"
|
||||
OpticalMarginAlignment="TrimSideBearings"/>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
<Style
|
||||
x:Key="TitleContentControlStyle"
|
||||
BasedOn="{StaticResource BaseContentControlStyle}"
|
||||
TargetType="ContentControl">
|
||||
<Setter Property="FontFamily" Value="{ThemeResource PivotTitleFontFamily}"/>
|
||||
<Setter Property="FontWeight" Value="{ThemeResource PivotTitleThemeFontWeight}"/>
|
||||
<Setter Property="FontSize" Value="{ThemeResource PivotTitleFontSize}"/>
|
||||
</Style>
|
||||
</Grid.Resources>
|
||||
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
<ContentControl
|
||||
x:Name="TitleContentControl"
|
||||
Grid.Row="0"
|
||||
Margin="{StaticResource PivotPortraitThemePadding}"
|
||||
Content="{TemplateBinding Title}"
|
||||
ContentTemplate="{TemplateBinding TitleTemplate}"
|
||||
IsTabStop="False"
|
||||
Style="{StaticResource TitleContentControlStyle}"
|
||||
Visibility="Collapsed"/>
|
||||
|
||||
<Grid Grid.Row="1">
|
||||
<Grid.Resources>
|
||||
<ControlTemplate x:Key="NextTemplate" TargetType="Button">
|
||||
<Border
|
||||
x:Name="Root"
|
||||
Background="{ThemeResource PivotNextButtonBackground}"
|
||||
BorderBrush="{ThemeResource PivotNextButtonBorderBrush}"
|
||||
BorderThickness="{ThemeResource PivotNavButtonBorderThemeThickness}">
|
||||
<FontIcon
|
||||
x:Name="Arrow"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
FontFamily="{ThemeResource SymbolThemeFontFamily}"
|
||||
FontSize="12"
|
||||
Foreground="{ThemeResource PivotNextButtonForeground}"
|
||||
Glyph=""
|
||||
MirroredWhenRightToLeft="True"
|
||||
UseLayoutRounding="False"/>
|
||||
<VisualStateManager.VisualStateGroups>
|
||||
<VisualStateGroup x:Name="CommonStates">
|
||||
<VisualState x:Name="Normal"/>
|
||||
<VisualState x:Name="PointerOver">
|
||||
<Storyboard>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="Root" Storyboard.TargetProperty="Background">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource PivotNextButtonBackgroundPointerOver}"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="Root" Storyboard.TargetProperty="BorderBrush">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource PivotNextButtonBorderBrushPointerOver}"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="Arrow" Storyboard.TargetProperty="Foreground">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource PivotNextButtonForegroundPointerOver}"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
<VisualState x:Name="Pressed">
|
||||
<Storyboard>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="Root" Storyboard.TargetProperty="Background">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource PivotNextButtonBackgroundPressed}"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="Root" Storyboard.TargetProperty="BorderBrush">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource PivotNextButtonBorderBrushPressed}"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="Arrow" Storyboard.TargetProperty="Foreground">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource PivotNextButtonForegroundPressed}"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateManager.VisualStateGroups>
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
<ControlTemplate x:Key="PreviousTemplate" TargetType="Button">
|
||||
<Border
|
||||
x:Name="Root"
|
||||
Background="{ThemeResource PivotPreviousButtonBackground}"
|
||||
BorderBrush="{ThemeResource PivotPreviousButtonBorderBrush}"
|
||||
BorderThickness="{ThemeResource PivotNavButtonBorderThemeThickness}">
|
||||
<FontIcon
|
||||
x:Name="Arrow"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
FontFamily="{ThemeResource SymbolThemeFontFamily}"
|
||||
FontSize="12"
|
||||
Foreground="{ThemeResource PivotPreviousButtonForeground}"
|
||||
Glyph=""
|
||||
MirroredWhenRightToLeft="True"
|
||||
UseLayoutRounding="False"/>
|
||||
<VisualStateManager.VisualStateGroups>
|
||||
<VisualStateGroup x:Name="CommonStates">
|
||||
<VisualState x:Name="Normal"/>
|
||||
<VisualState x:Name="PointerOver">
|
||||
<Storyboard>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="Root" Storyboard.TargetProperty="Background">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource PivotPreviousButtonBackgroundPointerOver}"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="Root" Storyboard.TargetProperty="BorderBrush">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource PivotPreviousButtonBorderBrushPointerOver}"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="Arrow" Storyboard.TargetProperty="Foreground">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource PivotPreviousButtonForegroundPointerOver}"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
<VisualState x:Name="Pressed">
|
||||
<Storyboard>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="Root" Storyboard.TargetProperty="Background">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource PivotPreviousButtonBackgroundPressed}"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="Root" Storyboard.TargetProperty="BorderBrush">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource PivotPreviousButtonBorderBrushPressed}"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="Arrow" Storyboard.TargetProperty="Foreground">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource PivotPreviousButtonForegroundPressed}"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateManager.VisualStateGroups>
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
</Grid.Resources>
|
||||
<ScrollViewer
|
||||
x:Name="ScrollViewer"
|
||||
Margin="{TemplateBinding Padding}"
|
||||
VerticalContentAlignment="Stretch"
|
||||
BringIntoViewOnFocusChange="False"
|
||||
HorizontalScrollBarVisibility="Hidden"
|
||||
HorizontalSnapPointsAlignment="Center"
|
||||
HorizontalSnapPointsType="MandatorySingle"
|
||||
Template="{StaticResource ScrollViewerScrollBarlessTemplate}"
|
||||
VerticalScrollBarVisibility="Disabled"
|
||||
VerticalScrollMode="Disabled"
|
||||
VerticalSnapPointsType="None"
|
||||
ZoomMode="Disabled">
|
||||
<PivotPanel x:Name="Panel" VerticalAlignment="Stretch">
|
||||
<Grid x:Name="PivotLayoutElement">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.RenderTransform>
|
||||
<CompositeTransform x:Name="PivotLayoutElementTranslateTransform"/>
|
||||
</Grid.RenderTransform>
|
||||
|
||||
<ItemsPresenter x:Name="PivotItemPresenter" Grid.Row="1">
|
||||
<ItemsPresenter.RenderTransform>
|
||||
<TransformGroup>
|
||||
<TranslateTransform x:Name="ItemsPresenterTranslateTransform"/>
|
||||
<CompositeTransform x:Name="ItemsPresenterCompositeTransform"/>
|
||||
</TransformGroup>
|
||||
</ItemsPresenter.RenderTransform>
|
||||
</ItemsPresenter>
|
||||
|
||||
<Border
|
||||
Grid.Row="0"
|
||||
Margin="16,16,16,0"
|
||||
cw:Effects.Shadow="{ThemeResource CompatCardShadow}">
|
||||
<Grid Style="{ThemeResource AcrylicSecondaryGridCardStyle}">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<ContentPresenter
|
||||
x:Name="LeftHeaderPresenter"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
Content="{TemplateBinding LeftHeader}"
|
||||
ContentTemplate="{TemplateBinding LeftHeaderTemplate}"/>
|
||||
<ContentControl
|
||||
x:Name="HeaderClipper"
|
||||
Grid.Column="1"
|
||||
HorizontalContentAlignment="Stretch"
|
||||
UseSystemFocusVisuals="{StaticResource UseSystemFocusVisuals}">
|
||||
<ContentControl.Clip>
|
||||
<RectangleGeometry x:Name="HeaderClipperGeometry"/>
|
||||
</ContentControl.Clip>
|
||||
<Grid>
|
||||
<Grid.RenderTransform>
|
||||
<CompositeTransform x:Name="HeaderOffsetTranslateTransform"/>
|
||||
</Grid.RenderTransform>
|
||||
<PivotHeaderPanel x:Name="StaticHeader" Visibility="Collapsed">
|
||||
<PivotHeaderPanel.RenderTransform>
|
||||
<CompositeTransform x:Name="StaticHeaderTranslateTransform"/>
|
||||
</PivotHeaderPanel.RenderTransform>
|
||||
</PivotHeaderPanel>
|
||||
<PivotHeaderPanel x:Name="Header">
|
||||
<PivotHeaderPanel.RenderTransform>
|
||||
<CompositeTransform x:Name="HeaderTranslateTransform"/>
|
||||
</PivotHeaderPanel.RenderTransform>
|
||||
</PivotHeaderPanel>
|
||||
<Rectangle
|
||||
x:Name="FocusFollower"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
Control.IsTemplateFocusTarget="True"
|
||||
Fill="Transparent"
|
||||
IsHitTestVisible="False"/>
|
||||
</Grid>
|
||||
</ContentControl>
|
||||
<Button
|
||||
x:Name="PreviousButton"
|
||||
Grid.Column="1"
|
||||
Width="20"
|
||||
Height="36"
|
||||
Margin="{ThemeResource PivotNavButtonMargin}"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Top"
|
||||
Background="Transparent"
|
||||
IsEnabled="False"
|
||||
IsTabStop="False"
|
||||
Opacity="0"
|
||||
Template="{StaticResource PreviousTemplate}"
|
||||
UseSystemFocusVisuals="False"/>
|
||||
<Button
|
||||
x:Name="NextButton"
|
||||
Grid.Column="1"
|
||||
Width="20"
|
||||
Height="36"
|
||||
Margin="{ThemeResource PivotNavButtonMargin}"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Top"
|
||||
Background="Transparent"
|
||||
IsEnabled="False"
|
||||
IsTabStop="False"
|
||||
Opacity="0"
|
||||
Template="{StaticResource NextTemplate}"
|
||||
UseSystemFocusVisuals="False"/>
|
||||
<ContentPresenter
|
||||
x:Name="RightHeaderPresenter"
|
||||
Grid.Column="2"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
Content="{TemplateBinding RightHeader}"
|
||||
ContentTemplate="{TemplateBinding RightHeaderTemplate}"/>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
</Grid>
|
||||
</PivotPanel>
|
||||
</ScrollViewer>
|
||||
|
||||
</Grid>
|
||||
|
||||
<VisualStateManager.VisualStateGroups>
|
||||
<VisualStateGroup x:Name="Orientation">
|
||||
<VisualState x:Name="Portrait">
|
||||
|
||||
<Storyboard>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="TitleContentControl" Storyboard.TargetProperty="Margin">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource PivotPortraitThemePadding}"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
<VisualState x:Name="Landscape">
|
||||
|
||||
<Storyboard>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="TitleContentControl" Storyboard.TargetProperty="Margin">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource PivotLandscapeThemePadding}"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
|
||||
</VisualStateGroup>
|
||||
<VisualStateGroup x:Name="NavigationButtonsVisibility">
|
||||
<VisualState x:Name="NavigationButtonsHidden"/>
|
||||
<VisualState x:Name="NavigationButtonsVisible">
|
||||
|
||||
<Storyboard>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="NextButton" Storyboard.TargetProperty="Opacity">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="1"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="NextButton" Storyboard.TargetProperty="IsEnabled">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="True"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="PreviousButton" Storyboard.TargetProperty="Opacity">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="1"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="PreviousButton" Storyboard.TargetProperty="IsEnabled">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="True"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
<VisualState x:Name="PreviousButtonVisible">
|
||||
|
||||
<Storyboard>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="PreviousButton" Storyboard.TargetProperty="Opacity">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="1"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="PreviousButton" Storyboard.TargetProperty="IsEnabled">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="True"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
<VisualState x:Name="NextButtonVisible">
|
||||
|
||||
<Storyboard>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="NextButton" Storyboard.TargetProperty="Opacity">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="1"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="NextButton" Storyboard.TargetProperty="IsEnabled">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="True"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
|
||||
</VisualStateGroup>
|
||||
<VisualStateGroup x:Name="HeaderStates">
|
||||
<VisualState x:Name="HeaderDynamic"/>
|
||||
<VisualState x:Name="HeaderStatic">
|
||||
|
||||
<Storyboard>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="Header" Storyboard.TargetProperty="Visibility">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="Collapsed"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="StaticHeader" Storyboard.TargetProperty="Visibility">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="Visible"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
|
||||
</VisualStateGroup>
|
||||
|
||||
</VisualStateManager.VisualStateGroups>
|
||||
</Grid>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
<Style x:Key="CardPivotStyle2" TargetType="Pivot">
|
||||
<Setter Property="Margin" Value="0"/>
|
||||
<Setter Property="Padding" Value="0"/>
|
||||
<Setter Property="Background" Value="{ThemeResource PivotBackground}"/>
|
||||
<Setter Property="IsTabStop" Value="False"/>
|
||||
<Setter Property="ItemsPanel">
|
||||
<Setter.Value>
|
||||
<ItemsPanelTemplate>
|
||||
<Grid/>
|
||||
</ItemsPanelTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="Pivot">
|
||||
<Grid
|
||||
x:Name="RootElement"
|
||||
HorizontalAlignment="{TemplateBinding HorizontalAlignment}"
|
||||
VerticalAlignment="{TemplateBinding VerticalAlignment}"
|
||||
Background="{TemplateBinding Background}">
|
||||
<Grid.Resources>
|
||||
<Style x:Key="BaseContentControlStyle" TargetType="ContentControl">
|
||||
<Setter Property="FontFamily" Value="XamlAutoFontFamily"/>
|
||||
<Setter Property="FontWeight" Value="SemiBold"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="ContentControl">
|
||||
<ContentPresenter
|
||||
Margin="{TemplateBinding Padding}"
|
||||
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
|
||||
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
|
||||
Content="{TemplateBinding Content}"
|
||||
ContentTemplate="{TemplateBinding ContentTemplate}"
|
||||
ContentTransitions="{TemplateBinding ContentTransitions}"
|
||||
OpticalMarginAlignment="TrimSideBearings"/>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
<Style
|
||||
x:Key="TitleContentControlStyle"
|
||||
BasedOn="{StaticResource BaseContentControlStyle}"
|
||||
TargetType="ContentControl">
|
||||
<Setter Property="FontFamily" Value="{ThemeResource PivotTitleFontFamily}"/>
|
||||
<Setter Property="FontWeight" Value="{ThemeResource PivotTitleThemeFontWeight}"/>
|
||||
<Setter Property="FontSize" Value="{ThemeResource PivotTitleFontSize}"/>
|
||||
</Style>
|
||||
</Grid.Resources>
|
||||
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
<ContentControl
|
||||
x:Name="TitleContentControl"
|
||||
Grid.Row="0"
|
||||
Margin="{StaticResource PivotPortraitThemePadding}"
|
||||
Content="{TemplateBinding Title}"
|
||||
ContentTemplate="{TemplateBinding TitleTemplate}"
|
||||
IsTabStop="False"
|
||||
Style="{StaticResource TitleContentControlStyle}"
|
||||
Visibility="Collapsed"/>
|
||||
|
||||
<Grid Grid.Row="1">
|
||||
<Grid.Resources>
|
||||
<ControlTemplate x:Key="NextTemplate" TargetType="Button">
|
||||
<Border
|
||||
x:Name="Root"
|
||||
Background="{ThemeResource PivotNextButtonBackground}"
|
||||
BorderBrush="{ThemeResource PivotNextButtonBorderBrush}"
|
||||
BorderThickness="{ThemeResource PivotNavButtonBorderThemeThickness}">
|
||||
<FontIcon
|
||||
x:Name="Arrow"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
FontFamily="{ThemeResource SymbolThemeFontFamily}"
|
||||
FontSize="12"
|
||||
Foreground="{ThemeResource PivotNextButtonForeground}"
|
||||
Glyph=""
|
||||
MirroredWhenRightToLeft="True"
|
||||
UseLayoutRounding="False"/>
|
||||
<VisualStateManager.VisualStateGroups>
|
||||
<VisualStateGroup x:Name="CommonStates">
|
||||
<VisualState x:Name="Normal"/>
|
||||
<VisualState x:Name="PointerOver">
|
||||
<Storyboard>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="Root" Storyboard.TargetProperty="Background">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource PivotNextButtonBackgroundPointerOver}"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="Root" Storyboard.TargetProperty="BorderBrush">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource PivotNextButtonBorderBrushPointerOver}"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="Arrow" Storyboard.TargetProperty="Foreground">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource PivotNextButtonForegroundPointerOver}"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
<VisualState x:Name="Pressed">
|
||||
<Storyboard>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="Root" Storyboard.TargetProperty="Background">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource PivotNextButtonBackgroundPressed}"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="Root" Storyboard.TargetProperty="BorderBrush">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource PivotNextButtonBorderBrushPressed}"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="Arrow" Storyboard.TargetProperty="Foreground">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource PivotNextButtonForegroundPressed}"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateManager.VisualStateGroups>
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
<ControlTemplate x:Key="PreviousTemplate" TargetType="Button">
|
||||
<Border
|
||||
x:Name="Root"
|
||||
Background="{ThemeResource PivotPreviousButtonBackground}"
|
||||
BorderBrush="{ThemeResource PivotPreviousButtonBorderBrush}"
|
||||
BorderThickness="{ThemeResource PivotNavButtonBorderThemeThickness}">
|
||||
<FontIcon
|
||||
x:Name="Arrow"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
FontFamily="{ThemeResource SymbolThemeFontFamily}"
|
||||
FontSize="12"
|
||||
Foreground="{ThemeResource PivotPreviousButtonForeground}"
|
||||
Glyph=""
|
||||
MirroredWhenRightToLeft="True"
|
||||
UseLayoutRounding="False"/>
|
||||
<VisualStateManager.VisualStateGroups>
|
||||
<VisualStateGroup x:Name="CommonStates">
|
||||
<VisualState x:Name="Normal"/>
|
||||
<VisualState x:Name="PointerOver">
|
||||
<Storyboard>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="Root" Storyboard.TargetProperty="Background">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource PivotPreviousButtonBackgroundPointerOver}"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="Root" Storyboard.TargetProperty="BorderBrush">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource PivotPreviousButtonBorderBrushPointerOver}"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="Arrow" Storyboard.TargetProperty="Foreground">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource PivotPreviousButtonForegroundPointerOver}"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
<VisualState x:Name="Pressed">
|
||||
<Storyboard>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="Root" Storyboard.TargetProperty="Background">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource PivotPreviousButtonBackgroundPressed}"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="Root" Storyboard.TargetProperty="BorderBrush">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource PivotPreviousButtonBorderBrushPressed}"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="Arrow" Storyboard.TargetProperty="Foreground">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource PivotPreviousButtonForegroundPressed}"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateManager.VisualStateGroups>
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
</Grid.Resources>
|
||||
<ScrollViewer
|
||||
x:Name="ScrollViewer"
|
||||
Margin="{TemplateBinding Padding}"
|
||||
VerticalContentAlignment="Stretch"
|
||||
BringIntoViewOnFocusChange="False"
|
||||
HorizontalScrollBarVisibility="Hidden"
|
||||
HorizontalSnapPointsAlignment="Center"
|
||||
HorizontalSnapPointsType="MandatorySingle"
|
||||
Template="{StaticResource ScrollViewerScrollBarlessTemplate}"
|
||||
VerticalScrollBarVisibility="Disabled"
|
||||
VerticalScrollMode="Disabled"
|
||||
VerticalSnapPointsType="None"
|
||||
ZoomMode="Disabled">
|
||||
<PivotPanel x:Name="Panel" VerticalAlignment="Stretch">
|
||||
<Grid x:Name="PivotLayoutElement">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.RenderTransform>
|
||||
<CompositeTransform x:Name="PivotLayoutElementTranslateTransform"/>
|
||||
</Grid.RenderTransform>
|
||||
|
||||
|
||||
|
||||
<ItemsPresenter x:Name="PivotItemPresenter" Grid.Row="1">
|
||||
<ItemsPresenter.RenderTransform>
|
||||
<TransformGroup>
|
||||
<TranslateTransform x:Name="ItemsPresenterTranslateTransform"/>
|
||||
<CompositeTransform x:Name="ItemsPresenterCompositeTransform"/>
|
||||
</TransformGroup>
|
||||
</ItemsPresenter.RenderTransform>
|
||||
</ItemsPresenter>
|
||||
|
||||
<Border
|
||||
Grid.Row="0"
|
||||
Margin="16,16,16,0"
|
||||
cw:Effects.Shadow="{ThemeResource CompatCardShadow}">
|
||||
<Grid Style="{ThemeResource AcrylicSecondaryGridCardStyle}">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<ContentPresenter
|
||||
x:Name="LeftHeaderPresenter"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
Content="{TemplateBinding LeftHeader}"
|
||||
ContentTemplate="{TemplateBinding LeftHeaderTemplate}"/>
|
||||
<ContentControl
|
||||
x:Name="HeaderClipper"
|
||||
Grid.Column="1"
|
||||
HorizontalContentAlignment="Stretch"
|
||||
UseSystemFocusVisuals="{StaticResource UseSystemFocusVisuals}">
|
||||
<ContentControl.Clip>
|
||||
<RectangleGeometry x:Name="HeaderClipperGeometry"/>
|
||||
</ContentControl.Clip>
|
||||
<Grid>
|
||||
<Grid.RenderTransform>
|
||||
<CompositeTransform x:Name="HeaderOffsetTranslateTransform"/>
|
||||
</Grid.RenderTransform>
|
||||
<PivotHeaderPanel x:Name="StaticHeader" Visibility="Collapsed">
|
||||
<PivotHeaderPanel.RenderTransform>
|
||||
<CompositeTransform x:Name="StaticHeaderTranslateTransform"/>
|
||||
</PivotHeaderPanel.RenderTransform>
|
||||
</PivotHeaderPanel>
|
||||
<PivotHeaderPanel x:Name="Header">
|
||||
<PivotHeaderPanel.RenderTransform>
|
||||
<CompositeTransform x:Name="HeaderTranslateTransform"/>
|
||||
</PivotHeaderPanel.RenderTransform>
|
||||
</PivotHeaderPanel>
|
||||
<Rectangle
|
||||
x:Name="FocusFollower"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
Control.IsTemplateFocusTarget="True"
|
||||
Fill="Transparent"
|
||||
IsHitTestVisible="False"/>
|
||||
</Grid>
|
||||
</ContentControl>
|
||||
<Button
|
||||
x:Name="PreviousButton"
|
||||
Grid.Column="1"
|
||||
Width="20"
|
||||
Height="36"
|
||||
Margin="{ThemeResource PivotNavButtonMargin}"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Top"
|
||||
Background="Transparent"
|
||||
IsEnabled="False"
|
||||
IsTabStop="False"
|
||||
Opacity="0"
|
||||
Template="{StaticResource PreviousTemplate}"
|
||||
UseSystemFocusVisuals="False"/>
|
||||
<Button
|
||||
x:Name="NextButton"
|
||||
Grid.Column="1"
|
||||
Width="20"
|
||||
Height="36"
|
||||
Margin="{ThemeResource PivotNavButtonMargin}"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Top"
|
||||
Background="Transparent"
|
||||
IsEnabled="False"
|
||||
IsTabStop="False"
|
||||
Opacity="0"
|
||||
Template="{StaticResource NextTemplate}"
|
||||
UseSystemFocusVisuals="False"/>
|
||||
<ContentPresenter
|
||||
x:Name="RightHeaderPresenter"
|
||||
Grid.Column="2"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
Content="{TemplateBinding RightHeader}"
|
||||
ContentTemplate="{TemplateBinding RightHeaderTemplate}"/>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
</Grid>
|
||||
</PivotPanel>
|
||||
</ScrollViewer>
|
||||
|
||||
</Grid>
|
||||
|
||||
<VisualStateManager.VisualStateGroups>
|
||||
<VisualStateGroup x:Name="Orientation">
|
||||
<VisualState x:Name="Portrait">
|
||||
|
||||
<Storyboard>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="TitleContentControl" Storyboard.TargetProperty="Margin">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource PivotPortraitThemePadding}"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
<VisualState x:Name="Landscape">
|
||||
|
||||
<Storyboard>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="TitleContentControl" Storyboard.TargetProperty="Margin">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource PivotLandscapeThemePadding}"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
|
||||
</VisualStateGroup>
|
||||
<VisualStateGroup x:Name="NavigationButtonsVisibility">
|
||||
<VisualState x:Name="NavigationButtonsHidden"/>
|
||||
<VisualState x:Name="NavigationButtonsVisible">
|
||||
|
||||
<Storyboard>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="NextButton" Storyboard.TargetProperty="Opacity">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="1"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="NextButton" Storyboard.TargetProperty="IsEnabled">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="True"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="PreviousButton" Storyboard.TargetProperty="Opacity">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="1"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="PreviousButton" Storyboard.TargetProperty="IsEnabled">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="True"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
<VisualState x:Name="PreviousButtonVisible">
|
||||
|
||||
<Storyboard>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="PreviousButton" Storyboard.TargetProperty="Opacity">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="1"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="PreviousButton" Storyboard.TargetProperty="IsEnabled">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="True"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
<VisualState x:Name="NextButtonVisible">
|
||||
|
||||
<Storyboard>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="NextButton" Storyboard.TargetProperty="Opacity">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="1"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="NextButton" Storyboard.TargetProperty="IsEnabled">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="True"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
|
||||
</VisualStateGroup>
|
||||
<VisualStateGroup x:Name="HeaderStates">
|
||||
<VisualState x:Name="HeaderDynamic"/>
|
||||
<VisualState x:Name="HeaderStatic">
|
||||
|
||||
<Storyboard>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="Header" Storyboard.TargetProperty="Visibility">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="Collapsed"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="StaticHeader" Storyboard.TargetProperty="Visibility">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="Visible"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
|
||||
</VisualStateGroup>
|
||||
|
||||
</VisualStateManager.VisualStateGroups>
|
||||
</Grid>
|
||||
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</ResourceDictionary>
|
||||
@@ -42,7 +42,7 @@
|
||||
Grid.ColumnSpan="2"
|
||||
Margin="{TemplateBinding Padding}">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="*" MaxWidth="{Binding Path=(shch:ScrollViewerHelper.LeftPanelMaxWidth), RelativeSource={RelativeSource Mode=TemplatedParent}}"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<ScrollContentPresenter x:Name="ScrollContentPresenter" ContentTemplate="{TemplateBinding ContentTemplate}"/>
|
||||
|
||||
@@ -1,115 +0,0 @@
|
||||
<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:cwc="using:CommunityToolkit.WinUI.Controls"
|
||||
xmlns:shcp="using:Snap.Hutao.Control.Panel"
|
||||
xmlns:win="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="ms-appx:///CommunityToolkit.WinUI.Controls.Segmented/SegmentedItem/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="cwc:Segmented"/>
|
||||
|
||||
<Style x:Key="DefaultSegmentedStyle" TargetType="cwc: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>
|
||||
<shcp:EqualPanel
|
||||
HorizontalAlignment="{Binding (cw:FrameworkElementExtensions.Ancestor).HorizontalAlignment, RelativeSource={RelativeSource Self}}"
|
||||
cw:FrameworkElementExtensions.AncestorType="cwc:Segmented"
|
||||
Spacing="{ThemeResource SegmentedItemSpacing}"/>
|
||||
</ItemsPanelTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="cwc: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="cwc: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="cwc: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>
|
||||
@@ -21,14 +21,6 @@
|
||||
<Setter Property="Margin" Value="1,29,0,5"/>
|
||||
</Style.Setters>
|
||||
</Style>
|
||||
<Style
|
||||
x:Key="SettingsCardHeaderTextBlockStyle"
|
||||
BasedOn="{StaticResource BodyStrongTextBlockStyle}"
|
||||
TargetType="TextBlock">
|
||||
<Style.Setters>
|
||||
<Setter Property="Margin" Value="1,0,0,5"/>
|
||||
</Style.Setters>
|
||||
</Style>
|
||||
<Style
|
||||
x:Key="SettingsContentComboBoxStyle"
|
||||
BasedOn="{StaticResource DefaultComboBoxStyle}"
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Win32;
|
||||
using Windows.UI;
|
||||
|
||||
namespace Snap.Hutao.Control.Theme;
|
||||
|
||||
internal static class SystemColors
|
||||
{
|
||||
public static Color BaseLowColor(bool isDarkMode)
|
||||
{
|
||||
return isDarkMode ? StructMarshal.Color(0x33FFFFFF) : StructMarshal.Color(0x33000000);
|
||||
}
|
||||
|
||||
public static Color BaseMediumLowColor(bool isDarkMode)
|
||||
{
|
||||
return isDarkMode ? StructMarshal.Color(0x66FFFFFF) : StructMarshal.Color(0x66000000);
|
||||
}
|
||||
|
||||
public static Color BaseHighColor(bool isDarkMode)
|
||||
{
|
||||
return isDarkMode ? StructMarshal.Color(0xFFFFFFFF) : StructMarshal.Color(0xFF000000);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||
<Thickness x:Key="ListViewInSplitPanePadding">0,2</Thickness>
|
||||
<!-- https://github.com/microsoft/microsoft-ui-xaml/issues/4811 -->
|
||||
<x:Int32 x:Key="__DiscardPageOverride">0</x:Int32>
|
||||
</ResourceDictionary>
|
||||
@@ -4,7 +4,6 @@
|
||||
<x:String x:Key="DocumentLink_Home">https://hut.ao</x:String>
|
||||
<x:String x:Key="DocumentLink_MhyAccountSwitch">https://hut.ao/features/mhy-account-switch.html</x:String>
|
||||
<x:String x:Key="DocumentLink_Translate">https://translate.hut.ao</x:String>
|
||||
<x:String x:Key="DocumentLink_Loopback">https://hut.ao/zh/advanced/FAQ.html#%E5%A6%82%E4%BD%95%E9%80%9A%E8%BF%87%E7%BD%91%E7%BB%9C%E4%BB%A3%E7%90%86%E4%BD%BF%E7%94%A8%E8%83%A1%E6%A1%83%E5%B7%A5%E5%85%B7%E7%AE%B1</x:String>
|
||||
|
||||
<!-- Other -->
|
||||
<x:String x:Key="HolographicHat_GetToken_Release">https://github.com/HolographicHat/GetToken/releases/latest</x:String>
|
||||
@@ -34,6 +33,5 @@
|
||||
<x:String x:Key="UI_EmotionIcon271">https://api.snapgenshin.com/static/raw/EmotionIcon/UI_EmotionIcon271.png</x:String>
|
||||
<x:String x:Key="UI_EmotionIcon272">https://api.snapgenshin.com/static/raw/EmotionIcon/UI_EmotionIcon272.png</x:String>
|
||||
<x:String x:Key="UI_EmotionIcon293">https://api.snapgenshin.com/static/raw/EmotionIcon/UI_EmotionIcon293.png</x:String>
|
||||
<x:String x:Key="UI_EmotionIcon433">https://api.snapgenshin.com/static/raw/EmotionIcon/UI_EmotionIcon433.png</x:String>
|
||||
<x:String x:Key="UI_EmotionIcon445">https://api.snapgenshin.com/static/raw/EmotionIcon/UI_EmotionIcon445.png</x:String>
|
||||
</ResourceDictionary>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// 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;
|
||||
@@ -9,7 +10,7 @@ namespace Snap.Hutao.Core.Caching;
|
||||
/// 为图像缓存提供抽象
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
internal interface IImageCache
|
||||
internal interface IImageCache : ICastService
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the file path containing cached item for given Uri
|
||||
|
||||
@@ -26,12 +26,12 @@ internal sealed partial class ImageCache : IImageCache, IImageCacheFilePathOpera
|
||||
{
|
||||
private const string CacheFolderName = nameof(ImageCache);
|
||||
|
||||
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 FrozenDictionary<int, TimeSpan> retryCountToDelay = new Dictionary<int, TimeSpan>()
|
||||
{
|
||||
[0] = TimeSpan.FromSeconds(4),
|
||||
[1] = TimeSpan.FromSeconds(16),
|
||||
[2] = TimeSpan.FromSeconds(64),
|
||||
}.ToFrozenDictionary();
|
||||
|
||||
private readonly ConcurrentDictionary<string, Task> concurrentTasks = new();
|
||||
|
||||
@@ -42,20 +42,10 @@ internal sealed partial class ImageCache : IImageCache, IImageCacheFilePathOpera
|
||||
private string? baseFolder;
|
||||
private string? cacheFolder;
|
||||
|
||||
private string CacheFolder
|
||||
{
|
||||
get => LazyInitializer.EnsureInitialized(ref cacheFolder, () =>
|
||||
{
|
||||
baseFolder ??= serviceProvider.GetRequiredService<RuntimeOptions>().LocalCache;
|
||||
DirectoryInfo info = Directory.CreateDirectory(Path.Combine(baseFolder, CacheFolderName));
|
||||
return info.FullName;
|
||||
});
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void RemoveInvalid()
|
||||
{
|
||||
RemoveCore(Directory.GetFiles(CacheFolder).Where(file => IsFileInvalid(file, false)));
|
||||
RemoveInternal(Directory.GetFiles(GetCacheFolder()).Where(file => IsFileInvalid(file, false)));
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
@@ -72,7 +62,7 @@ internal sealed partial class ImageCache : IImageCache, IImageCacheFilePathOpera
|
||||
return;
|
||||
}
|
||||
|
||||
string folder = CacheFolder;
|
||||
string folder = GetCacheFolder();
|
||||
string[] files = Directory.GetFiles(folder);
|
||||
|
||||
List<string> filesToDelete = [];
|
||||
@@ -85,16 +75,16 @@ internal sealed partial class ImageCache : IImageCache, IImageCacheFilePathOpera
|
||||
}
|
||||
}
|
||||
|
||||
RemoveCore(filesToDelete);
|
||||
RemoveInternal(filesToDelete);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async ValueTask<ValueFile> GetFileFromCacheAsync(Uri uri)
|
||||
{
|
||||
string fileName = GetCacheFileName(uri);
|
||||
string filePath = Path.Combine(CacheFolder, fileName);
|
||||
string filePath = Path.Combine(GetCacheFolder(), fileName);
|
||||
|
||||
if (!IsFileInvalid(filePath))
|
||||
if (File.Exists(filePath) && new FileInfo(filePath).Length != 0)
|
||||
{
|
||||
return filePath;
|
||||
}
|
||||
@@ -104,12 +94,10 @@ internal sealed partial class ImageCache : IImageCache, IImageCacheFilePathOpera
|
||||
{
|
||||
if (concurrentTasks.TryAdd(fileName, taskCompletionSource.Task))
|
||||
{
|
||||
logger.LogDebug("Begin downloading image file from '{Uri}' to '{File}'", uri, filePath);
|
||||
await DownloadFileAsync(uri, filePath).ConfigureAwait(false);
|
||||
}
|
||||
else if (concurrentTasks.TryGetValue(fileName, out Task? task))
|
||||
{
|
||||
logger.LogDebug("Waiting for a queued image download task to complete for '{Uri}'", uri);
|
||||
await task.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
@@ -127,7 +115,7 @@ internal sealed partial class ImageCache : IImageCache, IImageCacheFilePathOpera
|
||||
public ValueFile GetFileFromCategoryAndName(string category, string fileName)
|
||||
{
|
||||
Uri dummyUri = Web.HutaoEndpoints.StaticRaw(category, fileName).ToUri();
|
||||
return Path.Combine(CacheFolder, GetCacheFileName(dummyUri));
|
||||
return Path.Combine(GetCacheFolder(), GetCacheFileName(dummyUri));
|
||||
}
|
||||
|
||||
private static string GetCacheFileName(Uri uri)
|
||||
@@ -149,18 +137,17 @@ internal sealed partial class ImageCache : IImageCache, IImageCacheFilePathOpera
|
||||
return fileInfo.Length == 0;
|
||||
}
|
||||
|
||||
private void RemoveCore(IEnumerable<string> filePaths)
|
||||
private void RemoveInternal(IEnumerable<string> filePaths)
|
||||
{
|
||||
foreach (string filePath in filePaths)
|
||||
{
|
||||
try
|
||||
{
|
||||
File.Delete(filePath);
|
||||
logger.LogInformation("Remove cached image succeed:{File}", filePath);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogWarning(ex, "Remove cached image failed:{File}", filePath);
|
||||
logger.LogWarning(ex, "Remove Cache Image Failed:{File}", filePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -168,17 +155,14 @@ internal sealed partial class ImageCache : IImageCache, IImageCacheFilePathOpera
|
||||
[SuppressMessage("", "SH003")]
|
||||
private async Task DownloadFileAsync(Uri uri, string baseFile)
|
||||
{
|
||||
logger.LogInformation("Begin downloading for {Uri}", uri);
|
||||
|
||||
int retryCount = 0;
|
||||
HttpClient httpClient = httpClientFactory.CreateClient(nameof(ImageCache));
|
||||
while (retryCount < 3)
|
||||
{
|
||||
using (HttpResponseMessage message = await httpClient.GetAsync(uri, HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false))
|
||||
{
|
||||
if (message.RequestMessage is { RequestUri: { } target } && target != uri)
|
||||
{
|
||||
logger.LogDebug("The Request '{Source}' has been redirected to '{Target}'", uri, target);
|
||||
}
|
||||
|
||||
if (message.IsSuccessStatusCode)
|
||||
{
|
||||
using (Stream httpStream = await message.Content.ReadAsStreamAsync().ConfigureAwait(false))
|
||||
@@ -197,7 +181,7 @@ internal sealed partial class ImageCache : IImageCache, IImageCacheFilePathOpera
|
||||
{
|
||||
retryCount++;
|
||||
TimeSpan delay = message.Headers.RetryAfter?.Delta ?? retryCountToDelay[retryCount];
|
||||
logger.LogInformation("Retry download '{Uri}' after {Delay}.", uri, delay);
|
||||
logger.LogInformation("Retry {Uri} after {Delay}.", uri, delay);
|
||||
await Task.Delay(delay).ConfigureAwait(false);
|
||||
break;
|
||||
}
|
||||
@@ -208,4 +192,18 @@ internal sealed partial class ImageCache : IImageCache, IImageCacheFilePathOpera
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private string GetCacheFolder()
|
||||
{
|
||||
if (cacheFolder is not null)
|
||||
{
|
||||
return cacheFolder;
|
||||
}
|
||||
|
||||
baseFolder ??= serviceProvider.GetRequiredService<RuntimeOptions>().LocalCache;
|
||||
DirectoryInfo info = Directory.CreateDirectory(Path.Combine(baseFolder, CacheFolderName));
|
||||
cacheFolder = info.FullName;
|
||||
|
||||
return cacheFolder;
|
||||
}
|
||||
}
|
||||
@@ -1,29 +1,25 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using CommunityToolkit.WinUI.Collections;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Snap.Hutao.Model;
|
||||
using Snap.Hutao.Model.Entity.Database;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Specialized;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Snap.Hutao.Core.Database;
|
||||
|
||||
internal sealed class ObservableReorderableDbCollection<TEntity> : ObservableCollection<TEntity>
|
||||
where TEntity : class, IReorderable
|
||||
internal sealed class ObservableReorderableDbCollection<T> : ObservableCollection<T>
|
||||
where T : class, IReorderable
|
||||
{
|
||||
private readonly IServiceProvider serviceProvider;
|
||||
private readonly DbContext dbContext;
|
||||
private bool previousChangeIsRemoved;
|
||||
|
||||
public ObservableReorderableDbCollection(List<TEntity> items, IServiceProvider serviceProvider)
|
||||
: base(AdjustIndex(items.SortBy(x => x.Index)))
|
||||
public ObservableReorderableDbCollection(List<T> items, DbContext dbContext)
|
||||
: base(AdjustIndex(items))
|
||||
{
|
||||
this.serviceProvider = serviceProvider;
|
||||
this.dbContext = dbContext;
|
||||
}
|
||||
|
||||
public IAdvancedCollectionView? View { get; set; }
|
||||
|
||||
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
base.OnCollectionChanged(e);
|
||||
@@ -31,18 +27,26 @@ internal sealed class ObservableReorderableDbCollection<TEntity> : ObservableCol
|
||||
switch (e.Action)
|
||||
{
|
||||
case NotifyCollectionChangedAction.Remove:
|
||||
previousChangeIsRemoved = true;
|
||||
break;
|
||||
case NotifyCollectionChangedAction.Add:
|
||||
if (!previousChangeIsRemoved)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
OnReorder();
|
||||
previousChangeIsRemoved = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private static List<TEntity> AdjustIndex(List<TEntity> list)
|
||||
private static List<T> AdjustIndex(List<T> list)
|
||||
{
|
||||
Span<TEntity> span = CollectionsMarshal.AsSpan(list);
|
||||
Span<T> span = CollectionsMarshal.AsSpan(list);
|
||||
for (int i = 0; i < list.Count; i++)
|
||||
{
|
||||
ref readonly TEntity item = ref span[i];
|
||||
ref readonly T item = ref span[i];
|
||||
item.Index = i;
|
||||
}
|
||||
|
||||
@@ -51,79 +55,12 @@ internal sealed class ObservableReorderableDbCollection<TEntity> : ObservableCol
|
||||
|
||||
private void OnReorder()
|
||||
{
|
||||
using (View?.DeferRefresh())
|
||||
AdjustIndex((List<T>)Items);
|
||||
|
||||
DbSet<T> dbSet = dbContext.Set<T>();
|
||||
foreach (ref readonly T item in CollectionsMarshal.AsSpan((List<T>)Items))
|
||||
{
|
||||
AdjustIndex((List<TEntity>)Items);
|
||||
|
||||
using (IServiceScope scope = serviceProvider.CreateScope())
|
||||
{
|
||||
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
||||
DbSet<TEntity> dbSet = appDbContext.Set<TEntity>();
|
||||
foreach (ref readonly TEntity item in CollectionsMarshal.AsSpan((List<TEntity>)Items))
|
||||
{
|
||||
dbSet.UpdateAndSave(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[SuppressMessage("", "SA1402")]
|
||||
internal sealed class ObservableReorderableDbCollection<TEntityOnly, TEntity> : ObservableCollection<TEntityOnly>
|
||||
where TEntityOnly : class, IEntityOnly<TEntity>
|
||||
where TEntity : class, IReorderable
|
||||
{
|
||||
private readonly IServiceProvider serviceProvider;
|
||||
|
||||
public ObservableReorderableDbCollection(List<TEntityOnly> items, IServiceProvider serviceProvider)
|
||||
: base(AdjustIndex(items.SortBy(x => x.Entity.Index)))
|
||||
{
|
||||
this.serviceProvider = serviceProvider;
|
||||
}
|
||||
|
||||
public IAdvancedCollectionView? View { get; set; }
|
||||
|
||||
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
base.OnCollectionChanged(e);
|
||||
|
||||
switch (e.Action)
|
||||
{
|
||||
case NotifyCollectionChangedAction.Remove:
|
||||
case NotifyCollectionChangedAction.Add:
|
||||
OnReorder();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private static List<TEntityOnly> AdjustIndex(List<TEntityOnly> list)
|
||||
{
|
||||
Span<TEntityOnly> span = CollectionsMarshal.AsSpan(list);
|
||||
for (int i = 0; i < list.Count; i++)
|
||||
{
|
||||
ref readonly TEntityOnly item = ref span[i];
|
||||
item.Entity.Index = i;
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
private void OnReorder()
|
||||
{
|
||||
using (View?.DeferRefresh())
|
||||
{
|
||||
AdjustIndex((List<TEntityOnly>)Items);
|
||||
|
||||
using (IServiceScope scope = serviceProvider.CreateScope())
|
||||
{
|
||||
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
||||
|
||||
DbSet<TEntity> dbSet = appDbContext.Set<TEntity>();
|
||||
foreach (ref readonly TEntityOnly item in CollectionsMarshal.AsSpan((List<TEntityOnly>)Items))
|
||||
{
|
||||
dbSet.UpdateAndSave(item.Entity);
|
||||
}
|
||||
}
|
||||
dbSet.UpdateAndSave(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,7 @@ namespace Snap.Hutao.Core.Database;
|
||||
internal static class QueryableExtension
|
||||
{
|
||||
/// <summary>
|
||||
/// <code>source.Where(predicate).ExecuteDelete()</code>
|
||||
/// source.Where(predicate).ExecuteDelete()
|
||||
/// </summary>
|
||||
/// <typeparam name="TSource">源类型</typeparam>
|
||||
/// <param name="source">源</param>
|
||||
@@ -27,7 +27,7 @@ internal static class QueryableExtension
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <code>source.Where(predicate).ExecuteDeleteAsync(token)</code>
|
||||
/// source.Where(predicate).ExecuteDeleteAsync(token)
|
||||
/// </summary>
|
||||
/// <typeparam name="TSource">源类型</typeparam>
|
||||
/// <param name="source">源</param>
|
||||
|
||||
@@ -6,7 +6,6 @@ namespace Snap.Hutao.Core.DependencyInjection.Abstraction;
|
||||
/// <summary>
|
||||
/// 可转换类型服务
|
||||
/// </summary>
|
||||
[Obsolete("Not useful anymore")]
|
||||
internal interface ICastService
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Core.DependencyInjection.Abstraction;
|
||||
|
||||
/// <summary>
|
||||
/// 有名称的对象
|
||||
/// 指示该对象可通过名称区分
|
||||
/// </summary>
|
||||
[Obsolete("无意义的接口")]
|
||||
[HighQuality]
|
||||
internal interface INamedService
|
||||
{
|
||||
/// <summary>
|
||||
/// 名称
|
||||
/// </summary>
|
||||
string Name { get; }
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Core.DependencyInjection.Abstraction;
|
||||
|
||||
/// <summary>
|
||||
/// 海外服/HoYoLAB 可区分
|
||||
/// </summary>
|
||||
[Obsolete("Use IOverseaSupportFactory instead")]
|
||||
internal interface IOverseaSupport
|
||||
{
|
||||
/// <summary>
|
||||
/// 是否为 海外服/HoYoLAB
|
||||
/// </summary>
|
||||
public bool IsOversea { get; }
|
||||
}
|
||||
@@ -17,7 +17,6 @@ internal static class CastServiceExtension
|
||||
/// <typeparam name="T">目标转换类型</typeparam>
|
||||
/// <param name="service">对象</param>
|
||||
/// <returns>转换类型后的对象</returns>
|
||||
[Obsolete("Not useful anymore")]
|
||||
public static T? As<T>(this ICastService service)
|
||||
where T : class
|
||||
{
|
||||
|
||||
@@ -2,9 +2,11 @@
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using Snap.Hutao.Core.IO.Http.DynamicProxy;
|
||||
using Snap.Hutao.Core.Logging;
|
||||
using Snap.Hutao.Service;
|
||||
using System.Globalization;
|
||||
using System.Net.Http;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Windows.Globalization;
|
||||
|
||||
@@ -31,7 +33,7 @@ internal static class DependencyInjection
|
||||
.AddJsonOptions()
|
||||
.AddDatabase()
|
||||
.AddInjections()
|
||||
.AddAllHttpClients()
|
||||
.AddHttpClients()
|
||||
|
||||
// Discrete services
|
||||
.AddSingleton<IMessenger, WeakReferenceMessenger>()
|
||||
@@ -41,6 +43,7 @@ internal static class DependencyInjection
|
||||
|
||||
serviceProvider.InitializeConsoleWindow();
|
||||
serviceProvider.InitializeCulture();
|
||||
serviceProvider.InitializedDynamicHttpProxy();
|
||||
|
||||
return serviceProvider;
|
||||
}
|
||||
@@ -51,13 +54,8 @@ internal static class DependencyInjection
|
||||
CultureOptions cultureOptions = serviceProvider.GetRequiredService<CultureOptions>();
|
||||
cultureOptions.SystemCulture = CultureInfo.CurrentCulture;
|
||||
|
||||
ILogger<CultureOptions> logger = serviceProvider.GetRequiredService<ILogger<CultureOptions>>();
|
||||
logger.LogDebug("System Culture: {System}", cultureOptions.SystemCulture);
|
||||
|
||||
CultureInfo cultureInfo = cultureOptions.CurrentCulture;
|
||||
|
||||
logger.LogDebug("Current Culture: {Current}", cultureInfo);
|
||||
|
||||
CultureInfo.DefaultThreadCurrentCulture = cultureInfo;
|
||||
CultureInfo.DefaultThreadCurrentUICulture = cultureInfo;
|
||||
CultureInfo.CurrentCulture = cultureInfo;
|
||||
@@ -68,9 +66,13 @@ internal static class DependencyInjection
|
||||
SH.Culture = cultureInfo;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static void InitializeConsoleWindow(this IServiceProvider serviceProvider)
|
||||
{
|
||||
_ = serviceProvider.GetRequiredService<ConsoleWindowLifeTime>();
|
||||
}
|
||||
|
||||
private static void InitializedDynamicHttpProxy(this IServiceProvider serviceProvider)
|
||||
{
|
||||
HttpClient.DefaultProxy = serviceProvider.GetRequiredService<DynamicHttpProxy>();
|
||||
}
|
||||
}
|
||||
@@ -47,13 +47,17 @@ internal static class IocConfiguration
|
||||
{
|
||||
if (context.Database.GetPendingMigrations().Any())
|
||||
{
|
||||
#if DEBUG
|
||||
System.Diagnostics.Debug.WriteLine("[Database] Performing AppDbContext Migrations");
|
||||
#endif
|
||||
context.Database.Migrate();
|
||||
}
|
||||
}
|
||||
|
||||
builder
|
||||
#if DEBUG
|
||||
.EnableSensitiveDataLogging()
|
||||
#endif
|
||||
.UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking)
|
||||
.UseSqlite(sqlConnectionString);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Core.IO.Http.Proxy;
|
||||
using Snap.Hutao.Web.Hoyolab;
|
||||
using System.Net.Http;
|
||||
|
||||
@@ -15,27 +14,12 @@ internal static partial class IocHttpClientConfiguration
|
||||
{
|
||||
private const string ApplicationJson = "application/json";
|
||||
|
||||
public static IServiceCollection AddAllHttpClients(this IServiceCollection services)
|
||||
{
|
||||
services
|
||||
.ConfigureHttpClientDefaults(clientBuilder =>
|
||||
{
|
||||
clientBuilder
|
||||
.ConfigurePrimaryHttpMessageHandler(() => new HttpClientHandler())
|
||||
.ConfigurePrimaryHttpMessageHandler((handler, provider) =>
|
||||
{
|
||||
HttpClientHandler clientHandler = (HttpClientHandler)handler;
|
||||
clientHandler.AllowAutoRedirect = true;
|
||||
clientHandler.UseProxy = true;
|
||||
clientHandler.Proxy = provider.GetRequiredService<DynamicHttpProxy>();
|
||||
});
|
||||
})
|
||||
.AddHttpClients();
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
/// <summary>
|
||||
/// 添加 <see cref="HttpClient"/>
|
||||
/// 此方法将会自动生成
|
||||
/// </summary>
|
||||
/// <param name="services">集合</param>
|
||||
/// <returns>可继续操作的集合</returns>
|
||||
public static partial IServiceCollection AddHttpClients(this IServiceCollection services);
|
||||
|
||||
/// <summary>
|
||||
@@ -105,7 +89,6 @@ internal static partial class IocHttpClientConfiguration
|
||||
/// HoYoLAB web
|
||||
/// </summary>
|
||||
/// <param name="client">配置后的客户端</param>
|
||||
[SuppressMessage("", "IDE0051")]
|
||||
private static void XRpc4Configuration(HttpClient client)
|
||||
{
|
||||
client.Timeout = Timeout.InfiniteTimeSpan;
|
||||
|
||||
@@ -17,6 +17,6 @@ internal readonly struct MeasureExecutionToken : IDisposable
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
logger.LogDebug("{Caller} toke {Time} ms", callerName, stopwatch.GetElapsedTime().TotalMilliseconds);
|
||||
logger.LogInformation("{Caller} toke {Time} ms", callerName, stopwatch.GetElapsedTime().TotalMilliseconds);
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,6 @@ namespace Snap.Hutao.Core.ExceptionService;
|
||||
/// 数据库损坏异常
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
[Obsolete("Use HutaoException instead")]
|
||||
internal sealed class DatabaseCorruptedException : Exception
|
||||
{
|
||||
/// <summary>
|
||||
|
||||
@@ -23,16 +23,8 @@ 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")]
|
||||
|
||||
@@ -11,44 +11,10 @@ internal sealed class HutaoException : Exception
|
||||
Kind = kind;
|
||||
}
|
||||
|
||||
private HutaoException(string message, Exception? innerException)
|
||||
public HutaoException(string message, Exception? innerException)
|
||||
: base($"{message}\n{innerException?.Message}", innerException)
|
||||
{
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
throw new HutaoException(kind, message, innerException);
|
||||
}
|
||||
}
|
||||
|
||||
public static void ThrowIfNot(bool condition, HutaoExceptionKind kind, string message, Exception? innerException = default)
|
||||
{
|
||||
if (!condition)
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -6,15 +6,4 @@ namespace Snap.Hutao.Core.ExceptionService;
|
||||
internal enum HutaoExceptionKind
|
||||
{
|
||||
None,
|
||||
|
||||
// Foundation
|
||||
ServiceTypeCastFailed,
|
||||
|
||||
// IO
|
||||
FileSystemCreateFileInsufficientPermissions,
|
||||
PrivateNamedPipeContentHashIncorrect,
|
||||
|
||||
// Service
|
||||
GachaStatisticsInvalidItemId,
|
||||
GameFpsUnlockingFailed,
|
||||
}
|
||||
@@ -8,7 +8,6 @@ namespace Snap.Hutao.Core.ExceptionService;
|
||||
/// 用户的计算机中的某些设置不符合要求
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
[Obsolete("Use HutaoException instead")]
|
||||
internal sealed class RuntimeEnvironmentException : Exception
|
||||
{
|
||||
/// <summary>
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
|
||||
using Snap.Hutao.Service.Game;
|
||||
using Snap.Hutao.Service.Game.Package;
|
||||
using System.IO;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Snap.Hutao.Core.ExceptionService;
|
||||
@@ -13,7 +12,6 @@ namespace Snap.Hutao.Core.ExceptionService;
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
[System.Diagnostics.StackTraceHidden]
|
||||
[Obsolete("Use HutaoException instead")]
|
||||
internal static class ThrowHelper
|
||||
{
|
||||
[DoesNotReturn]
|
||||
@@ -37,22 +35,6 @@ internal static class ThrowHelper
|
||||
throw new GameFileOperationException(message, inner);
|
||||
}
|
||||
|
||||
[DoesNotReturn]
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
public static InvalidDataException InvalidData(string message, Exception? inner = default)
|
||||
{
|
||||
throw new InvalidDataException(message, inner);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
public static void InvalidDataIf([DoesNotReturnIf(true)] bool condition, string message, Exception? inner = default)
|
||||
{
|
||||
if (condition)
|
||||
{
|
||||
throw new InvalidDataException(message, inner);
|
||||
}
|
||||
}
|
||||
|
||||
[DoesNotReturn]
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
public static InvalidOperationException InvalidOperation(string message, Exception? inner = default)
|
||||
@@ -74,15 +56,6 @@ internal static class ThrowHelper
|
||||
throw new NotSupportedException(message);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
public static void NotSupportedIf(bool condition, string message)
|
||||
{
|
||||
if (condition)
|
||||
{
|
||||
throw new NotSupportedException(message);
|
||||
}
|
||||
}
|
||||
|
||||
[DoesNotReturn]
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
public static OperationCanceledException OperationCanceled(string message, Exception? inner = default)
|
||||
|
||||
@@ -7,7 +7,6 @@ namespace Snap.Hutao.Core.ExceptionService;
|
||||
/// 用户数据损坏异常
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
[Obsolete("Use HutaoException instead")]
|
||||
internal sealed class UserdataCorruptedException : Exception
|
||||
{
|
||||
/// <summary>
|
||||
|
||||
@@ -1,46 +1,49 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using Snap.Hutao.Win32.Registry;
|
||||
using System.Net;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Snap.Hutao.Core.IO.Http.Proxy;
|
||||
namespace Snap.Hutao.Core.IO.Http.DynamicProxy;
|
||||
|
||||
[Injection(InjectAs.Singleton)]
|
||||
internal sealed partial class DynamicHttpProxy : ObservableObject, IWebProxy, IDisposable
|
||||
internal sealed partial class DynamicHttpProxy : IWebProxy, IDisposable
|
||||
{
|
||||
private const string ProxySettingPath = @"HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Internet Settings\Connections";
|
||||
|
||||
private static readonly Lazy<MethodInfo> LazyConstructSystemProxyMethod = new(GetConstructSystemProxyMethod);
|
||||
private static readonly MethodInfo ConstructSystemProxyMethod;
|
||||
|
||||
private readonly IServiceProvider serviceProvider;
|
||||
private readonly RegistryWatcher watcher;
|
||||
|
||||
private IWebProxy innerProxy = default!;
|
||||
|
||||
public DynamicHttpProxy(IServiceProvider serviceProvider)
|
||||
static DynamicHttpProxy()
|
||||
{
|
||||
this.serviceProvider = serviceProvider;
|
||||
UpdateInnerProxy();
|
||||
Type? systemProxyInfoType = typeof(System.Net.Http.SocketsHttpHandler).Assembly.GetType("System.Net.Http.SystemProxyInfo");
|
||||
ArgumentNullException.ThrowIfNull(systemProxyInfoType);
|
||||
|
||||
watcher = new(ProxySettingPath, OnSystemProxySettingsChanged);
|
||||
MethodInfo? constructSystemProxyMethod = systemProxyInfoType.GetMethod("ConstructSystemProxy", BindingFlags.Static | BindingFlags.Public);
|
||||
ArgumentNullException.ThrowIfNull(constructSystemProxyMethod);
|
||||
ConstructSystemProxyMethod = constructSystemProxyMethod;
|
||||
}
|
||||
|
||||
public DynamicHttpProxy()
|
||||
{
|
||||
UpdateProxy();
|
||||
|
||||
watcher = new(ProxySettingPath, UpdateProxy);
|
||||
watcher.Start();
|
||||
}
|
||||
|
||||
public string CurrentProxyUri
|
||||
/// <inheritdoc/>
|
||||
public ICredentials? Credentials
|
||||
{
|
||||
get
|
||||
{
|
||||
Uri? proxyUri = GetProxy("https://hut.ao".ToUri());
|
||||
return proxyUri is null
|
||||
? SH.ViewPageFeedbackCurrentProxyNoProxyDescription
|
||||
: proxyUri.AbsoluteUri;
|
||||
}
|
||||
get => InnerProxy.Credentials;
|
||||
set => InnerProxy.Credentials = value;
|
||||
}
|
||||
|
||||
public IWebProxy InnerProxy
|
||||
private IWebProxy InnerProxy
|
||||
{
|
||||
get => innerProxy;
|
||||
|
||||
@@ -57,10 +60,13 @@ internal sealed partial class DynamicHttpProxy : ObservableObject, IWebProxy, ID
|
||||
}
|
||||
}
|
||||
|
||||
public ICredentials? Credentials
|
||||
[MemberNotNull(nameof(innerProxy))]
|
||||
public void UpdateProxy()
|
||||
{
|
||||
get => InnerProxy.Credentials;
|
||||
set => InnerProxy.Credentials = value;
|
||||
IWebProxy? proxy = ConstructSystemProxyMethod.Invoke(default, default) as IWebProxy;
|
||||
ArgumentNullException.ThrowIfNull(proxy);
|
||||
|
||||
InnerProxy = proxy;
|
||||
}
|
||||
|
||||
public Uri? GetProxy(Uri destination)
|
||||
@@ -78,33 +84,4 @@ internal sealed partial class DynamicHttpProxy : ObservableObject, IWebProxy, ID
|
||||
(innerProxy as IDisposable)?.Dispose();
|
||||
watcher.Dispose();
|
||||
}
|
||||
|
||||
public void OnSystemProxySettingsChanged()
|
||||
{
|
||||
UpdateInnerProxy();
|
||||
|
||||
// TaskContext can't be injected directly since there are some recursive dependencies.
|
||||
ITaskContext taskContext = serviceProvider.GetRequiredService<ITaskContext>();
|
||||
taskContext.BeginInvokeOnMainThread(() => OnPropertyChanged(nameof(CurrentProxyUri)));
|
||||
}
|
||||
|
||||
private static MethodInfo GetConstructSystemProxyMethod()
|
||||
{
|
||||
Type? systemProxyInfoType = typeof(System.Net.Http.SocketsHttpHandler).Assembly.GetType("System.Net.Http.SystemProxyInfo");
|
||||
ArgumentNullException.ThrowIfNull(systemProxyInfoType);
|
||||
|
||||
MethodInfo? constructSystemProxyMethod = systemProxyInfoType.GetMethod("ConstructSystemProxy", BindingFlags.Static | BindingFlags.Public);
|
||||
ArgumentNullException.ThrowIfNull(constructSystemProxyMethod);
|
||||
|
||||
return constructSystemProxyMethod;
|
||||
}
|
||||
|
||||
[MemberNotNull(nameof(innerProxy))]
|
||||
private void UpdateInnerProxy()
|
||||
{
|
||||
IWebProxy? proxy = LazyConstructSystemProxyMethod.Value.Invoke(default, default) as IWebProxy;
|
||||
ArgumentNullException.ThrowIfNull(proxy);
|
||||
|
||||
InnerProxy = proxy;
|
||||
}
|
||||
}
|
||||
@@ -1,87 +0,0 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using Snap.Hutao.Win32.Foundation;
|
||||
using Snap.Hutao.Win32.NetworkManagement.WindowsFirewall;
|
||||
using Snap.Hutao.Win32.Security;
|
||||
using System.Runtime.InteropServices;
|
||||
using static Snap.Hutao.Win32.AdvApi32;
|
||||
using static Snap.Hutao.Win32.ApiMsWinNetIsolation;
|
||||
using static Snap.Hutao.Win32.Macros;
|
||||
|
||||
namespace Snap.Hutao.Core.IO.Http.Loopback;
|
||||
|
||||
[Injection(InjectAs.Singleton)]
|
||||
internal sealed unsafe class LoopbackManager : ObservableObject
|
||||
{
|
||||
private readonly RuntimeOptions runtimeOptions;
|
||||
private readonly ITaskContext taskContext;
|
||||
|
||||
private readonly string hutaoContainerStringSID = default!;
|
||||
private bool isLoopbackEnabled;
|
||||
|
||||
public LoopbackManager(IServiceProvider serviceProvider)
|
||||
{
|
||||
runtimeOptions = serviceProvider.GetRequiredService<RuntimeOptions>();
|
||||
taskContext = serviceProvider.GetRequiredService<ITaskContext>();
|
||||
|
||||
INET_FIREWALL_APP_CONTAINER* pContainers = default;
|
||||
try
|
||||
{
|
||||
{
|
||||
WIN32_ERROR error = NetworkIsolationEnumAppContainers(NETISO_FLAG.NETISO_FLAG_MAX, out uint acCount, out pContainers);
|
||||
Marshal.ThrowExceptionForHR(HRESULT_FROM_WIN32(error));
|
||||
for (uint i = 0; i < acCount; i++)
|
||||
{
|
||||
INET_FIREWALL_APP_CONTAINER* pContainer = pContainers + i;
|
||||
ReadOnlySpan<char> appContainerName = MemoryMarshal.CreateReadOnlySpanFromNullTerminated(pContainer->appContainerName);
|
||||
if (appContainerName.Equals(runtimeOptions.FamilyName, StringComparison.Ordinal))
|
||||
{
|
||||
ConvertSidToStringSidW(pContainer->appContainerSid, out PWSTR stringSid);
|
||||
hutaoContainerStringSID = MemoryMarshal.CreateReadOnlySpanFromNullTerminated(stringSid).ToString();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
// This function returns 1 rather than 0 specfied in the document.
|
||||
_ = NetworkIsolationFreeAppContainers(pContainers);
|
||||
}
|
||||
|
||||
{
|
||||
WIN32_ERROR error = NetworkIsolationGetAppContainerConfig(out uint accCount, out SID_AND_ATTRIBUTES* pSids);
|
||||
Marshal.ThrowExceptionForHR(HRESULT_FROM_WIN32(error));
|
||||
for (uint i = 0; i < accCount; i++)
|
||||
{
|
||||
ConvertSidToStringSidW((pSids + i)->Sid, out PWSTR stringSid);
|
||||
ReadOnlySpan<char> stringSidSpan = MemoryMarshal.CreateReadOnlySpanFromNullTerminated(stringSid);
|
||||
if (stringSidSpan.Equals(hutaoContainerStringSID, StringComparison.Ordinal))
|
||||
{
|
||||
IsLoopbackEnabled = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsLoopbackEnabled { get => isLoopbackEnabled; private set => SetProperty(ref isLoopbackEnabled, value); }
|
||||
|
||||
public void EnableLoopback()
|
||||
{
|
||||
NetworkIsolationGetAppContainerConfig(out uint accCount, out SID_AND_ATTRIBUTES* pSids);
|
||||
List<SID_AND_ATTRIBUTES> sids = new((int)(accCount + 1));
|
||||
for (uint i = 0; i < accCount; i++)
|
||||
{
|
||||
sids.Add(*(pSids + i));
|
||||
}
|
||||
|
||||
ConvertStringSidToSidW(hutaoContainerStringSID, out PSID pSid);
|
||||
SID_AND_ATTRIBUTES sidAndAttributes = default;
|
||||
sidAndAttributes.Sid = pSid;
|
||||
sids.Add(sidAndAttributes);
|
||||
IsLoopbackEnabled = NetworkIsolationSetAppContainerConfig(CollectionsMarshal.AsSpan(sids)) is WIN32_ERROR.ERROR_SUCCESS;
|
||||
}
|
||||
}
|
||||
@@ -18,7 +18,6 @@ internal sealed class HttpShardCopyWorker<TStatus> : IDisposable
|
||||
private readonly long contentLength;
|
||||
private readonly int bufferSize;
|
||||
private readonly SafeFileHandle destFileHandle;
|
||||
private readonly int maxDegreeOfParallelism;
|
||||
private readonly List<Shard> shards;
|
||||
|
||||
private HttpShardCopyWorker(HttpShardCopyWorkerOptions<TStatus> options)
|
||||
@@ -29,7 +28,6 @@ internal sealed class HttpShardCopyWorker<TStatus> : IDisposable
|
||||
contentLength = options.ContentLength;
|
||||
bufferSize = options.BufferSize;
|
||||
destFileHandle = options.GetFileHandle();
|
||||
maxDegreeOfParallelism = options.MaxDegreeOfParallelism;
|
||||
shards = CalculateShards(contentLength);
|
||||
|
||||
static List<Shard> CalculateShards(long contentLength)
|
||||
@@ -58,11 +56,7 @@ internal sealed class HttpShardCopyWorker<TStatus> : IDisposable
|
||||
public Task CopyAsync(IProgress<TStatus> progress, CancellationToken token = default)
|
||||
{
|
||||
ShardProgress shardProgress = new(progress, statusFactory, contentLength);
|
||||
ParallelOptions parallelOptions = new()
|
||||
{
|
||||
MaxDegreeOfParallelism = maxDegreeOfParallelism,
|
||||
};
|
||||
return Parallel.ForEachAsync(shards, parallelOptions, (shard, token) => CopyShardAsync(shard, shardProgress, token));
|
||||
return Parallel.ForEachAsync(shards, token, (shard, token) => CopyShardAsync(shard, shardProgress, token));
|
||||
|
||||
async ValueTask CopyShardAsync(Shard shard, IProgress<ShardStatus> progress, CancellationToken token)
|
||||
{
|
||||
|
||||
@@ -22,8 +22,6 @@ internal sealed class HttpShardCopyWorkerOptions<TStatus>
|
||||
|
||||
public int BufferSize { get; set; } = 80 * 1024;
|
||||
|
||||
public int MaxDegreeOfParallelism { get; set; } = Environment.ProcessorCount;
|
||||
|
||||
public SafeFileHandle GetFileHandle()
|
||||
{
|
||||
return File.OpenHandle(DestinationFilePath, FileMode.Create, FileAccess.Write, FileShare.None, FileOptions.RandomAccess | FileOptions.Asynchronous, ContentLength);
|
||||
|
||||
@@ -29,7 +29,7 @@ internal readonly struct TempFile : IDisposable
|
||||
}
|
||||
catch (UnauthorizedAccessException ex)
|
||||
{
|
||||
HutaoException.Throw(HutaoExceptionKind.FileSystemCreateFileInsufficientPermissions, SH.CoreIOTempFileCreateFail, ex);
|
||||
ThrowHelper.RuntimeEnvironment(SH.CoreIOTempFileCreateFail, ex);
|
||||
}
|
||||
|
||||
if (delete)
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
using CommunityToolkit.WinUI.Notifications;
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
using Snap.Hutao.Core.LifeCycle.InterProcess;
|
||||
using Microsoft.Windows.AppLifecycle;
|
||||
using Snap.Hutao.Core.Setting;
|
||||
using Snap.Hutao.Service.DailyNote;
|
||||
using Snap.Hutao.Service.Discord;
|
||||
@@ -24,9 +24,24 @@ namespace Snap.Hutao.Core.LifeCycle;
|
||||
[SuppressMessage("", "CA1001")]
|
||||
internal sealed partial class Activation : IActivation
|
||||
{
|
||||
/// <summary>
|
||||
/// 操作
|
||||
/// </summary>
|
||||
public const string Action = nameof(Action);
|
||||
|
||||
/// <summary>
|
||||
/// Uid
|
||||
/// </summary>
|
||||
public const string Uid = nameof(Uid);
|
||||
|
||||
/// <summary>
|
||||
/// 启动游戏启动参数
|
||||
/// </summary>
|
||||
public const string LaunchGame = nameof(LaunchGame);
|
||||
|
||||
/// <summary>
|
||||
/// 从剪贴板导入成就
|
||||
/// </summary>
|
||||
public const string ImportUIAFFromClipboard = nameof(ImportUIAFFromClipboard);
|
||||
|
||||
private const string CategoryAchievement = "ACHIEVEMENT";
|
||||
@@ -40,20 +55,29 @@ internal sealed partial class Activation : IActivation
|
||||
private readonly SemaphoreSlim activateSemaphore = new(1);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Activate(HutaoActivationArguments args)
|
||||
public void Activate(object? sender, AppActivationArguments args)
|
||||
{
|
||||
if (ToastNotificationManagerCompat.WasCurrentProcessToastActivated())
|
||||
_ = sender;
|
||||
if (!ToastNotificationManagerCompat.WasCurrentProcessToastActivated())
|
||||
{
|
||||
return;
|
||||
HandleActivationAsync(args, true).SafeForget();
|
||||
}
|
||||
|
||||
HandleActivationAsync(args).SafeForget();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Initialize()
|
||||
public void NonRedirectToActivate(object? sender, AppActivationArguments args)
|
||||
{
|
||||
serviceProvider.GetRequiredService<PrivateNamedPipeServer>().RunAsync().SafeForget();
|
||||
_ = sender;
|
||||
if (!ToastNotificationManagerCompat.WasCurrentProcessToastActivated())
|
||||
{
|
||||
HandleActivationAsync(args, false).SafeForget();
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void InitializeWith(AppInstance appInstance)
|
||||
{
|
||||
appInstance.Activated += Activate;
|
||||
ToastNotificationManagerCompat.OnActivated += NotificationActivate;
|
||||
}
|
||||
|
||||
@@ -71,40 +95,44 @@ internal sealed partial class Activation : IActivation
|
||||
}
|
||||
}
|
||||
|
||||
private async ValueTask HandleActivationAsync(HutaoActivationArguments args)
|
||||
private async ValueTask HandleActivationAsync(AppActivationArguments args, bool isRedirected)
|
||||
{
|
||||
if (activateSemaphore.CurrentCount > 0)
|
||||
{
|
||||
using (await activateSemaphore.EnterAsync().ConfigureAwait(false))
|
||||
{
|
||||
await HandleActivationCoreAsync(args).ConfigureAwait(false);
|
||||
await HandleActivationCoreAsync(args, isRedirected).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async ValueTask HandleActivationCoreAsync(HutaoActivationArguments args)
|
||||
private async ValueTask HandleActivationCoreAsync(AppActivationArguments args, bool isRedirected)
|
||||
{
|
||||
if (args.Kind is HutaoActivationKind.Protocol)
|
||||
if (args.Kind == ExtendedActivationKind.Protocol)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(args.ProtocolActivatedUri);
|
||||
await HandleUrlActivationAsync(args.ProtocolActivatedUri, args.IsRedirectTo).ConfigureAwait(false);
|
||||
}
|
||||
else if (args.Kind is HutaoActivationKind.Launch)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(args.LaunchActivatedArguments);
|
||||
switch (args.LaunchActivatedArguments)
|
||||
if (args.TryGetProtocolActivatedUri(out Uri? uri))
|
||||
{
|
||||
case LaunchGame:
|
||||
{
|
||||
await HandleLaunchGameActionAsync().ConfigureAwait(false);
|
||||
break;
|
||||
}
|
||||
await HandleUrlActivationAsync(uri, isRedirected).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
else if (args.Kind == ExtendedActivationKind.Launch)
|
||||
{
|
||||
if (args.TryGetLaunchActivatedArgument(out string? arguments))
|
||||
{
|
||||
switch (arguments)
|
||||
{
|
||||
case LaunchGame:
|
||||
{
|
||||
await HandleLaunchGameActionAsync().ConfigureAwait(false);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
await HandleNormalLaunchActionAsync().ConfigureAwait(false);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
await HandleNormalLaunchActionAsync().ConfigureAwait(false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -148,15 +176,17 @@ internal sealed partial class Activation : IActivation
|
||||
|
||||
await taskContext.SwitchToBackgroundAsync();
|
||||
|
||||
if (serviceProvider.GetRequiredService<IMetadataService>() is IMetadataServiceInitialization metadataServiceInitialization)
|
||||
{
|
||||
metadataServiceInitialization.InitializeInternalAsync().SafeForget();
|
||||
}
|
||||
serviceProvider
|
||||
.GetRequiredService<IMetadataService>()
|
||||
.As<IMetadataServiceInitialization>()?
|
||||
.InitializeInternalAsync()
|
||||
.SafeForget();
|
||||
|
||||
if (serviceProvider.GetRequiredService<IHutaoUserService>() is IHutaoUserServiceInitialization hutaoUserServiceInitialization)
|
||||
{
|
||||
hutaoUserServiceInitialization.InitializeInternalAsync().SafeForget();
|
||||
}
|
||||
serviceProvider
|
||||
.GetRequiredService<IHutaoUserService>()
|
||||
.As<IHutaoUserServiceInitialization>()?
|
||||
.InitializeInternalAsync()
|
||||
.SafeForget();
|
||||
|
||||
serviceProvider
|
||||
.GetRequiredService<IDiscordService>()
|
||||
@@ -164,7 +194,7 @@ internal sealed partial class Activation : IActivation
|
||||
.SafeForget();
|
||||
}
|
||||
|
||||
private async ValueTask HandleUrlActivationAsync(Uri uri, bool isRedirectTo)
|
||||
private async ValueTask HandleUrlActivationAsync(Uri uri, bool isRedirected)
|
||||
{
|
||||
UriBuilder builder = new(uri);
|
||||
|
||||
@@ -177,13 +207,13 @@ internal sealed partial class Activation : IActivation
|
||||
case CategoryAchievement:
|
||||
{
|
||||
await WaitMainWindowAsync().ConfigureAwait(false);
|
||||
await HandleAchievementActionAsync(action, parameter, isRedirectTo).ConfigureAwait(false);
|
||||
await HandleAchievementActionAsync(action, parameter, isRedirected).ConfigureAwait(false);
|
||||
break;
|
||||
}
|
||||
|
||||
case CategoryDailyNote:
|
||||
{
|
||||
await HandleDailyNoteActionAsync(action, parameter, isRedirectTo).ConfigureAwait(false);
|
||||
await HandleDailyNoteActionAsync(action, parameter, isRedirected).ConfigureAwait(false);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -195,10 +225,10 @@ internal sealed partial class Activation : IActivation
|
||||
}
|
||||
}
|
||||
|
||||
private async ValueTask HandleAchievementActionAsync(string action, string parameter, bool isRedirectTo)
|
||||
private async ValueTask HandleAchievementActionAsync(string action, string parameter, bool isRedirected)
|
||||
{
|
||||
_ = parameter;
|
||||
_ = isRedirectTo;
|
||||
_ = isRedirected;
|
||||
switch (action)
|
||||
{
|
||||
case UrlActionImport:
|
||||
@@ -215,7 +245,7 @@ internal sealed partial class Activation : IActivation
|
||||
}
|
||||
}
|
||||
|
||||
private async ValueTask HandleDailyNoteActionAsync(string action, string parameter, bool isRedirectTo)
|
||||
private async ValueTask HandleDailyNoteActionAsync(string action, string parameter, bool isRedirected)
|
||||
{
|
||||
_ = parameter;
|
||||
switch (action)
|
||||
@@ -234,7 +264,7 @@ internal sealed partial class Activation : IActivation
|
||||
}
|
||||
|
||||
// Check if it's redirected.
|
||||
if (!isRedirectTo)
|
||||
if (!isRedirected)
|
||||
{
|
||||
// It's a direct open process, should exit immediately.
|
||||
Process.GetCurrentProcess().Kill();
|
||||
|
||||
@@ -36,7 +36,7 @@ internal static class AppActivationArgumentsExtensions
|
||||
/// <param name="activatedEventArgs">应用程序激活参数</param>
|
||||
/// <param name="arguments">参数</param>
|
||||
/// <returns>是否存在参数</returns>
|
||||
public static bool TryGetLaunchActivatedArguments(this AppActivationArguments activatedEventArgs, [NotNullWhen(true)] out string? arguments)
|
||||
public static bool TryGetLaunchActivatedArgument(this AppActivationArguments activatedEventArgs, [NotNullWhen(true)] out string? arguments)
|
||||
{
|
||||
arguments = null;
|
||||
if (activatedEventArgs.Data is not ILaunchActivatedEventArgs launchArgs)
|
||||
|
||||
@@ -2,11 +2,10 @@
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.Windows.AppLifecycle;
|
||||
using Snap.Hutao.Win32.Foundation;
|
||||
using Snap.Hutao.Win32.System.Com;
|
||||
using static Snap.Hutao.Win32.ConstValues;
|
||||
using static Snap.Hutao.Win32.Kernel32;
|
||||
using static Snap.Hutao.Win32.Ole32;
|
||||
using Windows.Win32.Foundation;
|
||||
using Windows.Win32.Security;
|
||||
using Windows.Win32.System.Com;
|
||||
using static Windows.Win32.PInvoke;
|
||||
|
||||
namespace Snap.Hutao.Core.LifeCycle;
|
||||
|
||||
@@ -19,7 +18,7 @@ internal static class AppInstanceExtension
|
||||
private static readonly WaitCallback RunActionWaitCallback = RunAction;
|
||||
|
||||
// Hold the reference here to prevent memory corruption.
|
||||
private static HANDLE redirectEventHandle;
|
||||
private static HANDLE redirectEventHandle = HANDLE.Null;
|
||||
|
||||
/// <summary>
|
||||
/// 同步非阻塞重定向
|
||||
@@ -28,24 +27,19 @@ internal static class AppInstanceExtension
|
||||
/// <param name="args">参数</param>
|
||||
public static unsafe void RedirectActivationTo(this AppInstance appInstance, AppActivationArguments args)
|
||||
{
|
||||
try
|
||||
{
|
||||
redirectEventHandle = CreateEventW(default, true, false, default);
|
||||
redirectEventHandle = CreateEvent(default(SECURITY_ATTRIBUTES*), true, false, null);
|
||||
|
||||
// use ThreadPool.UnsafeQueueUserWorkItem to cancel stacktrace
|
||||
// like ExecutionContext.SuppressFlow
|
||||
ThreadPool.UnsafeQueueUserWorkItem(RunActionWaitCallback, () =>
|
||||
{
|
||||
appInstance.RedirectActivationToAsync(args).AsTask().Wait();
|
||||
SetEvent(redirectEventHandle);
|
||||
});
|
||||
|
||||
CoWaitForMultipleObjects(CWMO_FLAGS.CWMO_DEFAULT, INFINITE, [redirectEventHandle], out uint _);
|
||||
}
|
||||
finally
|
||||
// use ThreadPool.UnsafeQueueUserWorkItem to cancel stacktrace
|
||||
// like ExecutionContext.SuppressFlow
|
||||
ThreadPool.UnsafeQueueUserWorkItem(RunActionWaitCallback, () =>
|
||||
{
|
||||
CloseHandle(redirectEventHandle);
|
||||
}
|
||||
appInstance.RedirectActivationToAsync(args).AsTask().Wait();
|
||||
SetEvent(redirectEventHandle);
|
||||
});
|
||||
|
||||
ReadOnlySpan<HANDLE> handles = new(ref redirectEventHandle);
|
||||
CoWaitForMultipleObjects((uint)CWMO_FLAGS.CWMO_DEFAULT, INFINITE, handles, out uint _);
|
||||
CloseHandle(redirectEventHandle);
|
||||
}
|
||||
|
||||
[SuppressMessage("", "SH007")]
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
using Microsoft.UI.Xaml;
|
||||
using Snap.Hutao.Core.Windowing;
|
||||
using Snap.Hutao.Win32.Foundation;
|
||||
using Windows.Win32.Foundation;
|
||||
using WinRT.Interop;
|
||||
|
||||
namespace Snap.Hutao.Core.LifeCycle;
|
||||
@@ -19,6 +19,6 @@ internal static class CurrentWindowReferenceExtension
|
||||
{
|
||||
return reference.Window is IWindowOptionsSource optionsSource
|
||||
? optionsSource.WindowOptions.Hwnd
|
||||
: WindowNative.GetWindowHandle(reference.Window);
|
||||
: (HWND)WindowNative.GetWindowHandle(reference.Window);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.Windows.AppLifecycle;
|
||||
|
||||
namespace Snap.Hutao.Core.LifeCycle;
|
||||
|
||||
internal sealed class HutaoActivationArguments
|
||||
{
|
||||
public bool IsRedirectTo { get; set; }
|
||||
|
||||
public HutaoActivationKind Kind { get; set; }
|
||||
|
||||
public Uri? ProtocolActivatedUri { get; set; }
|
||||
|
||||
public string? LaunchActivatedArguments { get; set; }
|
||||
|
||||
public static HutaoActivationArguments FromAppActivationArguments(AppActivationArguments args, bool isRedirected = false)
|
||||
{
|
||||
HutaoActivationArguments result = new()
|
||||
{
|
||||
IsRedirectTo = isRedirected,
|
||||
};
|
||||
|
||||
switch (args.Kind)
|
||||
{
|
||||
case ExtendedActivationKind.Launch:
|
||||
{
|
||||
result.Kind = HutaoActivationKind.Launch;
|
||||
if (args.TryGetLaunchActivatedArguments(out string? arguments))
|
||||
{
|
||||
result.LaunchActivatedArguments = arguments;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case ExtendedActivationKind.Protocol:
|
||||
{
|
||||
result.Kind = HutaoActivationKind.Protocol;
|
||||
if (args.TryGetProtocolActivatedUri(out Uri? uri))
|
||||
{
|
||||
result.ProtocolActivatedUri = uri;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user