1 /**
2 * Copyright: Copyright Auburn Sounds 2015 and later.
3 * License:   $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0)
4 * Authors:   Guillaume Piolat
5 */
6 module dplug.gui.bargraph;
7 
8 import std.math;
9 
10 import dplug.gui.element;
11 import dplug.core.sync;
12 import dplug.core.math;
13 import std.algorithm.comparison: clamp;
14 
15 // Vertical bargraphs made of LEDs
16 class UIBargraph : UIElement
17 {
18 public:
19 nothrow:
20 @nogc:
21 
22     struct LED
23     {
24         RGBA diffuse;
25     }
26 
27     /// Creates a new bargraph.
28     /// [minValue .. maxValue] is the interval of values that will span [0..1] once remapped.
29     this(UIContext context, int numChannels, float minValue, float maxValue,
30          int redLeds = 0, int orangeLeds = 3, int yellowLeds = 0, int magentaLeds = 9)
31     {
32         super(context);
33 
34         _values = mallocSliceNoInit!float(numChannels);
35         _values[] = 0;
36 
37         _minValue = minValue;
38         _maxValue = maxValue;
39 
40         _leds = makeAlignedBuffer!LED();
41 
42         foreach (i; 0..redLeds)
43             _leds.pushBack( LED(RGBA(255, 32, 0, 255)) );
44 
45         foreach (i; 0..orangeLeds)
46             _leds.pushBack( LED(RGBA(255, 128, 64, 255)) );
47 
48         foreach (i; 0..yellowLeds)
49             _leds.pushBack( LED(RGBA(255, 255, 64, 255)) );
50 
51         foreach (i; 0..magentaLeds)
52             _leds.pushBack( LED(RGBA(226, 120, 249, 255)) );
53 
54          _valueMutex = makeMutex();
55     }
56 
57     ~this()
58     {
59         _values.freeSlice();
60     }
61 
62 
63     override void onDraw(ImageRef!RGBA diffuseMap, ImageRef!L16 depthMap, ImageRef!RGBA materialMap, box2i[] dirtyRects) nothrow @nogc
64     {
65         int numLeds = cast(int)_leds.length;
66         int numChannels = cast(int)_values.length;
67         int width = _position.width;
68         int height = _position.height;
69         float border = width * 0.06f;
70 
71         box2f available = box2f(border, border, width - border, height - border);
72 
73         float heightPerLed = cast(float)(available.height) / cast(float)numLeds;
74         float widthPerLed = cast(float)(available.width) / cast(float)numChannels;
75 
76         float tolerance = 1.0f / numLeds;
77 
78         foreach(channel; 0..numChannels)
79         {
80             float value = getValue(channel);
81             float x0 = border + widthPerLed * (channel + 0.15f);
82             float x1 = x0 + widthPerLed * 0.7f;
83 
84             foreach(i; 0..numLeds)
85             {
86                 float y0 = border + heightPerLed * (i + 0.1f);
87                 float y1 = y0 + heightPerLed * 0.8f;
88 
89                 depthMap.aaFillRectFloat!false(x0, y0, x1, y1, L16(16000));
90 
91                 float ratio = 1 - i / cast(float)(numLeds - 1);
92 
93                 ubyte shininess = cast(ubyte)(0.5f + 160.0f * (1 - smoothStep(value - tolerance, value, ratio)));
94 
95                 RGBA color = _leds[i].diffuse;
96                 color.r = (color.r * (255 + shininess) + 255) / 510;
97                 color.g = (color.g * (255 + shininess) + 255) / 510;
98                 color.b = (color.b * (255 + shininess) + 255) / 510;
99                 color.a = shininess;
100                 diffuseMap.aaFillRectFloat!false(x0, y0, x1, y1, color);
101 
102                 materialMap.aaFillRectFloat!false(x0, y0, x1, y1, RGBA(0, 128, 255, 255));
103 
104             }
105         }
106     }
107 
108     void setValues(const(float)[] values) nothrow @nogc
109     {
110         {
111             _valueMutex.lock();
112             assert(values.length == _values.length);
113 
114             // remap all values
115             foreach(i; 0..values.length)
116             {
117                 _values[i] = linmap!float(values[i], _minValue, _maxValue, 0, 1);
118                 _values[i] = clamp!float(_values[i], 0, 1);
119             }
120             _valueMutex.unlock();
121         }
122         setDirtyWhole();
123     }
124 
125     float getValue(int channel) nothrow @nogc
126     {
127         float res = void;
128         _valueMutex.lock();
129         res = _values[channel];
130         _valueMutex.unlock();
131         return res;
132     }
133 
134 protected:
135 
136     AlignedBuffer!LED _leds;
137 
138     UncheckedMutex _valueMutex;
139     float[] _values;
140     float _minValue;
141     float _maxValue;
142 }