/
githubmirror
/
Files
Обзор
Документация
Войти
/
githubmirror
/
Files
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
src/Files.App/Views/MainPage.xaml.cs
548 строк
20 KB
yair100
Feature: Updated Status Bar design (#18789)
04 авг 2026, 01:51
Не верифицирован
04 авг 2026, 01:51
ca7b24d
Код
Авторство
О чём код?
// Copyright (c) Files Community // Licensed under the MIT License. using CommunityToolkit.WinUI; using Files.App.Controls; using Microsoft.Extensions.Logging; using Microsoft.UI.Dispatching; using Microsoft.UI.Input; using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; using Microsoft.UI.Xaml.Input; using Microsoft.UI.Xaml.Media; using Microsoft.UI.Xaml.Navigation; using System.Runtime.InteropServices; using Windows.Foundation.Metadata; using Windows.Graphics; using Windows.UI.Input; using WinUIEx; using GridSplitter = Files.App.Controls.GridSplitter; using VirtualKey = Windows.System.VirtualKey; namespace Files.App.Views { public sealed partial class MainPage : Page { private IGeneralSettingsService generalSettingsService { get; } = Ioc.Default.GetRequiredService<IGeneralSettingsService>(); private readonly IContentPageContext ContentPageContext = Ioc.Default.GetRequiredService<IContentPageContext>(); public IUserSettingsService UserSettingsService { get; } private readonly IWindowContext WindowContext = Ioc.Default.GetRequiredService<IWindowContext>(); private readonly ICommandManager Commands = Ioc.Default.GetRequiredService<ICommandManager>(); public SidebarViewModel SidebarAdaptiveViewModel { get; } public MainPageViewModel ViewModel { get; } private bool keyReleased = true; private DispatcherQueueTimer _updateDateDisplayTimer; private readonly Dictionary<TabBarItem, double> _sidebarScrollByTab = new(); private TabBarItem? _previousSidebarTab; public MainPage() { InitializeComponent(); // Dependency Injection UserSettingsService = Ioc.Default.GetRequiredService<IUserSettingsService>(); SidebarAdaptiveViewModel = Ioc.Default.GetRequiredService<SidebarViewModel>(); SidebarAdaptiveViewModel.PaneFlyout = (MenuFlyout)Resources["SidebarContextMenu"]; ViewModel = Ioc.Default.GetRequiredService<MainPageViewModel>(); if (AppLanguageHelper.IsPreferredLanguageRtl) { MainWindow.Instance.SetExtendedWindowStyle(ExtendedWindowStyle.LayoutRtl); FlowDirection = FlowDirection.RightToLeft; } ViewModel.PropertyChanged += ViewModel_PropertyChanged; UserSettingsService.OnSettingChangedEvent += UserSettingsService_OnSettingChangedEvent; ContentPageContext.PropertyChanged += ContentPageContext_PropertyChanged; _updateDateDisplayTimer = DispatcherQueue.CreateTimer(); _updateDateDisplayTimer.Interval = TimeSpan.FromSeconds(1); _updateDateDisplayTimer.Tick += UpdateDateDisplayTimer_Tick; ApplySidebarWidthState(); } private async Task AppRunningAsAdminPromptAsync() { var runningAsAdminPrompt = new ContentDialog { Title = Strings.FilesRunningAsAdmin.ToLocalized(), Content = Strings.FilesRunningAsAdminContent.ToLocalized(), PrimaryButtonText = "Ok".ToLocalized(), SecondaryButtonText = Strings.DontShowAgain.ToLocalized() }; var result = await SetContentDialogRoot(runningAsAdminPrompt).TryShowAsync(); if (result == ContentDialogResult.Secondary) UserSettingsService.ApplicationSettingsService.ShowRunningAsAdminPrompt = false; } // WINUI3 private ContentDialog SetContentDialogRoot(ContentDialog contentDialog) { if (ApiInformation.IsApiContractPresent("Windows.Foundation.UniversalApiContract", 8)) contentDialog.XamlRoot = MainWindow.Instance.Content.XamlRoot; return contentDialog; } private void UserSettingsService_OnSettingChangedEvent(object? sender, SettingChangedEventArgs e) { switch (e.SettingName) { case nameof(IInfoPaneSettingsService.IsInfoPaneEnabled): LoadPaneChanged(); break; case nameof(IAppearanceSettingsService.SidebarWidth): ApplySidebarWidthState(); break; } } private void HorizontalMultitaskingControl_Loaded(object sender, RoutedEventArgs e) { TabControl.DragArea.SizeChanged += (_, _) => MainWindow.Instance.RaiseSetTitleBarDragRegion(SetTitleBarDragRegion); TabControl.SizeChanged += (_, _) => MainWindow.Instance.RaiseSetTitleBarDragRegion(SetTitleBarDragRegion); if (ViewModel.MultitaskingControl is not TabBar) { ViewModel.MultitaskingControl = TabControl; ViewModel.MultitaskingControls.Add(TabControl); ViewModel.MultitaskingControl.CurrentInstanceChanged += MultitaskingControl_CurrentInstanceChanged; } } private int SetTitleBarDragRegion(InputNonClientPointerSource source, SizeInt32 size, double scaleFactor, Func<UIElement, RectInt32?, RectInt32> getScaledRect) { var height = (int)TabControl.ActualHeight; source.SetRegionRects(NonClientRegionKind.Passthrough, [getScaledRect(this, new RectInt32(0, 0, (int)(TabControl.ActualWidth + TabControl.Margin.Left - TabControl.DragArea.ActualWidth), height))]); return height; } public async void TabItemContent_ContentChanged(object? sender, TabBarItemParameter e) { if (SidebarAdaptiveViewModel.PaneHolder is null) return; var paneArgs = e.NavigationParameter as PaneNavigationArguments; SidebarAdaptiveViewModel.UpdateSidebarSelectedItemFromArgs(SidebarAdaptiveViewModel.PaneHolder.IsLeftPaneActive ? paneArgs?.LeftPaneNavPathParam : paneArgs?.RightPaneNavPathParam); LoadPaneChanged(); UpdateNavToolbarProperties(); await NavigationHelpers.UpdateInstancePropertiesAsync(paneArgs); // Save the updated tab list AppLifecycleHelper.SaveSessionTabs(); } public async void MultitaskingControl_CurrentInstanceChanged(object? sender, CurrentInstanceChangedEventArgs e) { // Add null check for the event args and CurrentInstance if (e?.CurrentInstance == null) return; // Safely unsubscribe from previous instance if (SidebarAdaptiveViewModel?.PaneHolder is not null) SidebarAdaptiveViewModel.PaneHolder.PropertyChanged -= PaneHolder_PropertyChanged; var navArgs = e.CurrentInstance.TabBarItemParameter?.NavigationParameter; if (e.CurrentInstance is IShellPanesPage currentInstance && SidebarAdaptiveViewModel != null) { SidebarAdaptiveViewModel.PaneHolder = currentInstance; SidebarAdaptiveViewModel.PaneHolder.PropertyChanged += PaneHolder_PropertyChanged; } SidebarAdaptiveViewModel?.NotifyInstanceRelatedPropertiesChanged((navArgs as PaneNavigationArguments)?.LeftPaneNavPathParam); // Safely access nested properties with null checks var statusBarViewModel = SidebarAdaptiveViewModel?.PaneHolder?.ActivePaneOrColumn?.SlimContentPage?.StatusBarViewModel; if (statusBarViewModel is not null) statusBarViewModel.ShowLocals = true; UpdateNavToolbarProperties(); LoadPaneChanged(); e.CurrentInstance.ContentChanged -= TabItemContent_ContentChanged; e.CurrentInstance.ContentChanged += TabItemContent_ContentChanged; await NavigationHelpers.UpdateInstancePropertiesAsync(navArgs); // Focus the content of the selected tab item (this also avoids an issue where the Omnibar sometimes steals the focus) await Task.Delay(100); if (!App.AppModel.IsMainWindowClosed && ContentPageContext?.ShellPage?.PaneHolder != null) ContentPageContext.ShellPage.PaneHolder.FocusActivePane(); } private void PaneHolder_PropertyChanged(object? sender, PropertyChangedEventArgs e) { SidebarAdaptiveViewModel.NotifyInstanceRelatedPropertiesChanged(SidebarAdaptiveViewModel.PaneHolder.ActivePane?.TabBarItemParameter?.NavigationParameter?.ToString()); UpdateNavToolbarProperties(); LoadPaneChanged(); } private void ContentPageContext_PropertyChanged(object? sender, PropertyChangedEventArgs e) { if (e.PropertyName is nameof(IContentPageContext.PageType)) LoadPaneChanged(); } private void UpdateNavToolbarProperties() { if (NavToolbar is not null) NavToolbar.ViewModel = SidebarAdaptiveViewModel.PaneHolder?.ActivePaneOrColumn.ToolbarViewModel; if (InnerNavigationToolbar is not null) InnerNavigationToolbar.ViewModel = SidebarAdaptiveViewModel.PaneHolder?.ActivePaneOrColumn.ToolbarViewModel; } protected override void OnNavigatedTo(NavigationEventArgs e) { _ = ViewModel.OnNavigatedToAsync(e); } protected override async void OnPreviewKeyDown(KeyRoutedEventArgs e) => await OnPreviewKeyDownAsync(e); private async Task OnPreviewKeyDownAsync(KeyRoutedEventArgs e) { base.OnPreviewKeyDown(e); switch (e.Key) { case VirtualKey.Menu: case VirtualKey.Control: case VirtualKey.Shift: case VirtualKey.LeftWindows: case VirtualKey.RightWindows: break; default: var currentModifiers = HotKeyHelpers.GetCurrentKeyModifiers(); HotKey hotKey = new((Keys)e.Key, currentModifiers); var source = e.OriginalSource as DependencyObject; // A textbox takes precedence over certain hotkeys. if (source?.FindAscendantOrSelf<TextBox>() is not null) break; // Execute command for hotkey var command = Commands[hotKey]; if (command.Code is CommandCodes.OpenItem && (source?.FindAscendantOrSelf<Omnibar>() is not null || source?.FindAscendantOrSelf<AppBarButton>() is not null)) break; // Prevent ctrl + c from overriding copy in textblocks if (currentModifiers == KeyModifiers.Ctrl && e.Key is VirtualKey.C && (FrameworkElement)FocusManager.GetFocusedElement(MainWindow.Instance.Content.XamlRoot) is TextBlock) break; if (command.Code is not CommandCodes.None && keyReleased) { keyReleased = false; e.Handled = command.IsExecutable; await command.ExecuteAsync(); } break; } } protected override void OnPreviewKeyUp(KeyRoutedEventArgs e) { base.OnPreviewKeyUp(e); switch (e.Key) { case VirtualKey.Menu: case VirtualKey.Control: case VirtualKey.Shift: case VirtualKey.LeftWindows: case VirtualKey.RightWindows: break; default: keyReleased = true; break; } } // A workaround for issue with OnPreviewKeyUp not being called when the hotkey displays a dialog protected override void OnLostFocus(RoutedEventArgs e) { base.OnLostFocus(e); keyReleased = true; } private void Page_Loaded(object sender, RoutedEventArgs e) { MainWindow.Instance.AppWindow.Changed += (_, _) => MainWindow.Instance.RaiseSetTitleBarDragRegion(SetTitleBarDragRegion); // Defers loading until after the page has loaded to improve startup perf FindName(nameof(InnerNavigationToolbar)); FindName(nameof(TabControl)); FindName(nameof(NavToolbar)); // Notify user that drag and drop is disabled // Prompt is disabled in the dev environment to prevent issues with the automation testing // ToDo put this in a StartupPromptService if ( AppLifecycleHelper.AppEnvironment is not AppEnvironment.Dev && WindowContext.IsRunningAsAdmin && UserSettingsService.ApplicationSettingsService.ShowRunningAsAdminPrompt ) { DispatcherQueue.TryEnqueue(async () => await AppRunningAsAdminPromptAsync()); } } private void PreviewPane_Loaded(object sender, RoutedEventArgs e) { _updateDateDisplayTimer.Start(); } private void PreviewPane_Unloaded(object sender, RoutedEventArgs e) { _updateDateDisplayTimer.Stop(); } private void UpdateDateDisplayTimer_Tick(object sender, object e) { if (!App.AppModel.IsMainWindowClosed) InfoPane?.ViewModel.UpdateDateDisplay(); else App.Logger.LogWarning("UpdateDateDisplayTimer_Tick: Timer firing after window closed!"); } private void Page_SizeChanged(object sender, SizeChangedEventArgs e) { switch (InfoPane?.Position) { case PreviewPanePositions.Right when ContentColumn.ActualWidth == ContentColumn.MinWidth: UserSettingsService.InfoPaneSettingsService.VerticalSizePx += e.NewSize.Width - e.PreviousSize.Width; UpdatePositioning(); break; case PreviewPanePositions.Bottom when ContentRow.ActualHeight == ContentRow.MinHeight: UserSettingsService.InfoPaneSettingsService.HorizontalSizePx += e.NewSize.Height - e.PreviousSize.Height; UpdatePositioning(); break; } } private void SidebarControl_Loaded(object sender, RoutedEventArgs e) { // Set the correct tab margin on startup SidebarAdaptiveViewModel.UpdateTabControlMargin(); SidebarAdaptiveViewModel.EnsureTabExpansionTrackingInitialized(); SidebarControl.HoverToOpenDelay = TimeSpan.FromMilliseconds(Constants.DragAndDrop.HoverToOpenTimespan); SidebarControl.HoverToExpandDelay = TimeSpan.FromMilliseconds(Constants.DragAndDrop.HoverToExpandTimespan); // VM.SidebarDisplayMode rejects Minimal (it only tracks user preference) so the flat tree mirrors SidebarView.DisplayMode separately. SidebarAdaptiveViewModel.ActualDisplayMode = SidebarControl.DisplayMode; SidebarControl.RegisterPropertyChangedCallback(SidebarView.DisplayModeProperty, (_, _) => SidebarAdaptiveViewModel.ActualDisplayMode = SidebarControl.DisplayMode); } private void RootGrid_SizeChanged(object sender, SizeChangedEventArgs e) => LoadPaneChanged(); private void UpdatePositioning() { if (InfoPane is null || !ViewModel.ShouldPreviewPaneBeActive) { VisualStateManager.GoToState(this, "InfoPanePositionNone", true); } else { InfoPane.UpdatePosition(RootGrid.ActualWidth, RootGrid.ActualHeight); switch (InfoPane.Position) { case PreviewPanePositions.None: VisualStateManager.GoToState(this, "InfoPanePositionNone", true); break; case PreviewPanePositions.Right: InfoPaneSizer.ChangeCursor(InputSystemCursor.Create(InputSystemCursorShape.SizeWestEast)); InfoPaneColumnDefinition.Width = new(UserSettingsService.InfoPaneSettingsService.VerticalSizePx); VisualStateManager.GoToState(this, "InfoPanePositionRight", true); break; case PreviewPanePositions.Bottom: InfoPaneSizer.ChangeCursor(InputSystemCursor.Create(InputSystemCursorShape.SizeNorthSouth)); InfoPaneRowDefinition.Height = new(UserSettingsService.InfoPaneSettingsService.HorizontalSizePx); VisualStateManager.GoToState(this, "InfoPanePositionBottom", true); break; } } } private void PaneSplitter_ManipulationCompleted(object sender, ManipulationCompletedRoutedEventArgs e) { switch (InfoPane?.Position) { case PreviewPanePositions.Right: UserSettingsService.InfoPaneSettingsService.VerticalSizePx = InfoPane.ActualWidth; break; case PreviewPanePositions.Bottom: UserSettingsService.InfoPaneSettingsService.HorizontalSizePx = InfoPane.ActualHeight; break; } this.ChangeCursor(InputSystemCursor.Create(InputSystemCursorShape.Arrow)); } private void ApplySidebarWidthState() { if (UserSettingsService.AppearanceSettingsService.SidebarWidth > 360) VisualStateManager.GoToState(this, "LargeSidebarWidthState", true); else if (UserSettingsService.AppearanceSettingsService.SidebarWidth > 280) VisualStateManager.GoToState(this, "MediumSidebarWidthState", true); else VisualStateManager.GoToState(this, "SmallSidebarWidthState", true); } private void LoadPaneChanged() { try { var isHomePage = !(SidebarAdaptiveViewModel.PaneHolder?.ActivePane?.InstanceViewModel?.IsPageTypeNotHome ?? false); var isReleaseNotesPage = SidebarAdaptiveViewModel.PaneHolder?.ActivePane?.InstanceViewModel?.IsPageTypeReleaseNotes ?? false; var isSettingsPage = SidebarAdaptiveViewModel.PaneHolder?.ActivePane?.InstanceViewModel?.IsPageTypeSettings ?? false; var isMultiPane = SidebarAdaptiveViewModel.PaneHolder?.IsMultiPaneActive ?? false; var isBigEnough = !App.AppModel.IsMainWindowClosed && (MainWindow.Instance.Bounds.Width > 450 && MainWindow.Instance.Bounds.Height > 450 || RootGrid.ActualWidth > 700 && MainWindow.Instance.Bounds.Height > 360); ViewModel.ShouldPreviewPaneBeDisplayed = ((!isHomePage && !isReleaseNotesPage && !isSettingsPage) || isMultiPane) && isBigEnough; ViewModel.ShouldPreviewPaneBeActive = UserSettingsService.InfoPaneSettingsService.IsInfoPaneEnabled && ViewModel.ShouldPreviewPaneBeDisplayed; UpdatePositioning(); } catch (Exception ex) { // Handle exception in case WinUI Windows is closed // (see https://github.com/files-community/Files/issues/15599) App.Logger.LogWarning(ex, ex.Message); } } private async void ViewModel_PropertyChanged(object? sender, PropertyChangedEventArgs e) { if (e.PropertyName == nameof(ViewModel.ShouldPreviewPaneBeActive) && ViewModel.ShouldPreviewPaneBeActive) await Ioc.Default.GetRequiredService<InfoPaneViewModel>().UpdateSelectedItemPreviewAsync(); else if (e.PropertyName == nameof(ViewModel.SelectedTabItem)) HandleSidebarTabChange(); } private void HandleSidebarTabChange() { if (_previousSidebarTab is not null) _sidebarScrollByTab[_previousSidebarTab] = SidebarControl.VerticalScrollOffset; var newTab = ViewModel.SelectedTabItem; _previousSidebarTab = newTab; if (newTab is null) return; var savedOffset = _sidebarScrollByTab.GetValueOrDefault(newTab); // Defer to after the flat-tree's tab-state restoration dispatcher work so the content extent has caught up before scrolling. DispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, () => SidebarControl.ScrollToVerticalOffset(savedOffset)); } private void RootGrid_PreviewKeyDown(object sender, KeyRoutedEventArgs e) { switch (e.Key) { case VirtualKey.Menu: case VirtualKey.Control: case VirtualKey.Shift: case VirtualKey.LeftWindows: case VirtualKey.RightWindows: break; default: var currentModifiers = HotKeyHelpers.GetCurrentKeyModifiers(); HotKey hotKey = new((Keys)e.Key, currentModifiers); // Prevents the arrow key events from navigating the list instead of switching compact overlay if (Commands[hotKey].Code is CommandCodes.EnterCompactOverlay or CommandCodes.ExitCompactOverlay) Focus(FocusState.Keyboard); break; } } private void NavToolbar_Loaded(object sender, RoutedEventArgs e) => UpdateNavToolbarProperties(); private void PaneSplitter_ManipulationStarted(object sender, ManipulationStartedRoutedEventArgs e) { this.ChangeCursor(InputSystemCursor.Create(InfoPane.Position == PreviewPanePositions.Right ? InputSystemCursorShape.SizeWestEast : InputSystemCursorShape.SizeNorthSouth)); } private void SettingsButton_AccessKeyInvoked(UIElement sender, AccessKeyInvokedEventArgs args) { // Suppress access key invocation if any dialog is open if (VisualTreeHelper.GetOpenPopupsForXamlRoot(MainWindow.Instance.Content.XamlRoot).Any()) args.Handled = true; } private void Page_PointerReleased(object sender, PointerRoutedEventArgs e) { // Workaround for issue where clicking an empty area in the window (toolbar, title bar etc) prevents keyboard // shortcuts from working properly, see https://github.com/microsoft/microsoft-ui-xaml/issues/6467 DispatcherQueue.TryEnqueue(() => ContentPageContext.ShellPage?.PaneHolder.FocusActivePane()); } private void SidebarControl_ItemContextInvoked(object sender, ItemContextInvokedArgs e) { SidebarAdaptiveViewModel.HandleItemContextInvokedAsync(sender, e); } private async void SidebarControl_ItemDragOver(object sender, ItemDragOverEventArgs e) { // GetDeferral()/Complete() can throw COMException if the underlying drag operation has already been released (e.g. canceled by the system or window closed) var deferral = SafetyExtensions.IgnoreExceptions(() => e.RawEvent.GetDeferral(), App.Logger); await SafetyExtensions.IgnoreExceptions(async () => { await SidebarAdaptiveViewModel.HandleItemDragOverAsync(e); }, App.Logger); if (deferral is not null) SafetyExtensions.IgnoreExceptions(() => deferral.Complete(), App.Logger); } private async void SidebarControl_ItemDropped(object sender, ItemDroppedEventArgs e) { // GetDeferral()/Complete() can throw COMException if the underlying drag operation has already been released (e.g. canceled by the system or window closed) var deferral = SafetyExtensions.IgnoreExceptions(() => e.RawEvent.GetDeferral(), App.Logger); await SafetyExtensions.IgnoreExceptions(async () => { await SidebarAdaptiveViewModel.HandleItemDroppedAsync(e); }, App.Logger); if (deferral is not null) SafetyExtensions.IgnoreExceptions(() => deferral.Complete(), App.Logger); } private void SidebarControl_ItemInvoked(object sender, ItemInvokedEventArgs e) { if (sender is not SidebarItem { Item: ISidebarItemModel item }) return; if (item is INavigationControlItem navItem && string.Equals(navItem.Path, "Settings", StringComparison.OrdinalIgnoreCase)) _ = AnimateSettingsIconAsync(); SidebarAdaptiveViewModel.HandleItemInvokedAsync(item, e.PointerUpdateKind); } private async Task AnimateSettingsIconAsync() { AnimatedIcon.SetState(SettingAnimatedIcon, "Pressed"); await Task.Delay(140); AnimatedIcon.SetState(SettingAnimatedIcon, "Normal"); } } }