From 2ac0e3ed2672736de757a54d91750321d6a9158c Mon Sep 17 00:00:00 2001 From: "dan_clark@outlook.com" Date: Wed, 23 Mar 2022 15:44:32 +0000 Subject: [PATCH] project --- .gitattributes | 63 +++ .gitignore | 363 ++++++++++++++++++ README.md | 1 + .../DataTemplateBuilder.cs | 15 + .../DataTemplateCollection.cs | 12 + TheXamlGuy.TaskbarGroup.Core/Disposer.cs | 78 ++++ .../EventAggregatorInvoker.cs | 14 + .../IAsyncMessageHandler.cs | 12 + .../IBindViewModel.cs | 7 + .../IDataTemplateBuilder.cs | 9 + .../IDataTemplateCollection.cs | 7 + .../IDispatcherTimer.cs | 9 + .../IDispatcherTimerFactory.cs | 7 + TheXamlGuy.TaskbarGroup.Core/IDisposer.cs | 13 + TheXamlGuy.TaskbarGroup.Core/IDropTarget.cs | 7 + .../IEventAggregatorInvoker.cs | 9 + .../IHostingExtensions.cs | 21 + .../IInitializable.cs | 7 + TheXamlGuy.TaskbarGroup.Core/IMediator.cs | 17 + .../IMessageHandler.cs | 12 + TheXamlGuy.TaskbarGroup.Core/IMessenger.cs | 14 + .../IObservableExtensions.cs | 31 ++ .../IPointerMonitor.cs | 7 + .../IServiceCollectionExtensions.cs | 22 ++ .../IServiceFactory.cs | 10 + TheXamlGuy.TaskbarGroup.Core/ITaskbar.cs | 8 + .../ITaskbarButton.cs | 9 + .../ITaskbarButtonMonitor.cs | 7 + .../ITaskbarMonitor.cs | 7 + .../ITemplateSelector.cs | 7 + .../IWndProcMonitor.cs | 7 + .../IsExternalInit.cs | 13 + .../MaybeNullWhenAttribute.cs | 24 ++ TheXamlGuy.TaskbarGroup.Core/Mediator.cs | 70 ++++ TheXamlGuy.TaskbarGroup.Core/Messenger.cs | 54 +++ .../NativeMethods.json | 4 + .../NativeMethods.txt | 17 + .../ObservableCollectionViewModel.cs | 132 +++++++ .../ObservableViewModel.cs | 68 ++++ TheXamlGuy.TaskbarGroup.Core/PointerButton.cs | 9 + TheXamlGuy.TaskbarGroup.Core/PointerDrag.cs | 4 + .../PointerLocation.cs | 4 + .../PointerMonitor.cs | 155 ++++++++ TheXamlGuy.TaskbarGroup.Core/PointerMoved.cs | 4 + .../PointerPressed.cs | 4 + .../PointerReleased.cs | 6 + .../RECTExtensions.cs | 14 + TheXamlGuy.TaskbarGroup.Core/Screen.cs | 109 ++++++ .../ServiceFactory.cs | 30 ++ .../SystemInformationHelper.cs | 33 ++ TheXamlGuy.TaskbarGroup.Core/Taskbar.cs | 80 ++++ TheXamlGuy.TaskbarGroup.Core/TaskbarButton.cs | 103 +++++ .../TaskbarButtonBounds.cs | 23 ++ .../TaskbarButtonCreated.cs | 4 + .../TaskbarButtonDragEnter.cs | 4 + .../TaskbarButtonDragOver.cs | 4 + .../TaskbarButtonEntered.cs | 4 + .../TaskbarButtonInvoked.cs | 4 + .../TaskbarButtonMonitor.cs | 153 ++++++++ .../TaskbarButtonRemoved.cs | 4 + .../TaskbarButtonUpdated.cs | 4 + .../TaskbarChanged.cs | 4 + .../TaskbarMonitor.cs | 30 ++ .../TaskbarPlacement.cs | 10 + TheXamlGuy.TaskbarGroup.Core/TaskbarState.cs | 11 + .../TheXamlGuy.TaskbarGroup.Core.csproj | 42 ++ TheXamlGuy.TaskbarGroup.Core/WindowHelper.cs | 37 ++ TheXamlGuy.TaskbarGroup.Core/WndProc.cs | 4 + .../WndProcMessages.cs | 14 + .../WndProcMonitor.cs | 68 ++++ .../DataTemplateFactory.cs | 44 +++ .../DropTarget.cs | 61 +++ .../IBindViewModelExtensions.cs | 37 ++ .../IDataTemplateFactory.cs | 10 + .../IServiceCollectionExtensions.cs | 18 + .../IWindowPrivate.cs | 24 ++ .../Properties/AssemblyInfo.cs | 29 ++ ...lGuy.TaskbarGroup.Flyout.Foundation.rd.xml | 33 ++ .../TemplateSelector.cs | 31 ++ ...lGuy.TaskbarGroup.Flyout.Foundation.csproj | 160 ++++++++ TheXamlGuy.TaskbarGroup.Flyout/App.xaml | 10 + TheXamlGuy.TaskbarGroup.Flyout/App.xaml.cs | 20 + .../Assets/LockScreenLogo.scale-200.png | Bin 0 -> 1430 bytes .../Assets/SplashScreen.scale-200.png | Bin 0 -> 7700 bytes .../Assets/Square150x150Logo.scale-200.png | Bin 0 -> 2937 bytes .../Assets/Square44x44Logo.scale-200.png | Bin 0 -> 1647 bytes ...x44Logo.targetsize-24_altform-unplated.png | Bin 0 -> 1255 bytes .../Assets/StoreLogo.png | Bin 0 -> 1451 bytes .../Assets/Wide310x150Logo.scale-200.png | Bin 0 -> 3204 bytes .../Controls/TaskbarButtonFlyout.cs | 77 ++++ .../Controls/TaskbarButtonFlyout.xaml | 115 ++++++ .../Controls/TaskbarButtonFlyoutPlacement.cs | 10 + .../TaskbarButtonFlyoutTemplateSettings.cs | 50 +++ .../Package.appxmanifest | 49 +++ .../Properties/AssemblyInfo.cs | 29 ++ .../Properties/Default.rd.xml | 31 ++ .../TheXamlGuy.TaskbarGroup.Flyout.csproj | 159 ++++++++ .../Themes/Generic.xaml | 5 + .../Views/TaskbarButtonGroupItemViewModel.cs | 15 + .../Views/TaskbarButtonGroupView.xaml | 34 ++ .../Views/TaskbarButtonGroupView.xaml.cs | 16 + .../Views/TaskbarButtonGroupViewModel.cs | 23 ++ .../Views/TaskbarButtonView.xaml | 13 + .../Views/TaskbarButtonView.xaml.cs | 14 + .../Views/TaskbarButtonViewModel.cs | 22 ++ .../DataTemplateFactory.cs | 41 ++ .../DispatcherTimer.cs | 37 ++ .../DispatcherTimerFactory.cs | 13 + .../FileDropTarget.cs | 82 ++++ .../IDataTemplateFactory.cs | 9 + .../IServiceCollectionExtensions.cs | 18 + .../TemplateGenerator.cs | 20 + .../TemplateGeneratorControl.cs | 22 ++ .../TemplateSelector.cs | 32 ++ .../TheXamlGuy.TaskbarGroup.Foundation.csproj | 19 + .../VisualExtensions.cs | 26 ++ .../WindowExtensions.cs | 29 ++ TheXamlGuy.TaskbarGroup.sln | 147 +++++++ TheXamlGuy.TaskbarGroup/App.xaml | 10 + TheXamlGuy.TaskbarGroup/App.xaml.cs | 47 +++ TheXamlGuy.TaskbarGroup/AssemblyInfo.cs | 10 + .../LifeCycles/ApplicationHost.cs | 58 +++ .../TaskbarButtonFlyoutActivation.cs | 6 + .../TaskbarButtonFlyoutActivationHandler.cs | 70 ++++ TheXamlGuy.TaskbarGroup/Properties/Program.cs | 17 + .../TheXamlGuy.TaskbarGroup.csproj | 26 ++ .../Windows/TaskbarButtonFlyoutWindow.cs | 80 ++++ .../Windows/TransparentXamlWindow.cs | 25 ++ TheXamlGuy.TaskbarGroup/Windows/XamlWindow.cs | 41 ++ 129 files changed, 4197 insertions(+) create mode 100644 .gitattributes create mode 100644 .gitignore create mode 100644 README.md create mode 100644 TheXamlGuy.TaskbarGroup.Core/DataTemplateBuilder.cs create mode 100644 TheXamlGuy.TaskbarGroup.Core/DataTemplateCollection.cs create mode 100644 TheXamlGuy.TaskbarGroup.Core/Disposer.cs create mode 100644 TheXamlGuy.TaskbarGroup.Core/EventAggregatorInvoker.cs create mode 100644 TheXamlGuy.TaskbarGroup.Core/IAsyncMessageHandler.cs create mode 100644 TheXamlGuy.TaskbarGroup.Core/IBindViewModel.cs create mode 100644 TheXamlGuy.TaskbarGroup.Core/IDataTemplateBuilder.cs create mode 100644 TheXamlGuy.TaskbarGroup.Core/IDataTemplateCollection.cs create mode 100644 TheXamlGuy.TaskbarGroup.Core/IDispatcherTimer.cs create mode 100644 TheXamlGuy.TaskbarGroup.Core/IDispatcherTimerFactory.cs create mode 100644 TheXamlGuy.TaskbarGroup.Core/IDisposer.cs create mode 100644 TheXamlGuy.TaskbarGroup.Core/IDropTarget.cs create mode 100644 TheXamlGuy.TaskbarGroup.Core/IEventAggregatorInvoker.cs create mode 100644 TheXamlGuy.TaskbarGroup.Core/IHostingExtensions.cs create mode 100644 TheXamlGuy.TaskbarGroup.Core/IInitializable.cs create mode 100644 TheXamlGuy.TaskbarGroup.Core/IMediator.cs create mode 100644 TheXamlGuy.TaskbarGroup.Core/IMessageHandler.cs create mode 100644 TheXamlGuy.TaskbarGroup.Core/IMessenger.cs create mode 100644 TheXamlGuy.TaskbarGroup.Core/IObservableExtensions.cs create mode 100644 TheXamlGuy.TaskbarGroup.Core/IPointerMonitor.cs create mode 100644 TheXamlGuy.TaskbarGroup.Core/IServiceCollectionExtensions.cs create mode 100644 TheXamlGuy.TaskbarGroup.Core/IServiceFactory.cs create mode 100644 TheXamlGuy.TaskbarGroup.Core/ITaskbar.cs create mode 100644 TheXamlGuy.TaskbarGroup.Core/ITaskbarButton.cs create mode 100644 TheXamlGuy.TaskbarGroup.Core/ITaskbarButtonMonitor.cs create mode 100644 TheXamlGuy.TaskbarGroup.Core/ITaskbarMonitor.cs create mode 100644 TheXamlGuy.TaskbarGroup.Core/ITemplateSelector.cs create mode 100644 TheXamlGuy.TaskbarGroup.Core/IWndProcMonitor.cs create mode 100644 TheXamlGuy.TaskbarGroup.Core/IsExternalInit.cs create mode 100644 TheXamlGuy.TaskbarGroup.Core/MaybeNullWhenAttribute.cs create mode 100644 TheXamlGuy.TaskbarGroup.Core/Mediator.cs create mode 100644 TheXamlGuy.TaskbarGroup.Core/Messenger.cs create mode 100644 TheXamlGuy.TaskbarGroup.Core/NativeMethods.json create mode 100644 TheXamlGuy.TaskbarGroup.Core/NativeMethods.txt create mode 100644 TheXamlGuy.TaskbarGroup.Core/ObservableCollectionViewModel.cs create mode 100644 TheXamlGuy.TaskbarGroup.Core/ObservableViewModel.cs create mode 100644 TheXamlGuy.TaskbarGroup.Core/PointerButton.cs create mode 100644 TheXamlGuy.TaskbarGroup.Core/PointerDrag.cs create mode 100644 TheXamlGuy.TaskbarGroup.Core/PointerLocation.cs create mode 100644 TheXamlGuy.TaskbarGroup.Core/PointerMonitor.cs create mode 100644 TheXamlGuy.TaskbarGroup.Core/PointerMoved.cs create mode 100644 TheXamlGuy.TaskbarGroup.Core/PointerPressed.cs create mode 100644 TheXamlGuy.TaskbarGroup.Core/PointerReleased.cs create mode 100644 TheXamlGuy.TaskbarGroup.Core/RECTExtensions.cs create mode 100644 TheXamlGuy.TaskbarGroup.Core/Screen.cs create mode 100644 TheXamlGuy.TaskbarGroup.Core/ServiceFactory.cs create mode 100644 TheXamlGuy.TaskbarGroup.Core/SystemInformationHelper.cs create mode 100644 TheXamlGuy.TaskbarGroup.Core/Taskbar.cs create mode 100644 TheXamlGuy.TaskbarGroup.Core/TaskbarButton.cs create mode 100644 TheXamlGuy.TaskbarGroup.Core/TaskbarButtonBounds.cs create mode 100644 TheXamlGuy.TaskbarGroup.Core/TaskbarButtonCreated.cs create mode 100644 TheXamlGuy.TaskbarGroup.Core/TaskbarButtonDragEnter.cs create mode 100644 TheXamlGuy.TaskbarGroup.Core/TaskbarButtonDragOver.cs create mode 100644 TheXamlGuy.TaskbarGroup.Core/TaskbarButtonEntered.cs create mode 100644 TheXamlGuy.TaskbarGroup.Core/TaskbarButtonInvoked.cs create mode 100644 TheXamlGuy.TaskbarGroup.Core/TaskbarButtonMonitor.cs create mode 100644 TheXamlGuy.TaskbarGroup.Core/TaskbarButtonRemoved.cs create mode 100644 TheXamlGuy.TaskbarGroup.Core/TaskbarButtonUpdated.cs create mode 100644 TheXamlGuy.TaskbarGroup.Core/TaskbarChanged.cs create mode 100644 TheXamlGuy.TaskbarGroup.Core/TaskbarMonitor.cs create mode 100644 TheXamlGuy.TaskbarGroup.Core/TaskbarPlacement.cs create mode 100644 TheXamlGuy.TaskbarGroup.Core/TaskbarState.cs create mode 100644 TheXamlGuy.TaskbarGroup.Core/TheXamlGuy.TaskbarGroup.Core.csproj create mode 100644 TheXamlGuy.TaskbarGroup.Core/WindowHelper.cs create mode 100644 TheXamlGuy.TaskbarGroup.Core/WndProc.cs create mode 100644 TheXamlGuy.TaskbarGroup.Core/WndProcMessages.cs create mode 100644 TheXamlGuy.TaskbarGroup.Core/WndProcMonitor.cs create mode 100644 TheXamlGuy.TaskbarGroup.Flyout.Foundation/DataTemplateFactory.cs create mode 100644 TheXamlGuy.TaskbarGroup.Flyout.Foundation/DropTarget.cs create mode 100644 TheXamlGuy.TaskbarGroup.Flyout.Foundation/IBindViewModelExtensions.cs create mode 100644 TheXamlGuy.TaskbarGroup.Flyout.Foundation/IDataTemplateFactory.cs create mode 100644 TheXamlGuy.TaskbarGroup.Flyout.Foundation/IServiceCollectionExtensions.cs create mode 100644 TheXamlGuy.TaskbarGroup.Flyout.Foundation/IWindowPrivate.cs create mode 100644 TheXamlGuy.TaskbarGroup.Flyout.Foundation/Properties/AssemblyInfo.cs create mode 100644 TheXamlGuy.TaskbarGroup.Flyout.Foundation/Properties/TheXamlGuy.TaskbarGroup.Flyout.Foundation.rd.xml create mode 100644 TheXamlGuy.TaskbarGroup.Flyout.Foundation/TemplateSelector.cs create mode 100644 TheXamlGuy.TaskbarGroup.Flyout.Foundation/TheXamlGuy.TaskbarGroup.Flyout.Foundation.csproj create mode 100644 TheXamlGuy.TaskbarGroup.Flyout/App.xaml create mode 100644 TheXamlGuy.TaskbarGroup.Flyout/App.xaml.cs create mode 100644 TheXamlGuy.TaskbarGroup.Flyout/Assets/LockScreenLogo.scale-200.png create mode 100644 TheXamlGuy.TaskbarGroup.Flyout/Assets/SplashScreen.scale-200.png create mode 100644 TheXamlGuy.TaskbarGroup.Flyout/Assets/Square150x150Logo.scale-200.png create mode 100644 TheXamlGuy.TaskbarGroup.Flyout/Assets/Square44x44Logo.scale-200.png create mode 100644 TheXamlGuy.TaskbarGroup.Flyout/Assets/Square44x44Logo.targetsize-24_altform-unplated.png create mode 100644 TheXamlGuy.TaskbarGroup.Flyout/Assets/StoreLogo.png create mode 100644 TheXamlGuy.TaskbarGroup.Flyout/Assets/Wide310x150Logo.scale-200.png create mode 100644 TheXamlGuy.TaskbarGroup.Flyout/Controls/TaskbarButtonFlyout.cs create mode 100644 TheXamlGuy.TaskbarGroup.Flyout/Controls/TaskbarButtonFlyout.xaml create mode 100644 TheXamlGuy.TaskbarGroup.Flyout/Controls/TaskbarButtonFlyoutPlacement.cs create mode 100644 TheXamlGuy.TaskbarGroup.Flyout/Controls/TaskbarButtonFlyoutTemplateSettings.cs create mode 100644 TheXamlGuy.TaskbarGroup.Flyout/Package.appxmanifest create mode 100644 TheXamlGuy.TaskbarGroup.Flyout/Properties/AssemblyInfo.cs create mode 100644 TheXamlGuy.TaskbarGroup.Flyout/Properties/Default.rd.xml create mode 100644 TheXamlGuy.TaskbarGroup.Flyout/TheXamlGuy.TaskbarGroup.Flyout.csproj create mode 100644 TheXamlGuy.TaskbarGroup.Flyout/Themes/Generic.xaml create mode 100644 TheXamlGuy.TaskbarGroup.Flyout/Views/TaskbarButtonGroupItemViewModel.cs create mode 100644 TheXamlGuy.TaskbarGroup.Flyout/Views/TaskbarButtonGroupView.xaml create mode 100644 TheXamlGuy.TaskbarGroup.Flyout/Views/TaskbarButtonGroupView.xaml.cs create mode 100644 TheXamlGuy.TaskbarGroup.Flyout/Views/TaskbarButtonGroupViewModel.cs create mode 100644 TheXamlGuy.TaskbarGroup.Flyout/Views/TaskbarButtonView.xaml create mode 100644 TheXamlGuy.TaskbarGroup.Flyout/Views/TaskbarButtonView.xaml.cs create mode 100644 TheXamlGuy.TaskbarGroup.Flyout/Views/TaskbarButtonViewModel.cs create mode 100644 TheXamlGuy.TaskbarGroup.Foundation/DataTemplateFactory.cs create mode 100644 TheXamlGuy.TaskbarGroup.Foundation/DispatcherTimer.cs create mode 100644 TheXamlGuy.TaskbarGroup.Foundation/DispatcherTimerFactory.cs create mode 100644 TheXamlGuy.TaskbarGroup.Foundation/FileDropTarget.cs create mode 100644 TheXamlGuy.TaskbarGroup.Foundation/IDataTemplateFactory.cs create mode 100644 TheXamlGuy.TaskbarGroup.Foundation/IServiceCollectionExtensions.cs create mode 100644 TheXamlGuy.TaskbarGroup.Foundation/TemplateGenerator.cs create mode 100644 TheXamlGuy.TaskbarGroup.Foundation/TemplateGeneratorControl.cs create mode 100644 TheXamlGuy.TaskbarGroup.Foundation/TemplateSelector.cs create mode 100644 TheXamlGuy.TaskbarGroup.Foundation/TheXamlGuy.TaskbarGroup.Foundation.csproj create mode 100644 TheXamlGuy.TaskbarGroup.Foundation/VisualExtensions.cs create mode 100644 TheXamlGuy.TaskbarGroup.Foundation/WindowExtensions.cs create mode 100644 TheXamlGuy.TaskbarGroup.sln create mode 100644 TheXamlGuy.TaskbarGroup/App.xaml create mode 100644 TheXamlGuy.TaskbarGroup/App.xaml.cs create mode 100644 TheXamlGuy.TaskbarGroup/AssemblyInfo.cs create mode 100644 TheXamlGuy.TaskbarGroup/LifeCycles/ApplicationHost.cs create mode 100644 TheXamlGuy.TaskbarGroup/LifeCycles/TaskbarButtonFlyoutActivation.cs create mode 100644 TheXamlGuy.TaskbarGroup/LifeCycles/TaskbarButtonFlyoutActivationHandler.cs create mode 100644 TheXamlGuy.TaskbarGroup/Properties/Program.cs create mode 100644 TheXamlGuy.TaskbarGroup/TheXamlGuy.TaskbarGroup.csproj create mode 100644 TheXamlGuy.TaskbarGroup/Windows/TaskbarButtonFlyoutWindow.cs create mode 100644 TheXamlGuy.TaskbarGroup/Windows/TransparentXamlWindow.cs create mode 100644 TheXamlGuy.TaskbarGroup/Windows/XamlWindow.cs diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..1ff0c42 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,63 @@ +############################################################################### +# Set default behavior to automatically normalize line endings. +############################################################################### +* text=auto + +############################################################################### +# Set default behavior for command prompt diff. +# +# This is need for earlier builds of msysgit that does not have it on by +# default for csharp files. +# Note: This is only used by command line +############################################################################### +#*.cs diff=csharp + +############################################################################### +# Set the merge driver for project and solution files +# +# Merging from the command prompt will add diff markers to the files if there +# are conflicts (Merging from VS is not affected by the settings below, in VS +# the diff markers are never inserted). Diff markers may cause the following +# file extensions to fail to load in VS. An alternative would be to treat +# these files as binary and thus will always conflict and require user +# intervention with every merge. To do so, just uncomment the entries below +############################################################################### +#*.sln merge=binary +#*.csproj merge=binary +#*.vbproj merge=binary +#*.vcxproj merge=binary +#*.vcproj merge=binary +#*.dbproj merge=binary +#*.fsproj merge=binary +#*.lsproj merge=binary +#*.wixproj merge=binary +#*.modelproj merge=binary +#*.sqlproj merge=binary +#*.wwaproj merge=binary + +############################################################################### +# behavior for image files +# +# image files are treated as binary by default. +############################################################################### +#*.jpg binary +#*.png binary +#*.gif binary + +############################################################################### +# diff behavior for common document formats +# +# Convert binary document formats to text before diffing them. This feature +# is only available from the command line. Turn it on by uncommenting the +# entries below. +############################################################################### +#*.doc diff=astextplain +#*.DOC diff=astextplain +#*.docx diff=astextplain +#*.DOCX diff=astextplain +#*.dot diff=astextplain +#*.DOT diff=astextplain +#*.pdf diff=astextplain +#*.PDF diff=astextplain +#*.rtf diff=astextplain +#*.RTF diff=astextplain diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9491a2f --- /dev/null +++ b/.gitignore @@ -0,0 +1,363 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Ww][Ii][Nn]32/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Oo]ut/ +[Ll]og/ +[Ll]ogs/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# ASP.NET Scaffolding +ScaffoldingReadMe.txt + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Coverlet is a free, cross platform Code Coverage Tool +coverage*.json +coverage*.xml +coverage*.info + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ + +# Fody - auto-generated XML schema +FodyWeavers.xsd \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..5184a65 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +TheXamlGuy.TaskbarGroup \ No newline at end of file diff --git a/TheXamlGuy.TaskbarGroup.Core/DataTemplateBuilder.cs b/TheXamlGuy.TaskbarGroup.Core/DataTemplateBuilder.cs new file mode 100644 index 0000000..ec183fc --- /dev/null +++ b/TheXamlGuy.TaskbarGroup.Core/DataTemplateBuilder.cs @@ -0,0 +1,15 @@ +namespace TheXamlGuy.TaskbarGroup.Core +{ + public class DataTemplateBuilder : IDataTemplateBuilder + { + private readonly Dictionary items = new(); + + public IDataTemplateCollection DataTemplates => new DataTemplateCollection(items); + + public IDataTemplateBuilder Map() + { + items.Add(typeof(TViewModel), typeof(TView)); + return this; + } + } +} diff --git a/TheXamlGuy.TaskbarGroup.Core/DataTemplateCollection.cs b/TheXamlGuy.TaskbarGroup.Core/DataTemplateCollection.cs new file mode 100644 index 0000000..5f52daa --- /dev/null +++ b/TheXamlGuy.TaskbarGroup.Core/DataTemplateCollection.cs @@ -0,0 +1,12 @@ +using System.Collections.ObjectModel; + +namespace TheXamlGuy.TaskbarGroup.Core +{ + public class DataTemplateCollection : ReadOnlyDictionary, IDataTemplateCollection + { + public DataTemplateCollection(IDictionary dictionary) : base(dictionary) + { + + } + } +} diff --git a/TheXamlGuy.TaskbarGroup.Core/Disposer.cs b/TheXamlGuy.TaskbarGroup.Core/Disposer.cs new file mode 100644 index 0000000..c18992d --- /dev/null +++ b/TheXamlGuy.TaskbarGroup.Core/Disposer.cs @@ -0,0 +1,78 @@ +using System.Reactive.Linq; +using System.Reactive.Disposables; +using System.Runtime.CompilerServices; +using System.Collections; + +namespace TheXamlGuy.TaskbarGroup.Core +{ + + public class Disposer : IDisposer + { + private readonly ConditionalWeakTable subjects = new(); + + public void Add(object subject, params object[] objects) + { + var disposables = subjects.GetOrCreateValue(subject); + + foreach (var disposable in objects.OfType()) + { + disposables.Add(disposable); + } + + foreach (var notDisposable in objects.Where(x => x is not IDisposable)) + { + disposables.Add(Disposable.Create(() => MakeNotDisposable(notDisposable))); + } + } + + public void Dispose(object subject) + { + if (subjects.TryGetValue(subject, out CompositeDisposable disposables)) + { + disposables.Dispose(); + } + } + + public void Remove(object subject, IDisposable disposer) + { + var disposables = subjects.GetOrCreateValue(subject); + if (disposer != null) + { + disposables.Remove(disposer); + } + } + + public TDisposable Replace(object subject, IDisposable disposer, TDisposable replacement) where TDisposable : IDisposable + { + var disposables = subjects.GetOrCreateValue(subject); + if (disposer is not null) + { + disposables.Remove(disposer); + } + + disposables.Add(replacement); + return replacement; + } + + private void MakeNotDisposable(object target) + { + if (target is IEnumerable enumerableTarget) + { + foreach (var item in enumerableTarget) + { + MakeNotDisposable(item); + } + } + + if (target is IDisposable disposableTarget) + { + disposableTarget.Dispose(); + } + + if (target is not IDisposable) + { + Dispose(target); + } + } + } +} diff --git a/TheXamlGuy.TaskbarGroup.Core/EventAggregatorInvoker.cs b/TheXamlGuy.TaskbarGroup.Core/EventAggregatorInvoker.cs new file mode 100644 index 0000000..40cc3f4 --- /dev/null +++ b/TheXamlGuy.TaskbarGroup.Core/EventAggregatorInvoker.cs @@ -0,0 +1,14 @@ +using System.Reflection; + +namespace TheXamlGuy.TaskbarGroup.Core +{ + public class EventAggregatorInvoker : IEventAggregatorInvoker + { + public void Invoke(object target, TMessage message, MethodInfo methodInfo) + { + if (message is null) throw new ArgumentNullException(nameof(message)); + + methodInfo.Invoke(target, new object[] { message }); + } + } +} \ No newline at end of file diff --git a/TheXamlGuy.TaskbarGroup.Core/IAsyncMessageHandler.cs b/TheXamlGuy.TaskbarGroup.Core/IAsyncMessageHandler.cs new file mode 100644 index 0000000..f291eb7 --- /dev/null +++ b/TheXamlGuy.TaskbarGroup.Core/IAsyncMessageHandler.cs @@ -0,0 +1,12 @@ +namespace TheXamlGuy.TaskbarGroup.Core +{ + public interface IAsyncMessageHandler + { + Task Handle(TMessage message, CancellationToken canellationToken = default); + } + + public interface IAsyncMessageHandler + { + Task Handle(TMessage message, CancellationToken cancellationToken = default); + } +} diff --git a/TheXamlGuy.TaskbarGroup.Core/IBindViewModel.cs b/TheXamlGuy.TaskbarGroup.Core/IBindViewModel.cs new file mode 100644 index 0000000..40c3eb8 --- /dev/null +++ b/TheXamlGuy.TaskbarGroup.Core/IBindViewModel.cs @@ -0,0 +1,7 @@ +namespace TheXamlGuy.TaskbarGroup.Core +{ + public interface IBindViewModel where TViewModel : class + { + public TViewModel ViewModel { get; set; } + } +} diff --git a/TheXamlGuy.TaskbarGroup.Core/IDataTemplateBuilder.cs b/TheXamlGuy.TaskbarGroup.Core/IDataTemplateBuilder.cs new file mode 100644 index 0000000..7bb5fdb --- /dev/null +++ b/TheXamlGuy.TaskbarGroup.Core/IDataTemplateBuilder.cs @@ -0,0 +1,9 @@ +namespace TheXamlGuy.TaskbarGroup.Core +{ + public interface IDataTemplateBuilder + { + IDataTemplateCollection DataTemplates { get; } + + IDataTemplateBuilder Map(); + } +} diff --git a/TheXamlGuy.TaskbarGroup.Core/IDataTemplateCollection.cs b/TheXamlGuy.TaskbarGroup.Core/IDataTemplateCollection.cs new file mode 100644 index 0000000..e8a6746 --- /dev/null +++ b/TheXamlGuy.TaskbarGroup.Core/IDataTemplateCollection.cs @@ -0,0 +1,7 @@ +namespace TheXamlGuy.TaskbarGroup.Core +{ + public interface IDataTemplateCollection : IReadOnlyDictionary + { + + } +} diff --git a/TheXamlGuy.TaskbarGroup.Core/IDispatcherTimer.cs b/TheXamlGuy.TaskbarGroup.Core/IDispatcherTimer.cs new file mode 100644 index 0000000..5ed64a0 --- /dev/null +++ b/TheXamlGuy.TaskbarGroup.Core/IDispatcherTimer.cs @@ -0,0 +1,9 @@ +namespace TheXamlGuy.TaskbarGroup.Core +{ + public interface IDispatcherTimer + { + void Start(); + + void Stop(); + } +} diff --git a/TheXamlGuy.TaskbarGroup.Core/IDispatcherTimerFactory.cs b/TheXamlGuy.TaskbarGroup.Core/IDispatcherTimerFactory.cs new file mode 100644 index 0000000..183c2cd --- /dev/null +++ b/TheXamlGuy.TaskbarGroup.Core/IDispatcherTimerFactory.cs @@ -0,0 +1,7 @@ +namespace TheXamlGuy.TaskbarGroup.Core +{ + public interface IDispatcherTimerFactory + { + IDispatcherTimer Create(Action actionDelegate, TimeSpan interval); + } +} diff --git a/TheXamlGuy.TaskbarGroup.Core/IDisposer.cs b/TheXamlGuy.TaskbarGroup.Core/IDisposer.cs new file mode 100644 index 0000000..05c172d --- /dev/null +++ b/TheXamlGuy.TaskbarGroup.Core/IDisposer.cs @@ -0,0 +1,13 @@ +namespace TheXamlGuy.TaskbarGroup.Core +{ + public interface IDisposer + { + void Add(object subject, params object[] objects); + + void Dispose(object subject); + + void Remove(object subject, IDisposable disposer); + + TDisposable Replace(object subject, IDisposable disposer, TDisposable replacement) where TDisposable : IDisposable; + } +} \ No newline at end of file diff --git a/TheXamlGuy.TaskbarGroup.Core/IDropTarget.cs b/TheXamlGuy.TaskbarGroup.Core/IDropTarget.cs new file mode 100644 index 0000000..109bd3c --- /dev/null +++ b/TheXamlGuy.TaskbarGroup.Core/IDropTarget.cs @@ -0,0 +1,7 @@ +namespace TheXamlGuy.TaskbarGroup.Core +{ + public interface IDropTarget + { + void Register(TTarget target); + } +} diff --git a/TheXamlGuy.TaskbarGroup.Core/IEventAggregatorInvoker.cs b/TheXamlGuy.TaskbarGroup.Core/IEventAggregatorInvoker.cs new file mode 100644 index 0000000..c9349cd --- /dev/null +++ b/TheXamlGuy.TaskbarGroup.Core/IEventAggregatorInvoker.cs @@ -0,0 +1,9 @@ +using System.Reflection; + +namespace TheXamlGuy.TaskbarGroup.Core +{ + public interface IEventAggregatorInvoker + { + void Invoke(object target, TMessage message, MethodInfo methodInfo); + } +} \ No newline at end of file diff --git a/TheXamlGuy.TaskbarGroup.Core/IHostingExtensions.cs b/TheXamlGuy.TaskbarGroup.Core/IHostingExtensions.cs new file mode 100644 index 0000000..126024a --- /dev/null +++ b/TheXamlGuy.TaskbarGroup.Core/IHostingExtensions.cs @@ -0,0 +1,21 @@ +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; + +namespace TheXamlGuy.TaskbarGroup.Core +{ + public static class IHostingExtensions + { + public static IHostBuilder ConfigureDataTemplates(this IHostBuilder hostBuilder, Action builderDelegate) + { + hostBuilder.ConfigureServices((hostBuilderContext, serviceCollection) => + { + var builder = new DataTemplateBuilder(); + builderDelegate?.Invoke(builder); + + serviceCollection.AddSingleton(builder.DataTemplates); + }); + + return hostBuilder; + } + } +} diff --git a/TheXamlGuy.TaskbarGroup.Core/IInitializable.cs b/TheXamlGuy.TaskbarGroup.Core/IInitializable.cs new file mode 100644 index 0000000..ba762e8 --- /dev/null +++ b/TheXamlGuy.TaskbarGroup.Core/IInitializable.cs @@ -0,0 +1,7 @@ +namespace TheXamlGuy.TaskbarGroup.Core +{ + public interface IInitializable + { + void Initialize(); + } +} \ No newline at end of file diff --git a/TheXamlGuy.TaskbarGroup.Core/IMediator.cs b/TheXamlGuy.TaskbarGroup.Core/IMediator.cs new file mode 100644 index 0000000..56640cd --- /dev/null +++ b/TheXamlGuy.TaskbarGroup.Core/IMediator.cs @@ -0,0 +1,17 @@ + +namespace TheXamlGuy.TaskbarGroup.Core +{ + public interface IMediator + { + void Handle(object request, params object[] parameters); + void Handle() where TEvent : new(); + TResponse Handle(params object[] parameters) where TRequest : new(); + TResponse Handle(object request, params object[] parameters); + Task HandleAsync(object request, CancellationToken cancellationToken, params object[] parameters); + Task HandleAsync(object request, params object[] parameters); + Task HandleAsync() where TEvent : new(); + Task HandleAsync(params object[] parameters) where TRequest : new(); + Task HandleAsync(object request, CancellationToken cancellationToken, params object[] parameters); + Task HandleAsync(object request, params object[] parameters); + } +} \ No newline at end of file diff --git a/TheXamlGuy.TaskbarGroup.Core/IMessageHandler.cs b/TheXamlGuy.TaskbarGroup.Core/IMessageHandler.cs new file mode 100644 index 0000000..eaf99ec --- /dev/null +++ b/TheXamlGuy.TaskbarGroup.Core/IMessageHandler.cs @@ -0,0 +1,12 @@ +namespace TheXamlGuy.TaskbarGroup.Core +{ + public interface IMessageHandler + { + void Handle(TMessage message); + } + + public interface IMessageHandler + { + TReturn Handle(TMessage message); + } +} diff --git a/TheXamlGuy.TaskbarGroup.Core/IMessenger.cs b/TheXamlGuy.TaskbarGroup.Core/IMessenger.cs new file mode 100644 index 0000000..e05ce6c --- /dev/null +++ b/TheXamlGuy.TaskbarGroup.Core/IMessenger.cs @@ -0,0 +1,14 @@ +using System.Reactive.Concurrency; +using System.Reactive.Subjects; + +namespace TheXamlGuy.TaskbarGroup.Core +{ + public interface IMessenger + { + void Send() where TMessage : new(); + + void Send(TMessage message); + + IDisposable Subscribe(Action actionDelegate, IScheduler? scheduler = null, Func? where = null); + } +} \ No newline at end of file diff --git a/TheXamlGuy.TaskbarGroup.Core/IObservableExtensions.cs b/TheXamlGuy.TaskbarGroup.Core/IObservableExtensions.cs new file mode 100644 index 0000000..e084c5a --- /dev/null +++ b/TheXamlGuy.TaskbarGroup.Core/IObservableExtensions.cs @@ -0,0 +1,31 @@ +using System.Reactive.Linq; + +namespace TheXamlGuy.TaskbarGroup.Core +{ + public record FileDropped(); + + public static class IObservableExtensions + { + public static IDisposable WeakSubscribe(this IObservable observable, IEventAggregatorInvoker invoker, Action actionDelegate) + { + var methodInfo = actionDelegate.Method; + var weakReference = new WeakReference(actionDelegate.Target); + IDisposable? subscription = null; + + subscription = observable.Subscribe(item => + { + if (weakReference.Target is object target) + { + invoker.Invoke(target, item, methodInfo); + } + else + { + subscription?.Dispose(); + } + }); + + return subscription; + } + + } +} diff --git a/TheXamlGuy.TaskbarGroup.Core/IPointerMonitor.cs b/TheXamlGuy.TaskbarGroup.Core/IPointerMonitor.cs new file mode 100644 index 0000000..9a10caa --- /dev/null +++ b/TheXamlGuy.TaskbarGroup.Core/IPointerMonitor.cs @@ -0,0 +1,7 @@ +namespace TheXamlGuy.TaskbarGroup.Core +{ + public interface IPointerMonitor : IInitializable, IDisposable + { + + } +} \ No newline at end of file diff --git a/TheXamlGuy.TaskbarGroup.Core/IServiceCollectionExtensions.cs b/TheXamlGuy.TaskbarGroup.Core/IServiceCollectionExtensions.cs new file mode 100644 index 0000000..7fbce88 --- /dev/null +++ b/TheXamlGuy.TaskbarGroup.Core/IServiceCollectionExtensions.cs @@ -0,0 +1,22 @@ +using Microsoft.Extensions.DependencyInjection; + +namespace TheXamlGuy.TaskbarGroup.Core +{ + public static class IServiceCollectionExtensions + { + public static IServiceCollection AddRequiredCore(this IServiceCollection serviceCollection) + { + return serviceCollection + .AddSingleton() + .AddSingleton(provider => new ServiceFactory(provider.GetService, (type, parameter) => ActivatorUtilities.CreateInstance(provider, type, parameter))) + .AddSingleton() + .AddSingleton() + .AddSingleton() + .AddSingleton() + .AddSingleton() + .AddSingleton() + .AddSingleton() + .AddSingleton(); + } + } +} diff --git a/TheXamlGuy.TaskbarGroup.Core/IServiceFactory.cs b/TheXamlGuy.TaskbarGroup.Core/IServiceFactory.cs new file mode 100644 index 0000000..d875fe5 --- /dev/null +++ b/TheXamlGuy.TaskbarGroup.Core/IServiceFactory.cs @@ -0,0 +1,10 @@ +namespace TheXamlGuy.TaskbarGroup.Core +{ + public interface IServiceFactory + { + T Create(params object[] parameters); + + T Create(Type type); + T Create(); + } +} \ No newline at end of file diff --git a/TheXamlGuy.TaskbarGroup.Core/ITaskbar.cs b/TheXamlGuy.TaskbarGroup.Core/ITaskbar.cs new file mode 100644 index 0000000..ec095ac --- /dev/null +++ b/TheXamlGuy.TaskbarGroup.Core/ITaskbar.cs @@ -0,0 +1,8 @@ + +namespace TheXamlGuy.TaskbarGroup.Core +{ + public interface ITaskbar + { + TaskbarState GetCurrentState(); + } +} \ No newline at end of file diff --git a/TheXamlGuy.TaskbarGroup.Core/ITaskbarButton.cs b/TheXamlGuy.TaskbarGroup.Core/ITaskbarButton.cs new file mode 100644 index 0000000..2191ccc --- /dev/null +++ b/TheXamlGuy.TaskbarGroup.Core/ITaskbarButton.cs @@ -0,0 +1,9 @@ +namespace TheXamlGuy.TaskbarGroup.Core +{ + public interface ITaskbarButton : IDisposable + { + TaskbarButtonBounds Bounds { get; } + + string Name { get; } + } +} diff --git a/TheXamlGuy.TaskbarGroup.Core/ITaskbarButtonMonitor.cs b/TheXamlGuy.TaskbarGroup.Core/ITaskbarButtonMonitor.cs new file mode 100644 index 0000000..2e5af6f --- /dev/null +++ b/TheXamlGuy.TaskbarGroup.Core/ITaskbarButtonMonitor.cs @@ -0,0 +1,7 @@ +namespace TheXamlGuy.TaskbarGroup.Core +{ + public interface ITaskbarButtonMonitor : IInitializable + { + + } +} \ No newline at end of file diff --git a/TheXamlGuy.TaskbarGroup.Core/ITaskbarMonitor.cs b/TheXamlGuy.TaskbarGroup.Core/ITaskbarMonitor.cs new file mode 100644 index 0000000..9b3d4a5 --- /dev/null +++ b/TheXamlGuy.TaskbarGroup.Core/ITaskbarMonitor.cs @@ -0,0 +1,7 @@ +namespace TheXamlGuy.TaskbarGroup.Core +{ + public interface ITaskbarMonitor : IInitializable + { + + } +} \ No newline at end of file diff --git a/TheXamlGuy.TaskbarGroup.Core/ITemplateSelector.cs b/TheXamlGuy.TaskbarGroup.Core/ITemplateSelector.cs new file mode 100644 index 0000000..343a947 --- /dev/null +++ b/TheXamlGuy.TaskbarGroup.Core/ITemplateSelector.cs @@ -0,0 +1,7 @@ +namespace TheXamlGuy.TaskbarGroup.Core +{ + public interface ITemplateSelector + { + + } +} diff --git a/TheXamlGuy.TaskbarGroup.Core/IWndProcMonitor.cs b/TheXamlGuy.TaskbarGroup.Core/IWndProcMonitor.cs new file mode 100644 index 0000000..90f8224 --- /dev/null +++ b/TheXamlGuy.TaskbarGroup.Core/IWndProcMonitor.cs @@ -0,0 +1,7 @@ +namespace TheXamlGuy.TaskbarGroup.Core +{ + public interface IWndProcMonitor : IInitializable, IDisposable + { + + } +} \ No newline at end of file diff --git a/TheXamlGuy.TaskbarGroup.Core/IsExternalInit.cs b/TheXamlGuy.TaskbarGroup.Core/IsExternalInit.cs new file mode 100644 index 0000000..aee96d2 --- /dev/null +++ b/TheXamlGuy.TaskbarGroup.Core/IsExternalInit.cs @@ -0,0 +1,13 @@ +namespace System.Runtime.CompilerServices +{ + using System.ComponentModel; + /// + /// Reserved to be used by the compiler for tracking metadata. + /// This class should not be used by developers in source code. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public static class IsExternalInit + { + + } +} \ No newline at end of file diff --git a/TheXamlGuy.TaskbarGroup.Core/MaybeNullWhenAttribute.cs b/TheXamlGuy.TaskbarGroup.Core/MaybeNullWhenAttribute.cs new file mode 100644 index 0000000..87da08d --- /dev/null +++ b/TheXamlGuy.TaskbarGroup.Core/MaybeNullWhenAttribute.cs @@ -0,0 +1,24 @@ +namespace System.Diagnostics.CodeAnalysis +{ + /// Specifies that an output will not be null even if the corresponding type allows it. Specifies that an input argument was not null when the call returns. + [AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue, Inherited = false)] + public sealed class NotNullAttribute : Attribute { } + + /// Specifies that when a method returns , the parameter may be null even if the corresponding type disallows it. + [AttributeUsage(AttributeTargets.Parameter, Inherited = false)] + public sealed class MaybeNullWhenAttribute : Attribute + { + /// Initializes the attribute with the specified return value condition. + /// + /// The return value condition. If the method returns this value, the associated parameter may be null. + /// + public MaybeNullWhenAttribute(bool returnValue) => ReturnValue = returnValue; + + /// Gets the return value condition. + public bool ReturnValue { get; } + } + + // NOTE: you can find the full list of attributes in this gist: + // https://gist.github.com/Sergio0694/eb988b243dd4a720a66fe369b63e5b08. + // Keeping this one shorter so that the Medium embed doesn't take up too much space. +} \ No newline at end of file diff --git a/TheXamlGuy.TaskbarGroup.Core/Mediator.cs b/TheXamlGuy.TaskbarGroup.Core/Mediator.cs new file mode 100644 index 0000000..a1a9c00 --- /dev/null +++ b/TheXamlGuy.TaskbarGroup.Core/Mediator.cs @@ -0,0 +1,70 @@ +namespace TheXamlGuy.TaskbarGroup.Core +{ + public class Mediator : IMediator + { + private readonly IServiceFactory serviceFactory; + + public Mediator(IServiceFactory serviceFactory) + { + this.serviceFactory = serviceFactory; + } + + public void Handle() where TEvent : new() + { + Handle(new TEvent()); + } + + public TResponse Handle(params object[] parameters) where TRequest : new() + { + return Handle(new TRequest(), parameters); + } + + public void Handle(object request, params object[] parameters) + { + GetHandler(typeof(IMessageHandler<>).MakeGenericType(request.GetType()), parameters) + .Handle((dynamic)request); + } + + public TResponse Handle(object request, params object[] parameters) + { + return GetHandler(typeof(IMessageHandler<,>).MakeGenericType(typeof(TResponse), request.GetType()), parameters) + .Handle((dynamic)request); + } + + public Task HandleAsync() where TEvent : new() + { + return HandleAsync(new TEvent()); + } + + public Task HandleAsync(params object[] parameters) where TRequest : new() + { + return HandleAsync(new TRequest(), parameters); + } + public Task HandleAsync(object request, CancellationToken cancellationToken, params object[] parameters) + { + return GetHandler(typeof(IAsyncMessageHandler<>).MakeGenericType(request.GetType()), parameters) + .Handle((dynamic)request, cancellationToken); + } + + public Task HandleAsync(object request, params object[] parameters) + { + return HandleAsync(request, CancellationToken.None, parameters); + } + + public Task HandleAsync(object request, CancellationToken cancellationToken, params object[] parameters) + { + return GetHandler(typeof(IAsyncMessageHandler<,>).MakeGenericType(typeof(TResponse), request.GetType()), parameters) + .Handle((dynamic)request, cancellationToken); + } + + public Task HandleAsync(object request, params object[] parameters) + { + return HandleAsync(request, CancellationToken.None, parameters); + } + + private dynamic GetHandler(Type type, params object[] parameters) + { + return parameters.Length == 0 ? serviceFactory.Create(type) : serviceFactory.Create(type, parameters); + } + } +} diff --git a/TheXamlGuy.TaskbarGroup.Core/Messenger.cs b/TheXamlGuy.TaskbarGroup.Core/Messenger.cs new file mode 100644 index 0000000..9e1a684 --- /dev/null +++ b/TheXamlGuy.TaskbarGroup.Core/Messenger.cs @@ -0,0 +1,54 @@ +using System.Reactive.Concurrency; +using System.Collections.Concurrent; +using System.Reactive.Linq; +using System.Reactive.Subjects; + +namespace TheXamlGuy.TaskbarGroup.Core +{ + public class Messenger : IMessenger + { + public IEventAggregatorInvoker invoker; + private readonly ConcurrentDictionary subjects = new(); + + private IScheduler dispatcher; + + public Messenger(IEventAggregatorInvoker invoker) + { + var synchronizationContext = SynchronizationContext.Current; + if (synchronizationContext is null) throw new NullReferenceException(nameof(synchronizationContext)); + + this.invoker = invoker; + + dispatcher = new SynchronizationContextScheduler(synchronizationContext); + } + public ISubject GetSubject() + { + return (ISubject)subjects.GetOrAdd(typeof(TMessage), type => new BehaviorSubject(default)); + } + + public void Send() where TMessage : new() + { + Send(new TMessage()); + } + + public void Send(TMessage message) + { + GetSubject().OnNext(message); + } + + public IDisposable Subscribe(Action actionDelegate, IScheduler? scheduler = null, Func? where = null) + { + if (scheduler is null) + { + scheduler = Scheduler.Default; + } + + if (where == null) + { + where = x => true; + } + + return GetSubject().AsObservable().Skip(1).Where(where).ObserveOn(scheduler).WeakSubscribe(invoker, actionDelegate); + } + } +} diff --git a/TheXamlGuy.TaskbarGroup.Core/NativeMethods.json b/TheXamlGuy.TaskbarGroup.Core/NativeMethods.json new file mode 100644 index 0000000..e1360a4 --- /dev/null +++ b/TheXamlGuy.TaskbarGroup.Core/NativeMethods.json @@ -0,0 +1,4 @@ +{ + "$schema": "https://aka.ms/CsWin32.schema.json", + "public": true +} \ No newline at end of file diff --git a/TheXamlGuy.TaskbarGroup.Core/NativeMethods.txt b/TheXamlGuy.TaskbarGroup.Core/NativeMethods.txt new file mode 100644 index 0000000..83ec0d3 --- /dev/null +++ b/TheXamlGuy.TaskbarGroup.Core/NativeMethods.txt @@ -0,0 +1,17 @@ +SetWindowsHookEx +GetModuleHandle +CallNextHookEx +GetPhysicalCursorPos +FindWindowEx +FindWindow +GetWindowRect +DestroyWindow +DefWindowProcW +CreateWindowExW +RegisterClassW +GetSystemMetrics +MonitorFromWindow +RegisterWindowMessage +GetDpiForWindow +SetWindowPos +SHCreateShellItemArrayFromDataObject \ No newline at end of file diff --git a/TheXamlGuy.TaskbarGroup.Core/ObservableCollectionViewModel.cs b/TheXamlGuy.TaskbarGroup.Core/ObservableCollectionViewModel.cs new file mode 100644 index 0000000..452728b --- /dev/null +++ b/TheXamlGuy.TaskbarGroup.Core/ObservableCollectionViewModel.cs @@ -0,0 +1,132 @@ +using System.Collections.ObjectModel; + +namespace TheXamlGuy.TaskbarGroup.Core +{ + public class ObservableCollectionViewModel : + ObservableCollection, IDisposable + where TItemViewModel : class + { + private readonly IDisposer disposer; + private readonly IMessenger messenger; + private readonly IServiceFactory serviceFactory; + + public ObservableCollectionViewModel(IMessenger messenger, + IServiceFactory serviceFactory, + IDisposer disposer) + { + this.messenger = messenger; + this.serviceFactory = serviceFactory; + this.disposer = disposer; + } + + public bool IsInitialized { get; protected set; } + + public void Add() + { + var item = serviceFactory.Create(); + disposer.Add(this, item); + + base.Add(item); + } + + public void Add(object parameter, params object[] parameters) + { + var item = serviceFactory.Create(new[] { parameter }.Concat(parameters)); + disposer.Add(this, item); + + base.Add(item); + } + + public new void Clear() + { + foreach (var item in this) + { + disposer.Dispose(item); + } + + base.Clear(); + } + + public void Dispose() + { + OnDisposing(); + + disposer.Dispose(this); + GC.SuppressFinalize(this); + } + + public void Initialize() + { + if (IsInitialized) + { + return; + } + + IsInitialized = true; + OnInitialize(); + } + + public void Insert(params object[] parameters) where TItem : TItemViewModel + { + var item = serviceFactory.Create(parameters); + disposer.Add(this, item); + + base.Add(item); + } + + public new void Insert(int index, TItemViewModel item) + { + disposer.Add(this, item); + base.Insert(index, item); + } + + public void Insert(int index, params object[] parameters) where TItem : TItemViewModel + { + var item = serviceFactory.Create(parameters); + disposer.Add(this, item); + + base.Insert(index, item); + } + + public void Insert(TItemViewModel item) + { + base.Insert(0, item); + disposer.Add(item); + } + + public new void Remove(TItemViewModel item) + { + disposer.Dispose(item); + base.Remove(item); + } + + protected virtual void OnDisposing() + { + } + + protected virtual void OnInitialize() + { + + } + + protected void Publish(TEvent @event) + { + messenger.Send(@event); + } + + protected void Publish() where TEvent : new() + { + messenger.Send(); + } + + protected void Register(Action action) + { + disposer.Add(this, messenger.Subscribe(action)); + } + + protected void Register(Action action, Func condition) + { + disposer.Add(this, messenger.Subscribe(action, null, condition)); + } + } +} diff --git a/TheXamlGuy.TaskbarGroup.Core/ObservableViewModel.cs b/TheXamlGuy.TaskbarGroup.Core/ObservableViewModel.cs new file mode 100644 index 0000000..0f4fbfb --- /dev/null +++ b/TheXamlGuy.TaskbarGroup.Core/ObservableViewModel.cs @@ -0,0 +1,68 @@ +using CommunityToolkit.Mvvm.ComponentModel; + +namespace TheXamlGuy.TaskbarGroup.Core +{ + [INotifyPropertyChanged] + public partial class ObservableViewModel : IDisposable + { + private readonly IDisposer disposer; + private readonly IMessenger messenger; + + public ObservableViewModel(IMessenger messenger, + IServiceFactory serviceFactory, + IDisposer disposer) + { + this.messenger = messenger; + this.disposer = disposer; + } + + public bool IsInitialized { get; protected set; } + + public void Dispose() + { + OnDisposing(); + + disposer.Dispose(this); + GC.SuppressFinalize(this); + } + + public void Initialize() + { + if (IsInitialized) + { + return; + } + + IsInitialized = true; + OnInitialize(); + } + protected virtual void OnDisposing() + { + } + + protected virtual void OnInitialize() + { + + } + + protected void Publish(TEvent @event) + { + messenger.Send(@event); + } + + protected void Publish() where TEvent : new() + { + messenger.Send(); + } + + protected void Register(Action action) + { + disposer.Add(this, messenger.Subscribe(action)); + } + + protected void Register(Action action, Func condition) + { + disposer.Add(this, messenger.Subscribe(action, null, condition)); + } + } +} diff --git a/TheXamlGuy.TaskbarGroup.Core/PointerButton.cs b/TheXamlGuy.TaskbarGroup.Core/PointerButton.cs new file mode 100644 index 0000000..6f62584 --- /dev/null +++ b/TheXamlGuy.TaskbarGroup.Core/PointerButton.cs @@ -0,0 +1,9 @@ +namespace TheXamlGuy.TaskbarGroup.Core +{ + public enum PointerButton + { + Left, + Middle, + Right + } +} diff --git a/TheXamlGuy.TaskbarGroup.Core/PointerDrag.cs b/TheXamlGuy.TaskbarGroup.Core/PointerDrag.cs new file mode 100644 index 0000000..0d8271f --- /dev/null +++ b/TheXamlGuy.TaskbarGroup.Core/PointerDrag.cs @@ -0,0 +1,4 @@ +namespace TheXamlGuy.TaskbarGroup.Core +{ + public record PointerDrag(PointerLocation Location); +} diff --git a/TheXamlGuy.TaskbarGroup.Core/PointerLocation.cs b/TheXamlGuy.TaskbarGroup.Core/PointerLocation.cs new file mode 100644 index 0000000..2114404 --- /dev/null +++ b/TheXamlGuy.TaskbarGroup.Core/PointerLocation.cs @@ -0,0 +1,4 @@ +namespace TheXamlGuy.TaskbarGroup.Core +{ + public record PointerLocation(int X, int Y); +} diff --git a/TheXamlGuy.TaskbarGroup.Core/PointerMonitor.cs b/TheXamlGuy.TaskbarGroup.Core/PointerMonitor.cs new file mode 100644 index 0000000..f951524 --- /dev/null +++ b/TheXamlGuy.TaskbarGroup.Core/PointerMonitor.cs @@ -0,0 +1,155 @@ +using Windows.Win32; +using Windows.Win32.UI.WindowsAndMessaging; +using Windows.Win32.Foundation; +using System.Diagnostics.CodeAnalysis; + +namespace TheXamlGuy.TaskbarGroup.Core +{ + public class PointerMonitor : IPointerMonitor + { + private readonly IMessenger messenger; + private bool isDisposed; + private bool isPointerPressed; + private HOOKPROC? mouseEventDelegate; + private UnhookWindowsHookExSafeHandle? mouseHandle; + private bool isPointerDrag; + + public PointerMonitor(IMessenger messenger) + { + this.messenger = messenger; + } + + ~PointerMonitor() + { + Dispose(false); + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + public unsafe void Initialize() + { + InitializeHook(); + } + + protected virtual void Dispose(bool disposing) + { + if (!isDisposed) + { + RemoveHook(); + isDisposed = true; + } + } + + private unsafe void InitializeHook() + { + mouseEventDelegate = new HOOKPROC(MouseProc); + mouseHandle = PInvoke.SetWindowsHookEx(WINDOWS_HOOK_ID.WH_MOUSE_LL, mouseEventDelegate, PInvoke.GetModuleHandle("user32.dll"), 0); + } + + private unsafe bool TryGetPointer(out POINT point) + { + fixed (POINT* lpPointLocal = &point) + { + return PInvoke.GetPhysicalCursorPos(lpPointLocal); + } + } + + private bool TryGetPointerLocation([MaybeNullWhen(false)]out PointerLocation location) + { + if (TryGetPointer(out POINT point)) + { + location = new PointerLocation(point.x, point.y); + return true; + + } + + location = null; + return false; + } + + private LRESULT MouseProc(int nCode, WPARAM wParam, LPARAM lParam) + { + if (nCode >= 0) + { + + if (TryGetPointerLocation(out var location)) + { + switch ((uint)wParam.Value) + { + case (uint)WndProcMessages.WM_MOUSEMOVE: + SendPointerMoved(location); + break; + case (uint)WndProcMessages.WM_LBUTTONUP: + SendPointerReleased(location, PointerButton.Left); + break; + case (uint)WndProcMessages.WM_MBUTTONUP: + SendPointerReleased(location, PointerButton.Middle); + break; + case (uint)WndProcMessages.WM_RBUTTONUP: + SendPointerReleased(location, PointerButton.Right); + break; + case (uint)WndProcMessages.WM_LBUTTONDOWN: + SendPointerPressed(location, PointerButton.Left); + break; + case (uint)WndProcMessages.WM_MBUTTONDOWN: + SendPointerPressed(location, PointerButton.Middle); + break; + case (uint)WndProcMessages.WM_RBUTTONDOWN: + SendPointerPressed(location, PointerButton.Right); + break; + } + } + } + + return PInvoke.CallNextHookEx(mouseHandle, nCode, wParam, lParam); + } + + private unsafe void RemoveHook() + { + if (mouseHandle is not null && mouseHandle.DangerousGetHandle() != IntPtr.Zero) + { + PInvoke.UnhookWindowsHookEx((HHOOK)mouseHandle.DangerousGetHandle()); + } + } + + private void SendPointerMoved(PointerLocation location) + { + if (isPointerPressed) + { + if (!isPointerDrag) + { + isPointerDrag = true; + } + + messenger.Send(new PointerDrag(location)); + } + + messenger.Send(new PointerMoved(location)); + } + + private void SendPointerPressed(PointerLocation location, PointerButton button) + { + isPointerPressed = true; + messenger.Send(new PointerPressed(location, button)); + } + + private void SendPointerReleased(PointerLocation location, PointerButton button) + { + if (isPointerPressed) + { + if (isPointerDrag) + { + isPointerDrag = false; + messenger.Send(new PointerDragReleased(location, button)); + } + + isPointerPressed = false; + messenger.Send(new PointerReleased(location, button)); + } + } + } +} diff --git a/TheXamlGuy.TaskbarGroup.Core/PointerMoved.cs b/TheXamlGuy.TaskbarGroup.Core/PointerMoved.cs new file mode 100644 index 0000000..b1a4421 --- /dev/null +++ b/TheXamlGuy.TaskbarGroup.Core/PointerMoved.cs @@ -0,0 +1,4 @@ +namespace TheXamlGuy.TaskbarGroup.Core +{ + public record PointerMoved(PointerLocation Location); +} diff --git a/TheXamlGuy.TaskbarGroup.Core/PointerPressed.cs b/TheXamlGuy.TaskbarGroup.Core/PointerPressed.cs new file mode 100644 index 0000000..ef500fc --- /dev/null +++ b/TheXamlGuy.TaskbarGroup.Core/PointerPressed.cs @@ -0,0 +1,4 @@ +namespace TheXamlGuy.TaskbarGroup.Core +{ + public record PointerPressed(PointerLocation Location, PointerButton Button = PointerButton.Left); +} diff --git a/TheXamlGuy.TaskbarGroup.Core/PointerReleased.cs b/TheXamlGuy.TaskbarGroup.Core/PointerReleased.cs new file mode 100644 index 0000000..bb93811 --- /dev/null +++ b/TheXamlGuy.TaskbarGroup.Core/PointerReleased.cs @@ -0,0 +1,6 @@ +namespace TheXamlGuy.TaskbarGroup.Core +{ + public record PointerDragReleased(PointerLocation Location, PointerButton Button = PointerButton.Left); + + public record PointerReleased(PointerLocation Location, PointerButton Button = PointerButton.Left); +} diff --git a/TheXamlGuy.TaskbarGroup.Core/RECTExtensions.cs b/TheXamlGuy.TaskbarGroup.Core/RECTExtensions.cs new file mode 100644 index 0000000..2550cd8 --- /dev/null +++ b/TheXamlGuy.TaskbarGroup.Core/RECTExtensions.cs @@ -0,0 +1,14 @@ +using Windows.Foundation; +using Windows.Win32.Foundation; + +namespace TheXamlGuy.TaskbarGroup.Core +{ + internal static class RECTExtensions + { + internal static Rect ToRect(this RECT rect) + { + if (rect.right - rect.left < 0 || rect.bottom - rect.top < 0) return new Rect(rect.left, rect.top, 0, 0); + return new Rect(rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top); + } + } +} \ No newline at end of file diff --git a/TheXamlGuy.TaskbarGroup.Core/Screen.cs b/TheXamlGuy.TaskbarGroup.Core/Screen.cs new file mode 100644 index 0000000..7480d9c --- /dev/null +++ b/TheXamlGuy.TaskbarGroup.Core/Screen.cs @@ -0,0 +1,109 @@ +using System.Runtime.InteropServices; +using Windows.Foundation; +using Windows.Win32; +using Windows.Win32.Foundation; +using Windows.Win32.UI.WindowsAndMessaging; +using Windows.Win32.Graphics.Gdi; + +namespace TheXamlGuy.TaskbarGroup.Core +{ + public class Screen + { + private const int CCHDEVICENAME = 32; + private const int PRIMARY_MONITOR = unchecked((int)0xBAADF00D); + private static readonly bool _multiMonitorSupport; + + private readonly IntPtr _monitorHandle; + + static Screen() + { + _multiMonitorSupport = PInvoke.GetSystemMetrics(SYSTEM_METRICS_INDEX.SM_CMONITORS) != 0; + } + + internal Screen(IntPtr monitorHandle) + { + if (!_multiMonitorSupport || monitorHandle == (IntPtr)PRIMARY_MONITOR) + { + Bounds = SystemInformationHelper.VirtualScreen; + Primary = true; + DeviceName = "DISPLAY"; + } + else + { + var monitorData = GetMonitorData(monitorHandle); + + Bounds = new Rect(monitorData.MonitorRect.left, monitorData.MonitorRect.top, monitorData.MonitorRect.right - monitorData.MonitorRect.left, monitorData.MonitorRect.bottom - monitorData.MonitorRect.top); + Primary = (monitorData.Flags & (int)MonitorFlag.MONITOR_DEFAULTTOPRIMARY) != 0; + DeviceName = monitorData.DeviceName; + } + + _monitorHandle = monitorHandle; + } + + private enum MonitorFlag : uint + { + MONITOR_DEFAULTTONULL = 0, + MONITOR_DEFAULTTOPRIMARY = 1, + MONITOR_DEFAULTTONEAREST = 2 + } + + public Rect Bounds { get; } + + public string DeviceName { get; } + + public bool Primary { get; } + + public Rect WorkingArea => GetWorkingArea(); + + public static Screen FromHandle(IntPtr handle) + { + return _multiMonitorSupport ? new Screen(PInvoke.MonitorFromWindow((HWND)handle, MONITOR_FROM_FLAGS.MONITOR_DEFAULTTONEAREST)) : new Screen((IntPtr)PRIMARY_MONITOR); + } + + public override bool Equals(object? obj) + { + if (obj is not Screen monitor) return false; + return _monitorHandle == monitor._monitorHandle; + } + + public override int GetHashCode() + { + return (int)_monitorHandle; + } + + [DllImport("user32.dll", EntryPoint = "GetMonitorInfo", CharSet = CharSet.Auto, SetLastError = true)] + private static extern bool GetMonitorInfoEx(IntPtr hMonitor, ref MonitorData lpmi); + + private MonitorData GetMonitorData(IntPtr monitorHandle) + { + var monitorData = new MonitorData(); + monitorData.Size = Marshal.SizeOf(monitorData); + GetMonitorInfoEx(monitorHandle, ref monitorData); + + return monitorData; + } + + private Rect GetWorkingArea() + { + if (!_multiMonitorSupport || _monitorHandle == (IntPtr)PRIMARY_MONITOR) + { + return SystemInformationHelper.WorkingArea; + } + + var monitorData = GetMonitorData(_monitorHandle); + return new Rect(monitorData.WorkAreaRect.left, monitorData.WorkAreaRect.top, monitorData.WorkAreaRect.right - monitorData.WorkAreaRect.left, monitorData.WorkAreaRect.bottom - monitorData.WorkAreaRect.top); + } + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] + private struct MonitorData + { + public int Size; + public RECT MonitorRect; + public RECT WorkAreaRect; + public uint Flags; + + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = CCHDEVICENAME)] + public string DeviceName; + } + } +} \ No newline at end of file diff --git a/TheXamlGuy.TaskbarGroup.Core/ServiceFactory.cs b/TheXamlGuy.TaskbarGroup.Core/ServiceFactory.cs new file mode 100644 index 0000000..1e34e31 --- /dev/null +++ b/TheXamlGuy.TaskbarGroup.Core/ServiceFactory.cs @@ -0,0 +1,30 @@ +namespace TheXamlGuy.TaskbarGroup.Core +{ + public class ServiceFactory : IServiceFactory + { + private readonly Func factory; + + private readonly Func factoryWithParameters; + + public ServiceFactory(Func factory, Func factoryWithParameters) + { + this.factory = factory; + this.factoryWithParameters = factoryWithParameters; + } + + public T Create(params object[] parameters) + { + return (T)factoryWithParameters(typeof(T), parameters); + } + + public T Create(Type type) + { + return (T)factory(type); + } + + public T Create() + { + return (T)factory(typeof(T)); + } + } +} \ No newline at end of file diff --git a/TheXamlGuy.TaskbarGroup.Core/SystemInformationHelper.cs b/TheXamlGuy.TaskbarGroup.Core/SystemInformationHelper.cs new file mode 100644 index 0000000..9c87457 --- /dev/null +++ b/TheXamlGuy.TaskbarGroup.Core/SystemInformationHelper.cs @@ -0,0 +1,33 @@ +using System.Runtime.InteropServices; +using Windows.Foundation; +using Windows.Win32; +using Windows.Win32.Foundation; +using Windows.Win32.UI.WindowsAndMessaging; + +namespace TheXamlGuy.TaskbarGroup.Core +{ + internal static class SystemInformationHelper + { + private const int SPI_GETWORKAREA = 48; + + public static Rect VirtualScreen => GetVirtualScreen(); + public static Rect WorkingArea => GetWorkingArea(); + + private static Rect GetVirtualScreen() + { + var size = new Size(PInvoke.GetSystemMetrics(SYSTEM_METRICS_INDEX.SM_CXSCREEN), PInvoke.GetSystemMetrics(SYSTEM_METRICS_INDEX.SM_CYSCREEN)); + return new Rect(0, 0, size.Width, size.Height); + } + + private static Rect GetWorkingArea() + { + var rect = new RECT(); + + SystemParametersInfo(SPI_GETWORKAREA, 0, ref rect, 0); + return new Rect(rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top); + } + + [DllImport("user32.dll", CharSet = CharSet.Auto)] + private static extern bool SystemParametersInfo(int nAction, int nParam, ref RECT rc, int nUpdate); + } +} \ No newline at end of file diff --git a/TheXamlGuy.TaskbarGroup.Core/Taskbar.cs b/TheXamlGuy.TaskbarGroup.Core/Taskbar.cs new file mode 100644 index 0000000..39b591e --- /dev/null +++ b/TheXamlGuy.TaskbarGroup.Core/Taskbar.cs @@ -0,0 +1,80 @@ +using System.Runtime.InteropServices; +using Windows.Win32.Foundation; + +namespace TheXamlGuy.TaskbarGroup.Core +{ + public class Taskbar : ITaskbar + { + private const string ShellTrayHandleName = "Shell_TrayWnd"; + + private enum AppBarEdge : uint + { + Left = 0, + Top = 1, + Right = 2, + Bottom = 3 + } + + private enum AppBarMessage : uint + { + New = 0x00000000, + Remove = 0x00000001, + QueryPos = 0x00000002, + SetPos = 0x00000003, + GetState = 0x00000004, + GetTaskbarPos = 0x00000005, + Activate = 0x00000006, + GetAutoHideBar = 0x00000007, + SetAutoHideBar = 0x00000008, + WindowPosChanged = 0x00000009, + SetState = 0x0000000A, + } + + public TaskbarState GetCurrentState() + { + var handle = GetSystemTrayHandle(); + var state = new TaskbarState + { + Screen = Screen.FromHandle(handle) + }; + + var appBarData = GetAppBarData(handle); + GetAppBarPosition(ref appBarData); + + state.Rect = appBarData.rect.ToRect(); + state.Placement = (TaskbarPlacement)appBarData.uEdge; + + return state; + } + + [DllImport("user32.dll", SetLastError = true)] + private static extern IntPtr DefWindowProcW(IntPtr handle, uint msg, IntPtr wParam, IntPtr lParam); + + private static IntPtr GetSystemTrayHandle() => WindowHelper.Find(ShellTrayHandleName); + + [DllImport("shell32.dll", SetLastError = true)] + private static extern IntPtr SHAppBarMessage(AppBarMessage dwMessage, ref AppBarData pData); + + private AppBarData GetAppBarData(IntPtr handle) + { + return new AppBarData + { + cbSize = (uint)Marshal.SizeOf(typeof(AppBarData)), + hWnd = handle + }; + } + + private void GetAppBarPosition(ref AppBarData appBarData) => SHAppBarMessage(AppBarMessage.GetTaskbarPos, ref appBarData); + + [StructLayout(LayoutKind.Sequential)] + private struct AppBarData + { + public uint cbSize; + public IntPtr hWnd; + public uint uCallbackMessage; + public AppBarEdge uEdge; + public RECT rect; + public int lParam; + } + } +} \ No newline at end of file diff --git a/TheXamlGuy.TaskbarGroup.Core/TaskbarButton.cs b/TheXamlGuy.TaskbarGroup.Core/TaskbarButton.cs new file mode 100644 index 0000000..ebbac49 --- /dev/null +++ b/TheXamlGuy.TaskbarGroup.Core/TaskbarButton.cs @@ -0,0 +1,103 @@ +namespace TheXamlGuy.TaskbarGroup.Core +{ + public class TaskbarButton : ITaskbarButton + { + private readonly IMessenger messenger; + private readonly IDisposer disposer; + private bool isWithinBounds; + private bool isDrag; + + public TaskbarButton(IMessenger messenger, + IDisposer disposer, + string name, + TaskbarButtonBounds bounds) + { + this.messenger = messenger; + this.disposer = disposer; + Name = name; + Bounds = bounds; + + disposer.Add(this, messenger.Subscribe(OnPointerReleased)); + disposer.Add(this, messenger.Subscribe(OnPointerMoved)); + disposer.Add(this, messenger.Subscribe(OnPointerDrag)); + } + + public TaskbarButtonBounds Bounds { get; internal set; } + + public string Name { get; internal set; } + + public void Dispose() + { + disposer.Dispose(this); + GC.SuppressFinalize(this); + } + + private bool IsWithinBounds(PointerLocation args) + { + if (args.X >= Bounds.X + && args.X <= Bounds.X + Bounds.Width + && args.Y >= Bounds.Y + && args.Y <= Bounds.Y + Bounds.Height) + { + return true; + } + else + { + return false; + } + } + + private void OnPointerDrag(PointerDrag args) + { + if (isWithinBounds) + { + if (isDrag) + { + messenger.Send(new TaskbarButtonDragOver(this)); + } + else + { + messenger.Send(new TaskbarButtonDragEnter(this)); + } + + isDrag = true; + } + else + { + isDrag = false; + } + } + + private void OnPointerMoved(PointerMoved args) + { + if (IsWithinBounds(args.Location)) + { + if (isWithinBounds) + { + return; + } + + isWithinBounds = true; + messenger.Send(new TaskbarButtonEntered(this)); + } + else + { + isDrag = false; + isWithinBounds = false; + } + } + + private void OnPointerReleased(PointerReleased args) + { + if (!isDrag && isWithinBounds) + { + messenger.Send(new TaskbarButtonInvoked(this)); + } + + if (isDrag) + { + isDrag = false; + } + } + } +} diff --git a/TheXamlGuy.TaskbarGroup.Core/TaskbarButtonBounds.cs b/TheXamlGuy.TaskbarGroup.Core/TaskbarButtonBounds.cs new file mode 100644 index 0000000..abe12b5 --- /dev/null +++ b/TheXamlGuy.TaskbarGroup.Core/TaskbarButtonBounds.cs @@ -0,0 +1,23 @@ +namespace TheXamlGuy.TaskbarGroup.Core +{ + public record TaskbarButtonBounds + { + public TaskbarButtonBounds() + { + + } + + public TaskbarButtonBounds(int x, int y, int width, int height) + { + X = x; + Y = y; + Width = width; + Height = height; + } + + public int X { get; } + public int Y { get; } + public int Width { get; } + public int Height { get; } + } +} diff --git a/TheXamlGuy.TaskbarGroup.Core/TaskbarButtonCreated.cs b/TheXamlGuy.TaskbarGroup.Core/TaskbarButtonCreated.cs new file mode 100644 index 0000000..b44d7b3 --- /dev/null +++ b/TheXamlGuy.TaskbarGroup.Core/TaskbarButtonCreated.cs @@ -0,0 +1,4 @@ +namespace TheXamlGuy.TaskbarGroup.Core +{ + public record TaskbarButtonCreated(TaskbarButton Button); +} diff --git a/TheXamlGuy.TaskbarGroup.Core/TaskbarButtonDragEnter.cs b/TheXamlGuy.TaskbarGroup.Core/TaskbarButtonDragEnter.cs new file mode 100644 index 0000000..995e4ad --- /dev/null +++ b/TheXamlGuy.TaskbarGroup.Core/TaskbarButtonDragEnter.cs @@ -0,0 +1,4 @@ +namespace TheXamlGuy.TaskbarGroup.Core +{ + public record TaskbarButtonDragEnter(TaskbarButton Button); +} diff --git a/TheXamlGuy.TaskbarGroup.Core/TaskbarButtonDragOver.cs b/TheXamlGuy.TaskbarGroup.Core/TaskbarButtonDragOver.cs new file mode 100644 index 0000000..1a4b16d --- /dev/null +++ b/TheXamlGuy.TaskbarGroup.Core/TaskbarButtonDragOver.cs @@ -0,0 +1,4 @@ +namespace TheXamlGuy.TaskbarGroup.Core +{ + public record TaskbarButtonDragOver(TaskbarButton Button); +} diff --git a/TheXamlGuy.TaskbarGroup.Core/TaskbarButtonEntered.cs b/TheXamlGuy.TaskbarGroup.Core/TaskbarButtonEntered.cs new file mode 100644 index 0000000..5a7158b --- /dev/null +++ b/TheXamlGuy.TaskbarGroup.Core/TaskbarButtonEntered.cs @@ -0,0 +1,4 @@ +namespace TheXamlGuy.TaskbarGroup.Core +{ + public record TaskbarButtonEntered(TaskbarButton Button); +} diff --git a/TheXamlGuy.TaskbarGroup.Core/TaskbarButtonInvoked.cs b/TheXamlGuy.TaskbarGroup.Core/TaskbarButtonInvoked.cs new file mode 100644 index 0000000..0a98079 --- /dev/null +++ b/TheXamlGuy.TaskbarGroup.Core/TaskbarButtonInvoked.cs @@ -0,0 +1,4 @@ +namespace TheXamlGuy.TaskbarGroup.Core +{ + public record TaskbarButtonInvoked(TaskbarButton Button); +} diff --git a/TheXamlGuy.TaskbarGroup.Core/TaskbarButtonMonitor.cs b/TheXamlGuy.TaskbarGroup.Core/TaskbarButtonMonitor.cs new file mode 100644 index 0000000..25e5998 --- /dev/null +++ b/TheXamlGuy.TaskbarGroup.Core/TaskbarButtonMonitor.cs @@ -0,0 +1,153 @@ +using Windows.Win32.Foundation; +using UIAutomationClient; +using System.Diagnostics; + +namespace TheXamlGuy.TaskbarGroup.Core +{ + public class TaskbarButtonMonitor : ITaskbarButtonMonitor + { + private readonly IDispatcherTimer dispatcherTimer; + private readonly IDispatcherTimerFactory dispatcherTimerFactory; + private readonly IServiceFactory serviceFactory; + private readonly IMessenger messenger; + private readonly Dictionary taskbarButtons = new(); + private RECT taskbarBoundsCache; + private IUIAutomationCondition? taskListCondition; + private IUIAutomationElement? taskListElement; + private HWND taskListHandle; + + public TaskbarButtonMonitor(IMessenger messenger, + IDispatcherTimerFactory dispatcherTimerFactory, + IServiceFactory serviceFactory) + { + this.messenger = messenger; + this.dispatcherTimerFactory = dispatcherTimerFactory; + this.serviceFactory = serviceFactory; + + dispatcherTimer = dispatcherTimerFactory.Create(OnDispatcher, TimeSpan.FromMilliseconds(500)); + } + + public void Initialize() + { + var clientUIAutomation = new CUIAutomation(); + taskListCondition = clientUIAutomation.CreateTrueCondition(); + + var trayHandle = WindowHelper.Find("Shell_TrayWnd"); + + var rebarHandle = WindowHelper.Find("ReBarWindow32", trayHandle); + var taskHandle = WindowHelper.Find("MSTaskSwWClass", rebarHandle); + taskListHandle = WindowHelper.Find("MSTaskListWClass", taskHandle); + + taskListElement = clientUIAutomation.ElementFromHandle(taskListHandle); + + if (WindowHelper.TryGetBounds(taskListHandle, out var bounds)) + { + taskbarBoundsCache = bounds; + } + + dispatcherTimer.Start(); + UpdateTaskbarButtons(); + } + + private bool CheckDirtyTaskbarRegion() + { + if (WindowHelper.TryGetBounds(taskListHandle, out var bounds)) + { + var width = taskbarBoundsCache.right - taskbarBoundsCache.left; + var height = taskbarBoundsCache.bottom - taskbarBoundsCache.top; + + var deltaWidth = bounds.right - bounds.left; + var deltaHeight = bounds.bottom - bounds.top; + + if (width != deltaWidth || height != deltaHeight) + { + taskbarBoundsCache = bounds; + return true; + } + } + + return false; + } + + private Dictionary FindTaskbarButtons() + { + var taskElements = taskListElement?.FindAll(TreeScope.TreeScope_Descendants | TreeScope.TreeScope_Children, taskListCondition); + + var buttons = new Dictionary(); + if (taskElements is not null) + { + for (int index = 0; index <= taskElements.Length - 1; index++) + { + var taskUIElement = taskElements.GetElement(index); + var name = taskUIElement.CurrentName; + var rect = taskUIElement.CurrentBoundingRectangle; + + buttons.Add(name, rect); + } + } + + return buttons; + } + + private void OnDispatcher() + { + dispatcherTimer.Stop(); + + if (CheckDirtyTaskbarRegion()) + { + UpdateTaskbarButtons(); + } + + dispatcherTimer.Start(); + } + + private void UpdateTaskbarButtons() + { + if (taskListElement is null) + { + return; + } + + var buttons = FindTaskbarButtons(); + + foreach (var buttonToRemove in taskbarButtons.Where(taskbarButton => !buttons.ContainsKey(taskbarButton.Key))) + { + var key = buttonToRemove.Key; + var button = buttonToRemove.Value; + + Debug.WriteLine($"{key} button removed"); + + taskbarButtons.Remove(key); + messenger.Send(new TaskbarButtonRemoved(button)); + + button.Dispose(); + } + + foreach (var button in buttons) + { + var name = button.Key; + var bounds = button.Value; + + var buttonBounds = new TaskbarButtonBounds(bounds.left, + bounds.top, + bounds.right - bounds.left, + bounds.bottom - bounds.top); + + if (taskbarButtons.TryGetValue(name, out var taskbarButton)) + { + Debug.WriteLine($"{name} button updated"); + + taskbarButtons[name].Bounds = buttonBounds; + messenger.Send(new TaskbarButtonUpdated(taskbarButtons[name])); + } + else + { + Debug.WriteLine($"{name} button added"); + + taskbarButtons.Add(name, serviceFactory.Create(name, buttonBounds)); + messenger.Send(new TaskbarButtonCreated(taskbarButtons[name])); + } + } + } + } +} diff --git a/TheXamlGuy.TaskbarGroup.Core/TaskbarButtonRemoved.cs b/TheXamlGuy.TaskbarGroup.Core/TaskbarButtonRemoved.cs new file mode 100644 index 0000000..5f9452b --- /dev/null +++ b/TheXamlGuy.TaskbarGroup.Core/TaskbarButtonRemoved.cs @@ -0,0 +1,4 @@ +namespace TheXamlGuy.TaskbarGroup.Core +{ + public record TaskbarButtonRemoved(TaskbarButton Button); +} diff --git a/TheXamlGuy.TaskbarGroup.Core/TaskbarButtonUpdated.cs b/TheXamlGuy.TaskbarGroup.Core/TaskbarButtonUpdated.cs new file mode 100644 index 0000000..38a95ba --- /dev/null +++ b/TheXamlGuy.TaskbarGroup.Core/TaskbarButtonUpdated.cs @@ -0,0 +1,4 @@ +namespace TheXamlGuy.TaskbarGroup.Core +{ + public record TaskbarButtonUpdated(TaskbarButton Button); +} diff --git a/TheXamlGuy.TaskbarGroup.Core/TaskbarChanged.cs b/TheXamlGuy.TaskbarGroup.Core/TaskbarChanged.cs new file mode 100644 index 0000000..2da329e --- /dev/null +++ b/TheXamlGuy.TaskbarGroup.Core/TaskbarChanged.cs @@ -0,0 +1,4 @@ +namespace TheXamlGuy.TaskbarGroup.Core +{ + public record TaskbarChanged; +} \ No newline at end of file diff --git a/TheXamlGuy.TaskbarGroup.Core/TaskbarMonitor.cs b/TheXamlGuy.TaskbarGroup.Core/TaskbarMonitor.cs new file mode 100644 index 0000000..99e7242 --- /dev/null +++ b/TheXamlGuy.TaskbarGroup.Core/TaskbarMonitor.cs @@ -0,0 +1,30 @@ +using Windows.Win32; + +namespace TheXamlGuy.TaskbarGroup.Core +{ + public class TaskbarMonitor : ITaskbarMonitor + { + private const int SPI_SETWORKAREA = 0x002F; + + private readonly uint WM_TASKBARCREATED = PInvoke.RegisterWindowMessage("TaskbarCreated"); + private readonly IMessenger messenger; + + public TaskbarMonitor(IMessenger messenger) + { + this.messenger = messenger; + } + + public void Initialize() + { + messenger.Subscribe(OnWndProc); + } + + private void OnWndProc(WndProc args) + { + if (args.Message == WM_TASKBARCREATED || args.Message == (int)WndProcMessages.WM_SETTINGCHANGE && (int)args.WParam == SPI_SETWORKAREA) + { + messenger.Send(); + } + } + } +} \ No newline at end of file diff --git a/TheXamlGuy.TaskbarGroup.Core/TaskbarPlacement.cs b/TheXamlGuy.TaskbarGroup.Core/TaskbarPlacement.cs new file mode 100644 index 0000000..238b6ae --- /dev/null +++ b/TheXamlGuy.TaskbarGroup.Core/TaskbarPlacement.cs @@ -0,0 +1,10 @@ +namespace TheXamlGuy.TaskbarGroup.Core +{ + public enum TaskbarPlacement + { + Left = 0, + Top = 1, + Right = 2, + Bottom = 3 + } +} \ No newline at end of file diff --git a/TheXamlGuy.TaskbarGroup.Core/TaskbarState.cs b/TheXamlGuy.TaskbarGroup.Core/TaskbarState.cs new file mode 100644 index 0000000..85845d8 --- /dev/null +++ b/TheXamlGuy.TaskbarGroup.Core/TaskbarState.cs @@ -0,0 +1,11 @@ +using Windows.Foundation; + +namespace TheXamlGuy.TaskbarGroup.Core +{ + public struct TaskbarState + { + public TaskbarPlacement Placement; + public Rect Rect; + public Screen Screen; + } +} \ No newline at end of file diff --git a/TheXamlGuy.TaskbarGroup.Core/TheXamlGuy.TaskbarGroup.Core.csproj b/TheXamlGuy.TaskbarGroup.Core/TheXamlGuy.TaskbarGroup.Core.csproj new file mode 100644 index 0000000..0468f29 --- /dev/null +++ b/TheXamlGuy.TaskbarGroup.Core/TheXamlGuy.TaskbarGroup.Core.csproj @@ -0,0 +1,42 @@ + + + netstandard2.0 + enable + enable + True + 10.0 + x64;x86 + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + tlbimp + 0 + 1 + 930299ce-9965-4dec-b0f4-a54848d4b667 + 0 + false + true + + + 0 + 1 + 944de083-8fb8-45cf-bcb7-c477acb2f897 + 0 + tlbimp + false + true + + + diff --git a/TheXamlGuy.TaskbarGroup.Core/WindowHelper.cs b/TheXamlGuy.TaskbarGroup.Core/WindowHelper.cs new file mode 100644 index 0000000..0df9f1d --- /dev/null +++ b/TheXamlGuy.TaskbarGroup.Core/WindowHelper.cs @@ -0,0 +1,37 @@ +using Windows.Win32; +using Windows.Win32.Foundation; +using Windows.Win32.UI.WindowsAndMessaging; + +namespace TheXamlGuy.TaskbarGroup.Core +{ + public class WindowHelper + { + public static void MoveAndResize(HWND handle, int x, int y, int width, int height) + { + PInvoke.SetWindowPos(handle, new HWND(), x, y, width, height, 0); + } + + public static void BringToForeground(HWND handle) + { + if (TryGetBounds(handle, out var bounds)) + { + PInvoke.SetWindowPos(handle, new HWND(), bounds.left, bounds.top, bounds.right - bounds.left, bounds.bottom - bounds.top, SET_WINDOW_POS_FLAGS.SWP_SHOWWINDOW | SET_WINDOW_POS_FLAGS.SWP_NOACTIVATE); + } + } + + public static HWND Find(string windowName) => PInvoke.FindWindow(windowName, null); + + public static HWND Find(string windowName, HWND parentHandle) + { + return PInvoke.FindWindowEx(parentHandle, new HWND(), windowName, null); + } + + public static unsafe bool TryGetBounds(IntPtr handle, out RECT rect) + { + fixed (RECT* lpRectLocal = &rect) + { + return PInvoke.GetWindowRect(new HWND(handle), lpRectLocal); + } + } + } +} \ No newline at end of file diff --git a/TheXamlGuy.TaskbarGroup.Core/WndProc.cs b/TheXamlGuy.TaskbarGroup.Core/WndProc.cs new file mode 100644 index 0000000..73aff14 --- /dev/null +++ b/TheXamlGuy.TaskbarGroup.Core/WndProc.cs @@ -0,0 +1,4 @@ +namespace TheXamlGuy.TaskbarGroup.Core +{ + public record WndProc(uint Message, uint WParam, uint LParam); +} \ No newline at end of file diff --git a/TheXamlGuy.TaskbarGroup.Core/WndProcMessages.cs b/TheXamlGuy.TaskbarGroup.Core/WndProcMessages.cs new file mode 100644 index 0000000..92f0a48 --- /dev/null +++ b/TheXamlGuy.TaskbarGroup.Core/WndProcMessages.cs @@ -0,0 +1,14 @@ +namespace TheXamlGuy.TaskbarGroup.Core +{ + internal enum WndProcMessages + { + WM_LBUTTONUP = 0x0202, + WM_MBUTTONUP = 0x0208, + WM_RBUTTONUP = 0x0205, + WM_MOUSEMOVE = 0x0200, + WM_SETTINGCHANGE = 0x001A, + WM_MBUTTONDOWN = 0x0207, + WM_LBUTTONDOWN = 0x0201, + WM_RBUTTONDOWN = 0x0204 + } +} diff --git a/TheXamlGuy.TaskbarGroup.Core/WndProcMonitor.cs b/TheXamlGuy.TaskbarGroup.Core/WndProcMonitor.cs new file mode 100644 index 0000000..2722b1c --- /dev/null +++ b/TheXamlGuy.TaskbarGroup.Core/WndProcMonitor.cs @@ -0,0 +1,68 @@ +using Windows.Win32; +using Windows.Win32.Foundation; +using Windows.Win32.Graphics.Gdi; +using Windows.Win32.UI.WindowsAndMessaging; + +namespace TheXamlGuy.TaskbarGroup.Core +{ + public class WndProcMonitor : IWndProcMonitor + { + private WNDPROC? handler; + private readonly IMessenger messenger; + + public WndProcMonitor(IMessenger messenger) + { + this.messenger = messenger; + } + + public IntPtr Handle { get; private set; } + + public void Dispose() + { + PInvoke.DestroyWindow((HWND)Handle); + } + + private unsafe void InitializeWndProc() + { + var windowName = Guid.NewGuid().ToString(); + handler = Wndproc; + + WNDCLASSW wndProcWindow; + + wndProcWindow.style = 0; + wndProcWindow.lpfnWndProc = handler; + wndProcWindow.cbClsExtra = 0; + wndProcWindow.cbWndExtra = 0; + wndProcWindow.hInstance = new HINSTANCE(); + wndProcWindow.hIcon = new HICON(); + wndProcWindow.hCursor = new HCURSOR(); + wndProcWindow.hbrBackground = new HBRUSH(); + + fixed (char* menuName = "") + { + wndProcWindow.lpszMenuName = new PCWSTR(menuName); + } + + fixed (char* className = windowName) + { + wndProcWindow.lpszClassName = new PCWSTR(className); + } + + PInvoke.RegisterClass(wndProcWindow); + Handle = PInvoke.CreateWindowEx(0, wndProcWindow.lpszClassName, new PCWSTR(), 0, 0, 0, 0, 0, new HWND(), + new HMENU(), + new HINSTANCE()); + } + + private LRESULT Wndproc(HWND param0, uint param1, WPARAM param2, LPARAM param3) + { + messenger.Send(new WndProc(param1, (uint)param2.Value, (uint)param3.Value)); + return PInvoke.DefWindowProc(param0, param1, param2, param3); + } + + public void Initialize() + { + InitializeWndProc(); + } + } +} \ No newline at end of file diff --git a/TheXamlGuy.TaskbarGroup.Flyout.Foundation/DataTemplateFactory.cs b/TheXamlGuy.TaskbarGroup.Flyout.Foundation/DataTemplateFactory.cs new file mode 100644 index 0000000..9161ef9 --- /dev/null +++ b/TheXamlGuy.TaskbarGroup.Flyout.Foundation/DataTemplateFactory.cs @@ -0,0 +1,44 @@ +using System; +using TheXamlGuy.TaskbarGroup.Core; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Markup; +using System.Reflection; + +namespace TheXamlGuy.TaskbarGroup.Flyout.Foundation +{ + public class DataTemplateFactory : IDataTemplateFactory + { + private readonly IDataTemplateCollection datatemplateCollection; + + public DataTemplateFactory(IDataTemplateCollection datatemplateCollection) + { + this.datatemplateCollection = datatemplateCollection; + } + + public virtual DataTemplate Create(Type dataType) + { + if (dataType is null) throw new ArgumentNullException(nameof(dataType)); + + if (!datatemplateCollection.TryGetValue(dataType, out Type viewType)) + { + var assembly = dataType.GetTypeInfo().Assembly; + viewType = Type.GetType($"{dataType.FullName?.Replace("ViewModel", "View")}, {assembly.FullName}"); + } + + if (viewType is not null) + { + var xaml = $"" + + $"" + + ""; + + return (DataTemplate)XamlReader.Load(xaml); + } + + return new DataTemplate(); + } + } +} diff --git a/TheXamlGuy.TaskbarGroup.Flyout.Foundation/DropTarget.cs b/TheXamlGuy.TaskbarGroup.Flyout.Foundation/DropTarget.cs new file mode 100644 index 0000000..33a6c74 --- /dev/null +++ b/TheXamlGuy.TaskbarGroup.Flyout.Foundation/DropTarget.cs @@ -0,0 +1,61 @@ +using System; +using System.Collections.Generic; +using Windows.ApplicationModel.DataTransfer; +using Windows.Storage; +using Windows.UI.Xaml; + +namespace TheXamlGuy.TaskbarGroup.Flyout.Foundation +{ + public class DropTarget + { + public void Initialize(UIElement target) + { + target.DragOver += OnDragOver; + target.Drop += OnDrop; + } + + private async void OnDrop(object sender, DragEventArgs args) + { + if (args.DataView.Contains(StandardDataFormats.StorageItems)) + { + var items = await args.DataView.GetStorageItemsAsync(); + if (items.Count > 0) + { + foreach (var storageItem in items) + { + if (storageItem is StorageFile storageFile) + { + if (storageFile.Path is { Length: > 0 }) + { + + } + else + { + var properties = await storageFile.Properties.RetrievePropertiesAsync(new List + { + "System.AppUserModel.ID" + }); + + var appUserModelId = properties["System.AppUserModel.ID"]; + if (appUserModelId is not null) + { + + } + } + } + + if (storageItem is StorageFolder storageFolder) + { + + } + } + } + } + } + + private void OnDragOver(object sender, DragEventArgs args) + { + args.AcceptedOperation = DataPackageOperation.Link; + } + } +} diff --git a/TheXamlGuy.TaskbarGroup.Flyout.Foundation/IBindViewModelExtensions.cs b/TheXamlGuy.TaskbarGroup.Flyout.Foundation/IBindViewModelExtensions.cs new file mode 100644 index 0000000..28a00a7 --- /dev/null +++ b/TheXamlGuy.TaskbarGroup.Flyout.Foundation/IBindViewModelExtensions.cs @@ -0,0 +1,37 @@ +using TheXamlGuy.TaskbarGroup.Core; +using Windows.UI.Xaml; + +namespace TheXamlGuy.TaskbarGroup.Flyout.Foundation +{ + public static class IBindViewModelExtensions + { + public static void Bind(this IBindViewModel view) where TViewModel : class + { + if (view is FrameworkElement frameworkElement) + { + void DataContextChanged(FrameworkElement sender, DataContextChangedEventArgs args) + { + var viewModelProperty = view.GetType().GetProperty("ViewModel"); + if (viewModelProperty is not null) + { + viewModelProperty.SetMethod.Invoke(sender, new[] { sender.DataContext }); + } + } + + frameworkElement.DataContextChanged += DataContextChanged; + } + } + + public static void Bind(this IBindViewModel view, object viewModel) where TViewModel : class + { + if (view is FrameworkElement frameworkElement) + { + var viewModelProperty = view.GetType().GetProperty("ViewModel"); + if (viewModelProperty is not null) + { + viewModelProperty.SetValue(frameworkElement, viewModel); + } + } + } + } +} diff --git a/TheXamlGuy.TaskbarGroup.Flyout.Foundation/IDataTemplateFactory.cs b/TheXamlGuy.TaskbarGroup.Flyout.Foundation/IDataTemplateFactory.cs new file mode 100644 index 0000000..33d5c64 --- /dev/null +++ b/TheXamlGuy.TaskbarGroup.Flyout.Foundation/IDataTemplateFactory.cs @@ -0,0 +1,10 @@ +using System; +using Windows.UI.Xaml; + +namespace TheXamlGuy.TaskbarGroup.Flyout.Foundation +{ + public interface IDataTemplateFactory + { + DataTemplate Create(Type type); + } +} diff --git a/TheXamlGuy.TaskbarGroup.Flyout.Foundation/IServiceCollectionExtensions.cs b/TheXamlGuy.TaskbarGroup.Flyout.Foundation/IServiceCollectionExtensions.cs new file mode 100644 index 0000000..7f6238c --- /dev/null +++ b/TheXamlGuy.TaskbarGroup.Flyout.Foundation/IServiceCollectionExtensions.cs @@ -0,0 +1,18 @@ +using Microsoft.Extensions.DependencyInjection; +using System; +using System.Collections.Generic; +using TheXamlGuy.TaskbarGroup.Core; + +namespace TheXamlGuy.TaskbarGroup.Flyout.Foundation +{ + public static class IServiceCollectionExtensions + { + public static IServiceCollection AddRequiredFlyoutFoundation(this IServiceCollection serviceCollection) + { + return serviceCollection + .AddSingleton() + .AddSingleton(new DataTemplateCollection(new Dictionary())) + .AddSingleton(); + } + } +} diff --git a/TheXamlGuy.TaskbarGroup.Flyout.Foundation/IWindowPrivate.cs b/TheXamlGuy.TaskbarGroup.Flyout.Foundation/IWindowPrivate.cs new file mode 100644 index 0000000..d8d99d7 --- /dev/null +++ b/TheXamlGuy.TaskbarGroup.Flyout.Foundation/IWindowPrivate.cs @@ -0,0 +1,24 @@ +using System; +using System.Runtime.InteropServices; +using Windows.Foundation; +using Windows.UI.Xaml; + +//PLACEHOLDER - Replace with the *correct* GUID. I haven't found any hint for this, yet. +[ComImport, Guid("15645012-8F3F-5090-B584-DF078FCC509A"), InterfaceType(ComInterfaceType.InterfaceIsIInspectable)] +public interface IAtlasRequestCallback +{ + bool AtlasRequest(uint width, uint height, Windows.Graphics.DirectX.DirectXPixelFormat pixelFormat); +} + +[ComImport, Guid("06636C29-5A17-458D-8EA2-2422D997A922"), InterfaceType(ComInterfaceType.InterfaceIsIInspectable)] +public interface IWindowPrivate +{ + bool TransparentBackground { get; set; } + void Show(); + void Hide(); + void MoveWindow(int x, int y, int width, int height); + void SetAtlasSizeHint(uint width, uint height); + void ReleaseGraphicsDeviceOnSuspend(bool enable); + void SetAtlasRequestCallback(IAtlasRequestCallback callback); + Rect GetWindowContentBoundsForElement(DependencyObject element); +} \ No newline at end of file diff --git a/TheXamlGuy.TaskbarGroup.Flyout.Foundation/Properties/AssemblyInfo.cs b/TheXamlGuy.TaskbarGroup.Flyout.Foundation/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..e59e4c9 --- /dev/null +++ b/TheXamlGuy.TaskbarGroup.Flyout.Foundation/Properties/AssemblyInfo.cs @@ -0,0 +1,29 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("TheXamlGuy.TaskbarGroup.Flyout.Foundation")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("TheXamlGuy.TaskbarGroup.Flyout.Foundation")] +[assembly: AssemblyCopyright("Copyright © 2022")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] +[assembly: ComVisible(false)] \ No newline at end of file diff --git a/TheXamlGuy.TaskbarGroup.Flyout.Foundation/Properties/TheXamlGuy.TaskbarGroup.Flyout.Foundation.rd.xml b/TheXamlGuy.TaskbarGroup.Flyout.Foundation/Properties/TheXamlGuy.TaskbarGroup.Flyout.Foundation.rd.xml new file mode 100644 index 0000000..7801bd7 --- /dev/null +++ b/TheXamlGuy.TaskbarGroup.Flyout.Foundation/Properties/TheXamlGuy.TaskbarGroup.Flyout.Foundation.rd.xml @@ -0,0 +1,33 @@ + + + + + + + + + diff --git a/TheXamlGuy.TaskbarGroup.Flyout.Foundation/TemplateSelector.cs b/TheXamlGuy.TaskbarGroup.Flyout.Foundation/TemplateSelector.cs new file mode 100644 index 0000000..20380bb --- /dev/null +++ b/TheXamlGuy.TaskbarGroup.Flyout.Foundation/TemplateSelector.cs @@ -0,0 +1,31 @@ +using TheXamlGuy.TaskbarGroup.Core; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; + +namespace TheXamlGuy.TaskbarGroup.Flyout.Foundation +{ + public class TemplateSelector : DataTemplateSelector, ITemplateSelector + { + private readonly DataTemplateFactory dataTemplateFactory; + + public TemplateSelector(DataTemplateFactory dataTemplateFactory) + { + this.dataTemplateFactory = dataTemplateFactory; + } + + protected override DataTemplate SelectTemplateCore(object item, DependencyObject container) + { + if (item is not null) + { + var dataType = item.GetType(); + var dataTemplate = dataTemplateFactory.Create(dataType); + if (dataTemplate is not null) + { + return dataTemplate; + } + } + + return base.SelectTemplateCore(item, container); + } + } +} diff --git a/TheXamlGuy.TaskbarGroup.Flyout.Foundation/TheXamlGuy.TaskbarGroup.Flyout.Foundation.csproj b/TheXamlGuy.TaskbarGroup.Flyout.Foundation/TheXamlGuy.TaskbarGroup.Flyout.Foundation.csproj new file mode 100644 index 0000000..c032125 --- /dev/null +++ b/TheXamlGuy.TaskbarGroup.Flyout.Foundation/TheXamlGuy.TaskbarGroup.Flyout.Foundation.csproj @@ -0,0 +1,160 @@ + + + + + Debug + AnyCPU + {35B035A4-E21B-4379-936B-6DEDA31AF860} + Library + Properties + TheXamlGuy.TaskbarGroup.Flyout.Foundation + TheXamlGuy.TaskbarGroup.Flyout.Foundation + en-US + UAP + 10.0.19041.0 + 10.0.19041.0 + 14 + 512 + {A5A43C5B-DE2A-4C0C-9213-0A381AF9435A};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + 10.0 + enable + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE;NETFX_CORE;WINDOWS_UWP + prompt + 4 + + + x86 + true + bin\x86\Debug\ + DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP + ;2008 + full + false + prompt + + + x86 + bin\x86\Release\ + TRACE;NETFX_CORE;WINDOWS_UWP + true + ;2008 + pdbonly + false + prompt + + + ARM + true + bin\ARM\Debug\ + DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP + ;2008 + full + false + prompt + + + ARM + bin\ARM\Release\ + TRACE;NETFX_CORE;WINDOWS_UWP + true + ;2008 + pdbonly + false + prompt + + + ARM64 + true + bin\ARM64\Debug\ + DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP + ;2008 + full + false + prompt + + + ARM64 + bin\ARM64\Release\ + TRACE;NETFX_CORE;WINDOWS_UWP + true + ;2008 + pdbonly + false + prompt + + + x64 + true + bin\x64\Debug\ + DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP + ;2008 + full + false + prompt + + + x64 + bin\x64\Release\ + TRACE;NETFX_CORE;WINDOWS_UWP + true + ;2008 + pdbonly + false + prompt + + + PackageReference + + + + + + + + + + + + + + + 6.0.0 + + + 6.2.13 + + + 2.8.0-prerelease.220118001 + + + + + {40d170f4-f8c1-4fae-8a22-a22bf096bbef} + TheXamlGuy.TaskbarGroup.Core + + + + 14.0 + + + + \ No newline at end of file diff --git a/TheXamlGuy.TaskbarGroup.Flyout/App.xaml b/TheXamlGuy.TaskbarGroup.Flyout/App.xaml new file mode 100644 index 0000000..3e941fd --- /dev/null +++ b/TheXamlGuy.TaskbarGroup.Flyout/App.xaml @@ -0,0 +1,10 @@ + + + + + diff --git a/TheXamlGuy.TaskbarGroup.Flyout/App.xaml.cs b/TheXamlGuy.TaskbarGroup.Flyout/App.xaml.cs new file mode 100644 index 0000000..92c335e --- /dev/null +++ b/TheXamlGuy.TaskbarGroup.Flyout/App.xaml.cs @@ -0,0 +1,20 @@ +using Microsoft.Toolkit.Win32.UI.XamlHost; +using Windows.ApplicationModel.Activation; +using Windows.UI.Xaml; + +namespace TheXamlGuy.TaskbarGroup.Flyout +{ + public sealed partial class App : XamlApplication + { + public App() + { + Initialize(); + } + + protected override void OnLaunched(LaunchActivatedEventArgs args) + { + (Window.Current as object as IWindowPrivate).TransparentBackground = true; + base.OnLaunched(args); + } + } +} diff --git a/TheXamlGuy.TaskbarGroup.Flyout/Assets/LockScreenLogo.scale-200.png b/TheXamlGuy.TaskbarGroup.Flyout/Assets/LockScreenLogo.scale-200.png new file mode 100644 index 0000000000000000000000000000000000000000..735f57adb5dfc01886d137b4e493d7e97cf13af3 GIT binary patch literal 1430 zcmaJ>TTC2P7~aKltDttVHYH6u8Io4i*}3fO&d$gd*bA_<3j~&e7%8(eXJLfhS!M@! zKrliY>>6yT4+Kr95$!DoD(Qn-5TP|{V_KS`k~E6(LGS@#`v$hQo&^^BKsw3HIsZBT z_y6C2n`lK@apunKojRQ^(_P}Mgewt$(^BBKCTZ;*xa?J3wQ7~@S0lUvbcLeq1Bg4o zH-bvQi|wt~L7q$~a-gDFP!{&TQfc3fX*6=uHv* zT&1&U(-)L%Xp^djI2?~eBF2cxC@YOP$+9d?P&h?lPy-9M2UT9fg5jKm1t$m#iWE{M zIf%q9@;fyT?0UP>tcw-bLkz;s2LlKl2qeP0w zECS7Ate+Awk|KQ+DOk;fl}Xsy4o^CY=pwq%QAAKKl628_yNPsK>?A>%D8fQG6IgdJ ztnxttBz#NI_a@fk7SU`WtrpsfZsNs9^0(2a z@C3#YO3>k~w7?2hipBf{#b6`}Xw1hlG$yi?;1dDs7k~xDAw@jiI*+tc;t2Lflg&bM)0!Y;0_@=w%`LW^8DsYpS#-bLOklX9r?Ei}TScw|4DbpW%+7 zFgAI)f51s}{y-eWb|vrU-Ya!GuYKP)J7z#*V_k^Xo>4!1Yqj*m)x&0L^tg3GJbVAJ zJ-Pl$R=NAabouV=^z_t;^K*0AvFs!vYU>_<|I^#c?>>CR<(T?=%{;U=aI*SbZADLH z&(f2wz_Y0??Tf|g;?|1Znw6}6U43Q#qNRwv1vp9uFn1)V#*4p&%$mP9x&15^OaBiDS(XppT|z^>;B{PLVEbS3IFYV yGvCsSX*m literal 0 HcmV?d00001 diff --git a/TheXamlGuy.TaskbarGroup.Flyout/Assets/SplashScreen.scale-200.png b/TheXamlGuy.TaskbarGroup.Flyout/Assets/SplashScreen.scale-200.png new file mode 100644 index 0000000000000000000000000000000000000000..023e7f1feda78d5100569825acedfd213a0d84e9 GIT binary patch literal 7700 zcmeHLYj~4Yw%(;oxoEH#Kxq-eR|+VkP17b#Vk;?4QwkI+A{L04G+#<<(x#Un1#+h5>eArRq zTw$)ZvTWW_Y?bDho0nPVTh08+s`sp!j74rJTTtXIDww0SILedFv?sZ?yb@@}GN;#8 znk_b~Q(A0YR#uV4ef!osoV1M3;vQ8N$O|fStfgf$S5;ddUNv`tWtGjM;koG#N;7M< zP*84lnx(bn_KF&9Z5Ai$)#Cs3a|$OFw>WKCT$of*L7_CqQEinflT|W{JT+aKp-E0v zsxmYg)1(T>DROm+LN1eQw8}KCTp=C!$H7`PU!t9_Hw@TsTI2`udRZv*!a5`#A9hK6Y95L(CDUX&_@QxKV z_feX{UhA#ZWlvgpL$#w^D#lq`_A4AzDqd|Zv6y9PX&DNcN|l}_D^{q@GG&H^Pg583 z8FI6N8^H7b5WjGp;urW)d7F+_lcp%KsLX0viCmE(OHH+=%ZfD_=`voUuoUxFO^L;- z;!;2{g-YiiO6m4bs89OuF9!p{FGtH-f%8<2gY!h9s)4ciN%{Kh1+`}{^}M~+TDH9N z^Z5PlgVXMC&2&k*Hw^Lb9gny#ro$MOIxIt{+r)EA10$VR3 zanN8D{TUkl+v0CQ_>ZoHP<M-x#8@8ZiT#$Kh`(uRaX1g$Bg|qy$<#7 zSSAi{Nb8Y=lvNVeio+UGLCAtoLBfL`iOv`)yoJMDJBN>4IH@(l7YRF;61@>qq1iM9 zr@b#OC~SAxSle?5Pp8Z78{VO0YFr1x7kZU64Z23eLf2T2#6J_t;-E}DkB?NufZ0Ug zi?J&byXeaB-uTNVhuiM!UVQw}bZrJ3GtAETYp->!{q#zfN7D3AS9@Q7*V^85jGx#R z(QxYV(wW#F0XF9^^s>>H8pPlVJ>)3Oz z&_X8Sf@~?cH_O*cgi$U#`v`RRfv#y3m(ZpKk^5uLup+lVs$~}FZU$r_+}#hl%?g5m z-u-}-666ssp-xWQak~>PPy$mRc|~?pVSs1_@mBEXpPVfLF6(Ktf1S* zPPh@QZ=tFMs?LM2(5P3L2;l_6XX6s&cYsP1ip#eg0`ZEP0HGYh{UmS@o`MihLLvkU zgyAG0G`b1|qjxxh1(ODKFE%AP}Dq=3vK$P7TXP4GrM1kQ72!GUVMDl`rDC&2;TA}*nF z8$nQD&6ys_nc1*E7$*1S@R8$ymy(sQV}imGSedB@{!QR5P&N_H=-^o!?LsWs+2|mH z-e=)T^SvI)=_JIm7}j4;@*Z17=(#}m=~YF~z~CLI+vdAGlJDcdF$TM?CVI1%LhUrN zaa6DJ=Yh$)$k&Oz{-~8yw^GM^8prYxSxo zvI4k#ibryMa%%*8oI-5m61Koa_A_xg=(fwp0aBX{;X4Q;NXUhtaoJDo1>TqhWtn=_ zd5~chq#&6~c%8JZK#t_&J(9EVUU&upYeIovLt1>vaHe}UUq>#RGQj!EN#5+0@T`(@ z^g~>*c`VGRiSt;!$_4+0hk^I!@O3``5=sZ8IwlxWW7km1B&_t&E*u0_9UBa#VqwY* zz>nxv?FAsVnRaD(Bui=6i==BFUw0k4n$>`umU`F2l?7CYTD^)c2X+d9X&ddS9|gj? zM?knGkGCX&W8offw8aLC2$D{PjC3nVZwd4k?eZH8*mZ)U@3Qk8RDFOz_#WUA#vnzy zyP>KrCfKwSXea7}jgJjBc}PGY+4#6%lbZyjhy`5sZd_Vy6Wz;ixa?czkN}J9It1K6 zY!eu>|AwF^fwZlLAYyQI*lM@^>O>Iu6Vf6i>Q$?v!SeUS<{>UYMwz$*%Aq?w^`j{h z!$GZbhu=^D{&ET8;))LL%ZBDZkQqRd2;u~!d9bHGmLRhLDctNgYyjsuvoSZ#iVdoB z2!f--UUA#U;<{je#?cYt^{PIyKa%hW>}uepWMyAI{{Zo7?2>?$c9;whJae%oN|I-kpTQSx_C$Z&;f zi2i)qmEn=y4U0uvk)$m;zKfjPK@oc?I`}1Jzl$Q~aoKBd3kt7L#7gyt|A_qgz6ai< z=X%D1i!d2h?rHR^R8SUj&G||dkC?DT>{o#Yau<@uqVT{Xef&XG}5*E4aPk{}~ zplx&XhaV)&1EfI3Em;Bw#O5SV^c;{twb-1Rw)+=0!e_BLbd7tYmXCH0wrlOSS+~`7He8Iqx0{CN+DVit9;*6L~JAN zD&cyT)2?h}xnYmL?^)<7YyzZ3$FHU^Eg;DLqAV{#wv#Wj7S`Jdl1pX&{3(uZ?!uh} zDc$ZTNV*7le_W6}Hju~GMTxZQ1aWCeUc%!jv3MHAzt>Y-nQK%zfT*3ebDQA5b?iGn; zBjv3B+GhLTexd_(CzZDP4|#n5^~scvB6#Pk%Ho!kQ>yYw((Dv{6=$g3jT1!u6gORW zx5#`7Wy-ZHRa~IxGHdrp(bm%lf>2%J660nj$fCqN(epv@y!l9s7@k6EvxS{AMP>WY zX4$@F8^kayphIx-RGO$+LYl9YdoI5d|4#q9##`_F5Xnx`&GPzp2fB{-{P@ATw=X@~ z_|&^UMWAKD;jjBKTK(~o?cUFRK8EX=6>cXpfzg4ZpMB>*w_^8GSiT-Jp|xBOnzM+j z*09-@-~qJ(eqWq5@R4i^u4^{McCP(!3}C|v_WsTR*bIUxN(Nx`u##3B4{sE`Z`v8w zAwIG`?1~PkID~W{uDzmqH98Pew_1(;x2%8r^vY{)_&J2K)cN{W+h5+g)ZcjP&Ci#O zgy|8K@4kyMfwilHd&6TDlhb%++Pk!>9HRld6HT7gwyZGrxS$}CsD6`>6!!2K1@Mjf z(P0WYB7V_OFZyeWrbOFb>O54BNXf~K&?}3=^v;v_wT{DKr?jN^DtN&DXwX%u?s*c6`%8>WFz z7}YW^tp0bp^NriE)AB6M2l<7rn7fzePtR*omOevpfm9n?}2V*+0iW;S)C zhg`NAjL?D=W#k*$aR{>pGf~lD-rVtD;5jW1_*Jn1j1=es@Kcx4ySM_bwcQCT=d+DV z>Sz~L=Hj@(X%31nK$mWI@7d>}ORB`K(p=+`UD)+99YUGQc7y^bHZ1F(8|tL0 zdK*DT0kSXG_{BKTpP2*2PecdKV9;dq$^ZZDP;Nyq1kp-&GI5eAyZsK!e3V zK@rPy*{(`KIfo+lc878mDKk^V#`VT05}64kBtk%DgwLrOvLMj5-;*GNKv6c6pzMuL z6EP%ob|_0IW}lLRXCP2!9wWhEw3LA7iF#1O1mIZ@Z=6&bz41F;@S_GvYAG-#CW3z{ zP3+6vHhvP&A3$##Vo9$dT^#MoGg^|MDm=Bt1d2RRwSZ<;ZHICpLBv5Xs!D?BH^(9_ z7`H=N&^v|Z-%mP}wNzG{aiFCsRgwzwq!N6obW9+7(R; z(SZ=23`|`>qil!LMGG{_Heq!BD>(Y-zV9wD)}hz25JA37YR%39;kI4y9pgtcUass6 zP24}ZY$vvYeI`zy&)A_X#nY3017ap*0&jx|mVwyGhg3;!keU53a}Uhm3BZI$N$6Se zLWlAmy1S0xKJm4G_U@sN_Tm=`$xWJSEwKU98rZ&)1R^*$$1vA3oG#&*%SMxY_~oGP zP&PFJatFLM-Ps%84IV-+Ow)T{C7cqUAvauy4C z(FRz&?6$Rypj{xO!`y=*J5o4@U8Q-(y5(*=YoKeZ+-1YdljXxkA#B)zo=FeQH#?Le zycNUmEEHWO9a=X^pb#&cOq7-`7UA87#|S22)<7RUtZo|(zibX=w;K3qur9vy#`MNV z6UUcf9ZwEnKCCp+OoBnF@OdbvH)ANXO0o~Pi9l8=x3))}L<#vO0-~O4!~--Ket?d} zJaqsj<@CD1%S2cTW%rOP{Vto%0sGW~1RMa_j^)5nil0Yw- z0EE#bP+l4#P^%PQ+N*oxu1Zq05xZ!bXfYTg>9c{(Iw*lnjR^>kz%lAN^zFce7rppy zY8zA~3GD=A6d*hze&l4D_wA~+O!56)BZTe_rEu}Ezi<4!kG|W#amBZ5{&XS2@6R~H z{9o^y*BkH4$~yX9U&@CgbOzX1bn9xqF|zh$Dh0Y5y*E0e90*$!ObrHY3Ok0`2=O~r zCuke6KrP9KOf?V(YDsM<6pX2nVoN%M$LT^q#FmtaF?1^27F*IcNX~XRB(|hCFvdcc zc)$=S-)acdk$g4?_>jRqxpI6M3vHZk?0c^3=byamYDNf;uB{3NlKW5IhnOS3DNkMV z?tK8?kJ}pmvp%&&eTVOVjHP`q34hN1@!aK}H(K!vI`~gf|Gv+FNEQD5Yd<~yX7k_l h&G-K)@HZb3BABY{)U1?^%I#E6`MGoTtustd{~yM6srvu` literal 0 HcmV?d00001 diff --git a/TheXamlGuy.TaskbarGroup.Flyout/Assets/Square150x150Logo.scale-200.png b/TheXamlGuy.TaskbarGroup.Flyout/Assets/Square150x150Logo.scale-200.png new file mode 100644 index 0000000000000000000000000000000000000000..af49fec1a5484db1d52a7f9b5ec90a27c7030186 GIT binary patch literal 2937 zcma)84OCO-8BSud5)jwMLRVKgX(S?$n?Ld|vrsm<$CF7)&zTbyy1FE5bU`Q17MRv`9ue$;R(@8kR;#vJ*IM0>cJIAOte!d7oRgdH zd%ySjdB6L9=gX^A6)VzH7p2l@v~3zJAMw|DFy#^)F@@F*`mqUn=Il>l)8_+ab;nOW{%+iPx z+s{Eu|&pIs)Z7{La9~?xKfyl z#43?gjEL15d4WbOZo#SiP%>DB^+BcnJ=7dHEe;r#G=tuw|ka z%q@}##Uh7;tc%L_64m(kHtw74ty%BJMb)_1)#S0j`)F8_1jF7vScpsnH=0V19bO8y zR`0SjIdCUo&=>JwMQF8KHA<{ODHTiQh}0^@5QRmCA?gOH6_H3K^-_sNB^RrdNuK-R zOO*vOrKCVvDwgUck`kF(E7j{I#iiN;b*ZdCt4m@HPA`EuEqGGf4%!K<;(=I=&Vyrw z%TwcWtxa}8mCZ%Cyf&ActJ6_$ox5z6-D!0-dvnRx6t7y3d+h6QYpKWO;8OdnvERo7 zuEf>ih5`wqY)~o@OeVt-wM?Q!>QzdGRj!bz6fzYrfw$hZfAKzr2-M+D+R>}~oT574c;_3zquHcElqKIsryILt3g8n3jcMb+j?i?-L3FpZJ z2WRVBRdDPc+G5aaYg#5hpE+6nQ|(VSoxT3|biF;BUq#==-27Xi=gihDPYP$7?=9cP zYKE$jeQ|3~_L0VG-(F~2ZPyD0=k{J4Q~h(t__{-mz_w8{JDY9{`1ouzz!Vr5!ECdE z6U~O1k8c}24V7~zzXWTV-Pe4)y}wQJS&q%H5`Fo_f_JvIU489aCX$;P`u#!I-=^4ijC2{&9!O&h>mi?9oYD=GC#%)6{GzN6nQYw+Fal50!#x^asjBBR50i`+mho*ttoqV)ubM2KD9S~k7+FR4>{29?6 z{!l6kDdyTN0YJ9LgkPWeXm|gyi@zM3?0@{&pXT12w|78&W-q!RRF)&iLCEZVH<|fR zN0fr2^t8H(>L?>K#>^+jWROLral(Qy-xoBq1U7A&DV||wClb)Otd9?(gZ|8znMF}D zf<1haWz^s0qgecz;RFGt0C-B4g`jNGHsFU+;{<%t65v^sjk^h$lmWn#B0#_)9ij&d z-~lc`A)YYExi^7sBuPM^Y|wA2g*5?`K?#7tzELQYNxGo$UB$4J8RJp1k(8Jj+~hMT zlN~>M@KTTh^--8y3PK_NZ@AC!{PT=CziBzGd+wTJ^@icH!Bd}%)g8V)%K?|c&WTUk zy}qv1C%(fjRoZ4ozC3{O%@5?)XzH35zHns$pgU*Q?fj4v?fp1Qbm+j;3l;9jam9Da zXVcKjPlQ73x78QPu|Ffm6x?`~e3oD=gl=4kYK?={kD5j~QCXU)`HSdduNNENzA*2$ zOm3PzF!lN5e*06-f1Uot67wY#{o-S1!KZ7E=!~7ynnk9_iJR#kFoNbAOT#^2Gd17F zMmvU6>lndZQGd|ax9kUoXXO+$N?|j@6qpsF&_j7YXvwo_C{JpmLw5&#e6k>atv%es z5)7r*Wvv_JkUpT}M!_o!nVlEk1Zbl=a*2hQ*<|%*K1Glj^FcF`6kTzGQ3lz~2tCc@ z&x|tj;aH&1&9HwcJBcT`;{?a+pnej;M1HO(6Z{#J!cZA04hnFl;NXA+&`=7bjW_^o zfC40u3LMG?NdPtwGl>Tq6u}*QG)}-y;)lu-_>ee3kibW(69n0$0Zy!}9rQz%*v1iO zT9_H>99yIrSPYVy6^);rR}7Yo=J_T@hi+qhTZXnVWyf;JDYm5#eYLTxr*?kiNn!+Y zQ+LUkBafNJ#rH#C(?d5^;gw9o#%daEI{mA*LHPIHPU`#|H$hD zwm>0&+kahQ)E#%~k>&5@&#Vg82H?s%71=)(soi@174pi9--2{w{1$}Sz4zGn3Du&x bht0Iza^2ykEt4(epJ78uh5nDlX8(TxzDYwP literal 0 HcmV?d00001 diff --git a/TheXamlGuy.TaskbarGroup.Flyout/Assets/Square44x44Logo.scale-200.png b/TheXamlGuy.TaskbarGroup.Flyout/Assets/Square44x44Logo.scale-200.png new file mode 100644 index 0000000000000000000000000000000000000000..ce342a2ec8a61291ba76c54604aea7e9d20af11b GIT binary patch literal 1647 zcmaJ?eM}Q)7(e+G1Q(|`V9JhTI2>MkceK4;p;PR&$Pi?ejk3YQ_3o`S&|W_dsOZ8# zWPTt69g`t$ab`0cj-Y0yiBSOqmd)tG7G(}M5aP0_%&9TijB#&)I{zSE^4@#z^FF`l z`8{8`o%wlL(UI|y2!cdsuVamHH~H86F!*-15em4)NqUpCQM5?aoC_eCf@lV4wvF2a zjDQn1JBL69f&@2M3rvzJcfE!eZ8FZUBlFlC5RD)it33{mF9#B82AiyQE%w)`vlwa> zv{<1sm&kSKK$&%2jSFn7$t&P%%6Ue>R=EAnG8N7fqynWG8L3p!4801a;8{+nliO(qd(jNJ_?+9W3#hLIDLoT6~3fx9=`CC-D}-AMrpEO7HK zt3$GicGPc?GmDjy7K2P@La;eu4!$zWCZ`ym{Z$b zu-O6RM&K4JT|BIZB`E-gxqG%FzanI#+2FFmqHqXG7yxWB=w55RGOM)$xMb(>kSNR z2w=1AZi%z=AmG~yea~XaXJR!v7vLn(RUnELfiB1|6D84ICOS}^Zo2AdN}<&*h}G_u z{xZ!(%>tLT3J3<5XhWy-tg+6)0nmUUENLW8TWA{R6bgVd3X;anYFZ^IRis*_P-C-r z;i>%1^eL3UI2-{w8nuFFcs0e~7J{O2k^~Ce%+Ly4U?|=!0LH=t6()xi<^I-rs+9sF z*q{E-CxZbGPeu#a;XJwE;9S1?#R&uns>^0G3p`hEUF*v`M?@h%T%J%RChmD|EVydq zmHWh*_=S%emRC*mhxaVLzT@>Z2SX0u9v*DIJ@WC^kLVdlGV6LpK$KIrlJqc zpJ921)+3JJdTx|<`G&kXpKkjGJv=76R`yYIQ{#c-`%+`#V(7}Q;&@6U8!Td1`d;?N z_9mnI#?AA}4J!r)LN4!E-@H5eXauuB7TOawS>Y|{-P?NNx-lq+z1W-+y(;39P&&LP zL{N80?&=C*qKmdA^moMZRuPcD!B<*mq$ch=0Cnlitw#txRWhb3%TQvPqjkC`F69G4b! ze7z9MZ#+;_#l?H37UqUhDFb^l&s2{oM$3I0o^Q!yx;;V)QmCMo)Tb_ui|mit8MS?U zm##6$sZZ1$@|s%?l@>4Z<*Q}sRBSKMhb4I{e5LdEhsHIHTe8Bod5c>6QtT>$XgUBz z6MK`kO$=jmt@FqggOhJ5j~e@ygRbG;<{Vu)*+nn9aQeo0;$#j;|MS=S$&L?BeV25z xs3B`@=#`5TF{^6(A1rvdY@|-RtQ|iS5{tyX+wH?;n8E)G$kykv-D^wh{{!TZT%7;_ literal 0 HcmV?d00001 diff --git a/TheXamlGuy.TaskbarGroup.Flyout/Assets/Square44x44Logo.targetsize-24_altform-unplated.png b/TheXamlGuy.TaskbarGroup.Flyout/Assets/Square44x44Logo.targetsize-24_altform-unplated.png new file mode 100644 index 0000000000000000000000000000000000000000..f6c02ce97e0a802b85f6021e822c89f8bf57d5cd GIT binary patch literal 1255 zcmaJ>TWs4@7*5+{G#S+&C!qC#> zf>5N3P6jO*Cz>ug*(_DmW=)kea&m$gZ^+nyiF`;j%w@}y8)>p*SH}C`m?DXeieF2U zyQHecc_L%Gh!7GMt+hG06y;+|p4>m~}PjA}rKViGiEnn7G0ZO<>G|7q;2?NwGCM3s?eued6%hd$B+ z*kQJ{#~$S=DFE(%=E+UkmlEI*%3llUf~8Ja9YU1Vui0IbGBkW_gHB%Rd&!!ioX zs40O?i9I{};kle7GMvE7(rk`la=gTI)47=>%?q@^iL-nUo3}h4S}N-KHn8t5mVP8w z&bSErwp+37 zNJJ8?a|{r5Q3R0Z5s-LB1WHOwYC@7pCHWND#cL1cZ?{kJ368_*(UDWUDyb<}0y@o# zfMF016iMWPCb6obAxT$JlB6(2DrlXDTB&!0`!m??4F(qWMhjVZo?JXQmz`1*58Z=& zcDmB|S-E@j?BoFGix0flckqdS4jsPNzhfWyWIM98GxcLs89C(~dw%$_t;JjX-SD}E zfiGV;{8Q%8r}w9x>EEigW81>`kvnU@pK)4+xk9@+bNj9L!AAZ@SZ@q|)&BmY3+HZx zul~BeG4|}-;L%cHViQGQX?^zFfO0&#cHwel=d`lH9sJ-@Sl@n*(8J2>%Ac`IxyY?Q z{=GhWvC#gu-~Ia7*n{=+;qM?Ul_wy1+u7ho;=`>EwP^g~R@{unBds`!#@}tluZQpS zm)M~nYEifJWJGx?_6DcTy>#uh%>!H9=hb^(v`=m3F1{L>db=<5_tm+_&knAQ2EU$s Mu9UqpbNZeC0BbUo^Z)<= literal 0 HcmV?d00001 diff --git a/TheXamlGuy.TaskbarGroup.Flyout/Assets/StoreLogo.png b/TheXamlGuy.TaskbarGroup.Flyout/Assets/StoreLogo.png new file mode 100644 index 0000000000000000000000000000000000000000..7385b56c0e4d3c6b0efe3324aa1194157d837826 GIT binary patch literal 1451 zcmaJ>eN5D57_Z|bH;{0+1#mbl)eTU3{h)Wf7EZV?;HD@XL@{B`Ui%(2aMxQ~xdXSv z5nzWi(LW)U2=Vc-cY@s7nPt{i0hc6!7xN4NNHI#EQl>YNBy8l4%x9gr_W-j zEZMQmmTIy(>;lblRfh`dIyTgc9W5d!VP$L4(kKrN1c5G~(O_#xG zAJCNTstD^5SeXFB+&$h=ToJP2H>xr$iqPs-#O*;4(!Fjw25-!gEb*)mU}=)J;Iu>w zxK(5XoD0wrPSKQ~rbL^Cw6O_03*l*}i=ydbu7adJ6y;%@tjFeXIXT+ms30pmbOP%Q zX}S;+LBh8Tea~TSkHzvX6$rYb)+n&{kSbIqh|c7hmlxmwSiq5iVhU#iEQ<>a18|O^Sln-8t&+t`*{qBWo5M?wFM(JuimAOb5!K#D}XbslM@#1ZVz_;!9U zpfEpLAOz=0g@bd6Xj_ILi-x^!M}73h^o@}hM$1jflTs|Yuj9AL@A3<-?MV4!^4q`e z)fO@A;{9K^?W?DbnesnPr6kK>$zaKo&;FhFd(GYFCIU^T+OIMb%Tqo+P%oq(IdX7S zf6+HLO?7o0m+p>~Tp5UrXWh!UH!wZ5kv!E`_w)PTpI(#Iw{AS`gH4^b(bm^ZCq^FZ zY9DD7bH}rq9mg88+KgA$Zp!iWncuU2n1AuIa@=sWvUR-s`Qb{R*kk(SPU^`$6BXz8 zn#7yaFOIK%qGxyi`dYtm#&qqox0$h=pNi#u=M8zUG@bpiZ=3sT=1}Trr}39cC)H|v zbL?W)=&s4zrh)7>L(|cc%$1#!zfL?HjpeP%T+x_a+jZ16b^iKOHxFEX$7d|8${H-* zIrOJ5w&i$>*D>AKaIoYg`;{L@jM((Kt?$N$5OnuPqVvq**Nm}(f0wwOF%iX_Pba;V z;m@wxX&NcV3?<1+u?A{y_DIj7#m3Af1rCE)o`D&Y3}0%7E;iX1yMDiS)sh0wKi!36 zL!Wmq?P^Ku&rK~HJd97KkLTRl>ScGFYZNlYytWnhmuu|)L&ND8_PmkayQb{HOY640 bno1(wj@u8DCVuFR|31B*4ek@pZJqxCDDe1x literal 0 HcmV?d00001 diff --git a/TheXamlGuy.TaskbarGroup.Flyout/Assets/Wide310x150Logo.scale-200.png b/TheXamlGuy.TaskbarGroup.Flyout/Assets/Wide310x150Logo.scale-200.png new file mode 100644 index 0000000000000000000000000000000000000000..288995b397fdbef1fb7e85afd71445d5de1952c5 GIT binary patch literal 3204 zcmbVPeQXow8NYmBd90>}0NP?GhXW~VaeThm=a0tV#EwJMI!)6M3}|c4_Bl3=Kd>G0 z(GHx1wl<7(tP?FsOQkTilSo*iIvF%uArExJ73~P zSv1xEy!U(Wd4A9D`FQV@W3@F^qJ@PEF$@z`Z!*BbFsS(^?B zyiAzJ+q})bkgiQHWqEb*jJD-coHYr1^iocg)l!Qa{Xqs-l~6J}p-|##ZHYofskQ3$ zI0;xzXyhazBeXhIsg5A=%ufo@f)1yy&ScKS0;HF^!r_2UE^lpZEom(+@duma3awTv zCrCL-%D_SvYWIcdHkmI}#50(fkUi)Qgx!80ju>g1za^}ff>JI8Z@^-iCiaCgg@TgF z+vtE?Q9{VQUX&MW9SYYmGcxA14%N2@7FwBTD4N<(2{nWgV8$e3?-F=L^&FrtWn~(U_Q~~^uYiyeY6-KoTnfh9AWz@ zIKje0)u!_Lw)E}G!#kEfwKVdNt(UAf9*f>tEL_(=xco-T%jTi@7YlC3hs2ik%Le0H ztj}RTeCF(5mwvi3_56>-yB?l;J>-1%!9~=fs|QcNG3J~a@JCu`4SB460s0ZO+##4fFUSGLcj_ja^fL4&BKALfb#$6$O?>P@qx2Agl^x0i&ugt zsy5Pyu=()`7HRMG3IB7F1@`_ z+-!J%#i6e^U$e#+C%Q>_qVRzWRsG^W_n+@OcX@vzI&z;mzHNb!GQ?LWA(wtpqHqTM z1OFw_{Zn?fD)p)`c`kOgv{de=v@suGRqY{N^U7gI1VF3*F=obwaXI6ob5__Yn zVTguS!%(NI09J8x#AO_aW!9W7k*UvB;IWDFC3srwftr{kHj%g)fvnAm;&h_dnl~

