forked from unoplatform/Elmish.Uno
-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathViewModel.cs
179 lines (155 loc) · 9.4 KB
/
ViewModel.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics;
using System.Windows.Input;
using Elmish.Uno;
using Microsoft.FSharp.Collections;
using Microsoft.FSharp.Core;
using Windows.UI.Core;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Data;
namespace Elmish.Windows
{
public class DynamicCustomProperty<TValue> : ICustomProperty
{
public Func<TValue> Getter { get; }
public Action<TValue> Setter { get; }
public Func<object, TValue> IndexGetter { get; }
public Action<object, TValue> IndexSetter { get; }
public object GetValue(object target) => Getter.Invoke();
public void SetValue(object target, object value) => Setter.Invoke((TValue)value);
public object GetIndexedValue(object target, object index) => IndexGetter.Invoke(index);
public void SetIndexedValue(object target, object value, object index) => IndexSetter.Invoke(index, (TValue)value);
public bool CanRead => Getter != null || IndexGetter != null;
public bool CanWrite => Setter != null || IndexSetter != null;
public string Name { get; }
public Type Type => typeof(TValue);
public DynamicCustomProperty(string name, Func<TValue> getter, Action<TValue> setter = null, Func<object, TValue> indexGetter = null, Action<object, TValue> indexSetter = null)
{
Name = name;
Getter = getter;
Setter = setter;
IndexGetter = indexGetter;
IndexSetter = indexSetter;
}
}
internal class ViewModel<TModel, TMsg> : Uno.ViewModel<TModel, TMsg>, ICustomPropertyProvider
{
public ViewModel(TModel initialModel, FSharpFunc<TMsg, Unit> dispatch, FSharpList<Binding<TModel, TMsg>> bindings, ElmConfig config, string propNameChain) : base(initialModel, dispatch, bindings, config, propNameChain) { }
public override Uno.ViewModel<object, object> Create(object initialModel, FSharpFunc<object, Unit> dispatch, FSharpList<Binding<object, object>> bindings, ElmConfig config, string propNameChain)
=> new ViewModel<object, object>(initialModel, dispatch, bindings, config, propNameChain);
private ICustomProperty GetProperty(string name)
{
if (name == "CurrentModel") return new DynamicCustomProperty<object>(name, () => this.CurrentModel);
if (name == "HasErrors") return new DynamicCustomProperty<bool>(name, () => ((INotifyDataErrorInfo)this).HasErrors);
if (!this.Bindings.TryGetValue(name, out var binding)) Debugger.Break();
switch (binding)
{
case VmBinding<TModel, TMsg>.OneWay oneWay:
return new DynamicCustomProperty<object>(name, () => TryGetMember(oneWay));
case VmBinding<TModel, TMsg>.OneWayLazy oneWayLazy:
return new DynamicCustomProperty<object>(name, () => TryGetMember(oneWayLazy));
case VmBinding<TModel, TMsg>.OneWaySeq oneWaySeq:
return new DynamicCustomProperty<ObservableCollection<object>>(name,
() => (ObservableCollection<object>) TryGetMember(oneWaySeq));
case VmBinding<TModel, TMsg>.TwoWay twoWay:
return new DynamicCustomProperty<object>(name, () => TryGetMember(twoWay), value => TrySetMember(value, twoWay));
case VmBinding<TModel, TMsg>.TwoWayValidate twoWayValidate:
return new DynamicCustomProperty<object>(name, () => TryGetMember(twoWayValidate), value => TrySetMember(value, twoWayValidate));
case VmBinding<TModel, TMsg>.Cmd cmd:
return new DynamicCustomProperty<ICommand>(name, () => TryGetMember(cmd) as ICommand);
case VmBinding<TModel, TMsg>.CmdParam cmdParam:
return new DynamicCustomProperty<object>(name, () => TryGetMember(cmdParam));
case VmBinding<TModel, TMsg>.SubModel subModel:
return new DynamicCustomProperty<ViewModel<object, object>>(name,
() => TryGetMember(subModel) as ViewModel<object, object>);
case VmBinding<TModel, TMsg>.SubModelSeq subModelSeq:
return new DynamicCustomProperty<ObservableCollection<Uno.ViewModel<object, object>>>(name,
() => (ObservableCollection<Uno.ViewModel<object, object>>) TryGetMember(subModelSeq));
case VmBinding<TModel, TMsg>.SubModelSelectedItem subModelSelectedItem:
return new DynamicCustomProperty<ViewModel<object, object>>(name,
() => (ViewModel<object, object>) TryGetMember(subModelSelectedItem));
case VmBinding<TModel, TMsg>.Cached cached:
return new DynamicCustomProperty<object>(name,
() => TryGetMember(cached), value => TrySetMember(value, cached));
default:
return null;
//throw new NotSupportedException();
}
}
public ICustomProperty GetCustomProperty(string name) => GetProperty(name);
public ICustomProperty GetIndexedProperty(string name, Type type) => GetProperty(name);
public string GetStringRepresentation() => CurrentModel.ToString();
public Type Type => CurrentModel.GetType();
}
}
namespace Elmish.Uno
{
[RequireQualifiedAccess, CompilationMapping(SourceConstructFlags.Module)]
public static class ViewModel
{
public static object DesignInstance<TModel, TMsg>(TModel model, FSharpList<Binding<TModel, TMsg>> bindings)
{
var emptyDispatch = FuncConvert.FromAction((TMsg msg) => { });
return new Elmish.Windows.ViewModel<TModel, TMsg>(model, emptyDispatch, bindings, ElmConfig.Default, "main");
}
public static object DesignInstance<T, TModel, TMsg>(TModel model, Program<T, TModel, TMsg, FSharpList<Binding<TModel, TMsg>>> program)
{
var emptyDispatch = FuncConvert.FromAction((TMsg msg) => { });
var mapping = FSharpFunc<TModel, FSharpFunc<TMsg, Unit>>.InvokeFast(ProgramModule.view(program), model, emptyDispatch);
return DesignInstance(model, mapping);
}
public static void StartLoop<TModel, TMsg>(ElmConfig config, FrameworkElement element, Action<Program<Microsoft.FSharp.Core.Unit, TModel, TMsg, FSharpList<Binding<TModel, TMsg>>>> programRun, Program<Unit, TModel, TMsg, FSharpList<Binding<TModel, TMsg>>> program)
{
FSharpRef<FSharpOption<ViewModel<TModel, TMsg>>> lastModel = new FSharpRef<FSharpOption<ViewModel<TModel, TMsg>>>(null);
FSharpFunc<FSharpFunc<TMsg, Unit>, FSharpFunc<TMsg, Unit>> syncDispatch =
FuncConvert.FromAction(MakeSyncDispatch<TMsg>(element));
var setSate = FuncConvert.FromAction(MakeSetState(config, element, program, lastModel));
programRun.Invoke(
ProgramModule.withSyncDispatch(syncDispatch,
ProgramModule.withSetState(setSate, program)));
}
public static void StartLoop<T, TModel, TMsg>(ElmConfig config, FrameworkElement element, Action<T, Program<T, TModel, TMsg, FSharpList<Binding<TModel, TMsg>>>> programRun, Program<T, TModel, TMsg, FSharpList<Binding<TModel, TMsg>>> program, T arg)
{
FSharpRef<FSharpOption<ViewModel<TModel, TMsg>>> lastModel = new FSharpRef<FSharpOption<ViewModel<TModel, TMsg>>>(null);
FSharpFunc<FSharpFunc<TMsg, Unit>, FSharpFunc<TMsg, Unit>> syncDispatch =
FuncConvert.FromAction(MakeSyncDispatch<TMsg>(element));
var setSate = FuncConvert.FromAction(MakeSetState(config, element, program, lastModel));
programRun.Invoke(arg,
ProgramModule.withSyncDispatch(syncDispatch,
ProgramModule.withSetState(setSate, program)));
}
private static Action<TModel, FSharpFunc<TMsg, Unit>> MakeSetState<TArg, TModel, TMsg>(ElmConfig config, FrameworkElement element, Program<TArg, TModel, TMsg, FSharpList<Binding<TModel, TMsg>>> program, FSharpRef<FSharpOption<ViewModel<TModel, TMsg>>> lastModel)
{
void SetState(TModel model, FSharpFunc<TMsg, Unit> dispatch)
{
FSharpOption<ViewModel<TModel, TMsg>> contents = lastModel.contents;
if (contents != null)
{
contents.Value.UpdateModel(model);
return;
}
var bindedModel = ProgramModule.view(program).Invoke(model);
var Bindings = bindedModel.Invoke(dispatch);
var viewModel = new Elmish.Windows.ViewModel<TModel, TMsg>(model, dispatch, Bindings, config, "main");
element.DataContext = viewModel;
lastModel.contents = FSharpOption<ViewModel<TModel, TMsg>>.Some(viewModel);
}
return SetState;
}
private static Action<FSharpFunc<TMsg, Unit>, TMsg> MakeSyncDispatch<TMsg>(FrameworkElement element)
{
void UiDispatch(FSharpFunc<TMsg, Unit> innerDispatch, TMsg msg)
{
void DoDispatch(TMsg m)
{
Console.WriteLine("Dispatch");
innerDispatch.Invoke(m);
}
_ = element.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => DoDispatch(msg));
}
return UiDispatch;
}
}
}