mirror of
https://github.com/BeyondDimension/SteamTools.git
synced 2026-10-19 15:21:26 +08:00
Compare commits
219 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| deee8d056f | |||
| 3016469c7f | |||
| 526fb7a78d | |||
| 2790ffc5f1 | |||
| e22897e2ae | |||
| 00b8c4dfcf | |||
| 9a2d2c6c94 | |||
| 29fb43fc38 | |||
| 4ca3066644 | |||
| 890f2fab37 | |||
| f561f841e3 | |||
| a016631d68 | |||
| 367c97b480 | |||
| 6367bc5ed1 | |||
| 2b69b98f3c | |||
| a6010ba5f4 | |||
| 0c67cda96a | |||
| c23823a5af | |||
| f8424a2915 | |||
| 152fc74869 | |||
| 7c96f7af46 | |||
| 9b5bc82873 | |||
| 011c08266f | |||
| bd43f16dd6 | |||
| 8bd7085d35 | |||
| 1bb5bef800 | |||
| da73e4cb84 | |||
| d11c1afc12 | |||
| f21f069a2b | |||
| 242866fe88 | |||
| 46d87bc2d6 | |||
| 70ce3237cd | |||
| e57e7db81b | |||
| 5bbddf86cd | |||
| 541399f011 | |||
| 287ff69011 | |||
| b00fbba75f | |||
| f9f0325610 | |||
| 6dae018cb7 | |||
| a675d8ea2f | |||
| 586f5d6a6c | |||
| e4058219d0 | |||
| 9adeb39b4f | |||
| 2f71afc818 | |||
| aa271827b3 | |||
| 88ed73d720 | |||
| cde6e0d724 | |||
| df5e359d0d | |||
| 14e050a942 | |||
| 568483c454 | |||
| 926fd00fc7 | |||
| 5c64dd1168 | |||
| 5dd50aeeca | |||
| c2bb27c537 | |||
| 9cf88df056 | |||
| 995aaee884 | |||
| 99e915adbe | |||
| b8801d6b82 | |||
| 73639376aa | |||
| 5852423652 | |||
| 18012f775e | |||
| 9054d4d095 | |||
| e19011a785 | |||
| a63dfc0846 | |||
| 40539d99ed | |||
| 9ae1584506 | |||
| 38f35d36e0 | |||
| b3c18e2042 | |||
| 5ea5fe1e83 | |||
| 9c71e89645 | |||
| 8a745839fe | |||
| 1a237a10bc | |||
| 655f943f5d | |||
| 77f8178557 | |||
| c33dfc8e70 | |||
| 803b626e8a | |||
| f9228b93a3 | |||
| 60ec614d46 | |||
| 237190384f | |||
| 7dd576a6c5 | |||
| 3fb0057883 | |||
| aeccf68061 | |||
| 0806f61448 | |||
| b67adfd67c | |||
| 97fb2ed673 | |||
| 0beadc8733 | |||
| d0bc99d5a5 | |||
| 0852c57ed8 | |||
| 84653abace | |||
| 2a738a36a1 | |||
| b18925a1a0 | |||
| 29fd1f0b19 | |||
| d414267c9f | |||
| af8739bb59 | |||
| e140902de6 | |||
| 4a5e3447ff | |||
| a97357099d | |||
| 7131deb860 | |||
| 268b803ce7 | |||
| 6a9af1d95f | |||
| 1bf13ffb4b | |||
| 1b90cd4bce | |||
| 62efead062 | |||
| 2754fb5dac | |||
| 8fff1d1828 | |||
| 9805ad4114 | |||
| 83ae3102dd | |||
| c3911fd5bf | |||
| ef27310e4f | |||
| 9ea1c83342 | |||
| bbcee67fe0 | |||
| 4706561d42 | |||
| c9d5fe37bb | |||
| 6bca078614 | |||
| a4481011eb | |||
| 81d39b5436 | |||
| 4bff8602d1 | |||
| 4e434d25fe | |||
| 4e2cfc23ae | |||
| e61c240db7 | |||
| b1a0f178e5 | |||
| 5d7d00c623 | |||
| c9b3191f2b | |||
| e8ac8a52d6 | |||
| 34b4dc9ef7 | |||
| 47e16e3fc4 | |||
| bbd9623311 | |||
| 30d5a7d14f | |||
| f8d02c64b0 | |||
| 80c28caed9 | |||
| 4d91ce0690 | |||
| d92968caf4 | |||
| 57459be38c | |||
| 276f150866 | |||
| 209634c73e | |||
| d22a83228f | |||
| 8d1e8b2795 | |||
| ef728120e4 | |||
| df158c340e | |||
| bf450815ae | |||
| ee1187482c | |||
| 755fda1cd3 | |||
| 72fad6c877 | |||
| c6873c255a | |||
| ffa89cef34 | |||
| db058c79f6 | |||
| 5be7f5984d | |||
| 2dbe0df4aa | |||
| beef9611f7 | |||
| b340c365ea | |||
| 924779d29f | |||
| 5bd73c86e8 | |||
| 95052a6af1 | |||
| 0aedef2bb6 | |||
| 3038f72e49 | |||
| 710ee5f350 | |||
| 74eb79b7f5 | |||
| f626cbe96c | |||
| bfbb1e36d4 | |||
| dc2826af1c | |||
| d3dcb913a8 | |||
| 5a6f18109d | |||
| eb25310530 | |||
| db5ff210f9 | |||
| b1d7faee8a | |||
| 0ff633ec8f | |||
| d0ae475c50 | |||
| 47cb686ed1 | |||
| 053bd2097b | |||
| c945161805 | |||
| 4c848ef373 | |||
| 12d49ef32a | |||
| 3afb735fb9 | |||
| f24a828cca | |||
| d56312246e | |||
| dc3dac0bdd | |||
| 16758c9295 | |||
| cda93da954 | |||
| 93e35508f3 | |||
| efc234e3fa | |||
| c31848929f | |||
| 0eff585092 | |||
| eaeb90d853 | |||
| 09dc6a5e81 | |||
| 8a56c52940 | |||
| 2f14d083ee | |||
| 07d5c3de27 | |||
| 9e4c4836c0 | |||
| d34a5d3347 | |||
| fd119ae0e1 | |||
| 3d59c24dec | |||
| ea315a9b44 | |||
| d5592b4bc2 | |||
| 2df32c67d0 | |||
| fdaf795202 | |||
| a7572ecd50 | |||
| d433d60d29 | |||
| 3fe8f931a2 | |||
| a69d97fcdb | |||
| aa63e3ab9f | |||
| a56fc36380 | |||
| 4c5428be5f | |||
| e6953e50b8 | |||
| a961f68242 | |||
| 72f301d526 | |||
| 2b18e03098 | |||
| 9fffe8458a | |||
| ea7282f290 | |||
| 3714710dd8 | |||
| 00c228abd8 | |||
| 54fc1e3593 | |||
| d2d04a37cf | |||
| 0600a5ca9f | |||
| 01dc528e21 | |||
| 9014198aeb | |||
| bfd6b82a95 | |||
| a6a45e5324 | |||
| 0ed0416f69 | |||
| ce6fd9a4d6 |
@@ -216,6 +216,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Steam++", "src\BD.WTTS.Clie
|
||||
EndProject
|
||||
Project("{C7167F0D-BC9F-4E6E-AFE1-012C56B48DB5}") = "BD.WTTS.Client.AppHost.Bridge.Package", "src\BD.WTTS.Client.AppHost.Bridge.Package\BD.WTTS.Client.AppHost.Bridge.Package.wapproj", "{27795C79-2994-4514-8656-BA34FCEE8901}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ArchiSteamFarm", "ref\ArchiSteamFarm\ArchiSteamFarm\ArchiSteamFarm.csproj", "{8C34FCD5-8C7F-4B10-A970-7DEA8A61F56B}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
@@ -1260,6 +1262,26 @@ Global
|
||||
{27795C79-2994-4514-8656-BA34FCEE8901}.Release|x86.ActiveCfg = Release|x86
|
||||
{27795C79-2994-4514-8656-BA34FCEE8901}.Release|x86.Build.0 = Release|x86
|
||||
{27795C79-2994-4514-8656-BA34FCEE8901}.Release|x86.Deploy.0 = Release|x86
|
||||
{8C34FCD5-8C7F-4B10-A970-7DEA8A61F56B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{8C34FCD5-8C7F-4B10-A970-7DEA8A61F56B}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{8C34FCD5-8C7F-4B10-A970-7DEA8A61F56B}.Debug|ARM.ActiveCfg = Debug|Any CPU
|
||||
{8C34FCD5-8C7F-4B10-A970-7DEA8A61F56B}.Debug|ARM.Build.0 = Debug|Any CPU
|
||||
{8C34FCD5-8C7F-4B10-A970-7DEA8A61F56B}.Debug|ARM64.ActiveCfg = Debug|Any CPU
|
||||
{8C34FCD5-8C7F-4B10-A970-7DEA8A61F56B}.Debug|ARM64.Build.0 = Debug|Any CPU
|
||||
{8C34FCD5-8C7F-4B10-A970-7DEA8A61F56B}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{8C34FCD5-8C7F-4B10-A970-7DEA8A61F56B}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{8C34FCD5-8C7F-4B10-A970-7DEA8A61F56B}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{8C34FCD5-8C7F-4B10-A970-7DEA8A61F56B}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{8C34FCD5-8C7F-4B10-A970-7DEA8A61F56B}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{8C34FCD5-8C7F-4B10-A970-7DEA8A61F56B}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{8C34FCD5-8C7F-4B10-A970-7DEA8A61F56B}.Release|ARM.ActiveCfg = Release|Any CPU
|
||||
{8C34FCD5-8C7F-4B10-A970-7DEA8A61F56B}.Release|ARM.Build.0 = Release|Any CPU
|
||||
{8C34FCD5-8C7F-4B10-A970-7DEA8A61F56B}.Release|ARM64.ActiveCfg = Release|Any CPU
|
||||
{8C34FCD5-8C7F-4B10-A970-7DEA8A61F56B}.Release|ARM64.Build.0 = Release|Any CPU
|
||||
{8C34FCD5-8C7F-4B10-A970-7DEA8A61F56B}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{8C34FCD5-8C7F-4B10-A970-7DEA8A61F56B}.Release|x64.Build.0 = Release|Any CPU
|
||||
{8C34FCD5-8C7F-4B10-A970-7DEA8A61F56B}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{8C34FCD5-8C7F-4B10-A970-7DEA8A61F56B}.Release|x86.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
@@ -1326,6 +1348,7 @@ Global
|
||||
{35676E23-8E54-4785-A75B-98EB0650596B} = {99D97BD4-3F51-48BC-8BD2-647F6D268BB0}
|
||||
{26D8AA24-2ACE-4167-BC2B-117B9BB76AA4} = {5541B406-4A9A-4AC8-AA77-CE14E133DB80}
|
||||
{27795C79-2994-4514-8656-BA34FCEE8901} = {5541B406-4A9A-4AC8-AA77-CE14E133DB80}
|
||||
{8C34FCD5-8C7F-4B10-A970-7DEA8A61F56B} = {99D97BD4-3F51-48BC-8BD2-647F6D268BB0}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {567F588F-E95C-4286-8B30-DB3B2200221C}
|
||||
|
||||
@@ -40,14 +40,14 @@ else
|
||||
fi
|
||||
}
|
||||
certutil_Init() {
|
||||
certutil -d sql:$HOME/.pki/nssdb -L
|
||||
certutil -d $HOME/.pki/nssdb -L
|
||||
ret=$?
|
||||
if [ $ret -ne 0 ]; then
|
||||
echo "即将初始化 certutil sql:\$HOME/.pki/nssdb"
|
||||
mkdir -p sql:$HOME/.pki
|
||||
mkdir -p sql:$HOME/.pki/nssdb
|
||||
chmod 700 sql:$HOME/.pki/nssdb
|
||||
certutil -d sql:$HOME/.pki/nssdb -N --empty-password
|
||||
echo "即将初始化 certutil \$HOME/.pki/nssdb"
|
||||
mkdir -p $HOME/.pki
|
||||
mkdir -p $HOME/.pki/nssdb
|
||||
chmod 700 $HOME/.pki/nssdb
|
||||
certutil -d $HOME/.pki/nssdb -N --empty-password
|
||||
else
|
||||
echo "certutil nssdb 正常"
|
||||
fi
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
#!/bin/bash
|
||||
#添加桌面文件
|
||||
run_path=$(dirname "$0")
|
||||
base_path="$(dirname "$run_path")"
|
||||
exec_name="Steam++"
|
||||
rm -rf "$HOME/Desktop/Watt Toolkit.desktop" 2>/dev/null
|
||||
echo "#!/usr/bin/env xdg-open
|
||||
[Desktop Entry]
|
||||
Name=Watt Toolkit
|
||||
Exec=$base_path/$exec_name.sh
|
||||
Icon=$base_path/Icons/Watt-Toolkit.png
|
||||
Terminal=false
|
||||
Type=Application
|
||||
StartupNotify=false" >"$HOME/Desktop/Watt Toolkit.desktop"
|
||||
chmod +x "$HOME/Desktop/Watt Toolkit.desktop"
|
||||
exit 0
|
||||
@@ -0,0 +1,8 @@
|
||||
#!/usr/bin/env xdg-open
|
||||
[Desktop Entry]
|
||||
Name=°²×° Watt Toolkit
|
||||
Exec=curl https://steampp.net/pkg/latest/steamdeck/install.sh | bash -s --
|
||||
Icon=steamdeck-gaming-return
|
||||
Terminal=true
|
||||
Type=Application
|
||||
StartupNotify=false
|
||||
@@ -0,0 +1,189 @@
|
||||
#!/bin/bash
|
||||
run_path=$(dirname "$0")
|
||||
base_path="$(dirname "$run_path")"
|
||||
mkdir -p "$base_path"
|
||||
cd "$base_path" || exit 1
|
||||
appVer_path="$base_path/WattToolkit.AppVer"
|
||||
exec_name="Steam++"
|
||||
tar_name="WattToolkit.tgz"
|
||||
tar_path="$base_path/$tar_name"
|
||||
app_name="Watt Toolkit"
|
||||
PROCESS_NAMES=("$exec_name" "$app_name")
|
||||
export LC_ALL=en_US.UTF-8
|
||||
Install_certutil() {
|
||||
# 判断发行版类型
|
||||
if command -v certutil &>/dev/null; then
|
||||
echo "certutil 工具已安装。"
|
||||
else
|
||||
echo "证书导入以及验证需要使用 certutil 工具。"
|
||||
# 判断包管理器
|
||||
if command -v apt &>/dev/null; then
|
||||
# 使用 apt (Debian/Ubuntu)
|
||||
sudo apt update
|
||||
sudo apt install -y libnss3-tools
|
||||
elif command -v dnf &>/dev/null; then
|
||||
# 使用 dnf (Fedora)
|
||||
sudo dnf install -y nss-tools
|
||||
elif command -v yum &>/dev/null; then
|
||||
# 使用 yum (CentOS/Red Hat)
|
||||
sudo yum install -y nss-tools
|
||||
elif command -v pacman &>/dev/null; then
|
||||
# 使用 pacman (Arch Linux)
|
||||
# sudo pacman -S nss
|
||||
echo "请手动安装 certutil 工具。"
|
||||
exit 1
|
||||
else
|
||||
echo "请手动安装 certutil 工具。"
|
||||
exit 1
|
||||
fi
|
||||
echo "certutil 工具已安装。"
|
||||
fi
|
||||
}
|
||||
certutil_Init() {
|
||||
certutil -d $HOME/.pki/nssdb -L
|
||||
ret=$?
|
||||
if [ $ret -ne 0 ]; then
|
||||
echo "即将初始化 certutil \$HOME/.pki/nssdb"
|
||||
mkdir -p $HOME/.pki
|
||||
mkdir -p $HOME/.pki/nssdb
|
||||
chmod 700 $HOME/.pki/nssdb
|
||||
certutil -d $HOME/.pki/nssdb -N --empty-password
|
||||
else
|
||||
echo "certutil nssdb 正常"
|
||||
fi
|
||||
}
|
||||
Install_jq() {
|
||||
# Check if jq is already installed
|
||||
if command -v jq &>/dev/null; then
|
||||
echo "jq 工具已安装。"
|
||||
else
|
||||
echo "jq 用来解析版本更新。"
|
||||
# Check the package manager
|
||||
if command -v apt &>/dev/null; then
|
||||
# Using apt (Debian/Ubuntu)
|
||||
sudo apt update
|
||||
sudo apt install -y jq
|
||||
elif command -v dnf &>/dev/null; then
|
||||
# Using dnf (Fedora)
|
||||
sudo dnf install -y jq
|
||||
elif command -v yum &>/dev/null; then
|
||||
# Using yum (CentOS/Red Hat)
|
||||
sudo yum install -y jq
|
||||
elif command -v pacman &>/dev/null; then
|
||||
# Using pacman (Arch Linux)
|
||||
sudo pacman -S jq
|
||||
else
|
||||
echo "请手动安装 jq 工具。"
|
||||
exit 1
|
||||
fi
|
||||
echo "请手动安装 jq 工具。"
|
||||
fi
|
||||
}
|
||||
Install_zenity() {
|
||||
# 判断发行版类型
|
||||
if command -v zenity &>/dev/null; then
|
||||
echo "zenity 工具已安装。"
|
||||
else
|
||||
echo "安装过程需要 zenity 工具。"
|
||||
# 判断包管理器
|
||||
if command -v apt &>/dev/null; then
|
||||
# 使用 apt (Debian/Ubuntu)
|
||||
sudo apt update
|
||||
sudo apt install -y zenity
|
||||
elif command -v dnf &>/dev/null; then
|
||||
# 使用 dnf (Fedora)
|
||||
sudo dnf install -y zenity
|
||||
elif command -v yum &>/dev/null; then
|
||||
# 使用 yum (CentOS/Red Hat)
|
||||
sudo yum install -y zenity
|
||||
elif command -v pacman &>/dev/null; then
|
||||
# 使用 pacman (Arch Linux)
|
||||
# sudo pacman -S zenity
|
||||
echo "请手动安装 zenity 工具。"
|
||||
exit 1
|
||||
else
|
||||
echo "请手动安装 zenity 工具。"
|
||||
exit 1
|
||||
fi
|
||||
echo "zenity 工具已安装。"
|
||||
fi
|
||||
}
|
||||
|
||||
Show_Run() {
|
||||
local param1=$1
|
||||
# 显示提示框,询问是否运行程序
|
||||
zenity --question --text="$1" --width=400
|
||||
|
||||
# 获取上一个命令的退出码
|
||||
response=$?
|
||||
|
||||
if [ $response -eq 0 ]; then
|
||||
echo "程序已启动。"
|
||||
exit 0 &
|
||||
# 用户点击了 "运行" 按钮,启动程序
|
||||
/bin/sh -c "$base_path/$exec_name.sh"
|
||||
else
|
||||
# 用户点击了 "关闭" 按钮,退出脚本
|
||||
exit 0
|
||||
fi
|
||||
}
|
||||
Kill_Process() {
|
||||
# 尝试的次数
|
||||
Kill_MAX_RETRIES=3
|
||||
|
||||
# 循环尝试终止进程
|
||||
for process_name in "${PROCESS_NAMES[@]}"; do
|
||||
kill_retry=1
|
||||
while [ $kill_retry -le $Kill_MAX_RETRIES ]; do
|
||||
pid=$(pgrep "$process_name")
|
||||
if [ -n "$pid" ]; then
|
||||
echo "尝试 $kill_retry: 进程 $process_name 正在运行中。正在终止..."
|
||||
kill $pid
|
||||
sleep 2
|
||||
else
|
||||
break
|
||||
fi
|
||||
kill_retry=$((kill_retry + 1))
|
||||
done
|
||||
done
|
||||
|
||||
for process_name in "${PROCESS_NAMES[@]}"; do
|
||||
# 检查是否成功终止进程
|
||||
if pgrep -x "$process_name" >/dev/null; then
|
||||
echo "无法终止程序 $process_name。尝试次数已达上限。"
|
||||
exit 1
|
||||
else
|
||||
echo "程序 $process_name 已成功终止。"
|
||||
fi
|
||||
done
|
||||
|
||||
}
|
||||
Install_certutil
|
||||
Install_zenity
|
||||
Install_jq
|
||||
certutil_Init
|
||||
Kill_Process
|
||||
|
||||
dotnet_exec="$dotnet_path/dotnet/dotnet"
|
||||
if [ -x "$dotnet_exec" ]; then
|
||||
echo "文件具有执行权限。"
|
||||
else
|
||||
chmod +x "$dotnet_exec"
|
||||
fi
|
||||
chmod +x "$base_path/$exec_name.sh"
|
||||
# xdg-icon-resource install "$base_path/Icons/Watt-Toolkit.png" --size 128 Watt-Toolkit
|
||||
#添加桌面文件
|
||||
rm -rf "$HOME/Desktop/Watt Toolkit.desktop" 2>/dev/null
|
||||
echo "#!/usr/bin/env xdg-open
|
||||
[Desktop Entry]
|
||||
Name=Watt Toolkit
|
||||
Exec=$base_path/$exec_name.sh
|
||||
Icon=$base_path/Icons/Watt-Toolkit.png
|
||||
Terminal=false
|
||||
Type=Application
|
||||
StartupNotify=false" >"$HOME/Desktop/Watt Toolkit.desktop"
|
||||
chmod +x "$HOME/Desktop/Watt Toolkit.desktop"
|
||||
# update-desktop-database ~/.local/share/applications
|
||||
#运行程序
|
||||
Show_Run "下载安装完成,是否启动程序?"
|
||||
exit 0
|
||||
@@ -41,14 +41,14 @@ Install_certutil() {
|
||||
fi
|
||||
}
|
||||
certutil_Init() {
|
||||
certutil -d sql:$HOME/.pki/nssdb -L
|
||||
certutil -d $HOME/.pki/nssdb -L
|
||||
ret=$?
|
||||
if [ $ret -ne 0 ]; then
|
||||
echo "即将初始化 certutil sql:\$HOME/.pki/nssdb"
|
||||
mkdir -p sql:$HOME/.pki
|
||||
mkdir -p sql:$HOME/.pki/nssdb
|
||||
chmod 700 sql:$HOME/.pki/nssdb
|
||||
certutil -d sql:$HOME/.pki/nssdb -N --empty-password
|
||||
echo "即将初始化 certutil \$HOME/.pki/nssdb"
|
||||
mkdir -p $HOME/.pki
|
||||
mkdir -p $HOME/.pki/nssdb
|
||||
chmod 700 $HOME/.pki/nssdb
|
||||
certutil -d $HOME/.pki/nssdb -N --empty-password
|
||||
else
|
||||
echo "certutil nssdb 正常"
|
||||
fi
|
||||
+50
-11
@@ -1,6 +1,6 @@
|
||||
#!/bin/bash
|
||||
base_path="$HOME/WattToolkit"
|
||||
export LC_ALL=en_US.UTF-8
|
||||
run_path=$(dirname "$0")
|
||||
base_path="$(dirname "$run_path")"
|
||||
exec_name="Steam++"
|
||||
app_name="Watt Toolkit"
|
||||
AppData="$XDG_DATA_HOME/Steam++"
|
||||
@@ -8,7 +8,37 @@ AppData_t="$HOME/.local/share/Steam++"
|
||||
Cache="$XDG_CACHE_HOME/Steam++"
|
||||
Cache_t="$HOME/.cache/Steam+"
|
||||
PROCESS_NAMES=("$exec_name" "$app_name")
|
||||
export LC_ALL=en_US.UTF-8
|
||||
#检查是否存在 Steam++.sh 避免卸载错误
|
||||
file_to_check="$base_path/Steam++.sh"
|
||||
folders_to_check=("dotnet" "modules" "native" "script" "assemblies")
|
||||
if [ -e "$file_to_check" ]; then
|
||||
echo "文件 $file_to_check 存在"
|
||||
else
|
||||
echo "文件 $file_to_check 不存在"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
for folder in "${folders_to_check[@]}"; do
|
||||
folder_path="$base_path/$folder"
|
||||
if [ -d "$folder_path" ]; then
|
||||
echo "文件夹 $folder_path 存在"
|
||||
else
|
||||
echo "文件夹 $folder_path 不存在"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
echo "验证该文件夹是 $app_name 安装目录,请确认是否卸载。"
|
||||
|
||||
read -p "所有文件和文件夹都存在,确认卸载吗? (输入 'y' 确认): " confirmation
|
||||
|
||||
if [ "${confirmation,,}" == "y" ]; then
|
||||
echo "开始执行卸载操作"
|
||||
# 在这里添加您的卸载操作
|
||||
else
|
||||
echo "用户取消卸载操作"
|
||||
exit 0
|
||||
fi
|
||||
Kill_Process() {
|
||||
# 尝试的次数
|
||||
Kill_MAX_RETRIES=3
|
||||
@@ -42,19 +72,28 @@ Kill_Process() {
|
||||
}
|
||||
Kill_Process
|
||||
|
||||
zenity --question --text="是否删除用户数据" --width=400
|
||||
zenity --question --text="是否删除证书?" --width=400
|
||||
# 获取上一个命令的退出码
|
||||
response=$?
|
||||
delCer=$?
|
||||
|
||||
if [ $response -eq 0 ]; then
|
||||
rm -rfq "$AppData" 2>/dev/null
|
||||
rm -rfq "$AppData_t" 2>/dev/null
|
||||
if [ $delCer -eq 0 ]; then
|
||||
certutil -D -d $HOME/.pki/nssdb -n "SteamTools"
|
||||
fi
|
||||
|
||||
zenity --question --text="是否删除用户数据?" --width=400
|
||||
# 获取上一个命令的退出码
|
||||
delUserData=$?
|
||||
|
||||
if [ $delUserData -eq 0 ]; then
|
||||
rm -rf $AppData 2>/dev/null
|
||||
rm -rf $AppData_t 2>/dev/null
|
||||
certutil -D -d $HOME/.pki/nssdb -n "SteamTools"
|
||||
else
|
||||
echo "保留用户数据方便下次安装。"
|
||||
echo "如需删除可手动删除该目录:$AppData 或者 $AppData_t"
|
||||
fi
|
||||
rm -rfq "$Cache" 2>/dev/null
|
||||
rm -rfq "$Cache_t" 2>/dev/null
|
||||
rm -rfq "$base_path" 2>/dev/null
|
||||
rm -rfq "$HOME/Desktop/Watt Toolkit.desktop" 2>/dev/null
|
||||
rm -rf $Cache 2>/dev/null
|
||||
rm -rf $Cache_t 2>/dev/null
|
||||
rm -rf $base_path 2>/dev/null
|
||||
rm -rf "$HOME/Desktop/Watt Toolkit.desktop" 2>/dev/null
|
||||
zenity --info --text="卸载完成!" --width=300
|
||||
|
||||
+19
-19
@@ -14,7 +14,7 @@ CodesignKey_Name="mossimo code"
|
||||
App_Modules_Path="$App_Path/$Publish_Path/modules"
|
||||
echo "编译主程序"
|
||||
cd "$App_Path"
|
||||
dotnet publish -c Release -p:PublishDir="$Publish_Path" -f net7.0-macos -p:CreatePackage=false -p:EnableCodeSigning=true -p:CodesignKey="$CodesignKey_Name"
|
||||
dotnet publish -c Release -p:PublishDir="$Publish_Path" -f net8.0-macos -p:CreatePackage=false -p:EnableCodeSigning=true -p:CodesignKey="$CodesignKey_Name"
|
||||
|
||||
rm -r "$App_Modules_Path"
|
||||
mkdir -p "$App_Modules_Path"
|
||||
@@ -30,59 +30,59 @@ Copy_Plugins() {
|
||||
echo "编译加速插件"
|
||||
Plugins_Accelerator_Path="$Base_Path/src/BD.WTTS.Client.Plugins.Accelerator"
|
||||
cd "$Plugins_Accelerator_Path"
|
||||
dotnet publish -c Release -p:PublishDir="$Publish_Path" -f net7.0-macos
|
||||
Copy_Plugins "$Plugins_Release_Path/net7.0-macos" "Accelerator" "BD.WTTS.Client.Plugins.Accelerator.dll"
|
||||
dotnet publish -c Release -p:PublishDir="$Publish_Path" -f net8.0-macos
|
||||
Copy_Plugins "$Plugins_Release_Path/net8.0-macos" "Accelerator" "BD.WTTS.Client.Plugins.Accelerator.dll"
|
||||
|
||||
Plugins_Accelerator_ReverseProxy_Path="$Base_Path/src/BD.WTTS.Client.Plugins.Accelerator.ReverseProxy"
|
||||
cd "$Plugins_Accelerator_ReverseProxy_Path"
|
||||
# 根据 Build_Mode 执行不同逻辑
|
||||
if [ "$Build_Mode" = "any" ]; then
|
||||
dotnet publish -c Release -p:PublishDir="$Publish_Path/arm64" -f net7.0 -r osx-arm64 -p:PublishSingleFile=true --self-contained -p:EnableCodeSigning=true -p:CodesignKey="$CodesignKey_Name"
|
||||
dotnet publish -c Release -p:PublishDir="$Publish_Path/x64" -f net7.0 -r osx-x64 -p:PublishSingleFile=true --self-contained -p:EnableCodeSigning=true -p:CodesignKey="$CodesignKey_Name"
|
||||
dotnet publish -c Release -p:PublishDir="$Publish_Path/arm64" -f net8.0 -r osx-arm64 -p:PublishSingleFile=true --self-contained -p:EnableCodeSigning=true -p:CodesignKey="$CodesignKey_Name"
|
||||
dotnet publish -c Release -p:PublishDir="$Publish_Path/x64" -f net8.0 -r osx-x64 -p:PublishSingleFile=true --self-contained -p:EnableCodeSigning=true -p:CodesignKey="$CodesignKey_Name"
|
||||
lipo -create "$Publish_Path/arm64/Steam++.Accelerator" "$Publish_Path/x64/Steam++.Accelerator" -output "$Publish_Path/Steam++.Accelerator"
|
||||
Copy_Plugins "$Publish_Path" "Accelerator" "Steam++.Accelerator"
|
||||
elif [ "$Build_Mode" = "arm64" ]; then
|
||||
dotnet publish -c Release -p:PublishDir="$Publish_Path/arm64" -f net7.0 -r osx-arm64 -p:PublishSingleFile=true --self-contained -p:EnableCodeSigning=true -p:CodesignKey="$CodesignKey_Name"
|
||||
dotnet publish -c Release -p:PublishDir="$Publish_Path/arm64" -f net8.0 -r osx-arm64 -p:PublishSingleFile=true --self-contained -p:EnableCodeSigning=true -p:CodesignKey="$CodesignKey_Name"
|
||||
Copy_Plugins "$Publish_Path/arm64" "Accelerator" "Steam++.Accelerator"
|
||||
elif [ "$Build_Mode" = "x64" ]; then
|
||||
dotnet publish -c Release -p:PublishDir="$Publish_Path/x64" -f net7.0 -r osx-x64 -p:PublishSingleFile=true --self-contained -p:EnableCodeSigning=true -p:CodesignKey="$CodesignKey_Name"
|
||||
dotnet publish -c Release -p:PublishDir="$Publish_Path/x64" -f net8.0 -r osx-x64 -p:PublishSingleFile=true --self-contained -p:EnableCodeSigning=true -p:CodesignKey="$CodesignKey_Name"
|
||||
Copy_Plugins "$Publish_Path/x64" "Accelerator" "Steam++.Accelerator"
|
||||
fi
|
||||
|
||||
echo "编译令牌插件"
|
||||
Plugins_Authenticator_Path="$Base_Path/src/BD.WTTS.Client.Plugins.Authenticator"
|
||||
cd "$Plugins_Authenticator_Path"
|
||||
dotnet publish -c Release -p:PublishDir="$Publish_Path" -f net7.0-macos -p:EnableCodeSigning=true -p:CodesignKey="$CodesignKey_Name"
|
||||
Copy_Plugins "$Plugins_Release_Path/net7.0-macos" "Authenticator" "BD.WTTS.Client.Plugins.Authenticator.dll"
|
||||
dotnet publish -c Release -p:PublishDir="$Publish_Path" -f net8.0-macos -p:EnableCodeSigning=true -p:CodesignKey="$CodesignKey_Name"
|
||||
Copy_Plugins "$Plugins_Release_Path/net8.0-macos" "Authenticator" "BD.WTTS.Client.Plugins.Authenticator.dll"
|
||||
|
||||
echo "编译账户插件"
|
||||
Plugins_GameAccount_Path="$Base_Path/src/BD.WTTS.Client.Plugins.GameAccount"
|
||||
cd "$Plugins_GameAccount_Path"
|
||||
dotnet publish -c Release -p:PublishDir="$Publish_Path" -f net7.0-macos -p:EnableCodeSigning=true -p:CodesignKey="$CodesignKey_Name"
|
||||
Copy_Plugins "$Plugins_Release_Path/net7.0-macos" "GameAccount" "BD.WTTS.Client.Plugins.GameAccount.dll"
|
||||
dotnet publish -c Release -p:PublishDir="$Publish_Path" -f net8.0-macos -p:EnableCodeSigning=true -p:CodesignKey="$CodesignKey_Name"
|
||||
Copy_Plugins "$Plugins_Release_Path/net8.0-macos" "GameAccount" "BD.WTTS.Client.Plugins.GameAccount.dll"
|
||||
|
||||
echo "编译游戏库插件"
|
||||
Plugins_GameList_Path="$Base_Path/src/BD.WTTS.Client.Plugins.GameList"
|
||||
cd "$Plugins_GameList_Path"
|
||||
dotnet publish -c Release -p:PublishDir="$Publish_Path" -f net7.0-macos -p:EnableCodeSigning=true -p:CodesignKey="$CodesignKey_Name"
|
||||
Copy_Plugins "$Plugins_Release_Path/net7.0-macos" "GameList" "BD.WTTS.Client.Plugins.GameList.dll"
|
||||
dotnet publish -c Release -p:PublishDir="$Publish_Path" -f net8.0-macos -p:EnableCodeSigning=true -p:CodesignKey="$CodesignKey_Name"
|
||||
Copy_Plugins "$Plugins_Release_Path/net8.0-macos" "GameList" "BD.WTTS.Client.Plugins.GameList.dll"
|
||||
|
||||
echo "复制插件到程序目录"
|
||||
cd "$App_Path"
|
||||
|
||||
# 根据 Build_Mode 执行不同逻辑
|
||||
if [ "$Build_Mode" = "any" ]; then
|
||||
cp -r "$App_Modules_Path" "$Plugins_Release_Path/net7.0-macos/Steam++.app/Contents/MonoBundle"
|
||||
cp -r "$App_Modules_Path" "$Plugins_Release_Path/net8.0-macos/Steam++.app/Contents/MonoBundle"
|
||||
elif [ "$Build_Mode" = "arm64" ]; then
|
||||
cp -r "$App_Modules_Path" "$Plugins_Release_Path/net7.0-macos/osx-arm64/Steam++.app/Contents/MonoBundle"
|
||||
cp -r "$Plugins_Release_Path/net7.0-macos/Steam++.app/Contents/Resources" "$Plugins_Release_Path/net7.0-macos/Steam++.app/Contents"
|
||||
cp -r "$App_Modules_Path" "$Plugins_Release_Path/net8.0-macos/osx-arm64/Steam++.app/Contents/MonoBundle"
|
||||
cp -r "$Plugins_Release_Path/net8.0-macos/Steam++.app/Contents/Resources" "$Plugins_Release_Path/net8.0-macos/Steam++.app/Contents"
|
||||
elif [ "$Build_Mode" = "x64" ]; then
|
||||
cp -r "$App_Modules_Path" "$Plugins_Release_Path/net7.0-macos/osx-x64/Steam++.app/Contents/MonoBundle"
|
||||
cp -r "$Plugins_Release_Path/net7.0-macos/Steam++.app/Contents/Resources" "$Plugins_Release_Path/net7.0-macos/Steam++.app/Contents"
|
||||
cp -r "$App_Modules_Path" "$Plugins_Release_Path/net8.0-macos/osx-x64/Steam++.app/Contents/MonoBundle"
|
||||
cp -r "$Plugins_Release_Path/net8.0-macos/Steam++.app/Contents/Resources" "$Plugins_Release_Path/net8.0-macos/Steam++.app/Contents"
|
||||
fi
|
||||
|
||||
echo "代码签名重新手动签名"
|
||||
App_Publish_Path="$App_Path/bin/Release/net7.0-macos/Steam++.app"
|
||||
App_Publish_Path="$App_Path/bin/Release/net8.0-macos/Steam++.app"
|
||||
# #代码签名重新手动签名
|
||||
codesign --remove-signature "$App_Publish_Path"
|
||||
codesign --remove-signature "$App_Publish_Path/Contents/MacOS/Steam++"
|
||||
|
||||
@@ -964,6 +964,209 @@
|
||||
}
|
||||
],
|
||||
"Usings": ""
|
||||
},
|
||||
"SteamIdleSettings": {
|
||||
"ClassFilePath": "..\\src\\BD.WTTS.Client.Plugins.SteamIdleCard\\Settings",
|
||||
"InterfaceFilePath": "..\\src\\BD.WTTS.Client.Plugins.SteamIdleCard\\Settings\\Abstractions",
|
||||
"Properties": [
|
||||
{
|
||||
"TypeName": "TimeSpan",
|
||||
"PropertyName": "IdleTime",
|
||||
"DefaultValue": "TimeSpan.FromMinutes(6)",
|
||||
"DefaultValueIsConst": false,
|
||||
"Summary": "挂卡状态更新时间",
|
||||
"IsRegionOrEndregion": null,
|
||||
"IsValueType": true
|
||||
},
|
||||
{
|
||||
"TypeName": "IdleRule",
|
||||
"PropertyName": "IdleRule",
|
||||
"DefaultValue": "IdleRule.FastMode",
|
||||
"DefaultValueIsConst": false,
|
||||
"Summary": "运行规则",
|
||||
"IsRegionOrEndregion": null,
|
||||
"IsValueType": true
|
||||
},
|
||||
{
|
||||
"TypeName": "IdleSequentital",
|
||||
"PropertyName": "IdleSequentital",
|
||||
"DefaultValue": "IdleSequentital.Default",
|
||||
"DefaultValueIsConst": false,
|
||||
"Summary": "运行顺序",
|
||||
"IsRegionOrEndregion": null,
|
||||
"IsValueType": true
|
||||
},
|
||||
{
|
||||
"TypeName": "int",
|
||||
"PropertyName": "MaxIdleCount",
|
||||
"DefaultValue": "30",
|
||||
"DefaultValueIsConst": false,
|
||||
"Summary": "最大并行运行游戏数量",
|
||||
"IsRegionOrEndregion": null,
|
||||
"IsValueType": true
|
||||
},
|
||||
{
|
||||
"TypeName": "double",
|
||||
"PropertyName": "MinRunTime",
|
||||
"DefaultValue": "2",
|
||||
"DefaultValueIsConst": false,
|
||||
"Summary": "最少游戏时间 hours",
|
||||
"IsRegionOrEndregion": null,
|
||||
"IsValueType": true
|
||||
},
|
||||
{
|
||||
"TypeName": "double",
|
||||
"PropertyName": "SwitchTime",
|
||||
"DefaultValue": "5000",
|
||||
"DefaultValueIsConst": false,
|
||||
"Summary": "自动切换游戏时间间隔 ms",
|
||||
"IsRegionOrEndregion": null,
|
||||
"IsValueType": true
|
||||
},
|
||||
{
|
||||
"TypeName": "double",
|
||||
"PropertyName": "RefreshBadgesTime",
|
||||
"DefaultValue": "6",
|
||||
"DefaultValueIsConst": false,
|
||||
"Summary": "自动刷新徽章数据时间间隔 min",
|
||||
"IsRegionOrEndregion": null,
|
||||
"IsValueType": true
|
||||
}
|
||||
],
|
||||
"Usings": ""
|
||||
},
|
||||
"ASFSettings": {
|
||||
"ClassFilePath": "..\\src\\BD.WTTS.Client.Plugins.ArchiSteamFarmPlus\\Settings",
|
||||
"InterfaceFilePath": "..\\src\\BD.WTTS.Client.Plugins.ArchiSteamFarmPlus\\Settings\\Abstractions",
|
||||
"Properties": [
|
||||
{
|
||||
"TypeName": "string",
|
||||
"PropertyName": "ArchiSteamFarmExePath",
|
||||
"DefaultValue": "string.Empty",
|
||||
"DefaultValueIsConst": false,
|
||||
"Summary": "ASF 路径",
|
||||
"IsRegionOrEndregion": null,
|
||||
"IsValueType": false
|
||||
},
|
||||
{
|
||||
"TypeName": "bool",
|
||||
"PropertyName": "AutoRunArchiSteamFarm",
|
||||
"DefaultValue": "false",
|
||||
"DefaultValueIsConst": false,
|
||||
"Summary": "程序启动时自动运行 ASF",
|
||||
"IsRegionOrEndregion": null,
|
||||
"IsValueType": true
|
||||
},
|
||||
{
|
||||
"TypeName": "bool",
|
||||
"PropertyName": "CheckArchiSteamFarmExe",
|
||||
"DefaultValue": "false",
|
||||
"DefaultValueIsConst": false,
|
||||
"Summary": "检查文件安全性",
|
||||
"IsRegionOrEndregion": null,
|
||||
"IsValueType": true
|
||||
},
|
||||
{
|
||||
"TypeName": "int",
|
||||
"PropertyName": "ConsoleMaxLineDefault",
|
||||
"DefaultValue": "200",
|
||||
"DefaultValueIsConst": true,
|
||||
"Summary": "控制台默认最大行数",
|
||||
"IsRegionOrEndregion": null,
|
||||
"IsValueType": true
|
||||
},
|
||||
{
|
||||
"TypeName": "int",
|
||||
"PropertyName": "MinRangeConsoleMaxLine",
|
||||
"DefaultValue": "DefaultConsoleMaxLineDefault",
|
||||
"DefaultValueIsConst": true,
|
||||
"Summary": "控制台默认最大行数范围最小值",
|
||||
"IsRegionOrEndregion": null,
|
||||
"IsValueType": true
|
||||
},
|
||||
{
|
||||
"TypeName": "int",
|
||||
"PropertyName": "MaxRangeConsoleMaxLine",
|
||||
"DefaultValue": "5000",
|
||||
"DefaultValueIsConst": true,
|
||||
"Summary": "控制台默认最大行数范围最大值",
|
||||
"IsRegionOrEndregion": null,
|
||||
"IsValueType": true
|
||||
},
|
||||
{
|
||||
"TypeName": "int",
|
||||
"PropertyName": "ConsoleMaxLine",
|
||||
"DefaultValue": "DefaultConsoleMaxLineDefault",
|
||||
"DefaultValueIsConst": false,
|
||||
"Summary": "控制台最大行数",
|
||||
"IsRegionOrEndregion": null,
|
||||
"IsValueType": true
|
||||
},
|
||||
{
|
||||
"TypeName": "int",
|
||||
"PropertyName": "ConsoleFontSizeDefault",
|
||||
"DefaultValue": "14",
|
||||
"DefaultValueIsConst": true,
|
||||
"Summary": "",
|
||||
"IsRegionOrEndregion": null,
|
||||
"IsValueType": true
|
||||
},
|
||||
{
|
||||
"TypeName": "int",
|
||||
"PropertyName": "MinRangeConsoleFontSize",
|
||||
"DefaultValue": "8",
|
||||
"DefaultValueIsConst": true,
|
||||
"Summary": "",
|
||||
"IsRegionOrEndregion": null,
|
||||
"IsValueType": true
|
||||
},
|
||||
{
|
||||
"TypeName": "int",
|
||||
"PropertyName": "MaxRangeConsoleFontSize",
|
||||
"DefaultValue": "24",
|
||||
"DefaultValueIsConst": true,
|
||||
"Summary": "",
|
||||
"IsRegionOrEndregion": null,
|
||||
"IsValueType": true
|
||||
},
|
||||
{
|
||||
"TypeName": "int",
|
||||
"PropertyName": "ConsoleFontSize",
|
||||
"DefaultValue": "DefaultConsoleFontSizeDefault",
|
||||
"DefaultValueIsConst": false,
|
||||
"Summary": "控制台字体大小",
|
||||
"IsRegionOrEndregion": null,
|
||||
"IsValueType": true
|
||||
},
|
||||
{
|
||||
"TypeName": "int",
|
||||
"PropertyName": "IPCPortIdValue",
|
||||
"DefaultValue": "6242",
|
||||
"DefaultValueIsConst": true,
|
||||
"Summary": "",
|
||||
"IsRegionOrEndregion": null,
|
||||
"IsValueType": null
|
||||
},
|
||||
{
|
||||
"TypeName": "int",
|
||||
"PropertyName": "IPCPortId",
|
||||
"DefaultValue": "DefaultIPCPortIdValue",
|
||||
"DefaultValueIsConst": false,
|
||||
"Summary": "IPC 端口号,默认值为 <see cref=\"DefaultIPCPortIdValue\" />",
|
||||
"IsRegionOrEndregion": null,
|
||||
"IsValueType": true
|
||||
},
|
||||
{
|
||||
"TypeName": "bool",
|
||||
"PropertyName": "IPCPortOccupiedRandom",
|
||||
"DefaultValue": "true",
|
||||
"DefaultValueIsConst": false,
|
||||
"Summary": "IPC 端口号被占用时是否随机一个未使用的端口号,默认值 <see langword=\"true\" />",
|
||||
"IsRegionOrEndregion": null,
|
||||
"IsValueType": true
|
||||
}
|
||||
],
|
||||
"Usings": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-1
Submodule ref/Avalonia.Image2 updated: aa4914e2f5...c4162f332f
+1
-1
Submodule ref/Common updated: e258b48fbf...4fd40563b7
+1
-1
Submodule ref/DirectoryPackages updated: 0972067aba...6974b6215e
Submodule ref/SteamAchievementManager updated: bfcf236fbc...4c578c1cc4
+1
-1
Submodule ref/SteamClient updated: 866d8ac88d...079a2c22ef
Submodule ref/WTTS.MicroServices.ClientSDK updated: 0f04842586...29a5a63dcb
+1
-1
Submodule ref/WinAuth updated: 06269f56a1...d3f45cffe2
Binary file not shown.
Binary file not shown.
@@ -21,7 +21,7 @@ public static partial class AssemblyInfo
|
||||
/// <summary>
|
||||
/// RC 版本号,范围 0~9,只增不减
|
||||
/// </summary>
|
||||
const string ver_for_rc = "3";
|
||||
const string ver_for_rc = "5";
|
||||
|
||||
public const string FileVersion = $"{Version2}.{ver_for_rc}0{ver_for_preview}.0";
|
||||
|
||||
|
||||
@@ -22,6 +22,9 @@ global using Avalonia.Metadata;
|
||||
[assembly: XmlnsDefinition("https://steampp.net/ui", "BD.WTTS.Converters")]
|
||||
[assembly: XmlnsDefinition("https://steampp.net/ui", "BD.WTTS.Markup")]
|
||||
[assembly: XmlnsDefinition("https://steampp.net/ui", "BD.WTTS.Client.Resources")]
|
||||
[assembly: XmlnsDefinition("https://steampp.net/ui", "Mobius.Models")]
|
||||
[assembly: XmlnsDefinition("https://steampp.net/ui", "Mobius")]
|
||||
|
||||
[assembly: XmlnsDefinition("https://steampp.net/services", "BD.WTTS.Services")]
|
||||
[assembly: XmlnsDefinition("https://steampp.net/services", "BD.WTTS.Plugins")]
|
||||
[assembly: XmlnsDefinition("https://steampp.net/services", "BD.WTTS.Plugins.Abstractions")]
|
||||
|
||||
@@ -10,6 +10,6 @@
|
||||
<legacyCorruptedStateExceptionsPolicy enabled="true"/>
|
||||
</runtime>
|
||||
<appSettings>
|
||||
<add key="d" value="7.0.11"/>
|
||||
<add key="d" value="8.0.0"/>
|
||||
</appSettings>
|
||||
</configuration>
|
||||
@@ -16,9 +16,9 @@ static unsafe partial class Program
|
||||
{
|
||||
static Program()
|
||||
{
|
||||
dotnet_version_major = "7";
|
||||
dotnet_version_major = "8";
|
||||
dotnet_version_minor = "0";
|
||||
dotnet_version_build = "11";
|
||||
dotnet_version_build = "0";
|
||||
dotnet_version = $"{dotnet_version_major}.{dotnet_version_minor}.{dotnet_version_build}";
|
||||
}
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1"><assemblyIdentity version="1.0.0.0" name="BeyondDimension.WattToolkit.AppHost"/><trustInfo xmlns="urn:schemas-microsoft-com:asm.v2"><security><requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3"><requestedExecutionLevel level="asInvoker" uiAccess="false" /></requestedPrivileges></security></trustInfo><compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1"><application><supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}" /><supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}" /><supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}" /><supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}" /><supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" /></application></compatibility></assembly>
|
||||
<?xml version="1.0" encoding="utf-8"?><assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1"><assemblyIdentity version="1.0.0.0" name="BeyondDimension.WattToolkit.AppHost"/><trustInfo xmlns="urn:schemas-microsoft-com:asm.v2"><security><requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3"><requestedExecutionLevel level="requireAdministrator" uiAccess="false" /></requestedPrivileges></security></trustInfo><compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1"><application><supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}" /><supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}" /><supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}" /><supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}" /><supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" /></application></compatibility></assembly>
|
||||
@@ -98,14 +98,14 @@
|
||||
<ProjectReference Include="..\BD.WTTS.Client\BD.WTTS.Client.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'windows' AND Exists('$(NugetPackageRoot)\system.drawing.common\$(Version_System_Drawing_Common)\runtimes\win\lib\net7.0\System.Drawing.Common.dll')">
|
||||
<ItemGroup Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'windows' AND Exists('$(NugetPackageRoot)\system.drawing.common\$(Version_System_Drawing_Common)\lib\net8.0\System.Drawing.Common.dll')">
|
||||
<!-- Windows Only -->
|
||||
<!-- 修复:存在 Microsoft.WindowsDesktop.App 依赖时不会将 System.Drawing.Common.dll 复制到输出目录-->
|
||||
<None Include="$(NugetPackageRoot)\system.drawing.common\$(Version_System_Drawing_Common)\runtimes\win\lib\net7.0\System.Drawing.Common.dll">
|
||||
<None Include="$(NugetPackageRoot)\system.drawing.common\$(Version_System_Drawing_Common)\lib\net8.0\System.Drawing.Common.dll">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<Reference Include="System.Drawing.Common">
|
||||
<HintPath>$(NugetPackageRoot)\system.drawing.common\$(Version_System_Drawing_Common)\runtimes\win\lib\net7.0\System.Drawing.Common.dll</HintPath>
|
||||
<HintPath>$(NugetPackageRoot)\system.drawing.common\$(Version_System_Drawing_Common)\lib\net8.0\System.Drawing.Common.dll</HintPath>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
@@ -22,6 +22,8 @@
|
||||
<string>NSApplication</string>
|
||||
<key>XSAppIconAssets</key>
|
||||
<string>Assets.xcassets/AppIcon.appiconset</string>
|
||||
<key>CFBundleIconFile</key>
|
||||
<string>AppIcon.icns</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>net.steampp.app</string>
|
||||
<key>CFBundleURLTypes</key>
|
||||
|
||||
@@ -1 +1 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1"><assemblyIdentity version="1.0.0.0" name="BeyondDimension.WattToolkit.App"/><trustInfo xmlns="urn:schemas-microsoft-com:asm.v2"><security><requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3"><requestedExecutionLevel level="asInvoker" uiAccess="false" /></requestedPrivileges></security></trustInfo><compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1"><application><supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}" /><supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}" /><supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}" /><supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}" /><supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" /></application></compatibility></assembly>
|
||||
<?xml version="1.0" encoding="utf-8"?><assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1"><assemblyIdentity version="1.0.0.0" name="BeyondDimension.WattToolkit.App"/><trustInfo xmlns="urn:schemas-microsoft-com:asm.v2"><security><requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3"><requestedExecutionLevel level="requireAdministrator" uiAccess="false" /></requestedPrivileges></security></trustInfo><compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1"><application><supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}" /><supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}" /><supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}" /><supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}" /><supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" /></application></compatibility></assembly>
|
||||
@@ -126,6 +126,12 @@
|
||||
<ProjectReference Include="..\BD.WTTS.Client\BD.WTTS.Client.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Update="UI\Views\Controls\Widgets\CarouselItems.cs">
|
||||
<DependentUpon>%(Filename)</DependentUpon>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
|
||||
<Import Project="..\TFM_NETX_WITH_DESKTOP.props" />
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -18,6 +18,8 @@ public sealed class EnumLocalizationNameConverter : IValueConverter
|
||||
else
|
||||
{
|
||||
text = Strings.ResourceManager.GetString(desc, Strings.Culture);
|
||||
if (string.IsNullOrEmpty(text))
|
||||
return desc;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(text))
|
||||
|
||||
+7
-3
@@ -1,19 +1,23 @@
|
||||
namespace BD.WTTS.Converters;
|
||||
|
||||
public sealed class VisbleStringConverter : IMultiValueConverter
|
||||
public sealed class VisibleStringConverter : IMultiValueConverter
|
||||
{
|
||||
public object? Convert(IList<object?> values, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
if (values.Count >= 3 && values.FirstOrDefault() is bool isVisble)
|
||||
if (values.Count >= 2 && values.FirstOrDefault() is bool isVisble)
|
||||
{
|
||||
if (isVisble)
|
||||
{
|
||||
return values[1];
|
||||
}
|
||||
else
|
||||
else if (values.Count >= 3)
|
||||
{
|
||||
return values[2];
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return values;
|
||||
}
|
||||
@@ -2,18 +2,11 @@ namespace BD.WTTS.UI;
|
||||
|
||||
public static class ControlExtensions
|
||||
{
|
||||
public static void SetViewModel<T>(this Control c, bool useCache = true) where T : ViewModelBase
|
||||
public static void SetViewModel<T>(this Control c) where T : ViewModelBase, new()
|
||||
{
|
||||
if (c.DataContext == null || c.DataContext.GetType() != typeof(T))
|
||||
{
|
||||
T cx;
|
||||
|
||||
if (useCache)
|
||||
cx = IViewModelManager.Instance.Get<T>();
|
||||
else
|
||||
cx = Activator.CreateInstance<T>();
|
||||
|
||||
c.DataContext = cx;
|
||||
c.DataContext = new T();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
namespace BD.WTTS;
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="IEnumerable"/> extensions methods
|
||||
/// </summary>
|
||||
public static class IEnumerableExtensions
|
||||
{
|
||||
public static IEnumerable<IEnumerable> Batch(this IEnumerable source, int size)
|
||||
{
|
||||
object[]? bucket = null;
|
||||
var count = 0;
|
||||
|
||||
foreach (var item in source)
|
||||
{
|
||||
bucket ??= new object[size];
|
||||
|
||||
bucket[count++] = item;
|
||||
|
||||
if (count == size)
|
||||
{
|
||||
yield return bucket;
|
||||
bucket = null;
|
||||
count = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (bucket != null && count > 0)
|
||||
yield return bucket.Take(count).ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -4,7 +4,7 @@ using System.Collections.Immutable;
|
||||
namespace BD.WTTS.Services.Implementation;
|
||||
|
||||
/// <inheritdoc cref="IWindowManager"/>
|
||||
sealed class AvaloniaWindowManagerImpl : IWindowManagerImpl
|
||||
public sealed class AvaloniaWindowManagerImpl : IWindowManagerImpl
|
||||
{
|
||||
Type? IWindowManagerImpl.WindowType => typeof(Window);
|
||||
|
||||
@@ -116,7 +116,7 @@ sealed class AvaloniaWindowManagerImpl : IWindowManagerImpl
|
||||
|
||||
if (viewModel is IWindowViewModel window)
|
||||
{
|
||||
window.Close = () => td?.Hide(TaskDialogStandardResult.Close);
|
||||
window.Close = b => td?.Hide(b ? TaskDialogStandardResult.OK : TaskDialogStandardResult.Close);
|
||||
}
|
||||
|
||||
td.DataContext = viewModel;
|
||||
|
||||
@@ -88,7 +88,7 @@ partial class App
|
||||
new Binding { Path = "Res.CloseSteam", Source = ResourceService.Current, Mode = BindingMode.OneWay },
|
||||
new Binding { Path = "Res.StartSteam", Source = ResourceService.Current, Mode = BindingMode.OneWay },
|
||||
},
|
||||
Converter = (VisbleStringConverter)App.Instance.FindResource("VisbleStringConverter")!,
|
||||
Converter = (VisibleStringConverter)App.Instance.FindResource(nameof(VisibleStringConverter))!,
|
||||
},
|
||||
Command = ReactiveCommand.Create(async () =>
|
||||
{
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
<spp:EnumEqualValueConverter x:Key="EnumEqualValueConverter" />
|
||||
<spp:StringEqualsConverter x:Key="StringEqualsConverter" />
|
||||
<spp:EnumLocalizationNameConverter x:Key="EnumLocalizationNameConverter" />
|
||||
<spp:VisbleStringConverter x:Key="VisbleStringConverter" />
|
||||
<spp:VisibleStringConverter x:Key="VisibleStringConverter" />
|
||||
</Application.Resources>
|
||||
<Application.Styles>
|
||||
<sty:FluentAvaloniaTheme PreferSystemTheme="False" PreferUserAccentColor="True" />
|
||||
|
||||
@@ -46,6 +46,7 @@ public sealed partial class App : Application
|
||||
#if WINDOWS || LINUX || MACOS
|
||||
if (GeneralSettings.MinimizeOnStartup.Value)
|
||||
Startup.Instance.IsMinimize = true;
|
||||
|
||||
if (Startup.Instance.IsSteamRun)
|
||||
{
|
||||
try
|
||||
@@ -93,15 +94,16 @@ public sealed partial class App : Application
|
||||
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
||||
{
|
||||
MainWindow = InitializeMainWindow?.Invoke(this) ?? new MainWindow();
|
||||
|
||||
desktop.MainWindow =
|
||||
#if !UI_DEMO
|
||||
Startup.Instance.IsMinimize ? null :
|
||||
Startup.Instance.IsMinimize && MainWindow is MainWindow ? null :
|
||||
#endif
|
||||
MainWindow;
|
||||
|
||||
if (Startup.Instance.IsMinimize && MainWindow is AppWindow appWindow)
|
||||
{
|
||||
Task2.InBackground(() => appWindow.SplashScreen.RunTasks(CancellationToken.None));
|
||||
Task2.InBackground(() => appWindow.SplashScreen?.RunTasks(CancellationToken.None));
|
||||
}
|
||||
}
|
||||
else if (ApplicationLifetime is ISingleViewApplicationLifetime singleView)
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 2.3 KiB |
@@ -13,8 +13,9 @@
|
||||
<Thickness x:Key="PageMargin">10</Thickness>
|
||||
<sys:Double x:Key="SettingsSpacing">3</sys:Double>
|
||||
|
||||
<!--<sys:Char x:Key="PasswordChar">●</sys:Char>-->
|
||||
<sys:Char x:Key="PasswordChar">•</sys:Char>
|
||||
<sys:Char x:Key="PasswordChar">●</sys:Char>
|
||||
<!--<sys:Char x:Key="PasswordChar">•</sys:Char>
|
||||
<sys:Char x:Key="PasswordChar">·</sys:Char>-->
|
||||
<FontFamily x:Key="ContentControlThemeFontFamily">Null</FontFamily>
|
||||
<BoxShadows x:Key="InfoBoxShadow">4 4 32 0 #66000000</BoxShadows>
|
||||
|
||||
@@ -44,6 +45,7 @@
|
||||
|
||||
<StyleInclude Source="/UI/Styling/InfoBox.axaml" />
|
||||
<StyleInclude Source="/UI/Styling/Controls/PageBase.axaml" />
|
||||
<StyleInclude Source="/UI/Styling/Controls/CarouselItems.axaml" />
|
||||
<StyleInclude Source="/UI/Styling/Controls/OptionsDisplayItem.axaml" />
|
||||
<StyleInclude Source="/UI/Styling/Controls/RippleEffect.axaml" />
|
||||
<StyleInclude Source="/UI/Styling/Controls/Draggable.axaml" />
|
||||
|
||||
@@ -180,6 +180,7 @@
|
||||
<TextBlock
|
||||
Grid.Row="2"
|
||||
Grid.Column="1"
|
||||
Grid.ColumnSpan="2"
|
||||
Margin="0,5"
|
||||
VerticalAlignment="Top"
|
||||
Classes="CaptionTextBlockStyle"
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
<!-- AutoCompleteBox -->
|
||||
<Style Selector="AutoCompleteBox.Search /template/ TextBox#PART_TextBox">
|
||||
<Setter Property="Watermark" Value="{ReflectionBinding Path=Res.Search, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
|
||||
<Setter Property="Watermark" Value="{Binding Path=Res.Search, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
|
||||
<Setter Property="InnerRightContent">
|
||||
<Template>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
<Styles
|
||||
xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:generic="using:System.Collections.Generic"
|
||||
xmlns:spp="https://steampp.net/ui"
|
||||
xmlns:sys="using:System"
|
||||
xmlns:ui="using:FluentAvalonia.UI.Controls"
|
||||
x:CompileBindings="True">
|
||||
<Design.PreviewWith>
|
||||
<Border Padding="20">
|
||||
<spp:CarouselItems>
|
||||
<spp:CarouselItems.Items>
|
||||
<Border Background="Red" />
|
||||
<Border Background="Blue" />
|
||||
<Border Background="Black" />
|
||||
<Border Background="Yellow" />
|
||||
</spp:CarouselItems.Items>
|
||||
</spp:CarouselItems>
|
||||
</Border>
|
||||
</Design.PreviewWith>
|
||||
|
||||
<Style Selector="spp|CarouselItems">
|
||||
<Setter Property="Template">
|
||||
<ControlTemplate>
|
||||
<Border
|
||||
BorderBrush="{DynamicResource CardStrokeColorDefaultBrush}"
|
||||
BorderThickness="0"
|
||||
ClipToBounds="True"
|
||||
CornerRadius="8">
|
||||
<Grid RowDefinitions="*,5,Auto">
|
||||
<Carousel Name="CarouselControl" Classes="AnimeSlide">
|
||||
<Carousel.ItemTemplate>
|
||||
<DataTemplate DataType="sys:Object">
|
||||
<ItemsControl ItemTemplate="{Binding $parent[spp:CarouselItems].ItemTemplate}" ItemsSource="{Binding}">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<spp:FixedWrapPanel ItemsPerLine="{Binding $parent[spp:CarouselItems].ItemsPerPage}" Spacing="5" />
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
</ItemsControl>
|
||||
</DataTemplate>
|
||||
</Carousel.ItemTemplate>
|
||||
</Carousel>
|
||||
<Button
|
||||
Name="Left"
|
||||
HorizontalAlignment="Left"
|
||||
Background="#80000000"
|
||||
Classes="action"
|
||||
IsVisible="False">
|
||||
<ui:SymbolIcon FontSize="18" Symbol="ChevronLeft" />
|
||||
</Button>
|
||||
<Button
|
||||
Name="Right"
|
||||
HorizontalAlignment="Right"
|
||||
Background="#80000000"
|
||||
Classes="action"
|
||||
IsVisible="False">
|
||||
<ui:SymbolIcon FontSize="18" Symbol="ChevronRight" />
|
||||
</Button>
|
||||
|
||||
<Panel
|
||||
Grid.Row="2"
|
||||
Height="10"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Bottom">
|
||||
<ItemsRepeater Name="Swiper" IsVisible="False">
|
||||
<ItemsRepeater.Layout>
|
||||
<StackLayout
|
||||
DisableVirtualization="True"
|
||||
Orientation="Horizontal"
|
||||
Spacing="8" />
|
||||
</ItemsRepeater.Layout>
|
||||
<ItemsRepeater.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<DataTemplate.DataType>
|
||||
<x:Type x:TypeArguments="x:Int32, x:String" TypeName="generic:KeyValuePair" />
|
||||
</DataTemplate.DataType>
|
||||
<Button
|
||||
Padding="0"
|
||||
Classes="Plain"
|
||||
Command="{Binding $parent[spp:CarouselItems].CarouselBannerIndexCommand}"
|
||||
CommandParameter="{Binding Key}">
|
||||
<Ellipse
|
||||
Name="IndexEllipse"
|
||||
Width="10"
|
||||
Height="10"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Fill="{Binding Value}"
|
||||
Opacity="0.6"
|
||||
StrokeThickness="0" />
|
||||
</Button>
|
||||
</DataTemplate>
|
||||
</ItemsRepeater.ItemTemplate>
|
||||
</ItemsRepeater>
|
||||
</Panel>
|
||||
</Grid>
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
|
||||
<Style Selector="spp|CarouselItems /template/ Button.action">
|
||||
<Setter Property="Width" Value="36" />
|
||||
<Setter Property="Height" Value="64" />
|
||||
<Setter Property="VerticalAlignment" Value="Center" />
|
||||
<Setter Property="HorizontalContentAlignment" Value="Center" />
|
||||
<Setter Property="VerticalContentAlignment" Value="Center" />
|
||||
<Setter Property="Foreground" Value="White" />
|
||||
</Style>
|
||||
</Styles>
|
||||
@@ -75,19 +75,29 @@
|
||||
</ContentPresenter.IsVisible>-->
|
||||
</ContentPresenter>
|
||||
|
||||
<ContentPresenter
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Content="{TemplateBinding NoResultMessage}"
|
||||
Foreground="{TemplateBinding Foreground}"
|
||||
Theme="{StaticResource SubtitleTextBlockStyle}">
|
||||
<ContentPresenter.IsVisible>
|
||||
<Panel HorizontalAlignment="Center" VerticalAlignment="Center">
|
||||
<spp:Image2
|
||||
Width="100"
|
||||
Height="100"
|
||||
Margin="0,0,0,120"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
RenderOptions.BitmapInterpolationMode="HighQuality"
|
||||
Source="avares://BD.WTTS.Client.Avalonia/UI/Assets/Icons/empty.png"
|
||||
Stretch="UniformToFill" />
|
||||
<Panel.IsVisible>
|
||||
<MultiBinding Converter="{x:Static BoolConverters.And}">
|
||||
<Binding Path="IsShowNoResultText" RelativeSource="{RelativeSource TemplatedParent}" />
|
||||
<Binding Path="!IsLoading" RelativeSource="{RelativeSource TemplatedParent}" />
|
||||
</MultiBinding>
|
||||
</ContentPresenter.IsVisible>
|
||||
</ContentPresenter>
|
||||
</Panel.IsVisible>
|
||||
<ContentPresenter
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Content="{TemplateBinding NoResultMessage}"
|
||||
Foreground="{TemplateBinding Foreground}"
|
||||
Theme="{StaticResource SubtitleTextBlockStyle}" />
|
||||
</Panel>
|
||||
</Panel>
|
||||
</ControlTemplate>
|
||||
</Setter>
|
||||
|
||||
@@ -39,8 +39,8 @@
|
||||
ContentTemplate="{TemplateBinding ContentTemplate}" />
|
||||
<Panel
|
||||
Name="FilePickerTipBorder"
|
||||
MaxWidth="{ReflectionBinding $parent.Bounds.Width}"
|
||||
MaxHeight="{ReflectionBinding $parent.Bounds.Height}">
|
||||
MaxWidth="{Binding $parent.Bounds.Width}"
|
||||
MaxHeight="{Binding $parent.Bounds.Height}">
|
||||
<Border Background="{DynamicResource ControlOnImageFillColorSecondaryBrush}" Opacity="0.8">
|
||||
<StackPanel
|
||||
Margin="10,5"
|
||||
@@ -49,9 +49,7 @@
|
||||
<ui:SymbolIcon FontSize="30" Symbol="Download" />
|
||||
<TextBlock
|
||||
Foreground="{DynamicResource TextFillColorPrimaryBrush}"
|
||||
Text="{ReflectionBinding Path=Res.CustomFilePicker_Tip,
|
||||
Mode=OneWay,
|
||||
Source={x:Static s:ResourceService.Current}}"
|
||||
Text="{Binding Path=Res.CustomFilePicker_Tip, Mode=OneWay, Source={x:Static s:ResourceService.Current}}"
|
||||
TextAlignment="Center"
|
||||
Theme="{StaticResource BodyStrongTextBlockStyle}" />
|
||||
</StackPanel>
|
||||
|
||||
@@ -59,12 +59,10 @@
|
||||
<BrushTransition Property="BorderBrush" Duration="00:00:00.083" />
|
||||
</Transitions>
|
||||
</ui:FABorder.Transitions>
|
||||
<Grid
|
||||
MinHeight="{TemplateBinding MinHeight}"
|
||||
ColumnDefinitions="Auto,*,Auto,Auto"
|
||||
RowDefinitions="*,Auto">
|
||||
<Grid MinHeight="{TemplateBinding MinHeight}" ColumnDefinitions="Auto,*,Auto,Auto">
|
||||
<Viewbox
|
||||
Name="IconHost"
|
||||
Grid.RowSpan="2"
|
||||
Width="{DynamicResource SettingsExpanderItemIconSize}"
|
||||
Height="{DynamicResource SettingsExpanderItemIconSize}"
|
||||
HorizontalAlignment="Left"
|
||||
@@ -76,28 +74,31 @@
|
||||
Margin="{DynamicResource SettingsExpanderItemContentMargin}"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center">
|
||||
<TextBlock
|
||||
<ContentPresenter
|
||||
Name="HeaderText"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
Classes="BaseTextBlockStyle"
|
||||
Text="{TemplateBinding Header}"
|
||||
Content="{TemplateBinding Header}"
|
||||
TextTrimming="CharacterEllipsis" />
|
||||
|
||||
<TextBlock
|
||||
<ContentPresenter
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Top"
|
||||
HorizontalContentAlignment="Stretch"
|
||||
Classes="CaptionTextBlockStyle"
|
||||
Content="{TemplateBinding Description}"
|
||||
FontSize="12"
|
||||
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
|
||||
IsVisible="{TemplateBinding Description,
|
||||
Converter={StaticResource IsNullConverter},
|
||||
ConverterParameter=invert}"
|
||||
Text="{TemplateBinding Description}"
|
||||
TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
|
||||
<ContentPresenter
|
||||
Name="ActionButton"
|
||||
Grid.RowSpan="2"
|
||||
Grid.Column="2"
|
||||
Margin="{DynamicResource SettingsExpanderItemFooterMargin}"
|
||||
HorizontalAlignment="Right"
|
||||
@@ -106,6 +107,7 @@
|
||||
|
||||
<ui:SymbolIcon
|
||||
Name="Chevron"
|
||||
Grid.RowSpan="2"
|
||||
Grid.Column="3"
|
||||
Margin="24,4,0,4"
|
||||
VerticalAlignment="Center"
|
||||
|
||||
@@ -17,10 +17,7 @@
|
||||
Classes="Circle"
|
||||
Value="60" />
|
||||
<ProgressBar Width="150" IsIndeterminate="True" />
|
||||
<ProgressBar
|
||||
Width="150"
|
||||
Classes="notransitions"
|
||||
Value="50" />
|
||||
<ProgressBar Width="150" Value="50" />
|
||||
|
||||
<TextBlock Text="Material Progress" />
|
||||
<ProgressBar
|
||||
@@ -31,7 +28,6 @@
|
||||
<ProgressBar
|
||||
Width="150"
|
||||
Height="5"
|
||||
Classes="Material notransitions"
|
||||
Value="50" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
@@ -58,7 +54,7 @@
|
||||
<Setter Property="Width" Value="{DynamicResource ProgressBarMinHeight}" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="ProgressBar:not(.notransitions) /template/ Border#PART_Indicator">
|
||||
<Style Selector="ProgressBar[IsIndeterminate=False] /template/ Border#PART_Indicator">
|
||||
<Setter Property="Transitions">
|
||||
<Transitions>
|
||||
<DoubleTransition
|
||||
|
||||
@@ -296,8 +296,8 @@
|
||||
</Styles.Resources>
|
||||
|
||||
<Style Selector="ToggleSwitch">
|
||||
<Setter Property="OffContent" Value="{ReflectionBinding Path=Res.Off, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
|
||||
<Setter Property="OnContent" Value="{ReflectionBinding Path=Res.On, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
|
||||
<Setter Property="OffContent" Value="{Binding Path=Res.Off, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
|
||||
<Setter Property="OnContent" Value="{Binding Path=Res.On, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
|
||||
<Setter Property="MinWidth" Value="0" />
|
||||
</Style>
|
||||
|
||||
|
||||
@@ -52,7 +52,7 @@ public class ReactiveAppWindow<TViewModel> : AppWindow, IViewFor<TViewModel>, IV
|
||||
|
||||
//SystemDecorations = SystemDecorations.BorderOnly;
|
||||
Background = null;
|
||||
TransparencyBackgroundFallback = Brushes.Transparent;
|
||||
TransparencyBackgroundFallback = OperatingSystem2.IsMacOS() ? Brushes.Black : Brushes.Transparent;
|
||||
TransparencyLevelHint = new WindowTransparencyLevel[] { UISettings.WindowBackgroundMaterial.Value.ToWindowTransparencyLevel() };
|
||||
|
||||
if (IsSaveWindowSize)
|
||||
|
||||
@@ -8,9 +8,7 @@
|
||||
xmlns:spp="https://steampp.net/ui"
|
||||
d:DesignHeight="450"
|
||||
d:DesignWidth="800"
|
||||
IsVisible="{ReflectionBinding IsShowAdvertise,
|
||||
Mode=OneWay,
|
||||
Source={x:Static s:AdvertiseService.Current}}"
|
||||
IsVisible="{Binding IsShowAdvertise, Mode=OneWay, Source={x:Static s:AdvertiseService.Current}}"
|
||||
mc:Ignorable="d">
|
||||
<Border ClipToBounds="True" CornerRadius="{DynamicResource ControlCornerRadius}">
|
||||
<Panel>
|
||||
@@ -20,15 +18,14 @@
|
||||
<Button
|
||||
Padding="0"
|
||||
Classes="Plain"
|
||||
Command="{ReflectionBinding ClickAdvertisementCommand,
|
||||
Mode=OneWay,
|
||||
Source={x:Static s:AdvertiseService.Current}}"
|
||||
Command="{Binding ClickAdvertisementCommand, Mode=OneWay, Source={x:Static s:AdvertiseService.Current}}"
|
||||
CommandParameter="{Binding}"
|
||||
Cursor="Hand"
|
||||
ToolTip.Tip="{Binding Describe}">
|
||||
<spp:Image2
|
||||
Width="{Binding $parent[UserControl].Bounds.Width}"
|
||||
Height="{Binding $parent[UserControl].Bounds.Height}"
|
||||
EnableCancelToken="False"
|
||||
Source="{Binding ImageUrl}"
|
||||
Stretch="UniformToFill" />
|
||||
</Button>
|
||||
@@ -39,17 +36,13 @@
|
||||
Margin="10"
|
||||
FontSize="10"
|
||||
Foreground="White"
|
||||
Text="{ReflectionBinding Path=Res.Advertisement,
|
||||
Mode=OneWay,
|
||||
Source={x:Static s:ResourceService.Current}}" />
|
||||
Text="{Binding Path=Res.Advertisement, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
|
||||
<Button
|
||||
Name="CloseAdBtn"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Top"
|
||||
Background="Transparent"
|
||||
Content="{ReflectionBinding Path=Res.Advertisement_Close,
|
||||
Mode=OneWay,
|
||||
Source={x:Static s:ResourceService.Current}}"
|
||||
Content="{Binding Path=Res.Advertisement_Close, Mode=OneWay, Source={x:Static s:ResourceService.Current}}"
|
||||
Cursor="Hand"
|
||||
Foreground="White" />
|
||||
</Panel>
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
namespace BD.WTTS.UI.Views.Controls;
|
||||
|
||||
public enum AlignContent
|
||||
{
|
||||
FlexStart,
|
||||
FlexEnd,
|
||||
Center,
|
||||
Stretch,
|
||||
SpaceBetween,
|
||||
SpaceAround,
|
||||
SpaceEvenly
|
||||
}
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
namespace BD.WTTS.UI.Views.Controls;
|
||||
|
||||
[SuppressMessage("Naming", "CA1717:Only FlagsAttribute enums should have plural names")]
|
||||
public enum AlignItems
|
||||
{
|
||||
FlexStart,
|
||||
FlexEnd,
|
||||
Center,
|
||||
Stretch
|
||||
}
|
||||
@@ -0,0 +1,183 @@
|
||||
namespace BD.WTTS.UI.Views.Controls;
|
||||
|
||||
public static class Flex
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines an attached property to control the alignment of a specific child in a flex layout.
|
||||
/// </summary>
|
||||
public static readonly AttachedProperty<AlignItems?> AlignSelfProperty =
|
||||
AvaloniaProperty.RegisterAttached<Layoutable, AlignItems?>("AlignSelf", typeof(Flex));
|
||||
|
||||
/// <summary>
|
||||
/// Defines an attached property to control the order of a specific child in a flex layout.
|
||||
/// </summary>
|
||||
public static readonly AttachedProperty<int> OrderProperty =
|
||||
AvaloniaProperty.RegisterAttached<Layoutable, int>("Order", typeof(Flex));
|
||||
|
||||
public static readonly AttachedProperty<FlexBasis> BasisProperty =
|
||||
AvaloniaProperty.RegisterAttached<Layoutable, FlexBasis>("Basis", typeof(Flex), FlexBasis.Auto);
|
||||
|
||||
public static readonly AttachedProperty<double> ShrinkProperty =
|
||||
AvaloniaProperty.RegisterAttached<Layoutable, double>("Shrink", typeof(Flex), 1.0, validate: v => v >= 0.0);
|
||||
|
||||
public static readonly AttachedProperty<double> GrowProperty =
|
||||
AvaloniaProperty.RegisterAttached<Layoutable, double>("Grow", typeof(Flex), 0.0, validate: v => v >= 0.0);
|
||||
|
||||
internal static readonly AttachedProperty<double> BaseLengthProperty =
|
||||
AvaloniaProperty.RegisterAttached<Layoutable, double>("BaseLength", typeof(Flex), 0.0);
|
||||
|
||||
internal static readonly AttachedProperty<double> CurrentLengthProperty =
|
||||
AvaloniaProperty.RegisterAttached<Layoutable, double>("CurrentLength", typeof(Flex), 0.0);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the child alignment in a flex layout
|
||||
/// </summary>
|
||||
public static AlignItems? GetAlignSelf(Layoutable layoutable)
|
||||
{
|
||||
if (layoutable is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(layoutable));
|
||||
}
|
||||
|
||||
return layoutable.GetValue(AlignSelfProperty);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the child alignment in a flex layout
|
||||
/// </summary>
|
||||
public static void SetAlignSelf(Layoutable layoutable, AlignItems? value)
|
||||
{
|
||||
if (layoutable is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(layoutable));
|
||||
}
|
||||
|
||||
layoutable.SetValue(AlignSelfProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the child order in a flex layout
|
||||
/// </summary>
|
||||
public static int GetOrder(Layoutable layoutable)
|
||||
{
|
||||
if (layoutable is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(layoutable));
|
||||
}
|
||||
|
||||
return layoutable.GetValue(OrderProperty);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the child order in a flex layout
|
||||
/// </summary>
|
||||
public static void SetOrder(Layoutable layoutable, int value)
|
||||
{
|
||||
if (layoutable is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(layoutable));
|
||||
}
|
||||
|
||||
layoutable.SetValue(OrderProperty, value);
|
||||
}
|
||||
|
||||
public static FlexBasis GetBasis(Layoutable layoutable)
|
||||
{
|
||||
if (layoutable is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(layoutable));
|
||||
}
|
||||
|
||||
return layoutable.GetValue(BasisProperty);
|
||||
}
|
||||
|
||||
public static void SetBasis(Layoutable layoutable, FlexBasis value)
|
||||
{
|
||||
if (layoutable is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(layoutable));
|
||||
}
|
||||
|
||||
layoutable.SetValue(BasisProperty, value);
|
||||
}
|
||||
|
||||
public static double GetShrink(Layoutable layoutable)
|
||||
{
|
||||
if (layoutable is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(layoutable));
|
||||
}
|
||||
|
||||
return layoutable.GetValue(ShrinkProperty);
|
||||
}
|
||||
|
||||
public static void SetShrink(Layoutable layoutable, double value)
|
||||
{
|
||||
if (layoutable is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(layoutable));
|
||||
}
|
||||
|
||||
layoutable.SetValue(ShrinkProperty, value);
|
||||
}
|
||||
|
||||
public static double GetGrow(Layoutable layoutable)
|
||||
{
|
||||
if (layoutable is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(layoutable));
|
||||
}
|
||||
|
||||
return layoutable.GetValue(GrowProperty);
|
||||
}
|
||||
|
||||
public static void SetGrow(Layoutable layoutable, double value)
|
||||
{
|
||||
if (layoutable is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(layoutable));
|
||||
}
|
||||
|
||||
layoutable.SetValue(GrowProperty, value);
|
||||
}
|
||||
|
||||
internal static double GetBaseLength(Layoutable layoutable)
|
||||
{
|
||||
if (layoutable is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(layoutable));
|
||||
}
|
||||
|
||||
return layoutable.GetValue(BaseLengthProperty);
|
||||
}
|
||||
|
||||
internal static void SetBaseLength(Layoutable layoutable, double value)
|
||||
{
|
||||
if (layoutable is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(layoutable));
|
||||
}
|
||||
|
||||
layoutable.SetValue(BaseLengthProperty, value);
|
||||
}
|
||||
|
||||
internal static double GetCurrentLength(Layoutable layoutable)
|
||||
{
|
||||
if (layoutable is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(layoutable));
|
||||
}
|
||||
|
||||
return layoutable.GetValue(CurrentLengthProperty);
|
||||
}
|
||||
|
||||
internal static void SetCurrentLength(Layoutable layoutable, double value)
|
||||
{
|
||||
if (layoutable is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(layoutable));
|
||||
}
|
||||
|
||||
layoutable.SetValue(CurrentLengthProperty, value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
namespace BD.WTTS.UI.Views.Controls;
|
||||
|
||||
public readonly struct FlexBasis : IEquatable<FlexBasis>
|
||||
{
|
||||
public double Value { get; }
|
||||
|
||||
public FlexBasisKind Kind { get; }
|
||||
|
||||
public FlexBasis(double value, FlexBasisKind kind)
|
||||
{
|
||||
if (value < 0 || double.IsNaN(value) || double.IsInfinity(value))
|
||||
throw new ArgumentException($"Invalid basis value: {value}", nameof(value));
|
||||
if (kind < FlexBasisKind.Auto || kind > FlexBasisKind.Relative)
|
||||
throw new ArgumentException($"Invalid basis kind: {kind}", nameof(kind));
|
||||
Value = value;
|
||||
Kind = kind;
|
||||
}
|
||||
|
||||
public FlexBasis(double value) : this(value, FlexBasisKind.Absolute) { }
|
||||
|
||||
public static FlexBasis Auto => new(0.0, FlexBasisKind.Auto);
|
||||
|
||||
public bool IsAuto => Kind == FlexBasisKind.Auto;
|
||||
|
||||
public bool IsAbsolute => Kind == FlexBasisKind.Absolute;
|
||||
|
||||
public bool IsRelative => Kind == FlexBasisKind.Relative;
|
||||
|
||||
[SuppressMessage("ReSharper", "CompareOfFloatsByEqualityOperator")]
|
||||
public bool Equals(FlexBasis other) =>
|
||||
(IsAuto && other.IsAuto) || (Value == other.Value && Kind == other.Kind);
|
||||
|
||||
public override bool Equals(object? obj) =>
|
||||
obj is FlexBasis other && Equals(other);
|
||||
|
||||
public override int GetHashCode() =>
|
||||
HashCode.Combine(Value, (int)Kind);
|
||||
|
||||
public static bool operator ==(FlexBasis left, FlexBasis right) =>
|
||||
left.Equals(right);
|
||||
|
||||
public static bool operator !=(FlexBasis left, FlexBasis right) =>
|
||||
!left.Equals(right);
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return Kind switch
|
||||
{
|
||||
FlexBasisKind.Auto => "Auto",
|
||||
FlexBasisKind.Absolute => FormattableString.Invariant($"{Value:G17}"),
|
||||
FlexBasisKind.Relative => FormattableString.Invariant($"{Value * 100:G17}%"),
|
||||
_ => throw new InvalidOperationException(),
|
||||
};
|
||||
}
|
||||
|
||||
public static FlexBasis Parse(string str)
|
||||
{
|
||||
return str.ToUpperInvariant() switch
|
||||
{
|
||||
"AUTO" => Auto,
|
||||
var s when s.EndsWith("%") => new FlexBasis(ParseDouble(s[..^1].Trim()) / 100, FlexBasisKind.Relative),
|
||||
_ => new FlexBasis(ParseDouble(str), FlexBasisKind.Absolute),
|
||||
};
|
||||
double ParseDouble(string s) => double.Parse(s, CultureInfo.InvariantCulture);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace BD.WTTS.UI.Views.Controls;
|
||||
|
||||
public enum FlexBasisKind
|
||||
{
|
||||
Auto,
|
||||
Absolute,
|
||||
Relative,
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace BD.WTTS.UI.Views.Controls;
|
||||
|
||||
public enum FlexDirection
|
||||
{
|
||||
Row,
|
||||
RowReverse,
|
||||
Column,
|
||||
ColumnReverse
|
||||
}
|
||||
@@ -0,0 +1,468 @@
|
||||
namespace BD.WTTS.UI.Views.Controls;
|
||||
|
||||
public sealed class FlexPanel : Panel
|
||||
{
|
||||
private static readonly Func<Layoutable, int> s_getOrder = x => x is { } y ? Flex.GetOrder(y) : 0;
|
||||
private static readonly Func<Layoutable, bool> s_isVisible = x => x.IsVisible;
|
||||
|
||||
/// <summary>
|
||||
/// Defines the <see cref="Direction"/> property.
|
||||
/// </summary>
|
||||
public static readonly StyledProperty<FlexDirection> DirectionProperty =
|
||||
AvaloniaProperty.Register<FlexPanel, FlexDirection>(nameof(Direction));
|
||||
|
||||
/// <summary>
|
||||
/// Defines the <see cref="JustifyContent"/> property.
|
||||
/// </summary>
|
||||
public static readonly StyledProperty<JustifyContent> JustifyContentProperty =
|
||||
AvaloniaProperty.Register<FlexPanel, JustifyContent>(nameof(JustifyContent));
|
||||
|
||||
/// <summary>
|
||||
/// Defines the <see cref="AlignItems"/> property.
|
||||
/// </summary>
|
||||
public static readonly StyledProperty<AlignItems> AlignItemsProperty =
|
||||
AvaloniaProperty.Register<FlexPanel, AlignItems>(nameof(AlignItems));
|
||||
|
||||
/// <summary>
|
||||
/// Defines the <see cref="AlignContent"/> property.
|
||||
/// </summary>
|
||||
public static readonly StyledProperty<AlignContent> AlignContentProperty =
|
||||
AvaloniaProperty.Register<FlexPanel, AlignContent>(nameof(AlignContent));
|
||||
|
||||
/// <summary>
|
||||
/// Defines the <see cref="Wrap"/> property.
|
||||
/// </summary>
|
||||
public static readonly StyledProperty<FlexWrap> WrapProperty =
|
||||
AvaloniaProperty.Register<FlexPanel, FlexWrap>(nameof(Wrap), FlexWrap.Wrap);
|
||||
|
||||
/// <summary>
|
||||
/// Defines the <see cref="ColumnSpacing"/> property.
|
||||
/// </summary>
|
||||
public static readonly StyledProperty<double> ColumnSpacingProperty =
|
||||
AvaloniaProperty.Register<FlexPanel, double>(nameof(ColumnSpacing));
|
||||
|
||||
/// <summary>
|
||||
/// Defines the <see cref="RowSpacing"/> property.
|
||||
/// </summary>
|
||||
public static readonly StyledProperty<double> RowSpacingProperty =
|
||||
AvaloniaProperty.Register<FlexPanel, double>(nameof(RowSpacing));
|
||||
|
||||
private FlexLayoutState? _state;
|
||||
|
||||
static FlexPanel()
|
||||
{
|
||||
AffectsMeasure<FlexPanel>(
|
||||
DirectionProperty,
|
||||
JustifyContentProperty,
|
||||
WrapProperty,
|
||||
ColumnSpacingProperty,
|
||||
RowSpacingProperty);
|
||||
|
||||
AffectsArrange<FlexPanel>(
|
||||
AlignItemsProperty,
|
||||
AlignContentProperty);
|
||||
|
||||
AffectsParentMeasure<FlexPanel>(
|
||||
HorizontalAlignmentProperty,
|
||||
VerticalAlignmentProperty,
|
||||
Flex.OrderProperty,
|
||||
Flex.BasisProperty,
|
||||
Flex.ShrinkProperty,
|
||||
Flex.GrowProperty);
|
||||
|
||||
AffectsParentArrange<FlexPanel>(
|
||||
Flex.AlignSelfProperty);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the flex direction
|
||||
/// </summary>
|
||||
public FlexDirection Direction
|
||||
{
|
||||
get => GetValue(DirectionProperty);
|
||||
set => SetValue(DirectionProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the flex justify content mode
|
||||
/// </summary>
|
||||
public JustifyContent JustifyContent
|
||||
{
|
||||
get => GetValue(JustifyContentProperty);
|
||||
set => SetValue(JustifyContentProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the flex align items mode
|
||||
/// </summary>
|
||||
public AlignItems AlignItems
|
||||
{
|
||||
get => GetValue(AlignItemsProperty);
|
||||
set => SetValue(AlignItemsProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the flex align content mode
|
||||
/// </summary>
|
||||
public AlignContent AlignContent
|
||||
{
|
||||
get => GetValue(AlignContentProperty);
|
||||
set => SetValue(AlignContentProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the flex wrap mode
|
||||
/// </summary>
|
||||
public FlexWrap Wrap
|
||||
{
|
||||
get => GetValue(WrapProperty);
|
||||
set => SetValue(WrapProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the column spacing
|
||||
/// </summary>
|
||||
public double ColumnSpacing
|
||||
{
|
||||
get => GetValue(ColumnSpacingProperty);
|
||||
set => SetValue(ColumnSpacingProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the row spacing
|
||||
/// </summary>
|
||||
public double RowSpacing
|
||||
{
|
||||
get => GetValue(RowSpacingProperty);
|
||||
set => SetValue(RowSpacingProperty, value);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override Size MeasureOverride(Size availableSize)
|
||||
{
|
||||
var children = (IReadOnlyList<Layoutable>)Children;
|
||||
|
||||
var isColumn = Direction is FlexDirection.Column or FlexDirection.ColumnReverse;
|
||||
|
||||
var max = Uv.FromSize(availableSize, isColumn);
|
||||
var spacing = Uv.FromSize(ColumnSpacing, RowSpacing, isColumn);
|
||||
|
||||
var (lineU, lineV, lineShrink, lineGrow, lineAutoMargins) = (0.0, 0.0, 0.0, 0.0, 0);
|
||||
var (childIndex, firstChildIndex, itemIndex, lineIndex) = (0, 0, 0, 0);
|
||||
|
||||
var lines = new List<FlexLine>();
|
||||
children = children.Where(s_isVisible).OrderBy(s_getOrder).ToArray();
|
||||
|
||||
foreach (var element in children)
|
||||
{
|
||||
var basis = Flex.GetBasis(element);
|
||||
var flexConstraint = basis.Kind switch
|
||||
{
|
||||
FlexBasisKind.Auto => max.U,
|
||||
FlexBasisKind.Absolute => basis.Value,
|
||||
FlexBasisKind.Relative => max.U * basis.Value / 100,
|
||||
_ => throw new InvalidOperationException()
|
||||
};
|
||||
element.Measure(Uv.ToSize(max.WithU(flexConstraint), isColumn));
|
||||
|
||||
var size = Uv.FromSize(element.DesiredSize, isColumn);
|
||||
var flexLength = basis.Kind switch
|
||||
{
|
||||
FlexBasisKind.Auto => size.U,
|
||||
FlexBasisKind.Absolute or FlexBasisKind.Relative => Math.Max(size.U, flexConstraint),
|
||||
_ => throw new InvalidOperationException()
|
||||
};
|
||||
size = size.WithU(flexLength);
|
||||
Flex.SetBaseLength(element, flexLength);
|
||||
Flex.SetCurrentLength(element, flexLength);
|
||||
|
||||
if (Wrap != FlexWrap.NoWrap && lineU + size.U + itemIndex * spacing.U > max.U)
|
||||
{
|
||||
lines.Add(new FlexLine(firstChildIndex, childIndex - 1,
|
||||
lineU, lineV, lineShrink, lineGrow, lineAutoMargins));
|
||||
(lineU, lineV, lineShrink, lineGrow, lineAutoMargins) = (0.0, 0.0, 0.0, 0.0, 0);
|
||||
firstChildIndex = childIndex;
|
||||
itemIndex = 0;
|
||||
lineIndex++;
|
||||
}
|
||||
|
||||
lineU += size.U;
|
||||
lineV = Math.Max(lineV, size.V);
|
||||
lineShrink += Flex.GetShrink(element);
|
||||
lineGrow += Flex.GetGrow(element);
|
||||
lineAutoMargins += GetItemAutoMargins(element, isColumn);
|
||||
itemIndex++;
|
||||
childIndex++;
|
||||
}
|
||||
|
||||
if (itemIndex != 0)
|
||||
{
|
||||
lines.Add(new FlexLine(firstChildIndex, firstChildIndex + itemIndex - 1,
|
||||
lineU, lineV, lineShrink, lineGrow, lineAutoMargins));
|
||||
}
|
||||
|
||||
var state = new FlexLayoutState(children, lines, Wrap);
|
||||
|
||||
var totalSpacingV = (lines.Count - 1) * spacing.V;
|
||||
var panelSizeU = lines.Count > 0 ? lines.Max(line => line.U + (line.Count - 1) * spacing.U) : 0.0;
|
||||
|
||||
// Reizing along main axis using grow and shrink factors can affect cross axis, so remeasure affected items and lines.
|
||||
foreach (var line in lines)
|
||||
{
|
||||
var (itemsCount, totalSpacingU, totalU, freeU) = GetLineMeasureU(line, max.U, spacing.U);
|
||||
var (lineMult, autoMargins, remainingFreeU) = GetLineMultInfo(line, freeU);
|
||||
if (lineMult != 0.0 && remainingFreeU != 0.0)
|
||||
{
|
||||
foreach (var element in state.GetLineItems(line))
|
||||
{
|
||||
var baseLength = Flex.GetBaseLength(element);
|
||||
var mult = GetItemMult(element, freeU);
|
||||
if (mult != 0.0)
|
||||
{
|
||||
var length = Math.Max(0.0, baseLength + remainingFreeU * mult / lineMult);
|
||||
element.Measure(Uv.ToSize(max.WithU(length), isColumn));
|
||||
}
|
||||
}
|
||||
|
||||
line.V = state.GetLineItems(line).Max(i => Uv.FromSize(i.DesiredSize, isColumn).V);
|
||||
}
|
||||
}
|
||||
|
||||
_state = state;
|
||||
var totalLineV = lines.Sum(l => l.V);
|
||||
var panelSize = lines.Count == 0 ? default : new Uv(panelSizeU, totalLineV + totalSpacingV);
|
||||
return Uv.ToSize(panelSize, isColumn);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override Size ArrangeOverride(Size finalSize)
|
||||
{
|
||||
var state = _state ?? throw new InvalidOperationException();
|
||||
|
||||
var isColumn = Direction is FlexDirection.Column or FlexDirection.ColumnReverse;
|
||||
var isReverse = Direction is FlexDirection.RowReverse or FlexDirection.ColumnReverse;
|
||||
|
||||
var panelSize = Uv.FromSize(finalSize, isColumn);
|
||||
var spacing = Uv.FromSize(ColumnSpacing, RowSpacing, isColumn);
|
||||
|
||||
var linesCount = state.Lines.Count;
|
||||
var totalLineV = state.Lines.Sum(s => s.V);
|
||||
var totalSpacingV = (linesCount - 1) * spacing.V;
|
||||
var totalV = totalLineV + totalSpacingV;
|
||||
var freeV = panelSize.V - totalV;
|
||||
|
||||
var alignContent = freeV >= 0.0 ? AlignContent : AlignContent switch
|
||||
{
|
||||
AlignContent.FlexStart or AlignContent.Stretch or AlignContent.SpaceBetween => AlignContent.FlexStart,
|
||||
AlignContent.Center or AlignContent.SpaceAround or AlignContent.SpaceEvenly => AlignContent.Center,
|
||||
AlignContent.FlexEnd => AlignContent.FlexEnd,
|
||||
_ => throw new InvalidOperationException()
|
||||
};
|
||||
|
||||
var (spacingV, v) = alignContent switch
|
||||
{
|
||||
AlignContent.FlexStart => (spacing.V, 0.0),
|
||||
AlignContent.FlexEnd => (spacing.V, freeV),
|
||||
AlignContent.Center => (spacing.V, freeV / 2),
|
||||
AlignContent.Stretch => (spacing.V, 0.0),
|
||||
AlignContent.SpaceBetween => (spacing.V + freeV / (linesCount - 1), 0.0),
|
||||
AlignContent.SpaceAround => (spacing.V + freeV / linesCount, freeV / linesCount / 2),
|
||||
AlignContent.SpaceEvenly => (spacing.V + freeV / (linesCount + 1), freeV / (linesCount + 1)),
|
||||
_ => throw new InvalidOperationException()
|
||||
};
|
||||
|
||||
var scaleV = alignContent == AlignContent.Stretch ? (panelSize.V - totalSpacingV) / totalLineV : 1.0;
|
||||
|
||||
foreach (var line in state.Lines)
|
||||
{
|
||||
var lineV = scaleV * line.V;
|
||||
var (itemsCount, totalSpacingU, totalU, freeU) = GetLineMeasureU(line, panelSize.U, spacing.U);
|
||||
var (lineMult, lineAutoMargins, remainingFreeU) = GetLineMultInfo(line, freeU);
|
||||
|
||||
var currentFreeU = remainingFreeU;
|
||||
if (lineMult != 0.0 && remainingFreeU != 0.0)
|
||||
{
|
||||
foreach (var element in state.GetLineItems(line))
|
||||
{
|
||||
var baseLength = Flex.GetBaseLength(element);
|
||||
var mult = GetItemMult(element, freeU);
|
||||
if (mult != 0.0)
|
||||
{
|
||||
var length = Math.Max(0.0, baseLength + remainingFreeU * mult / lineMult);
|
||||
Flex.SetCurrentLength(element, length);
|
||||
currentFreeU -= length - baseLength;
|
||||
}
|
||||
}
|
||||
}
|
||||
remainingFreeU = currentFreeU;
|
||||
|
||||
if (lineAutoMargins != 0 && remainingFreeU != 0.0)
|
||||
{
|
||||
foreach (var element in state.GetLineItems(line))
|
||||
{
|
||||
var baseLength = Flex.GetCurrentLength(element);
|
||||
var autoMargins = GetItemAutoMargins(element, isColumn);
|
||||
if (autoMargins != 0)
|
||||
{
|
||||
var length = Math.Max(0.0, baseLength + remainingFreeU * autoMargins / lineAutoMargins);
|
||||
Flex.SetCurrentLength(element, length);
|
||||
currentFreeU -= length - baseLength;
|
||||
}
|
||||
}
|
||||
}
|
||||
remainingFreeU = currentFreeU;
|
||||
|
||||
var (spacingU, u) = line.Grow > 0 ? (spacing.U, 0.0) : JustifyContent switch
|
||||
{
|
||||
JustifyContent.FlexStart => (spacing.U, 0.0),
|
||||
JustifyContent.FlexEnd => (spacing.U, remainingFreeU),
|
||||
JustifyContent.Center => (spacing.U, remainingFreeU / 2),
|
||||
JustifyContent.SpaceBetween => (spacing.U + remainingFreeU / (itemsCount - 1), 0.0),
|
||||
JustifyContent.SpaceAround => (spacing.U + remainingFreeU / itemsCount, remainingFreeU / itemsCount / 2),
|
||||
JustifyContent.SpaceEvenly => (spacing.U + remainingFreeU / (itemsCount + 1), remainingFreeU / (itemsCount + 1)),
|
||||
_ => throw new InvalidOperationException()
|
||||
};
|
||||
|
||||
foreach (var element in state.GetLineItems(line))
|
||||
{
|
||||
var size = Uv.FromSize(element.DesiredSize, isColumn).WithU(Flex.GetCurrentLength(element));
|
||||
var align = Flex.GetAlignSelf(element) ?? AlignItems;
|
||||
|
||||
var positionV = align switch
|
||||
{
|
||||
AlignItems.FlexStart => v,
|
||||
AlignItems.FlexEnd => v + lineV - size.V,
|
||||
AlignItems.Center => v + (lineV - size.V) / 2,
|
||||
AlignItems.Stretch => v,
|
||||
_ => throw new InvalidOperationException()
|
||||
};
|
||||
|
||||
size = size.WithV(align == AlignItems.Stretch ? lineV : size.V);
|
||||
var position = new Uv(isReverse ? panelSize.U - size.U - u : u, positionV);
|
||||
element.Arrange(new Rect(Uv.ToPoint(position, isColumn), Uv.ToSize(size, isColumn)));
|
||||
|
||||
u += size.U + spacingU;
|
||||
}
|
||||
|
||||
v += lineV + spacingV;
|
||||
}
|
||||
|
||||
return finalSize;
|
||||
}
|
||||
|
||||
private static (int ItemsCount, double TotalSpacingU, double TotalU, double FreeU) GetLineMeasureU(
|
||||
FlexLine line, double panelSizeU, double spacingU)
|
||||
{
|
||||
var itemsCount = line.Count;
|
||||
var totalSpacingU = (itemsCount - 1) * spacingU;
|
||||
var totalU = line.U + totalSpacingU;
|
||||
var freeU = panelSizeU - totalU;
|
||||
return (itemsCount, totalSpacingU, totalU, freeU);
|
||||
}
|
||||
|
||||
private static (double LineMult, double LineAutoMargins, double RemainingFreeU) GetLineMultInfo(FlexLine line, double freeU)
|
||||
{
|
||||
var lineMult = freeU switch
|
||||
{
|
||||
< 0 => line.Shrink,
|
||||
> 0 => line.Grow,
|
||||
_ => 0.0,
|
||||
};
|
||||
// https://www.w3.org/TR/css-flexbox-1/#remaining-free-space
|
||||
// Sum of flex factors less than 1 reduces remaining free space to be distributed.
|
||||
return lineMult is > 0 and < 1
|
||||
? (lineMult, line.AutoMargins, freeU * lineMult)
|
||||
: (lineMult, line.AutoMargins, freeU);
|
||||
}
|
||||
|
||||
private static double GetItemMult(Layoutable element, double freeU)
|
||||
{
|
||||
var mult = freeU switch
|
||||
{
|
||||
< 0 => Flex.GetShrink(element),
|
||||
> 0 => Flex.GetGrow(element),
|
||||
_ => 0.0,
|
||||
};
|
||||
return mult;
|
||||
}
|
||||
|
||||
private static int GetItemAutoMargins(Layoutable element, bool isColumn)
|
||||
{
|
||||
return isColumn
|
||||
? element.VerticalAlignment switch
|
||||
{
|
||||
VerticalAlignment.Stretch => 0,
|
||||
VerticalAlignment.Top or VerticalAlignment.Bottom => 1,
|
||||
VerticalAlignment.Center => 2,
|
||||
_ => throw new InvalidOperationException()
|
||||
}
|
||||
: element.HorizontalAlignment switch
|
||||
{
|
||||
HorizontalAlignment.Stretch => 0,
|
||||
HorizontalAlignment.Left or HorizontalAlignment.Right => 1,
|
||||
HorizontalAlignment.Center => 2,
|
||||
_ => throw new InvalidOperationException()
|
||||
};
|
||||
}
|
||||
|
||||
private readonly struct FlexLayoutState
|
||||
{
|
||||
private readonly IReadOnlyList<Layoutable> _children;
|
||||
|
||||
public IReadOnlyList<FlexLine> Lines { get; }
|
||||
|
||||
public FlexLayoutState(IReadOnlyList<Layoutable> children, List<FlexLine> lines, FlexWrap wrap)
|
||||
{
|
||||
if (wrap == FlexWrap.WrapReverse)
|
||||
{
|
||||
lines.Reverse();
|
||||
}
|
||||
_children = children;
|
||||
Lines = lines;
|
||||
}
|
||||
|
||||
public IEnumerable<Layoutable> GetLineItems(FlexLine line)
|
||||
{
|
||||
for (var i = line.First; i <= line.Last; i++)
|
||||
yield return _children[i];
|
||||
}
|
||||
}
|
||||
|
||||
private class FlexLine
|
||||
{
|
||||
public FlexLine(int first, int last, double u, double v, double shrink, double grow, int autoMargins)
|
||||
{
|
||||
First = first;
|
||||
Last = last;
|
||||
U = u;
|
||||
V = v;
|
||||
Shrink = shrink;
|
||||
Grow = grow;
|
||||
AutoMargins = autoMargins;
|
||||
}
|
||||
|
||||
/// <summary>First item index.</summary>
|
||||
public int First { get; }
|
||||
|
||||
/// <summary>Last item index.</summary>
|
||||
public int Last { get; }
|
||||
|
||||
/// <summary>Sum of main sizes of items.</summary>
|
||||
public double U { get; }
|
||||
|
||||
/// <summary>Max of cross sizes of items.</summary>
|
||||
public double V { get; set; }
|
||||
|
||||
/// <summary>Sum of shrink factors of flexible items.</summary>
|
||||
public double Shrink { get; }
|
||||
|
||||
/// <summary>Sum of grow factors of flexible items.</summary>
|
||||
public double Grow { get; }
|
||||
|
||||
/// <summary>Number of "auto margins" along main axis.</summary>
|
||||
public int AutoMargins { get; }
|
||||
|
||||
/// <summary>Number of items.</summary>
|
||||
public int Count => Last - First + 1;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace BD.WTTS.UI.Views.Controls;
|
||||
|
||||
public enum FlexWrap
|
||||
{
|
||||
NoWrap,
|
||||
Wrap,
|
||||
WrapReverse
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
namespace BD.WTTS.UI.Views.Controls;
|
||||
|
||||
public enum JustifyContent
|
||||
{
|
||||
FlexStart,
|
||||
FlexEnd,
|
||||
Center,
|
||||
SpaceBetween,
|
||||
SpaceAround,
|
||||
SpaceEvenly
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
namespace BD.WTTS.UI.Views.Controls;
|
||||
|
||||
internal readonly struct Uv(double u, double v)
|
||||
{
|
||||
public double U { get; } = u;
|
||||
|
||||
public double V { get; } = v;
|
||||
|
||||
public static Uv FromSize(double width, double height, bool swap) =>
|
||||
new Uv(swap ? height : width, swap ? width : height);
|
||||
|
||||
public static Uv FromSize(Size size, bool swap) =>
|
||||
FromSize(size.Width, size.Height, swap);
|
||||
|
||||
public static Point ToPoint(Uv uv, bool swap) =>
|
||||
new Point(swap ? uv.V : uv.U, swap ? uv.U : uv.V);
|
||||
|
||||
public static Size ToSize(Uv uv, bool swap) =>
|
||||
new Size(swap ? uv.V : uv.U, swap ? uv.U : uv.V);
|
||||
|
||||
public Uv WithU(double u) =>
|
||||
new Uv(u, V);
|
||||
|
||||
public Uv WithV(double v) =>
|
||||
new Uv(U, v);
|
||||
|
||||
public override string ToString() =>
|
||||
$"U: {U}, V: {V}";
|
||||
}
|
||||
@@ -1,15 +1,17 @@
|
||||
using Avalonia.Controls;
|
||||
|
||||
namespace BD.WTTS.UI.Views.Controls;
|
||||
|
||||
public class OptionsDisplayItem : TemplatedControl
|
||||
{
|
||||
public static readonly StyledProperty<string> HeaderProperty =
|
||||
AvaloniaProperty.Register<OptionsDisplayItem, string>(nameof(Header));
|
||||
public static readonly StyledProperty<object?> HeaderProperty =
|
||||
AvaloniaProperty.Register<OptionsDisplayItem, object?>(nameof(Header));
|
||||
|
||||
public static readonly StyledProperty<string> DescriptionProperty =
|
||||
AvaloniaProperty.Register<OptionsDisplayItem, string>(nameof(Description));
|
||||
public static readonly StyledProperty<object?> DescriptionProperty =
|
||||
AvaloniaProperty.Register<OptionsDisplayItem, object?>(nameof(Description));
|
||||
|
||||
public static readonly StyledProperty<FAIconElement> IconProperty =
|
||||
AvaloniaProperty.Register<OptionsDisplayItem, FAIconElement>(nameof(Icon));
|
||||
public static readonly StyledProperty<object?> IconProperty =
|
||||
AvaloniaProperty.Register<OptionsDisplayItem, object?>(nameof(Icon));
|
||||
|
||||
public static readonly StyledProperty<bool> NavigatesProperty =
|
||||
AvaloniaProperty.Register<OptionsDisplayItem, bool>(nameof(Navigates));
|
||||
@@ -29,19 +31,19 @@ public class OptionsDisplayItem : TemplatedControl
|
||||
public static readonly StyledProperty<ICommand> NavigationCommandProperty =
|
||||
AvaloniaProperty.Register<OptionsDisplayItem, ICommand>(nameof(NavigationCommand));
|
||||
|
||||
public string Header
|
||||
public object? Header
|
||||
{
|
||||
get => GetValue(HeaderProperty);
|
||||
set => SetValue(HeaderProperty, value);
|
||||
}
|
||||
|
||||
public string Description
|
||||
public object? Description
|
||||
{
|
||||
get => GetValue(DescriptionProperty);
|
||||
set => SetValue(DescriptionProperty, value);
|
||||
}
|
||||
|
||||
public FAIconElement Icon
|
||||
public object? Icon
|
||||
{
|
||||
get => GetValue(IconProperty);
|
||||
set => SetValue(IconProperty, value);
|
||||
@@ -112,7 +114,7 @@ public class OptionsDisplayItem : TemplatedControl
|
||||
}
|
||||
else if (change.Property == DescriptionProperty)
|
||||
{
|
||||
PseudoClasses.Set(":desc", !string.IsNullOrEmpty(change.GetNewValue<string>()));
|
||||
PseudoClasses.Set(":desc", change.GetNewValue<object?>() != null);
|
||||
}
|
||||
else if (change.Property == IsExpandedProperty)
|
||||
PseudoClasses.Set(":expanded", change.GetNewValue<bool>());
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
</UserControl.Styles>
|
||||
<Border
|
||||
BorderBrush="{DynamicResource CardStrokeColorDefaultBrush}"
|
||||
BorderThickness="1"
|
||||
BorderThickness="0"
|
||||
ClipToBounds="True"
|
||||
CornerRadius="8">
|
||||
<Panel>
|
||||
|
||||
@@ -15,7 +15,7 @@ namespace BD.WTTS.UI.Views.Controls
|
||||
/// Defines the <see cref="AutoScrollInterval"/> property.
|
||||
/// </summary>
|
||||
public static readonly StyledProperty<int> AutoScrollIntervalProperty =
|
||||
AvaloniaProperty.Register<CarouselBanner, int>(nameof(AutoScrollInterval), 8000);
|
||||
AvaloniaProperty.Register<CarouselBanner, int>(nameof(AutoScrollInterval), 6000);
|
||||
|
||||
/// <summary>
|
||||
/// Defines the Avalonia.Controls.ItemsControl.Items property.
|
||||
@@ -29,6 +29,12 @@ namespace BD.WTTS.UI.Views.Controls
|
||||
public static readonly StyledProperty<IDataTemplate?> ItemTemplateProperty =
|
||||
Carousel.ItemTemplateProperty.AddOwner<CarouselBanner>();
|
||||
|
||||
///// <summary>
|
||||
///// Defines the <see cref="GroupView"/> property.
|
||||
///// </summary>
|
||||
//public static readonly StyledProperty<bool> GroupViewProperty =
|
||||
// AvaloniaProperty.Register<CarouselBanner, bool>(nameof(GroupView), true);
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the items to display.
|
||||
/// </summary>
|
||||
@@ -65,23 +71,32 @@ namespace BD.WTTS.UI.Views.Controls
|
||||
set => SetValue(AutoScrollIntervalProperty, value);
|
||||
}
|
||||
|
||||
///// <summary>
|
||||
///// GroupView
|
||||
///// </summary>
|
||||
//public bool GroupView
|
||||
//{
|
||||
// get => GetValue(GroupViewProperty);
|
||||
// set => SetValue(GroupViewProperty, value);
|
||||
//}
|
||||
|
||||
private Timer? _timer;
|
||||
|
||||
public CarouselBanner()
|
||||
{
|
||||
InitializeComponent();
|
||||
Left.Click += (s, e) => SwiperPrevious();
|
||||
Right.Click += (s, e) => SwiperNext();
|
||||
Left.Command = ReactiveCommand.Create(SwiperPrevious);
|
||||
Right.Command = ReactiveCommand.Create(SwiperNext);
|
||||
|
||||
//CarouselControl[!Carousel.Items] = this[!Items];
|
||||
CarouselControl[!Carousel.ItemsSourceProperty] = this[!ItemsSourceProperty];
|
||||
CarouselControl[!Carousel.ItemTemplateProperty] = this[!ItemTemplateProperty];
|
||||
|
||||
this.GetObservable(ItemsSourceProperty)
|
||||
CarouselControl.GetObservable(Carousel.ItemCountProperty)
|
||||
.Subscribe(_ => SwipersLoad());
|
||||
|
||||
CarouselControl.GetObservable(Carousel.SelectedIndexProperty)
|
||||
.Subscribe(x => SwipersLoad());
|
||||
.Subscribe(_ => SwipersLoad());
|
||||
|
||||
this.GetObservable(AutoScrollProperty)
|
||||
.Subscribe(x =>
|
||||
@@ -107,6 +122,40 @@ namespace BD.WTTS.UI.Views.Controls
|
||||
});
|
||||
}
|
||||
|
||||
//protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
|
||||
//{
|
||||
// if (change.Property == ItemsSourceProperty)
|
||||
// {
|
||||
// SwipersLoad();
|
||||
// }
|
||||
// else if (change.Property == AutoScrollProperty)
|
||||
// {
|
||||
// if (change.NewValue is bool x)
|
||||
// {
|
||||
// if (x && _timer == null)
|
||||
// {
|
||||
// _timer = new Timer(_ =>
|
||||
// {
|
||||
// if (!this.IsPointerOver)
|
||||
// {
|
||||
// Dispatcher.UIThread.Post(SwiperNext, DispatcherPriority.Background);
|
||||
// }
|
||||
// }, nameof(AutoScroll), AutoScrollInterval, AutoScrollInterval);
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// if (_timer != null)
|
||||
// {
|
||||
// _timer.Dispose();
|
||||
// _timer = null;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// base.OnPropertyChanged(change);
|
||||
//}
|
||||
|
||||
private void CarouselBannerIndexButton_Click(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
if (sender is Button button && button.Tag is int index)
|
||||
|
||||
@@ -0,0 +1,234 @@
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Layout;
|
||||
using FluentAvalonia.Core;
|
||||
using System.Reactive;
|
||||
|
||||
namespace BD.WTTS.UI.Views.Controls;
|
||||
|
||||
public class CarouselItems : ItemsControl
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the <see cref="AutoScroll"/> property.
|
||||
/// </summary>
|
||||
public static readonly StyledProperty<bool> AutoScrollProperty =
|
||||
AvaloniaProperty.Register<CarouselItems, bool>(nameof(AutoScroll), true);
|
||||
|
||||
/// <summary>
|
||||
/// Defines the <see cref="AutoScrollInterval"/> property.
|
||||
/// </summary>
|
||||
public static readonly StyledProperty<int> AutoScrollIntervalProperty =
|
||||
AvaloniaProperty.Register<CarouselItems, int>(nameof(AutoScrollInterval), 6000);
|
||||
|
||||
/// <summary>
|
||||
/// Defines the <see cref="ItemsPerPage"/> property.
|
||||
/// </summary>
|
||||
public static readonly StyledProperty<int> ItemsPerPageProperty =
|
||||
AvaloniaProperty.Register<CarouselItems, int>(nameof(ItemsPerPage), 4);
|
||||
|
||||
/// <summary>
|
||||
/// AutoScroll
|
||||
/// </summary>
|
||||
public bool AutoScroll
|
||||
{
|
||||
get => GetValue(AutoScrollProperty);
|
||||
set => SetValue(AutoScrollProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 自动播放间隔时间(单位毫秒)
|
||||
/// </summary>
|
||||
public int AutoScrollInterval
|
||||
{
|
||||
get => GetValue(AutoScrollIntervalProperty);
|
||||
set => SetValue(AutoScrollIntervalProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ItemsPerPage
|
||||
/// </summary>
|
||||
public int ItemsPerPage
|
||||
{
|
||||
get => GetValue(ItemsPerPageProperty);
|
||||
set => SetValue(ItemsPerPageProperty, value);
|
||||
}
|
||||
|
||||
Carousel? _carouselControl;
|
||||
Button? _leftButton;
|
||||
Button? _rightButton;
|
||||
ItemsRepeater? _swiper;
|
||||
Timer? _timer;
|
||||
|
||||
public ICommand? CarouselBannerIndexCommand { get; }
|
||||
|
||||
public CarouselItems()
|
||||
{
|
||||
CarouselBannerIndexCommand = ReactiveCommand.Create<int>(CarouselBannerIndex);
|
||||
}
|
||||
|
||||
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
|
||||
{
|
||||
base.OnApplyTemplate(e);
|
||||
|
||||
_carouselControl = e.NameScope.Find<Carousel>("CarouselControl");
|
||||
_leftButton = e.NameScope.Find<Button>("Left");
|
||||
_rightButton = e.NameScope.Find<Button>("Right");
|
||||
_swiper = e.NameScope.Find<ItemsRepeater>("Swiper");
|
||||
|
||||
if (_leftButton != null)
|
||||
{
|
||||
_leftButton.Command = ReactiveCommand.Create(SwiperPrevious);
|
||||
}
|
||||
if (_rightButton != null)
|
||||
{
|
||||
_rightButton.Command = ReactiveCommand.Create(SwiperNext);
|
||||
}
|
||||
|
||||
if (_carouselControl != null)
|
||||
{
|
||||
_carouselControl.GetObservable(Carousel.ItemCountProperty)
|
||||
.Subscribe(_ => SwipersLoad());
|
||||
|
||||
_carouselControl.GetObservable(Carousel.SelectedIndexProperty)
|
||||
.Subscribe(_ => SwipersLoad());
|
||||
|
||||
RefreshItemsSource();
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
|
||||
{
|
||||
base.OnPropertyChanged(change);
|
||||
|
||||
if (change.Property == ItemCountProperty)
|
||||
{
|
||||
RefreshItemsSource();
|
||||
}
|
||||
else if (change.Property == ItemsPerPageProperty)
|
||||
{
|
||||
RefreshItemsSource();
|
||||
}
|
||||
else if (change.Property == AutoScrollProperty)
|
||||
{
|
||||
var x = change.GetNewValue<bool>();
|
||||
if (x && _timer == null)
|
||||
{
|
||||
_timer = new Timer(_ =>
|
||||
{
|
||||
if (!this.IsPointerOver)
|
||||
{
|
||||
Dispatcher.UIThread.Post(SwiperNext, DispatcherPriority.Background);
|
||||
}
|
||||
}, nameof(AutoScroll), AutoScrollInterval, AutoScrollInterval);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_timer != null)
|
||||
{
|
||||
_timer.Dispose();
|
||||
_timer = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnPointerEntered(PointerEventArgs e)
|
||||
{
|
||||
base.OnPointerEntered(e);
|
||||
|
||||
if (_swiper == null || _leftButton == null || _rightButton == null)
|
||||
return;
|
||||
|
||||
_leftButton.IsVisible = _rightButton.IsVisible = _swiper.IsVisible;
|
||||
}
|
||||
|
||||
protected override void OnPointerExited(PointerEventArgs e)
|
||||
{
|
||||
base.OnPointerExited(e);
|
||||
|
||||
if (_swiper == null || _leftButton == null || _rightButton == null)
|
||||
return;
|
||||
|
||||
_leftButton.IsVisible = _rightButton.IsVisible = false;
|
||||
}
|
||||
|
||||
void RefreshItemsSource()
|
||||
{
|
||||
if (_carouselControl != null)
|
||||
{
|
||||
var items = ItemsSource?.Batch(ItemsPerPage);
|
||||
_carouselControl.ItemsSource = items;
|
||||
}
|
||||
}
|
||||
|
||||
void CarouselBannerIndex(int index)
|
||||
{
|
||||
if (_carouselControl != null)
|
||||
_carouselControl.SelectedIndex = index;
|
||||
}
|
||||
|
||||
void SwipersLoad()
|
||||
{
|
||||
if (_carouselControl == null || _swiper == null || _leftButton == null || _rightButton == null)
|
||||
return;
|
||||
|
||||
if (_carouselControl.ItemCount <= 0)
|
||||
{
|
||||
_swiper.ItemsSource = null;
|
||||
return;
|
||||
}
|
||||
if (_carouselControl.ItemCount == 1)
|
||||
{
|
||||
_swiper.IsVisible = false;
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
_swiper.IsVisible = true;
|
||||
var arr = new Dictionary<int, string>();
|
||||
for (var i = 0; i < _carouselControl.ItemCount; i++)
|
||||
{
|
||||
arr.Add(i, "#ADADAD");
|
||||
}
|
||||
var index = _carouselControl.SelectedIndex < 0 ? 0 : _carouselControl.SelectedIndex;
|
||||
arr[index] = "#FFFFFF";
|
||||
|
||||
_swiper.ItemsSource = arr;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 滑动到下一个
|
||||
/// </summary>
|
||||
public void SwiperNext()
|
||||
{
|
||||
if (_carouselControl == null || _carouselControl.ItemCount < 1)
|
||||
return;
|
||||
|
||||
if (_carouselControl.SelectedIndex < _carouselControl.ItemCount - 1)
|
||||
{
|
||||
_carouselControl.Next();
|
||||
}
|
||||
else
|
||||
{
|
||||
_carouselControl.SelectedIndex = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 滑动到上一个
|
||||
/// </summary>
|
||||
public void SwiperPrevious()
|
||||
{
|
||||
if (_carouselControl == null || _carouselControl.ItemCount < 1)
|
||||
return;
|
||||
|
||||
if (_carouselControl.SelectedIndex > 0)
|
||||
{
|
||||
_carouselControl.Previous();
|
||||
}
|
||||
else
|
||||
{
|
||||
_carouselControl.SelectedIndex = _carouselControl.ItemCount - 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,25 +2,50 @@ using static System.Math;
|
||||
|
||||
namespace BD.WTTS.UI.Views.Controls;
|
||||
|
||||
/// <summary>
|
||||
/// 固定的 WrapPanel
|
||||
/// </summary>
|
||||
public class FixedWrapPanel : Panel, INavigableContainer
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the <see cref="ItemsPerLine"/> property.
|
||||
/// </summary>
|
||||
public static readonly StyledProperty<int> ItemsPerLineProperty =
|
||||
AvaloniaProperty.Register<FixedWrapPanel, int>(nameof(ItemsPerLine), 3);
|
||||
|
||||
/// <summary>
|
||||
/// Defines the <see cref="Spacing"/> property.
|
||||
/// </summary>
|
||||
public static readonly StyledProperty<double> SpacingProperty =
|
||||
AvaloniaProperty.Register<FixedWrapPanel, double>(nameof(Spacing), 0);
|
||||
|
||||
static FixedWrapPanel()
|
||||
{
|
||||
AffectsMeasure<FixedWrapPanel>(ItemsPerLineProperty);
|
||||
}
|
||||
|
||||
public double ItemsPerLine
|
||||
/// <summary>
|
||||
/// ItemsPerLine
|
||||
/// </summary>
|
||||
public int ItemsPerLine
|
||||
{
|
||||
get => GetValue(ItemsPerLineProperty);
|
||||
set => SetValue(ItemsPerLineProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Spacing
|
||||
/// </summary>
|
||||
public double Spacing
|
||||
{
|
||||
get => GetValue(SpacingProperty);
|
||||
set => SetValue(SpacingProperty, value);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
IInputElement INavigableContainer.GetControl(NavigationDirection direction, IInputElement? from, bool wrap)
|
||||
{
|
||||
int index = from is not null ? Children.IndexOf((Control)from) : -1;
|
||||
int index = from is Control control ? Children.IndexOf(control) : -1;
|
||||
switch (direction)
|
||||
{
|
||||
case NavigationDirection.First:
|
||||
@@ -57,13 +82,14 @@ public class FixedWrapPanel : Panel, INavigableContainer
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override Size MeasureOverride(Size constraint)
|
||||
{
|
||||
double itemWidth = constraint.Width / ItemsPerLine;
|
||||
double itemWidth = (constraint.Width - (Spacing * (ItemsPerLine - 1))) / ItemsPerLine;
|
||||
MutableSize currentLineSize = default;
|
||||
MutableSize panelSize = default;
|
||||
Size lineConstraint = new(constraint.Width, constraint.Height);
|
||||
Size childConstraint = new(itemWidth, constraint.Height);
|
||||
Size childConstraint = new(itemWidth - Spacing, constraint.Height);
|
||||
|
||||
for (int i = 0, count = Children.Count; i < count; i++)
|
||||
{
|
||||
@@ -76,7 +102,7 @@ public class FixedWrapPanel : Panel, INavigableContainer
|
||||
child.Measure(childConstraint);
|
||||
Size childSize = new(itemWidth, child.DesiredSize.Height);
|
||||
|
||||
if (MathUtilities.GreaterThan(currentLineSize.Width + childSize.Width, lineConstraint.Width))
|
||||
if (MathUtilities.GreaterThan(currentLineSize.Width + childSize.Width + Spacing, lineConstraint.Width))
|
||||
{
|
||||
// Need to switch to another line
|
||||
panelSize.Width = Max(currentLineSize.Width, panelSize.Width);
|
||||
@@ -86,21 +112,25 @@ public class FixedWrapPanel : Panel, INavigableContainer
|
||||
else
|
||||
{
|
||||
// Continue to accumulate a line
|
||||
currentLineSize.Width += childSize.Width;
|
||||
currentLineSize.Width += childSize.Width + Spacing;
|
||||
currentLineSize.Height = Max(childSize.Height, currentLineSize.Height);
|
||||
}
|
||||
}
|
||||
|
||||
// The last line size, if any should be added
|
||||
panelSize.Width = Max(currentLineSize.Width, panelSize.Width);
|
||||
panelSize.Height += currentLineSize.Height;
|
||||
panelSize.Height = currentLineSize.Height;
|
||||
|
||||
if (double.IsInfinity(panelSize.Width))
|
||||
panelSize.Width = 0;
|
||||
|
||||
return panelSize.ToSize();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override Size ArrangeOverride(Size finalSize)
|
||||
{
|
||||
double itemWidth = finalSize.Width / ItemsPerLine;
|
||||
double itemWidth = (finalSize.Width - (Spacing * (ItemsPerLine - 1))) / ItemsPerLine;
|
||||
int firstInLine = 0;
|
||||
double accumulatedHeight = 0;
|
||||
var currentLineSize = default(MutableSize);
|
||||
@@ -139,7 +169,7 @@ public class FixedWrapPanel : Panel, INavigableContainer
|
||||
return finalSize;
|
||||
}
|
||||
|
||||
private void ArrangeLine(double y, double height, int start, int end, double width)
|
||||
void ArrangeLine(double y, double height, int start, int end, double width)
|
||||
{
|
||||
double x = 0;
|
||||
for (int i = start; i < end; i++)
|
||||
@@ -150,12 +180,20 @@ public class FixedWrapPanel : Panel, INavigableContainer
|
||||
continue;
|
||||
}
|
||||
|
||||
child.Arrange(new Rect(x, y, width, height));
|
||||
x += width;
|
||||
if (i < end - 1)
|
||||
{
|
||||
child.Arrange(new Rect(x, y, width - Spacing, height));
|
||||
x += width + Spacing;
|
||||
}
|
||||
else
|
||||
{
|
||||
child.Arrange(new Rect(x, y, width, height));
|
||||
x += width;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private struct MutableSize
|
||||
record struct MutableSize
|
||||
{
|
||||
internal MutableSize(double width, double height)
|
||||
{
|
||||
@@ -172,7 +210,7 @@ public class FixedWrapPanel : Panel, INavigableContainer
|
||||
internal double Width;
|
||||
internal double Height;
|
||||
|
||||
internal Size ToSize()
|
||||
internal readonly Size ToSize()
|
||||
{
|
||||
return new Size(Width, Height);
|
||||
}
|
||||
|
||||
@@ -15,9 +15,7 @@
|
||||
<StackPanel Spacing="{StaticResource SettingsSpacing}">
|
||||
<TextBlock
|
||||
Margin="0,0,0,5"
|
||||
Text="{ReflectionBinding Path=Res.About,
|
||||
Mode=OneWay,
|
||||
Source={x:Static s:ResourceService.Current}}"
|
||||
Text="{Binding Path=Res.About, Mode=OneWay, Source={x:Static s:ResourceService.Current}}"
|
||||
Theme="{StaticResource SubtitleTextBlockStyle}" />
|
||||
|
||||
<spp:OptionsDisplayItem
|
||||
@@ -140,9 +138,7 @@
|
||||
Margin="12,4,0,0"
|
||||
VerticalAlignment="Center"
|
||||
Foreground="{DynamicResource TextFillColorPrimaryBrush}"
|
||||
Text="{ReflectionBinding Path=Res.Link,
|
||||
Mode=OneWay,
|
||||
Source={x:Static s:ResourceService.Current}}" />
|
||||
Text="{Binding Path=Res.Link, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
|
||||
<ItemsRepeater Margin="0,0,6,6" ItemsSource="{Binding Hyperlinks, Mode=OneTime}">
|
||||
<ItemsRepeater.Layout>
|
||||
<UniformGridLayout MinItemWidth="166" />
|
||||
@@ -165,9 +161,7 @@
|
||||
Margin="12,4,0,0"
|
||||
VerticalAlignment="Center"
|
||||
Foreground="{DynamicResource TextFillColorPrimaryBrush}"
|
||||
Text="{ReflectionBinding Path=Res.OpenSourceLibrary,
|
||||
Mode=OneWay,
|
||||
Source={x:Static s:ResourceService.Current}}" />
|
||||
Text="{Binding Path=Res.OpenSourceLibrary, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
|
||||
<ItemsRepeater Margin="0,0,6,6" ItemsSource="{Binding OSL, Mode=OneTime}">
|
||||
<ItemsRepeater.Layout>
|
||||
<UniformGridLayout MinItemWidth="290" />
|
||||
|
||||
@@ -8,7 +8,8 @@ public partial class AboutPage : ReactiveUserControl<AboutPageViewModel>
|
||||
public AboutPage()
|
||||
{
|
||||
InitializeComponent();
|
||||
this.SetViewModel<AboutPageViewModel>(false);
|
||||
DataContext = new AboutPageViewModel();
|
||||
this.SetViewModel<AboutPageViewModel>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
</spp:PageBase.PreviewImage>
|
||||
<spp:PageBase.TabContent>
|
||||
<TabStrip
|
||||
Name="TabSelect"
|
||||
Margin="4"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Top"
|
||||
@@ -110,19 +111,45 @@
|
||||
</DropDownButton>
|
||||
</StackPanel>
|
||||
</spp:PageBase.ActionContent>
|
||||
<Border Background="{DynamicResource CardBackgroundFillColorDefaultBrush}" CornerRadius="{DynamicResource ControlCornerRadius}">
|
||||
<Grid RowDefinitions="*,Auto">
|
||||
<TextBox
|
||||
HorizontalContentAlignment="Stretch"
|
||||
VerticalContentAlignment="Stretch"
|
||||
Classes="readonlybox"
|
||||
Text="{Binding DebugString}"
|
||||
TextAlignment="Start"
|
||||
TextWrapping="Wrap" />
|
||||
<TextBox
|
||||
Name="CommandTextBox"
|
||||
Grid.Row="1"
|
||||
Watermark="{Binding Path=Res.Debug_InstructionPlaceholder, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<Carousel SelectedIndex="{Binding #TabSelect.SelectedIndex}">
|
||||
<Border Background="{DynamicResource CardBackgroundFillColorDefaultBrush}" CornerRadius="{DynamicResource ControlCornerRadius}">
|
||||
<Grid RowDefinitions="*,Auto">
|
||||
<TextBox
|
||||
HorizontalContentAlignment="Stretch"
|
||||
VerticalContentAlignment="Stretch"
|
||||
Classes="readonlybox"
|
||||
Text="{Binding DebugString}"
|
||||
TextAlignment="Start"
|
||||
TextWrapping="Wrap" />
|
||||
<TextBox
|
||||
Name="CommandTextBox"
|
||||
Grid.Row="1"
|
||||
Watermark="{Binding Path=Res.Debug_InstructionPlaceholder, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<Panel>
|
||||
<TextBlock Text="Test Control" />
|
||||
<ItemsControl>
|
||||
<ItemsControl.Items>
|
||||
<Border Height="30" Background="Red" />
|
||||
<Border Background="Blue" />
|
||||
<Border Background="Yellow" />
|
||||
<Border Height="30" Background="Yellow" />
|
||||
<Border Background="Red" />
|
||||
<Border Background="Blue" />
|
||||
</ItemsControl.Items>
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<spp:FixedWrapPanel ItemsPerLine="3" Spacing="5" />
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
</ItemsControl>
|
||||
</Panel>
|
||||
|
||||
<Panel>
|
||||
<TextBlock Text="Test Control" />
|
||||
</Panel>
|
||||
</Carousel>
|
||||
</spp:PageBase>
|
||||
|
||||
@@ -7,7 +7,7 @@ public partial class DebugPage : PageBase<DebugPageViewModel>
|
||||
public DebugPage()
|
||||
{
|
||||
InitializeComponent();
|
||||
this.SetViewModel<DebugPageViewModel>(false);
|
||||
this.SetViewModel<DebugPageViewModel>();
|
||||
|
||||
CommandTextBox.KeyUp += (s, e) =>
|
||||
{
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
x:Class="BD.WTTS.UI.Views.Pages.HomePage"
|
||||
xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:col="using:System.Collections.Generic"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:s="https://steampp.net/services"
|
||||
@@ -125,6 +126,7 @@
|
||||
|
||||
<Grid Margin="0,-10,0,0" RowDefinitions="Auto,*">
|
||||
<spp:CarouselBanner
|
||||
MinHeight="350"
|
||||
HorizontalContentAlignment="Stretch"
|
||||
VerticalContentAlignment="Stretch"
|
||||
ItemsSource="{Binding NavigationBanners}">
|
||||
@@ -148,7 +150,7 @@
|
||||
</Border>
|
||||
</spp:AppItem.Status>
|
||||
<spp:AppItem.Image>
|
||||
<spp:Image2 Source="{Binding ImageUrl}" />
|
||||
<spp:Image2 EnableCancelToken="False" Source="{Binding ImageUrl}" />
|
||||
</spp:AppItem.Image>
|
||||
</spp:AppItem>
|
||||
</DataTemplate>
|
||||
@@ -227,31 +229,34 @@
|
||||
DataContext="{Binding Articles[2]}" />
|
||||
</Grid>
|
||||
|
||||
<!--<ItemsControl ItemsSource="{Binding Articles}">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<Grid ColumnDefinitions="*,*,*" />
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate DataType="spp:ArticleItemDTO">
|
||||
<spp:AppItem
|
||||
Title="{Binding Title}"
|
||||
Classes="Vertical Scale"
|
||||
Description="{Binding Introduction}"
|
||||
Tags="{Binding AuthorName}">
|
||||
<spp:AppItem.Image>
|
||||
<spp:Image2 RenderOptions.BitmapInterpolationMode="HighQuality" Source="{Binding CoverUrl}" />
|
||||
</spp:AppItem.Image>
|
||||
<spp:AppItem.Status>
|
||||
<Border Classes="Status">
|
||||
<TextBlock Text="{Binding CreationTime, StringFormat={}{yyyy-MM-dd}}" />
|
||||
</Border>
|
||||
</spp:AppItem.Status>
|
||||
</spp:AppItem>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>-->
|
||||
<!--<spp:CarouselItems ItemsPerPage="3" ItemsSource="{Binding Articles}">
|
||||
<spp:CarouselItems.ItemTemplate>
|
||||
<DataTemplate x:DataType="spp:ArticleItemDTO">
|
||||
<spp:AppItem
|
||||
Title="{Binding Title}"
|
||||
Classes="Vertical Scale"
|
||||
ClickCommand="{Binding OpenBrowserCommand, Source={x:Static spp:App.Instance}}"
|
||||
Cursor="Hand"
|
||||
Description="{Binding Introduction}"
|
||||
Tags="{Binding AuthorName}">
|
||||
<spp:AppItem.ClickCommandParameter>
|
||||
<MultiBinding Converter="{StaticResource StringFormatConverter}">
|
||||
<CompiledBinding Path="." Source="{x:Static spp:HomePageViewModel.OfficialWebsite_Article_Detail_}" />
|
||||
<CompiledBinding Path="Id" />
|
||||
</MultiBinding>
|
||||
</spp:AppItem.ClickCommandParameter>
|
||||
<spp:AppItem.Image>
|
||||
<spp:Image2 RenderOptions.BitmapInterpolationMode="HighQuality" Source="{Binding CoverUrl}" />
|
||||
</spp:AppItem.Image>
|
||||
<spp:AppItem.Status>
|
||||
<Border Classes="Status">
|
||||
<TextBlock Text="{Binding CreationTime, StringFormat={}{0:yyyy-MM-dd}}" />
|
||||
</Border>
|
||||
</spp:AppItem.Status>
|
||||
</spp:AppItem>
|
||||
</DataTemplate>
|
||||
</spp:CarouselItems.ItemTemplate>
|
||||
</spp:CarouselItems>-->
|
||||
|
||||
<DockPanel>
|
||||
<TextBlock
|
||||
@@ -268,7 +273,172 @@
|
||||
Theme="{StaticResource HyperlinkButton}" />
|
||||
</DockPanel>
|
||||
|
||||
<ScrollViewer
|
||||
<spp:CarouselItems
|
||||
MinHeight="250"
|
||||
ItemsPerPage="4"
|
||||
ItemsSource="{Binding Shops}">
|
||||
<spp:CarouselItems.ItemTemplate>
|
||||
<DataTemplate x:DataType="spp:ShopRecommendGoodItem">
|
||||
<Border spp:Animations.EnableAnimations="True">
|
||||
<spp:AppItem
|
||||
Title="{Binding Name}"
|
||||
MaxWidth="260"
|
||||
Classes="Vertical Scale"
|
||||
ClickCommand="{Binding OpenBrowserCommand, Source={x:Static spp:App.Instance}}"
|
||||
Cursor="Hand"
|
||||
Tags="{Binding GoodsAllCategorysText}"
|
||||
ToolTip.Tip="{Binding Brief}">
|
||||
<spp:AppItem.ClickCommandParameter>
|
||||
<MultiBinding Converter="{StaticResource StringFormatConverter}">
|
||||
<CompiledBinding Path="." Source="{x:Static spp:HomePageViewModel.WattGame_Goods_Detail_}" />
|
||||
<CompiledBinding Path="Id" />
|
||||
</MultiBinding>
|
||||
</spp:AppItem.ClickCommandParameter>
|
||||
<spp:AppItem.Image>
|
||||
<spp:Image2
|
||||
EnableCancelToken="False"
|
||||
RenderOptions.BitmapInterpolationMode="HighQuality"
|
||||
Source="{Binding Image}" />
|
||||
</spp:AppItem.Image>
|
||||
<spp:AppItem.Status>
|
||||
<Border Classes="Status">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock
|
||||
VerticalAlignment="Center"
|
||||
FontSize="12"
|
||||
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
|
||||
IsVisible="{Binding !!DiscountValue}"
|
||||
Text="{Binding Mktprice, Converter={StaticResource StringFormatConverter}, ConverterParameter={x:Static spp:CurrencyCode.CNY}}"
|
||||
TextDecorations="Strikethrough" />
|
||||
<TextBlock
|
||||
VerticalAlignment="Center"
|
||||
FontSize="14"
|
||||
Text="{Binding Price, Converter={StaticResource StringFormatConverter}, ConverterParameter={x:Static spp:CurrencyCode.CNY}}" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</spp:AppItem.Status>
|
||||
<spp:AppItem.ActionButton>
|
||||
<Border
|
||||
Height="24"
|
||||
Padding="8,0"
|
||||
Background="#4c6b22"
|
||||
ClipToBounds="True"
|
||||
CornerRadius="4"
|
||||
IsVisible="{Binding !!DiscountValue}">
|
||||
<TextBlock
|
||||
VerticalAlignment="Center"
|
||||
Foreground="#beee11"
|
||||
Text="{Binding DiscountValue, StringFormat={}-{0}%}" />
|
||||
</Border>
|
||||
</spp:AppItem.ActionButton>
|
||||
</spp:AppItem>
|
||||
</Border>
|
||||
</DataTemplate>
|
||||
</spp:CarouselItems.ItemTemplate>
|
||||
</spp:CarouselItems>
|
||||
|
||||
<!--<spp:CarouselBanner
|
||||
MinHeight="250"
|
||||
HorizontalContentAlignment="Stretch"
|
||||
VerticalContentAlignment="Stretch"
|
||||
ItemsSource="{Binding Shops}">
|
||||
<spp:CarouselBanner.Resources>
|
||||
<DataTemplate x:Key="ShopItem" DataType="spp:ShopRecommendGoodItem">
|
||||
<Border spp:Animations.EnableAnimations="True">
|
||||
<spp:AppItem
|
||||
Title="{Binding Name}"
|
||||
MaxWidth="260"
|
||||
Classes="Vertical Scale"
|
||||
ClickCommand="{Binding OpenBrowserCommand, Source={x:Static spp:App.Instance}}"
|
||||
Cursor="Hand"
|
||||
Tags="{Binding GoodsAllCategorysText}"
|
||||
ToolTip.Tip="{Binding Brief}">
|
||||
<spp:AppItem.ClickCommandParameter>
|
||||
<MultiBinding Converter="{StaticResource StringFormatConverter}">
|
||||
<CompiledBinding Path="." Source="{x:Static spp:HomePageViewModel.WattGame_Goods_Detail_}" />
|
||||
<CompiledBinding Path="Id" />
|
||||
</MultiBinding>
|
||||
</spp:AppItem.ClickCommandParameter>
|
||||
<spp:AppItem.Image>
|
||||
<spp:Image2 RenderOptions.BitmapInterpolationMode="HighQuality" Source="{Binding Image}" />
|
||||
</spp:AppItem.Image>
|
||||
<spp:AppItem.Status>
|
||||
<Border Classes="Status">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock
|
||||
VerticalAlignment="Center"
|
||||
FontSize="12"
|
||||
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
|
||||
IsVisible="{Binding !!DiscountValue}"
|
||||
Text="{Binding Mktprice, Converter={StaticResource StringFormatConverter}, ConverterParameter={x:Static spp:CurrencyCode.CNY}}"
|
||||
TextDecorations="Strikethrough" />
|
||||
<TextBlock
|
||||
VerticalAlignment="Center"
|
||||
FontSize="14"
|
||||
Text="{Binding Price, Converter={StaticResource StringFormatConverter}, ConverterParameter={x:Static spp:CurrencyCode.CNY}}" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</spp:AppItem.Status>
|
||||
<spp:AppItem.ActionButton>
|
||||
<Border
|
||||
Height="24"
|
||||
Padding="8,0"
|
||||
Background="#4c6b22"
|
||||
ClipToBounds="True"
|
||||
CornerRadius="4"
|
||||
IsVisible="{Binding !!DiscountValue}">
|
||||
<TextBlock
|
||||
VerticalAlignment="Center"
|
||||
Foreground="#beee11"
|
||||
Text="{Binding DiscountValue, StringFormat={}-{0}%}" />
|
||||
</Border>
|
||||
</spp:AppItem.ActionButton>
|
||||
</spp:AppItem>
|
||||
</Border>
|
||||
</DataTemplate>
|
||||
</spp:CarouselBanner.Resources>
|
||||
<spp:CarouselBanner.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<DataTemplate.DataType>
|
||||
<x:Type x:TypeArguments="spp:ShopRecommendGoodItem" TypeName="col:List" />
|
||||
</DataTemplate.DataType>
|
||||
<Grid ColumnDefinitions="*,10,*,10,*,10,*">
|
||||
<ContentPresenter
|
||||
Grid.Column="0"
|
||||
Content="{Binding [0]}"
|
||||
ContentTemplate="{StaticResource ShopItem}" />
|
||||
<ContentPresenter
|
||||
Grid.Column="2"
|
||||
Content="{Binding [1]}"
|
||||
ContentTemplate="{StaticResource ShopItem}" />
|
||||
<ContentPresenter
|
||||
Grid.Column="4"
|
||||
Content="{Binding [2]}"
|
||||
ContentTemplate="{StaticResource ShopItem}" />
|
||||
<ContentPresenter
|
||||
Grid.Column="6"
|
||||
Content="{Binding [3]}"
|
||||
ContentTemplate="{StaticResource ShopItem}" />
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
-->
|
||||
<!--<DataTemplate>
|
||||
<DataTemplate.DataType>
|
||||
<x:Type x:TypeArguments="spp:ShopRecommendGoodItem" TypeName="col:IEnumerable" />
|
||||
</DataTemplate.DataType>
|
||||
<ItemsControl ItemTemplate="{StaticResource ShopItem}" ItemsSource="{Binding}">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<Grid ColumnDefinitions="*,*,*,*" />
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
</ItemsControl>
|
||||
</DataTemplate>-->
|
||||
<!--
|
||||
</spp:CarouselBanner.ItemTemplate>
|
||||
</spp:CarouselBanner>-->
|
||||
|
||||
<!--<ScrollViewer
|
||||
Name="ShopsScrollViewer"
|
||||
Margin="0,0,0,20"
|
||||
Padding="0,0,0,15"
|
||||
@@ -336,7 +506,7 @@
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</ScrollViewer>
|
||||
</ScrollViewer>-->
|
||||
|
||||
<TextBlock
|
||||
Margin="-3,0,0,0"
|
||||
|
||||
@@ -7,7 +7,7 @@ public partial class HomePage : ReactiveUserControl<HomePageViewModel>
|
||||
public HomePage()
|
||||
{
|
||||
InitializeComponent();
|
||||
this.SetViewModel<HomePageViewModel>(false);
|
||||
this.SetViewModel<HomePageViewModel>();
|
||||
|
||||
//ShopsScrollViewer.PointerWheelChanged += ShopsScrollViewer_PointerWheelChanged;
|
||||
}
|
||||
|
||||
@@ -263,19 +263,19 @@
|
||||
</MenuItem.Header>
|
||||
</MenuItem>
|
||||
<MenuItem Header="-" IsVisible="{Binding Path=User, Mode=OneWay, Source={x:Static s:UserService.Current}, Converter={StaticResource IsNullConverter}, ConverterParameter=invert}" />
|
||||
<MenuItem
|
||||
Command="{Binding OpenBrowserCommand, Source={x:Static spp:App.Instance}}"
|
||||
<MenuItem
|
||||
Command="{Binding Path=OpenAuthOfficialCommand, Source={x:Static s:UserService.Current}}"
|
||||
CommandParameter="https://steampp.net/account/center/info"
|
||||
Header="{Binding Path=Res.PersonalCenter, Mode=OneWay, Source={x:Static s:ResourceService.Current}}"
|
||||
IsVisible="{Binding Path=User, Mode=OneWay, Source={x:Static s:UserService.Current}, Converter={StaticResource IsNullConverter}, ConverterParameter=invert}" />
|
||||
<!--<MenuItem Header="{Binding Path=Res.MyOrder, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" IsVisible="{Binding Path=User, Mode=OneWay, Source={x:Static s:UserService.Current}, Converter={StaticResource IsNullConverter}, ConverterParameter=invert}" />-->
|
||||
<MenuItem
|
||||
Command="{Binding OpenBrowserCommand, Source={x:Static spp:App.Instance}}"
|
||||
<!--<MenuItem Header="{Binding Path=Res.MyOrder, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" IsVisible="{Binding Path=User, Mode=OneWay, Source={x:Static s:UserService.Current}, Converter={StaticResource IsNullConverter}, ConverterParameter=invert}" />-->
|
||||
<MenuItem
|
||||
Command="{Binding Path=OpenAuthOfficialCommand, Source={x:Static s:UserService.Current}}"
|
||||
CommandParameter="https://steampp.net/account/center/bind"
|
||||
Header="{Binding Path=Res.BindThirdPartyAccount, Mode=OneWay, Source={x:Static s:ResourceService.Current}}"
|
||||
IsVisible="{Binding Path=User, Mode=OneWay, Source={x:Static s:UserService.Current}, Converter={StaticResource IsNullConverter}, ConverterParameter=invert}" />
|
||||
<MenuItem
|
||||
Command="{Binding OpenBrowserCommand, Source={x:Static spp:App.Instance}}"
|
||||
<MenuItem
|
||||
Command="{Binding Path=OpenAuthOfficialCommand, Source={x:Static s:UserService.Current}}"
|
||||
CommandParameter="https://steampp.net/account/center/safe"
|
||||
Header="{Binding Path=Res.PersonalSafe, Mode=OneWay, Source={x:Static s:ResourceService.Current}}"
|
||||
IsVisible="{Binding Path=User, Mode=OneWay, Source={x:Static s:UserService.Current}, Converter={StaticResource IsNullConverter}, ConverterParameter=invert}" />
|
||||
|
||||
@@ -7,7 +7,7 @@ public partial class SettingsPage : PageBase<SettingsPageViewModel>
|
||||
public SettingsPage()
|
||||
{
|
||||
InitializeComponent();
|
||||
this.SetViewModel<SettingsPageViewModel>(false);
|
||||
this.SetViewModel<SettingsPageViewModel>();
|
||||
|
||||
//SettingsScrollTab.SelectionChanged += SettingsScrollTab_SelectionChanged;
|
||||
|
||||
|
||||
@@ -102,6 +102,7 @@
|
||||
Command="{Binding $parent[spp:Settings_Plugin].((spp:SettingsPageViewModel)DataContext).DeletePlugin_Click}"
|
||||
CommandParameter="{Binding Data}"
|
||||
Content="{Binding Path=Res.Plugin_Delete, Mode=OneWay, Source={x:Static s:ResourceService.Current}}"
|
||||
Foreground="{DynamicResource SystemFillColorCriticalBrush}"
|
||||
IsClickEnabled="True" />
|
||||
</ui:SettingsExpander>
|
||||
</DataTemplate>
|
||||
|
||||
@@ -66,7 +66,7 @@
|
||||
Margin="0,5"
|
||||
HorizontalAlignment="Stretch"
|
||||
HorizontalContentAlignment="Left"
|
||||
Command="{ReflectionBinding $parent[UserControl].DataContext.ChooseChannel}"
|
||||
Command="{Binding $parent[UserControl].((spp:LoginOrRegisterWindowViewModel)DataContext).ChooseChannel}"
|
||||
CommandParameter="{Binding Id, Mode=OneWay}">
|
||||
<DockPanel>
|
||||
<Panel Margin="5,0,0,0" DockPanel.Dock="Left">
|
||||
|
||||
@@ -34,7 +34,7 @@ public partial class LoginOrRegisterPage : ReactiveUserControl<LoginOrRegisterWi
|
||||
protected override void OnLoaded(RoutedEventArgs e)
|
||||
{
|
||||
base.OnLoaded(e);
|
||||
this.SetViewModel<LoginOrRegisterWindowViewModel>(false);
|
||||
this.SetViewModel<LoginOrRegisterWindowViewModel>();
|
||||
|
||||
if (this.ViewModel != null)
|
||||
{
|
||||
@@ -42,9 +42,21 @@ public partial class LoginOrRegisterPage : ReactiveUserControl<LoginOrRegisterWi
|
||||
if (UserService.Current.IsAuthenticated)
|
||||
{
|
||||
Toast.Show(ToastIcon.Info, "当前已是登录状态");
|
||||
this.ViewModel.Close();
|
||||
this.ViewModel.Close(false);
|
||||
}
|
||||
}
|
||||
Application.Current!.UrlsOpened += Current_UrlsOpened;
|
||||
}
|
||||
|
||||
private async void Current_UrlsOpened(object? sender, UrlOpenedEventArgs e)
|
||||
{
|
||||
var loginUrl = e.Urls.Where(x => x.StartsWith(Constants.UrlSchemes.Login)).FirstOrDefault();
|
||||
if (loginUrl != null)
|
||||
{
|
||||
//var token = loginUrl.TrimStart(Constants.UrlSchemes.Login);
|
||||
var token = loginUrl[Constants.UrlSchemes.Login.Length..];
|
||||
await ThirdPartyLoginHelper.LoginForStr(token);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnDataContextChanged(EventArgs e)
|
||||
@@ -59,6 +71,7 @@ public partial class LoginOrRegisterPage : ReactiveUserControl<LoginOrRegisterWi
|
||||
|
||||
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
|
||||
{
|
||||
Application.Current!.UrlsOpened -= Current_UrlsOpened;
|
||||
base.OnDetachedFromVisualTree(e);
|
||||
if (DataContext is LoginOrRegisterWindowViewModel vm)
|
||||
{
|
||||
|
||||
@@ -52,6 +52,7 @@ public sealed partial class MainWindow : ReactiveAppWindow<MainWindowViewModel>
|
||||
}
|
||||
Task2.InBackground(async () =>
|
||||
{
|
||||
IViewModelManager.Instance.Get<HomePageViewModel>().GetServerContent();
|
||||
await AdvertiseService.Current.RefrshAdvertiseAsync();
|
||||
NoticeService.Current.GetNewsAsync();
|
||||
});
|
||||
@@ -169,13 +170,14 @@ public sealed class AppSplashScreen : IApplicationSplashScreen
|
||||
#pragma warning disable SA1114 // Parameter list should follow declaration
|
||||
IViewModelManager.Instance.InitViewModels(new TabItemViewModel[]
|
||||
{
|
||||
new MenuTabItemViewModel("Welcome")
|
||||
{
|
||||
PageType = typeof(HomePage),
|
||||
IsResourceGet = true,
|
||||
IconKey = "avares://BD.WTTS.Client.Avalonia/UI/Assets/Icons/home.ico",
|
||||
},
|
||||
}, ImmutableArray.Create<TabItemViewModel>(
|
||||
new MenuTabItemViewModel("Welcome")
|
||||
{
|
||||
PageType = typeof(HomePage),
|
||||
IsResourceGet = true,
|
||||
IconKey = "avares://BD.WTTS.Client.Avalonia/UI/Assets/Icons/home.ico",
|
||||
},
|
||||
},
|
||||
ImmutableArray.Create<TabItemViewModel>(
|
||||
#if DEBUG
|
||||
new MenuTabItemViewModel("Debug")
|
||||
{
|
||||
|
||||
@@ -93,6 +93,9 @@ public interface IPCSubProcessService : IDisposable
|
||||
var nativeLibraryPath = Environment.GetEnvironmentVariable(EnvKey_NativeLibraryPath);
|
||||
if (!string.IsNullOrWhiteSpace(nativeLibraryPath))
|
||||
{
|
||||
var nativeLibraryPaths = nativeLibraryPath.Split(';',
|
||||
StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
// 监听当前应用程序域的程序集加载
|
||||
AppDomain.CurrentDomain.AssemblyLoad += (_, args)
|
||||
=> CurrentDomain_AssemblyLoad(args.LoadedAssembly);
|
||||
@@ -119,23 +122,34 @@ public interface IPCSubProcessService : IDisposable
|
||||
return libraryName;
|
||||
}
|
||||
var libraryFileName = GetLibraryFileName(libraryName);
|
||||
var libraryPath = Path.Combine(nativeLibraryPath, libraryFileName);
|
||||
if (File.Exists(libraryPath) &&
|
||||
NativeLibrary.TryLoad(libraryPath, out var handle))
|
||||
string libraryPath = libraryName;
|
||||
try
|
||||
{
|
||||
return handle;
|
||||
}
|
||||
else if (!OperatingSystem.IsWindows() && !libraryFileName.StartsWith("lib", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
libraryFileName = $"lib{libraryFileName}";
|
||||
libraryPath = Path.Combine(nativeLibraryPath, libraryFileName);
|
||||
if (File.Exists(libraryPath) &&
|
||||
NativeLibrary.TryLoad(libraryPath, out handle))
|
||||
foreach (var nativeLibraryPath in nativeLibraryPaths)
|
||||
{
|
||||
return handle;
|
||||
libraryPath = Path.Combine(nativeLibraryPath, libraryFileName);
|
||||
if (File.Exists(libraryPath) &&
|
||||
NativeLibrary.TryLoad(libraryPath, out var handle))
|
||||
{
|
||||
return handle;
|
||||
}
|
||||
else if (!OperatingSystem.IsWindows() && !libraryFileName.StartsWith("lib", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
libraryFileName = $"lib{libraryFileName}";
|
||||
libraryPath = Path.Combine(nativeLibraryPath, libraryFileName);
|
||||
if (File.Exists(libraryPath) &&
|
||||
NativeLibrary.TryLoad(libraryPath, out handle))
|
||||
{
|
||||
return handle;
|
||||
}
|
||||
}
|
||||
}
|
||||
return NativeLibrary.Load(libraryName, assembly, searchPath);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new FileNotFoundException(null, libraryPath, ex);
|
||||
}
|
||||
return NativeLibrary.Load(libraryName, assembly, searchPath);
|
||||
}
|
||||
NativeLibrary.SetDllImportResolver(loadedAssembly, Delegate);
|
||||
}
|
||||
|
||||
+3
-3
@@ -32,9 +32,9 @@ sealed class ReverseProxyHttpClientHandler : DelegatingHandler
|
||||
var tlsSniValue = domainConfig.GetTlsSniPattern().WithDomain(uri.Host).WithRandom();
|
||||
request.SetRequestContext(new RequestContext(isHttps, tlsSniValue));
|
||||
|
||||
//// 设置请求头 host,修改协议为 http
|
||||
//request.Headers.Host = uri.Host;
|
||||
//request.RequestUri = new UriBuilder(uri) { Scheme = Uri.UriSchemeHttp }.Uri;
|
||||
// 设置请求头 host,修改协议为 http
|
||||
request.Headers.Host = uri.Host;
|
||||
request.RequestUri = new UriBuilder(uri) { Scheme = Uri.UriSchemeHttp }.Uri;
|
||||
|
||||
if (domainConfig.Timeout != null)
|
||||
{
|
||||
|
||||
+1
-1
@@ -191,7 +191,7 @@ sealed class HttpLocalRequestMiddleware
|
||||
}
|
||||
if (hasReqContent)
|
||||
{
|
||||
req.Content!.Headers.ContentType = MediaTypeHeaderValue.Parse(context.Request.ContentType);
|
||||
req.Content!.Headers.ContentType = MediaTypeHeaderValue.Parse(context.Request.ContentType.ThrowIsNull());
|
||||
req.Content.Headers.ContentLength = context.Request.ContentLength;
|
||||
}
|
||||
var rsp = await HttpClient.SendAsync(req, HttpCompletionOption.ResponseHeadersRead, context.RequestAborted).ConfigureAwait(false);
|
||||
|
||||
+1
-1
@@ -89,7 +89,7 @@ sealed partial class HttpReverseProxyMiddleware
|
||||
|
||||
var forwarderRequestConfig = new ForwarderRequestConfig()
|
||||
{
|
||||
Version = context.Request.Protocol.StartsWith("HTTP/2.0") ? System.Net.HttpVersion.Version20 : System.Net.HttpVersion.Version11,
|
||||
Version = context.Request.Protocol.StartsWith("HTTP/2") ? System.Net.HttpVersion.Version20 : System.Net.HttpVersion.Version11,
|
||||
};
|
||||
|
||||
var error = await httpForwarder.SendAsync(context, destinationPrefix, httpClient, forwarderRequestConfig, HttpTransformer.Empty);
|
||||
|
||||
@@ -10,14 +10,16 @@
|
||||
|
||||
<ItemGroup>
|
||||
<None Remove="UI\Assets\accelerator.ico" />
|
||||
<None Remove="UI\Assets\defaultBackground.png" />
|
||||
<None Remove="UI\Assets\script.ico" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<AvaloniaResource Include="UI\Assets\accelerator.ico" />
|
||||
<AvaloniaResource Include="UI\Assets\defaultBackground.png" />
|
||||
<AvaloniaResource Include="UI\Assets\script.ico" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Include="..\AssemblyInfo.Xaml.cs">
|
||||
<LinkBase>Properties</LinkBase>
|
||||
@@ -108,20 +110,57 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Update="Properties\Resources.Designer.cs">
|
||||
<DesignTime>True</DesignTime>
|
||||
<AutoGen>True</AutoGen>
|
||||
<DependentUpon>Resources.resx</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Update="Properties\Resources.Designer.cs">
|
||||
<DesignTime>True</DesignTime>
|
||||
<AutoGen>True</AutoGen>
|
||||
<DependentUpon>Resources.resx</DependentUpon>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Update="Properties\Resources.resx">
|
||||
<Generator>ResXFileCodeGenerator</Generator>
|
||||
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
|
||||
</EmbeddedResource>
|
||||
<EmbeddedResource Update="Properties\Resources.resx">
|
||||
<Generator>ResXFileCodeGenerator</Generator>
|
||||
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
|
||||
</EmbeddedResource>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'windows'">
|
||||
<Compile Update="Properties\WinDivertResource.Designer.cs">
|
||||
<DesignTime>True</DesignTime>
|
||||
<AutoGen>True</AutoGen>
|
||||
<DependentUpon>WinDivertResource.resx</DependentUpon>
|
||||
</Compile>
|
||||
<EmbeddedResource Update="Properties\WinDivertResource.resx">
|
||||
<Generator>ResXFileCodeGenerator</Generator>
|
||||
<LastGenOutput>WinDivertResource.Designer.cs</LastGenOutput>
|
||||
</EmbeddedResource>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) != 'windows'">
|
||||
<Compile Remove="Properties\WinDivertResource.Designer.cs" />
|
||||
<EmbeddedResource Remove="Properties\WinDivertResource.resx" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Include="$(MSBuildProjectDirectory)\..\XunYouSDK\**\*.cs">
|
||||
<LinkBase>XunYouSDK</LinkBase>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition=" Exists('$(MSBuildProjectDirectory)\..\..\..\XunYouSDK\src\Sample.ConsoleApp\XunYouSDK\XunYouSDK.AppId.cs') ">
|
||||
<Compile Remove="$(MSBuildProjectDirectory)\..\XunYouSDK\XunYouSDK.AppId.cs" />
|
||||
<Compile Include="$(MSBuildProjectDirectory)\..\..\..\XunYouSDK\src\Sample.ConsoleApp\XunYouSDK\XunYouSDK.AppId.cs">
|
||||
<LinkBase>XunYouSDK</LinkBase>
|
||||
</Compile>
|
||||
<!--<None Condition=" $([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'windows' AND Exists('$(MSBuildProjectDirectory)\..\..\..\XunYouSDK\src\Sample.ConsoleApp\xunyoucall.dll') " Include="$(MSBuildProjectDirectory)\..\..\..\XunYouSDK\src\Sample.ConsoleApp\xunyoucall.dll">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>-->
|
||||
</ItemGroup>
|
||||
|
||||
<Import Project="..\TFM_NETX_WITH_DESKTOP.props" />
|
||||
|
||||
|
||||
<ItemGroup>
|
||||
<UpToDateCheckInput Remove="UI\Views\Pages\AcceleratorPage2.axaml" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -8,7 +8,8 @@ namespace BD.WTTS.Entities;
|
||||
[DebuggerDisplay("{DebuggerDisplay(),nq}")]
|
||||
public sealed class Script : IEntity<int>
|
||||
{
|
||||
public const string TableName = "1B2D820C";
|
||||
//TableName = ScriptV2
|
||||
public const string TableName = "CAEC0F80";
|
||||
public const string ColumnName_Id = "E386BC36";
|
||||
public const string ColumnName_Enable = "6DB11594";
|
||||
public const string ColumnName_CachePath = "997C6B59";
|
||||
|
||||
@@ -0,0 +1,132 @@
|
||||
#if WINDOWS
|
||||
using WinDivertBinRes = BD.WTTS.Properties.WinDivertResource;
|
||||
|
||||
namespace Mobius.Helpers;
|
||||
|
||||
/// <summary>
|
||||
/// 初始化 WinDivert 助手类
|
||||
/// </summary>
|
||||
static class WinDivertInitHelper
|
||||
{
|
||||
const string libraryName = "WinDivert.dll";
|
||||
|
||||
static readonly string? libraryPath;
|
||||
static readonly string? libraryDirPath;
|
||||
//static nint libraryIntPtr;
|
||||
|
||||
///// <summary>
|
||||
///// 配置本机库路径解析
|
||||
///// </summary>
|
||||
///// <param name="libraryName"></param>
|
||||
///// <param name="assembly"></param>
|
||||
///// <param name="searchPath"></param>
|
||||
///// <returns></returns>
|
||||
//public static nint DllImportResolver(string libraryName,
|
||||
// Assembly assembly,
|
||||
// DllImportSearchPath? searchPath)
|
||||
//{
|
||||
// switch (libraryName)
|
||||
// {
|
||||
// case WinDivertInitHelper.libraryName:
|
||||
// if (libraryIntPtr != default)
|
||||
// return libraryIntPtr;
|
||||
// break;
|
||||
// }
|
||||
// return default;
|
||||
//}
|
||||
|
||||
static bool isInitialize = false;
|
||||
|
||||
/// <summary>
|
||||
/// 初始化 WinDivert
|
||||
/// </summary>
|
||||
public static async Task InitializeAsync()
|
||||
{
|
||||
if (libraryPath != null && libraryDirPath != null)
|
||||
{
|
||||
bool isWrite;
|
||||
var libraryFileInfo = new FileInfo(libraryPath);
|
||||
if (libraryFileInfo.Exists)
|
||||
{
|
||||
if (libraryFileInfo.Length > 20000000L) // 文件不可能大于 20 MB
|
||||
{
|
||||
libraryFileInfo.Delete();
|
||||
isWrite = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
var libraryBytes = await File.ReadAllBytesAsync(libraryPath);
|
||||
var libraryHash = SHA256.HashData(libraryBytes);
|
||||
var libraryHashRes = SHA256.HashData(WinDivertBinRes.WinDivert_dll);
|
||||
if (libraryHash.SequenceEqual(libraryHashRes)) // 哈希值相同,不写入
|
||||
{
|
||||
isWrite = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
libraryFileInfo.Delete();
|
||||
isWrite = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
isWrite = true;
|
||||
}
|
||||
|
||||
if (isWrite) // 检查文件不存在时写入
|
||||
{
|
||||
switch (RuntimeInformation.ProcessArchitecture)
|
||||
{
|
||||
//case Architecture.X86:
|
||||
// break;
|
||||
case Architecture.X64:
|
||||
IOPath.DirCreateByNotExists(libraryDirPath);
|
||||
var task1 = File.WriteAllBytesAsync(Path.Combine(libraryDirPath, "WinDivert64.sys"), WinDivertBinRes.WinDivert64_sys);
|
||||
var task2 = File.WriteAllBytesAsync(libraryPath, WinDivertBinRes.WinDivert_dll);
|
||||
await Task.WhenAll(task1, task2);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isInitialize)
|
||||
return;
|
||||
isInitialize = true;
|
||||
//if (IsSupported && libraryPath != null)
|
||||
//{
|
||||
// try
|
||||
// {
|
||||
// libraryIntPtr = NativeLibrary.Load(libraryPath);
|
||||
// }
|
||||
// catch
|
||||
// {
|
||||
// }
|
||||
// if (libraryIntPtr != default)
|
||||
// {
|
||||
// NativeLibrary.SetDllImportResolver(typeof(WinDivertSharp.WinDivert).Assembly, DllImportResolver);
|
||||
// return;
|
||||
// }
|
||||
//}
|
||||
IsSupported = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 当前运行环境是否支持使用 WinDivert
|
||||
/// </summary>
|
||||
public static bool IsSupported { get; private set; }
|
||||
|
||||
static WinDivertInitHelper()
|
||||
{
|
||||
switch (RuntimeInformation.OSArchitecture)
|
||||
{
|
||||
//case Architecture.X86:
|
||||
case Architecture.X64:
|
||||
IsSupported = true;
|
||||
libraryPath = GlobalDllImportResolver.GetLibraryPath("WinDivert.dll");
|
||||
libraryDirPath = Path.GetDirectoryName(libraryPath);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -11,6 +11,7 @@ sealed class AcceleratorAutoMapperProfile : Profile
|
||||
.ForMember(x => x.Icon, d => d.MapFrom(x => x.IconUrl))
|
||||
.ForMember(x => x.Description, d => d.MapFrom(x => x.Describe))
|
||||
.ForMember(x => x.Enable, d => d.MapFrom(x => !x.Disable))
|
||||
.ForMember(x => x.IsBuild, d => d.MapFrom(x => x.IsCompile))
|
||||
.ForMember(x => x.Id, d => d.MapFrom(x => x.LocalId));
|
||||
CreateMap<Script, ScriptDTO>()
|
||||
.ForMember(x => x.LocalId, d => d.MapFrom(x => x.Id))
|
||||
@@ -18,6 +19,7 @@ sealed class AcceleratorAutoMapperProfile : Profile
|
||||
.ForMember(x => x.Describe, d => d.MapFrom(x => x.Description))
|
||||
.ForMember(x => x.IconUrl, d => d.MapFrom(x => x.Icon))
|
||||
.ForMember(x => x.Disable, d => d.MapFrom(x => !x.Enable))
|
||||
.ForMember(x => x.IsCompile, d => d.MapFrom(x => x.IsBuild))
|
||||
.ForMember(x => x.Id, d => d.MapFrom(x => x.Pid));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
namespace Mobius.Models;
|
||||
|
||||
/// <summary>
|
||||
/// 迅游加速状态模型类
|
||||
/// </summary>
|
||||
[MP2Obj(MP2SerializeLayout.Explicit)]
|
||||
public partial class XunYouAccelStateModel
|
||||
{
|
||||
/// <inheritdoc cref="XunYouState"/>
|
||||
[MP2Key(0)]
|
||||
public XunYouState State { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 当前加速的游戏 id。
|
||||
/// </summary>
|
||||
[MP2Key(1)]
|
||||
public int GameId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 当前加速的游戏区 id。
|
||||
/// </summary>
|
||||
[MP2Key(2)]
|
||||
public int AreaId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 当前加速游戏的服 id,没有则为 0。
|
||||
/// </summary>
|
||||
[MP2Key(3)]
|
||||
public int ServerId { get; set; }
|
||||
|
||||
/// <inheritdoc cref="XunYouAccelStateEx"/>
|
||||
[MP2Key(4)]
|
||||
public XunYouAccelStateEx? AccelState { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,216 @@
|
||||
namespace BD.WTTS.Models;
|
||||
|
||||
[MP2Obj(MP2SerializeLayout.Explicit)]
|
||||
public partial class XunYouGameViewModel : ReactiveSerializationObject, IReactiveObject
|
||||
{
|
||||
/// <inheritdoc cref="XunYouGame"/>
|
||||
[MP2Key(0), JsonPropertyOrder(0)]
|
||||
public XunYouGame Model { get; set; } = model;
|
||||
|
||||
/// <summary>
|
||||
/// 加速延迟
|
||||
/// </summary>
|
||||
[Reactive]
|
||||
[XmlIgnore, IgnoreDataMember, SystemTextJsonIgnore, NewtonsoftJsonIgnore, MPIgnore, MP2Ignore]
|
||||
public uint PingValue { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 加速延迟是否无效
|
||||
/// </summary>
|
||||
[Reactive]
|
||||
[XmlIgnore, IgnoreDataMember, SystemTextJsonIgnore, NewtonsoftJsonIgnore, MPIgnore, MP2Ignore]
|
||||
public bool PingValueIsInvalid { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 设置加速延迟
|
||||
/// </summary>
|
||||
/// <param name="value"></param>
|
||||
public void SetPingValue(uint value)
|
||||
{
|
||||
PingValueIsInvalid = value >= 1000;
|
||||
if (PingValueIsInvalid) // 延迟大于等于1000,就显示--
|
||||
PingValue = default;
|
||||
else
|
||||
PingValue = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 加速丢包率
|
||||
/// </summary>
|
||||
[Reactive]
|
||||
[XmlIgnore, IgnoreDataMember, SystemTextJsonIgnore, NewtonsoftJsonIgnore, MPIgnore, MP2Ignore]
|
||||
public float PingSpeedLoss { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 游戏信息
|
||||
/// </summary>
|
||||
[Reactive]
|
||||
[XmlIgnore, IgnoreDataMember, SystemTextJsonIgnore, NewtonsoftJsonIgnore, MPIgnore, MP2Ignore]
|
||||
public XunYouGameInfo? GameInfo { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否正在加速等待
|
||||
/// </summary>
|
||||
[Reactive]
|
||||
[MP2Ignore, SystemTextJsonIgnore]
|
||||
public bool IsAccelerating { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 加速进度
|
||||
/// </summary>
|
||||
[Reactive]
|
||||
[MP2Ignore, SystemTextJsonIgnore]
|
||||
public int AcceleratingProgress { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否已加速
|
||||
/// </summary>
|
||||
[Reactive]
|
||||
[MP2Ignore, SystemTextJsonIgnore]
|
||||
public bool IsAccelerated { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否正在停止加速等待
|
||||
/// </summary>
|
||||
[Reactive]
|
||||
[MP2Ignore, SystemTextJsonIgnore]
|
||||
public bool IsStopAccelerating { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 最后加速时间
|
||||
/// </summary>
|
||||
[Reactive]
|
||||
[MP2Key(1), JsonPropertyOrder(1)]
|
||||
|
||||
public DateTimeOffset? LastAccelerateTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 加速的区服
|
||||
/// </summary>
|
||||
[Reactive]
|
||||
[MP2Key(2), JsonPropertyOrder(2)]
|
||||
public XunYouGameArea? SelectedArea { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 加速的区服服务器
|
||||
/// </summary>
|
||||
[Reactive]
|
||||
[MP2Key(3), JsonPropertyOrder(3)]
|
||||
public XunYouGameServer? SelectedServer { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="XunYouGame"/> 的视图模型
|
||||
/// </summary>
|
||||
sealed partial class XunYouGameViewModel(XunYouGame model) : ReactiveSerializationObject
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="XunYouGameViewModel"/> class.
|
||||
/// </summary>
|
||||
[MP2Constructor, SystemTextJsonConstructor]
|
||||
public XunYouGameViewModel() : this(new())
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ViewModel => Model
|
||||
/// </summary>
|
||||
/// <param name="vm"></param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static implicit operator XunYouGame(XunYouGameViewModel vm) => vm.Model;
|
||||
|
||||
/// <summary>
|
||||
/// Model => ViewModel
|
||||
/// </summary>
|
||||
/// <param name="model"></param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static implicit operator XunYouGameViewModel(XunYouGame model) => new(model);
|
||||
|
||||
/// <inheritdoc cref="XunYouGame.Id"/>
|
||||
[XmlIgnore, IgnoreDataMember, SystemTextJsonIgnore, NewtonsoftJsonIgnore, MPIgnore, MP2Ignore]
|
||||
public int Id { get => Model.Id; set => Model.Id = value; }
|
||||
|
||||
/// <inheritdoc cref="XunYouGame.Name"/>
|
||||
[XmlIgnore, IgnoreDataMember, SystemTextJsonIgnore, NewtonsoftJsonIgnore, MPIgnore, MP2Ignore]
|
||||
public string? Name { get => Model.Name; set => Model.Name = value; }
|
||||
|
||||
/// <inheritdoc cref="XunYouGame.IconUrl"/>
|
||||
[XmlIgnore, IgnoreDataMember, SystemTextJsonIgnore, NewtonsoftJsonIgnore, MPIgnore, MP2Ignore]
|
||||
public string? IconUrl { get => Model.IconUrl; set => Model.IconUrl = value; }
|
||||
|
||||
/// <inheritdoc cref="XunYouGame.PicUrl"/>
|
||||
[XmlIgnore, IgnoreDataMember, SystemTextJsonIgnore, NewtonsoftJsonIgnore, MPIgnore, MP2Ignore]
|
||||
public string? PicUrl { get => Model.PicUrl; set => Model.PicUrl = value; }
|
||||
|
||||
/// <inheritdoc cref="XunYouGame.PicMD5"/>
|
||||
[XmlIgnore, IgnoreDataMember, SystemTextJsonIgnore, NewtonsoftJsonIgnore, MPIgnore, MP2Ignore]
|
||||
public string? PicMD5 { get => Model.PicMD5; set => Model.PicMD5 = value; }
|
||||
|
||||
/// <inheritdoc cref="XunYouGame.LogoUrl"/>
|
||||
[XmlIgnore, IgnoreDataMember, SystemTextJsonIgnore, NewtonsoftJsonIgnore, MPIgnore, MP2Ignore]
|
||||
public string? LogoUrl { get => Model.LogoUrl; set => Model.LogoUrl = value; }
|
||||
|
||||
/// <inheritdoc cref="XunYouGame.LogoMD5"/>
|
||||
[XmlIgnore, IgnoreDataMember, SystemTextJsonIgnore, NewtonsoftJsonIgnore, MPIgnore, MP2Ignore]
|
||||
public string? LogoMD5 { get => Model.LogoMD5; set => Model.LogoMD5 = value; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string ToString() => Model.ToString()!;
|
||||
}
|
||||
|
||||
sealed class LastAccelerateTimeComparer : IComparer<XunYouGameViewModel>
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public int Compare(XunYouGameViewModel? x, XunYouGameViewModel? y)
|
||||
{
|
||||
if (x == null || y == null)
|
||||
return 0;
|
||||
if (x.IsAccelerating)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
else if (y.IsAccelerating)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
if (x.IsAccelerated)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
else if (y.IsAccelerated)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
else if (x.LastAccelerateTime.HasValue && y.LastAccelerateTime.HasValue)
|
||||
{
|
||||
return y.LastAccelerateTime.Value.CompareTo(x.LastAccelerateTime.Value);
|
||||
}
|
||||
else if (x.LastAccelerateTime.HasValue)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
else if (y.LastAccelerateTime.HasValue)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
var mygamekeys = GameAcceleratorSettings.MyGames.Value?.Keys;
|
||||
if (mygamekeys != null)
|
||||
{
|
||||
var x2 = mygamekeys.IndexOf(x.Id);
|
||||
var y2 = mygamekeys.IndexOf(y.Id);
|
||||
if (x2 < y2)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
else if (x2 > y2)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
using BD.WTTS.Properties;
|
||||
using BD.WTTS.UI.Views.Pages;
|
||||
using dotnetCampus.Ipc.CompilerServices.GeneratedProxies;
|
||||
using dotnetCampus.Ipc.Pipes;
|
||||
|
||||
namespace BD.WTTS.Plugins;
|
||||
|
||||
@@ -8,6 +10,13 @@ namespace BD.WTTS.Plugins;
|
||||
#endif
|
||||
public sealed class Plugin : PluginBase<Plugin>, IPlugin
|
||||
{
|
||||
static Plugin()
|
||||
{
|
||||
#if WINDOWS
|
||||
XunYouSDK.Initialize();
|
||||
#endif
|
||||
}
|
||||
|
||||
const string moduleName = AssemblyInfo.Accelerator;
|
||||
|
||||
public override Guid Id => Guid.Parse(AssemblyInfo.AcceleratorId);
|
||||
@@ -43,6 +52,7 @@ public sealed class Plugin : PluginBase<Plugin>, IPlugin
|
||||
|
||||
readonly TaskCompletionSource<IReverseProxyService> reverseProxyService = new();
|
||||
readonly TaskCompletionSource<ICertificateManager> certificateManager = new();
|
||||
//readonly TaskCompletionSource<IAcceleratorService> acceleratorService = new();
|
||||
|
||||
public override void ConfigureDemandServices(IServiceCollection services, Startup startup)
|
||||
{
|
||||
@@ -56,6 +66,16 @@ public sealed class Plugin : PluginBase<Plugin>, IPlugin
|
||||
services.AddSingleton(_ => certificateManager.Task.GetAwaiter().GetResult());
|
||||
#endif
|
||||
}
|
||||
//if (startup.HasIPCRoot)
|
||||
//{
|
||||
// services.AddSingleton<IAcceleratorService, BackendAcceleratorServiceImpl>();
|
||||
//}
|
||||
//else if (startup.IsMainProcess)
|
||||
{
|
||||
//services.AddSingleton(_ => acceleratorService.Task.GetAwaiter().GetResult());
|
||||
services.AddSingleton<IAcceleratorService, BackendAcceleratorServiceImpl>();
|
||||
services.AddSingleton<IXunYouAccelStateToFrontendCallback, XunYouAccelStateToFrontendCallbackImpl>();
|
||||
}
|
||||
|
||||
if (startup.HasServerApiClient)
|
||||
{
|
||||
@@ -64,6 +84,14 @@ public sealed class Plugin : PluginBase<Plugin>, IPlugin
|
||||
}
|
||||
}
|
||||
|
||||
sealed class XunYouAccelStateToFrontendCallbackImpl : IXunYouAccelStateToFrontendCallback
|
||||
{
|
||||
public void XunYouAccelStateToFrontendCallback(XunYouAccelStateModel m)
|
||||
{
|
||||
GameAcceleratorService.Current.XYAccelState = m;
|
||||
}
|
||||
}
|
||||
|
||||
public override void ConfigureRequiredServices(IServiceCollection services, Startup startup)
|
||||
{
|
||||
#if (WINDOWS || MACCATALYST || MACOS || LINUX) && !(IOS || ANDROID)
|
||||
@@ -71,6 +99,18 @@ public sealed class Plugin : PluginBase<Plugin>, IPlugin
|
||||
#endif
|
||||
}
|
||||
|
||||
public override void ConfigureServices(IpcProvider ipcProvider, Startup startup)
|
||||
{
|
||||
//if (startup.HasIPCRoot)
|
||||
//{
|
||||
// ipcProvider.CreateIpcJoint(Ioc.Get<IAcceleratorService>());
|
||||
//}
|
||||
//else if (startup.IsMainProcess)
|
||||
//{
|
||||
// ipcProvider.CreateIpcJoint(Ioc.Get<IXunYouAccelStateToFrontendCallback>());
|
||||
//}
|
||||
}
|
||||
|
||||
public override async ValueTask OnInitializeAsync()
|
||||
{
|
||||
var ipc = IPCMainProcessService.Instance;
|
||||
@@ -94,6 +134,7 @@ public sealed class Plugin : PluginBase<Plugin>, IPlugin
|
||||
// 从子进程中获取 IPC 远程服务
|
||||
await GetIpcRemoteServiceAsync(moduleName, ipc, reverseProxyService);
|
||||
await GetIpcRemoteServiceAsync(moduleName, ipc, certificateManager);
|
||||
//await GetIpcRemoteServiceAsync(IPlatformService.IPCRoot.moduleName, ipc, acceleratorService);
|
||||
#if DEBUG
|
||||
//try
|
||||
//{
|
||||
@@ -125,6 +166,7 @@ public sealed class Plugin : PluginBase<Plugin>, IPlugin
|
||||
try
|
||||
{
|
||||
await ProxyService.Current.ExitAsync();
|
||||
GameAcceleratorSettings.MyGames.Save();
|
||||
}
|
||||
catch
|
||||
{
|
||||
@@ -196,5 +238,6 @@ public sealed class Plugin : PluginBase<Plugin>, IPlugin
|
||||
public override IEnumerable<(Action<IServiceCollection>? @delegate, bool isInvalid, string name)>? GetConfiguration(bool directoryExists)
|
||||
{
|
||||
yield return GetConfiguration<ProxySettings_>(directoryExists);
|
||||
yield return GetConfiguration<GameAcceleratorSettings_>(directoryExists);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
// C# 10 定义全局 using
|
||||
|
||||
#pragma warning disable IDE0079 // 请删除不必要的忽略
|
||||
#pragma warning disable IDE0005
|
||||
#pragma warning disable SA1209 // Using alias directives should be placed after other using directives
|
||||
#pragma warning disable SA1211 // Using alias directives should be ordered alphabetically by alias name
|
||||
|
||||
global using Mobius;
|
||||
global using Mobius.Enums;
|
||||
global using Mobius.Models;
|
||||
+83
@@ -0,0 +1,83 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// 此代码由工具生成。
|
||||
// 运行时版本:4.0.30319.42000
|
||||
//
|
||||
// 对此文件的更改可能会导致不正确的行为,并且如果
|
||||
// 重新生成代码,这些更改将会丢失。
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace BD.WTTS.Properties {
|
||||
using System;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 一个强类型的资源类,用于查找本地化的字符串等。
|
||||
/// </summary>
|
||||
// 此类是由 StronglyTypedResourceBuilder
|
||||
// 类通过类似于 ResGen 或 Visual Studio 的工具自动生成的。
|
||||
// 若要添加或移除成员,请编辑 .ResX 文件,然后重新运行 ResGen
|
||||
// (以 /str 作为命令选项),或重新生成 VS 项目。
|
||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||
internal class WinDivertResource {
|
||||
|
||||
private static global::System.Resources.ResourceManager resourceMan;
|
||||
|
||||
private static global::System.Globalization.CultureInfo resourceCulture;
|
||||
|
||||
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||
internal WinDivertResource() {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 返回此类使用的缓存的 ResourceManager 实例。
|
||||
/// </summary>
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
internal static global::System.Resources.ResourceManager ResourceManager {
|
||||
get {
|
||||
if (object.ReferenceEquals(resourceMan, null)) {
|
||||
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("BD.WTTS.Properties.WinDivertResource", typeof(WinDivertResource).Assembly);
|
||||
resourceMan = temp;
|
||||
}
|
||||
return resourceMan;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 重写当前线程的 CurrentUICulture 属性,对
|
||||
/// 使用此强类型资源类的所有资源查找执行重写。
|
||||
/// </summary>
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
internal static global::System.Globalization.CultureInfo Culture {
|
||||
get {
|
||||
return resourceCulture;
|
||||
}
|
||||
set {
|
||||
resourceCulture = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找 System.Byte[] 类型的本地化资源。
|
||||
/// </summary>
|
||||
internal static byte[] WinDivert_dll {
|
||||
get {
|
||||
object obj = ResourceManager.GetObject("WinDivert_dll", resourceCulture);
|
||||
return ((byte[])(obj));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找 System.Byte[] 类型的本地化资源。
|
||||
/// </summary>
|
||||
internal static byte[] WinDivert64_sys {
|
||||
get {
|
||||
object obj = ResourceManager.GetObject("WinDivert64_sys", resourceCulture);
|
||||
return ((byte[])(obj));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 1.3
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">1.3</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1">this is my long string</data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
[base64 mime encoded serialized .NET Framework object]
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
[base64 mime encoded string representing a byte array form of the .NET Framework object]
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>1.3</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<assembly alias="System.Windows.Forms" name="System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
|
||||
<data name="WinDivert64_sys" type="System.Resources.ResXFileRef, System.Windows.Forms">
|
||||
<value>..\..\..\ref\WinDivert64.sys;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</data>
|
||||
<data name="WinDivert_dll" type="System.Resources.ResXFileRef, System.Windows.Forms">
|
||||
<value>..\..\..\ref\WinDivert.dll;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -47,17 +47,8 @@ internal sealed class ScriptRepository : Repository<Script, int>, IScriptReposit
|
||||
const string sql_ =
|
||||
$"{SQLStrings.Update}[{Script.TableName}] " +
|
||||
$"set [{Script.ColumnName_CachePath}] = '{{0}}' " +
|
||||
$"where [{Script.ColumnName_CachePath}] = {{1}}";
|
||||
$"where [{Script.ColumnName_Id}] = {{1}}";
|
||||
var sql = string.Format(sql_, item.CachePath, item.LocalId);
|
||||
//var sql =
|
||||
// SQLStrings.Update +
|
||||
// "[" + Script.TableName + "]" +
|
||||
// " set " +
|
||||
// "[" + Script.ColumnName_Enable + "]" +
|
||||
// $" = {item.Enable}" +
|
||||
// " where " +
|
||||
// "[" + Script.ColumnName_Id + "]" +
|
||||
// $" = {item.LocalId}";
|
||||
var r = await dbConnection.ExecuteAsync(sql);
|
||||
return r;
|
||||
}, cancellationToken: cancellationToken).ConfigureAwait(false);
|
||||
|
||||
+192
@@ -0,0 +1,192 @@
|
||||
using System.Threading.Channels;
|
||||
|
||||
namespace BD.WTTS.Services.Implementation;
|
||||
|
||||
partial class BackendAcceleratorServiceImpl
|
||||
{
|
||||
XunYouState lastXunYouState = default;
|
||||
|
||||
int XunYouAccelStateCallback(XunYouState status, nint thisptr)
|
||||
{
|
||||
lastXunYouState = status;
|
||||
XunYouAccelStateModel m;
|
||||
switch (status)
|
||||
{
|
||||
case XunYouState.加速中:
|
||||
case XunYouState.加速已完成:
|
||||
case XunYouState.停止加速中:
|
||||
m = GetXunYouAccelStateModel(status);
|
||||
break;
|
||||
default:
|
||||
m = new XunYouAccelStateModel
|
||||
{
|
||||
State = status,
|
||||
};
|
||||
break;
|
||||
}
|
||||
#if DEBUG
|
||||
static string? EnumToStringDebug<T>(T? value) where T : struct, Enum
|
||||
{
|
||||
if (value is null)
|
||||
return null;
|
||||
|
||||
if (Enum.IsDefined(typeof(T), value))
|
||||
{
|
||||
return $"{value}({ConvertibleHelper.Convert<int, T>(value.Value)})";
|
||||
}
|
||||
return value.ToString();
|
||||
}
|
||||
Console.WriteLine($"迅游加速状态变更:{EnumToStringDebug((XunYouState?)m.State)}, {EnumToStringDebug(m.AccelState)}, GameId: {m.GameId}, AreaId: {m.AreaId}, ServerId: {m.ServerId}");
|
||||
#endif
|
||||
XunYouAccelStateToFrontendCallback(m);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*async*/
|
||||
void XunYouAccelStateToFrontendCallback(XunYouAccelStateModel m)
|
||||
{
|
||||
if (disposedValue)
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
// 通知前端
|
||||
//var ipc = Ioc.Get<IPCSubProcessService>();
|
||||
//var accelStateToFrontendCallback = ipc.GetService<IXunYouAccelStateToFrontendCallback>();
|
||||
var accelStateToFrontendCallback = Ioc.Get<IXunYouAccelStateToFrontendCallback>();
|
||||
accelStateToFrontendCallback.ThrowIsNull(nameof(accelStateToFrontendCallback)).XunYouAccelStateToFrontendCallback(m);
|
||||
//await Plugin.IpcServer.HubContext.Clients.All.SendAsync(nameof(XunYouAccelStateModel), m);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(nameof(BackendAcceleratorServiceImpl), ex, "XunYouAccelStateToFrontendCallback fail.");
|
||||
}
|
||||
}
|
||||
|
||||
static XunYouAccelStateModel GetXunYouAccelStateModel(XunYouState status)
|
||||
{
|
||||
var accState = XunYouSDK.GetAccelStateEx(out var gameid, out var areaid, out var serverid);
|
||||
return new XunYouAccelStateModel
|
||||
{
|
||||
State = status,
|
||||
AccelState = accState,
|
||||
GameId = gameid,
|
||||
AreaId = areaid,
|
||||
ServerId = serverid,
|
||||
};
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<ApiRsp<bool>> XY_IsInstall(CancellationToken cancellationToken = default)
|
||||
{
|
||||
var result = XunYouSDK.IsInstall();
|
||||
await Task.CompletedTask;
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<ApiRsp<XunYouUninstallCode>> XY_Uninstall(CancellationToken cancellationToken = default)
|
||||
{
|
||||
var result = XunYouSDK.Uninstall();
|
||||
await Task.CompletedTask;
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<ApiRsp<int>> XY_StartEx2(
|
||||
string openid,
|
||||
string nickname,
|
||||
int gameid,
|
||||
int area,
|
||||
int server,
|
||||
string? areaPath = default,
|
||||
string? svrPath = default,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var result = XunYouSDK.StartEx2(
|
||||
openid,
|
||||
nickname,
|
||||
gameid,
|
||||
area,
|
||||
server,
|
||||
null,
|
||||
default,
|
||||
GameAcceleratorSettings.WattAcceleratorDirPath.Value!,
|
||||
areaPath,
|
||||
svrPath);
|
||||
await Task.CompletedTask;
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<ApiRsp<int>> XY_StartAccel(
|
||||
int gameid,
|
||||
int area,
|
||||
int serverid = default,
|
||||
string? areaName = default,
|
||||
string? svrName = default,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var result = XunYouSDK.StartAccel(
|
||||
gameid,
|
||||
area,
|
||||
areaName,
|
||||
serverid,
|
||||
svrName);
|
||||
await Task.CompletedTask;
|
||||
return result == XunYouSendResultCode.发送失败 ? 300 : 101;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<ApiRsp<XunYouAccelStateModel?>> XY_GetAccelStateEx(CancellationToken cancellationToken = default)
|
||||
{
|
||||
var result = GetXunYouAccelStateModel(lastXunYouState);
|
||||
await Task.CompletedTask;
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async IAsyncEnumerable<ApiRsp<int>> XY_Install(string installPath, [EnumeratorCancellation] CancellationToken cancellationToken = default)
|
||||
{
|
||||
var channel = Channel.CreateUnbounded<int>();
|
||||
int XunYouDownLoadCallback(int par, nint thisptr)
|
||||
{
|
||||
channel.Writer.TryWrite(par);
|
||||
if (par > 100)
|
||||
{
|
||||
channel.Writer.Complete();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
var installPackPath = Plugin.Instance.CacheDirectory;
|
||||
var result = XunYouSDK.Install(XunYouDownLoadCallback, default, installPath, installPackPath);
|
||||
if (result == 101) // 成功
|
||||
{
|
||||
await foreach (var item in channel.Reader.ReadAllAsync(cancellationToken))
|
||||
{
|
||||
yield return item;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
yield return result;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<ApiRsp<XunYouSendResultCode>> XY_StopAccel(CancellationToken cancellationToken = default)
|
||||
{
|
||||
var result = XunYouSDK.Stop();
|
||||
await Task.CompletedTask;
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<ApiRsp<XunYouIsRunningCode>> XY_IsRunning(CancellationToken cancellationToken = default)
|
||||
{
|
||||
var result = XunYouSDK.IsRunning();
|
||||
await Task.CompletedTask;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
namespace BD.WTTS.Services.Implementation;
|
||||
|
||||
sealed partial class BackendAcceleratorServiceImpl : IAcceleratorService, IDisposable
|
||||
{
|
||||
bool disposedValue;
|
||||
|
||||
public BackendAcceleratorServiceImpl()
|
||||
{
|
||||
}
|
||||
|
||||
public void InitStateSubscribe()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (XunYouSDK.IsSupported)
|
||||
XunYouSDK.GetState(XunYouAccelStateCallback);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(nameof(BackendAcceleratorServiceImpl), ex, "GetAccelStateEx2 fail.");
|
||||
}
|
||||
}
|
||||
|
||||
void Dispose(bool disposing)
|
||||
{
|
||||
if (!disposedValue)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
// 释放托管状态(托管对象)
|
||||
}
|
||||
|
||||
// 释放未托管的资源(未托管的对象)并重写终结器
|
||||
// 将大型字段设置为 null
|
||||
disposedValue = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
// 不要更改此代码。请将清理代码放入“Dispose(bool disposing)”方法中
|
||||
Dispose(disposing: true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
@@ -134,7 +134,10 @@ public sealed class ScriptManager : GeneralHttpClientFactory, IScriptManager
|
||||
if (jsSaveInfo.Exists)
|
||||
{
|
||||
if (isNoRepeat)
|
||||
{
|
||||
jsSaveInfo.Delete();
|
||||
fileInfo.CopyTo(jsSavePath);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -366,17 +369,16 @@ public sealed class ScriptManager : GeneralHttpClientFactory, IScriptManager
|
||||
static IApiRsp OK_Script_DeleteSuccess() => ApiRspHelper.Ok(AppResources.Script_DeleteSuccess);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 版本升级 检查数据库 数据 是否正确 不正确删除重新打包缓存文件
|
||||
/// </summary>
|
||||
/// <param name="list"></param>
|
||||
/// <returns></returns>
|
||||
public async Task<IEnumerable<ScriptDTO>> CheckFiles(IEnumerable<ScriptDTO> list)
|
||||
{
|
||||
var scripts = list.ToList();
|
||||
foreach (var item in list)
|
||||
{
|
||||
//检查缓存文件夹如果不是 IScriptManager.DirName_Build 替换成 IScriptManager.DirName_Build 开头
|
||||
if (!item.CachePath.StartsWith($"{IScriptManager.DirName_Build}/"))
|
||||
{
|
||||
item.CachePath = Path.Combine(IScriptManager.DirName_Build, item.FileName!);
|
||||
await scriptRepository.SaveScriptCachePathAsync(item, default);
|
||||
}
|
||||
if (!CheckFile(item))
|
||||
{
|
||||
var temp = await DeleteScriptAsync(item);
|
||||
@@ -391,6 +393,38 @@ public sealed class ScriptManager : GeneralHttpClientFactory, IScriptManager
|
||||
//toast.Show($"脚本:{item.Name}_文件丢失,删除失败去尝试手动删除");
|
||||
Toast.Show(ToastIcon.Error, Strings.Script_NoFileDeleteError_.Format(item.Name));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
//检查缓存文件夹如果不是 IScriptManager.DirName_Build 替换成 IScriptManager.DirName_Build 开头
|
||||
if (!item.CachePath.StartsWith($"{IScriptManager.DirName_Build}{Path.DirectorySeparatorChar}"))
|
||||
{
|
||||
var oldCachePath = Path.Combine(Plugin.Instance.CacheDirectory, item.CachePath);
|
||||
item.CachePath = Path.Combine(IScriptManager.DirName_Build, item.FileName!);
|
||||
if (File.Exists(oldCachePath))
|
||||
{
|
||||
File.Delete(oldCachePath);
|
||||
}
|
||||
await TryReadFileAsync(item, true);
|
||||
//清理 脚本内容 后续 IPC 传递 插件进程读取。
|
||||
item.Content = string.Empty;
|
||||
await scriptRepository.SaveScriptCachePathAsync(item, default);
|
||||
}
|
||||
else
|
||||
{
|
||||
var cachePath = Path.Combine(Plugin.Instance.CacheDirectory, item.CachePath);
|
||||
var file = new FileInfo(cachePath);
|
||||
//检查缓存文件是否为空 为空则 刷新
|
||||
if (file.Exists)
|
||||
{
|
||||
if (file.Length == 0)
|
||||
{
|
||||
file.Delete();
|
||||
await TryReadFileAsync(item, true);
|
||||
//清理 脚本内容 后续 IPC 传递 插件进程读取。
|
||||
item.Content = string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return scripts;
|
||||
@@ -405,8 +439,9 @@ public sealed class ScriptManager : GeneralHttpClientFactory, IScriptManager
|
||||
/// 修改为仅尝试判断文件是否存在
|
||||
/// </summary>
|
||||
/// <param name="item"></param>
|
||||
/// <param name="isReadContent"></param>
|
||||
/// <returns></returns>
|
||||
public async Task<ScriptDTO> TryReadFileAsync(ScriptDTO item)
|
||||
public async Task<ScriptDTO> TryReadFileAsync(ScriptDTO item, bool isReadContent = false)
|
||||
{
|
||||
var cachePath = Path.Combine(Plugin.Instance.CacheDirectory, item.CachePath);
|
||||
if (File.Exists(cachePath))
|
||||
@@ -419,7 +454,10 @@ public sealed class ScriptManager : GeneralHttpClientFactory, IScriptManager
|
||||
var infoPath = Path.Combine(Plugin.Instance.AppDataDirectory, item.FilePath);
|
||||
if (File.Exists(infoPath))
|
||||
{
|
||||
//item.Content = File.ReadAllText(infoPath);
|
||||
if (isReadContent)
|
||||
{
|
||||
item.Content = File.ReadAllText(infoPath);
|
||||
}
|
||||
if (!await BuildScriptAsync(item, fileInfo, item.IsCompile))
|
||||
{
|
||||
toast.Show(ToastIcon.Error, AppResources.Script_ReadFileError_.Format(item.Name));
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
namespace BD.WTTS.Services;
|
||||
|
||||
partial interface IAcceleratorService // XunYou
|
||||
{
|
||||
/// <summary>
|
||||
/// 迅游是否安装
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
Task<ApiRsp<bool>> XY_IsInstall(CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// 卸载迅游
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
Task<ApiRsp<XunYouUninstallCode>> XY_Uninstall(CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// 迅游启动加速
|
||||
/// </summary>
|
||||
/// <param name="openid">OpenId</param>
|
||||
/// <param name="nickname">用户昵称</param>
|
||||
/// <param name="gameid">游戏ID</param>
|
||||
/// <param name="area">区服Id</param>
|
||||
/// <param name="server">游戏服Id</param>
|
||||
/// <param name="areaPath">区服名称</param>
|
||||
/// <param name="svrPath">游戏服名</param>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns></returns>
|
||||
Task<ApiRsp<int>> XY_StartEx2(
|
||||
string openid,
|
||||
string nickname,
|
||||
int gameid,
|
||||
int area,
|
||||
int server,
|
||||
string? areaPath = default,
|
||||
string? svrPath = default,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// 同步发送加速命令
|
||||
/// </summary>
|
||||
/// <param name="gameid">游戏Id</param>
|
||||
/// <param name="area">区服Id</param>
|
||||
/// <param name="serverid"></param>
|
||||
/// <param name="areaName">区服名称</param>
|
||||
/// <param name="svrName">游戏服名</param>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns></returns>
|
||||
Task<ApiRsp<int>> XY_StartAccel(
|
||||
int gameid,
|
||||
int area,
|
||||
int serverid = default,
|
||||
string? areaName = default,
|
||||
string? svrName = default,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// 安装迅游
|
||||
/// </summary>
|
||||
/// <param name="installPath">安装路径</param>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns></returns>
|
||||
IAsyncEnumerable<ApiRsp<int>> XY_Install(string installPath, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// 获取迅游加速状态
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns></returns>
|
||||
Task<ApiRsp<XunYouAccelStateModel?>> XY_GetAccelStateEx(CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// 停止加速
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns></returns>
|
||||
Task<ApiRsp<XunYouSendResultCode>> XY_StopAccel(CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// 迅游加速器是否在运行
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
Task<ApiRsp<XunYouIsRunningCode>> XY_IsRunning(CancellationToken cancellationToken = default);
|
||||
}
|
||||
|
||||
partial interface IXunYouAccelStateToFrontendCallback
|
||||
{
|
||||
void XunYouAccelStateToFrontendCallback(XunYouAccelStateModel m);
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
using dotnetCampus.Ipc.CompilerServices.Attributes;
|
||||
|
||||
namespace BD.WTTS.Services;
|
||||
|
||||
/// <summary>
|
||||
/// 加速模块服务,连接前后端的胶水服务
|
||||
/// </summary>
|
||||
//[IpcPublic(Timeout = AssemblyInfo.IpcTimeout, IgnoresIpcException = false)]
|
||||
public partial interface IAcceleratorService
|
||||
{
|
||||
void InitStateSubscribe();
|
||||
}
|
||||
@@ -0,0 +1,646 @@
|
||||
// ReSharper disable once CheckNamespace
|
||||
using Avalonia.Platform.Storage;
|
||||
using Avalonia.Threading;
|
||||
using BD.Common.Models;
|
||||
using BD.WTTS.UI.Views.Controls;
|
||||
using BD.WTTS.UI.Views.Pages;
|
||||
using FluentAvalonia.UI.Controls;
|
||||
|
||||
namespace BD.WTTS.Services;
|
||||
|
||||
public sealed partial class GameAcceleratorService
|
||||
#if (WINDOWS || MACCATALYST || MACOS || LINUX) && !(IOS || ANDROID)
|
||||
: ReactiveObject
|
||||
#endif
|
||||
{
|
||||
static GameAcceleratorService? mCurrent;
|
||||
|
||||
public static GameAcceleratorService Current => mCurrent ??= new();
|
||||
|
||||
public SourceCache<XunYouGameViewModel, int> Games { get; }
|
||||
|
||||
[Reactive]
|
||||
public IReadOnlyCollection<XunYouGame>? AllGames { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public bool IsLoadingGames { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public string? SearchText { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public XunYouGameViewModel? CurrentAcceleratorGame { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public XunYouAccelStateModel? XYAccelState { get; set; }
|
||||
|
||||
public ICommand DeleteMyGameCommand { get; }
|
||||
|
||||
public ICommand GameAcceleratorCommand { get; }
|
||||
|
||||
public ICommand InstallAcceleratorCommand { get; }
|
||||
|
||||
public ICommand UninstallAcceleratorCommand { get; }
|
||||
|
||||
public ICommand AcceleratorChangeAreaCommand { get; }
|
||||
|
||||
GameAcceleratorService()
|
||||
{
|
||||
Ioc.Get<IAcceleratorService>().InitStateSubscribe(); // 仅旧项目(WattToolkit)上监听
|
||||
|
||||
Games = new(t => t.Id);
|
||||
|
||||
DeleteMyGameCommand = ReactiveCommand.Create<XunYouGameViewModel>(DeleteMyGame);
|
||||
GameAcceleratorCommand = ReactiveCommand.CreateFromTask<XunYouGameViewModel>(GameAccelerator);
|
||||
InstallAcceleratorCommand = ReactiveCommand.CreateFromTask(InstallAccelerator);
|
||||
UninstallAcceleratorCommand = ReactiveCommand.Create(UninstallAccelerator);
|
||||
AcceleratorChangeAreaCommand = ReactiveCommand.Create<XunYouGameViewModel>(AcceleratorChangeArea);
|
||||
|
||||
if (XunYouSDK.IsSupported)
|
||||
{
|
||||
this.WhenValueChanged(x => x.XYAccelState, false)
|
||||
.ObserveOn(RxApp.MainThreadScheduler)
|
||||
.Subscribe(x =>
|
||||
{
|
||||
if (x == null)
|
||||
return;
|
||||
|
||||
switch (x.State)
|
||||
{
|
||||
case XunYouState.加速中:
|
||||
if (x.GameId != 0 && CurrentAcceleratorGame == null)
|
||||
{
|
||||
var game2 = Games.Items.FirstOrDefault(s => s.Id == x.GameId);
|
||||
if (game2 != null)
|
||||
{
|
||||
game2.IsAccelerated = false;
|
||||
game2.IsAccelerating = true;
|
||||
game2.LastAccelerateTime = DateTimeOffset.Now;
|
||||
CurrentAcceleratorGame = game2;
|
||||
}
|
||||
else
|
||||
{
|
||||
var game = AllGames?.FirstOrDefault(s => s.Id == x.GameId);
|
||||
if (game != null)
|
||||
{
|
||||
var cApp = new XunYouGameViewModel(game)
|
||||
{
|
||||
IsAccelerated = false,
|
||||
LastAccelerateTime = DateTimeOffset.Now,
|
||||
IsAccelerating = true,
|
||||
};
|
||||
CurrentAcceleratorGame = cApp;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case XunYouState.加速已完成:
|
||||
if (x.GameId != 0)
|
||||
{
|
||||
var game2 = Games.Items.FirstOrDefault(s => s.Id == x.GameId);
|
||||
if (game2 != null)
|
||||
{
|
||||
SetGameStatus(game2, x.AreaId, x.ServerId);
|
||||
}
|
||||
else
|
||||
{
|
||||
var game = AllGames?.FirstOrDefault(s => s.Id == x.GameId);
|
||||
if (game != null)
|
||||
{
|
||||
SetGameStatus(game, x.AreaId, x.ServerId);
|
||||
}
|
||||
else
|
||||
{
|
||||
RefreshAllGames();
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case XunYouState.未加速:
|
||||
case XunYouState.停止加速中:
|
||||
case XunYouState.加速已断开:
|
||||
//先停止测速
|
||||
XunYouSDK.StopTestSpeded();
|
||||
|
||||
Games.Items.Where(s => s.IsAccelerating || s.IsAccelerated).ForEach(s =>
|
||||
{
|
||||
RestoreGameStatus(s);
|
||||
Games.Refresh(s);
|
||||
});
|
||||
if (CurrentAcceleratorGame != null)
|
||||
{
|
||||
RestoreGameStatus(CurrentAcceleratorGame);
|
||||
CurrentAcceleratorGame = null;
|
||||
}
|
||||
break;
|
||||
case XunYouState.登录中:
|
||||
case XunYouState.登录失败:
|
||||
case XunYouState.登录成功:
|
||||
case XunYouState.启动中:
|
||||
case XunYouState.启动成功:
|
||||
break;
|
||||
default:
|
||||
Games.Items.Where(s => s.IsAccelerating || s.IsAccelerated).ForEach(s =>
|
||||
{
|
||||
RestoreGameStatus(s);
|
||||
Games.Refresh(s);
|
||||
});
|
||||
if (CurrentAcceleratorGame != null)
|
||||
{
|
||||
RestoreGameStatus(CurrentAcceleratorGame);
|
||||
CurrentAcceleratorGame = null;
|
||||
}
|
||||
Toast.Show(ToastIcon.Warning, x.State.ToString());
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
RefreshAllGames();
|
||||
LoadGames();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 恢复加速游戏状态
|
||||
/// </summary>
|
||||
private static void RestoreGameStatus(XunYouGameViewModel app)
|
||||
{
|
||||
app.SetPingValue(0);
|
||||
app.PingSpeedLoss = 0;
|
||||
app.IsAccelerating = false;
|
||||
app.IsAccelerated = false;
|
||||
app.IsStopAccelerating = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置加速游戏状态
|
||||
/// </summary>
|
||||
void SetGameStatus(XunYouGameViewModel game, int areaId = 0, int serverId = 0)
|
||||
{
|
||||
if (CurrentAcceleratorGame != null && CurrentAcceleratorGame.Id == game.Id)
|
||||
{
|
||||
CurrentAcceleratorGame.IsAccelerating = false;
|
||||
CurrentAcceleratorGame.IsAccelerated = true;
|
||||
CurrentAcceleratorGame.LastAccelerateTime = DateTimeOffset.Now;
|
||||
|
||||
if (CurrentAcceleratorGame.SelectedArea?.Id != areaId)
|
||||
{
|
||||
var gameInfo = XunYouSDK.GetGameInfo(game.Id);
|
||||
CurrentAcceleratorGame.SelectedArea = gameInfo?.Areas?.FirstOrDefault(s => s.Id == areaId);
|
||||
}
|
||||
if (CurrentAcceleratorGame.SelectedServer?.Id != serverId)
|
||||
{
|
||||
CurrentAcceleratorGame.SelectedServer = CurrentAcceleratorGame.SelectedArea?.Servers?.FirstOrDefault(s => s.Id == serverId);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var cApp = new XunYouGameViewModel(game)
|
||||
{
|
||||
IsAccelerated = true,
|
||||
LastAccelerateTime = DateTimeOffset.Now,
|
||||
IsAccelerating = false,
|
||||
};
|
||||
|
||||
var gameInfo = XunYouSDK.GetGameInfo(game.Id);
|
||||
cApp.SelectedArea = gameInfo?.Areas?.FirstOrDefault(s => s.Id == areaId);
|
||||
cApp.SelectedServer = cApp.SelectedArea?.Servers?.FirstOrDefault(s => s.Id == serverId);
|
||||
|
||||
CurrentAcceleratorGame = cApp;
|
||||
}
|
||||
|
||||
if (GameAcceleratorSettings.MyGames.ContainsKey(CurrentAcceleratorGame.Id))
|
||||
{
|
||||
GameAcceleratorSettings.MyGames.Remove(CurrentAcceleratorGame.Id);
|
||||
}
|
||||
GameAcceleratorSettings.MyGames.TryAdd(CurrentAcceleratorGame.Id, CurrentAcceleratorGame);
|
||||
Games.AddOrUpdate(CurrentAcceleratorGame);
|
||||
|
||||
//加速后
|
||||
Toast.Show(ToastIcon.Success, "加速成功");
|
||||
int testSpeedCallback(SpeedCallbackWrapper w)
|
||||
{
|
||||
if (CurrentAcceleratorGame != null)
|
||||
{
|
||||
CurrentAcceleratorGame.SetPingValue(w.Struct.PingSpeed);
|
||||
CurrentAcceleratorGame.PingSpeedLoss = w.Struct.PingSpeedLoss;
|
||||
}
|
||||
#if DEBUG
|
||||
Console.WriteLine($"测速通知状态:{w.State},SpeedCallbackInfo: ErrorDesc/{w.ErrorDesc}, ErrorCode/{w.Struct.ErrorCode}, PingSpeed/{w.Struct.PingSpeed}, PingLocal/{w.Struct.PingLocal}, PingSpeedLoss/{w.Struct.PingSpeedLoss}, PingLocalLoss/{w.Struct.PingLocalLoss}");
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
var speedCode = XunYouSDK.TestSpeed(CurrentAcceleratorGame.Id,
|
||||
CurrentAcceleratorGame.SelectedArea?.Id ?? 0,
|
||||
CurrentAcceleratorGame.SelectedServer?.Id ?? 0,
|
||||
testSpeedCallback);
|
||||
#if DEBUG
|
||||
if (speedCode == XunYouTestSpeedCode.成功)
|
||||
{
|
||||
Toast.Show(ToastIcon.Info, "发送测速指令");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
public async Task GameAccelerator(XunYouGameViewModel app)
|
||||
{
|
||||
if (app.IsAccelerating)
|
||||
return;
|
||||
|
||||
app.IsAccelerating = true;
|
||||
if (!app.IsAccelerated)
|
||||
{
|
||||
app.LastAccelerateTime = DateTimeOffset.Now;
|
||||
CurrentAcceleratorGame = app;
|
||||
if (UserService.Current.User?.WattOpenId == null)
|
||||
{
|
||||
Toast.Show(ToastIcon.Warning, "需要登录账号才能使用游戏加速功能!");
|
||||
app.IsAccelerating = false;
|
||||
return;
|
||||
}
|
||||
var xunYouIsInstall = await Ioc.Get<IAcceleratorService>().XY_IsInstall();
|
||||
if (xunYouIsInstall.HandleUI(out var isInstall))
|
||||
{
|
||||
if (!isInstall)
|
||||
{
|
||||
//if (!await IWindowManager.Instance.ShowTaskDialogAsync(
|
||||
// new MessageBoxWindowViewModel() { Content = "需要下载 Watt 加速器插件才可使用,确定要下载吗?" }, title: "未安装加速插件", isCancelButton: true))
|
||||
//{
|
||||
// app.IsAccelerating = false;
|
||||
// return;
|
||||
//}
|
||||
|
||||
if (!await IWindowManager.Instance.ShowTaskDialogAsync(new MessageBoxWindowViewModel(),
|
||||
pageContent: new AcceleratorPathAskBox(), title: "未安装加速插件", isCancelButton: true))
|
||||
{
|
||||
app.IsAccelerating = false;
|
||||
return;
|
||||
}
|
||||
|
||||
await InstallAccelerator();
|
||||
}
|
||||
|
||||
#region 通过 XY_GetAccelStateEx 判断是否已启动迅游
|
||||
|
||||
//var accStateR = await IAcceleratorService.Instance.XY_GetAccelStateEx();
|
||||
//bool isStartXY = false;
|
||||
//if (accStateR.HandleUI(out var accStateResponse))
|
||||
//{
|
||||
// isStartXY = accStateResponse.AccelState != XunYouAccelStateEx.获取失败;
|
||||
// if (isStartXY && accStateResponse.State != XunYouState.未加速)
|
||||
// {
|
||||
// Toast.Show(ToastIcon.Warning, accStateResponse.State.ToString() ?? "失败");
|
||||
// return;
|
||||
// }
|
||||
//}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 通过 XY_IsRunning 判断是否已启动迅游
|
||||
|
||||
var apiRspIsRunning = await Ioc.Get<IAcceleratorService>().XY_IsRunning();
|
||||
if (!apiRspIsRunning.HandleUI(out var isRunningCode))
|
||||
{
|
||||
// 调用后端失败,显示错误并中止逻辑
|
||||
app.IsAccelerating = false;
|
||||
return;
|
||||
}
|
||||
|
||||
bool isStartXY = isRunningCode == XunYouIsRunningCode.加速器已启动;
|
||||
|
||||
#endregion
|
||||
|
||||
//加速中
|
||||
|
||||
var gameInfo = XunYouSDK.GetGameInfo(app.Id);
|
||||
if (gameInfo == null)
|
||||
{
|
||||
Toast.Show(ToastIcon.Warning, "获取游戏信息失败");
|
||||
app.IsAccelerating = false;
|
||||
return;
|
||||
}
|
||||
app.GameInfo = gameInfo;
|
||||
var vm = new GameInfoPageViewModel(app);
|
||||
var result = await IWindowManager.Instance.ShowTaskDialogAsync(vm, $"{app.Name} - 区服选择", pageContent: new GameInfoPage(), isOkButton: false, disableScroll: true);
|
||||
if (!result || app.SelectedArea == null)
|
||||
{
|
||||
app.IsAccelerating = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (GameAcceleratorSettings.MyGames.ContainsKey(app.Id))
|
||||
{
|
||||
GameAcceleratorSettings.MyGames.Remove(app.Id);
|
||||
}
|
||||
GameAcceleratorSettings.MyGames.TryAdd(app.Id, app);
|
||||
Current.Games.AddOrUpdate(app);
|
||||
|
||||
var start = isStartXY ? await Ioc.Get<IAcceleratorService>().XY_StartAccel(app.Id,
|
||||
app.SelectedArea.Id,
|
||||
app.SelectedServer?.Id ?? 0,
|
||||
app.SelectedArea.Name,
|
||||
app.SelectedServer?.Name)
|
||||
: await Ioc.Get<IAcceleratorService>().XY_StartEx2(
|
||||
UserService.Current.User.WattOpenId,
|
||||
UserService.Current.User.NickName,
|
||||
app.Id,
|
||||
app.SelectedArea.Id,
|
||||
app.SelectedServer?.Id ?? 0,
|
||||
app.SelectedArea.Name,
|
||||
app.SelectedServer?.Name);
|
||||
if (start.HandleUI(out var startCode))
|
||||
{
|
||||
if (startCode == 101)
|
||||
{
|
||||
app.AcceleratingProgress = 0;
|
||||
Toast.Show(ToastIcon.Info, "正在加速中...");
|
||||
}
|
||||
else
|
||||
{
|
||||
Toast.Show(ToastIcon.Error, "加速启动失败");
|
||||
app.IsAccelerating = false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
app.IsAccelerating = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var stopRequest = await Ioc.Get<IAcceleratorService>().XY_StopAccel();
|
||||
if (stopRequest.HandleUI(out var code))
|
||||
{
|
||||
// 停止加速
|
||||
if (code == XunYouSendResultCode.发送成功)
|
||||
{
|
||||
Toast.Show(ToastIcon.Info, "正在停止加速中...");
|
||||
}
|
||||
else
|
||||
{
|
||||
app.IsAccelerating = false;
|
||||
Toast.Show(ToastIcon.Error, "停止加速失败,请尝试去加速器客户端停止加速");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void DeleteMyGame(XunYouGameViewModel app)
|
||||
{
|
||||
//if (await WindowManagerService.Current.ShowTaskDialogAsync(
|
||||
// new MessageBoxWindowViewModel() { Content = Strings.Script_ReplaceTips }, isCancelButton: true, isDialog: false))
|
||||
//{
|
||||
//}
|
||||
Current.Games.RemoveKey(app.Id);
|
||||
GameAcceleratorSettings.MyGames.Remove(app.Id);
|
||||
Toast.Show(ToastIcon.Success, "已移除");
|
||||
}
|
||||
|
||||
public static void AddMyGame(XunYouGameViewModel app)
|
||||
{
|
||||
app.LastAccelerateTime = DateTimeOffset.Now;
|
||||
GameAcceleratorSettings.MyGames.Remove(app.Id);
|
||||
if (GameAcceleratorSettings.MyGames.TryAdd(app.Id, app))
|
||||
{
|
||||
Current.Games.AddOrUpdate(app);
|
||||
Toast.Show(ToastIcon.Success, "已添加到游戏列表");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 加载迅游游戏数据
|
||||
/// </summary>
|
||||
public void LoadGames()
|
||||
{
|
||||
if (!XunYouSDK.IsSupported)
|
||||
return;
|
||||
|
||||
Task2.InBackground(() =>
|
||||
{
|
||||
if (!IsLoadingGames)
|
||||
{
|
||||
IsLoadingGames = true;
|
||||
if (GameAcceleratorSettings.MyGames.Any_Nullable())
|
||||
{
|
||||
Games.Clear();
|
||||
Games.AddOrUpdate(GameAcceleratorSettings.MyGames.Value!.Values);
|
||||
}
|
||||
else
|
||||
{
|
||||
var games = XunYouSDK.GetHotGames();
|
||||
if (games != null)
|
||||
{
|
||||
Games.Clear();
|
||||
GameAcceleratorSettings.MyGames.AddRange(games.Select(s => new KeyValuePair<int, XunYouGameViewModel>(s.Id, new XunYouGameViewModel(s))));
|
||||
Games.AddOrUpdate(GameAcceleratorSettings.MyGames.Value!.Values);
|
||||
}
|
||||
}
|
||||
|
||||
IsLoadingGames = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void RefreshAllGames()
|
||||
{
|
||||
Task2.InBackground(async () =>
|
||||
{
|
||||
var games = XunYouSDK.GetAllGames();
|
||||
if (games != null)
|
||||
{
|
||||
AllGames = new ReadOnlyCollection<XunYouGame>(games);
|
||||
}
|
||||
|
||||
//判断是否已经在加速
|
||||
var accState = await Ioc.Get<IAcceleratorService>().XY_GetAccelStateEx();
|
||||
if (accState.HandleUI() && accState.Content != null)
|
||||
{
|
||||
if (accState.Content.GameId > 0 && accState.Content.AccelState is
|
||||
XunYouAccelStateEx.启动加速中 or
|
||||
XunYouAccelStateEx.已加速)
|
||||
{
|
||||
//var game = Games.Lookup(accState.Content.GameId);
|
||||
//if (game.HasValue)
|
||||
//{
|
||||
// game.Value.IsAccelerated = true;
|
||||
// game.Value.LastAccelerateTime = DateTime.Now;
|
||||
// var gameinfo = XunYouSDK.GetGameInfo(game.Value.Id);
|
||||
// game.Value.SelectedArea = gameinfo?.Areas?.FirstOrDefault(s => s.Id == accState.Content.AreaId);
|
||||
//}
|
||||
|
||||
var game = games?.FirstOrDefault(s => s.Id == accState.Content.GameId);
|
||||
if (game != null)
|
||||
{
|
||||
MainThread2.BeginInvokeOnMainThread(() =>
|
||||
{
|
||||
SetGameStatus(game, accState.Content.AreaId, accState.Content.ServerId);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static async Task InstallAccelerator()
|
||||
{
|
||||
var xunYouIsInstall = await Ioc.Get<IAcceleratorService>().XY_IsInstall();
|
||||
if (xunYouIsInstall.HandleUI(out var isInstall))
|
||||
{
|
||||
if (isInstall)
|
||||
{
|
||||
Toast.Show(ToastIcon.Info, "已安装Watt加速器");
|
||||
return;
|
||||
}
|
||||
var td = new TaskDialog
|
||||
{
|
||||
Title = "下载插件",
|
||||
ShowProgressBar = true,
|
||||
IconSource = new SymbolIconSource { Symbol = FluentAvalonia.UI.Controls.Symbol.Download },
|
||||
SubHeader = "下载 Watt 加速器 插件",
|
||||
Content = "正在初始化,请稍候",
|
||||
XamlRoot = AvaloniaWindowManagerImpl.GetWindowTopLevel(),
|
||||
//Buttons =
|
||||
//{
|
||||
// new TaskDialogButton(Strings.Cancel, TaskDialogStandardResult.Cancel)
|
||||
//}
|
||||
};
|
||||
var install = Ioc.Get<IAcceleratorService>().XY_Install(GameAcceleratorSettings.WattAcceleratorDirPath.Value!);
|
||||
|
||||
td.Opened += async (s, e) =>
|
||||
{
|
||||
await foreach (var item in install)
|
||||
{
|
||||
if (item.HandleUI(out var content))
|
||||
{
|
||||
switch (content)
|
||||
{
|
||||
case < 100:
|
||||
Dispatcher.UIThread.Post(() => { td.Content = $"正在下载 {item.Content}%"; });
|
||||
td.SetProgressBarState(item.Content, TaskDialogProgressState.Normal);
|
||||
break;
|
||||
case 100:
|
||||
td.SetProgressBarState(item.Content, TaskDialogProgressState.Indeterminate);
|
||||
Dispatcher.UIThread.Post(() => { td.Content = $"下载完成,正在安装..."; });
|
||||
break;
|
||||
case (int)XunYouDownLoadCode.安装成功:
|
||||
//处理成功
|
||||
//Dispatcher.UIThread.Post(() => { td.Content = $"安装完成"; });
|
||||
Dispatcher.UIThread.Post(() => { td.Hide(TaskDialogStandardResult.OK); });
|
||||
td.Hide();
|
||||
break;
|
||||
case int n when n > 101 && n < (int)XunYouDownLoadCode.启动安装程序失败:
|
||||
//处理失败
|
||||
break;
|
||||
// Code 和进度重叠 递进 1000 XunYouInstallOrStartCode.默认 XunYouInstallOrStartCode.已安装
|
||||
case 1000:
|
||||
Dispatcher.UIThread.Post(() => { td.Content = $"默认"; });
|
||||
// XunYouInstallOrStartCode.默认
|
||||
break;
|
||||
case 1001:
|
||||
Dispatcher.UIThread.Post(() => { td.Content = $"已安装"; });
|
||||
// XunYouInstallOrStartCode.已安装
|
||||
Dispatcher.UIThread.Post(() => { td.Hide(TaskDialogStandardResult.OK); });
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
//_ = Task.Run(() => { XunYouSDK.InstallAsync(progress, Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), "WattAccelerator")); });
|
||||
|
||||
var result = await td.ShowAsync(true);
|
||||
}
|
||||
}
|
||||
|
||||
public static async void UninstallAccelerator()
|
||||
{
|
||||
var xunYouIsInstall = await Ioc.Get<IAcceleratorService>().XY_IsInstall();
|
||||
if (xunYouIsInstall.HandleUI(out var isInstall))
|
||||
{
|
||||
if (!isInstall)
|
||||
{
|
||||
Toast.Show(ToastIcon.Info, "未安装,不需要卸载");
|
||||
return;
|
||||
}
|
||||
var uninstall = await Ioc.Get<IAcceleratorService>().XY_Uninstall();
|
||||
if (uninstall.HandleUI(out var code))
|
||||
{
|
||||
if (code == 0)
|
||||
{
|
||||
Toast.Show(ToastIcon.Success, "卸载成功");
|
||||
}
|
||||
else
|
||||
{
|
||||
Toast.Show(ToastIcon.Error, "卸载失败");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async void AcceleratorChangeArea(XunYouGameViewModel app)
|
||||
{
|
||||
var gameInfo = XunYouSDK.GetGameInfo(app.Id);
|
||||
if (gameInfo == null)
|
||||
{
|
||||
Toast.Show(ToastIcon.Warning, "获取游戏信息失败");
|
||||
return;
|
||||
}
|
||||
|
||||
var vm = new GameInfoPageViewModel(new XunYouGameViewModel { Name = app.Name, GameInfo = gameInfo, SelectedArea = app.SelectedArea, SelectedServer = app.SelectedServer });
|
||||
var result = await IWindowManager.Instance.ShowTaskDialogAsync(vm, $"{app.Name} - 区服选择", pageContent: new GameInfoPage(), isOkButton: false, disableScroll: true);
|
||||
if (!result || vm.XunYouGame.SelectedArea == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (app.SelectedArea?.Id == vm.XunYouGame.SelectedArea?.Id && app.SelectedServer?.Id == vm.XunYouGame.SelectedServer?.Id)
|
||||
{
|
||||
// 切换区服选择不变时不重新加速
|
||||
return;
|
||||
}
|
||||
|
||||
app.GameInfo = gameInfo;
|
||||
app.SelectedArea = vm.XunYouGame.SelectedArea;
|
||||
app.SelectedServer = vm.XunYouGame.SelectedServer;
|
||||
|
||||
if (app.IsAccelerated)
|
||||
{
|
||||
//重新加速
|
||||
var start = await Ioc.Get<IAcceleratorService>().XY_StartAccel(app.Id, app.SelectedArea?.Id ?? 0, app.SelectedServer?.Id ?? 0, app.SelectedArea?.Name);
|
||||
if (start.HandleUI(out var startCode))
|
||||
{
|
||||
if (startCode == 101)
|
||||
{
|
||||
app.IsAccelerating = true;
|
||||
app.IsAccelerated = false;
|
||||
app.AcceleratingProgress = 0;
|
||||
CurrentAcceleratorGame = app;
|
||||
Toast.Show(ToastIcon.Info, "正在加速中...");
|
||||
}
|
||||
else
|
||||
{
|
||||
Toast.Show(ToastIcon.Error, "加速启动失败");
|
||||
app.IsAccelerating = false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
app.IsAccelerating = false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
await GameAccelerator(app);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task RefreshXYAccelState()
|
||||
{
|
||||
var result = await Ioc.Get<IAcceleratorService>().XY_GetAccelStateEx();
|
||||
if (result.HandleUI(out var content))
|
||||
{
|
||||
XYAccelState = content;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -183,6 +183,13 @@ partial class ProxyService
|
||||
return Strings.CommunityFix_SetAsSystemPACProxyFail;
|
||||
}
|
||||
break;
|
||||
#if WINDOWS
|
||||
case ProxyMode.DNSIntercept:
|
||||
{
|
||||
await Mobius.Helpers.WinDivertInitHelper.InitializeAsync();
|
||||
}
|
||||
break;
|
||||
#endif
|
||||
}
|
||||
|
||||
if (!OperatingSystem.IsWindows())
|
||||
@@ -336,6 +343,14 @@ partial class ProxyService
|
||||
await platformService.SetAsSystemPACProxyAsync(false);
|
||||
}
|
||||
break;
|
||||
#if WINDOWS
|
||||
case ProxyMode.DNSIntercept:
|
||||
{
|
||||
// 停止时也调用初始化
|
||||
await Mobius.Helpers.WinDivertInitHelper.InitializeAsync();
|
||||
}
|
||||
break;
|
||||
#endif
|
||||
}
|
||||
StopTimer(); // 停止 UI 计时器
|
||||
#if MACOS
|
||||
|
||||
@@ -12,7 +12,7 @@ public sealed partial class ProxyService
|
||||
{
|
||||
static ProxyService? mCurrent;
|
||||
|
||||
public static ProxyService Current => mCurrent ?? new();
|
||||
public static ProxyService Current => mCurrent ??= new();
|
||||
|
||||
readonly IReverseProxyService reverseProxyService = IReverseProxyService.Constants.Instance;
|
||||
readonly IScriptManager scriptManager = IScriptManager.Instance;
|
||||
@@ -21,8 +21,6 @@ public sealed partial class ProxyService
|
||||
|
||||
ProxyService()
|
||||
{
|
||||
mCurrent = this;
|
||||
|
||||
ProxyDomains = new SourceCache<AccelerateProjectGroupDTO, string>(s => s.Name);
|
||||
ProxyScripts = new SourceList<ScriptDTO>();
|
||||
|
||||
@@ -34,6 +32,7 @@ public sealed partial class ProxyService
|
||||
.Subscribe(_ => SelectGroup = ProxyDomains.Items.FirstOrDefault());
|
||||
|
||||
this.WhenValueChanged(x => x.ProxyStatus, false)
|
||||
.ObserveOn(RxApp.MainThreadScheduler)
|
||||
.Subscribe(async proxyStatusLeft =>
|
||||
{
|
||||
bool proxyStatusRight;
|
||||
@@ -330,23 +329,24 @@ public sealed partial class ProxyService
|
||||
#endif
|
||||
if (result.IsSuccess)
|
||||
{
|
||||
if (ProxySettings.SupportProxyServicesStatus.Value.Any_Nullable() && result.Content.Any_Nullable())
|
||||
{
|
||||
var items = result.Content!.SelectMany(s => s.Items!);
|
||||
foreach (var item in items)
|
||||
{
|
||||
if (ProxySettings.SupportProxyServicesStatus.Value.Contains(item.Id.ToString()))
|
||||
{
|
||||
item.ThreeStateEnable = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ProxyDomains.AddOrUpdate(result.Content!);
|
||||
}
|
||||
|
||||
LoadOrSaveLocalAccelerate();
|
||||
|
||||
if (ProxySettings.SupportProxyServicesStatus.Value.Any_Nullable() && ProxyDomains.Items.Any_Nullable())
|
||||
{
|
||||
var items = ProxyDomains.Items!.SelectMany(s => s.Items!);
|
||||
var enableItems = ProxySettings.SupportProxyServicesStatus.Value;
|
||||
foreach (var item in items)
|
||||
{
|
||||
if (enableItems.Contains(item.Id.ToString()))
|
||||
{
|
||||
item.ThreeStateEnable = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//if (IsLoadImage && ProxyDomains.Items.Any_Nullable())
|
||||
//{
|
||||
// foreach (var item in ProxyDomains.Items)
|
||||
@@ -443,6 +443,8 @@ public sealed partial class ProxyService
|
||||
|
||||
//拉取 GM.js
|
||||
await BasicsInfoAsync();
|
||||
//初始化后检查脚本更新
|
||||
CheckScriptUpdate();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
// ReSharper disable once CheckNamespace
|
||||
namespace BD.WTTS.Settings;
|
||||
|
||||
[JsonSourceGenerationOptions(WriteIndented = true, IgnoreReadOnlyProperties = true)]
|
||||
[JsonSerializable(typeof(GameAcceleratorSettings_))]
|
||||
internal partial class GameAcceleratorSettingsContext : JsonSerializerContext
|
||||
{
|
||||
static GameAcceleratorSettingsContext? instance;
|
||||
|
||||
public static GameAcceleratorSettingsContext Instance
|
||||
=> instance ??= new GameAcceleratorSettingsContext(ISettings.GetDefaultOptions());
|
||||
}
|
||||
|
||||
[MPObj, MP2Obj(SerializeLayout.Explicit)]
|
||||
public sealed partial class GameAcceleratorSettings_ : ISettings, ISettings<GameAcceleratorSettings_>
|
||||
{
|
||||
public const string Name = nameof(GameAcceleratorSettings);
|
||||
|
||||
static string ISettings.Name => Name;
|
||||
|
||||
static JsonSerializerContext ISettings.JsonSerializerContext
|
||||
=> GameAcceleratorSettingsContext.Instance;
|
||||
|
||||
static JsonTypeInfo ISettings.JsonTypeInfo
|
||||
=> GameAcceleratorSettingsContext.Instance.GameAcceleratorSettings_;
|
||||
|
||||
static JsonTypeInfo<GameAcceleratorSettings_> ISettings<GameAcceleratorSettings_>.JsonTypeInfo
|
||||
=> GameAcceleratorSettingsContext.Instance.GameAcceleratorSettings_;
|
||||
|
||||
/// <summary>
|
||||
/// 我的加速游戏列表
|
||||
/// </summary>
|
||||
[MP2Key(0), JsonPropertyOrder(0)]
|
||||
public Dictionary<int, XunYouGameViewModel> MyGames { get; set; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// 加速插件安装路径
|
||||
/// </summary>
|
||||
[MP2Key(1), JsonPropertyOrder(1)]
|
||||
public string WattAcceleratorDirPath { get; set; } = DefaultWattAcceleratorDirPath;
|
||||
|
||||
public static readonly string DefaultWattAcceleratorDirPath = OperatingSystem.IsWindows() ?
|
||||
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86), "WattAccelerator") : string.Empty;
|
||||
|
||||
}
|
||||
|
||||
public static partial class GameAcceleratorSettings
|
||||
{
|
||||
/// <inheritdoc cref="GameAcceleratorSettingsModel.MyGames"/>
|
||||
public static SettingsProperty<int, XunYouGameViewModel, Dictionary<int, XunYouGameViewModel>, GameAcceleratorSettings_> MyGames { get; }
|
||||
= new();
|
||||
|
||||
/// <inheritdoc cref="GameAcceleratorSettingsModel.WattAcceleratorDirPath"/>
|
||||
public static SettingsProperty<string, GameAcceleratorSettings_> WattAcceleratorDirPath { get; }
|
||||
= new(GameAcceleratorSettings_.DefaultWattAcceleratorDirPath);
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 1.0 MiB |
@@ -37,6 +37,9 @@ public sealed partial class AcceleratorPageViewModel
|
||||
await ProxyService.Current.InitializeAccelerateAsync();
|
||||
else
|
||||
Toast.Show(ToastIcon.Warning, AppResources.Warning_PleaseStopAccelerate);
|
||||
|
||||
// 刷新时重新加载迅游游戏数据
|
||||
GameAcceleratorService.Current.LoadGames();
|
||||
});
|
||||
|
||||
ProxySettingsCommand = ReactiveCommand.Create(() =>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user