1 module mvvm.common;
2 
3 import dlangui;
4 import rx;
5 import std.range : isOutputRange, put;
6 import std.traits;
7 
8 class ReactiveProperty(T) : Subject!T
9 {
10 public:
11     this()
12     {
13         this(T.init);
14     }
15 
16     this(T value)
17     {
18         _subject = new SubjectObject!T;
19         _value = value;
20     }
21 
22 public:
23     inout(T) value() inout @property
24     {
25         return _value;
26     }
27 
28     void value(T value) @property
29     {
30         if (_value != value)
31         {
32             _value = value;
33             .put(_subject, value);
34         }
35     }
36 
37 public:
38     auto subscribe(TObserver)(auto ref TObserver observer)
39     {
40         .put(observer, value);
41         return _subject.doSubscribe(observer);
42     }
43 
44     Disposable subscribe(Observer!T observer)
45     {
46         .put(observer, value);
47         return disposableObject(_subject.doSubscribe(observer));
48     }
49 
50     void put(T obj)
51     {
52         value = obj;
53     }
54 
55     void failure(Exception e)
56     {
57         _subject.failure(e);
58     }
59 
60     void completed()
61     {
62         _subject.completed();
63     }
64 
65 private:
66     SubjectObject!T _subject;
67     T _value;
68 }
69 
70 interface Command
71 {
72     void execute();
73     bool canExecute() const @property;
74     inout(Observable!bool) canExecuteObservable() inout @property;
75 }
76 
77 class DelegateCommand : Command
78 {
79     this(TObservable)(void delegate() onExecute, TObservable observable)
80     {
81         static assert(isObservable!(TObservable, bool));
82         assert(onExecute !is null);
83 
84         _onExecute = onExecute;
85         _canExecuteObservable = new ReactiveProperty!bool(false);
86         _canExecuteObservable.doSubscribe((bool b) { _canExecute = b; });
87 
88         observable.doSubscribe(_canExecuteObservable);
89     }
90 
91     void execute()
92     {
93         if (_onExecute !is null && _canExecute)
94             _onExecute();
95     }
96 
97     bool canExecute() const @property
98     {
99         return _canExecute;
100     }
101 
102     inout(Observable!bool) canExecuteObservable() inout @property
103     {
104         return _canExecuteObservable;
105     }
106 
107 private:
108     void delegate() _onExecute;
109     ReactiveProperty!bool _canExecuteObservable;
110     bool _canExecute;
111 }
112 
113 //############################
114 // Binding methods
115 //############################
116 
117 Disposable bindText(TSubject)(EditWidgetBase editWidget, TSubject property)
118 {
119     static assert(isObservable!(TSubject, string));
120     static assert(isOutputRange!(TSubject, string));
121 
122     import std.conv : to;
123 
124     auto d1 = editWidget.contentChange.asObservable().doSubscribe((EditableContent _) {
125         .put(property, to!string(editWidget.text));
126     });
127     auto d2 = property.doSubscribe((string value) {
128         editWidget.text = to!dstring(value);
129     });
130     return new CompositeDisposable(d1, d2);
131 }
132 
133 Disposable bindText(TObservable)(TextWidget textWidget, TObservable property)
134 {
135     static assert(isObservable!(TObservable, string));
136 
137     import std.conv : to;
138 
139     return disposableObject(property.doSubscribe((string value) {
140             textWidget.text = to!dstring(value);
141         }));
142 }
143 
144 Disposable bind(Button button, Command command)
145 {
146     auto d1 = button.click.asObservable().doSubscribe((Widget _) {
147         command.execute();
148     });
149     auto d2 = command.canExecuteObservable.doSubscribe((bool b) {
150         button.enabled = b;
151     });
152     return new CompositeDisposable(d1, d2);
153 }
154 
155 Disposable bind(TSubject)(SwitchButton button, TSubject property)
156 {
157     static assert(isObservable!(TSubject, bool));
158     static assert(isOutputRange!(TSubject, bool));
159 
160     auto d1 = button.click.asObservable().doSubscribe((Widget _) {
161         .put(property, button.checked);
162     });
163     auto d2 = property.doSubscribe((bool b) { button.checked = b; });
164     return new CompositeDisposable(d1, d2);
165 }
166 
167 //Utility
168 
169 ///Wrap a Signal!T as Observable
170 auto asObservable(T)(ref T signal) if (is(T == Signal!U, U) && is(U == interface))
171 {
172     static if (is(T == Signal!U, U))
173     {
174         alias return_t = ReturnType!(__traits(getMember, U, __traits(allMembers, U)[0]));
175         alias param_t = ParameterTypeTuple!(__traits(getMember, U, __traits(allMembers, U)[0]));
176         static assert(param_t.length == 1);
177     }
178 
179     static struct LocalObservable
180     {
181         alias ElementType = param_t[0];
182         this(ref T signal)
183         {
184             _subscribe = (Observer!ElementType o) {
185                 auto dg = (ElementType w) {
186                     .put(o, w);
187                     static if (is(return_t == bool))
188                     {
189                         return true;
190                     }
191                 };
192 
193                 signal.connect(dg);
194 
195                 return new AnonymouseDisposable({ signal.disconnect(dg); });
196             };
197         }
198 
199         auto subscribe(U)(U observer)
200         {
201             return _subscribe(observerObject!ElementType(observer));
202         }
203 
204         Disposable delegate(Observer!ElementType) _subscribe;
205     }
206 
207     return LocalObservable(signal);
208 }