mirror of
https://jihulab.com/DGP-Studio/Snap.Hutao.git
synced 2025-11-19 21:02:53 +08:00
add system backdrop mica
This commit is contained in:
@@ -11,7 +11,7 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.1.1" />
|
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.1.2" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
<IsPackable>false</IsPackable>
|
<IsPackable>false</IsPackable>
|
||||||
<LangVersion>latest</LangVersion>
|
<LangVersion>latest</LangVersion>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
|
<Platforms>x64</Platforms>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@@ -62,24 +62,24 @@ Global
|
|||||||
{DCA5678C-896E-49FB-97A7-5A504A5CFF17}.Release|Any CPU.Build.0 = Release|Any CPU
|
{DCA5678C-896E-49FB-97A7-5A504A5CFF17}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
{DCA5678C-896E-49FB-97A7-5A504A5CFF17}.Release|arm64.ActiveCfg = Release|Any CPU
|
{DCA5678C-896E-49FB-97A7-5A504A5CFF17}.Release|arm64.ActiveCfg = Release|Any CPU
|
||||||
{DCA5678C-896E-49FB-97A7-5A504A5CFF17}.Release|arm64.Build.0 = Release|Any CPU
|
{DCA5678C-896E-49FB-97A7-5A504A5CFF17}.Release|arm64.Build.0 = Release|Any CPU
|
||||||
{DCA5678C-896E-49FB-97A7-5A504A5CFF17}.Release|x64.ActiveCfg = Release|Any CPU
|
{DCA5678C-896E-49FB-97A7-5A504A5CFF17}.Release|x64.ActiveCfg = Release|x64
|
||||||
{DCA5678C-896E-49FB-97A7-5A504A5CFF17}.Release|x64.Build.0 = Release|Any CPU
|
{DCA5678C-896E-49FB-97A7-5A504A5CFF17}.Release|x64.Build.0 = Release|x64
|
||||||
{DCA5678C-896E-49FB-97A7-5A504A5CFF17}.Release|x86.ActiveCfg = Release|Any CPU
|
{DCA5678C-896E-49FB-97A7-5A504A5CFF17}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
{DCA5678C-896E-49FB-97A7-5A504A5CFF17}.Release|x86.Build.0 = Release|Any CPU
|
{DCA5678C-896E-49FB-97A7-5A504A5CFF17}.Release|x86.Build.0 = Release|Any CPU
|
||||||
{8B96721E-5604-47D2-9B72-06FEBAD0CE00}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{8B96721E-5604-47D2-9B72-06FEBAD0CE00}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{8B96721E-5604-47D2-9B72-06FEBAD0CE00}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{8B96721E-5604-47D2-9B72-06FEBAD0CE00}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{8B96721E-5604-47D2-9B72-06FEBAD0CE00}.Debug|arm64.ActiveCfg = Debug|Any CPU
|
{8B96721E-5604-47D2-9B72-06FEBAD0CE00}.Debug|arm64.ActiveCfg = Debug|Any CPU
|
||||||
{8B96721E-5604-47D2-9B72-06FEBAD0CE00}.Debug|arm64.Build.0 = Debug|Any CPU
|
{8B96721E-5604-47D2-9B72-06FEBAD0CE00}.Debug|arm64.Build.0 = Debug|Any CPU
|
||||||
{8B96721E-5604-47D2-9B72-06FEBAD0CE00}.Debug|x64.ActiveCfg = Debug|Any CPU
|
{8B96721E-5604-47D2-9B72-06FEBAD0CE00}.Debug|x64.ActiveCfg = Debug|x64
|
||||||
{8B96721E-5604-47D2-9B72-06FEBAD0CE00}.Debug|x64.Build.0 = Debug|Any CPU
|
{8B96721E-5604-47D2-9B72-06FEBAD0CE00}.Debug|x64.Build.0 = Debug|x64
|
||||||
{8B96721E-5604-47D2-9B72-06FEBAD0CE00}.Debug|x86.ActiveCfg = Debug|Any CPU
|
{8B96721E-5604-47D2-9B72-06FEBAD0CE00}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
{8B96721E-5604-47D2-9B72-06FEBAD0CE00}.Debug|x86.Build.0 = Debug|Any CPU
|
{8B96721E-5604-47D2-9B72-06FEBAD0CE00}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
{8B96721E-5604-47D2-9B72-06FEBAD0CE00}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{8B96721E-5604-47D2-9B72-06FEBAD0CE00}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{8B96721E-5604-47D2-9B72-06FEBAD0CE00}.Release|Any CPU.Build.0 = Release|Any CPU
|
{8B96721E-5604-47D2-9B72-06FEBAD0CE00}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
{8B96721E-5604-47D2-9B72-06FEBAD0CE00}.Release|arm64.ActiveCfg = Release|Any CPU
|
{8B96721E-5604-47D2-9B72-06FEBAD0CE00}.Release|arm64.ActiveCfg = Release|Any CPU
|
||||||
{8B96721E-5604-47D2-9B72-06FEBAD0CE00}.Release|arm64.Build.0 = Release|Any CPU
|
{8B96721E-5604-47D2-9B72-06FEBAD0CE00}.Release|arm64.Build.0 = Release|Any CPU
|
||||||
{8B96721E-5604-47D2-9B72-06FEBAD0CE00}.Release|x64.ActiveCfg = Release|Any CPU
|
{8B96721E-5604-47D2-9B72-06FEBAD0CE00}.Release|x64.ActiveCfg = Release|x64
|
||||||
{8B96721E-5604-47D2-9B72-06FEBAD0CE00}.Release|x64.Build.0 = Release|Any CPU
|
{8B96721E-5604-47D2-9B72-06FEBAD0CE00}.Release|x64.Build.0 = Release|x64
|
||||||
{8B96721E-5604-47D2-9B72-06FEBAD0CE00}.Release|x86.ActiveCfg = Release|Any CPU
|
{8B96721E-5604-47D2-9B72-06FEBAD0CE00}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
{8B96721E-5604-47D2-9B72-06FEBAD0CE00}.Release|x86.Build.0 = Release|Any CPU
|
{8B96721E-5604-47D2-9B72-06FEBAD0CE00}.Release|x86.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
// Copyright (c) DGP Studio. All rights reserved.
|
||||||
|
// Licensed under the MIT license.
|
||||||
|
|
||||||
|
namespace Snap.Hutao.Control.HostBackdrop;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 回退行为
|
||||||
|
/// </summary>
|
||||||
|
public enum BackbdropFallBackBehavior
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 回退到无
|
||||||
|
/// </summary>
|
||||||
|
None,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 回退到亚克力
|
||||||
|
/// </summary>
|
||||||
|
Acrylic,
|
||||||
|
}
|
||||||
184
src/Snap.Hutao/Snap.Hutao/Control/Backdroping/SystemBackdrop.cs
Normal file
184
src/Snap.Hutao/Snap.Hutao/Control/Backdroping/SystemBackdrop.cs
Normal file
@@ -0,0 +1,184 @@
|
|||||||
|
// Copyright (c) DGP Studio. All rights reserved.
|
||||||
|
// Licensed under the MIT license.
|
||||||
|
|
||||||
|
using Microsoft.UI.Composition;
|
||||||
|
using Microsoft.UI.Composition.SystemBackdrops;
|
||||||
|
using Microsoft.UI.Xaml;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using Windows.System;
|
||||||
|
using WinRT;
|
||||||
|
|
||||||
|
namespace Snap.Hutao.Control.HostBackdrop;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 系统背景帮助类
|
||||||
|
/// </summary>
|
||||||
|
public class SystemBackdrop
|
||||||
|
{
|
||||||
|
private readonly Window window;
|
||||||
|
private readonly BackbdropFallBackBehavior fallBackBehavior;
|
||||||
|
|
||||||
|
private WindowsSystemDispatcherQueueHelper? dispatcherQueueHelper;
|
||||||
|
private ISystemBackdropControllerWithTargets? backdropController;
|
||||||
|
private SystemBackdropConfiguration? configurationSource;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 构造一个新的系统背景帮助类
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="window">窗体</param>
|
||||||
|
/// <param name="fallBackBehavior">回退行为</param>
|
||||||
|
public SystemBackdrop(Window window, BackbdropFallBackBehavior fallBackBehavior = BackbdropFallBackBehavior.Acrylic)
|
||||||
|
{
|
||||||
|
this.window = window;
|
||||||
|
this.fallBackBehavior = fallBackBehavior;
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum BackDropType
|
||||||
|
{
|
||||||
|
None,
|
||||||
|
Acrylic,
|
||||||
|
Mica,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 尝试设置背景
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>是否设置成功</returns>
|
||||||
|
public bool TrySetBackdrop()
|
||||||
|
{
|
||||||
|
BackDropType targetBackDropType = ResolveBackdropType();
|
||||||
|
|
||||||
|
if (targetBackDropType == BackDropType.None)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
dispatcherQueueHelper = new WindowsSystemDispatcherQueueHelper();
|
||||||
|
dispatcherQueueHelper.EnsureWindowsSystemDispatcherQueueController();
|
||||||
|
|
||||||
|
// Hooking up the policy object
|
||||||
|
configurationSource = new SystemBackdropConfiguration();
|
||||||
|
window.Activated += WindowActivated;
|
||||||
|
window.Closed += WindowClosed;
|
||||||
|
((FrameworkElement)window.Content).ActualThemeChanged += WindowThemeChanged;
|
||||||
|
|
||||||
|
// Initial configuration state.
|
||||||
|
configurationSource.IsInputActive = true;
|
||||||
|
SetConfigurationSourceTheme();
|
||||||
|
|
||||||
|
backdropController = targetBackDropType switch
|
||||||
|
{
|
||||||
|
BackDropType.Mica => new MicaController(),
|
||||||
|
BackDropType.Acrylic => new DesktopAcrylicController(),
|
||||||
|
_ => throw Must.NeverHappen(),
|
||||||
|
};
|
||||||
|
|
||||||
|
ICompositionSupportsSystemBackdrop target = window.As<ICompositionSupportsSystemBackdrop>();
|
||||||
|
backdropController.AddSystemBackdropTarget(target);
|
||||||
|
backdropController.SetSystemBackdropConfiguration(configurationSource);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private BackDropType ResolveBackdropType()
|
||||||
|
{
|
||||||
|
BackDropType targetBackDropType = BackDropType.None;
|
||||||
|
if (MicaController.IsSupported())
|
||||||
|
{
|
||||||
|
targetBackDropType = BackDropType.Mica;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (fallBackBehavior == BackbdropFallBackBehavior.Acrylic)
|
||||||
|
{
|
||||||
|
if (DesktopAcrylicController.IsSupported())
|
||||||
|
{
|
||||||
|
targetBackDropType = BackDropType.Acrylic;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return targetBackDropType;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WindowActivated(object sender, WindowActivatedEventArgs args)
|
||||||
|
{
|
||||||
|
Must.NotNull(configurationSource!);
|
||||||
|
configurationSource.IsInputActive = args.WindowActivationState != WindowActivationState.Deactivated;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WindowClosed(object sender, WindowEventArgs args)
|
||||||
|
{
|
||||||
|
// Make sure any Mica/Acrylic controller is disposed so it doesn't try to
|
||||||
|
// use this closed window.
|
||||||
|
if (backdropController != null)
|
||||||
|
{
|
||||||
|
backdropController.Dispose();
|
||||||
|
backdropController = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
window.Activated -= WindowActivated;
|
||||||
|
configurationSource = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WindowThemeChanged(FrameworkElement sender, object args)
|
||||||
|
{
|
||||||
|
if (configurationSource != null)
|
||||||
|
{
|
||||||
|
SetConfigurationSourceTheme();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetConfigurationSourceTheme()
|
||||||
|
{
|
||||||
|
Must.NotNull(configurationSource!).Theme = ((FrameworkElement)window.Content).ActualTheme switch
|
||||||
|
{
|
||||||
|
ElementTheme.Dark => SystemBackdropTheme.Dark,
|
||||||
|
ElementTheme.Light => SystemBackdropTheme.Light,
|
||||||
|
ElementTheme.Default => SystemBackdropTheme.Default,
|
||||||
|
_ => throw Must.NeverHappen(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private class WindowsSystemDispatcherQueueHelper
|
||||||
|
{
|
||||||
|
private object dispatcherQueueController = null!;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 确保系统调度队列控制器存在
|
||||||
|
/// </summary>
|
||||||
|
public void EnsureWindowsSystemDispatcherQueueController()
|
||||||
|
{
|
||||||
|
if (DispatcherQueue.GetForCurrentThread() != null)
|
||||||
|
{
|
||||||
|
// one already exists, so we'll just use it.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dispatcherQueueController == null)
|
||||||
|
{
|
||||||
|
DispatcherQueueOptions options;
|
||||||
|
options.DwSize = Marshal.SizeOf(typeof(DispatcherQueueOptions));
|
||||||
|
options.ThreadType = 2; // DQTYPE_THREAD_CURRENT
|
||||||
|
options.ApartmentType = 2; // DQTAT_COM_STA
|
||||||
|
|
||||||
|
_ = CreateDispatcherQueueController(options, ref dispatcherQueueController!);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[DllImport("CoreMessaging.dll")]
|
||||||
|
private static extern int CreateDispatcherQueueController(
|
||||||
|
[In] DispatcherQueueOptions options,
|
||||||
|
[In, Out, MarshalAs(UnmanagedType.IUnknown)] ref object dispatcherQueueController);
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
private struct DispatcherQueueOptions
|
||||||
|
{
|
||||||
|
internal int DwSize;
|
||||||
|
internal int ThreadType;
|
||||||
|
internal int ApartmentType;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,4 +12,24 @@ internal static class SettingKeys
|
|||||||
/// 上次打开时App的版本
|
/// 上次打开时App的版本
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public const string LastAppVersion = "LastAppVersion";
|
public const string LastAppVersion = "LastAppVersion";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 窗体左侧
|
||||||
|
/// </summary>
|
||||||
|
public const string WindowLeft = "WindowLeft";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 窗体顶部
|
||||||
|
/// </summary>
|
||||||
|
public const string WindowTop = "WindowTop";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 窗体右侧
|
||||||
|
/// </summary>
|
||||||
|
public const string WindowRight = "WindowRight";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 窗体底部
|
||||||
|
/// </summary>
|
||||||
|
public const string WindowBottom = "WindowBottom";
|
||||||
}
|
}
|
||||||
30
src/Snap.Hutao/Snap.Hutao/Core/Win32/POINT.cs
Normal file
30
src/Snap.Hutao/Snap.Hutao/Core/Win32/POINT.cs
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
// Copyright (c) DGP Studio. All rights reserved.
|
||||||
|
// Licensed under the MIT license.
|
||||||
|
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace Snap.Hutao.Core.Win32;
|
||||||
|
|
||||||
|
[SuppressMessage("", "SA1600")]
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
public struct POINT
|
||||||
|
{
|
||||||
|
public int X;
|
||||||
|
public int Y;
|
||||||
|
|
||||||
|
public POINT(int x, int y)
|
||||||
|
{
|
||||||
|
this.X = x;
|
||||||
|
this.Y = y;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static implicit operator System.Drawing.Point(POINT p)
|
||||||
|
{
|
||||||
|
return new System.Drawing.Point(p.X, p.Y);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static implicit operator POINT(System.Drawing.Point p)
|
||||||
|
{
|
||||||
|
return new POINT(p.X, p.Y);
|
||||||
|
}
|
||||||
|
}
|
||||||
129
src/Snap.Hutao/Snap.Hutao/Core/Win32/RECT.cs
Normal file
129
src/Snap.Hutao/Snap.Hutao/Core/Win32/RECT.cs
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
// Copyright (c) DGP Studio. All rights reserved.
|
||||||
|
// Licensed under the MIT license.
|
||||||
|
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace Snap.Hutao.Core.Win32;
|
||||||
|
|
||||||
|
[SuppressMessage("", "SA1132")]
|
||||||
|
[SuppressMessage("", "SA1600")]
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
public struct RECT
|
||||||
|
{
|
||||||
|
public int Left, Top, Right, Bottom;
|
||||||
|
|
||||||
|
public RECT(int left, int top, int right, int bottom)
|
||||||
|
{
|
||||||
|
Left = left;
|
||||||
|
Top = top;
|
||||||
|
Right = right;
|
||||||
|
Bottom = bottom;
|
||||||
|
}
|
||||||
|
|
||||||
|
public RECT(System.Drawing.Rectangle r)
|
||||||
|
: this(r.Left, r.Top, r.Right, r.Bottom)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public int X
|
||||||
|
{
|
||||||
|
get => Left;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
Right -= Left - value;
|
||||||
|
Left = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int Y
|
||||||
|
{
|
||||||
|
get => Top;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
Bottom -= Top - value;
|
||||||
|
Top = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int Height
|
||||||
|
{
|
||||||
|
get => Bottom - Top;
|
||||||
|
set => Bottom = value + Top;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int Width
|
||||||
|
{
|
||||||
|
get => Right - Left;
|
||||||
|
set => Right = value + Left;
|
||||||
|
}
|
||||||
|
|
||||||
|
public System.Drawing.Point Location
|
||||||
|
{
|
||||||
|
get => new(Left, Top);
|
||||||
|
set
|
||||||
|
{
|
||||||
|
X = value.X;
|
||||||
|
Y = value.Y;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public System.Drawing.Size Size
|
||||||
|
{
|
||||||
|
get => new(Width, Height);
|
||||||
|
set
|
||||||
|
{
|
||||||
|
Width = value.Width;
|
||||||
|
Height = value.Height;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static implicit operator System.Drawing.Rectangle(RECT r)
|
||||||
|
{
|
||||||
|
return new System.Drawing.Rectangle(r.Left, r.Top, r.Width, r.Height);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static implicit operator RECT(System.Drawing.Rectangle r)
|
||||||
|
{
|
||||||
|
return new RECT(r);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool operator ==(RECT r1, RECT r2)
|
||||||
|
{
|
||||||
|
return r1.Equals(r2);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool operator !=(RECT r1, RECT r2)
|
||||||
|
{
|
||||||
|
return !r1.Equals(r2);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Equals(RECT r)
|
||||||
|
{
|
||||||
|
return r.Left == Left && r.Top == Top && r.Right == Right && r.Bottom == Bottom;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool Equals(object? obj)
|
||||||
|
{
|
||||||
|
if (obj is RECT rect)
|
||||||
|
{
|
||||||
|
return Equals(rect);
|
||||||
|
}
|
||||||
|
else if (obj is System.Drawing.Rectangle rectangle)
|
||||||
|
{
|
||||||
|
return Equals(new RECT(rectangle));
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int GetHashCode()
|
||||||
|
{
|
||||||
|
return ((System.Drawing.Rectangle)this).GetHashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return string.Format(CultureInfo.CurrentCulture, "{{Left={0},Top={1},Right={2},Bottom={3}}}", Left, Top, Right, Bottom);
|
||||||
|
}
|
||||||
|
}
|
||||||
89
src/Snap.Hutao/Snap.Hutao/Core/Win32/ShowWindowCommand.cs
Normal file
89
src/Snap.Hutao/Snap.Hutao/Core/Win32/ShowWindowCommand.cs
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
// Copyright (c) DGP Studio. All rights reserved.
|
||||||
|
// Licensed under the MIT license.
|
||||||
|
|
||||||
|
namespace Snap.Hutao.Core.Win32;
|
||||||
|
|
||||||
|
[SuppressMessage("", "SA1600")]
|
||||||
|
public enum ShowWindowCommand
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Hides the window and activates another window.
|
||||||
|
/// </summary>
|
||||||
|
Hide = 0,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Activates and displays a window. If the window is minimized or
|
||||||
|
/// maximized, the system restores it to its original size and position.
|
||||||
|
/// An application should specify this flag when displaying the window
|
||||||
|
/// for the first time.
|
||||||
|
/// </summary>
|
||||||
|
Normal = 1,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Activates the window and displays it as a minimized window.
|
||||||
|
/// </summary>
|
||||||
|
ShowMinimized = 2,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Maximizes the specified window.
|
||||||
|
/// </summary>
|
||||||
|
Maximize = 3, // is this the right value?
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Activates the window and displays it as a maximized window.
|
||||||
|
/// </summary>
|
||||||
|
ShowMaximized = 3,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Displays a window in its most recent size and position. This value
|
||||||
|
/// is similar to <see cref="Win32.ShowWindowCommand.Normal"/>, except
|
||||||
|
/// the window is not activated.
|
||||||
|
/// </summary>
|
||||||
|
ShowNoActivate = 4,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Activates the window and displays it in its current size and position.
|
||||||
|
/// </summary>
|
||||||
|
Show = 5,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Minimizes the specified window and activates the next top-level
|
||||||
|
/// window in the Z order.
|
||||||
|
/// </summary>
|
||||||
|
Minimize = 6,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Displays the window as a minimized window. This value is similar to
|
||||||
|
/// <see cref="Win32.ShowWindowCommand.ShowMinimized"/>, except the
|
||||||
|
/// window is not activated.
|
||||||
|
/// </summary>
|
||||||
|
ShowMinNoActive = 7,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Displays the window in its current size and position. This value is
|
||||||
|
/// similar to <see cref="Win32.ShowWindowCommand.Show"/>, except the
|
||||||
|
/// window is not activated.
|
||||||
|
/// </summary>
|
||||||
|
ShowNA = 8,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Activates and displays the window. If the window is minimized or
|
||||||
|
/// maximized, the system restores it to its original size and position.
|
||||||
|
/// An application should specify this flag when restoring a minimized window.
|
||||||
|
/// </summary>
|
||||||
|
Restore = 9,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the show state based on the SW_* value specified in the
|
||||||
|
/// STARTUPINFO structure passed to the CreateProcess function by the
|
||||||
|
/// program that started the application.
|
||||||
|
/// </summary>
|
||||||
|
ShowDefault = 10,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// <b>Windows 2000/XP:</b> Minimizes a window, even if the thread
|
||||||
|
/// that owns the window is not responding. This flag should only be
|
||||||
|
/// used when minimizing windows from a different thread.
|
||||||
|
/// </summary>
|
||||||
|
ForceMinimize = 11,
|
||||||
|
}
|
||||||
65
src/Snap.Hutao/Snap.Hutao/Core/Win32/User32.cs
Normal file
65
src/Snap.Hutao/Snap.Hutao/Core/Win32/User32.cs
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
// Copyright (c) DGP Studio. All rights reserved.
|
||||||
|
// Licensed under the MIT license.
|
||||||
|
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace Snap.Hutao.Core.Win32;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 包含 user32.dll 平台调用的代码
|
||||||
|
/// </summary>
|
||||||
|
internal static class User32
|
||||||
|
{
|
||||||
|
[SuppressMessage("", "SA1600")]
|
||||||
|
[DllImport("user32.dll", SetLastError = true)]
|
||||||
|
[return: MarshalAs(UnmanagedType.Bool)]
|
||||||
|
public static extern bool GetWindowPlacement(IntPtr hWnd, ref WINDOWPLACEMENT lpwndpl);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the show state and the restored, minimized, and maximized positions of the specified window.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="hWnd">
|
||||||
|
/// A handle to the window.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="lpwndpl">
|
||||||
|
/// A pointer to a WINDOWPLACEMENT structure that specifies the new show state and window positions.
|
||||||
|
/// <para>
|
||||||
|
/// Before calling SetWindowPlacement, set the length member of the WINDOWPLACEMENT structure to sizeof(WINDOWPLACEMENT). SetWindowPlacement fails if the length member is not set correctly.
|
||||||
|
/// </para>
|
||||||
|
/// </param>
|
||||||
|
/// <returns>
|
||||||
|
/// If the function succeeds, the return value is nonzero.
|
||||||
|
/// <para>
|
||||||
|
/// If the function fails, the return value is zero. To get extended error information, call GetLastError.
|
||||||
|
/// </para>
|
||||||
|
/// </returns>
|
||||||
|
[DllImport("user32.dll", SetLastError = true)]
|
||||||
|
[return: MarshalAs(UnmanagedType.Bool)]
|
||||||
|
public static extern bool SetWindowPlacement(IntPtr hWnd, [In] ref WINDOWPLACEMENT lpwndpl);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Changes the text of the specified window's title bar (if it has one). If the specified window is a control, the
|
||||||
|
/// text of the control is changed. However, SetWindowText cannot change the text of a control in another application.
|
||||||
|
/// <para>
|
||||||
|
/// Go to <see href="https://msdn.microsoft.com/en-us/library/windows/desktop/ms633546%28v=vs.85%29.aspx"/> for more
|
||||||
|
/// information
|
||||||
|
/// </para>
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="hwnd">C++ ( hWnd [in]. Type: HWND )<br />A handle to the window or control whose text is to be changed.</param>
|
||||||
|
/// <param name="lpString">C++ ( lpString [in, optional]. Type: LPCTSTR )<br />The new title or control text.</param>
|
||||||
|
/// <returns>
|
||||||
|
/// If the function succeeds, the return value is nonzero. If the function fails, the return value is zero.<br />
|
||||||
|
/// To get extended error information, call GetLastError.
|
||||||
|
/// </returns>
|
||||||
|
/// <remarks>
|
||||||
|
/// If the target window is owned by the current process, <see cref="SetWindowText" /> causes a WM_SETTEXT message to
|
||||||
|
/// be sent to the specified window or control. If the control is a list box control created with the WS_CAPTION style,
|
||||||
|
/// however, <see cref="SetWindowText" /> sets the text for the control, not for the list box entries.<br />To set the
|
||||||
|
/// text of a control in another process, send the WM_SETTEXT message directly instead of calling
|
||||||
|
/// <see cref="SetWindowText" />. The <see cref="SetWindowText" /> function does not expand tab characters (ASCII code
|
||||||
|
/// 0x09). Tab characters are displayed as vertical bar(|) characters.<br />For an example go to
|
||||||
|
/// <see href="https://msdn.microsoft.com/en-us/library/windows/desktop/ms644928%28v=vs.85%29.aspx#sending">Sending a Message. </see>
|
||||||
|
/// </remarks>
|
||||||
|
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
|
||||||
|
public static extern bool SetWindowText(IntPtr hwnd, string lpString);
|
||||||
|
}
|
||||||
60
src/Snap.Hutao/Snap.Hutao/Core/Win32/WINDOWPLACEMENT.cs
Normal file
60
src/Snap.Hutao/Snap.Hutao/Core/Win32/WINDOWPLACEMENT.cs
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
// Copyright (c) DGP Studio. All rights reserved.
|
||||||
|
// Licensed under the MIT license.
|
||||||
|
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace Snap.Hutao.Core.Win32;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Contains information about the placement of a window on the screen.
|
||||||
|
/// </summary>
|
||||||
|
[Serializable]
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
internal struct WINDOWPLACEMENT
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The length of the structure, in bytes. Before calling the GetWindowPlacement or SetWindowPlacement functions, set this member to sizeof(WINDOWPLACEMENT).
|
||||||
|
/// <para>
|
||||||
|
/// GetWindowPlacement and SetWindowPlacement fail if this member is not set correctly.
|
||||||
|
/// </para>
|
||||||
|
/// </summary>
|
||||||
|
public int Length;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Specifies flags that control the position of the minimized window and the method by which the window is restored.
|
||||||
|
/// </summary>
|
||||||
|
public int Flags;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The current show state of the window.
|
||||||
|
/// </summary>
|
||||||
|
public ShowWindowCommand ShowCmd;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The coordinates of the window's upper-left corner when the window is minimized.
|
||||||
|
/// </summary>
|
||||||
|
public POINT MinPosition;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The coordinates of the window's upper-left corner when the window is maximized.
|
||||||
|
/// </summary>
|
||||||
|
public POINT MaxPosition;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The window's coordinates when the window is in the restored position.
|
||||||
|
/// </summary>
|
||||||
|
public RECT NormalPosition;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the default (empty) value.
|
||||||
|
/// </summary>
|
||||||
|
public static WINDOWPLACEMENT Default
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
WINDOWPLACEMENT result = default(WINDOWPLACEMENT);
|
||||||
|
result.Length = Marshal.SizeOf(result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
27
src/Snap.Hutao/Snap.Hutao/Extension/EnumExtensions.cs
Normal file
27
src/Snap.Hutao/Snap.Hutao/Extension/EnumExtensions.cs
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
// Copyright (c) DGP Studio. All rights reserved.
|
||||||
|
// Licensed under the MIT license.
|
||||||
|
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
namespace Snap.Hutao.Extension;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 枚举拓展
|
||||||
|
/// </summary>
|
||||||
|
public static class EnumExtensions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 获取枚举的描述
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TEnum">枚举的类型</typeparam>
|
||||||
|
/// <param name="enum">枚举值</param>
|
||||||
|
/// <returns>描述</returns>
|
||||||
|
public static string GetDescription<TEnum>(this TEnum @enum)
|
||||||
|
where TEnum : struct, Enum
|
||||||
|
{
|
||||||
|
string enumName = Must.NotNull(Enum.GetName(@enum)!);
|
||||||
|
FieldInfo? field = @enum.GetType().GetField(enumName);
|
||||||
|
DescriptionAttribute? attr = field?.GetCustomAttribute<DescriptionAttribute>();
|
||||||
|
return attr?.Description ?? enumName;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,7 +8,7 @@
|
|||||||
mc:Ignorable="d"
|
mc:Ignorable="d"
|
||||||
Closed="MainWindowClosed">
|
Closed="MainWindowClosed">
|
||||||
|
|
||||||
<Grid Background="{ThemeResource SolidBackgroundFillColorBaseBrush}">
|
<Grid>
|
||||||
<Grid.RowDefinitions>
|
<Grid.RowDefinitions>
|
||||||
<RowDefinition Height="48.8"/>
|
<RowDefinition Height="48.8"/>
|
||||||
<RowDefinition/>
|
<RowDefinition/>
|
||||||
|
|||||||
@@ -3,6 +3,12 @@
|
|||||||
|
|
||||||
using Microsoft.UI.Xaml;
|
using Microsoft.UI.Xaml;
|
||||||
using Snap.Hutao.Context.Database;
|
using Snap.Hutao.Context.Database;
|
||||||
|
using Snap.Hutao.Control.HostBackdrop;
|
||||||
|
using Snap.Hutao.Core.Setting;
|
||||||
|
using Snap.Hutao.Core.Win32;
|
||||||
|
using System.Drawing;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using WinRT.Interop;
|
||||||
|
|
||||||
namespace Snap.Hutao;
|
namespace Snap.Hutao;
|
||||||
|
|
||||||
@@ -13,22 +19,76 @@ namespace Snap.Hutao;
|
|||||||
public sealed partial class MainWindow : Window
|
public sealed partial class MainWindow : Window
|
||||||
{
|
{
|
||||||
private readonly AppDbContext appDbContext;
|
private readonly AppDbContext appDbContext;
|
||||||
|
private readonly ILogger<MainWindow> logger;
|
||||||
|
private readonly IntPtr handle;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 构造一个新的主窗体
|
/// 构造一个新的主窗体
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="appDbContext">数据库上下文</param>
|
/// <param name="appDbContext">数据库上下文</param>
|
||||||
public MainWindow(AppDbContext appDbContext)
|
/// <param name="logger">日志器</param>
|
||||||
|
public MainWindow(AppDbContext appDbContext, ILogger<MainWindow> logger)
|
||||||
{
|
{
|
||||||
this.appDbContext = appDbContext;
|
this.appDbContext = appDbContext;
|
||||||
|
this.logger = logger;
|
||||||
|
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
|
||||||
ExtendsContentIntoTitleBar = true;
|
ExtendsContentIntoTitleBar = true;
|
||||||
SetTitleBar(TitleBarView.DragableArea);
|
SetTitleBar(TitleBarView.DragableArea);
|
||||||
|
|
||||||
|
handle = WindowNative.GetWindowHandle(this);
|
||||||
|
InitializeWindow();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static RECT RetriveWindowRect()
|
||||||
|
{
|
||||||
|
int left = LocalSetting.GetValueType<int>(SettingKeys.WindowLeft);
|
||||||
|
int top = LocalSetting.GetValueType<int>(SettingKeys.WindowTop);
|
||||||
|
int right = LocalSetting.GetValueType<int>(SettingKeys.WindowRight);
|
||||||
|
int bottom = LocalSetting.GetValueType<int>(SettingKeys.WindowBottom);
|
||||||
|
|
||||||
|
return new RECT(left, top, right, bottom);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InitializeWindow()
|
||||||
|
{
|
||||||
|
RECT rect = RetriveWindowRect();
|
||||||
|
if (rect.Size.IsEmpty)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
WINDOWPLACEMENT windowPlacement = new()
|
||||||
|
{
|
||||||
|
Length = Marshal.SizeOf<WINDOWPLACEMENT>(),
|
||||||
|
MaxPosition = new Point(-1, -1),
|
||||||
|
NormalPosition = rect,
|
||||||
|
ShowCmd = ShowWindowCommand.Normal,
|
||||||
|
};
|
||||||
|
|
||||||
|
User32.SetWindowPlacement(handle, ref windowPlacement);
|
||||||
|
User32.SetWindowText(handle, "胡桃");
|
||||||
|
|
||||||
|
bool micaApplied = new SystemBackdrop(this).TrySetBackdrop();
|
||||||
|
logger.LogInformation("{name} 设置{result}", nameof(SystemBackdrop), micaApplied ? "成功" : "失败");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SaveWindowRect()
|
||||||
|
{
|
||||||
|
WINDOWPLACEMENT windowPlacement = WINDOWPLACEMENT.Default;
|
||||||
|
User32.GetWindowPlacement(handle, ref windowPlacement);
|
||||||
|
|
||||||
|
LocalSetting.SetValueType(SettingKeys.WindowLeft, windowPlacement.NormalPosition.Left);
|
||||||
|
LocalSetting.SetValueType(SettingKeys.WindowTop, windowPlacement.NormalPosition.Top);
|
||||||
|
LocalSetting.SetValueType(SettingKeys.WindowRight, windowPlacement.NormalPosition.Right);
|
||||||
|
LocalSetting.SetValueType(SettingKeys.WindowBottom, windowPlacement.NormalPosition.Bottom);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void MainWindowClosed(object sender, WindowEventArgs args)
|
private void MainWindowClosed(object sender, WindowEventArgs args)
|
||||||
{
|
{
|
||||||
|
SaveWindowRect();
|
||||||
|
|
||||||
// save datebase
|
// save datebase
|
||||||
int changes = appDbContext.SaveChanges();
|
int changes = appDbContext.SaveChanges();
|
||||||
Verify.Operation(changes == 0, "存在可避免的未经处理的数据库更改");
|
Verify.Operation(changes == 0, "存在可避免的未经处理的数据库更改");
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
// Copyright (c) DGP Studio. All rights reserved.
|
||||||
|
// Licensed under the MIT license.
|
||||||
|
|
||||||
|
namespace Snap.Hutao.Model.Annotation;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 枚举的文本描述特性
|
||||||
|
/// </summary>
|
||||||
|
[AttributeUsage(AttributeTargets.Field, AllowMultiple = false, Inherited = false)]
|
||||||
|
internal class DescriptionAttribute : Attribute
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 构造一个新的枚举的文本描述特性
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="description">描述</param>
|
||||||
|
public DescriptionAttribute(string description)
|
||||||
|
{
|
||||||
|
Description = description;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取文本描述
|
||||||
|
/// </summary>
|
||||||
|
public string Description { get; init; }
|
||||||
|
}
|
||||||
@@ -17,6 +17,7 @@ public enum FightProperty
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 基础生命值
|
/// 基础生命值
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[Description("基础生命值")]
|
||||||
FIGHT_PROP_BASE_HP = 1,
|
FIGHT_PROP_BASE_HP = 1,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -27,11 +28,13 @@ public enum FightProperty
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 生命值加成百分比
|
/// 生命值加成百分比
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[Description("生命值")]
|
||||||
FIGHT_PROP_HP_PERCENT = 3,
|
FIGHT_PROP_HP_PERCENT = 3,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 基础攻击力
|
/// 基础攻击力
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[Description("基础攻击力")]
|
||||||
FIGHT_PROP_BASE_ATTACK = 4,
|
FIGHT_PROP_BASE_ATTACK = 4,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -42,11 +45,13 @@ public enum FightProperty
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 攻击力百分比
|
/// 攻击力百分比
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[Description("攻击力")]
|
||||||
FIGHT_PROP_ATTACK_PERCENT = 6,
|
FIGHT_PROP_ATTACK_PERCENT = 6,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 基础防御力
|
/// 基础防御力
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[Description("基础防御力")]
|
||||||
FIGHT_PROP_BASE_DEFENSE = 7,
|
FIGHT_PROP_BASE_DEFENSE = 7,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -57,6 +62,7 @@ public enum FightProperty
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 防御力百分比
|
/// 防御力百分比
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[Description("防御力")]
|
||||||
FIGHT_PROP_DEFENSE_PERCENT = 9,
|
FIGHT_PROP_DEFENSE_PERCENT = 9,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -82,6 +88,7 @@ public enum FightProperty
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 暴击率
|
/// 暴击率
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[Description("暴击率")]
|
||||||
FIGHT_PROP_CRITICAL = 20,
|
FIGHT_PROP_CRITICAL = 20,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -92,11 +99,13 @@ public enum FightProperty
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 暴击伤害
|
/// 暴击伤害
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[Description("暴击伤害")]
|
||||||
FIGHT_PROP_CRITICAL_HURT = 22,
|
FIGHT_PROP_CRITICAL_HURT = 22,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 元素充能效率
|
/// 元素充能效率
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[Description("元素充能效率")]
|
||||||
FIGHT_PROP_CHARGE_EFFICIENCY = 23,
|
FIGHT_PROP_CHARGE_EFFICIENCY = 23,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -112,6 +121,7 @@ public enum FightProperty
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 治疗提升
|
/// 治疗提升
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[Description("治疗加成")]
|
||||||
FIGHT_PROP_HEAL_ADD = 26,
|
FIGHT_PROP_HEAL_ADD = 26,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -122,6 +132,7 @@ public enum FightProperty
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 元素精通
|
/// 元素精通
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[Description("元素精通")]
|
||||||
FIGHT_PROP_ELEMENT_MASTERY = 28,
|
FIGHT_PROP_ELEMENT_MASTERY = 28,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -132,6 +143,7 @@ public enum FightProperty
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 物理伤害加成
|
/// 物理伤害加成
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[Description("物理伤害加成")]
|
||||||
FIGHT_PROP_PHYSICAL_ADD_HURT = 30,
|
FIGHT_PROP_PHYSICAL_ADD_HURT = 30,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -147,16 +159,19 @@ public enum FightProperty
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 火元素伤害加成
|
/// 火元素伤害加成
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[Description("火元素伤害加成")]
|
||||||
FIGHT_PROP_FIRE_ADD_HURT = 40,
|
FIGHT_PROP_FIRE_ADD_HURT = 40,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 雷元素伤害加成
|
/// 雷元素伤害加成
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[Description("雷元素伤害加成")]
|
||||||
FIGHT_PROP_ELEC_ADD_HURT = 41,
|
FIGHT_PROP_ELEC_ADD_HURT = 41,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 水元素伤害加成
|
/// 水元素伤害加成
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[Description("水元素伤害加成")]
|
||||||
FIGHT_PROP_WATER_ADD_HURT = 42,
|
FIGHT_PROP_WATER_ADD_HURT = 42,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -167,16 +182,19 @@ public enum FightProperty
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 风元素伤害加成
|
/// 风元素伤害加成
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[Description("风元素伤害加成")]
|
||||||
FIGHT_PROP_WIND_ADD_HURT = 44,
|
FIGHT_PROP_WIND_ADD_HURT = 44,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 岩元素伤害加成
|
/// 岩元素伤害加成
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[Description("岩元素伤害加成")]
|
||||||
FIGHT_PROP_ROCK_ADD_HURT = 45,
|
FIGHT_PROP_ROCK_ADD_HURT = 45,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 冰元素伤害加成
|
/// 冰元素伤害加成
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[Description("冰元素伤害加成")]
|
||||||
FIGHT_PROP_ICE_ADD_HURT = 46,
|
FIGHT_PROP_ICE_ADD_HURT = 46,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ namespace Snap.Hutao.Model.Metadata.Avatar;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class ProudableSkill : SkillBase
|
public class ProudableSkill : SkillBase
|
||||||
{
|
{
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 组Id
|
/// 组Id
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
// Copyright (c) DGP Studio. All rights reserved.
|
||||||
|
// Licensed under the MIT license.
|
||||||
|
|
||||||
|
using Microsoft.UI.Xaml.Data;
|
||||||
|
|
||||||
|
namespace Snap.Hutao.Model.Metadata.Converter;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 角色头像转换器
|
||||||
|
/// </summary>
|
||||||
|
internal class IconConverter : IValueConverter
|
||||||
|
{
|
||||||
|
private const string BaseUrl = "https://upload-bbs.mihoyo.com/game_record/genshin/character_icon/{0}.png";
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public object Convert(object value, Type targetType, object parameter, string language)
|
||||||
|
{
|
||||||
|
return new Uri(string.Format(BaseUrl, value));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public object ConvertBack(object value, Type targetType, object parameter, string language)
|
||||||
|
{
|
||||||
|
throw Must.NeverHappen();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
// Copyright (c) DGP Studio. All rights reserved.
|
||||||
|
// Licensed under the MIT license.
|
||||||
|
|
||||||
|
using Microsoft.UI.Xaml.Data;
|
||||||
|
|
||||||
|
namespace Snap.Hutao.Model.Metadata.Converter;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 角色侧面头像转换器
|
||||||
|
/// </summary>
|
||||||
|
internal class SideIconConverter : IValueConverter
|
||||||
|
{
|
||||||
|
private const string BaseUrl = "https://upload-bbs.mihoyo.com/game_record/genshin/character_side_icon/{0}.png";
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public object Convert(object value, Type targetType, object parameter, string language)
|
||||||
|
{
|
||||||
|
return new Uri(string.Format(BaseUrl, value));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public object ConvertBack(object value, Type targetType, object parameter, string language)
|
||||||
|
{
|
||||||
|
throw Must.NeverHappen();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,7 +9,7 @@
|
|||||||
<Identity
|
<Identity
|
||||||
Name="7f0db578-026f-4e0b-a75b-d5d06bb0a74d"
|
Name="7f0db578-026f-4e0b-a75b-d5d06bb0a74d"
|
||||||
Publisher="CN=DGP Studio"
|
Publisher="CN=DGP Studio"
|
||||||
Version="1.0.6.0" />
|
Version="1.0.9.0" />
|
||||||
|
|
||||||
<Properties>
|
<Properties>
|
||||||
<DisplayName>胡桃</DisplayName>
|
<DisplayName>胡桃</DisplayName>
|
||||||
|
|||||||
@@ -31,8 +31,9 @@ public interface IUserService
|
|||||||
/// 通常用户是未初始化的
|
/// 通常用户是未初始化的
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="user">待添加的用户</param>
|
/// <param name="user">待添加的用户</param>
|
||||||
|
/// <param name="uid">用户的米游社UID</param>
|
||||||
/// <returns>用户初始化是否成功</returns>
|
/// <returns>用户初始化是否成功</returns>
|
||||||
Task<UserAddResult> TryAddUserAsync(User user);
|
Task<UserAddResult> TryAddUserAsync(User user, string uid);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 异步移除用户
|
/// 异步移除用户
|
||||||
|
|||||||
@@ -11,7 +11,12 @@ public enum UserAddResult
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 添加成功
|
/// 添加成功
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Ok,
|
Added,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 用户的Cookie成功更新
|
||||||
|
/// </summary>
|
||||||
|
Updated,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 已经存在该用户
|
/// 已经存在该用户
|
||||||
|
|||||||
@@ -69,31 +69,45 @@ internal class UserService : IUserService
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public async Task<UserAddResult> TryAddUserAsync(User user)
|
public async Task<UserAddResult> TryAddUserAsync(User newUser, string uid)
|
||||||
{
|
{
|
||||||
string? newUsersCookie = user.Cookie;
|
Must.NotNull(cachedUsers!);
|
||||||
|
|
||||||
// Prevent users add same account.
|
// 查找是否有相同的uid
|
||||||
bool userAlreadyExists = await appDbContext.Users
|
User? userWithSameUid = cachedUsers
|
||||||
.AnyAsync(u => u.Cookie == newUsersCookie)
|
.SingleOrDefault(u => u.UserInfo!.Uid == uid);
|
||||||
.ConfigureAwait(false);
|
|
||||||
|
|
||||||
if (userAlreadyExists)
|
if (userWithSameUid != null)
|
||||||
{
|
{
|
||||||
return UserAddResult.AlreadyExists;
|
// Prevent users from adding same cookie.
|
||||||
|
if (userWithSameUid.Cookie == newUser.Cookie)
|
||||||
|
{
|
||||||
|
return UserAddResult.AlreadyExists;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Try update user here.
|
||||||
|
userWithSameUid.Cookie = newUser.Cookie;
|
||||||
|
appDbContext.Users.Update(userWithSameUid);
|
||||||
|
|
||||||
|
await appDbContext
|
||||||
|
.SaveChangesAsync()
|
||||||
|
.ConfigureAwait(false);
|
||||||
|
|
||||||
|
return UserAddResult.Updated;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool userInitialized = await user
|
// must continue on the caller thread.
|
||||||
.InitializeAsync(userClient, userGameRoleClient)
|
if (await newUser.InitializeAsync(userClient, userGameRoleClient))
|
||||||
.ConfigureAwait(false);
|
|
||||||
|
|
||||||
if (userInitialized)
|
|
||||||
{
|
{
|
||||||
appDbContext.Users.Add(user);
|
appDbContext.Users.Add(newUser);
|
||||||
|
|
||||||
await appDbContext
|
await appDbContext
|
||||||
.SaveChangesAsync()
|
.SaveChangesAsync()
|
||||||
.ConfigureAwait(false);
|
.ConfigureAwait(false);
|
||||||
return UserAddResult.Ok;
|
|
||||||
|
return UserAddResult.Added;
|
||||||
}
|
}
|
||||||
|
|
||||||
return UserAddResult.InitializeFailed;
|
return UserAddResult.InitializeFailed;
|
||||||
@@ -111,16 +125,23 @@ internal class UserService : IUserService
|
|||||||
{
|
{
|
||||||
if (cachedUsers == null)
|
if (cachedUsers == null)
|
||||||
{
|
{
|
||||||
appDbContext.Users.Load();
|
await appDbContext.Users
|
||||||
|
.LoadAsync()
|
||||||
|
.ConfigureAwait(false);
|
||||||
|
|
||||||
cachedUsers = appDbContext.Users.Local.ToObservableCollection();
|
cachedUsers = appDbContext.Users.Local.ToObservableCollection();
|
||||||
|
|
||||||
foreach (User user in cachedUsers)
|
foreach (User user in cachedUsers)
|
||||||
{
|
{
|
||||||
user.RemoveCommand = removeCommand;
|
user.RemoveCommand = removeCommand;
|
||||||
await user.InitializeAsync(userClient, userGameRoleClient);
|
await user
|
||||||
|
.InitializeAsync(userClient, userGameRoleClient)
|
||||||
|
.ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
CurrentUser = await appDbContext.Users.SingleOrDefaultAsync(user => user.IsSelected);
|
CurrentUser = await appDbContext.Users
|
||||||
|
.SingleOrDefaultAsync(user => user.IsSelected)
|
||||||
|
.ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
return cachedUsers;
|
return cachedUsers;
|
||||||
@@ -129,15 +150,15 @@ internal class UserService : IUserService
|
|||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public IDictionary<string, string> ParseCookie(string cookie)
|
public IDictionary<string, string> ParseCookie(string cookie)
|
||||||
{
|
{
|
||||||
Dictionary<string, string> cookieDictionary = new();
|
SortedDictionary<string, string> cookieDictionary = new();
|
||||||
|
|
||||||
string[] values = cookie.TrimEnd(';').Split(';');
|
string[] values = cookie.TrimEnd(';').Split(';');
|
||||||
foreach (string[] parts in values.Select(c => c.Split(new[] { '=' }, 2)))
|
foreach (string[] parts in values.Select(c => c.Split('=', 2)))
|
||||||
{
|
{
|
||||||
string cookieName = parts[0].Trim();
|
string cookieName = parts[0].Trim();
|
||||||
string cookieValue = parts.Length == 1 ? string.Empty : parts[1].Trim();
|
string cookieValue = parts.Length == 1 ? string.Empty : parts[1].Trim();
|
||||||
|
|
||||||
cookieDictionary[cookieName] = cookieValue;
|
cookieDictionary.Add(cookieName, cookieValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
return cookieDictionary;
|
return cookieDictionary;
|
||||||
|
|||||||
@@ -36,6 +36,7 @@
|
|||||||
<None Remove="View\Page\AnnouncementPage.xaml" />
|
<None Remove="View\Page\AnnouncementPage.xaml" />
|
||||||
<None Remove="View\Page\SettingPage.xaml" />
|
<None Remove="View\Page\SettingPage.xaml" />
|
||||||
<None Remove="View\Page\WelcomePage.xaml" />
|
<None Remove="View\Page\WelcomePage.xaml" />
|
||||||
|
<None Remove="View\Page\WikiAvatarPage.xaml" />
|
||||||
<None Remove="View\TitleView.xaml" />
|
<None Remove="View\TitleView.xaml" />
|
||||||
<None Remove="View\UserView.xaml" />
|
<None Remove="View\UserView.xaml" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
@@ -132,6 +133,11 @@
|
|||||||
<ProjectReference Include="..\SettingsUI\SettingsUI.csproj" />
|
<ProjectReference Include="..\SettingsUI\SettingsUI.csproj" />
|
||||||
<ProjectReference Include="..\Snap.Hutao.SourceGeneration\Snap.Hutao.SourceGeneration.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
|
<ProjectReference Include="..\Snap.Hutao.SourceGeneration\Snap.Hutao.SourceGeneration.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Page Update="View\Page\WikiAvatarPage.xaml">
|
||||||
|
<Generator>MSBuild:Compile</Generator>
|
||||||
|
</Page>
|
||||||
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Page Update="View\Page\AchievementPage.xaml">
|
<Page Update="View\Page\AchievementPage.xaml">
|
||||||
<Generator>MSBuild:Compile</Generator>
|
<Generator>MSBuild:Compile</Generator>
|
||||||
|
|||||||
@@ -14,10 +14,11 @@ public sealed partial class UserDialog : ContentDialog
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 构造一个新的添加用户对话框
|
/// 构造一个新的添加用户对话框
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public UserDialog()
|
/// <param name="window">呈现的父窗口</param>
|
||||||
|
public UserDialog(Microsoft.UI.Xaml.Window window)
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
XamlRoot = App.Window!.Content.XamlRoot;
|
XamlRoot = window.Content.XamlRoot;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -29,7 +30,7 @@ public sealed partial class UserDialog : ContentDialog
|
|||||||
ContentDialogResult result = await ShowAsync();
|
ContentDialogResult result = await ShowAsync();
|
||||||
string cookie = InputText.Text;
|
string cookie = InputText.Text;
|
||||||
|
|
||||||
return new(result != ContentDialogResult.Secondary, cookie);
|
return new(result == ContentDialogResult.Primary, cookie);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void InputTextChanged(object sender, TextChangedEventArgs e)
|
private void InputTextChanged(object sender, TextChangedEventArgs e)
|
||||||
|
|||||||
@@ -34,6 +34,12 @@
|
|||||||
</NavigationViewItem.Icon>
|
</NavigationViewItem.Icon>
|
||||||
</NavigationViewItem>
|
</NavigationViewItem>
|
||||||
|
|
||||||
|
<NavigationViewItem Content="角色" helper:NavHelper.NavigateTo="page:WikiAvatarPage">
|
||||||
|
<NavigationViewItem.Icon>
|
||||||
|
<FontIcon Glyph=""/>
|
||||||
|
</NavigationViewItem.Icon>
|
||||||
|
</NavigationViewItem>
|
||||||
|
|
||||||
</NavigationView.MenuItems>
|
</NavigationView.MenuItems>
|
||||||
|
|
||||||
<NavigationView.PaneFooter>
|
<NavigationView.PaneFooter>
|
||||||
|
|||||||
@@ -28,6 +28,6 @@ public sealed partial class MainView : UserControl
|
|||||||
|
|
||||||
navigationService = Ioc.Default.GetRequiredService<INavigationService>();
|
navigationService = Ioc.Default.GetRequiredService<INavigationService>();
|
||||||
navigationService.Initialize(NavView, ContentFrame);
|
navigationService.Initialize(NavView, ContentFrame);
|
||||||
navigationService.Navigate<WelcomePage>(INavigationAwaiter.Default, false);
|
navigationService.Navigate<AnnouncementPage>(INavigationAwaiter.Default, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
33
src/Snap.Hutao/Snap.Hutao/View/Page/WikiAvatarPage.xaml
Normal file
33
src/Snap.Hutao/Snap.Hutao/View/Page/WikiAvatarPage.xaml
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
<Page
|
||||||
|
x:Class="Snap.Hutao.View.Page.WikiAvatarPage"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:local="using:Snap.Hutao.View.Page"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:mxi="using:Microsoft.Xaml.Interactivity"
|
||||||
|
xmlns:mxic="using:Microsoft.Xaml.Interactions.Core"
|
||||||
|
mc:Ignorable="d"
|
||||||
|
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
|
||||||
|
<mxi:Interaction.Behaviors>
|
||||||
|
<mxic:EventTriggerBehavior EventName="Loaded">
|
||||||
|
<mxic:InvokeCommandAction Command="{Binding OpenUICommand}"/>
|
||||||
|
</mxic:EventTriggerBehavior>
|
||||||
|
</mxi:Interaction.Behaviors>
|
||||||
|
<Grid>
|
||||||
|
<SplitView IsPaneOpen="True" DisplayMode="Inline">
|
||||||
|
<SplitView.Pane>
|
||||||
|
<ListView
|
||||||
|
SelectionMode="Single"
|
||||||
|
ItemsSource="{Binding Avatars}"
|
||||||
|
SelectedItem="{Binding Selected,Mode=TwoWay}"/>
|
||||||
|
</SplitView.Pane>
|
||||||
|
<SplitView.Content>
|
||||||
|
<Grid>
|
||||||
|
<TextBlock
|
||||||
|
Text="{Binding Selected.Name}"/>
|
||||||
|
</Grid>
|
||||||
|
</SplitView.Content>
|
||||||
|
</SplitView>
|
||||||
|
</Grid>
|
||||||
|
</Page>
|
||||||
35
src/Snap.Hutao/Snap.Hutao/View/Page/WikiAvatarPage.xaml.cs
Normal file
35
src/Snap.Hutao/Snap.Hutao/View/Page/WikiAvatarPage.xaml.cs
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
// Copyright (c) DGP Studio. All rights reserved.
|
||||||
|
// Licensed under the MIT license.
|
||||||
|
|
||||||
|
using Microsoft.UI.Xaml;
|
||||||
|
using Microsoft.UI.Xaml.Controls;
|
||||||
|
using Microsoft.UI.Xaml.Controls.Primitives;
|
||||||
|
using Microsoft.UI.Xaml.Data;
|
||||||
|
using Microsoft.UI.Xaml.Input;
|
||||||
|
using Microsoft.UI.Xaml.Media;
|
||||||
|
using Microsoft.UI.Xaml.Navigation;
|
||||||
|
using Snap.Hutao.ViewModel;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Runtime.InteropServices.WindowsRuntime;
|
||||||
|
using Windows.Foundation;
|
||||||
|
using Windows.Foundation.Collections;
|
||||||
|
|
||||||
|
namespace Snap.Hutao.View.Page;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 角色资料页
|
||||||
|
/// </summary>
|
||||||
|
public sealed partial class WikiAvatarPage : Microsoft.UI.Xaml.Controls.Page
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 构造一个新的角色资料页
|
||||||
|
/// </summary>
|
||||||
|
public WikiAvatarPage()
|
||||||
|
{
|
||||||
|
DataContext = Ioc.Default.GetRequiredService<WikiAvatarViewModel>();
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -35,7 +35,6 @@
|
|||||||
Text="{Binding SelectedUser.UserInfo.Nickname,Mode=OneWay}"
|
Text="{Binding SelectedUser.UserInfo.Nickname,Mode=OneWay}"
|
||||||
TextTrimming="CharacterEllipsis"/>
|
TextTrimming="CharacterEllipsis"/>
|
||||||
<Button
|
<Button
|
||||||
x:Name="UsersFlyoutButton"
|
|
||||||
Background="Transparent"
|
Background="Transparent"
|
||||||
BorderBrush="{x:Null}"
|
BorderBrush="{x:Null}"
|
||||||
Height="38.4"
|
Height="38.4"
|
||||||
@@ -184,8 +183,7 @@
|
|||||||
<AppBarButton
|
<AppBarButton
|
||||||
Icon="Add"
|
Icon="Add"
|
||||||
Label="添加新用户"
|
Label="添加新用户"
|
||||||
Command="{Binding AddUserCommand}"
|
Command="{Binding AddUserCommand}"/>
|
||||||
CommandParameter="{x:Bind UsersFlyoutButton.Flyout}"/>
|
|
||||||
</CommandBar>
|
</CommandBar>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Flyout>
|
</Flyout>
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
// Licensed under the MIT license.
|
// Licensed under the MIT license.
|
||||||
|
|
||||||
using CommunityToolkit.Mvvm.ComponentModel;
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
using Microsoft.UI.Xaml.Controls;
|
|
||||||
using Snap.Hutao.Core.Threading;
|
using Snap.Hutao.Core.Threading;
|
||||||
using Snap.Hutao.Factory.Abstraction;
|
using Snap.Hutao.Factory.Abstraction;
|
||||||
using Snap.Hutao.Model.Entity;
|
using Snap.Hutao.Model.Entity;
|
||||||
@@ -20,6 +19,8 @@ namespace Snap.Hutao.ViewModel;
|
|||||||
[Injection(InjectAs.Transient)]
|
[Injection(InjectAs.Transient)]
|
||||||
internal class UserViewModel : ObservableObject
|
internal class UserViewModel : ObservableObject
|
||||||
{
|
{
|
||||||
|
private const string AccountIdKey = "account_id";
|
||||||
|
|
||||||
private readonly IUserService userService;
|
private readonly IUserService userService;
|
||||||
private readonly IInfoBarService infoBarService;
|
private readonly IInfoBarService infoBarService;
|
||||||
private readonly ICommand removeUserCommandCache;
|
private readonly ICommand removeUserCommandCache;
|
||||||
@@ -39,7 +40,7 @@ internal class UserViewModel : ObservableObject
|
|||||||
this.infoBarService = infoBarService;
|
this.infoBarService = infoBarService;
|
||||||
|
|
||||||
OpenUICommand = asyncRelayCommandFactory.Create(OpenUIAsync);
|
OpenUICommand = asyncRelayCommandFactory.Create(OpenUIAsync);
|
||||||
AddUserCommand = asyncRelayCommandFactory.Create<Flyout>(AddUserAsync);
|
AddUserCommand = asyncRelayCommandFactory.Create(AddUserAsync);
|
||||||
|
|
||||||
removeUserCommandCache = asyncRelayCommandFactory.Create<User>(RemoveUserAsync);
|
removeUserCommandCache = asyncRelayCommandFactory.Create<User>(RemoveUserAsync);
|
||||||
}
|
}
|
||||||
@@ -83,10 +84,10 @@ internal class UserViewModel : ObservableObject
|
|||||||
// O(1) to validate cookie
|
// O(1) to validate cookie
|
||||||
foreach ((string key, string value) in map)
|
foreach ((string key, string value) in map)
|
||||||
{
|
{
|
||||||
if (key == "account_id" || key == "cookie_token" || key == "ltoken" || key == "ltuid")
|
if (key == AccountIdKey || key == "cookie_token" || key == "ltoken" || key == "ltuid")
|
||||||
{
|
{
|
||||||
validFlag--;
|
validFlag--;
|
||||||
filteredCookie[key] = value;
|
filteredCookie.Add(key, value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -107,12 +108,10 @@ internal class UserViewModel : ObservableObject
|
|||||||
SelectedUser = userService.CurrentUser;
|
SelectedUser = userService.CurrentUser;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task AddUserAsync(Flyout? flyout)
|
private async Task AddUserAsync()
|
||||||
{
|
{
|
||||||
// hide the flyout, otherwise dialog can't open.
|
MainWindow mainWindow = Ioc.Default.GetRequiredService<MainWindow>();
|
||||||
flyout?.Hide();
|
Result<bool, string> result = await new UserDialog(mainWindow).GetInputCookieAsync();
|
||||||
|
|
||||||
Result<bool, string> result = await new UserDialog().GetInputCookieAsync();
|
|
||||||
|
|
||||||
// user confirms the input
|
// user confirms the input
|
||||||
if (result.IsOk)
|
if (result.IsOk)
|
||||||
@@ -128,22 +127,27 @@ internal class UserViewModel : ObservableObject
|
|||||||
RemoveCommand = removeUserCommandCache,
|
RemoveCommand = removeUserCommandCache,
|
||||||
};
|
};
|
||||||
|
|
||||||
switch (await userService.TryAddUserAsync(user))
|
switch (await userService.TryAddUserAsync(user, filteredCookie[AccountIdKey]))
|
||||||
{
|
{
|
||||||
case UserAddResult.Ok:
|
case UserAddResult.Added:
|
||||||
infoBarService.Success($"用户 [{user.UserInfo!.Nickname}] 添加成功");
|
infoBarService.Success($"用户 [{user.UserInfo!.Nickname}] 添加成功");
|
||||||
break;
|
break;
|
||||||
|
case UserAddResult.Updated:
|
||||||
|
infoBarService.Success($"用户 [{user.UserInfo!.Nickname}] 更新成功");
|
||||||
|
break;
|
||||||
case UserAddResult.AlreadyExists:
|
case UserAddResult.AlreadyExists:
|
||||||
infoBarService.Information($"用户 [{user.UserInfo!.Nickname}] 已经存在");
|
infoBarService.Information($"用户 [{user.UserInfo!.Nickname}] 已经存在");
|
||||||
break;
|
break;
|
||||||
case UserAddResult.InitializeFailed:
|
case UserAddResult.InitializeFailed:
|
||||||
infoBarService.Warning("此Cookie无法获取用户信息,请重新输入");
|
infoBarService.Warning("此 Cookie 无法获取用户信息,请重新输入");
|
||||||
break;
|
break;
|
||||||
|
default:
|
||||||
|
throw Must.NeverHappen();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
infoBarService.Warning("提供的字符串并不是有效的Cookie,请重新输入");
|
infoBarService.Warning("提供的文本不是正确的 Cookie ,请重新输入");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
57
src/Snap.Hutao/Snap.Hutao/ViewModel/WikiAvatarViewModel.cs
Normal file
57
src/Snap.Hutao/Snap.Hutao/ViewModel/WikiAvatarViewModel.cs
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
// Copyright (c) DGP Studio. All rights reserved.
|
||||||
|
// Licensed under the MIT license.
|
||||||
|
|
||||||
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
|
using Snap.Hutao.Factory.Abstraction;
|
||||||
|
using Snap.Hutao.Model.Metadata.Avatar;
|
||||||
|
using Snap.Hutao.Service.Metadata;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Snap.Hutao.ViewModel;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 角色资料视图模型
|
||||||
|
/// </summary>
|
||||||
|
[Injection(InjectAs.Transient)]
|
||||||
|
internal class WikiAvatarViewModel : ObservableObject
|
||||||
|
{
|
||||||
|
private readonly IMetadataService metadataService;
|
||||||
|
|
||||||
|
private List<Avatar>? avatars;
|
||||||
|
private Avatar? selected;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 构造一个新的角色资料视图模型
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="metadataService">元数据服务</param>
|
||||||
|
/// <param name="asyncRelayCommandFactory">异步命令工厂</param>
|
||||||
|
public WikiAvatarViewModel(IMetadataService metadataService, IAsyncRelayCommandFactory asyncRelayCommandFactory)
|
||||||
|
{
|
||||||
|
this.metadataService = metadataService;
|
||||||
|
OpenUICommand = asyncRelayCommandFactory.Create(OpenUIAsync);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 角色列表
|
||||||
|
/// </summary>
|
||||||
|
public List<Avatar>? Avatars { get => avatars; set => SetProperty(ref avatars, value); }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 选中的角色
|
||||||
|
/// </summary>
|
||||||
|
public Avatar? Selected { get => selected; set => SetProperty(ref selected, value); }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 打开页面命令
|
||||||
|
/// </summary>
|
||||||
|
public ICommand OpenUICommand { get; }
|
||||||
|
|
||||||
|
private async Task OpenUIAsync()
|
||||||
|
{
|
||||||
|
if (await metadataService.InitializeAsync())
|
||||||
|
{
|
||||||
|
IEnumerable<Avatar>? avatars = await metadataService.GetAvatarsAsync();
|
||||||
|
Avatars = new List<Avatar>(avatars);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -32,7 +32,7 @@ public class Announcement : AnnouncementContent
|
|||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
DateTime now = DateTime.UtcNow + TimeSpan.FromHours(8);
|
DateTimeOffset now = DateTimeOffset.UtcNow;
|
||||||
|
|
||||||
// 尚未开始
|
// 尚未开始
|
||||||
if (StartTime > now)
|
if (StartTime > now)
|
||||||
@@ -76,7 +76,7 @@ public class Announcement : AnnouncementContent
|
|||||||
if (timePercent == 0)
|
if (timePercent == 0)
|
||||||
{
|
{
|
||||||
// UTC+8
|
// UTC+8
|
||||||
DateTime currentTime = DateTime.UtcNow.AddHours(8);
|
DateTimeOffset currentTime = DateTimeOffset.UtcNow;
|
||||||
TimeSpan current = currentTime - StartTime;
|
TimeSpan current = currentTime - StartTime;
|
||||||
TimeSpan total = EndTime - StartTime;
|
TimeSpan total = EndTime - StartTime;
|
||||||
timePercent = current / total;
|
timePercent = current / total;
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ public struct PlayerUid
|
|||||||
/// <param name="region">服务器,当提供该参数时会无条件信任</param>
|
/// <param name="region">服务器,当提供该参数时会无条件信任</param>
|
||||||
public PlayerUid(string value, string? region = default)
|
public PlayerUid(string value, string? region = default)
|
||||||
{
|
{
|
||||||
Requires.Argument(value.Length == 9, nameof(value), "uid应为9位数字");
|
Requires.Argument(value.Length == 9, nameof(value), "uid 应为9位数字");
|
||||||
Value = value;
|
Value = value;
|
||||||
|
|
||||||
if (region != null)
|
if (region != null)
|
||||||
|
|||||||
Reference in New Issue
Block a user