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