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