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 e) { if (DataContext is CopilotViewModel viewModel && e.NewValue is CopilotFileItem fileItem && !fileItem.IsFolder)