Skip to content

Commit 721e9c2

Browse files
authored
Develop (#2)
* Update BindingExtensions.cs * Update MainView.cs * 1.0.7 Update
1 parent 0d8af88 commit 721e9c2

File tree

5 files changed

+166
-74
lines changed

5 files changed

+166
-74
lines changed

Runtime/Scripts/BindingExtensions.cs

Lines changed: 69 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using JetBrains.Annotations;
12
using System;
23
using System.Collections;
34
using System.Collections.Generic;
@@ -7,22 +8,52 @@
78

89
namespace RedMoon.ReactiveKit
910
{
10-
public static class BindingExtensions
11+
public static partial class BindingExtensions
1112
{
1213
#region Visual Element
13-
public static IDisposable BindEnabled(this VisualElement element, IReadOnlyReactiveProperty<bool> property)
14+
/// <summary>
15+
/// Binds Element's Enabled State to Boolean Observable
16+
/// </summary>
17+
/// <param name="element">Element to Bind</param>
18+
/// <param name="observable">Observable Stream to Subscribe to</param>
19+
/// <returns>Disposable for Disposing Subscription</returns>
20+
[MustUseReturnValue]
21+
public static IDisposable BindEnabled(this VisualElement element, IObservable<bool> observable)
22+
{
23+
return observable.ObserveOnMainThread().DistinctUntilChanged().Subscribe(element.SetEnabled);
24+
}
25+
/// <summary>
26+
/// Binds Element's Visibility State to Visibility Observable
27+
/// </summary>
28+
/// <param name="element">Element to Bind</param>
29+
/// <param name="observable">Observable Stream to Subscribe to</param>
30+
/// <returns>Disposable for Disposing Subscription</returns>
31+
[MustUseReturnValue]
32+
public static IDisposable BindVisibility(this VisualElement element, IObservable<Visibility> observable)
1433
{
15-
return property.SubscribeWithState(element, (__property, __element) =>
16-
{
17-
element.SetEnabled(__property);
18-
});
34+
return observable.ObserveOnMainThread().DistinctUntilChanged().Subscribe(value => element.style.visibility = value);
1935
}
36+
/// <summary>
37+
/// Bind Element's Click Callback to Command
38+
/// </summary>
39+
/// <param name="element">Element to Bind</param>
40+
/// <param name="command">Command to Execute</param>
41+
/// <returns>Disposable for Disposing Subscription</returns>
42+
[MustUseReturnValue]
2043
public static IDisposable BindClick(this VisualElement element, IReactiveCommand<ClickEvent> command)
2144
{
22-
var d1 = element.BindEnabled(command.CanExecute);
23-
var d2 = element.BindCallback(command);
45+
IDisposable d1 = element.BindEnabled(command.CanExecute);
46+
IDisposable d2 = element.BindCallback(command);
2447
return StableCompositeDisposable.Create(d1, d2);
2548
}
49+
/// <summary>
50+
/// Bind Element's Click Callback to Command
51+
/// </summary>
52+
/// <param name="element">Element to Bind</param>
53+
/// <param name="command">Command to Execute</param>
54+
/// <param name="dataForCallback">Callback as a part of Command</param>
55+
/// <returns>Disposable for Disposing Subscription</returns>
56+
[MustUseReturnValue]
2657
public static IDisposable BindClick<TArgs>(this VisualElement element, IReactiveCommand<(ClickEvent, TArgs)> command, TArgs dataForCallback)
2758
{
2859
var d1 = element.BindEnabled(command.CanExecute);
@@ -32,97 +63,77 @@ public static IDisposable BindClick<TArgs>(this VisualElement element, IReactive
3263
#endregion
3364

3465
#region Callback Event Handler
66+
[MustUseReturnValue]
3567
public static IDisposable BindCallback<TEventType>(this CallbackEventHandler element, EventCallback<TEventType> callback, TrickleDown trickleDown = TrickleDown.NoTrickleDown) where TEventType : EventBase<TEventType>, new()
3668
{
3769
element.RegisterCallback(callback, trickleDown);
3870
return Disposable.Create(() => { element.UnregisterCallback(callback, trickleDown); });
3971
}
72+
[MustUseReturnValue]
4073
public static IDisposable BindCallback<TEventType, TUserArgsType>(this CallbackEventHandler element, EventCallback<TEventType, TUserArgsType> callback, TUserArgsType dataForCallback, TrickleDown trickleDown = TrickleDown.NoTrickleDown) where TEventType : EventBase<TEventType>, new()
4174
{
4275
element.RegisterCallback(callback, dataForCallback, trickleDown);
4376
return Disposable.Create(() => { element.UnregisterCallback(callback, trickleDown); });
4477
}
78+
[MustUseReturnValue]
4579
public static IDisposable BindCallback<TEventType>(this CallbackEventHandler element, IReactiveCommand<TEventType> command, TrickleDown trickleDown = TrickleDown.NoTrickleDown) where TEventType : EventBase<TEventType>, new()
4680
{
47-
var callback = new EventCallback<TEventType>((ev) => command.Execute(ev));
81+
EventCallback<TEventType> callback = new EventCallback<TEventType>((ev) => command.Execute(ev));
4882
element.RegisterCallback(callback, trickleDown);
4983
return Disposable.Create(() => { element.UnregisterCallback(callback, trickleDown); });
5084
}
85+
[MustUseReturnValue]
5186
public static IDisposable BindCallback<TEventType, TUserArgsType>(this CallbackEventHandler element, IReactiveCommand<(TEventType, TUserArgsType)> command, TUserArgsType dataForCallback, TrickleDown trickleDown = TrickleDown.NoTrickleDown) where TEventType : EventBase<TEventType>, new()
5287
{
53-
var callback = new EventCallback<TEventType, TUserArgsType>((ev, args) => command.Execute((ev, args)));
88+
EventCallback<TEventType, TUserArgsType> callback = new EventCallback<TEventType, TUserArgsType>((ev, args) => command.Execute((ev, args)));
5489
element.RegisterCallback(callback, dataForCallback, trickleDown);
5590
return Disposable.Create(() => { element.UnregisterCallback(callback, trickleDown); });
5691
}
5792
#endregion
5893

5994
#region Notifications
95+
[MustUseReturnValue]
6096
public static IDisposable BindValueChanged<T>(this INotifyValueChanged<T> element, IReactiveCommand<ChangeEvent<T>> command)
6197
{
62-
var callback = new EventCallback<ChangeEvent<T>>((ev) => command.Execute(ev));
98+
EventCallback<ChangeEvent<T>> callback = new EventCallback<ChangeEvent<T>>((ev) => command.Execute(ev));
6399
element.RegisterValueChangedCallback(callback);
64100
return Disposable.Create(() => { element.UnregisterValueChangedCallback(callback); });
65101
}
102+
/// <summary>
103+
/// Binds a Property Value to Element Changing
104+
/// </summary>
105+
/// <typeparam name="T">Type for Change Event</typeparam>
106+
/// <param name="element">Element that Notifies Change</param>
107+
/// <param name="property">Property to Change on Notification</param>
108+
/// <returns>Disposable for Disposing Subscription</returns>
109+
[MustUseReturnValue]
66110
public static IDisposable BindValueChanged<T>(this INotifyValueChanged<T> element, IReactiveProperty<T> property)
67111
{
68-
var callback = new EventCallback<ChangeEvent<T>>((ev) => property.Value = ev.newValue);
112+
EventCallback<ChangeEvent<T>> callback = new EventCallback<ChangeEvent<T>>((ev) => property.Value = ev.newValue);
69113
element.RegisterValueChangedCallback(callback);
70114
return Disposable.Create(() => { element.UnregisterValueChangedCallback(callback); });
71115
}
72116
/// <summary>
73-
/// Binds Element changes to Update Property Values.
74-
/// Then Initializes Values.
117+
/// Binds an Element to a Property Changing
118+
/// Does not Trigger Element Notifications
75119
/// </summary>
76-
/// <typeparam name="T"></typeparam>
77-
/// <param name="element">Element with Value Changed Callbacks</param>
78-
/// <param name="property">Reactive Property that value should change on a Value Changed Callback</param>
79-
/// <param name="stateIsProperty">Whether Default Value should be Default Property Value</param>
80-
/// <returns></returns>
81-
public static IDisposable BindValueChangedWithState<T>(this INotifyValueChanged<T> element, IReactiveProperty<T> property, bool stateIsProperty = true)
82-
{
83-
var d1 = BindValueChanged(element, property);
84-
if (stateIsProperty)
85-
{
86-
element.value = property.Value;
87-
}
88-
else
89-
{
90-
property.Value = element.value;
91-
}
92-
return d1;
93-
}
94-
public static IDisposable BindToValueChanged<T>(this INotifyValueChanged<T> element, IReactiveProperty<T> property)
95-
{
96-
return property.SubscribeWithState(element, (__property, __element) =>
97-
{
98-
__element.SetValueWithoutNotify(__property);
99-
});
100-
}
101-
public static IDisposable BindToValueChangedWithState<T>(this INotifyValueChanged<T> element, IReactiveProperty<T> property, bool stateIsProperty = true)
120+
/// <typeparam name="T">Type for Fields</typeparam>
121+
/// <param name="element">Element that has value updated</param>
122+
/// <param name="observable">Observable to update value</param>
123+
/// <returns>Disposable for Disposing Subscription</returns>
124+
[MustUseReturnValue]
125+
public static IDisposable BindToValueChanged<T>(this INotifyValueChanged<T> element, IObservable<T> observable)
102126
{
103-
var d1 = BindToValueChanged(element, property);
104-
if (stateIsProperty)
105-
{
106-
element.value = property.Value;
107-
}
108-
else
109-
{
110-
property.Value = element.value;
111-
}
112-
return d1;
127+
return observable.ObserveOnMainThread().DistinctUntilChanged().Subscribe((ev) => element.SetValueWithoutNotify(ev));
113128
}
129+
[MustUseReturnValue]
114130
public static IDisposable BindTwoWayValueChanged<T>(this INotifyValueChanged<T> element, IReactiveProperty<T> property)
115131
{
116-
var d1 = BindValueChanged(element, property);
117-
var d2 = BindToValueChanged(element, property);
118-
return StableCompositeDisposable.Create(d1, d2);
119-
}
120-
public static IDisposable BindTwoWayValueChangedWithState<T>(this INotifyValueChanged<T> element, IReactiveProperty<T> property, bool stateIsProperty = true)
121-
{
122-
var d1 = BindValueChangedWithState(element, property, stateIsProperty);
123-
var d2 = BindToValueChangedWithState(element, property, stateIsProperty);
132+
IDisposable d1 = BindValueChanged(element, property);
133+
IDisposable d2 = BindToValueChanged(element, property);
124134
return StableCompositeDisposable.Create(d1, d2);
125135
}
126136
#endregion
137+
127138
}
128139
}

Runtime/Scripts/EventExtensions.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,15 @@ public static class EventExtensions
1414
{
1515
return new EventCallbackObservable<TEventType>(element);
1616
}
17+
18+
public static IDisposable SubscribeToExecuteCommand<T>(this IObservable<T> observable, IReactiveCommand<T> reactive)
19+
{
20+
return observable.Subscribe(x => reactive.Execute(x));
21+
}
22+
public static IDisposable SubscribeToUpdateProperty<T>(this IObservable<T> observable, IReactiveProperty<T> property)
23+
{
24+
return observable.Subscribe(x => property.Value = x);
25+
}
1726
}
1827

1928
internal class EventCallbackObservable<TEventType> : OperatorObservableBase<TEventType> where TEventType : EventBase<TEventType>, new()

Samples/SamplesBasicUI/Scripts/MainView.cs

Lines changed: 63 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,31 +18,85 @@ public override void OnActivation(MainViewModel viewModel, CompositeDisposable d
1818
Button button = Root.Q<Button>("ReactiveButton");
1919
Label label = Root.Q<Label>("Output");
2020
TextField field = Root.Q<TextField>("ReactiveTextField");
21+
Slider slider = Root.Q<Slider>("ReactiveSlider");
22+
MinMaxSlider minMaxSlider = Root.Q<MinMaxSlider>("ReactiveMinMaxSlider");
23+
Toggle toggle = Root.Q<Toggle>("ReactiveToggle");
24+
2125
Label fieldLabel = field.labelElement;
2226

2327
//Bind such that whenever OnButtonClick can Execute, you can click button.
2428
//Bind such that whenever Button is clicked, it executes OnButtonClick.
25-
button.BindClick(viewModel.OnButtonClick).AddTo(disposable);
29+
button
30+
.BindClick(viewModel.OnButtonClick)
31+
.AddTo(disposable);
2632

2733
//Turn Event into Observable
2834
//Buffer based on Throttle Window
2935
//Check that there is more than 2 clicks
30-
//Double Click Detected
36+
//Select Last ClickEvent for Processing
3137
var clickStream = button.AsObservable<ClickEvent>();
32-
clickStream.Buffer(clickStream.Throttle(TimeSpan.FromMilliseconds(250))).Where(xs => xs.Count >= 2).Subscribe(x =>
33-
{
34-
viewModel.OnDoubleClick.Execute(x.Last());
35-
});
38+
clickStream
39+
.Buffer(clickStream.Throttle(TimeSpan.FromMilliseconds(250)))
40+
.Where(xs => xs.Count >= 2)
41+
.Select(x => x.Last())
42+
.SubscribeToExecuteCommand(viewModel.OnDoubleClick)
43+
.AddTo(disposable);
3644

3745
//Bind such that whenever DebugText changes, it updates label value.
3846
label.BindToValueChanged(viewModel.DebugText).AddTo(disposable);
3947

4048
//Bind such that whenever field value changes, it executes OnTextFieldChange
4149
//Bind such that whenever field value changes, it updates reactive text. Initializes to default field value.
42-
field.BindValueChanged(viewModel.OnTextFieldChange).AddTo(disposable);
43-
field.BindValueChangedWithState(viewModel.ReactiveText, false).AddTo(disposable);
50+
field
51+
.BindValueChanged(viewModel.OnTextFieldChange)
52+
.AddTo(disposable);
53+
field
54+
.BindValueChanged(viewModel.ReactiveText)
55+
.AddTo(disposable);
4456
//Bind such that whenever ReactiveText changes, it updates fieldlabel value.
45-
fieldLabel.BindToValueChanged(viewModel.ReactiveText).AddTo(disposable);
57+
fieldLabel
58+
.BindToValueChanged(viewModel.ReactiveText)
59+
.AddTo(disposable);
60+
61+
//Observe the Slider Changing outside of Events
62+
//Buffer based on Throttle Window
63+
//Select Last
64+
var sliderStream = slider.ObserveEveryValueChanged(x => x.value);
65+
var fullStream = sliderStream
66+
.Buffer(sliderStream.Throttle(TimeSpan.FromMilliseconds(250)))
67+
.Select(x => x.Last());
68+
//Have Stream Update Max Value
69+
fullStream
70+
.SubscribeToUpdateProperty(viewModel.MaxValue)
71+
.AddTo(disposable);
72+
//Have Stream Execute Change Event
73+
fullStream
74+
.SubscribeToExecuteCommand(viewModel.OnSliderFieldChange)
75+
.AddTo(disposable);
76+
77+
//Sometimes Binding to Certain Properties needs to be done manually.
78+
//In this case lowLimit and highLimit are properties that do not inform
79+
viewModel
80+
.MinMaxValue
81+
.Subscribe(x => UpdateMinMaxSlider(minMaxSlider, x))
82+
.AddTo(disposable);
83+
84+
//Bind to Observe Values Changing
85+
//Then Execute Commands
86+
minMaxSlider
87+
.ObserveEveryValueChanged(x => x.value)
88+
.SubscribeToExecuteCommand(viewModel.OnMinMaxSliderFieldChange)
89+
.AddTo(disposable);
90+
91+
toggle
92+
.BindValueChanged(viewModel.OnToggleChange)
93+
.AddTo(disposable);
94+
}
95+
96+
private void UpdateMinMaxSlider(MinMaxSlider slider, Vector2 limit)
97+
{
98+
slider.lowLimit = limit.x;
99+
slider.highLimit = limit.y;
46100
}
47101
}
48102
}

