diff --git a/Ink Canvas/Controls/PptNavBar.xaml b/Ink Canvas/Controls/PptNavBar.xaml new file mode 100644 index 00000000..6de155d7 --- /dev/null +++ b/Ink Canvas/Controls/PptNavBar.xaml @@ -0,0 +1,136 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Ink Canvas/Controls/PptNavBar.xaml.cs b/Ink Canvas/Controls/PptNavBar.xaml.cs new file mode 100644 index 00000000..fff897a1 --- /dev/null +++ b/Ink Canvas/Controls/PptNavBar.xaml.cs @@ -0,0 +1,313 @@ +using System; +using System.Collections.Generic; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Windows.Threading; + +namespace Ink_Canvas.Controls +{ + /// + /// PPT 翻页 + 增强预览一体化控件。 + /// 通过 切换底部条 (LB/RB) 与侧边条 (LS/RS) 布局, + /// 预览列表内嵌于同一个 Border,展开时占据按钮组之外的剩余空间。 + /// + public partial class PptNavBar : UserControl + { + public sealed class PreviewItem + { + public int SlideNumber { get; set; } + public BitmapImage Thumbnail { get; set; } + } + + public enum NavDirection + { + LeftBottom, + RightBottom, + LeftSide, + RightSide + } + + public static readonly DependencyProperty DirectionProperty = DependencyProperty.Register( + nameof(Direction), typeof(NavDirection), typeof(PptNavBar), + new PropertyMetadata(NavDirection.LeftBottom, OnDirectionChanged)); + + public static readonly DependencyProperty CurrentSlideProperty = DependencyProperty.Register( + nameof(CurrentSlide), typeof(int), typeof(PptNavBar), + new PropertyMetadata(0, OnPageChanged)); + + public static readonly DependencyProperty TotalSlidesProperty = DependencyProperty.Register( + nameof(TotalSlides), typeof(int), typeof(PptNavBar), + new PropertyMetadata(0, OnPageChanged)); + + public static readonly DependencyProperty PreviewItemsProperty = DependencyProperty.Register( + nameof(PreviewItems), typeof(IList), typeof(PptNavBar), + new PropertyMetadata(null, OnPreviewItemsChanged)); + + public static readonly DependencyProperty IsPreviewExpandedProperty = DependencyProperty.Register( + nameof(IsPreviewExpanded), typeof(bool), typeof(PptNavBar), + new PropertyMetadata(false, OnIsPreviewExpandedChanged)); + + public NavDirection Direction + { + get => (NavDirection)GetValue(DirectionProperty); + set => SetValue(DirectionProperty, value); + } + + public int CurrentSlide + { + get => (int)GetValue(CurrentSlideProperty); + set => SetValue(CurrentSlideProperty, value); + } + + public int TotalSlides + { + get => (int)GetValue(TotalSlidesProperty); + set => SetValue(TotalSlidesProperty, value); + } + + public IList PreviewItems + { + get => (IList)GetValue(PreviewItemsProperty); + set => SetValue(PreviewItemsProperty, value); + } + + public bool IsPreviewExpanded + { + get => (bool)GetValue(IsPreviewExpandedProperty); + set => SetValue(IsPreviewExpandedProperty, value); + } + + public event EventHandler PreviousClick; + public event EventHandler NextClick; + public event EventHandler PageClick; + public event EventHandler SlideSelected; + public event EventHandler PreviousPressedDown; + public event EventHandler NextPressedDown; + public event EventHandler PressEnded; + + // 静态几何(左下/右下:水平箭头;左侧/右侧:垂直箭头) + private static readonly Geometry HArrowLeft = Geometry.Parse("F0 M24,24z M0,0z M3.3994,12.9642C2.86687,12.4317,2.86687,11.5683,3.3994,11.0358L9.94485,4.49031C10.4774,3.95777 11.3408,3.95777 11.8733,4.49031 12.4059,5.02284 12.4059,5.88625 11.8733,6.41878L7.65575,10.6364 19.6364,10.6364C20.3895,10.6364 21,11.2469 21,12 21,12.7531 20.3895,13.3636 19.6364,13.3636L7.65575,13.3636 11.8733,17.5812C12.4059,18.1137 12.4059,18.9772 11.8733,19.5097 11.3408,20.0422 10.4774,20.0422 9.94485,19.5097L3.3994,12.9642z"); + private static readonly Geometry HArrowRight = Geometry.Parse("F0 M24,24z M0,0z M20.6006,12.9642C21.1331,12.4317,21.1331,11.5683,20.6006,11.0358L14.0551,4.49031C13.5226,3.95777 12.6592,3.95777 12.1267,4.49031 11.5941,5.02284 11.5941,5.88625 12.1267,6.41878L16.3443,10.6364 4.36364,10.6364C3.61052,10.6364 3,11.2469 3,12 3,12.7531 3.61052,13.3636 4.36364,13.3636L16.3443,13.3636 12.1267,17.5812C11.5941,18.1137 11.5941,18.9772 12.1267,19.5097 12.6592,20.0422 13.5226,20.0422 14.0551,19.5097L20.6006,12.9642z"); + private static readonly Geometry VArrowUp = Geometry.Parse("F0 M24,24z M0,0z M11.0357,3.3994C11.5682,2.86687,12.4316,2.86687,12.9641,3.3994L19.5096,9.94485C20.0421,10.4774 20.0421,11.3408 19.5096,11.8733 18.9771,12.4059 18.1137,12.4059 17.5811,11.8733L13.3635,7.65575 13.3635,19.6364C13.3635,20.3895 12.753,21 11.9999,21 11.2468,21 10.6363,20.3895 10.6363,19.6364L10.6363,7.65575 6.41869,11.8733C5.88616,12.4059 5.02275,12.4059 4.49022,11.8733 3.95769,11.3408 3.95769,10.4774 4.49022,9.94485L11.0357,3.3994z"); + private static readonly Geometry VArrowDown = Geometry.Parse("F0 M24,24z M0,0z M11.0357,20.6006C11.5682,21.1331,12.4316,21.1331,12.9641,20.6006L19.5096,14.0551C20.0421,13.5226 20.0421,12.6592 19.5096,12.1267 18.9771,11.5941 18.1137,11.5941 17.5811,12.1267L13.3635,16.3443 13.3635,4.36364C13.3635,3.61052 12.753,3 11.9999,3 11.2468,3 10.6363,3.61052 10.6363,4.36364L10.6363,16.3443 6.41869,12.1267C5.88616,11.5941 5.02275,11.5941 4.49022,12.1267 3.95769,12.6592 3.95769,13.5226 4.49022,14.0551L11.0357,20.6006z"); + + public PptNavBar() + { + InitializeComponent(); + ApplyDirection(Direction); + } + + private static void OnDirectionChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + if (d is PptNavBar bar) bar.ApplyDirection((NavDirection)e.NewValue); + } + + private static void OnPageChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + if (d is PptNavBar bar) bar.RefreshPageText(); + } + + private static void OnPreviewItemsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + if (d is PptNavBar bar) + { + bar.PreviewList.ItemsSource = e.NewValue as IList; + bar.SyncPreviewSelection(); + } + } + + private static void OnIsPreviewExpandedChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + if (d is PptNavBar bar) + { + bar.PreviewList.Visibility = (bool)e.NewValue ? Visibility.Visible : Visibility.Collapsed; + if ((bool)e.NewValue) bar.SyncPreviewSelection(); + } + } + + private void ApplyDirection(NavDirection dir) + { + // 默认值 + DockPanel.SetDock(PreviewList, Dock.Top); + ButtonRow.Orientation = Orientation.Horizontal; + ButtonRow.ClearValue(WidthProperty); + ButtonRow.ClearValue(HeightProperty); + PreviewList.ClearValue(WidthProperty); + PreviewList.ClearValue(HeightProperty); + PreviewList.ClearValue(MaxHeightProperty); + PreviewList.ClearValue(MaxWidthProperty); + + switch (dir) + { + case NavDirection.LeftBottom: + case NavDirection.RightBottom: + DockPanel.SetDock(PreviewList, Dock.Top); + DockPanel.SetDock(ButtonRow, Dock.Bottom); + ButtonRow.Orientation = Orientation.Horizontal; + ButtonRow.Height = 50; + // ListBox 宽度跟随 ButtonRow 实际宽度 + PreviewList.SetBinding(WidthProperty, new System.Windows.Data.Binding(nameof(ButtonRow.ActualWidth)) { Source = ButtonRow }); + PreviewList.MaxHeight = 380; + PreviousButtonGeometry.Geometry = HArrowLeft; + NextButtonGeometry.Geometry = HArrowRight; + break; + case NavDirection.LeftSide: + DockPanel.SetDock(PreviewList, Dock.Right); + DockPanel.SetDock(ButtonRow, Dock.Left); + ButtonRow.Orientation = Orientation.Vertical; + ButtonRow.Width = 50; + PreviewList.Width = 240; + PreviewList.MaxHeight = 480; + PreviousButtonGeometry.Geometry = VArrowUp; + NextButtonGeometry.Geometry = VArrowDown; + break; + case NavDirection.RightSide: + DockPanel.SetDock(PreviewList, Dock.Left); + DockPanel.SetDock(ButtonRow, Dock.Right); + ButtonRow.Orientation = Orientation.Vertical; + ButtonRow.Width = 50; + PreviewList.Width = 240; + PreviewList.MaxHeight = 480; + PreviousButtonGeometry.Geometry = VArrowUp; + NextButtonGeometry.Geometry = VArrowDown; + break; + } + } + + private void RefreshPageText() + { + if (CurrentSlide > 0 && TotalSlides > 0) + { + PageNowText.Text = CurrentSlide.ToString(); + PageTotalText.Text = $"/ {TotalSlides}"; + } + else + { + PageNowText.Text = "?"; + PageTotalText.Text = "/ ?"; + } + SyncPreviewSelection(); + } + + private void SyncPreviewSelection() + { + if (PreviewItems == null || CurrentSlide <= 0) return; + foreach (var item in PreviewItems) + { + if (item.SlideNumber == CurrentSlide) + { + PreviewList.SelectedItem = item; + PreviewList.ScrollIntoView(item); + return; + } + } + } + + private void SetFeedback(Border feedback, double opacity) => feedback.Opacity = opacity; + + private object _lastDown; + + private void PreviousButton_MouseDown(object sender, MouseButtonEventArgs e) + { + _lastDown = sender; + SetFeedback(PreviousButtonFeedbackBorder, 0.15); + PreviousPressedDown?.Invoke(this, EventArgs.Empty); + } + + private void PreviousButton_MouseUp(object sender, MouseButtonEventArgs e) + { + SetFeedback(PreviousButtonFeedbackBorder, 0); + PressEnded?.Invoke(this, EventArgs.Empty); + if (_lastDown != sender) return; + _lastDown = null; + PreviousClick?.Invoke(this, EventArgs.Empty); + } + + private void PreviousButton_MouseLeave(object sender, MouseEventArgs e) + { + SetFeedback(PreviousButtonFeedbackBorder, 0); + _lastDown = null; + PressEnded?.Invoke(this, EventArgs.Empty); + } + + private void NextButton_MouseDown(object sender, MouseButtonEventArgs e) + { + _lastDown = sender; + SetFeedback(NextButtonFeedbackBorder, 0.15); + NextPressedDown?.Invoke(this, EventArgs.Empty); + } + + private void NextButton_MouseUp(object sender, MouseButtonEventArgs e) + { + SetFeedback(NextButtonFeedbackBorder, 0); + PressEnded?.Invoke(this, EventArgs.Empty); + if (_lastDown != sender) return; + _lastDown = null; + NextClick?.Invoke(this, EventArgs.Empty); + } + + private void NextButton_MouseLeave(object sender, MouseEventArgs e) + { + SetFeedback(NextButtonFeedbackBorder, 0); + _lastDown = null; + PressEnded?.Invoke(this, EventArgs.Empty); + } + + private void PageButton_MouseDown(object sender, MouseButtonEventArgs e) + { + _lastDown = sender; + SetFeedback(PageButtonFeedbackBorder, 0.15); + } + + private void PageButton_MouseUp(object sender, MouseButtonEventArgs e) + { + SetFeedback(PageButtonFeedbackBorder, 0); + if (_lastDown != sender) return; + _lastDown = null; + PageClick?.Invoke(this, EventArgs.Empty); + } + + private void PageButton_MouseLeave(object sender, MouseEventArgs e) + { + SetFeedback(PageButtonFeedbackBorder, 0); + _lastDown = null; + } + + private void PreviewList_MouseUp(object sender, MouseButtonEventArgs e) + { + if (PreviewList.SelectedItem is PreviewItem item) + { + SlideSelected?.Invoke(this, item.SlideNumber); + } + } + + public void ApplyTheme(bool isDark) + { + var fgBrush = isDark ? Brushes.White : new SolidColorBrush(Color.FromRgb(39, 39, 42)); + var feedbackBrush = isDark ? Brushes.White : new SolidColorBrush(Color.FromRgb(24, 24, 27)); + var bgBrush = isDark + ? new SolidColorBrush(Color.FromRgb(39, 39, 42)) + : new SolidColorBrush(Color.FromRgb(244, 244, 245)); + var borderBrush = isDark + ? new SolidColorBrush(Color.FromRgb(82, 82, 91)) + : new SolidColorBrush(Color.FromRgb(161, 161, 170)); + + PreviousButtonGeometry.Brush = fgBrush; + NextButtonGeometry.Brush = fgBrush; + PreviousButtonFeedbackBorder.Background = feedbackBrush; + NextButtonFeedbackBorder.Background = feedbackBrush; + PageButtonFeedbackBorder.Background = feedbackBrush; + PageNowText.Foreground = fgBrush; + PageTotalText.Foreground = fgBrush; + RootBorder.Background = bgBrush; + RootBorder.BorderBrush = borderBrush; + Resources["PptNavBarItemForeground"] = fgBrush; + } + + public void SetPageButtonVisibility(Visibility v) => PageButtonBorder.Visibility = v; + public void SetBarOpacity(double opacity) => RootBorder.Opacity = opacity; + } +} \ No newline at end of file diff --git a/Ink Canvas/Helpers/PPTUIManager.cs b/Ink Canvas/Helpers/PPTUIManager.cs index c7415539..f9be65a7 100644 --- a/Ink Canvas/Helpers/PPTUIManager.cs +++ b/Ink Canvas/Helpers/PPTUIManager.cs @@ -86,18 +86,8 @@ namespace Ink_Canvas.Helpers _mainWindow.BtnPPTSlideShow.Visibility = Visibility.Collapsed; _mainWindow.BtnPPTSlideShowEnd.Visibility = Visibility.Visible; - // 只有在页数有效时才更新页码显示 - if (currentSlide > 0 && totalSlides > 0) - { - _mainWindow.PPTBtnPageNow.Text = currentSlide.ToString(); - _mainWindow.PPTBtnPageTotal.Text = $"/ {totalSlides}"; - } - else - { - // 页数无效时清空页码显示 - _mainWindow.PPTBtnPageNow.Text = "?"; - _mainWindow.PPTBtnPageTotal.Text = "/ ?"; - } + // 同步页码到所有翻页条 + 兼容旧绑定的隐藏 placeholder + SetPageNumberOnAllBars(currentSlide, totalSlides); UpdateNavigationPanelsVisibility(); UpdateNavigationButtonStyles(); @@ -163,18 +153,7 @@ namespace Ink_Canvas.Helpers { try { - // 只有在页数有效时才更新页码显示 - if (currentSlide > 0 && totalSlides > 0) - { - _mainWindow.PPTBtnPageNow.Text = currentSlide.ToString(); - _mainWindow.PPTBtnPageTotal.Text = $"/ {totalSlides}"; - } - else - { - // 页数无效时清空页码显示 - _mainWindow.PPTBtnPageNow.Text = "?"; - _mainWindow.PPTBtnPageTotal.Text = "/ ?"; - } + SetPageNumberOnAllBars(currentSlide, totalSlides); } catch (Exception ex) { @@ -183,6 +162,34 @@ namespace Ink_Canvas.Helpers }); } + private void SetPageNumberOnAllBars(int currentSlide, int totalSlides) + { + var bars = new[] + { + _mainWindow.LeftBottomPanelForPPTNavigation, + _mainWindow.RightBottomPanelForPPTNavigation, + _mainWindow.LeftSidePanelForPPTNavigation, + _mainWindow.RightSidePanelForPPTNavigation, + }; + foreach (var bar in bars) + { + if (bar == null) continue; + bar.CurrentSlide = currentSlide; + bar.TotalSlides = totalSlides; + } + // 兼容旧绑定(其它界面通过 ElementName 引用 PPTBtnPageNow / PPTBtnPageTotal) + if (currentSlide > 0 && totalSlides > 0) + { + _mainWindow.PPTBtnPageNow.Text = currentSlide.ToString(); + _mainWindow.PPTBtnPageTotal.Text = $"/ {totalSlides}"; + } + else + { + _mainWindow.PPTBtnPageNow.Text = "?"; + _mainWindow.PPTBtnPageTotal.Text = "/ ?"; + } + } + /// /// 处理PPT放映状态变化 /// @@ -391,16 +398,17 @@ namespace Ink_Canvas.Helpers // 页码按钮显示 var pageButtonVisibility = options[0] == '2' ? Visibility.Visible : Visibility.Collapsed; - _mainWindow.PPTLSPageButton.Visibility = pageButtonVisibility; - _mainWindow.PPTRSPageButton.Visibility = pageButtonVisibility; + _mainWindow.LeftSidePanelForPPTNavigation.SetPageButtonVisibility(pageButtonVisibility); + _mainWindow.RightSidePanelForPPTNavigation.SetPageButtonVisibility(pageButtonVisibility); - // 透明度设置 - 直接使用用户设置的透明度值 - _mainWindow.PPTBtnLSBorder.Opacity = PPTLSButtonOpacity; - _mainWindow.PPTBtnRSBorder.Opacity = PPTRSButtonOpacity; + // 透明度 + _mainWindow.LeftSidePanelForPPTNavigation.SetBarOpacity(PPTLSButtonOpacity); + _mainWindow.RightSidePanelForPPTNavigation.SetBarOpacity(PPTRSButtonOpacity); // 颜色主题 bool isDarkTheme = options[2] == '2'; - ApplyButtonTheme(_mainWindow.PPTBtnLSBorder, _mainWindow.PPTBtnRSBorder, isDarkTheme, true); + _mainWindow.LeftSidePanelForPPTNavigation.ApplyTheme(isDarkTheme); + _mainWindow.RightSidePanelForPPTNavigation.ApplyTheme(isDarkTheme); } catch (Exception ex) { @@ -419,113 +427,23 @@ namespace Ink_Canvas.Helpers // 页码按钮显示 var pageButtonVisibility = options[0] == '2' ? Visibility.Visible : Visibility.Collapsed; - _mainWindow.PPTLBPageButton.Visibility = pageButtonVisibility; - _mainWindow.PPTRBPageButton.Visibility = pageButtonVisibility; + _mainWindow.LeftBottomPanelForPPTNavigation.SetPageButtonVisibility(pageButtonVisibility); + _mainWindow.RightBottomPanelForPPTNavigation.SetPageButtonVisibility(pageButtonVisibility); - // 透明度设置 - 直接使用用户设置的透明度值 - _mainWindow.PPTBtnLBBorder.Opacity = PPTLBButtonOpacity; - _mainWindow.PPTBtnRBBorder.Opacity = PPTRBButtonOpacity; + // 透明度 + _mainWindow.LeftBottomPanelForPPTNavigation.SetBarOpacity(PPTLBButtonOpacity); + _mainWindow.RightBottomPanelForPPTNavigation.SetBarOpacity(PPTRBButtonOpacity); // 颜色主题 bool isDarkTheme = options[2] == '2'; - ApplyButtonTheme(_mainWindow.PPTBtnLBBorder, _mainWindow.PPTBtnRBBorder, isDarkTheme, false); + _mainWindow.LeftBottomPanelForPPTNavigation.ApplyTheme(isDarkTheme); + _mainWindow.RightBottomPanelForPPTNavigation.ApplyTheme(isDarkTheme); } catch (Exception ex) { LogHelper.WriteLogToFile($"更新底部按钮样式失败: {ex}", LogHelper.LogType.Error); } } - - private void ApplyButtonTheme(Border leftBorder, Border rightBorder, bool isDarkTheme, bool isSideButton) - { - try - { - Color backgroundColor, borderColor, foregroundColor, feedbackColor; - - if (isDarkTheme) - { - backgroundColor = Color.FromRgb(39, 39, 42); - borderColor = Color.FromRgb(82, 82, 91); - foregroundColor = Colors.White; - feedbackColor = Colors.White; - } - else - { - backgroundColor = Color.FromRgb(244, 244, 245); - borderColor = Color.FromRgb(161, 161, 170); - foregroundColor = Color.FromRgb(39, 39, 42); - feedbackColor = Color.FromRgb(24, 24, 27); - } - - // 应用背景和边框颜色 - var backgroundBrush = new SolidColorBrush(backgroundColor); - var borderBrush = new SolidColorBrush(borderColor); - - leftBorder.Background = backgroundBrush; - leftBorder.BorderBrush = borderBrush; - rightBorder.Background = backgroundBrush; - rightBorder.BorderBrush = borderBrush; - - // 应用图标和文字颜色 - var foregroundBrush = new SolidColorBrush(foregroundColor); - var feedbackBrush = new SolidColorBrush(feedbackColor); - - if (isSideButton) - { - ApplySideButtonColors(foregroundBrush, feedbackBrush); - } - else - { - ApplyBottomButtonColors(foregroundBrush, feedbackBrush); - } - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"应用按钮主题失败: {ex}", LogHelper.LogType.Error); - } - } - - private void ApplySideButtonColors(SolidColorBrush foregroundBrush, SolidColorBrush feedbackBrush) - { - // 图标颜色 - _mainWindow.PPTLSPreviousButtonGeometry.Brush = foregroundBrush; - _mainWindow.PPTRSPreviousButtonGeometry.Brush = foregroundBrush; - _mainWindow.PPTLSNextButtonGeometry.Brush = foregroundBrush; - _mainWindow.PPTRSNextButtonGeometry.Brush = foregroundBrush; - - // 反馈背景颜色 - _mainWindow.PPTLSPreviousButtonFeedbackBorder.Background = feedbackBrush; - _mainWindow.PPTRSPreviousButtonFeedbackBorder.Background = feedbackBrush; - _mainWindow.PPTLSPageButtonFeedbackBorder.Background = feedbackBrush; - _mainWindow.PPTRSPageButtonFeedbackBorder.Background = feedbackBrush; - _mainWindow.PPTLSNextButtonFeedbackBorder.Background = feedbackBrush; - _mainWindow.PPTRSNextButtonFeedbackBorder.Background = feedbackBrush; - - // 文字颜色 - TextBlock.SetForeground(_mainWindow.PPTLSPageButton, foregroundBrush); - TextBlock.SetForeground(_mainWindow.PPTRSPageButton, foregroundBrush); - } - - private void ApplyBottomButtonColors(SolidColorBrush foregroundBrush, SolidColorBrush feedbackBrush) - { - // 图标颜色 - _mainWindow.PPTLBPreviousButtonGeometry.Brush = foregroundBrush; - _mainWindow.PPTRBPreviousButtonGeometry.Brush = foregroundBrush; - _mainWindow.PPTLBNextButtonGeometry.Brush = foregroundBrush; - _mainWindow.PPTRBNextButtonGeometry.Brush = foregroundBrush; - - // 反馈背景颜色 - _mainWindow.PPTLBPreviousButtonFeedbackBorder.Background = feedbackBrush; - _mainWindow.PPTRBPreviousButtonFeedbackBorder.Background = feedbackBrush; - _mainWindow.PPTLBPageButtonFeedbackBorder.Background = feedbackBrush; - _mainWindow.PPTRBPageButtonFeedbackBorder.Background = feedbackBrush; - _mainWindow.PPTLBNextButtonFeedbackBorder.Background = feedbackBrush; - _mainWindow.PPTRBNextButtonFeedbackBorder.Background = feedbackBrush; - - // 文字颜色 - TextBlock.SetForeground(_mainWindow.PPTLBPageButton, foregroundBrush); - TextBlock.SetForeground(_mainWindow.PPTRBPageButton, foregroundBrush); - } #endregion } } diff --git a/Ink Canvas/MainWindow.xaml b/Ink Canvas/MainWindow.xaml index 31b93e50..3e1590dd 100644 --- a/Ink Canvas/MainWindow.xaml +++ b/Ink Canvas/MainWindow.xaml @@ -2451,285 +2451,35 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + private const string PowerPointSlideShowWindowClassName = "screenClass"; - private Popup _pptEnhancedPreviewPopup; - private ListBox _pptEnhancedPreviewListBox; #endregion #region PPT Managers @@ -227,6 +225,7 @@ namespace Ink_Canvas { // 初始化长按定时器 InitializeLongPressTimer(); + WirePptNavBars(); // 完全清理旧模式 try @@ -1377,7 +1376,7 @@ namespace Ink_Canvas { try { - await Application.Current.Dispatcher.InvokeAsync(() => DestroyPptEnhancedPreviewPopup()); + await Application.Current.Dispatcher.InvokeAsync(() => CollapseAllPptNavBarPreviews()); if (Settings.Automation.IsAutoFoldAfterPPTSlideShow && !isFloatingBarFolded) { @@ -2251,100 +2250,14 @@ namespace Ink_Canvas /// 2. 检查是否启用了PPT按钮页码点击功能 /// 3. 根据按下的按钮设置相应的反馈边框透明度 /// - private void PPTNavigationBtn_MouseDown(object sender, MouseButtonEventArgs e) + private void PPTNavigationBtn_MouseDown(object sender, MouseButtonEventArgs e) { } + private void PPTNavigationBtn_MouseLeave(object sender, MouseEventArgs e) { } + private void PPTNavigationBtn_MouseUp(object sender, MouseButtonEventArgs e) { } + + /// 由 PptNavBar 控件 PageClick 事件触发的页码点击逻辑。 + private async Task OnPptNavBarPageClickAsync(Controls.PptNavBar bar) { - lastBorderMouseDownObject = sender; if (!Settings.PowerPointSettings.EnablePPTButtonPageClickable) return; - if (sender == PPTLSPageButton) - { - PPTLSPageButtonFeedbackBorder.Opacity = 0.15; - } - else if (sender == PPTRSPageButton) - { - PPTRSPageButtonFeedbackBorder.Opacity = 0.15; - } - else if (sender == PPTLBPageButton) - { - PPTLBPageButtonFeedbackBorder.Opacity = 0.15; - } - else if (sender == PPTRBPageButton) - { - PPTRBPageButtonFeedbackBorder.Opacity = 0.15; - } - } - - /// - /// 处理PPT导航按钮的鼠标离开事件 - /// - /// 事件的来源对象 - /// 鼠标事件参数 - /// - /// 该方法在用户鼠标离开PPT导航按钮时执行以下操作: - /// 1. 重置按下的按钮对象为null - /// 2. 根据离开的按钮设置相应的反馈边框透明度为0(隐藏反馈效果) - /// - private void PPTNavigationBtn_MouseLeave(object sender, MouseEventArgs e) - { - lastBorderMouseDownObject = null; - if (sender == PPTLSPageButton) - { - PPTLSPageButtonFeedbackBorder.Opacity = 0; - } - else if (sender == PPTRSPageButton) - { - PPTRSPageButtonFeedbackBorder.Opacity = 0; - } - else if (sender == PPTLBPageButton) - { - PPTLBPageButtonFeedbackBorder.Opacity = 0; - } - else if (sender == PPTRBPageButton) - { - PPTRBPageButtonFeedbackBorder.Opacity = 0; - } - } - - /// - /// 处理PPT导航按钮的鼠标释放事件 - /// - /// 事件的来源对象 - /// 鼠标按钮事件参数 - /// - /// 该方法在用户释放PPT导航按钮时执行以下操作: - /// 1. 检查释放的按钮是否与按下的按钮一致 - /// 2. 隐藏按钮的反馈效果 - /// 3. 检查是否启用了PPT按钮页码点击功能 - /// 4. 检查PPT是否已连接且在放映状态 - /// 5. 设置背景透明度和颜色 - /// 6. 切换到光标模式 - /// 7. 尝试显示PPT幻灯片导航 - /// 8. 如果浮动栏未折叠,则调整其位置 - /// 9. 捕获并记录可能的异常 - /// - private async void PPTNavigationBtn_MouseUp(object sender, MouseButtonEventArgs e) - { - if (lastBorderMouseDownObject != sender) return; - - if (sender == PPTLSPageButton) - { - PPTLSPageButtonFeedbackBorder.Opacity = 0; - } - else if (sender == PPTRSPageButton) - { - PPTRSPageButtonFeedbackBorder.Opacity = 0; - } - else if (sender == PPTLBPageButton) - { - PPTLBPageButtonFeedbackBorder.Opacity = 0; - } - else if (sender == PPTRBPageButton) - { - PPTRBPageButtonFeedbackBorder.Opacity = 0; - } - - if (!Settings.PowerPointSettings.EnablePPTButtonPageClickable) return; - - // 使用新的PPT管理器检查连接状态 if (_pptManager?.IsConnected != true || _pptManager?.IsInSlideShow != true) { LogHelper.WriteLogToFile("PPT未连接或未在放映状态,无法执行页码点击操作", LogHelper.LogType.Warning); @@ -2357,17 +2270,41 @@ namespace Ink_Canvas GridTransparencyFakeBackground.Background = new SolidColorBrush(StringToColor("#01FFFFFF")); CursorIcon_Click(null, null); - if (Settings.PowerPointSettings.EnablePPTButtonEnhancedPreview) + if (Settings.PowerPointSettings.EnablePPTButtonEnhancedPreview && bar != null) { - await ShowEnhancedPptPreviewAsync(sender as FrameworkElement); + if (bar.IsPreviewExpanded) + { + bar.IsPreviewExpanded = false; + } + else + { + var slides = await Task.Run(BuildPptPreviewItems); + if (slides == null || slides.Count == 0) + { + LogHelper.WriteLogToFile("PPT增强预览未生成可用缩略图,改用默认导航", LogHelper.LogType.Warning); + _pptManager.TryShowSlideNavigation(); + } + else + { + var items = new List(slides.Count); + foreach (var s in slides) + { + items.Add(new Controls.PptNavBar.PreviewItem + { + SlideNumber = s.SlideNumber, + Thumbnail = s.Thumbnail + }); + } + bar.PreviewItems = items; + bar.CurrentSlide = _pptManager?.GetCurrentSlideNumber() ?? 0; + bar.IsPreviewExpanded = true; + } + } } else { - // 使用新的PPT管理器显示导航 if (_pptManager.TryShowSlideNavigation()) { - LogHelper.WriteLogToFile("成功显示PPT幻灯片导航", LogHelper.LogType.Trace); - // 若启用了“翻页时跳过PPT动画”,显示导航后把焦点拉回本窗口 if (Settings.PowerPointSettings.SkipAnimationsWhenGoNext) { try { this.Activate(); } catch { } @@ -2379,7 +2316,6 @@ namespace Ink_Canvas } } - // 控制居中 if (!isFloatingBarFolded) { await Task.Delay(100); @@ -2392,255 +2328,69 @@ namespace Ink_Canvas } } + private void OnPptNavBarSlideSelected(Controls.PptNavBar bar, int slideNumber) + { + try { _pptManager?.TryNavigateToSlide(slideNumber); } + catch (Exception ex) { LogHelper.WriteLogToFile($"PPT增强预览跳转异常: {ex}", LogHelper.LogType.Error); } + finally { if (bar != null) bar.IsPreviewExpanded = false; } + } + private sealed class PptEnhancedPreviewItem { public int SlideNumber { get; set; } public BitmapImage Thumbnail { get; set; } } - private async Task ShowEnhancedPptPreviewAsync(FrameworkElement placementTarget = null) + private void CollapseAllPptNavBarPreviews() { - if (_pptEnhancedPreviewPopup != null && _pptEnhancedPreviewPopup.IsOpen && placementTarget != null && - ReferenceEquals(_pptEnhancedPreviewPopup.PlacementTarget, placementTarget)) + var bars = new[] { - _pptEnhancedPreviewPopup.IsOpen = false; - return; - } - - var slides = await Task.Run(BuildPptPreviewItems); - if (slides == null || slides.Count == 0) + LeftBottomPanelForPPTNavigation, + RightBottomPanelForPPTNavigation, + LeftSidePanelForPPTNavigation, + RightSidePanelForPPTNavigation, + }; + foreach (var bar in bars) { - _pptManager.TryShowSlideNavigation(); - return; - } - - await Application.Current.Dispatcher.InvokeAsync(() => - { - EnsurePptEnhancedPreviewPopupCreated(); - if (_pptEnhancedPreviewListBox == null || _pptEnhancedPreviewPopup == null) return; - - _pptEnhancedPreviewListBox.ItemsSource = slides; - var currentSlide = _pptManager?.GetCurrentSlideNumber() ?? 0; - if (currentSlide > 0) + if (bar == null) continue; + try { - var selected = slides.Find(s => s.SlideNumber == currentSlide); - _pptEnhancedPreviewListBox.SelectedItem = selected; - if (selected != null) - { - _pptEnhancedPreviewListBox.ScrollIntoView(selected); - } + bar.IsPreviewExpanded = false; + bar.PreviewItems = null; } - - var anchor = placementTarget ?? PPTLSPageButton; - if (anchor != null) - { - _pptEnhancedPreviewPopup.PlacementTarget = anchor; - _pptEnhancedPreviewPopup.HorizontalOffset = 0; - _pptEnhancedPreviewPopup.VerticalOffset = 0; - - var anchorRef = anchor; - _pptEnhancedPreviewPopup.CustomPopupPlacementCallback = (popupSize, targetSize, _) => - { - System.Windows.Point pt; - PopupPrimaryAxis axis; - if (anchorRef == PPTLBPageButton || anchorRef == PPTRBPageButton) - { - // 底部翻页按钮:弹窗位于按钮上方,水平居中 - pt = new System.Windows.Point((targetSize.Width - popupSize.Width) / 2, -popupSize.Height); - axis = PopupPrimaryAxis.Horizontal; - } - else if (anchorRef == PPTRSPageButton) - { - // 右侧翻页按钮:弹窗位于按钮左侧,底边与按钮底边对齐(向上展开) - pt = new System.Windows.Point(-popupSize.Width, targetSize.Height - popupSize.Height); - axis = PopupPrimaryAxis.Vertical; - } - else - { - // 左侧翻页按钮(默认):弹窗位于按钮右侧,底边与按钮底边对齐 - pt = new System.Windows.Point(targetSize.Width, targetSize.Height - popupSize.Height); - axis = PopupPrimaryAxis.Vertical; - } - return new[] { new CustomPopupPlacement(pt, axis) }; - }; - _pptEnhancedPreviewPopup.Placement = PlacementMode.Custom; - } - - _pptEnhancedPreviewPopup.IsOpen = true; - }); - } - - private void DestroyPptEnhancedPreviewPopup() - { - try - { - if (_pptEnhancedPreviewListBox != null) - { - _pptEnhancedPreviewListBox.MouseUp -= PPTEnhancedPreviewListBox_OnMouseUp; - _pptEnhancedPreviewListBox.ItemsSource = null; - } - - if (_pptEnhancedPreviewPopup != null) - { - _pptEnhancedPreviewPopup.IsOpen = false; - _pptEnhancedPreviewPopup.Child = null; - _pptEnhancedPreviewPopup.PlacementTarget = null; - } - } - catch - { - // ignore dispose errors - } - finally - { - _pptEnhancedPreviewListBox = null; - _pptEnhancedPreviewPopup = null; + catch { } } } - private void EnsurePptEnhancedPreviewPopupCreated() + /// 在 MainWindow 加载完成后调用,把 4 个 PptNavBar 的事件接到本类。 + private void WirePptNavBars() { - if (_pptEnhancedPreviewPopup != null) return; - - var listBox = new ListBox + var bars = new[] { - Width = 248, - MaxHeight = 560, - Background = Brushes.Transparent, - BorderBrush = Brushes.Transparent, - BorderThickness = new Thickness(0), - Padding = new Thickness(6, 4, 6, 8), - SelectionMode = SelectionMode.Single + LeftBottomPanelForPPTNavigation, + RightBottomPanelForPPTNavigation, + LeftSidePanelForPPTNavigation, + RightSidePanelForPPTNavigation, }; - listBox.SetValue(ScrollViewer.VerticalScrollBarVisibilityProperty, ScrollBarVisibility.Auto); - listBox.SetValue(ScrollViewer.HorizontalScrollBarVisibilityProperty, ScrollBarVisibility.Disabled); - listBox.MouseUp += PPTEnhancedPreviewListBox_OnMouseUp; - - var templateXaml = @" - - - - - - - - - - - - - - - - - - - - -"; - listBox.ItemTemplate = (DataTemplate)XamlReader.Parse(templateXaml); - - var itemStyleXaml = @" -"; - listBox.ItemContainerStyle = (Style)XamlReader.Parse(itemStyleXaml); - - var headerXaml = @" - - - - - -"; - var header = (Border)XamlReader.Parse(headerXaml); - var headerGrid = (Grid)header.Child; - ((TextBlock)headerGrid.Children[0]).Text = "幻灯片预览"; - var countText = (TextBlock)headerGrid.Children[1]; - listBox.Loaded += (s, e) => + foreach (var bar in bars) { - var items = listBox.ItemsSource as System.Collections.IList; - countText.Text = items != null ? $"共 {items.Count} 页" : string.Empty; - }; - - var rootXaml = @" - - - - - -"; - var rootBorder = (Border)XamlReader.Parse(rootXaml); - var dock = (DockPanel)rootBorder.Child; - DockPanel.SetDock(header, Dock.Top); - dock.Children.Add(header); - dock.Children.Add(listBox); - - _pptEnhancedPreviewListBox = listBox; - _pptEnhancedPreviewPopup = new Popup - { - AllowsTransparency = true, - StaysOpen = true, - Placement = PlacementMode.Right, - PopupAnimation = PopupAnimation.Fade, - Child = rootBorder - }; - } - - private void PPTEnhancedPreviewListBox_OnMouseUp(object sender, System.Windows.Input.MouseButtonEventArgs e) - { - if (_pptEnhancedPreviewListBox?.SelectedItem is not PptEnhancedPreviewItem item) return; - try - { - if (_pptManager?.TryNavigateToSlide(item.SlideNumber) != true) + if (bar == null) continue; + bar.PreviousClick += (s, e) => BtnPPTSlidesUp_Click(BtnPPTSlidesUp, null); + bar.NextClick += (s, e) => BtnPPTSlidesDown_Click(BtnPPTSlidesDown, null); + bar.PreviousPressedDown += (s, e) => { - LogHelper.WriteLogToFile($"PPT增强预览跳转失败:{item.SlideNumber}", LogHelper.LogType.Warning); - } - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"PPT增强预览跳转异常: {ex}", LogHelper.LogType.Error); - } - finally - { - if (_pptEnhancedPreviewPopup != null) + if (Settings.PowerPointSettings.EnablePPTButtonLongPressPageTurn) + StartLongPressDetection(s, false); + }; + bar.NextPressedDown += (s, e) => { - _pptEnhancedPreviewPopup.IsOpen = false; - } + if (Settings.PowerPointSettings.EnablePPTButtonLongPressPageTurn) + StartLongPressDetection(s, true); + }; + bar.PressEnded += (s, e) => StopLongPressDetection(); + var captured = bar; + bar.PageClick += async (s, e) => await OnPptNavBarPageClickAsync(captured); + bar.SlideSelected += (s, slideNumber) => OnPptNavBarSlideSelected(captured, slideNumber); } } @@ -2860,7 +2610,7 @@ namespace Ink_Canvas { try { - await Application.Current.Dispatcher.InvokeAsync(() => DestroyPptEnhancedPreviewPopup()); + await Application.Current.Dispatcher.InvokeAsync(() => CollapseAllPptNavBarPreviews()); if (Settings.Automation.IsAutoFoldAfterPPTSlideShow && !isFloatingBarFolded) { @@ -2886,209 +2636,30 @@ namespace Ink_Canvas /// private void GridPPTControlPrevious_MouseDown(object sender, MouseButtonEventArgs e) { - lastBorderMouseDownObject = sender; - if (sender == PPTLSPreviousButtonBorder) - { - PPTLSPreviousButtonFeedbackBorder.Opacity = 0.15; - } - else if (sender == PPTRSPreviousButtonBorder) - { - PPTRSPreviousButtonFeedbackBorder.Opacity = 0.15; - } - else if (sender == PPTLBPreviousButtonBorder) - { - PPTLBPreviousButtonFeedbackBorder.Opacity = 0.15; - } - else if (sender == PPTRBPreviousButtonBorder) - { - PPTRBPreviousButtonFeedbackBorder.Opacity = 0.15; - } - - // 启动长按检测 - if (Settings.PowerPointSettings.EnablePPTButtonLongPressPageTurn) - { - StartLongPressDetection(sender, false); - } + // 旧 XAML 入口已废弃,事件由 PptNavBar 控件转发;保留方法签名以兼容潜在外部引用。 } - /// - /// 处理PPT上一页控制按钮的鼠标离开事件 - /// - /// 事件的来源对象 - /// 鼠标事件参数 - /// - /// 该方法在用户鼠标离开PPT上一页控制按钮时执行以下操作: - /// 1. 重置按下的按钮对象为null - /// 2. 根据离开的按钮设置相应的反馈边框透明度为0(隐藏反馈效果) - /// 3. 停止长按检测 - /// private void GridPPTControlPrevious_MouseLeave(object sender, MouseEventArgs e) { - lastBorderMouseDownObject = null; - if (sender == PPTLSPreviousButtonBorder) - { - PPTLSPreviousButtonFeedbackBorder.Opacity = 0; - } - else if (sender == PPTRSPreviousButtonBorder) - { - PPTRSPreviousButtonFeedbackBorder.Opacity = 0; - } - else if (sender == PPTLBPreviousButtonBorder) - { - PPTLBPreviousButtonFeedbackBorder.Opacity = 0; - } - else if (sender == PPTRBPreviousButtonBorder) - { - PPTRBPreviousButtonFeedbackBorder.Opacity = 0; - } - - // 停止长按检测 StopLongPressDetection(); } - /// - /// 处理PPT上一页控制按钮的鼠标释放事件 - /// - /// 事件的来源对象 - /// 鼠标按钮事件参数 - /// - /// 该方法在用户释放PPT上一页控制按钮时执行以下操作: - /// 1. 检查释放的按钮是否与按下的按钮一致 - /// 2. 根据释放的按钮设置相应的反馈边框透明度为0(隐藏反馈效果) - /// 3. 停止长按检测 - /// 4. 调用上一页按钮的点击事件处理方法,实现切换到上一页的功能 - /// private void GridPPTControlPrevious_MouseUp(object sender, MouseButtonEventArgs e) { - if (lastBorderMouseDownObject != sender) return; - if (sender == PPTLSPreviousButtonBorder) - { - PPTLSPreviousButtonFeedbackBorder.Opacity = 0; - } - else if (sender == PPTRSPreviousButtonBorder) - { - PPTRSPreviousButtonFeedbackBorder.Opacity = 0; - } - else if (sender == PPTLBPreviousButtonBorder) - { - PPTLBPreviousButtonFeedbackBorder.Opacity = 0; - } - else if (sender == PPTRBPreviousButtonBorder) - { - PPTRBPreviousButtonFeedbackBorder.Opacity = 0; - } - - // 停止长按检测 StopLongPressDetection(); - BtnPPTSlidesUp_Click(BtnPPTSlidesUp, null); } - /// - /// 处理PPT下一页控制按钮的鼠标按下事件 - /// - /// 事件的来源对象 - /// 鼠标按钮事件参数 - /// - /// 该方法在用户按下PPT下一页控制按钮时执行以下操作: - /// 1. 记录按下的按钮对象 - /// 2. 根据按下的按钮设置相应的反馈边框透明度 - /// 3. 如果启用了PPT按钮长按翻页功能,则启动长按检测 - /// private void GridPPTControlNext_MouseDown(object sender, MouseButtonEventArgs e) { - lastBorderMouseDownObject = sender; - if (sender == PPTLSNextButtonBorder) - { - PPTLSNextButtonFeedbackBorder.Opacity = 0.15; - } - else if (sender == PPTRSNextButtonBorder) - { - PPTRSNextButtonFeedbackBorder.Opacity = 0.15; - } - else if (sender == PPTLBNextButtonBorder) - { - PPTLBNextButtonFeedbackBorder.Opacity = 0.15; - } - else if (sender == PPTRBNextButtonBorder) - { - PPTRBNextButtonFeedbackBorder.Opacity = 0.15; - } - - // 启动长按检测 - if (Settings.PowerPointSettings.EnablePPTButtonLongPressPageTurn) - { - StartLongPressDetection(sender, true); - } + // 旧 XAML 入口已废弃,事件由 PptNavBar 控件转发;保留方法签名以兼容潜在外部引用。 } - /// - /// 处理PPT下一页控制按钮的鼠标离开事件 - /// - /// 事件的来源对象 - /// 鼠标事件参数 - /// - /// 该方法在用户鼠标离开PPT下一页控制按钮时执行以下操作: - /// 1. 重置按下的按钮对象为null - /// 2. 根据离开的按钮设置相应的反馈边框透明度为0(隐藏反馈效果) - /// 3. 停止长按检测 - /// private void GridPPTControlNext_MouseLeave(object sender, MouseEventArgs e) { - lastBorderMouseDownObject = null; - if (sender == PPTLSNextButtonBorder) - { - PPTLSNextButtonFeedbackBorder.Opacity = 0; - } - else if (sender == PPTRSNextButtonBorder) - { - PPTRSNextButtonFeedbackBorder.Opacity = 0; - } - else if (sender == PPTLBNextButtonBorder) - { - PPTLBNextButtonFeedbackBorder.Opacity = 0; - } - else if (sender == PPTRBNextButtonBorder) - { - PPTRBNextButtonFeedbackBorder.Opacity = 0; - } - - // 停止长按检测 StopLongPressDetection(); } - /// - /// 处理PPT下一页控制按钮的鼠标释放事件 - /// - /// 事件的来源对象 - /// 鼠标按钮事件参数 - /// - /// 该方法在用户释放PPT下一页控制按钮时执行以下操作: - /// 1. 检查释放的按钮是否与按下的按钮一致 - /// 2. 根据释放的按钮设置相应的反馈边框透明度为0(隐藏反馈效果) - /// 3. 停止长按检测 - /// 4. 调用下一页按钮的点击事件处理方法,实现切换到下一页的功能 - /// private void GridPPTControlNext_MouseUp(object sender, MouseButtonEventArgs e) { - if (lastBorderMouseDownObject != sender) return; - if (sender == PPTLSNextButtonBorder) - { - PPTLSNextButtonFeedbackBorder.Opacity = 0; - } - else if (sender == PPTRSNextButtonBorder) - { - PPTRSNextButtonFeedbackBorder.Opacity = 0; - } - else if (sender == PPTLBNextButtonBorder) - { - PPTLBNextButtonFeedbackBorder.Opacity = 0; - } - else if (sender == PPTRBNextButtonBorder) - { - PPTRBNextButtonFeedbackBorder.Opacity = 0; - } - - // 停止长按检测 StopLongPressDetection(); - BtnPPTSlidesDown_Click(BtnPPTSlidesDown, null); } diff --git a/Ink Canvas/Windows/SettingsViews/Pages/PowerPointPage.xaml b/Ink Canvas/Windows/SettingsViews/Pages/PowerPointPage.xaml index 5c975dab..ea25fb9d 100644 --- a/Ink Canvas/Windows/SettingsViews/Pages/PowerPointPage.xaml +++ b/Ink Canvas/Windows/SettingsViews/Pages/PowerPointPage.xaml @@ -83,9 +83,9 @@ - - + @@ -110,9 +110,9 @@ - - + @@ -137,9 +137,9 @@ - - + @@ -164,9 +164,9 @@ - - +