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>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.1.1" />
|
||||
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.1.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
<IsPackable>false</IsPackable>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<Nullable>enable</Nullable>
|
||||
<Platforms>x64</Platforms>
|
||||
</PropertyGroup>
|
||||
|
||||
<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|arm64.ActiveCfg = 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.Build.0 = Release|Any CPU
|
||||
{DCA5678C-896E-49FB-97A7-5A504A5CFF17}.Release|x64.ActiveCfg = Release|x64
|
||||
{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.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.Build.0 = 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|x64.ActiveCfg = Debug|Any CPU
|
||||
{8B96721E-5604-47D2-9B72-06FEBAD0CE00}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{8B96721E-5604-47D2-9B72-06FEBAD0CE00}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{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.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.Build.0 = 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|x64.ActiveCfg = Release|Any CPU
|
||||
{8B96721E-5604-47D2-9B72-06FEBAD0CE00}.Release|x64.Build.0 = Release|Any CPU
|
||||
{8B96721E-5604-47D2-9B72-06FEBAD0CE00}.Release|x64.ActiveCfg = Release|x64
|
||||
{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.Build.0 = Release|Any CPU
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -81,4 +81,4 @@ internal static class Property<TOwner>
|
||||
{
|
||||
return DependencyProperty.RegisterAttached(name, typeof(TProperty), typeof(TOwner), new(callback));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,4 +12,24 @@ internal static class SettingKeys
|
||||
/// 上次打开时App的版本
|
||||
/// </summary>
|
||||
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"
|
||||
Closed="MainWindowClosed">
|
||||
|
||||
<Grid Background="{ThemeResource SolidBackgroundFillColorBaseBrush}">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="48.8"/>
|
||||
<RowDefinition/>
|
||||
|
||||
@@ -3,6 +3,12 @@
|
||||
|
||||
using Microsoft.UI.Xaml;
|
||||
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;
|
||||
|
||||
@@ -13,22 +19,76 @@ namespace Snap.Hutao;
|
||||
public sealed partial class MainWindow : Window
|
||||
{
|
||||
private readonly AppDbContext appDbContext;
|
||||
private readonly ILogger<MainWindow> logger;
|
||||
private readonly IntPtr handle;
|
||||
|
||||
/// <summary>
|
||||
/// 构造一个新的主窗体
|
||||
/// </summary>
|
||||
/// <param name="appDbContext">数据库上下文</param>
|
||||
public MainWindow(AppDbContext appDbContext)
|
||||
/// <param name="logger">日志器</param>
|
||||
public MainWindow(AppDbContext appDbContext, ILogger<MainWindow> logger)
|
||||
{
|
||||
this.appDbContext = appDbContext;
|
||||
this.logger = logger;
|
||||
|
||||
InitializeComponent();
|
||||
|
||||
ExtendsContentIntoTitleBar = true;
|
||||
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)
|
||||
{
|
||||
SaveWindowRect();
|
||||
|
||||
// save datebase
|
||||
int changes = appDbContext.SaveChanges();
|
||||
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>
|
||||
[Description("基础生命值")]
|
||||
FIGHT_PROP_BASE_HP = 1,
|
||||
|
||||
/// <summary>
|
||||
@@ -27,11 +28,13 @@ public enum FightProperty
|
||||
/// <summary>
|
||||
/// 生命值加成百分比
|
||||
/// </summary>
|
||||
[Description("生命值")]
|
||||
FIGHT_PROP_HP_PERCENT = 3,
|
||||
|
||||
/// <summary>
|
||||
/// 基础攻击力
|
||||
/// </summary>
|
||||
[Description("基础攻击力")]
|
||||
FIGHT_PROP_BASE_ATTACK = 4,
|
||||
|
||||
/// <summary>
|
||||
@@ -42,11 +45,13 @@ public enum FightProperty
|
||||
/// <summary>
|
||||
/// 攻击力百分比
|
||||
/// </summary>
|
||||
[Description("攻击力")]
|
||||
FIGHT_PROP_ATTACK_PERCENT = 6,
|
||||
|
||||
/// <summary>
|
||||
/// 基础防御力
|
||||
/// </summary>
|
||||
[Description("基础防御力")]
|
||||
FIGHT_PROP_BASE_DEFENSE = 7,
|
||||
|
||||
/// <summary>
|
||||
@@ -57,6 +62,7 @@ public enum FightProperty
|
||||
/// <summary>
|
||||
/// 防御力百分比
|
||||
/// </summary>
|
||||
[Description("防御力")]
|
||||
FIGHT_PROP_DEFENSE_PERCENT = 9,
|
||||
|
||||
/// <summary>
|
||||
@@ -82,6 +88,7 @@ public enum FightProperty
|
||||
/// <summary>
|
||||
/// 暴击率
|
||||
/// </summary>
|
||||
[Description("暴击率")]
|
||||
FIGHT_PROP_CRITICAL = 20,
|
||||
|
||||
/// <summary>
|
||||
@@ -92,11 +99,13 @@ public enum FightProperty
|
||||
/// <summary>
|
||||
/// 暴击伤害
|
||||
/// </summary>
|
||||
[Description("暴击伤害")]
|
||||
FIGHT_PROP_CRITICAL_HURT = 22,
|
||||
|
||||
/// <summary>
|
||||
/// 元素充能效率
|
||||
/// </summary>
|
||||
[Description("元素充能效率")]
|
||||
FIGHT_PROP_CHARGE_EFFICIENCY = 23,
|
||||
|
||||
/// <summary>
|
||||
@@ -112,6 +121,7 @@ public enum FightProperty
|
||||
/// <summary>
|
||||
/// 治疗提升
|
||||
/// </summary>
|
||||
[Description("治疗加成")]
|
||||
FIGHT_PROP_HEAL_ADD = 26,
|
||||
|
||||
/// <summary>
|
||||
@@ -122,6 +132,7 @@ public enum FightProperty
|
||||
/// <summary>
|
||||
/// 元素精通
|
||||
/// </summary>
|
||||
[Description("元素精通")]
|
||||
FIGHT_PROP_ELEMENT_MASTERY = 28,
|
||||
|
||||
/// <summary>
|
||||
@@ -132,6 +143,7 @@ public enum FightProperty
|
||||
/// <summary>
|
||||
/// 物理伤害加成
|
||||
/// </summary>
|
||||
[Description("物理伤害加成")]
|
||||
FIGHT_PROP_PHYSICAL_ADD_HURT = 30,
|
||||
|
||||
/// <summary>
|
||||
@@ -147,16 +159,19 @@ public enum FightProperty
|
||||
/// <summary>
|
||||
/// 火元素伤害加成
|
||||
/// </summary>
|
||||
[Description("火元素伤害加成")]
|
||||
FIGHT_PROP_FIRE_ADD_HURT = 40,
|
||||
|
||||
/// <summary>
|
||||
/// 雷元素伤害加成
|
||||
/// </summary>
|
||||
[Description("雷元素伤害加成")]
|
||||
FIGHT_PROP_ELEC_ADD_HURT = 41,
|
||||
|
||||
/// <summary>
|
||||
/// 水元素伤害加成
|
||||
/// </summary>
|
||||
[Description("水元素伤害加成")]
|
||||
FIGHT_PROP_WATER_ADD_HURT = 42,
|
||||
|
||||
/// <summary>
|
||||
@@ -167,16 +182,19 @@ public enum FightProperty
|
||||
/// <summary>
|
||||
/// 风元素伤害加成
|
||||
/// </summary>
|
||||
[Description("风元素伤害加成")]
|
||||
FIGHT_PROP_WIND_ADD_HURT = 44,
|
||||
|
||||
/// <summary>
|
||||
/// 岩元素伤害加成
|
||||
/// </summary>
|
||||
[Description("岩元素伤害加成")]
|
||||
FIGHT_PROP_ROCK_ADD_HURT = 45,
|
||||
|
||||
/// <summary>
|
||||
/// 冰元素伤害加成
|
||||
/// </summary>
|
||||
[Description("冰元素伤害加成")]
|
||||
FIGHT_PROP_ICE_ADD_HURT = 46,
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -8,7 +8,6 @@ namespace Snap.Hutao.Model.Metadata.Avatar;
|
||||
/// </summary>
|
||||
public class ProudableSkill : SkillBase
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// 组Id
|
||||
/// </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
|
||||
Name="7f0db578-026f-4e0b-a75b-d5d06bb0a74d"
|
||||
Publisher="CN=DGP Studio"
|
||||
Version="1.0.6.0" />
|
||||
Version="1.0.9.0" />
|
||||
|
||||
<Properties>
|
||||
<DisplayName>胡桃</DisplayName>
|
||||
|
||||
@@ -31,8 +31,9 @@ public interface IUserService
|
||||
/// 通常用户是未初始化的
|
||||
/// </summary>
|
||||
/// <param name="user">待添加的用户</param>
|
||||
/// <param name="uid">用户的米游社UID</param>
|
||||
/// <returns>用户初始化是否成功</returns>
|
||||
Task<UserAddResult> TryAddUserAsync(User user);
|
||||
Task<UserAddResult> TryAddUserAsync(User user, string uid);
|
||||
|
||||
/// <summary>
|
||||
/// 异步移除用户
|
||||
|
||||
@@ -11,7 +11,12 @@ public enum UserAddResult
|
||||
/// <summary>
|
||||
/// 添加成功
|
||||
/// </summary>
|
||||
Ok,
|
||||
Added,
|
||||
|
||||
/// <summary>
|
||||
/// 用户的Cookie成功更新
|
||||
/// </summary>
|
||||
Updated,
|
||||
|
||||
/// <summary>
|
||||
/// 已经存在该用户
|
||||
|
||||
@@ -69,31 +69,45 @@ internal class UserService : IUserService
|
||||
}
|
||||
|
||||
/// <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.
|
||||
bool userAlreadyExists = await appDbContext.Users
|
||||
.AnyAsync(u => u.Cookie == newUsersCookie)
|
||||
.ConfigureAwait(false);
|
||||
// 查找是否有相同的uid
|
||||
User? userWithSameUid = cachedUsers
|
||||
.SingleOrDefault(u => u.UserInfo!.Uid == uid);
|
||||
|
||||
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
|
||||
.InitializeAsync(userClient, userGameRoleClient)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
if (userInitialized)
|
||||
// must continue on the caller thread.
|
||||
if (await newUser.InitializeAsync(userClient, userGameRoleClient))
|
||||
{
|
||||
appDbContext.Users.Add(user);
|
||||
appDbContext.Users.Add(newUser);
|
||||
|
||||
await appDbContext
|
||||
.SaveChangesAsync()
|
||||
.ConfigureAwait(false);
|
||||
return UserAddResult.Ok;
|
||||
|
||||
return UserAddResult.Added;
|
||||
}
|
||||
|
||||
return UserAddResult.InitializeFailed;
|
||||
@@ -111,16 +125,23 @@ internal class UserService : IUserService
|
||||
{
|
||||
if (cachedUsers == null)
|
||||
{
|
||||
appDbContext.Users.Load();
|
||||
await appDbContext.Users
|
||||
.LoadAsync()
|
||||
.ConfigureAwait(false);
|
||||
|
||||
cachedUsers = appDbContext.Users.Local.ToObservableCollection();
|
||||
|
||||
foreach (User user in cachedUsers)
|
||||
{
|
||||
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;
|
||||
@@ -129,15 +150,15 @@ internal class UserService : IUserService
|
||||
/// <inheritdoc/>
|
||||
public IDictionary<string, string> ParseCookie(string cookie)
|
||||
{
|
||||
Dictionary<string, string> cookieDictionary = new();
|
||||
SortedDictionary<string, string> cookieDictionary = new();
|
||||
|
||||
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 cookieValue = parts.Length == 1 ? string.Empty : parts[1].Trim();
|
||||
|
||||
cookieDictionary[cookieName] = cookieValue;
|
||||
cookieDictionary.Add(cookieName, cookieValue);
|
||||
}
|
||||
|
||||
return cookieDictionary;
|
||||
|
||||
@@ -36,6 +36,7 @@
|
||||
<None Remove="View\Page\AnnouncementPage.xaml" />
|
||||
<None Remove="View\Page\SettingPage.xaml" />
|
||||
<None Remove="View\Page\WelcomePage.xaml" />
|
||||
<None Remove="View\Page\WikiAvatarPage.xaml" />
|
||||
<None Remove="View\TitleView.xaml" />
|
||||
<None Remove="View\UserView.xaml" />
|
||||
</ItemGroup>
|
||||
@@ -132,6 +133,11 @@
|
||||
<ProjectReference Include="..\SettingsUI\SettingsUI.csproj" />
|
||||
<ProjectReference Include="..\Snap.Hutao.SourceGeneration\Snap.Hutao.SourceGeneration.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Page Update="View\Page\WikiAvatarPage.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Page Update="View\Page\AchievementPage.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
|
||||
@@ -14,10 +14,11 @@ public sealed partial class UserDialog : ContentDialog
|
||||
/// <summary>
|
||||
/// 构造一个新的添加用户对话框
|
||||
/// </summary>
|
||||
public UserDialog()
|
||||
/// <param name="window">呈现的父窗口</param>
|
||||
public UserDialog(Microsoft.UI.Xaml.Window window)
|
||||
{
|
||||
InitializeComponent();
|
||||
XamlRoot = App.Window!.Content.XamlRoot;
|
||||
XamlRoot = window.Content.XamlRoot;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -29,7 +30,7 @@ public sealed partial class UserDialog : ContentDialog
|
||||
ContentDialogResult result = await ShowAsync();
|
||||
string cookie = InputText.Text;
|
||||
|
||||
return new(result != ContentDialogResult.Secondary, cookie);
|
||||
return new(result == ContentDialogResult.Primary, cookie);
|
||||
}
|
||||
|
||||
private void InputTextChanged(object sender, TextChangedEventArgs e)
|
||||
|
||||
@@ -34,6 +34,12 @@
|
||||
</NavigationViewItem.Icon>
|
||||
</NavigationViewItem>
|
||||
|
||||
<NavigationViewItem Content="角色" helper:NavHelper.NavigateTo="page:WikiAvatarPage">
|
||||
<NavigationViewItem.Icon>
|
||||
<FontIcon Glyph=""/>
|
||||
</NavigationViewItem.Icon>
|
||||
</NavigationViewItem>
|
||||
|
||||
</NavigationView.MenuItems>
|
||||
|
||||
<NavigationView.PaneFooter>
|
||||
|
||||
@@ -28,6 +28,6 @@ public sealed partial class MainView : UserControl
|
||||
|
||||
navigationService = Ioc.Default.GetRequiredService<INavigationService>();
|
||||
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}"
|
||||
TextTrimming="CharacterEllipsis"/>
|
||||
<Button
|
||||
x:Name="UsersFlyoutButton"
|
||||
Background="Transparent"
|
||||
BorderBrush="{x:Null}"
|
||||
Height="38.4"
|
||||
@@ -184,8 +183,7 @@
|
||||
<AppBarButton
|
||||
Icon="Add"
|
||||
Label="添加新用户"
|
||||
Command="{Binding AddUserCommand}"
|
||||
CommandParameter="{x:Bind UsersFlyoutButton.Flyout}"/>
|
||||
Command="{Binding AddUserCommand}"/>
|
||||
</CommandBar>
|
||||
</StackPanel>
|
||||
</Flyout>
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Snap.Hutao.Core.Threading;
|
||||
using Snap.Hutao.Factory.Abstraction;
|
||||
using Snap.Hutao.Model.Entity;
|
||||
@@ -20,6 +19,8 @@ namespace Snap.Hutao.ViewModel;
|
||||
[Injection(InjectAs.Transient)]
|
||||
internal class UserViewModel : ObservableObject
|
||||
{
|
||||
private const string AccountIdKey = "account_id";
|
||||
|
||||
private readonly IUserService userService;
|
||||
private readonly IInfoBarService infoBarService;
|
||||
private readonly ICommand removeUserCommandCache;
|
||||
@@ -39,7 +40,7 @@ internal class UserViewModel : ObservableObject
|
||||
this.infoBarService = infoBarService;
|
||||
|
||||
OpenUICommand = asyncRelayCommandFactory.Create(OpenUIAsync);
|
||||
AddUserCommand = asyncRelayCommandFactory.Create<Flyout>(AddUserAsync);
|
||||
AddUserCommand = asyncRelayCommandFactory.Create(AddUserAsync);
|
||||
|
||||
removeUserCommandCache = asyncRelayCommandFactory.Create<User>(RemoveUserAsync);
|
||||
}
|
||||
@@ -83,10 +84,10 @@ internal class UserViewModel : ObservableObject
|
||||
// O(1) to validate cookie
|
||||
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--;
|
||||
filteredCookie[key] = value;
|
||||
filteredCookie.Add(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -107,12 +108,10 @@ internal class UserViewModel : ObservableObject
|
||||
SelectedUser = userService.CurrentUser;
|
||||
}
|
||||
|
||||
private async Task AddUserAsync(Flyout? flyout)
|
||||
private async Task AddUserAsync()
|
||||
{
|
||||
// hide the flyout, otherwise dialog can't open.
|
||||
flyout?.Hide();
|
||||
|
||||
Result<bool, string> result = await new UserDialog().GetInputCookieAsync();
|
||||
MainWindow mainWindow = Ioc.Default.GetRequiredService<MainWindow>();
|
||||
Result<bool, string> result = await new UserDialog(mainWindow).GetInputCookieAsync();
|
||||
|
||||
// user confirms the input
|
||||
if (result.IsOk)
|
||||
@@ -128,22 +127,27 @@ internal class UserViewModel : ObservableObject
|
||||
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}] 添加成功");
|
||||
break;
|
||||
case UserAddResult.Updated:
|
||||
infoBarService.Success($"用户 [{user.UserInfo!.Nickname}] 更新成功");
|
||||
break;
|
||||
case UserAddResult.AlreadyExists:
|
||||
infoBarService.Information($"用户 [{user.UserInfo!.Nickname}] 已经存在");
|
||||
break;
|
||||
case UserAddResult.InitializeFailed:
|
||||
infoBarService.Warning("此Cookie无法获取用户信息,请重新输入");
|
||||
infoBarService.Warning("此 Cookie 无法获取用户信息,请重新输入");
|
||||
break;
|
||||
default:
|
||||
throw Must.NeverHappen();
|
||||
}
|
||||
}
|
||||
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
|
||||
{
|
||||
DateTime now = DateTime.UtcNow + TimeSpan.FromHours(8);
|
||||
DateTimeOffset now = DateTimeOffset.UtcNow;
|
||||
|
||||
// 尚未开始
|
||||
if (StartTime > now)
|
||||
@@ -76,7 +76,7 @@ public class Announcement : AnnouncementContent
|
||||
if (timePercent == 0)
|
||||
{
|
||||
// UTC+8
|
||||
DateTime currentTime = DateTime.UtcNow.AddHours(8);
|
||||
DateTimeOffset currentTime = DateTimeOffset.UtcNow;
|
||||
TimeSpan current = currentTime - StartTime;
|
||||
TimeSpan total = EndTime - StartTime;
|
||||
timePercent = current / total;
|
||||
|
||||
@@ -17,7 +17,7 @@ public struct PlayerUid
|
||||
/// <param name="region">服务器,当提供该参数时会无条件信任</param>
|
||||
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;
|
||||
|
||||
if (region != null)
|
||||
|
||||
Reference in New Issue
Block a user