1 module soundtab.ui.noterangewidget; 2 3 import dlangui.widgets.widget; 4 import soundtab.ui.noteutil; 5 import dlangui.core.signals; 6 7 interface NoteRangeChangeHandler { 8 void onNoteRangeChange(int minNote, int maxNote); 9 } 10 11 class NoteRangeWidget : Widget { 12 13 Signal!NoteRangeChangeHandler onNoteRangeChange; 14 15 double _currentPitch = 440; 16 17 int _minNote; 18 int _maxNote; 19 int _keyWidth; 20 int _keys; 21 22 int _rangeStart; 23 int _rangeEnd; 24 25 @property int rangeStart() { return _rangeStart; } 26 @property int rangeEnd() { return _rangeEnd; } 27 28 this() { 29 super("pitch"); 30 styleId = "EDIT_LINE"; 31 layoutWidth = FILL_PARENT; 32 _minNote = fullNameToNote("C0"); 33 _maxNote = fullNameToNote("B8"); 34 _rangeStart = fullNameToNote("C2"); 35 _rangeEnd = fullNameToNote("C6"); 36 tooltipText = "Left mouse button - set range start; Right mous button - set range end"; 37 } 38 39 void setPitch(double freq) { 40 _currentPitch = freq; 41 invalidate(); 42 } 43 44 void handleRangeChange(int start, int end) { 45 if (_rangeStart == start && _rangeEnd == end) 46 return; 47 if (_rangeStart == start) { 48 _rangeEnd = end; 49 // end is moved 50 if (_rangeEnd - _rangeStart < 12) 51 _rangeStart = _rangeEnd - 12; 52 } else { 53 // start is moved 54 _rangeStart = start; 55 if (_rangeEnd - _rangeStart < 12) 56 _rangeEnd = _rangeStart + 12; 57 } 58 if (isBlackNote(_rangeStart)) 59 _rangeStart--; 60 if (isBlackNote(_rangeEnd)) 61 _rangeEnd++; 62 if (_rangeEnd - _rangeStart < 12) 63 _rangeEnd = _rangeStart + 12; 64 if (_rangeStart < _minNote) { 65 _rangeStart = _minNote; 66 if (_rangeEnd - _rangeStart < 12) 67 _rangeEnd = _rangeStart + 12; 68 } 69 if (_rangeEnd > _maxNote) { 70 _rangeEnd = _maxNote; 71 if (_rangeEnd - _rangeStart < 12) 72 _rangeStart = _rangeEnd - 12; 73 } 74 //Log.d("NoteRangeWidget: new note range = ", noteToFullName(_rangeStart), " .. ", noteToFullName(_rangeEnd)); 75 if (onNoteRangeChange.assigned) 76 onNoteRangeChange(_rangeStart, _rangeEnd); 77 } 78 79 /// process mouse event; return true if event is processed by widget. 80 override bool onMouseEvent(MouseEvent event) { 81 if (event.action == MouseAction.ButtonDown || (event.action == MouseAction.Move && event.buttonFlags)) { 82 int note = noteByPoint(event.x); 83 int side = 0; 84 if (event.action == MouseAction.ButtonDown && event.button == MouseButton.Left) 85 side = -1; 86 if (event.action == MouseAction.Move && event.buttonFlags & MouseFlag.LButton) 87 side = -1; 88 if (event.action == MouseAction.ButtonDown && event.button == MouseButton.Right) 89 side = 1; 90 if (event.action == MouseAction.Move && event.buttonFlags & MouseFlag.RButton) 91 side = 1; 92 if (side < 0) { 93 handleRangeChange(note, _rangeEnd); 94 invalidate(); 95 return true; 96 } 97 if (side > 0) { 98 handleRangeChange(_rangeStart, note); 99 invalidate(); 100 return true; 101 } 102 } 103 return super.onMouseEvent(event); 104 } 105 106 /** 107 Measure widget according to desired width and height constraints. (Step 1 of two phase layout). 108 */ 109 override void measure(int parentWidth, int parentHeight) { 110 int w = parentWidth; 111 Rect m = margins; 112 Rect p = padding; 113 w -= m.left + m.right + p.left + p.right; 114 _keys = whiteNotesInRange(_minNote, _maxNote); 115 _keyWidth = w / _keys; 116 if (_keyWidth < 4) 117 _keyWidth = 4; 118 119 int h = _keyWidth * 5; 120 measuredContent(parentWidth, parentHeight, _keyWidth * _keys, h); 121 } 122 123 int noteByPoint(int x) { 124 Rect rc = _pos; 125 applyMargins(rc); 126 applyPadding(rc); 127 int delta = (rc.width - 2) - (_keyWidth * _keys); 128 rc.left += delta / 2; 129 rc.right = rc.left + _keyWidth * _keys + 1; 130 rc.shrink(1, 1); 131 if (x < rc.left) 132 return _minNote; 133 if (x >= rc.right) 134 return _maxNote; 135 Rect keyRect = rc; 136 int index = 0; 137 for (int i = _minNote; i <= _maxNote; i++) { 138 if (!isBlackNote(i)) { 139 keyRect.left = rc.left + _keyWidth * index; 140 keyRect.right = keyRect.left + _keyWidth - 1; 141 if (x < keyRect.right) 142 return i; 143 index++; 144 } 145 } 146 return _maxNote; 147 } 148 149 /// Set widget rectangle to specified value and layout widget contents. (Step 2 of two phase layout). 150 override void layout(Rect rc) { 151 if (visibility == Visibility.Gone) { 152 return; 153 } 154 super.layout(rc); 155 int w = rc.width; 156 Rect m = margins; 157 Rect p = padding; 158 w -= m.left + m.right + p.left + p.right; 159 _keys = whiteNotesInRange(_minNote, _maxNote); 160 _keyWidth = w / _keys; 161 if (_keyWidth < 4) 162 _keyWidth = 4; 163 } 164 165 166 /// Draw widget at its position to buffer 167 override void onDraw(DrawBuf buf) { 168 if (visibility != Visibility.Visible) 169 return; 170 Rect rc = _pos; 171 applyMargins(rc); 172 auto saver = ClipRectSaver(buf, rc, alpha); 173 //buf.fillRect(rc, 0x000000); 174 applyPadding(rc); 175 _needDraw = false; 176 int delta = (rc.width - 2) - (_keyWidth * _keys); 177 rc.left += delta / 2; 178 rc.right = rc.left + _keyWidth * _keys + 1; 179 buf.fillRect(rc, 0x000000); 180 rc.shrink(1, 1); 181 Rect keyRect = rc; 182 183 double note = toLogScale(_currentPitch); 184 int currentNote = getNearestNote(note); 185 int index = 0; 186 int noteDelta = cast(int)((note - currentNote) * 1000); 187 for (int i = _minNote; i <= _maxNote; i++) { 188 bool inRange = (i >= _rangeStart) && (i <= _rangeEnd); 189 if (!isBlackNote(i)) { 190 uint color = inRange ? 0xFFFFFF : 0xC0C0C0; 191 //if (i == currentNote) 192 // color = 0xFFC0C0; 193 keyRect.left = rc.left + _keyWidth * index; 194 keyRect.right = keyRect.left + _keyWidth - 1; 195 buf.fillRect(keyRect, color); 196 if (i == currentNote) { 197 Rect hrc = keyRect; 198 hrc.left += 1; 199 hrc.right -= 1; 200 hrc.bottom -= 1; 201 buf.fillRect(hrc, 0xFFC0C0); 202 int x = hrc.middlex + hrc.width * noteDelta / 1000; 203 buf.fillRect(Rect(x, hrc.top, x + 1, hrc.bottom), 0xFF0000); 204 } 205 index++; 206 } 207 if (isBlackNote(i - 1)) { 208 uint color = 0x000000; 209 if (i - 1 == currentNote) 210 color = 0xFFC0C0; 211 212 Rect blackRect = keyRect; 213 blackRect.bottom = blackRect.bottom - blackRect.height * 40 / 100; 214 blackRect.left -= _keyWidth / 2; 215 blackRect.right -= _keyWidth / 2; 216 blackRect.left += _keyWidth / 6; 217 blackRect.right -= _keyWidth / 6; 218 buf.fillRect(blackRect, 0x000000); 219 if (color != 0x000000) { 220 blackRect.left += 2; 221 blackRect.right -= 2; 222 blackRect.bottom -= 2; 223 //blackRect.top += 1; 224 buf.fillRect(blackRect, color); 225 int x = blackRect.middlex + blackRect.width * noteDelta / 1000; 226 buf.fillRect(Rect(x, blackRect.top, x + 1, blackRect.bottom), 0xFF0000); 227 } 228 } 229 } 230 } 231 232 } 233