1/*
2
3	StarWindow.cpp
4
5	by Pierre Raynaud-Richard.
6
7*/
8
9/*
10	Copyright 1999, Be Incorporated.   All Rights Reserved.
11	This file may be used under the terms of the Be Sample Code License.
12*/
13
14#include <Application.h>
15
16#include "StarWindow.h"
17#include "Stars.h"
18
19#include <string.h>
20#include <stdlib.h>
21
22#include <AppFileInfo.h>
23#include <FindDirectory.h>
24#include <Alert.h>
25#include <File.h>
26#include <Path.h>
27
28#include <Debug.h>
29
30// return the version_info of a file, described by its
31// name and its generic folder (in find_directory syntax).
32status_t get_file_version_info(	directory_which	dir,
33								char			*filename,
34								version_info	*info) {
35	BPath 			path;
36	BFile			file;
37	status_t		res;
38	BAppFileInfo	appinfo;
39
40	// find the directory
41	if ((res = find_directory(dir, &path)) != B_NO_ERROR)
42		return res;
43
44	// find the file
45	path.Append(filename);
46	file.SetTo(path.Path(), O_RDONLY);
47	if ((res = file.InitCheck()) != B_NO_ERROR)
48		return res;
49
50	// get the version_info
51	if ((res = appinfo.SetTo(&file)) != B_NO_ERROR)
52		return res;
53	return appinfo.GetVersionInfo(info, B_APP_VERSION_KIND);
54}
55
56enum {
57	// pseudo-random generator parameters (not very good ones,
58	// but good enough for what we do here).
59	CRC_START		= 0x56dec231,
60	CRC_KEY			= 0x1789feb3
61};
62
63
64StarWindow::StarWindow(BRect frame, const char *name)
65	: BDirectWindow(frame, name, B_TITLED_WINDOW, 0)
66{
67	uint32			i;
68	int32			x, y, dx, dy, cnt, square;
69
70	// init the crc pseudo-random generator
71	crc_alea = CRC_START;
72
73	// allocate the star struct array
74	star_count_max = 8192;
75	star_count = 0;
76	star_list = (star*)malloc(sizeof(star)*star_count_max);
77
78	// initialise the default state of the star array
79	for (i = 0; i < star_count_max; i++) {
80		// peek a random vector. This is certainly not the nicest way
81		// to do it (the probability and the angle are linked), but that's
82		// simple and doesn't require any trigonometry.
83		do {
84			dx = (crc_alea&0xffff) - 0x8000;
85			CrcStep();
86			CrcStep();
87
88			dy = (crc_alea&0xffff) - 0x8000;
89			CrcStep();
90			CrcStep();
91		} while ((dx == 0) && (dy == 0));
92
93		// enforce a minimal length by doubling the vector as many times
94		// as needed.
95		square = dx*dx+dy*dy;
96		while (square < 0x08000000) {
97			dx <<= 1;
98			dy <<= 1;
99			square <<= 2;
100		}
101
102		// save the starting speed vector.
103		star_list[i].dx0 = dx;
104		star_list[i].dy0 = dy;
105
106		// simulate the animation to see how many moves are needed to
107		// get out by at least 1024 in one direction. That will give us
108		// an minimal value for how long we should wait before restarting
109		// the animation. It wouldn't work if the window was getting
110		// much larger than 2048 pixels in one dimension.
111		cnt = 0;
112		x = 0;
113		y = 0;
114		while ((x<0x4000000) && (x>-0x4000000) && (y<0x4000000) && (y>-0x4000000)) {
115			x += dx;
116			y += dy;
117			dx += (dx>>4);
118			dy += (dy>>4);
119			cnt++;
120		}
121
122		// add a random compenent [0 to 15] to the minimal count before
123		// restart.
124		star_list[i].count0 = cnt + ((crc_alea&0xf0000)>>16);
125		// make the star initialy invisible and fixed, then spread the
126		// value of their restart countdown so that they won't start all
127		// at the same time, but progressively
128		star_list[i].last_draw = INVALID;
129		star_list[i].x = 0x40000000;
130		star_list[i].y = 0x40000000;
131		star_list[i].dx = 0;
132		star_list[i].dy = 0;
133		star_list[i].count = (i&255);
134	}
135
136	// allocate the semaphore used to synchronise the star animation drawing access.
137	drawing_lock = create_sem(0, "star locker");
138
139	// spawn the star animation thread (we have better set the force quit flag to
140	// false first).
141	kill_my_thread = false;
142	my_thread = spawn_thread(StarWindow::StarAnimation, "StarAnimation",
143							 B_DISPLAY_PRIORITY, (void*)this);
144	resume_thread(my_thread);
145
146	// add a view in the background to insure that the content area will
147	// be properly erased in black. This erase mechanism is not synchronised
148	// with the star animaton, which means that from time to time, some
149	// stars will be erreneously erased by the view redraw. But as every
150	// single star is erased and redraw every frame, that graphic glitch
151	// will last less than a frame, and that just in the area being redraw
152	// because of a window resize, move... Which means the glitch won't
153	// be noticeable. The other solution for erasing the background would
154	// have been to do it on our own (which means some serious region
155	// calculation and handling all color_space). Better to use the kit
156	// to do it, as it gives us access to hardware acceleration...
157	frame.OffsetTo(0.0, 0.0);
158	//view = new BView(frame, "", B_FOLLOW_ALL, B_WILL_DRAW);
159
160	// The only think we want from the view mechanism is to
161	// erase the background in black. Because of the way the
162	// star animation is done, this erasing operation doesn't
163	// need to be synchronous with the animation. That the
164	// reason why we use both the direct access and the view
165	// mechanism to draw in the same area of the StarWindow.
166	// Such thing is usualy not recommended as synchronisation
167	// is generally an issue (drawing in random order usualy
168	// gives remanent incorrect result).
169	// set the view color to be black (nicer update).
170	//view->SetViewColor(0, 0, 0);
171	//AddChild(view);
172
173	// Add a shortcut to switch in and out of fullscreen mode.
174	AddShortcut('f', B_COMMAND_KEY, new BMessage('full'));
175
176	// As we said before, the window shouldn't get wider than 2048 in any
177	// direction, so those limits will do.
178	SetSizeLimits(40.0, 2000.0, 40.0, 2000.0);
179
180	// If the graphic card/graphic driver we use doesn't support directwindow
181	// in window mode, then we need to switch to fullscreen immediately, or
182	// the user won't see anything, as long as it doesn't used the undocumented
183	// shortcut. That would be bad behavior...
184	if (!BDirectWindow::SupportsWindowMode()) {
185		bool		sSwapped;
186		char		*buf;
187		BAlert		*quit_alert;
188
189		key_map *map;
190		get_key_map(&map, &buf);
191
192		if (map != NULL) {
193			sSwapped = (map->left_control_key == 0x5d)
194				&& (map->left_command_key == 0x5c);
195		} else
196			sSwapped = false;
197
198		free(map);
199		free(buf);
200		quit_alert = new BAlert("QuitAlert", sSwapped ?
201		                        "This demo runs only in full screen mode.\n"
202		                        "While running, press 'Ctrl-Q' to quit.":
203		                        "This demo runs only in full screen mode.\n"
204		                        "While running, press 'Alt-Q' to quit.",
205		                        "Quit", "Start demo", NULL,
206		                        B_WIDTH_AS_USUAL, B_WARNING_ALERT);
207		if (quit_alert->Go() == 0)
208			((StarsApp*)be_app)->abort_required = true;
209		else
210			SetFullScreen(true);
211	}
212}
213
214
215StarWindow::~StarWindow()
216{
217	// force the drawing_thread to quit. This is the easiest way to deal
218	// with potential closing problem. When it's not practical, we
219	// recommand to use Hide() and Sync() to force the disconnection of
220	// the direct access, and use some other flag to guarantee that your
221	// drawing thread won't draw anymore. After that, you can pursue the
222	// window destructor and kill your drawing thread...
223	kill_my_thread = true;
224	delete_sem(drawing_lock);
225
226	status_t result;
227	wait_for_thread(my_thread, &result);
228
229	// Free window resources. As they're used by the drawing thread, we
230	// need to terminate that thread before freeing them, or we could crash.
231	free(star_list);
232}
233
234
235bool
236StarWindow::QuitRequested()
237{
238	be_app->PostMessage(B_QUIT_REQUESTED);
239	return true;
240}
241
242
243void
244StarWindow::MessageReceived(BMessage *message)
245{
246	int8 key_code;
247
248	switch (message->what) {
249		// Switch between full-screen mode and windowed mode.
250		case 'full':
251			SetFullScreen(!IsFullScreen());
252			break;
253		case B_KEY_DOWN:
254			if (!IsFullScreen())
255				break;
256			if (message->FindInt8("byte", &key_code) != B_OK)
257				break;
258			if (key_code == B_ESCAPE)
259				PostMessage(B_QUIT_REQUESTED);
260			break;
261		default:
262			BDirectWindow::MessageReceived(message);
263			break;
264	}
265}
266
267
268void
269StarWindow::CrcStep()
270{
271	// basic crc pseudo-random generator
272	crc_alea <<= 1;
273	if (crc_alea < 0)
274		crc_alea ^= CRC_KEY;
275}
276
277
278void
279StarWindow::DirectConnected(direct_buffer_info *info)
280{
281	// you need to use that mask to read the buffer state.
282	switch (info->buffer_state & B_DIRECT_MODE_MASK) {
283		// start a direct screen connection.
284		case B_DIRECT_START:
285			SwitchContext(info);		// update the direct screen infos.
286			release_sem(drawing_lock);	// unblock the animation thread.
287			break;
288		// stop a direct screen connection.
289		case B_DIRECT_STOP:
290			acquire_sem(drawing_lock);	// block the animation thread.
291			break;
292		// modify the state of a direct screen connection.
293		case B_DIRECT_MODIFY:
294			acquire_sem(drawing_lock);	// block the animation thread.
295			SwitchContext(info);		// update the direct screen infos.
296			release_sem(drawing_lock);	// unblock the animation thread.
297			break;
298
299		default:
300			break;
301	}
302}
303
304
305// This function update the internal graphic context of the StarWindow
306// object to reflect the infos send through the DirectConnected API.
307// It also update the state of stars (and erase some of them) to
308// insure a clean transition during resize. As this function is called
309// in DirectConnected, it's a bad idea to do any heavy drawing (long)
310// operation. But as we only update the stars (the background will be
311// updated a little later by the view system), it's not a big deal.
312void
313StarWindow::SwitchContext(direct_buffer_info *info)
314{
315	star			*s;
316	int32			x, y, deltax, deltay;
317	uint32			i, j, window_area, cx, cy;
318	uint32			star_count_new;
319	clipping_rect	*r;
320
321	// calculate the new star count, depending the size of the window frame.
322	// we do that because we want to keep the star count proportionnal to
323	// the size of the window, to keep an similar overall star density feeling
324	window_area = (info->window_bounds.right-info->window_bounds.left+1)*
325				  (info->window_bounds.bottom-info->window_bounds.top+1);
326	// max out beyond 1M pixels.
327	if (window_area > (1<<20))
328		window_area = (1<<20);
329	star_count_new = (star_count_max*(window_area>>10))>>10;
330	if (star_count_new > star_count_max)
331		star_count_new = star_count_max;
332
333	// set the position of the new center of the window (in case of move or resize)
334	cx = (info->window_bounds.right+info->window_bounds.left+1)/2;
335	cy = (info->window_bounds.bottom+info->window_bounds.top+1)/2;
336
337	// update to the new clipping region. The local copy is kept relative
338	// to the center of the animation (origin of the star coordinate).
339	clipping_bound.left = info->clip_bounds.left - cx;
340	clipping_bound.right = info->clip_bounds.right - cx;
341	clipping_bound.top = info->clip_bounds.top - cy;
342	clipping_bound.bottom = info->clip_bounds.bottom - cy;
343	// the clipping count is bounded (see comment in header file).
344	clipping_list_count = info->clip_list_count;
345	if (clipping_list_count > MAX_CLIPPING_RECT_COUNT)
346		clipping_list_count = MAX_CLIPPING_RECT_COUNT;
347	for (i=0; i<clipping_list_count; i++) {
348		clipping_list[i].left = info->clip_list[i].left - cx;
349		clipping_list[i].right = info->clip_list[i].right - cx;
350		clipping_list[i].top = info->clip_list[i].top - cy;
351		clipping_list[i].bottom = info->clip_list[i].bottom - cy;
352	}
353
354	// update the new rowbyte
355	// NOTE: "row_bytes" is completely misnamed, and was misused too
356	row_bytes = info->bytes_per_row / (info->bits_per_pixel / 8);
357
358	// update the screen bases (only one of the 3 will be really used).
359	draw_ptr8 = (uint8*)info->bits + info->bytes_per_row
360		* info->window_bounds.top + info->window_bounds.left
361		* (info->bits_per_pixel / 8);
362		// Note: parenthesis around "info->bits_per_pixel / 8"
363		// are needed to avoid an overflow when info->window_bounds.left
364		// becomes negative.
365
366	draw_ptr16 = (uint16*)draw_ptr8;
367	draw_ptr32 = (uint32*)draw_ptr8;
368
369	// cancel the erasing of all stars if the buffer has been reset.
370	// Because of a bug in the R3 direct window protocol, B_BUFFER_RESET is not set
371	// whew showing a previously hidden window. The second test is a reasonnable
372	// way to work around that bug...
373	if ((info->buffer_state & B_BUFFER_RESET) ||
374		(need_r3_buffer_reset_work_around &&
375		 ((info->buffer_state & (B_DIRECT_MODE_MASK|B_BUFFER_MOVED)) == B_DIRECT_START))) {
376		s = star_list;
377		for (i=0; i<star_count_max; i++) {
378			s->last_draw = INVALID;
379			s++;
380		}
381	}
382	// in the other case, update the stars that will stay visible.
383	else {
384		// calculate the delta vector due to window resize or move.
385		deltax = cx_old - (cx - info->window_bounds.left);
386		deltay = cy_old - (cy - info->window_bounds.top);
387		// check all the stars previously used.
388		s = star_list;
389		for (i=0; i<star_count; i++) {
390			// if the star wasn't visible before, then no more question.
391			if (s->last_draw == INVALID)
392				goto not_defined;
393			// convert the old position into the new referential.
394			x = (s->x>>16) + deltax;
395			y = (s->y>>16) + deltay;
396			// check if the old position is still visible in the new clipping
397			if ((x < clipping_bound.left) || (x > clipping_bound.right) ||
398				(y < clipping_bound.top) || (y > clipping_bound.bottom))
399				goto invisible;
400			if (clipping_list_count == 1)
401				goto visible;
402			r = clipping_list;
403			for (j=0; j<clipping_list_count; j++) {
404				if ((x >= r->left) && (x <= r->right) &&
405					(y >= r->top) && (y <= r->bottom))
406					goto visible;
407				r++;
408			}
409			goto invisible;
410
411			// if it's still visible...
412		visible:
413			if (i >= star_count_new) {
414				// ...and the star won't be used anylonger, then we erase it.
415				if (pixel_depth == 32)
416					draw_ptr32[s->last_draw] = 0;
417				else if (pixel_depth == 16)
418					draw_ptr16[s->last_draw] = 0;
419				else
420					draw_ptr8[s->last_draw] = 0;
421			}
422			goto not_defined;
423
424			// if the star just became invisible and it was because the
425			// context was modified and not fully stop, then we need to erase
426			// those stars who just became invisible (or they could leave
427			// artefacts in the drawing area in some cases). This problem is
428			// a side effect of the interaction between a complex resizing
429			// case (using 2 B_DIRECT_MODIFY per step), and the dynamic
430			// star count management we are doing. In most case, you never
431			// have to erase things going out of the clipping region...
432		invisible:
433			if ((info->buffer_state & B_DIRECT_MODE_MASK) == B_DIRECT_MODIFY) {
434				if (pixel_depth == 32)
435					draw_ptr32[s->last_draw] = 0;
436				else if (pixel_depth == 16)
437					draw_ptr16[s->last_draw] = 0;
438				else
439					draw_ptr8[s->last_draw] = 0;
440			}
441			// and set its last position as invalid.
442			s->last_draw = INVALID;
443		not_defined:
444			s++;
445		}
446
447		// initialise all the new star (the ones which weren't used but
448		// will be use after that context update) to set their last position
449		// as invalid.
450		s = star_list+star_count;
451		for (i=star_count; i<star_count_new; i++) {
452			s->last_draw = INVALID;
453			s++;
454		}
455	}
456
457	// update the window origin offset.
458	window_offset = row_bytes*(cy-info->window_bounds.top) + (cx-info->window_bounds.left);
459
460	// set the pixel_depth and the pixel data, from the color_space.
461	switch (info->pixel_format) {
462	case B_RGBA32 :
463	case B_RGB32 :
464		pixel_depth = 32;
465		((uint8*)&pixel32)[0] = 0x20;
466		((uint8*)&pixel32)[1] = 0xff;
467		((uint8*)&pixel32)[2] = 0x20;
468		((uint8*)&pixel32)[3] = 0xff;
469		break;
470	case B_RGB16 :
471		pixel_depth = 16;
472		((uint8*)&pixel16)[0] = 0xe0;
473		((uint8*)&pixel16)[1] = 0x07;
474		break;
475	case B_RGB15 :
476	case B_RGBA15 :
477		pixel_depth = 16;
478		((uint8*)&pixel16)[0] = 0xe0;
479		((uint8*)&pixel16)[1] = 0x03;
480		break;
481	case B_CMAP8 :
482		pixel_depth = 8;
483		pixel8 = 52;
484		break;
485	case B_RGBA32_BIG :
486	case B_RGB32_BIG :
487		pixel_depth = 32;
488		((uint8*)&pixel32)[3] = 0x20;
489		((uint8*)&pixel32)[2] = 0xff;
490		((uint8*)&pixel32)[1] = 0x20;
491		((uint8*)&pixel32)[0] = 0xff;
492		break;
493	case B_RGB16_BIG :
494		pixel_depth = 16;
495		((uint8*)&pixel16)[1] = 0xe0;
496		((uint8*)&pixel16)[0] = 0x07;
497		break;
498	case B_RGB15_BIG :
499	case B_RGBA15_BIG :
500		pixel_depth = 16;
501		((uint8*)&pixel16)[1] = 0xe0;
502		((uint8*)&pixel16)[0] = 0x03;
503		break;
504	default:	// unsupported color space?
505		fprintf(stderr, "ERROR - unsupported color space!\n");
506		exit(1);
507		break;
508	}
509
510	// set the new star count.
511	star_count = star_count_new;
512
513	// save a copy of the variables used to calculate the move of the window center
514	cx_old = cx - info->window_bounds.left;
515	cy_old = cy - info->window_bounds.top;
516}
517
518
519// This is the thread doing the star animation itself. It would be easy to
520// adapt to do any other sort of pixel animation.
521status_t
522StarWindow::StarAnimation(void *data)
523{
524	star			*s;
525	int32			x, y;
526	uint32			i, j;
527	bigtime_t		time;
528	StarWindow		*w;
529	clipping_rect	*r;
530
531	// receive a pointer to the StarWindow object.
532	w = (StarWindow*)data;
533
534	// loop, frame after frame, until asked to quit.
535	while (!w->kill_my_thread) {
536		// we want a frame to take at least 16 ms.
537		time = system_time()+16000;
538
539		// get the right to do direct screen access.
540		while (acquire_sem(w->drawing_lock) == B_INTERRUPTED)
541			;
542		if (w->kill_my_thread)
543			break;
544
545		// go through the array of star, for all currently used star.
546		s = w->star_list;
547		for (i=0; i<w->star_count; i++) {
548			if (s->count == 0) {
549				// restart the star animation, from a random point close to
550				// the center [-16, +15], both axis.
551				x = s->x = ((w->crc_alea&0x1f00)>>8) - 16;
552				y = s->y = ((w->crc_alea&0x1f0000)>>16) - 16;
553				s->dx = s->dx0;
554				s->dy = s->dy0;
555				// add a small random component to the duration of the star
556				s->count = s->count0 + (w->crc_alea&0x7);
557				w->CrcStep();
558			}
559			else {
560				// just move the star
561				s->count--;
562				x = s->x += s->dx;
563				y = s->y += s->dy;
564				s->dx += (s->dx>>4);
565				s->dy += (s->dy>>4);
566			}
567			// erase the previous position, if necessary
568			if (s->last_draw != INVALID) {
569				if (w->pixel_depth == 32)
570					w->draw_ptr32[s->last_draw] = 0;
571				else if (w->pixel_depth == 16)
572					w->draw_ptr16[s->last_draw] = 0;
573				else
574					w->draw_ptr8[s->last_draw] = 0;
575			}
576			// check if the new position is visible in the current clipping
577			x >>= 16;
578			y >>= 16;
579			if ((x < w->clipping_bound.left) || (x > w->clipping_bound.right) ||
580				(y < w->clipping_bound.top) || (y > w->clipping_bound.bottom))
581				goto invisible;
582			if (w->clipping_list_count == 1) {
583		visible:
584				// if it's visible, then draw it.
585				s->last_draw = w->window_offset + w->row_bytes*y + x;
586				if (w->pixel_depth == 32)
587					w->draw_ptr32[s->last_draw] = w->pixel32;
588				else if (w->pixel_depth == 16)
589					w->draw_ptr16[s->last_draw] = w->pixel16;
590				else
591					w->draw_ptr8[s->last_draw] = w->pixel8;
592				goto loop;
593			}
594			// handle complex clipping cases
595			r = w->clipping_list;
596			for (j=0; j<w->clipping_list_count; j++) {
597				if ((x >= r->left) && (x <= r->right) &&
598					(y >= r->top) && (y <= r->bottom))
599					goto visible;
600				r++;
601			}
602		invisible:
603			// if not visible, register the fact that the star wasn't draw.
604			s->last_draw = INVALID;
605		loop:
606			s++;
607		}
608
609		// release the direct screen access
610		release_sem(w->drawing_lock);
611
612		// snooze for whatever time is left from the initial allocation done
613		// at the beginning of the loop.
614		time -= system_time();
615		if (time > 0)
616			snooze(time);
617	}
618	return 0;
619}
620
621