Skip to content

Commit 5fdbd63

Browse files
weitzhandlermadmonkey
authored andcommitted
feature: Added Uno support (reactiveui#2067)
1 parent 0e0a1e7 commit 5fdbd63

21 files changed

+603
-31
lines changed

build.cake

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ var packageWhitelist = new List<FilePath>
2424
MakeAbsolute(File("./src/ReactiveUI.Fody.Helpers/ReactiveUI.Fody.Helpers.csproj")),
2525
MakeAbsolute(File("./src/ReactiveUI.AndroidSupport/ReactiveUI.AndroidSupport.csproj")),
2626
MakeAbsolute(File("./src/ReactiveUI.XamForms/ReactiveUI.XamForms.csproj")),
27+
MakeAbsolute(File("./src/ReactiveUI.Uno/ReactiveUI.Uno.csproj")),
2728
};
2829

2930
if (IsRunningOnWindows())

src/ReactiveUI.Tests/API/ApiApprovalTests.ReactiveUI.net461.approved.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
[assembly: System.Runtime.CompilerServices.InternalsVisibleToAttribute("ReactiveUI.AndroidSupport")]
22
[assembly: System.Runtime.CompilerServices.InternalsVisibleToAttribute("ReactiveUI.Tests")]
3+
[assembly: System.Runtime.CompilerServices.InternalsVisibleToAttribute("ReactiveUI.Uno")]
34
[assembly: System.Runtime.CompilerServices.InternalsVisibleToAttribute("ReactiveUI.Winforms")]
45
[assembly: System.Runtime.CompilerServices.InternalsVisibleToAttribute("ReactiveUI.Wpf")]
56
[assembly: System.Runtime.CompilerServices.InternalsVisibleToAttribute("ReactiveUI.XamForms")]

