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 "Gradient.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 color_step* 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 (color_step* step = fGradient->ColorAt(fDropIndex)) { 205 color.alpha = step->color.alpha; 206 } 207 fGradient->SetColor(fDropIndex, color); 208 fCurrentStepIndex = fDropIndex; 209 fDropIndex = -1; 210 update = true; 211 } else if (fDropOffset >= 0.0) { 212 fCurrentStepIndex = fGradient->AddColor(color, fDropOffset); 213 fDropOffset = -1.0; 214 update = true; 215 } 216 if (update) { 217 _UpdateColors(); 218 if (!IsFocus()) 219 MakeFocus(true); 220 else 221 Invalidate(); 222 _UpdateCurrentColor(); 223 } 224 } 225 } 226 break; 227 default: 228 BView::MessageReceived(message); 229 } 230 } 231 232 // KeyDown 233 void 234 GradientControl::KeyDown(const char* bytes, int32 numBytes) 235 { 236 bool handled = false; 237 bool update = false; 238 if (fEnabled) { 239 if (numBytes > 0) { 240 handled = true; 241 int32 count = fGradient->CountColors(); 242 switch (bytes[0]) { 243 case B_DELETE: 244 // remove step 245 update = fGradient->RemoveColor(fCurrentStepIndex); 246 if (update) { 247 fCurrentStepIndex = max_c(0, fCurrentStepIndex - 1); 248 _UpdateCurrentColor(); 249 } 250 break; 251 252 case B_HOME: 253 case B_END: 254 case B_LEFT_ARROW: 255 case B_RIGHT_ARROW: { 256 if (color_step* step = fGradient->ColorAt(fCurrentStepIndex)) { 257 BRect r = _GradientBitmapRect(); 258 float x = r.left + r.Width() * step->offset; 259 switch (bytes[0]) { 260 case B_LEFT_ARROW: 261 // move step to the left 262 x = max_c(r.left, x - 1.0); 263 break; 264 case B_RIGHT_ARROW: 265 // move step to the right 266 x = min_c(r.right, x + 1.0); 267 break; 268 case B_HOME: 269 // move step to the start 270 x = r.left; 271 break; 272 case B_END: 273 // move step to the start 274 x = r.right; 275 break; 276 } 277 update = fGradient->SetOffset(fCurrentStepIndex, (x - r.left) / r.Width()); 278 } 279 break; 280 } 281 282 case B_UP_ARROW: 283 // previous step 284 fCurrentStepIndex--; 285 if (fCurrentStepIndex < 0) { 286 fCurrentStepIndex = count - 1; 287 } 288 _UpdateCurrentColor(); 289 break; 290 case B_DOWN_ARROW: 291 // next step 292 fCurrentStepIndex++; 293 if (fCurrentStepIndex >= count) { 294 fCurrentStepIndex = 0; 295 } 296 _UpdateCurrentColor(); 297 break; 298 299 default: 300 handled = false; 301 break; 302 } 303 } 304 } 305 if (!handled) 306 BView::KeyDown(bytes, numBytes); 307 else { 308 if (update) 309 _UpdateColors(); 310 Invalidate(); 311 } 312 } 313 314 // Draw 315 void 316 GradientControl::Draw(BRect updateRect) 317 { 318 BRect b = _GradientBitmapRect(); 319 b.InsetBy(-2.0, -2.0); 320 // background 321 // left of gradient rect 322 BRect lb(updateRect.left, updateRect.top, b.left - 1, b.bottom); 323 if (lb.IsValid()) 324 FillRect(lb, B_SOLID_LOW); 325 // right of gradient rect 326 BRect rb(b.right + 1, updateRect.top, updateRect.right, b.bottom); 327 if (rb.IsValid()) 328 FillRect(rb, B_SOLID_LOW); 329 // bottom of gradient rect 330 BRect bb(updateRect.left, b.bottom + 1, updateRect.right, updateRect.bottom); 331 if (bb.IsValid()) 332 FillRect(bb, B_SOLID_LOW); 333 334 bool isFocus = IsFocus() && Window()->IsActive(); 335 336 rgb_color bg = LowColor(); 337 rgb_color shadow; 338 rgb_color darkShadow; 339 rgb_color light; 340 rgb_color black; 341 342 if (fEnabled) { 343 shadow = tint_color(bg, B_DARKEN_1_TINT); 344 darkShadow = tint_color(bg, B_DARKEN_3_TINT); 345 light = tint_color(bg, B_LIGHTEN_MAX_TINT); 346 black = tint_color(bg, B_DARKEN_MAX_TINT); 347 } else { 348 shadow = bg; 349 darkShadow = tint_color(bg, B_DARKEN_1_TINT); 350 light = tint_color(bg, B_LIGHTEN_2_TINT); 351 black = tint_color(bg, B_DARKEN_2_TINT); 352 } 353 354 rgb_color focus = isFocus ? ui_color(B_KEYBOARD_NAVIGATION_COLOR) 355 : black; 356 357 stroke_frame(this, b, shadow, shadow, light, light); 358 b.InsetBy(1.0, 1.0); 359 if (isFocus) 360 stroke_frame(this, b, focus, focus, focus, focus); 361 else 362 stroke_frame(this, b, darkShadow, darkShadow, bg, bg); 363 b.InsetBy(1.0, 1.0); 364 365 // DrawBitmapAsync(fGradientBitmap, b.LeftTop()); 366 // Sync(); 367 DrawBitmap(fGradientBitmap, b.LeftTop()); 368 369 // show drop offset 370 if (fDropOffset >= 0.0) { 371 SetHighColor(255, 0, 0, 255); 372 float x = b.left + b.Width() * fDropOffset; 373 StrokeLine(BPoint(x, b.top), BPoint(x, b.bottom)); 374 } 375 376 BPoint markerPos; 377 markerPos.y = b.bottom + 4.0; 378 BPoint leftBottom(-6.0, 6.0); 379 BPoint rightBottom(6.0, 6.0); 380 for (int32 i = 0; color_step* step = fGradient->ColorAt(i); i++) { 381 markerPos.x = b.left + (b.Width() * step->offset); 382 383 if (i == fCurrentStepIndex) { 384 SetLowColor(focus); 385 if (isFocus) { 386 StrokeLine(markerPos + leftBottom + BPoint(1.0, 4.0), 387 markerPos + rightBottom + BPoint(-1.0, 4.0), B_SOLID_LOW); 388 } 389 } else { 390 SetLowColor(black); 391 } 392 393 // override in case this is the drop index step 394 if (i == fDropIndex) 395 SetLowColor(255, 0, 0, 255); 396 397 StrokeTriangle(markerPos, 398 markerPos + leftBottom, 399 markerPos + rightBottom, B_SOLID_LOW); 400 if (fEnabled) { 401 SetHighColor(step->color); 402 } else { 403 rgb_color c = step->color; 404 c.red = (uint8)(((uint32)bg.red + (uint32)c.red) / 2); 405 c.green = (uint8)(((uint32)bg.green + (uint32)c.green) / 2); 406 c.blue = (uint8)(((uint32)bg.blue + (uint32)c.blue) / 2); 407 SetHighColor(c); 408 } 409 FillTriangle(markerPos + BPoint(0.0, 1.0), 410 markerPos + leftBottom + BPoint(1.0, 0.0), 411 markerPos + rightBottom + BPoint(-1.0, 0.0)); 412 StrokeLine(markerPos + leftBottom + BPoint(0.0, 1.0), 413 markerPos + rightBottom + BPoint(0.0, 1.0), B_SOLID_LOW); 414 } 415 } 416 417 // FrameResized 418 void 419 GradientControl::FrameResized(float width, float height) 420 { 421 BRect r = _GradientBitmapRect(); 422 _AllocBitmap(r.IntegerWidth() + 1, r.IntegerHeight() + 1); 423 _UpdateColors(); 424 Invalidate(); 425 426 } 427 428 // SetGradient 429 void 430 GradientControl::SetGradient(const ::Gradient* gradient) 431 { 432 if (!gradient) 433 return; 434 435 *fGradient = *gradient; 436 _UpdateColors(); 437 438 fDropOffset = -1.0; 439 fDropIndex = -1; 440 fDraggingStepIndex = -1; 441 if (fCurrentStepIndex > gradient->CountColors() - 1) 442 fCurrentStepIndex = gradient->CountColors() - 1; 443 444 Invalidate(); 445 } 446 447 // SetCurrentStop 448 void 449 GradientControl::SetCurrentStop(const rgb_color& color) 450 { 451 if (fEnabled && fCurrentStepIndex >= 0) { 452 fGradient->SetColor(fCurrentStepIndex, color); 453 _UpdateColors(); 454 Invalidate(); 455 } 456 } 457 458 // GetCurrentStop 459 bool 460 GradientControl::GetCurrentStop(rgb_color* color) const 461 { 462 color_step* stop = fGradient->ColorAt(fCurrentStepIndex); 463 if (stop && color) { 464 *color = stop->color; 465 return true; 466 } 467 return false; 468 } 469 470 // SetEnabled 471 void 472 GradientControl::SetEnabled(bool enabled) 473 { 474 if (enabled == fEnabled) 475 return; 476 477 fEnabled = enabled; 478 479 if (!fEnabled) 480 fDropIndex = -1; 481 482 _UpdateColors(); 483 Invalidate(); 484 } 485 486 // blend_colors 487 inline void 488 blend_colors(uint8* d, uint8 alpha, uint8 c1, uint8 c2, uint8 c3) 489 { 490 if (alpha > 0) { 491 if (alpha == 255) { 492 d[0] = c1; 493 d[1] = c2; 494 d[2] = c3; 495 } else { 496 d[0] += (uint8)(((c1 - d[0]) * alpha) >> 8); 497 d[1] += (uint8)(((c2 - d[1]) * alpha) >> 8); 498 d[2] += (uint8)(((c3 - d[2]) * alpha) >> 8); 499 } 500 } 501 } 502 503 // _UpdateColors 504 void 505 GradientControl::_UpdateColors() 506 { 507 if (!fGradientBitmap || !fGradientBitmap->IsValid()) 508 return; 509 510 // fill in top row by gradient 511 uint8* topRow = (uint8*)fGradientBitmap->Bits(); 512 uint32 width = fGradientBitmap->Bounds().IntegerWidth() + 1; 513 fGradient->MakeGradient((uint32*)topRow, width); 514 // flip colors, since they are the wrong endianess 515 // make colors the disabled look 516 // TODO: apply gamma lut 517 uint8* p = topRow; 518 if (!fEnabled) { 519 rgb_color bg = LowColor(); 520 for (uint32 x = 0; x < width; x++) { 521 uint8 p0 = p[0]; 522 p[0] = (uint8)(((uint32)bg.blue + (uint32)p[2]) / 2); 523 p[1] = (uint8)(((uint32)bg.green + (uint32)p[1]) / 2); 524 p[2] = (uint8)(((uint32)bg.red + (uint32)p0) / 2); 525 p += 4; 526 } 527 } else { 528 for (uint32 x = 0; x < width; x++) { 529 uint8 p0 = p[0]; 530 p[0] = p[2]; 531 p[2] = p0; 532 p += 4; 533 } 534 } 535 // copy top row to rest of bitmap 536 uint32 height = fGradientBitmap->Bounds().IntegerHeight() + 1; 537 uint32 bpr = fGradientBitmap->BytesPerRow(); 538 uint8* dstRow = topRow + bpr; 539 for (uint32 i = 1; i < height; i++) { 540 memcpy(dstRow, topRow, bpr); 541 dstRow += bpr; 542 } 543 // post process bitmap to underlay it with a pattern 544 // in order to make gradient steps with alpha more visible! 545 uint8* row = topRow; 546 for (uint32 i = 0; i < height; i++) { 547 uint8* p = row; 548 for (uint32 x = 0; x < width; x++) { 549 uint8 alpha = p[3]; 550 if (alpha < 255) { 551 p[3] = 255; 552 alpha = 255 - alpha; 553 if (x % 8 >= 4) { 554 if (i % 8 >= 4) { 555 blend_colors(p, alpha, 556 kAlphaLow.blue, 557 kAlphaLow.green, 558 kAlphaLow.red); 559 } else { 560 blend_colors(p, alpha, 561 kAlphaHigh.blue, 562 kAlphaHigh.green, 563 kAlphaHigh.red); 564 } 565 } else { 566 if (i % 8 >= 4) { 567 blend_colors(p, alpha, 568 kAlphaHigh.blue, 569 kAlphaHigh.green, 570 kAlphaHigh.red); 571 } else { 572 blend_colors(p, alpha, 573 kAlphaLow.blue, 574 kAlphaLow.green, 575 kAlphaLow.red); 576 } 577 } 578 } 579 p += 4; 580 } 581 row += bpr; 582 } 583 } 584 585 // _AllocBitmap 586 void 587 GradientControl::_AllocBitmap(int32 width, int32 height) 588 { 589 if (width < 2 || height < 2) 590 return; 591 592 delete fGradientBitmap; 593 fGradientBitmap = new BBitmap(BRect(0, 0, width - 1, height - 1), 0, B_RGB32); 594 } 595 596 // _GradientBitmapRect 597 BRect 598 GradientControl::_GradientBitmapRect() const 599 { 600 BRect r = Bounds(); 601 r.left += 6.0; 602 r.top += 2.0; 603 r.right -= 6.0; 604 r.bottom -= 14.0; 605 return r; 606 } 607 608 // _StepIndexFor 609 int32 610 GradientControl::_StepIndexFor(BPoint where) const 611 { 612 int32 index = -1; 613 BRect r = _GradientBitmapRect(); 614 BRect markerFrame(Bounds()); 615 for (int32 i = 0; color_step* step = fGradient->ColorAt(i); i++) { 616 markerFrame.left = markerFrame.right = r.left + (r.Width() * step->offset); 617 markerFrame.InsetBy(-6.0, 0.0); 618 if (markerFrame.Contains(where)) { 619 index = i; 620 break; 621 } 622 } 623 return index; 624 } 625 626 // _OffsetFor 627 float 628 GradientControl::_OffsetFor(BPoint where) const 629 { 630 BRect r = _GradientBitmapRect(); 631 float offset = (where.x - r.left) / r.Width(); 632 offset = max_c(0.0, offset); 633 offset = min_c(1.0, offset); 634 return offset; 635 } 636 637 // _UpdateCurrentColor 638 void 639 GradientControl::_UpdateCurrentColor() const 640 { 641 if (!fMessage || !fTarget || !fTarget->Looper()) 642 return; 643 // set the CanvasView current color 644 if (color_step* step = fGradient->ColorAt(fCurrentStepIndex)) { 645 BMessage message(*fMessage); 646 store_color_in_message(&message, step->color); 647 fTarget->Looper()->PostMessage(&message, fTarget); 648 } 649 } 650