xref: /haiku/src/apps/mandelbrot/Mandelbrot.cpp (revision 05fc1277c47440dc36134816d70e5723c99cfcd2)
1 /*
2  * Copyright 2016, Haiku, Inc. All rights reserved.
3  * Distributed under the terms of the MIT license.
4  *
5  * Authors:
6  *		Augustin Cavalier <waddlesplash>
7  */
8 
9 
10 #include <AboutWindow.h>
11 #include <Application.h>
12 #include <Bitmap.h>
13 #include <Catalog.h>
14 #include <MenuBar.h>
15 #include <LayoutBuilder.h>
16 #include <View.h>
17 #include <Window.h>
18 
19 #include <algorithm>
20 
21 #include "FractalEngine.h"
22 
23 #undef B_TRANSLATION_CONTEXT
24 #define B_TRANSLATION_CONTEXT "MandelbrotWindow"
25 
26 
27 // #pragma mark - FractalView
28 
29 
30 class FractalView : public BView {
31 public:
32 	FractalView();
33 	~FractalView();
34 
35 	virtual void AttachedToWindow();
36 	virtual void FrameResized(float, float);
37 	virtual void Pulse();
38 
39 	virtual void MouseDown(BPoint where);
40 	virtual void MouseMoved(BPoint where, uint32 mode, const BMessage*);
41 	virtual void MouseUp(BPoint where);
42 
43 	virtual void MessageReceived(BMessage* msg);
44 	virtual void Draw(BRect updateRect);
45 
46 			void ResetPosition();
47 			void RedrawFractal();
48 			FractalEngine* fFractalEngine;
49 
50 private:
51 			BRect GetDragFrame();
52 
53 	bool fSizeChanged;
54 	bool fOwnBitmap;
55 
56 	BPoint fSelectStart;
57 	BPoint fSelectEnd;
58 	bool fSelecting;
59 	uint32 fMouseButtons;
60 
61 	BBitmap* fDisplayBitmap;
62 
63 	double fLocationX;
64 	double fLocationY;
65 	double fSize;
66 };
67 
68 
69 FractalView::FractalView()
70 	:
71 	BView(NULL, B_WILL_DRAW | B_FRAME_EVENTS | B_PULSE_NEEDED),
72 	fFractalEngine(NULL),
73 	fSizeChanged(false),
74 	fOwnBitmap(false),
75 	fSelecting(false),
76 	fDisplayBitmap(NULL),
77 	fLocationX(0),
78 	fLocationY(0),
79 	fSize(0.005)
80 {
81 	SetHighColor(make_color(255, 255, 255, 255));
82 }
83 
84 
85 FractalView::~FractalView()
86 {
87 	if (fOwnBitmap)
88 		delete fDisplayBitmap;
89 }
90 
91 
92 void FractalView::ResetPosition()
93 {
94 	fLocationX = 0;
95 	fLocationY = 0;
96 	fSize = 0.005;
97 }
98 
99 
100 void FractalView::AttachedToWindow()
101 {
102 	fFractalEngine = new FractalEngine(this, Window());
103 	fFractalEngine->Run();
104 	BMessage msg(FractalEngine::MSG_RESIZE);
105 	msg.AddUInt16("width", 641);
106 	msg.AddUInt16("height", 462);
107 	fFractalEngine->PostMessage(&msg);
108 	RedrawFractal();
109 }
110 
111 
112 void FractalView::FrameResized(float, float)
113 {
114 	fSizeChanged = true;
115 }
116 
117 
118 void FractalView::Pulse()
119 {
120 	if (!fSizeChanged)
121 		return;
122 	BMessage msg(FractalEngine::MSG_RESIZE);
123 	msg.AddUInt16("width", (uint16)Frame().Width() + 1);
124 	msg.AddUInt16("height", (uint16)Frame().Height() + 1);
125 	fFractalEngine->PostMessage(&msg);
126 	// The renderer will create new bitmaps, so we own the bitmap now
127 	fOwnBitmap = true;
128 	fSizeChanged = false;
129 	RedrawFractal();
130 }
131 
132 
133 BRect FractalView::GetDragFrame()
134 {
135 	BRect dragZone = BRect(std::min(fSelectStart.x, fSelectEnd.x),
136 		std::min(fSelectStart.y, fSelectEnd.y),
137 		std::max(fSelectStart.x, fSelectEnd.x),
138 		std::max(fSelectStart.y, fSelectEnd.y)),
139 		frame = Frame();
140 	float width = dragZone.Width(),
141 		height = width * (frame.Height() / frame.Width());
142 
143 	float x1 = fSelectStart.x, y1 = fSelectStart.y,	x2, y2;
144 	if (fSelectStart.x < fSelectEnd.x)
145 		x2 = x1 + width;
146 	else
147 		x2 = x1 - width;
148 	if (fSelectStart.y < fSelectEnd.y)
149 		y2 = y1 + height;
150 	else
151 		y2 = y1 - height;
152 	return BRect(x1, y1, x2, y2);
153 }
154 
155 
156 void FractalView::MouseDown(BPoint where)
157 {
158 	fSelecting = true;
159 	fSelectStart = where;
160 	GetMouse(&where, &fMouseButtons);
161 }
162 
163 
164 void FractalView::MouseMoved(BPoint where, uint32 mode, const BMessage*)
165 {
166 	if (fSelecting) {
167 		fSelectEnd = where;
168 		Invalidate();
169 	}
170 }
171 
172 
173 void FractalView::MouseUp(BPoint where)
174 {
175 	BRect frame = Frame();
176 	fSelecting = false;
177 	if (fabs(fSelectStart.x - where.x) > 4) {
178 		fSelectEnd = where;
179 		BRect dragFrame = GetDragFrame();
180 		BPoint lt = dragFrame.LeftTop();
181 		float centerX = lt.x + dragFrame.Width() / 2,
182 			centerY = lt.y + dragFrame.Height() / 2;
183 		fLocationX = ((centerX - frame.Width() / 2) * fSize + fLocationX);
184 		fLocationY = ((centerY - frame.Height() / 2) * -fSize + fLocationY);
185 
186 		fSize = (dragFrame.Width() * fSize) / frame.Width();
187 	} else {
188 		fLocationX = ((where.x - frame.Width() / 2) * fSize + fLocationX);
189 		fLocationY = ((where.y - frame.Height() / 2) * -fSize + fLocationY);
190 		if (fMouseButtons & B_PRIMARY_MOUSE_BUTTON)
191 			fSize /= 2;
192 		else
193 			fSize *= 2;
194 	}
195 	RedrawFractal();
196 }
197 
198 
199 void FractalView::MessageReceived(BMessage* msg)
200 {
201 	switch (msg->what) {
202 	case B_MOUSE_WHEEL_CHANGED: {
203 		float change = msg->FindFloat("be:wheel_delta_y");
204 		BPoint where;
205 		GetMouse(&where, NULL);
206 		BRect frame = Frame();
207 		fLocationX = ((where.x - frame.Width() / 2) * fSize + fLocationX);
208 		fLocationY = ((where.y - frame.Height() / 2) * -fSize + fLocationY);
209 		if (change < 0)
210 			fSize /= 1.5;
211 		else
212 			fSize *= 1.5;
213 		RedrawFractal();
214 		break;
215 	}
216 
217 	case FractalEngine::MSG_RENDER_COMPLETE:
218 		if (fOwnBitmap) {
219 			fOwnBitmap = false;
220 			delete fDisplayBitmap;
221 		}
222 		fDisplayBitmap = NULL; // In case the following line fails
223 		msg->FindPointer("bitmap", (void**)&fDisplayBitmap);
224 		Invalidate();
225 		break;
226 
227 	default:
228 		BView::MessageReceived(msg);
229 		break;
230 	}
231 }
232 
233 
234 void FractalView::RedrawFractal()
235 {
236 	BMessage message(FractalEngine::MSG_RENDER);
237 	message.AddDouble("locationX", fLocationX);
238 	message.AddDouble("locationY", fLocationY);
239 	message.AddDouble("size", fSize);
240 	fFractalEngine->PostMessage(&message);
241 }
242 
243 
244 void FractalView::Draw(BRect updateRect)
245 {
246 	DrawBitmap(fDisplayBitmap, updateRect, updateRect);
247 
248 	if (fSelecting) {
249 		StrokeRect(GetDragFrame());
250 	}
251 }
252 
253 
254 // #pragma mark - MandelbrotWindow
255 
256 
257 class MandelbrotWindow : public BWindow
258 {
259 public:
260 	enum {
261 		MSG_MANDELBROT_SET = 'MndW',
262 		MSG_BURNINGSHIP_SET,
263 		MSG_TRICORN_SET,
264 		MSG_JULIA_SET,
265 		MSG_ORBITTRAP_SET,
266 		MSG_MULTIBROT_SET,
267 
268 		MSG_ROYAL_PALETTE,
269 		MSG_DEEPFROST_PALETTE,
270 		MSG_FROST_PALETTE,
271 		MSG_FIRE_PALETTE,
272 		MSG_MIDNIGHT_PALETTE,
273 		MSG_GRASSLAND_PALETTE,
274 		MSG_LIGHTNING_PALETTE,
275 		MSG_SPRING_PALETTE,
276 		MSG_HIGHCONTRAST_PALETTE,
277 
278 		MSG_ITER_128,
279 		MSG_ITER_512,
280 		MSG_ITER_1024,
281 		MSG_ITER_4096,
282 		MSG_ITER_8192,
283 		MSG_ITER_12288,
284 		MSG_ITER_16384
285 	};
286 				MandelbrotWindow(BRect frame);
287 				~MandelbrotWindow() {}
288 
289 	virtual void MessageReceived(BMessage* msg);
290 	virtual bool QuitRequested();
291 
292 private:
293 		FractalView* fFractalView;
294 };
295 
296 
297 MandelbrotWindow::MandelbrotWindow(BRect frame)
298 	:
299 	BWindow(frame, B_TRANSLATE_SYSTEM_NAME("Mandelbrot"), B_TITLED_WINDOW_LOOK,
300 		B_NORMAL_WINDOW_FEEL, 0L),
301 	fFractalView(new FractalView)
302 {
303 	SetPulseRate(250000); // pulse twice per second
304 
305 	BMenuBar* menuBar = new BMenuBar("MenuBar");
306 	BMenu* setMenu;
307 	BMenu* paletteMenu;
308 	BMenu* iterMenu;
309 	BLayoutBuilder::Menu<>(menuBar)
310 		.AddMenu(B_TRANSLATE("File"))
311 			.AddItem(B_TRANSLATE("About"), B_ABOUT_REQUESTED)
312 			.AddItem(B_TRANSLATE("Quit"), B_QUIT_REQUESTED, 'Q')
313 		.End()
314 		.AddMenu(B_TRANSLATE("Set"))
315 			.GetMenu(setMenu)
316 			.AddItem(B_TRANSLATE("Mandelbrot"), MSG_MANDELBROT_SET)
317 			.AddItem(B_TRANSLATE("Burning Ship"), MSG_BURNINGSHIP_SET)
318 			.AddItem(B_TRANSLATE("Tricorn"), MSG_TRICORN_SET)
319 			.AddItem(B_TRANSLATE("Julia"), MSG_JULIA_SET)
320 			.AddItem(B_TRANSLATE("Orbit Trap"), MSG_ORBITTRAP_SET)
321 			.AddItem(B_TRANSLATE("Multibrot"), MSG_MULTIBROT_SET)
322 		.End()
323 		.AddMenu(B_TRANSLATE("Palette"))
324 			.GetMenu(paletteMenu)
325 			.AddItem(B_TRANSLATE("Royal"), MSG_ROYAL_PALETTE)
326 			.AddItem(B_TRANSLATE("Deepfrost"), MSG_DEEPFROST_PALETTE)
327 			.AddItem(B_TRANSLATE("Frost"), MSG_FROST_PALETTE)
328 			.AddItem(B_TRANSLATE("Fire"), MSG_FIRE_PALETTE)
329 			.AddItem(B_TRANSLATE("Midnight"), MSG_MIDNIGHT_PALETTE)
330 			.AddItem(B_TRANSLATE("Grassland"), MSG_GRASSLAND_PALETTE)
331 			.AddItem(B_TRANSLATE("Lightning"), MSG_LIGHTNING_PALETTE)
332 			.AddItem(B_TRANSLATE("Spring"), MSG_SPRING_PALETTE)
333 			.AddItem(B_TRANSLATE("High contrast"), MSG_HIGHCONTRAST_PALETTE)
334 		.End()
335 		.AddMenu(B_TRANSLATE("Iterations"))
336 			.GetMenu(iterMenu)
337 			.AddItem("128", MSG_ITER_128)
338 			.AddItem("512", MSG_ITER_512)
339 			.AddItem("1024", MSG_ITER_1024)
340 			.AddItem("4096", MSG_ITER_4096)
341 			.AddItem("8192", MSG_ITER_8192)
342 			.AddItem("12288", MSG_ITER_12288)
343 			.AddItem("16384", MSG_ITER_16384)
344 		.End()
345 	.End();
346 	setMenu->SetRadioMode(true);
347 	setMenu->FindItem(MSG_MANDELBROT_SET)->SetMarked(true);
348 	paletteMenu->SetRadioMode(true);
349 	paletteMenu->FindItem(MSG_ROYAL_PALETTE)->SetMarked(true);
350 	iterMenu->SetRadioMode(true);
351 	iterMenu->FindItem(MSG_ITER_1024)->SetMarked(true);
352 
353 	BLayoutBuilder::Group<>(this, B_VERTICAL, 0)
354 		.SetInsets(0)
355 		.Add(menuBar)
356 		.Add(fFractalView)
357 	.End();
358 }
359 
360 
361 #define HANDLE_SET(uiwhat, id) \
362 	case uiwhat: { \
363 		BMessage msg(FractalEngine::MSG_CHANGE_SET); \
364 		msg.AddUInt8("set", id); \
365 		fFractalView->fFractalEngine->PostMessage(&msg); \
366 		fFractalView->ResetPosition(); \
367 		fFractalView->RedrawFractal(); \
368 		break; \
369 	}
370 #define HANDLE_PALETTE(uiwhat, id) \
371 	case uiwhat: { \
372 		BMessage msg(FractalEngine::MSG_SET_PALETTE); \
373 		msg.AddUInt8("palette", id); \
374 		fFractalView->fFractalEngine->PostMessage(&msg); \
375 		fFractalView->RedrawFractal(); \
376 		break; \
377 	}
378 #define HANDLE_ITER(uiwhat, id) \
379 	case uiwhat: { \
380 		BMessage msg(FractalEngine::MSG_SET_ITERATIONS); \
381 		msg.AddUInt16("iterations", id); \
382 		fFractalView->fFractalEngine->PostMessage(&msg); \
383 		fFractalView->RedrawFractal(); \
384 		break; \
385 	}
386 void
387 MandelbrotWindow::MessageReceived(BMessage* msg)
388 {
389 	switch (msg->what) {
390 	HANDLE_SET(MSG_MANDELBROT_SET, 0)
391 	HANDLE_SET(MSG_BURNINGSHIP_SET, 1)
392 	HANDLE_SET(MSG_TRICORN_SET, 2)
393 	HANDLE_SET(MSG_JULIA_SET, 3)
394 	HANDLE_SET(MSG_ORBITTRAP_SET, 4)
395 	HANDLE_SET(MSG_MULTIBROT_SET, 5)
396 
397 	HANDLE_PALETTE(MSG_ROYAL_PALETTE, 0)
398 	HANDLE_PALETTE(MSG_DEEPFROST_PALETTE, 1)
399 	HANDLE_PALETTE(MSG_FROST_PALETTE, 2)
400 	HANDLE_PALETTE(MSG_FIRE_PALETTE, 3)
401 	HANDLE_PALETTE(MSG_MIDNIGHT_PALETTE, 4)
402 	HANDLE_PALETTE(MSG_GRASSLAND_PALETTE, 5)
403 	HANDLE_PALETTE(MSG_LIGHTNING_PALETTE, 6)
404 	HANDLE_PALETTE(MSG_SPRING_PALETTE, 7)
405 	HANDLE_PALETTE(MSG_HIGHCONTRAST_PALETTE, 8)
406 
407 	HANDLE_ITER(MSG_ITER_128, 128)
408 	HANDLE_ITER(MSG_ITER_512, 512)
409 	HANDLE_ITER(MSG_ITER_1024, 1024)
410 	HANDLE_ITER(MSG_ITER_4096, 4096)
411 	HANDLE_ITER(MSG_ITER_8192, 8192)
412 	HANDLE_ITER(MSG_ITER_12288, 12288)
413 	HANDLE_ITER(MSG_ITER_16384, 16384)
414 
415 	case B_ABOUT_REQUESTED: {
416 		BAboutWindow* wind = new BAboutWindow("Mandelbrot", "application/x-vnd.Haiku-Mandelbrot");
417 		const char* authors[] = {
418 			"Augustin Cavalier <waddlesplash>",
419 			B_TRANSLATE("kerwizzy (original FractalEngine author)"),
420 			NULL
421 		};
422 		wind->AddCopyright(2016, "Haiku, Inc.");
423 		wind->AddAuthors(authors);
424 		wind->Show();
425 		break;
426 	}
427 
428 	default:
429 		BWindow::MessageReceived(msg);
430 		break;
431 	}
432 }
433 #undef HANDLE_SET
434 #undef HANDLE_PALETTE
435 #undef HANDLE_ITER
436 
437 
438 bool
439 MandelbrotWindow::QuitRequested()
440 {
441 	if (BWindow::QuitRequested()) {
442 		be_app->PostMessage(B_QUIT_REQUESTED);
443 		return true;
444 	}
445 	return false;
446 }
447 
448 
449 // #pragma mark - MandelbrotApp
450 
451 
452 class MandelbrotApp : public BApplication
453 {
454 public:
455 				MandelbrotApp()
456 					: BApplication("application/x-vnd.Haiku-Mandelbrot") {}
457 
458 		void	ReadyToRun();
459 		bool	QuitRequested() { return true; }
460 };
461 
462 
463 void
464 MandelbrotApp::ReadyToRun()
465 {
466 	MandelbrotWindow* wind = new MandelbrotWindow(BRect(0, 0, 640, 480));
467 	wind->CenterOnScreen();
468 	wind->Show();
469 }
470 
471 
472 int
473 main(int argc, char* argv[])
474 {
475 	MandelbrotApp().Run();
476 	return 0;
477 }
478