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 }