1 /**
2 Dplug-Wren stdlib. This API is accessed from Wren with `import "ui"`.
3 
4 Copyright: Guillaume Piolat 2021.
5 License:   $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0)
6 */
7 module dplug.wren.wren_ui;
8 
9 import core.stdc.string : strcmp;
10 import wren.vm;
11 import wren.common;
12 
13 import dplug.gui.element;
14 import dplug.wren.wrensupport;
15 import dplug.wren.describe;
16 
17 
18 private static immutable string uiModuleSource = import("ui.wren");
19 
20 nothrow @nogc:
21 
22 // UI
23 
24 void ui_width(WrenVM* vm)
25 {
26     WrenSupport ws = cast(WrenSupport) vm.config.userData;
27     IUIContext context = ws.uiContext();
28     vec2i userSize = context.getUISizeInPixelsUser();
29     wrenSetSlotDouble(vm, 0, cast(double)(userSize.x));
30  }
31 
32  void ui_height(WrenVM* vm)
33  {
34     WrenSupport ws = cast(WrenSupport) vm.config.userData;
35     IUIContext context = ws.uiContext();
36     vec2i userSize = context.getUISizeInPixelsUser();
37     wrenSetSlotDouble(vm, 0, cast(double)(userSize.y));
38 }
39 
40 void ui_defaultWidth(WrenVM* vm)
41 {
42     WrenSupport ws = cast(WrenSupport) vm.config.userData;
43     IUIContext context = ws.uiContext();
44     wrenSetSlotDouble(vm, 0, context.getDefaultUIWidth());
45 }
46 
47 void ui_defaultHeight(WrenVM* vm)
48 {
49     WrenSupport ws = cast(WrenSupport) vm.config.userData;
50     IUIContext context = ws.uiContext();
51     wrenSetSlotDouble(vm, 0, context.getDefaultUIHeight());
52 }
53 
54 // Elements
55 
56 void element_allocate(WrenVM* vm)
57 {
58     UIElementBridge* bridge = cast(UIElementBridge*)wrenSetSlotNewForeign(vm, 0, 0, UIElementBridge.sizeof);
59     
60     WrenSupport ws = cast(WrenSupport) vm.config.userData;
61     IUIContext context = ws.uiContext();
62     bridge.elem = null;
63 }
64 
65 void element_findIdAndBecomeThat(WrenVM* vm)
66 {
67     UIElementBridge* bridge = cast(UIElementBridge*) wrenGetSlotForeign(vm, 0);
68     const(char)* id = wrenGetSlotString(vm, 1);
69 
70     WrenSupport ws = cast(WrenSupport) vm.config.userData;
71     IUIContext context = ws.uiContext();
72     bridge.elem = context.getElementById(id); // Note: could be null
73 }
74 
75 void element_width(WrenVM* vm)
76 {
77     UIElementBridge* bridge = cast(UIElementBridge*) wrenGetSlotForeign(vm, 0);
78     double width = bridge.elem.position.width;    
79     wrenSetSlotDouble(vm, 0, width);
80 }
81 
82 void element_height(WrenVM* vm)
83 {
84     UIElementBridge* bridge = cast(UIElementBridge*) wrenGetSlotForeign(vm, 0);
85     double height = bridge.elem.position.height;    
86     wrenSetSlotDouble(vm, 0, height);
87 }
88 
89 void element_setposition(WrenVM* vm)
90 {
91     UIElementBridge* bridge = cast(UIElementBridge*) wrenGetSlotForeign(vm, 0);
92     assert(bridge.elem); // TODO error
93 
94     // Avoid crash if passed Rectangle.new(false, 1, 4, 4), just do nothing in that case
95     if ( wrenGetSlotType(vm, 1) !=  WrenType.WREN_TYPE_NUM )
96         return;
97     if ( wrenGetSlotType(vm, 2) !=  WrenType.WREN_TYPE_NUM )
98         return;
99     if ( wrenGetSlotType(vm, 3) !=  WrenType.WREN_TYPE_NUM )
100         return;
101     if ( wrenGetSlotType(vm, 4) !=  WrenType.WREN_TYPE_NUM )
102         return;
103 
104     double x = wrenGetSlotDouble(vm, 1);
105     double y = wrenGetSlotDouble(vm, 2);
106     double w = wrenGetSlotDouble(vm, 3);
107     double h = wrenGetSlotDouble(vm, 4);
108     bridge.elem.position = box2i.rectangle(cast(int)x, cast(int)y, cast(int)w, cast(int)h);
109 }
110 
111 void element_setvisibility(WrenVM* vm)
112 {
113     UIElementBridge* bridge = cast(UIElementBridge*) wrenGetSlotForeign(vm, 0);
114     assert(bridge.elem); // TODO error
115 
116     // If not passed a bool, do nothing
117     if (wrenGetSlotType(vm, 1) == WrenType.WREN_TYPE_BOOL)
118     {
119         bool visibleFlag = wrenGetSlotBool(vm, 1);
120         bridge.elem.visibility = visibleFlag;
121     }
122 }
123 
124 void element_setzorder(WrenVM* vm)
125 {
126     UIElementBridge* bridge = cast(UIElementBridge*) wrenGetSlotForeign(vm, 0);
127     assert(bridge.elem); // TODO error
128 
129     // If not passed a Num, do nothing
130     if (wrenGetSlotType(vm, 1) == WrenType.WREN_TYPE_NUM)
131     {
132         double z = wrenGetSlotDouble(vm, 1);
133 
134         if (z > int.max)
135             return;
136         if (z < int.min)
137             return;
138 
139         // Just truncate => fractional zOrder does nothing.
140         int iz = cast(int)z;
141 
142         bridge.elem.zOrder = iz;
143     }
144 }
145 
146 void element_setProperty(WrenVM* vm)
147 {
148     UIElementBridge* bridge = cast(UIElementBridge*) wrenGetSlotForeign(vm, 0);
149     assert(bridge.elem);  // TODO error
150 
151     int classIndex = cast(int) wrenGetSlotDouble(vm, 1);
152     int propIndex = cast(int) wrenGetSlotDouble(vm, 2);
153     WrenSupport ws = cast(WrenSupport) vm.config.userData;
154     ScriptPropertyDesc* desc = ws.getScriptProperty(classIndex, propIndex);
155     assert(desc !is null);
156 
157     ubyte* raw = cast(ubyte*)(cast(void*)bridge.elem) + desc.offset;
158 
159     bool changed = false;
160 
161     // Note: we check the property type, if it's the wrong type then nothing happens. Styling failure is not an error.
162     // Visual error like this are silent but will have a visual effect.
163     WrenType slot3_t = wrenGetSlotType(vm, 3);
164 
165     final switch(desc.type)
166     {
167         case ScriptPropertyType.bool_:
168         { 
169             if (slot3_t != WrenType.WREN_TYPE_BOOL)
170                 return;
171             bool* valuePtr = cast(bool*)raw;
172             bool current = *valuePtr;
173             bool newValue = wrenGetSlotBool(vm, 3);
174             changed = newValue != current;
175             *valuePtr = newValue;
176             break;
177         }
178         case ScriptPropertyType.byte_: 
179         {
180             if (slot3_t != WrenType.WREN_TYPE_NUM)
181                 return;
182             byte* valuePtr = cast(byte*)raw;
183             byte current = *valuePtr;
184             byte newValue =  cast(byte) wrenGetSlotDouble(vm, 3);
185             changed = newValue != current;
186             *valuePtr = newValue;
187             break;
188         }
189         case ScriptPropertyType.ubyte_: 
190         {
191             if (slot3_t != WrenType.WREN_TYPE_NUM)
192                 return;
193             ubyte* valuePtr = cast(ubyte*)raw;
194             ubyte current = *valuePtr;
195             ubyte newValue =  cast(ubyte) wrenGetSlotDouble(vm, 3);
196             changed = newValue != current;
197             *valuePtr = newValue;
198             break;
199         }   
200         case ScriptPropertyType.short_: 
201         {
202             if (slot3_t != WrenType.WREN_TYPE_NUM)
203                 return;
204             short* valuePtr = cast(short*)raw;
205             short current = *valuePtr;
206             short newValue =  cast(short) wrenGetSlotDouble(vm, 3);
207             changed = newValue != current;
208             *valuePtr = newValue;
209             break;
210         }
211         case ScriptPropertyType.ushort_: 
212         {
213             if (slot3_t != WrenType.WREN_TYPE_NUM)
214                 return;
215             ushort* valuePtr = cast(ushort*)raw;
216             ushort current = *valuePtr;
217             ushort newValue =  cast(ushort) wrenGetSlotDouble(vm, 3);
218             changed = newValue != current;
219             *valuePtr = newValue;
220             break;
221         }
222         case ScriptPropertyType.int_: 
223         {
224             if (slot3_t != WrenType.WREN_TYPE_NUM)
225                 return;
226             int* valuePtr = cast(int*)raw;
227             int current = *valuePtr;
228             int newValue =  cast(int) wrenGetSlotDouble(vm, 3);
229             changed = newValue != current;
230             *valuePtr = newValue;
231             break;
232         }
233         case ScriptPropertyType.uint_: 
234         {
235             if (slot3_t != WrenType.WREN_TYPE_NUM)
236                 return;
237             uint* valuePtr = cast(uint*)raw;
238             uint current = *valuePtr;
239             uint newValue =  cast(uint) wrenGetSlotDouble(vm, 3);
240             changed = newValue != current;
241             *valuePtr = newValue;
242             break;
243         }
244         case ScriptPropertyType.float_: 
245         {
246             if (slot3_t != WrenType.WREN_TYPE_NUM)
247                 return;
248             float* valuePtr = cast(float*)raw;
249             float current = *valuePtr;
250             float newValue =  cast(float) wrenGetSlotDouble(vm, 3);
251              changed = !(newValue == current); // so that NaN don't provoke redraw
252             *valuePtr = newValue;
253             break;
254         }
255         case ScriptPropertyType.double_: 
256         {
257             if (slot3_t != WrenType.WREN_TYPE_NUM)
258                 return;
259             double* valuePtr = cast(double*)raw;
260             double current = *valuePtr;
261             double newValue =  wrenGetSlotDouble(vm, 3);
262             changed = !(newValue == current); // so that NaN don't provoke redraw
263             *valuePtr = newValue;
264             break;
265         }
266         case ScriptPropertyType.RGBA:    assert(false);
267     }
268 
269     // Changing a @ScriptProperty calls setDirtyWhole on the UIElement if the property changed
270     if (changed)
271         bridge.elem.setDirtyWhole();
272 }
273 
274 void element_setPropertyRGBA(WrenVM* vm)
275 {
276     UIElementBridge* bridge = cast(UIElementBridge*) wrenGetSlotForeign(vm, 0);
277     assert(bridge.elem); // TODO error
278 
279     int classIndex = cast(int) wrenGetSlotDouble(vm, 1);
280     int propIndex = cast(int) wrenGetSlotDouble(vm, 2);
281     WrenSupport ws = cast(WrenSupport) vm.config.userData;
282     ScriptPropertyDesc* desc = ws.getScriptProperty(classIndex, propIndex);
283     assert(desc !is null);
284     assert(desc.type == ScriptPropertyType.RGBA);
285 
286     ubyte* raw = cast(ubyte*)(cast(void*)bridge.elem) + desc.offset;
287     RGBA* pRGBA = cast(RGBA*)(raw);
288 
289     // check for right type, else ignore line
290     if (wrenGetSlotType(vm, 3) != WrenType.WREN_TYPE_NUM)
291         return;
292     if (wrenGetSlotType(vm, 4) != WrenType.WREN_TYPE_NUM)
293         return;
294     if (wrenGetSlotType(vm, 5) != WrenType.WREN_TYPE_NUM)
295         return;
296     if (wrenGetSlotType(vm, 6) != WrenType.WREN_TYPE_NUM)
297         return;
298 
299     double r = wrenGetSlotDouble(vm, 3);
300     double g = wrenGetSlotDouble(vm, 4);
301     double b = wrenGetSlotDouble(vm, 5);
302     double a = wrenGetSlotDouble(vm, 6);
303     if (r < 0) r = 0;
304     if (g < 0) g = 0;
305     if (b < 0) b = 0;
306     if (a < 0) a = 0;
307     if (r > 255) r = 255;
308     if (g > 255) g = 255;
309     if (b > 255) b = 255;
310     if (a > 255) a = 255;
311     RGBA newColor = RGBA(cast(ubyte)r, cast(ubyte)g, cast(ubyte)b, cast(ubyte)a);
312     bool changed = newColor != *pRGBA;
313     *pRGBA = newColor;
314 
315     // Changing a @ScriptProperty calls setDirtyWhole on the UIElement if the property changed
316     if (changed)
317         bridge.elem.setDirtyWhole();
318 }
319 
320 void element_getProperty(WrenVM* vm)
321 {
322     UIElementBridge* bridge = cast(UIElementBridge*) wrenGetSlotForeign(vm, 0);
323     assert(bridge.elem); // TODO error
324 
325     int classIndex = cast(int) wrenGetSlotDouble(vm, 1);
326     int propIndex = cast(int) wrenGetSlotDouble(vm, 2);
327     WrenSupport ws = cast(WrenSupport) vm.config.userData;
328     ScriptPropertyDesc* desc = ws.getScriptProperty(classIndex, propIndex);
329     assert(desc !is null);
330 
331     ubyte* raw = cast(ubyte*)(cast(void*)bridge.elem) + desc.offset;
332 
333     final switch(desc.type)
334     {
335         case ScriptPropertyType.bool_:   wrenSetSlotBool(vm, 0, *cast(bool*)raw); break;
336         case ScriptPropertyType.byte_:   wrenSetSlotDouble(vm, 0, *cast(byte*)raw); break;
337         case ScriptPropertyType.ubyte_:  wrenSetSlotDouble(vm, 0, *cast(ubyte*)raw); break;
338         case ScriptPropertyType.short_:  wrenSetSlotDouble(vm, 0, *cast(short*)raw); break;
339         case ScriptPropertyType.ushort_: wrenSetSlotDouble(vm, 0, *cast(ushort*)raw); break;
340         case ScriptPropertyType.int_:    wrenSetSlotDouble(vm, 0, *cast(int*)raw); break;
341         case ScriptPropertyType.uint_:   wrenSetSlotDouble(vm, 0, *cast(uint*)raw); break;
342         case ScriptPropertyType.float_: 
343         {
344             float f = *cast(float*)raw;
345             wrenSetSlotDouble(vm, 0, f); break;
346         }
347         case ScriptPropertyType.double_: wrenSetSlotDouble(vm, 0, *cast(double*)raw); break;
348         case ScriptPropertyType.RGBA:    assert(false);
349     }
350 }
351 
352 void element_getPropertyRGBA(WrenVM* vm)
353 {
354     UIElementBridge* bridge = cast(UIElementBridge*) wrenGetSlotForeign(vm, 0);
355     assert(bridge.elem); // TODO error
356 
357     int classIndex = cast(int) wrenGetSlotDouble(vm, 1);
358     int propIndex = cast(int) wrenGetSlotDouble(vm, 2);
359     WrenSupport ws = cast(WrenSupport) vm.config.userData;
360     ScriptPropertyDesc* desc = ws.getScriptProperty(classIndex, propIndex);
361     assert(desc !is null);
362 
363     ubyte* raw = cast(ubyte*)(cast(void*)bridge.elem) + desc.offset;
364     int channel = cast(int) wrenGetSlotDouble(vm, 3);
365     assert(channel >= 0 && channel < 4);
366     wrenSetSlotDouble(vm, 0, raw[channel]);
367 }
368 
369 struct UIElementBridge
370 {
371     UIElement elem;
372 }
373 
374 const(char)* wrenUIModuleSource()
375 {
376     return assumeZeroTerminated(uiModuleSource);
377 }
378 
379 WrenForeignMethodFn wrenUIBindForeignMethod(WrenVM* vm, const(char)* className, bool isStatic, const(char)* signature) nothrow
380 {
381     if (strcmp(className, "UI") == 0)
382     {
383         if (isStatic && strcmp(signature, "width") == 0) return &ui_width;
384         if (isStatic && strcmp(signature, "height") == 0) return &ui_height;
385         if (isStatic && strcmp(signature, "defaultWidth") == 0) return &ui_defaultWidth;
386         if (isStatic && strcmp(signature, "defaultHeight") == 0) return &ui_defaultHeight;
387     }
388 
389     if (strcmp(className, "Element") == 0)
390     {
391         if (strcmp(signature, "<allocate>") == 0) return &element_allocate;
392         if (strcmp(signature, "width") == 0) return &element_width;
393         if (strcmp(signature, "height") == 0) return &element_height;
394         if (strcmp(signature, "setPosition_(_,_,_,_)") == 0) return &element_setposition;
395         if (strcmp(signature, "setVisibility_(_)") == 0) return &element_setvisibility;
396         if (strcmp(signature, "setZOrder_(_)") == 0) return &element_setzorder;
397         if (strcmp(signature, "findIdAndBecomeThat_(_)") == 0) return &element_findIdAndBecomeThat;
398         if (strcmp(signature, "setProp_(_,_,_)") == 0) return &element_setProperty;
399         if (strcmp(signature, "setPropRGBA_(_,_,_,_,_,_)") == 0) return &element_setPropertyRGBA;
400         if (strcmp(signature, "getProp_(_,_)") == 0) return &element_getProperty;
401         if (strcmp(signature, "getPropRGBA_(_,_,_)") == 0) return &element_getPropertyRGBA;
402     }
403     return null;
404 }
405 
406 WrenForeignClassMethods wrenUIForeignClass(WrenVM* vm, const(char)* className) nothrow
407 {
408     WrenForeignClassMethods methods;
409     methods.allocate = null;
410     methods.finalize = null;
411     
412     if (strcmp(className, "Element") == 0)
413     {
414         methods.allocate = &element_allocate;
415     }
416     return methods;
417 }