1 /**
2 Copyright: Guillaume Piolat 2015-2017.
3 License:   $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0)
4 */
5 module gui;
6 
7 import core.stdc.stdlib;
8 
9 import std.math;
10 
11 import dplug.core;
12 import dplug.math;
13 import dplug.gui;
14 import dplug.pbrwidgets;
15 import dplug.client;
16 import dplug.flatwidgets;
17 import dplug.wren;
18 import leveldisplay;
19 import main;
20 
21 //debug = voxelExport;
22 
23 // Plugin GUI, based on PBRBackgroundGUI.
24 // If you don't want to use PBR, you not inherit from it.
25 class DistortGUI : PBRBackgroundGUI!("basecolor.jpg", "emissive.png", "material.png",
26                                      "depth.png", "skybox.jpg",
27 
28                                      // In development, enter here the absolute path to the gfx directory.
29                                      // This allows to reload background images at debug-time with the press of ENTER.
30                                      `/home/myuser/my/path/to/Dplug/examples/distort/gfx/`)
31 {
32 public:
33 nothrow:
34 @nogc:
35 
36     this(DistortClient client)
37     {
38         _client = client;
39 
40         static immutable float[7] ratios = [0.5f, 0.75f, 1.0f, 1.25f, 1.5f, 1.75f, 2.0f];
41         super( makeSizeConstraintsDiscrete(620, 330, ratios) );
42 
43         // Note: PBRCompositor default lighting might change in a future version (increase of light to allow white plastics).
44         //       So we keep the value.
45         PBRCompositor comp = cast(PBRCompositor)compositor;
46         comp.light1Color = vec3f(0.26, 0.24, 0.22f) * 0.98f;
47         comp.light2Dir = vec3f(-0.5f, 1.0f, 0.23f).normalized;
48         comp.light2Color = vec3f(0.36, 0.38f, 0.40) * 1.148;
49         comp.light3Dir = vec3f(0.0f, 1.0f, 0.1f).normalized;
50         comp.light3Color = vec3f(0.2f, 0.2f, 0.2f) * 0.84f;
51         comp.ambientLight = 0.042f;
52         comp.skyboxAmount = 0.56f;
53 
54         // Sets the number of pixels recomputed around dirtied controls.
55         // This is a tradeoff between Emissive light accuracy and speed.
56         // This needs to be adjusted visually.
57         setUpdateMargin(30);
58 
59         // All resources are bundled as a string import.
60         // You can avoid resource compilers that way.
61         // The only cost is that each resource is in each binary, this creates overhead with
62         _font = mallocNew!Font(cast(ubyte[])( import("VeraBd.ttf") ));
63 
64         // Builds the UI hierarchy
65         // Meanwhile, we hardcode each position.  
66 
67         _knobImageData = loadKnobImage( import("imageknob.png") );
68         addChild(_imageKnob = mallocNew!UIImageKnob(context(), _knobImageData, cast(FloatParameter) _client.param(paramBias)));
69 
70         // Add procedural knobs
71         addChild(_driveKnob = mallocNew!UIKnob(context(), cast(FloatParameter) _client.param(paramDrive)));
72 
73         // Add sliders
74         addChild(_inputSlider = mallocNew!UISlider(context(), cast(FloatParameter) _client.param(paramInput)));
75 
76         addChild(_outputSlider = mallocNew!UISlider(context(), cast(FloatParameter) _client.param(paramOutput)));
77 
78         // Add switch
79         addChild(_onOffSwitch = mallocNew!UIOnOffSwitch(context(), cast(BoolParameter) _client.param(paramOnOff)));
80   
81 
82         // Add bargraphs
83         addChild(_inputLevel = mallocNew!UILevelDisplay(context()));
84         addChild(_outputLevel = mallocNew!UILevelDisplay(context()));
85 
86         // Add resizer corner
87         addChild(_resizer = mallocNew!UIWindowResizer(context()));
88 
89         // Global color correction.
90         // Very useful at the end of the UI creating process.
91         // As the sole Raw-only widget it is always on top and doesn't need zOrder adjustment.
92         {
93             mat3x4!float colorCorrectionMatrix = mat3x4!float(- 0.07f, 1.0f , 1.15f, 0.03f,
94                                                               + 0.01f, 0.93f, 1.16f, 0.08f,
95                                                               + 0.0f , 1.0f , 1.10f, -0.01f);
96             addChild(_colorCorrection = mallocNew!UIColorCorrection(context()));
97             _colorCorrection.setLiftGammaGainContrastRGB(colorCorrectionMatrix);
98         }
99 
100         // Enable all things Wren
101         mixin(fieldIdentifiersAreIDs!DistortGUI); // Each UIElement in this object receives its identifier as runtime ID, ie. _inputSlider receives ID "_inputSlider"
102         context.enableWrenSupport();
103         //debug
104         //    context.wrenSupport.addModuleFileWatch("plugin", `/my/absolute/path/to/plugin.wren`); // debug => live reload, enter absolute path here
105         //else
106             context.wrenSupport.addModuleSource("plugin", import("plugin.wren"));                 // no debug => static scripts
107         context.wrenSupport.registerScriptExports!DistortGUI; // Note: for now, only UIElement should be @ScriptExport
108         context.wrenSupport.callCreateUI();
109 
110         debug(voxelExport)
111             context.requestUIScreenshot(); // onScreenshot will be called at next render, can be called from anywhere
112     }
113 
114     override void onAnimate(double dt, double time)
115     {
116         context.wrenSupport.callReflowWhenScriptsChange(dt);
117     }
118 
119     ~this()
120     {
121         // Note: UI widgets are owned by the UI and don't need to be destroyed manually
122         //       However some of the resources they consumed aren't owned by them, but borrowed.
123         _font.destroyFree();
124         _knobImageData.destroyFree();
125         context.disableWrenSupport();
126 
127         version(Dplug_ProfileUI)
128         {
129             writeFile(`/home/myuser/plugin-trace.json`, context.profiler.toBytes());
130             browseNoGC("https://ui.perfetto.dev/"); // A webtool to read that trace
131         }
132     }
133 
134     override void reflow()
135     {
136         super.reflow();
137         context.wrenSupport.callReflow();
138     }
139 
140     void sendFeedbackToUI(float* inputRMS, float* outputRMS, int frames, float sampleRate)
141     {
142         _inputLevel.sendFeedbackToUI(inputRMS, frames, sampleRate);
143         _outputLevel.sendFeedbackToUI(outputRMS, frames, sampleRate);
144     }
145 
146     debug(voxelExport)
147     {
148         // Show how to do a .qb export of final PBR render
149         override void onScreenshot(ImageRef!RGBA finalRender,
150                                    WindowPixelFormat pixelFormat,
151                                    ImageRef!RGBA diffuseMap,
152                                    ImageRef!L16 depthMap,
153                                    ImageRef!RGBA materialMap)
154         {
155             ubyte[] qb = encodeScreenshotAsQB(finalRender, pixelFormat, depthMap); // alternatively: encodeScreenshotAsPNG
156             if (qb)
157             {
158                 writeFile(`/my/path/to/distort.qb`, qb);
159                 free(qb.ptr);
160             }
161         }
162     }
163 
164 private:
165     DistortClient _client;
166 
167     // Resources
168     Font _font;
169     KnobImage _knobImageData;
170 
171     // Widgets can be exported to Wren. This allow styling through a plugin.wren script.
172     @ScriptExport 
173     {
174         UISlider _inputSlider;
175         UIKnob _driveKnob;
176         UISlider _outputSlider;
177         UIOnOffSwitch _onOffSwitch;
178         UILevelDisplay _inputLevel, _outputLevel;
179         UIColorCorrection _colorCorrection;
180         UIImageKnob _imageKnob;
181         UIWindowResizer _resizer;
182     }
183 }
184