Files
better-genshin-impact/BetterGenshinImpact/View/Controls/Drawer/CustomDrawer.cs
辉鸭蛋 6f87a0c4d0 脚本仓库V2 (#1707)
* feat: add custom drawer control and integrate it into the UI

* 更新仓库UI

* feat: implement Git-based repository update mechanism and improve error handling

* feat: add reset repository functionality with confirmation dialog

* 修改打开队伍配置界面的重试次数和日志

* feat: add drawer open/close events and improve drawer closing logic

* feat: enhance WebpagePanel navigation handling and improve initialization logic

* feat: add drawer opened event handling and improve navigation completion logic

* feat: implement dynamic height adjustment for WebpagePanel using Grid container

* feat: update drawer dimensions and apply dynamic sizing based on position

* feat: add CustomDrawer component and integrate with MapPathingViewModel for enhanced navigation

* feat: integrate WebView2 for Markdown file navigation in MapPathingViewModel
2025-06-17 03:13:56 +08:00

410 lines
13 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.ComponentModel;
namespace BetterGenshinImpact.View.Controls.Drawer;
public class CustomDrawer : ContentControl
{
#region
public static readonly DependencyProperty IsOpenProperty =
DependencyProperty.Register(nameof(IsOpen), typeof(bool), typeof(CustomDrawer),
new PropertyMetadata(false, OnIsOpenChanged));
public static readonly DependencyProperty DrawerPositionProperty =
DependencyProperty.Register(nameof(DrawerPosition), typeof(DrawerPosition), typeof(CustomDrawer),
new PropertyMetadata(DrawerPosition.Right, OnDrawerPositionChanged));
public static readonly DependencyProperty OpenWidthProperty =
DependencyProperty.Register(nameof(OpenWidth), typeof(double), typeof(CustomDrawer),
new PropertyMetadata(400.0));
public static readonly DependencyProperty OpenHeightProperty =
DependencyProperty.Register(nameof(OpenHeight), typeof(double), typeof(CustomDrawer),
new PropertyMetadata(300.0));
public static readonly DependencyProperty AnimationDurationProperty =
DependencyProperty.Register(nameof(AnimationDuration), typeof(TimeSpan), typeof(CustomDrawer),
new PropertyMetadata(TimeSpan.FromMilliseconds(200)));
public static readonly DependencyProperty BackgroundOpacityProperty =
DependencyProperty.Register(nameof(BackgroundOpacity), typeof(double), typeof(CustomDrawer),
new PropertyMetadata(0.6));
public static readonly DependencyProperty DrawerBackgroundProperty =
DependencyProperty.Register(nameof(DrawerBackground), typeof(Brush), typeof(CustomDrawer),
new PropertyMetadata(Brushes.Black));
#endregion
#region
/// <summary>
/// 抽屉打开后触发的事件
/// </summary>
public event EventHandler Opened;
/// <summary>
/// 抽屉关闭前触发的事件,可以取消关闭操作
/// </summary>
public event CancelEventHandler Closing;
/// <summary>
/// 引发 Opened 事件
/// </summary>
protected virtual void OnOpened()
{
Opened?.Invoke(this, EventArgs.Empty);
}
/// <summary>
/// 引发 Closing 事件
/// </summary>
/// <returns>如果取消关闭,则返回 true否则返回 false</returns>
protected virtual bool OnClosing()
{
if (Closing != null)
{
CancelEventArgs args = new CancelEventArgs();
Closing(this, args);
return args.Cancel;
}
return false;
}
#endregion
#region
public bool IsOpen
{
get => (bool)GetValue(IsOpenProperty);
set => SetValue(IsOpenProperty, value);
}
public DrawerPosition DrawerPosition
{
get => (DrawerPosition)GetValue(DrawerPositionProperty);
set => SetValue(DrawerPositionProperty, value);
}
public double OpenWidth
{
get => (double)GetValue(OpenWidthProperty);
set => SetValue(OpenWidthProperty, value);
}
public double OpenHeight
{
get => (double)GetValue(OpenHeightProperty);
set => SetValue(OpenHeightProperty, value);
}
public TimeSpan AnimationDuration
{
get => (TimeSpan)GetValue(AnimationDurationProperty);
set => SetValue(AnimationDurationProperty, value);
}
public double BackgroundOpacity
{
get => (double)GetValue(BackgroundOpacityProperty);
set => SetValue(BackgroundOpacityProperty, value);
}
public Brush DrawerBackground
{
get => (Brush)GetValue(DrawerBackgroundProperty);
set => SetValue(DrawerBackgroundProperty, value);
}
#endregion
private Border _backgroundOverlay;
private Border _drawerContainer;
private Grid _mainGrid;
static CustomDrawer()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(CustomDrawer),
new FrameworkPropertyMetadata(typeof(CustomDrawer)));
}
public CustomDrawer()
{
this.Loaded += CustomDrawer_Loaded;
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
_mainGrid = GetTemplateChild("PART_MainGrid") as Grid;
_backgroundOverlay = GetTemplateChild("PART_BackgroundOverlay") as Border;
_drawerContainer = GetTemplateChild("PART_DrawerContainer") as Border;
if (_backgroundOverlay != null)
{
_backgroundOverlay.MouseDown += BackgroundOverlay_MouseDown;
}
UpdateDrawerPosition();
UpdateOpenState(false);
}
private void CustomDrawer_Loaded(object sender, RoutedEventArgs e)
{
UpdateOpenState(false);
}
private void BackgroundOverlay_MouseDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
CloseDrawer();
}
private static void OnIsOpenChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is CustomDrawer drawer)
{
bool newValue = (bool)e.NewValue;
bool oldValue = (bool)e.OldValue;
// 如果是从打开到关闭状态,需要触发关闭前事件
if (oldValue && !newValue)
{
bool cancel = drawer.OnClosing();
if (cancel)
{
// 如果取消关闭,则恢复 IsOpen 为 true
drawer.IsOpen = true;
return;
}
}
// 更新抽屉状态(动画)
drawer.UpdateOpenState(true);
}
}
private static void OnDrawerPositionChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is CustomDrawer drawer)
{
drawer.UpdateDrawerPosition();
drawer.UpdateOpenState(false);
}
}
private void UpdateDrawerPosition()
{
if (_drawerContainer == null) return;
switch (DrawerPosition)
{
case DrawerPosition.Left:
_drawerContainer.HorizontalAlignment = HorizontalAlignment.Left;
_drawerContainer.VerticalAlignment = VerticalAlignment.Stretch;
_drawerContainer.Width = OpenWidth;
_drawerContainer.Height = double.NaN;
break;
case DrawerPosition.Right:
_drawerContainer.HorizontalAlignment = HorizontalAlignment.Right;
_drawerContainer.VerticalAlignment = VerticalAlignment.Stretch;
_drawerContainer.Width = OpenWidth;
_drawerContainer.Height = double.NaN;
break;
case DrawerPosition.Top:
_drawerContainer.HorizontalAlignment = HorizontalAlignment.Stretch;
_drawerContainer.VerticalAlignment = VerticalAlignment.Top;
_drawerContainer.Width = double.NaN;
_drawerContainer.Height = OpenHeight;
break;
case DrawerPosition.Bottom:
_drawerContainer.HorizontalAlignment = HorizontalAlignment.Stretch;
_drawerContainer.VerticalAlignment = VerticalAlignment.Bottom;
_drawerContainer.Width = double.NaN;
_drawerContainer.Height = OpenHeight;
break;
}
}
private void UpdateOpenState(bool animate)
{
if (_drawerContainer == null || _backgroundOverlay == null) return;
_backgroundOverlay.IsHitTestVisible = IsOpen;
_drawerContainer.Opacity = 1;
if (IsOpen)
{
Visibility = Visibility.Visible;
}
// 每次更新状态时重新应用宽高
switch (DrawerPosition)
{
case DrawerPosition.Left:
case DrawerPosition.Right:
_drawerContainer.Width = OpenWidth;
_drawerContainer.Height = double.NaN;
break;
case DrawerPosition.Top:
case DrawerPosition.Bottom:
_drawerContainer.Width = double.NaN;
_drawerContainer.Height = OpenHeight;
break;
}
if (animate)
{
// 动画背景遮罩
DoubleAnimation backgroundAnimation = new DoubleAnimation
{
To = IsOpen ? BackgroundOpacity : 0,
Duration = AnimationDuration
};
_backgroundOverlay.BeginAnimation(OpacityProperty, backgroundAnimation);
// 确保RenderTransform已设置
if (_drawerContainer.RenderTransform == null || !(_drawerContainer.RenderTransform is TranslateTransform))
{
_drawerContainer.RenderTransform = new TranslateTransform();
}
TranslateTransform transform = (TranslateTransform)_drawerContainer.RenderTransform;
// 动画抽屉
DoubleAnimation drawerAnimation = new DoubleAnimation
{
Duration = AnimationDuration,
// 弹性动画效果
// EasingFunction = IsOpen
// ? new BackEase { EasingMode = EasingMode.EaseOut, Amplitude = 0.3 }
// : new ExponentialEase { EasingMode = EasingMode.EaseIn, Exponent = 6 }
};
// 如果是打开操作在动画完成时触发Opened事件
if (IsOpen)
{
drawerAnimation.Completed += (s, e) => OnOpened();
}
switch (DrawerPosition)
{
case DrawerPosition.Left:
// 打开时,先设置初始位置
if (IsOpen)
{
transform.X = -OpenWidth;
}
drawerAnimation.To = IsOpen ? 0 : -OpenWidth;
transform.BeginAnimation(TranslateTransform.XProperty, drawerAnimation);
break;
case DrawerPosition.Right:
// 打开时,先设置初始位置
if (IsOpen)
{
transform.X = OpenWidth;
}
drawerAnimation.To = IsOpen ? 0 : OpenWidth;
transform.BeginAnimation(TranslateTransform.XProperty, drawerAnimation);
break;
case DrawerPosition.Top:
// 打开时,先设置初始位置
if (IsOpen)
{
transform.Y = -OpenHeight;
}
drawerAnimation.To = IsOpen ? 0 : -OpenHeight;
transform.BeginAnimation(TranslateTransform.YProperty, drawerAnimation);
break;
case DrawerPosition.Bottom:
// 打开时,先设置初始位置
if (IsOpen)
{
transform.Y = OpenHeight;
}
drawerAnimation.To = IsOpen ? 0 : OpenHeight;
transform.BeginAnimation(TranslateTransform.YProperty, drawerAnimation);
break;
}
if (!IsOpen)
{
drawerAnimation.Completed += (s, e) =>
{
if (!IsOpen)
{
Visibility = Visibility.Collapsed;
}
};
}
}
else
{
// 无动画直接设置
_backgroundOverlay.Opacity = IsOpen ? BackgroundOpacity : 0;
TranslateTransform transform = new TranslateTransform();
_drawerContainer.RenderTransform = transform;
switch (DrawerPosition)
{
case DrawerPosition.Left:
transform.X = IsOpen ? 0 : -OpenWidth;
break;
case DrawerPosition.Right:
transform.X = IsOpen ? 0 : OpenWidth;
break;
case DrawerPosition.Top:
transform.Y = IsOpen ? 0 : -OpenHeight;
break;
case DrawerPosition.Bottom:
transform.Y = IsOpen ? 0 : OpenHeight;
break;
}
Visibility = IsOpen ? Visibility.Visible : Visibility.Collapsed;
// 如果是无动画模式且正在打开立即触发Opened事件
if (IsOpen)
{
OnOpened();
}
}
}
/// <summary>
/// 关闭抽屉的方法,会触发 Closing 事件并可能被取消
/// </summary>
/// <returns>如果成功关闭返回 true如果被取消返回 false</returns>
public bool CloseDrawer()
{
if (!IsOpen)
return true;
if (OnClosing())
return false;
IsOpen = false;
return true;
}
}
public enum DrawerPosition
{
Left,
Right,
Top,
Bottom
}