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 "CanvasView.h" 10 11 #include <Bitmap.h> 12 #include <Region.h> 13 14 #include <stdio.h> 15 16 #include "ui_defines.h" 17 18 #include "CommandStack.h" 19 #include "IconRenderer.h" 20 21 using std::nothrow; 22 23 // constructor 24 CanvasView::CanvasView(BRect frame) 25 : StateView(frame, "canvas view", B_FOLLOW_ALL, 26 B_WILL_DRAW | B_FRAME_EVENTS), 27 fBitmap(new BBitmap(BRect(0, 0, 63, 63), 0, B_RGB32)), 28 fBackground(new BBitmap(BRect(0, 0, 63, 63), 0, B_RGB32)), 29 fIcon(NULL), 30 fRenderer(new IconRenderer(fBitmap)), 31 fDirtyIconArea(fBitmap->Bounds()), 32 33 fCanvasOrigin(0.0, 0.0), 34 fZoomLevel(1.0), 35 36 fMouseFilterMode(SNAPPING_OFF), 37 38 fOffsreenBitmap(NULL), 39 fOffsreenView(NULL) 40 { 41 #if __HAIKU__ 42 SetFlags(Flags() | B_SUBPIXEL_PRECISE); 43 #endif // __HAIKU__ 44 45 _MakeBackground(); 46 fRenderer->SetBackground(fBackground); 47 } 48 49 // destructor 50 CanvasView::~CanvasView() 51 { 52 SetIcon(NULL); 53 delete fRenderer; 54 delete fBitmap; 55 delete fBackground; 56 57 _FreeBackBitmap(); 58 } 59 60 // #pragma mark - 61 62 // AttachedToWindow 63 void 64 CanvasView::AttachedToWindow() 65 { 66 StateView::AttachedToWindow(); 67 68 SetViewColor(B_TRANSPARENT_COLOR); 69 SetLowColor(kStripesHigh); 70 SetHighColor(kStripesLow); 71 72 _AllocBackBitmap(Bounds().Width(), Bounds().Height()); 73 74 _SetZoom(8.0); 75 } 76 77 // FrameResized 78 void 79 CanvasView::FrameResized(float width, float height) 80 { 81 _AllocBackBitmap(width, height); 82 } 83 84 // Draw 85 void 86 CanvasView::Draw(BRect updateRect) 87 { 88 if (!fOffsreenView) { 89 _DrawInto(this, updateRect); 90 } else { 91 BPoint boundsLeftTop = Bounds().LeftTop(); 92 if (fOffsreenBitmap->Lock()) { 93 fOffsreenView->PushState(); 94 95 // apply scrolling offset to offscreen view 96 fOffsreenView->SetOrigin(-boundsLeftTop.x, -boundsLeftTop.y); 97 // mirror the clipping of this view 98 // to the offscreen view for performance 99 BRegion clipping; 100 GetClippingRegion(&clipping); 101 fOffsreenView->ConstrainClippingRegion(&clipping); 102 fOffsreenView->FillRect(updateRect, B_SOLID_LOW); 103 104 _DrawInto(fOffsreenView, updateRect); 105 106 fOffsreenView->PopState(); 107 fOffsreenView->Sync(); 108 109 fOffsreenBitmap->Unlock(); 110 } 111 // compensate scrolling offset in BView 112 BRect bitmapRect = updateRect; 113 bitmapRect.OffsetBy(-boundsLeftTop.x, -boundsLeftTop.y); 114 115 SetDrawingMode(B_OP_COPY); 116 DrawBitmap(fOffsreenBitmap, bitmapRect, updateRect); 117 } 118 } 119 120 // #pragma mark - 121 122 // MouseDown 123 void 124 CanvasView::MouseDown(BPoint where) 125 { 126 if (!IsFocus()) 127 MakeFocus(true); 128 129 _FilterMouse(&where); 130 131 StateView::MouseDown(where); 132 } 133 134 135 // MouseMoved 136 void 137 CanvasView::MouseMoved(BPoint where, uint32 transit, 138 const BMessage* dragMessage) 139 { 140 _FilterMouse(&where); 141 142 StateView::MouseMoved(where, transit, dragMessage); 143 } 144 145 // #pragma mark - 146 147 // ScrollOffsetChanged 148 void 149 CanvasView::ScrollOffsetChanged(BPoint oldOffset, BPoint newOffset) 150 { 151 BPoint offset = newOffset - oldOffset; 152 ScrollBy(offset.x, offset.y); 153 154 MouseMoved(fMouseInfo.position + offset, fMouseInfo.transit, NULL); 155 } 156 157 // VisibleSizeChanged 158 void 159 CanvasView::VisibleSizeChanged(float oldWidth, float oldHeight, 160 float newWidth, float newHeight) 161 { 162 } 163 164 // #pragma mark - 165 166 // AreaInvalidated 167 void 168 CanvasView::AreaInvalidated(const BRect& area) 169 { 170 if (fDirtyIconArea.Contains(area)) 171 return; 172 173 fDirtyIconArea = fDirtyIconArea | area; 174 175 BRect viewArea(area); 176 ConvertFromCanvas(&viewArea); 177 Invalidate(viewArea); 178 } 179 180 181 // #pragma mark - 182 183 // SetIcon 184 void 185 CanvasView::SetIcon(Icon* icon) 186 { 187 if (fIcon == icon) 188 return; 189 190 if (fIcon) 191 fIcon->RemoveListener(this); 192 193 fIcon = icon; 194 fRenderer->SetIcon(icon); 195 196 if (fIcon) 197 fIcon->AddListener(this); 198 } 199 200 // SetMouseFilterMode 201 void 202 CanvasView::SetMouseFilterMode(uint32 mode) 203 { 204 if (fMouseFilterMode == mode) 205 return; 206 207 fMouseFilterMode = mode; 208 Invalidate(_CanvasRect()); 209 } 210 211 // ConvertFromCanvas 212 void 213 CanvasView::ConvertFromCanvas(BPoint* point) const 214 { 215 point->x = point->x * fZoomLevel + fCanvasOrigin.x; 216 point->y = point->y * fZoomLevel + fCanvasOrigin.y; 217 } 218 219 // ConvertToCanvas 220 void 221 CanvasView::ConvertToCanvas(BPoint* point) const 222 { 223 point->x = (point->x - fCanvasOrigin.x) / fZoomLevel; 224 point->y = (point->y - fCanvasOrigin.y) / fZoomLevel; 225 } 226 227 // ConvertFromCanvas 228 void 229 CanvasView::ConvertFromCanvas(BRect* r) const 230 { 231 r->left = r->left * fZoomLevel + fCanvasOrigin.x; 232 r->top = r->top * fZoomLevel + fCanvasOrigin.y; 233 r->right++; 234 r->bottom++; 235 r->right = r->right * fZoomLevel + fCanvasOrigin.x; 236 r->bottom = r->bottom * fZoomLevel + fCanvasOrigin.y; 237 r->right--; 238 r->bottom--; 239 } 240 241 // ConvertToCanvas 242 void 243 CanvasView::ConvertToCanvas(BRect* r) const 244 { 245 r->left = (r->left - fCanvasOrigin.x) / fZoomLevel; 246 r->top = (r->top - fCanvasOrigin.y) / fZoomLevel; 247 r->right = (r->right - fCanvasOrigin.x) / fZoomLevel; 248 r->bottom = (r->bottom - fCanvasOrigin.y) / fZoomLevel; 249 } 250 251 // #pragma mark - 252 253 // _HandleKeyDown 254 bool 255 CanvasView::_HandleKeyDown(uint32 key, uint32 modifiers) 256 { 257 switch (key) { 258 case 'z': 259 case 'y': 260 if (modifiers & B_SHIFT_KEY) 261 CommandStack()->Redo(); 262 else 263 CommandStack()->Undo(); 264 break; 265 266 case '+': 267 _SetZoom(_NextZoomInLevel(fZoomLevel)); 268 break; 269 case '-': 270 _SetZoom(_NextZoomOutLevel(fZoomLevel)); 271 break; 272 273 default: 274 return StateView::_HandleKeyDown(key, modifiers); 275 } 276 277 return true; 278 } 279 280 // _CanvasRect() 281 BRect 282 CanvasView::_CanvasRect() const 283 { 284 BRect r = fBitmap->Bounds(); 285 ConvertFromCanvas(&r); 286 return r; 287 } 288 289 // _AllocBackBitmap 290 void 291 CanvasView::_AllocBackBitmap(float width, float height) 292 { 293 // sanity check 294 if (width <= 0.0 || height <= 0.0) 295 return; 296 297 if (fOffsreenBitmap) { 298 // see if the bitmap needs to be expanded 299 BRect b = fOffsreenBitmap->Bounds(); 300 if (b.Width() >= width && b.Height() >= height) 301 return; 302 303 // it does; clean up: 304 _FreeBackBitmap(); 305 } 306 307 BRect b(0.0, 0.0, width, height); 308 fOffsreenBitmap = new (nothrow) BBitmap(b, B_RGB32, true); 309 if (!fOffsreenBitmap) { 310 fprintf(stderr, "CanvasView::_AllocBackBitmap(): failed to allocate\n"); 311 return; 312 } 313 if (fOffsreenBitmap->IsValid()) { 314 fOffsreenView = new BView(b, 0, B_FOLLOW_NONE, B_WILL_DRAW); 315 BFont font; 316 GetFont(&font); 317 fOffsreenView->SetFont(&font); 318 fOffsreenView->SetHighColor(HighColor()); 319 fOffsreenView->SetLowColor(LowColor()); 320 fOffsreenView->SetFlags(Flags()); 321 fOffsreenBitmap->AddChild(fOffsreenView); 322 } else { 323 _FreeBackBitmap(); 324 fprintf(stderr, "CanvasView::_AllocBackBitmap(): bitmap invalid\n"); 325 } 326 } 327 328 // _FreeBackBitmap 329 void 330 CanvasView::_FreeBackBitmap() 331 { 332 if (fOffsreenBitmap) { 333 delete fOffsreenBitmap; 334 fOffsreenBitmap = NULL; 335 fOffsreenView = NULL; 336 } 337 } 338 339 // _DrawInto 340 void 341 CanvasView::_DrawInto(BView* view, BRect updateRect) 342 { 343 if (fDirtyIconArea.IsValid()) { 344 fRenderer->Render(fDirtyIconArea); 345 fDirtyIconArea.Set(LONG_MAX, LONG_MAX, LONG_MIN, LONG_MIN); 346 } 347 348 // icon 349 BRect canvas(_CanvasRect()); 350 view->DrawBitmap(fBitmap, fBitmap->Bounds(), canvas); 351 352 // grid 353 int32 gridLines = 0; 354 int32 scale = 1; 355 switch (fMouseFilterMode) { 356 case SNAPPING_64: 357 gridLines = 63; 358 break; 359 case SNAPPING_32: 360 gridLines = 31; 361 scale = 2; 362 break; 363 case SNAPPING_16: 364 gridLines = 15; 365 scale = 4; 366 break; 367 case SNAPPING_OFF: 368 default: 369 break; 370 } 371 view->SetDrawingMode(B_OP_BLEND); 372 for (int32 i = 1; i <= gridLines; i++) { 373 BPoint cross(i * scale, i * scale); 374 ConvertFromCanvas(&cross); 375 view->StrokeLine(BPoint(canvas.left, cross.y), 376 BPoint(canvas.right, cross.y)); 377 view->StrokeLine(BPoint(cross.x, canvas.top), 378 BPoint(cross.x, canvas.bottom)); 379 } 380 view->SetDrawingMode(B_OP_COPY); 381 382 // outside icon 383 BRegion outside(Bounds() & updateRect); 384 outside.Exclude(canvas); 385 view->FillRegion(&outside, kStripes); 386 387 StateView::Draw(view, updateRect); 388 } 389 390 // _FilterMouse 391 void 392 CanvasView::_FilterMouse(BPoint* where) const 393 { 394 switch (fMouseFilterMode) { 395 396 case SNAPPING_64: 397 ConvertToCanvas(where); 398 where->x = floorf(where->x + 0.5); 399 where->y = floorf(where->y + 0.5); 400 ConvertFromCanvas(where); 401 break; 402 403 case SNAPPING_32: 404 ConvertToCanvas(where); 405 where->x /= 2.0; 406 where->y /= 2.0; 407 where->x = floorf(where->x + 0.5); 408 where->y = floorf(where->y + 0.5); 409 where->x *= 2.0; 410 where->y *= 2.0; 411 ConvertFromCanvas(where); 412 break; 413 414 case SNAPPING_16: 415 ConvertToCanvas(where); 416 where->x /= 4.0; 417 where->y /= 4.0; 418 where->x = floorf(where->x + 0.5); 419 where->y = floorf(where->y + 0.5); 420 where->x *= 4.0; 421 where->y *= 4.0; 422 ConvertFromCanvas(where); 423 break; 424 425 case SNAPPING_OFF: 426 default: 427 break; 428 } 429 } 430 431 // _MakeBackground 432 void 433 CanvasView::_MakeBackground() 434 { 435 uint8* row = (uint8*)fBackground->Bits(); 436 uint32 bpr = fBackground->BytesPerRow(); 437 uint32 width = fBackground->Bounds().IntegerWidth() + 1; 438 uint32 height = fBackground->Bounds().IntegerHeight() + 1; 439 440 const GammaTable& lut = fRenderer->GammaTable(); 441 uint8 redLow = lut.dir(kAlphaLow.red); 442 uint8 greenLow = lut.dir(kAlphaLow.blue); 443 uint8 blueLow = lut.dir(kAlphaLow.green); 444 uint8 redHigh = lut.dir(kAlphaHigh.red); 445 uint8 greenHigh = lut.dir(kAlphaHigh.blue); 446 uint8 blueHigh = lut.dir(kAlphaHigh.green); 447 448 for (uint32 y = 0; y < height; y++) { 449 uint8* p = row; 450 for (uint32 x = 0; x < width; x++) { 451 p[3] = 255; 452 if (x % 8 >= 4) { 453 if (y % 8 >= 4) { 454 p[0] = blueLow; 455 p[1] = greenLow; 456 p[2] = redLow; 457 } else { 458 p[0] = blueHigh; 459 p[1] = greenHigh; 460 p[2] = redHigh; 461 } 462 } else { 463 if (y % 8 >= 4) { 464 p[0] = blueHigh; 465 p[1] = greenHigh; 466 p[2] = redHigh; 467 } else { 468 p[0] = blueLow; 469 p[1] = greenLow; 470 p[2] = redLow; 471 } 472 } 473 p += 4; 474 } 475 row += bpr; 476 } 477 } 478 479 // #pragma mark - 480 481 // _NextZoomInLevel 482 double 483 CanvasView::_NextZoomInLevel(double zoom) const 484 { 485 if (zoom < 1) 486 return 1; 487 if (zoom < 1.5) 488 return 1.5; 489 if (zoom < 2) 490 return 2; 491 if (zoom < 3) 492 return 3; 493 if (zoom < 4) 494 return 4; 495 if (zoom < 6) 496 return 6; 497 if (zoom < 8) 498 return 8; 499 if (zoom < 16) 500 return 16; 501 if (zoom < 32) 502 return 32; 503 return 64; 504 } 505 506 // _NextZoomOutLevel 507 double 508 CanvasView::_NextZoomOutLevel(double zoom) const 509 { 510 if (zoom > 32) 511 return 32; 512 if (zoom > 16) 513 return 16; 514 if (zoom > 8) 515 return 8; 516 if (zoom > 6) 517 return 6; 518 if (zoom > 4) 519 return 4; 520 if (zoom > 3) 521 return 3; 522 if (zoom > 2) 523 return 2; 524 if (zoom > 1.5) 525 return 1.5; 526 return 1; 527 } 528 529 // _SetZoom 530 void 531 CanvasView::_SetZoom(double zoomLevel) 532 { 533 if (fZoomLevel == zoomLevel) 534 return; 535 536 // zoom into mouse position, or into center of view 537 BPoint anchor = MouseInfo()->position; 538 BRect bounds(Bounds()); 539 if (!bounds.Contains(anchor)) { 540 bounds = _CanvasRect(); 541 anchor.x = (bounds.left + bounds.right) / 2.0; 542 anchor.y = (bounds.top + bounds.bottom) / 2.0; 543 } 544 printf("anchor: %.2f, %.2f\n", anchor.x, anchor.y); 545 546 BPoint offset; 547 if (fZoomLevel < zoomLevel) { 548 offset.x = anchor.x * (zoomLevel / fZoomLevel) - anchor.x; 549 offset.y = anchor.y * (zoomLevel / fZoomLevel) - anchor.y; 550 } else { 551 offset.x = -anchor.x * (zoomLevel / fZoomLevel); 552 offset.y = -anchor.y * (zoomLevel / fZoomLevel); 553 } 554 555 fZoomLevel = zoomLevel; 556 SetDataRect(_LayoutCanvas()); 557 558 printf("offset: %.2f, %.2f\n", offset.x, offset.y); 559 560 SetScrollOffset(ScrollOffset() + offset); 561 562 Invalidate(); 563 } 564 565 // _LayoutCanvas 566 BRect 567 CanvasView::_LayoutCanvas() 568 { 569 if (!fBitmap) 570 return BRect(0, 0, -1, -1); 571 572 // size of zoomed bitmap 573 BRect r(fBitmap->Bounds()); 574 r.OffsetTo(0, 0); 575 r.right = floorf((r.Width() + 1) * fZoomLevel + 0.5) - 1; 576 r.bottom = floorf((r.Height() + 1) * fZoomLevel + 0.5) - 1; 577 578 // TODO: ask manipulators to extend size 579 580 // left top of canvas within empty area 581 fCanvasOrigin.x = floorf(r.Width() * 0.25); 582 fCanvasOrigin.y = floorf(r.Height() * 0.25); 583 584 // resize for empty area around bitmap 585 r.right += r.Width() * 0.5; 586 r.bottom += r.Height() * 0.5; 587 588 return r; 589 } 590 591