xref: /haiku/src/tests/servers/app/transformation/main.cpp (revision 0754c319592cd8a523959d85fb06ab23c64a98a6)
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