xref: /haiku/src/apps/mandelbrot/Mandelbrot.cpp (revision b08627f310bb2e80bca50176e7a758182384735a)
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 	fMouseButtons = 0;
161 	Window()->CurrentMessage()->FindInt32("buttons", (int32*)&fMouseButtons);
162 }
163 
164 
165 void FractalView::MouseMoved(BPoint where, uint32 mode, const BMessage*)
166 {
167 	if (fSelecting) {
168 		fSelectEnd = where;
169 		Invalidate();
170 	}
171 }
172 
173 
174 void FractalView::MouseUp(BPoint where)
175 {
176 	BRect frame = Frame();
177 	fSelecting = false;
178 	if (fabs(fSelectStart.x - where.x) > 4) {
179 		fSelectEnd = where;
180 		BRect dragFrame = GetDragFrame();
181 		BPoint lt = dragFrame.LeftTop();
182 		float centerX = lt.x + dragFrame.Width() / 2,
183 			centerY = lt.y + dragFrame.Height() / 2;
184 		fLocationX = ((centerX - frame.Width() / 2) * fSize + fLocationX);
185 		fLocationY = ((centerY - frame.Height() / 2) * -fSize + fLocationY);
186 
187 		fSize = (dragFrame.Width() * fSize) / frame.Width();
188 	} else {
189 		fLocationX = ((where.x - frame.Width() / 2) * fSize + fLocationX);
190 		fLocationY = ((where.y - frame.Height() / 2) * -fSize + fLocationY);
191 		if (fMouseButtons & B_PRIMARY_MOUSE_BUTTON)
192 			fSize /= 2;
193 		else
194 			fSize *= 2;
195 	}
196 	RedrawFractal();
197 }
198 
199 
200 void FractalView::MessageReceived(BMessage* msg)
201 {
202 	switch (msg->what) {
203 	case B_MOUSE_WHEEL_CHANGED: {
204 		float change = msg->FindFloat("be:wheel_delta_y");
205 		BPoint where;
206 		GetMouse(&where, NULL);
207 		BRect frame = Frame();
208 		fLocationX = ((where.x - frame.Width() / 2) * fSize + fLocationX);
209 		fLocationY = ((where.y - frame.Height() / 2) * -fSize + fLocationY);
210 		if (change < 0)
211 			fSize /= 1.5;
212 		else
213 			fSize *= 1.5;
214 		RedrawFractal();
215 		break;
216 	}
217 
218 	case FractalEngine::MSG_RENDER_COMPLETE:
219 		if (fOwnBitmap) {
220 			fOwnBitmap = false;
221 			delete fDisplayBitmap;
222 		}
223 		fDisplayBitmap = NULL; // In case the following line fails
224 		msg->FindPointer("bitmap", (void**)&fDisplayBitmap);
225 		Invalidate();
226 		break;
227 
228 	default:
229 		BView::MessageReceived(msg);
230 		break;
231 	}
232 }
233 
234 
235 void FractalView::RedrawFractal()
236 {
237 	BMessage message(FractalEngine::MSG_RENDER);
238 	message.AddDouble("locationX", fLocationX);
239 	message.AddDouble("locationY", fLocationY);
240 	message.AddDouble("size", fSize);
241 	fFractalEngine->PostMessage(&message);
242 }
243 
244 
245 void FractalView::Draw(BRect updateRect)
246 {
247 	DrawBitmap(fDisplayBitmap, updateRect, updateRect);
248 
249 	if (fSelecting) {
250 		StrokeRect(GetDragFrame());
251 	}
252 }
253 
254 
255 // #pragma mark - MandelbrotWindow
256 
257 
258 class MandelbrotWindow : public BWindow
259 {
260 public:
261 	enum {
262 		MSG_MANDELBROT_SET = 'MndW',
263 		MSG_BURNINGSHIP_SET,
264 		MSG_TRICORN_SET,
265 		MSG_JULIA_SET,
266 		MSG_ORBITTRAP_SET,
267 		MSG_MULTIBROT_SET,
268 
269 		MSG_ROYAL_PALETTE,
270 		MSG_DEEPFROST_PALETTE,
271 		MSG_FROST_PALETTE,
272 		MSG_FIRE_PALETTE,
273 		MSG_MIDNIGHT_PALETTE,
274 		MSG_GRASSLAND_PALETTE,
275 		MSG_LIGHTNING_PALETTE,
276 		MSG_SPRING_PALETTE,
277 		MSG_HIGHCONTRAST_PALETTE,
278 
279 		MSG_ITER_128,
280 		MSG_ITER_512,
281 		MSG_ITER_1024,
282 		MSG_ITER_4096,
283 		MSG_ITER_8192,
284 		MSG_ITER_12288,
285 		MSG_ITER_16384
286 	};
287 				MandelbrotWindow(BRect frame);
288 				~MandelbrotWindow() {}
289 
290 	virtual void MessageReceived(BMessage* msg);
291 	virtual bool QuitRequested();
292 
293 private:
294 		FractalView* fFractalView;
295 };
296 
297 
298 MandelbrotWindow::MandelbrotWindow(BRect frame)
299 	:
300 	BWindow(frame, B_TRANSLATE_SYSTEM_NAME("Mandelbrot"), B_TITLED_WINDOW_LOOK,
301 		B_NORMAL_WINDOW_FEEL, 0L),
302 	fFractalView(new FractalView)
303 {
304 	SetPulseRate(250000); // pulse twice per second
305 
306 	BMenuBar* menuBar = new BMenuBar("MenuBar");
307 	BMenu* setMenu;
308 	BMenu* paletteMenu;
309 	BMenu* iterMenu;
310 	BLayoutBuilder::Menu<>(menuBar)
311 		.AddMenu(B_TRANSLATE("File"))
312 			.AddItem(B_TRANSLATE("About"), B_ABOUT_REQUESTED)
313 			.AddItem(B_TRANSLATE("Quit"), B_QUIT_REQUESTED, 'Q')
314 		.End()
315 		.AddMenu(B_TRANSLATE("Set"))
316 			.GetMenu(setMenu)
317 			.AddItem(B_TRANSLATE("Mandelbrot"), MSG_MANDELBROT_SET)
318 			.AddItem(B_TRANSLATE("Burning Ship"), MSG_BURNINGSHIP_SET)
319 			.AddItem(B_TRANSLATE("Tricorn"), MSG_TRICORN_SET)
320 			.AddItem(B_TRANSLATE("Julia"), MSG_JULIA_SET)
321 			.AddItem(B_TRANSLATE("Orbit Trap"), MSG_ORBITTRAP_SET)
322 			.AddItem(B_TRANSLATE("Multibrot"), MSG_MULTIBROT_SET)
323 		.End()
324 		.AddMenu(B_TRANSLATE("Palette"))
325 			.GetMenu(paletteMenu)
326 			.AddItem(B_TRANSLATE("Royal"), MSG_ROYAL_PALETTE)
327 			.AddItem(B_TRANSLATE("Deepfrost"), MSG_DEEPFROST_PALETTE)
328 			.AddItem(B_TRANSLATE("Frost"), MSG_FROST_PALETTE)
329 			.AddItem(B_TRANSLATE("Fire"), MSG_FIRE_PALETTE)
330 			.AddItem(B_TRANSLATE("Midnight"), MSG_MIDNIGHT_PALETTE)
331 			.AddItem(B_TRANSLATE("Grassland"), MSG_GRASSLAND_PALETTE)
332 			.AddItem(B_TRANSLATE("Lightning"), MSG_LIGHTNING_PALETTE)
333 			.AddItem(B_TRANSLATE("Spring"), MSG_SPRING_PALETTE)
334 			.AddItem(B_TRANSLATE("High contrast"), MSG_HIGHCONTRAST_PALETTE)
335 		.End()
336 		.AddMenu(B_TRANSLATE("Iterations"))
337 			.GetMenu(iterMenu)
338 			.AddItem("128", MSG_ITER_128)
339 			.AddItem("512", MSG_ITER_512)
340 			.AddItem("1024", MSG_ITER_1024)
341 			.AddItem("4096", MSG_ITER_4096)
342 			.AddItem("8192", MSG_ITER_8192)
343 			.AddItem("12288", MSG_ITER_12288)
344 			.AddItem("16384", MSG_ITER_16384)
345 		.End()
346 	.End();
347 	setMenu->SetRadioMode(true);
348 	setMenu->FindItem(MSG_MANDELBROT_SET)->SetMarked(true);
349 	paletteMenu->SetRadioMode(true);
350 	paletteMenu->FindItem(MSG_ROYAL_PALETTE)->SetMarked(true);
351 	iterMenu->SetRadioMode(true);
352 	iterMenu->FindItem(MSG_ITER_1024)->SetMarked(true);
353 
354 	BLayoutBuilder::Group<>(this, B_VERTICAL, 0)
355 		.SetInsets(0)
356 		.Add(menuBar)
357 		.Add(fFractalView)
358 	.End();
359 }
360 
361 
362 #define HANDLE_SET(uiwhat, id) \
363 	case uiwhat: { \
364 		BMessage msg(FractalEngine::MSG_CHANGE_SET); \
365 		msg.AddUInt8("set", id); \
366 		fFractalView->fFractalEngine->PostMessage(&msg); \
367 		fFractalView->ResetPosition(); \
368 		fFractalView->RedrawFractal(); \
369 		break; \
370 	}
371 #define HANDLE_PALETTE(uiwhat, id) \
372 	case uiwhat: { \
373 		BMessage msg(FractalEngine::MSG_SET_PALETTE); \
374 		msg.AddUInt8("palette", id); \
375 		fFractalView->fFractalEngine->PostMessage(&msg); \
376 		fFractalView->RedrawFractal(); \
377 		break; \
378 	}
379 #define HANDLE_ITER(uiwhat, id) \
380 	case uiwhat: { \
381 		BMessage msg(FractalEngine::MSG_SET_ITERATIONS); \
382 		msg.AddUInt16("iterations", id); \
383 		fFractalView->fFractalEngine->PostMessage(&msg); \
384 		fFractalView->RedrawFractal(); \
385 		break; \
386 	}
387 void
388 MandelbrotWindow::MessageReceived(BMessage* msg)
389 {
390 	switch (msg->what) {
391 	HANDLE_SET(MSG_MANDELBROT_SET, 0)
392 	HANDLE_SET(MSG_BURNINGSHIP_SET, 1)
393 	HANDLE_SET(MSG_TRICORN_SET, 2)
394 	HANDLE_SET(MSG_JULIA_SET, 3)
395 	HANDLE_SET(MSG_ORBITTRAP_SET, 4)
396 	HANDLE_SET(MSG_MULTIBROT_SET, 5)
397 
398 	HANDLE_PALETTE(MSG_ROYAL_PALETTE, 0)
399 	HANDLE_PALETTE(MSG_DEEPFROST_PALETTE, 1)
400 	HANDLE_PALETTE(MSG_FROST_PALETTE, 2)
401 	HANDLE_PALETTE(MSG_FIRE_PALETTE, 3)
402 	HANDLE_PALETTE(MSG_MIDNIGHT_PALETTE, 4)
403 	HANDLE_PALETTE(MSG_GRASSLAND_PALETTE, 5)
404 	HANDLE_PALETTE(MSG_LIGHTNING_PALETTE, 6)
405 	HANDLE_PALETTE(MSG_SPRING_PALETTE, 7)
406 	HANDLE_PALETTE(MSG_HIGHCONTRAST_PALETTE, 8)
407 
408 	HANDLE_ITER(MSG_ITER_128, 128)
409 	HANDLE_ITER(MSG_ITER_512, 512)
410 	HANDLE_ITER(MSG_ITER_1024, 1024)
411 	HANDLE_ITER(MSG_ITER_4096, 4096)
412 	HANDLE_ITER(MSG_ITER_8192, 8192)
413 	HANDLE_ITER(MSG_ITER_12288, 12288)
414 	HANDLE_ITER(MSG_ITER_16384, 16384)
415 
416 	case B_ABOUT_REQUESTED: {
417 		BAboutWindow* wind = new BAboutWindow("Mandelbrot", "application/x-vnd.Haiku-Mandelbrot");
418 		const char* authors[] = {
419 			"Augustin Cavalier <waddlesplash>",
420 			B_TRANSLATE("kerwizzy (original FractalEngine author)"),
421 			NULL
422 		};
423 		wind->AddCopyright(2016, "Haiku, Inc.");
424 		wind->AddAuthors(authors);
425 		wind->Show();
426 		break;
427 	}
428 
429 	default:
430 		BWindow::MessageReceived(msg);
431 		break;
432 	}
433 }
434 #undef HANDLE_SET
435 #undef HANDLE_PALETTE
436 #undef HANDLE_ITER
437 
438 
439 bool
440 MandelbrotWindow::QuitRequested()
441 {
442 	if (BWindow::QuitRequested()) {
443 		be_app->PostMessage(B_QUIT_REQUESTED);
444 		return true;
445 	}
446 	return false;
447 }
448 
449 
450 // #pragma mark - MandelbrotApp
451 
452 
453 class MandelbrotApp : public BApplication
454 {
455 public:
456 				MandelbrotApp()
457 					: BApplication("application/x-vnd.Haiku-Mandelbrot") {}
458 
459 		void	ReadyToRun();
460 		bool	QuitRequested() { return true; }
461 };
462 
463 
464 void
465 MandelbrotApp::ReadyToRun()
466 {
467 	MandelbrotWindow* wind = new MandelbrotWindow(BRect(0, 0, 640, 480));
468 	wind->CenterOnScreen();
469 	wind->Show();
470 }
471 
472 
473 int
474 main(int argc, char* argv[])
475 {
476 	MandelbrotApp().Run();
477 	return 0;
478 }
479