Skip to content

Commit

Permalink
Added support for WinUI3 desktop (#2897)
Browse files Browse the repository at this point in the history
  • Loading branch information
harvinders committed Aug 22, 2021
1 parent 43fb50d commit 66a4d38
Show file tree
Hide file tree
Showing 10 changed files with 719 additions and 8 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -369,4 +369,5 @@ src/Tools/
**/[Rr]esources/[Rr]esource.[Dd]esigner.cs

# MSBuild generator editor configs
**/*.GeneratedMSBuildEditorConfig.editorconfig
**/*.GeneratedMSBuildEditorConfig.editorconfig
/app
8 changes: 6 additions & 2 deletions src/ReactiveUI.Uwp/Common/RoutedViewHost.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,19 @@
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Reactive.Linq;
using System.Windows;
using ReactiveUI;
using Splat;

#if NETFX_CORE || HAS_UNO
#if HAS_WINUI
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
#elif NETFX_CORE || HAS_UNO
using System.Windows;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
#else

using System.Windows;
using System.Windows.Controls;

#endif
Expand Down
10 changes: 8 additions & 2 deletions src/ReactiveUI.Uwp/Common/ViewModelViewHost.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,22 @@
using System.Reactive;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Windows;
using Splat;

#if NETFX_CORE || HAS_UNO
#if HAS_WINUI

using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;

#elif NETFX_CORE || HAS_UNO

using System.Windows;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;

#else

using System.Windows;
using System.Windows.Controls;

#endif
Expand Down
58 changes: 58 additions & 0 deletions src/ReactiveUI.WinUI/ActivationForViewFetcher.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// Copyright (c) 2021 .NET Foundation and Contributors. All rights reserved.
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for full license information.

using System;
using System.Linq;
using System.Reactive.Linq;
using System.Reflection;
using Microsoft.UI.Xaml;
using Windows.Foundation;

namespace ReactiveUI.WinUI
{
/// <summary>
/// ActiveationForViewFetcher is how ReactiveUI determine when a
/// View is activated or deactivated. This is usually only used when porting
/// ReactiveUI to a new UI framework.
/// </summary>
public class ActivationForViewFetcher : IActivationForViewFetcher
{
/// <inheritdoc/>
public int GetAffinityForView(Type view) => typeof(FrameworkElement).GetTypeInfo().IsAssignableFrom(view.GetTypeInfo()) ? 10 : 0;

/// <inheritdoc/>
public IObservable<bool> GetActivationForView(IActivatableView view)
{
if (!(view is FrameworkElement fe))
{
return Observable<bool>.Empty;
}

var viewLoaded = Observable.FromEvent<TypedEventHandler<FrameworkElement, object>, bool>(
eventHandler =>
{
void Handler(FrameworkElement sender, object e) => eventHandler(true);
return Handler;
},
x => fe.Loading += x,
x => fe.Loading -= x);

var viewUnloaded = Observable.FromEvent<RoutedEventHandler, bool>(
eventHandler =>
{
void Handler(object sender, RoutedEventArgs e) => eventHandler(false);
return Handler;
},
x => fe.Unloaded += x,
x => fe.Unloaded -= x);

return viewLoaded
.Merge(viewUnloaded)
.Select(b => b ? fe.WhenAnyValue(x => x.IsHitTestVisible).SkipWhile(x => !x) : Observables.False)
.Switch()
.DistinctUntilChanged();
}
}
}
211 changes: 211 additions & 0 deletions src/ReactiveUI.WinUI/DispatcherQueueScheduler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT License.
// See the LICENSE file in the project root for more information.
// <auto-generated />

#if HAS_WINUI
using System.Reactive.Disposables;
using System.Threading;
using Microsoft.UI.Dispatching;

namespace System.Reactive.Concurrency
{
/// <summary>
/// Represents an object that schedules units of work on a <see cref="System.Windows.Threading.Dispatcher"/>.
/// </summary>
/// <remarks>
/// This scheduler type is typically used indirectly through the <see cref="Linq.DispatcherObservable.ObserveOnDispatcher{TSource}(IObservable{TSource})"/> and <see cref="Linq.DispatcherObservable.SubscribeOnDispatcher{TSource}(IObservable{TSource})"/> methods that use the Dispatcher on the calling thread.
/// </remarks>
public class DispatcherQueueScheduler : LocalScheduler, ISchedulerPeriodic
{
/// <summary>
/// Gets the scheduler that schedules work on the <see cref="System.Windows.Threading.Dispatcher"/> for the current thread.
/// </summary>
public static DispatcherQueueScheduler Current
{
get
{
var dispatcher = DispatcherQueue.GetForCurrentThread();
if (dispatcher == null)
{
throw new InvalidOperationException("There is no current dispatcher thread");
}

return new DispatcherQueueScheduler(dispatcher);
}
}

/// <summary>
/// Constructs a <see cref="DispatcherScheduler"/> that schedules units of work on the given <see cref="System.Windows.Threading.Dispatcher"/>.
/// </summary>
/// <param name="dispatcher"><see cref="DispatcherScheduler"/> to schedule work on.</param>
/// <exception cref="ArgumentNullException"><paramref name="dispatcher"/> is <c>null</c>.</exception>
public DispatcherQueueScheduler(DispatcherQueue dispatcher)
{
Dispatcher = dispatcher ?? throw new ArgumentNullException(nameof(dispatcher));
Priority = DispatcherQueuePriority.Normal;

}

/// <summary>
/// Constructs a <see cref="DispatcherScheduler"/> that schedules units of work on the given <see cref="System.Windows.Threading.Dispatcher"/> at the given priority.
/// </summary>
/// <param name="dispatcher"><see cref="DispatcherScheduler"/> to schedule work on.</param>
/// <param name="priority">Priority at which units of work are scheduled.</param>
/// <exception cref="ArgumentNullException"><paramref name="dispatcher"/> is <c>null</c>.</exception>
public DispatcherQueueScheduler(DispatcherQueue dispatcher, DispatcherQueuePriority priority)
{
Dispatcher = dispatcher ?? throw new ArgumentNullException(nameof(dispatcher));
Priority = priority;
}

/// <summary>
/// Gets the <see cref="System.Windows.Threading.Dispatcher"/> associated with the <see cref="DispatcherScheduler"/>.
/// </summary>
public DispatcherQueue Dispatcher { get; }

/// <summary>
/// Gets the priority at which work items will be dispatched.
/// </summary>
public DispatcherQueuePriority Priority { get; }

/// <summary>
/// Schedules an action to be executed on the dispatcher.
/// </summary>
/// <typeparam name="TState">The type of the state passed to the scheduled action.</typeparam>
/// <param name="state">State passed to the action to be executed.</param>
/// <param name="action">Action to be executed.</param>
/// <returns>The disposable object used to cancel the scheduled action (best effort).</returns>
/// <exception cref="ArgumentNullException"><paramref name="action"/> is <c>null</c>.</exception>
public override IDisposable Schedule<TState>(TState state, Func<IScheduler, TState, IDisposable> action)
{
if (action == null)
{
throw new ArgumentNullException(nameof(action));
}

var d = new SingleAssignmentDisposable();

Dispatcher.TryEnqueue(Priority,
() =>
{
if (!d.IsDisposed)
{
d.Disposable = action(this, state);
}
});

return d;
}

/// <summary>
/// Schedules an action to be executed after <paramref name="dueTime"/> on the dispatcher, using a <see cref="System.Windows.Threading.DispatcherTimer"/> object.
/// </summary>
/// <typeparam name="TState">The type of the state passed to the scheduled action.</typeparam>
/// <param name="state">State passed to the action to be executed.</param>
/// <param name="action">Action to be executed.</param>
/// <param name="dueTime">Relative time after which to execute the action.</param>
/// <returns>The disposable object used to cancel the scheduled action (best effort).</returns>
/// <exception cref="ArgumentNullException"><paramref name="action"/> is <c>null</c>.</exception>
public override IDisposable Schedule<TState>(TState state, TimeSpan dueTime, Func<IScheduler, TState, IDisposable> action)
{
if (action == null)
{
throw new ArgumentNullException(nameof(action));
}

var dt = Scheduler.Normalize(dueTime);
if (dt.Ticks == 0)
{
return Schedule(state, action);
}

return ScheduleSlow(state, dt, action);
}

private IDisposable ScheduleSlow<TState>(TState state, TimeSpan dueTime, Func<IScheduler, TState, IDisposable> action)
{
var d = new MultipleAssignmentDisposable();

var timer = Dispatcher.CreateTimer();

timer.Tick += (s, e) =>
{
var t = Interlocked.Exchange(ref timer, null);
if (t != null)
{
try
{
d.Disposable = action(this, state);
}
finally
{
t.Stop();
action = static (s, t) => Disposable.Empty;
}
}
};

timer.Interval = dueTime;
timer.Start();

d.Disposable = Disposable.Create(() =>
{
var t = Interlocked.Exchange(ref timer, null);
if (t != null)
{
t.Stop();
action = static (s, t) => Disposable.Empty;
}
});

return d;
}

/// <summary>
/// Schedules a periodic piece of work on the dispatcher, using a <see cref="System.Windows.Threading.DispatcherTimer"/> object.
/// </summary>
/// <typeparam name="TState">The type of the state passed to the scheduled action.</typeparam>
/// <param name="state">Initial state passed to the action upon the first iteration.</param>
/// <param name="period">Period for running the work periodically.</param>
/// <param name="action">Action to be executed, potentially updating the state.</param>
/// <returns>The disposable object used to cancel the scheduled recurring action (best effort).</returns>
/// <exception cref="ArgumentNullException"><paramref name="action"/> is <c>null</c>.</exception>
/// <exception cref="ArgumentOutOfRangeException"><paramref name="period"/> is less than <see cref="TimeSpan.Zero"/>.</exception>
public IDisposable SchedulePeriodic<TState>(TState state, TimeSpan period, Func<TState, TState> action)
{
if (period < TimeSpan.Zero)
{
throw new ArgumentOutOfRangeException(nameof(period));
}

if (action == null)
{
throw new ArgumentNullException(nameof(action));
}

var timer = Dispatcher.CreateTimer();

var state1 = state;

timer.Tick += (s, e) =>
{
state1 = action(state1);
};

timer.Interval = period;
timer.Start();

return Disposable.Create(() =>
{
var t = Interlocked.Exchange(ref timer, null);
if (t != null)
{
t.Stop();
action = static _ => _;
}
});
}
}
}
#endif
29 changes: 29 additions & 0 deletions src/ReactiveUI.WinUI/ReactiveUI.WinUI.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net5.0-windows10.0.19041.0</TargetFramework>
<TargetPlatformMinVersion>10.0.17763.0</TargetPlatformMinVersion>
<PackageDescription>Contains the ReactiveUI platform specific extensions for WinUI Desktop</PackageDescription>
<RootNamespace>ReactiveUI.WinUI.Desktop</RootNamespace>
<RuntimeIdentifiers>win10-x86;win10-x64</RuntimeIdentifiers>
<DefineConstants>HAS_WINUI</DefineConstants>
<PackageTags>mvvm;reactiveui;rx;reactive extensions;observable;LINQ;events;winui</PackageTags>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<UseWinUI>true</UseWinUI>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.ProjectReunion" Version="0.8.2" />
<PackageReference Include="Microsoft.ProjectReunion.Foundation" Version="0.8.2" />
<PackageReference Include="Microsoft.ProjectReunion.WinUI" Version="0.8.2" />
<PackageReference Include="System.Reactive" Version="5.0.0" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\ReactiveUI\ReactiveUI.csproj" />
</ItemGroup>
<ItemGroup>
<Compile Include="..\ReactiveUI.Uwp\common\**\*.cs" LinkBase="common" />
<None Include="..\ReactiveUI.Uwp\Rx\**\*.cs" LinkBase="Rx" />
</ItemGroup>

</Project>

0 comments on commit 66a4d38

Please sign in to comment.