From bca780f886797cffccb16f8330e0895aa16b22e6 Mon Sep 17 00:00:00 2001 From: Yawen Hou Date: Wed, 7 Dec 2022 19:56:01 -0500 Subject: [PATCH] [Peek] Add customized title bar (#22600) * Add basic button UI * Add function to get default app name and to open file in default app * Correct error output * Add filename to titlebar * Remove titlebar text from Resw * Add basic button UI * Add function to get default app name and to open file in default app * Add filename to titlebar * Correct error output * Remove titlebar text from Resw * Add SetDragRectangles * Correct logic, update function name * Add localization * Cleanup and adaptive width * Add fileIndex/NumberOfFiles for multiple files activation * Refine titlebar styles * Update error message; Return HResult from native methods; Update variable initialisation and string null testing * Titlebar height and adaptive width refinement * Add fallback to launch app picker if fail to open default app * Temp change to hide AppTitle_FileCount * Update launch button to command; Add keyboard accelerator * Update titlebar inactive background color * Update tooltip to add keyboard accelerator * Add comments to resw file * Fix accidental deletion from previous merge Co-authored-by: Jojo Zhou Co-authored-by: Yawen Hou --- .../peek/Peek.UI/Helpers/DefaultAppHelper.cs | 41 ++++++ src/modules/peek/Peek.UI/MainWindow.xaml | 8 +- src/modules/peek/Peek.UI/MainWindow.xaml.cs | 2 +- .../peek/Peek.UI/Native/NativeMethods.cs | 36 +++++ .../peek/Peek.UI/Strings/en-us/Resources.resw | 22 ++- src/modules/peek/Peek.UI/Views/TitleBar.xaml | 124 ++++++++++++++++- .../peek/Peek.UI/Views/TitleBar.xaml.cs | 128 +++++++++++++++++- 7 files changed, 342 insertions(+), 19 deletions(-) create mode 100644 src/modules/peek/Peek.UI/Helpers/DefaultAppHelper.cs diff --git a/src/modules/peek/Peek.UI/Helpers/DefaultAppHelper.cs b/src/modules/peek/Peek.UI/Helpers/DefaultAppHelper.cs new file mode 100644 index 00000000000..1064a86d275 --- /dev/null +++ b/src/modules/peek/Peek.UI/Helpers/DefaultAppHelper.cs @@ -0,0 +1,41 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Text; +using Peek.Common.Models; +using Peek.UI.Native; + +namespace Peek.UI.Helpers +{ + public static class DefaultAppHelper + { + public static string TryGetDefaultAppName(string extension) + { + string appName = string.Empty; + + // Get the length of the app name + uint length = 0; + HResult ret = NativeMethods.AssocQueryString(NativeMethods.AssocF.Verify, NativeMethods.AssocStr.FriendlyAppName, extension, null, null, ref length); + if (ret != HResult.False) + { + Debug.WriteLine($"Error when getting accessString for {extension} file: {Marshal.GetExceptionForHR((int)ret)!.Message}"); + return appName; + } + + // Get the the app name + StringBuilder sb = new ((int)length); + ret = NativeMethods.AssocQueryString(NativeMethods.AssocF.Verify, NativeMethods.AssocStr.FriendlyAppName, extension, null, sb, ref length); + if (ret != HResult.Ok) + { + Debug.WriteLine($"Error when getting accessString for {extension} file: {Marshal.GetExceptionForHR((int)ret)!.Message}" ); + return appName; + } + + appName = sb.ToString(); + return appName; + } + } +} diff --git a/src/modules/peek/Peek.UI/MainWindow.xaml b/src/modules/peek/Peek.UI/MainWindow.xaml index 437c141fa8e..45ceb78a358 100644 --- a/src/modules/peek/Peek.UI/MainWindow.xaml +++ b/src/modules/peek/Peek.UI/MainWindow.xaml @@ -22,11 +22,15 @@ - + - + Peek + Name of application. - - Peek + + ({0}/{1} files) + Text for the file count in the titlebar. 0: the index of the current file. 1: the total number of files selected. + + + Open with + Text for button to launch the application picker. + + + Open with (Enter) + Tooltip for button to launch the application picker. + + + Open with {0} + Text for button to launch default application. 0: name of the default application. + + + Open with {0} (Enter) + Tooltip for button to launch default application. 0: name of the default application. \ No newline at end of file diff --git a/src/modules/peek/Peek.UI/Views/TitleBar.xaml b/src/modules/peek/Peek.UI/Views/TitleBar.xaml index 51fb817eb5a..4e3239446fe 100644 --- a/src/modules/peek/Peek.UI/Views/TitleBar.xaml +++ b/src/modules/peek/Peek.UI/Views/TitleBar.xaml @@ -10,16 +10,126 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d"> - + + + + + + - + + + + x:Name="AppTitle_FileCount" + x:Uid="AppTitle_FileCount" + Margin="4,0,0,0" + VerticalAlignment="Center" + FontWeight="Bold" + Style="{StaticResource CaptionTextBlockStyle}" + Text="{x:Bind FileCountText, Mode=OneWay}" + Visibility="Collapsed" /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/modules/peek/Peek.UI/Views/TitleBar.xaml.cs b/src/modules/peek/Peek.UI/Views/TitleBar.xaml.cs index 9574efdf76e..04102ae2dec 100644 --- a/src/modules/peek/Peek.UI/Views/TitleBar.xaml.cs +++ b/src/modules/peek/Peek.UI/Views/TitleBar.xaml.cs @@ -5,34 +5,76 @@ namespace Peek.UI.Views { using System; + using CommunityToolkit.Mvvm.ComponentModel; + using CommunityToolkit.Mvvm.Input; using ManagedCommon; using Microsoft.UI; using Microsoft.UI.Windowing; + using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; + using Peek.Common.Models; + using Peek.UI.Helpers; + using Windows.ApplicationModel.Resources; + using Windows.Storage; + using Windows.System; using WinUIEx; - using MUX = Microsoft.UI.Xaml; + [INotifyPropertyChanged] public sealed partial class TitleBar : UserControl { + public static readonly DependencyProperty FileProperty = + DependencyProperty.Register( + nameof(File), + typeof(File), + typeof(TitleBar), + new PropertyMetadata(null, (d, e) => ((TitleBar)d).OnFilePropertyChanged())); + + public static readonly DependencyProperty NumberOfFilesProperty = + DependencyProperty.Register( + nameof(NumberOfFiles), + typeof(int), + typeof(TitleBar), + new PropertyMetadata(null, null)); + + private string? defaultAppName; + + [ObservableProperty] + private string openWithAppText = ResourceLoader.GetForViewIndependentUse().GetString("LaunchAppButton_OpenWith_Text"); + + [ObservableProperty] + private string openWithAppToolTip = ResourceLoader.GetForViewIndependentUse().GetString("LaunchAppButton_OpenWith_ToolTip"); + + [ObservableProperty] + private string? fileCountText; + public TitleBar() { InitializeComponent(); } - public void SetToWindow(MainWindow mainWindow) + public File File + { + get => (File)GetValue(FileProperty); + set => SetValue(FileProperty, value); + } + + public int NumberOfFiles + { + get => (int)GetValue(NumberOfFilesProperty); + set => SetValue(NumberOfFilesProperty, value); + } + + public void SetTitleBarToWindow(MainWindow mainWindow) { if (AppWindowTitleBar.IsCustomizationSupported()) { - AppWindow window = mainWindow.GetAppWindow(); - window.TitleBar.ExtendsContentIntoTitleBar = true; - window.TitleBar.ButtonBackgroundColor = Colors.Transparent; - mainWindow.SetTitleBar(this); + UpdateTitleBarCustomization(mainWindow); } else { var hWnd = WinRT.Interop.WindowNative.GetWindowHandle(this); ThemeHelpers.SetImmersiveDarkMode(hWnd, ThemeHelpers.GetAppTheme() == AppTheme.Dark); - Visibility = MUX.Visibility.Collapsed; + Visibility = Visibility.Collapsed; // Set window icon WindowId windowId = Win32Interop.GetWindowIdFromWindow(hWnd); @@ -40,5 +82,77 @@ public void SetToWindow(MainWindow mainWindow) appWindow.SetIcon("Assets/Icon.ico"); } } + + private void UpdateTitleBarCustomization(MainWindow mainWindow) + { + if (AppWindowTitleBar.IsCustomizationSupported()) + { + AppWindow appWindow = mainWindow.GetAppWindow(); + appWindow.TitleBar.ExtendsContentIntoTitleBar = true; + appWindow.TitleBar.ButtonBackgroundColor = Colors.Transparent; + appWindow.TitleBar.ButtonInactiveBackgroundColor = Colors.Transparent; + appWindow.TitleBar.SetDragRectangles(new Windows.Graphics.RectInt32[] + { + new Windows.Graphics.RectInt32(0, 0, (int)TitleBarRootContainer.ActualWidth, (int)TitleBarRootContainer.ActualHeight), + }); + + mainWindow.SetTitleBar(this); + } + } + + private void OnFilePropertyChanged() + { + UpdateFileCountText(); + UpdateDefaultAppToLaunch(); + } + + private void UpdateFileCountText() + { + // Update file count + if (NumberOfFiles > 1) + { + // TODO: Update the hardcoded fileIndex when the NFQ PR gets merged + int currentFileIndex = 1; + string fileCountTextFormat = ResourceLoader.GetForViewIndependentUse().GetString("AppTitle_FileCounts_Text"); + FileCountText = string.Format(fileCountTextFormat, currentFileIndex, NumberOfFiles); + } + } + + private void UpdateDefaultAppToLaunch() + { + // Update the name of default app to launch + defaultAppName = DefaultAppHelper.TryGetDefaultAppName(File.Extension); + + string openWithAppTextFormat = ResourceLoader.GetForViewIndependentUse().GetString("LaunchAppButton_OpenWithApp_Text"); + OpenWithAppText = string.Format(openWithAppTextFormat, defaultAppName); + + string openWithAppToolTipFormat = ResourceLoader.GetForViewIndependentUse().GetString("LaunchAppButton_OpenWithApp_ToolTip"); + OpenWithAppToolTip = string.Format(openWithAppToolTipFormat, defaultAppName); + } + + [RelayCommand] + private async void LaunchDefaultAppButtonAsync() + { + StorageFile storageFile = await File.GetStorageFileAsync(); + LauncherOptions options = new (); + + if (string.IsNullOrEmpty(defaultAppName)) + { + // If there's no default app found, open the App picker + options.DisplayApplicationPicker = true; + } + else + { + // Try to launch the default app for current file format + bool result = await Launcher.LaunchFileAsync(storageFile, options); + + if (!result) + { + // If we couldn't successfully open the default app, open the App picker as a fallback + options.DisplayApplicationPicker = true; + await Launcher.LaunchFileAsync(storageFile, options); + } + } + } } }