From 815728b9c47fc6b8fb866cb2dcdad431acfaeedf Mon Sep 17 00:00:00 2001
From: uye <99072975+ABA2396@users.noreply.github.com>
Date: Sat, 28 Mar 2026 13:49:59 +0800
Subject: [PATCH] =?UTF-8?q?feat:=20=E8=87=AA=E5=8A=A8=E6=88=98=E6=96=97?=
=?UTF-8?q?=E6=BB=91=E5=9D=97=E5=8A=A8=E7=94=BB=E6=95=88=E6=9E=9C?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
src/MaaWpfGui/Views/UI/CopilotView.xaml | 95 +++++++++-----
src/MaaWpfGui/Views/UI/CopilotView.xaml.cs | 145 +++++++++++++++++++++
2 files changed, 209 insertions(+), 31 deletions(-)
diff --git a/src/MaaWpfGui/Views/UI/CopilotView.xaml b/src/MaaWpfGui/Views/UI/CopilotView.xaml
index ef63ac482d..a2433aa024 100644
--- a/src/MaaWpfGui/Views/UI/CopilotView.xaml
+++ b/src/MaaWpfGui/Views/UI/CopilotView.xaml
@@ -42,37 +42,70 @@
Padding="2"
Background="{DynamicResource MouseOverRegionBrushOpacity75}"
CornerRadius="4">
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/MaaWpfGui/Views/UI/CopilotView.xaml.cs b/src/MaaWpfGui/Views/UI/CopilotView.xaml.cs
index 124d17307e..b228b486a1 100644
--- a/src/MaaWpfGui/Views/UI/CopilotView.xaml.cs
+++ b/src/MaaWpfGui/Views/UI/CopilotView.xaml.cs
@@ -11,10 +11,17 @@
// but WITHOUT ANY WARRANTY
//
+using System;
using System.Threading.Tasks;
using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Media;
+using System.Windows.Media.Animation;
+using System.Windows.Threading;
using MaaWpfGui.Models;
using MaaWpfGui.ViewModels.UI;
+using Point = System.Windows.Point;
+using Rect = System.Windows.Rect;
namespace MaaWpfGui.Views.UI;
@@ -23,11 +30,149 @@ namespace MaaWpfGui.Views.UI;
///
public partial class CopilotView
{
+ private static readonly Duration CopilotTabAnimationDuration = new(TimeSpan.FromMilliseconds(180));
+
public CopilotView()
{
InitializeComponent();
}
+ private void CopilotTabList_Loaded(object sender, RoutedEventArgs e) => QueueCopilotTabIndicatorUpdate(false);
+
+ private void CopilotTabList_SelectionChanged(object sender, SelectionChangedEventArgs e)
+ {
+ if (e.Source == sender)
+ {
+ QueueCopilotTabIndicatorUpdate(true);
+ }
+ }
+
+ private void CopilotTabList_SizeChanged(object sender, SizeChangedEventArgs e) => QueueCopilotTabIndicatorUpdate(false);
+
+ private void CopilotTabItem_Loaded(object sender, RoutedEventArgs e) => QueueCopilotTabIndicatorUpdate(false);
+
+ private void CopilotTabItem_SizeChanged(object sender, SizeChangedEventArgs e) => QueueCopilotTabIndicatorUpdate(false);
+
+ private void QueueCopilotTabIndicatorUpdate(bool animate) => Dispatcher.BeginInvoke(() => UpdateCopilotTabIndicator(animate), DispatcherPriority.Loaded);
+
+ private void UpdateCopilotTabIndicator(bool animate)
+ {
+ if (!TryGetSelectedCopilotTabBounds(out var bounds))
+ {
+ if (!IsLoaded || CopilotTabList.SelectedItem is null)
+ {
+ HideCopilotTabIndicator();
+ }
+
+ return;
+ }
+
+ CopilotTabIndicator.Opacity = 1;
+ TranslateTransform translateTransform = EnsureCopilotTabIndicatorTransform();
+ bool shouldAnimate = animate && CopilotTabIndicator.Width > 0 && CopilotTabIndicator.Height > 0;
+ if (!shouldAnimate)
+ {
+ StopCopilotTabIndicatorAnimations();
+ SetCopilotTabIndicatorBounds(translateTransform, bounds);
+ return;
+ }
+
+ AnimateCopilotTabIndicator(translateTransform, TranslateTransform.XProperty, bounds.X);
+ AnimateCopilotTabIndicator(translateTransform, TranslateTransform.YProperty, bounds.Y);
+ AnimateCopilotTabIndicator(CopilotTabIndicator, WidthProperty, bounds.Width);
+ AnimateCopilotTabIndicator(CopilotTabIndicator, HeightProperty, bounds.Height);
+ }
+
+ private void HideCopilotTabIndicator()
+ {
+ StopCopilotTabIndicatorAnimations();
+ CopilotTabIndicator.Opacity = 0;
+ }
+
+ private bool TryGetSelectedCopilotTabBounds(out Rect bounds)
+ {
+ bounds = default;
+ if (!IsLoaded || CopilotTabList.SelectedItem is null)
+ {
+ return false;
+ }
+
+ if (CopilotTabList.ItemContainerGenerator.ContainerFromItem(CopilotTabList.SelectedItem) is not ListBoxItem item ||
+ !item.IsLoaded ||
+ item.ActualWidth <= 0 ||
+ item.ActualHeight <= 0)
+ {
+ return false;
+ }
+
+ try
+ {
+ Point topLeft = item.TransformToAncestor(CopilotTabHost).Transform(new Point(0, 0));
+ bounds = new Rect(topLeft.X, topLeft.Y, item.ActualWidth, item.ActualHeight);
+ return true;
+ }
+ catch
+ {
+ return false;
+ }
+ }
+
+ private TranslateTransform EnsureCopilotTabIndicatorTransform()
+ {
+ if (CopilotTabIndicator.RenderTransform is TranslateTransform translateTransform)
+ {
+ return translateTransform;
+ }
+
+ translateTransform = new TranslateTransform();
+ CopilotTabIndicator.RenderTransform = translateTransform;
+ return translateTransform;
+ }
+
+ private void SetCopilotTabIndicatorBounds(TranslateTransform translateTransform, Rect bounds)
+ {
+ translateTransform.X = bounds.X;
+ translateTransform.Y = bounds.Y;
+ CopilotTabIndicator.Width = bounds.Width;
+ CopilotTabIndicator.Height = bounds.Height;
+ }
+
+ private void StopCopilotTabIndicatorAnimations()
+ {
+ if (CopilotTabIndicator.RenderTransform is TranslateTransform translateTransform)
+ {
+ StopCopilotTabIndicatorAnimations(translateTransform);
+ }
+
+ CopilotTabIndicator.BeginAnimation(WidthProperty, null);
+ CopilotTabIndicator.BeginAnimation(HeightProperty, null);
+ }
+
+ private static void StopCopilotTabIndicatorAnimations(TranslateTransform translateTransform)
+ {
+ translateTransform.BeginAnimation(TranslateTransform.XProperty, null);
+ translateTransform.BeginAnimation(TranslateTransform.YProperty, null);
+ }
+
+ private static void AnimateCopilotTabIndicator(TranslateTransform target, DependencyProperty property, double toValue)
+ {
+ target.BeginAnimation(property, CreateCopilotTabIndicatorAnimation(toValue));
+ }
+
+ private static void AnimateCopilotTabIndicator(Border target, DependencyProperty property, double toValue)
+ {
+ target.BeginAnimation(property, CreateCopilotTabIndicatorAnimation(toValue));
+ }
+
+ private static DoubleAnimation CreateCopilotTabIndicatorAnimation(double toValue)
+ {
+ return new DoubleAnimation {
+ To = toValue,
+ Duration = CopilotTabAnimationDuration,
+ EasingFunction = new CubicEase { EasingMode = EasingMode.EaseOut },
+ };
+ }
+
private void FileTreeView_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs