Skip to content

Commit

Permalink
feature: Added Uno support (reactiveui#2067)
Browse files Browse the repository at this point in the history
  • Loading branch information
weitzhandler authored and madmonkey committed Jul 12, 2019
1 parent 0e0a1e7 commit 5fdbd63
Show file tree
Hide file tree
Showing 21 changed files with 603 additions and 31 deletions.
1 change: 1 addition & 0 deletions build.cake
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ var packageWhitelist = new List<FilePath>
MakeAbsolute(File("./src/ReactiveUI.Fody.Helpers/ReactiveUI.Fody.Helpers.csproj")),
MakeAbsolute(File("./src/ReactiveUI.AndroidSupport/ReactiveUI.AndroidSupport.csproj")),
MakeAbsolute(File("./src/ReactiveUI.XamForms/ReactiveUI.XamForms.csproj")),
MakeAbsolute(File("./src/ReactiveUI.Uno/ReactiveUI.Uno.csproj")),
};

if (IsRunningOnWindows())
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
[assembly: System.Runtime.CompilerServices.InternalsVisibleToAttribute("ReactiveUI.AndroidSupport")]
[assembly: System.Runtime.CompilerServices.InternalsVisibleToAttribute("ReactiveUI.Tests")]
[assembly: System.Runtime.CompilerServices.InternalsVisibleToAttribute("ReactiveUI.Uno")]
[assembly: System.Runtime.CompilerServices.InternalsVisibleToAttribute("ReactiveUI.Winforms")]
[assembly: System.Runtime.CompilerServices.InternalsVisibleToAttribute("ReactiveUI.Wpf")]
[assembly: System.Runtime.CompilerServices.InternalsVisibleToAttribute("ReactiveUI.XamForms")]
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
[assembly: System.Runtime.CompilerServices.InternalsVisibleToAttribute("ReactiveUI.AndroidSupport")]
[assembly: System.Runtime.CompilerServices.InternalsVisibleToAttribute("ReactiveUI.Tests")]
[assembly: System.Runtime.CompilerServices.InternalsVisibleToAttribute("ReactiveUI.Uno")]
[assembly: System.Runtime.CompilerServices.InternalsVisibleToAttribute("ReactiveUI.Winforms")]
[assembly: System.Runtime.CompilerServices.InternalsVisibleToAttribute("ReactiveUI.Wpf")]
[assembly: System.Runtime.CompilerServices.InternalsVisibleToAttribute("ReactiveUI.XamForms")]
Expand Down
66 changes: 66 additions & 0 deletions src/ReactiveUI.Uno/ActivationForViewFetcher.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// Copyright (c) 2019 .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;
using System.Reactive.Linq;
using System.Reflection;

using Windows.Foundation;
using Windows.UI.Xaml;

namespace ReactiveUI
{
/// <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)
{
return typeof(FrameworkElement).GetTypeInfo().IsAssignableFrom(view.GetTypeInfo()) ? 10 : 0;
}

/// <inheritdoc/>
public IObservable<bool> GetActivationForView(IActivatable view)
{
var fe = view as FrameworkElement;

if (fe == null)
{
return Observable<bool>.Empty;
}

#pragma warning disable SA1114 // Parameter list after.
#if NETSTANDARD || MAC
var viewLoaded = Observable.FromEvent<RoutedEventHandler, bool>(
#else
var viewLoaded = Observable.FromEvent<TypedEventHandler<DependencyObject, object>, bool>(
#endif
eventHandler => (_, __) => eventHandler(true),
x => fe.Loading += x,
x => fe.Loading -= x);
var viewUnloaded = Observable.FromEvent<RoutedEventHandler, bool>(
handler =>
{
void EventHandler(object sender, RoutedEventArgs e) => handler(false);
return EventHandler;
},
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();
}
}
}
251 changes: 251 additions & 0 deletions src/ReactiveUI.Uno/CoreDispatcherScheduler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,251 @@
// Copyright (c) 2019 .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.

// <auto-generated />

using System;
using System.Collections.Generic;
using System.Reactive.Disposables;
using System.Runtime.ExceptionServices;
using System.Text;
using System.Threading;

using Windows.UI.Core;
using Windows.UI.Xaml;

namespace System.Reactive.Concurrency
{
/// <summary>
/// Represents an object that schedules units of work on a <see cref="CoreDispatcher"/>.
/// </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 current Dispatcher.
/// </remarks>
[CLSCompliant(false)]
public sealed class CoreDispatcherScheduler : LocalScheduler, ISchedulerPeriodic
{
/// <summary>
/// Constructs a <see cref="CoreDispatcherScheduler"/> that schedules units of work on the given <see cref="CoreDispatcher"/>.
/// </summary>
/// <param name="dispatcher">Dispatcher to schedule work on.</param>
/// <exception cref="ArgumentNullException"><paramref name="dispatcher"/> is <c>null</c>.</exception>
public CoreDispatcherScheduler(CoreDispatcher dispatcher)
{
Dispatcher = dispatcher ?? throw new ArgumentNullException(nameof(dispatcher));
Priority = CoreDispatcherPriority.Normal;
}

/// <summary>
/// Constructs a <see cref="CoreDispatcherScheduler"/> that schedules units of work on the given <see cref="CoreDispatcher"/> with the given priority.
/// </summary>
/// <param name="dispatcher">Dispatcher to schedule work on.</param>
/// <param name="priority">Priority for scheduled units of work.</param>
/// <exception cref="ArgumentNullException"><paramref name="dispatcher"/> is <c>null</c>.</exception>
public CoreDispatcherScheduler(CoreDispatcher dispatcher, CoreDispatcherPriority priority)
{
Dispatcher = dispatcher ?? throw new ArgumentNullException(nameof(dispatcher));
Priority = priority;
}

/// <summary>
/// Gets the scheduler that schedules work on the <see cref="CoreDispatcher"/> associated with the current Window.
/// </summary>
public static CoreDispatcherScheduler Current
{
get
{
var window = Window.Current;
if (window == null)
{
throw new InvalidOperationException("There is no current window that has been created.");
}

return new CoreDispatcherScheduler(window.Dispatcher);
}
}

/// <summary>
/// Gets the <see cref="CoreDispatcher"/> associated with the <see cref="CoreDispatcherScheduler"/>.
/// </summary>
public CoreDispatcher Dispatcher { get; }

/// <summary>
/// Gets the priority at which work is scheduled.
/// </summary>
public CoreDispatcherPriority 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();

var res = Dispatcher.RunAsync(Priority, () =>
{
if (!d.IsDisposed)
{
try
{
d.Disposable = action(this, state);
}
catch (Exception ex)
{
//
// Work-around for the behavior of throwing from RunAsync not propagating
// the exception to the Application.UnhandledException event (as of W8RP)
// as our users have come to expect from previous XAML stacks using Rx.
//
// If we wouldn't do this, there'd be an observable behavioral difference
// between scheduling with TimeSpan.Zero or using this overload.
//
// For scheduler implementation guidance rules, see TaskPoolScheduler.cs
// in System.Reactive.PlatformServices\Reactive\Concurrency.
//
var timer = new DispatcherTimer
{
Interval = TimeSpan.Zero
};
timer.Tick += (o, e) =>
{
timer.Stop();
ExceptionDispatchInfo.Capture(ex).Throw();
};
timer.Start();
}
}
});

return StableCompositeDisposable.Create(
d,
Disposable.Create(res, _ => _.Cancel())
);
}

/// <summary>
/// Schedules an action to be executed after <paramref name="dueTime"/> on the dispatcher, using a <see cref="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 = new DispatcherTimer();

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

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

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

return d;
}

/// <summary>
/// Schedules a periodic piece of work on the dispatcher, using a <see cref="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)
{
//
// According to MSDN documentation, the default is TimeSpan.Zero, so that's definitely valid.
// Empirical observation - negative values seem to be normalized to TimeSpan.Zero, but let's not go there.
//
if (period < TimeSpan.Zero)
{
throw new ArgumentOutOfRangeException(nameof(period));
}

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

var timer = new DispatcherTimer();

var state1 = state;

timer.Tick += (o, 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 = _ => _;
}
});
}
}
}
42 changes: 42 additions & 0 deletions src/ReactiveUI.Uno/PlatformRegistrations.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// Copyright (c) 2019 .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.Reactive.Concurrency;
using System.Reactive.PlatformServices;

namespace ReactiveUI
{
/// <summary>
/// UWP platform registrations.
/// </summary>
/// <seealso cref="ReactiveUI.IWantsToRegisterStuff" />
public class PlatformRegistrations : IWantsToRegisterStuff
{
/// <inheritdoc/>
public void Register(Action<Func<object>, Type> registerFunction)
{
registerFunction(() => new PlatformOperations(), typeof(IPlatformOperations));
registerFunction(() => new ActivationForViewFetcher(), typeof(IActivationForViewFetcher));
registerFunction(() => new DependencyObjectObservableForProperty(), typeof(ICreatesObservableForProperty));
registerFunction(() => new BooleanToVisibilityTypeConverter(), typeof(IBindingTypeConverter));
registerFunction(() => new AutoDataTemplateBindingHook(), typeof(IPropertyBindingHook));
registerFunction(() => new WinRTAppDataDriver(), typeof(ISuspensionDriver));

#if NETSTANDARD
if (WasmPlatformEnlightenmentProvider.IsWasm)
{
RxApp.TaskpoolScheduler = WasmScheduler.Default;
RxApp.MainThreadScheduler = WasmScheduler.Default;
}
else
#endif
{
RxApp.TaskpoolScheduler = TaskPoolScheduler.Default;
RxApp.MainThreadScheduler = new WaitForDispatcherScheduler(() => CoreDispatcherScheduler.Current);
}
}
}
}
Loading

0 comments on commit 5fdbd63

Please sign in to comment.