1 /*
2  * Copyright (C) 2020 Linux Studio Plugins Project <https://lsp-plug.in/>
3  *           (C) 2020 Vladimir Sadovnikov <sadko4u@gmail.com>
4  *
5  * This file is part of lsp-plugins
6  * Created on: 21 июн. 2017 г.
7  *
8  * lsp-plugins is free software: you can redistribute it and/or modify
9  * it under the terms of the GNU Lesser General Public License as published by
10  * the Free Software Foundation, either version 3 of the License, or
11  * any later version.
12  *
13  * lsp-plugins is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * GNU Lesser General Public License for more details.
17  *
18  * You should have received a copy of the GNU Lesser General Public License
19  * along with lsp-plugins. If not, see <https://www.gnu.org/licenses/>.
20  */
21 
22 #include <ui/tk/tk.h>
23 
24 namespace lsp
25 {
26     namespace tk
27     {
28         const w_class_t LSPButton::metadata = { "LSPButton", &LSPWidget::metadata };
29 
LSPButton(LSPDisplay * dpy)30         LSPButton::LSPButton(LSPDisplay *dpy):
31             LSPWidget(dpy),
32             sColor(this),
33             sFont(this),
34             sTitle(this)
35         {
36             nWidth      = 18;
37             nHeight     = 18;
38             nMinWidth   = 18;
39             nMinHeight  = 18;
40             nState      = S_EDITABLE;
41             nBMask      = 0;
42             nChanges    = 0;
43 
44             pClass      = &metadata;
45         }
46 
~LSPButton()47         LSPButton::~LSPButton()
48         {
49         }
50 
init()51         status_t LSPButton::init()
52         {
53             status_t result = LSPWidget::init();
54             if (result != STATUS_OK)
55                 return result;
56 
57             sFont.init();
58             sFont.set_size(12.0f);
59 
60             init_color(C_BUTTON_FACE, &sColor);
61             init_color(C_BUTTON_TEXT, sFont.color());
62             sTitle.bind();
63 
64             ui_handler_id_t id = 0;
65             id = sSlots.add(LSPSLOT_CHANGE, slot_on_change, self());
66             if (id < 0) return -id;
67             id = sSlots.add(LSPSLOT_SUBMIT, slot_on_submit, self());
68             if (id < 0) return -id;
69 
70             return STATUS_OK;
71         }
72 
slot_on_change(LSPWidget * sender,void * ptr,void * data)73         status_t LSPButton::slot_on_change(LSPWidget *sender, void *ptr, void *data)
74         {
75             LSPButton *_this = widget_ptrcast<LSPButton>(ptr);
76             return (_this != NULL) ? _this->on_change() : STATUS_BAD_ARGUMENTS;
77         }
78 
slot_on_submit(LSPWidget * sender,void * ptr,void * data)79         status_t LSPButton::slot_on_submit(LSPWidget *sender, void *ptr, void *data)
80         {
81             LSPButton *_this = widget_ptrcast<LSPButton>(ptr);
82             return (_this != NULL) ? _this->on_submit() : STATUS_BAD_ARGUMENTS;
83         }
84 
on_change()85         status_t LSPButton::on_change()
86         {
87             return STATUS_OK;
88         }
89 
on_submit()90         status_t LSPButton::on_submit()
91         {
92             return STATUS_OK;
93         }
94 
check_mouse_over(ssize_t x,ssize_t y)95         bool LSPButton::check_mouse_over(ssize_t x, ssize_t y)
96         {
97             x              -= sSize.nLeft;
98             y              -= sSize.nTop;
99 
100             ssize_t left    = ssize_t(sSize.nWidth - nWidth) >> 1;
101             ssize_t top     = ssize_t(sSize.nHeight - nHeight) >> 1;
102             ssize_t right   = left + nWidth;
103             ssize_t bottom  = top + nHeight;
104 
105             return ((x >= left) && (x <= right) && (y >= top) && (y <= bottom));
106         }
107 
set_trigger()108         void LSPButton::set_trigger()
109         {
110             if (nState & S_TRIGGER)
111                 return;
112             nState      = (nState & (~S_TOGGLE)) | S_TRIGGER;
113 
114             query_draw();
115         }
116 
set_toggle()117         void LSPButton::set_toggle()
118         {
119             if (nState & S_TOGGLE)
120                 return;
121             nState      = (nState & (~S_TRIGGER)) | S_TOGGLE;
122 
123             query_draw();
124         }
125 
set_normal()126         void LSPButton::set_normal()
127         {
128             if (!(nState & (S_TOGGLE | S_TRIGGER)))
129                 return;
130             nState      = nState & (~(S_TRIGGER | S_TOGGLE));
131 
132             query_draw();
133         }
134 
set_down(bool value)135         void LSPButton::set_down(bool value)
136         {
137             nState     &= ~(S_DOWN | S_PRESSED | S_TOGGLED);
138 
139             if (value)
140                 nState     |= S_DOWN | ((nState & S_TRIGGER) ? S_PRESSED : S_TOGGLED);
141 
142             query_draw();
143         }
144 
set_led(bool value)145         void LSPButton::set_led(bool value)
146         {
147             size_t state = nState;
148             if (value)
149                 nState     |= S_LED;
150             else
151                 nState     &= ~S_LED;
152 
153             if (nState != state)
154                 query_draw();
155         }
156 
set_editable(bool value)157         void LSPButton::set_editable(bool value)
158         {
159             size_t state = nState;
160             if (value)
161                 nState     |= S_EDITABLE;
162             else
163                 nState     &= ~S_EDITABLE;
164 
165             if (nState != state)
166                 query_draw();
167         }
168 
set_min_width(size_t value)169         void LSPButton::set_min_width(size_t value)
170         {
171             if (nMinWidth == value)
172                 return;
173             nMinWidth       = value;
174             query_resize();
175         }
176 
set_min_height(size_t value)177         void LSPButton::set_min_height(size_t value)
178         {
179             if (nMinHeight == value)
180                 return;
181             nMinHeight      = value;
182             query_resize();
183         }
184 
set_min_size(size_t width,size_t height)185         void LSPButton::set_min_size(size_t width, size_t height)
186         {
187             if ((nMinWidth == width) && (nMinHeight == height))
188                 return;
189             nMinWidth       = width;
190             nMinHeight      = height;
191             query_resize();
192         }
193 
draw(ISurface * s)194         void LSPButton::draw(ISurface *s)
195         {
196             IGradient *gr = NULL;
197             size_t pressed = nState;
198 
199             // Prepare palette
200             Color bg_color(sBgColor);
201             Color color(sColor);
202             color.scale_lightness(brightness());
203 
204             // Draw background
205             s->fill_rect(0, 0, sSize.nWidth, sSize.nHeight, bg_color);
206 
207             // Calculate real boundaries
208             ssize_t c_x     = (sSize.nWidth >> 1);
209             ssize_t c_y     = (sSize.nHeight >> 1);
210 
211             // Calculate parameters
212             Color hole(0.0f, 0.0f, 0.0f);
213             float b_rad  = sqrtf(nWidth*nWidth + nHeight*nHeight);
214             size_t bsize = (nWidth < nHeight) ? nWidth : nHeight;
215             ssize_t b_w  = nWidth >> 1;
216             ssize_t b_h  = nHeight >> 1;
217             ssize_t b_r  = bsize >> 1;          // Button radius
218             ssize_t b_rr = 2 + (bsize >> 4);    // Button rounding radius
219             ssize_t l_rr = (bsize >> 2);
220 
221             // Draw hole
222             bool aa = s->set_antialiasing(true);
223             s->fill_round_rect(c_x - b_w - 1, c_y - b_h - 1, nWidth + 2, nHeight + 2, b_rr + 1, hole);
224 
225             // Change size if pressed
226             ssize_t b_l = b_rr;
227             if (pressed & S_PRESSED)
228             {
229                 b_l ++;
230                 b_r --;
231                 b_w --;
232                 b_h --;
233                 b_rr --;
234             }
235             else if (pressed & S_TOGGLED)
236             {
237                 b_r --;
238                 b_w --;
239                 b_h --;
240             }
241             else
242                 b_l ++;
243 
244             float lightness = color.lightness();
245             if (pressed & S_LED)
246             {
247                 // Draw light
248 //                size_t flag = (nState & S_TRIGGER) ? S_PRESSED : S_TOGGLED;
249 
250                 if (pressed & S_DOWN)
251                 {
252                     ssize_t x_rr = l_rr - 1;
253 
254                     gr  =  s->linear_gradient(c_x, c_y - b_h, c_x, c_y - b_h - x_rr);
255                     gr->add_color(0.0, color, 0.5f);
256                     gr->add_color(1.0, color, 1.0f);
257                     s->fill_triangle(c_x - b_w - l_rr, c_y - b_h - l_rr, c_x + b_w + l_rr, c_y - b_h - l_rr, c_x, c_y, gr);
258                     delete gr;
259 
260                     gr  =  s->linear_gradient(c_x, c_y + b_h, c_x, c_y + b_h + x_rr);
261                     gr->add_color(0.0, color, 0.5f);
262                     gr->add_color(1.0, color, 1.0f);
263                     s->fill_triangle(c_x + b_w + l_rr, c_y + b_h + l_rr, c_x - b_w - l_rr, c_y + b_h + l_rr, c_x, c_y, gr);
264                     delete gr;
265 
266                     gr  =  s->linear_gradient(c_x - b_w, c_y, c_x - b_w - x_rr, c_y);
267                     gr->add_color(0.0, color, 0.5f);
268                     gr->add_color(1.0, color, 1.0f);
269                     s->fill_triangle(c_x - b_w - l_rr, c_y - b_h - l_rr, c_x - b_w - l_rr, c_y + b_h + l_rr, c_x, c_y, gr);
270                     delete gr;
271 
272                     gr  =  s->linear_gradient(c_x + b_w, c_y, c_x + b_w + x_rr, c_y);
273                     gr->add_color(0.0, color, 0.5f);
274                     gr->add_color(1.0, color, 1.0f);
275                     s->fill_triangle(c_x + b_w + l_rr, c_y + b_h + l_rr, c_x + b_w + l_rr, c_y - b_h - l_rr, c_x, c_y, gr);
276                     delete gr;
277                 }
278                 else
279                     lightness  *= 0.5f;
280             }
281 
282             for (ssize_t i=0; (i++)<b_l; )
283             {
284                 float bright = lightness * sqrtf(i * i) / b_l;
285 
286                 if (pressed & S_PRESSED)
287                     gr = s->radial_gradient(c_x - b_w, c_y + b_h, b_rad * 0.25f, c_x - b_w, c_y + b_h, b_rad * 3.0f);
288                 else if (pressed & S_TOGGLED)
289                     gr = s->radial_gradient(c_x - b_w, c_y + b_h, b_rad * 0.25f, c_x - b_w, c_y + b_h, b_rad * 3.0f);
290                 else
291                     gr = s->radial_gradient(c_x + b_w, c_y - b_h, b_rad * 0.25f, c_x + b_w, c_y - b_h, b_rad * 3.0f);
292 
293                 Color cl(color);
294                 cl.lightness(bright);
295                 gr->add_color(0.0f, cl);
296                 cl.darken(0.9f);
297                 gr->add_color(1.0f, cl);
298 
299                 s->fill_round_rect(c_x - b_w, c_y - b_h, b_w*2, b_h*2, b_rr, gr);
300                 delete gr; // Delete gradient!
301 
302                 if ((--b_r) < 0)
303                     b_r = 0;
304                 if ((--b_w) < 0)
305                     b_w = 0;
306                 if ((--b_h) < 0)
307                     b_h = 0;
308             }
309 
310             if (pressed & S_LED)
311             {
312                 Color cl(color);
313                 cl.lightness(lightness);
314 
315                 gr = s->radial_gradient(c_x - b_w, c_y + b_h, b_rad * 0.25f, c_x, c_y, b_rad * 0.8f);
316                 gr->add_color(0.0, cl);
317                 gr->add_color(1.0, 1.0f, 1.0f, 1.0f);
318                 s->fill_round_rect(c_x - b_w, c_y - b_h, b_w * 2.0f, b_h * 2.0f, b_rr, gr);
319                 delete gr;
320             }
321 
322             // Output text
323             LSPString title;
324             sTitle.format(&title);
325             if (title.length() > 0)
326             {
327                 text_parameters_t tp;
328                 font_parameters_t fp;
329 
330                 Color font_color(sFont.raw_color());
331                 font_color.scale_lightness(brightness());
332 
333                 sFont.get_parameters(s, &fp);
334                 sFont.get_text_parameters(s, &tp, &title);
335 
336                 if (pressed & S_PRESSED)
337                 {
338                     c_y++;
339                     c_x++;
340                 }
341 
342                 sFont.draw(s, c_x - (tp.XAdvance * 0.5f), c_y - (fp.Height * 0.5f) + fp.Ascent, font_color, &title);
343             }
344 
345             s->set_antialiasing(aa);
346         }
347 
size_request(size_request_t * r)348         void LSPButton::size_request(size_request_t *r)
349         {
350             r->nMaxWidth    = -1;
351             r->nMaxHeight   = -1;
352             r->nMinWidth    = nMinWidth;
353             r->nMinHeight   = nMinHeight;
354 
355             LSPString title;
356             sTitle.format(&title);
357 
358             if (title.length() > 0)
359             {
360                 text_parameters_t tp;
361                 font_parameters_t fp;
362 
363                 ISurface *s = pDisplay->create_surface(1, 1);
364 
365                 if (s != NULL)
366                 {
367                     sFont.get_parameters(s, &fp);
368                     sFont.get_text_parameters(s, &tp, &title);
369                     s->destroy();
370                     delete s;
371 
372                     tp.Width       += 10;
373                     fp.Height      += 10;
374 
375                     if (r->nMinWidth < tp.Width)
376                         r->nMinWidth    = tp.Width;
377                     if (r->nMinHeight < fp.Height)
378                         r->nMinHeight   = fp.Height;
379                 }
380             }
381 
382             size_t size     = (nWidth < nHeight) ? nWidth : nHeight;
383             size_t delta    = (nState & S_LED) ? 2 + (size >> 2) : 2;
384 
385             r->nMinWidth   += delta;
386             r->nMinHeight  += delta;
387         }
388 
realize(const realize_t * r)389         void LSPButton::realize(const realize_t *r)
390         {
391             LSPWidget::realize(r);
392 
393             nWidth      = nMinWidth;
394             nHeight     = nMinHeight;
395 
396             LSPString title;
397             sTitle.format(&title);
398             if (title.length() <= 0)
399                 return;
400 
401             text_parameters_t tp;
402             font_parameters_t fp;
403             ISurface *s = pDisplay->create_surface(1, 1);
404             if (s == NULL)
405                 return;
406 
407             sFont.get_parameters(s, &fp);
408             sFont.get_text_parameters(s, &tp, &title);
409             s->destroy();
410             delete s;
411 
412             tp.Width       += 10;
413             fp.Height      += 10;
414 
415             if (nWidth < tp.Width)
416                 nWidth      = tp.Width;
417             if (nHeight < fp.Height)
418                 nHeight     = fp.Height;
419         }
420 
on_mouse_down(const ws_event_t * e)421         status_t LSPButton::on_mouse_down(const ws_event_t *e)
422         {
423             if (!(nState & S_EDITABLE))
424                 return STATUS_OK;
425 
426             take_focus();
427 
428             bool m_over         = check_mouse_over(e->nLeft, e->nTop);
429             size_t mask         = nBMask;
430             nBMask             |= (1 << e->nCode);
431 
432             if (!mask)
433             {
434                 if (!m_over)
435                 {
436                     nState             |= S_OUT; // Mark that out of the button area
437                     return STATUS_OK;
438                 }
439                 else
440                     nChanges        = 0;
441             }
442 
443             if (nState & S_OUT) // Mouse button was initially pressed out of the button area
444                 return STATUS_OK;
445 
446             // Update state according to mouse position and mouse button state
447             size_t state        = nState;
448             if ((nBMask == (1 << MCB_LEFT)) && (m_over))
449                 nState     |= S_PRESSED;
450             else
451                 nState     &= ~S_PRESSED;
452 
453             // Special case for trigger button
454             if ((nState & S_TRIGGER) && (state != nState))
455             {
456                 if ((nState & S_PRESSED) && (!(nState & S_DOWN)))
457                 {
458                     nState      |= S_DOWN;
459                     nChanges    ++;
460                     sSlots.execute(LSPSLOT_CHANGE, this);
461                 }
462                 else if ((!(nState & S_PRESSED)) && (nState & S_DOWN))
463                 {
464                     nState      &= ~S_DOWN;
465                     nChanges    ++;
466                     sSlots.execute(LSPSLOT_CHANGE, this);
467                 }
468             }
469 
470             // Query draw if state changed
471             if (state != nState)
472                 query_draw();
473 
474             return STATUS_OK;
475         }
476 
on_mouse_up(const ws_event_t * e)477         status_t LSPButton::on_mouse_up(const ws_event_t *e)
478         {
479             if (!(nState & S_EDITABLE))
480                 return STATUS_OK;
481 
482             size_t mask     = nBMask;
483             nBMask         &= ~(1 << e->nCode);
484 
485             // Mouse button was initially pressed out of the button area, ignore this case
486             if ((nBMask == 0) && (nState & S_OUT))
487             {
488                 nState &= ~S_OUT;
489                 return STATUS_OK;
490             }
491 
492             size_t state        = nState;
493             bool m_over         = check_mouse_over(e->nLeft, e->nTop);
494 
495             if (nState & S_TRIGGER)
496             {
497                 // Update state according to mouse position and mouse button state
498                 size_t state        = nState;
499                 if ((nBMask == (1 << MCB_LEFT)) && (m_over))
500                     nState     |= S_PRESSED;
501                 else
502                     nState     &= ~S_PRESSED;
503 
504                 if (state != nState)
505                 {
506                     if ((nState & S_PRESSED) && (!(nState & S_DOWN)))
507                     {
508                         nState      |= S_DOWN;
509                         nChanges    ++;
510                         sSlots.execute(LSPSLOT_CHANGE, this);
511                     }
512                     else if ((!(nState & S_PRESSED)) && (nState & S_DOWN))
513                     {
514                         nState      &= ~S_DOWN;
515                         nChanges    ++;
516                         sSlots.execute(LSPSLOT_CHANGE, this);
517                     }
518                 }
519             }
520             else if (nState & S_TOGGLE)
521             {
522                 if ((mask == (1 << MCB_LEFT)) && (e->nCode == MCB_LEFT) && (m_over))
523                     nState ^= S_TOGGLED;
524 
525                 if (state != nState)
526                 {
527                     if ((nState & S_TOGGLED) && (!(nState & S_DOWN)))
528                     {
529                         nState      |= S_DOWN;
530                         nChanges    ++;
531                         sSlots.execute(LSPSLOT_CHANGE, this);
532                     }
533                     else if ((!(nState & S_TOGGLED)) && (nState & S_DOWN))
534                     {
535                         nState      &= ~S_DOWN;
536                         nChanges    ++;
537                         sSlots.execute(LSPSLOT_CHANGE, this);
538                     }
539                 }
540             }
541             else
542             {
543                 // Released left mouse button over the button widget?
544                 if ((mask == (1 << MCB_LEFT)) && (e->nCode == MCB_LEFT))
545                 {
546                     nState &= ~(S_PRESSED | S_TOGGLED | S_DOWN);
547                     if (m_over)
548                     {
549                         nChanges    ++;
550                         sSlots.execute(LSPSLOT_CHANGE, this);
551                     }
552                 }
553             }
554 
555             if ((nBMask == (1 << MCB_LEFT)) && (m_over))
556                 nState     |= S_PRESSED;
557             else
558                 nState     &= ~S_PRESSED;
559 
560             if ((mask == size_t(1 << e->nCode)) && (nChanges > 0))
561             {
562                 sSlots.execute(LSPSLOT_SUBMIT, this);
563                 nChanges = 0;
564             }
565 
566             // Query draw if state changed
567             if (state != nState)
568                 query_draw();
569 
570             return STATUS_OK;
571         }
572 
on_mouse_move(const ws_event_t * e)573         status_t LSPButton::on_mouse_move(const ws_event_t *e)
574         {
575             if (!(nState & S_EDITABLE))
576                 return STATUS_OK;
577 
578             // Mouse button was initially pressed out of the button area, ignore this case
579             if (nState & S_OUT)
580                 return STATUS_OK;
581 
582             // Update state according to mouse position and mouse button state
583             size_t state        = nState;
584             if ((nBMask == (1 << MCB_LEFT)) && (check_mouse_over(e->nLeft, e->nTop)))
585                 nState     |= S_PRESSED;
586             else
587                 nState     &= ~S_PRESSED;
588 
589             // Special case for trigger button
590             if ((nState & S_TRIGGER) && (state != nState))
591             {
592                 if ((nState & S_PRESSED) && (!(nState & S_DOWN)))
593                 {
594                     nState      |= S_DOWN;
595                     nChanges    ++;
596                     sSlots.execute(LSPSLOT_CHANGE, this);
597                 }
598                 else if ((!(nState & S_PRESSED)) && (nState & S_DOWN))
599                 {
600                     nState      &= ~S_DOWN;
601                     nChanges    ++;
602                     sSlots.execute(LSPSLOT_CHANGE, this);
603                 }
604             }
605 
606             // Query draw if state changed
607             if (state != nState)
608                 query_draw();
609 
610             return STATUS_OK;
611         }
612 
613     } /* namespace tk */
614 } /* namespace lsp */
615