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