Compare commits

...
Author SHA1 Message Date
RMBGAME 67209f3861 🚧 Misc 2024-06-25 14:16:31 +08:00
RMBGAME 110c92d909 🐛⬆️ Avalonia 11.0-rc1 2024-06-24 14:26:48 +08:00
Aigio Liu cdc773ba74 📝 System.ArgumentException: Decoded string is not a valid IDN name. (Parameter 'unicode') 2024-06-16 00:48:32 +08:00
Aigio Liu 8ea8b73aab 🐛 System.ArgumentException: Decoded string is not a valid IDN name. (Parameter 'unicode')
at System.Globalization.IdnMapping.IcuGetAsciiCore(String unicodeString, Char* unicode, Int32 count)
   at System.Globalization.IdnMapping.GetAscii(String unicode, Int32 index, Int32 count)
   at System.Security.Cryptography.X509Certificates.SubjectAlternativeNameBuilder.AddDnsName(String dnsName)
   at BD.WTTS.Services.Implementation.CertGenerator.Add(SubjectAlternativeNameBuilder builder, String name)
2024-06-16 00:46:51 +08:00
RMBGAME 0614e172b8 ✨ 账号切换新增显示用户名设置项 2024-06-07 23:58:49 +08:00
yingpanwang 4c73a5b622 Feautre/joinsteamauthenticator (#3362)
* ✨ 导入steam令牌 新增添加 联合steam 令牌方式
2024-06-06 15:01:15 +08:00
RMBGAME 34341172b8 🚧 ItemsRepeater 2024-05-29 19:15:59 +08:00
RMBGAME 6813ed9ece ⬆️ FA 2.1.0-preview5 2024-05-27 18:23:02 +08:00
RMBGAME 3eb916e78a 🐛 蒸汽平台切换账号支持 2024-05-18 21:37:25 +08:00
RMBGAME 7424d05f54 💄 Avalonia_Edit 2024-05-17 20:27:04 +08:00
RMBGAME 76829b0ca2 🐛 fixed 挂时长启动暂停按钮显示问题 2024-05-10 17:24:43 +08:00
RMBGAME bbe98f68f6 🐛 游戏已加速情况下启动程序闪退问题修复 2024-05-06 19:04:46 +08:00
john.jiang 96025c9ef5 🎨 fixs 2024-05-06 17:12:18 +08:00
john.jiang 2fb12f1b7f 🎨 修复快速掉卡模式剩最后一个游戏闪退Bug 2024-05-06 17:07:07 +08:00
Mossimo 542b528b93 🐛 修复 Ubuntu 版本文件不存在中断问题 2024-04-30 16:28:23 +08:00
RMBGAME 0a4326924d 💄 修复加速模式文本显示错误 2024-04-30 16:13:49 +08:00
john.jiang e8a9990354 🎨 挂卡列表虚拟化缓存加长 2024-04-30 15:25:43 +08:00
Aigio Liu 4e72f9ccf9 🐛 ClickAdvertisement 2024-04-30 13:54:13 +08:00
Aigio Liu 6df6ed93cb 🐛 ClickAdvertisement 2024-04-30 13:42:09 +08:00
Aigio Liu 605beeb918 🐛 CopyToClipboardCommand 2024-04-30 13:38:13 +08:00
Aigio Liu 2bdfbefda9 🐛 OpenAuthUrl 2024-04-30 13:30:34 +08:00
Aigio Liu 6747549cbc 🐛 App.OpenBrowserCommand & IPartialGameAccountSettings null 2024-04-30 12:43:47 +08:00
Mossimo 0d4c0b27f7 🐛 fix 修复2月与新版本差异 2024-04-30 11:58:31 +08:00
Mossimo c501066a3d 🐛 fix 回退 websocket macos 使用 url 协议 2024-04-30 11:47:02 +08:00
john.jiang 57a6b24dad 🎨 修复挂卡价格显示 2024-04-30 11:40:37 +08:00
john.jiang 143011fbd0 🎨 挂卡帐号不一致 Toast 警告,账号登录页登陆成功先跳转主页再获取数据 2024-04-30 11:23:34 +08:00
RMBGAME 11bc9cd440 💄 略微缩减标题栏高度 2024-04-30 00:02:06 +08:00
RMBGAME 72e9068571 💄 简单的加速疑难解答 2024-04-29 19:28:28 +08:00
yingpanwang 5842d5f80f Merge pull request #3321 from BeyondDimension/fix/authenticator-ui
🐛 修复替换令牌时恢复代码不显示的问题
2024-04-29 18:08:06 +08:00
wangyingpan bc19a02f4f 🐛 修复替换令牌时恢复代码不显示的问题
调整添加令牌时的提示信息
2024-04-29 18:07:05 +08:00
Aigio Liu d59d63d57e 🎨 vipEndTimeString 2024-04-29 16:13:40 +08:00
yingpanwang 9ad6e8fa8d Fix/authenticator verifycode UI (#3313)
* 🐛 修复添加令牌邮箱/短信验证ui显示

* 🎨 删除绑定手机号步骤
🐛 修复替换令牌逻辑验证码信息展示错误的问题

* 🐛 修复添加令牌邮箱/短信验证ui显示

* 🎨 删除绑定手机号步骤
🐛 修复替换令牌逻辑验证码信息展示错误的问题

* ✨ 替换令牌中添加绑定手机号窜哦做
2024-04-29 15:27:28 +08:00
Aigio Liu 45ef15b220 🎨 VipEndTimeString 2024-04-29 15:04:47 +08:00
Aigio Liu 25dfd1f888 🐛 VipEndTimeString 2024-04-29 15:01:57 +08:00
Aigio Liu 0495cd3079 💚 CI 2024-04-29 13:14:45 +08:00
Aigio Liu ea9c45d78b 🎨 VipEndTimeString 2024-04-29 13:11:51 +08:00
Aigio Liu 4427fd1a5f 🐛 VipEndTimeString 2024-04-29 12:18:03 +08:00
RMBGAME edf3c3a72c ⬆️ SteamClient 2024-04-29 12:10:10 +08:00
Aigio Liu 55490d2351 🐛 VipEndTimeString 2024-04-29 11:59:22 +08:00
Aigio Liu 17ae6655b4 🐛 VipEndTimeString 2024-04-29 11:55:34 +08:00
john.jiang 342d25deac 🎨 受限账户徽章页处理 2024-04-29 10:59:58 +08:00
RMBGAME f5616dac2e 🐛💄 UI改进,修复加速可能导致的循环修改hosts问题 2024-04-28 22:15:29 +08:00
john.jiang 28914c53f6 🎨 fixs 2024-04-28 19:24:41 +08:00
john.jiang f0cbe8ef88 🎨 会员时长文本显示调整 2024-04-28 19:11:01 +08:00
RMBGAME e0b45bf8af 💄🐛 修复可能导致启动加速无限修改hosts的错误 2024-04-28 17:43:30 +08:00
RMBGAME 854fe420fd 🐛💄 Misc 2024-04-28 17:43:30 +08:00
Aigio Liu c95ce1087a 🐛 Shortcut 2024-04-28 17:29:44 +08:00
Aigio Liu 14ae662675 ✨ URI 激活 2024-04-28 16:18:58 +08:00
RMBGAME 1594bd250e 🐛💄 UI错误修复 2024-04-28 16:09:32 +08:00
Aigio Liu c83ab4fb08 🐛 VipEndTime 2024-04-28 15:35:45 +08:00
Aigio Liu e690454e0c 🐛 VipEndTime 2024-04-28 15:29:13 +08:00
john.jiang 27b4f4bb10 🎨 会员时间显示格式调整 2024-04-28 15:03:42 +08:00
john.jiang 70315c3a99 🎨 fixs 2024-04-28 11:13:45 +08:00
Aigio Liu b8e49a5332 🎨 XY.GetVipEndTime 2024-04-28 10:38:15 +08:00
RMBGAME 1374dc52b8 🐛 2024-04-27 17:40:46 +08:00
RMBGAME 150f93e7b7 ⬆️ Avalonia 11.1.0-beta2 2024-04-27 16:17:59 +08:00
RMBGAME d1aea71f67 🐛 修复一些图片加载问题 2024-04-25 18:51:36 +08:00
RMBGAME 37a522ec7f ⬆️ Image2 2024-04-25 17:43:03 +08:00
RMBGAME 8b816b9787 💄 UI调整优化
现在不需要赞助也可以设置启动默认页
2024-04-25 17:41:49 +08:00
Aigio Liu 4f487a5ca8 🐛 OpenBrowserCommand 2024-04-22 17:03:53 +08:00
Aigio Liu 6c7a547a83 🎨 GetVipEndTime 2024-04-22 17:00:47 +08:00
Aigio Liu bde5751d69 ✨ 会员时间显示与联系客服 2024-04-22 16:45:37 +08:00
Aigio Liu 23544b6c39 🎨 XunYouSDK.GetVipEndTime 2024-04-22 16:13:12 +08:00
RMBGAME 2c60a9fb69 💄 feedback 2024-04-22 16:03:10 +08:00
RMBGAME 4ce67c8f1a 💄 Misc 2024-04-22 11:22:35 +08:00
Aigio Liu e8a1a6153e 🎨 OpenBrowserCommand 2024-04-22 10:26:58 +08:00
ff m 452fa2ac84 🐛 离线安装也支持选择初始化位置 2024-04-20 18:51:23 +08:00
ff m 60e6d71d48 ✨ 安装脚本支持安装到指定目录,支持选择desktop创建至 $HOME/.local/share/applications/ or $HOME/Desktop
The most significant changes to the `online_install.sh` script involve the addition of user prompts for customization of the installation path and the location of the desktop file. The script now includes a loop to allow the user to input a custom installation path or use the default one. If the user provides an invalid path, they are prompted to input again. The script also creates the installation path and switches to it, outputting an error message and exiting with status 1 if it fails to do so. A new function, `InitDesktop`, has been added to prompt the user to choose where to add the desktop file. The user can choose between `$HOME/.local/share/applications/` and `$HOME/Desktop`. The script then removes any existing desktop file in the chosen location and creates a new one. If the user provides an invalid option, they are prompted to input again.

List of changes:

1. A loop has been added to prompt the user to input a custom installation path or use the default path. If the user provides an invalid path, they are prompted to input again (online_install.sh).

2. The script now creates the installation path and switches to it. If it fails to do either, it outputs an error message and exits with status 1 (online_install.sh).

3. A function `InitDesktop` has been added to prompt the user to choose where to add the desktop file. The user can choose between `$HOME/.local/share/applications/` and `$HOME/Desktop`. The script then removes any existing desktop file in the chosen location and creates a new one. If the user provides an invalid option, they are prompted to input again (online_install.sh).

4. The function `InitDesktop` is called to execute the above changes (online_install.sh).
2024-04-20 18:39:43 +08:00
RMBGAME ae0e2349e7 🐛 fix Errors caused by testing modifications 2024-04-20 17:28:47 +08:00
Aigio Liu 3a0f2a4950 🐛 ShowStart 2024-04-19 21:26:22 +08:00
Aigio Liu 5263863c23 🐛 ShowStart 2024-04-19 20:22:41 +08:00
RMBGAME c52f3c3e00 💄 测试加速项 Beta徽章 2024-04-18 20:39:51 +08:00
RMBGAME be61df890e 🐛 修复 隔行扫描gif图 动态头像显示错误 2024-04-17 23:05:49 +08:00
Aigio Liu 6a7c9113a0 ✨ XunYouState.启动游戏成功/启动游戏失败 2024-04-16 19:15:26 +08:00
Aigio Liu ac953af7a1 🎨 hosts mark duplicate auto reset 2024-04-16 14:15:31 +08:00
john.jiang 147a6b911f 🎨 家庭监护账号Cookie取消本地储存,有效期太短 2024-04-16 13:29:30 +08:00
wangyingpan 972054ca7c ⬆️ 更新 ClientSDK 2024-04-15 17:13:03 +08:00
RMBGAME 0cea809b40 🐛 fix 2024-04-15 16:24:23 +08:00
lxy829 7ec34e59a7 🎨 切换提示 2024-04-12 16:12:36 +08:00
RMBGAME 00d41dfbc8 🚧 Misc 2024-04-12 14:52:07 +08:00
RMBGAME 0272a34e5c ✨ 游戏加速 启动游戏按钮 2024-04-12 12:21:15 +08:00
wangyingpan d450753c92 添加代理token 缓存 2024-04-11 14:29:30 +08:00
wangyingpan 1e6263c241 🎨 调整尝试获取服务端代理token 方法 2024-04-11 13:51:41 +08:00
RMBGAME a4602d29a7 🐛 fix background img 2024-04-10 16:40:00 +08:00
john.jiang 7623788991 🎨 Idle挂卡家庭监护账号处理 2024-04-09 15:27:57 +08:00
Aigio Liu 8075183be1 ✨ XYSDK 2024-04-09 15:20:39 +08:00
ff m 058f1a9bf2 🐛 屏蔽 IPC 脚本内容优化 2024-04-08 20:52:09 +08:00
wangyingpan 196b7f0f5e 🐛 回撤ForwarderRequestConfig的错误修改 2024-04-03 18:29:14 +08:00
wangyingpan 99d24b2513 Merge branch 'develop' of https://github.com/BeyondDimension/SteamTools into develop 2024-04-03 18:26:35 +08:00
wangyingpan 7e3fb6cf06 ✨ 启动加速时请求服务端代理token,代理设置添加服务端代理token属性 2024-04-03 18:26:29 +08:00
lxy829 a0a0dd9bc7 🎨 fix 2024-04-03 16:37:21 +08:00
lxy829 81f75e61ca 🎨 Shortcut 指向协议 2024-04-03 16:08:16 +08:00
john.jiang 11890c68a8 🎨 修复挂卡顺序切换图片不显示 2024-04-03 11:20:32 +08:00
RMBGAME be8621d4d5 ✨ 增加代理http3支持
修复twitch登录失效
修复twitch直播聊天失效
修复twtich面板内图片加载失败
修复Github Docs网站代理无效且异常重定向
修复Github education网站代理无效且异常重定向
修复Gravatar加速失效
新增Open Spotify加速
新增Github Dev加速
2024-04-02 19:07:16 +08:00
wangyingpan 511c84b2ba 添加服务端代理转发头 2024-04-02 18:31:07 +08:00
john.jiang 9e75da9e46 🎨 fixs 2024-04-02 10:57:53 +08:00
john.jiang 547b360db8 🎨 Idle 启动失败提示 2024-04-02 10:56:37 +08:00
john.jiang 9be21a8e0a 🎨 挂卡完成提示完善 2024-04-01 17:00:46 +08:00
john.jiang efbb41bf3f 🎨 快速挂卡模式优化 2024-04-01 14:31:45 +08:00
RMBGAME c2220359dd 🐛 fix 2024-03-29 22:16:46 +08:00
lxy829 a788afdeeb 🎨 Steam 账号加载方式优化 2024-03-28 11:19:34 +08:00
BuknSS b543a889a0 🎨 Remove temporary test code. 2024-03-27 16:08:52 +08:00
lxy829 478950b75f 🎨 GameAccountShortcut 2024-03-26 19:13:07 +08:00
Aigio Liu 27b4114d7d 🔖 3.0.0-rc.8 2024-03-26 15:30:41 +08:00
Aigio Liu 6ead1f3666 🐛 |Error|HostsFileS|TryReadHosts fail, methodName: ContainsHostsByTag. | System.IO.IOException: The process cannot access the file 'C:\WINDOWS\system32\drivers\etc\hosts' because it is being used by another process.
at Microsoft.Win32.SafeHandles.SafeFileHandle.CreateFile(String fullPath, FileMode mode, FileAccess access, FileShare share, FileOptions options)
   at Microsoft.Win32.SafeHandles.SafeFileHandle.Open(String fullPath, FileMode mode, FileAccess access, FileShare share, FileOptions options, Int64 preallocationSize, Nullable`1 unixCreateMode)
   at System.IO.Strategies.OSFileStreamStrategy..ctor(String path, FileMode mode, FileAccess access, FileShare share, FileOptions options, Int64 preallocationSize, Nullable`1 unixCreateMode)
   at System.IO.Strategies.FileStreamHelpers.ChooseStrategyCore(String path, FileMode mode, FileAccess access, FileShare share, FileOptions options, Int64 preallocationSize, Nullable`1 unixCreateMode)
   at System.IO.StreamReader.ValidateArgsAndOpenPath(String path, Encoding encoding, Int32 bufferSize)
   at System.IO.File.ReadAllLines(String path, Encoding encoding)
2024-03-26 14:24:36 +08:00
Aigio Liu 22a015d8b9 ✨ hosts 文件监听 2024-03-26 12:02:59 +08:00
BuknSS f5b7cd6db8 🐛 Fix not regenerating a valid certificate after the certificate expires. 2024-03-26 11:02:33 +08:00
lxy829 5a397d1311 🎨 SteamSwitchShortcut 2024-03-25 18:59:03 +08:00
Aigio Liu 0c1bedaeb1 🔥 iotest 2024-03-22 21:50:32 +08:00
Aigio Liu e959dcc017 ✨ 查看证书信息 2024-03-22 19:05:31 +08:00
RMBGAME 41610453a0 🐛 fix 挂卡首次登录后数据加载问题 2024-03-20 20:10:21 +08:00
ff m adf3e0ca9d 🐛 UrlsOpened 11.1 使用新 API 2024-03-20 00:21:00 +08:00
Aigio Liu 21e80a5b7b 🎨 XunYouSDK.GetGameInfo 2024-03-18 13:57:58 +08:00
Aigio Liu 9a78f387cf 🔖 3.0.0-rc.7 2024-03-16 19:20:17 +08:00
Aigio Liu 8a091cee0c 🐛 tabItems 2024-03-16 19:01:32 +08:00
RMBGAME 28f9dde2a4 Revert "🐛 PluginCount"
This reverts commit 01e8478ce0.
2024-03-16 18:44:34 +08:00
RMBGAME aa88832d09 🐛 账号切换备注 ToolTip 显示 2024-03-16 18:44:17 +08:00
ff m 3da18b39ac 🐛 HttpUtility.UrlEncode 特定日期下无法格式 下午 这个中文
2024%2f4%2f15+下午4%3a13%3a52+%2b00%3a00
2024-03-16 14:54:33 +08:00
Aigio Liu 01e8478ce0 🐛 PluginCount 2024-03-16 14:45:04 +08:00
RMBGAME 0de026257a 🐛 fix 登录窗口登录成功后未正常关闭 2024-03-15 22:33:08 +08:00
Aigio Liu 5ff097d7d6 🐛 UISettings.StartDefaultPageName 在最小化启动时无效 x2 2024-03-15 19:14:48 +08:00
Aigio Liu 59effc34a3 🐛 UISettings.StartDefaultPageName 在最小化启动时无效 2024-03-15 17:25:36 +08:00
Aigio Liu afd21a3285 ✨ 加速页面 Tab 选项框下标记住选择 2024-03-15 14:14:04 +08:00
Aigio Liu c7ffeb6046 🔖 3.0.0-rc.6 2024-03-15 13:12:02 +08:00
Aigio Liu e659e3d502 ✨ Visual Studio App Center Retirement
https://learn.microsoft.com/en-us/appcenter/retirement
2024-03-15 11:44:02 +08:00
Aigio Liu 077a333d49 🎨 改进主窗口数据加载 2024-03-15 11:32:50 +08:00
Aigio Liu 056a73e3f0 🐛 起始页数据加载 2024-03-15 11:32:50 +08:00
RMBGAME db5095c205 🐛 显示加速模式与脚本状态 2024-03-13 18:11:55 +08:00
RMBGAME a1789cec16 🐛 fix挂时长在游戏列表未加载完成时进入错误的标注了游戏所属 2024-03-13 17:44:28 +08:00
Aigio Liu 9a234ae84f 🐛 MvvmService thread safe 2024-03-13 16:41:15 +08:00
Aigio Liu 8692eaef7c 🐛 ProxyService thread safe 2024-03-13 16:09:30 +08:00
lxy829 cb6516ce53 🎨 EpicSwitch 2024-03-13 11:35:20 +08:00
RMBGAME d8362ce9ea ✨ 支持设置启动时默认页(仍有一些问题) 2024-03-13 05:17:01 +08:00
RMBGAME 0cfdff80cd 🐛 修复加速项目勾选三级菜单启动加速没生效问题
修复账号切换首次打开没有读取缓存依然加载问题
2024-03-12 20:43:11 +08:00
RMBGAME 15c67bcd52 🐛 fix Steam账号切换删除时缓存没有正常清理问题 2024-03-10 21:42:12 +08:00
RMBGAME 3ebe7de3d7 💄 Misc 2024-03-10 21:12:57 +08:00
Aigio Liu deee8d056f 🎨 FileVersion 2024-03-08 17:57:36 +08:00
ff m 3016469c7f 🐛 Fix macOs Icons 2024-03-08 17:44:38 +08:00
Aigio Liu 526fb7a78d 🐛 图片加载缓存 2024-03-08 15:56:13 +08:00
RMBGAME 2790ffc5f1 🐛 Carousel CancelToken 2024-03-08 15:14:14 +08:00
RMBGAME e22897e2ae 🐛 挂卡登录异常处理 2024-03-08 15:03:46 +08:00
RMBGAME 00b8c4dfcf 💄 插件设置的一些样式 2024-03-08 14:54:10 +08:00
Aigio Liu 9a2d2c6c94 🐛 PublishTool 2024-03-08 14:36:19 +08:00
Aigio Liu 29fb43fc38 ⬆️ 3.0.0 rc.5 2024-03-08 11:30:22 +08:00
Aigio Liu 4ca3066644 🐛 https://github.com/BeyondDimension/SteamTools/issues/3213 https://github.com/BeyondDimension/SteamTools/issues/3214 2024-03-08 11:04:23 +08:00
RMBGAME 890f2fab37 🐛 fix最小化启动失效问题 2024-03-07 19:50:38 +08:00
Aigio Liu f561f841e3 🐛 MSStore DNS Mode x3 2024-03-07 18:59:45 +08:00
Aigio Liu a016631d68 🐛 MSStore DNS Mode x2 2024-03-07 18:24:46 +08:00
ff m 367c97b480 Script 文件存在强制替换 Delete 后未移动文件导致丢失。 2024-03-07 18:21:59 +08:00
Aigio L 6367bc5ed1 🐛 MSStore DNS Mode (#3211) 2024-03-07 17:07:59 +08:00
lxy829 2b69b98f3c 🎨 Riot Games and Rockstar switch 2024-03-06 16:28:47 +08:00
Aigio L a6010ba5f4 Merge pull request #3198 from BeyondDimension/develop
v3.0.0-rc.4_win_x64_240306_1204508630992
2024-03-06 13:42:14 +08:00
Aigio Liu 0c67cda96a 🔖 3.0.0 240306_1204508630992 2024-03-06 13:39:59 +08:00
Aigio Liu c23823a5af 🐛 https://github.com/BeyondDimension/SteamTools/issues/3150 & XY_INSTALL 2024-03-05 18:36:00 +08:00
Aigio Liu f8424a2915 🐛 AppxManifestXml 2024-03-05 15:19:03 +08:00
Aigio Liu 152fc74869 🎨 MainTitle 2024-03-05 15:05:49 +08:00
RMBGAME 7c96f7af46 🐛 无游戏列表时UI展示 2024-03-05 14:53:11 +08:00
Aigio Liu 9b5bc82873 🔖 [Release] Steam++_v3.0.0-rc.4_win_x64_240305_1352180388780 2024-03-05 14:54:16 +08:00
RMBGAME 011c08266f ✨ 安装路径选择 2024-03-05 11:53:21 +08:00
RMBGAME bd43f16dd6 💄 GameAcc 2024-03-01 20:37:05 +08:00
RMBGAME 8bd7085d35 🐛 fix代理模式设置被还原问题 2024-03-01 19:49:05 +08:00
Aigio Liu 1bb5bef800 🔖 [Release] Steam++_v3.0.0-rc.4_win_x64_240301_1817017115562 2024-03-01 18:46:10 +08:00
Aigio Liu da73e4cb84 🎨 Publish NSIS 2024-03-01 18:02:59 +08:00
RMBGAME d11c1afc12 🐛 fix 账号切换uniqueId bugs 2024-03-01 18:00:43 +08:00
Aigio Liu f21f069a2b 🎨 PubTool 2024-03-01 15:57:45 +08:00
Aigio Liu 242866fe88 🎨 PublishTool 2024-03-01 14:10:08 +08:00
Aigio Liu 46d87bc2d6 🎨 WinDivertSharp.NativeAssets.Win32 2024-02-29 17:22:37 +08:00
RMBGAME 70ce3237cd 🐛 修复第一次区服选择丢失问题 2024-02-29 16:44:56 +08:00
RMBGAME e57e7db81b 🐛 fix XY_GetAccelStateEx InBackground MainThread Call Exception 2024-02-28 21:07:01 +08:00
Aigio Liu 5bbddf86cd 🐛 xunyou_testspeed_ex 2024-02-28 20:33:59 +08:00
RMBGAME 541399f011 🐛 Misc 2024-02-28 15:48:00 +08:00
Aigio Liu 287ff69011 🐛 游戏有服信息时,无法测速问题,新增接口xunyou_testspeed_ex 2024-02-28 15:31:30 +08:00
Aigio Liu b00fbba75f 🐛 Log XunYouAccelStateToFrontendCallback 2024-02-27 22:13:17 +08:00
RMBGAME f9f0325610 🐛 Fix StateChange not runing MainThread 2024-02-27 21:04:09 +08:00
Aigio Liu 6dae018cb7 ✨ XunYouAccelStateEx 2024-02-27 18:56:40 +08:00
Aigio Liu a675d8ea2f 🎨 XunYouAccelStateCallback 2024-02-27 18:56:40 +08:00
RMBGAME 586f5d6a6c 🐛 The most significant changes include the update of the ItemsPerLine property in CarouselItems.axaml to use a binding to the parent spp:CarouselItems's ItemsPerPage property, the replacement of a large block of code in HomePage.axaml with a new block that sets up a spp:CarouselItems control with a custom ItemTemplate, and the addition of a new line of code in GameAcceleratorService.cs to update the LastAccelerateTime property of the app object.
Here is a list of the changes:

1. In `CarouselItems.axaml`, the `ItemsPerLine` property of `spp:FixedWrapPanel` has been updated to use a binding to the parent `spp:CarouselItems`'s `ItemsPerPage` property. This change allows the number of items per line to dynamically adjust based on the parent's `ItemsPerPage` property.

2. In `HomePage.axaml`, a large block of code related to an `ItemsControl` has been commented out and replaced with a new block of code that sets up a `spp:CarouselItems` control with a custom `ItemTemplate`. This change simplifies the code and makes it easier to manage the items in the carousel.

3. In `GameAcceleratorService.cs`, a new line of code has been added to update the `LastAccelerateTime` property of the `app` object to the current time when the `startCode` is `101`. This change ensures that the `LastAccelerateTime` property is always up-to-date.

4. In `GameInfoPageViewModel.cs`, two new lines of code have been added to set the `SelectedArea` and `SelectedServer` properties of `XunYouGame` to `null` in the `BackSelectArea` method. This change resets the selected area and server when the user goes back to the select area page.

5. In `GameAppItem.axaml`, several changes have been made to the styles and properties of various elements. These changes improve the appearance of the game app item.

6. In `AcceleratorPage2.axaml.cs`, the `SearchGameBox_SelectionChanged` event handler has been replaced with a `SearchGameBox_DropDownClosed` event handler. This change improves the user experience by resetting the search box and scrolling the game list to the top after a game is selected.
2024-02-27 17:47:52 +08:00
Aigio Liu e4058219d0 🐛 热门游戏默认排序 2024-02-27 14:47:12 +08:00
Aigio Liu 9adeb39b4f 🐛 游戏信息根据 Id 排序与加速器上一致 2024-02-27 13:32:24 +08:00
Aigio Liu 2f71afc818 🐛 切换区服数据不变时不重新加速 & 屏蔽安装路径设置项界面 2024-02-27 12:48:14 +08:00
Aigio Liu aa271827b3 🐛 监听加速服切换 2024-02-26 17:11:11 +08:00
Aigio Liu 88ed73d720 🐛 首次启动切换区服 2024-02-26 16:49:11 +08:00
Aigio Liu cde6e0d724 ✨ 刷新时重新加载迅游游戏数据 2024-02-26 16:20:25 +08:00
RMBGAME df5e359d0d 🐛 fix监听加速区服切换BUG 2024-02-26 15:56:50 +08:00
RMBGAME 14e050a942 🐛 The most significant changes involve modifications to the GameAcceleratorService and the layout of the game accelerator page. In GameAcceleratorService, the RestoreGameStatus method now sets PingValue to a new default value, and the logic for adding and updating games has been altered. The condition for early return in the game area selection method has also been changed. The layout of the game accelerator page has been restructured, with changes to the placement of ScrollViewer and GameDetail component.
1. The default value for `PingValue` in `XunYouGameViewModel` has been changed from an empty string to a constant string `DefaultPingValue` set to `"--"`.
2. `RestoreGameStatus` method in `GameAcceleratorService` now sets `PingValue` to `DefaultPingValue` instead of `"0"`.
3. The logic for adding and updating games in `GameAcceleratorService` has been modified. If the game is found in `MyGames`, it is now removed before being added again.
4. The condition for early return in the game area selection method in `GameAcceleratorService` now checks if `SelectedArea` is null on `vm.XunYouGame` instead of `app`.
5. The layout of the game accelerator page in `AcceleratorPage2.axaml` has been changed. The `ScrollViewer` has been moved inside the `ContentLoader` and the `GameDetail` component has been moved outside the `ScrollViewer`.
6. The `ItemsRepeater` in `GameListPage.axaml` now has a `VerticalCacheLength` of `300`.
2024-02-23 20:36:04 +08:00
Aigio Liu 568483c454 🎨 PingValue/PingValueIsInvalid 2024-02-23 19:43:32 +08:00
Aigio Liu 926fd00fc7 🐛 PingValue 2024-02-23 18:56:07 +08:00
Aigio Liu 5c64dd1168 🐛 MyGames Save 2024-02-23 17:26:57 +08:00
Aigio Liu 5dd50aeeca 🎨 延迟大于等于1000,显示-- 2024-02-23 16:05:29 +08:00
RMBGAME c2bb27c537 🐛 The most significant changes involve the GameAcceleratorService class and the GameAppItem.axaml and GameDetail.axaml files. In the GameAcceleratorService class, the variable game2 is now used instead of game in certain cases, and the properties IsAccelerated, IsAccelerating, and LastAccelerateTime are now set directly on game2. The class also now includes SelectedArea and SelectedServer in the GameInfoPageViewModel instance creation and uses an async version of GameAccelerator. In the GameAppItem.axaml and GameDetail.axaml files, new styles and properties have been added and some properties have been moved or removed.
Changes:

1. The `GameAcceleratorService` class now uses `game2` instead of `game` in the switch case for `XunYouState.加速中` and `XunYouState.加速已完成`. The properties `IsAccelerated`, `IsAccelerating`, and `LastAccelerateTime` are now set directly on `game2` (Code Change 1).
2. The `GameAcceleratorService` class now includes `SelectedArea` and `SelectedServer` in the `GameInfoPageViewModel` instance creation (Code Change 2).
3. The `GameAcceleratorService` class now uses an async version of `GameAccelerator` (Code Change 3).
4. The `GameInfoPageViewModel` class has been updated to change the second parameter of `WhenPropertyChanged` from `false` to `true` for `XunYouGame.SelectedArea` (Code Change 4).
5. The `GameAppItem.axaml` file has been updated with a new style for `Grid Panel.AppName TextBlock.LeftMargin` and new properties for `spp:Image2`. The `Margin` property of `TextBlock` has been moved to a new class `Classes.LeftMargin` (Code Change 5).
6. The `GameDetail.axaml` file has been updated to move the `spp:Image2` inside a `StackPanel` and add `IsVisible` and `Margin` properties to it. The `Margin` property of `TextBlock` has been removed (Code Change 6).
7. The `AcceleratorPage2.axaml.cs` file has been updated to add a new dictionary `dictPinYinArray` and a new method `GameContains` that checks if a game contains a given text using case-insensitive comparison and Pinyin comparison. The `TextSelector` and `TextFilter` properties of `SearchGameBox` have been set to `null` and `GameContains` respectively (Code Change 7).
2024-02-23 15:10:30 +08:00
Aigio Liu 9cf88df056 ✨ 确定性编译 2024-02-23 14:56:56 +08:00
RMBGAME 995aaee884 🐛 The most significant changes are in the AuthenticatorHomePageViewModel.cs and AuthenticatorHomePage.axaml files. In the ViewModel, a new else block has been added to set IsVerificationPass to true when the condition for the if statement is not met. In the axaml file, the visibility of the StackPanel is now directly bound to the IsVerificationPass property, simplifying the visibility condition. The MultiBinding converter code has been commented out, indicating it is no longer in use.
List of changes:

1. A new else block has been added to the `AuthenticatorHomePageViewModel.cs` file. This block sets `IsVerificationPass` to true when the if statement's condition is not met.

2. In the `AuthenticatorHomePage.axaml` file, the visibility of the `StackPanel` is now directly bound to the `IsVerificationPass` property. This change simplifies the visibility condition of the `StackPanel`.

3. The `MultiBinding` converter code, which was previously used to bind `IsVerificationPass` and `Auths.Count` to the visibility of the `StackPanel`, has been commented out. This indicates that it is no longer in use but is kept for possible future reference.

4. No changes have been made to the `TextBlock` element. It is included for context.
2024-02-23 01:23:23 +08:00
RMBGAME 99e915adbe 🐛 The most significant changes involve the GameAccelerator method in the GameAcceleratorService class and the GameAcceleratorCommand ReactiveCommand. The GameAccelerator method has been changed from an asynchronous void method to an asynchronous Task method, allowing for improved error handling and control flow. The GameAcceleratorCommand has been updated to reflect this change. Additionally, a new property IsAccelerating has been added to the XunYouGameViewModel class to better track the state of the game acceleration process.
Changes:

1. The `GameAccelerator` method in the `GameAcceleratorService` class has been changed from an asynchronous void method to an asynchronous Task method. This change allows for better error handling and control flow.

2. The `GameAcceleratorCommand` ReactiveCommand has been updated to use `CreateFromTask` instead of `Create`, reflecting the change in the `GameAccelerator` method.

3. A new property `IsAccelerating` has been added to the `XunYouGameViewModel` class. This property is set to `true` at the start of the `GameAccelerator` method and is set to `false` in various places where the method may return early due to an error or condition not being met. This change allows for better tracking of the state of the game acceleration process.

4. The line setting `app.IsAccelerating` to `true` has been removed from the end of the `GameAccelerator` method, as it is now set at the start of the method.

5. In the case where the game acceleration is stopped successfully, the line setting `app.IsAccelerating` to `true` has been removed. In the case where stopping the game acceleration fails, `app.IsAccelerating` is now set to `false`.
2024-02-23 01:23:23 +08:00
ff m b8801d6b82 🐛 调整更新文本大小按钮位置. 2024-02-22 18:17:58 +08:00
Aigio Liu 73639376aa 🐛 Application Crash - Watt Toolkit
---------------------------
Stopped program because of exception, name: AppDomain, isTerminating: True
System.ObjectDisposedException: Cannot access a disposed object.
Object name: 'IServiceProvider'.
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.ThrowHelper.ThrowObjectDisposedException()
   at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(IServiceProvider provider, Type serviceType)
   at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService[T](IServiceProvider provider)
   at System.Log.get_Factory()
   at System.Log.CreateLogger(String tag)
   at System.Log.Error(String tag, String msg, Object[] args)
   at BD.WTTS.Services.Implementation.BackendAcceleratorServiceImpl.XunYouAccelStateToFrontendCallback(XunYouAccelStateModel m) in C:\Code\2023\SteamTools\src\BD.WTTS.Client.Plugins.Accelerator\Services.Implementation\BackendAcceleratorServiceImpl.XunYou.cs:line 49
   at BD.WTTS.Services.Implementation.BackendAcceleratorServiceImpl.XunYouAccelStateCallback(XunYouState status, IntPtr thisptr) in C:\Code\2023\SteamTools\src\BD.WTTS.Client.Plugins.Accelerator\Services.Implementation\BackendAcceleratorServiceImpl.XunYou.cs:line 30
---------------------------
确定
---------------------------
2024-02-22 18:16:24 +08:00
RMBGAME 5852423652 🐛 The most significant changes involve the GameAcceleratorService.cs file, where new cases have been added to the switch statement to handle different states of the game accelerator. The SetGameStatus method has also been updated to check and update the CurrentAcceleratorGame properties. In addition, changes have been made to the GameDetail.axaml, AcceleratorPage2.axaml, AcceleratorPage2.axaml.cs, and MainFramePage.axaml files to modify UI elements and functionality.
1. `GameAcceleratorService.cs` has been updated to include a new case for `XunYouState.加速中` in the switch statement. This case checks if the `GameId` is not 0 and `CurrentAcceleratorGame` is null, then it creates a new `XunYouGameViewModel` instance and assigns it to `CurrentAcceleratorGame`.
2. A new case for `XunYouState.加速已断开` has been added to the switch statement in `GameAcceleratorService.cs`. This case stops the speed test and updates the game status.
3. The `SetGameStatus` method in `GameAcceleratorService.cs` has been updated to check if `CurrentAcceleratorGame` is not null and its `Id` is equal to `game.Id`. If true, it updates the `CurrentAcceleratorGame` properties. If `SelectedArea` is null, it fetches the game info and updates `SelectedArea` and `SelectedServer`.
4. The `GameDetail.axaml` file has been updated to change the `Foreground` color of two `TextBlock` elements.
5. The `AcceleratorPage2.axaml` file has been updated to remove some UI elements related to the Watt Accelerator installation path and add them back later in the file. The order of some elements has been changed.
6. The `AcceleratorPage2.axaml.cs` file has been updated to clear the selected item in `SearchGameBox` after adding a game to `GameAcceleratorService`.
7. The `MainFramePage.axaml` file has been updated to comment out a `TabStripItem` related to the community fix.
2024-02-22 17:27:27 +08:00
RMBGAME 18012f775e 🐛 GameAcc 2024-02-22 15:58:40 +08:00
Aigio Liu 9054d4d095 🐛 globalXunYouDownLoadCallback_xunyou_install 2024-02-22 15:54:58 +08:00
Aigio Liu e19011a785 🐛 本机库路径 & 切换加速 2024-02-22 15:02:52 +08:00
Aigio Liu a63dfc0846 🐛 PublishTool 2024-02-22 04:57:44 +08:00
RMBGAME 40539d99ed ⬆️ Avalonia 11.0.9 2024-02-21 19:25:43 +08:00
RMBGAME 9ae1584506 🚧 ScriptUpdate 2024-02-21 19:18:12 +08:00
Mossimo 38f35d36e0 🐛 遗漏初始化后检测脚本更新 2024-02-21 19:03:59 +08:00
Aigio Liu b3c18e2042 ⬆️ Common 2024-02-21 18:47:02 +08:00
RMBGAME 5ea5fe1e83 🚧 GameAcc 2024-02-21 18:44:23 +08:00
RMBGAME 9c71e89645 🚧 GameAcc 2024-02-21 18:08:40 +08:00
Aigio Liu 8a745839fe 🐛 GameAcceleratorSettings 2024-02-21 17:42:45 +08:00
Aigio Liu 1a237a10bc 🐛 InitStateSubscribe 2024-02-21 16:49:56 +08:00
Aigio Liu 655f943f5d 🐛 单例服务 2024-02-21 16:25:50 +08:00
Mossimo 77f8178557 🐛 添加新表 旧数据全部失效 旧数据 IsBuild 全部异常 2024-02-21 16:19:33 +08:00
Aigio Liu c33dfc8e70 🔧 Ipc XYSDK 2024-02-21 16:05:25 +08:00
Aigio Liu 803b626e8a 🐛 Settings 2024-02-21 15:57:05 +08:00
Aigio Liu f9228b93a3 🐛 Login2 2024-02-21 14:55:12 +08:00
Aigio Liu 60ec614d46 🐛 Login 2024-02-21 14:42:06 +08:00
Aigio Liu 237190384f 🐛 Urls 2024-02-21 14:13:46 +08:00
Aigio Liu 7dd576a6c5 ⬆️ ClientSDK 2024-02-21 14:10:53 +08:00
Aigio Liu 3fb0057883 🐛 GameAcc 2024-02-21 13:53:10 +08:00
Aigio Liu aeccf68061 🐛 GameAcc 2024-02-21 00:06:44 +08:00
RMBGAME 0806f61448 🚧 Fix 2024-02-20 22:22:52 +08:00
RMBGAME b67adfd67c 🚧 GameAcc 2024-02-20 21:45:58 +08:00
RMBGAME 97fb2ed673 🚧 GameAcc 2024-02-20 21:45:50 +08:00
Aigio Liu 0beadc8733 🚑 GameAcc 2024-02-20 21:05:02 +08:00
Aigio Liu d0bc99d5a5 🐛 ApiRsp Message 2024-02-20 20:34:00 +08:00
Aigio Liu 0852c57ed8 ✨ Resx GameAccelerator 2024-02-20 17:17:06 +08:00
Aigio L 84653abace Merge branch 'BeyondDimension:develop' into develop 2024-02-20 16:11:27 +08:00
Aigio Liu 2a738a36a1 🐛 XYSDK 2024-02-20 16:12:08 +08:00
Aigio Liu b18925a1a0 ✨ XYSDK 2024-02-20 16:08:41 +08:00
ff m 29fd1f0b19 🐛 撤回错误修复 3.0 该地方需要补完 旧版本不需要 2024-02-20 16:03:43 +08:00
RMBGAME d414267c9f 🐛 Misc 2024-02-19 18:05:46 +08:00
lxy829 af8739bb59 🎨 移除 Origin 平台账号切换支持 2024-02-19 15:57:27 +08:00
lxy829 e140902de6 🎨 BattleNetSwitcher 2024-02-19 14:53:39 +08:00
john.jiang 4a5e3447ff 🎨 ArchiSteamFarmPlus fixs 2024-02-19 14:51:33 +08:00
Aigio Liu a97357099d 🐛 fix https://github.com/BeyondDimension/SteamTools/issues/3163 2024-02-19 14:00:47 +08:00
john.jiang 7131deb860 🎨 ArchiSteamFarmPlus 2024-02-06 15:50:36 +08:00
lxy829 268b803ce7 🎨 Platformsjson 2024-02-06 15:45:13 +08:00
lxy829 6a9af1d95f 🎨 PlatformSwitcher 2024-02-06 10:37:51 +08:00
RMBGAME 1bf13ffb4b 🚧 Misc 2024-02-02 21:08:45 +08:00
RMBGAME 1b90cd4bce 🚧 Misc 2024-02-02 19:42:25 +08:00
Mossimo 62efead062 🐛 Add OpenId 调用 v1 版本接口 2024-02-02 17:51:59 +08:00
ff m 2754fb5dac 🐛 fix script NotFound 2024-01-29 22:54:49 +08:00
RMBGAME 8fff1d1828 💄 FixedWrapPanel 2024-01-29 18:00:20 +08:00
RMBGAME 9805ad4114 🐛 修复某些情况导入SDA令牌出错 2024-01-04 11:32:26 +08:00
Mossimo 83ae3102dd ↑ update SteamClient
🐛 Fix LC_ALL = en_US.UTF-8 保持兼容性
2023-12-22 17:07:18 +08:00
RMBGAME c3911fd5bf 🚧 FlexPanel 2023-12-21 11:58:29 +08:00
RMBGAME ef27310e4f 💄✨ CarouselItems Impl 2023-12-20 14:44:27 +08:00
RMBGAME 9ea1c83342 🐛 修复成就 云存档窗口在部分设置下打不开的问题 2023-12-18 14:49:35 +08:00
RMBGAME bbcee67fe0 💄✨ CarouselItems Control 2023-12-15 18:23:34 +08:00
RMBGAME 4706561d42 🐛 修复挂卡登录账号不便于查看令牌问题 2023-12-12 14:36:08 +08:00
RMBGAME c9d5fe37bb 🐛 Misc 2023-12-08 18:12:02 +08:00
Mossimo 6bca078614 🐛 添加 离线初始化安装 优化卸载脚本 (现在会验证父级目录 存在已知文件夹 且需要用户输入 y 执行卸载) 2023-12-06 11:24:42 +08:00
Mossimo a4481011eb 🐛 Fix 迁移 MoveDirByScripts 父级目录未创建直接 Move 异常 2023-12-05 18:38:02 +08:00
Mossimo 81d39b5436 🐛 Fix Script 2023-12-05 18:38:02 +08:00
Mossimo 4bff8602d1 ✨ 一键登录商城 2023-12-05 18:38:02 +08:00
john.jiang 4e434d25fe 🎨 submodule update 2023-12-04 11:52:00 +08:00
john.jiang 4e2cfc23ae 🎨 fixs DefaultIdleRule 2023-11-30 18:03:46 +08:00
326 changed files with 14721 additions and 2185 deletions
+5 -5
View File
@@ -68,19 +68,19 @@ jobs:
run: dotnet --info
- name: Build BD.WTTS.Client.AppHost
run: dotnet build -c Release src\BD.WTTS.Client.AppHost\BD.WTTS.Client.AppHost.csproj --nologo -v q /property:WarningLevel=1
run: dotnet build -c Release src\BD.WTTS.Client.AppHost\BD.WTTS.Client.AppHost.csproj --nologo -v q --property:WarningLevel=1;DebugType=pdbonly
- name: Build BD.WTTS.Client.Plugins.Accelerator.ReverseProxy
run: dotnet build -c Release src\BD.WTTS.Client.Plugins.Accelerator.ReverseProxy\BD.WTTS.Client.Plugins.Accelerator.ReverseProxy.csproj --nologo -v q /property:WarningLevel=1
run: dotnet build -c Release src\BD.WTTS.Client.Plugins.Accelerator.ReverseProxy\BD.WTTS.Client.Plugins.Accelerator.ReverseProxy.csproj --nologo -v q --property:WarningLevel=1;DebugType=pdbonly
- name: Build BD.WTTS.Client.Avalonia.App
run: dotnet build -c Release src\BD.WTTS.Client.Avalonia.App\BD.WTTS.Client.Avalonia.App.csproj --nologo -v q /property:WarningLevel=1
run: dotnet build -c Release src\BD.WTTS.Client.Avalonia.App\BD.WTTS.Client.Avalonia.App.csproj --nologo -v q --property:WarningLevel=1;DebugType=pdbonly
- name: Build BD.WTTS.Client.Avalonia.Designer.HostApp
run: dotnet build -c Release src\BD.WTTS.Client.Avalonia.Designer.HostApp\BD.WTTS.Client.Avalonia.Designer.HostApp.csproj --nologo -v q /property:WarningLevel=1
run: dotnet build -c Release src\BD.WTTS.Client.Avalonia.Designer.HostApp\BD.WTTS.Client.Avalonia.Designer.HostApp.csproj --nologo -v q --property:WarningLevel=1;DebugType=pdbonly
- name: Test
run: dotnet test src/BD.WTTS.UnitTest/BD.WTTS.UnitTest.csproj -c Release -p:GeneratePackageOnBuild=false --nologo -v q /property:WarningLevel=1
run: dotnet test src/BD.WTTS.UnitTest/BD.WTTS.UnitTest.csproj -c Release -p:GeneratePackageOnBuild=false --nologo -v q --property:WarningLevel=1;DebugType=pdbonly
# release:
# name: Release
+208
View File
@@ -0,0 +1,208 @@
#!/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
InitDesktop() {
while true; do
# 使用 zenity 提示用户选择安装路径或使用默认路径
choice=$(zenity --list --radiolist --title="请选择要添加到的位置" --column="选择" --column="路径" TRUE "$HOME/.local/share/applications/" FALSE "$HOME/Desktop")
# 检查用户输入
if [ "$choice" == "$HOME/.local/share/applications/" ]; then
target_dir="$HOME/.local/share/applications/"
break
elif [ "$choice" == "$HOME/Desktop" ]; then
target_dir="$HOME/Desktop/"
break
else
echo "无效选项,请输入 1 或 2。"
fi
done
#添加桌面文件
rm -rf "$target_dir/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" >"$target_dir/Watt Toolkit.desktop"
chmod +x "$target_dir/Watt Toolkit.desktop"
}
InitDesktop
# update-desktop-database ~/.local/share/applications
#运行程序
Show_Run "下载安装完成,是否启动程序?"
exit 0
+61 -9
View File
@@ -1,7 +1,38 @@
#!/bin/bash
base_path="$HOME/WattToolkit"
mkdir -p "$base_path"
cd "$base_path" || exit 1
# 设置默认的 base_path 参数
default_base_path="$HOME/WattToolkit"
base_path="$default_base_path"
# 循环直到用户输入有效路径或直接回车
while true; do
# 使用 zenity 提示用户选择安装路径或使用默认路径
custom_base_path=$(zenity --entry --title="安装路径" --text="请输入安装路径(默认为 $default_base_path,不输入则使用默认路径)")
# 如果用户提供了自定义路径,则使用该路径
if [ -n "$custom_base_path" ]; then
base_path="$custom_base_path"
break
elif [ -z "$custom_base_path" ]; then
# 用户直接回车,使用默认路径
break
else
# 用户输入无效路径,重新提示
echo "无效路径,请重新输入。"
fi
done
# 创建安装路径
mkdir -p "$base_path" || {
echo "无法创建安装路径 $base_path,请检查权限或路径是否正确。"
exit 1
}
# 切换到安装路径
cd "$base_path" || {
echo "无法切换到安装路径 $base_path,请检查权限或路径是否正确。"
exit 1
}
appVer_path="$base_path/WattToolkit.AppVer"
exec_name="Steam++"
tar_name="WattToolkit.tgz"
@@ -182,7 +213,9 @@ Get_NewVer() {
sleep 1
#本地版本 Hash
o_sha384=$(cat "AppVer")
if [ -f "AppVer" ]; then
o_sha384=$(cat "AppVer")
fi
if [ -e "AppVer" ]; then
if [ "${o_sha384,,}" = "${n_sha384,,}" ]; then
Show_Run "已是最新版本,是否启动程序?"
@@ -326,17 +359,36 @@ Kill_Process
#解压
Decompression
# 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
InitDesktop() {
while true; do
# 使用 zenity 提示用户选择安装路径或使用默认路径
choice=$(zenity --list --radiolist --title="请选择要添加到的位置" --column="选择" --column="路径" TRUE "$HOME/.local/share/applications/" FALSE "$HOME/Desktop")
# 检查用户输入
if [ "$choice" == "$HOME/.local/share/applications/" ]; then
target_dir="$HOME/.local/share/applications/"
break
elif [ "$choice" == "$HOME/Desktop" ]; then
target_dir="$HOME/Desktop/"
break
else
echo "无效选项,请输入 1 或 2。"
fi
done
#添加桌面文件
rm -rf "$target_dir/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"
StartupNotify=false" >"$target_dir/Watt Toolkit.desktop"
chmod +x "$target_dir/Watt Toolkit.desktop"
}
InitDesktop
# update-desktop-database ~/.local/share/applications
#运行程序
Show_Run "下载安装完成,是否启动程序?"
+32 -2
View File
@@ -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
+28 -1
View File
@@ -212,6 +212,15 @@
"Summary": "菜单排序",
"IsRegionOrEndregion": null,
"IsValueType": null
},
{
"TypeName": "string",
"PropertyName": "StartDefaultPageName",
"DefaultValue": "null",
"DefaultValueIsConst": true,
"Summary": "程序启动时默认页",
"IsRegionOrEndregion": null,
"IsValueType": null
}
],
"Usings": ""
@@ -487,6 +496,15 @@
"Summary": "账号平台设置集合",
"IsRegionOrEndregion": null,
"IsValueType": null
},
{
"TypeName": "bool",
"PropertyName": "IsShowAccountName",
"DefaultValue": "false",
"DefaultValueIsConst": false,
"Summary": "是否显示账号用户名",
"IsRegionOrEndregion": null,
"IsValueType": null
}
],
"Usings": ""
@@ -961,6 +979,15 @@
"Summary": "自定义 DNS over HTTPS 地址",
"IsRegionOrEndregion": null,
"IsValueType": null
},
{
"TypeName": "int",
"PropertyName": "AcceleratorTabsSelectedIndex",
"DefaultValue": "0",
"DefaultValueIsConst": false,
"Summary": "加速页面 Tab 选项卡选中下标",
"IsRegionOrEndregion": null,
"IsValueType": null
}
],
"Usings": ""
@@ -981,7 +1008,7 @@
{
"TypeName": "IdleRule",
"PropertyName": "IdleRule",
"DefaultValue": "IdleRule.OnlyOneGame",
"DefaultValue": "IdleRule.FastMode",
"DefaultValueIsConst": false,
"Summary": "运行规则",
"IsRegionOrEndregion": null,
BIN
View File
Binary file not shown.
Binary file not shown.
+2 -2
View File
@@ -16,12 +16,12 @@ public static partial class AssemblyInfo
/// <summary>
/// 预览版本号,范围 1~9,只增不减
/// </summary>
const string ver_for_preview = "7";
const string ver_for_preview = "8";
/// <summary>
/// RC 版本号,范围 0~9,只增不减
/// </summary>
const string ver_for_rc = "3";
const string ver_for_rc = "8";
public const string FileVersion = $"{Version2}.{ver_for_rc}0{ver_for_preview}.0";
+3
View File
@@ -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")]
@@ -38,9 +38,9 @@ public interface IFontManagerImpl2 : IFontManagerImpl
return Impl.TryCreateGlyphTypeface(familyName, style, weight, stretch, out glyphTypeface);
}
bool IFontManagerImpl.TryCreateGlyphTypeface(Stream stream, [NotNullWhen(returnValue: true)] out IGlyphTypeface? glyphTypeface)
bool IFontManagerImpl.TryCreateGlyphTypeface(Stream stream, FontSimulations fontSimulations, [NotNullWhen(returnValue: true)] out IGlyphTypeface? glyphTypeface)
{
return Impl.TryCreateGlyphTypeface(stream, out glyphTypeface);
return Impl.TryCreateGlyphTypeface(stream, fontSimulations, out glyphTypeface);
}
//IGlyphTypeface IFontManagerImpl.CreateGlyphTypeface(Typeface typeface)
@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<Package
xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10"
@@ -8,70 +8,76 @@
xmlns:desktop2="http://schemas.microsoft.com/appx/manifest/desktop/windows10/2"
IgnorableNamespaces="uap rescap desktop desktop2">
<Identity
Name="4651ED44255E.47979655102CE"
Publisher="CN=A90E406B-B2D3-4A23-B061-0FA1D65C4F66"
Version="3.0.207.0" />
<Identity
Name="4651ED44255E.47979655102CE"
Publisher="CN=A90E406B-B2D3-4A23-B061-0FA1D65C4F66"
Version="3.0.207.0" />
<Properties>
<DisplayName>Watt Toolkit</DisplayName>
<PublisherDisplayName>软妹币玩家</PublisherDisplayName>
<Logo>Images\StoreLogo.png</Logo>
</Properties>
<Properties>
<DisplayName>Watt Toolkit</DisplayName>
<PublisherDisplayName>软妹币玩家</PublisherDisplayName>
<Logo>Images\StoreLogo.png</Logo>
</Properties>
<Dependencies>
<TargetDeviceFamily Name="Windows.Universal" MinVersion="10.0.17763.0" MaxVersionTested="10.0.19041.0" />
<TargetDeviceFamily Name="Windows.Desktop" MinVersion="10.0.17763.0" MaxVersionTested="10.0.19041.0" />
</Dependencies>
<Dependencies>
<TargetDeviceFamily Name="Windows.Universal" MinVersion="10.0.17763.0" MaxVersionTested="10.0.19041.0" />
<TargetDeviceFamily Name="Windows.Desktop" MinVersion="10.0.17763.0" MaxVersionTested="10.0.19041.0" />
</Dependencies>
<Resources>
<Resource Language="x-generate"/>
</Resources>
<Resources>
<Resource Language="x-generate"/>
</Resources>
<Applications>
<Application Id="App"
Executable="$targetnametoken$.exe"
EntryPoint="$targetentrypoint$">
<uap:VisualElements
DisplayName="Watt Toolkit"
Description="「Watt Toolkit」是一个开源跨平台的多功能游戏工具箱,此工具的大部分功能都是需要您下载安装 Steam 才能使用。"
BackgroundColor="transparent"
Square150x150Logo="Images\Square150x150Logo.png"
Square44x44Logo="Images\Square44x44Logo.png">
<uap:DefaultTile Wide310x150Logo="Images\Wide310x150Logo.png" Square71x71Logo="Images\SmallTile.png" Square310x310Logo="Images\LargeTile.png" ShortName="Steam++">
<uap:ShowNameOnTiles>
<uap:ShowOn Tile="square150x150Logo"/>
<uap:ShowOn Tile="wide310x150Logo"/>
<uap:ShowOn Tile="square310x310Logo"/>
</uap:ShowNameOnTiles>
</uap:DefaultTile >
<uap:SplashScreen Image="Images\SplashScreen.png" />
<uap:InitialRotationPreference>
<uap:Rotation Preference="landscape"/>
</uap:InitialRotationPreference>
<uap:LockScreen BadgeLogo="Images\BadgeLogo.png" Notification="badgeAndTileText"/>
</uap:VisualElements>
<Extensions>
<desktop:Extension Category="windows.fullTrustProcess" Executable="Steam++\Steam++.exe" />
<desktop:Extension Category="windows.startupTask" Executable="Steam++\Steam++.exe" EntryPoint="Windows.FullTrustApplication">
<desktop:StartupTask TaskId="BootAutoStartTask" Enabled="true" DisplayName="Steam++ System Boot Run" />
</desktop:Extension>
</Extensions>
</Application>
</Applications>
<Applications>
<Application Id="App"
Executable="$targetnametoken$.exe"
EntryPoint="$targetentrypoint$">
<uap:VisualElements
DisplayName="Watt Toolkit"
Description="「Watt Toolkit」是一个开源跨平台的多功能游戏工具箱,此工具的大部分功能都是需要您下载安装 Steam 才能使用。"
BackgroundColor="transparent"
Square150x150Logo="Images\Square150x150Logo.png"
Square44x44Logo="Images\Square44x44Logo.png">
<uap:DefaultTile Wide310x150Logo="Images\Wide310x150Logo.png" Square71x71Logo="Images\SmallTile.png" Square310x310Logo="Images\LargeTile.png" ShortName="Steam++">
<uap:ShowNameOnTiles>
<uap:ShowOn Tile="square150x150Logo"/>
<uap:ShowOn Tile="wide310x150Logo"/>
<uap:ShowOn Tile="square310x310Logo"/>
</uap:ShowNameOnTiles>
</uap:DefaultTile >
<uap:SplashScreen Image="Images\SplashScreen.png" />
<uap:InitialRotationPreference>
<uap:Rotation Preference="landscape"/>
</uap:InitialRotationPreference>
<uap:LockScreen BadgeLogo="Images\BadgeLogo.png" Notification="badgeAndTileText"/>
</uap:VisualElements>
<Extensions>
<desktop:Extension Category="windows.fullTrustProcess" Executable="Steam++\Steam++.exe" />
<desktop:Extension Category="windows.startupTask" Executable="Steam++\Steam++.exe" EntryPoint="Windows.FullTrustApplication">
<desktop:StartupTask TaskId="BootAutoStartTask" Enabled="true" DisplayName="Steam++ System Boot Run" />
</desktop:Extension>
<uap:Extension Category="windows.protocol">
<uap:Protocol Name="spp">
<uap:Logo>Images\Square44x44Logo.png</uap:Logo>
<uap:DisplayName>Steam++ URI Scheme</uap:DisplayName>
</uap:Protocol>
</uap:Extension>
</Extensions>
</Application>
</Applications>
<Extensions>
<desktop2:Extension Category="windows.firewallRules">
<desktop2:FirewallRules Executable="Steam++\modules\Accelerator\Steam++.Accelerator.exe">
<desktop2:Rule Direction="in" IPProtocol="TCP" Profile="all"/>
<desktop2:Rule Direction="in" IPProtocol="UDP" Profile="all"/>
</desktop2:FirewallRules>
</desktop2:Extension>
</Extensions>
<Extensions>
<desktop2:Extension Category="windows.firewallRules">
<desktop2:FirewallRules Executable="Steam++\modules\Accelerator\Steam++.Accelerator.exe">
<desktop2:Rule Direction="in" IPProtocol="TCP" Profile="all"/>
<desktop2:Rule Direction="in" IPProtocol="UDP" Profile="all"/>
</desktop2:FirewallRules>
</desktop2:Extension>
</Extensions>
<Capabilities>
<Capability Name="internetClient" />
<rescap:Capability Name="runFullTrust" />
<rescap:Capability Name="allowElevation" />
</Capabilities>
<Capabilities>
<Capability Name="internetClient" />
<rescap:Capability Name="runFullTrust" />
<rescap:Capability Name="allowElevation" />
</Capabilities>
</Package>
@@ -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>
@@ -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>
@@ -81,8 +81,10 @@
<PackageReference Include="Avalonia" />
<PackageReference Include="Avalonia.ReactiveUI" />
<PackageReference Include="Avalonia.Controls.DataGrid" />
<PackageReference Include="Avalonia.Controls.ItemsRepeater" />
<PackageReference Include="Avalonia.Xaml.Behaviors" />
<!--<PackageReference Include="Avalonia.AvaloniaEdit" />-->
<PackageReference Include="Avalonia.AvaloniaEdit" />
<PackageReference Include="AvaloniaEdit.TextMate" />
<!--<PackageReference Include="Markdown.Avalonia" />-->
<PackageReference Include="FluentAvaloniaUI" />
<PackageReference Include="LiveChartsCore.SkiaSharpView.Avalonia" />
@@ -126,6 +128,15 @@
<ProjectReference Include="..\BD.WTTS.Client\BD.WTTS.Client.csproj" />
</ItemGroup>
<ItemGroup>
<Compile Update="UI\Views\Controls\Widgets\CarouselItems.cs">
<DependentUpon>%(Filename)</DependentUpon>
</Compile>
<Compile Update="UI\Views\Pages\PluginStorePage.axaml.cs">
<DependentUpon>PluginStorePage.axaml</DependentUpon>
</Compile>
</ItemGroup>
<Import Project="..\TFM_NETX_WITH_DESKTOP.props" />
</Project>
@@ -0,0 +1,111 @@
namespace BD.WTTS.Converters;
public sealed class WaveProgressValueConverter : IValueConverter
{
public static readonly WaveProgressValueConverter Instance = new();
/// <inheritdoc/>
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
{
if (value is not double i) return 0;
return 155 - (i * 2.1);
}
/// <inheritdoc/>
public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
{
return BindingOperations.DoNothing;
}
}
public class WaveProgressValueColorConverter : IValueConverter
{
public static readonly WaveProgressValueColorConverter Instance = new();
/// <inheritdoc/>
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
{
if (value is not double d) return Brushes.Black;
if (d > 50) return Brushes.GhostWhite;
return Application.Current?.ActualThemeVariant == ThemeVariant.Dark ? Brushes.GhostWhite : Brushes.Black;
}
/// <inheritdoc/>
public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
{
return BindingOperations.DoNothing;
}
}
public class WaveProgressValueTextConverter : IValueConverter
{
public static readonly WaveProgressValueTextConverter Instance = new();
/// <inheritdoc/>
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
{
return value is not double d ? "0%" : $"{d:#0}%";
}
/// <inheritdoc/>
public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
{
return BindingOperations.DoNothing;
}
}
public class WaveProgressGradientOffsetConverter : IValueConverter
{
public static readonly WaveProgressGradientOffsetConverter Instance = new();
/// <inheritdoc/>
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
{
if (value is not double v)
return Brushes.Blue;
var app = Application.Current;
if (app == null)
return null;
Color primaryColor = Colors.DodgerBlue;
Color accentColor = Colors.Transparent;
try
{
if (app.FindResource("SystemAccentColor") is Color primaryColor2)
{
primaryColor = primaryColor2;
}
if (app.FindResource("SystemAccentColorLight2") is Color accentColor2)
{
accentColor = accentColor2;
}
}
catch { }
var isLight = app.RequestedThemeVariant == ThemeVariant.Light;
v /= 100;
v += isLight ? 0.2 : 0.4;
if (v > 1)
v = 1;
return new LinearGradientBrush()
{
EndPoint = new RelativePoint(0.5, 1, RelativeUnit.Relative),
StartPoint = new RelativePoint(0.5, 0, RelativeUnit.Relative),
GradientStops = new GradientStops()
{
new GradientStop() { Color = primaryColor, Offset = 0 },
new GradientStop() { Color = isLight ? Colors.Transparent : accentColor, Offset = v },
},
};
}
/// <inheritdoc/>
public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
{
return BindingOperations.DoNothing;
}
}
@@ -2,7 +2,7 @@ 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, bool useCache = false) where T : ViewModelBase, new()
{
if (c.DataContext == null || c.DataContext.GetType() != typeof(T))
{
@@ -11,7 +11,7 @@ public static class ControlExtensions
if (useCache)
cx = IViewModelManager.Instance.Get<T>();
else
cx = Activator.CreateInstance<T>();
cx = new T();
c.DataContext = cx;
}
@@ -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();
}
}
@@ -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);
@@ -113,10 +113,9 @@ sealed class AvaloniaWindowManagerImpl : IWindowManagerImpl
if (viewModel != null)
{
if (viewModel is IWindowViewModel window)
{
window.Close = () => td?.Hide(TaskDialogStandardResult.Close);
window.Close = b => td?.Hide(b ? TaskDialogStandardResult.OK : TaskDialogStandardResult.Close);
}
td.DataContext = viewModel;
+10 -2
View File
@@ -125,12 +125,20 @@ partial class App
{
subMenu = new NativeMenu();
foreach (var sub in item.Value.Items)
subMenu.Add(new NativeMenuItem
{
var menu = new NativeMenuItem
{
Header = sub.Name,
Command = sub.Command,
CommandParameter = sub.CommandParameter,
});
};
if (sub.IsVisible is Avalonia.Data.IBinding v)
menu[!NativeMenuItem.IsVisibleProperty] = v;
if (sub.IsEnabled is Avalonia.Data.IBinding e)
menu[!NativeMenuItem.IsEnabledProperty] = e;
subMenu.Add(menu);
}
menus.Add(new NativeMenuItem
{
Header = item.Value.Name,
+32 -6
View File
@@ -11,6 +11,12 @@ public sealed partial class App : Application
{
const string TAG = "AvaApp";
public App()
{
OpenBrowserCommand = ReactiveCommand.Create<object?>(OpenBrowserCommandCore);
CopyToClipboardCommand = ReactiveCommand.Create<object?>(CopyToClipboardCommandCore);
}
/// <summary>
/// 获取当前主窗口
/// </summary>
@@ -46,6 +52,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 +100,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)
@@ -223,16 +231,32 @@ public sealed partial class App : Application
public static FontFamily DefaultFontFamily => _DefaultFontFamily.Value;
public void OpenBrowserCommand(object url)
public ICommand OpenBrowserCommand { get; }
public async void OpenBrowserCommandCore(object? url)
{
try
{
if (url == null || string.IsNullOrEmpty(url.ToString()))
var urlString = url?.ToString();
if (string.IsNullOrEmpty(urlString))
{
Toast.Show(ToastIcon.Warning, "打开链接失败");
return;
}
Browser2.Open(url.ToString());
try
{
Uri uri = new(urlString);
if (uri.Host.EndsWith("steampp.net", StringComparison.OrdinalIgnoreCase) ||
uri.Host.EndsWith("mossimo.net", StringComparison.OrdinalIgnoreCase))
{
await UserService.Current.OpenAuthUrl(urlString);
return;
}
}
catch
{
}
Browser2.Open(urlString);
}
catch (Exception ex)
{
@@ -240,7 +264,9 @@ public sealed partial class App : Application
}
}
public async void CopyToClipboardCommand(object text)
public ICommand CopyToClipboardCommand { get; }
public async void CopyToClipboardCommandCore(object? text)
{
try
{
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

@@ -6,15 +6,16 @@
x:CompileBindings="True">
<Styles.Resources>
<ResourceDictionary>
<sys:Double x:Key="TitleBarHeight">60</sys:Double>
<Thickness x:Key="TitleBarAfterMargin">0,60,0,0</Thickness>
<sys:Double x:Key="TitleBarHeight">50</sys:Double>
<Thickness x:Key="TitleBarAfterMargin">0,50,0,0</Thickness>
<sys:Double x:Key="SplashScreenImageWidth">100</sys:Double>
<Thickness x:Key="PageMargin">10</Thickness>
<sys:Double x:Key="SettingsSpacing">3</sys:Double>
<!--<sys:Char x:Key="PasswordChar">&#x25CF;</sys:Char>-->
<sys:Char x:Key="PasswordChar">•</sys:Char>
<sys:Char x:Key="PasswordChar">&#x25CF;</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" />
@@ -64,6 +66,7 @@
<StyleInclude Source="/UI/Styling/Controls/IconElement.axaml" />
<StyleInclude Source="/UI/Styling/Controls/CustomFilePicker.axaml" />
<StyleInclude Source="/UI/Styling/Controls/NumericUpDown.axaml" />
<StyleInclude Source="/UI/Styling/Controls/Skeleton.axaml" />
<Style Selector="FlyoutPresenter.NoPadding">
<Setter Property="Padding" Value="0" />
@@ -7,7 +7,7 @@
<Border Padding="20">
<!-- Add Controls for Previewer Here -->
<StackPanel Spacing="10">
<ui:HyperlinkButton Content="更新日志" />
<HyperlinkButton Content="更新日志" />
<Button Content="更新日志" Theme="{StaticResource HyperlinkButton}" />
<Button
Content="更新日志"
@@ -87,7 +87,7 @@
<Setter Property="CornerRadius" Value="{DynamicResource ControlCornerRadius}" />
<Setter Property="Template">
<ControlTemplate>
<ui:FABorder
<Border
Name="Root"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
@@ -101,19 +101,19 @@
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}"
RecognizesAccessKey="True" />
</ui:FABorder>
</Border>
</ControlTemplate>
</Setter>
<Style Selector="^:pointerover">
<Style Selector="^ /template/ ui|FABorder#Root">
<Style Selector="^ /template/ Border#Root">
<Setter Property="Background" Value="{DynamicResource SubtleFillColorSecondaryBrush}" />
</Style>
</Style>
<Style Selector="^:pressed">
<Style Selector="^ /template/ ui|FABorder#Root">
<Style Selector="^ /template/ Border#Root">
<Setter Property="Background" Value="{DynamicResource SubtleFillColorTertiaryBrush}" />
</Style>
<Style Selector="^ /template/ ContentPresenter#PART_ContentPresenter">
@@ -122,7 +122,7 @@
</Style>
<Style Selector="^:disabled">
<Style Selector="^ /template/ ui|FABorder#Root">
<Style Selector="^ /template/ Border#Root">
<Setter Property="Background" Value="{DynamicResource SubtleFillColorDisabledBrush}" />
</Style>
<Style Selector="^ /template/ ContentPresenter#PART_ContentPresenter">
@@ -131,7 +131,7 @@
</Style>
<Style Selector="^:checked">
<Style Selector="^ /template/ ui|FABorder#Root">
<Style Selector="^ /template/ Border#Root">
<Setter Property="Background" Value="{DynamicResource ToggleButtonBackgroundChecked}" />
</Style>
<Style Selector="^ /template/ ContentPresenter#PART_ContentPresenter">
@@ -140,7 +140,7 @@
<Style Selector="^:pointerover">
<Style Selector="^ /template/ ui|FABorder#Root">
<Style Selector="^ /template/ Border#Root">
<Setter Property="Background" Value="{DynamicResource ToggleButtonBackgroundCheckedPointerOver}" />
</Style>
<Style Selector="^ /template/ ContentPresenter#PART_ContentPresenter">
@@ -149,7 +149,7 @@
</Style>
<Style Selector="^:pressed">
<Style Selector="^ /template/ ui|FABorder#Root">
<Style Selector="^ /template/ Border#Root">
<Setter Property="Background" Value="{DynamicResource ToggleButtonBackgroundCheckedPressed}" />
</Style>
<Style Selector="^ /template/ ContentPresenter#PART_ContentPresenter">
@@ -158,7 +158,7 @@
</Style>
<Style Selector="^:disabled">
<Style Selector="^ /template/ ui|FABorder#Root">
<Style Selector="^ /template/ Border#Root">
<Setter Property="Background" Value="{DynamicResource SubtleFillColorDisabledBrush}" />
</Style>
<Style Selector="^ /template/ ContentPresenter#PART_ContentPresenter">
@@ -168,19 +168,19 @@
</Style>
<Style Selector="^:indeterminate">
<Style Selector="^ /template/ ui|FABorder#Root">
<Style Selector="^ /template/ Border#Root">
<Setter Property="Background" Value="{DynamicResource SubtleFillColorTransparentBrush}" />
</Style>
<Style Selector="^:pointerover">
<Style Selector="^ /template/ ui|FABorder#Root">
<Style Selector="^ /template/ Border#Root">
<Setter Property="Background" Value="{DynamicResource SubtleFillColorSecondaryBrush}" />
</Style>
</Style>
<Style Selector="^:pressed">
<Style Selector="^ /template/ ui|FABorder#Root">
<Style Selector="^ /template/ Border#Root">
<Setter Property="Background" Value="{DynamicResource SubtleFillColorTertiaryBrush}" />
</Style>
<Style Selector="^ /template/ ContentPresenter#PART_ContentPresenter">
@@ -189,7 +189,7 @@
</Style>
<Style Selector="^:disabled">
<Style Selector="^ /template/ ui|FABorder#Root">
<Style Selector="^ /template/ Border#Root">
<Setter Property="Background" Value="{DynamicResource SubtleFillColorDisabledBrush}" />
</Style>
<Style Selector="^ /template/ ContentPresenter#PART_ContentPresenter">
@@ -214,10 +214,13 @@
<Style Selector="Button.Plain:pointerover">
<Setter Property="Background" Value="Transparent" />
</Style>
<Style Selector="Button.Plain:pointerover /template/ ui|FABorder#Root">
<Style Selector="Button.Plain:pointerover /template/ Border#Root">
<Setter Property="Background" Value="Transparent" />
</Style>
<Style Selector="Button.Plain:pointerover /template/ ContentPresenter">
<Setter Property="Background" Value="Transparent" />
</Style>
</Styles>
@@ -0,0 +1,111 @@
<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">
<ItemsControl Name="Swiper" IsVisible="False">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" Spacing="8" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.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>
</ItemsControl.ItemTemplate>
</ItemsControl>
</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>
@@ -18,27 +18,15 @@
HorizontalAlignment="Center"
VerticalAlignment="Center"
IsVisible="{TemplateBinding IsLoading}"
Spacing="8">
<spp:Image2
Spacing="10">
<!--<spp:Image2
Width="100"
Height="100"
HorizontalAlignment="Center"
VerticalAlignment="Bottom"
RenderOptions.BitmapInterpolationMode="HighQuality"
Source="avares://BD.WTTS.Client.Avalonia/UI/Assets/mona-loading-default.gif"
Stretch="UniformToFill" />
<TextBlock
HorizontalAlignment="Center"
Text="{Binding Path=Res.GameList_IsLoading, Mode=OneWay, Source={x:Static s:ResourceService.Current}}"
Theme="{StaticResource SubtitleTextBlockStyle}" />
<TextBlock
HorizontalAlignment="Center"
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
IsVisible="{TemplateBinding CustomLoadingText,
Converter={StaticResource IsNullConverter},
ConverterParameter=invert}"
Text="{TemplateBinding CustomLoadingText}"
Theme="{StaticResource BodyStrongTextBlockStyle}" />
Stretch="UniformToFill" />-->
<Border
Width="35"
Height="35"
@@ -51,6 +39,18 @@
Minimum="{TemplateBinding Minimum}"
Value="{TemplateBinding ProgressValue}" />
</Border>
<TextBlock
HorizontalAlignment="Center"
Text="{Binding Path=Res.GameList_IsLoading, Mode=OneWay, Source={x:Static s:ResourceService.Current}}"
Theme="{StaticResource SubtitleTextBlockStyle}" />
<TextBlock
HorizontalAlignment="Center"
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
IsVisible="{TemplateBinding CustomLoadingText,
Converter={StaticResource IsNullConverter},
ConverterParameter=invert}"
Text="{TemplateBinding CustomLoadingText}"
Theme="{StaticResource BodyStrongTextBlockStyle}" />
</StackPanel>
<!--<TextBlock
@@ -75,21 +75,34 @@
</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>
<Style Selector="^:loading /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Effect" Value="blur(5)" />
</Style>
</ControlTheme>
</ResourceDictionary>
@@ -46,19 +46,19 @@
<Setter Property="Template">
<ControlTemplate>
<StackPanel>
<ui:FABorder
<Border
Name="LayoutRoot"
Padding="{TemplateBinding Padding}"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="{TemplateBinding CornerRadius}">
<ui:FABorder.Transitions>
<Border.Transitions>
<Transitions>
<BrushTransition Property="Background" Duration="00:00:00.083" />
<BrushTransition Property="BorderBrush" Duration="00:00:00.083" />
</Transitions>
</ui:FABorder.Transitions>
</Border.Transitions>
<Grid MinHeight="{TemplateBinding MinHeight}" ColumnDefinitions="Auto,*,Auto,Auto">
<Viewbox
Name="IconHost"
@@ -113,7 +113,7 @@
VerticalAlignment="Center"
FontSize="18" />
</Grid>
</ui:FABorder>
</Border>
<ContentPresenter
Name="ExpandedContentPresenter"
@@ -144,10 +144,10 @@
<Setter Property="IsVisible" Value="False" />
</Style>
<Style Selector="spp|OptionsDisplayItem /template/ ui|FABorder#LayoutRoot:pointerover">
<Style Selector="spp|OptionsDisplayItem /template/ Border#LayoutRoot:pointerover">
<Setter Property="Background" Value="{DynamicResource ControlFillColorSecondaryBrush}" />
</Style>
<Style Selector="spp|OptionsDisplayItem:pressed /template/ ui|FABorder#LayoutRoot:pointerover">
<Style Selector="spp|OptionsDisplayItem:pressed /template/ Border#LayoutRoot:pointerover">
<Setter Property="Background" Value="{DynamicResource ControlFillColorTertiaryBrush}" />
</Style>
@@ -179,7 +179,7 @@
<Setter Property="CornerRadius" Value="{Binding CornerRadius, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource BottomCornerRadiusFilterConverter}}" />
</Style>
<Style Selector="spp|OptionsDisplayItem:expanded /template/ ui|FABorder#LayoutRoot">
<Style Selector="spp|OptionsDisplayItem:expanded /template/ Border#LayoutRoot">
<Setter Property="CornerRadius" Value="{Binding CornerRadius, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource TopCornerRadiusFilterConverter}}" />
</Style>
@@ -195,19 +195,19 @@
<Style Selector="spp|OptionsDisplayItem.Vertical">
<Setter Property="Template">
<ControlTemplate>
<ui:FABorder
<Border
Name="LayoutRoot"
Padding="12"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="{TemplateBinding CornerRadius}">
<ui:FABorder.Transitions>
<Border.Transitions>
<Transitions>
<BrushTransition Property="Background" Duration="00:00:00.083" />
<BrushTransition Property="BorderBrush" Duration="00:00:00.083" />
</Transitions>
</ui:FABorder.Transitions>
</Border.Transitions>
<Grid RowDefinitions="*,*">
<Border
Width="72"
@@ -241,7 +241,7 @@
TextWrapping="Wrap" />
</StackPanel>
</Grid>
</ui:FABorder>
</Border>
</ControlTemplate>
</Setter>
</Style>
@@ -38,18 +38,18 @@
<Setter Property="MinHeight" Value="{DynamicResource SettingsExpanderItemMinHeight}" />
<Setter Property="Template">
<ControlTemplate>
<ui:FABorder
<Border
Name="Root"
Padding="{TemplateBinding Padding}"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<ui:FABorder.Transitions>
<Border.Transitions>
<Transitions>
<BrushTransition Property="Background" Duration="00:00:00.083" />
<BrushTransition Property="BorderBrush" Duration="00:00:00.083" />
</Transitions>
</ui:FABorder.Transitions>
</Border.Transitions>
<Grid ColumnDefinitions="*,Auto,Auto" RowDefinitions="*,Auto">
<Viewbox
@@ -102,7 +102,7 @@
<ContentPresenter Name="ExpandChevronActionIconContainer" Content="{Binding TemplateSettings.ActionIcon, RelativeSource={RelativeSource TemplatedParent}}" />
</Viewbox>
</Grid>
</ui:FABorder>
</Border>
</ControlTemplate>
</Setter>
@@ -110,7 +110,7 @@
<Setter Property="IsVisible" Value="False" />
</Style>
<Style Selector="^:nth-last-child(1) /template/ ui|FABorder#Root">
<Style Selector="^:nth-last-child(1) /template/ Border#Root">
<Setter Property="CornerRadius" Value="{Binding Source={StaticResource ControlCornerRadius}, Converter={StaticResource BottomCornerRadiusFilterConverter}}" />
</Style>
@@ -152,11 +152,11 @@
</Style>
<Style Selector="^:allowClick">
<Style Selector="^:pointerover /template/ ui|FABorder#Root">
<Style Selector="^:pointerover /template/ Border#Root">
<Setter Property="Background" Value="{DynamicResource ControlFillColorSecondaryBrush}" />
<Setter Property="BorderBrush" Value="{DynamicResource ControlFillColorSecondaryBrush}" />
</Style>
<Style Selector="^:pressed /template/ ui|FABorder#Root">
<Style Selector="^:pressed /template/ Border#Root">
<Setter Property="Background" Value="{DynamicResource ControlFillColorTertiaryBrush}" />
<Setter Property="BorderBrush" Value="{DynamicResource ControlStrokeColorDefaultBrush}" />
<Setter Property="TextElement.Foreground" Value="{DynamicResource TextFillColorSecondaryBrush}" />
@@ -164,7 +164,7 @@
</Style>
<Style Selector="^:disabled">
<Style Selector="^ /template/ ui|FABorder#Root">
<Style Selector="^ /template/ Border#Root">
<Setter Property="Background" Value="{DynamicResource ControlFillColorDisabledBrush}" />
<Setter Property="BorderBrush" Value="{DynamicResource ControlStrokeColorDefaultBrush}" />
<Setter Property="TextElement.Foreground" Value="{DynamicResource TextFillColorDisabledBrush}" />
@@ -0,0 +1,111 @@
<Styles
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:spp="https://steampp.net/ui"
x:CompileBindings="True">
<Design.PreviewWith>
<Border Padding="20">
<!-- Add Controls for Previewer Here -->
<StackPanel>
<ToggleSwitch Name="loading" Content="Loading" />
<ToggleSwitch Name="active" Content="Active" />
<Grid
MinWidth="400"
HorizontalAlignment="Left"
ColumnDefinitions="auto,*">
<spp:Skeleton
Width="100"
Height="100"
VerticalAlignment="Center"
CornerRadius="2"
IsActive="{Binding #active.IsChecked}"
IsLoading="{Binding #loading.IsChecked}">
<Image RenderOptions.BitmapInterpolationMode="HighQuality" Source="avares://BD.WTTS.Client.Avalonia/UI/Assets/ApplicationIcon.ico" />
</spp:Skeleton>
<UniformGrid
Grid.Column="1"
Margin="5,0"
Columns="1"
Rows="3">
<spp:Skeleton
Margin="0,0,200,0"
VerticalAlignment="Top"
CornerRadius="2"
IsActive="{Binding #active.IsChecked}"
IsLoading="{Binding #loading.IsChecked}">
<SelectableTextBlock Margin="0,2" Text="是先有鸡还是先有蛋" />
</spp:Skeleton>
<spp:Skeleton
Margin="0,0,100,0"
VerticalAlignment="Center"
CornerRadius="2"
IsActive="{Binding #active.IsChecked}"
IsLoading="{Binding #loading.IsChecked}">
<SelectableTextBlock Margin="0,2" Text="什么是工匠精神" />
</spp:Skeleton>
<spp:Skeleton
VerticalAlignment="Bottom"
CornerRadius="2"
IsActive="{Binding #active.IsChecked}"
IsLoading="{Binding #loading.IsChecked}">
<SelectableTextBlock Margin="0,2" Text="看山不是山,看水不是水,看山还是山,看水还是水" />
</spp:Skeleton>
</UniformGrid>
</Grid>
</StackPanel>
</Border>
</Design.PreviewWith>
<Styles.Resources>
<ControlTheme x:Key="{x:Type spp:Skeleton}" TargetType="spp:Skeleton">
<Setter Property="Background" Value="Transparent" />
<Setter Property="Template">
<ControlTemplate TargetType="spp:Skeleton">
<Border
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
ClipToBounds="{TemplateBinding ClipToBounds}"
CornerRadius="{TemplateBinding CornerRadius}">
<Panel>
<ContentPresenter
x:Name="PART_ContentPresenter"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}" />
<spp:PureRectangle
x:Name="PART_LoadingBorder"
Background="{DynamicResource SkeletonDefaultBackground}"
Classes.Active="{Binding Path=IsActive, RelativeSource={RelativeSource TemplatedParent}}"
IsHitTestVisible="{TemplateBinding IsLoading}"
IsVisible="{TemplateBinding IsLoading}" />
<spp:PureRectangle
x:Name="PART_ActiveBorder"
IsHitTestVisible="{TemplateBinding IsLoading}"
IsVisible="{TemplateBinding IsLoading}" />
</Panel>
</Border>
</ControlTemplate>
</Setter>
</ControlTheme>
</Styles.Resources>
<Style Selector="spp|Skeleton[IsActive=True][IsLoading=True] /template/ spp|PureRectangle#PART_ActiveBorder">
<Style.Animations>
<Animation
Easing="CubicEaseInOut"
FillMode="None"
IterationCount="Infinite"
PlaybackDirection="Alternate"
Duration="0:0:1.4">
<KeyFrame Cue="0%">
<Setter Property="Background" Value="{DynamicResource SkeletonStartAnimationBackground}" />
</KeyFrame>
<KeyFrame Cue="100%">
<Setter Property="Background" Value="{DynamicResource SkeletonEndAnimationBackground}" />
</KeyFrame>
</Animation>
</Style.Animations>
</Style>
</Styles>
@@ -95,7 +95,7 @@
<Setter Property="Template">
<ControlTemplate>
<LayoutTransformControl Name="PART_LayoutTransformControl" UseRenderTransform="True">
<ui:FABorder
<Border
Name="ContentRoot"
VerticalAlignment="Top"
BorderBrush="{DynamicResource InfoBarBorderBrush}"
@@ -205,13 +205,13 @@
</Button>
</Grid>
</Panel>
</ui:FABorder>
</Border>
</LayoutTransformControl>
</ControlTemplate>
</Setter>
<Style Selector="^:informational">
<Style Selector="^ /template/ ui|FABorder#ContentRoot">
<Style Selector="^ /template/ Border#ContentRoot">
<!--<Setter Property="Background" Value="{DynamicResource InfoBarInformationalSeverityBackgroundBrush}" />-->
<Setter Property="Background" Value="{DynamicResource SolidBackgroundFillColorBaseBrush}" />
</Style>
@@ -226,7 +226,7 @@
</Style>
<Style Selector="^:error">
<Style Selector="^ /template/ ui|FABorder#ContentRoot">
<Style Selector="^ /template/ Border#ContentRoot">
<Setter Property="Background" Value="{DynamicResource InfoBarErrorSeverityBackgroundBrush}" />
</Style>
<Style Selector="^ /template/ Ellipse#IconBackground">
@@ -241,7 +241,7 @@
</Style>
<Style Selector="^:warning">
<Style Selector="^ /template/ ui|FABorder#ContentRoot">
<Style Selector="^ /template/ Border#ContentRoot">
<Setter Property="Background" Value="{DynamicResource InfoBarWarningSeverityBackgroundBrush}" />
</Style>
<Style Selector="^ /template/ Ellipse#IconBackground">
@@ -255,7 +255,7 @@
</Style>
<Style Selector="^:success">
<Style Selector="^ /template/ ui|FABorder#ContentRoot">
<Style Selector="^ /template/ Border#ContentRoot">
<Setter Property="Background" Value="{DynamicResource InfoBarSuccessSeverityBackgroundBrush}" />
</Style>
<Style Selector="^ /template/ Ellipse#IconBackground">
@@ -294,7 +294,7 @@
</Style>
<!-- Visible -->
<Style Selector="^:hidden /template/ ui|FABorder#ContentRoot">
<Style Selector="^:hidden /template/ Border#ContentRoot">
<Setter Property="IsVisible" Value="False" />
</Style>
@@ -75,7 +75,7 @@
<Setter Property="VerticalAlignment" Value="Stretch" />
<Setter Property="Template">
<ControlTemplate>
<ui:FABorder
<Border
Name="Root"
VerticalAlignment="{TemplateBinding VerticalAlignment}"
Background="{TemplateBinding Background}"
@@ -83,19 +83,19 @@
BorderThickness="1"
CornerRadius="{TemplateBinding CornerRadius}"
TemplatedControl.IsTemplateFocusTarget="True">
<ui:FABorder.Transitions>
<Border.Transitions>
<Transitions>
<BrushTransition Property="Background" Duration="00:00:00.083" />
</Transitions>
</ui:FABorder.Transitions>
</Border.Transitions>
<Panel>
<ui:FABorder
<Border
Name="Hover"
Margin="3"
Background="Transparent"
CornerRadius="{TemplateBinding CornerRadius}">
<ui:FABorder.Transitions>
<Border.Transitions>
<Transitions>
<BrushTransition Property="Background" Duration="00:00:00.083" />
<TransformOperationsTransition
@@ -103,8 +103,8 @@
Property="RenderTransform"
Duration="00:00:00.167" />
</Transitions>
</ui:FABorder.Transitions>
</ui:FABorder>
</Border.Transitions>
</Border>
<ContentPresenter
Name="PART_ContentPresenter"
@@ -139,19 +139,19 @@
</Rectangle>
</Panel>
</ui:FABorder>
</Border>
</ControlTemplate>
</Setter>
<Style Selector="^:pointerover">
<Style Selector="^ /template/ ui|FABorder#Hover">
<Style Selector="^ /template/ Border#Hover">
<Setter Property="Background" Value="{DynamicResource SubtleFillColorSecondaryBrush}" />
</Style>
</Style>
<Style Selector="^:pressed">
<Style Selector="^ /template/ ui|FABorder#Hover">
<Style Selector="^ /template/ Border#Hover">
<Setter Property="Background" Value="{DynamicResource SubtleFillColorTertiaryBrush}" />
<Setter Property="RenderTransform" Value="scale(0.96)" />
</Style>
@@ -161,10 +161,10 @@
</Style>
<Style Selector="^:selected">
<Style Selector="^ /template/ ui|FABorder#Hover">
<Style Selector="^ /template/ Border#Hover">
<Setter Property="IsVisible" Value="False" />
</Style>
<Style Selector="^ /template/ ui|FABorder#Root">
<Style Selector="^ /template/ Border#Root">
<Setter Property="Background" Value="{DynamicResource ControlFillColorDefaultBrush}" />
<Setter Property="BorderBrush" Value="{DynamicResource ControlElevationBorderBrush}" />
</Style>
@@ -181,10 +181,10 @@
</Style>
<Style Selector="^:disabled">
<Style Selector="^ /template/ ui|FABorder#Hover">
<Style Selector="^ /template/ Border#Hover">
<Setter Property="IsVisible" Value="False" />
</Style>
<Style Selector="^ /template/ ui|FABorder#Root">
<Style Selector="^ /template/ Border#Root">
<Setter Property="Background" Value="{DynamicResource ControlFillColorDisabledBrush}" />
<Setter Property="BorderBrush" Value="{DynamicResource ControlAltFillColorSecondaryBrush}" />
</Style>
@@ -80,7 +80,7 @@
<Setter Property="Template">
<ControlTemplate>
<Panel Name="LayoutRoot" Background="{x:Null}">
<ui:FABorder
<Border
Name="ContentRoot"
MinWidth="{DynamicResource TaskDialogMinWidth}"
MinHeight="{DynamicResource TaskDialogMinHeight}"
@@ -216,7 +216,7 @@
</ItemsControl>
</Border>
</Grid>
</ui:FABorder>
</Border>
</Panel>
</ControlTemplate>
</Setter>
@@ -303,7 +303,7 @@
<Style Selector="^ /template/ Panel#LayoutRoot">
<Setter Property="Background" Value="{DynamicResource TaskDialogSmokeFill}" />
</Style>
<Style Selector="^ /template/ ui|FABorder#ContentRoot">
<Style Selector="^ /template/ Border#ContentRoot">
<Setter Property="BorderBrush" Value="{DynamicResource TaskDialogBorderBrush}" />
<Setter Property="CornerRadius" Value="{DynamicResource OverlayCornerRadius}" />
<Setter Property="BorderThickness" Value="{DynamicResource TaskDialogBorderWidth}" />
@@ -332,7 +332,7 @@
</Animation>
</Style.Animations>
</Style>
<Style Selector="^:hidden /template/ ui|FABorder#ContentRoot">
<Style Selector="^:hidden /template/ Border#ContentRoot">
<Style.Animations>
<Animation FillMode="Forward" Duration="00:00:00.167">
<KeyFrame Cue="0%">
@@ -368,7 +368,7 @@
</Animation>
</Style.Animations>
</Style>
<Style Selector="^:open /template/ ui|FABorder#ContentRoot">
<Style Selector="^:open /template/ Border#ContentRoot">
<Style.Animations>
<Animation FillMode="Forward" Duration="00:00:00.250">
<KeyFrame Cue="0%">
@@ -389,7 +389,7 @@
<Setter Property="Template">
<ControlTemplate>
<Panel Name="LayoutRoot" Background="{x:Null}">
<ui:FABorder
<Border
Name="ContentRoot"
MinWidth="{DynamicResource TaskDialogMinWidth}"
MinHeight="{DynamicResource TaskDialogMinHeight}"
@@ -521,7 +521,7 @@
</ItemsControl>
</Border>
</Grid>
</ui:FABorder>
</Border>
</Panel>
</ControlTemplate>
</Setter>
@@ -14,4 +14,5 @@
</Styles.Resources>
<StyleInclude Source="/UI/Styling/Controls.axaml" />
<StyleInclude Source="avares://AvaloniaEdit/Themes/Fluent/AvaloniaEdit.xaml" />
</Styles>
@@ -38,6 +38,7 @@
<x:String x:Key="Back">&#xE112;</x:String>
<x:String x:Key="Sort">&#xE174;</x:String>
<x:String x:Key="Move">&#xE7C2;</x:String>
<x:String x:Key="Device">&#xE7FB;</x:String>
<ui:ImageIconSource x:Key="AppIcon" Source="avares://BD.WTTS.Client.Avalonia/UI/Assets/ApplicationIcon.ico" />
@@ -21,4 +21,16 @@
</OnPlatform>
</RenderOptions.BitmapInterpolationMode>-->
</ImageBrush>
<LinearGradientBrush x:Key="SkeletonStartAnimationBackground" StartPoint="0%,100%" EndPoint="100%,100%">
<GradientStop Offset="0" Color="#FF1C1F23" />
<GradientStop Offset="0" Color="#FF2E3238" />
<GradientStop Offset="1" Color="#FF1C1F23" />
</LinearGradientBrush>
<LinearGradientBrush x:Key="SkeletonEndAnimationBackground" StartPoint="0%,100%" EndPoint="100%,100%">
<GradientStop Offset="0" Color="#FF1C1F23" />
<GradientStop Offset="1" Color="#FF2E3238" />
<GradientStop Offset="1" Color="#FF1C1F23" />
</LinearGradientBrush>
<SolidColorBrush x:Key="SkeletonDefaultBackground" Color="#FF1C1F23" />
</ResourceDictionary>
@@ -21,4 +21,16 @@
</OnPlatform>
</RenderOptions.BitmapInterpolationMode>-->
</ImageBrush>
<LinearGradientBrush x:Key="SkeletonStartAnimationBackground" StartPoint="0%,100%" EndPoint="100%,100%">
<GradientStop Offset="0" Color="#FFF9F9F9" />
<GradientStop Offset="0" Color="#FFE6E8EA" />
<GradientStop Offset="1" Color="#FFF9F9F9" />
</LinearGradientBrush>
<LinearGradientBrush x:Key="SkeletonEndAnimationBackground" StartPoint="0%,100%" EndPoint="100%,100%">
<GradientStop Offset="0" Color="#FFF9F9F9" />
<GradientStop Offset="1" Color="#FFE6E8EA" />
<GradientStop Offset="1" Color="#FFF9F9F9" />
</LinearGradientBrush>
<SolidColorBrush x:Key="SkeletonDefaultBackground" Color="#FFF9F9F9" />
</ResourceDictionary>
@@ -210,7 +210,7 @@
<wnd:MinMaxCloseControl
Name="SystemCaptionButtons"
Height="{Binding TemplateSettings.TitleBarHeight, RelativeSource={RelativeSource TemplatedParent}}"
Margin="18,0"
Margin="14,0"
HorizontalAlignment="Right"
VerticalAlignment="Top" />
@@ -1,5 +1,7 @@
using Avalonia.Markup.Xaml.MarkupExtensions;
using Avalonia.Rendering.Composition;
using FluentAvalonia.UI.Controls.Experimental;
using FluentAvalonia.UI.Navigation;
namespace BD.WTTS.UI.Views.Pages;
@@ -7,6 +9,8 @@ public class PageBase : UserControl
{
public PageBase() : base()
{
_disposables = new();
SizeChanged += PageBaseSizeChanged;
if (HeaderBackground == null)
@@ -14,6 +18,8 @@ public class PageBase : UserControl
this[!HeaderBackgroundProperty] = new DynamicResourceExtension("PageHeaderBackgroundBrush");
}
_disposables.Add(UISettings.WindowBackgroundCustomImage.Subscribe(x => IsShowBackgroundImage = !x));
//AddHandler(Frame.NavigatingFromEvent, FrameNavigatingFrom, RoutingStrategies.Direct);
//AddHandler(Frame.NavigatedToEvent, FrameNavigatedTo, RoutingStrategies.Direct);
}
@@ -139,6 +145,7 @@ public class PageBase : UserControl
//_previewImageHost = e.NameScope.Find<IconSourceElement>("PreviewImageElement");
//_detailsHost = e.NameScope.Find<StackPanel>("DetailsTextHost");
_optionsHost = e.NameScope.Find<Panel>("OptionsRegion");
_tabsHost = e.NameScope.Find<Border>("TabRegion");
_detailsPanel = e.NameScope.Find<Panel>("PageDetails");
@@ -157,6 +164,7 @@ public class PageBase : UserControl
{
base.OnUnloaded(e);
_hasLoaded = false;
_disposables.Dispose();
}
private void PageBaseSizeChanged(object? sender, SizeChangedEventArgs e)
@@ -266,33 +274,28 @@ public class PageBase : UserControl
ec.ImplicitAnimations = ani;
}
//private void FrameNavigatingFrom(object sender, NavigatingCancelEventArgs e)
//private void FrameNavigatingFrom(object? sender, NavigatingCancelEventArgs e)
//{
// // If TargetType is not set, we know we're currently on a CoreControls page since those
// // are grouped pages - whereas, FA controls only display one control per page and
// // set all the extra properties
// bool isFAControlPage = TargetType != null;
// // Only setup the ConnectedAnimation if it makes sense
// if ((!isFAControlPage && e.SourcePageType == typeof(CoreControlsPageViewModel)) ||
// (isFAControlPage && e.SourcePageType == typeof(FAControlsOverviewPageViewModel)))
// if ((e.SourcePageType == typeof(HomePageViewModel)) ||
// (e.SourcePageType == typeof(PluginStorePageViewModel)))
// {
// // Only setup the Back connected animation if we're going back to the
// // controls list pages
// var svc = ConnectedAnimationService.GetForView(TopLevel.GetTopLevel(this));
// svc.PrepareToAnimate("BackAnimation", (Control)_previewImageHost.Parent);
// NavigationService.Instance.PreviousPage = this;
// //NavigationService.Instance.PreviousPage = this;
// }
//}
//private void FrameNavigatedTo(object sender, NavigationEventArgs e)
//private void FrameNavigatedTo(object? sender, NavigationEventArgs e)
//{
// var svc = ConnectedAnimationService.GetForView(TopLevel.GetTopLevel(this));
// var animation = svc.GetAnimation("ForwardAnimation");
// if (animation != null)
// {
// var coordinated = new List<Visual>
// var coordinated = new List<Visual?>
// {
// _optionsHost,
// _detailsHost,
@@ -315,6 +318,7 @@ public class PageBase : UserControl
//private IconSourceElement? _previewImageHost;
//private StackPanel? _detailsHost;
private ScrollViewer? _scroller;
private CompositeDisposable _disposables;
}
public class PageBase<TViewModel> : PageBase, IViewFor<TViewModel> where TViewModel : class
@@ -324,13 +328,14 @@ public class PageBase<TViewModel> : PageBase, IViewFor<TViewModel> where TViewMo
/// <summary>
/// Initializes a new instance of the <see cref="PageBase{TViewModel}"/> class.
/// </summary>
public PageBase()
public PageBase() : base()
{
// This WhenActivated block calls ViewModel's WhenActivated
// block if the ViewModel implements IActivatableViewModel.
this.WhenActivated(disposables =>
{
disposables.Add(UISettings.WindowBackgroundCustomImage.Subscribe(x => IsShowBackgroundImage = !x));
//disposables.Add(UISettings.WindowBackgroundCustomImage.Subscribe(x => IsShowBackgroundImage = !x));
});
this.GetObservable(ViewModelProperty).Subscribe(OnViewModelChanged);
}
@@ -25,6 +25,7 @@
<spp:Image2
Width="{Binding $parent[UserControl].Bounds.Width}"
Height="{Binding $parent[UserControl].Bounds.Height}"
EnableCancelToken="False"
Source="{Binding ImageUrl}"
Stretch="UniformToFill" />
</Button>
@@ -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}";
}
@@ -126,7 +126,7 @@ public class OptionsDisplayItem : TemplatedControl
{
base.OnApplyTemplate(e);
_layoutRoot = e.NameScope.Find<FABorder>("LayoutRoot");
_layoutRoot = e.NameScope.Find<Border>("LayoutRoot");
if (_layoutRoot != null)
{
_layoutRoot.PointerPressed += OnLayoutRootPointerPressed;
@@ -172,5 +172,5 @@ public class OptionsDisplayItem : TemplatedControl
private bool _isPressed;
private bool _isExpanded;
private FABorder? _layoutRoot;
private Border? _layoutRoot;
}
@@ -0,0 +1,47 @@
namespace BD.WTTS.UI.Views.Controls;
public class PureCircle : Control
{
public static readonly StyledProperty<double> DiameterProperty = AvaloniaProperty.Register<PureCircle, double>(
nameof(Diameter));
public double Diameter
{
get => GetValue(DiameterProperty);
set => SetValue(DiameterProperty, value);
}
public static readonly StyledProperty<IBrush?>
BackgroundProperty = Border.BackgroundProperty.AddOwner<PureCircle>();
public IBrush? Background
{
get => GetValue(BackgroundProperty);
set => SetValue(BackgroundProperty, value);
}
static PureCircle()
{
AffectsMeasure<PureCircle>(DiameterProperty);
AffectsArrange<PureCircle>(DiameterProperty);
AffectsRender<PureCircle>(BackgroundProperty, DiameterProperty);
}
protected override Size MeasureOverride(Size availableSize)
{
double diameter = Math.Max(0, Diameter);
return new Size(diameter, diameter);
}
protected override Size ArrangeOverride(Size finalSize)
{
double diameter = Math.Max(0, Diameter);
return new Size(diameter, diameter);
}
public override void Render(DrawingContext context)
{
double radius = Math.Max(0, Diameter) / 2;
context.DrawEllipse(Background, null, new(radius, radius), radius, radius);
}
}
@@ -0,0 +1,27 @@
namespace BD.WTTS.UI.Views.Controls;
/// <summary>
/// Provide the most simplified shape implementation. This is a rectangle with a background, without border and corner radius.
/// </summary>
public class PureRectangle : Control
{
public static readonly StyledProperty<IBrush?> BackgroundProperty =
Border.BackgroundProperty.AddOwner<PureRectangle>();
public IBrush? Background
{
get => GetValue(BackgroundProperty);
set => SetValue(BackgroundProperty, value);
}
static PureRectangle()
{
AffectsRender<PureRectangle>(BackgroundProperty);
}
public override void Render(DrawingContext context)
{
base.Render(context);
context.DrawRectangle(Background, null, new Rect(Bounds.Size));
}
}
@@ -0,0 +1,30 @@
namespace BD.WTTS.UI.Views.Controls;
public class PureRing : PureCircle
{
public static readonly StyledProperty<double> InnerDiameterProperty = AvaloniaProperty.Register<PureRing, double>(
nameof(InnerDiameter));
public double InnerDiameter
{
get => GetValue(InnerDiameterProperty);
set => SetValue(InnerDiameterProperty, value);
}
static PureRing()
{
AffectsRender<PureCircle>(InnerDiameterProperty);
}
public override void Render(DrawingContext context)
{
double outer = Diameter / 2;
double inner = InnerDiameter / 2;
double thickness = outer - inner;
context.DrawEllipse(null,
new Pen(Background, outer - inner),
new Point(outer, outer),
outer - thickness / 2,
outer - thickness / 2);
}
}
@@ -62,14 +62,13 @@
Margin="0,0,0,5"
HorizontalAlignment="Center"
VerticalAlignment="Bottom">
<ItemsRepeater Name="Swiper" IsVisible="False">
<ItemsRepeater.Layout>
<StackLayout
DisableVirtualization="True"
Orientation="Horizontal"
Spacing="8" />
</ItemsRepeater.Layout>
<ItemsRepeater.ItemTemplate>
<ItemsControl Name="Swiper" IsVisible="False">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" Spacing="8" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<DataTemplate.DataType>
<x:Type x:TypeArguments="x:Int32, x:String" TypeName="generic:KeyValuePair" />
@@ -110,8 +109,8 @@
</i:Interaction.Behaviors>-->
</Button>
</DataTemplate>
</ItemsRepeater.ItemTemplate>
</ItemsRepeater>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Panel>
</Panel>
</Border>
@@ -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.
@@ -85,8 +85,8 @@ namespace BD.WTTS.UI.Views.Controls
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];
@@ -96,7 +96,7 @@ namespace BD.WTTS.UI.Views.Controls
.Subscribe(_ => SwipersLoad());
CarouselControl.GetObservable(Carousel.SelectedIndexProperty)
.Subscribe(x => SwipersLoad());
.Subscribe(_ => SwipersLoad());
this.GetObservable(AutoScrollProperty)
.Subscribe(x =>
@@ -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;
ItemsControl? _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<Avalonia.Controls.ItemsControl>("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);
}
@@ -0,0 +1,22 @@
namespace BD.WTTS.UI.Views.Controls;
public class Skeleton : ContentControl
{
public static readonly StyledProperty<bool> IsActiveProperty =
AvaloniaProperty.Register<Skeleton, bool>(nameof(IsActive));
public bool IsActive
{
get { return GetValue(IsActiveProperty); }
set { SetValue(IsActiveProperty, value); }
}
public static readonly StyledProperty<bool> IsLoadingProperty =
AvaloniaProperty.Register<Skeleton, bool>(nameof(IsLoading));
public bool IsLoading
{
get => GetValue(IsLoadingProperty);
set => SetValue(IsLoadingProperty, value);
}
}
@@ -0,0 +1,141 @@
<UserControl
x:Class="BD.WTTS.UI.Views.Controls.WaveProgress"
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:spp="https://steampp.net/ui"
d:DesignHeight="450"
d:DesignWidth="800"
mc:Ignorable="d">
<UserControl.Styles>
<Style Selector="spp|WaveProgress">
<Setter Property="Template">
<ControlTemplate>
<Border
Canvas.Left="20"
Canvas.Top="20"
Canvas.Right="20"
Canvas.Bottom="20"
Width="200"
Height="200"
Margin="20"
Background="Transparent"
BorderBrush="{DynamicResource ControlElevationBorderBrush}"
BorderThickness="1.2"
BoxShadow="{DynamicResource LowShadow}"
ClipToBounds="False"
CornerRadius="100">
<Border.Resources>
<spp:WaveProgressValueConverter x:Key="WValueConvert" />
<spp:WaveProgressValueTextConverter x:Key="WValueTextConvert" />
<spp:WaveProgressValueColorConverter x:Key="WValueColorConvert" />
<spp:WaveProgressGradientOffsetConverter x:Key="WGradientConverter" />
</Border.Resources>
<Border
Width="200"
Height="200"
HorizontalAlignment="Center"
VerticalAlignment="Center"
ClipToBounds="True"
CornerRadius="100">
<Panel>
<Canvas Width="200" Height="200">
<Path
Canvas.Top="{TemplateBinding Value,
Converter={StaticResource WValueConvert}}"
Classes="WaveAnimatedFast"
Data="M 0,50 H 700 V 350 H 0 M 0,50 C 25,55 75,45 100,50 S 175,55 200,50 S 275,45 300,50 S 375,55 400,50 S 475,45 500,50 S 575,55 600,50 S 675,45 700,50"
Fill="{TemplateBinding Value,
Converter={StaticResource WGradientConverter}}"
Opacity="0.45"
StrokeThickness="0">
<Path.Transitions>
<Transitions>
<DoubleTransition Property="Canvas.Top" Duration="0:0:1.5">
<DoubleTransition.Easing>
<CircularEaseOut />
</DoubleTransition.Easing>
</DoubleTransition>
</Transitions>
</Path.Transitions>
</Path>
<Path
Canvas.Top="{TemplateBinding Value,
Converter={StaticResource WValueConvert}}"
Classes="WaveAnimated"
Data="M 0,50 H 700 V 350 H 0 M 0,50 C 25,55 75,45 100,50 S 175,55 200,50 S 275,45 300,50 S 375,55 400,50 S 475,45 500,50 S 575,55 600,50 S 675,45 700,50"
Fill="{TemplateBinding Value,
Converter={StaticResource WGradientConverter}}"
Opacity="0.8"
StrokeThickness="0">
<Path.Transitions>
<Transitions>
<DoubleTransition Property="Canvas.Top" Duration="0:0:2.5">
<DoubleTransition.Easing>
<CircularEaseOut />
</DoubleTransition.Easing>
</DoubleTransition>
</Transitions>
</Path.Transitions>
</Path>
</Canvas>
<TextBlock
Name="PART_Text"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Foreground="{TemplateBinding Value,
Converter={StaticResource WValueColorConvert}}"
Text="{TemplateBinding Value,
Converter={StaticResource WValueTextConvert}}"
Theme="{StaticResource TitleTextBlockStyle}">
<TextBlock.Transitions>
<Transitions>
<DoubleTransition Property="Opacity" Duration="0:0:0.3" />
</Transitions>
</TextBlock.Transitions>
</TextBlock>
</Panel>
</Border>
</Border>
</ControlTemplate>
</Setter>
</Style>
<Style Selector="spp|WaveProgress[IsTextVisible=True] /template/ TextBlock#PART_Text">
<Setter Property="Opacity" Value="1" />
</Style>
<Style Selector="spp|WaveProgress[IsTextVisible=False] /template/ TextBlock#PART_Text">
<Setter Property="Opacity" Value="0" />
</Style>
<Style Selector="Path.WaveAnimated">
<Style.Animations>
<Animation IterationCount="INFINITE" Duration="0:0:6">
<KeyFrame Cue="0%">
<Setter Property="Canvas.Left" Value="-100" />
</KeyFrame>
<KeyFrame Cue="100%">
<Setter Property="Canvas.Left" Value="-500" />
</KeyFrame>
</Animation>
</Style.Animations>
</Style>
<Style Selector="Path.WaveAnimatedFast">
<Style.Animations>
<Animation IterationCount="INFINITE" Duration="0:0:3">
<KeyFrame Cue="0%">
<Setter Property="Canvas.Left" Value="-100" />
</KeyFrame>
<KeyFrame Cue="100%">
<Setter Property="Canvas.Left" Value="-500" />
</KeyFrame>
</Animation>
</Style.Animations>
</Style>
</UserControl.Styles>
</UserControl>
@@ -0,0 +1,63 @@
namespace BD.WTTS.UI.Views.Controls;
public sealed partial class WaveProgress : UserControl
{
public static readonly DirectProperty<WaveProgress, double> ValueProperty =
AvaloniaProperty.RegisterDirect<WaveProgress, double>(
nameof(Value),
o => o.Value,
(o, v) => o.Value = v,
defaultBindingMode: BindingMode.TwoWay,
enableDataValidation: true);
public static readonly StyledProperty<bool> IsTextVisibleProperty =
AvaloniaProperty.Register<WaveProgress, bool>(nameof(IsTextVisible), defaultValue: true);
public WaveProgress()
{
InitializeComponent();
//var theme = App.Current.thm;
//theme.OnBaseThemeChanged += _ =>
//{
// Value++;
// Value--;
//};
//theme.OnColorThemeChanged += _ =>
//{
// Value++;
// Value--;
//};
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
private double _value = 65;
public double Value
{
get => _value;
set
{
if (value is < 0 or > 100)
return;
SetAndRaise(ValueProperty, ref _value, value);
}
}
public bool IsTextVisible
{
get
{
return GetValue(IsTextVisibleProperty);
}
set
{
SetValue(IsTextVisibleProperty, value);
}
}
}
@@ -139,20 +139,24 @@
VerticalAlignment="Center"
Foreground="{DynamicResource TextFillColorPrimaryBrush}"
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" />
</ItemsRepeater.Layout>
<ItemsRepeater.ItemTemplate>
<ItemsControl Margin="0,0,6,6" ItemsSource="{Binding Hyperlinks, Mode=OneTime}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate x:DataType="spp:HyperlinkModel">
<Button
Command="{Binding Command, Mode=OneWay}"
Content="{Binding Text, Mode=OneWay}"
Cursor="Hand"
Theme="{StaticResource HyperlinkButton}" />
<Border MinWidth="166">
<Button
Command="{Binding Command, Mode=OneWay}"
Content="{Binding Text, Mode=OneWay}"
Cursor="Hand"
Theme="{StaticResource HyperlinkButton}" />
</Border>
</DataTemplate>
</ItemsRepeater.ItemTemplate>
</ItemsRepeater>
</ItemsControl.ItemTemplate>
</ItemsControl>
<Border
Height="1"
Margin="-50,0"
@@ -162,20 +166,24 @@
VerticalAlignment="Center"
Foreground="{DynamicResource TextFillColorPrimaryBrush}"
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" />
</ItemsRepeater.Layout>
<ItemsRepeater.ItemTemplate>
<ItemsControl Margin="0,0,6,6" ItemsSource="{Binding OSL, Mode=OneTime}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate x:DataType="spp:HyperlinkModel">
<Button
Command="{Binding Command, Mode=OneWay}"
Content="{Binding Text, Mode=OneWay}"
Cursor="Hand"
Theme="{StaticResource HyperlinkButton}" />
<Border MinWidth="290">
<Button
Command="{Binding Command, Mode=OneWay}"
Content="{Binding Text, Mode=OneWay}"
Cursor="Hand"
Theme="{StaticResource HyperlinkButton}" />
</Border>
</DataTemplate>
</ItemsRepeater.ItemTemplate>
</ItemsRepeater>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</spp:OptionsDisplayItem.Content>
</spp:OptionsDisplayItem>
@@ -8,7 +8,8 @@ public partial class AboutPage : ReactiveUserControl<AboutPageViewModel>
public AboutPage()
{
InitializeComponent();
this.SetViewModel<AboutPageViewModel>(false);
DataContext = new AboutPageViewModel();
this.SetViewModel<AboutPageViewModel>();
}
/// <summary>
@@ -12,6 +12,7 @@
d:DesignWidth="800"
x:CompileBindings="True"
x:DataType="spp:DebugPageViewModel"
ScrollViewer.VerticalScrollBarVisibility="Disabled"
mc:Ignorable="d">
<Design.DataContext>
<spp:DebugPageViewModel />
@@ -21,6 +22,7 @@
</spp:PageBase.PreviewImage>
<spp:PageBase.TabContent>
<TabStrip
Name="TabSelect"
Margin="4"
HorizontalAlignment="Right"
VerticalAlignment="Top"
@@ -110,19 +112,91 @@
</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>
<StackPanel>
<ToggleSwitch Name="loading" Content="Loading" />
<ToggleSwitch Name="active" Content="Active" />
<Grid
MinWidth="400"
HorizontalAlignment="Left"
ColumnDefinitions="auto,*">
<spp:Skeleton
Width="100"
Height="100"
VerticalAlignment="Center"
CornerRadius="2"
IsActive="{Binding #active.IsChecked}"
IsLoading="{Binding #loading.IsChecked}">
<Image RenderOptions.BitmapInterpolationMode="HighQuality" Source="avares://BD.WTTS.Client.Avalonia/UI/Assets/ApplicationIcon.ico" />
</spp:Skeleton>
<UniformGrid
Grid.Column="1"
Margin="5,0"
Columns="1"
Rows="3">
<spp:Skeleton
Margin="0,0,200,0"
VerticalAlignment="Top"
CornerRadius="2"
IsActive="{Binding #active.IsChecked}"
IsLoading="{Binding #loading.IsChecked}">
<SelectableTextBlock Margin="0,2" Text="不忘初心,方得始终" />
</spp:Skeleton>
<spp:Skeleton
Margin="0,0,100,0"
VerticalAlignment="Center"
CornerRadius="2"
IsActive="{Binding #active.IsChecked}"
IsLoading="{Binding #loading.IsChecked}">
<SelectableTextBlock Margin="0,2" Text="---" />
</spp:Skeleton>
<spp:Skeleton
VerticalAlignment="Bottom"
CornerRadius="2"
IsActive="{Binding #active.IsChecked}"
IsLoading="{Binding #loading.IsChecked}">
<SelectableTextBlock Margin="0,2" Text="路漫漫其修远兮,吾将上下而求索。" />
</spp:Skeleton>
</UniformGrid>
</Grid>
</StackPanel>
</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) =>
{
@@ -9,7 +9,7 @@
d:DesignWidth="800"
mc:Ignorable="d">
<Panel>
<Image Source="/UI/Assets/back.png" Stretch="UniformToFill" />
<!--<Image Source="/UI/Assets/back.png" Stretch="UniformToFill" />-->
<!--<TextBlock
HorizontalAlignment="Center"
VerticalAlignment="Center"
@@ -150,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>
@@ -229,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
@@ -270,7 +273,71 @@
Theme="{StaticResource HyperlinkButton}" />
</DockPanel>
<spp:CarouselBanner
<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"
@@ -354,7 +421,8 @@
ContentTemplate="{StaticResource ShopItem}" />
</Grid>
</DataTemplate>
<!--<DataTemplate>
-->
<!--<DataTemplate>
<DataTemplate.DataType>
<x:Type x:TypeArguments="spp:ShopRecommendGoodItem" TypeName="col:IEnumerable" />
</DataTemplate.DataType>
@@ -366,8 +434,9 @@
</ItemsControl.ItemsPanel>
</ItemsControl>
</DataTemplate>-->
<!--
</spp:CarouselBanner.ItemTemplate>
</spp:CarouselBanner>
</spp:CarouselBanner>-->
<!--<ScrollViewer
Name="ShopsScrollViewer"
@@ -7,7 +7,7 @@ public partial class HomePage : ReactiveUserControl<HomePageViewModel>
public HomePage()
{
InitializeComponent();
this.SetViewModel<HomePageViewModel>(true);
this.SetViewModel<HomePageViewModel>(useCache: true);
//ShopsScrollViewer.PointerWheelChanged += ShopsScrollViewer_PointerWheelChanged;
}
@@ -24,10 +24,10 @@
</Style>
<Style Selector="ui|NavigationView.AppNav[IsBackButtonVisible=False] SplitView /template/ ContentPresenter#PART_PanePresenter">
<Setter Property="Margin" Value="0 50 0 0" />
<Setter Property="Margin" Value="0 42 0 0" />
</Style>
<Style Selector="ui|NavigationView.AppNav[IsBackButtonVisible=True] SplitView /template/ ContentPresenter#PART_PanePresenter">
<Setter Property="Margin" Value="0 10 0 0" />
<Setter Property="Margin" Value="0 2 0 0" />
</Style>
<Style Selector="ui|NavigationViewItem.AppNav">
@@ -216,9 +216,22 @@
<StackPanel VerticalAlignment="Center" IsVisible="{Binding Path=User, Mode=OneWay, Source={x:Static s:UserService.Current}, Converter={StaticResource IsNullConverter}, ConverterParameter=invert}">
<TextBlock Text="{Binding Path=User.NickName, Mode=OneWay, Source={x:Static s:UserService.Current}}" />
<Border VerticalAlignment="Center" IsVisible="{Binding Path=User.UserType, Mode=OneWay, Source={x:Static s:UserService.Current}, Converter={StaticResource EnumEqualValueConverter}, ConverterParameter={x:Static enum:UserType.Sponsor}}">
<TextBlock FontSize="10" Text="{Binding Path=Res.User_Sponsor, Mode=OneWay, Source={x:Static s:ResourceService.Current}, StringFormat={}❤️{0}}" />
<Border
Padding="5,1"
VerticalAlignment="Center"
CornerRadius="2"
IsVisible="{Binding Path=User.UserType, Mode=OneWay, Source={x:Static s:UserService.Current}, Converter={StaticResource EnumEqualValueConverter}, ConverterParameter={x:Static enum:UserType.Sponsor}}">
<Border.Background>
<LinearGradientBrush StartPoint="0%,0%" EndPoint="100%,100%">
<GradientStop Offset="0.0" Color="#453a94" />
<GradientStop Offset="1.0" Color="#f43b47" />
</LinearGradientBrush>
</Border.Background>
<TextBlock
VerticalAlignment="Center"
FontSize="10"
Foreground="White"
Text="{Binding Path=Res.User_Sponsor, Mode=OneWay, Source={x:Static s:ResourceService.Current}, StringFormat={}❤️{0}}" />
</Border>
</StackPanel>
@@ -263,18 +276,18 @@
</MenuItem.Header>
</MenuItem>
<MenuItem Header="-" IsVisible="{Binding Path=User, Mode=OneWay, Source={x:Static s:UserService.Current}, Converter={StaticResource IsNullConverter}, ConverterParameter=invert}" />
<MenuItem
<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
<!--<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
<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}}"
@@ -318,7 +331,7 @@
PaneDisplayMode="Left">
<ui:NavigationView.Resources>
<!-- Override this -->
<Thickness x:Key="NavigationViewContentMargin">0,60,0,0</Thickness>
<Thickness x:Key="NavigationViewContentMargin">0,50,0,0</Thickness>
</ui:NavigationView.Resources>
<ui:NavigationView.Styles>
<Style Selector="Button#NavigationViewBackButton">
@@ -329,7 +342,7 @@
<Setter Property="Content" Value="&#xF0D5;" />
</Style>
<Style Selector="Grid#PaneToggleButtonGrid">
<Setter Property="Margin" Value="5 5 0 0" />
<Setter Property="Margin" Value="5 0 0 0" />
<Setter Property="ToolTip.Tip" Value="{Binding Path=Res.Back, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
<Setter Property="ToolTip.Placement" Value="Bottom" />
</Style>
@@ -0,0 +1,88 @@
<UserControl
x:Class="BD.WTTS.UI.Views.Pages.PluginStorePage"
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:s="https://steampp.net/services"
xmlns:spp="https://steampp.net/ui"
xmlns:ui="using:FluentAvalonia.UI.Controls"
d:DesignHeight="450"
d:DesignWidth="800"
x:CompileBindings="True"
x:DataType="spp:PluginStorePageViewModel"
mc:Ignorable="d">
<ScrollViewer Padding="20">
<!--<spp:ContentLoader IsLoading="False">-->
<StackPanel
x:Name="HomeRoot"
MaxWidth="1080"
Spacing="20">
<TextBlock
Margin="-3,0,0,0"
Text="插件商店是什么?"
Theme="{StaticResource TitleTextBlockStyle}" />
<TextBlock Text="3.0 版本的计划是将所有功能插件化,拆分后按需下载安装,每个人都可以只下载自己需要的插件功能使用,并且支持所有人都可以开发插件,等等还有许多暂时不方便说的内容,因为计划的过于异想天开,且遇到的问题有点多,所以开发难度有点点大,这个功能上线还需要一点时间,上线后 3.0 也将正式发布正式版,对一直等待的大家说声抱歉麻烦在多等等了 😭,如果您不习惯启动后是插件商店页面也可以在界面设置中修改启动默认页。" TextWrapping="WrapWithOverflow" />
<TextBlock
Margin="-3,0,0,0"
Text="已安装功能"
Theme="{StaticResource TitleTextBlockStyle}" />
<ItemsControl ItemsSource="{Binding MainWindow2.TabItems, Mode=OneWay, Source={x:Static s:IViewModelManager.Instance}}">
<ItemsControl.Resources>
<ControlTheme
x:Key="NavButtonStyle"
BasedOn="{StaticResource {x:Type Button}}"
TargetType="Button">
<Setter Property="Width" Value="120" />
<Setter Property="Padding" Value="0" />
<Setter Property="Margin" Value="6" />
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Setter Property="VerticalContentAlignment" Value="Stretch" />
<Style Selector="^ /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="ClipToBounds" Value="True" />
</Style>
</ControlTheme>
</ItemsControl.Resources>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate DataType="spp:TabItemViewModel">
<Border spp:Animations.EnableAnimations="True">
<Button
Command="{Binding $parent[spp:PluginStorePage].((spp:PluginStorePageViewModel)DataContext).NavgationToMenuPageCommand}"
CommandParameter="{Binding}"
Theme="{StaticResource NavButtonStyle}">
<Grid RowDefinitions="3*,*">
<Border
Name="IconHost"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
CornerRadius="4 4 0 0"
RenderOptions.BitmapInterpolationMode="HighQuality">
<spp:Image2
Width="96"
Height="96"
Margin="0,10,0,0"
Source="{Binding IconKey}"
Stretch="Uniform" />
</Border>
<TextBlock
Grid.Row="1"
Margin="12"
FontWeight="SemiBold"
Text="{Binding Name}" />
</Grid>
</Button>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</ScrollViewer>
</UserControl>
@@ -0,0 +1,92 @@
using FluentAvalonia.UI.Controls.Experimental;
using FluentAvalonia.UI.Navigation;
namespace BD.WTTS.UI.Views.Pages;
public partial class PluginStorePage : UserControl
{
public PluginStorePage()
{
InitializeComponent();
this.SetViewModel<PluginStorePageViewModel>(useCache: true);
// Use the frame events here to ensure ConnectedAnimations still work with
// Back/Forward navigation and not just explicit page invokes
//AddHandler(Frame.NavigatingFromEvent, OnNavigatingFrom, RoutingStrategies.Direct);
//AddHandler(Frame.NavigatedToEvent, OnNavigatedTo, RoutingStrategies.Direct);
}
//private void PluginStorePage_Click(object? sender, RoutedEventArgs e)
//{
// if (sender != null && sender is Button b && b.DataContext is TabItemViewModel fci)
// {
// _animationPage = fci;
// //NavigationService.Instance.NavigateFromContext(fci);
// }
//}
//private void OnNavigatedTo(object? sender, NavigationEventArgs e)
//{
// if (_animationPage == null)
// return;
// var svc = ConnectedAnimationService.GetForView(TopLevel.GetTopLevel(this));
// var anim = svc.GetAnimation("BackAnimation");
// if (anim == null)
// return;
// var item = this.GetVisualDescendants()
// ?.Where(x => x is Button && x.DataContext == _animationPage)
// ?.FirstOrDefault()
// ?.GetVisualDescendants()
// .Where(x => x is Border && x.Name == "IconHost")
// .FirstOrDefault();
// var presenter = item;// GetAnimationSource();
// // In WinUI, ConnectedAnimation is somehow exempt from all clipping behaviors
// // Here, we are not, so disable ClipToBounds on all elements in the SettingsExpander
// // The rest are taken care of in the xaml.
// // NOTE: The ScrollViewer is not changed here as that's important for scrolling - thus
// // the animation will be cut off, but the back animation is pretty fast and mostly is
// // only visible closer to the element so we're ok, I think
// var x = presenter?.GetVisualParent();
// while (!(x is ScrollContentPresenter) && x != null)
// {
// x.ClipToBounds = false;
// x = x.GetVisualParent();
// }
// anim.Configuration = new DirectConnectedAnimationConfiguration();
// anim.TryStart(presenter);
//}
//private void OnNavigatingFrom(object? sender, NavigatingCancelEventArgs e)
//{
// if (_animationPage == null)
// return;
// // We're not navigating to a control page, don't set up the animation & clear
// // the previous animation source
// if (!e.SourcePageType.Name.Equals(nameof(TabItemViewModel)))
// {
// _animationPage = null;
// _animationPage = null;
// return;
// }
// var item = this.GetVisualDescendants()
// ?.Where(x => x is ListBoxItem && x.DataContext == _animationPage)
// ?.FirstOrDefault()
// ?.GetVisualDescendants()
// .Where(x => x is Viewbox && x.Name == "IconHost")
// .FirstOrDefault();
// var svc = ConnectedAnimationService.GetForView(TopLevel.GetTopLevel(this));
// svc.PrepareToAnimate("ForwardAnimation", item);
//}
//private TabItemViewModel? _animationPage;
}
@@ -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>
@@ -22,6 +22,23 @@
Text="{Binding Path=Res.Settings_UI, Mode=OneWay, Source={x:Static s:ResourceService.Current}}"
Theme="{StaticResource SubtitleTextBlockStyle}" />
<Border>
<ui:SettingsExpander Header="{Binding Path=Res.Settings_UI_StartDefaultPage, Mode=OneWay, Source={x:Static s:ResourceService.Current}}">
<ui:SettingsExpander.IconSource>
<ui:FontIconSource FontFamily="{StaticResource SymbolThemeFontFamily}" Glyph="{StaticResource Device}" />
</ui:SettingsExpander.IconSource>
<ui:SettingsExpander.Footer>
<ComboBox ItemsSource="{Binding Path=MainWindow2.TabItems, Mode=OneWay, Source={x:Static s:IViewModelManager.Instance}}" SelectedItem="{Binding SelectDefaultPage, Mode=TwoWay}">
<ComboBox.ItemTemplate>
<DataTemplate DataType="spp:TabItemViewModel">
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</ui:SettingsExpander.Footer>
</ui:SettingsExpander>
</Border>
<Border IsEnabled="{Binding Path=User, Mode=OneWay, Source={x:Static s:UserService.Current}, Converter={StaticResource IsNullConverter}, ConverterParameter=invert}">
<ui:SettingsExpander
Description="{Binding Path=Res.Advertisement_NoShow_Tip, Mode=OneWay, Source={x:Static s:ResourceService.Current}}"
@@ -172,13 +172,13 @@
<WrapPanel Margin="0,20" HorizontalAlignment="Center">
<TextBlock Text="{Binding Path=Res.User_LoginUnexpectedPrompt, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" Theme="{StaticResource CaptionTextBlockStyle}" />
<ui:HyperlinkButton
<HyperlinkButton
Margin="5,0"
Padding="0"
HorizontalContentAlignment="Center"
Command="{Binding ManualLogin}">
<TextBlock Text="{Binding Path=Res.Login_ManualLogin, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" Theme="{StaticResource CaptionTextBlockStyle}" />
</ui:HyperlinkButton>
</HyperlinkButton>
</WrapPanel>
<ProgressBar
@@ -222,23 +222,23 @@
FontSize="12"
Text="{Binding Path=Res.User_RegisterAgreed, Mode=OneWay, Source={x:Static s:ResourceService.Current}}"
Theme="{StaticResource CaptionTextBlockStyle}" />
<ui:HyperlinkButton
<HyperlinkButton
Margin="5,0"
Padding="0"
Command="{Binding OpenHyperlink}"
CommandParameter="Agreement"
Cursor="Hand">
<TextBlock Text="{Binding Path=Res.User_Agreement, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" Theme="{StaticResource CaptionTextBlockStyle}" />
</ui:HyperlinkButton>
</HyperlinkButton>
<TextBlock Text="{Binding Path=Res.And, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" Theme="{StaticResource CaptionTextBlockStyle}" />
<ui:HyperlinkButton
<HyperlinkButton
Margin="5,0"
Padding="0"
Command="{Binding OpenHyperlink}"
CommandParameter="Privacy"
Cursor="Hand">
<TextBlock Text="{Binding Path=Res.User_Privacy, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" Theme="{StaticResource CaptionTextBlockStyle}" />
</ui:HyperlinkButton>
</HyperlinkButton>
</WrapPanel>
<StackPanel
@@ -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,20 @@ public partial class LoginOrRegisterPage : ReactiveUserControl<LoginOrRegisterWi
if (UserService.Current.IsAuthenticated)
{
Toast.Show(ToastIcon.Info, "当前已是登录状态");
this.ViewModel.Close();
this.ViewModel.Close(false);
}
}
// 11.1 使用新 API https://docs.avaloniaui.net/docs/concepts/services/activatable-lifetime#handling-uri-activation
//if (Application.Current.TryGetFeature<IActivatableLifetime>() is { } activatableLifetime)
//{
// activatableLifetime.Activated += (s, a) =>
// {
// if (a is ProtocolActivatedEventArgs protocolArgs && protocolArgs.Kind == ActivationKind.OpenUri)
// {
// Console.WriteLine($"App activated via Uri: {protocolArgs.Uri}");
// }
// };
//}
Application.Current!.UrlsOpened += Current_UrlsOpened;
}
@@ -54,8 +65,8 @@ public partial class LoginOrRegisterPage : ReactiveUserControl<LoginOrRegisterWi
if (loginUrl != null)
{
//var token = loginUrl.TrimStart(Constants.UrlSchemes.Login);
var token = loginUrl.Substring(Constants.UrlSchemes.Login.Length, loginUrl.Length - Constants.UrlSchemes.Login.Length);
await ThirdPartyLoginHelper.OnMessageAsync(token);
var token = loginUrl[Constants.UrlSchemes.Login.Length..];
await ThirdPartyLoginHelper.LoginForStr(token);
}
}
@@ -37,7 +37,36 @@ public sealed partial class MainWindow : ReactiveAppWindow<MainWindowViewModel>
}
}
protected override void OnOpened(EventArgs e)
DateTime lastOpenedTime;
internal bool SetStartDefaultPageName()
{
if (NavigationService.Instance.CurrnetPage != null)
return false;
var mw2 = IViewModelManager.Instance.MainWindow2;
if (mw2 == null)
return false;
Type? pageType = null;
if (!string.IsNullOrEmpty(UISettings.StartDefaultPageName.Value))
{
var tabItems = mw2?.TabItems;
if (tabItems == null)
return false;
var page = tabItems.OfType<MenuTabItemViewModel>()
.FirstOrDefault(s => s.Id == UISettings.StartDefaultPageName.Value);
if (page != null)
{
pageType = page.PageType;
}
}
INavigationService.Instance.Navigate(pageType ?? typeof(PluginStorePage));
return true;
}
protected override async void OnOpened(EventArgs e)
{
base.OnOpened(e);
if (Steamworks.SteamClient.IsValid)
@@ -46,24 +75,23 @@ public sealed partial class MainWindow : ReactiveAppWindow<MainWindowViewModel>
}
if (AppSplashScreen.IsInitialized)
{
if (NavigationService.Instance.CurrnetPage == null)
{
INavigationService.Instance.Navigate(typeof(HomePage));
}
Task2.InBackground(async () =>
{
IViewModelManager.Instance.Get<HomePageViewModel>().GetServerContent();
await AdvertiseService.Current.RefrshAdvertiseAsync();
NoticeService.Current.GetNewsAsync();
});
try // 在主窗口显示时调用此函数检查是否需要显示新版本通知窗口
{
Ioc.Get_Nullable<IAppUpdateService>()?.OnMainOpenTryShowNewVersionWindow();
}
catch
{
SetStartDefaultPageName();
}
if (lastOpenedTime == default ||
(DateTime.Now - lastOpenedTime) > TimeSpan.FromMinutes(30))
{
lastOpenedTime = DateTime.Now;
await IViewModelManager.Instance.Get<HomePageViewModel>().GetServerContent();
await AdvertiseService.Current.RefrshAdvertiseAsync();
await NoticeService.Current.GetNewsAsync();
}
try // 在主窗口显示时调用此函数检查是否需要显示新版本通知窗口
{
Ioc.Get_Nullable<IAppUpdateService>()?.OnMainOpenTryShowNewVersionWindow();
}
catch
{
}
}
}
}
@@ -72,7 +100,7 @@ public sealed class AppSplashScreen : IApplicationSplashScreen
{
public static bool IsInitialized = false;
public WindowViewModel? ViewModel { get; }
//public WindowViewModel? ViewModel { get; }
public string? AppName { get; }
@@ -162,7 +190,7 @@ public sealed class AppSplashScreen : IApplicationSplashScreen
});
AdvertiseService.Current.InitAdvertise();
NoticeService.Current.GetNewsAsync();
await NoticeService.Current.GetNewsAsync();
var mainWindow = App.Instance.MainWindow;
mainWindow.ThrowIsNull();
@@ -170,13 +198,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")
{
@@ -187,7 +216,7 @@ public sealed class AppSplashScreen : IApplicationSplashScreen
#endif
new MenuTabItemViewModel("Plugin_Store")
{
PageType = null,
PageType = typeof(PluginStorePage),
IsResourceGet = true,
IconKey = "avares://BD.WTTS.Client.Avalonia/UI/Assets/Icons/store.ico",
},
@@ -204,8 +233,10 @@ public sealed class AppSplashScreen : IApplicationSplashScreen
{
mainWindow.DataContext = IViewModelManager.Instance.MainWindow;
s.InitSettingSubscribe();
INavigationService.Instance.Navigate(typeof(HomePage));
if (mainWindow is MainWindow mainWindow1)
{
mainWindow1.SetStartDefaultPageName();
}
});
#if STARTUP_WATCH_TRACE || DEBUG
WatchTrace.Record("InitMainWindowViewModel");
@@ -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);
}
@@ -123,6 +123,7 @@ public static class KestrelServerOptionsExtensions
var httpsPort = IReverseProxyConfig.HttpsPort;
options.Listen(IReverseProxyService.Constants.Instance.ProxyIp, httpsPort, listen =>
{
listen.Protocols = HttpProtocols.Http1AndHttp2;
listen.UseFlowAnalyze();
listen.UseTls();
});
@@ -155,7 +155,21 @@ public static class CertGenerator
}
else
{
builder.AddDnsName(name);
try
{
builder.AddDnsName(name);
}
catch
{
/* name = Environment.MachineName
* System.ArgumentException: Decoded string is not a valid IDN name. (Parameter 'unicode')
* at System.Globalization.IdnMapping.IcuGetAsciiCore(String unicodeString, Char* unicode, Int32 count)
* at System.Globalization.IdnMapping.GetAscii(String unicode, Int32 index, Int32 count)
* at System.Security.Cryptography.X509Certificates.SubjectAlternativeNameBuilder.AddDnsName(String dnsName)
* at BD.WTTS.Services.Implementation.CertGenerator.Add(SubjectAlternativeNameBuilder builder, String name)
* at BD.WTTS.Services.Implementation.CertGenerator.CreateEndCertificate(X509Certificate2 issuerCertificate, X500DistinguishedName subjectName, IEnumerable`1 extraDnsNames, Nullable`1 notBefore, Nullable`1 notAfter, Int32 rsaKeySizeInBits)
*/
}
}
}
@@ -450,4 +450,36 @@ sealed partial class CertificateManagerImpl : ICertificateManager
var result = ICertificateManager.Constants.IsCertificateInstalled(platformService, packable);
return result;
}
public string GetCertificateInfo()
{
var rootCert = RootCertificate;
if (rootCert == null)
{
GenerateCertificate();
}
rootCert = RootCertificate;
if (rootCert == null)
{
return "";
}
StringBuilder b = new();
b.AppendLine("Subject:");
b.AppendLine(rootCert.Subject);
b.AppendLine("SerialNumber:");
b.AppendLine(rootCert.SerialNumber);
b.AppendLine("PeriodValidity:");
b.Append(rootCert.GetEffectiveDateString());
b.Append(" ~ ");
b.Append(rootCert.GetExpirationDateString());
b.AppendLine();
b.AppendLine("SHA256:");
b.AppendLine(rootCert.GetCertHashStringCompat(HashAlgorithmName.SHA256));
b.AppendLine("SHA1:");
b.AppendLine(rootCert.GetCertHashStringCompat(HashAlgorithmName.SHA1));
return b.ToString();
}
}
@@ -46,4 +46,9 @@ sealed class LazyCertificateManager : ICertificateManager
{
return impl().DeleteRootCertificate();
}
public string GetCertificateInfo()
{
return impl().GetCertificateInfo();
}
}
@@ -32,7 +32,7 @@ sealed class ReverseProxyHttpClientHandler : DelegatingHandler
var tlsSniValue = domainConfig.GetTlsSniPattern().WithDomain(uri.Host).WithRandom();
request.SetRequestContext(new RequestContext(isHttps, tlsSniValue));
// 设置请求头 host,修改协议为 http
// 设置请求头 host,因为连接中已带端口号故 修改协议为 http
request.Headers.Host = uri.Host;
request.RequestUri = new UriBuilder(uri) { Scheme = Uri.UriSchemeHttp }.Uri;
@@ -80,7 +80,15 @@ sealed partial class HttpReverseProxyMiddleware
return;
}
var destinationPrefix = GetDestinationPrefix(context.Request.Scheme, context.Request.Host, domainConfig.Destination);
var destination = domainConfig.Destination;
if (domainConfig.Destination?.AbsoluteUri.Contains("@") == true)
{
var newUrl = domainConfig.Destination.AbsoluteUri.Replace("@domain", context.Request.Host.Host);
newUrl = newUrl.Replace("@uri", context.Request.RawUrl());
destination = new Uri(newUrl);
}
var destinationPrefix = GetDestinationPrefix(context.Request.Scheme, context.Request.Host, destination);
var httpClient = httpClientFactory.CreateHttpClient(context.Request.Host.Host, domainConfig);
if (!string.IsNullOrEmpty(domainConfig.UserAgent))
{
@@ -89,9 +97,19 @@ sealed partial class HttpReverseProxyMiddleware
var forwarderRequestConfig = new ForwarderRequestConfig()
{
Version = context.Request.Protocol.StartsWith("HTTP/2") ? System.Net.HttpVersion.Version20 : System.Net.HttpVersion.Version11,
Version = context.Request.Protocol switch
{
var protocol when protocol.StartsWith("HTTP/2") => System.Net.HttpVersion.Version20,
var protocol when protocol.StartsWith("HTTP/3") => System.Net.HttpVersion.Version30,
_ => System.Net.HttpVersion.Version11,
},
};
if (domainConfig.IsServerSideProxy)
{
SetWattHeaders(context, reverseProxyConfig.Service.ServerSideProxyToken);
}
var error = await httpForwarder.SendAsync(context, destinationPrefix, httpClient, forwarderRequestConfig, HttpTransformer.Empty);
if (error != ForwarderError.None)
@@ -393,6 +411,15 @@ sealed partial class HttpReverseProxyMiddleware
return false;
}
static void SetWattHeaders(HttpContext context, string? token)
{
context.Request.Headers.TryAdd("X-Watt-Origin-Dest-Scheme", context.Request.Scheme);
context.Request.Headers.TryAdd("X-Watt-Origin-Dest-Host", context.Request.Host.ToString());
context.Request.Headers.TryAdd("X-Watt-Origin-Dest-PathAndQuery", context.Request.GetEncodedPathAndQuery());
context.Request.Headers.TryAdd("X-Watt-Token", token ?? string.Empty);
}
const int UTF8PreambleLength = 3;
const byte UTF8PreambleByte2 = 0xBF;
const int UTF8PreambleFirst2Bytes = 0xEFBB;
@@ -89,6 +89,8 @@ abstract class ReverseProxyServiceImpl : IReverseProxySettings
public string? CustomDohAddres { get; set; }
public string? ServerSideProxyToken { get; set; }
/// <summary>
/// 获取一个随机的未使用的端口
/// </summary>
@@ -157,7 +159,7 @@ abstract class ReverseProxyServiceImpl : IReverseProxySettings
return StartProxyResultCode.DeserializeReverseProxySettingsFail;
}
// Linux 如果是443 需要验证 是否允许使用
// Linux 如果是443 需要验证 是否允许使用
if (OperatingSystem.IsLinux())
{
if (string.IsNullOrWhiteSpace(reverseProxySettings.ProxyIp))
@@ -178,7 +180,6 @@ abstract class ReverseProxyServiceImpl : IReverseProxySettings
protected virtual void CheckRootCertificate()
{
}
protected abstract Task<StartProxyResult> StartProxyImpl();
@@ -193,6 +193,9 @@ public interface ICertificateManager
var isRootCertificateInstalled = IsCertificateInstalled();
if (!isRootCertificateInstalled)
{
// 生成证书
certificateManager.GenerateCertificate();
// 安装证书
ICertificateManager.Constants.TrustRootCertificate(
GetCerFilePath, platformService, certificate2);
@@ -271,4 +274,10 @@ public interface ICertificateManager
/// <param name="filePath"></param>
/// <returns></returns>
bool? GenerateCertificate();
/// <summary>
/// 获取证书信息
/// </summary>
/// <returns></returns>
string GetCertificateInfo();
}
@@ -1,6 +1,9 @@
using dotnetCampus.Ipc.CompilerServices.Attributes;
#if APP_REVERSE_PROXY
using InstanceReverseProxyService = BD.WTTS.Services.Implementation.YarpReverseProxyServiceImpl;
#else
using InstanceReverseProxyService = BD.WTTS.Services.IReverseProxyService;
#endif
@@ -59,6 +62,7 @@ public partial interface IReverseProxyService : IDisposable
Task StopProxyAsync();
#if DEBUG
string GetDebugString()
{
return $"Pid: {Environment.ProcessId}, Exe: {Environment.ProcessPath}, Asm: {Assembly.GetAssembly(GetType())?.FullName}";
@@ -69,6 +73,7 @@ public partial interface IReverseProxyService : IDisposable
StartProxyResult r = new(StartProxyResultCode.Exception, new Exception("aaaa"));
return Task.FromResult(r);
}
#endif
}
@@ -139,7 +144,7 @@ public partial interface IReverseProxySettings
/// </summary>
ushort Socks5ProxyPortId { get; set; }
#endregion
#endregion Socks5
#region TwoLevelAgent(二级代理)
@@ -155,7 +160,7 @@ public partial interface IReverseProxySettings
string? TwoLevelAgentPassword { get; set; }
#endregion
#endregion TwoLevelAgent(二级代理)
IPAddress? ProxyDNS { get; set; }
@@ -167,6 +172,11 @@ public partial interface IReverseProxySettings
bool UseDoh { get; set; }
string? CustomDohAddres { get; set; }
/// <summary>
/// 服务端代理令牌
/// </summary>
string? ServerSideProxyToken { get; set; }
}
[MP2Obj(SerializeLayout.Explicit)]
@@ -214,7 +224,9 @@ public readonly partial record struct ReverseProxySettings(
[property:MP2Key(20)]
bool UseDoh,
[property:MP2Key(21)]
string? CustomDohAddres)
string? CustomDohAddres,
[property:MP2Key(22)]
string? ServerSideProxyToken)
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal IPAddress GetProxyIp() => GetProxyIp(ProxyIp);
@@ -267,6 +279,7 @@ public readonly partial record struct ReverseProxySettings(
settings.IsSupportIpv6 = IsSupportIpv6;
settings.UseDoh = UseDoh;
settings.CustomDohAddres = CustomDohAddres;
settings.ServerSideProxyToken = ServerSideProxyToken;
}
#endif
}
@@ -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,250 @@
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>
/// PicInfo
/// </summary>
[Reactive]
[XmlIgnore, IgnoreDataMember, SystemTextJsonIgnore, NewtonsoftJsonIgnore, MPIgnore, MP2Ignore]
public XunYouPicInfo? PicInfo { get; set; }
/// <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; }
XunYouGameInfo? _GameInfo;
/// <summary>
/// 游戏信息
/// </summary>
[XmlIgnore, IgnoreDataMember, SystemTextJsonIgnore, NewtonsoftJsonIgnore, MPIgnore, MP2Ignore]
public XunYouGameInfo? GameInfo
{
get => _GameInfo;
set
{
_GameInfo = value;
this.RaisePropertyChanged();
this.RaisePropertyChanged(nameof(ShowStart));
}
}
/// <inheritdoc cref="XunYouGameInfo.ShowStart"/>
[XmlIgnore, IgnoreDataMember, SystemTextJsonIgnore, NewtonsoftJsonIgnore, MPIgnore, MP2Ignore]
public bool ShowStart => IsAccelerated && (GameInfo != null && GameInfo.ShowStart);
/// <summary>
/// 是否正在加速等待
/// </summary>
[Reactive]
[MP2Ignore, SystemTextJsonIgnore]
public bool IsAccelerating { get; set; }
/// <summary>
/// 加速进度
/// </summary>
[Reactive]
[MP2Ignore, SystemTextJsonIgnore]
public int AcceleratingProgress { get; set; }
bool _IsAccelerated;
/// <summary>
/// 是否已加速
/// </summary>
[MP2Ignore, SystemTextJsonIgnore]
public bool IsAccelerated
{
get => _IsAccelerated;
set
{
if (_IsAccelerated != value)
{
_IsAccelerated = value;
this.RaisePropertyChanged();
this.RaisePropertyChanged(nameof(ShowStart));
}
}
}
/// <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;
}
}
}

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