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