1// main.cpp
2
3#include <math.h>
4#include <stdio.h>
5#include <stdlib.h>
6#include <string.h>
7
8#include <Application.h>
9#include <Bitmap.h>
10#include <Button.h>
11#include <Message.h>
12#include <MessageRunner.h>
13#include <Messenger.h>
14#include <View.h>
15#include <Window.h>
16
17#include "bitmap.h"
18
19enum {
20	MSG_RESET	= 'rset',
21	MSG_TICK	= 'tick',
22};
23
24#define SPEED 2.0
25
26// random_number_between
27float
28random_number_between(float v1, float v2)
29{
30	if (v1 < v2)
31		return v1 + fmod(rand() / 1000.0, (v2 - v1));
32	else if (v2 < v1)
33		return v2 + fmod(rand() / 1000.0, (v1 - v2));
34	return v1;
35}
36
37// TestView
38class TestView : public BView {
39
40 public:
41					TestView(BRect frame, const char* name,
42							 uint32 resizeFlags, uint32 flags);
43
44	virtual	void	AttachedToWindow();
45	virtual	void	MessageReceived(BMessage* message);
46
47	virtual	void	Draw(BRect updateRect);
48
49	virtual	void	MouseDown(BPoint where);
50	virtual	void	MouseUp(BPoint where);
51	virtual	void	MouseMoved(BPoint where, uint32 transit,
52							   const BMessage* dragMessage);
53
54 private:
55			void	_ResetRect();
56			void	_InvalidateBitmapRect(BRect r);
57			void	_DrawCross(BPoint where, rgb_color c);
58
59	struct point {
60		double x;
61		double y;
62		double direction_x;
63		double direction_y;
64		double velocity_x;
65		double velocity_y;
66	};
67	struct color_cycle {
68		uint8 value;
69		double direction;
70	};
71
72			void	_FillBitmap(point* polygon);
73			void	_InitPolygon(const BRect& b, point* polygon) const;
74			void	_InitColor(color_cycle* color) const;
75			void	_MorphPolygon(const BRect& b, point* polygon);
76			void	_MorphColor(color_cycle* color);
77
78	BBitmap*		fBitmap;
79	BView*			fOffscreenView;
80	BMessageRunner*	fTicker;
81	BRect			fBitmapRect;
82
83	enum {
84		TRACKING_NONE = 0,
85
86		TRACKING_LEFT,
87		TRACKING_RIGHT,
88		TRACKING_TOP,
89		TRACKING_BOTTOM,
90
91		TRACKING_LEFT_TOP,
92		TRACKING_RIGHT_TOP,
93		TRACKING_LEFT_BOTTOM,
94		TRACKING_RIGHT_BOTTOM,
95
96		TRACKING_ALL
97	};
98
99	uint32			fTracking;
100	BPoint			fLastMousePos;
101
102	point			fPolygon[4];
103	color_cycle		fColor[3];
104};
105
106// constructor
107TestView::TestView(BRect frame, const char* name,
108				   uint32 resizeFlags, uint32 flags)
109	: BView(frame, name, resizeFlags, flags),
110//	  fBitmap(new BBitmap(BRect(0, 0, kBitmapWidth - 1, kBitmapHeight -1), 0, kBitmapFormat)),
111//	  fBitmap(new BBitmap(BRect(0, 0, 32 - 1, 8 - 1), 0, B_CMAP8)),
112//	  fBitmap(new BBitmap(BRect(0, 0, 32 - 1, 8 - 1), 0, B_GRAY8)),
113	  fBitmap(new BBitmap(BRect(0, 0, 199, 99), B_RGB32, true)),
114//	  fBitmap(new BBitmap(BRect(0, 0, 639, 479), B_RGB32, true)),
115//	  fBitmap(new BBitmap(BRect(0, 0, 639, 479), B_CMAP8, true)),
116//	  fBitmap(new BBitmap(BRect(0, 0, 199, 99), B_CMAP8, true)),
117//	  fBitmap(new BBitmap(BRect(0, 0, 199, 99), B_GRAY8, true)),
118	  fOffscreenView(new BView(fBitmap->Bounds(), "Offscreen view",
119							   B_FOLLOW_ALL, B_WILL_DRAW | B_SUBPIXEL_PRECISE)),
120	  fTicker(NULL),
121	  fBitmapRect(),
122	  fTracking(TRACKING_NONE),
123	  fLastMousePos(-1.0, -1.0)
124{
125	SetViewColor(B_TRANSPARENT_COLOR);
126	SetLowColor(ui_color(B_PANEL_BACKGROUND_COLOR));
127//	uint32 size = min_c((uint32)fBitmap->BitsLength(), sizeof(kBitmapBits));
128//	memcpy(fBitmap->Bits(), kBitmapBits, size);
129/*	uint8* bits = (uint8*)fBitmap->Bits();
130	uint32 width = fBitmap->Bounds().IntegerWidth() + 1;
131	uint32 height = fBitmap->Bounds().IntegerHeight() + 1;
132	uint32 bpr = fBitmap->BytesPerRow();
133printf("width: %ld, height: %ld, bpr: %ld\n", width, height, bpr);
134	int32 index = 0;
135	for (uint32 y = 0; y < height; y++) {
136		uint8* h = bits;
137		for (uint32 x = 0; x < width; x++) {
138			*h = index++;
139			h++;
140		}
141		bits += bpr;
142	}
143BRect a(0.0, 10.0, 20.0, 10.0);
144BRect b(0.0, 10.0, 10.0, 30.0);
145printf("Intersects: %d\n", a.Intersects(b));*/
146	if (fBitmap->Lock()) {
147		fBitmap->AddChild(fOffscreenView);
148		fOffscreenView->SetBlendingMode(B_CONSTANT_ALPHA, B_ALPHA_COMPOSITE);
149		fBitmap->Unlock();
150	}
151
152	srand((long int)system_time());
153	_InitPolygon(fBitmap->Bounds(), fPolygon);
154	_InitColor(fColor);
155
156	_ResetRect();
157}
158
159// AttachedToWindow
160void
161TestView::AttachedToWindow()
162{
163	BMessenger mess(this, Window());
164	BMessage msg(MSG_TICK);
165	fTicker = new BMessageRunner(mess, &msg, 40000LL);
166}
167
168// MessageReceived
169void
170TestView::MessageReceived(BMessage* message)
171{
172	switch (message->what) {
173		case MSG_RESET: {
174			BRect old = fBitmapRect;
175			_ResetRect();
176			_InvalidateBitmapRect(old | fBitmapRect);
177			break;
178		}
179		case MSG_TICK:
180			_MorphPolygon(fBitmap->Bounds(), fPolygon);
181			_MorphColor(fColor);
182			_FillBitmap(fPolygon);
183			Invalidate(fBitmapRect);
184			break;
185		default:
186			BView::MessageReceived(message);
187			break;
188	}
189}
190
191// Draw
192void
193TestView::Draw(BRect updateRect)
194{
195	SetDrawingMode(B_OP_ALPHA);
196	DrawBitmap(fBitmap, fBitmap->Bounds(), fBitmapRect);
197
198	SetDrawingMode(B_OP_COPY);
199	// background arround bitmap
200	BRect topOfBitmap(updateRect.left, updateRect.top, updateRect.right, fBitmapRect.top - 1);
201	if (topOfBitmap.IsValid())
202		FillRect(topOfBitmap, B_SOLID_LOW);
203
204	BRect leftOfBitmap(updateRect.left, fBitmapRect.top, fBitmapRect.left - 1, fBitmapRect.bottom);
205	if (leftOfBitmap.IsValid())
206		FillRect(leftOfBitmap, B_SOLID_LOW);
207
208	BRect rightOfBitmap(fBitmapRect.right + 1, fBitmapRect.top, updateRect.right, fBitmapRect.bottom);
209	if (rightOfBitmap.IsValid())
210		FillRect(rightOfBitmap, B_SOLID_LOW);
211
212	BRect bottomOfBitmap(updateRect.left, fBitmapRect.bottom + 1, updateRect.right, updateRect.bottom);
213	if (bottomOfBitmap.IsValid())
214		FillRect(bottomOfBitmap, B_SOLID_LOW);
215
216	// indicate the frame to see any errors in the drawing code
217	rgb_color red = (rgb_color){ 255, 0, 0, 255 };
218	_DrawCross(fBitmapRect.LeftTop() + BPoint(-1.0, -1.0), red);
219	_DrawCross(fBitmapRect.RightTop() + BPoint(1.0, -1.0), red);
220	_DrawCross(fBitmapRect.LeftBottom() + BPoint(-1.0, 1.0), red);
221	_DrawCross(fBitmapRect.RightBottom() + BPoint(1.0, 1.0), red);
222
223	// text
224	SetDrawingMode(B_OP_ALPHA);
225	const char* message = "Click and drag to move and resize the bitmap!";
226	BPoint textPos(20.0, 30.0);
227	SetHighColor(255, 255, 255, 180);
228	DrawString(message, textPos);
229	SetHighColor(0, 0, 0, 180);
230	DrawString(message, textPos + BPoint(-1.0, -1.0));
231}
232
233// hit_test
234bool
235hit_test(BPoint where, BPoint p)
236{
237	BRect r(p, p);
238	r.InsetBy(-5.0, -5.0);
239	return r.Contains(where);
240}
241
242// hit_test
243bool
244hit_test(BPoint where, BPoint a, BPoint b)
245{
246	BRect r(a, b);
247	if (a.x == b.x)
248		r.InsetBy(-3.0, 0.0);
249	else
250		r.InsetBy(0.0, -3.0);
251	return r.Contains(where);
252}
253
254// MouseDown
255void
256TestView::MouseDown(BPoint where)
257{
258	fTracking = TRACKING_NONE;
259
260	// check if we hit a corner
261	if (hit_test(where, fBitmapRect.LeftTop()))
262		fTracking = TRACKING_LEFT_TOP;
263	else if (hit_test(where, fBitmapRect.RightTop()))
264		fTracking = TRACKING_RIGHT_TOP;
265	else if (hit_test(where, fBitmapRect.LeftBottom()))
266		fTracking = TRACKING_LEFT_BOTTOM;
267	else if (hit_test(where, fBitmapRect.RightBottom()))
268		fTracking = TRACKING_RIGHT_BOTTOM;
269	// check if we hit a side
270	else if (hit_test(where, fBitmapRect.LeftTop(), fBitmapRect.RightTop()))
271		fTracking = TRACKING_TOP;
272	else if (hit_test(where, fBitmapRect.LeftTop(), fBitmapRect.LeftBottom()))
273		fTracking = TRACKING_LEFT;
274	else if (hit_test(where, fBitmapRect.RightTop(), fBitmapRect.RightBottom()))
275		fTracking = TRACKING_RIGHT;
276	else if (hit_test(where, fBitmapRect.LeftBottom(), fBitmapRect.RightBottom()))
277		fTracking = TRACKING_BOTTOM;
278	// check if we hit inside the rect
279	else if (fBitmapRect.Contains(where))
280		fTracking = TRACKING_ALL;
281
282	fLastMousePos = where;
283}
284
285// MouseUp
286void
287TestView::MouseUp(BPoint where)
288{
289	fTracking = TRACKING_NONE;
290}
291
292// MouseMoved
293void
294TestView::MouseMoved(BPoint where, uint32 transit,
295					 const BMessage* dragMessage)
296{
297	if (fTracking > TRACKING_NONE) {
298		BRect old = fBitmapRect;
299		BPoint offset = where - fLastMousePos;
300		switch (fTracking) {
301			case TRACKING_LEFT_TOP:
302				fBitmapRect.Set(fBitmapRect.left + offset.x,
303								fBitmapRect.top + offset.y,
304								fBitmapRect.right,
305								fBitmapRect.bottom);
306				break;
307			case TRACKING_RIGHT_BOTTOM:
308				fBitmapRect.Set(fBitmapRect.left,
309								fBitmapRect.top,
310								fBitmapRect.right + offset.x,
311								fBitmapRect.bottom + offset.y);
312				break;
313			case TRACKING_LEFT_BOTTOM:
314				fBitmapRect.Set(fBitmapRect.left + offset.x,
315								fBitmapRect.top,
316								fBitmapRect.right,
317								fBitmapRect.bottom + offset.y);
318				break;
319			case TRACKING_RIGHT_TOP:
320				fBitmapRect.Set(fBitmapRect.left,
321								fBitmapRect.top + offset.y,
322								fBitmapRect.right + offset.x,
323								fBitmapRect.bottom);
324				break;
325			case TRACKING_LEFT:
326				fBitmapRect.Set(fBitmapRect.left + offset.x,
327								fBitmapRect.top,
328								fBitmapRect.right,
329								fBitmapRect.bottom);
330				break;
331			case TRACKING_TOP:
332				fBitmapRect.Set(fBitmapRect.left,
333								fBitmapRect.top + offset.y,
334								fBitmapRect.right,
335								fBitmapRect.bottom);
336				break;
337			case TRACKING_RIGHT:
338				fBitmapRect.Set(fBitmapRect.left,
339								fBitmapRect.top,
340								fBitmapRect.right + offset.x,
341								fBitmapRect.bottom);
342				break;
343			case TRACKING_BOTTOM:
344				fBitmapRect.Set(fBitmapRect.left,
345								fBitmapRect.top,
346								fBitmapRect.right,
347								fBitmapRect.bottom + offset.y);
348				break;
349			case TRACKING_ALL:
350			default:
351				fBitmapRect.OffsetBy(offset);
352				break;
353		}
354		fLastMousePos = where;
355		if (old != fBitmapRect)
356			_InvalidateBitmapRect(old | fBitmapRect);
357	}
358}
359
360// _ResetRect
361void
362TestView::_ResetRect()
363{
364	fBitmapRect = fBitmap->Bounds();
365	fBitmapRect.OffsetBy(floorf((Bounds().Width() - fBitmapRect.Width()) / 2.0 + 0.5),
366						 floorf((Bounds().Height() - fBitmapRect.Height()) / 2.0 + 0.5));
367}
368
369// _InvalidateBitmapRect
370void
371TestView::_InvalidateBitmapRect(BRect r)
372{
373	r.InsetBy(-4.0, -4.0);
374	Invalidate(r);
375}
376
377// _DrawCross
378void
379TestView::_DrawCross(BPoint where, rgb_color c)
380{
381	BeginLineArray(4);
382		AddLine(BPoint(where.x, where.y - 3),
383				BPoint(where.x, where.y - 1), c);
384		AddLine(BPoint(where.x, where.y + 1),
385				BPoint(where.x, where.y + 3), c);
386		AddLine(BPoint(where.x - 3, where.y),
387				BPoint(where.x - 1, where.y), c);
388		AddLine(BPoint(where.x + 1, where.y),
389				BPoint(where.x + 3, where.y), c);
390	EndLineArray();
391}
392
393// _FillBitmap
394void
395TestView::_FillBitmap(point* polygon)
396{
397	if (fBitmap->Lock()) {
398		fOffscreenView->SetDrawingMode(B_OP_COPY);
399		fOffscreenView->SetHighColor(0, 0, 0, 30);
400		fOffscreenView->FillRect(fOffscreenView->Bounds());
401
402		fOffscreenView->SetDrawingMode(B_OP_ALPHA);
403		fOffscreenView->SetHighColor(fColor[0].value,
404									 fColor[1].value,
405									 fColor[2].value,
406									 30);
407		fOffscreenView->SetPenSize(4);
408		fOffscreenView->SetLineMode(B_BUTT_CAP, B_ROUND_JOIN);
409
410		BPoint pointList[4];
411		pointList[0].x = polygon[0].x;
412		pointList[0].y = polygon[0].y;
413		pointList[1].x = polygon[1].x;
414		pointList[1].y = polygon[1].y;
415		pointList[2].x = polygon[2].x;
416		pointList[2].y = polygon[2].y;
417		pointList[3].x = polygon[3].x;
418		pointList[3].y = polygon[3].y;
419
420		fOffscreenView->StrokePolygon(pointList, 4);
421
422		fOffscreenView->Sync();
423		fBitmap->Unlock();
424	}
425}
426
427// _InitPolygon
428void
429TestView::_InitPolygon(const BRect& b, point* polygon) const
430{
431	polygon[0].x = b.left;
432	polygon[0].y = b.top;
433	polygon[0].direction_x = random_number_between(-SPEED, SPEED);
434	polygon[0].direction_y = random_number_between(-SPEED, SPEED);
435	polygon[0].velocity_x = 0.0;
436	polygon[0].velocity_y = 0.0;
437	polygon[1].x = b.right;
438	polygon[1].y = b.top;
439	polygon[1].direction_x = random_number_between(-SPEED, SPEED);
440	polygon[1].direction_y = random_number_between(-SPEED, SPEED);
441	polygon[1].velocity_x = 0.0;
442	polygon[1].velocity_y = 0.0;
443	polygon[2].x = b.right;
444	polygon[2].y = b.bottom;
445	polygon[2].direction_x = random_number_between(-SPEED, SPEED);
446	polygon[2].direction_y = random_number_between(-SPEED, SPEED);
447	polygon[2].velocity_x = 0.0;
448	polygon[2].velocity_y = 0.0;
449	polygon[3].x = b.left;
450	polygon[3].y = b.bottom;
451	polygon[3].direction_x = random_number_between(-SPEED, SPEED);
452	polygon[3].direction_y = random_number_between(-SPEED, SPEED);
453	polygon[3].velocity_x = 0.0;
454	polygon[3].velocity_y = 0.0;
455}
456
457// _InitColor
458void
459TestView::_InitColor(color_cycle* color) const
460{
461	color[0].value = 0;
462	color[0].direction = random_number_between(-SPEED * 4, SPEED * 4);
463	color[1].value = 0;
464	color[1].direction = random_number_between(-SPEED * 4, SPEED * 4);
465	color[2].value = 0;
466	color[2].direction = random_number_between(-SPEED * 4, SPEED * 4);
467}
468
469// morph
470inline void
471morph(double* value, double* direction, double* velocity, double min, double max)
472{
473	*value += *velocity;
474
475	// flip direction if necessary
476	if (*value < min && *direction < 0.0) {
477		*direction = -*direction;
478	} else if (*value > max && *direction > 0.0) {
479		*direction = -*direction;
480	}
481
482	// accelerate velocity
483	if (*direction < 0.0) {
484		if (*velocity > *direction)
485			*velocity += *direction / 10.0;
486		// truncate velocity
487		if (*velocity < *direction)
488			*velocity = *direction;
489	} else {
490		if (*velocity < *direction)
491			*velocity += *direction / 10.0;
492		// truncate velocity
493		if (*velocity > *direction)
494			*velocity = *direction;
495	}
496}
497
498// morph
499inline void
500morph(uint8* value, double* direction)
501{
502	int32 v = (int32)(*value + *direction);
503	if (v < 0) {
504		v = 0;
505		*direction = -*direction;
506	} else if (v > 255) {
507		v = 255;
508		*direction = -*direction;
509	}
510	*value = (uint8)v;
511}
512
513// _MorphPolygon
514void
515TestView::_MorphPolygon(const BRect& b, point* polygon)
516{
517	morph(&polygon[0].x, &polygon[0].direction_x, &polygon[0].velocity_x, b.left, b.right);
518	morph(&polygon[1].x, &polygon[1].direction_x, &polygon[1].velocity_x, b.left, b.right);
519	morph(&polygon[2].x, &polygon[2].direction_x, &polygon[2].velocity_x, b.left, b.right);
520	morph(&polygon[3].x, &polygon[3].direction_x, &polygon[3].velocity_x, b.left, b.right);
521	morph(&polygon[0].y, &polygon[0].direction_y, &polygon[0].velocity_y, b.top, b.bottom);
522	morph(&polygon[1].y, &polygon[1].direction_y, &polygon[1].velocity_y, b.top, b.bottom);
523	morph(&polygon[2].y, &polygon[2].direction_y, &polygon[2].velocity_y, b.top, b.bottom);
524	morph(&polygon[3].y, &polygon[3].direction_y, &polygon[3].velocity_y, b.top, b.bottom);
525}
526
527// _MorphColor
528void
529TestView::_MorphColor(color_cycle* color)
530{
531	morph(&color[0].value, &color[0].direction);
532	morph(&color[1].value, &color[1].direction);
533	morph(&color[2].value, &color[2].direction);
534}
535
536// show_window
537void
538show_window(BRect frame, const char* name)
539{
540	BWindow* window = new BWindow(frame, name,
541								  B_TITLED_WINDOW,
542								  B_ASYNCHRONOUS_CONTROLS | B_QUIT_ON_WINDOW_CLOSE);
543
544	BView* view = new TestView(window->Bounds(), "test", B_FOLLOW_ALL,
545							   B_WILL_DRAW/* | B_FULL_UPDATE_ON_RESIZE*/);
546
547	window->AddChild(view);
548	BRect b(0.0, 0.0, 60.0, 15.0);
549	b.OffsetTo(5.0, view->Bounds().bottom - (b.Height() + 15.0));
550	BButton* control = new BButton(b, "button", "Reset", new BMessage(MSG_RESET),
551								   B_FOLLOW_LEFT | B_FOLLOW_BOTTOM);
552	view->AddChild(control);
553	control->SetTarget(view);
554	control->SetViewUIColor(B_PANEL_BACKGROUND_COLOR);
555
556	window->Show();
557}
558
559// main
560int
561main(int argc, char** argv)
562{
563	BApplication* app = new BApplication("application/x.vnd-Haiku.BitmapDrawing");
564
565//	BRect frame(10.0, 30.0, 790.0, 590.0);
566	BRect frame(10.0, 30.0, 330.0, 220.0);
567	show_window(frame, "BitmapDrawing");
568
569	app->Run();
570
571	delete app;
572	return 0;
573}
574