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