MY- zf+K}sCe8qU6Ujs`3ua{U0Of$R_gVQBuUA za0v=mu#vIOqiiAZOr&h*$WyOw&k-xr$;G4Ixa!#TJNr>95(h>l%)PUy4p+^SgR(uR zta%k*?ny-+nAr8spEk1fo{J4i!b^Fia`N{_F6@zidA2ZTTrjl#^5Z-2KfB@Cu}l9s z(*|Z2jc?p~vn2f)3y9i*7zJV1L{$?|&q)4oaT;uXi6>1GkRXVTOzAz(RHEmr=eFIi z`}<>-Q?K0GN8!IYxeP1XKXO+jsJbp~o^);Bc;%b7Flpe7;1`Ny@3r7ZR;?R)aJt8C ziNlEC<@3f_lIV4TwV}&e;D!Ee5_|e#g0LUh=5vmYWYm7&2h*M>QPKvGh9-)wfMMW3 z8J9b%1k7dzPzO0_NGQy92BZ^FR6R~6;^6?lqO;-QUP4BY%cG%3vEhbm#>4vIhPBh3 z-+pZGjh$x%Hp{?=FHsMp0&wNPlj00us{&`1ZOZTqs8%4X&xH=UDr*xyBW(Zp&Em94 zf)ZSfn#yg0N)>!1kWdkqJ^S*z0FF5|fj&qcE#Na|%OY0$uO>!&hP+1ywfD_WXk@4J(?MBftK7>$Nvqh@tDuarN%PrTLQ2Uzysx>UV=V zk^RrDSvdQ?0;=hY67EgII-f4`t=+i*yS=Y~!XlqIy_4x&%+OdfbKOFPXS2X5%4R{N z$SQMX^AK6(fA Closed; + + public event EventHandler Opened; + + public TaskbarButtonFlyoutTemplateSettings TemplateSettings + { + get => (TaskbarButtonFlyoutTemplateSettings)GetValue(TemplateSettingsProperty); + set => SetValue(TemplateSettingsProperty, value); + } + + public bool IsOpen { get; private set; } + + public void Close() + { + if(container is not null) + { + container.Child = null; + } + } + + protected override void OnApplyTemplate() + { + container = GetTemplateChild("Container") as Border; + if (container != null) + { + child = container.Child; + container.Child = null; + } + } + + public void ShowAt(TaskbarButtonFlyoutPlacement taskbarPlacement) + { + VisualStateManager.GoToState(this, "DefaultPlacement", true); + + child.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity)); + + var width = child.DesiredSize.Width - 1; + var height = child.DesiredSize.Height - 1; + + TemplateSettings.SetValue(TaskbarButtonFlyoutTemplateSettings.HeightProperty, height); + TemplateSettings.SetValue(TaskbarButtonFlyoutTemplateSettings.WidthProperty, width); + + TemplateSettings.SetValue(TaskbarButtonFlyoutTemplateSettings.NegativeHeightProperty, -height); + TemplateSettings.SetValue(TaskbarButtonFlyoutTemplateSettings.NegativeWidthProperty, -width); + + VisualStateManager.GoToState(this, $"{taskbarPlacement}Placement", true); + + container.Child = child; + } + } +} diff --git a/TheXamlGuy.TaskbarGroup.Flyout/Controls/TaskbarButtonFlyout.xaml b/TheXamlGuy.TaskbarGroup.Flyout/Controls/TaskbarButtonFlyout.xaml new file mode 100644 index 0000000..e635fcd --- /dev/null +++ b/TheXamlGuy.TaskbarGroup.Flyout/Controls/TaskbarButtonFlyout.xaml @@ -0,0 +1,115 @@ + + + + + + + 1 + + + + + + 1 + + + + + diff --git a/TheXamlGuy.TaskbarGroup.Flyout/Controls/TaskbarButtonFlyoutPlacement.cs b/TheXamlGuy.TaskbarGroup.Flyout/Controls/TaskbarButtonFlyoutPlacement.cs new file mode 100644 index 0000000..683e402 --- /dev/null +++ b/TheXamlGuy.TaskbarGroup.Flyout/Controls/TaskbarButtonFlyoutPlacement.cs @@ -0,0 +1,10 @@ +namespace TheXamlGuy.TaskbarGroup.Flyout.Controls +{ + public enum TaskbarButtonFlyoutPlacement + { + Left, + Top, + Right, + Bottom, + } +} diff --git a/TheXamlGuy.TaskbarGroup.Flyout/Controls/TaskbarButtonFlyoutTemplateSettings.cs b/TheXamlGuy.TaskbarGroup.Flyout/Controls/TaskbarButtonFlyoutTemplateSettings.cs new file mode 100644 index 0000000..db6d602 --- /dev/null +++ b/TheXamlGuy.TaskbarGroup.Flyout/Controls/TaskbarButtonFlyoutTemplateSettings.cs @@ -0,0 +1,50 @@ +using Windows.UI.Xaml; + +namespace TheXamlGuy.TaskbarGroup.Flyout.Controls +{ + public class TaskbarButtonFlyoutTemplateSettings : DependencyObject + { + public static readonly DependencyProperty HeightProperty = + DependencyProperty.Register(nameof(Height), + typeof(double), typeof(TaskbarButtonFlyoutTemplateSettings), + new PropertyMetadata(0d)); + + public static readonly DependencyProperty NegativeHeightProperty = + DependencyProperty.Register(nameof(NegativeHeight), + typeof(double), typeof(TaskbarButtonFlyoutTemplateSettings), + new PropertyMetadata(0d)); + + public static readonly DependencyProperty NegativeWidthProperty = + DependencyProperty.Register(nameof(NegativeWidth), + typeof(double), typeof(TaskbarButtonFlyoutTemplateSettings), + new PropertyMetadata(0d)); + + public static readonly DependencyProperty WidthProperty = + DependencyProperty.Register(nameof(Width), + typeof(double), typeof(TaskbarButtonFlyoutTemplateSettings), + new PropertyMetadata(0d)); + + public double Height + { + get => (double)GetValue(HeightProperty); + set => SetValue(HeightProperty, value); + } + public double NegativeHeight + { + get => (double)GetValue(NegativeHeightProperty); + set => SetValue(NegativeHeightProperty, value); + } + + public double NegativeWidth + { + get => (double)GetValue(NegativeWidthProperty); + set => SetValue(NegativeWidthProperty, value); + } + + public double Width + { + get => (double)GetValue(WidthProperty); + set => SetValue(WidthProperty, value); + } + } +} \ No newline at end of file diff --git a/TheXamlGuy.TaskbarGroup.Flyout/Package.appxmanifest b/TheXamlGuy.TaskbarGroup.Flyout/Package.appxmanifest new file mode 100644 index 0000000..5aeb49b --- /dev/null +++ b/TheXamlGuy.TaskbarGroup.Flyout/Package.appxmanifest @@ -0,0 +1,49 @@ + + + + + + + + + + TheXamlGuy.TaskbarGroup.Flyout + Daniel Clark + Assets\StoreLogo.png + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/TheXamlGuy.TaskbarGroup.Flyout/Properties/AssemblyInfo.cs b/TheXamlGuy.TaskbarGroup.Flyout/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..f87221c --- /dev/null +++ b/TheXamlGuy.TaskbarGroup.Flyout/Properties/AssemblyInfo.cs @@ -0,0 +1,29 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("TheXamlGuy.TaskbarGroup.Flyout")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("TheXamlGuy.TaskbarGroup.Flyout")] +[assembly: AssemblyCopyright("Copyright © 2022")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] +[assembly: ComVisible(false)] \ No newline at end of file diff --git a/TheXamlGuy.TaskbarGroup.Flyout/Properties/Default.rd.xml b/TheXamlGuy.TaskbarGroup.Flyout/Properties/Default.rd.xml new file mode 100644 index 0000000..af00722 --- /dev/null +++ b/TheXamlGuy.TaskbarGroup.Flyout/Properties/Default.rd.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/TheXamlGuy.TaskbarGroup.Flyout/TheXamlGuy.TaskbarGroup.Flyout.csproj b/TheXamlGuy.TaskbarGroup.Flyout/TheXamlGuy.TaskbarGroup.Flyout.csproj new file mode 100644 index 0000000..5f559af --- /dev/null +++ b/TheXamlGuy.TaskbarGroup.Flyout/TheXamlGuy.TaskbarGroup.Flyout.csproj @@ -0,0 +1,159 @@ + + + + + Debug + x86 + {4B56828E-30D0-4EAF-89BC-480480989E6C} + AppContainerExe + Properties + TheXamlGuy.TaskbarGroup.Flyout + TheXamlGuy.TaskbarGroup.Flyout + en-US + UAP + 10.0.19041.0 + 10.0.19041.0 + 14 + 512 + {A5A43C5B-DE2A-4C0C-9213-0A381AF9435A};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + true + false + 10.0 + enable + + + true + bin\x86\Debug\ + DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP + ;2008 + full + x86 + false + prompt + true + + + bin\x86\Release\ + TRACE;NETFX_CORE;WINDOWS_UWP + true + ;2008 + pdbonly + x86 + false + prompt + true + true + + + true + bin\x64\Debug\ + DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP + ;2008 + full + x64 + false + prompt + true + + + bin\x64\Release\ + TRACE;NETFX_CORE;WINDOWS_UWP + true + ;2008 + pdbonly + x64 + false + prompt + true + true + + + PackageReference + + + + App.xaml + + + + + + + + TaskbarButtonGroupView.xaml + + + + TaskbarButtonView.xaml + + + + + + Designer + + + + + + + + + + + + + + + MSBuild:Compile + Designer + + + + + 6.2.12 + + + 6.1.3 + + + 2.8.0-prerelease.220118001 + + + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + + + {40D170F4-F8C1-4FAE-8A22-A22BF096BBEF} + TheXamlGuy.TaskbarGroup.Core + + + {35b035a4-e21b-4379-936b-6deda31af860} + TheXamlGuy.TaskbarGroup.Flyout.Foundation + + + + 14.0 + + + + \ No newline at end of file diff --git a/TheXamlGuy.TaskbarGroup.Flyout/Themes/Generic.xaml b/TheXamlGuy.TaskbarGroup.Flyout/Themes/Generic.xaml new file mode 100644 index 0000000..a3efb7a --- /dev/null +++ b/TheXamlGuy.TaskbarGroup.Flyout/Themes/Generic.xaml @@ -0,0 +1,5 @@ + + + + + diff --git a/TheXamlGuy.TaskbarGroup.Flyout/Views/TaskbarButtonGroupItemViewModel.cs b/TheXamlGuy.TaskbarGroup.Flyout/Views/TaskbarButtonGroupItemViewModel.cs new file mode 100644 index 0000000..3ebda79 --- /dev/null +++ b/TheXamlGuy.TaskbarGroup.Flyout/Views/TaskbarButtonGroupItemViewModel.cs @@ -0,0 +1,15 @@ +using TheXamlGuy.TaskbarGroup.Core; + +namespace TheXamlGuy.TaskbarGroup.Flyout +{ + public class TaskbarButtonGroupItemViewModel : ObservableViewModel + { + public TaskbarButtonGroupItemViewModel(IMessenger messenger, + IServiceFactory serviceFactory, + IDisposer disposer) : base(messenger, serviceFactory, disposer) + { + } + + public string Name { get; set; } + } +} diff --git a/TheXamlGuy.TaskbarGroup.Flyout/Views/TaskbarButtonGroupView.xaml b/TheXamlGuy.TaskbarGroup.Flyout/Views/TaskbarButtonGroupView.xaml new file mode 100644 index 0000000..5c85f21 --- /dev/null +++ b/TheXamlGuy.TaskbarGroup.Flyout/Views/TaskbarButtonGroupView.xaml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/TheXamlGuy.TaskbarGroup.Flyout/Views/TaskbarButtonGroupView.xaml.cs b/TheXamlGuy.TaskbarGroup.Flyout/Views/TaskbarButtonGroupView.xaml.cs new file mode 100644 index 0000000..4ef5b85 --- /dev/null +++ b/TheXamlGuy.TaskbarGroup.Flyout/Views/TaskbarButtonGroupView.xaml.cs @@ -0,0 +1,16 @@ +using TheXamlGuy.TaskbarGroup.Core; +using TheXamlGuy.TaskbarGroup.Flyout.Foundation; + +namespace TheXamlGuy.TaskbarGroup.Flyout +{ + public sealed partial class TaskbarButtonGroupView : IBindViewModel + { + public TaskbarButtonGroupView() + { + InitializeComponent(); + this.Bind(); + } + + public TaskbarButtonGroupViewModel ViewModel { get; set; } + } +} diff --git a/TheXamlGuy.TaskbarGroup.Flyout/Views/TaskbarButtonGroupViewModel.cs b/TheXamlGuy.TaskbarGroup.Flyout/Views/TaskbarButtonGroupViewModel.cs new file mode 100644 index 0000000..a2f689d --- /dev/null +++ b/TheXamlGuy.TaskbarGroup.Flyout/Views/TaskbarButtonGroupViewModel.cs @@ -0,0 +1,23 @@ +using CommunityToolkit.Mvvm.ComponentModel; +using TheXamlGuy.TaskbarGroup.Core; + +namespace TheXamlGuy.TaskbarGroup.Flyout +{ + public partial class TaskbarButtonGroupViewModel : ObservableCollectionViewModel + { + public TaskbarButtonGroupViewModel(IMessenger messenger, + IServiceFactory serviceFactory, + IDisposer disposer) : base(messenger, serviceFactory, disposer) + { + Register(OnFileDropped); + } + + [ObservableProperty] + private string name = "hello"; + + private void OnFileDropped(FileDropped args) + { + Add(); + } + } +} diff --git a/TheXamlGuy.TaskbarGroup.Flyout/Views/TaskbarButtonView.xaml b/TheXamlGuy.TaskbarGroup.Flyout/Views/TaskbarButtonView.xaml new file mode 100644 index 0000000..5ef39b2 --- /dev/null +++ b/TheXamlGuy.TaskbarGroup.Flyout/Views/TaskbarButtonView.xaml @@ -0,0 +1,13 @@ + + + + + diff --git a/TheXamlGuy.TaskbarGroup.Flyout/Views/TaskbarButtonView.xaml.cs b/TheXamlGuy.TaskbarGroup.Flyout/Views/TaskbarButtonView.xaml.cs new file mode 100644 index 0000000..639c605 --- /dev/null +++ b/TheXamlGuy.TaskbarGroup.Flyout/Views/TaskbarButtonView.xaml.cs @@ -0,0 +1,14 @@ +using TheXamlGuy.TaskbarGroup.Core; + +namespace TheXamlGuy.TaskbarGroup.Flyout +{ + public sealed partial class TaskbarButtonView : IBindViewModel + { + public TaskbarButtonView() + { + InitializeComponent(); + } + + public TaskbarButtonViewModel ViewModel { get; set; } + } +} diff --git a/TheXamlGuy.TaskbarGroup.Flyout/Views/TaskbarButtonViewModel.cs b/TheXamlGuy.TaskbarGroup.Flyout/Views/TaskbarButtonViewModel.cs new file mode 100644 index 0000000..4f5b305 --- /dev/null +++ b/TheXamlGuy.TaskbarGroup.Flyout/Views/TaskbarButtonViewModel.cs @@ -0,0 +1,22 @@ +using TheXamlGuy.TaskbarGroup.Core; +using TheXamlGuy.TaskbarGroup.Flyout.Foundation; + +namespace TheXamlGuy.TaskbarGroup.Flyout +{ + public class TaskbarButtonViewModel : ObservableViewModel + { + public TaskbarButtonViewModel(IMessenger messenger, + IServiceFactory serviceFactory, + IDisposer disposer, + TemplateSelector templateSelector, + TaskbarButtonGroupViewModel taskbarButtonGroupViewModel) : base(messenger, serviceFactory, disposer) + { + TemplateSelector = templateSelector; + TaskbarButtonGroupViewModel = taskbarButtonGroupViewModel; + } + + public TemplateSelector TemplateSelector { get; } + + public TaskbarButtonGroupViewModel TaskbarButtonGroupViewModel { get; } + } +} diff --git a/TheXamlGuy.TaskbarGroup.Foundation/DataTemplateFactory.cs b/TheXamlGuy.TaskbarGroup.Foundation/DataTemplateFactory.cs new file mode 100644 index 0000000..c224c36 --- /dev/null +++ b/TheXamlGuy.TaskbarGroup.Foundation/DataTemplateFactory.cs @@ -0,0 +1,41 @@ +using System.Reflection; +using System.Windows; +using TheXamlGuy.TaskbarGroup.Core; + +namespace TheXamlGuy.TaskbarGroup.Foundation +{ + public class DataTemplateFactory : IDataTemplateFactory + { + private readonly IDataTemplateCollection datatemplateCollection; + private readonly IServiceFactory serviceFactory; + + public DataTemplateFactory(IDataTemplateCollection datatemplateCollection, + IServiceFactory serviceFactory) + { + this.datatemplateCollection = datatemplateCollection; + this.serviceFactory = serviceFactory; + } + + public virtual DataTemplate? Create(Type dataType) + { + if (dataType is null) throw new ArgumentNullException(nameof(dataType)); + + if (!datatemplateCollection.TryGetValue(dataType, out Type? viewType)) + { + var assembly = dataType.GetTypeInfo().Assembly; + viewType = Type.GetType($"{dataType.FullName?.Replace("ViewModel", "View")}, {assembly.FullName}"); + } + + if (viewType is not null) + { + var view = serviceFactory.Create(viewType); + if (view is not null) + { + return TemplateGenerator.CreateDataTemplate(() => view); + } + } + + return null; + } + } +} diff --git a/TheXamlGuy.TaskbarGroup.Foundation/DispatcherTimer.cs b/TheXamlGuy.TaskbarGroup.Foundation/DispatcherTimer.cs new file mode 100644 index 0000000..8e1bb15 --- /dev/null +++ b/TheXamlGuy.TaskbarGroup.Foundation/DispatcherTimer.cs @@ -0,0 +1,37 @@ +using System; +using TheXamlGuy.TaskbarGroup.Core; + +namespace TheXamlGuy.TaskbarGroup.Foundation +{ + public class DispatcherTimer : IDispatcherTimer + { + private readonly System.Windows.Threading.DispatcherTimer timer; + private readonly Action actionDelegate; + + public DispatcherTimer(Action actionDelegate, TimeSpan interval) + { + timer = new System.Windows.Threading.DispatcherTimer + { + Interval = interval + }; + + timer.Tick += OnTick; + this.actionDelegate = actionDelegate; + } + + private void OnTick(object? sender, EventArgs args) + { + actionDelegate?.Invoke(); + } + + public void Start() + { + timer.Start(); + } + + public void Stop() + { + timer.Stop(); + } + } +} diff --git a/TheXamlGuy.TaskbarGroup.Foundation/DispatcherTimerFactory.cs b/TheXamlGuy.TaskbarGroup.Foundation/DispatcherTimerFactory.cs new file mode 100644 index 0000000..8f3675f --- /dev/null +++ b/TheXamlGuy.TaskbarGroup.Foundation/DispatcherTimerFactory.cs @@ -0,0 +1,13 @@ +using System; +using TheXamlGuy.TaskbarGroup.Core; + +namespace TheXamlGuy.TaskbarGroup.Foundation +{ + public class DispatcherTimerFactory : IDispatcherTimerFactory + { + public IDispatcherTimer Create(Action actionDelegate, TimeSpan interval) + { + return new DispatcherTimer(actionDelegate, interval); + } + } +} diff --git a/TheXamlGuy.TaskbarGroup.Foundation/FileDropTarget.cs b/TheXamlGuy.TaskbarGroup.Foundation/FileDropTarget.cs new file mode 100644 index 0000000..3e05a4b --- /dev/null +++ b/TheXamlGuy.TaskbarGroup.Foundation/FileDropTarget.cs @@ -0,0 +1,82 @@ +using System.Diagnostics; +using System.Windows; +using System.Windows.Shell; +using TheXamlGuy.TaskbarGroup.Core; +using Microsoft.WindowsAPICodePack.Shell; + +namespace TheXamlGuy.TaskbarGroup.Foundation +{ + public class FileDropTarget : IDropTarget + { + private UIElement? target; + private readonly IMessenger messenger; + + public FileDropTarget(IMessenger messenger) + { + this.messenger = messenger; + } + + public void Register(UIElement target) + { + if (this.target is not null) + { + target.DragOver -= OnDragOver; + target.DragEnter -= OnDragEnter; + target.Drop -= OnDrop; + } + + this.target = target; + + target.DragOver += OnDragOver; + target.DragEnter += OnDragEnter; + target.Drop += OnDrop; + } + + private void OnDrop(object sender, DragEventArgs args) + { + String[] fileName = (String[])args.Data.GetFormats(); + + var ddd = ShellObjectCollection.FromDataObject((System.Runtime.InteropServices.ComTypes.IDataObject)args.Data); + + //args.Handled = true; + //var fileName = GetFileName(args.Data); + //messenger.Publish(); + + } + + private string GetFileName(IDataObject data) + { + var filenames = (string[])data.GetData(DataFormats.FileDrop); + return filenames[0]; + } + + private bool IsFileDrop(IDataObject data) + { + return data.GetDataPresent(DataFormats.FileDrop); + } + + private void OnDragEnter(object sender, DragEventArgs args) + { + if (IsFileDrop(args.Data)) + { + args.Effects = DragDropEffects.Link; + } + else + { + args.Effects = DragDropEffects.None; + } + } + + private void OnDragOver(object sender, DragEventArgs args) + { + if (IsFileDrop(args.Data)) + { + args.Effects = DragDropEffects.Link; + } + else + { + args.Effects = DragDropEffects.None; + } + } + } +} diff --git a/TheXamlGuy.TaskbarGroup.Foundation/IDataTemplateFactory.cs b/TheXamlGuy.TaskbarGroup.Foundation/IDataTemplateFactory.cs new file mode 100644 index 0000000..2178988 --- /dev/null +++ b/TheXamlGuy.TaskbarGroup.Foundation/IDataTemplateFactory.cs @@ -0,0 +1,9 @@ +using System.Windows; + +namespace TheXamlGuy.TaskbarGroup.Foundation +{ + public interface IDataTemplateFactory + { + DataTemplate? Create(Type type); + } +} diff --git a/TheXamlGuy.TaskbarGroup.Foundation/IServiceCollectionExtensions.cs b/TheXamlGuy.TaskbarGroup.Foundation/IServiceCollectionExtensions.cs new file mode 100644 index 0000000..76b01c2 --- /dev/null +++ b/TheXamlGuy.TaskbarGroup.Foundation/IServiceCollectionExtensions.cs @@ -0,0 +1,18 @@ +using Microsoft.Extensions.DependencyInjection; +using TheXamlGuy.TaskbarGroup.Core; + +namespace TheXamlGuy.TaskbarGroup.Foundation +{ + public static class IServiceCollectionExtensions + { + public static IServiceCollection AddRequiredFoundation(this IServiceCollection serviceCollection) + { + return serviceCollection + .AddSingleton() + .AddSingleton(new DataTemplateCollection(new Dictionary())) + .AddSingleton() + .AddSingleton() + .AddTransient(); + } + } +} diff --git a/TheXamlGuy.TaskbarGroup.Foundation/TemplateGenerator.cs b/TheXamlGuy.TaskbarGroup.Foundation/TemplateGenerator.cs new file mode 100644 index 0000000..a44cac6 --- /dev/null +++ b/TheXamlGuy.TaskbarGroup.Foundation/TemplateGenerator.cs @@ -0,0 +1,20 @@ +using System.Windows; + +namespace TheXamlGuy.TaskbarGroup.Foundation +{ + public static class TemplateGenerator + { + public static DataTemplate CreateDataTemplate(Func factory) + { + var frameworkElementFactory = new FrameworkElementFactory(typeof(TemplateGeneratorControl)); + frameworkElementFactory.SetValue(TemplateGeneratorControl.FactoryProperty, factory); + + var dataTemplate = new DataTemplate(typeof(DependencyObject)) + { + VisualTree = frameworkElementFactory + }; + + return dataTemplate; + } + } +} diff --git a/TheXamlGuy.TaskbarGroup.Foundation/TemplateGeneratorControl.cs b/TheXamlGuy.TaskbarGroup.Foundation/TemplateGeneratorControl.cs new file mode 100644 index 0000000..31daccd --- /dev/null +++ b/TheXamlGuy.TaskbarGroup.Foundation/TemplateGeneratorControl.cs @@ -0,0 +1,22 @@ +using System.Windows; +using System.Windows.Controls; + +namespace TheXamlGuy.TaskbarGroup.Foundation +{ + internal sealed class TemplateGeneratorControl : ContentControl + { + internal static readonly DependencyProperty FactoryProperty = + DependencyProperty.Register("Factory", typeof(Func), + typeof(TemplateGeneratorControl), new PropertyMetadata(null, + OnFactoryPropertyChanged)); + + private static void OnFactoryPropertyChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs args) + { + if (dependencyObject is TemplateGeneratorControl sender && args.NewValue is not null) + { + var factory = (Func)args.NewValue; + sender.Content = factory(); + } + } + } +} diff --git a/TheXamlGuy.TaskbarGroup.Foundation/TemplateSelector.cs b/TheXamlGuy.TaskbarGroup.Foundation/TemplateSelector.cs new file mode 100644 index 0000000..4178ec7 --- /dev/null +++ b/TheXamlGuy.TaskbarGroup.Foundation/TemplateSelector.cs @@ -0,0 +1,32 @@ +using System.Windows; +using System.Windows.Controls; +using TheXamlGuy.TaskbarGroup.Core; + +namespace TheXamlGuy.TaskbarGroup.Foundation +{ + public class TemplateSelector : DataTemplateSelector, ITemplateSelector + { + private readonly DataTemplateFactory dataTemplateFactory; + + public TemplateSelector(DataTemplateFactory dataTemplateFactory) + { + this.dataTemplateFactory = dataTemplateFactory; + } + + public override DataTemplate SelectTemplate(object item, DependencyObject container) + { + if (item is not null) + { + var dataType = item.GetType(); + var dataTemplate = dataTemplateFactory.Create(dataType); + if (dataTemplate is not null) + { + dataTemplate.Seal(); + return dataTemplate; + } + } + + return base.SelectTemplate(item, container); + } + } +} diff --git a/TheXamlGuy.TaskbarGroup.Foundation/TheXamlGuy.TaskbarGroup.Foundation.csproj b/TheXamlGuy.TaskbarGroup.Foundation/TheXamlGuy.TaskbarGroup.Foundation.csproj new file mode 100644 index 0000000..b8b501a --- /dev/null +++ b/TheXamlGuy.TaskbarGroup.Foundation/TheXamlGuy.TaskbarGroup.Foundation.csproj @@ -0,0 +1,19 @@ + + + netcoreapp3.1 + true + enable + enable + 10.0 + x64;x86 + + + + + + + + + + + diff --git a/TheXamlGuy.TaskbarGroup.Foundation/VisualExtensions.cs b/TheXamlGuy.TaskbarGroup.Foundation/VisualExtensions.cs new file mode 100644 index 0000000..d96436e --- /dev/null +++ b/TheXamlGuy.TaskbarGroup.Foundation/VisualExtensions.cs @@ -0,0 +1,26 @@ +using System.Windows; +using System.Windows.Media; + +namespace TheXamlGuy.TaskbarGroup.Foundation +{ + public static class VisualExtensions + { + private static Matrix GetDpi(this Visual visual) + { + var source = PresentationSource.FromVisual(visual); + if (source?.CompositionTarget != null) return (Matrix)source?.CompositionTarget.TransformToDevice; + + return default; + } + + public static double DpiY(this Visual visual) + { + return GetDpi(visual).M22; + } + + public static double DpiX(this Visual visual) + { + return GetDpi(visual).M11; + } + } +} diff --git a/TheXamlGuy.TaskbarGroup.Foundation/WindowExtensions.cs b/TheXamlGuy.TaskbarGroup.Foundation/WindowExtensions.cs new file mode 100644 index 0000000..7624752 --- /dev/null +++ b/TheXamlGuy.TaskbarGroup.Foundation/WindowExtensions.cs @@ -0,0 +1,29 @@ +using System; +using System.Windows; +using System.Windows.Interop; +using TheXamlGuy.TaskbarGroup.Core; +using Windows.Win32.Foundation; + +namespace TheXamlGuy.TaskbarGroup.Foundation +{ + public static class WindowExtensions + { + public static IntPtr GetHandle(this Window window) + { + return new WindowInteropHelper(window).Handle; + } + + public static void MoveAndResize(this Window window, int x, int y, int width, int height) + { + var handle = window.GetHandle(); + WindowHelper.MoveAndResize(new HWND(handle), x, y, width, height); + } + + public static void BringToForeground(this Window window) + { + var handle = window.GetHandle(); + WindowHelper.BringToForeground(new HWND(handle)); + } + + } +} diff --git a/TheXamlGuy.TaskbarGroup.sln b/TheXamlGuy.TaskbarGroup.sln new file mode 100644 index 0000000..b30e907 --- /dev/null +++ b/TheXamlGuy.TaskbarGroup.sln @@ -0,0 +1,147 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31903.59 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TheXamlGuy.TaskbarGroup.Foundation", "TheXamlGuy.TaskbarGroup.Foundation\TheXamlGuy.TaskbarGroup.Foundation.csproj", "{C632FABF-A191-406F-962B-CBE1F9B18A12}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TheXamlGuy.TaskbarGroup.Core", "TheXamlGuy.TaskbarGroup.Core\TheXamlGuy.TaskbarGroup.Core.csproj", "{40D170F4-F8C1-4FAE-8A22-A22BF096BBEF}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TheXamlGuy.TaskbarGroup.Flyout", "TheXamlGuy.TaskbarGroup.Flyout\TheXamlGuy.TaskbarGroup.Flyout.csproj", "{4B56828E-30D0-4EAF-89BC-480480989E6C}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TheXamlGuy.TaskbarGroup", "TheXamlGuy.TaskbarGroup\TheXamlGuy.TaskbarGroup.csproj", "{1F70F55F-C04F-4FF7-8824-EF06AC4595BA}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TheXamlGuy.TaskbarGroup.Flyout.Foundation", "TheXamlGuy.TaskbarGroup.Flyout.Foundation\TheXamlGuy.TaskbarGroup.Flyout.Foundation.csproj", "{35B035A4-E21B-4379-936B-6DEDA31AF860}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|ARM = Debug|ARM + Debug|ARM64 = Debug|ARM64 + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|ARM = Release|ARM + Release|ARM64 = Release|ARM64 + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {C632FABF-A191-406F-962B-CBE1F9B18A12}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C632FABF-A191-406F-962B-CBE1F9B18A12}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C632FABF-A191-406F-962B-CBE1F9B18A12}.Debug|ARM.ActiveCfg = Debug|Any CPU + {C632FABF-A191-406F-962B-CBE1F9B18A12}.Debug|ARM.Build.0 = Debug|Any CPU + {C632FABF-A191-406F-962B-CBE1F9B18A12}.Debug|ARM64.ActiveCfg = Debug|Any CPU + {C632FABF-A191-406F-962B-CBE1F9B18A12}.Debug|ARM64.Build.0 = Debug|Any CPU + {C632FABF-A191-406F-962B-CBE1F9B18A12}.Debug|x64.ActiveCfg = Debug|x64 + {C632FABF-A191-406F-962B-CBE1F9B18A12}.Debug|x64.Build.0 = Debug|x64 + {C632FABF-A191-406F-962B-CBE1F9B18A12}.Debug|x86.ActiveCfg = Debug|Any CPU + {C632FABF-A191-406F-962B-CBE1F9B18A12}.Debug|x86.Build.0 = Debug|Any CPU + {C632FABF-A191-406F-962B-CBE1F9B18A12}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C632FABF-A191-406F-962B-CBE1F9B18A12}.Release|Any CPU.Build.0 = Release|Any CPU + {C632FABF-A191-406F-962B-CBE1F9B18A12}.Release|ARM.ActiveCfg = Release|Any CPU + {C632FABF-A191-406F-962B-CBE1F9B18A12}.Release|ARM.Build.0 = Release|Any CPU + {C632FABF-A191-406F-962B-CBE1F9B18A12}.Release|ARM64.ActiveCfg = Release|Any CPU + {C632FABF-A191-406F-962B-CBE1F9B18A12}.Release|ARM64.Build.0 = Release|Any CPU + {C632FABF-A191-406F-962B-CBE1F9B18A12}.Release|x64.ActiveCfg = Release|Any CPU + {C632FABF-A191-406F-962B-CBE1F9B18A12}.Release|x64.Build.0 = Release|Any CPU + {C632FABF-A191-406F-962B-CBE1F9B18A12}.Release|x86.ActiveCfg = Release|Any CPU + {C632FABF-A191-406F-962B-CBE1F9B18A12}.Release|x86.Build.0 = Release|Any CPU + {40D170F4-F8C1-4FAE-8A22-A22BF096BBEF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {40D170F4-F8C1-4FAE-8A22-A22BF096BBEF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {40D170F4-F8C1-4FAE-8A22-A22BF096BBEF}.Debug|ARM.ActiveCfg = Debug|Any CPU + {40D170F4-F8C1-4FAE-8A22-A22BF096BBEF}.Debug|ARM.Build.0 = Debug|Any CPU + {40D170F4-F8C1-4FAE-8A22-A22BF096BBEF}.Debug|ARM64.ActiveCfg = Debug|Any CPU + {40D170F4-F8C1-4FAE-8A22-A22BF096BBEF}.Debug|ARM64.Build.0 = Debug|Any CPU + {40D170F4-F8C1-4FAE-8A22-A22BF096BBEF}.Debug|x64.ActiveCfg = Debug|x64 + {40D170F4-F8C1-4FAE-8A22-A22BF096BBEF}.Debug|x64.Build.0 = Debug|x64 + {40D170F4-F8C1-4FAE-8A22-A22BF096BBEF}.Debug|x86.ActiveCfg = Debug|Any CPU + {40D170F4-F8C1-4FAE-8A22-A22BF096BBEF}.Debug|x86.Build.0 = Debug|Any CPU + {40D170F4-F8C1-4FAE-8A22-A22BF096BBEF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {40D170F4-F8C1-4FAE-8A22-A22BF096BBEF}.Release|Any CPU.Build.0 = Release|Any CPU + {40D170F4-F8C1-4FAE-8A22-A22BF096BBEF}.Release|ARM.ActiveCfg = Release|Any CPU + {40D170F4-F8C1-4FAE-8A22-A22BF096BBEF}.Release|ARM.Build.0 = Release|Any CPU + {40D170F4-F8C1-4FAE-8A22-A22BF096BBEF}.Release|ARM64.ActiveCfg = Release|Any CPU + {40D170F4-F8C1-4FAE-8A22-A22BF096BBEF}.Release|ARM64.Build.0 = Release|Any CPU + {40D170F4-F8C1-4FAE-8A22-A22BF096BBEF}.Release|x64.ActiveCfg = Release|Any CPU + {40D170F4-F8C1-4FAE-8A22-A22BF096BBEF}.Release|x64.Build.0 = Release|Any CPU + {40D170F4-F8C1-4FAE-8A22-A22BF096BBEF}.Release|x86.ActiveCfg = Release|Any CPU + {40D170F4-F8C1-4FAE-8A22-A22BF096BBEF}.Release|x86.Build.0 = Release|Any CPU + {4B56828E-30D0-4EAF-89BC-480480989E6C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4B56828E-30D0-4EAF-89BC-480480989E6C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4B56828E-30D0-4EAF-89BC-480480989E6C}.Debug|Any CPU.Deploy.0 = Debug|Any CPU + {4B56828E-30D0-4EAF-89BC-480480989E6C}.Debug|ARM.ActiveCfg = Debug|Any CPU + {4B56828E-30D0-4EAF-89BC-480480989E6C}.Debug|ARM.Build.0 = Debug|Any CPU + {4B56828E-30D0-4EAF-89BC-480480989E6C}.Debug|ARM.Deploy.0 = Debug|Any CPU + {4B56828E-30D0-4EAF-89BC-480480989E6C}.Debug|ARM64.ActiveCfg = Debug|Any CPU + {4B56828E-30D0-4EAF-89BC-480480989E6C}.Debug|ARM64.Build.0 = Debug|Any CPU + {4B56828E-30D0-4EAF-89BC-480480989E6C}.Debug|ARM64.Deploy.0 = Debug|Any CPU + {4B56828E-30D0-4EAF-89BC-480480989E6C}.Debug|x64.ActiveCfg = Debug|x64 + {4B56828E-30D0-4EAF-89BC-480480989E6C}.Debug|x64.Build.0 = Debug|x64 + {4B56828E-30D0-4EAF-89BC-480480989E6C}.Debug|x64.Deploy.0 = Debug|x64 + {4B56828E-30D0-4EAF-89BC-480480989E6C}.Debug|x86.ActiveCfg = Debug|x86 + {4B56828E-30D0-4EAF-89BC-480480989E6C}.Debug|x86.Build.0 = Debug|x86 + {4B56828E-30D0-4EAF-89BC-480480989E6C}.Debug|x86.Deploy.0 = Debug|x86 + {4B56828E-30D0-4EAF-89BC-480480989E6C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4B56828E-30D0-4EAF-89BC-480480989E6C}.Release|Any CPU.Build.0 = Release|Any CPU + {4B56828E-30D0-4EAF-89BC-480480989E6C}.Release|Any CPU.Deploy.0 = Release|Any CPU + {4B56828E-30D0-4EAF-89BC-480480989E6C}.Release|ARM.ActiveCfg = Release|Any CPU + {4B56828E-30D0-4EAF-89BC-480480989E6C}.Release|ARM.Build.0 = Release|Any CPU + {4B56828E-30D0-4EAF-89BC-480480989E6C}.Release|ARM.Deploy.0 = Release|Any CPU + {4B56828E-30D0-4EAF-89BC-480480989E6C}.Release|ARM64.ActiveCfg = Release|Any CPU + {4B56828E-30D0-4EAF-89BC-480480989E6C}.Release|ARM64.Build.0 = Release|Any CPU + {4B56828E-30D0-4EAF-89BC-480480989E6C}.Release|ARM64.Deploy.0 = Release|Any CPU + {4B56828E-30D0-4EAF-89BC-480480989E6C}.Release|x64.ActiveCfg = Release|x64 + {4B56828E-30D0-4EAF-89BC-480480989E6C}.Release|x64.Build.0 = Release|x64 + {4B56828E-30D0-4EAF-89BC-480480989E6C}.Release|x64.Deploy.0 = Release|x64 + {4B56828E-30D0-4EAF-89BC-480480989E6C}.Release|x86.ActiveCfg = Release|x86 + {4B56828E-30D0-4EAF-89BC-480480989E6C}.Release|x86.Build.0 = Release|x86 + {4B56828E-30D0-4EAF-89BC-480480989E6C}.Release|x86.Deploy.0 = Release|x86 + {1F70F55F-C04F-4FF7-8824-EF06AC4595BA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1F70F55F-C04F-4FF7-8824-EF06AC4595BA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1F70F55F-C04F-4FF7-8824-EF06AC4595BA}.Debug|ARM.ActiveCfg = Debug|Any CPU + {1F70F55F-C04F-4FF7-8824-EF06AC4595BA}.Debug|ARM.Build.0 = Debug|Any CPU + {1F70F55F-C04F-4FF7-8824-EF06AC4595BA}.Debug|ARM64.ActiveCfg = Debug|Any CPU + {1F70F55F-C04F-4FF7-8824-EF06AC4595BA}.Debug|ARM64.Build.0 = Debug|Any CPU + {1F70F55F-C04F-4FF7-8824-EF06AC4595BA}.Debug|x64.ActiveCfg = Debug|x64 + {1F70F55F-C04F-4FF7-8824-EF06AC4595BA}.Debug|x64.Build.0 = Debug|x64 + {1F70F55F-C04F-4FF7-8824-EF06AC4595BA}.Debug|x86.ActiveCfg = Debug|Any CPU + {1F70F55F-C04F-4FF7-8824-EF06AC4595BA}.Debug|x86.Build.0 = Debug|Any CPU + {1F70F55F-C04F-4FF7-8824-EF06AC4595BA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1F70F55F-C04F-4FF7-8824-EF06AC4595BA}.Release|Any CPU.Build.0 = Release|Any CPU + {1F70F55F-C04F-4FF7-8824-EF06AC4595BA}.Release|ARM.ActiveCfg = Release|Any CPU + {1F70F55F-C04F-4FF7-8824-EF06AC4595BA}.Release|ARM.Build.0 = Release|Any CPU + {1F70F55F-C04F-4FF7-8824-EF06AC4595BA}.Release|ARM64.ActiveCfg = Release|Any CPU + {1F70F55F-C04F-4FF7-8824-EF06AC4595BA}.Release|ARM64.Build.0 = Release|Any CPU + {1F70F55F-C04F-4FF7-8824-EF06AC4595BA}.Release|x64.ActiveCfg = Release|Any CPU + {1F70F55F-C04F-4FF7-8824-EF06AC4595BA}.Release|x64.Build.0 = Release|Any CPU + {1F70F55F-C04F-4FF7-8824-EF06AC4595BA}.Release|x86.ActiveCfg = Release|Any CPU + {1F70F55F-C04F-4FF7-8824-EF06AC4595BA}.Release|x86.Build.0 = Release|Any CPU + {35B035A4-E21B-4379-936B-6DEDA31AF860}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {35B035A4-E21B-4379-936B-6DEDA31AF860}.Debug|Any CPU.Build.0 = Debug|Any CPU + {35B035A4-E21B-4379-936B-6DEDA31AF860}.Debug|ARM.ActiveCfg = Debug|ARM + {35B035A4-E21B-4379-936B-6DEDA31AF860}.Debug|ARM.Build.0 = Debug|ARM + {35B035A4-E21B-4379-936B-6DEDA31AF860}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {35B035A4-E21B-4379-936B-6DEDA31AF860}.Debug|ARM64.Build.0 = Debug|ARM64 + {35B035A4-E21B-4379-936B-6DEDA31AF860}.Debug|x64.ActiveCfg = Debug|x64 + {35B035A4-E21B-4379-936B-6DEDA31AF860}.Debug|x64.Build.0 = Debug|x64 + {35B035A4-E21B-4379-936B-6DEDA31AF860}.Debug|x86.ActiveCfg = Debug|x86 + {35B035A4-E21B-4379-936B-6DEDA31AF860}.Debug|x86.Build.0 = Debug|x86 + {35B035A4-E21B-4379-936B-6DEDA31AF860}.Release|Any CPU.ActiveCfg = Release|Any CPU + {35B035A4-E21B-4379-936B-6DEDA31AF860}.Release|Any CPU.Build.0 = Release|Any CPU + {35B035A4-E21B-4379-936B-6DEDA31AF860}.Release|ARM.ActiveCfg = Release|ARM + {35B035A4-E21B-4379-936B-6DEDA31AF860}.Release|ARM.Build.0 = Release|ARM + {35B035A4-E21B-4379-936B-6DEDA31AF860}.Release|ARM64.ActiveCfg = Release|ARM64 + {35B035A4-E21B-4379-936B-6DEDA31AF860}.Release|ARM64.Build.0 = Release|ARM64 + {35B035A4-E21B-4379-936B-6DEDA31AF860}.Release|x64.ActiveCfg = Release|x64 + {35B035A4-E21B-4379-936B-6DEDA31AF860}.Release|x64.Build.0 = Release|x64 + {35B035A4-E21B-4379-936B-6DEDA31AF860}.Release|x86.ActiveCfg = Release|x86 + {35B035A4-E21B-4379-936B-6DEDA31AF860}.Release|x86.Build.0 = Release|x86 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {579B0371-17D6-45C2-99DD-E630CE50AF90} + EndGlobalSection +EndGlobal diff --git a/TheXamlGuy.TaskbarGroup/App.xaml b/TheXamlGuy.TaskbarGroup/App.xaml new file mode 100644 index 0000000..c593785 --- /dev/null +++ b/TheXamlGuy.TaskbarGroup/App.xaml @@ -0,0 +1,10 @@ + + + + + + + diff --git a/TheXamlGuy.TaskbarGroup/App.xaml.cs b/TheXamlGuy.TaskbarGroup/App.xaml.cs new file mode 100644 index 0000000..f87366d --- /dev/null +++ b/TheXamlGuy.TaskbarGroup/App.xaml.cs @@ -0,0 +1,47 @@ +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using System.IO; +using System.Reflection; +using System.Windows; +using TheXamlGuy.TaskbarGroup.Core; +using TheXamlGuy.TaskbarGroup.Flyout; +using TheXamlGuy.TaskbarGroup.Flyout.Foundation; +using TheXamlGuy.TaskbarGroup.Foundation; + +namespace TheXamlGuy.TaskbarGroup +{ + public partial class App : Application + { + private IHost? host; + + protected override async void OnStartup(StartupEventArgs args) + { + var appLocation = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location); + + host = Host.CreateDefaultBuilder() + .ConfigureAppConfiguration(config => + { + config.SetBasePath(appLocation); + }) + .ConfigureServices(ConfigureServices) + .Build(); + + await host.StartAsync(); + } + + private void ConfigureServices(HostBuilderContext context, IServiceCollection services) + { + services.AddHostedService() + .AddRequiredCore() + .AddRequiredFoundation() + .AddRequiredFlyoutFoundation() + .AddTransient, TaskbarButtonFlyoutActivationHandler>() + .AddSingleton() + .AddTransient() + .AddTransient() + .AddTransient() + .AddTransient(); + } + } +} diff --git a/TheXamlGuy.TaskbarGroup/AssemblyInfo.cs b/TheXamlGuy.TaskbarGroup/AssemblyInfo.cs new file mode 100644 index 0000000..8b5504e --- /dev/null +++ b/TheXamlGuy.TaskbarGroup/AssemblyInfo.cs @@ -0,0 +1,10 @@ +using System.Windows; + +[assembly: ThemeInfo( + ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located + //(used if a resource is not found in the page, + // or application resource dictionaries) + ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located + //(used if a resource is not found in the page, + // app, or any theme specific resource dictionaries) +)] diff --git a/TheXamlGuy.TaskbarGroup/LifeCycles/ApplicationHost.cs b/TheXamlGuy.TaskbarGroup/LifeCycles/ApplicationHost.cs new file mode 100644 index 0000000..2543693 --- /dev/null +++ b/TheXamlGuy.TaskbarGroup/LifeCycles/ApplicationHost.cs @@ -0,0 +1,58 @@ +using Microsoft.Extensions.Hosting; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using TheXamlGuy.TaskbarGroup.Core; + +namespace TheXamlGuy.TaskbarGroup +{ + public sealed class ApplicationHost : IHostedService + { + private readonly IEnumerable initializables; + private bool isInitialized; + private readonly TaskbarButtonFlyoutWindow flyoutWindow; + + public ApplicationHost(IEnumerable initializables, + TaskbarButtonFlyoutWindow flyoutWindow) + { + this.initializables = initializables; + this.flyoutWindow = flyoutWindow; + } + + public async Task StartAsync(CancellationToken cancellationToken) + { + await InitializeAsync(); + await StartupAsync(); + + isInitialized = true; + } + + public async Task StopAsync(CancellationToken cancellationToken) + { + await Task.CompletedTask; + } + + private async Task InitializeAsync() + { + if (!isInitialized) + { + foreach (var initializable in initializables) + { + initializable.Initialize(); + } + + await Task.CompletedTask; + } + } + + private async Task StartupAsync() + { + if (!isInitialized) + { + flyoutWindow.Show(); + + await Task.CompletedTask; + } + } + } +} diff --git a/TheXamlGuy.TaskbarGroup/LifeCycles/TaskbarButtonFlyoutActivation.cs b/TheXamlGuy.TaskbarGroup/LifeCycles/TaskbarButtonFlyoutActivation.cs new file mode 100644 index 0000000..4e40bab --- /dev/null +++ b/TheXamlGuy.TaskbarGroup/LifeCycles/TaskbarButtonFlyoutActivation.cs @@ -0,0 +1,6 @@ +using TheXamlGuy.TaskbarGroup.Core; + +namespace TheXamlGuy.TaskbarGroup +{ + public record TaskbarButtonFlyoutActivation(TaskbarButton Button); +} diff --git a/TheXamlGuy.TaskbarGroup/LifeCycles/TaskbarButtonFlyoutActivationHandler.cs b/TheXamlGuy.TaskbarGroup/LifeCycles/TaskbarButtonFlyoutActivationHandler.cs new file mode 100644 index 0000000..7f3b4c5 --- /dev/null +++ b/TheXamlGuy.TaskbarGroup/LifeCycles/TaskbarButtonFlyoutActivationHandler.cs @@ -0,0 +1,70 @@ +using TheXamlGuy.TaskbarGroup.Core; +using TheXamlGuy.TaskbarGroup.Flyout; +using TheXamlGuy.TaskbarGroup.Flyout.Controls; +using TheXamlGuy.TaskbarGroup.Flyout.Foundation; +using TheXamlGuy.TaskbarGroup.Foundation; + +namespace TheXamlGuy.TaskbarGroup +{ + public class TaskbarButtonFlyoutActivationHandler : IMessageHandler + { + private readonly TaskbarButtonFlyoutWindow window; + private readonly TaskbarButtonViewModel taskbarButtonViewModel; + private readonly TaskbarButtonView taskbarButtonView; + private readonly ITaskbar taskbar; + + public TaskbarButtonFlyoutActivationHandler(ITaskbar taskbar, + TaskbarButtonViewModel taskbarButtonViewModel, + TaskbarButtonView taskbarButtonView, + TaskbarButtonFlyoutWindow window) + { + this.taskbar = taskbar; + this.taskbarButtonViewModel = taskbarButtonViewModel; + this.taskbarButtonView = taskbarButtonView; + this.window = window; + } + + public void Handle(TaskbarButtonFlyoutActivation message) + { + var button = message.Button; + var dpiX = window.DpiX(); + var dpiY = window.DpiY(); + + var taskbarState = taskbar.GetCurrentState(); + + var placement = TaskbarButtonFlyoutPlacement.Bottom; + switch (taskbarState.Placement) + { + case TaskbarPlacement.Left: + placement = TaskbarButtonFlyoutPlacement.Left; + break; + + case TaskbarPlacement.Top: + placement = TaskbarButtonFlyoutPlacement.Top; + break; + + case TaskbarPlacement.Right: + placement = TaskbarButtonFlyoutPlacement.Right; + break; + + case TaskbarPlacement.Bottom: + placement = TaskbarButtonFlyoutPlacement.Bottom; + window.Left = ((button.Bounds.X + (button.Bounds.Width / 2)) / dpiX) - (window.Width / 2); + window.Top = (button.Bounds.Y / dpiY) - window.Height; + break; + } + + if (window.XamlContent is TaskbarButtonFlyout flyout) + { + flyout.Margin = new Windows.UI.Xaml.Thickness(6); + + taskbarButtonView.Bind(taskbarButtonViewModel); + flyout.Content = taskbarButtonView; + + flyout.ShowAt(placement); + } + + window.Activate(); + } + } +} diff --git a/TheXamlGuy.TaskbarGroup/Properties/Program.cs b/TheXamlGuy.TaskbarGroup/Properties/Program.cs new file mode 100644 index 0000000..2a37422 --- /dev/null +++ b/TheXamlGuy.TaskbarGroup/Properties/Program.cs @@ -0,0 +1,17 @@ +using System; + +namespace TheXamlGuy.TaskbarGroup +{ + public class Program + { + [STAThread()] + public static void Main() + { + using (new Flyout.App()) + { + var app = new App(); + app.Run(); + } + } + } +} diff --git a/TheXamlGuy.TaskbarGroup/TheXamlGuy.TaskbarGroup.csproj b/TheXamlGuy.TaskbarGroup/TheXamlGuy.TaskbarGroup.csproj new file mode 100644 index 0000000..96a187b --- /dev/null +++ b/TheXamlGuy.TaskbarGroup/TheXamlGuy.TaskbarGroup.csproj @@ -0,0 +1,26 @@ + + + WinExe + netcoreapp3.1 + true + TheXamlGuy.TaskbarGroup.Program + uap10.0.19041 + x64;x86 + 10.0 + enable + + + + + + + + + + + + + + + + diff --git a/TheXamlGuy.TaskbarGroup/Windows/TaskbarButtonFlyoutWindow.cs b/TheXamlGuy.TaskbarGroup/Windows/TaskbarButtonFlyoutWindow.cs new file mode 100644 index 0000000..f79de11 --- /dev/null +++ b/TheXamlGuy.TaskbarGroup/Windows/TaskbarButtonFlyoutWindow.cs @@ -0,0 +1,80 @@ +using System; +using System.Windows; +using System.Windows.Media; +using System.Windows.Threading; +using TheXamlGuy.TaskbarGroup.Core; +using TheXamlGuy.TaskbarGroup.Flyout.Controls; + +namespace TheXamlGuy.TaskbarGroup +{ + public class TaskbarButtonFlyoutWindow : TransparentXamlWindow + { + private readonly IMediator mediator; + private bool isDpiChanging; + + public TaskbarButtonFlyoutWindow(IMessenger messenger, + IMediator mediator) + { + this.mediator = mediator; + + DpiChanged += OnDpiChanged; + Deactivated += OnDeactivated; + Topmost = true; + Width = 258; + Height = 258; + + messenger.Subscribe(OnTaskbarButtonInvoked); + messenger.Subscribe(OnTaskbarButtonDragEnter); + } + + private void OnDeactivated(object? sender, EventArgs args) + { + if (XamlContent is TaskbarButtonFlyout flyout) + { + flyout.Close(); + } + } + + private async void OnDpiChanged(object sender, DpiChangedEventArgs args) + { + if (isDpiChanging) return; + + isDpiChanging = true; + + await Dispatcher.Invoke(async () => + { + Visibility = Visibility.Visible; + await Dispatcher.BeginInvoke(new Action(() => + { + VisualTreeHelper.SetRootDpi(this, args.OldDpi); + }), DispatcherPriority.ContextIdle, null); + + await Dispatcher.BeginInvoke(new Action(() => + { + VisualTreeHelper.SetRootDpi(this, args.NewDpi); + }), DispatcherPriority.ContextIdle, null); + + await Dispatcher.BeginInvoke(new Action(() => + { + Visibility = Visibility.Hidden; + isDpiChanging = false; + }), DispatcherPriority.ContextIdle, null); + }); + } + + private void OnTaskbarButtonDragEnter(TaskbarButtonDragEnter args) + { + Dispatcher.Invoke(() => Open(args.Button)); + } + + private void OnTaskbarButtonInvoked(TaskbarButtonInvoked args) + { + Dispatcher.Invoke(() => Open(args.Button)); + } + + private void Open(TaskbarButton button) + { + mediator.Handle(new TaskbarButtonFlyoutActivation(button)); + } + } +} diff --git a/TheXamlGuy.TaskbarGroup/Windows/TransparentXamlWindow.cs b/TheXamlGuy.TaskbarGroup/Windows/TransparentXamlWindow.cs new file mode 100644 index 0000000..7485033 --- /dev/null +++ b/TheXamlGuy.TaskbarGroup/Windows/TransparentXamlWindow.cs @@ -0,0 +1,25 @@ +using Microsoft.Toolkit.Wpf.UI.XamlHost; +using System.Windows; +using System.Windows.Media; + +namespace TheXamlGuy.TaskbarGroup +{ + public class TransparentXamlWindow : XamlWindow where TXamlContent : Windows.UI.Xaml.UIElement + { + public TransparentXamlWindow() => PrepareDefaultWindow(); + + protected override WindowsXamlHost OnInitializing(WindowsXamlHost xamlHost) + { + return base.OnInitializing(xamlHost); + } + + private void PrepareDefaultWindow() + { + ShowInTaskbar = false; + WindowStyle = WindowStyle.None; + ResizeMode = ResizeMode.NoResize; + AllowsTransparency = true; + Background = new SolidColorBrush(Colors.Transparent); + } + } +} diff --git a/TheXamlGuy.TaskbarGroup/Windows/XamlWindow.cs b/TheXamlGuy.TaskbarGroup/Windows/XamlWindow.cs new file mode 100644 index 0000000..19ab128 --- /dev/null +++ b/TheXamlGuy.TaskbarGroup/Windows/XamlWindow.cs @@ -0,0 +1,41 @@ +using Microsoft.Toolkit.Wpf.UI.XamlHost; +using System.Windows; + +namespace TheXamlGuy.TaskbarGroup +{ + public class XamlWindow : Window where TXamlContent : Windows.UI.Xaml.UIElement + { + private WindowsXamlHost? xamlHost; + + public XamlWindow() + { + Initialize(); + } + + public TXamlContent? XamlContent + { + get + { + if (xamlHost is null) return null; + return xamlHost.GetUwpInternalObject() as TXamlContent; + } + } + + protected virtual WindowsXamlHost OnInitializing(WindowsXamlHost xamlHost) + { + xamlHost.InitialTypeName = typeof(TXamlContent).FullName; + xamlHost.HorizontalAlignment = HorizontalAlignment.Stretch; + xamlHost.VerticalAlignment = VerticalAlignment.Stretch; + + return xamlHost; + } + + private void Initialize() + { + xamlHost = new WindowsXamlHost(); + OnInitializing(xamlHost); + + Content = xamlHost; + } + } +}