Compare commits

...
Author SHA1 Message Date
dependabot[bot] 560d87bf67 Bump ref/DirectoryPackages from 07bf9c2 to 0a1c04d
Bumps [ref/DirectoryPackages](https://github.com/BeyondDimension/DirectoryPackages) from `07bf9c2` to `0a1c04d`.
- [Commits](https://github.com/BeyondDimension/DirectoryPackages/compare/07bf9c2d477bc02547f01864bba44db78780e48d...0a1c04d40deec6b081f118b713446058241fe78c)

---
updated-dependencies:
- dependency-name: ref/DirectoryPackages
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-12-21 23:15:57 +00: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
47 changed files with 2113 additions and 468 deletions
+189
View File
@@ -0,0 +1,189 @@
#!/bin/bash
run_path=$(dirname "$0")
base_path="$(dirname "$run_path")"
mkdir -p "$base_path"
cd "$base_path" || exit 1
appVer_path="$base_path/WattToolkit.AppVer"
exec_name="Steam++"
tar_name="WattToolkit.tgz"
tar_path="$base_path/$tar_name"
app_name="Watt Toolkit"
PROCESS_NAMES=("$exec_name" "$app_name")
export LC_ALL=en_US.UTF-8
Install_certutil() {
# 判断发行版类型
if command -v certutil &>/dev/null; then
echo "certutil 工具已安装。"
else
echo "证书导入以及验证需要使用 certutil 工具。"
# 判断包管理器
if command -v apt &>/dev/null; then
# 使用 apt (Debian/Ubuntu)
sudo apt update
sudo apt install -y libnss3-tools
elif command -v dnf &>/dev/null; then
# 使用 dnf (Fedora)
sudo dnf install -y nss-tools
elif command -v yum &>/dev/null; then
# 使用 yum (CentOS/Red Hat)
sudo yum install -y nss-tools
elif command -v pacman &>/dev/null; then
# 使用 pacman (Arch Linux)
# sudo pacman -S nss
echo "请手动安装 certutil 工具。"
exit 1
else
echo "请手动安装 certutil 工具。"
exit 1
fi
echo "certutil 工具已安装。"
fi
}
certutil_Init() {
certutil -d $HOME/.pki/nssdb -L
ret=$?
if [ $ret -ne 0 ]; then
echo "即将初始化 certutil \$HOME/.pki/nssdb"
mkdir -p $HOME/.pki
mkdir -p $HOME/.pki/nssdb
chmod 700 $HOME/.pki/nssdb
certutil -d $HOME/.pki/nssdb -N --empty-password
else
echo "certutil nssdb 正常"
fi
}
Install_jq() {
# Check if jq is already installed
if command -v jq &>/dev/null; then
echo "jq 工具已安装。"
else
echo "jq 用来解析版本更新。"
# Check the package manager
if command -v apt &>/dev/null; then
# Using apt (Debian/Ubuntu)
sudo apt update
sudo apt install -y jq
elif command -v dnf &>/dev/null; then
# Using dnf (Fedora)
sudo dnf install -y jq
elif command -v yum &>/dev/null; then
# Using yum (CentOS/Red Hat)
sudo yum install -y jq
elif command -v pacman &>/dev/null; then
# Using pacman (Arch Linux)
sudo pacman -S jq
else
echo "请手动安装 jq 工具。"
exit 1
fi
echo "请手动安装 jq 工具。"
fi
}
Install_zenity() {
# 判断发行版类型
if command -v zenity &>/dev/null; then
echo "zenity 工具已安装。"
else
echo "安装过程需要 zenity 工具。"
# 判断包管理器
if command -v apt &>/dev/null; then
# 使用 apt (Debian/Ubuntu)
sudo apt update
sudo apt install -y zenity
elif command -v dnf &>/dev/null; then
# 使用 dnf (Fedora)
sudo dnf install -y zenity
elif command -v yum &>/dev/null; then
# 使用 yum (CentOS/Red Hat)
sudo yum install -y zenity
elif command -v pacman &>/dev/null; then
# 使用 pacman (Arch Linux)
# sudo pacman -S zenity
echo "请手动安装 zenity 工具。"
exit 1
else
echo "请手动安装 zenity 工具。"
exit 1
fi
echo "zenity 工具已安装。"
fi
}
Show_Run() {
local param1=$1
# 显示提示框,询问是否运行程序
zenity --question --text="$1" --width=400
# 获取上一个命令的退出码
response=$?
if [ $response -eq 0 ]; then
echo "程序已启动。"
exit 0 &
# 用户点击了 "运行" 按钮,启动程序
/bin/sh -c "$base_path/$exec_name.sh"
else
# 用户点击了 "关闭" 按钮,退出脚本
exit 0
fi
}
Kill_Process() {
# 尝试的次数
Kill_MAX_RETRIES=3
# 循环尝试终止进程
for process_name in "${PROCESS_NAMES[@]}"; do
kill_retry=1
while [ $kill_retry -le $Kill_MAX_RETRIES ]; do
pid=$(pgrep "$process_name")
if [ -n "$pid" ]; then
echo "尝试 $kill_retry: 进程 $process_name 正在运行中。正在终止..."
kill $pid
sleep 2
else
break
fi
kill_retry=$((kill_retry + 1))
done
done
for process_name in "${PROCESS_NAMES[@]}"; do
# 检查是否成功终止进程
if pgrep -x "$process_name" >/dev/null; then
echo "无法终止程序 $process_name。尝试次数已达上限。"
exit 1
else
echo "程序 $process_name 已成功终止。"
fi
done
}
Install_certutil
Install_zenity
Install_jq
certutil_Init
Kill_Process
dotnet_exec="$dotnet_path/dotnet/dotnet"
if [ -x "$dotnet_exec" ]; then
echo "文件具有执行权限。"
else
chmod +x "$dotnet_exec"
fi
chmod +x "$base_path/$exec_name.sh"
# xdg-icon-resource install "$base_path/Icons/Watt-Toolkit.png" --size 128 Watt-Toolkit
#添加桌面文件
rm -rf "$HOME/Desktop/Watt Toolkit.desktop" 2>/dev/null
echo "#!/usr/bin/env xdg-open
[Desktop Entry]
Name=Watt Toolkit
Exec=$base_path/$exec_name.sh
Icon=$base_path/Icons/Watt-Toolkit.png
Terminal=false
Type=Application
StartupNotify=false" >"$HOME/Desktop/Watt Toolkit.desktop"
chmod +x "$HOME/Desktop/Watt Toolkit.desktop"
# update-desktop-database ~/.local/share/applications
#运行程序
Show_Run "下载安装完成,是否启动程序?"
exit 0
+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=zn_CN.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
+1 -1
View File
@@ -981,7 +981,7 @@
{
"TypeName": "IdleRule",
"PropertyName": "IdleRule",
"DefaultValue": "IdleRule.OnlyOneGame",
"DefaultValue": "IdleRule.FastMode",
"DefaultValueIsConst": false,
"Summary": "运行规则",
"IsRegionOrEndregion": null,
@@ -126,6 +126,12 @@
<ProjectReference Include="..\BD.WTTS.Client\BD.WTTS.Client.csproj" />
</ItemGroup>
<ItemGroup>
<Compile Update="UI\Views\Controls\Widgets\CarouselItems.cs">
<DependentUpon>%(Filename)</DependentUpon>
</Compile>
</ItemGroup>
<Import Project="..\TFM_NETX_WITH_DESKTOP.props" />
</Project>
@@ -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();
}
}
+3 -2
View File
@@ -44,7 +44,8 @@ public sealed partial class App : Application
AvaloniaXamlLoader.Load(this);
#if WINDOWS || LINUX || MACOS
if (GeneralSettings.MinimizeOnStartup.Value)
//非主窗口不执行启动时最小化逻辑
if (GeneralSettings.MinimizeOnStartup.Value && MainWindow is MainWindow)
Startup.Instance.IsMinimize = true;
if (Startup.Instance.IsSteamRun)
{
@@ -101,7 +102,7 @@ public sealed partial class App : Application
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)
@@ -13,8 +13,9 @@
<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" />
@@ -0,0 +1,113 @@
<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">
<Panel>
<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="{TemplateBinding ItemsPerPage}" Spacing="5" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</DataTemplate>
</Carousel.ItemTemplate>
</Carousel>
<Button
Name="Left"
HorizontalAlignment="Left"
Classes="action"
IsVisible="False"
Theme="{StaticResource TransparentButton}">
<ui:SymbolIcon FontSize="14" Symbol="ChevronLeft" />
</Button>
<Button
Name="Right"
HorizontalAlignment="Right"
Classes="action"
IsVisible="False"
Theme="{StaticResource TransparentButton}">
<ui:SymbolIcon FontSize="14" Symbol="ChevronRight" />
</Button>
<Panel
Height="10"
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>
<DataTemplate>
<DataTemplate.DataType>
<x:Type x:TypeArguments="x:Int32, x:String" TypeName="generic:KeyValuePair" />
</DataTemplate.DataType>
<Button
Padding="0"
Classes="Plain"
Command="{Binding $parent[spp:CarouselItems].CarouselBannerIndexCommand}"
CommandParameter="{Binding Key}">
<Ellipse
Name="IndexEllipse"
Width="10"
Height="10"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Fill="{Binding Value}"
Opacity="0.6"
StrokeThickness="0" />
</Button>
</DataTemplate>
</ItemsRepeater.ItemTemplate>
</ItemsRepeater>
</Panel>
</Panel>
</Border>
</ControlTemplate>
</Setter>
</Style>
<Style Selector="spp|CarouselItems /template/ Button.action">
<Setter Property="Margin" Value="4" />
<Setter Property="Width" Value="36" />
<Setter Property="Height" Value="36" />
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="HorizontalContentAlignment" Value="Center" />
<Setter Property="VerticalContentAlignment" Value="Center" />
<Setter Property="Foreground" Value="White" />
</Style>
</Styles>
@@ -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}";
}
@@ -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,206 @@
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);
}
private Carousel? _carouselControl;
private Button? _leftButton;
private Button? _rightButton;
private ItemsRepeater? _swiper;
private Timer? _timer;
public ICommand? CarouselBannerIndexCommand { get; }
public CarouselItems()
{
CarouselBannerIndexCommand = ReactiveCommand.Create<int>(CarouselBannerIndex);
}
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{
base.OnApplyTemplate(e);
_carouselControl = e.NameScope.Find<Carousel>("CarouselControl");
_leftButton = e.NameScope.Find<Button>("Left");
_rightButton = e.NameScope.Find<Button>("Right");
_swiper = e.NameScope.Find<ItemsRepeater>("Swiper");
if (_leftButton != null)
{
_leftButton.Command = ReactiveCommand.Create(SwiperPrevious);
}
if (_rightButton != null)
{
_rightButton.Command = ReactiveCommand.Create(SwiperNext);
}
if (_carouselControl != null)
{
_carouselControl.GetObservable(Carousel.ItemCountProperty)
.Subscribe(_ => SwipersLoad());
_carouselControl.GetObservable(Carousel.SelectedIndexProperty)
.Subscribe(_ => SwipersLoad());
}
}
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs 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;
}
}
}
base.OnPropertyChanged(change);
}
void RefreshItemsSource()
{
if (_carouselControl != null)
{
var items = ItemsSource?.Batch(ItemsPerPage);
_carouselControl.ItemsSource = items;
}
}
private void CarouselBannerIndex(int index)
{
if (_carouselControl != null)
_carouselControl.SelectedIndex = index;
}
private void SwipersLoad()
{
if (_carouselControl == null || _swiper == null || _leftButton == null || _rightButton == null)
return;
if (_carouselControl.ItemCount <= 0)
{
_swiper.ItemsSource = null;
return;
}
if (_carouselControl.ItemCount == 1)
{
_leftButton.IsVisible = _rightButton.IsVisible = _swiper.IsVisible = false;
return;
}
else
{
_leftButton.IsVisible = _rightButton.IsVisible = _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;
}
}
private void SwiperNext()
{
if (_carouselControl == null || _carouselControl.ItemCount < 1)
return;
if (_carouselControl.SelectedIndex < _carouselControl.ItemCount - 1)
{
_carouselControl.Next();
}
else
{
_carouselControl.SelectedIndex = 0;
}
}
private void SwiperPrevious()
{
if (_carouselControl == null || _carouselControl.ItemCount < 1)
return;
if (_carouselControl.SelectedIndex > 0)
{
_carouselControl.Previous();
}
else
{
_carouselControl.SelectedIndex = _carouselControl.ItemCount - 1;
}
}
}
@@ -7,17 +7,26 @@ public class FixedWrapPanel : Panel, INavigableContainer
public static readonly StyledProperty<int> ItemsPerLineProperty =
AvaloniaProperty.Register<FixedWrapPanel, int>(nameof(ItemsPerLine), 3);
public static readonly StyledProperty<double> SpacingProperty =
AvaloniaProperty.Register<FixedWrapPanel, double>(nameof(Spacing), 0);
static FixedWrapPanel()
{
AffectsMeasure<FixedWrapPanel>(ItemsPerLineProperty);
}
public double ItemsPerLine
public int ItemsPerLine
{
get => GetValue(ItemsPerLineProperty);
set => SetValue(ItemsPerLineProperty, value);
}
public double Spacing
{
get => GetValue(SpacingProperty);
set => SetValue(SpacingProperty, value);
}
IInputElement INavigableContainer.GetControl(NavigationDirection direction, IInputElement? from, bool wrap)
{
int index = from is not null ? Children.IndexOf((Control)from) : -1;
@@ -63,7 +72,7 @@ public class FixedWrapPanel : Panel, INavigableContainer
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 +85,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,14 +95,17 @@ 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();
}
@@ -150,8 +162,8 @@ public class FixedWrapPanel : Panel, INavigableContainer
continue;
}
child.Arrange(new Rect(x, y, width, height));
x += width;
child.Arrange(new Rect(x, y, width - Spacing, height));
x += width + Spacing;
}
}
@@ -21,6 +21,7 @@
</spp:PageBase.PreviewImage>
<spp:PageBase.TabContent>
<TabStrip
Name="TabSelect"
Margin="4"
HorizontalAlignment="Right"
VerticalAlignment="Top"
@@ -110,19 +111,45 @@
</DropDownButton>
</StackPanel>
</spp:PageBase.ActionContent>
<Border Background="{DynamicResource CardBackgroundFillColorDefaultBrush}" CornerRadius="{DynamicResource ControlCornerRadius}">
<Grid RowDefinitions="*,Auto">
<TextBox
HorizontalContentAlignment="Stretch"
VerticalContentAlignment="Stretch"
Classes="readonlybox"
Text="{Binding DebugString}"
TextAlignment="Start"
TextWrapping="Wrap" />
<TextBox
Name="CommandTextBox"
Grid.Row="1"
Watermark="{Binding Path=Res.Debug_InstructionPlaceholder, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
</Grid>
</Border>
<Carousel SelectedIndex="{Binding #TabSelect.SelectedIndex}">
<Border Background="{DynamicResource CardBackgroundFillColorDefaultBrush}" CornerRadius="{DynamicResource ControlCornerRadius}">
<Grid RowDefinitions="*,Auto">
<TextBox
HorizontalContentAlignment="Stretch"
VerticalContentAlignment="Stretch"
Classes="readonlybox"
Text="{Binding DebugString}"
TextAlignment="Start"
TextWrapping="Wrap" />
<TextBox
Name="CommandTextBox"
Grid.Row="1"
Watermark="{Binding Path=Res.Debug_InstructionPlaceholder, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
</Grid>
</Border>
<Panel>
<TextBlock Text="Test Control" />
<ItemsControl>
<ItemsControl.Items>
<Border Height="30" Background="Red" />
<Border Background="Blue" />
<Border Background="Yellow" />
<Border Height="30" Background="Yellow" />
<Border Background="Red" />
<Border Background="Blue" />
</ItemsControl.Items>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<spp:FixedWrapPanel ItemsPerLine="3" Spacing="5" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</Panel>
<Panel>
<TextBlock Text="Test Control" />
</Panel>
</Carousel>
</spp:PageBase>
@@ -270,7 +270,68 @@
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 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 +415,8 @@
ContentTemplate="{StaticResource ShopItem}" />
</Grid>
</DataTemplate>
<!--<DataTemplate>
-->
<!--<DataTemplate>
<DataTemplate.DataType>
<x:Type x:TypeArguments="spp:ShopRecommendGoodItem" TypeName="col:IEnumerable" />
</DataTemplate.DataType>
@@ -366,8 +428,9 @@
</ItemsControl.ItemsPanel>
</ItemsControl>
</DataTemplate>-->
<!--
</spp:CarouselBanner.ItemTemplate>
</spp:CarouselBanner>
</spp:CarouselBanner>-->
<!--<ScrollViewer
Name="ShopsScrollViewer"
@@ -170,13 +170,14 @@ public sealed class AppSplashScreen : IApplicationSplashScreen
#pragma warning disable SA1114 // Parameter list should follow declaration
IViewModelManager.Instance.InitViewModels(new TabItemViewModel[]
{
new MenuTabItemViewModel("Welcome")
{
PageType = typeof(HomePage),
IsResourceGet = true,
IconKey = "avares://BD.WTTS.Client.Avalonia/UI/Assets/Icons/home.ico",
},
}, ImmutableArray.Create<TabItemViewModel>(
new MenuTabItemViewModel("Welcome")
{
PageType = typeof(HomePage),
IsResourceGet = true,
IconKey = "avares://BD.WTTS.Client.Avalonia/UI/Assets/Icons/home.ico",
},
},
ImmutableArray.Create<TabItemViewModel>(
#if DEBUG
new MenuTabItemViewModel("Debug")
{
@@ -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));
}
}
@@ -376,20 +376,6 @@ public sealed class ScriptManager : GeneralHttpClientFactory, IScriptManager
var scripts = list.ToList();
foreach (var item in list)
{
//检查缓存文件夹如果不是 IScriptManager.DirName_Build 替换成 IScriptManager.DirName_Build 开头
if (!item.CachePath.StartsWith($"{IScriptManager.DirName_Build}{Path.DirectorySeparatorChar}"))
{
item.CachePath = Path.Combine(IScriptManager.DirName_Build, item.FileName!);
var cachePath = Path.Combine(Plugin.Instance.CacheDirectory, item.CachePath);
if (File.Exists(cachePath))
{
File.Delete(cachePath);
}
await TryReadFileAsync(item, true);
//清理 脚本内容 后续 IPC 传递 插件进程读取。
item.Content = string.Empty;
await scriptRepository.SaveScriptCachePathAsync(item, default);
}
if (!CheckFile(item))
{
var temp = await DeleteScriptAsync(item);
@@ -404,6 +390,38 @@ public sealed class ScriptManager : GeneralHttpClientFactory, IScriptManager
//toast.Show($"脚本:{item.Name}_文件丢失,删除失败去尝试手动删除");
Toast.Show(ToastIcon.Error, Strings.Script_NoFileDeleteError_.Format(item.Name));
}
continue;
}
//检查缓存文件夹如果不是 IScriptManager.DirName_Build 替换成 IScriptManager.DirName_Build 开头
if (!item.CachePath.StartsWith($"{IScriptManager.DirName_Build}{Path.DirectorySeparatorChar}"))
{
var oldCachePath = Path.Combine(Plugin.Instance.CacheDirectory, item.CachePath);
item.CachePath = Path.Combine(IScriptManager.DirName_Build, item.FileName!);
if (File.Exists(oldCachePath))
{
File.Delete(oldCachePath);
}
await TryReadFileAsync(item, true);
//清理 脚本内容 后续 IPC 传递 插件进程读取。
item.Content = string.Empty;
await scriptRepository.SaveScriptCachePathAsync(item, default);
}
else
{
var cachePath = Path.Combine(Plugin.Instance.CacheDirectory, item.CachePath);
var file = new FileInfo(cachePath);
//检查缓存文件是否为空 为空则 刷新
if (file.Exists)
{
if (file.Length == 0)
{
file.Delete();
await TryReadFileAsync(item, true);
//清理 脚本内容 后续 IPC 传递 插件进程读取。
item.Content = string.Empty;
}
}
}
}
return scripts;
@@ -0,0 +1,29 @@
<UserControl
x:Class="BD.WTTS.UI.Views.Controls.GameAccelerator"
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="250"
d:DesignWidth="800"
mc:Ignorable="d">
<Grid RowDefinitions="Auto,*">
<StackPanel
HorizontalAlignment="Right"
Orientation="Horizontal"
Spacing="5">
<AutoCompleteBox MinWidth="200" Watermark="搜索游戏" />
<Button Content="Left" />
<Button Content="Right" />
</StackPanel>
<spp:CarouselItems Grid.Row="1">
<spp:CarouselItems.ItemTemplate>
<DataTemplate>
<TextBlock Text="test" />
</DataTemplate>
</spp:CarouselItems.ItemTemplate>
</spp:CarouselItems>
</Grid>
</UserControl>
@@ -0,0 +1,11 @@
using Avalonia.Controls;
namespace BD.WTTS.UI.Views.Controls;
public partial class GameAccelerator : UserControl
{
public GameAccelerator()
{
InitializeComponent();
}
}
@@ -13,8 +13,6 @@
<Panel>
<lvc:CartesianChart
x:Name="Chart"
MinWidth="400"
MinHeight="160"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Background="Transparent" />
@@ -1,9 +1,12 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Data;
using Avalonia.Threading;
using Avalonia.VisualTree;
using LiveChartsCore;
using LiveChartsCore.Motion;
using LiveChartsCore.SkiaSharpView;
using LiveChartsCore.SkiaSharpView.Avalonia;
using LiveChartsCore.SkiaSharpView.Drawing;
using SkiaSharp;
@@ -49,39 +52,55 @@ public partial class ProxyChartView : UserControl
}
};
//public ISeries[] Series { get; set; }
private readonly List<RateTick> writes = new();
private readonly List<RateTick> reads = new();
private readonly ObservableCollection<RateTick> writes = new();
private readonly ObservableCollection<RateTick> reads = new();
public Func<double, string> XFormatter { get; } = timestamp => ((long)timestamp).ToDateTimeS().ToString("HH:mm:ss");
public Func<double, string> YFormatter { get; } = value => $"{IOPath.GetDisplayFileSizeString(value)}/s";
CancellationTokenSource cancellation = new();
public static readonly AvaloniaProperty<IEnumerable<ISeries>> SeriesProperty =
AvaloniaProperty.Register<CartesianChart, IEnumerable<ISeries>>("Series", Enumerable.Empty<ISeries>(), inherits: true);
public ObservableCollection<ISeries> Series
{
get
{
return (ObservableCollection<ISeries>)GetValue(SeriesProperty);
}
set
{
SetValue(SeriesProperty, value);
}
}
public ProxyChartView()
{
InitializeComponent();
this.readSeries.Values = reads;
this.writeSeries.Values = writes;
Series = [readSeries, writeSeries];
if (Chart != null)
{
//Chart.UpdateFinished += Chart_UpdateFinished;
Chart.Series = new ISeries[] { readSeries, writeSeries };
//Chart.Series = new ISeries[] { readSeries, writeSeries };
Chart[!CartesianChart.SeriesProperty] = this[!SeriesProperty];
Chart.XAxes = new Axis[] { new Axis { Labeler = XFormatter } };
Chart.YAxes = new Axis[] { new Axis { Labeler = YFormatter, MinLimit = 0 } };
}
CancellationTokenSource? cancellation = null;
ProxyService.Current.WhenAnyValue(x => x.ProxyStatus)
.Subscribe(x =>
{
if (x)
{
cancellation = new CancellationTokenSource();
FlushFlowChartAsync(cancellation.Token);
Task2.InBackground(FlushFlowChartAsync, true);
}
else
{
@@ -96,9 +115,7 @@ public partial class ProxyChartView : UserControl
});
}
private static double GetTimestamp(DateTime dateTime) => dateTime.ToUnixTimeSeconds();
private class RateTick
private struct RateTick
{
public double Rate { get; }
@@ -111,15 +128,18 @@ public partial class ProxyChartView : UserControl
}
}
private async void FlushFlowChartAsync(CancellationToken token)
private void FlushFlowChartAsync()
{
while (!token.IsCancellationRequested)
while (!cancellation.IsCancellationRequested)
{
try
{
var flowStatistics = IReverseProxyService.Constants.Instance.GetFlowStatistics();
if (flowStatistics == null)
{
Thread.Sleep(1000);
continue;
}
var isAttachedToVisualTree = this.IsAttachedToVisualTree();
@@ -132,7 +152,7 @@ public partial class ProxyChartView : UserControl
});
}
var timestamp = GetTimestamp(DateTime.Now);
var timestamp = DateTime.Now.ToUnixTimeSeconds();
reads.Add(new RateTick(flowStatistics.ReadRate, timestamp));
writes.Add(new RateTick(flowStatistics.WriteRate, timestamp));
@@ -143,24 +163,26 @@ public partial class ProxyChartView : UserControl
this.writes.RemoveAt(0);
}
if (Chart != null && isAttachedToVisualTree)
Dispatcher.UIThread.Post(() =>
{
Chart.Series = new ISeries[] { readSeries, writeSeries };
});
//if (Chart != null && isAttachedToVisualTree)
//{
// Dispatcher.UIThread.Post(() =>
// {
// Chart.Series = new ISeries[] { readSeries, writeSeries };
// });
//}
}
catch
{
}
finally
{
await Task.Delay(TimeSpan.FromSeconds(1d), CancellationToken.None);
Thread.Sleep(1000);
}
}
}
//解决内存泄露问题
//https://github.com/beto-rodriguez/LiveCharts2/issues/1080#issuecomment-1601536016
////解决内存泄露问题
////https://github.com/beto-rodriguez/LiveCharts2/issues/1080#issuecomment-1601536016
//private static void Chart_UpdateFinished(LiveChartsCore.Kernel.Sketches.IChartView<SkiaSharpDrawingContext> chart)
//{
// // Chart library leaks PaintTasks
@@ -169,7 +191,7 @@ public partial class ProxyChartView : UserControl
// // Periodically clean up drawables, this may cause the chart to blip if the user mouses over it during this time
// if (chart.CoreCanvas.DrawablesCount > 50)
// {
// chart.CoreCanvas.SetPaintTasks(new HashSet<LiveChartsCore.Drawing.IPaint<SkiaSharpDrawingContext>>());
// chart.CoreCanvas.SetPaintTasks([]);
// }
// }
//}
@@ -106,7 +106,6 @@
DockPanel.Dock="Right"
IsHitTestVisible="False" />
<Button
Padding="8,4"
Command="{Binding RefreshCommand}"
@@ -288,7 +288,7 @@
<MenuFlyout>
<MenuItem
Command="{Binding CopyCode}"
Header="{Binding Path=Res.Up, Mode=OneWay, Source={x:Static s:ResourceService.Current}}"
Header="{Binding Path=Res.LocalAuth_Copy, Mode=OneWay, Source={x:Static s:ResourceService.Current}}"
ToolTip.Tip="{Binding Path=Res.LocalAuth_Copy, Mode=OneWay, Source={x:Static s:ResourceService.Current}}">
<MenuItem.Icon>
<ui:SymbolIcon Symbol="Copy" />
@@ -63,7 +63,7 @@ public partial interface ISteamIdleSettings
/// <summary>
/// 运行规则的默认值
/// </summary>
static readonly IdleRule DefaultIdleRule = IdleRule.OnlyOneGame;
static readonly IdleRule DefaultIdleRule = IdleRule.FastMode;
/// <summary>
/// 运行顺序的默认值
@@ -223,11 +223,17 @@ public sealed partial class IdleCardPageViewModel : ViewModelBase
if (!SteamLoginState.Success)
{
var vm = new IdleSteamLoginPageViewModel(ref SteamLoginState);
await IWindowManager.Instance.ShowTaskDialogAsync(vm, Strings.Steam_Login,
pageContent: new IdleSteamLoginPage(), okButtonText: Strings.Confirm, isOkButton: false);
IsLogin = SteamLoginState.Success;
LoginViewModel = new IdleSteamLoginPageViewModel(ref SteamLoginState);
LoginViewModel.Close += () =>
{
ViewState = 1;
IsLogin = SteamLoginState.Success;
};
ViewState = 0;
//var vm = new IdleSteamLoginPageViewModel(ref SteamLoginState);
//await IWindowManager.Instance.ShowTaskDialogAsync(vm, Strings.Steam_Login,
// pageContent: new IdleSteamLoginPage(), okButtonText: Strings.Confirm, isOkButton: false);
//IsLogin = SteamLoginState.Success;
}
var sid = SteamConnectService.Current.CurrentSteamUser?.SteamId64;
@@ -68,6 +68,12 @@ public sealed partial class IdleCardPageViewModel
[Reactive]
public int TotalCardsRemaining { get; set; }
[Reactive]
public int ViewState { get; set; } = 1;
[Reactive]
public IdleSteamLoginPageViewModel? LoginViewModel { get; set; }
public int DropCardsCount => Math.Max(DroppedCardsCount - TotalCardsRemaining, 0);
[Reactive]
@@ -216,16 +216,18 @@
</StackPanel>
</spp:PageBase.PaneContent>
<!--<Carousel Classes="AnimeSlide" SelectedIndex="0">-->
<spp:ContentLoader CustomLoadingText="{Binding Path=Res.EnableAccelerationPrompt, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" IsLoading="{Binding IsLoaing}">
<!-- IsVisible="{Binding !IsLoaing}" -->
<Grid
ColumnDefinitions="*,240"
IsVisible="{Binding !IsLoaing}"
RowDefinitions="Auto,Auto,*">
<spp:OptionsDisplayItem Expands="True" IsExpanded="True">
<spp:OptionsDisplayItem.Header>
<!--<TextBlock>
<Carousel Classes="AnimeSlide" SelectedIndex="{Binding ViewState}">
<spp:IdleSteamLoginPage DataContext="{Binding LoginViewModel}" />
<spp:ContentLoader CustomLoadingText="{Binding Path=Res.EnableAccelerationPrompt, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" IsLoading="{Binding IsLoaing}">
<!-- IsVisible="{Binding !IsLoaing}" -->
<Grid
ColumnDefinitions="*,240"
IsVisible="{Binding !IsLoaing}"
RowDefinitions="Auto,Auto,*">
<spp:OptionsDisplayItem Expands="True" IsExpanded="True">
<spp:OptionsDisplayItem.Header>
<!--<TextBlock>
<TextBlock.Text>
<MultiBinding Converter="{StaticResource VisibleStringConverter}">
<CompiledBinding
@@ -243,337 +245,337 @@
</MultiBinding>
</TextBlock.Text>
</TextBlock>-->
<TextBlock>
<TextBlock.Text>
<MultiBinding Converter="{StaticResource VisibleStringConverter}">
<CompiledBinding Mode="OneWay" Path="IsLogin" />
<CompiledBinding Mode="OneWay" Path="UserIdleInfo.UserName" />
<CompiledBinding
Mode="OneWay"
Path="Res.Idle_NeedLoginSteam"
Source="{x:Static s:ResourceService.Current}" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</spp:OptionsDisplayItem.Header>
<spp:OptionsDisplayItem.Description>
<Panel>
<TextBlock IsVisible="{Binding IsLogin}">
<TextBlock>
<TextBlock.Text>
<MultiBinding Converter="{StaticResource StringFormatConverter}">
<MultiBinding Converter="{StaticResource VisibleStringConverter}">
<CompiledBinding Mode="OneWay" Path="IsLogin" />
<CompiledBinding Mode="OneWay" Path="UserIdleInfo.UserName" />
<CompiledBinding
Mode="OneWay"
Path="Res.SteamIdle_UserExpString_"
Path="Res.Idle_NeedLoginSteam"
Source="{x:Static s:ResourceService.Current}" />
<CompiledBinding Mode="OneWay" Path="UserIdleInfo.UserLevel" />
<CompiledBinding Mode="OneWay" Path="UserIdleInfo.CurrentExp" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
<TextBlock IsVisible="{Binding !IsLogin}" Text="{Binding Path=Res.SteamIdle_SteamClientUserTips, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
</Panel>
</spp:OptionsDisplayItem.Description>
<spp:OptionsDisplayItem.Icon>
<spp:Image2
Width="64"
Height="64"
VerticalAlignment="Center"
DecodeWidth="64"
DockPanel.Dock="Top"
FallbackSource="avares://BD.WTTS.Client.Avalonia/UI/Assets/avatar.jpg"
Source="{Binding UserIdleInfo.AvatarUrl, Mode=OneWay}"
Stretch="UniformToFill">
<spp:Image2.Clip>
<EllipseGeometry Rect="0,0,64,64" />
</spp:Image2.Clip>
</spp:Image2>
</spp:OptionsDisplayItem.Icon>
<spp:OptionsDisplayItem.ActionButton>
<Button VerticalAlignment="Center" Command="{Binding LoginSteamCommand}">
<WrapPanel>
<ui:SymbolIcon Margin="0,0,5,0" Symbol="AddFriend" />
<TextBlock>
</spp:OptionsDisplayItem.Header>
<spp:OptionsDisplayItem.Description>
<Panel>
<TextBlock IsVisible="{Binding IsLogin}">
<TextBlock.Text>
<MultiBinding Converter="{StaticResource VisibleStringConverter}">
<CompiledBinding Mode="OneWay" Path="IsLogin" />
<MultiBinding Converter="{StaticResource StringFormatConverter}">
<CompiledBinding
Mode="OneWay"
Path="Res.Logout"
Source="{x:Static s:ResourceService.Current}" />
<CompiledBinding
Mode="OneWay"
Path="Res.User_Login"
Path="Res.SteamIdle_UserExpString_"
Source="{x:Static s:ResourceService.Current}" />
<CompiledBinding Mode="OneWay" Path="UserIdleInfo.UserLevel" />
<CompiledBinding Mode="OneWay" Path="UserIdleInfo.CurrentExp" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</WrapPanel>
</Button>
</spp:OptionsDisplayItem.ActionButton>
<spp:OptionsDisplayItem.Content>
<StackPanel
Margin="24,0"
VerticalAlignment="Center"
Orientation="Vertical"
Spacing="6">
<TextBlock IsVisible="{Binding !IsLogin}" Text="{Binding Path=Res.SteamIdle_SteamClientUserTips, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
</Panel>
</spp:OptionsDisplayItem.Description>
<spp:OptionsDisplayItem.Icon>
<spp:Image2
Width="64"
Height="64"
VerticalAlignment="Center"
DecodeWidth="64"
DockPanel.Dock="Top"
FallbackSource="avares://BD.WTTS.Client.Avalonia/UI/Assets/avatar.jpg"
Source="{Binding UserIdleInfo.AvatarUrl, Mode=OneWay}"
Stretch="UniformToFill">
<spp:Image2.Clip>
<EllipseGeometry Rect="0,0,64,64" />
</spp:Image2.Clip>
</spp:Image2>
</spp:OptionsDisplayItem.Icon>
<spp:OptionsDisplayItem.ActionButton>
<Button VerticalAlignment="Center" Command="{Binding LoginSteamCommand}">
<WrapPanel>
<ui:SymbolIcon Margin="0,0,5,0" Symbol="AddFriend" />
<TextBlock>
<TextBlock.Text>
<MultiBinding Converter="{StaticResource VisibleStringConverter}">
<CompiledBinding Mode="OneWay" Path="IsLogin" />
<CompiledBinding
Mode="OneWay"
Path="Res.Logout"
Source="{x:Static s:ResourceService.Current}" />
<CompiledBinding
Mode="OneWay"
Path="Res.User_Login"
Source="{x:Static s:ResourceService.Current}" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</WrapPanel>
</Button>
</spp:OptionsDisplayItem.ActionButton>
<spp:OptionsDisplayItem.Content>
<StackPanel
Margin="24,0"
VerticalAlignment="Center"
Orientation="Vertical"
Spacing="6">
<TextBlock
Margin="0,6,0,0"
FontWeight="SemiBold"
IsVisible="{Binding IsLogin}">
<TextBlock.Text>
<MultiBinding Converter="{StaticResource StringFormatConverter}">
<CompiledBinding
Mode="OneWay"
Path="Res.SteamIdle_UserNextExpString_"
Source="{x:Static s:ResourceService.Current}" />
<CompiledBinding Mode="OneWay" Path="UserIdleInfo.NextLevel" />
<CompiledBinding Mode="OneWay" Path="UserIdleInfo.UpExp" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
<TextBlock
Margin="0,6,0,0"
FontWeight="SemiBold"
IsVisible="{Binding !IsLogin}"
Text="{Binding Source={x:Static s:ResourceService.Current}, Path=Res.Idle_NeedLoginSteam, Mode=OneWay}" />
<ProgressBar
Height="10"
Margin="0,0,0,6"
Classes="Material"
Maximum="100"
Value="{Binding UserIdleInfo.NextLevelExpPercentage}" />
<TextBlock
Margin="0,6,0,0"
FontWeight="SemiBold"
IsVisible="{Binding IsLogin}">
<TextBlock.Text>
<MultiBinding Converter="{StaticResource StringFormatConverter}">
<CompiledBinding
Mode="OneWay"
Path="Res.SteamIdle_UserNextExpString_"
Source="{x:Static s:ResourceService.Current}" />
<CompiledBinding Mode="OneWay" Path="UserIdleInfo.NextLevel" />
<CompiledBinding Mode="OneWay" Path="UserIdleInfo.UpExp" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
<TextBlock
Margin="0,6,0,0"
FontWeight="SemiBold"
IsVisible="{Binding !IsLogin}"
Text="{Binding Source={x:Static s:ResourceService.Current}, Path=Res.Idle_NeedLoginSteam, Mode=OneWay}" />
<ProgressBar
Height="10"
Margin="0,0,0,6"
Classes="Material"
Maximum="100"
Value="{Binding UserIdleInfo.NextLevelExpPercentage}" />
<!--<Border
<!--<Border
Height="1"
Margin="-50,6"
Background="{DynamicResource ApplicationPageBackgroundThemeBrush}" />-->
<Panel>
<WrapPanel HorizontalAlignment="Left">
<TextBlock FontSize="36" Text="{Binding TotalCardsRemaining}" />
<TextBlock VerticalAlignment="Center" Text=" 张剩余卡牌掉落" />
</WrapPanel>
<WrapPanel HorizontalAlignment="Right">
<TextBlock VerticalAlignment="Center" Text="估算价值 ¥ " />
<TextBlock FontSize="36" Text="{Binding TotalCardsAvgPrice, StringFormat={}{0:0.00}}" />
</WrapPanel>
</Panel>
<Panel>
<WrapPanel HorizontalAlignment="Left">
<TextBlock FontSize="36" Text="{Binding TotalCardsRemaining}" />
<TextBlock VerticalAlignment="Center" Text=" 张剩余卡牌掉落" />
</WrapPanel>
<WrapPanel HorizontalAlignment="Right">
<TextBlock VerticalAlignment="Center" Text="估算价值 ¥ " />
<TextBlock FontSize="36" Text="{Binding TotalCardsAvgPrice, StringFormat={}{0:0.00}}" />
</WrapPanel>
</Panel>
<Border
Height="1"
Margin="-50,6"
Background="{DynamicResource ApplicationPageBackgroundThemeBrush}" />
<Border
Height="1"
Margin="-50,6"
Background="{DynamicResource ApplicationPageBackgroundThemeBrush}" />
<StackPanel
Margin="0,6,0,0"
Orientation="Horizontal"
Spacing="5">
<TextBlock VerticalAlignment="Center" Text="当前 Steam 客户端登录账号:" />
<spp:Image2
Width="24"
Height="24"
Margin="0,0,5,0"
VerticalAlignment="Center"
DecodeWidth="36"
FallbackSource="avares://BD.WTTS.Client.Avalonia/UI/Assets/avatar.jpg"
Source="{Binding Source={x:Static s:SteamConnectService.Current}, Path=CurrentSteamUser.AvatarMedium, Mode=OneWay}"
Stretch="Uniform">
<spp:Image2.Clip>
<EllipseGeometry Rect="0,0,24,24" />
</spp:Image2.Clip>
</spp:Image2>
<TextBlock VerticalAlignment="Center">
<TextBlock.Text>
<MultiBinding Converter="{StaticResource VisibleStringConverter}">
<CompiledBinding
Mode="OneWay"
Path="IsConnectToSteam"
Source="{x:Static s:SteamConnectService.Current}" />
<CompiledBinding
Mode="OneWay"
Path="CurrentSteamUser.SteamNickName"
Source="{x:Static s:SteamConnectService.Current}" />
<CompiledBinding
Mode="OneWay"
Path="Res.SteamAppType_Unknown"
Source="{x:Static s:ResourceService.Current}" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
<StackPanel
Margin="0,6,0,0"
Orientation="Horizontal"
Spacing="5">
<TextBlock VerticalAlignment="Center" Text="当前 Steam 客户端登录账号:" />
<spp:Image2
Width="24"
Height="24"
Margin="0,0,5,0"
VerticalAlignment="Center"
DecodeWidth="36"
FallbackSource="avares://BD.WTTS.Client.Avalonia/UI/Assets/avatar.jpg"
Source="{Binding Source={x:Static s:SteamConnectService.Current}, Path=CurrentSteamUser.AvatarMedium, Mode=OneWay}"
Stretch="Uniform">
<spp:Image2.Clip>
<EllipseGeometry Rect="0,0,24,24" />
</spp:Image2.Clip>
</spp:Image2>
<TextBlock VerticalAlignment="Center">
<TextBlock.Text>
<MultiBinding Converter="{StaticResource VisibleStringConverter}">
<CompiledBinding
Mode="OneWay"
Path="IsConnectToSteam"
Source="{x:Static s:SteamConnectService.Current}" />
<CompiledBinding
Mode="OneWay"
Path="CurrentSteamUser.SteamNickName"
Source="{x:Static s:SteamConnectService.Current}" />
<CompiledBinding
Mode="OneWay"
Path="Res.SteamAppType_Unknown"
Source="{x:Static s:ResourceService.Current}" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</StackPanel>
</StackPanel>
</StackPanel>
</spp:OptionsDisplayItem.Content>
</spp:OptionsDisplayItem>
</spp:OptionsDisplayItem.Content>
</spp:OptionsDisplayItem>
<!-- Description="{Binding CurrentIdle.AppName}" -->
<spp:OptionsDisplayItem
Grid.Column="1"
Expands="True"
IsExpanded="True">
<spp:OptionsDisplayItem.Header>
<TextBlock>
<TextBlock.Text>
<MultiBinding Converter="{StaticResource VisibleStringConverter}">
<CompiledBinding Mode="OneWay" Path="RunState" />
<CompiledBinding
Mode="OneWay"
Path="Res.SteamIdle_IdleRuning"
Source="{x:Static s:ResourceService.Current}" />
<CompiledBinding
Mode="OneWay"
Path="Res.SteamIdle_IdleNotRuning"
Source="{x:Static s:ResourceService.Current}" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</spp:OptionsDisplayItem.Header>
<spp:OptionsDisplayItem.Description>
<Panel>
<TextBlock
Foreground="{DynamicResource SystemFillColorCriticalBrush}"
IsVisible="{Binding !IsRunningSteamProcess, Source={x:Static s:SteamConnectService.Current}, Mode=OneWay}"
Text="{Binding Path=Res.SteamNotRuning, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
<TextBlock
Foreground="{DynamicResource SystemFillColorSuccessBrush}"
IsVisible="{Binding IsRunningSteamProcess, Source={x:Static s:SteamConnectService.Current}, Mode=OneWay}"
Text="{Binding Path=Res.SteamRuning, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
</Panel>
</spp:OptionsDisplayItem.Description>
<spp:OptionsDisplayItem.Icon>
<Panel
Width="6"
Height="6"
Margin="5,0,3,0">
<Ellipse Fill="#47A42B" IsVisible="{Binding RunState}" />
<Ellipse Fill="#FF4F58" IsVisible="{Binding !RunState}" />
</Panel>
</spp:OptionsDisplayItem.Icon>
<spp:OptionsDisplayItem.Content>
<StackPanel
Margin="24,0"
VerticalAlignment="Center"
Orientation="Vertical"
Spacing="6">
<Panel ToolTip.Tip="{Binding DropCardsCount, Mode=OneWay, Converter={StaticResource StringFormatConverter}, ConverterParameter=SteamIdle_DroppedCardsCountTip_}">
<Arc
Width="80"
Height="80"
StartAngle="0"
Stretch="None"
Stroke="{DynamicResource ControlStrokeColorSecondaryBrush}"
StrokeThickness="5"
SweepAngle="360" />
<ProgressBar
Width="80"
Height="80"
Classes="Circle"
Maximum="{Binding DroppedCardsCount}"
Value="{Binding TotalCardsRemaining}" />
<!-- Description="{Binding CurrentIdle.AppName}" -->
<spp:OptionsDisplayItem
Grid.Column="1"
Expands="True"
IsExpanded="True">
<spp:OptionsDisplayItem.Header>
<TextBlock>
<TextBlock.Text>
<MultiBinding Converter="{StaticResource VisibleStringConverter}">
<CompiledBinding Mode="OneWay" Path="RunState" />
<CompiledBinding
Mode="OneWay"
Path="Res.SteamIdle_IdleRuning"
Source="{x:Static s:ResourceService.Current}" />
<CompiledBinding
Mode="OneWay"
Path="Res.SteamIdle_IdleNotRuning"
Source="{x:Static s:ResourceService.Current}" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</spp:OptionsDisplayItem.Header>
<spp:OptionsDisplayItem.Description>
<Panel>
<TextBlock
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{Binding Path=IdleTime, Mode=OneWay, Converter={StaticResource StringFormatConverter}}" />
Foreground="{DynamicResource SystemFillColorCriticalBrush}"
IsVisible="{Binding !IsRunningSteamProcess, Source={x:Static s:SteamConnectService.Current}, Mode=OneWay}"
Text="{Binding Path=Res.SteamNotRuning, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
<TextBlock
Foreground="{DynamicResource SystemFillColorSuccessBrush}"
IsVisible="{Binding IsRunningSteamProcess, Source={x:Static s:SteamConnectService.Current}, Mode=OneWay}"
Text="{Binding Path=Res.SteamRuning, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
</Panel>
</spp:OptionsDisplayItem.Description>
<spp:OptionsDisplayItem.Icon>
<Panel
Width="6"
Height="6"
Margin="5,0,3,0">
<Ellipse Fill="#47A42B" IsVisible="{Binding RunState}" />
<Ellipse Fill="#FF4F58" IsVisible="{Binding !RunState}" />
</Panel>
</spp:OptionsDisplayItem.Icon>
<spp:OptionsDisplayItem.Content>
<StackPanel
Margin="24,0"
VerticalAlignment="Center"
Orientation="Vertical"
Spacing="6">
<Panel ToolTip.Tip="{Binding DropCardsCount, Mode=OneWay, Converter={StaticResource StringFormatConverter}, ConverterParameter=SteamIdle_DroppedCardsCountTip_}">
<Arc
Width="80"
Height="80"
StartAngle="0"
Stretch="None"
Stroke="{DynamicResource ControlStrokeColorSecondaryBrush}"
StrokeThickness="5"
SweepAngle="360" />
<ProgressBar
Width="80"
Height="80"
Classes="Circle"
Maximum="{Binding DroppedCardsCount}"
Value="{Binding TotalCardsRemaining}" />
<TextBlock
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{Binding Path=IdleTime, Mode=OneWay, Converter={StaticResource StringFormatConverter}}" />
</Panel>
<WrapPanel HorizontalAlignment="Center">
<TextBlock VerticalAlignment="Center" Text="挂卡进度 " />
<TextBlock FontSize="22.6">
<TextBlock.Text>
<MultiBinding StringFormat="{}{0} / {1}">
<CompiledBinding Path="DropCardsCount" />
<CompiledBinding Path="DroppedCardsCount" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</WrapPanel>
<WrapPanel HorizontalAlignment="Center">
<TextBlock VerticalAlignment="Center" Text="挂卡进度 " />
<TextBlock FontSize="22.6">
<TextBlock.Text>
<MultiBinding StringFormat="{}{0} / {1}">
<CompiledBinding Path="DropCardsCount" />
<CompiledBinding Path="DroppedCardsCount" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</WrapPanel>
<WrapPanel HorizontalAlignment="Center">
<TextBlock FontSize="24.8" Text="{Binding RuningCount, Mode=OneWay}" />
<TextBlock VerticalAlignment="Center" Text=" 个游戏正在运行" />
</WrapPanel>
<WrapPanel HorizontalAlignment="Center">
<TextBlock FontSize="24.8" Text="{Binding RuningCount, Mode=OneWay}" />
<TextBlock VerticalAlignment="Center" Text=" 个游戏正在运行" />
</WrapPanel>
<!--<TextBlock
<!--<TextBlock
Grid.Row="2"
Grid.Column="1"
TextAlignment="Center">
<Run FontSize="22.6" Text="{Binding Badges.Count}" />
<Run Text=" 个徽章" />
</TextBlock>-->
</StackPanel>
</spp:OptionsDisplayItem.Content>
</spp:OptionsDisplayItem>
<spp:OptionsDisplayItem
Grid.Row="1"
Grid.ColumnSpan="2"
Description="{Binding IdleGameList.Count, Converter={StaticResource StringFormatConverter}, ConverterParameter=SteamIdle_RemainingGames_}"
Expands="True"
Header="挂卡游戏列表"
IsExpanded="True">
<spp:OptionsDisplayItem.Icon>
<ui:ImageIcon Source="{Binding Icon, Source={x:Static s:Plugin.Instance}, Mode=OneWay, Converter={StaticResource BitmapAssetValueConverter}}" />
</spp:OptionsDisplayItem.Icon>
<spp:OptionsDisplayItem.Content>
<Panel>
<StackPanel
Margin="0,30"
VerticalAlignment="Center"
Spacing="8">
<TextBlock
HorizontalAlignment="Center"
IsVisible="{Binding !IdleGameList.Count}"
Text="未获取到挂卡游戏数据" />
</StackPanel>
<ItemsRepeater DockPanel.Dock="Bottom" ItemsSource="{Binding IdleGameList}">
<ItemsRepeater.Layout>
<UniformGridLayout
ItemsJustification="Start"
ItemsStretch="Uniform"
MinColumnSpacing="5"
MinItemHeight="160"
MinItemWidth="280"
MinRowSpacing="5" />
</ItemsRepeater.Layout>
<ItemsRepeater.ItemTemplate>
<DataTemplate DataType="spp:IdleApp">
<spp:AppItem
Title="{Binding App.DisplayName}"
ContextFlyout="{StaticResource SharedMenuFlyout}"
Description="{Binding Tags}"
MoreFlyout="{StaticResource SharedMenuFlyout}"
Tags="{Binding AppId}">
<spp:AppItem.Status>
<Border Classes="Status">
<Panel>
<WrapPanel IsVisible="{Binding App.Process, Converter={StaticResource IsNullConverter}, ConverterParameter=invert, Mode=OneWay}">
<Ellipse
Width="6"
Height="6"
Margin="0,0,5,0"
Fill="#47A42B" />
<TextBlock
VerticalAlignment="Center"
FontSize="12"
Text="{Binding Path=Res.GameList_RunState, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
</WrapPanel>
<WrapPanel IsVisible="{Binding App.Process, Converter={StaticResource IsNullConverter}, Mode=OneWay}">
<Ellipse
Width="6"
Height="6"
Margin="0,0,5,0"
Fill="#FF4F58" />
<TextBlock
VerticalAlignment="Center"
FontSize="12"
Text="{Binding Path=Res.GameList_StopState, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
</WrapPanel>
</Panel>
</Border>
</spp:AppItem.Status>
<!--<spp:AppItem.ActionButton>
</spp:OptionsDisplayItem.Content>
</spp:OptionsDisplayItem>
<spp:OptionsDisplayItem
Grid.Row="1"
Grid.ColumnSpan="2"
Description="{Binding IdleGameList.Count, Converter={StaticResource StringFormatConverter}, ConverterParameter=SteamIdle_RemainingGames_}"
Expands="True"
Header="挂卡游戏列表"
IsExpanded="True">
<spp:OptionsDisplayItem.Icon>
<ui:ImageIcon Source="{Binding Icon, Source={x:Static s:Plugin.Instance}, Mode=OneWay, Converter={StaticResource BitmapAssetValueConverter}}" />
</spp:OptionsDisplayItem.Icon>
<spp:OptionsDisplayItem.Content>
<Panel>
<StackPanel
Margin="0,30"
VerticalAlignment="Center"
Spacing="8">
<TextBlock
HorizontalAlignment="Center"
IsVisible="{Binding !IdleGameList.Count}"
Text="未获取到挂卡游戏数据" />
</StackPanel>
<ItemsRepeater DockPanel.Dock="Bottom" ItemsSource="{Binding IdleGameList}">
<ItemsRepeater.Layout>
<UniformGridLayout
ItemsJustification="Start"
ItemsStretch="Uniform"
MinColumnSpacing="5"
MinItemHeight="160"
MinItemWidth="280"
MinRowSpacing="5" />
</ItemsRepeater.Layout>
<ItemsRepeater.ItemTemplate>
<DataTemplate DataType="spp:IdleApp">
<spp:AppItem
Title="{Binding App.DisplayName}"
ContextFlyout="{StaticResource SharedMenuFlyout}"
Description="{Binding Tags}"
MoreFlyout="{StaticResource SharedMenuFlyout}"
Tags="{Binding AppId}">
<spp:AppItem.Status>
<Border Classes="Status">
<Panel>
<WrapPanel IsVisible="{Binding App.Process, Converter={StaticResource IsNullConverter}, ConverterParameter=invert, Mode=OneWay}">
<Ellipse
Width="6"
Height="6"
Margin="0,0,5,0"
Fill="#47A42B" />
<TextBlock
VerticalAlignment="Center"
FontSize="12"
Text="{Binding Path=Res.GameList_RunState, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
</WrapPanel>
<WrapPanel IsVisible="{Binding App.Process, Converter={StaticResource IsNullConverter}, Mode=OneWay}">
<Ellipse
Width="6"
Height="6"
Margin="0,0,5,0"
Fill="#FF4F58" />
<TextBlock
VerticalAlignment="Center"
FontSize="12"
Text="{Binding Path=Res.GameList_StopState, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
</WrapPanel>
</Panel>
</Border>
</spp:AppItem.Status>
<!--<spp:AppItem.ActionButton>
<Button
Command="{Binding $parent[spp:PageBase].((spp:IdleAppsPageViewModel)DataContext).DeleteButtonCommand}"
CommandParameter="{Binding}"
@@ -586,9 +588,9 @@
</Viewbox>
</Button>
</spp:AppItem.ActionButton>-->
<spp:AppItem.Image>
<Panel x:Name="ItemImage">
<!--<i:Interaction.Behaviors>
<spp:AppItem.Image>
<Panel x:Name="ItemImage">
<!--<i:Interaction.Behaviors>
<ia:EventTriggerBehavior EventName="PointerEntered" SourceObject="{Binding #ItemImage}">
<ia:ChangePropertyAction
PropertyName="IsVisible"
@@ -602,50 +604,50 @@
Value="False" />
</ia:EventTriggerBehavior>
</i:Interaction.Behaviors>-->
<Panel MinWidth="{Binding $parent[spp:AppItem].MinWidth}" MaxWidth="{Binding $parent[spp:AppItem].MaxWidth}">
<spp:Image2
Name="AppImage"
DecodeWidth="120"
FallbackSource="avares://BD.WTTS.Client.Plugins.GameList/UI/Assets/defaultappimage.png"
RenderOptions.BitmapInterpolationMode="HighQuality"
Source="{Binding App.LibraryGridStream^}"
Stretch="UniformToFill" />
<TextBlock
Margin="8,0"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Foreground="{DynamicResource TextFillColorPrimaryBrush}"
IsVisible="{Binding #AppImage.IsFailed}"
Text="{Binding App.DisplayName}"
TextAlignment="Center"
TextWrapping="WrapWithOverflow"
Theme="{StaticResource BodyStrongTextBlockStyle}" />
<Panel MinWidth="{Binding $parent[spp:AppItem].MinWidth}" MaxWidth="{Binding $parent[spp:AppItem].MaxWidth}">
<spp:Image2
Name="AppImage"
DecodeWidth="120"
FallbackSource="avares://BD.WTTS.Client.Plugins.GameList/UI/Assets/defaultappimage.png"
RenderOptions.BitmapInterpolationMode="HighQuality"
Source="{Binding App.LibraryGridStream^}"
Stretch="UniformToFill" />
<TextBlock
Margin="8,0"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Foreground="{DynamicResource TextFillColorPrimaryBrush}"
IsVisible="{Binding #AppImage.IsFailed}"
Text="{Binding App.DisplayName}"
TextAlignment="Center"
TextWrapping="WrapWithOverflow"
Theme="{StaticResource BodyStrongTextBlockStyle}" />
<Border
Margin="10,0,10,-3"
HorizontalAlignment="Center"
VerticalAlignment="Bottom"
Background="{DynamicResource SolidBackgroundFillColorBaseBrush}"
CornerRadius="3"
IsVisible="{Binding App.IsInstalled}">
<DockPanel Margin="10,3" HorizontalAlignment="Center">
<TextBlock
Margin="0,0,5,0"
VerticalAlignment="Center"
FontSize="11"
Foreground="{DynamicResource TextFillColorPrimaryBrush}"
IsVisible="{Binding App.InstalledDrive, Converter={StaticResource IsNullConverter}, ConverterParameter=invert}"
Text="{Binding App.InstalledDrive}" />
<TextBlock
VerticalAlignment="Center"
FontSize="11"
Foreground="{DynamicResource TextFillColorPrimaryBrush}"
Text="{Binding App.SizeOnDisk, Converter={StaticResource StringFormatConverter}, ConverterParameter=size}"
TextWrapping="Wrap" />
</DockPanel>
</Border>
</Panel>
<!--<Panel
<Border
Margin="10,0,10,-3"
HorizontalAlignment="Center"
VerticalAlignment="Bottom"
Background="{DynamicResource SolidBackgroundFillColorBaseBrush}"
CornerRadius="3"
IsVisible="{Binding App.IsInstalled}">
<DockPanel Margin="10,3" HorizontalAlignment="Center">
<TextBlock
Margin="0,0,5,0"
VerticalAlignment="Center"
FontSize="11"
Foreground="{DynamicResource TextFillColorPrimaryBrush}"
IsVisible="{Binding App.InstalledDrive, Converter={StaticResource IsNullConverter}, ConverterParameter=invert}"
Text="{Binding App.InstalledDrive}" />
<TextBlock
VerticalAlignment="Center"
FontSize="11"
Foreground="{DynamicResource TextFillColorPrimaryBrush}"
Text="{Binding App.SizeOnDisk, Converter={StaticResource StringFormatConverter}, ConverterParameter=size}"
TextWrapping="Wrap" />
</DockPanel>
</Border>
</Panel>
<!--<Panel
x:Name="ItemBox"
Background="{DynamicResource SolidBackgroundFillColorBaseBrush}"
IsVisible="False"
@@ -692,18 +694,16 @@
</Viewbox>
</Button>
</Panel>-->
</Panel>
</spp:AppItem.Image>
</spp:AppItem>
</DataTemplate>
</ItemsRepeater.ItemTemplate>
</ItemsRepeater>
</Panel>
</spp:OptionsDisplayItem.Content>
</spp:OptionsDisplayItem>
</Grid>
</spp:ContentLoader>
<!--<spp:IdleSteamLoginPage />-->
<!--</Carousel>-->
</Panel>
</spp:AppItem.Image>
</spp:AppItem>
</DataTemplate>
</ItemsRepeater.ItemTemplate>
</ItemsRepeater>
</Panel>
</spp:OptionsDisplayItem.Content>
</spp:OptionsDisplayItem>
</Grid>
</spp:ContentLoader>
</Carousel>
</spp:PageBase>
@@ -154,6 +154,7 @@ public static class ThirdPartyLoginHelper
AppResources.User_Register);
close?.Invoke();
Toast.Show(ToastIcon.Success, msg);
await UserService.Current.RefreshShopTokenAsync();
}
static IDisposable? serverDisposable;
+18 -1
View File
@@ -65,10 +65,24 @@ public static class Migrations
{
MoveAccelerator();
}
//var v_3_0_rc4 = new Version(3, 0, 407);
//if (IApplication.IsDesktop() && PreviousVersion < v_3_0_rc4)
//{
// ClearScriptFile();
//}
}
}
}
static void ClearScriptFile()
{
var scriptDirectory = Path.Combine(IOPath.AppDataDirectory, AssemblyInfo.Plugins, AssemblyInfo.Accelerator, DirName_Scripts);
Directory.Delete(scriptDirectory, true);
var scriptCacheDirectory = Path.Combine(IOPath.CacheDirectory, AssemblyInfo.Plugins, AssemblyInfo.Accelerator, DirName_BuildScripts);
Directory.Delete(scriptCacheDirectory, true);
}
static void MoveDirByScripts(string rootPath, string? newDirName = null)
{
try
@@ -77,11 +91,14 @@ public static class Migrations
AssemblyInfo.Plugins,
AssemblyInfo.Accelerator,
newDirName ?? DirName_Scripts);
if (!Directory.Exists(newPath))
var dir = new DirectoryInfo(newPath);
if (!dir.Exists)
{
var oldPath = Path.Combine(rootPath, DirName_Scripts);
if (Directory.Exists(oldPath))
{
//创建至父级目录
dir.Parent?.Create();
Directory.Move(oldPath, newPath);
}
}
@@ -61,6 +61,16 @@ sealed class MicroServiceClient : MicroServiceClientBase
}
}
public sealed override async Task SaveShopAuthTokenAsync(JWTEntity authToken)
{
var user = await userManager.GetCurrentUserAsync();
if (user != null)
{
user.ShopAuthToken = authToken;
await userManager.SetCurrentUserAsync(user);
}
}
public sealed override async Task OnLoginedAsync(
IReadOnlyPhoneNumber? phoneNumber,
ILoginResponse response)
@@ -199,6 +199,39 @@ public sealed class UserService : ReactiveObject
await RefreshUserAsync(user, refreshCurrentUser);
}
public async Task RefreshShopTokenAsync(bool refreshToken = true)
{
var currentUser = await userManager.GetCurrentUserAsync();
if (refreshToken && currentUser != null)
{
var token = await csc.Shop.GetShopUserTokenAsync();
if (token.IsSuccess)
{
currentUser.ShopAuthToken = token.Content;
await userManager.SetCurrentUserAsync(currentUser);
}
}
}
/// <summary>
/// 带登录状态跳转 Url
/// </summary>
/// <param name="url">跳转地址</param>
/// <returns></returns>
public async Task OpenAuthUrl(string url)
{
var token = await userManager.GetAuthTokenAsync();
if (token == null)
{
Browser2.Open(url.ToString());
}
else
{
Browser2.Open(string.Format(Constants.Urls.OfficialWebsite_Fast_Login_, token.AccessToken, HttpUtility.UrlEncode(token.ExpiresIn.ToString()), HttpUtility.UrlEncode(url)));
}
}
public async Task RefreshUserAvatarAsync()
{
if (User != null)
@@ -86,6 +86,9 @@ public sealed partial class DebugPageViewModel : TabItemViewModel
break;
case "updatetray":
break;
case "gc":
GC.Collect();
break;
case "login":
if (cmds.Length > 1)
@@ -1,4 +1,5 @@
using BD.WTTS.Client.Resources;
using DynamicData;
using static BD.WTTS.Services.IMicroServiceClient;
namespace BD.WTTS.UI.ViewModels;
@@ -21,7 +22,8 @@ public sealed class HomePageViewModel : TabItemViewModel
public ObservableCollection<AdvertisementDTO> NavigationBanners { get; set; }
[Reactive]
public ObservableCollection<List<ShopRecommendGoodItem>> Shops { get; set; }
//public ObservableCollection<List<ShopRecommendGoodItem>> Shops { get; set; }
public ObservableCollection<ShopRecommendGoodItem> Shops { get; set; }
public ICommand NavgationToMenuPageCommand { get; }
@@ -31,7 +33,8 @@ public sealed class HomePageViewModel : TabItemViewModel
Articles = new ObservableCollection<ArticleItemDTO>() { null, null, null };
#pragma warning restore CS8625 // 无法将 null 字面量转换为非 null 的引用类型。
NavigationBanners = new ObservableCollection<AdvertisementDTO>();
Shops = new ObservableCollection<List<ShopRecommendGoodItem>>();
//Shops = new ObservableCollection<List<ShopRecommendGoodItem>>();
Shops = new ObservableCollection<ShopRecommendGoodItem>();
NavgationToMenuPageCommand = ReactiveCommand.Create<TabItemViewModel>(NavgationToMenuPage);
@@ -55,12 +58,12 @@ public sealed class HomePageViewModel : TabItemViewModel
var result2 = await Instance.Shop.RecommendGoods();
if (result2.IsSuccess && result2.Content != null)
{
Shops.Clear();
var data = result2.Content
.Select((v, i) => new { v, i })
.GroupBy(x => x.i / 4)
.Select(g => g.Select(x => x.v).ToList());
Shops.Add(data);
//Shops.Clear();
//var data = result2.Content
// .Select((v, i) => new { v, i })
// .GroupBy(x => x.i / 4)
// .Select(g => g.Select(x => x.v).ToList());
Shops.Add(result2.Content);
}
var result3 = await Instance.Advertisement.All(AdvertisementType.DeskTopHomeBanner);
@@ -45,6 +45,7 @@ public partial class LoginOrRegisterWindowViewModel : WindowViewModel, SendSmsUI
var msg = Strings.Success_.Format((rsp?.IsLoginOrRegister ?? false) ? Strings.User_Login : Strings.User_Register);
close?.Invoke();
Toast.Show(ToastIcon.Success, msg);
await UserService.Current.RefreshShopTokenAsync();
}
async Task SendSmsAsync() => await this.SendSmsAsync(() => new()