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