OSC: GUI tweaking
[ardour.git] / libs / surfaces / faderport8 / fp8_button.h
1 /* FaderPort8 Button Interface
2  *
3  * Copyright (C) 2017 Robin Gareus <robin@gareus.org>
4  *
5  * This program is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU General Public License
7  * as published by the Free Software Foundation; either version 2
8  * of the License, or (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
18  */
19
20 #ifndef _ardour_surfaces_fp8button_h_
21 #define _ardour_surfaces_fp8button_h_
22
23 #include <stdint.h>
24
25 #include "pbd/base_ui.h"
26 #include "pbd/signals.h"
27
28 #include "fp8_base.h"
29
30 namespace ArdourSurface {
31
32 /* virtual base-class and interface */
33 class FP8ButtonInterface
34 {
35 public:
36         FP8ButtonInterface () {}
37         virtual ~FP8ButtonInterface () {}
38
39         /* user API */
40         PBD::Signal0<void> pressed;
41         PBD::Signal0<void> released;
42
43         virtual bool is_pressed () const { return false; }
44         virtual bool is_active () const { return false; }
45
46         virtual void ignore_release () {}
47
48         /* internal API - called from midi thread,
49          * user pressed/released button the device
50          */
51         virtual bool midi_event (bool) = 0;
52
53         /* internal API - called from surface thread
54          * set Light on the button
55          */
56         virtual void set_active (bool a) = 0;
57         virtual void set_color (uint32_t rgba) {}
58         virtual void set_blinking (bool) {}
59
60         static bool force_change; // used during init
61 };
62
63 /* ****************************************************************************
64  * Implementations
65  */
66
67 class FP8DummyButton : public FP8ButtonInterface
68 {
69 public:
70         virtual void set_active (bool a) {}
71         virtual bool midi_event (bool) { return false; }
72 };
73
74
75 /* common implementation */
76 class FP8ButtonBase : public FP8ButtonInterface
77 {
78 public:
79         FP8ButtonBase (FP8Base& b)
80                 : _base (b)
81                 , _pressed (false)
82                 , _active (false)
83                 , _ignore_release (false)
84                 , _rgba (0)
85                 , _blinking (false)
86         { }
87
88         bool is_pressed () const { return _pressed; }
89         bool is_active () const { return _active; }
90
91         virtual bool midi_event (bool a)
92         {
93                 if (a == _pressed) {
94                         return false;
95                 }
96                 _pressed = a;
97                 if (a) {
98                         pressed (); /* EMIT SIGNAL */
99                 } else {
100                         if (_ignore_release) {
101                                 _ignore_release = false;
102                         } else {
103                                 released (); /* EMIT SIGNAL */
104                         }
105                 }
106                 return true;
107         }
108
109         void ignore_release () {
110                 if (_pressed) {
111                         _ignore_release = true;
112                 }
113         }
114
115         void set_blinking (bool yes) {
116                 if (yes && !_blinking) {
117                         _blinking = true;
118                         _base.BlinkIt.connect_same_thread (_blink_connection, boost::bind (&FP8ButtonBase::blink, this, _1));
119                 } else if (!yes && _blinking) {
120                         _blink_connection.disconnect ();
121                         blink (true);
122                         _blinking = false;
123                 }
124         }
125
126 protected:
127         FP8Base&              _base;
128         bool                  _pressed;
129         bool                  _active;
130         bool                  _ignore_release;
131         uint32_t              _rgba;
132         virtual void blink (bool onoff) = 0;
133
134 private:
135         PBD::ScopedConnection _blink_connection;
136         bool _blinking;
137 };
138
139 /* A basic LED or RGB button, not shift sensitive */
140 class FP8Button : public FP8ButtonBase
141 {
142 public:
143         FP8Button (FP8Base& b, uint8_t id, bool color = false)
144                 : FP8ButtonBase (b)
145                 , _midi_id (id)
146                 , _has_color (color)
147         { }
148
149         virtual void set_active (bool a)
150         {
151                 if (_active == a && !force_change) {
152                         return;
153                 }
154                 _active = a;
155                 _base.tx_midi3 (0x90, _midi_id, a ? 0x7f : 0x00);
156         }
157
158         void set_color (uint32_t rgba)
159         {
160                 if (!_has_color || _rgba == rgba) {
161                         return;
162                 }
163                 _rgba = rgba;
164                 _base.tx_midi3 (0x91, _midi_id, (_rgba >> 25) & 0x7f);
165                 _base.tx_midi3 (0x92, _midi_id, (_rgba >> 17) & 0x7f);
166                 _base.tx_midi3 (0x93, _midi_id, (_rgba >>  9) & 0x7f);
167         }
168
169 protected:
170         void blink (bool onoff)
171         {
172                 if (!_active) { return; }
173                 _base.tx_midi3 (0x90, _midi_id, onoff ? 0x7f : 0x00);
174         }
175
176         uint8_t  _midi_id; // MIDI-note
177         bool     _has_color;
178 };
179
180 /* footswitch and encoder-press buttons */
181 class FP8ReadOnlyButton : public FP8Button
182 {
183 public:
184         FP8ReadOnlyButton (FP8Base& b, uint8_t id, bool color = false)
185                 : FP8Button (b, id, color)
186         {}
187
188         void set_active (bool) { }
189 };
190
191 /* virtual button. used for shift toggle. */
192 class ShadowButton : public FP8ButtonBase
193 {
194 public:
195         ShadowButton (FP8Base& b)
196                 : FP8ButtonBase (b)
197         {}
198
199         PBD::Signal1<void, bool> ActiveChanged;
200         PBD::Signal0<void> ColourChanged;
201
202         uint32_t color () const { return _rgba; }
203
204         bool midi_event (bool a)
205         {
206                 assert (0);
207                 return false;
208         }
209
210         bool set_pressed (bool a)
211         {
212                 return FP8ButtonBase::midi_event (a);
213         }
214
215         void set_active (bool a)
216         {
217                 if (_active == a && !force_change) {
218                         return;
219                 }
220                 _active = a;
221                 ActiveChanged (a); /* EMIT SIGNAL */
222         }
223
224         void set_color (uint32_t rgba)
225         {
226                 if (_rgba == rgba) {
227                         return;
228                 }
229                 _rgba = rgba;
230                 ColourChanged ();
231         }
232
233 protected:
234         void blink (bool onoff) {
235                 if (!_active) { return; }
236                 ActiveChanged (onoff);
237         }
238 };
239
240 /* Wraps 2 buttons with the same physical MIDI ID */
241 class FP8DualButton : public FP8ButtonInterface
242 {
243 public:
244         FP8DualButton (FP8Base& b, uint8_t id, bool color = false)
245                 : _base (b)
246                 , _b0 (b)
247                 , _b1 (b)
248                 , _midi_id (id)
249                 , _has_color (color)
250                 , _rgba (0)
251                 , _shift (false)
252         {
253                 _b0.ActiveChanged.connect_same_thread (_button_connections, boost::bind (&FP8DualButton::active_changed, this, false, _1));
254                 _b1.ActiveChanged.connect_same_thread (_button_connections, boost::bind (&FP8DualButton::active_changed, this, true, _1));
255                 if (_has_color) {
256                         _b0.ColourChanged.connect_same_thread (_button_connections, boost::bind (&FP8DualButton::colour_changed, this, false));
257                         _b1.ColourChanged.connect_same_thread (_button_connections, boost::bind (&FP8DualButton::colour_changed, this, true));
258                 }
259         }
260
261         bool midi_event (bool a) {
262                 return (_shift ? _b1 : _b0).set_pressed (a);
263         }
264
265         void set_active (bool a) {
266                 /* This button is never directly used
267                  * by the libardour side API.
268                  */
269                 assert (0);
270         }
271
272         void active_changed (bool s, bool a) {
273                 if (s != _shift) {
274                         return;
275                 }
276                 _base.tx_midi3 (0x90, _midi_id, a ? 0x7f : 0x00);
277         }
278
279         void colour_changed (bool s) {
280                 if (s != _shift || !_has_color) {
281                         return;
282                 }
283                 uint32_t rgba = (_shift ? _b1 : _b0).color ();
284                 if (rgba == _rgba) {
285                         return;
286                 }
287                 _rgba = rgba;
288                 _base.tx_midi3 (0x91, _midi_id, (rgba >> 25) & 0x7f);
289                 _base.tx_midi3 (0x92, _midi_id, (rgba >> 17) & 0x7f);
290                 _base.tx_midi3 (0x93, _midi_id, (rgba >>  9) & 0x7f);
291         }
292
293         FP8ButtonInterface* button () { return &_b0; }
294         FP8ButtonInterface* button_shift () { return &_b1; }
295
296 protected:
297         FP8Base&     _base;
298
299         virtual void connect_toggle () = 0;
300
301         void shift_changed (bool shift) {
302                 if (_shift == shift) {
303                         return;
304                 }
305                 (_shift ? _b1 : _b0).set_pressed (false);
306                 _shift = shift;
307                 active_changed (_shift, (_shift ? _b1 : _b0).is_active());
308                 colour_changed (_shift);
309         }
310
311 private:
312         ShadowButton _b0;
313         ShadowButton _b1;
314         uint8_t      _midi_id; // MIDI-note
315         bool         _has_color;
316         uint32_t     _rgba;
317         bool         _shift;
318         PBD::ScopedConnectionList _button_connections;
319 };
320
321 class FP8ShiftSensitiveButton : public FP8DualButton
322 {
323 public:
324         FP8ShiftSensitiveButton (FP8Base& b, uint8_t id, bool color = false)
325                 :FP8DualButton (b, id, color)
326         {
327                 connect_toggle ();
328         }
329
330 protected:
331         void connect_toggle ()
332         {
333                 _base.ShiftButtonChange.connect_same_thread (_shift_connection, boost::bind (&FP8ShiftSensitiveButton::shift_changed, this, _1));
334         }
335
336 private:
337         PBD::ScopedConnection _shift_connection;
338 };
339
340 class FP8ARMSensitiveButton : public FP8DualButton
341 {
342 public:
343         FP8ARMSensitiveButton (FP8Base& b, uint8_t id, bool color = false)
344                 :FP8DualButton (b, id, color)
345         {
346                 connect_toggle ();
347         }
348
349 protected:
350         void connect_toggle ()
351         {
352                 _base.ARMButtonChange.connect_same_thread (_arm_connection, boost::bind (&FP8ARMSensitiveButton::shift_changed, this, _1));
353         }
354
355 private:
356         PBD::ScopedConnection _arm_connection;
357 };
358
359
360 // short press: activate in press, deactivate on release,
361 // long press + hold, activate on press, de-activate directly on release
362 // e.g. mute/solo  press + hold => changed()
363 class FP8MomentaryButton : public FP8ButtonInterface
364 {
365 public:
366         FP8MomentaryButton (FP8Base& b, uint8_t id)
367                 : _base (b)
368                 , _midi_id (id)
369                 , _pressed (false)
370                 , _active (false)
371         {}
372
373         ~FP8MomentaryButton () {
374                 _hold_connection.disconnect ();
375         }
376
377         PBD::Signal1<void, bool> StateChange;
378
379         void set_active (bool a)
380         {
381                 if (_active == a && !force_change) {
382                         return;
383                 }
384                 _active = a;
385                 _base.tx_midi3 (0x90, _midi_id, a ? 0x7f : 0x00);
386         }
387
388         void reset ()
389         {
390                 _was_active_on_press = false;
391                 _hold_connection.disconnect ();
392         }
393
394         bool midi_event (bool a)
395         {
396                 if (a == _pressed) {
397                         return false;
398                 }
399
400                 _pressed = a;
401
402                 if (a) {
403                         _was_active_on_press = _active;
404                 }
405
406                 if (a && !_active) {
407                         _momentaty = false;
408                         StateChange (true); /* EMIT SIGNAL */
409                         Glib::RefPtr<Glib::TimeoutSource> hold_timer =
410                                 Glib::TimeoutSource::create (500);
411                         hold_timer->attach (fp8_loop()->get_context());
412                         _hold_connection = hold_timer->connect (sigc::mem_fun (*this, &FP8MomentaryButton::hold_timeout));
413                 } else if (!a && _was_active_on_press) {
414                         _hold_connection.disconnect ();
415                         _momentaty = false;
416                         StateChange (false); /* EMIT SIGNAL */
417                 } else if (!a && _momentaty) {
418                         _hold_connection.disconnect ();
419                         _momentaty = false;
420                         StateChange (false); /* EMIT SIGNAL */
421                 }
422                 return true;
423         }
424
425 protected:
426         FP8Base& _base;
427         uint8_t  _midi_id; // MIDI-note
428         bool     _pressed;
429         bool     _momentaty;
430         bool     _was_active_on_press;
431         bool     _active;
432
433 private:
434         bool hold_timeout ()
435         {
436                 _momentaty = true;
437                 return false;
438         }
439         sigc::connection _hold_connection;
440 };
441
442 /* an auto-repeat button.
443  * press + hold emits continuous "press" events.
444  */
445 class FP8RepeatButton : public FP8Button
446 {
447 public:
448         FP8RepeatButton (FP8Base& b, uint8_t id, bool color = false)
449                 : FP8Button (b, id, color)
450                 , _skip (0)
451         {}
452
453         ~FP8RepeatButton ()
454         {
455                 stop_repeat ();
456         }
457
458         bool midi_event (bool a)
459         {
460                 bool rv = FP8Button::midi_event (a);
461                 if (rv && a) {
462                         start_repeat ();
463                 }
464                 return rv;
465         }
466
467         void stop_repeat ()
468         {
469                 _press_timeout_connection.disconnect ();
470         }
471
472 private:
473         void start_repeat ()
474         {
475                 stop_repeat ();
476                 _skip = 5;
477                 Glib::RefPtr<Glib::TimeoutSource> press_timer =
478                         Glib::TimeoutSource::create (100);
479                 press_timer->attach (fp8_loop()->get_context());
480                 _press_timeout_connection = press_timer->connect (sigc::mem_fun (*this, &FP8RepeatButton::repeat_press));
481         }
482
483         bool repeat_press ()
484         {
485                 if (!_pressed) {
486                         return false;
487                 }
488                 if (_skip > 0) {
489                         --_skip;
490                         return true;
491                 }
492                 pressed ();
493                 return true;
494         }
495
496         int _skip;
497         sigc::connection _press_timeout_connection;
498 };
499
500 } /* namespace */
501 #endif /* _ardour_surfaces_fp8button_h_ */