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 AnonymousDisposable({ 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 }