1 /* 2 * Copyright 2006, 2023, Haiku. 3 * Distributed under the terms of the MIT License. 4 * 5 * Authors: 6 * Stephan Aßmus <superstippi@gmx.de> 7 * Zardshard 8 */ 9 10 #include "GradientControl.h" 11 12 #include <stdio.h> 13 14 #include <AppDefs.h> 15 #include <Bitmap.h> 16 #include <ControlLook.h> 17 #include <Message.h> 18 #include <Window.h> 19 20 #include "ui_defines.h" 21 #include "support_ui.h" 22 23 #include "GradientTransformable.h" 24 25 26 GradientControl::GradientControl(BMessage* message, BHandler* target) 27 : BView(BRect(0, 0, 100, 19), "gradient control", B_FOLLOW_NONE, 28 B_WILL_DRAW | B_FRAME_EVENTS | B_NAVIGABLE), 29 fGradient(new ::Gradient()), 30 fGradientBitmap(NULL), 31 fDraggingStepIndex(-1), 32 fCurrentStepIndex(-1), 33 fDropOffset(-1.0), 34 fDropIndex(-1), 35 fEnabled(true), 36 fMessage(message), 37 fTarget(target) 38 { 39 FrameResized(Bounds().Width(), Bounds().Height()); 40 SetViewColor(B_TRANSPARENT_32_BIT); 41 SetLowColor(ui_color(B_PANEL_BACKGROUND_COLOR)); 42 } 43 44 45 GradientControl::~GradientControl() 46 { 47 delete fGradient; 48 delete fGradientBitmap; 49 delete fMessage; 50 } 51 52 53 #if LIB_LAYOUT 54 55 minimax 56 GradientControl::layoutprefs() 57 { 58 mpm.mini.x = 256 + 4; 59 mpm.maxi.x = mpm.mini.x + 10000; 60 mpm.mini.y = 20; 61 mpm.maxi.y = mpm.mini.y + 10; 62 63 mpm.weight = 2.0; 64 65 return mpm; 66 } 67 68 69 BRect 70 GradientControl::layout(BRect frame) 71 { 72 MoveTo(frame.LeftTop()); 73 ResizeTo(frame.Width(), frame.Height()); 74 return Frame(); 75 } 76 77 #endif // LIB_LAYOUT 78 79 80 void 81 GradientControl::WindowActivated(bool active) 82 { 83 if (IsFocus()) 84 Invalidate(); 85 } 86 87 88 void 89 GradientControl::MakeFocus(bool focus) 90 { 91 if (focus != IsFocus()) { 92 _UpdateCurrentColor(); 93 Invalidate(); 94 if (fTarget) { 95 if (BLooper* looper = fTarget->Looper()) 96 looper->PostMessage(MSG_GRADIENT_CONTROL_FOCUS_CHANGED, fTarget); 97 } 98 } 99 BView::MakeFocus(focus); 100 } 101 102 103 void 104 GradientControl::MouseDown(BPoint where) 105 { 106 if (!fEnabled) 107 return; 108 109 if (!IsFocus()) { 110 MakeFocus(true); 111 } 112 113 fDraggingStepIndex = _StepIndexFor(where); 114 115 if (fDraggingStepIndex >= 0) 116 SetMouseEventMask(B_POINTER_EVENTS, B_LOCK_WINDOW_FOCUS); 117 118 // handle double click 119 int32 clicks; 120 if (Window()->CurrentMessage()->FindInt32("clicks", &clicks) >= B_OK && clicks >= 2) { 121 if (fDraggingStepIndex < 0) { 122 // create a new offset at the click location that uses 123 // the interpolated color 124 float offset = _OffsetFor(where); 125 // create a clean gradient 126 uint32 width = fGradientBitmap->Bounds().IntegerWidth(); 127 uint8* temp = new uint8[width * 4]; 128 fGradient->MakeGradient((uint32*)temp, width); 129 // get the color at the offset 130 rgb_color color; 131 uint8* bits = temp; 132 bits += 4 * (uint32)((width - 1) * offset); 133 color.red = bits[0]; 134 color.green = bits[1]; 135 color.blue = bits[2]; 136 color.alpha = bits[3]; 137 fCurrentStepIndex = fGradient->AddColor(color, offset); 138 fDraggingStepIndex = -1; 139 _UpdateColors(); 140 Invalidate(); 141 _UpdateCurrentColor(); 142 delete[] temp; 143 } 144 } 145 146 if (fCurrentStepIndex != fDraggingStepIndex && fDraggingStepIndex >= 0) { 147 // start dragging this stop 148 fCurrentStepIndex = fDraggingStepIndex; 149 Invalidate(); 150 _UpdateCurrentColor(); 151 } 152 } 153 154 155 void 156 GradientControl::MouseUp(BPoint where) 157 { 158 fDraggingStepIndex = -1; 159 } 160 161 162 void 163 GradientControl::MouseMoved(BPoint where, uint32 transit, const BMessage* dragMessage) 164 { 165 if (!fEnabled) 166 return; 167 168 float offset = _OffsetFor(where); 169 170 if (fDraggingStepIndex >= 0) { 171 BGradient::ColorStop* step = fGradient->ColorAt(fDraggingStepIndex); 172 if (step) { 173 if (fGradient->SetOffset(fDraggingStepIndex, offset)) { 174 _UpdateColors(); 175 Invalidate(); 176 } 177 } 178 } 179 int32 dropIndex = -1; 180 float dropOffset = -1.0; 181 if (dragMessage && (transit == B_INSIDE_VIEW || transit == B_ENTERED_VIEW)) { 182 rgb_color dragColor; 183 if (restore_color_from_message(dragMessage, dragColor, 0) >= B_OK) { 184 dropIndex = _StepIndexFor(where); 185 // fall back to inserting a color step if no direct hit on an existing step 186 if (dropIndex < 0) 187 dropOffset = offset; 188 } 189 } 190 if (fDropOffset != dropOffset || fDropIndex != dropIndex) { 191 fDropOffset = dropOffset; 192 fDropIndex = dropIndex; 193 Invalidate(); 194 } 195 } 196 197 198 void 199 GradientControl::MessageReceived(BMessage* message) 200 { 201 switch (message->what) { 202 case B_PASTE: 203 if (fEnabled) { 204 rgb_color color; 205 if (restore_color_from_message(message, color, 0) >= B_OK) { 206 bool update = false; 207 if (fDropIndex >= 0) { 208 if (BGradient::ColorStop* step 209 = fGradient->ColorAt(fDropIndex)) { 210 color.alpha = step->color.alpha; 211 } 212 fGradient->SetColor(fDropIndex, color); 213 fCurrentStepIndex = fDropIndex; 214 fDropIndex = -1; 215 update = true; 216 } else if (fDropOffset >= 0.0) { 217 fCurrentStepIndex = fGradient->AddColor(color, 218 fDropOffset); 219 fDropOffset = -1.0; 220 update = true; 221 } 222 if (update) { 223 _UpdateColors(); 224 if (!IsFocus()) 225 MakeFocus(true); 226 else 227 Invalidate(); 228 _UpdateCurrentColor(); 229 } 230 } 231 } 232 break; 233 default: 234 BView::MessageReceived(message); 235 } 236 } 237 238 239 void 240 GradientControl::KeyDown(const char* bytes, int32 numBytes) 241 { 242 bool handled = false; 243 bool update = false; 244 if (fEnabled) { 245 if (numBytes > 0) { 246 handled = true; 247 int32 count = fGradient->CountColors(); 248 switch (bytes[0]) { 249 case B_DELETE: 250 // remove step 251 update = fGradient->RemoveColor(fCurrentStepIndex); 252 if (update) { 253 fCurrentStepIndex = max_c(0, fCurrentStepIndex - 1); 254 _UpdateCurrentColor(); 255 } 256 break; 257 258 case B_HOME: 259 case B_END: 260 case B_LEFT_ARROW: 261 case B_RIGHT_ARROW: { 262 if (BGradient::ColorStop* step 263 = fGradient->ColorAt(fCurrentStepIndex)) { 264 BRect r = _GradientBitmapRect(); 265 float x = r.left + r.Width() * step->offset; 266 switch (bytes[0]) { 267 case B_LEFT_ARROW: 268 // move step to the left 269 x = max_c(r.left, x - 1.0); 270 break; 271 case B_RIGHT_ARROW: 272 // move step to the right 273 x = min_c(r.right, x + 1.0); 274 break; 275 case B_HOME: 276 // move step to the start 277 x = r.left; 278 break; 279 case B_END: 280 // move step to the start 281 x = r.right; 282 break; 283 } 284 update = fGradient->SetOffset(fCurrentStepIndex, 285 (x - r.left) / r.Width()); 286 } 287 break; 288 } 289 290 case B_UP_ARROW: 291 // previous step 292 fCurrentStepIndex--; 293 if (fCurrentStepIndex < 0) { 294 fCurrentStepIndex = count - 1; 295 } 296 _UpdateCurrentColor(); 297 break; 298 case B_DOWN_ARROW: 299 // next step 300 fCurrentStepIndex++; 301 if (fCurrentStepIndex >= count) { 302 fCurrentStepIndex = 0; 303 } 304 _UpdateCurrentColor(); 305 break; 306 307 default: 308 handled = false; 309 break; 310 } 311 } 312 } 313 if (!handled) 314 BView::KeyDown(bytes, numBytes); 315 else { 316 if (update) 317 _UpdateColors(); 318 Invalidate(); 319 } 320 } 321 322 323 void 324 GradientControl::Draw(BRect updateRect) 325 { 326 BRect b = _GradientBitmapRect(); 327 b.InsetBy(-2.0, -2.0); 328 // background 329 // left of gradient rect 330 BRect lb(updateRect.left, updateRect.top, b.left - 1, b.bottom); 331 if (lb.IsValid()) 332 FillRect(lb, B_SOLID_LOW); 333 // right of gradient rect 334 BRect rb(b.right + 1, updateRect.top, updateRect.right, b.bottom); 335 if (rb.IsValid()) 336 FillRect(rb, B_SOLID_LOW); 337 // bottom of gradient rect 338 BRect bb(updateRect.left, b.bottom + 1, updateRect.right, updateRect.bottom); 339 if (bb.IsValid()) 340 FillRect(bb, B_SOLID_LOW); 341 342 bool isFocus = IsFocus() && Window()->IsActive(); 343 344 rgb_color bg = LowColor(); 345 rgb_color black; 346 rgb_color shadow; 347 rgb_color darkShadow; 348 rgb_color light; 349 350 if (fEnabled) { 351 shadow = tint_color(bg, B_DARKEN_1_TINT); 352 darkShadow = tint_color(bg, B_DARKEN_3_TINT); 353 light = tint_color(bg, B_LIGHTEN_MAX_TINT); 354 black = tint_color(bg, B_DARKEN_MAX_TINT); 355 } else { 356 shadow = bg; 357 darkShadow = tint_color(bg, B_DARKEN_1_TINT); 358 light = tint_color(bg, B_LIGHTEN_2_TINT); 359 black = tint_color(bg, B_DARKEN_2_TINT); 360 } 361 362 rgb_color focus = isFocus ? ui_color(B_KEYBOARD_NAVIGATION_COLOR) : black; 363 364 uint32 flags = 0; 365 366 if (be_control_look != NULL) { 367 if (!fEnabled) 368 flags |= BControlLook::B_DISABLED; 369 if (isFocus) 370 flags |= BControlLook::B_FOCUSED; 371 be_control_look->DrawTextControlBorder(this, b, updateRect, bg, flags); 372 } else { 373 stroke_frame(this, b, shadow, shadow, light, light); 374 b.InsetBy(1.0, 1.0); 375 if (isFocus) 376 stroke_frame(this, b, focus, focus, focus, focus); 377 else 378 stroke_frame(this, b, darkShadow, darkShadow, bg, bg); 379 b.InsetBy(1.0, 1.0); 380 } 381 382 // DrawBitmapAsync(fGradientBitmap, b.LeftTop()); 383 // Sync(); 384 DrawBitmap(fGradientBitmap, b.LeftTop()); 385 386 // show drop offset 387 if (fDropOffset >= 0.0) { 388 SetHighColor(255, 0, 0, 255); 389 float x = b.left + b.Width() * fDropOffset; 390 StrokeLine(BPoint(x, b.top), BPoint(x, b.bottom)); 391 } 392 393 BPoint markerPos; 394 markerPos.y = b.bottom + 4.0; 395 BPoint leftBottom(-6.0, 6.0); 396 BPoint rightBottom(6.0, 6.0); 397 for (int32 i = 0; BGradient::ColorStop* step = fGradient->ColorAt(i); 398 i++) { 399 markerPos.x = b.left + (b.Width() * step->offset); 400 401 if (i == fCurrentStepIndex) { 402 SetLowColor(focus); 403 if (isFocus) { 404 StrokeLine(markerPos + leftBottom + BPoint(1.0, 4.0), 405 markerPos + rightBottom + BPoint(-1.0, 4.0), B_SOLID_LOW); 406 } 407 } else { 408 SetLowColor(black); 409 } 410 411 // override in case this is the drop index step 412 if (i == fDropIndex) 413 SetLowColor(255, 0, 0, 255); 414 415 if (be_control_look != NULL) { 416 // TODO: Drop indication! 417 BRect rect(markerPos.x + leftBottom.x, markerPos.y, 418 markerPos.x + rightBottom.x, markerPos.y + rightBottom.y); 419 be_control_look->DrawSliderTriangle(this, rect, updateRect, bg, 420 step->color, flags, B_HORIZONTAL); 421 } else { 422 StrokeTriangle(markerPos, markerPos + leftBottom, 423 markerPos + rightBottom, B_SOLID_LOW); 424 if (fEnabled) { 425 SetHighColor(step->color); 426 } else { 427 rgb_color c = step->color; 428 c.red = (uint8)(((uint32)bg.red + (uint32)c.red) / 2); 429 c.green = (uint8)(((uint32)bg.green + (uint32)c.green) / 2); 430 c.blue = (uint8)(((uint32)bg.blue + (uint32)c.blue) / 2); 431 SetHighColor(c); 432 } 433 FillTriangle(markerPos + BPoint(0.0, 1.0), 434 markerPos + leftBottom + BPoint(1.0, 0.0), 435 markerPos + rightBottom + BPoint(-1.0, 0.0)); 436 StrokeLine(markerPos + leftBottom + BPoint(0.0, 1.0), 437 markerPos + rightBottom + BPoint(0.0, 1.0), B_SOLID_LOW); 438 } 439 } 440 } 441 442 443 void 444 GradientControl::FrameResized(float width, float height) 445 { 446 BRect r = _GradientBitmapRect(); 447 _AllocBitmap(r.IntegerWidth() + 1, r.IntegerHeight() + 1); 448 _UpdateColors(); 449 Invalidate(); 450 451 } 452 453 454 void 455 GradientControl::GetPreferredSize(float* width, float* height) 456 { 457 if (width != NULL) 458 *width = 100; 459 460 if (height != NULL) 461 *height = 19; 462 } 463 464 465 void 466 GradientControl::SetGradient(const ::Gradient* gradient) 467 { 468 if (!gradient) 469 return; 470 471 *fGradient = *gradient; 472 _UpdateColors(); 473 474 fDropOffset = -1.0; 475 fDropIndex = -1; 476 fDraggingStepIndex = -1; 477 if (fCurrentStepIndex > gradient->CountColors() - 1) 478 fCurrentStepIndex = gradient->CountColors() - 1; 479 480 Invalidate(); 481 } 482 483 484 void 485 GradientControl::SetCurrentStop(const rgb_color& color) 486 { 487 if (fEnabled && fCurrentStepIndex >= 0) { 488 fGradient->SetColor(fCurrentStepIndex, color); 489 _UpdateColors(); 490 Invalidate(); 491 } 492 } 493 494 495 bool 496 GradientControl::GetCurrentStop(rgb_color* color) const 497 { 498 BGradient::ColorStop* stop 499 = fGradient->ColorAt(fCurrentStepIndex); 500 if (stop && color) { 501 *color = stop->color; 502 return true; 503 } 504 return false; 505 } 506 507 508 void 509 GradientControl::SetEnabled(bool enabled) 510 { 511 if (enabled == fEnabled) 512 return; 513 514 fEnabled = enabled; 515 516 if (!fEnabled) 517 fDropIndex = -1; 518 519 _UpdateColors(); 520 Invalidate(); 521 } 522 523 524 inline void 525 blend_colors(uint8* d, uint8 alpha, uint8 c1, uint8 c2, uint8 c3) 526 { 527 if (alpha > 0) { 528 if (alpha == 255) { 529 d[0] = c1; 530 d[1] = c2; 531 d[2] = c3; 532 } else { 533 d[0] += (uint8)(((c1 - d[0]) * alpha) >> 8); 534 d[1] += (uint8)(((c2 - d[1]) * alpha) >> 8); 535 d[2] += (uint8)(((c3 - d[2]) * alpha) >> 8); 536 } 537 } 538 } 539 540 541 void 542 GradientControl::_UpdateColors() 543 { 544 if (!fGradientBitmap || !fGradientBitmap->IsValid()) 545 return; 546 547 // fill in top row by gradient 548 uint8* topRow = (uint8*)fGradientBitmap->Bits(); 549 uint32 width = fGradientBitmap->Bounds().IntegerWidth() + 1; 550 fGradient->MakeGradient((uint32*)topRow, width); 551 // flip colors, since they are the wrong endianess 552 // make colors the disabled look 553 // TODO: apply gamma lut 554 uint8* p = topRow; 555 if (!fEnabled) { 556 rgb_color bg = LowColor(); 557 for (uint32 x = 0; x < width; x++) { 558 uint8 p0 = p[0]; 559 p[0] = (uint8)(((uint32)bg.blue + (uint32)p[2]) / 2); 560 p[1] = (uint8)(((uint32)bg.green + (uint32)p[1]) / 2); 561 p[2] = (uint8)(((uint32)bg.red + (uint32)p0) / 2); 562 p += 4; 563 } 564 } else { 565 for (uint32 x = 0; x < width; x++) { 566 uint8 p0 = p[0]; 567 p[0] = p[2]; 568 p[2] = p0; 569 p += 4; 570 } 571 } 572 // copy top row to rest of bitmap 573 uint32 height = fGradientBitmap->Bounds().IntegerHeight() + 1; 574 uint32 bpr = fGradientBitmap->BytesPerRow(); 575 uint8* dstRow = topRow + bpr; 576 for (uint32 i = 1; i < height; i++) { 577 memcpy(dstRow, topRow, bpr); 578 dstRow += bpr; 579 } 580 // post process bitmap to underlay it with a pattern 581 // in order to make gradient steps with alpha more visible! 582 uint8* row = topRow; 583 for (uint32 i = 0; i < height; i++) { 584 uint8* p = row; 585 for (uint32 x = 0; x < width; x++) { 586 uint8 alpha = p[3]; 587 if (alpha < 255) { 588 p[3] = 255; 589 alpha = 255 - alpha; 590 if (x % 8 >= 4) { 591 if (i % 8 >= 4) { 592 blend_colors(p, alpha, 593 kAlphaLow.blue, 594 kAlphaLow.green, 595 kAlphaLow.red); 596 } else { 597 blend_colors(p, alpha, 598 kAlphaHigh.blue, 599 kAlphaHigh.green, 600 kAlphaHigh.red); 601 } 602 } else { 603 if (i % 8 >= 4) { 604 blend_colors(p, alpha, 605 kAlphaHigh.blue, 606 kAlphaHigh.green, 607 kAlphaHigh.red); 608 } else { 609 blend_colors(p, alpha, 610 kAlphaLow.blue, 611 kAlphaLow.green, 612 kAlphaLow.red); 613 } 614 } 615 } 616 p += 4; 617 } 618 row += bpr; 619 } 620 } 621 622 623 void 624 GradientControl::_AllocBitmap(int32 width, int32 height) 625 { 626 if (width < 2 || height < 2) 627 return; 628 629 delete fGradientBitmap; 630 fGradientBitmap = new BBitmap(BRect(0, 0, width - 1, height - 1), 0, B_RGB32); 631 } 632 633 634 BRect 635 GradientControl::_GradientBitmapRect() const 636 { 637 BRect r = Bounds(); 638 r.left += 6.0; 639 r.top += 2.0; 640 r.right -= 6.0; 641 r.bottom -= 14.0; 642 return r; 643 } 644 645 646 int32 647 GradientControl::_StepIndexFor(BPoint where) const 648 { 649 int32 index = -1; 650 BRect r = _GradientBitmapRect(); 651 BRect markerFrame(Bounds()); 652 for (int32 i = 0; BGradient::ColorStop* step 653 = fGradient->ColorAt(i); i++) { 654 markerFrame.left = markerFrame.right = r.left 655 + (r.Width() * step->offset); 656 markerFrame.InsetBy(-6.0, 0.0); 657 if (markerFrame.Contains(where)) { 658 index = i; 659 break; 660 } 661 } 662 return index; 663 } 664 665 666 float 667 GradientControl::_OffsetFor(BPoint where) const 668 { 669 BRect r = _GradientBitmapRect(); 670 float offset = (where.x - r.left) / r.Width(); 671 offset = max_c(0.0, offset); 672 offset = min_c(1.0, offset); 673 return offset; 674 } 675 676 677 void 678 GradientControl::_UpdateCurrentColor() const 679 { 680 if (!fMessage || !fTarget || !fTarget->Looper()) 681 return; 682 // set the CanvasView current color 683 if (BGradient::ColorStop* step = fGradient->ColorAt(fCurrentStepIndex)) { 684 BMessage message(*fMessage); 685 store_color_in_message(&message, step->color); 686 fTarget->Looper()->PostMessage(&message, fTarget); 687 } 688 } 689