src/ReactiveUI.Tests/API/ApiApprovalTests.ReactiveUI.netcoreapp2.0.approved.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
[assembly: System.Runtime.CompilerServices.InternalsVisibleToAttribute("ReactiveUI.AndroidSupport")]
22
[assembly: System.Runtime.CompilerServices.InternalsVisibleToAttribute("ReactiveUI.Tests")]
3+
[assembly: System.Runtime.CompilerServices.InternalsVisibleToAttribute("ReactiveUI.Uno")]
34
[assembly: System.Runtime.CompilerServices.InternalsVisibleToAttribute("ReactiveUI.Winforms")]
45
[assembly: System.Runtime.CompilerServices.InternalsVisibleToAttribute("ReactiveUI.Wpf")]
56
[assembly: System.Runtime.CompilerServices.InternalsVisibleToAttribute("ReactiveUI.XamForms")]
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
// Copyright (c) 2019 .NET Foundation and Contributors. All rights reserved.
2+
// Licensed to the .NET Foundation under one or more agreements.
3+
// The .NET Foundation licenses this file to you under the MIT license.
4+
// See the LICENSE file in the project root for full license information.
5+
6+
using System;
7+
using System.Linq;
8+
using System.Reactive;
9+
using System.Reactive.Linq;
10+
using System.Reflection;
11+
12+
using Windows.Foundation;
13+
using Windows.UI.Xaml;
14+
15+
namespace ReactiveUI
16+
{
17+
/// <summary>
18+
/// ActiveationForViewFetcher is how ReactiveUI determine when a
19+
/// View is activated or deactivated. This is usually only used when porting
20+
/// ReactiveUI to a new UI framework.
21+
/// </summary>
22+
public class ActivationForViewFetcher : IActivationForViewFetcher
23+
{
24+
/// <inheritdoc/>
25+
public int GetAffinityForView(Type view)
26+
{
27+
return typeof(FrameworkElement).GetTypeInfo().IsAssignableFrom(view.GetTypeInfo()) ? 10 : 0;
28+
}
29+
30+
/// <inheritdoc/>
31+
public IObservable<bool> GetActivationForView(IActivatable view)
32+
{
33+
var fe = view as FrameworkElement;
34+
35+
if (fe == null)
36+
{
37+
return Observable<bool>.Empty;
38+
}
39+
40+
#pragma warning disable SA1114 // Parameter list after.
41+
#if NETSTANDARD || MAC
42+
var viewLoaded = Observable.FromEvent<RoutedEventHandler, bool>(
43+
#else
44+
var viewLoaded = Observable.FromEvent<TypedEventHandler<DependencyObject, object>, bool>(
45+
#endif
46+
eventHandler => (_, __) => eventHandler(true),
47+
x => fe.Loading += x,
48+
x => fe.Loading -= x);
49+
50+
var viewUnloaded = Observable.FromEvent<RoutedEventHandler, bool>(
51+
handler =>
52+
{
53+
void EventHandler(object sender, RoutedEventArgs e) => handler(false);
54+
return EventHandler;
55+
},
56+
x => fe.Unloaded += x,
57+
x => fe.Unloaded -= x);
58+
59+
return viewLoaded
60+
.Merge(viewUnloaded)
61+
.Select(b => b ? fe.WhenAnyValue(x => x.IsHitTestVisible).SkipWhile(x => !x) : Observables.False)
62+
.Switch()
63+
.DistinctUntilChanged();
64+
}
65+
}
66+
}
Lines changed: 251 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,251 @@
1+
// Copyright (c) 2019 .NET Foundation and Contributors. All rights reserved.
2+
// Licensed to the .NET Foundation under one or more agreements.
3+
// The .NET Foundation licenses this file to you under the MIT license.
4+
// See the LICENSE file in the project root for full license information.
5+
6+
// <auto-generated />
7+
8+
using System;
9+
using System.Collections.Generic;
10+
using System.Reactive.Disposables;
11+
using System.Runtime.ExceptionServices;
12+
using System.Text;
13+
using System.Threading;
14+
15+
using Windows.UI.Core;
16+
using Windows.UI.Xaml;
17+
18+
namespace System.Reactive.Concurrency
19+
{
20+
/// <summary>
21+
/// Represents an object that schedules units of work on a <see cref="CoreDispatcher"/>.
22+
/// </summary>
23+
/// <remarks>
24+
/// 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.
25+
/// </remarks>
26+
[CLSCompliant(false)]
27+
public sealed class CoreDispatcherScheduler : LocalScheduler, ISchedulerPeriodic
28+
{
29+
/// <summary>
30+
/// Constructs a <see cref="CoreDispatcherScheduler"/> that schedules units of work on the given <see cref="CoreDispatcher"/>.
31+
/// </summary>
32+
/// <param name="dispatcher">Dispatcher to schedule work on.</param>
33+
/// <exception cref="ArgumentNullException"><paramref name="dispatcher"/> is <c>null</c>.</exception>
34+
public CoreDispatcherScheduler(CoreDispatcher dispatcher)
35+
{
36+
Dispatcher = dispatcher ?? throw new ArgumentNullException(nameof(dispatcher));
37+
Priority = CoreDispatcherPriority.Normal;
38+
}
39+
40+
/// <summary>
41+
/// Constructs a <see cref="CoreDispatcherScheduler"/> that schedules units of work on the given <see cref="CoreDispatcher"/> with the given priority.
42+
/// </summary>
43+
/// <param name="dispatcher">Dispatcher to schedule work on.</param>
44+
/// <param name="priority">Priority for scheduled units of work.</param>
45+
/// <exception cref="ArgumentNullException"><paramref name="dispatcher"/> is <c>null</c>.</exception>
46+
public CoreDispatcherScheduler(CoreDispatcher dispatcher, CoreDispatcherPriority priority)
47+
{
48+
Dispatcher = dispatcher ?? throw new ArgumentNullException(nameof(dispatcher));
49+
Priority = priority;
50+
}
51+
52+
/// <summary>
53+
/// Gets the scheduler that schedules work on the <see cref="CoreDispatcher"/> associated with the current Window.
54+
/// </summary>
55+
public static CoreDispatcherScheduler Current
56+
{
57+
get
58+
{
59+
var window = Window.Current;
60+
if (window == null)
61+
{
62+
throw new InvalidOperationException("There is no current window that has been created.");
63+
}
64+
65+
return new CoreDispatcherScheduler(window.Dispatcher);
66+
}
67+
}
68+
69+
/// <summary>
70+
/// Gets the <see cref="CoreDispatcher"/> associated with the <see cref="CoreDispatcherScheduler"/>.
71+
/// </summary>
72+
public CoreDispatcher Dispatcher { get; }
73+
74+
/// <summary>
75+
/// Gets the priority at which work is scheduled.
76+
/// </summary>
77+
public CoreDispatcherPriority Priority { get; }
78+
79+
/// <summary>
80+
/// Schedules an action to be executed on the dispatcher.
81+
/// </summary>
82+
/// <typeparam name="TState">The type of the state passed to the scheduled action.</typeparam>
83+
/// <param name="state">State passed to the action to be executed.</param>
84+
/// <param name="action">Action to be executed.</param>
85+
/// <returns>The disposable object used to cancel the scheduled action (best effort).</returns>
86+
/// <exception cref="ArgumentNullException"><paramref name="action"/> is <c>null</c>.</exception>
87+
public override IDisposable Schedule<TState>(TState state, Func<IScheduler, TState, IDisposable> action)
88+
{
89+
if (action == null)
90+
{
91+
throw new ArgumentNullException(nameof(action));
92+
}
93+
94+
var d = new SingleAssignmentDisposable();
95+
96+
var res = Dispatcher.RunAsync(Priority, () =>
97+
{
98+
if (!d.IsDisposed)
99+
{
100+
try
101+
{
102+
d.Disposable = action(this, state);
103+
}
104+
catch (Exception ex)
105+
{
106+
//
107+
// Work-around for the behavior of throwing from RunAsync not propagating
108+
// the exception to the Application.UnhandledException event (as of W8RP)
109+
// as our users have come to expect from previous XAML stacks using Rx.
110+
//
111+
// If we wouldn't do this, there'd be an observable behavioral difference
112+
// between scheduling with TimeSpan.Zero or using this overload.
113+
//
114+
// For scheduler implementation guidance rules, see TaskPoolScheduler.cs
115+
// in System.Reactive.PlatformServices\Reactive\Concurrency.
116+
//
117+
var timer = new DispatcherTimer
118+
{
119+
Interval = TimeSpan.Zero
120+
};
121+
timer.Tick += (o, e) =>
122+
{
123+
timer.Stop();
124+
ExceptionDispatchInfo.Capture(ex).Throw();
125+
};
126+
127+
timer.Start();
128+
}
129+
}
130+
});
131+
132+
return StableCompositeDisposable.Create(
133+
d,
134+
Disposable.Create(res, _ => _.Cancel())
135+
);
136+
}
137+
138+
/// <summary>
139+
/// Schedules an action to be executed after <paramref name="dueTime"/> on the dispatcher, using a <see cref="DispatcherTimer"/> object.
140+
/// </summary>
141+
/// <typeparam name="TState">The type of the state passed to the scheduled action.</typeparam>
142+
/// <param name="state">State passed to the action to be executed.</param>
143+
/// <param name="action">Action to be executed.</param>
144+
/// <param name="dueTime">Relative time after which to execute the action.</param>
145+
/// <returns>The disposable object used to cancel the scheduled action (best effort).</returns>
146+
/// <exception cref="ArgumentNullException"><paramref name="action"/> is <c>null</c>.</exception>
147+
public override IDisposable Schedule<TState>(TState state, TimeSpan dueTime, Func<IScheduler, TState, IDisposable> action)
148+
{
149+
if (action == null)
150+
{
151+
throw new ArgumentNullException(nameof(action));
152+
}
153+
154+
var dt = Scheduler.Normalize(dueTime);
155+
if (dt.Ticks == 0)
156+
{
157+
return Schedule(state, action);
158+
}
159+
160+
return ScheduleSlow(state, dt, action);
161+
}
162+
163+
private IDisposable ScheduleSlow<TState>(TState state, TimeSpan dueTime, Func<IScheduler, TState, IDisposable> action)
164+
{
165+
var d = new MultipleAssignmentDisposable();
166+
167+
var timer = new DispatcherTimer();
168+
169+
timer.Tick += (o, e) =>
170+
{
171+
var t = Interlocked.Exchange(ref timer, null);
172+
if (t != null)
173+
{
174+
try
175+
{
176+
d.Disposable = action(this, state);
177+
}
178+
finally
179+
{
180+
t.Stop();
181+
action = null;
182+
}
183+
}
184+
};
185+
186+
timer.Interval = dueTime;
187+
timer.Start();
188+
189+
d.Disposable = Disposable.Create(() =>
190+
{
191+
var t = Interlocked.Exchange(ref timer, null);
192+
if (t != null)
193+
{
194+
t.Stop();
195+
action = (_, __) => Disposable.Empty;
196+
}
197+
});
198+
199+
return d;
200+
}
201+
202+
/// <summary>
203+
/// Schedules a periodic piece of work on the dispatcher, using a <see cref="DispatcherTimer"/> object.
204+
/// </summary>
205+
/// <typeparam name="TState">The type of the state passed to the scheduled action.</typeparam>
206+
/// <param name="state">Initial state passed to the action upon the first iteration.</param>
207+
/// <param name="period">Period for running the work periodically.</param>
208+
/// <param name="action">Action to be executed, potentially updating the state.</param>
209+
/// <returns>The disposable object used to cancel the scheduled recurring action (best effort).</returns>
210+
/// <exception cref="ArgumentNullException"><paramref name="action"/> is <c>null</c>.</exception>
211+
/// <exception cref="ArgumentOutOfRangeException"><paramref name="period"/> is less than <see cref="TimeSpan.Zero"/>.</exception>
212+
public IDisposable SchedulePeriodic<TState>(TState state, TimeSpan period, Func<TState, TState> action)
213+
{
214+
//
215+
// According to MSDN documentation, the default is TimeSpan.Zero, so that's definitely valid.
216+
// Empirical observation - negative values seem to be normalized to TimeSpan.Zero, but let's not go there.
217+
//
218+
if (period < TimeSpan.Zero)
219+
{
220+
throw new ArgumentOutOfRangeException(nameof(period));
221+
}
222+
223+
if (action == null)
224+
{
225+
throw new ArgumentNullException(nameof(action));
226+
}
227+
228+
var timer = new DispatcherTimer();
229+
230+
var state1 = state;
231+
232+
timer.Tick += (o, e) =>
233+
{
234+
state1 = action(state1);
235+
};
236+
237+
timer.Interval = period;
238+
timer.Start();
239+
240+
return Disposable.Create(() =>
241+
{
242+
var t = Interlocked.Exchange(ref timer, null);
243+
if (t != null)
244+
{
245+
t.Stop();
246+
action = _ => _;
247+
}
248+
});
249+
}
250+
}
251+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
// Copyright (c) 2019 .NET Foundation and Contributors. All rights reserved.
2+
// Licensed to the .NET Foundation under one or more agreements.
3+
// The .NET Foundation licenses this file to you under the MIT license.
4+
// See the LICENSE file in the project root for full license information.
5+
6+
using System;
7+
using System.Reactive.Concurrency;
8+
using System.Reactive.PlatformServices;
9+
10+
namespace ReactiveUI
11+
{
12+
/// <summary>
13+
/// UWP platform registrations.
14+
/// </summary>
15+
/// <seealso cref="ReactiveUI.IWantsToRegisterStuff" />
16+
public class PlatformRegistrations : IWantsToRegisterStuff
17+
{
18+
/// <inheritdoc/>
19+
public void Register(Action<Func<object>, Type> registerFunction)
20+
{
21+
registerFunction(() => new PlatformOperations(), typeof(IPlatformOperations));
22+
registerFunction(() => new ActivationForViewFetcher(), typeof(IActivationForViewFetcher));
23+
registerFunction(() => new DependencyObjectObservableForProperty(), typeof(ICreatesObservableForProperty));
24+
registerFunction(() => new BooleanToVisibilityTypeConverter(), typeof(IBindingTypeConverter));
25+
registerFunction(() => new AutoDataTemplateBindingHook(), typeof(IPropertyBindingHook));
26+
registerFunction(() => new WinRTAppDataDriver(), typeof(ISuspensionDriver));
27+
28+
#if NETSTANDARD
29+
if (WasmPlatformEnlightenmentProvider.IsWasm)
30+
{
31+
RxApp.TaskpoolScheduler = WasmScheduler.Default;
32+
RxApp.MainThreadScheduler = WasmScheduler.Default;
33+
}
34+
else
35+
#endif
36+
{
37+
RxApp.TaskpoolScheduler = TaskPoolScheduler.Default;
38+
RxApp.MainThreadScheduler = new WaitForDispatcherScheduler(() => CoreDispatcherScheduler.Current);
39+
}
40+
}
41+
}
42+
}

0 commit comments

Comments
 (0)