project
@@ -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
|
||||
@@ -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
|
||||
@@ -0,0 +1,15 @@
|
||||
namespace TheXamlGuy.TaskbarGroup.Core
|
||||
{
|
||||
public class DataTemplateBuilder : IDataTemplateBuilder
|
||||
{
|
||||
private readonly Dictionary<Type, Type> items = new();
|
||||
|
||||
public IDataTemplateCollection DataTemplates => new DataTemplateCollection(items);
|
||||
|
||||
public IDataTemplateBuilder Map<TViewModel, TView>()
|
||||
{
|
||||
items.Add(typeof(TViewModel), typeof(TView));
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
using System.Collections.ObjectModel;
|
||||
|
||||
namespace TheXamlGuy.TaskbarGroup.Core
|
||||
{
|
||||
public class DataTemplateCollection : ReadOnlyDictionary<Type, Type>, IDataTemplateCollection
|
||||
{
|
||||
public DataTemplateCollection(IDictionary<Type, Type> dictionary) : base(dictionary)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<object, CompositeDisposable> subjects = new();
|
||||
|
||||
public void Add(object subject, params object[] objects)
|
||||
{
|
||||
var disposables = subjects.GetOrCreateValue(subject);
|
||||
|
||||
foreach (var disposable in objects.OfType<IDisposable>())
|
||||
{
|
||||
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<TDisposable>(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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
using System.Reflection;
|
||||
|
||||
namespace TheXamlGuy.TaskbarGroup.Core
|
||||
{
|
||||
public class EventAggregatorInvoker : IEventAggregatorInvoker
|
||||
{
|
||||
public void Invoke<TMessage>(object target, TMessage message, MethodInfo methodInfo)
|
||||
{
|
||||
if (message is null) throw new ArgumentNullException(nameof(message));
|
||||
|
||||
methodInfo.Invoke(target, new object[] { message });
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
namespace TheXamlGuy.TaskbarGroup.Core
|
||||
{
|
||||
public interface IAsyncMessageHandler<TMessage>
|
||||
{
|
||||
Task Handle(TMessage message, CancellationToken canellationToken = default);
|
||||
}
|
||||
|
||||
public interface IAsyncMessageHandler<TReturn, TMessage>
|
||||
{
|
||||
Task<TReturn> Handle(TMessage message, CancellationToken cancellationToken = default);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace TheXamlGuy.TaskbarGroup.Core
|
||||
{
|
||||
public interface IBindViewModel<TViewModel> where TViewModel : class
|
||||
{
|
||||
public TViewModel ViewModel { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace TheXamlGuy.TaskbarGroup.Core
|
||||
{
|
||||
public interface IDataTemplateBuilder
|
||||
{
|
||||
IDataTemplateCollection DataTemplates { get; }
|
||||
|
||||
IDataTemplateBuilder Map<TViewModel, TView>();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace TheXamlGuy.TaskbarGroup.Core
|
||||
{
|
||||
public interface IDataTemplateCollection : IReadOnlyDictionary<Type, Type>
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace TheXamlGuy.TaskbarGroup.Core
|
||||
{
|
||||
public interface IDispatcherTimer
|
||||
{
|
||||
void Start();
|
||||
|
||||
void Stop();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace TheXamlGuy.TaskbarGroup.Core
|
||||
{
|
||||
public interface IDispatcherTimerFactory
|
||||
{
|
||||
IDispatcherTimer Create(Action actionDelegate, TimeSpan interval);
|
||||
}
|
||||
}
|
||||
@@ -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<TDisposable>(object subject, IDisposable disposer, TDisposable replacement) where TDisposable : IDisposable;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace TheXamlGuy.TaskbarGroup.Core
|
||||
{
|
||||
public interface IDropTarget<TTarget>
|
||||
{
|
||||
void Register(TTarget target);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
using System.Reflection;
|
||||
|
||||
namespace TheXamlGuy.TaskbarGroup.Core
|
||||
{
|
||||
public interface IEventAggregatorInvoker
|
||||
{
|
||||
void Invoke<TMessage>(object target, TMessage message, MethodInfo methodInfo);
|
||||
}
|
||||
}
|
||||
@@ -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<IDataTemplateBuilder> builderDelegate)
|
||||
{
|
||||
hostBuilder.ConfigureServices((hostBuilderContext, serviceCollection) =>
|
||||
{
|
||||
var builder = new DataTemplateBuilder();
|
||||
builderDelegate?.Invoke(builder);
|
||||
|
||||
serviceCollection.AddSingleton(builder.DataTemplates);
|
||||
});
|
||||
|
||||
return hostBuilder;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace TheXamlGuy.TaskbarGroup.Core
|
||||
{
|
||||
public interface IInitializable
|
||||
{
|
||||
void Initialize();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
|
||||
namespace TheXamlGuy.TaskbarGroup.Core
|
||||
{
|
||||
public interface IMediator
|
||||
{
|
||||
void Handle(object request, params object[] parameters);
|
||||
void Handle<TEvent>() where TEvent : new();
|
||||
TResponse Handle<TResponse, TRequest>(params object[] parameters) where TRequest : new();
|
||||
TResponse Handle<TResponse>(object request, params object[] parameters);
|
||||
Task HandleAsync(object request, CancellationToken cancellationToken, params object[] parameters);
|
||||
Task HandleAsync(object request, params object[] parameters);
|
||||
Task HandleAsync<TEvent>() where TEvent : new();
|
||||
Task<TResponse> HandleAsync<TResponse, TRequest>(params object[] parameters) where TRequest : new();
|
||||
Task<TResponse> HandleAsync<TResponse>(object request, CancellationToken cancellationToken, params object[] parameters);
|
||||
Task<TResponse> HandleAsync<TResponse>(object request, params object[] parameters);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
namespace TheXamlGuy.TaskbarGroup.Core
|
||||
{
|
||||
public interface IMessageHandler<TMessage>
|
||||
{
|
||||
void Handle(TMessage message);
|
||||
}
|
||||
|
||||
public interface IMessageHandler<TReturn, TMessage>
|
||||
{
|
||||
TReturn Handle(TMessage message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
using System.Reactive.Concurrency;
|
||||
using System.Reactive.Subjects;
|
||||
|
||||
namespace TheXamlGuy.TaskbarGroup.Core
|
||||
{
|
||||
public interface IMessenger
|
||||
{
|
||||
void Send<TMessage>() where TMessage : new();
|
||||
|
||||
void Send<TMessage>(TMessage message);
|
||||
|
||||
IDisposable Subscribe<TMessage>(Action<TMessage> actionDelegate, IScheduler? scheduler = null, Func<TMessage, bool>? where = null);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
using System.Reactive.Linq;
|
||||
|
||||
namespace TheXamlGuy.TaskbarGroup.Core
|
||||
{
|
||||
public record FileDropped();
|
||||
|
||||
public static class IObservableExtensions
|
||||
{
|
||||
public static IDisposable WeakSubscribe<TMessage>(this IObservable<TMessage> observable, IEventAggregatorInvoker invoker, Action<TMessage> 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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace TheXamlGuy.TaskbarGroup.Core
|
||||
{
|
||||
public interface IPointerMonitor : IInitializable, IDisposable
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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<IDisposer, Disposer>()
|
||||
.AddSingleton<IServiceFactory>(provider => new ServiceFactory(provider.GetService, (type, parameter) => ActivatorUtilities.CreateInstance(provider, type, parameter)))
|
||||
.AddSingleton<IEventAggregatorInvoker, EventAggregatorInvoker>()
|
||||
.AddSingleton<IMessenger, Messenger>()
|
||||
.AddSingleton<IMediator, Mediator>()
|
||||
.AddSingleton<IInitializable, WndProcMonitor>()
|
||||
.AddSingleton<ITaskbar, Taskbar>()
|
||||
.AddSingleton<IInitializable, TaskbarMonitor>()
|
||||
.AddSingleton<IInitializable, PointerMonitor>()
|
||||
.AddSingleton<IInitializable, TaskbarButtonMonitor>();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
namespace TheXamlGuy.TaskbarGroup.Core
|
||||
{
|
||||
public interface IServiceFactory
|
||||
{
|
||||
T Create<T>(params object[] parameters);
|
||||
|
||||
T Create<T>(Type type);
|
||||
T Create<T>();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
|
||||
namespace TheXamlGuy.TaskbarGroup.Core
|
||||
{
|
||||
public interface ITaskbar
|
||||
{
|
||||
TaskbarState GetCurrentState();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace TheXamlGuy.TaskbarGroup.Core
|
||||
{
|
||||
public interface ITaskbarButton : IDisposable
|
||||
{
|
||||
TaskbarButtonBounds Bounds { get; }
|
||||
|
||||
string Name { get; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace TheXamlGuy.TaskbarGroup.Core
|
||||
{
|
||||
public interface ITaskbarButtonMonitor : IInitializable
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace TheXamlGuy.TaskbarGroup.Core
|
||||
{
|
||||
public interface ITaskbarMonitor : IInitializable
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace TheXamlGuy.TaskbarGroup.Core
|
||||
{
|
||||
public interface ITemplateSelector
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace TheXamlGuy.TaskbarGroup.Core
|
||||
{
|
||||
public interface IWndProcMonitor : IInitializable, IDisposable
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
namespace System.Runtime.CompilerServices
|
||||
{
|
||||
using System.ComponentModel;
|
||||
/// <summary>
|
||||
/// Reserved to be used by the compiler for tracking metadata.
|
||||
/// This class should not be used by developers in source code.
|
||||
/// </summary>
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public static class IsExternalInit
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
namespace System.Diagnostics.CodeAnalysis
|
||||
{
|
||||
/// <summary>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.</summary>
|
||||
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue, Inherited = false)]
|
||||
public sealed class NotNullAttribute : Attribute { }
|
||||
|
||||
/// <summary>Specifies that when a method returns <see cref="ReturnValue"/>, the parameter may be null even if the corresponding type disallows it.</summary>
|
||||
[AttributeUsage(AttributeTargets.Parameter, Inherited = false)]
|
||||
public sealed class MaybeNullWhenAttribute : Attribute
|
||||
{
|
||||
/// <summary>Initializes the attribute with the specified return value condition.</summary>
|
||||
/// <param name="returnValue">
|
||||
/// The return value condition. If the method returns this value, the associated parameter may be null.
|
||||
/// </param>
|
||||
public MaybeNullWhenAttribute(bool returnValue) => ReturnValue = returnValue;
|
||||
|
||||
/// <summary>Gets the return value condition.</summary>
|
||||
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.
|
||||
}
|
||||
@@ -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<TEvent>() where TEvent : new()
|
||||
{
|
||||
Handle(new TEvent());
|
||||
}
|
||||
|
||||
public TResponse Handle<TResponse, TRequest>(params object[] parameters) where TRequest : new()
|
||||
{
|
||||
return Handle<TResponse>(new TRequest(), parameters);
|
||||
}
|
||||
|
||||
public void Handle(object request, params object[] parameters)
|
||||
{
|
||||
GetHandler(typeof(IMessageHandler<>).MakeGenericType(request.GetType()), parameters)
|
||||
.Handle((dynamic)request);
|
||||
}
|
||||
|
||||
public TResponse Handle<TResponse>(object request, params object[] parameters)
|
||||
{
|
||||
return GetHandler(typeof(IMessageHandler<,>).MakeGenericType(typeof(TResponse), request.GetType()), parameters)
|
||||
.Handle((dynamic)request);
|
||||
}
|
||||
|
||||
public Task HandleAsync<TEvent>() where TEvent : new()
|
||||
{
|
||||
return HandleAsync(new TEvent());
|
||||
}
|
||||
|
||||
public Task<TResponse> HandleAsync<TResponse, TRequest>(params object[] parameters) where TRequest : new()
|
||||
{
|
||||
return HandleAsync<TResponse>(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<TResponse> HandleAsync<TResponse>(object request, CancellationToken cancellationToken, params object[] parameters)
|
||||
{
|
||||
return GetHandler(typeof(IAsyncMessageHandler<,>).MakeGenericType(typeof(TResponse), request.GetType()), parameters)
|
||||
.Handle((dynamic)request, cancellationToken);
|
||||
}
|
||||
|
||||
public Task<TResponse> HandleAsync<TResponse>(object request, params object[] parameters)
|
||||
{
|
||||
return HandleAsync<TResponse>(request, CancellationToken.None, parameters);
|
||||
}
|
||||
|
||||
private dynamic GetHandler(Type type, params object[] parameters)
|
||||
{
|
||||
return parameters.Length == 0 ? serviceFactory.Create<object>(type) : serviceFactory.Create<object>(type, parameters);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<Type, object> 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<TMessage> GetSubject<TMessage>()
|
||||
{
|
||||
return (ISubject<TMessage>)subjects.GetOrAdd(typeof(TMessage), type => new BehaviorSubject<TMessage>(default));
|
||||
}
|
||||
|
||||
public void Send<TMessage>() where TMessage : new()
|
||||
{
|
||||
Send(new TMessage());
|
||||
}
|
||||
|
||||
public void Send<TMessage>(TMessage message)
|
||||
{
|
||||
GetSubject<TMessage>().OnNext(message);
|
||||
}
|
||||
|
||||
public IDisposable Subscribe<TMessage>(Action<TMessage> actionDelegate, IScheduler? scheduler = null, Func<TMessage, bool>? where = null)
|
||||
{
|
||||
if (scheduler is null)
|
||||
{
|
||||
scheduler = Scheduler.Default;
|
||||
}
|
||||
|
||||
if (where == null)
|
||||
{
|
||||
where = x => true;
|
||||
}
|
||||
|
||||
return GetSubject<TMessage>().AsObservable().Skip(1).Where(where).ObserveOn(scheduler).WeakSubscribe(invoker, actionDelegate);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"$schema": "https://aka.ms/CsWin32.schema.json",
|
||||
"public": true
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
SetWindowsHookEx
|
||||
GetModuleHandle
|
||||
CallNextHookEx
|
||||
GetPhysicalCursorPos
|
||||
FindWindowEx
|
||||
FindWindow
|
||||
GetWindowRect
|
||||
DestroyWindow
|
||||
DefWindowProcW
|
||||
CreateWindowExW
|
||||
RegisterClassW
|
||||
GetSystemMetrics
|
||||
MonitorFromWindow
|
||||
RegisterWindowMessage
|
||||
GetDpiForWindow
|
||||
SetWindowPos
|
||||
SHCreateShellItemArrayFromDataObject
|
||||
@@ -0,0 +1,132 @@
|
||||
using System.Collections.ObjectModel;
|
||||
|
||||
namespace TheXamlGuy.TaskbarGroup.Core
|
||||
{
|
||||
public class ObservableCollectionViewModel<TItemViewModel> :
|
||||
ObservableCollection<TItemViewModel>, 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<TItemViewModel>();
|
||||
disposer.Add(this, item);
|
||||
|
||||
base.Add(item);
|
||||
}
|
||||
|
||||
public void Add(object parameter, params object[] parameters)
|
||||
{
|
||||
var item = serviceFactory.Create<TItemViewModel>(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<TItem>(params object[] parameters) where TItem : TItemViewModel
|
||||
{
|
||||
var item = serviceFactory.Create<TItem>(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<TItem>(int index, params object[] parameters) where TItem : TItemViewModel
|
||||
{
|
||||
var item = serviceFactory.Create<TItem>(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>(TEvent @event)
|
||||
{
|
||||
messenger.Send(@event);
|
||||
}
|
||||
|
||||
protected void Publish<TEvent>() where TEvent : new()
|
||||
{
|
||||
messenger.Send<TEvent>();
|
||||
}
|
||||
|
||||
protected void Register<TEvent>(Action<TEvent> action)
|
||||
{
|
||||
disposer.Add(this, messenger.Subscribe(action));
|
||||
}
|
||||
|
||||
protected void Register<TEvent>(Action<TEvent> action, Func<TEvent, bool> condition)
|
||||
{
|
||||
disposer.Add(this, messenger.Subscribe(action, null, condition));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>(TEvent @event)
|
||||
{
|
||||
messenger.Send(@event);
|
||||
}
|
||||
|
||||
protected void Publish<TEvent>() where TEvent : new()
|
||||
{
|
||||
messenger.Send<TEvent>();
|
||||
}
|
||||
|
||||
protected void Register<TEvent>(Action<TEvent> action)
|
||||
{
|
||||
disposer.Add(this, messenger.Subscribe(action));
|
||||
}
|
||||
|
||||
protected void Register<TEvent>(Action<TEvent> action, Func<TEvent, bool> condition)
|
||||
{
|
||||
disposer.Add(this, messenger.Subscribe(action, null, condition));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace TheXamlGuy.TaskbarGroup.Core
|
||||
{
|
||||
public enum PointerButton
|
||||
{
|
||||
Left,
|
||||
Middle,
|
||||
Right
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
namespace TheXamlGuy.TaskbarGroup.Core
|
||||
{
|
||||
public record PointerDrag(PointerLocation Location);
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
namespace TheXamlGuy.TaskbarGroup.Core
|
||||
{
|
||||
public record PointerLocation(int X, int Y);
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
namespace TheXamlGuy.TaskbarGroup.Core
|
||||
{
|
||||
public record PointerMoved(PointerLocation Location);
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
namespace TheXamlGuy.TaskbarGroup.Core
|
||||
{
|
||||
public record PointerPressed(PointerLocation Location, PointerButton Button = PointerButton.Left);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
namespace TheXamlGuy.TaskbarGroup.Core
|
||||
{
|
||||
public class ServiceFactory : IServiceFactory
|
||||
{
|
||||
private readonly Func<Type, object> factory;
|
||||
|
||||
private readonly Func<Type, object[], object> factoryWithParameters;
|
||||
|
||||
public ServiceFactory(Func<Type, object> factory, Func<Type, object[], object> factoryWithParameters)
|
||||
{
|
||||
this.factory = factory;
|
||||
this.factoryWithParameters = factoryWithParameters;
|
||||
}
|
||||
|
||||
public T Create<T>(params object[] parameters)
|
||||
{
|
||||
return (T)factoryWithParameters(typeof(T), parameters);
|
||||
}
|
||||
|
||||
public T Create<T>(Type type)
|
||||
{
|
||||
return (T)factory(type);
|
||||
}
|
||||
|
||||
public T Create<T>()
|
||||
{
|
||||
return (T)factory(typeof(T));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<PointerReleased>(OnPointerReleased));
|
||||
disposer.Add(this, messenger.Subscribe<PointerMoved>(OnPointerMoved));
|
||||
disposer.Add(this, messenger.Subscribe<PointerDrag>(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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
namespace TheXamlGuy.TaskbarGroup.Core
|
||||
{
|
||||
public record TaskbarButtonCreated(TaskbarButton Button);
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
namespace TheXamlGuy.TaskbarGroup.Core
|
||||
{
|
||||
public record TaskbarButtonDragEnter(TaskbarButton Button);
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
namespace TheXamlGuy.TaskbarGroup.Core
|
||||
{
|
||||
public record TaskbarButtonDragOver(TaskbarButton Button);
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
namespace TheXamlGuy.TaskbarGroup.Core
|
||||
{
|
||||
public record TaskbarButtonEntered(TaskbarButton Button);
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
namespace TheXamlGuy.TaskbarGroup.Core
|
||||
{
|
||||
public record TaskbarButtonInvoked(TaskbarButton Button);
|
||||
}
|
||||
@@ -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<string, TaskbarButton> 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<string, tagRECT> FindTaskbarButtons()
|
||||
{
|
||||
var taskElements = taskListElement?.FindAll(TreeScope.TreeScope_Descendants | TreeScope.TreeScope_Children, taskListCondition);
|
||||
|
||||
var buttons = new Dictionary<string, tagRECT>();
|
||||
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<TaskbarButton>(name, buttonBounds));
|
||||
messenger.Send(new TaskbarButtonCreated(taskbarButtons[name]));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
namespace TheXamlGuy.TaskbarGroup.Core
|
||||
{
|
||||
public record TaskbarButtonRemoved(TaskbarButton Button);
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
namespace TheXamlGuy.TaskbarGroup.Core
|
||||
{
|
||||
public record TaskbarButtonUpdated(TaskbarButton Button);
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
namespace TheXamlGuy.TaskbarGroup.Core
|
||||
{
|
||||
public record TaskbarChanged;
|
||||
}
|
||||
@@ -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<WndProc>(OnWndProc);
|
||||
}
|
||||
|
||||
private void OnWndProc(WndProc args)
|
||||
{
|
||||
if (args.Message == WM_TASKBARCREATED || args.Message == (int)WndProcMessages.WM_SETTINGCHANGE && (int)args.WParam == SPI_SETWORKAREA)
|
||||
{
|
||||
messenger.Send<TaskbarChanged>();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
namespace TheXamlGuy.TaskbarGroup.Core
|
||||
{
|
||||
public enum TaskbarPlacement
|
||||
{
|
||||
Left = 0,
|
||||
Top = 1,
|
||||
Right = 2,
|
||||
Bottom = 3
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
using Windows.Foundation;
|
||||
|
||||
namespace TheXamlGuy.TaskbarGroup.Core
|
||||
{
|
||||
public struct TaskbarState
|
||||
{
|
||||
public TaskbarPlacement Placement;
|
||||
public Rect Rect;
|
||||
public Screen Screen;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<ResolveComReferenceSilent>True</ResolveComReferenceSilent>
|
||||
<LangVersion>10.0</LangVersion>
|
||||
<Platforms>x64;x86</Platforms>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.0.0-preview2" />
|
||||
<PackageReference Include="Microsoft.CSharp" Version="4.7.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="6.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="6.0.0" />
|
||||
<PackageReference Include="Microsoft.Windows.CsWin32" Version="0.1.635-beta">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="System.Reactive" Version="5.0.0" />
|
||||
<PackageReference Include="System.Runtime.WindowsRuntime" Version="5.0.0-preview.5.20278.1" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<COMReference Include="UIA">
|
||||
<WrapperTool>tlbimp</WrapperTool>
|
||||
<VersionMinor>0</VersionMinor>
|
||||
<VersionMajor>1</VersionMajor>
|
||||
<Guid>930299ce-9965-4dec-b0f4-a54848d4b667</Guid>
|
||||
<Lcid>0</Lcid>
|
||||
<Isolated>false</Isolated>
|
||||
<EmbedInteropTypes>true</EmbedInteropTypes>
|
||||
</COMReference>
|
||||
<COMReference Include="UIAutomationClient">
|
||||
<VersionMinor>0</VersionMinor>
|
||||
<VersionMajor>1</VersionMajor>
|
||||
<Guid>944de083-8fb8-45cf-bcb7-c477acb2f897</Guid>
|
||||
<Lcid>0</Lcid>
|
||||
<WrapperTool>tlbimp</WrapperTool>
|
||||
<Isolated>false</Isolated>
|
||||
<EmbedInteropTypes>true</EmbedInteropTypes>
|
||||
</COMReference>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
namespace TheXamlGuy.TaskbarGroup.Core
|
||||
{
|
||||
public record WndProc(uint Message, uint WParam, uint LParam);
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 = $"<DataTemplate " +
|
||||
"xmlns:foundation=\"using:TheXamlGuy.TaskbarGroup.Flyout.Foundation\" " +
|
||||
"xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\" " +
|
||||
"xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\" " +
|
||||
$"xmlns:local=\"using:{viewType.Namespace}\">" +
|
||||
$"<local:{viewType.Name} />" +
|
||||
"</DataTemplate>";
|
||||
|
||||
return (DataTemplate)XamlReader.Load(xaml);
|
||||
}
|
||||
|
||||
return new DataTemplate();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<string>
|
||||
{
|
||||
"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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<TViewModel>(this IBindViewModel<TViewModel> 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<TViewModel>(this IBindViewModel<TViewModel> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
using System;
|
||||
using Windows.UI.Xaml;
|
||||
|
||||
namespace TheXamlGuy.TaskbarGroup.Flyout.Foundation
|
||||
{
|
||||
public interface IDataTemplateFactory
|
||||
{
|
||||
DataTemplate Create(Type type);
|
||||
}
|
||||
}
|
||||
@@ -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<TemplateSelector>()
|
||||
.AddSingleton<IDataTemplateCollection>(new DataTemplateCollection(new Dictionary<Type, Type>()))
|
||||
.AddSingleton<DataTemplateFactory>();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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)]
|
||||
@@ -0,0 +1,33 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
This file contains Runtime Directives, specifications about types your application accesses
|
||||
through reflection and other dynamic code patterns. Runtime Directives are used to control the
|
||||
.NET Native optimizer and ensure that it does not remove code accessed by your library. If your
|
||||
library does not do any reflection, then you generally do not need to edit this file. However,
|
||||
if your library reflects over types, especially types passed to it or derived from its types,
|
||||
then you should write Runtime Directives.
|
||||
|
||||
The most common use of reflection in libraries is to discover information about types passed
|
||||
to the library. Runtime Directives have three ways to express requirements on types passed to
|
||||
your library.
|
||||
|
||||
1. Parameter, GenericParameter, TypeParameter, TypeEnumerableParameter
|
||||
Use these directives to reflect over types passed as a parameter.
|
||||
|
||||
2. SubTypes
|
||||
Use a SubTypes directive to reflect over types derived from another type.
|
||||
|
||||
3. AttributeImplies
|
||||
Use an AttributeImplies directive to indicate that your library needs to reflect over
|
||||
types or methods decorated with an attribute.
|
||||
|
||||
For more information on writing Runtime Directives for libraries, please visit
|
||||
https://go.microsoft.com/fwlink/?LinkID=391919
|
||||
-->
|
||||
<Directives xmlns="http://schemas.microsoft.com/netfx/2013/01/metadata">
|
||||
<Library Name="TheXamlGuy.TaskbarGroup.Flyout.Foundation">
|
||||
|
||||
<!-- add directives for your library here -->
|
||||
|
||||
</Library>
|
||||
</Directives>
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProjectGuid>{35B035A4-E21B-4379-936B-6DEDA31AF860}</ProjectGuid>
|
||||
<OutputType>Library</OutputType>
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>TheXamlGuy.TaskbarGroup.Flyout.Foundation</RootNamespace>
|
||||
<AssemblyName>TheXamlGuy.TaskbarGroup.Flyout.Foundation</AssemblyName>
|
||||
<DefaultLanguage>en-US</DefaultLanguage>
|
||||
<TargetPlatformIdentifier>UAP</TargetPlatformIdentifier>
|
||||
<TargetPlatformVersion Condition=" '$(TargetPlatformVersion)' == '' ">10.0.19041.0</TargetPlatformVersion>
|
||||
<TargetPlatformMinVersion>10.0.19041.0</TargetPlatformMinVersion>
|
||||
<MinimumVisualStudioVersion>14</MinimumVisualStudioVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<ProjectTypeGuids>{A5A43C5B-DE2A-4C0C-9213-0A381AF9435A};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
|
||||
<LangVersion>10.0</LangVersion>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>bin\Debug\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>bin\Release\</OutputPath>
|
||||
<DefineConstants>TRACE;NETFX_CORE;WINDOWS_UWP</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x86'">
|
||||
<PlatformTarget>x86</PlatformTarget>
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<OutputPath>bin\x86\Debug\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP</DefineConstants>
|
||||
<NoWarn>;2008</NoWarn>
|
||||
<DebugType>full</DebugType>
|
||||
<UseVSHostingProcess>false</UseVSHostingProcess>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x86'">
|
||||
<PlatformTarget>x86</PlatformTarget>
|
||||
<OutputPath>bin\x86\Release\</OutputPath>
|
||||
<DefineConstants>TRACE;NETFX_CORE;WINDOWS_UWP</DefineConstants>
|
||||
<Optimize>true</Optimize>
|
||||
<NoWarn>;2008</NoWarn>
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<UseVSHostingProcess>false</UseVSHostingProcess>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|ARM'">
|
||||
<PlatformTarget>ARM</PlatformTarget>
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<OutputPath>bin\ARM\Debug\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP</DefineConstants>
|
||||
<NoWarn>;2008</NoWarn>
|
||||
<DebugType>full</DebugType>
|
||||
<UseVSHostingProcess>false</UseVSHostingProcess>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|ARM'">
|
||||
<PlatformTarget>ARM</PlatformTarget>
|
||||
<OutputPath>bin\ARM\Release\</OutputPath>
|
||||
<DefineConstants>TRACE;NETFX_CORE;WINDOWS_UWP</DefineConstants>
|
||||
<Optimize>true</Optimize>
|
||||
<NoWarn>;2008</NoWarn>
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<UseVSHostingProcess>false</UseVSHostingProcess>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|ARM64'">
|
||||
<PlatformTarget>ARM64</PlatformTarget>
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<OutputPath>bin\ARM64\Debug\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP</DefineConstants>
|
||||
<NoWarn>;2008</NoWarn>
|
||||
<DebugType>full</DebugType>
|
||||
<UseVSHostingProcess>false</UseVSHostingProcess>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|ARM64'">
|
||||
<PlatformTarget>ARM64</PlatformTarget>
|
||||
<OutputPath>bin\ARM64\Release\</OutputPath>
|
||||
<DefineConstants>TRACE;NETFX_CORE;WINDOWS_UWP</DefineConstants>
|
||||
<Optimize>true</Optimize>
|
||||
<NoWarn>;2008</NoWarn>
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<UseVSHostingProcess>false</UseVSHostingProcess>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<OutputPath>bin\x64\Debug\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP</DefineConstants>
|
||||
<NoWarn>;2008</NoWarn>
|
||||
<DebugType>full</DebugType>
|
||||
<UseVSHostingProcess>false</UseVSHostingProcess>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
<OutputPath>bin\x64\Release\</OutputPath>
|
||||
<DefineConstants>TRACE;NETFX_CORE;WINDOWS_UWP</DefineConstants>
|
||||
<Optimize>true</Optimize>
|
||||
<NoWarn>;2008</NoWarn>
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<UseVSHostingProcess>false</UseVSHostingProcess>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<RestoreProjectStyle>PackageReference</RestoreProjectStyle>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="DataTemplateFactory.cs" />
|
||||
<Compile Include="DropTarget.cs" />
|
||||
<Compile Include="IDataTemplateFactory.cs" />
|
||||
<Compile Include="IServiceCollectionExtensions.cs" />
|
||||
<Compile Include="IWindowPrivate.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="TemplateSelector.cs" />
|
||||
<Compile Include="IBindViewModelExtensions.cs" />
|
||||
<EmbeddedResource Include="Properties\TheXamlGuy.TaskbarGroup.Flyout.Foundation.rd.xml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions">
|
||||
<Version>6.0.0</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.NETCore.UniversalWindowsPlatform">
|
||||
<Version>6.2.13</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.UI.Xaml">
|
||||
<Version>2.8.0-prerelease.220118001</Version>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\TheXamlGuy.TaskbarGroup.Core\TheXamlGuy.TaskbarGroup.Core.csproj">
|
||||
<Project>{40d170f4-f8c1-4fae-8a22-a22bf096bbef}</Project>
|
||||
<Name>TheXamlGuy.TaskbarGroup.Core</Name>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<PropertyGroup Condition=" '$(VisualStudioVersion)' == '' or '$(VisualStudioVersion)' < '14.0' ">
|
||||
<VisualStudioVersion>14.0</VisualStudioVersion>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(MSBuildExtensionsPath)\Microsoft\WindowsXaml\v$(VisualStudioVersion)\Microsoft.Windows.UI.Xaml.CSharp.targets" />
|
||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||
Other similar extension points exist, see Microsoft.Common.targets.
|
||||
<Target Name="BeforeBuild"></Target><Target Name="AfterBuild"></Target>
|
||||
-->
|
||||
</Project>
|
||||
@@ -0,0 +1,10 @@
|
||||
<xamlhost:XamlApplication
|
||||
x:Class="TheXamlGuy.TaskbarGroup.Flyout.App"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:controls="using:Microsoft.UI.Xaml.Controls"
|
||||
xmlns:xamlhost="using:Microsoft.Toolkit.Win32.UI.XamlHost">
|
||||
<Application.Resources>
|
||||
<controls:XamlControlsResources />
|
||||
</Application.Resources>
|
||||
</xamlhost:XamlApplication>
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 7.5 KiB |
|
After Width: | Height: | Size: 2.9 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 3.1 KiB |
@@ -0,0 +1,77 @@
|
||||
using System;
|
||||
using Windows.Foundation;
|
||||
using Windows.UI.Xaml;
|
||||
using Windows.UI.Xaml.Controls;
|
||||
using Windows.UI.Xaml.Controls.Primitives;
|
||||
using Windows.UI.Xaml.Input;
|
||||
using Windows.UI.Xaml.Media.Animation;
|
||||
|
||||
namespace TheXamlGuy.TaskbarGroup.Flyout.Controls
|
||||
{
|
||||
public class TaskbarButtonFlyout : ContentControl
|
||||
{
|
||||
public static readonly DependencyProperty TemplateSettingsProperty =
|
||||
DependencyProperty.Register(nameof(TemplateSettings),
|
||||
typeof(TaskbarButtonFlyoutTemplateSettings), typeof(TaskbarButtonFlyout),
|
||||
new PropertyMetadata(null));
|
||||
|
||||
private UIElement child;
|
||||
private Border container;
|
||||
|
||||
public TaskbarButtonFlyout()
|
||||
{
|
||||
DefaultStyleKey = typeof(TaskbarButtonFlyout);
|
||||
TemplateSettings = new TaskbarButtonFlyoutTemplateSettings();
|
||||
}
|
||||
|
||||
public event EventHandler<object> Closed;
|
||||
|
||||
public event EventHandler<object> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
<ResourceDictionary
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:controls="using:TheXamlGuy.TaskbarGroup.Flyout.Controls">
|
||||
<ResourceDictionary.ThemeDictionaries>
|
||||
<ResourceDictionary x:Key="Default">
|
||||
<AcrylicBrush
|
||||
x:Key="AcrylicBackgroundFillColorBrush"
|
||||
BackgroundSource="HostBackdrop"
|
||||
FallbackColor="#2C2C2C"
|
||||
TintColor="#2C2C2C"
|
||||
TintOpacity="0.8" />
|
||||
<StaticResource x:Key="TaskbarButtonFlyoutBorderBrush" ResourceKey="SurfaceStrokeColorDefaultBrush" />
|
||||
<StaticResource x:Key="TaskbarButtonFlyoutBackgroundBrush" ResourceKey="AcrylicBackgroundFillColorBrush" />
|
||||
<Thickness x:Key="TaskbarButtonFlyoutBorderWidth">1</Thickness>
|
||||
</ResourceDictionary>
|
||||
<ResourceDictionary x:Key="Light">
|
||||
<AcrylicBrush
|
||||
x:Key="AcrylicBackgroundFillColorBrush"
|
||||
BackgroundSource="HostBackdrop"
|
||||
FallbackColor="#F9F9F9"
|
||||
TintColor="#FCFCFC"
|
||||
TintOpacity="0.8" />
|
||||
<StaticResource x:Key="TaskbarButtonFlyoutBorderBrush" ResourceKey="SurfaceStrokeColorDefaultBrush" />
|
||||
<StaticResource x:Key="TaskbarButtonFlyoutBackgroundBrush" ResourceKey="AcrylicBackgroundFillColorBrush" />
|
||||
<Thickness x:Key="TaskbarButtonFlyoutBorderWidth">1</Thickness>
|
||||
</ResourceDictionary>
|
||||
</ResourceDictionary.ThemeDictionaries>
|
||||
<AcrylicBrush
|
||||
x:Key="NotificationFlyoutPresenterBackgroundAccentBrush"
|
||||
BackgroundSource="HostBackdrop"
|
||||
FallbackColor="{ThemeResource SystemAccentColorDark1}"
|
||||
TintColor="{ThemeResource SystemAccentColorDark1}"
|
||||
TintOpacity="0.8" />
|
||||
<Style TargetType="controls:TaskbarButtonFlyout">
|
||||
<Setter Property="BorderThickness" Value="{ThemeResource TaskbarButtonFlyoutBorderWidth}" />
|
||||
<Setter Property="BorderBrush" Value="{ThemeResource TaskbarButtonFlyoutBorderBrush}" />
|
||||
<Setter Property="Background" Value="{ThemeResource TaskbarButtonFlyoutBackgroundBrush}" />
|
||||
<Setter Property="CornerRadius" Value="{ThemeResource OverlayCornerRadius}" />
|
||||
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
|
||||
<Setter Property="VerticalContentAlignment" Value="Stretch" />
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="controls:TaskbarButtonFlyout">
|
||||
<Border x:Name="Container" Margin="{TemplateBinding Margin}">
|
||||
<Grid x:Name="LayoutRoot">
|
||||
<Border
|
||||
x:Name="BackgroundElement"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
Background="{TemplateBinding Background}"
|
||||
BackgroundSizing="OuterBorderEdge"
|
||||
BorderBrush="{TemplateBinding BorderBrush}"
|
||||
BorderThickness="{TemplateBinding BorderThickness}"
|
||||
CornerRadius="{TemplateBinding CornerRadius}">
|
||||
<Border.Transitions>
|
||||
<TransitionCollection>
|
||||
<EntranceThemeTransition x:Name="EntranceThemeTransition" />
|
||||
</TransitionCollection>
|
||||
</Border.Transitions>
|
||||
<Grid>
|
||||
<ContentControl
|
||||
x:Name="ContentPresenter"
|
||||
Margin="{TemplateBinding Padding}"
|
||||
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
|
||||
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
|
||||
Content="{TemplateBinding Content}"
|
||||
ContentTemplate="{TemplateBinding ContentTemplate}"
|
||||
ContentTransitions="{TemplateBinding ContentTransitions}" />
|
||||
</Grid>
|
||||
</Border>
|
||||
</Grid>
|
||||
<VisualStateManager.VisualStateGroups>
|
||||
<VisualStateGroup x:Name="PlacementStates">
|
||||
<VisualState x:Name="DefaultPlacement" />
|
||||
<VisualState x:Name="BottomPlacement">
|
||||
<VisualState.Setters>
|
||||
<Setter Target="EntranceThemeTransition.FromHorizontalOffset" Value="0" />
|
||||
<Setter Target="EntranceThemeTransition.FromVerticalOffset" Value="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=TemplateSettings.Height}" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
<VisualState x:Name="TopPlacement">
|
||||
<VisualState.Setters>
|
||||
<Setter Target="EntranceThemeTransition.FromHorizontalOffset" Value="0" />
|
||||
<Setter Target="EntranceThemeTransition.FromVerticalOffset" Value="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=TemplateSettings.NegativeHeight}" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
<VisualState x:Name="LeftPlacement">
|
||||
<VisualState.Setters>
|
||||
<Setter Target="EntranceThemeTransition.FromHorizontalOffset" Value="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=TemplateSettings.NegativeWidth}" />
|
||||
<Setter Target="EntranceThemeTransition.FromVerticalOffset" Value="0" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
<VisualState x:Name="RightPlacement">
|
||||
<VisualState.Setters>
|
||||
<Setter Target="EntranceThemeTransition.FromHorizontalOffset" Value="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=TemplateSettings.Width}" />
|
||||
<Setter Target="EntranceThemeTransition.FromVerticalOffset" Value="0" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
<VisualStateGroup x:Name="ThemeStates">
|
||||
<VisualState x:Name="DefaultTheme" />
|
||||
<VisualState x:Name="ColorPrevalenceTheme">
|
||||
<VisualState.Setters>
|
||||
<Setter Target="BackgroundElement.Background" Value="{ThemeResource TaskbarButtonFlyoutPresenterBackgroundBrush}" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateManager.VisualStateGroups>
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</ResourceDictionary>
|
||||
@@ -0,0 +1,10 @@
|
||||
namespace TheXamlGuy.TaskbarGroup.Flyout.Controls
|
||||
{
|
||||
public enum TaskbarButtonFlyoutPlacement
|
||||
{
|
||||
Left,
|
||||
Top,
|
||||
Right,
|
||||
Bottom,
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<Package
|
||||
xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10"
|
||||
xmlns:mp="http://schemas.microsoft.com/appx/2014/phone/manifest"
|
||||
xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10"
|
||||
IgnorableNamespaces="uap mp">
|
||||
|
||||
<Identity
|
||||
Name="0abc410a-f3f8-42ba-8eba-1fc1f13f5548"
|
||||
Publisher="CN=Daniel Clark"
|
||||
Version="1.0.0.0" />
|
||||
|
||||
<mp:PhoneIdentity PhoneProductId="0abc410a-f3f8-42ba-8eba-1fc1f13f5548" PhonePublisherId="00000000-0000-0000-0000-000000000000"/>
|
||||
|
||||
<Properties>
|
||||
<DisplayName>TheXamlGuy.TaskbarGroup.Flyout</DisplayName>
|
||||
<PublisherDisplayName>Daniel Clark</PublisherDisplayName>
|
||||
<Logo>Assets\StoreLogo.png</Logo>
|
||||
</Properties>
|
||||
|
||||
<Dependencies>
|
||||
<TargetDeviceFamily Name="Windows.Universal" MinVersion="10.0.0.0" MaxVersionTested="10.0.0.0" />
|
||||
</Dependencies>
|
||||
|
||||
<Resources>
|
||||
<Resource Language="x-generate"/>
|
||||
</Resources>
|
||||
|
||||
<Applications>
|
||||
<Application Id="App"
|
||||
Executable="$targetnametoken$.exe"
|
||||
EntryPoint="TheXamlGuy.TaskbarGroup.Flyout.App">
|
||||
<uap:VisualElements
|
||||
DisplayName="TheXamlGuy.TaskbarGroup.Flyout"
|
||||
Square150x150Logo="Assets\Square150x150Logo.png"
|
||||
Square44x44Logo="Assets\Square44x44Logo.png"
|
||||
Description="TheXamlGuy.TaskbarGroup.Flyout"
|
||||
BackgroundColor="transparent">
|
||||
<uap:DefaultTile Wide310x150Logo="Assets\Wide310x150Logo.png"/>
|
||||
<uap:SplashScreen Image="Assets\SplashScreen.png" />
|
||||
</uap:VisualElements>
|
||||
</Application>
|
||||
</Applications>
|
||||
|
||||
<Capabilities>
|
||||
<Capability Name="internetClient" />
|
||||
</Capabilities>
|
||||
</Package>
|
||||
@@ -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)]
|
||||
@@ -0,0 +1,31 @@
|
||||
<!--
|
||||
This file contains Runtime Directives used by .NET Native. The defaults here are suitable for most
|
||||
developers. However, you can modify these parameters to modify the behavior of the .NET Native
|
||||
optimizer.
|
||||
|
||||
Runtime Directives are documented at https://go.microsoft.com/fwlink/?LinkID=391919
|
||||
|
||||
To fully enable reflection for App1.MyClass and all of its public/private members
|
||||
<Type Name="App1.MyClass" Dynamic="Required All"/>
|
||||
|
||||
To enable dynamic creation of the specific instantiation of AppClass<T> over System.Int32
|
||||
<TypeInstantiation Name="App1.AppClass" Arguments="System.Int32" Activate="Required Public" />
|
||||
|
||||
Using the Namespace directive to apply reflection policy to all the types in a particular namespace
|
||||
<Namespace Name="DataClasses.ViewModels" Serialize="All" />
|
||||
-->
|
||||
|
||||
<Directives xmlns="http://schemas.microsoft.com/netfx/2013/01/metadata">
|
||||
<Application>
|
||||
<!--
|
||||
An Assembly element with Name="*Application*" applies to all assemblies in
|
||||
the application package. The asterisks are not wildcards.
|
||||
-->
|
||||
<Assembly Name="*Application*" Dynamic="Required All" />
|
||||
|
||||
|
||||
<!-- Add your application specific runtime directives here. -->
|
||||
|
||||
|
||||
</Application>
|
||||
</Directives>
|
||||
@@ -0,0 +1,159 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">x86</Platform>
|
||||
<ProjectGuid>{4B56828E-30D0-4EAF-89BC-480480989E6C}</ProjectGuid>
|
||||
<OutputType>AppContainerExe</OutputType>
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>TheXamlGuy.TaskbarGroup.Flyout</RootNamespace>
|
||||
<AssemblyName>TheXamlGuy.TaskbarGroup.Flyout</AssemblyName>
|
||||
<DefaultLanguage>en-US</DefaultLanguage>
|
||||
<TargetPlatformIdentifier>UAP</TargetPlatformIdentifier>
|
||||
<TargetPlatformVersion Condition=" '$(TargetPlatformVersion)' == '' ">10.0.19041.0</TargetPlatformVersion>
|
||||
<TargetPlatformMinVersion>10.0.19041.0</TargetPlatformMinVersion>
|
||||
<MinimumVisualStudioVersion>14</MinimumVisualStudioVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<ProjectTypeGuids>{A5A43C5B-DE2A-4C0C-9213-0A381AF9435A};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
|
||||
<WindowsXamlEnableOverview>true</WindowsXamlEnableOverview>
|
||||
<AppxPackageSigningEnabled>false</AppxPackageSigningEnabled>
|
||||
<LangVersion>10.0</LangVersion>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x86'">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<OutputPath>bin\x86\Debug\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP</DefineConstants>
|
||||
<NoWarn>;2008</NoWarn>
|
||||
<DebugType>full</DebugType>
|
||||
<PlatformTarget>x86</PlatformTarget>
|
||||
<UseVSHostingProcess>false</UseVSHostingProcess>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<Prefer32Bit>true</Prefer32Bit>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x86'">
|
||||
<OutputPath>bin\x86\Release\</OutputPath>
|
||||
<DefineConstants>TRACE;NETFX_CORE;WINDOWS_UWP</DefineConstants>
|
||||
<Optimize>true</Optimize>
|
||||
<NoWarn>;2008</NoWarn>
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<PlatformTarget>x86</PlatformTarget>
|
||||
<UseVSHostingProcess>false</UseVSHostingProcess>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<Prefer32Bit>true</Prefer32Bit>
|
||||
<UseDotNetNativeToolchain>true</UseDotNetNativeToolchain>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<OutputPath>bin\x64\Debug\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP</DefineConstants>
|
||||
<NoWarn>;2008</NoWarn>
|
||||
<DebugType>full</DebugType>
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
<UseVSHostingProcess>false</UseVSHostingProcess>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<Prefer32Bit>true</Prefer32Bit>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
|
||||
<OutputPath>bin\x64\Release\</OutputPath>
|
||||
<DefineConstants>TRACE;NETFX_CORE;WINDOWS_UWP</DefineConstants>
|
||||
<Optimize>true</Optimize>
|
||||
<NoWarn>;2008</NoWarn>
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
<UseVSHostingProcess>false</UseVSHostingProcess>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<Prefer32Bit>true</Prefer32Bit>
|
||||
<UseDotNetNativeToolchain>true</UseDotNetNativeToolchain>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<RestoreProjectStyle>PackageReference</RestoreProjectStyle>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="App.xaml.cs">
|
||||
<DependentUpon>App.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Controls\TaskbarButtonFlyoutPlacement.cs" />
|
||||
<Compile Include="Controls\TaskbarButtonFlyoutTemplateSettings.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="Controls\TaskbarButtonFlyout.cs" />
|
||||
<Compile Include="Views\TaskbarButtonGroupItemViewModel.cs" />
|
||||
<Compile Include="Views\TaskbarButtonGroupView.xaml.cs">
|
||||
<DependentUpon>TaskbarButtonGroupView.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Views\TaskbarButtonGroupViewModel.cs" />
|
||||
<Compile Include="Views\TaskbarButtonView.xaml.cs">
|
||||
<DependentUpon>TaskbarButtonView.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Views\TaskbarButtonViewModel.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AppxManifest Include="Package.appxmanifest">
|
||||
<SubType>Designer</SubType>
|
||||
</AppxManifest>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Content Include="Properties\Default.rd.xml" />
|
||||
<Content Include="Assets\LockScreenLogo.scale-200.png" />
|
||||
<Content Include="Assets\SplashScreen.scale-200.png" />
|
||||
<Content Include="Assets\Square150x150Logo.scale-200.png" />
|
||||
<Content Include="Assets\Square44x44Logo.scale-200.png" />
|
||||
<Content Include="Assets\Square44x44Logo.targetsize-24_altform-unplated.png" />
|
||||
<Content Include="Assets\StoreLogo.png" />
|
||||
<Content Include="Assets\Wide310x150Logo.scale-200.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ApplicationDefinition Include="App.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
<SubType>Designer</SubType>
|
||||
</ApplicationDefinition>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NETCore.UniversalWindowsPlatform">
|
||||
<Version>6.2.12</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.Toolkit.Win32.UI.XamlApplication">
|
||||
<Version>6.1.3</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.UI.Xaml">
|
||||
<Version>2.8.0-prerelease.220118001</Version>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Page Include="Controls\TaskbarButtonFlyout.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
<Page Include="Themes\Generic.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
<Page Include="Views\TaskbarButtonGroupView.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
<Page Include="Views\TaskbarButtonView.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\TheXamlGuy.TaskbarGroup.Core\TheXamlGuy.TaskbarGroup.Core.csproj">
|
||||
<Project>{40D170F4-F8C1-4FAE-8A22-A22BF096BBEF}</Project>
|
||||
<Name>TheXamlGuy.TaskbarGroup.Core</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\TheXamlGuy.TaskbarGroup.Flyout.Foundation\TheXamlGuy.TaskbarGroup.Flyout.Foundation.csproj">
|
||||
<Project>{35b035a4-e21b-4379-936b-6deda31af860}</Project>
|
||||
<Name>TheXamlGuy.TaskbarGroup.Flyout.Foundation</Name>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<PropertyGroup Condition=" '$(VisualStudioVersion)' == '' or '$(VisualStudioVersion)' < '14.0' ">
|
||||
<VisualStudioVersion>14.0</VisualStudioVersion>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(MSBuildExtensionsPath)\Microsoft\WindowsXaml\v$(VisualStudioVersion)\Microsoft.Windows.UI.Xaml.CSharp.targets" />
|
||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||
Other similar extension points exist, see Microsoft.Common.targets.
|
||||
<Target Name="BeforeBuild"></Target><Target Name="AfterBuild"></Target>
|
||||
-->
|
||||
</Project>
|
||||
@@ -0,0 +1,5 @@
|
||||
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="/Controls/TaskbarButtonFlyout.xaml" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
</ResourceDictionary>
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
<UserControl
|
||||
x:Class="TheXamlGuy.TaskbarGroup.Flyout.TaskbarButtonGroupView"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
AllowDrop="True">
|
||||
<StackPanel>
|
||||
<TextBox HorizontalAlignment="Stretch" Text="{x:Bind ViewModel.Name, Mode=TwoWay}" />
|
||||
<GridView
|
||||
x:Name="GridView"
|
||||
IsItemClickEnabled="True"
|
||||
ItemsSource="{x:Bind ViewModel}"
|
||||
SelectionMode="None">
|
||||
<GridView.ItemContainerStyle>
|
||||
<Style TargetType="GridViewItem">
|
||||
<Setter Property="Margin" Value="0" />
|
||||
</Style>
|
||||
</GridView.ItemContainerStyle>
|
||||
<GridView.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Border
|
||||
Width="94"
|
||||
Height="84"
|
||||
Background="Transparent" />
|
||||
</DataTemplate>
|
||||
</GridView.ItemTemplate>
|
||||
<GridView.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<ItemsWrapGrid MaximumRowsOrColumns="2" Orientation="Horizontal" />
|
||||
</ItemsPanelTemplate>
|
||||
</GridView.ItemsPanel>
|
||||
</GridView>
|
||||
</StackPanel>
|
||||
|
||||
</UserControl>
|
||||