1 /* 2 * Copyright 2014 Stephan Aßmus <superstippi@gmx.de> 3 * All rights reserved. Distributed under the terms of the MIT license. 4 */ 5 6 7 #include <algorithm> 8 #include <stdio.h> 9 #include <string.h> 10 11 #include <Application.h> 12 #include <Bitmap.h> 13 #include <GradientLinear.h> 14 #include <LayoutBuilder.h> 15 #include <List.h> 16 #include <Message.h> 17 #include <Picture.h> 18 #include <PopUpMenu.h> 19 #include <Region.h> 20 #include <Resources.h> 21 #include <Roster.h> 22 #include <ScrollView.h> 23 #include <String.h> 24 #include <StringView.h> 25 #include <View.h> 26 #include <Window.h> 27 28 29 static const char* kAppSignature = "application/x-vnd.Haiku-Transformation"; 30 31 32 class Test { 33 public: 34 Test(const char* name); 35 virtual ~Test(); 36 37 const char* Name() const 38 { return fName.String(); } 39 40 virtual void Draw(BView* view, BRect updateRect) = 0; 41 42 private: 43 BString fName; 44 }; 45 46 47 Test::Test(const char* name) 48 : 49 fName(name) 50 { 51 } 52 53 54 Test::~Test() 55 { 56 } 57 58 59 class BitmapTest : public Test { 60 public: 61 BitmapTest(const char* name) 62 : 63 Test(name), 64 fBitmap(_LoadBitmap(555)) 65 { 66 } 67 68 private: 69 status_t 70 _GetAppResources(BResources& resources) const 71 { 72 app_info info; 73 status_t status = be_app->GetAppInfo(&info); 74 if (status != B_OK) 75 return status; 76 77 return resources.SetTo(&info.ref); 78 } 79 80 81 BBitmap* _LoadBitmap(int resourceID) const 82 { 83 BResources resources; 84 status_t status = _GetAppResources(resources); 85 if (status != B_OK) 86 return NULL; 87 88 size_t dataSize; 89 const void* data = resources.LoadResource(B_MESSAGE_TYPE, resourceID, 90 &dataSize); 91 if (data == NULL) 92 return NULL; 93 94 BMemoryIO stream(data, dataSize); 95 96 // Try to read as an archived bitmap. 97 BMessage archive; 98 status = archive.Unflatten(&stream); 99 if (status != B_OK) 100 return NULL; 101 102 BBitmap* bitmap = new BBitmap(&archive); 103 104 status = bitmap->InitCheck(); 105 if (status != B_OK) { 106 delete bitmap; 107 bitmap = NULL; 108 } 109 110 return bitmap; 111 } 112 113 protected: 114 BBitmap* fBitmap; 115 }; 116 117 118 119 // #pragma mark - TestView 120 121 122 class TestView : public BView { 123 public: 124 TestView(); 125 virtual ~TestView(); 126 127 virtual void Draw(BRect updateRect); 128 129 void SetTest(Test* test); 130 131 private: 132 Test* fTest; 133 }; 134 135 136 TestView::TestView() 137 : 138 BView(NULL, B_WILL_DRAW | B_FULL_UPDATE_ON_RESIZE), 139 fTest(NULL) 140 { 141 } 142 143 144 TestView::~TestView() 145 { 146 } 147 148 149 void 150 TestView::Draw(BRect updateRect) 151 { 152 if (fTest != NULL) 153 fTest->Draw(this, updateRect); 154 } 155 156 157 void 158 TestView::SetTest(Test* test) 159 { 160 fTest = test; 161 Invalidate(); 162 } 163 164 165 // #pragma mark - TestWindow 166 167 168 enum { 169 MSG_SELECT_TEST = 'stst' 170 }; 171 172 173 class TestWindow : public BWindow { 174 public: 175 TestWindow(); 176 virtual ~TestWindow(); 177 178 virtual void MessageReceived(BMessage* message); 179 180 void AddTest(Test* test); 181 void SetToTest(int32 index); 182 183 private: 184 TestView* fTestView; 185 186 BMenuField* fTestSelectionField; 187 188 BList fTests; 189 }; 190 191 192 TestWindow::TestWindow() 193 : 194 BWindow(BRect(50.0, 50.0, 450.0, 250.0), "Transformations Test", 195 B_DOCUMENT_WINDOW, B_ASYNCHRONOUS_CONTROLS | B_QUIT_ON_WINDOW_CLOSE 196 | B_AUTO_UPDATE_SIZE_LIMITS) 197 { 198 fTestView = new TestView(); 199 200 BScrollView* scrollView = new BScrollView("scroll", fTestView, 0, true, 201 true); 202 203 fTestSelectionField = new BMenuField("test selection", 204 "Select test:", new BPopUpMenu("select")); 205 206 BLayoutBuilder::Group<>(this, B_VERTICAL, 0.0f) 207 .AddGroup(B_HORIZONTAL) 208 .Add(fTestSelectionField) 209 .AddGlue() 210 .SetInsets(B_USE_DEFAULT_SPACING) 211 .End() 212 .Add(scrollView) 213 ; 214 } 215 216 217 TestWindow::~TestWindow() 218 { 219 for (int32 i = fTests.CountItems() - 1; i >= 0; i++) 220 delete (Test*)fTests.ItemAt(i); 221 } 222 223 224 void 225 TestWindow::MessageReceived(BMessage* message) 226 { 227 switch (message->what) { 228 case MSG_SELECT_TEST: 229 { 230 int32 index; 231 if (message->FindInt32("index", &index) == B_OK) 232 SetToTest(index); 233 break; 234 } 235 236 default: 237 BWindow::MessageReceived(message); 238 } 239 } 240 241 242 void 243 TestWindow::AddTest(Test* test) 244 { 245 if (test == NULL || fTests.HasItem(test)) 246 return; 247 248 if (!fTests.AddItem(test)) { 249 delete test; 250 return; 251 } 252 253 BMessage* message = new BMessage(MSG_SELECT_TEST); 254 message->AddInt32("index", fTests.CountItems() - 1); 255 256 BMenuItem* item = new BMenuItem(test->Name(), message); 257 if (!fTestSelectionField->Menu()->AddItem(item)) { 258 fTests.RemoveItem(fTests.CountItems() - 1); 259 delete test; 260 delete item; 261 return; 262 } 263 264 if (fTests.CountItems() == 1) 265 SetToTest(0); 266 } 267 268 269 void 270 TestWindow::SetToTest(int32 index) 271 { 272 Test* test = (Test*)fTests.ItemAt(index); 273 if (test == NULL) 274 return; 275 276 fTestSelectionField->Menu()->ItemAt(index)->SetMarked(true); 277 278 fTestView->SetTest(test); 279 } 280 281 282 // #pragma mark - Test1 283 284 285 class RectsTest : public Test { 286 public: 287 RectsTest() 288 : 289 Test("Rects") 290 { 291 } 292 293 virtual void Draw(BView* view, BRect updateRect) 294 { 295 view->DrawString("Rects", BPoint(20, 30)); 296 297 view->SetDrawingMode(B_OP_ALPHA); 298 view->SetBlendingMode(B_PIXEL_ALPHA, B_ALPHA_OVERLAY); 299 300 BRect rect(view->Bounds()); 301 rect.OffsetTo(B_ORIGIN); 302 303 rect.InsetBy(rect.Width() / 3, rect.Height() / 3); 304 BPoint center( 305 rect.left + rect.Width() / 2, 306 rect.top + rect.Height() / 2); 307 308 for (int32 i = 0; i < 360; i += 40) { 309 BAffineTransform transform; 310 transform.RotateBy(center, i * M_PI / 180.0); 311 view->SetTransform(transform); 312 313 view->SetHighColor(51, 151, 255, 20); 314 view->FillRect(rect); 315 316 view->SetHighColor(51, 255, 151, 180); 317 view->DrawString("Rect", center); 318 } 319 } 320 }; 321 322 323 // #pragma mark - AlphaMaskBitmapTest 324 325 326 class AlphaMaskBitmapTest : public BitmapTest { 327 public: 328 AlphaMaskBitmapTest() 329 : 330 BitmapTest("Alpha Masked Bitmap") 331 { 332 } 333 334 virtual void Draw(BView* view, BRect updateRect) 335 { 336 BRect rect(view->Bounds()); 337 338 if (fBitmap == NULL) { 339 view->SetHighColor(255, 0, 0); 340 view->FillRect(rect); 341 view->SetHighColor(0, 0, 0); 342 view->DrawString("Failed to load the bitmap.", BPoint(20, 20)); 343 return; 344 } 345 346 rect.left = (rect.Width() - fBitmap->Bounds().Width()) / 2; 347 rect.top = (rect.Height() - fBitmap->Bounds().Height()) / 2; 348 rect.right = rect.left + fBitmap->Bounds().Width(); 349 rect.bottom = rect.top + fBitmap->Bounds().Height(); 350 351 BPoint center( 352 rect.left + rect.Width() / 2, 353 rect.top + rect.Height() / 2); 354 355 BPicture picture; 356 view->BeginPicture(&picture); 357 view->SetDrawingMode(B_OP_ALPHA); 358 view->SetBlendingMode(B_PIXEL_ALPHA, B_ALPHA_COMPOSITE); 359 BFont font; 360 view->GetFont(&font); 361 font.SetSize(70); 362 view->SetFont(&font); 363 view->SetHighColor(0, 0, 0, 80); 364 view->FillRect(view->Bounds()); 365 view->SetHighColor(0, 0, 0, 255); 366 view->DrawString("CLIPPING", BPoint(0, center.y + 35)); 367 view->EndPicture(); 368 369 view->ClipToPicture(&picture); 370 371 BAffineTransform transform; 372 transform.RotateBy(center, 30 * M_PI / 180.0); 373 view->SetTransform(transform); 374 375 view->DrawBitmap(fBitmap, fBitmap->Bounds(), rect); 376 } 377 }; 378 379 380 // #pragma mark - Gradient 381 382 383 class GradientTest : public Test { 384 public: 385 GradientTest() 386 : 387 Test("Gradient") 388 { 389 } 390 391 virtual void Draw(BView* view, BRect updateRect) 392 { 393 BRect rect(view->Bounds()); 394 rect.InsetBy(rect.Width() / 3, rect.Height() / 3); 395 BPoint center( 396 rect.left + rect.Width() / 2, 397 rect.top + rect.Height() / 2); 398 399 BAffineTransform transform; 400 transform.RotateBy(center, 30.0 * M_PI / 180.0); 401 view->SetTransform(transform); 402 403 rgb_color top = (rgb_color){ 255, 255, 0, 255 }; 404 rgb_color bottom = (rgb_color){ 0, 255, 255, 255 }; 405 406 BGradientLinear gradient; 407 gradient.AddColor(top, 0.0f); 408 gradient.AddColor(bottom, 255.0f); 409 gradient.SetStart(rect.LeftTop()); 410 gradient.SetEnd(rect.LeftBottom()); 411 412 float radius = std::min(rect.Width() / 5, rect.Height() / 5); 413 414 view->FillRoundRect(rect, radius, radius, gradient); 415 } 416 }; 417 418 419 // #pragma mark - NestedStates 420 421 422 class NestedStatesTest : public Test { 423 public: 424 NestedStatesTest() 425 : 426 Test("Nested view states") 427 { 428 } 429 430 virtual void Draw(BView* view, BRect updateRect) 431 { 432 BAffineTransform transform; 433 transform.RotateBy(BPoint(100, 100), 30.0 * M_PI / 180.0); 434 view->SetTransform(transform); 435 436 rgb_color top = (rgb_color){ 255, 0, 0, 255 }; 437 rgb_color bottom = (rgb_color){ 255, 255, 0, 255 }; 438 439 BRect rect(20, 20, 120, 120); 440 441 BGradientLinear gradient; 442 gradient.AddColor(top, 0.0f); 443 gradient.AddColor(bottom, 255.0f); 444 gradient.SetStart(rect.LeftTop()); 445 gradient.SetEnd(rect.LeftBottom()); 446 447 view->FillRoundRect(rect, 20, 20, gradient); 448 449 view->PushState(); 450 // Should be in the same place! 451 view->StrokeRoundRect(rect, 20, 20); 452 453 // Now rotated by another 30 degree 454 view->SetTransform(transform); 455 456 view->SetDrawingMode(B_OP_ALPHA); 457 view->SetBlendingMode(B_PIXEL_ALPHA, B_ALPHA_COMPOSITE); 458 view->SetHighColor(0, 0, 255, 120); 459 view->FillRoundRect(rect, 20, 20); 460 461 view->PopState(); 462 } 463 }; 464 465 466 // #pragma mark - Clipping 467 468 469 class ClippingTest : public Test { 470 public: 471 ClippingTest() 472 : 473 Test("View bounds clipping") 474 { 475 } 476 477 virtual void Draw(BView* view, BRect updateRect) 478 { 479 BRect r (20, 20, 50, 50); 480 view->SetHighColor(ui_color(B_FAILURE_COLOR)); 481 view->FillRect(r); 482 483 BAffineTransform transform; 484 transform.TranslateBy(400, 400); 485 view->SetTransform(transform); 486 487 // Make sure this rectangle is drawn, even when the original one is out 488 // of the view bounds (for example because of scrolling). 489 view->SetHighColor(ui_color(B_SUCCESS_COLOR)); 490 view->FillRect(r); 491 } 492 }; 493 494 495 // #pragma mark - Clipping 496 497 498 class TextClippingTest : public Test { 499 public: 500 TextClippingTest() 501 : 502 Test("Text clipping") 503 { 504 } 505 506 virtual void Draw(BView* view, BRect updateRect) 507 { 508 BFont font; 509 view->GetFont(&font); 510 font.SetSize(70); 511 view->SetFont(&font); 512 513 float width = view->Bounds().Width(); 514 515 // The translation make the text, which has negative coordinates, be 516 // visible inside the viewport. 517 BAffineTransform transform; 518 transform.TranslateBy(width, 0); 519 view->SetTransform(transform); 520 521 const char* str = "CLIPPING"; 522 523 // Test the standard DrawString method 524 525 // Draw the text bounds 526 float size = view->StringWidth(str); 527 BRect r(-width, 0, size - width, 70); 528 view->SetHighColor(ui_color(B_SUCCESS_COLOR)); 529 view->FillRect(r); 530 531 // Draw the text (which should fit inside the bounds rectangle) 532 view->SetHighColor(0, 0, 0, 255); 533 view->DrawString(str, BPoint(-width, 70)); 534 535 // Test with offset-based DrawString 536 BPoint offsets[strlen(str)]; 537 for(unsigned int i = 0; i < strlen(str); i++) 538 { 539 offsets[i].x = i * 35 - width; 540 offsets[i].y = 145; 541 } 542 543 // Draw the text bounds 544 view->SetHighColor(ui_color(B_SUCCESS_COLOR)); 545 r = BRect(offsets[0], offsets[strlen(str) - 1]); 546 r.top = 75; 547 view->FillRect(r); 548 549 // Draw the text (which should fit inside the bounds rectangle) 550 view->SetHighColor(0, 0, 0, 255); 551 view->DrawString(str, offsets, strlen(str)); 552 553 } 554 }; 555 556 557 // #pragma mark - BitmapClipTest 558 559 560 class BitmapClipTest : public BitmapTest { 561 public: 562 BitmapClipTest() 563 : 564 BitmapTest("Bitmap clipping") 565 { 566 } 567 568 virtual void Draw(BView* view, BRect updateRect) 569 { 570 BRect rect(view->Bounds()); 571 572 if (fBitmap == NULL) { 573 view->SetHighColor(255, 0, 0); 574 view->FillRect(rect); 575 view->SetHighColor(0, 0, 0); 576 view->DrawString("Failed to load the bitmap.", BPoint(20, 20)); 577 return; 578 } 579 580 rect = fBitmap->Bounds(); 581 582 view->SetHighColor(ui_color(B_FAILURE_COLOR)); 583 view->FillRect(rect); 584 585 // The rect offset should compensate the transform translation, so the 586 // bitmap should be drawn at the view origin. It will then exactly 587 // cover the red rectangle, which should not be visible anymore. 588 rect.OffsetBy(0, 40); 589 590 BAffineTransform transform; 591 transform.TranslateBy(0, -40); 592 view->SetTransform(transform); 593 594 view->DrawBitmap(fBitmap, fBitmap->Bounds(), rect); 595 } 596 }; 597 598 599 // #pragma mark - PixelAlignTest 600 601 602 class PixelAlignTest : public Test { 603 public: 604 PixelAlignTest() 605 : 606 Test("Pixel alignment") 607 { 608 } 609 610 virtual void Draw(BView* view, BRect updateRect) 611 { 612 BRect rect(20, 20, 120, 120); 613 view->SetHighColor(ui_color(B_SUCCESS_COLOR)); 614 view->StrokeRect(rect); 615 616 BAffineTransform transform; 617 transform.TranslateBy(140, 0); 618 view->SetTransform(transform); 619 620 // Translating a pixel-aligned rectangle by an integer number of 621 // pixels should result in a pixel-aligned rectangle. 622 view->SetHighColor(ui_color(B_FAILURE_COLOR)); 623 view->StrokeRect(rect); 624 } 625 }; 626 627 628 // #pragma mark - 629 630 631 int 632 main(int argc, char** argv) 633 { 634 BApplication app(kAppSignature); 635 636 TestWindow* window = new TestWindow(); 637 638 window->AddTest(new RectsTest()); 639 window->AddTest(new BitmapClipTest()); 640 window->AddTest(new TextClippingTest()); 641 window->AddTest(new AlphaMaskBitmapTest()); 642 window->AddTest(new GradientTest()); 643 window->AddTest(new NestedStatesTest()); 644 window->AddTest(new ClippingTest()); 645 window->AddTest(new PixelAlignTest()); 646 647 window->SetToTest(2); 648 window->Show(); 649 650 app.Run(); 651 return 0; 652 } 653