From 70aecda89e963f55e28a4394307693dc49c6b271 Mon Sep 17 00:00:00 2001 From: 0x5BFA <62196528+0x5bfa@users.noreply.github.com> Date: Tue, 25 Aug 2026 21:42:36 +0900 Subject: [PATCH 1/7] Use XamlHostingKit for UWP hosting --- .nuget/README.md | 7 + Directory.Packages.props | 3 +- README.md | 4 + docs/focus-and-activation.md | 5 +- docs/getting-started.md | 40 ++ .../App.xaml | 34 +- .../App.xaml.cs | 233 +++++-- .../DesktopFlyouts.Uwp.Sample.TrayHost.csproj | 18 +- .../MainDesktopFlyout.xaml | 110 ---- .../MainDesktopFlyout.xaml.cs | 13 - .../MainDesktopMenuFlyout.xaml | 33 - .../MainDesktopMenuFlyout.xaml.cs | 22 - .../Program.cs | 3 +- src/DesktopFlyouts.Shared/DesktopFlyout.cs | 10 +- .../DesktopFlyouts.Shared.projitems | 3 +- .../DesktopMenuFlyout.cs | 15 +- .../XamlIslandApplication.cs | 28 + .../XamlIslandHostWindow.Uwp.cs | 614 ++++++++++++++++++ .../XamlIslandHostWindow.X11.cs | 2 + .../XamlIslandHostWindow.cs | 4 +- .../DesktopFlyouts.Uwp.csproj | 4 +- 21 files changed, 905 insertions(+), 300 deletions(-) delete mode 100644 samples/DesktopFlyouts.Uwp.Sample.TrayHost/MainDesktopFlyout.xaml delete mode 100644 samples/DesktopFlyouts.Uwp.Sample.TrayHost/MainDesktopFlyout.xaml.cs delete mode 100644 samples/DesktopFlyouts.Uwp.Sample.TrayHost/MainDesktopMenuFlyout.xaml delete mode 100644 samples/DesktopFlyouts.Uwp.Sample.TrayHost/MainDesktopMenuFlyout.xaml.cs create mode 100644 src/DesktopFlyouts.Shared/XamlIslandHostWindow.Uwp.cs diff --git a/.nuget/README.md b/.nuget/README.md index 6c4806a..3fb2961 100644 --- a/.nuget/README.md +++ b/.nuget/README.md @@ -1,5 +1,12 @@ DesktopFlyouts is a WinUI library for showing desktop flyouts from tray icons or programmatically. +## UWP desktop host + +The UWP package uses XamlHostingKit for System XAML initialization and message dispatch. Start the +application with `XamlIslandApplication.Start`, and create every additional flyout on its own XAML +thread with `XamlIslandApplication.CreateWindow`. See the getting-started guide below for the full +entry-point example. + ## Usage ### DesktopFlyout diff --git a/Directory.Packages.props b/Directory.Packages.props index 4824bf8..1bdccb9 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -18,8 +18,9 @@ - + + diff --git a/README.md b/README.md index c2b7e1b..211a70b 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,10 @@ The UWP version of sample app is currently under development. Recommend to use W > dotnet add package DesktopFlyouts.Uwp ``` +UWP desktop hosts initialize System XAML and its message loop through XamlHostingKit. See the +[UWP startup instructions](docs/getting-started.md#start-a-uwp-desktop-host); each additional +flyout is created on its own XAML thread with `XamlIslandApplication.CreateWindow`. + ### WinUI (WinAppSDK/WinUI3) NuGet badge diff --git a/docs/focus-and-activation.md b/docs/focus-and-activation.md index ab41e0e..563800d 100644 --- a/docs/focus-and-activation.md +++ b/docs/focus-and-activation.md @@ -48,4 +48,7 @@ Set it to `False` for sticky flyouts. ## UWP message loop integration -UWP desktop-host scenarios expose `TryPreTranslateMessage` on `DesktopFlyout` and `DesktopMenuFlyout`. Call it from the native message loop so keyboard navigation and accelerators can reach the hosted XAML island. +Start UWP desktop hosts with `XamlIslandApplication.Start`. XamlHostingKit owns the CoreWindow +message loop and performs XAML message translation, so application code must not run a second +native message loop. `TryPreTranslateMessage` remains available for source compatibility and +returns `False` with the XamlHostingKit host. diff --git a/docs/getting-started.md b/docs/getting-started.md index b0a8600..6e09e74 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -10,6 +10,46 @@ dotnet add package 0x5BFA.DesktopFlyouts.WinUI --prerelease dotnet add package 0x5BFA.DesktopFlyouts.Uwp --prerelease ``` +### Start a UWP desktop host + +The UWP package uses XamlHostingKit to own System XAML initialization and the desktop message loop. +Disable the generated XAML entry point and start the application through `XamlIslandApplication`: + +```xml +$(DefineConstants);DISABLE_XAML_GENERATED_MAIN +``` + +```csharp +using DesktopFlyouts.Shared; + +[STAThread] +static void Main() +{ + XamlIslandApplication.Start(_ => new App()); +} +``` + +Create the first `DesktopFlyout` or `DesktopMenuFlyout` on the main XAML thread. System XAML +allows one top-level window per thread, so create every additional flyout in its own callback: + +```csharp +private DesktopFlyout? _flyout; +private DesktopMenuFlyout? _menu; + +protected override void OnLaunched(LaunchActivatedEventArgs args) +{ + _flyout = new MainDesktopFlyout(); + + XamlIslandApplication.CreateWindow(_ => + { + _menu = new MainDesktopMenuFlyout(); + }); +} +``` + +XamlHostingKit dispatches each callback on the matching XAML thread. Dispatch later menu or +flyout operations back to the instance's `Dispatcher` when the caller runs on another thread. + Add the DesktopFlyouts namespace to XAML. ```xml diff --git a/samples/DesktopFlyouts.Uwp.Sample.TrayHost/App.xaml b/samples/DesktopFlyouts.Uwp.Sample.TrayHost/App.xaml index 960e994..8b2a083 100644 --- a/samples/DesktopFlyouts.Uwp.Sample.TrayHost/App.xaml +++ b/samples/DesktopFlyouts.Uwp.Sample.TrayHost/App.xaml @@ -1,36 +1,4 @@ - - - - - - - - - - - #09FFFFFF - #15FFFFFF - #18000000 - - - #40FFFFFF - #0F000000 - #10000000 - - - #09FFFFFF - #15FFFFFF - #18000000 - - - - - - - - + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" /> diff --git a/samples/DesktopFlyouts.Uwp.Sample.TrayHost/App.xaml.cs b/samples/DesktopFlyouts.Uwp.Sample.TrayHost/App.xaml.cs index 7f74b1b..5f07238 100644 --- a/samples/DesktopFlyouts.Uwp.Sample.TrayHost/App.xaml.cs +++ b/samples/DesktopFlyouts.Uwp.Sample.TrayHost/App.xaml.cs @@ -2,81 +2,172 @@ // Licensed under the MIT license. using System; +using System.IO; using System.Threading; +using DesktopFlyouts.Shared; +using Windows.ApplicationModel.Activation; using Windows.System; +using Windows.UI; +using Windows.UI.Core; using Windows.UI.Xaml; -using Windows.UI.Xaml.Hosting; -using Windows.Win32; -using Windows.Win32.Foundation; -using Windows.Win32.System.WinRT; -using Windows.Win32.UI.WindowsAndMessaging; +using Windows.UI.Xaml.Controls; +using Windows.UI.Xaml.Media; namespace DesktopFlyouts { - public partial class App : Application - { - private static SystemTrayIcon? _systemTrayIcon; - private static DesktopFlyout? _desktopFlyout; - private static DesktopMenuFlyout? _desktopMenuFlyout; - - public unsafe App() - { - PInvoke.RoInitialize(RO_INIT_TYPE.RO_INIT_SINGLETHREADED); - - _systemTrayIcon = new( - "Tray.ico", - "DesktopFlyouts sample app (UWP)", - new("022F5158-F05A-4FE1-B356-34F14B363625")); - - _systemTrayIcon.LeftClicked += SystemTrayIcon_LeftClicked; - _systemTrayIcon.RightClicked += SystemTrayIcon_RightClicked; - _systemTrayIcon.Show(); - - // Initialize XAML Island - WindowsXamlManager.InitializeForCurrentThread(); - SynchronizationContext.SetSynchronizationContext(new DispatcherQueueSynchronizationContext(DispatcherQueue.GetForCurrentThread())); - - // Initialize XAML flyouts - _desktopFlyout = new MainDesktopFlyout(); - _desktopMenuFlyout = new MainDesktopMenuFlyout(); - - MSG msg; - while (PInvoke.GetMessage(&msg, HWND.Null, 0U, 0U)) - { - if (!TryPreTranslateMessage(&msg)) - { - PInvoke.TranslateMessage(&msg); - PInvoke.DispatchMessage(&msg); - } - } - } - - private static unsafe bool TryPreTranslateMessage(MSG* msg) - { - return (_desktopFlyout?.TryPreTranslateMessage(msg) ?? false) || - (_desktopMenuFlyout?.TryPreTranslateMessage(msg) ?? false); - } - - private static void SystemTrayIcon_LeftClicked(object? sender, MouseEventReceivedEventArgs e) - { - if (_desktopFlyout is null) - return; - - if (_desktopFlyout.IsOpen) - _desktopFlyout.Hide(); - else - _desktopFlyout.Show(); - } - - private static void SystemTrayIcon_RightClicked(object? sender, MouseEventReceivedEventArgs e) - { - if (_desktopMenuFlyout is null) - return; - - if (_desktopMenuFlyout.IsOpen) - _desktopMenuFlyout.Hide(); - - _desktopMenuFlyout.Show(new(e.Point.X, e.Point.Y - 32)); - } - } + public partial class App : Application + { + private static SystemTrayIcon? _systemTrayIcon; + private static DesktopFlyout? _desktopFlyout; + private static volatile DesktopMenuFlyout? _desktopMenuFlyout; + + protected override void OnLaunched(LaunchActivatedEventArgs args) + { + SynchronizationContext.SetSynchronizationContext( + new DispatcherQueueSynchronizationContext(DispatcherQueue.GetForCurrentThread())); + + _systemTrayIcon = new( + Path.Combine(AppContext.BaseDirectory, "Tray.ico"), + "DesktopFlyouts sample app (UWP)", + new("022F5158-F05A-4FE1-B356-34F14B363625")); + + _systemTrayIcon.LeftClicked += SystemTrayIcon_LeftClicked; + _systemTrayIcon.RightClicked += SystemTrayIcon_RightClicked; + _systemTrayIcon.Show(); + + // The first flyout uses XamlHostingKit's main XamlWindow. + _desktopFlyout = CreateDesktopFlyout(); + + // System XAML permits one top-level window per thread. The menu therefore uses + // another XamlHostingKit window and is constructed on that window's XAML thread. + XamlIslandApplication.CreateWindow(_ => + { + SynchronizationContext.SetSynchronizationContext( + new DispatcherQueueSynchronizationContext(DispatcherQueue.GetForCurrentThread())); + _desktopMenuFlyout = CreateDesktopMenuFlyout(); + }); + } + + private static DesktopFlyout CreateDesktopFlyout() + { + var flyout = new DesktopFlyout + { + Width = 360, + }; + + flyout.Islands.Add(CreateIsland(new Button + { + Content = "A", + HorizontalAlignment = HorizontalAlignment.Center, + VerticalAlignment = VerticalAlignment.Center, + })); + + flyout.Islands.Add(CreateIsland(new TextBlock + { + Text = "Island 2", + HorizontalAlignment = HorizontalAlignment.Center, + VerticalAlignment = VerticalAlignment.Center, + })); + + var options = new ComboBox + { + HorizontalAlignment = HorizontalAlignment.Stretch, + }; + options.Items.Add("Option 1"); + options.Items.Add("Option 2"); + options.Items.Add("Option 3"); + + var panel = new StackPanel + { + Padding = new Thickness(16), + Spacing = 8, + }; + panel.Children.Add(new TextBlock + { + Text = "Island 3", + FontSize = 20, + FontWeight = Windows.UI.Text.FontWeights.SemiBold, + }); + panel.Children.Add(new TextBlock { Text = "Select option" }); + panel.Children.Add(options); + panel.Children.Add(new CalendarDatePicker()); + flyout.Islands.Add(CreateIsland(panel)); + + return flyout; + } + + private static DesktopFlyoutIsland CreateIsland(UIElement content) + { + return new() + { + Height = 180, + Background = new SolidColorBrush(Color.FromArgb(245, 32, 32, 32)), + Content = content, + }; + } + + private static DesktopMenuFlyout CreateDesktopMenuFlyout() + { + var menu = new DesktopMenuFlyout(); + var settings = new MenuFlyoutSubItem + { + Text = "Settings", + Icon = new FontIcon { Glyph = "\uE115" }, + }; + settings.Items.Add(new MenuFlyoutItem { Text = "Theme" }); + settings.Items.Add(new MenuFlyoutItem { Text = "Language" }); + settings.Items.Add(new MenuFlyoutItem { Text = "Privacy" }); + + var devices = new MenuFlyoutItem + { + Text = "Devices", + Icon = new FontIcon { Glyph = "\uE975" }, + }; + devices.Click += async (_, _) => await Launcher.LaunchUriAsync(new Uri("tif-secondaryapp:")); + + var exit = new MenuFlyoutItem + { + Text = "Exit", + Icon = new FontIcon { Glyph = "\uE8BB" }, + }; + exit.Click += (_, _) => + { + var dispatcher = _desktopFlyout?.Dispatcher; + if (dispatcher is not null) + _ = dispatcher.RunAsync(CoreDispatcherPriority.Normal, Current.Exit); + }; + + menu.Items.Add(settings); + menu.Items.Add(devices); + menu.Items.Add(new MenuFlyoutSeparator()); + menu.Items.Add(exit); + return menu; + } + + private static void SystemTrayIcon_LeftClicked(object? sender, MouseEventReceivedEventArgs e) + { + if (_desktopFlyout is null) + return; + + if (_desktopFlyout.IsOpen) + _desktopFlyout.Hide(); + else + _desktopFlyout.Show(); + } + + private static void SystemTrayIcon_RightClicked(object? sender, MouseEventReceivedEventArgs e) + { + var menuFlyout = _desktopMenuFlyout; + if (menuFlyout is null) + return; + + _ = menuFlyout.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => + { + if (menuFlyout.IsOpen) + menuFlyout.Hide(); + + menuFlyout.Show(new(e.Point.X, e.Point.Y - 32)); + }); + } + } } diff --git a/samples/DesktopFlyouts.Uwp.Sample.TrayHost/DesktopFlyouts.Uwp.Sample.TrayHost.csproj b/samples/DesktopFlyouts.Uwp.Sample.TrayHost/DesktopFlyouts.Uwp.Sample.TrayHost.csproj index ce9021a..0e83f9e 100644 --- a/samples/DesktopFlyouts.Uwp.Sample.TrayHost/DesktopFlyouts.Uwp.Sample.TrayHost.csproj +++ b/samples/DesktopFlyouts.Uwp.Sample.TrayHost/DesktopFlyouts.Uwp.Sample.TrayHost.csproj @@ -1,4 +1,4 @@ - + @@ -12,17 +12,27 @@ $(DefineConstants);DISABLE_XAML_GENERATED_MAIN true + true false + resources.pri + <_SupportEmbedFileResources>true + + $(TargetPlatformSdkRootOverride)\ + $([MSBuild]::GetRegistryValueFromView('HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows Kits\Installed Roots', 'KitsRoot10', null, RegistryView.Registry32, RegistryView.Default)) + $(WindowsKitsPath)bin\$(TargetWindowsVersion)\XamlCompiler\Microsoft.Windows.UI.Xaml.Common.targets + + - + + PreserveNewest + PreserveNewest + - - diff --git a/samples/DesktopFlyouts.Uwp.Sample.TrayHost/MainDesktopFlyout.xaml b/samples/DesktopFlyouts.Uwp.Sample.TrayHost/MainDesktopFlyout.xaml deleted file mode 100644 index 4a04cab..0000000 --- a/samples/DesktopFlyouts.Uwp.Sample.TrayHost/MainDesktopFlyout.xaml +++ /dev/null @@ -1,110 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/samples/DesktopFlyouts.Uwp.Sample.TrayHost/MainDesktopFlyout.xaml.cs b/samples/DesktopFlyouts.Uwp.Sample.TrayHost/MainDesktopFlyout.xaml.cs deleted file mode 100644 index 2e56d10..0000000 --- a/samples/DesktopFlyouts.Uwp.Sample.TrayHost/MainDesktopFlyout.xaml.cs +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright (c) 0x5BFA. All rights reserved. -// Licensed under the MIT license. - -namespace DesktopFlyouts -{ - public sealed partial class MainDesktopFlyout : DesktopFlyout - { - public MainDesktopFlyout() - { - InitializeComponent(); - } - } -} diff --git a/samples/DesktopFlyouts.Uwp.Sample.TrayHost/MainDesktopMenuFlyout.xaml b/samples/DesktopFlyouts.Uwp.Sample.TrayHost/MainDesktopMenuFlyout.xaml deleted file mode 100644 index b15804f..0000000 --- a/samples/DesktopFlyouts.Uwp.Sample.TrayHost/MainDesktopMenuFlyout.xaml +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/samples/DesktopFlyouts.Uwp.Sample.TrayHost/MainDesktopMenuFlyout.xaml.cs b/samples/DesktopFlyouts.Uwp.Sample.TrayHost/MainDesktopMenuFlyout.xaml.cs deleted file mode 100644 index 988477a..0000000 --- a/samples/DesktopFlyouts.Uwp.Sample.TrayHost/MainDesktopMenuFlyout.xaml.cs +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright (c) 0x5BFA. All rights reserved. -// Licensed under the MIT license. - -using System; - -using Windows.System; - -namespace DesktopFlyouts -{ - public sealed partial class MainDesktopMenuFlyout : DesktopMenuFlyout - { - public MainDesktopMenuFlyout() - { - InitializeComponent(); - } - - private async void OpenSecondaryApp(object sender, Windows.UI.Xaml.RoutedEventArgs e) - { - await Launcher.LaunchUriAsync(new Uri("tif-secondaryapp:")); - } - } -} diff --git a/samples/DesktopFlyouts.Uwp.Sample.TrayHost/Program.cs b/samples/DesktopFlyouts.Uwp.Sample.TrayHost/Program.cs index 1108627..887ff05 100644 --- a/samples/DesktopFlyouts.Uwp.Sample.TrayHost/Program.cs +++ b/samples/DesktopFlyouts.Uwp.Sample.TrayHost/Program.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. using System; +using DesktopFlyouts.Shared; namespace DesktopFlyouts { @@ -10,7 +11,7 @@ public class Program [STAThread] static void Main() { - _ = new App(); + XamlIslandApplication.Start(_ => new App()); } } } diff --git a/src/DesktopFlyouts.Shared/DesktopFlyout.cs b/src/DesktopFlyouts.Shared/DesktopFlyout.cs index 48a5a76..28088cf 100644 --- a/src/DesktopFlyouts.Shared/DesktopFlyout.cs +++ b/src/DesktopFlyouts.Shared/DesktopFlyout.cs @@ -147,7 +147,7 @@ protected override void OnApplyTemplate() /// public void Show() { - if (_disposed || _host?.DesktopWindowXamlSource is null || RootGrid is null || _isPopupAnimationPlaying) + if (_disposed || _host?.IsInitialized is not true || RootGrid is null || _isPopupAnimationPlaying) { _customPlacementBottomCenterPoint = null; return; @@ -297,8 +297,8 @@ public void NavigateFocus(XamlSourceFocusNavigationReason reason = XamlSourceFoc /// The native message to process. /// if the message was handled; otherwise, . /// - /// UWP desktop-host scenarios should call this from their native message loop so keyboard - /// navigation and accelerator processing can reach the hosted XAML island. + /// XamlHostingKit owns the UWP desktop message loop and performs XAML message translation. + /// This method remains available for source compatibility and returns . /// public unsafe bool TryPreTranslateMessage(MSG* msg) { @@ -346,7 +346,7 @@ private DesktopFlyoutPopupDirection UpdateFlyoutRegion( FoundationRect? resizeAnchorRegion = null, DesktopFlyoutPopupDirection resizePopupDirection = DesktopFlyoutPopupDirection.Vertical) { - if (_host?.DesktopWindowXamlSource is null || IslandsItemsControl is null) + if (_host?.IsInitialized is not true || IslandsItemsControl is null) return ResolvePopupDirection(PopupDirection, default, WindowHelpers.GetFlyoutWorkAreaRect(_customPlacementBottomCenterPoint)); var customBottomCenterPoint = _customPlacementBottomCenterPoint; @@ -419,7 +419,7 @@ private void UpdateOpenFlyoutLayout() _isPopupAnimationPlaying || _isUpdatingOpenFlyoutLayout || RootGrid is null || - _host?.DesktopWindowXamlSource is null) + _host?.IsInitialized is not true) { return; } diff --git a/src/DesktopFlyouts.Shared/DesktopFlyouts.Shared.projitems b/src/DesktopFlyouts.Shared/DesktopFlyouts.Shared.projitems index 2aecb33..88f9c94 100644 --- a/src/DesktopFlyouts.Shared/DesktopFlyouts.Shared.projitems +++ b/src/DesktopFlyouts.Shared/DesktopFlyouts.Shared.projitems @@ -41,10 +41,11 @@ + - \ No newline at end of file + diff --git a/src/DesktopFlyouts.Shared/DesktopMenuFlyout.cs b/src/DesktopFlyouts.Shared/DesktopMenuFlyout.cs index 2f830aa..f086b4d 100644 --- a/src/DesktopFlyouts.Shared/DesktopMenuFlyout.cs +++ b/src/DesktopFlyouts.Shared/DesktopMenuFlyout.cs @@ -7,6 +7,7 @@ #if UWP +using Windows.Graphics; using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; using Windows.UI.Xaml.Markup; @@ -101,7 +102,7 @@ protected override void OnItemsChanged(object e) /// public void Show(Point point) { - if (_disposed || _menuFlyout is null) + if (_disposed || _host?.IsInitialized is not true || _menuFlyout is null) return; UpdateFlyoutTheme(); @@ -110,6 +111,14 @@ public void Show(Point point) _host?.SetHWndRectRegion(new RectInt32() { Width = 1, Height = 1 }); _ = _host?.UpdateWindowVisibility(true); + ApplyTemplate(); + UpdateLayout(); + if (MenuFlyoutTargetControl is null) + { + _ = _host?.UpdateWindowVisibility(false); + return; + } + _menuFlyout.ShowAt(MenuFlyoutTargetControl); IsOpen = true; @@ -154,8 +163,8 @@ private void HostWindow_SystemSettingsChanged(object? sender, EventArgs e) /// The native message to process. /// if the message was handled; otherwise, . /// - /// UWP desktop-host scenarios should call this from their native message loop so keyboard - /// navigation and accelerator processing can reach the hosted XAML island. + /// XamlHostingKit owns the UWP desktop message loop and performs XAML message translation. + /// This method remains available for source compatibility and returns . /// public unsafe bool TryPreTranslateMessage(MSG* msg) { diff --git a/src/DesktopFlyouts.Shared/XamlIslandApplication.cs b/src/DesktopFlyouts.Shared/XamlIslandApplication.cs index 0e51709..f4ffb31 100644 --- a/src/DesktopFlyouts.Shared/XamlIslandApplication.cs +++ b/src/DesktopFlyouts.Shared/XamlIslandApplication.cs @@ -3,7 +3,9 @@ // Licensed under the MIT license. #if UWP +using System; using Windows.UI.Xaml; +using XamlHostingKit; #endif namespace DesktopFlyouts.Shared @@ -18,6 +20,32 @@ namespace DesktopFlyouts.Shared /// public partial class XamlIslandApplication : Application { + /// + /// Starts System XAML through XamlHostingKit and runs its desktop message loop. + /// + /// Creates the application's instance. + public new static void Start(ApplicationInitializationCallback initializationCallback) + { + ArgumentNullException.ThrowIfNull(initializationCallback); + + XamlApplication.Start(initializationCallback); + } + + /// + /// Creates another XamlHostingKit window and invokes a callback on its XAML thread. + /// + /// + /// Creates one or for the new window. + /// + /// + /// System XAML permits one top-level XAML window per thread. Create each additional + /// DesktopFlyouts host inside a separate callback supplied to this method. + /// + public static void CreateWindow(ApplicationInitializationCallback initializationCallback) + { + ArgumentNullException.ThrowIfNull(initializationCallback); + XamlApplication.CreateWindow(new WindowCreationOptions(), initializationCallback); + } } #endif } diff --git a/src/DesktopFlyouts.Shared/XamlIslandHostWindow.Uwp.cs b/src/DesktopFlyouts.Shared/XamlIslandHostWindow.Uwp.cs new file mode 100644 index 0000000..9bf45f5 --- /dev/null +++ b/src/DesktopFlyouts.Shared/XamlIslandHostWindow.Uwp.cs @@ -0,0 +1,614 @@ +#if UWP +// Copyright (c) 0x5BFA. All rights reserved. +// Licensed under the MIT license. + +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Runtime.InteropServices.Marshalling; +using System.Threading.Tasks; +using Windows.Foundation; +using Windows.Graphics; +using Windows.Graphics.Display; +using Windows.UI.Core; +using Windows.UI.ViewManagement; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; +using Windows.UI.Xaml.Hosting; +using Windows.Win32; +using Windows.Win32.Foundation; +using Windows.Win32.Graphics.Gdi; +using Windows.Win32.System.Com; +using Windows.Win32.System.WinRT; +using Windows.Win32.UI.WindowsAndMessaging; +using WinRT; +using XamlHostingKit; + +namespace DesktopFlyouts +{ + internal unsafe partial class XamlIslandHostWindow : IDisposable + { + private static readonly object s_cbtHookTargetsLock = new(); + private static readonly Dictionary> s_cbtHookTargetsByThread = []; + private static readonly object s_claimedWindowsLock = new(); + private static readonly HashSet s_claimedWindows = []; + + private readonly WNDPROC _xamlWndProc; + private readonly UISettings _uiSettings = new(); + private readonly Dictionary _subclassedXamlWndProcs = []; + + private XamlWindow? _xamlWindow; + private CoreWindow? _coreWindow; + private UIElement? _content; + private Grid? _contentRoot; + private HWND _xamlHwnd = default; + private HHOOK _cbtHook = default; + private uint _cbtHookThreadId; + private HWND _preservedForegroundHWnd = default; + private HWND _preservedActiveHWnd = default; + private HWND _preservedFocusHWnd = default; + private bool _disposed; + private DesktopFlyoutActivationMode _activationMode = DesktopFlyoutActivationMode.Activate; + + internal HWND HWnd { get; private set; } + + internal bool IsInitialized => _xamlWindow is not null; + + internal Rect WindowSize + { + get + { + RECT rect; + PInvoke.GetWindowRect(HWnd, &rect); + return new(rect.X, rect.Y, rect.Width, rect.Height); + } + } + + internal double XamlIslandRasterizationScale + { + get + { + try + { + return DisplayInformation.GetForCurrentView().RawPixelsPerViewPixel; + } + catch + { + return 1.0D; + } + } + } + + internal event EventHandler? WindowInactivated; + internal event EventHandler? SystemSettingsChanged; + + internal XamlIslandHostWindow() + { + _xamlWndProc = new(XamlWndProc); + + _xamlWindow = XamlWindow.Current; + if (_xamlWindow is null) + { + throw new InvalidOperationException( + "UWP DesktopFlyouts must be created inside a XamlHostingKit XamlWindow. " + + "Start the app with XamlIslandApplication.Start and create additional flyouts " + + "inside XamlIslandApplication.CreateWindow callbacks."); + } + + if (_xamlWindow.Content is not null) + { + throw new InvalidOperationException( + "A XamlHostingKit XamlWindow can host only one DesktopFlyout or DesktopMenuFlyout instance."); + } + + _xamlWindow.Title = "DesktopFlyoutHostWindow"; + _xamlWindow.Styles = WindowStyles.Popup; + _xamlWindow.ExtendedStyles = + WindowExtendedStyles.NoRedirectionBitmap | + WindowExtendedStyles.ToolWindow | + WindowExtendedStyles.Topmost; + + HWnd = (HWND)unchecked((nint)_xamlWindow.WindowHandle.Value); + if (HWnd.IsNull) + throw new InvalidOperationException("XamlHostingKit did not provide a host window handle."); + + lock (s_claimedWindowsLock) + { + if (!s_claimedWindows.Add((nint)HWnd.Value)) + { + throw new InvalidOperationException( + "A XamlHostingKit XamlWindow can host only one DesktopFlyout or DesktopMenuFlyout instance."); + } + } + + try + { + _coreWindow = _xamlWindow.CoreWindow; + InitializeCoreWindowHandle(); + + _coreWindow.Activated += CoreWindow_Activated; + _uiSettings.ColorValuesChanged += UISettings_ColorValuesChanged; + + ApplyActivationModeToWindows(); + } + catch + { + lock (s_claimedWindowsLock) + s_claimedWindows.Remove((nint)HWnd.Value); + + throw; + } + } + + internal void SetContent(UIElement content) + { + if (_disposed || _xamlWindow is null) + return; + + _content = content; + var dispatcher = _coreWindow?.Dispatcher; + if (dispatcher is null) + throw new InvalidOperationException("XamlHostingKit did not provide a CoreDispatcher."); + + // A derived control calls InitializeComponent after its base DesktopFlyout constructor. + // Attach it on the next dispatcher turn so XAML parsing completes before Window.Content + // starts loading templates and resources. + _ = dispatcher.RunAsync(CoreDispatcherPriority.High, () => + { + if (!_disposed && _xamlWindow is not null && ReferenceEquals(_content, content)) + { + _contentRoot = new Grid(); + _contentRoot.Children.Add(content); + _xamlWindow.Content = _contentRoot; + + var wasVisible = PInvoke.IsWindowVisible(HWnd); + if (!wasVisible) + { + PInvoke.SetWindowPos( + HWnd, + HWND.Null, + -32000, + -32000, + 1, + 1, + SET_WINDOW_POS_FLAGS.SWP_NOZORDER | SET_WINDOW_POS_FLAGS.SWP_NOACTIVATE); + PInvoke.ShowWindow(HWnd, SHOW_WINDOW_CMD.SW_SHOWNOACTIVATE); + } + + if (content is Control control) + { + control.ApplyTemplate(); + control.UpdateLayout(); + } + + if (!wasVisible) + PInvoke.ShowWindow(HWnd, SHOW_WINDOW_CMD.SW_HIDE); + + ApplyActivationModeToWindows(); + } + }); + } + + internal void PreserveActivationState() + { + if (_disposed) + return; + + _preservedForegroundHWnd = PInvoke.GetForegroundWindow(); + _preservedActiveHWnd = PInvoke.GetActiveWindow(); + _preservedFocusHWnd = PInvoke.GetFocus(); + } + + internal void RestoreActivationState() + { + if (_disposed) + return; + + if (!_preservedForegroundHWnd.IsNull) + PInvoke.SetForegroundWindow(_preservedForegroundHWnd); + + if (!_preservedActiveHWnd.IsNull) + PInvoke.SetActiveWindow(_preservedActiveHWnd); + + if (!_preservedFocusHWnd.IsNull) + PInvoke.SetFocus(_preservedFocusHWnd); + } + + internal void MoveAndResize(RectInt32 rect, bool activate = true) + { + if (_disposed) + return; + + var flags = activate ? 0 : SET_WINDOW_POS_FLAGS.SWP_NOACTIVATE; + PInvoke.SetWindowPos(HWnd, HWND.HWND_TOP, rect.X, rect.Y, rect.Width, rect.Height, flags); + } + + internal void Maximize(System.Drawing.Rectangle workArea, bool activate = true) + { + if (_disposed) + return; + + var flags = activate ? 0 : SET_WINDOW_POS_FLAGS.SWP_NOACTIVATE; + PInvoke.SetWindowPos(HWnd, HWND.HWND_TOP, workArea.X, workArea.Y, workArea.Width, workArea.Height, flags); + } + + internal void SetHWndRectRegion(RectInt32 rect) + { + if (_disposed) + return; + + SetWindowRectRegion(HWnd, rect); + SetWindowRectRegion(_xamlHwnd, rect); + } + + private static void SetWindowRectRegion(HWND hWnd, RectInt32 rect) + { + if (hWnd.IsNull) + return; + + HRGN region = PInvoke.CreateRectRgn(rect.X, rect.Y, rect.X + rect.Width, rect.Y + rect.Height); + if (region.IsNull) + return; + + if (PInvoke.SetWindowRgn(hWnd, region, false) == 0) + PInvoke.DeleteObject(region); + } + + internal ValueTask UpdateWindowVisibility(bool isVisible, bool activate = true) + { + if (_disposed) + return default; + + var command = isVisible + ? activate ? SHOW_WINDOW_CMD.SW_SHOW : SHOW_WINDOW_CMD.SW_SHOWNOACTIVATE + : SHOW_WINDOW_CMD.SW_HIDE; + + PInvoke.ShowWindow(HWnd, command); + + if (isVisible) + ApplyActivationModeToWindows(); + + return default; + } + + internal void SetActivationMode(DesktopFlyoutActivationMode activationMode) + { + if (_disposed) + return; + + _activationMode = activationMode; + ApplyActivationModeToWindows(); + } + + internal bool NavigateFocus(XamlSourceFocusNavigationReason reason = XamlSourceFocusNavigationReason.Programmatic) + { + if (_disposed || _xamlHwnd.IsNull || _activationMode is DesktopFlyoutActivationMode.NeverActivate) + return false; + + PInvoke.SetFocus(_xamlHwnd); + return _content is Control control && control.Focus(FocusState.Programmatic); + } + + public bool TryPreTranslateMessage(MSG* msg) + { + // XamlHostingKit owns the CoreWindow message loop and performs XAML message translation. + return false; + } + + private void InitializeCoreWindowHandle() + { + if (_coreWindow is null) + throw new InvalidOperationException("XamlHostingKit did not provide a CoreWindow."); + + void* ppv; + ((IUnknown*)((IWinRTObject)_coreWindow).NativeObject.ThisPtr)->QueryInterface( + (Guid*)Unsafe.AsPointer(ref Unsafe.AsRef(in IID.IID_ICoreWindowInterop)), &ppv); + + var wrappers = new StrategyBasedComWrappers(); + var interop = (ICoreWindowInterop)wrappers.GetOrCreateObjectForComInstance( + (nint)ppv, + CreateObjectFlags.None); + interop.get_WindowHandle((HWND*)Unsafe.AsPointer(ref _xamlHwnd)); + } + + private void ApplyActivationModeToWindows() + { + var neverActivate = _activationMode is DesktopFlyoutActivationMode.NeverActivate; + UpdateCbtHook(neverActivate); + if (neverActivate) + RefreshXamlWindowSubclasses(); + + SetNoActivateStyle(HWnd, neverActivate); + SetNoActivateStyle(_xamlHwnd, neverActivate); + + foreach (var hWnd in _subclassedXamlWndProcs.Keys) + SetNoActivateStyle((HWND)hWnd, neverActivate); + + if (!neverActivate) + UnsubclassXamlWindows(); + } + + private static void SetNoActivateStyle(HWND hWnd, bool enabled) + { + if (hWnd.IsNull) + return; + + var exStyle = (WINDOW_EX_STYLE)PInvoke.GetWindowLong(hWnd, WINDOW_LONG_PTR_INDEX.GWL_EXSTYLE); + exStyle = enabled + ? exStyle | WINDOW_EX_STYLE.WS_EX_NOACTIVATE + : exStyle & ~WINDOW_EX_STYLE.WS_EX_NOACTIVATE; + + PInvoke.SetWindowLong(hWnd, WINDOW_LONG_PTR_INDEX.GWL_EXSTYLE, (int)exStyle); + PInvoke.SetWindowPos( + hWnd, + HWND.Null, + 0, + 0, + 0, + 0, + SET_WINDOW_POS_FLAGS.SWP_NOMOVE | + SET_WINDOW_POS_FLAGS.SWP_NOSIZE | + SET_WINDOW_POS_FLAGS.SWP_NOZORDER | + SET_WINDOW_POS_FLAGS.SWP_NOACTIVATE | + SET_WINDOW_POS_FLAGS.SWP_FRAMECHANGED); + } + + private void UpdateCbtHook(bool enabled) + { + if (enabled) + { + EnsureCbtHook(); + return; + } + + RemoveCbtHook(); + } + + private void EnsureCbtHook() + { + if (_cbtHook != HHOOK.Null) + return; + + _cbtHookThreadId = PInvoke.GetCurrentThreadId(); + _cbtHook = PInvoke.SetWindowsHookEx( + WINDOWS_HOOK_ID.WH_CBT, + &CbtHookProc, + HINSTANCE.Null, + _cbtHookThreadId); + + if (_cbtHook != HHOOK.Null) + RegisterCbtHookTarget(_cbtHookThreadId); + } + + private void RemoveCbtHook() + { + if (_cbtHook == HHOOK.Null) + return; + + PInvoke.UnhookWindowsHookEx(_cbtHook); + _cbtHook = default; + UnregisterCbtHookTarget(_cbtHookThreadId); + _cbtHookThreadId = 0; + } + + private void RegisterCbtHookTarget(uint threadId) + { + lock (s_cbtHookTargetsLock) + { + if (!s_cbtHookTargetsByThread.TryGetValue(threadId, out var targets)) + { + targets = []; + s_cbtHookTargetsByThread[threadId] = targets; + } + + if (!targets.Contains(this)) + targets.Add(this); + } + } + + private void UnregisterCbtHookTarget(uint threadId) + { + if (threadId == 0) + return; + + lock (s_cbtHookTargetsLock) + { + if (!s_cbtHookTargetsByThread.TryGetValue(threadId, out var targets)) + return; + + targets.Remove(this); + if (targets.Count == 0) + s_cbtHookTargetsByThread.Remove(threadId); + } + } + + private static XamlIslandHostWindow[] GetCbtHookTargets(uint threadId) + { + lock (s_cbtHookTargetsLock) + { + return s_cbtHookTargetsByThread.TryGetValue(threadId, out var targets) + ? [.. targets] + : []; + } + } + + private void RefreshXamlWindowSubclasses() + { + if (_xamlHwnd.IsNull) + return; + + SubclassXamlWindow(_xamlHwnd); + SubclassChildWindows(HWnd); + SubclassChildWindows(_xamlHwnd); + } + + private void SubclassChildWindows(HWND parentHWnd) + { + for (var childHWnd = PInvoke.GetWindow(parentHWnd, GET_WINDOW_CMD.GW_CHILD); + !childHWnd.IsNull; + childHWnd = PInvoke.GetWindow(childHWnd, GET_WINDOW_CMD.GW_HWNDNEXT)) + { + SubclassXamlWindow(childHWnd); + SubclassChildWindows(childHWnd); + } + } + + private void SubclassXamlWindow(HWND hWnd) + { + if (hWnd.IsNull || _subclassedXamlWndProcs.ContainsKey((nint)hWnd.Value)) + return; + + var previousWndProc = (nint)PInvoke.SetWindowLongPtr( + hWnd, + WINDOW_LONG_PTR_INDEX.GWLP_WNDPROC, + Marshal.GetFunctionPointerForDelegate(_xamlWndProc)); + + _subclassedXamlWndProcs[(nint)hWnd.Value] = previousWndProc; + } + + private void UnsubclassXamlWindows() + { + foreach (var item in _subclassedXamlWndProcs) + { + var hWnd = (HWND)item.Key; + if (!hWnd.IsNull) + PInvoke.SetWindowLongPtr(hWnd, WINDOW_LONG_PTR_INDEX.GWLP_WNDPROC, item.Value); + } + + _subclassedXamlWndProcs.Clear(); + } + + private LRESULT XamlWndProc(HWND hWnd, uint uMsg, WPARAM wParam, LPARAM lParam) + { + switch (uMsg) + { + case PInvoke.WM_MOUSEACTIVATE: + if (_activationMode is DesktopFlyoutActivationMode.NeverActivate) + { + RestoreActivationState(); + return (LRESULT)(int)PInvoke.MA_NOACTIVATE; + } + break; + case PInvoke.WM_SETFOCUS: + if (_activationMode is DesktopFlyoutActivationMode.NeverActivate) + { + RestoreActivationState(); + return (LRESULT)0; + } + break; + } + + return CallPreviousXamlWndProc(hWnd, uMsg, wParam, lParam); + } + + [UnmanagedCallersOnly(CallConvs = [typeof(CallConvStdcall)])] + private static LRESULT CbtHookProc(int code, WPARAM wParam, LPARAM lParam) + { + if (code >= 0 && (code == PInvoke.HCBT_ACTIVATE || code == PInvoke.HCBT_SETFOCUS)) + { + var targetHWnd = (HWND)(nint)(nuint)wParam; + foreach (var target in GetCbtHookTargets(PInvoke.GetCurrentThreadId())) + { + if (target._activationMode is DesktopFlyoutActivationMode.NeverActivate && target.IsFlyoutWindow(targetHWnd)) + { + target.RestoreActivationState(); + return (LRESULT)1; + } + } + } + + return PInvoke.CallNextHookEx(HHOOK.Null, code, wParam, lParam); + } + + private bool IsFlyoutWindow(HWND hWnd) + { + if (hWnd.IsNull) + return false; + + if (hWnd == HWnd || hWnd == _xamlHwnd) + return true; + + if (_subclassedXamlWndProcs.ContainsKey((nint)hWnd.Value)) + return true; + + if (!HWnd.IsNull && PInvoke.IsChild(HWnd, hWnd)) + return true; + + if (!_xamlHwnd.IsNull && PInvoke.IsChild(_xamlHwnd, hWnd)) + return true; + + var rootHWnd = PInvoke.GetAncestor(hWnd, GET_ANCESTOR_FLAGS.GA_ROOT); + if (rootHWnd == HWnd || rootHWnd == _xamlHwnd) + return true; + + var rootOwnerHWnd = PInvoke.GetAncestor(hWnd, GET_ANCESTOR_FLAGS.GA_ROOTOWNER); + return rootOwnerHWnd == HWnd || rootOwnerHWnd == _xamlHwnd; + } + + private LRESULT CallPreviousXamlWndProc(HWND hWnd, uint uMsg, WPARAM wParam, LPARAM lParam) + { + if (!_subclassedXamlWndProcs.TryGetValue((nint)hWnd.Value, out var previousWndProc) || previousWndProc == 0) + return PInvoke.DefWindowProc(hWnd, uMsg, wParam, lParam); + + return PInvoke.CallWindowProc( + (delegate* unmanaged[Stdcall])(void*)previousWndProc, + hWnd, + uMsg, + wParam, + lParam); + } + + private void CoreWindow_Activated(CoreWindow sender, WindowActivatedEventArgs args) + { + if (!_disposed && args.WindowActivationState is CoreWindowActivationState.Deactivated) + WindowInactivated?.Invoke(this, EventArgs.Empty); + } + + private void UISettings_ColorValuesChanged(UISettings sender, object args) + { + var dispatcher = _coreWindow?.Dispatcher; + if (_disposed || dispatcher is null) + return; + + _ = dispatcher.TryRunAsync( + CoreDispatcherPriority.Normal, + () => SystemSettingsChanged?.Invoke(this, EventArgs.Empty)); + } + + public void Dispose() + { + if (_disposed) + return; + + _disposed = true; + RemoveCbtHook(); + UnsubclassXamlWindows(); + + _uiSettings.ColorValuesChanged -= UISettings_ColorValuesChanged; + if (_coreWindow is not null) + _coreWindow.Activated -= CoreWindow_Activated; + + if (_xamlWindow is not null) + _xamlWindow.Content = null; + + _contentRoot?.Children.Clear(); + _contentRoot = null; + _content = null; + + lock (s_claimedWindowsLock) + s_claimedWindows.Remove((nint)HWnd.Value); + + if (!HWnd.IsNull) + PInvoke.DestroyWindow(HWnd); + + _xamlWindow = null; + _coreWindow = null; + HWnd = default; + _xamlHwnd = default; + + GC.SuppressFinalize(this); + } + } +} +#endif diff --git a/src/DesktopFlyouts.Shared/XamlIslandHostWindow.X11.cs b/src/DesktopFlyouts.Shared/XamlIslandHostWindow.X11.cs index cbe4cd1..7642cd0 100644 --- a/src/DesktopFlyouts.Shared/XamlIslandHostWindow.X11.cs +++ b/src/DesktopFlyouts.Shared/XamlIslandHostWindow.X11.cs @@ -33,6 +33,8 @@ internal partial class XamlIslandHostWindow : IDisposable internal object? DesktopWindowXamlSource { get; private set; } + internal bool IsInitialized => DesktopWindowXamlSource is not null; + internal Rect WindowSize { get diff --git a/src/DesktopFlyouts.Shared/XamlIslandHostWindow.cs b/src/DesktopFlyouts.Shared/XamlIslandHostWindow.cs index 9a96af9..3578cf5 100644 --- a/src/DesktopFlyouts.Shared/XamlIslandHostWindow.cs +++ b/src/DesktopFlyouts.Shared/XamlIslandHostWindow.cs @@ -1,4 +1,4 @@ -#if !HAS_UNO +#if WASDK && !HAS_UNO // Copyright (c) 0x5BFA. All rights reserved. // Licensed under the MIT license. @@ -75,6 +75,8 @@ internal DesktopWindowXamlSource? DesktopWindowXamlSource private set; } + internal bool IsInitialized => DesktopWindowXamlSource is not null; + internal Rect WindowSize { [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/src/DesktopFlyouts.Uwp/DesktopFlyouts.Uwp.csproj b/src/DesktopFlyouts.Uwp/DesktopFlyouts.Uwp.csproj index ddf7a7e..12b02f9 100644 --- a/src/DesktopFlyouts.Uwp/DesktopFlyouts.Uwp.csproj +++ b/src/DesktopFlyouts.Uwp/DesktopFlyouts.Uwp.csproj @@ -1,4 +1,4 @@ - + @@ -41,6 +41,8 @@ + + From 5abd944de2ad56447a202ade4e23d1884088f16a Mon Sep 17 00:00:00 2001 From: 0x5BFA <62196528+0x5bfa@users.noreply.github.com> Date: Tue, 25 Aug 2026 22:12:02 +0900 Subject: [PATCH 2/7] Simplify the UWP sample packaging --- AGENTS.md | 12 +-- DesktopFlyouts.slnx | 9 --- Directory.Packages.props | 1 + .../App.xaml | 0 .../App.xaml.cs | 15 ---- .../DesktopFlyouts.Uwp.Sample.App/App2.xaml | 5 -- .../App2.xaml.cs | 67 --------------- .../Assets/LargeTile.scale-100.png | Bin .../Assets/LargeTile.scale-125.png | Bin .../Assets/LargeTile.scale-150.png | Bin .../Assets/LargeTile.scale-200.png | Bin .../Assets/LargeTile.scale-400.png | Bin .../Assets/LockScreenLogo.scale-200.png | Bin .../Assets/SmallTile.scale-100.png | Bin .../Assets/SmallTile.scale-125.png | Bin .../Assets/SmallTile.scale-150.png | Bin .../Assets/SmallTile.scale-200.png | Bin .../Assets/SmallTile.scale-400.png | Bin .../Assets/SplashScreen.scale-100.png | Bin .../Assets/SplashScreen.scale-125.png | Bin .../Assets/SplashScreen.scale-150.png | Bin .../Assets/SplashScreen.scale-200.png | Bin .../Assets/SplashScreen.scale-400.png | Bin .../Assets/Square150x150Logo.scale-100.png | Bin .../Assets/Square150x150Logo.scale-125.png | Bin .../Assets/Square150x150Logo.scale-150.png | Bin .../Assets/Square150x150Logo.scale-200.png | Bin .../Assets/Square150x150Logo.scale-400.png | Bin ...go.altform-lightunplated_targetsize-16.png | Bin ...go.altform-lightunplated_targetsize-24.png | Bin ...o.altform-lightunplated_targetsize-256.png | Bin ...go.altform-lightunplated_targetsize-32.png | Bin ...go.altform-lightunplated_targetsize-48.png | Bin ...x44Logo.altform-unplated_targetsize-16.png | Bin ...44Logo.altform-unplated_targetsize-256.png | Bin ...x44Logo.altform-unplated_targetsize-32.png | Bin ...x44Logo.altform-unplated_targetsize-48.png | Bin .../Assets/Square44x44Logo.scale-100.png | Bin .../Assets/Square44x44Logo.scale-125.png | Bin .../Assets/Square44x44Logo.scale-150.png | Bin .../Assets/Square44x44Logo.scale-200.png | Bin .../Assets/Square44x44Logo.scale-400.png | Bin .../Assets/Square44x44Logo.targetsize-16.png | Bin .../Assets/Square44x44Logo.targetsize-24.png | Bin ...x44Logo.targetsize-24_altform-unplated.png | Bin .../Assets/Square44x44Logo.targetsize-256.png | Bin .../Assets/Square44x44Logo.targetsize-32.png | Bin .../Assets/Square44x44Logo.targetsize-48.png | Bin .../Assets/StoreLogo.scale-100.png | Bin .../Assets/StoreLogo.scale-125.png | Bin .../Assets/StoreLogo.scale-150.png | Bin .../Assets/StoreLogo.scale-200.png | Bin .../Assets/StoreLogo.scale-400.png | Bin .../Assets/Wide310x150Logo.scale-100.png | Bin .../Assets/Wide310x150Logo.scale-125.png | Bin .../Assets/Wide310x150Logo.scale-150.png | Bin .../Assets/Wide310x150Logo.scale-200.png | Bin .../Assets/Wide310x150Logo.scale-400.png | Bin .../DesktopFlyouts.Uwp.Sample.App.csproj | 76 +++++++++++------- .../MainPage.xaml | 27 ------- .../MainPage.xaml.cs | 21 ----- .../Package.appxmanifest | 18 +---- .../Program.cs | 0 .../RootView.xaml | 14 ---- .../RootView.xaml.cs | 15 ---- .../RootViewModel.cs | 14 ---- .../Tray.ico | Bin ...esktopFlyouts.Uwp.Sample.Packaging.wapproj | 65 --------------- .../DesktopFlyouts.Uwp.Sample.TrayHost.csproj | 43 ---------- .../PublishProfiles/win-arm64.pubxml | 13 --- .../Properties/PublishProfiles/win-x64.pubxml | 13 --- .../Properties/PublishProfiles/win-x86.pubxml | 13 --- .../Properties/launchSettings.json | 7 -- .../app.manifest | 26 ------ 74 files changed, 56 insertions(+), 418 deletions(-) rename samples/{DesktopFlyouts.Uwp.Sample.TrayHost => DesktopFlyouts.Uwp.Sample.App}/App.xaml (100%) rename samples/{DesktopFlyouts.Uwp.Sample.TrayHost => DesktopFlyouts.Uwp.Sample.App}/App.xaml.cs (88%) delete mode 100644 samples/DesktopFlyouts.Uwp.Sample.App/App2.xaml delete mode 100644 samples/DesktopFlyouts.Uwp.Sample.App/App2.xaml.cs rename samples/{DesktopFlyouts.Uwp.Sample.Packaging => DesktopFlyouts.Uwp.Sample.App}/Assets/LargeTile.scale-100.png (100%) rename samples/{DesktopFlyouts.Uwp.Sample.Packaging => DesktopFlyouts.Uwp.Sample.App}/Assets/LargeTile.scale-125.png (100%) rename samples/{DesktopFlyouts.Uwp.Sample.Packaging => DesktopFlyouts.Uwp.Sample.App}/Assets/LargeTile.scale-150.png (100%) rename samples/{DesktopFlyouts.Uwp.Sample.Packaging => DesktopFlyouts.Uwp.Sample.App}/Assets/LargeTile.scale-200.png (100%) rename samples/{DesktopFlyouts.Uwp.Sample.Packaging => DesktopFlyouts.Uwp.Sample.App}/Assets/LargeTile.scale-400.png (100%) rename samples/{DesktopFlyouts.Uwp.Sample.Packaging => DesktopFlyouts.Uwp.Sample.App}/Assets/LockScreenLogo.scale-200.png (100%) rename samples/{DesktopFlyouts.Uwp.Sample.Packaging => DesktopFlyouts.Uwp.Sample.App}/Assets/SmallTile.scale-100.png (100%) rename samples/{DesktopFlyouts.Uwp.Sample.Packaging => DesktopFlyouts.Uwp.Sample.App}/Assets/SmallTile.scale-125.png (100%) rename samples/{DesktopFlyouts.Uwp.Sample.Packaging => DesktopFlyouts.Uwp.Sample.App}/Assets/SmallTile.scale-150.png (100%) rename samples/{DesktopFlyouts.Uwp.Sample.Packaging => DesktopFlyouts.Uwp.Sample.App}/Assets/SmallTile.scale-200.png (100%) rename samples/{DesktopFlyouts.Uwp.Sample.Packaging => DesktopFlyouts.Uwp.Sample.App}/Assets/SmallTile.scale-400.png (100%) rename samples/{DesktopFlyouts.Uwp.Sample.Packaging => DesktopFlyouts.Uwp.Sample.App}/Assets/SplashScreen.scale-100.png (100%) rename samples/{DesktopFlyouts.Uwp.Sample.Packaging => DesktopFlyouts.Uwp.Sample.App}/Assets/SplashScreen.scale-125.png (100%) rename samples/{DesktopFlyouts.Uwp.Sample.Packaging => DesktopFlyouts.Uwp.Sample.App}/Assets/SplashScreen.scale-150.png (100%) rename samples/{DesktopFlyouts.Uwp.Sample.Packaging => DesktopFlyouts.Uwp.Sample.App}/Assets/SplashScreen.scale-200.png (100%) rename samples/{DesktopFlyouts.Uwp.Sample.Packaging => DesktopFlyouts.Uwp.Sample.App}/Assets/SplashScreen.scale-400.png (100%) rename samples/{DesktopFlyouts.Uwp.Sample.Packaging => DesktopFlyouts.Uwp.Sample.App}/Assets/Square150x150Logo.scale-100.png (100%) rename samples/{DesktopFlyouts.Uwp.Sample.Packaging => DesktopFlyouts.Uwp.Sample.App}/Assets/Square150x150Logo.scale-125.png (100%) rename samples/{DesktopFlyouts.Uwp.Sample.Packaging => DesktopFlyouts.Uwp.Sample.App}/Assets/Square150x150Logo.scale-150.png (100%) rename samples/{DesktopFlyouts.Uwp.Sample.Packaging => DesktopFlyouts.Uwp.Sample.App}/Assets/Square150x150Logo.scale-200.png (100%) rename samples/{DesktopFlyouts.Uwp.Sample.Packaging => DesktopFlyouts.Uwp.Sample.App}/Assets/Square150x150Logo.scale-400.png (100%) rename samples/{DesktopFlyouts.Uwp.Sample.Packaging => DesktopFlyouts.Uwp.Sample.App}/Assets/Square44x44Logo.altform-lightunplated_targetsize-16.png (100%) rename samples/{DesktopFlyouts.Uwp.Sample.Packaging => DesktopFlyouts.Uwp.Sample.App}/Assets/Square44x44Logo.altform-lightunplated_targetsize-24.png (100%) rename samples/{DesktopFlyouts.Uwp.Sample.Packaging => DesktopFlyouts.Uwp.Sample.App}/Assets/Square44x44Logo.altform-lightunplated_targetsize-256.png (100%) rename samples/{DesktopFlyouts.Uwp.Sample.Packaging => DesktopFlyouts.Uwp.Sample.App}/Assets/Square44x44Logo.altform-lightunplated_targetsize-32.png (100%) rename samples/{DesktopFlyouts.Uwp.Sample.Packaging => DesktopFlyouts.Uwp.Sample.App}/Assets/Square44x44Logo.altform-lightunplated_targetsize-48.png (100%) rename samples/{DesktopFlyouts.Uwp.Sample.Packaging => DesktopFlyouts.Uwp.Sample.App}/Assets/Square44x44Logo.altform-unplated_targetsize-16.png (100%) rename samples/{DesktopFlyouts.Uwp.Sample.Packaging => DesktopFlyouts.Uwp.Sample.App}/Assets/Square44x44Logo.altform-unplated_targetsize-256.png (100%) rename samples/{DesktopFlyouts.Uwp.Sample.Packaging => DesktopFlyouts.Uwp.Sample.App}/Assets/Square44x44Logo.altform-unplated_targetsize-32.png (100%) rename samples/{DesktopFlyouts.Uwp.Sample.Packaging => DesktopFlyouts.Uwp.Sample.App}/Assets/Square44x44Logo.altform-unplated_targetsize-48.png (100%) rename samples/{DesktopFlyouts.Uwp.Sample.Packaging => DesktopFlyouts.Uwp.Sample.App}/Assets/Square44x44Logo.scale-100.png (100%) rename samples/{DesktopFlyouts.Uwp.Sample.Packaging => DesktopFlyouts.Uwp.Sample.App}/Assets/Square44x44Logo.scale-125.png (100%) rename samples/{DesktopFlyouts.Uwp.Sample.Packaging => DesktopFlyouts.Uwp.Sample.App}/Assets/Square44x44Logo.scale-150.png (100%) rename samples/{DesktopFlyouts.Uwp.Sample.Packaging => DesktopFlyouts.Uwp.Sample.App}/Assets/Square44x44Logo.scale-200.png (100%) rename samples/{DesktopFlyouts.Uwp.Sample.Packaging => DesktopFlyouts.Uwp.Sample.App}/Assets/Square44x44Logo.scale-400.png (100%) rename samples/{DesktopFlyouts.Uwp.Sample.Packaging => DesktopFlyouts.Uwp.Sample.App}/Assets/Square44x44Logo.targetsize-16.png (100%) rename samples/{DesktopFlyouts.Uwp.Sample.Packaging => DesktopFlyouts.Uwp.Sample.App}/Assets/Square44x44Logo.targetsize-24.png (100%) rename samples/{DesktopFlyouts.Uwp.Sample.Packaging => DesktopFlyouts.Uwp.Sample.App}/Assets/Square44x44Logo.targetsize-24_altform-unplated.png (100%) rename samples/{DesktopFlyouts.Uwp.Sample.Packaging => DesktopFlyouts.Uwp.Sample.App}/Assets/Square44x44Logo.targetsize-256.png (100%) rename samples/{DesktopFlyouts.Uwp.Sample.Packaging => DesktopFlyouts.Uwp.Sample.App}/Assets/Square44x44Logo.targetsize-32.png (100%) rename samples/{DesktopFlyouts.Uwp.Sample.Packaging => DesktopFlyouts.Uwp.Sample.App}/Assets/Square44x44Logo.targetsize-48.png (100%) rename samples/{DesktopFlyouts.Uwp.Sample.Packaging => DesktopFlyouts.Uwp.Sample.App}/Assets/StoreLogo.scale-100.png (100%) rename samples/{DesktopFlyouts.Uwp.Sample.Packaging => DesktopFlyouts.Uwp.Sample.App}/Assets/StoreLogo.scale-125.png (100%) rename samples/{DesktopFlyouts.Uwp.Sample.Packaging => DesktopFlyouts.Uwp.Sample.App}/Assets/StoreLogo.scale-150.png (100%) rename samples/{DesktopFlyouts.Uwp.Sample.Packaging => DesktopFlyouts.Uwp.Sample.App}/Assets/StoreLogo.scale-200.png (100%) rename samples/{DesktopFlyouts.Uwp.Sample.Packaging => DesktopFlyouts.Uwp.Sample.App}/Assets/StoreLogo.scale-400.png (100%) rename samples/{DesktopFlyouts.Uwp.Sample.Packaging => DesktopFlyouts.Uwp.Sample.App}/Assets/Wide310x150Logo.scale-100.png (100%) rename samples/{DesktopFlyouts.Uwp.Sample.Packaging => DesktopFlyouts.Uwp.Sample.App}/Assets/Wide310x150Logo.scale-125.png (100%) rename samples/{DesktopFlyouts.Uwp.Sample.Packaging => DesktopFlyouts.Uwp.Sample.App}/Assets/Wide310x150Logo.scale-150.png (100%) rename samples/{DesktopFlyouts.Uwp.Sample.Packaging => DesktopFlyouts.Uwp.Sample.App}/Assets/Wide310x150Logo.scale-200.png (100%) rename samples/{DesktopFlyouts.Uwp.Sample.Packaging => DesktopFlyouts.Uwp.Sample.App}/Assets/Wide310x150Logo.scale-400.png (100%) delete mode 100644 samples/DesktopFlyouts.Uwp.Sample.App/MainPage.xaml delete mode 100644 samples/DesktopFlyouts.Uwp.Sample.App/MainPage.xaml.cs rename samples/{DesktopFlyouts.Uwp.Sample.Packaging => DesktopFlyouts.Uwp.Sample.App}/Package.appxmanifest (59%) rename samples/{DesktopFlyouts.Uwp.Sample.TrayHost => DesktopFlyouts.Uwp.Sample.App}/Program.cs (100%) delete mode 100644 samples/DesktopFlyouts.Uwp.Sample.App/RootView.xaml delete mode 100644 samples/DesktopFlyouts.Uwp.Sample.App/RootView.xaml.cs delete mode 100644 samples/DesktopFlyouts.Uwp.Sample.App/RootViewModel.cs rename samples/{DesktopFlyouts.Uwp.Sample.TrayHost => DesktopFlyouts.Uwp.Sample.App}/Tray.ico (100%) delete mode 100644 samples/DesktopFlyouts.Uwp.Sample.Packaging/DesktopFlyouts.Uwp.Sample.Packaging.wapproj delete mode 100644 samples/DesktopFlyouts.Uwp.Sample.TrayHost/DesktopFlyouts.Uwp.Sample.TrayHost.csproj delete mode 100644 samples/DesktopFlyouts.Uwp.Sample.TrayHost/Properties/PublishProfiles/win-arm64.pubxml delete mode 100644 samples/DesktopFlyouts.Uwp.Sample.TrayHost/Properties/PublishProfiles/win-x64.pubxml delete mode 100644 samples/DesktopFlyouts.Uwp.Sample.TrayHost/Properties/PublishProfiles/win-x86.pubxml delete mode 100644 samples/DesktopFlyouts.Uwp.Sample.TrayHost/Properties/launchSettings.json delete mode 100644 samples/DesktopFlyouts.Uwp.Sample.TrayHost/app.manifest diff --git a/AGENTS.md b/AGENTS.md index 32983cb..b39a500 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -19,12 +19,8 @@ DesktopFlyouts is a WinUI library for showing lightweight desktop flyouts, menu └───samples ├───DesktopFlyouts.Wasdk.Sample.App │ └───DesktopFlyouts.Wasdk.Sample.App.csproj - ├───DesktopFlyouts.Uwp.Sample.App + └───DesktopFlyouts.Uwp.Sample.App │ └───DesktopFlyouts.Uwp.Sample.App.csproj - ├───DesktopFlyouts.Uwp.Sample.Packaging - │ └───DesktopFlyouts.Uwp.Sample.Packaging.wapproj - └───DesktopFlyouts.Uwp.Sample.TrayHost - └───DesktopFlyouts.Uwp.Sample.TrayHost.csproj ``` Most runtime behavior lives in `src/DesktopFlyouts.Shared` behind `#if WASDK`, `#if UWP`, and `#if HAS_UNO`. Keep all branches building when changing shared files. @@ -55,10 +51,10 @@ dotnet msbuild /restore:false src/DesktopFlyouts.Uno/DesktopFlyouts.Uno.csproj / dotnet msbuild /restore:false samples\DesktopFlyouts.Wasdk.Sample.App\DesktopFlyouts.Wasdk.Sample.App.csproj /p:Configuration=Debug /p:Platform=x64 /p:AppxBundle=Never ``` -3. Build the UWP library (Windows only): +3. Build the UWP sample and library (Windows only): ```powershell -dotnet msbuild /restore:false src\DesktopFlyouts.Uwp\DesktopFlyouts.Uwp.csproj /p:Configuration=Debug /p:Platform=x64 /p:AppxBundle=Never +dotnet msbuild /restore:false samples\DesktopFlyouts.Uwp.Sample.App\DesktopFlyouts.Uwp.Sample.App.csproj /p:Configuration=Debug /p:Platform=x64 /p:AppxBundle=Never ``` 4. Check whitespace and line endings: @@ -118,7 +114,7 @@ Validate one item at a time: 1. `git status --short --branch` to understand the current branch and dirty files. 2. Build the Uno library. 3. Build the WASDK sample (Windows only). -4. Build the UWP library when shared code changed (Windows only). +4. Build the UWP sample and library (Windows only). 5. Launch the WASDK sample as packaged, never by direct `.exe` (Windows only). 6. Confirm the app has a responsive process and real window handle (Windows only). 7. Run `git diff --check`. diff --git a/DesktopFlyouts.slnx b/DesktopFlyouts.slnx index d04044e..51d2006 100644 --- a/DesktopFlyouts.slnx +++ b/DesktopFlyouts.slnx @@ -24,15 +24,6 @@ - - - - - - - - - diff --git a/Directory.Packages.props b/Directory.Packages.props index 1bdccb9..2c12535 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -20,6 +20,7 @@ + diff --git a/samples/DesktopFlyouts.Uwp.Sample.TrayHost/App.xaml b/samples/DesktopFlyouts.Uwp.Sample.App/App.xaml similarity index 100% rename from samples/DesktopFlyouts.Uwp.Sample.TrayHost/App.xaml rename to samples/DesktopFlyouts.Uwp.Sample.App/App.xaml diff --git a/samples/DesktopFlyouts.Uwp.Sample.TrayHost/App.xaml.cs b/samples/DesktopFlyouts.Uwp.Sample.App/App.xaml.cs similarity index 88% rename from samples/DesktopFlyouts.Uwp.Sample.TrayHost/App.xaml.cs rename to samples/DesktopFlyouts.Uwp.Sample.App/App.xaml.cs index 5f07238..3952fa9 100644 --- a/samples/DesktopFlyouts.Uwp.Sample.TrayHost/App.xaml.cs +++ b/samples/DesktopFlyouts.Uwp.Sample.App/App.xaml.cs @@ -3,10 +3,8 @@ using System; using System.IO; -using System.Threading; using DesktopFlyouts.Shared; using Windows.ApplicationModel.Activation; -using Windows.System; using Windows.UI; using Windows.UI.Core; using Windows.UI.Xaml; @@ -23,9 +21,6 @@ public partial class App : Application protected override void OnLaunched(LaunchActivatedEventArgs args) { - SynchronizationContext.SetSynchronizationContext( - new DispatcherQueueSynchronizationContext(DispatcherQueue.GetForCurrentThread())); - _systemTrayIcon = new( Path.Combine(AppContext.BaseDirectory, "Tray.ico"), "DesktopFlyouts sample app (UWP)", @@ -42,8 +37,6 @@ protected override void OnLaunched(LaunchActivatedEventArgs args) // another XamlHostingKit window and is constructed on that window's XAML thread. XamlIslandApplication.CreateWindow(_ => { - SynchronizationContext.SetSynchronizationContext( - new DispatcherQueueSynchronizationContext(DispatcherQueue.GetForCurrentThread())); _desktopMenuFlyout = CreateDesktopMenuFlyout(); }); } @@ -118,13 +111,6 @@ private static DesktopMenuFlyout CreateDesktopMenuFlyout() settings.Items.Add(new MenuFlyoutItem { Text = "Language" }); settings.Items.Add(new MenuFlyoutItem { Text = "Privacy" }); - var devices = new MenuFlyoutItem - { - Text = "Devices", - Icon = new FontIcon { Glyph = "\uE975" }, - }; - devices.Click += async (_, _) => await Launcher.LaunchUriAsync(new Uri("tif-secondaryapp:")); - var exit = new MenuFlyoutItem { Text = "Exit", @@ -138,7 +124,6 @@ private static DesktopMenuFlyout CreateDesktopMenuFlyout() }; menu.Items.Add(settings); - menu.Items.Add(devices); menu.Items.Add(new MenuFlyoutSeparator()); menu.Items.Add(exit); return menu; diff --git a/samples/DesktopFlyouts.Uwp.Sample.App/App2.xaml b/samples/DesktopFlyouts.Uwp.Sample.App/App2.xaml deleted file mode 100644 index bc8ecf5..0000000 --- a/samples/DesktopFlyouts.Uwp.Sample.App/App2.xaml +++ /dev/null @@ -1,5 +0,0 @@ - diff --git a/samples/DesktopFlyouts.Uwp.Sample.App/App2.xaml.cs b/samples/DesktopFlyouts.Uwp.Sample.App/App2.xaml.cs deleted file mode 100644 index 5cc0681..0000000 --- a/samples/DesktopFlyouts.Uwp.Sample.App/App2.xaml.cs +++ /dev/null @@ -1,67 +0,0 @@ -// Copyright (c) 0x5BFA. All rights reserved. -// Licensed under the MIT license. - -using System; -using Windows.ApplicationModel; -using Windows.ApplicationModel.Activation; -using Windows.ApplicationModel.Core; -using Windows.UI.Xaml; -using Windows.UI.Xaml.Controls; -using Windows.UI.Xaml.Navigation; -using WinRT; - -namespace DesktopFlyouts -{ - public sealed partial class App2 : Application - { - public App2() - { - InitializeComponent(); - - Suspending += OnSuspending; - } - - protected override void OnActivated(IActivatedEventArgs args) - { - // Do not repeat app initialization when the Window already has content, - // just ensure that the window is active. - if (Window.Current.Content is not Frame rootFrame) - { - // Create a Frame to act as the navigation context and navigate to the first page - rootFrame = new Frame(); - rootFrame.NavigationFailed += OnNavigationFailed; - - if (args.PreviousExecutionState == ApplicationExecutionState.Terminated) - { - // TODO: Load state from previously suspended application - } - - // Place the frame in the current Window - Window.Current.Content = rootFrame; - } - - if (rootFrame.Content == null) - { - CoreApplication.GetCurrentView().TitleBar.ExtendViewIntoTitleBar = true; - - rootFrame.Navigate(typeof(MainPage), args.As().Data); - } - - // Ensure the current window is active - Window.Current.Activate(); - } - - private void OnNavigationFailed(object sender, NavigationFailedEventArgs e) - { - throw new Exception($"Failed to load page '{e.SourcePageType.FullName}'."); - } - - private void OnSuspending(object sender, SuspendingEventArgs e) - { - SuspendingDeferral deferral = e.SuspendingOperation.GetDeferral(); - - // TODO: Save application state and stop any background activity - deferral.Complete(); - } - } -} diff --git a/samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/LargeTile.scale-100.png b/samples/DesktopFlyouts.Uwp.Sample.App/Assets/LargeTile.scale-100.png similarity index 100% rename from samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/LargeTile.scale-100.png rename to samples/DesktopFlyouts.Uwp.Sample.App/Assets/LargeTile.scale-100.png diff --git a/samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/LargeTile.scale-125.png b/samples/DesktopFlyouts.Uwp.Sample.App/Assets/LargeTile.scale-125.png similarity index 100% rename from samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/LargeTile.scale-125.png rename to samples/DesktopFlyouts.Uwp.Sample.App/Assets/LargeTile.scale-125.png diff --git a/samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/LargeTile.scale-150.png b/samples/DesktopFlyouts.Uwp.Sample.App/Assets/LargeTile.scale-150.png similarity index 100% rename from samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/LargeTile.scale-150.png rename to samples/DesktopFlyouts.Uwp.Sample.App/Assets/LargeTile.scale-150.png diff --git a/samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/LargeTile.scale-200.png b/samples/DesktopFlyouts.Uwp.Sample.App/Assets/LargeTile.scale-200.png similarity index 100% rename from samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/LargeTile.scale-200.png rename to samples/DesktopFlyouts.Uwp.Sample.App/Assets/LargeTile.scale-200.png diff --git a/samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/LargeTile.scale-400.png b/samples/DesktopFlyouts.Uwp.Sample.App/Assets/LargeTile.scale-400.png similarity index 100% rename from samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/LargeTile.scale-400.png rename to samples/DesktopFlyouts.Uwp.Sample.App/Assets/LargeTile.scale-400.png diff --git a/samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/LockScreenLogo.scale-200.png b/samples/DesktopFlyouts.Uwp.Sample.App/Assets/LockScreenLogo.scale-200.png similarity index 100% rename from samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/LockScreenLogo.scale-200.png rename to samples/DesktopFlyouts.Uwp.Sample.App/Assets/LockScreenLogo.scale-200.png diff --git a/samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/SmallTile.scale-100.png b/samples/DesktopFlyouts.Uwp.Sample.App/Assets/SmallTile.scale-100.png similarity index 100% rename from samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/SmallTile.scale-100.png rename to samples/DesktopFlyouts.Uwp.Sample.App/Assets/SmallTile.scale-100.png diff --git a/samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/SmallTile.scale-125.png b/samples/DesktopFlyouts.Uwp.Sample.App/Assets/SmallTile.scale-125.png similarity index 100% rename from samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/SmallTile.scale-125.png rename to samples/DesktopFlyouts.Uwp.Sample.App/Assets/SmallTile.scale-125.png diff --git a/samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/SmallTile.scale-150.png b/samples/DesktopFlyouts.Uwp.Sample.App/Assets/SmallTile.scale-150.png similarity index 100% rename from samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/SmallTile.scale-150.png rename to samples/DesktopFlyouts.Uwp.Sample.App/Assets/SmallTile.scale-150.png diff --git a/samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/SmallTile.scale-200.png b/samples/DesktopFlyouts.Uwp.Sample.App/Assets/SmallTile.scale-200.png similarity index 100% rename from samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/SmallTile.scale-200.png rename to samples/DesktopFlyouts.Uwp.Sample.App/Assets/SmallTile.scale-200.png diff --git a/samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/SmallTile.scale-400.png b/samples/DesktopFlyouts.Uwp.Sample.App/Assets/SmallTile.scale-400.png similarity index 100% rename from samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/SmallTile.scale-400.png rename to samples/DesktopFlyouts.Uwp.Sample.App/Assets/SmallTile.scale-400.png diff --git a/samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/SplashScreen.scale-100.png b/samples/DesktopFlyouts.Uwp.Sample.App/Assets/SplashScreen.scale-100.png similarity index 100% rename from samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/SplashScreen.scale-100.png rename to samples/DesktopFlyouts.Uwp.Sample.App/Assets/SplashScreen.scale-100.png diff --git a/samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/SplashScreen.scale-125.png b/samples/DesktopFlyouts.Uwp.Sample.App/Assets/SplashScreen.scale-125.png similarity index 100% rename from samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/SplashScreen.scale-125.png rename to samples/DesktopFlyouts.Uwp.Sample.App/Assets/SplashScreen.scale-125.png diff --git a/samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/SplashScreen.scale-150.png b/samples/DesktopFlyouts.Uwp.Sample.App/Assets/SplashScreen.scale-150.png similarity index 100% rename from samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/SplashScreen.scale-150.png rename to samples/DesktopFlyouts.Uwp.Sample.App/Assets/SplashScreen.scale-150.png diff --git a/samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/SplashScreen.scale-200.png b/samples/DesktopFlyouts.Uwp.Sample.App/Assets/SplashScreen.scale-200.png similarity index 100% rename from samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/SplashScreen.scale-200.png rename to samples/DesktopFlyouts.Uwp.Sample.App/Assets/SplashScreen.scale-200.png diff --git a/samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/SplashScreen.scale-400.png b/samples/DesktopFlyouts.Uwp.Sample.App/Assets/SplashScreen.scale-400.png similarity index 100% rename from samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/SplashScreen.scale-400.png rename to samples/DesktopFlyouts.Uwp.Sample.App/Assets/SplashScreen.scale-400.png diff --git a/samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/Square150x150Logo.scale-100.png b/samples/DesktopFlyouts.Uwp.Sample.App/Assets/Square150x150Logo.scale-100.png similarity index 100% rename from samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/Square150x150Logo.scale-100.png rename to samples/DesktopFlyouts.Uwp.Sample.App/Assets/Square150x150Logo.scale-100.png diff --git a/samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/Square150x150Logo.scale-125.png b/samples/DesktopFlyouts.Uwp.Sample.App/Assets/Square150x150Logo.scale-125.png similarity index 100% rename from samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/Square150x150Logo.scale-125.png rename to samples/DesktopFlyouts.Uwp.Sample.App/Assets/Square150x150Logo.scale-125.png diff --git a/samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/Square150x150Logo.scale-150.png b/samples/DesktopFlyouts.Uwp.Sample.App/Assets/Square150x150Logo.scale-150.png similarity index 100% rename from samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/Square150x150Logo.scale-150.png rename to samples/DesktopFlyouts.Uwp.Sample.App/Assets/Square150x150Logo.scale-150.png diff --git a/samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/Square150x150Logo.scale-200.png b/samples/DesktopFlyouts.Uwp.Sample.App/Assets/Square150x150Logo.scale-200.png similarity index 100% rename from samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/Square150x150Logo.scale-200.png rename to samples/DesktopFlyouts.Uwp.Sample.App/Assets/Square150x150Logo.scale-200.png diff --git a/samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/Square150x150Logo.scale-400.png b/samples/DesktopFlyouts.Uwp.Sample.App/Assets/Square150x150Logo.scale-400.png similarity index 100% rename from samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/Square150x150Logo.scale-400.png rename to samples/DesktopFlyouts.Uwp.Sample.App/Assets/Square150x150Logo.scale-400.png diff --git a/samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/Square44x44Logo.altform-lightunplated_targetsize-16.png b/samples/DesktopFlyouts.Uwp.Sample.App/Assets/Square44x44Logo.altform-lightunplated_targetsize-16.png similarity index 100% rename from samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/Square44x44Logo.altform-lightunplated_targetsize-16.png rename to samples/DesktopFlyouts.Uwp.Sample.App/Assets/Square44x44Logo.altform-lightunplated_targetsize-16.png diff --git a/samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/Square44x44Logo.altform-lightunplated_targetsize-24.png b/samples/DesktopFlyouts.Uwp.Sample.App/Assets/Square44x44Logo.altform-lightunplated_targetsize-24.png similarity index 100% rename from samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/Square44x44Logo.altform-lightunplated_targetsize-24.png rename to samples/DesktopFlyouts.Uwp.Sample.App/Assets/Square44x44Logo.altform-lightunplated_targetsize-24.png diff --git a/samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/Square44x44Logo.altform-lightunplated_targetsize-256.png b/samples/DesktopFlyouts.Uwp.Sample.App/Assets/Square44x44Logo.altform-lightunplated_targetsize-256.png similarity index 100% rename from samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/Square44x44Logo.altform-lightunplated_targetsize-256.png rename to samples/DesktopFlyouts.Uwp.Sample.App/Assets/Square44x44Logo.altform-lightunplated_targetsize-256.png diff --git a/samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/Square44x44Logo.altform-lightunplated_targetsize-32.png b/samples/DesktopFlyouts.Uwp.Sample.App/Assets/Square44x44Logo.altform-lightunplated_targetsize-32.png similarity index 100% rename from samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/Square44x44Logo.altform-lightunplated_targetsize-32.png rename to samples/DesktopFlyouts.Uwp.Sample.App/Assets/Square44x44Logo.altform-lightunplated_targetsize-32.png diff --git a/samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/Square44x44Logo.altform-lightunplated_targetsize-48.png b/samples/DesktopFlyouts.Uwp.Sample.App/Assets/Square44x44Logo.altform-lightunplated_targetsize-48.png similarity index 100% rename from samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/Square44x44Logo.altform-lightunplated_targetsize-48.png rename to samples/DesktopFlyouts.Uwp.Sample.App/Assets/Square44x44Logo.altform-lightunplated_targetsize-48.png diff --git a/samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/Square44x44Logo.altform-unplated_targetsize-16.png b/samples/DesktopFlyouts.Uwp.Sample.App/Assets/Square44x44Logo.altform-unplated_targetsize-16.png similarity index 100% rename from samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/Square44x44Logo.altform-unplated_targetsize-16.png rename to samples/DesktopFlyouts.Uwp.Sample.App/Assets/Square44x44Logo.altform-unplated_targetsize-16.png diff --git a/samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/Square44x44Logo.altform-unplated_targetsize-256.png b/samples/DesktopFlyouts.Uwp.Sample.App/Assets/Square44x44Logo.altform-unplated_targetsize-256.png similarity index 100% rename from samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/Square44x44Logo.altform-unplated_targetsize-256.png rename to samples/DesktopFlyouts.Uwp.Sample.App/Assets/Square44x44Logo.altform-unplated_targetsize-256.png diff --git a/samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/Square44x44Logo.altform-unplated_targetsize-32.png b/samples/DesktopFlyouts.Uwp.Sample.App/Assets/Square44x44Logo.altform-unplated_targetsize-32.png similarity index 100% rename from samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/Square44x44Logo.altform-unplated_targetsize-32.png rename to samples/DesktopFlyouts.Uwp.Sample.App/Assets/Square44x44Logo.altform-unplated_targetsize-32.png diff --git a/samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/Square44x44Logo.altform-unplated_targetsize-48.png b/samples/DesktopFlyouts.Uwp.Sample.App/Assets/Square44x44Logo.altform-unplated_targetsize-48.png similarity index 100% rename from samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/Square44x44Logo.altform-unplated_targetsize-48.png rename to samples/DesktopFlyouts.Uwp.Sample.App/Assets/Square44x44Logo.altform-unplated_targetsize-48.png diff --git a/samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/Square44x44Logo.scale-100.png b/samples/DesktopFlyouts.Uwp.Sample.App/Assets/Square44x44Logo.scale-100.png similarity index 100% rename from samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/Square44x44Logo.scale-100.png rename to samples/DesktopFlyouts.Uwp.Sample.App/Assets/Square44x44Logo.scale-100.png diff --git a/samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/Square44x44Logo.scale-125.png b/samples/DesktopFlyouts.Uwp.Sample.App/Assets/Square44x44Logo.scale-125.png similarity index 100% rename from samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/Square44x44Logo.scale-125.png rename to samples/DesktopFlyouts.Uwp.Sample.App/Assets/Square44x44Logo.scale-125.png diff --git a/samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/Square44x44Logo.scale-150.png b/samples/DesktopFlyouts.Uwp.Sample.App/Assets/Square44x44Logo.scale-150.png similarity index 100% rename from samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/Square44x44Logo.scale-150.png rename to samples/DesktopFlyouts.Uwp.Sample.App/Assets/Square44x44Logo.scale-150.png diff --git a/samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/Square44x44Logo.scale-200.png b/samples/DesktopFlyouts.Uwp.Sample.App/Assets/Square44x44Logo.scale-200.png similarity index 100% rename from samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/Square44x44Logo.scale-200.png rename to samples/DesktopFlyouts.Uwp.Sample.App/Assets/Square44x44Logo.scale-200.png diff --git a/samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/Square44x44Logo.scale-400.png b/samples/DesktopFlyouts.Uwp.Sample.App/Assets/Square44x44Logo.scale-400.png similarity index 100% rename from samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/Square44x44Logo.scale-400.png rename to samples/DesktopFlyouts.Uwp.Sample.App/Assets/Square44x44Logo.scale-400.png diff --git a/samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/Square44x44Logo.targetsize-16.png b/samples/DesktopFlyouts.Uwp.Sample.App/Assets/Square44x44Logo.targetsize-16.png similarity index 100% rename from samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/Square44x44Logo.targetsize-16.png rename to samples/DesktopFlyouts.Uwp.Sample.App/Assets/Square44x44Logo.targetsize-16.png diff --git a/samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/Square44x44Logo.targetsize-24.png b/samples/DesktopFlyouts.Uwp.Sample.App/Assets/Square44x44Logo.targetsize-24.png similarity index 100% rename from samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/Square44x44Logo.targetsize-24.png rename to samples/DesktopFlyouts.Uwp.Sample.App/Assets/Square44x44Logo.targetsize-24.png diff --git a/samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/Square44x44Logo.targetsize-24_altform-unplated.png b/samples/DesktopFlyouts.Uwp.Sample.App/Assets/Square44x44Logo.targetsize-24_altform-unplated.png similarity index 100% rename from samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/Square44x44Logo.targetsize-24_altform-unplated.png rename to samples/DesktopFlyouts.Uwp.Sample.App/Assets/Square44x44Logo.targetsize-24_altform-unplated.png diff --git a/samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/Square44x44Logo.targetsize-256.png b/samples/DesktopFlyouts.Uwp.Sample.App/Assets/Square44x44Logo.targetsize-256.png similarity index 100% rename from samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/Square44x44Logo.targetsize-256.png rename to samples/DesktopFlyouts.Uwp.Sample.App/Assets/Square44x44Logo.targetsize-256.png diff --git a/samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/Square44x44Logo.targetsize-32.png b/samples/DesktopFlyouts.Uwp.Sample.App/Assets/Square44x44Logo.targetsize-32.png similarity index 100% rename from samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/Square44x44Logo.targetsize-32.png rename to samples/DesktopFlyouts.Uwp.Sample.App/Assets/Square44x44Logo.targetsize-32.png diff --git a/samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/Square44x44Logo.targetsize-48.png b/samples/DesktopFlyouts.Uwp.Sample.App/Assets/Square44x44Logo.targetsize-48.png similarity index 100% rename from samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/Square44x44Logo.targetsize-48.png rename to samples/DesktopFlyouts.Uwp.Sample.App/Assets/Square44x44Logo.targetsize-48.png diff --git a/samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/StoreLogo.scale-100.png b/samples/DesktopFlyouts.Uwp.Sample.App/Assets/StoreLogo.scale-100.png similarity index 100% rename from samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/StoreLogo.scale-100.png rename to samples/DesktopFlyouts.Uwp.Sample.App/Assets/StoreLogo.scale-100.png diff --git a/samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/StoreLogo.scale-125.png b/samples/DesktopFlyouts.Uwp.Sample.App/Assets/StoreLogo.scale-125.png similarity index 100% rename from samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/StoreLogo.scale-125.png rename to samples/DesktopFlyouts.Uwp.Sample.App/Assets/StoreLogo.scale-125.png diff --git a/samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/StoreLogo.scale-150.png b/samples/DesktopFlyouts.Uwp.Sample.App/Assets/StoreLogo.scale-150.png similarity index 100% rename from samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/StoreLogo.scale-150.png rename to samples/DesktopFlyouts.Uwp.Sample.App/Assets/StoreLogo.scale-150.png diff --git a/samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/StoreLogo.scale-200.png b/samples/DesktopFlyouts.Uwp.Sample.App/Assets/StoreLogo.scale-200.png similarity index 100% rename from samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/StoreLogo.scale-200.png rename to samples/DesktopFlyouts.Uwp.Sample.App/Assets/StoreLogo.scale-200.png diff --git a/samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/StoreLogo.scale-400.png b/samples/DesktopFlyouts.Uwp.Sample.App/Assets/StoreLogo.scale-400.png similarity index 100% rename from samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/StoreLogo.scale-400.png rename to samples/DesktopFlyouts.Uwp.Sample.App/Assets/StoreLogo.scale-400.png diff --git a/samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/Wide310x150Logo.scale-100.png b/samples/DesktopFlyouts.Uwp.Sample.App/Assets/Wide310x150Logo.scale-100.png similarity index 100% rename from samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/Wide310x150Logo.scale-100.png rename to samples/DesktopFlyouts.Uwp.Sample.App/Assets/Wide310x150Logo.scale-100.png diff --git a/samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/Wide310x150Logo.scale-125.png b/samples/DesktopFlyouts.Uwp.Sample.App/Assets/Wide310x150Logo.scale-125.png similarity index 100% rename from samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/Wide310x150Logo.scale-125.png rename to samples/DesktopFlyouts.Uwp.Sample.App/Assets/Wide310x150Logo.scale-125.png diff --git a/samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/Wide310x150Logo.scale-150.png b/samples/DesktopFlyouts.Uwp.Sample.App/Assets/Wide310x150Logo.scale-150.png similarity index 100% rename from samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/Wide310x150Logo.scale-150.png rename to samples/DesktopFlyouts.Uwp.Sample.App/Assets/Wide310x150Logo.scale-150.png diff --git a/samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/Wide310x150Logo.scale-200.png b/samples/DesktopFlyouts.Uwp.Sample.App/Assets/Wide310x150Logo.scale-200.png similarity index 100% rename from samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/Wide310x150Logo.scale-200.png rename to samples/DesktopFlyouts.Uwp.Sample.App/Assets/Wide310x150Logo.scale-200.png diff --git a/samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/Wide310x150Logo.scale-400.png b/samples/DesktopFlyouts.Uwp.Sample.App/Assets/Wide310x150Logo.scale-400.png similarity index 100% rename from samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/Wide310x150Logo.scale-400.png rename to samples/DesktopFlyouts.Uwp.Sample.App/Assets/Wide310x150Logo.scale-400.png diff --git a/samples/DesktopFlyouts.Uwp.Sample.App/DesktopFlyouts.Uwp.Sample.App.csproj b/samples/DesktopFlyouts.Uwp.Sample.App/DesktopFlyouts.Uwp.Sample.App.csproj index eae07fe..305b33d 100644 --- a/samples/DesktopFlyouts.Uwp.Sample.App/DesktopFlyouts.Uwp.Sample.App.csproj +++ b/samples/DesktopFlyouts.Uwp.Sample.App/DesktopFlyouts.Uwp.Sample.App.csproj @@ -1,30 +1,50 @@ - - - - - WinExe - net10.0-windows$(TargetWindowsVersion) - 10.0.17763.0 - win-$(Platform).pubxml - app.manifest - true - - true - false - - - - - - - - - - - - - - - + + + + + WinExe + net10.0-windows$(TargetWindowsVersion) + 10.0.17763.0 + win-$(Platform).pubxml + app.manifest + true + + $(DefineConstants);DISABLE_XAML_GENERATED_MAIN + + true + true + false + resources.pri + <_SupportEmbedFileResources>true + + + + + $(TargetPlatformSdkRootOverride)\ + $([MSBuild]::GetRegistryValueFromView('HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows Kits\Installed Roots', 'KitsRoot10', null, RegistryView.Registry32, RegistryView.Default)) + $(WindowsKitsPath)bin\$(TargetWindowsVersion)\XamlCompiler\Microsoft.Windows.UI.Xaml.Common.targets + + + + + + + + + + + PreserveNewest + PreserveNewest + + + + + + + + + + + diff --git a/samples/DesktopFlyouts.Uwp.Sample.App/MainPage.xaml b/samples/DesktopFlyouts.Uwp.Sample.App/MainPage.xaml deleted file mode 100644 index 1eb3d9e..0000000 --- a/samples/DesktopFlyouts.Uwp.Sample.App/MainPage.xaml +++ /dev/null @@ -1,27 +0,0 @@ - - - - - -