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