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 * kerwizzy 8 */ 9 10 11 #include <AboutWindow.h> 12 #include <Application.h> 13 #include <Bitmap.h> 14 #include <Catalog.h> 15 #include <MenuBar.h> 16 #include <LayoutBuilder.h> 17 #include <View.h> 18 #include <Window.h> 19 20 #include <algorithm> 21 22 #include "FractalEngine.h" 23 24 #undef B_TRANSLATION_CONTEXT 25 #define B_TRANSLATION_CONTEXT "MandelbrotWindow" 26 27 #define MANDELBROT_VIEW_REFRESH_FPS 10 28 29 // #pragma mark - FractalView 30 31 //#define TRACE_MANDELBROT_VIEW 32 #ifdef TRACE_MANDELBROT_VIEW 33 # include <stdio.h> 34 # define TRACE(x...) printf(x) 35 #else 36 # define TRACE(x...) 37 #endif 38 39 40 class FractalView : public BView { 41 public: 42 FractalView(); 43 ~FractalView(); 44 45 virtual void AttachedToWindow(); 46 virtual void FrameResized(float, float); 47 virtual void Pulse(); 48 49 virtual void MouseDown(BPoint where); 50 virtual void MouseMoved(BPoint where, uint32 mode, const BMessage*); 51 virtual void MouseUp(BPoint where); 52 53 virtual void MessageReceived(BMessage* msg); 54 virtual void Draw(BRect updateRect); 55 56 void ResetPosition(); 57 void SetLocationFromFrame(double frameX, double frameY); 58 void ZoomFractal(double originX, double originY, double zoomFactor); 59 void ZoomFractalFromFrame(double frameOriginX, double frameOriginY, 60 double zoomFactor); 61 void ImportBitsAndInvalidate(); 62 void RedrawFractal(); 63 void UpdateSize(); 64 void CreateDisplayBitmap(uint16 width, uint16 height); 65 FractalEngine* fFractalEngine; 66 67 private: 68 BRect GetDragFrame(); 69 70 BPoint fSelectStart; 71 BPoint fSelectEnd; 72 bool fSelecting; 73 uint32 fMouseButtons; 74 75 BBitmap* fDisplayBitmap; 76 77 double fLocationX; 78 double fLocationY; 79 double fSize; 80 }; 81 82 83 FractalView::FractalView() 84 : 85 BView(NULL, B_WILL_DRAW | B_FRAME_EVENTS | B_PULSE_NEEDED), 86 fFractalEngine(NULL), 87 fSelecting(false), 88 fDisplayBitmap(NULL), 89 fLocationX(0), 90 fLocationY(0), 91 fSize(0.005) 92 { 93 SetHighColor(make_color(255, 255, 255, 255)); 94 } 95 96 97 FractalView::~FractalView() 98 { 99 delete fDisplayBitmap; 100 } 101 102 103 void FractalView::ResetPosition() 104 { 105 fLocationX = 0; 106 fLocationY = 0; 107 fSize = 0.005; 108 } 109 110 111 void FractalView::AttachedToWindow() 112 { 113 fFractalEngine = new FractalEngine(this, Window()); 114 fFractalEngine->Run(); 115 TRACE("Attached to window\n"); 116 } 117 118 119 void FractalView::FrameResized(float, float) 120 { 121 TRACE("Frame Resize\n"); 122 UpdateSize(); 123 } 124 125 126 void FractalView::UpdateSize() 127 { 128 TRACE("Update Size\n"); 129 BMessage msg(FractalEngine::MSG_RESIZE); 130 131 uint16 width = (uint16)Frame().Width()+1; 132 uint16 height = (uint16)Frame().Height()+1; 133 134 msg.AddUInt16("width", width); 135 msg.AddUInt16("height", height); 136 137 CreateDisplayBitmap(width,height); 138 139 msg.AddPointer("bitmap",fDisplayBitmap); 140 141 fFractalEngine->PostMessage(&msg); // Create the new buffer 142 } 143 144 145 void FractalView::CreateDisplayBitmap(uint16 width,uint16 height) 146 { 147 delete fDisplayBitmap; 148 fDisplayBitmap = NULL; 149 TRACE("width %u height %u\n",width,height); 150 BRect rect(0, 0, width, height); 151 fDisplayBitmap = new BBitmap(rect, B_RGB24); 152 } 153 154 155 BRect FractalView::GetDragFrame() 156 { 157 BRect dragZone = BRect(std::min(fSelectStart.x, fSelectEnd.x), 158 std::min(fSelectStart.y, fSelectEnd.y), 159 std::max(fSelectStart.x, fSelectEnd.x), 160 std::max(fSelectStart.y, fSelectEnd.y)), 161 frame = Frame(); 162 float width = dragZone.Width(), 163 height = width * (frame.Height() / frame.Width()); 164 165 float x1 = fSelectStart.x, y1 = fSelectStart.y, x2, y2; 166 if (fSelectStart.x < fSelectEnd.x) 167 x2 = x1 + width; 168 else 169 x2 = x1 - width; 170 if (fSelectStart.y < fSelectEnd.y) 171 y2 = y1 + height; 172 else 173 y2 = y1 - height; 174 return BRect(x1, y1, x2, y2); 175 } 176 177 178 void FractalView::MouseDown(BPoint where) 179 { 180 fSelecting = true; 181 fSelectStart = where; 182 fMouseButtons = 0; 183 Window()->CurrentMessage()->FindInt32("buttons", (int32*)&fMouseButtons); 184 } 185 186 187 void FractalView::MouseMoved(BPoint where, uint32 mode, const BMessage*) 188 { 189 if (fSelecting) { 190 fSelectEnd = where; 191 Invalidate(); 192 } 193 } 194 195 196 void FractalView::SetLocationFromFrame(double frameX,double frameY) 197 { 198 BRect frame = Frame(); 199 200 fLocationX = ((frameX - frame.Width() / 2) * fSize + fLocationX); 201 fLocationY = ((frameY - frame.Height() / 2) * -fSize + fLocationY); 202 // -fSize because is in raster coordinates (y swapped) 203 } 204 205 206 void FractalView::ZoomFractalFromFrame(double frameOriginX, double frameOriginY, 207 double zoomFactor) 208 { 209 BRect frame = Frame(); 210 211 ZoomFractal((frameOriginX - frame.Width() / 2) * fSize + fLocationX, 212 (frameOriginY - frame.Height() / 2) * -fSize + fLocationY, 213 zoomFactor); 214 } 215 216 217 void FractalView::ZoomFractal(double originX, double originY, double zoomFactor) 218 { 219 double deltaX = originX - fLocationX; 220 double deltaY = originY - fLocationY; 221 222 TRACE("oX %g oY %g zoom %g\n", originX, originY, zoomFactor); 223 224 deltaX /= zoomFactor; 225 deltaY /= zoomFactor; 226 227 fLocationX = originX - deltaX; 228 fLocationY = originY - deltaY; 229 fSize /= zoomFactor; 230 } 231 232 233 void FractalView::MouseUp(BPoint where) 234 { 235 BRect frame = Frame(); 236 fSelecting = false; 237 if (fabs(fSelectStart.x - where.x) > 4) { 238 fSelectEnd = where; 239 BRect dragFrame = GetDragFrame(); 240 BPoint lt = dragFrame.LeftTop(); 241 float centerX = lt.x + dragFrame.Width() / 2, 242 centerY = lt.y + dragFrame.Height() / 2; 243 244 SetLocationFromFrame(centerX, centerY); 245 fSize = std::fabs((dragFrame.Width() * fSize) / frame.Width()); 246 } else { 247 if (fMouseButtons & B_PRIMARY_MOUSE_BUTTON) { 248 SetLocationFromFrame(where.x, where.y); 249 ZoomFractal(fLocationX, fLocationY, 2); 250 } else { 251 ZoomFractal(fLocationX, fLocationY, 0.5); 252 } 253 } 254 RedrawFractal(); 255 } 256 257 258 void FractalView::MessageReceived(BMessage* msg) 259 { 260 switch (msg->what) { 261 case B_MOUSE_WHEEL_CHANGED: { 262 float change = msg->FindFloat("be:wheel_delta_y"); 263 BPoint where; 264 GetMouse(&where, NULL); 265 double zoomFactor; 266 if (change < 0) 267 zoomFactor = 3.0/2.0; 268 else 269 zoomFactor = 2.0/3.0; 270 ZoomFractalFromFrame(where.x, where.y, zoomFactor); 271 272 RedrawFractal(); 273 break; 274 } 275 276 case FractalEngine::MSG_BUFFER_CREATED: 277 TRACE("Got buffer created msg.\n"); 278 279 ImportBitsAndInvalidate(); 280 RedrawFractal(); 281 break; 282 283 case FractalEngine::MSG_RENDER_COMPLETE: 284 TRACE("Got render complete msg.\n"); 285 286 Window()->SetPulseRate(0); 287 ImportBitsAndInvalidate(); 288 break; 289 290 default: 291 BView::MessageReceived(msg); 292 break; 293 } 294 } 295 296 297 void FractalView::Pulse() 298 { 299 ImportBitsAndInvalidate(); 300 } 301 302 303 void FractalView::ImportBitsAndInvalidate() 304 { 305 TRACE("Importing bits...\n"); 306 307 fFractalEngine->WriteToBitmap(fDisplayBitmap); 308 Invalidate(); 309 } 310 311 312 void FractalView::RedrawFractal() 313 { 314 Window()->SetPulseRate(1000000 / MANDELBROT_VIEW_REFRESH_FPS); 315 BMessage message(FractalEngine::MSG_RENDER); 316 message.AddDouble("locationX", fLocationX); 317 message.AddDouble("locationY", fLocationY); 318 message.AddDouble("size", fSize); 319 fFractalEngine->PostMessage(&message); 320 } 321 322 323 void FractalView::Draw(BRect updateRect) 324 { 325 DrawBitmap(fDisplayBitmap, updateRect, updateRect); 326 if (fSelecting) 327 StrokeRect(GetDragFrame()); 328 } 329 330 331 // #pragma mark - MandelbrotWindow 332 333 334 class MandelbrotWindow : public BWindow 335 { 336 public: 337 enum { 338 MSG_MANDELBROT_SET = 'MndW', 339 MSG_BURNINGSHIP_SET, 340 MSG_TRICORN_SET, 341 MSG_JULIA_SET, 342 MSG_ORBITTRAP_SET, 343 MSG_MULTIBROT_SET, 344 345 MSG_ROYAL_PALETTE, 346 MSG_DEEPFROST_PALETTE, 347 MSG_FROST_PALETTE, 348 MSG_FIRE_PALETTE, 349 MSG_MIDNIGHT_PALETTE, 350 MSG_GRASSLAND_PALETTE, 351 MSG_LIGHTNING_PALETTE, 352 MSG_SPRING_PALETTE, 353 MSG_HIGHCONTRAST_PALETTE, 354 355 MSG_ITER_128, 356 MSG_ITER_512, 357 MSG_ITER_1024, 358 MSG_ITER_4096, 359 MSG_ITER_8192, 360 MSG_ITER_12288, 361 MSG_ITER_16384, 362 363 MSG_SUBSAMPLING_1, 364 MSG_SUBSAMPLING_2, 365 MSG_SUBSAMPLING_3, 366 MSG_SUBSAMPLING_4 367 }; 368 MandelbrotWindow(BRect frame); 369 ~MandelbrotWindow() {} 370 371 virtual void MessageReceived(BMessage* msg); 372 virtual bool QuitRequested(); 373 374 private: 375 FractalView* fFractalView; 376 }; 377 378 379 MandelbrotWindow::MandelbrotWindow(BRect frame) 380 : 381 BWindow(frame, B_TRANSLATE_SYSTEM_NAME("Mandelbrot"), B_TITLED_WINDOW_LOOK, 382 B_NORMAL_WINDOW_FEEL, 0L), 383 fFractalView(new FractalView) 384 { 385 BMenuBar* menuBar = new BMenuBar("MenuBar"); 386 BMenu* setMenu; 387 BMenu* paletteMenu; 388 BMenu* iterMenu; 389 BMenu* subsamplingMenu; 390 BLayoutBuilder::Menu<>(menuBar) 391 .AddMenu(B_TRANSLATE("File")) 392 .AddItem(B_TRANSLATE("About"), B_ABOUT_REQUESTED) 393 .AddItem(B_TRANSLATE("Quit"), B_QUIT_REQUESTED, 'Q') 394 .End() 395 .AddMenu(B_TRANSLATE("Set")) 396 .GetMenu(setMenu) 397 .AddItem(B_TRANSLATE("Mandelbrot"), MSG_MANDELBROT_SET) 398 .AddItem(B_TRANSLATE("Burning Ship"), MSG_BURNINGSHIP_SET) 399 .AddItem(B_TRANSLATE("Tricorn"), MSG_TRICORN_SET) 400 .AddItem(B_TRANSLATE("Julia"), MSG_JULIA_SET) 401 .AddItem(B_TRANSLATE("Orbit Trap"), MSG_ORBITTRAP_SET) 402 .AddItem(B_TRANSLATE("Multibrot"), MSG_MULTIBROT_SET) 403 .End() 404 .AddMenu(B_TRANSLATE("Palette")) 405 .GetMenu(paletteMenu) 406 .AddItem(B_TRANSLATE("Royal"), MSG_ROYAL_PALETTE) 407 .AddItem(B_TRANSLATE("Deepfrost"), MSG_DEEPFROST_PALETTE) 408 .AddItem(B_TRANSLATE("Frost"), MSG_FROST_PALETTE) 409 .AddItem(B_TRANSLATE("Fire"), MSG_FIRE_PALETTE) 410 .AddItem(B_TRANSLATE("Midnight"), MSG_MIDNIGHT_PALETTE) 411 .AddItem(B_TRANSLATE("Grassland"), MSG_GRASSLAND_PALETTE) 412 .AddItem(B_TRANSLATE("Lightning"), MSG_LIGHTNING_PALETTE) 413 .AddItem(B_TRANSLATE("Spring"), MSG_SPRING_PALETTE) 414 .AddItem(B_TRANSLATE("High contrast"), MSG_HIGHCONTRAST_PALETTE) 415 .End() 416 .AddMenu(B_TRANSLATE("Iterations")) 417 .GetMenu(iterMenu) 418 .AddItem("128", MSG_ITER_128) 419 .AddItem("512", MSG_ITER_512) 420 .AddItem("1024", MSG_ITER_1024) 421 .AddItem("4096", MSG_ITER_4096) 422 .AddItem("8192", MSG_ITER_8192) 423 .AddItem("12288", MSG_ITER_12288) 424 .AddItem("16384", MSG_ITER_16384) 425 .End() 426 .AddMenu(B_TRANSLATE("Subsampling")) 427 .GetMenu(subsamplingMenu) 428 .AddItem(B_TRANSLATE("1 (none)"), MSG_SUBSAMPLING_1) 429 .AddItem("4", MSG_SUBSAMPLING_2) 430 .AddItem("9", MSG_SUBSAMPLING_3) 431 .AddItem("16", MSG_SUBSAMPLING_4) 432 .End() 433 .End(); 434 setMenu->SetRadioMode(true); 435 setMenu->FindItem(MSG_MANDELBROT_SET)->SetMarked(true); 436 paletteMenu->SetRadioMode(true); 437 paletteMenu->FindItem(MSG_ROYAL_PALETTE)->SetMarked(true); 438 iterMenu->SetRadioMode(true); 439 iterMenu->FindItem(MSG_ITER_1024)->SetMarked(true); 440 subsamplingMenu->SetRadioMode(true); 441 subsamplingMenu->FindItem(MSG_SUBSAMPLING_2)->SetMarked(true); 442 443 BLayoutBuilder::Group<>(this, B_VERTICAL, 0) 444 .SetInsets(0) 445 .Add(menuBar) 446 .Add(fFractalView) 447 .End(); 448 } 449 450 451 #define HANDLE_SET(uiwhat, id) \ 452 case uiwhat: { \ 453 BMessage msg(FractalEngine::MSG_CHANGE_SET); \ 454 msg.AddUInt8("set", id); \ 455 fFractalView->fFractalEngine->PostMessage(&msg); \ 456 fFractalView->ResetPosition(); \ 457 fFractalView->RedrawFractal(); \ 458 break; \ 459 } 460 #define HANDLE_PALETTE(uiwhat, id) \ 461 case uiwhat: { \ 462 BMessage msg(FractalEngine::MSG_SET_PALETTE); \ 463 msg.AddUInt8("palette", id); \ 464 fFractalView->fFractalEngine->PostMessage(&msg); \ 465 fFractalView->RedrawFractal(); \ 466 break; \ 467 } 468 #define HANDLE_ITER(uiwhat, id) \ 469 case uiwhat: { \ 470 BMessage msg(FractalEngine::MSG_SET_ITERATIONS); \ 471 msg.AddUInt16("iterations", id); \ 472 fFractalView->fFractalEngine->PostMessage(&msg); \ 473 fFractalView->RedrawFractal(); \ 474 break; \ 475 } 476 #define HANDLE_SUBSAMPLING(uiwhat, id) \ 477 case uiwhat: { \ 478 BMessage msg(FractalEngine::MSG_SET_SUBSAMPLING); \ 479 msg.AddUInt8("subsampling", id); \ 480 fFractalView->fFractalEngine->PostMessage(&msg); \ 481 fFractalView->RedrawFractal(); \ 482 break; \ 483 } 484 void 485 MandelbrotWindow::MessageReceived(BMessage* msg) 486 { 487 switch (msg->what) { 488 HANDLE_SET(MSG_MANDELBROT_SET, 0) 489 HANDLE_SET(MSG_BURNINGSHIP_SET, 1) 490 HANDLE_SET(MSG_TRICORN_SET, 2) 491 HANDLE_SET(MSG_JULIA_SET, 3) 492 HANDLE_SET(MSG_ORBITTRAP_SET, 4) 493 HANDLE_SET(MSG_MULTIBROT_SET, 5) 494 495 HANDLE_PALETTE(MSG_ROYAL_PALETTE, 0) 496 HANDLE_PALETTE(MSG_DEEPFROST_PALETTE, 1) 497 HANDLE_PALETTE(MSG_FROST_PALETTE, 2) 498 HANDLE_PALETTE(MSG_FIRE_PALETTE, 3) 499 HANDLE_PALETTE(MSG_MIDNIGHT_PALETTE, 4) 500 HANDLE_PALETTE(MSG_GRASSLAND_PALETTE, 5) 501 HANDLE_PALETTE(MSG_LIGHTNING_PALETTE, 6) 502 HANDLE_PALETTE(MSG_SPRING_PALETTE, 7) 503 HANDLE_PALETTE(MSG_HIGHCONTRAST_PALETTE, 8) 504 505 HANDLE_ITER(MSG_ITER_128, 128) 506 HANDLE_ITER(MSG_ITER_512, 512) 507 HANDLE_ITER(MSG_ITER_1024, 1024) 508 HANDLE_ITER(MSG_ITER_4096, 4096) 509 HANDLE_ITER(MSG_ITER_8192, 8192) 510 HANDLE_ITER(MSG_ITER_12288, 12288) 511 HANDLE_ITER(MSG_ITER_16384, 16384) 512 513 HANDLE_SUBSAMPLING(MSG_SUBSAMPLING_1, 1) 514 HANDLE_SUBSAMPLING(MSG_SUBSAMPLING_2, 2) 515 HANDLE_SUBSAMPLING(MSG_SUBSAMPLING_3, 3) 516 HANDLE_SUBSAMPLING(MSG_SUBSAMPLING_4, 4) 517 518 case B_ABOUT_REQUESTED: { 519 BAboutWindow* wind = new BAboutWindow("Mandelbrot", 520 "application/x-vnd.Haiku-Mandelbrot"); 521 522 const char* authors[] = { 523 "Augustin Cavalier <waddlesplash>", 524 "kerwizzy", 525 NULL 526 }; 527 wind->AddCopyright(2016, "Haiku, Inc."); 528 wind->AddAuthors(authors); 529 wind->Show(); 530 break; 531 } 532 533 default: 534 BWindow::MessageReceived(msg); 535 break; 536 } 537 } 538 #undef HANDLE_SET 539 #undef HANDLE_PALETTE 540 #undef HANDLE_ITER 541 #undef HANDLE_SUBSAMPLING 542 543 544 bool 545 MandelbrotWindow::QuitRequested() 546 { 547 if (BWindow::QuitRequested()) { 548 be_app->PostMessage(B_QUIT_REQUESTED); 549 return true; 550 } 551 return false; 552 } 553 554 555 // #pragma mark - MandelbrotApp 556 557 558 class MandelbrotApp : public BApplication 559 { 560 public: 561 MandelbrotApp() 562 : BApplication("application/x-vnd.Haiku-Mandelbrot") {} 563 564 void ReadyToRun(); 565 bool QuitRequested() { return true; } 566 }; 567 568 569 void 570 MandelbrotApp::ReadyToRun() 571 { 572 MandelbrotWindow* wind = new MandelbrotWindow(BRect(0, 0, 640, 480)); 573 wind->CenterOnScreen(); 574 wind->Show(); 575 } 576 577 578 int 579 main(int argc, char* argv[]) 580 { 581 MandelbrotApp().Run(); 582 return 0; 583 } 584