From 3f5a54f9f14e8e7fd2716178c04d4b400c85bfb1 Mon Sep 17 00:00:00 2001 From: Clint Rutkas Date: Wed, 11 Mar 2020 10:43:32 -0700 Subject: [PATCH] Added in generic ViewModel --- .../UnitTest1.cs | 2 +- .../App.xaml | 3 +- .../App.xaml.cs | 2 +- .../MainWindow.xaml | 7 +- .../MainWindow.xaml.cs | 7 +- ...rosoft.PowerToys.Settings.UI.Runner.csproj | 5 +- .../Program.cs | 6 +- .../Activation/ActivationHandler.cs | 40 +++++ .../Activation/DefaultActivationHandler.cs | 39 +++++ .../Microsoft.PowerToys.Settings.UI/App.xaml | 17 ++- .../App.xaml.cs | 4 +- .../Behaviors/NavigationViewHeaderBehavior.cs | 134 ++++++++++++++++ .../Behaviors/NavigationViewHeaderMode.cs | 9 ++ .../Helpers/NavHelper.cs | 31 ++++ .../Helpers/Observable.cs | 24 +++ .../Helpers/RelayCommand.cs | 57 +++++++ .../Helpers/ResourceExtensions.cs | 17 +++ .../Microsoft.PowerToys.Settings.UI.csproj | 77 +++++++++- .../Package.appxmanifest | 2 +- .../Properties/AssemblyInfo.cs | 4 +- .../Services/ActivationService.cs | 103 +++++++++++++ .../Services/NavigationService.cs | 102 +++++++++++++ .../Strings/en-us/Resources.resw | 144 ++++++++++++++++++ .../Styles/Page.xaml | 9 ++ .../Styles/TextBlock.xaml | 21 +++ .../Styles/_Colors.xaml | 5 + .../Styles/_FontSizes.xaml | 8 + .../Styles/_Thickness.xaml | 30 ++++ .../ViewModels/MainViewModel.cs | 13 ++ .../ViewModels/ShellViewModel.cs | 121 +++++++++++++++ .../ViewModels/Test1ViewModel.cs | 13 ++ .../ViewModels/Test2ViewModel.cs | 13 ++ .../ViewModels/Test3ViewModel.cs | 13 ++ .../Views/MainPage.xaml | 18 +++ .../Views/MainPage.xaml.cs | 18 +++ .../Views/ShellPage.xaml | 81 ++++++++++ .../Views/ShellPage.xaml.cs | 21 +++ .../Views/Test1Page.xaml | 18 +++ .../Views/Test1Page.xaml.cs | 18 +++ .../Views/Test2Page.xaml | 18 +++ .../Views/Test2Page.xaml.cs | 18 +++ .../Views/Test3Page.xaml | 18 +++ .../Views/Test3Page.xaml.cs | 18 +++ 43 files changed, 1305 insertions(+), 23 deletions(-) create mode 100644 src/core/Microsoft.PowerToys.Settings.UI/Activation/ActivationHandler.cs create mode 100644 src/core/Microsoft.PowerToys.Settings.UI/Activation/DefaultActivationHandler.cs create mode 100644 src/core/Microsoft.PowerToys.Settings.UI/Behaviors/NavigationViewHeaderBehavior.cs create mode 100644 src/core/Microsoft.PowerToys.Settings.UI/Behaviors/NavigationViewHeaderMode.cs create mode 100644 src/core/Microsoft.PowerToys.Settings.UI/Helpers/NavHelper.cs create mode 100644 src/core/Microsoft.PowerToys.Settings.UI/Helpers/Observable.cs create mode 100644 src/core/Microsoft.PowerToys.Settings.UI/Helpers/RelayCommand.cs create mode 100644 src/core/Microsoft.PowerToys.Settings.UI/Helpers/ResourceExtensions.cs create mode 100644 src/core/Microsoft.PowerToys.Settings.UI/Services/ActivationService.cs create mode 100644 src/core/Microsoft.PowerToys.Settings.UI/Services/NavigationService.cs create mode 100644 src/core/Microsoft.PowerToys.Settings.UI/Strings/en-us/Resources.resw create mode 100644 src/core/Microsoft.PowerToys.Settings.UI/Styles/Page.xaml create mode 100644 src/core/Microsoft.PowerToys.Settings.UI/Styles/TextBlock.xaml create mode 100644 src/core/Microsoft.PowerToys.Settings.UI/Styles/_Colors.xaml create mode 100644 src/core/Microsoft.PowerToys.Settings.UI/Styles/_FontSizes.xaml create mode 100644 src/core/Microsoft.PowerToys.Settings.UI/Styles/_Thickness.xaml create mode 100644 src/core/Microsoft.PowerToys.Settings.UI/ViewModels/MainViewModel.cs create mode 100644 src/core/Microsoft.PowerToys.Settings.UI/ViewModels/ShellViewModel.cs create mode 100644 src/core/Microsoft.PowerToys.Settings.UI/ViewModels/Test1ViewModel.cs create mode 100644 src/core/Microsoft.PowerToys.Settings.UI/ViewModels/Test2ViewModel.cs create mode 100644 src/core/Microsoft.PowerToys.Settings.UI/ViewModels/Test3ViewModel.cs create mode 100644 src/core/Microsoft.PowerToys.Settings.UI/Views/MainPage.xaml create mode 100644 src/core/Microsoft.PowerToys.Settings.UI/Views/MainPage.xaml.cs create mode 100644 src/core/Microsoft.PowerToys.Settings.UI/Views/ShellPage.xaml create mode 100644 src/core/Microsoft.PowerToys.Settings.UI/Views/ShellPage.xaml.cs create mode 100644 src/core/Microsoft.PowerToys.Settings.UI/Views/Test1Page.xaml create mode 100644 src/core/Microsoft.PowerToys.Settings.UI/Views/Test1Page.xaml.cs create mode 100644 src/core/Microsoft.PowerToys.Settings.UI/Views/Test2Page.xaml create mode 100644 src/core/Microsoft.PowerToys.Settings.UI/Views/Test2Page.xaml.cs create mode 100644 src/core/Microsoft.PowerToys.Settings.UI/Views/Test3Page.xaml create mode 100644 src/core/Microsoft.PowerToys.Settings.UI/Views/Test3Page.xaml.cs diff --git a/src/core/Microsoft.PowerToys.Settings.Test/UnitTest1.cs b/src/core/Microsoft.PowerToys.Settings.Test/UnitTest1.cs index efdf0b7925..90cb7f0276 100644 --- a/src/core/Microsoft.PowerToys.Settings.Test/UnitTest1.cs +++ b/src/core/Microsoft.PowerToys.Settings.Test/UnitTest1.cs @@ -1,6 +1,6 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; -namespace settingsUintTests +namespace Microsoft.PowerToys.Settings.Test { [TestClass] public class UnitTest1 diff --git a/src/core/Microsoft.PowerToys.Settings.UI.Island/App.xaml b/src/core/Microsoft.PowerToys.Settings.UI.Island/App.xaml index 55f56e9c44..3ec7ac0ca4 100644 --- a/src/core/Microsoft.PowerToys.Settings.UI.Island/App.xaml +++ b/src/core/Microsoft.PowerToys.Settings.UI.Island/App.xaml @@ -1,7 +1,6 @@ - diff --git a/src/core/Microsoft.PowerToys.Settings.UI.Island/App.xaml.cs b/src/core/Microsoft.PowerToys.Settings.UI.Island/App.xaml.cs index af812c14de..94e236231d 100644 --- a/src/core/Microsoft.PowerToys.Settings.UI.Island/App.xaml.cs +++ b/src/core/Microsoft.PowerToys.Settings.UI.Island/App.xaml.cs @@ -6,7 +6,7 @@ using System.Linq; using System.Threading.Tasks; using System.Windows; -namespace SettingsRunner +namespace Microsoft.PowerToys.Settings.UI.Runner { /// /// Interaction logic for App.xaml diff --git a/src/core/Microsoft.PowerToys.Settings.UI.Island/MainWindow.xaml b/src/core/Microsoft.PowerToys.Settings.UI.Island/MainWindow.xaml index 4c886ea863..a6a15fd743 100644 --- a/src/core/Microsoft.PowerToys.Settings.UI.Island/MainWindow.xaml +++ b/src/core/Microsoft.PowerToys.Settings.UI.Island/MainWindow.xaml @@ -1,16 +1,17 @@ - - + + diff --git a/src/core/Microsoft.PowerToys.Settings.UI.Island/MainWindow.xaml.cs b/src/core/Microsoft.PowerToys.Settings.UI.Island/MainWindow.xaml.cs index 9aebb03f60..ea022381ab 100644 --- a/src/core/Microsoft.PowerToys.Settings.UI.Island/MainWindow.xaml.cs +++ b/src/core/Microsoft.PowerToys.Settings.UI.Island/MainWindow.xaml.cs @@ -2,8 +2,9 @@ using System.Windows; using Microsoft.Toolkit.Wpf.UI.XamlHost; using Microsoft.PowerToys.Settings.UI.Controls; +using Microsoft.PowerToys.Settings.UI.Views; -namespace SettingsRunner +namespace Microsoft.PowerToys.Settings.UI.Runner { /// /// Interaction logic for MainWindow.xaml @@ -19,11 +20,11 @@ namespace SettingsRunner { // Hook up x:Bind source. WindowsXamlHost windowsXamlHost = sender as WindowsXamlHost; - DummyUserControl userControl = windowsXamlHost.GetUwpInternalObject() as DummyUserControl; + ShellPage userControl = windowsXamlHost.GetUwpInternalObject() as ShellPage; if (userControl != null) { - userControl.XamlIslandMessage = this.WPFMessage; + //userControl.XamlIslandMessage = this.WPFMessage; } } diff --git a/src/core/Microsoft.PowerToys.Settings.UI.Island/Microsoft.PowerToys.Settings.UI.Runner.csproj b/src/core/Microsoft.PowerToys.Settings.UI.Island/Microsoft.PowerToys.Settings.UI.Runner.csproj index 3a0649b42d..bb2b2f741f 100644 --- a/src/core/Microsoft.PowerToys.Settings.UI.Island/Microsoft.PowerToys.Settings.UI.Runner.csproj +++ b/src/core/Microsoft.PowerToys.Settings.UI.Island/Microsoft.PowerToys.Settings.UI.Runner.csproj @@ -4,7 +4,7 @@ WinExe netcoreapp3.1 true - SettingsRunner.Program + Microsoft.PowerToys.Settings.UI.Runner.Program Microsoft Corporation PowerToys Windows system utilities to maximize productivity @@ -18,6 +18,9 @@ x64 icon.ico + + + uap10.0.18362 diff --git a/src/core/Microsoft.PowerToys.Settings.UI.Island/Program.cs b/src/core/Microsoft.PowerToys.Settings.UI.Island/Program.cs index 566fe1508a..f98c8b5b02 100644 --- a/src/core/Microsoft.PowerToys.Settings.UI.Island/Program.cs +++ b/src/core/Microsoft.PowerToys.Settings.UI.Island/Program.cs @@ -2,16 +2,16 @@ using System.Collections.Generic; using System.Text; -namespace SettingsRunner +namespace Microsoft.PowerToys.Settings.UI.Runner { public class Program { [System.STAThreadAttribute()] public static void Main() { - using (new SettingsUI.App()) + using (new UI.App()) { - SettingsRunner.App app = new SettingsRunner.App(); + App app = new App(); app.InitializeComponent(); app.Run(); } diff --git a/src/core/Microsoft.PowerToys.Settings.UI/Activation/ActivationHandler.cs b/src/core/Microsoft.PowerToys.Settings.UI/Activation/ActivationHandler.cs new file mode 100644 index 0000000000..47e68c9e65 --- /dev/null +++ b/src/core/Microsoft.PowerToys.Settings.UI/Activation/ActivationHandler.cs @@ -0,0 +1,40 @@ +using System; +using System.Threading.Tasks; + +namespace Microsoft.PowerToys.Settings.UI.Activation +{ + // For more information on understanding and extending activation flow see + // https://github.com/Microsoft/WindowsTemplateStudio/blob/master/docs/activation.md + internal abstract class ActivationHandler + { + public abstract bool CanHandle(object args); + + public abstract Task HandleAsync(object args); + } + + // Extend this class to implement new ActivationHandlers + internal abstract class ActivationHandler : ActivationHandler + where T : class + { + // Override this method to add the activation logic in your activation handler + protected abstract Task HandleInternalAsync(T args); + + public override async Task HandleAsync(object args) + { + await HandleInternalAsync(args as T); + } + + public override bool CanHandle(object args) + { + // CanHandle checks the args is of type you have configured + return args is T && CanHandleInternal(args as T); + } + + // You can override this method to add extra validation on activation args + // to determine if your ActivationHandler should handle this activation args + protected virtual bool CanHandleInternal(T args) + { + return true; + } + } +} diff --git a/src/core/Microsoft.PowerToys.Settings.UI/Activation/DefaultActivationHandler.cs b/src/core/Microsoft.PowerToys.Settings.UI/Activation/DefaultActivationHandler.cs new file mode 100644 index 0000000000..55bfcdb510 --- /dev/null +++ b/src/core/Microsoft.PowerToys.Settings.UI/Activation/DefaultActivationHandler.cs @@ -0,0 +1,39 @@ +using System; +using System.Threading.Tasks; + +using Microsoft.PowerToys.Settings.UI.Services; + +using Windows.ApplicationModel.Activation; + +namespace Microsoft.PowerToys.Settings.UI.Activation +{ + internal class DefaultActivationHandler : ActivationHandler + { + private readonly Type _navElement; + + public DefaultActivationHandler(Type navElement) + { + _navElement = navElement; + } + + protected override async Task HandleInternalAsync(IActivatedEventArgs args) + { + // When the navigation stack isn't restored, navigate to the first page and configure + // the new page by passing required information in the navigation parameter + object arguments = null; + if (args is LaunchActivatedEventArgs launchArgs) + { + arguments = launchArgs.Arguments; + } + + NavigationService.Navigate(_navElement, arguments); + await Task.CompletedTask; + } + + protected override bool CanHandleInternal(IActivatedEventArgs args) + { + // None of the ActivationHandlers has handled the app activation + return NavigationService.Frame.Content == null && _navElement != null; + } + } +} diff --git a/src/core/Microsoft.PowerToys.Settings.UI/App.xaml b/src/core/Microsoft.PowerToys.Settings.UI/App.xaml index a11716c849..e5a4b1b823 100644 --- a/src/core/Microsoft.PowerToys.Settings.UI/App.xaml +++ b/src/core/Microsoft.PowerToys.Settings.UI/App.xaml @@ -1,10 +1,19 @@  + xmlns:xaml="using:Microsoft.Toolkit.Win32.UI.XamlHost"> - + + + + + + + + + + + \ No newline at end of file diff --git a/src/core/Microsoft.PowerToys.Settings.UI/App.xaml.cs b/src/core/Microsoft.PowerToys.Settings.UI/App.xaml.cs index c7c86d6c4b..42b3dc3a39 100644 --- a/src/core/Microsoft.PowerToys.Settings.UI/App.xaml.cs +++ b/src/core/Microsoft.PowerToys.Settings.UI/App.xaml.cs @@ -1,6 +1,6 @@ using Microsoft.Toolkit.Win32.UI.XamlHost; -namespace SettingsUI +namespace Microsoft.PowerToys.Settings.UI { public sealed partial class App : XamlApplication { @@ -8,5 +8,7 @@ namespace SettingsUI { this.Initialize(); } + + } } diff --git a/src/core/Microsoft.PowerToys.Settings.UI/Behaviors/NavigationViewHeaderBehavior.cs b/src/core/Microsoft.PowerToys.Settings.UI/Behaviors/NavigationViewHeaderBehavior.cs new file mode 100644 index 0000000000..13ecf294e3 --- /dev/null +++ b/src/core/Microsoft.PowerToys.Settings.UI/Behaviors/NavigationViewHeaderBehavior.cs @@ -0,0 +1,134 @@ +using Microsoft.PowerToys.Settings.UI.Services; +using Microsoft.Xaml.Interactivity; + +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; +using Windows.UI.Xaml.Navigation; + +using WinUI = Microsoft.UI.Xaml.Controls; + +namespace Microsoft.PowerToys.Settings.UI.Behaviors +{ + public class NavigationViewHeaderBehavior : Behavior + { + private static NavigationViewHeaderBehavior _current; + private Page _currentPage; + + public DataTemplate DefaultHeaderTemplate { get; set; } + + public object DefaultHeader + { + get { return GetValue(DefaultHeaderProperty); } + set { SetValue(DefaultHeaderProperty, value); } + } + + public static readonly DependencyProperty DefaultHeaderProperty = DependencyProperty.Register("DefaultHeader", typeof(object), typeof(NavigationViewHeaderBehavior), new PropertyMetadata(null, (d, e) => _current.UpdateHeader())); + + public static NavigationViewHeaderMode GetHeaderMode(Page item) + { + return (NavigationViewHeaderMode)item.GetValue(HeaderModeProperty); + } + + public static void SetHeaderMode(Page item, NavigationViewHeaderMode value) + { + item.SetValue(HeaderModeProperty, value); + } + + public static readonly DependencyProperty HeaderModeProperty = + DependencyProperty.RegisterAttached("HeaderMode", typeof(bool), typeof(NavigationViewHeaderBehavior), new PropertyMetadata(NavigationViewHeaderMode.Always, (d, e) => _current.UpdateHeader())); + + public static object GetHeaderContext(Page item) + { + return item.GetValue(HeaderContextProperty); + } + + public static void SetHeaderContext(Page item, object value) + { + item.SetValue(HeaderContextProperty, value); + } + + public static readonly DependencyProperty HeaderContextProperty = + DependencyProperty.RegisterAttached("HeaderContext", typeof(object), typeof(NavigationViewHeaderBehavior), new PropertyMetadata(null, (d, e) => _current.UpdateHeader())); + + public static DataTemplate GetHeaderTemplate(Page item) + { + return (DataTemplate)item.GetValue(HeaderTemplateProperty); + } + + public static void SetHeaderTemplate(Page item, DataTemplate value) + { + item.SetValue(HeaderTemplateProperty, value); + } + + public static readonly DependencyProperty HeaderTemplateProperty = + DependencyProperty.RegisterAttached("HeaderTemplate", typeof(DataTemplate), typeof(NavigationViewHeaderBehavior), new PropertyMetadata(null, (d, e) => _current.UpdateHeaderTemplate())); + + protected override void OnAttached() + { + base.OnAttached(); + _current = this; + NavigationService.Navigated += OnNavigated; + } + + protected override void OnDetaching() + { + base.OnDetaching(); + NavigationService.Navigated -= OnNavigated; + } + + private void OnNavigated(object sender, NavigationEventArgs e) + { + var frame = sender as Frame; + if (frame.Content is Page page) + { + _currentPage = page; + + UpdateHeader(); + UpdateHeaderTemplate(); + } + } + + private void UpdateHeader() + { + if (_currentPage != null) + { + var headerMode = GetHeaderMode(_currentPage); + if (headerMode == NavigationViewHeaderMode.Never) + { + AssociatedObject.Header = null; + AssociatedObject.AlwaysShowHeader = false; + } + else + { + var headerFromPage = GetHeaderContext(_currentPage); + if (headerFromPage != null) + { + AssociatedObject.Header = headerFromPage; + } + else + { + AssociatedObject.Header = DefaultHeader; + } + + if (headerMode == NavigationViewHeaderMode.Always) + { + AssociatedObject.AlwaysShowHeader = true; + } + else + { + AssociatedObject.AlwaysShowHeader = false; + } + } + } + } + + private void UpdateHeaderTemplate() + { + if (_currentPage != null) + { + var headerTemplate = GetHeaderTemplate(_currentPage); + AssociatedObject.HeaderTemplate = headerTemplate ?? DefaultHeaderTemplate; + } + } + } +} diff --git a/src/core/Microsoft.PowerToys.Settings.UI/Behaviors/NavigationViewHeaderMode.cs b/src/core/Microsoft.PowerToys.Settings.UI/Behaviors/NavigationViewHeaderMode.cs new file mode 100644 index 0000000000..7906ae9f69 --- /dev/null +++ b/src/core/Microsoft.PowerToys.Settings.UI/Behaviors/NavigationViewHeaderMode.cs @@ -0,0 +1,9 @@ +namespace Microsoft.PowerToys.Settings.UI.Behaviors +{ + public enum NavigationViewHeaderMode + { + Always, + Never, + Minimal + } +} diff --git a/src/core/Microsoft.PowerToys.Settings.UI/Helpers/NavHelper.cs b/src/core/Microsoft.PowerToys.Settings.UI/Helpers/NavHelper.cs new file mode 100644 index 0000000000..d435eae61a --- /dev/null +++ b/src/core/Microsoft.PowerToys.Settings.UI/Helpers/NavHelper.cs @@ -0,0 +1,31 @@ +using System; + +using Microsoft.UI.Xaml.Controls; + +using Windows.UI.Xaml; + +namespace Microsoft.PowerToys.Settings.UI.Helpers +{ + public class NavHelper + { + // This helper class allows to specify the page that will be shown when you click on a NavigationViewItem + // + // Usage in xaml: + // + // + // Usage in code: + // NavHelper.SetNavigateTo(navigationViewItem, typeof(MainPage)); + public static Type GetNavigateTo(NavigationViewItem item) + { + return (Type)item.GetValue(NavigateToProperty); + } + + public static void SetNavigateTo(NavigationViewItem item, Type value) + { + item.SetValue(NavigateToProperty, value); + } + + public static readonly DependencyProperty NavigateToProperty = + DependencyProperty.RegisterAttached("NavigateTo", typeof(Type), typeof(NavHelper), new PropertyMetadata(null)); + } +} diff --git a/src/core/Microsoft.PowerToys.Settings.UI/Helpers/Observable.cs b/src/core/Microsoft.PowerToys.Settings.UI/Helpers/Observable.cs new file mode 100644 index 0000000000..0b3bb6fa59 --- /dev/null +++ b/src/core/Microsoft.PowerToys.Settings.UI/Helpers/Observable.cs @@ -0,0 +1,24 @@ +using System; +using System.ComponentModel; +using System.Runtime.CompilerServices; + +namespace Microsoft.PowerToys.Settings.UI.Helpers +{ + public class Observable : INotifyPropertyChanged + { + public event PropertyChangedEventHandler PropertyChanged; + + protected void Set(ref T storage, T value, [CallerMemberName]string propertyName = null) + { + if (Equals(storage, value)) + { + return; + } + + storage = value; + OnPropertyChanged(propertyName); + } + + protected void OnPropertyChanged(string propertyName) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } +} diff --git a/src/core/Microsoft.PowerToys.Settings.UI/Helpers/RelayCommand.cs b/src/core/Microsoft.PowerToys.Settings.UI/Helpers/RelayCommand.cs new file mode 100644 index 0000000000..bae0f842a0 --- /dev/null +++ b/src/core/Microsoft.PowerToys.Settings.UI/Helpers/RelayCommand.cs @@ -0,0 +1,57 @@ +using System; +using System.Windows.Input; + +namespace Microsoft.PowerToys.Settings.UI.Helpers +{ + public class RelayCommand : ICommand + { + private readonly Action _execute; + + private readonly Func _canExecute; + + public event EventHandler CanExecuteChanged; + + public RelayCommand(Action execute) + : this(execute, null) + { + } + + public RelayCommand(Action execute, Func canExecute) + { + _execute = execute ?? throw new ArgumentNullException(nameof(execute)); + _canExecute = canExecute; + } + + public bool CanExecute(object parameter) => _canExecute == null || _canExecute(); + + public void Execute(object parameter) => _execute(); + + public void OnCanExecuteChanged() => CanExecuteChanged?.Invoke(this, EventArgs.Empty); + } + + public class RelayCommand : ICommand + { + private readonly Action _execute; + + private readonly Func _canExecute; + + public event EventHandler CanExecuteChanged; + + public RelayCommand(Action execute) + : this(execute, null) + { + } + + public RelayCommand(Action execute, Func canExecute) + { + _execute = execute ?? throw new ArgumentNullException(nameof(execute)); + _canExecute = canExecute; + } + + public bool CanExecute(object parameter) => _canExecute == null || _canExecute((T)parameter); + + public void Execute(object parameter) => _execute((T)parameter); + + public void OnCanExecuteChanged() => CanExecuteChanged?.Invoke(this, EventArgs.Empty); + } +} diff --git a/src/core/Microsoft.PowerToys.Settings.UI/Helpers/ResourceExtensions.cs b/src/core/Microsoft.PowerToys.Settings.UI/Helpers/ResourceExtensions.cs new file mode 100644 index 0000000000..d9c4ae6394 --- /dev/null +++ b/src/core/Microsoft.PowerToys.Settings.UI/Helpers/ResourceExtensions.cs @@ -0,0 +1,17 @@ +using System; +using System.Runtime.InteropServices; + +using Windows.ApplicationModel.Resources; + +namespace Microsoft.PowerToys.Settings.UI.Helpers +{ + internal static class ResourceExtensions + { + private static ResourceLoader _resLoader = new ResourceLoader(); + + public static string GetLocalized(this string resourceKey) + { + return _resLoader.GetString(resourceKey); + } + } +} diff --git a/src/core/Microsoft.PowerToys.Settings.UI/Microsoft.PowerToys.Settings.UI.csproj b/src/core/Microsoft.PowerToys.Settings.UI/Microsoft.PowerToys.Settings.UI.csproj index 077439d4cf..e32c2aa797 100644 --- a/src/core/Microsoft.PowerToys.Settings.UI/Microsoft.PowerToys.Settings.UI.csproj +++ b/src/core/Microsoft.PowerToys.Settings.UI/Microsoft.PowerToys.Settings.UI.csproj @@ -116,13 +116,43 @@ PackageReference + + App.xaml + + DummyUserControl.xaml + + + + + + + + + + + + + MainPage.xaml + + + ShellPage.xaml + + + Test1Page.xaml + + + Test2Page.xaml + + + Test3Page.xaml + @@ -150,13 +180,58 @@ 2.4.0-prerelease.191217001 + + 2.0.1 + + + + - Designer MSBuild:Compile + + MSBuild:Compile + Designer + + + MSBuild:Compile + Designer + + + MSBuild:Compile + Designer + + + MSBuild:Compile + Designer + + + MSBuild:Compile + Designer + + + MSBuild:Compile + Designer + + + MSBuild:Compile + Designer + + + MSBuild:Compile + Designer + + + MSBuild:Compile + Designer + + + MSBuild:Compile + Designer + 14.0 diff --git a/src/core/Microsoft.PowerToys.Settings.UI/Package.appxmanifest b/src/core/Microsoft.PowerToys.Settings.UI/Package.appxmanifest index db901142ab..1debcb290e 100644 --- a/src/core/Microsoft.PowerToys.Settings.UI/Package.appxmanifest +++ b/src/core/Microsoft.PowerToys.Settings.UI/Package.appxmanifest @@ -30,7 +30,7 @@ + EntryPoint="Microsoft.PowerToys.Settings.UI.App"> _shell; + + private object _lastActivationArgs; + + public ActivationService(App app, Type defaultNavItem, Lazy shell = null) + { + _app = app; + _shell = shell; + _defaultNavItem = defaultNavItem; + } + + public async Task ActivateAsync(object activationArgs) + { + if (IsInteractive(activationArgs)) + { + // Initialize services that you need before app activation + // take into account that the splash screen is shown while this code runs. + await InitializeAsync(); + + // Do not repeat app initialization when the Window already has content, + // just ensure that the window is active + if (Window.Current.Content == null) + { + // Create a Shell or Frame to act as the navigation context + Window.Current.Content = _shell?.Value ?? new Frame(); + } + } + + // Depending on activationArgs one of ActivationHandlers or DefaultActivationHandler + // will navigate to the first page + await HandleActivationAsync(activationArgs); + _lastActivationArgs = activationArgs; + + if (IsInteractive(activationArgs)) + { + // Ensure the current window is active + Window.Current.Activate(); + + // Tasks after activation + await StartupAsync(); + } + } + + private async Task InitializeAsync() + { + await Task.CompletedTask; + } + + private async Task HandleActivationAsync(object activationArgs) + { + var activationHandler = GetActivationHandlers() + .FirstOrDefault(h => h.CanHandle(activationArgs)); + + if (activationHandler != null) + { + await activationHandler.HandleAsync(activationArgs); + } + + if (IsInteractive(activationArgs)) + { + var defaultHandler = new DefaultActivationHandler(_defaultNavItem); + if (defaultHandler.CanHandle(activationArgs)) + { + await defaultHandler.HandleAsync(activationArgs); + } + } + } + + private async Task StartupAsync() + { + await Task.CompletedTask; + } + + private IEnumerable GetActivationHandlers() + { + yield break; + } + + private bool IsInteractive(object args) + { + return args is IActivatedEventArgs; + } + } +} diff --git a/src/core/Microsoft.PowerToys.Settings.UI/Services/NavigationService.cs b/src/core/Microsoft.PowerToys.Settings.UI/Services/NavigationService.cs new file mode 100644 index 0000000000..d82692a05d --- /dev/null +++ b/src/core/Microsoft.PowerToys.Settings.UI/Services/NavigationService.cs @@ -0,0 +1,102 @@ +using System; + +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; +using Windows.UI.Xaml.Media.Animation; +using Windows.UI.Xaml.Navigation; + +namespace Microsoft.PowerToys.Settings.UI.Services +{ + public static class NavigationService + { + public static event NavigatedEventHandler Navigated; + + public static event NavigationFailedEventHandler NavigationFailed; + + private static Frame _frame; + private static object _lastParamUsed; + + public static Frame Frame + { + get + { + if (_frame == null) + { + _frame = Window.Current.Content as Frame; + RegisterFrameEvents(); + } + + return _frame; + } + + set + { + UnregisterFrameEvents(); + _frame = value; + RegisterFrameEvents(); + } + } + + public static bool CanGoBack => Frame.CanGoBack; + + public static bool CanGoForward => Frame.CanGoForward; + + public static bool GoBack() + { + if (CanGoBack) + { + Frame.GoBack(); + return true; + } + + return false; + } + + public static void GoForward() => Frame.GoForward(); + + public static bool Navigate(Type pageType, object parameter = null, NavigationTransitionInfo infoOverride = null) + { + // Don't open the same page multiple times + if (Frame.Content?.GetType() != pageType || (parameter != null && !parameter.Equals(_lastParamUsed))) + { + var navigationResult = Frame.Navigate(pageType, parameter, infoOverride); + if (navigationResult) + { + _lastParamUsed = parameter; + } + + return navigationResult; + } + else + { + return false; + } + } + + public static bool Navigate(object parameter = null, NavigationTransitionInfo infoOverride = null) + where T : Page + => Navigate(typeof(T), parameter, infoOverride); + + private static void RegisterFrameEvents() + { + if (_frame != null) + { + _frame.Navigated += Frame_Navigated; + _frame.NavigationFailed += Frame_NavigationFailed; + } + } + + private static void UnregisterFrameEvents() + { + if (_frame != null) + { + _frame.Navigated -= Frame_Navigated; + _frame.NavigationFailed -= Frame_NavigationFailed; + } + } + + private static void Frame_NavigationFailed(object sender, NavigationFailedEventArgs e) => NavigationFailed?.Invoke(sender, e); + + private static void Frame_Navigated(object sender, NavigationEventArgs e) => Navigated?.Invoke(sender, e); + } +} diff --git a/src/core/Microsoft.PowerToys.Settings.UI/Strings/en-us/Resources.resw b/src/core/Microsoft.PowerToys.Settings.UI/Strings/en-us/Resources.resw new file mode 100644 index 0000000000..6e48d8371b --- /dev/null +++ b/src/core/Microsoft.PowerToys.Settings.UI/Strings/en-us/Resources.resw @@ -0,0 +1,144 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Microsoft.PowerToys.Settings.UI + Application display name + + + Microsoft.PowerToys.Settings.UI + Application description + + + Main + Navigation view item name for Main + + + Test1 + Navigation view item name for Test1 + + + Test2 + Navigation view item name for Test2 + + + Test3 + Navigation view item name for Test3 + + diff --git a/src/core/Microsoft.PowerToys.Settings.UI/Styles/Page.xaml b/src/core/Microsoft.PowerToys.Settings.UI/Styles/Page.xaml new file mode 100644 index 0000000000..24de3e2e70 --- /dev/null +++ b/src/core/Microsoft.PowerToys.Settings.UI/Styles/Page.xaml @@ -0,0 +1,9 @@ + + + + + diff --git a/src/core/Microsoft.PowerToys.Settings.UI/Styles/TextBlock.xaml b/src/core/Microsoft.PowerToys.Settings.UI/Styles/TextBlock.xaml new file mode 100644 index 0000000000..60e02e0f58 --- /dev/null +++ b/src/core/Microsoft.PowerToys.Settings.UI/Styles/TextBlock.xaml @@ -0,0 +1,21 @@ + + + + + + + + diff --git a/src/core/Microsoft.PowerToys.Settings.UI/Styles/_Colors.xaml b/src/core/Microsoft.PowerToys.Settings.UI/Styles/_Colors.xaml new file mode 100644 index 0000000000..a9e1d9d554 --- /dev/null +++ b/src/core/Microsoft.PowerToys.Settings.UI/Styles/_Colors.xaml @@ -0,0 +1,5 @@ + + + diff --git a/src/core/Microsoft.PowerToys.Settings.UI/Styles/_FontSizes.xaml b/src/core/Microsoft.PowerToys.Settings.UI/Styles/_FontSizes.xaml new file mode 100644 index 0000000000..7160b32323 --- /dev/null +++ b/src/core/Microsoft.PowerToys.Settings.UI/Styles/_FontSizes.xaml @@ -0,0 +1,8 @@ + + + 24 + 16 + + diff --git a/src/core/Microsoft.PowerToys.Settings.UI/Styles/_Thickness.xaml b/src/core/Microsoft.PowerToys.Settings.UI/Styles/_Thickness.xaml new file mode 100644 index 0000000000..2683ce8952 --- /dev/null +++ b/src/core/Microsoft.PowerToys.Settings.UI/Styles/_Thickness.xaml @@ -0,0 +1,30 @@ + + + + 24,0,0,0 + 0,24,0,0 + 24,0,24,0 + 0,0,24,0 + 0,24,0,24 + 24,24,24,24 + 0,0,0,24 + + + 12, 0, 0, 0 + 0, 12, 0, 0 + 0, 12, 0, 12 + 12, 0, 12, 0 + 0, 0, 12, 0 + 12, 12, 12, 12 + + + 8, 0, 0, 0 + 0, 8, 0, 0 + 8, 8, 8, 8 + + + 0, 4, 0, 0 + 0, 4, 4, 4 + diff --git a/src/core/Microsoft.PowerToys.Settings.UI/ViewModels/MainViewModel.cs b/src/core/Microsoft.PowerToys.Settings.UI/ViewModels/MainViewModel.cs new file mode 100644 index 0000000000..393b890315 --- /dev/null +++ b/src/core/Microsoft.PowerToys.Settings.UI/ViewModels/MainViewModel.cs @@ -0,0 +1,13 @@ +using System; + +using Microsoft.PowerToys.Settings.UI.Helpers; + +namespace Microsoft.PowerToys.Settings.UI.ViewModels +{ + public class MainViewModel : Observable + { + public MainViewModel() + { + } + } +} diff --git a/src/core/Microsoft.PowerToys.Settings.UI/ViewModels/ShellViewModel.cs b/src/core/Microsoft.PowerToys.Settings.UI/ViewModels/ShellViewModel.cs new file mode 100644 index 0000000000..6feb2e643f --- /dev/null +++ b/src/core/Microsoft.PowerToys.Settings.UI/ViewModels/ShellViewModel.cs @@ -0,0 +1,121 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using System.Windows.Input; + +using Microsoft.PowerToys.Settings.UI.Helpers; +using Microsoft.PowerToys.Settings.UI.Services; + +using Windows.System; +using Windows.UI.Xaml.Controls; +using Windows.UI.Xaml.Input; +using Windows.UI.Xaml.Navigation; + +using WinUI = Microsoft.UI.Xaml.Controls; + +namespace Microsoft.PowerToys.Settings.UI.ViewModels +{ + public class ShellViewModel : Observable + { + private readonly KeyboardAccelerator _altLeftKeyboardAccelerator = BuildKeyboardAccelerator(VirtualKey.Left, VirtualKeyModifiers.Menu); + private readonly KeyboardAccelerator _backKeyboardAccelerator = BuildKeyboardAccelerator(VirtualKey.GoBack); + + private bool _isBackEnabled; + private IList _keyboardAccelerators; + private WinUI.NavigationView _navigationView; + private WinUI.NavigationViewItem _selected; + private ICommand _loadedCommand; + private ICommand _itemInvokedCommand; + + public bool IsBackEnabled + { + get { return _isBackEnabled; } + set { Set(ref _isBackEnabled, value); } + } + + public WinUI.NavigationViewItem Selected + { + get { return _selected; } + set { Set(ref _selected, value); } + } + + public ICommand LoadedCommand => _loadedCommand ?? (_loadedCommand = new RelayCommand(OnLoaded)); + + public ICommand ItemInvokedCommand => _itemInvokedCommand ?? (_itemInvokedCommand = new RelayCommand(OnItemInvoked)); + + public ShellViewModel() + { + } + + public void Initialize(Frame frame, WinUI.NavigationView navigationView, IList keyboardAccelerators) + { + _navigationView = navigationView; + _keyboardAccelerators = keyboardAccelerators; + NavigationService.Frame = frame; + NavigationService.NavigationFailed += Frame_NavigationFailed; + NavigationService.Navigated += Frame_Navigated; + _navigationView.BackRequested += OnBackRequested; + } + + private async void OnLoaded() + { + // Keyboard accelerators are added here to avoid showing 'Alt + left' tooltip on the page. + // More info on tracking issue https://github.com/Microsoft/microsoft-ui-xaml/issues/8 + _keyboardAccelerators.Add(_altLeftKeyboardAccelerator); + _keyboardAccelerators.Add(_backKeyboardAccelerator); + await Task.CompletedTask; + } + + private void OnItemInvoked(WinUI.NavigationViewItemInvokedEventArgs args) + { + var item = _navigationView.MenuItems + .OfType() + .First(menuItem => (string)menuItem.Content == (string)args.InvokedItem); + var pageType = item.GetValue(NavHelper.NavigateToProperty) as Type; + NavigationService.Navigate(pageType); + } + + private void OnBackRequested(WinUI.NavigationView sender, WinUI.NavigationViewBackRequestedEventArgs args) + { + NavigationService.GoBack(); + } + + private void Frame_NavigationFailed(object sender, NavigationFailedEventArgs e) + { + throw e.Exception; + } + + private void Frame_Navigated(object sender, NavigationEventArgs e) + { + IsBackEnabled = NavigationService.CanGoBack; + Selected = _navigationView.MenuItems + .OfType() + .FirstOrDefault(menuItem => IsMenuItemForPageType(menuItem, e.SourcePageType)); + } + + private bool IsMenuItemForPageType(WinUI.NavigationViewItem menuItem, Type sourcePageType) + { + var pageType = menuItem.GetValue(NavHelper.NavigateToProperty) as Type; + return pageType == sourcePageType; + } + + private static KeyboardAccelerator BuildKeyboardAccelerator(VirtualKey key, VirtualKeyModifiers? modifiers = null) + { + var keyboardAccelerator = new KeyboardAccelerator() { Key = key }; + if (modifiers.HasValue) + { + keyboardAccelerator.Modifiers = modifiers.Value; + } + + keyboardAccelerator.Invoked += OnKeyboardAcceleratorInvoked; + return keyboardAccelerator; + } + + private static void OnKeyboardAcceleratorInvoked(KeyboardAccelerator sender, KeyboardAcceleratorInvokedEventArgs args) + { + var result = NavigationService.GoBack(); + args.Handled = result; + } + } +} diff --git a/src/core/Microsoft.PowerToys.Settings.UI/ViewModels/Test1ViewModel.cs b/src/core/Microsoft.PowerToys.Settings.UI/ViewModels/Test1ViewModel.cs new file mode 100644 index 0000000000..e768125b4b --- /dev/null +++ b/src/core/Microsoft.PowerToys.Settings.UI/ViewModels/Test1ViewModel.cs @@ -0,0 +1,13 @@ +using System; + +using Microsoft.PowerToys.Settings.UI.Helpers; + +namespace Microsoft.PowerToys.Settings.UI.ViewModels +{ + public class Test1ViewModel : Observable + { + public Test1ViewModel() + { + } + } +} diff --git a/src/core/Microsoft.PowerToys.Settings.UI/ViewModels/Test2ViewModel.cs b/src/core/Microsoft.PowerToys.Settings.UI/ViewModels/Test2ViewModel.cs new file mode 100644 index 0000000000..9c3e777d58 --- /dev/null +++ b/src/core/Microsoft.PowerToys.Settings.UI/ViewModels/Test2ViewModel.cs @@ -0,0 +1,13 @@ +using System; + +using Microsoft.PowerToys.Settings.UI.Helpers; + +namespace Microsoft.PowerToys.Settings.UI.ViewModels +{ + public class Test2ViewModel : Observable + { + public Test2ViewModel() + { + } + } +} diff --git a/src/core/Microsoft.PowerToys.Settings.UI/ViewModels/Test3ViewModel.cs b/src/core/Microsoft.PowerToys.Settings.UI/ViewModels/Test3ViewModel.cs new file mode 100644 index 0000000000..f9904f015e --- /dev/null +++ b/src/core/Microsoft.PowerToys.Settings.UI/ViewModels/Test3ViewModel.cs @@ -0,0 +1,13 @@ +using System; + +using Microsoft.PowerToys.Settings.UI.Helpers; + +namespace Microsoft.PowerToys.Settings.UI.ViewModels +{ + public class Test3ViewModel : Observable + { + public Test3ViewModel() + { + } + } +} diff --git a/src/core/Microsoft.PowerToys.Settings.UI/Views/MainPage.xaml b/src/core/Microsoft.PowerToys.Settings.UI/Views/MainPage.xaml new file mode 100644 index 0000000000..4edd03127b --- /dev/null +++ b/src/core/Microsoft.PowerToys.Settings.UI/Views/MainPage.xaml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/src/core/Microsoft.PowerToys.Settings.UI/Views/MainPage.xaml.cs b/src/core/Microsoft.PowerToys.Settings.UI/Views/MainPage.xaml.cs new file mode 100644 index 0000000000..116eb67551 --- /dev/null +++ b/src/core/Microsoft.PowerToys.Settings.UI/Views/MainPage.xaml.cs @@ -0,0 +1,18 @@ +using System; + +using Microsoft.PowerToys.Settings.UI.ViewModels; + +using Windows.UI.Xaml.Controls; + +namespace Microsoft.PowerToys.Settings.UI.Views +{ + public sealed partial class MainPage : Page + { + public MainViewModel ViewModel { get; } = new MainViewModel(); + + public MainPage() + { + InitializeComponent(); + } + } +} diff --git a/src/core/Microsoft.PowerToys.Settings.UI/Views/ShellPage.xaml b/src/core/Microsoft.PowerToys.Settings.UI/Views/ShellPage.xaml new file mode 100644 index 0000000000..5a95ecc58e --- /dev/null +++ b/src/core/Microsoft.PowerToys.Settings.UI/Views/ShellPage.xaml @@ -0,0 +1,81 @@ + + + + + + + + + + + + + PowerToys + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/core/Microsoft.PowerToys.Settings.UI/Views/ShellPage.xaml.cs b/src/core/Microsoft.PowerToys.Settings.UI/Views/ShellPage.xaml.cs new file mode 100644 index 0000000000..9ed7c1d562 --- /dev/null +++ b/src/core/Microsoft.PowerToys.Settings.UI/Views/ShellPage.xaml.cs @@ -0,0 +1,21 @@ +using System; + +using Microsoft.PowerToys.Settings.UI.ViewModels; + +using Windows.UI.Xaml.Controls; + +namespace Microsoft.PowerToys.Settings.UI.Views +{ + // TODO WTS: Change the icons and titles for all NavigationViewItems in ShellPage.xaml. + public sealed partial class ShellPage : UserControl + { + public ShellViewModel ViewModel { get; } = new ShellViewModel(); + + public ShellPage() + { + InitializeComponent(); + DataContext = ViewModel; + ViewModel.Initialize(shellFrame, navigationView, KeyboardAccelerators); + } + } +} diff --git a/src/core/Microsoft.PowerToys.Settings.UI/Views/Test1Page.xaml b/src/core/Microsoft.PowerToys.Settings.UI/Views/Test1Page.xaml new file mode 100644 index 0000000000..637b314207 --- /dev/null +++ b/src/core/Microsoft.PowerToys.Settings.UI/Views/Test1Page.xaml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/src/core/Microsoft.PowerToys.Settings.UI/Views/Test1Page.xaml.cs b/src/core/Microsoft.PowerToys.Settings.UI/Views/Test1Page.xaml.cs new file mode 100644 index 0000000000..889412aef3 --- /dev/null +++ b/src/core/Microsoft.PowerToys.Settings.UI/Views/Test1Page.xaml.cs @@ -0,0 +1,18 @@ +using System; + +using Microsoft.PowerToys.Settings.UI.ViewModels; + +using Windows.UI.Xaml.Controls; + +namespace Microsoft.PowerToys.Settings.UI.Views +{ + public sealed partial class Test1Page : Page + { + public Test1ViewModel ViewModel { get; } = new Test1ViewModel(); + + public Test1Page() + { + InitializeComponent(); + } + } +} diff --git a/src/core/Microsoft.PowerToys.Settings.UI/Views/Test2Page.xaml b/src/core/Microsoft.PowerToys.Settings.UI/Views/Test2Page.xaml new file mode 100644 index 0000000000..b10c611b8a --- /dev/null +++ b/src/core/Microsoft.PowerToys.Settings.UI/Views/Test2Page.xaml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/src/core/Microsoft.PowerToys.Settings.UI/Views/Test2Page.xaml.cs b/src/core/Microsoft.PowerToys.Settings.UI/Views/Test2Page.xaml.cs new file mode 100644 index 0000000000..ac763997ef --- /dev/null +++ b/src/core/Microsoft.PowerToys.Settings.UI/Views/Test2Page.xaml.cs @@ -0,0 +1,18 @@ +using System; + +using Microsoft.PowerToys.Settings.UI.ViewModels; + +using Windows.UI.Xaml.Controls; + +namespace Microsoft.PowerToys.Settings.UI.Views +{ + public sealed partial class Test2Page : Page + { + public Test2ViewModel ViewModel { get; } = new Test2ViewModel(); + + public Test2Page() + { + InitializeComponent(); + } + } +} diff --git a/src/core/Microsoft.PowerToys.Settings.UI/Views/Test3Page.xaml b/src/core/Microsoft.PowerToys.Settings.UI/Views/Test3Page.xaml new file mode 100644 index 0000000000..e07971dc4d --- /dev/null +++ b/src/core/Microsoft.PowerToys.Settings.UI/Views/Test3Page.xaml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/src/core/Microsoft.PowerToys.Settings.UI/Views/Test3Page.xaml.cs b/src/core/Microsoft.PowerToys.Settings.UI/Views/Test3Page.xaml.cs new file mode 100644 index 0000000000..509375bfb1 --- /dev/null +++ b/src/core/Microsoft.PowerToys.Settings.UI/Views/Test3Page.xaml.cs @@ -0,0 +1,18 @@ +using System; + +using Microsoft.PowerToys.Settings.UI.ViewModels; + +using Windows.UI.Xaml.Controls; + +namespace Microsoft.PowerToys.Settings.UI.Views +{ + public sealed partial class Test3Page : Page + { + public Test3ViewModel ViewModel { get; } = new Test3ViewModel(); + + public Test3Page() + { + InitializeComponent(); + } + } +}