Samples/SamplesBasicUI/Scripts/MainViewModel.cs

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,32 +12,50 @@ public class MainViewModel : ViewModel<MainViewModel>
1212
{
1313
public ReactiveProperty<string> DebugText { get; protected set; } = new ReactiveProperty<string>("");
1414
public ReactiveProperty<string> ReactiveText { get; protected set; } = new ReactiveProperty<string>("");
15+
public ReactiveProperty<float> MaxValue { get; protected set; } = new ReactiveProperty<float>();
16+
public ReactiveProperty<float> MinValue { get; protected set; } = new ReactiveProperty<float>();
17+
public IReadOnlyReactiveProperty<Vector2> MinMaxValue { get; protected set; }
1518

1619
public ReactiveCommand<ClickEvent> OnButtonClick;
1720
public ReactiveCommand<ClickEvent> OnDoubleClick;
1821
public ReactiveCommand<ChangeEvent<string>> OnTextFieldChange;
22+
public ReactiveCommand<float> OnSliderFieldChange;
23+
public ReactiveCommand<Vector2> OnMinMaxSliderFieldChange;
24+
public ReactiveCommand<ChangeEvent<bool>> OnToggleChange;
1925

2026
public override void OnInitialization()
2127
{
28+
MinMaxValue = MinValue.CombineLatest(MaxValue, (x, y) => new Vector2(x, y)).ToReactiveProperty();
29+
30+
//Note: it is best practice to try for one line lambdas or to de-anonymize functions
31+
//Unless of course, you want to make it difficult to inject code.
2232
OnButtonClick = new ReactiveCommand<ClickEvent>();
23-
OnButtonClick.Subscribe((c) => UpdateDebugText(c, "Button Clicked"));
33+
OnButtonClick.Subscribe((c) => UpdateDebugText("Button Clicked"));
2434

2535
OnDoubleClick = new ReactiveCommand<ClickEvent>();
26-
OnDoubleClick.Subscribe((c) => UpdateDebugText(c, "Double Clicked"));
27-
36+
OnDoubleClick.Subscribe((c) => UpdateDebugText("Double Clicked"));
2837

2938
OnTextFieldChange = new ReactiveCommand<ChangeEvent<string>>();
3039
OnTextFieldChange.Subscribe((c) => UpdateDebugText(c, "Field Changed:"));
40+
41+
OnSliderFieldChange = new ReactiveCommand<float>();
42+
OnSliderFieldChange.Subscribe((c) => UpdateDebugText($"Slider Changed: {c}"));
43+
44+
OnMinMaxSliderFieldChange = new ReactiveCommand<Vector2>();
45+
OnMinMaxSliderFieldChange.Subscribe((c) => UpdateDebugText($"MinMaxChanged: ({c.x},{c.y})"));
46+
47+
OnToggleChange = new ReactiveCommand<ChangeEvent<bool>>();
48+
OnToggleChange.Subscribe((c) => UpdateDebugText($"Toggle {c.newValue}"));
3149
}
3250

33-
private void UpdateDebugText(ChangeEvent<string> obj, string Text)
51+
52+
private void UpdateDebugText<T>(ChangeEvent<T> obj, string Text)
3453
{
3554
DebugText.Value += $"{Text} {obj.previousValue} -> {obj.newValue}\n";
3655
}
3756

38-
private void UpdateDebugText(ClickEvent obj, string Text)
57+
private void UpdateDebugText(string Text)
3958
{
40-
Debug.Log(obj);
4159
DebugText.Value += $"{Text}\n";
4260
}
4361
}

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
"displayName": "Red Moon Reactive Kit",
88
"name": "io.crazyjackel.redmoon-reactivekit",
99
"unity": "2020.3",
10-
"version": "1.0.6",
10+
"version": "1.0.7",
1111
"dependencies": {
1212
"com.unity.ui": "1.0.0-preview.18"
1313
},

0 commit comments

Comments
 (0)