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