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 = (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 MandelbrotWindow(BRect frame); 364 ~MandelbrotWindow() {} 365 366 virtual void MessageReceived(BMessage* msg); 367 virtual bool QuitRequested(); 368 369 private: 370 FractalView* fFractalView; 371 }; 372 373 374 MandelbrotWindow::MandelbrotWindow(BRect frame) 375 : 376 BWindow(frame, B_TRANSLATE_SYSTEM_NAME("Mandelbrot"), B_TITLED_WINDOW_LOOK, 377 B_NORMAL_WINDOW_FEEL, 0L), 378 fFractalView(new FractalView) 379 { 380 BMenuBar* menuBar = new BMenuBar("MenuBar"); 381 BMenu* setMenu; 382 BMenu* paletteMenu; 383 BMenu* iterMenu; 384 BLayoutBuilder::Menu<>(menuBar) 385 .AddMenu(B_TRANSLATE("File")) 386 .AddItem(B_TRANSLATE("About"), B_ABOUT_REQUESTED) 387 .AddItem(B_TRANSLATE("Quit"), B_QUIT_REQUESTED, 'Q') 388 .End() 389 .AddMenu(B_TRANSLATE("Set")) 390 .GetMenu(setMenu) 391 .AddItem(B_TRANSLATE("Mandelbrot"), MSG_MANDELBROT_SET) 392 .AddItem(B_TRANSLATE("Burning Ship"), MSG_BURNINGSHIP_SET) 393 .AddItem(B_TRANSLATE("Tricorn"), MSG_TRICORN_SET) 394 .AddItem(B_TRANSLATE("Julia"), MSG_JULIA_SET) 395 .AddItem(B_TRANSLATE("Orbit Trap"), MSG_ORBITTRAP_SET) 396 .AddItem(B_TRANSLATE("Multibrot"), MSG_MULTIBROT_SET) 397 .End() 398 .AddMenu(B_TRANSLATE("Palette")) 399 .GetMenu(paletteMenu) 400 .AddItem(B_TRANSLATE("Royal"), MSG_ROYAL_PALETTE) 401 .AddItem(B_TRANSLATE("Deepfrost"), MSG_DEEPFROST_PALETTE) 402 .AddItem(B_TRANSLATE("Frost"), MSG_FROST_PALETTE) 403 .AddItem(B_TRANSLATE("Fire"), MSG_FIRE_PALETTE) 404 .AddItem(B_TRANSLATE("Midnight"), MSG_MIDNIGHT_PALETTE) 405 .AddItem(B_TRANSLATE("Grassland"), MSG_GRASSLAND_PALETTE) 406 .AddItem(B_TRANSLATE("Lightning"), MSG_LIGHTNING_PALETTE) 407 .AddItem(B_TRANSLATE("Spring"), MSG_SPRING_PALETTE) 408 .AddItem(B_TRANSLATE("High contrast"), MSG_HIGHCONTRAST_PALETTE) 409 .End() 410 .AddMenu(B_TRANSLATE("Iterations")) 411 .GetMenu(iterMenu) 412 .AddItem("128", MSG_ITER_128) 413 .AddItem("512", MSG_ITER_512) 414 .AddItem("1024", MSG_ITER_1024) 415 .AddItem("4096", MSG_ITER_4096) 416 .AddItem("8192", MSG_ITER_8192) 417 .AddItem("12288", MSG_ITER_12288) 418 .AddItem("16384", MSG_ITER_16384) 419 .End() 420 .End(); 421 setMenu->SetRadioMode(true); 422 setMenu->FindItem(MSG_MANDELBROT_SET)->SetMarked(true); 423 paletteMenu->SetRadioMode(true); 424 paletteMenu->FindItem(MSG_ROYAL_PALETTE)->SetMarked(true); 425 iterMenu->SetRadioMode(true); 426 iterMenu->FindItem(MSG_ITER_1024)->SetMarked(true); 427 428 BLayoutBuilder::Group<>(this, B_VERTICAL, 0) 429 .SetInsets(0) 430 .Add(menuBar) 431 .Add(fFractalView) 432 .End(); 433 } 434 435 436 #define HANDLE_SET(uiwhat, id) \ 437 case uiwhat: { \ 438 BMessage msg(FractalEngine::MSG_CHANGE_SET); \ 439 msg.AddUInt8("set", id); \ 440 fFractalView->fFractalEngine->PostMessage(&msg); \ 441 fFractalView->ResetPosition(); \ 442 fFractalView->RedrawFractal(); \ 443 break; \ 444 } 445 #define HANDLE_PALETTE(uiwhat, id) \ 446 case uiwhat: { \ 447 BMessage msg(FractalEngine::MSG_SET_PALETTE); \ 448 msg.AddUInt8("palette", id); \ 449 fFractalView->fFractalEngine->PostMessage(&msg); \ 450 fFractalView->RedrawFractal(); \ 451 break; \ 452 } 453 #define HANDLE_ITER(uiwhat, id) \ 454 case uiwhat: { \ 455 BMessage msg(FractalEngine::MSG_SET_ITERATIONS); \ 456 msg.AddUInt16("iterations", id); \ 457 fFractalView->fFractalEngine->PostMessage(&msg); \ 458 fFractalView->RedrawFractal(); \ 459 break; \ 460 } 461 void 462 MandelbrotWindow::MessageReceived(BMessage* msg) 463 { 464 switch (msg->what) { 465 HANDLE_SET(MSG_MANDELBROT_SET, 0) 466 HANDLE_SET(MSG_BURNINGSHIP_SET, 1) 467 HANDLE_SET(MSG_TRICORN_SET, 2) 468 HANDLE_SET(MSG_JULIA_SET, 3) 469 HANDLE_SET(MSG_ORBITTRAP_SET, 4) 470 HANDLE_SET(MSG_MULTIBROT_SET, 5) 471 472 HANDLE_PALETTE(MSG_ROYAL_PALETTE, 0) 473 HANDLE_PALETTE(MSG_DEEPFROST_PALETTE, 1) 474 HANDLE_PALETTE(MSG_FROST_PALETTE, 2) 475 HANDLE_PALETTE(MSG_FIRE_PALETTE, 3) 476 HANDLE_PALETTE(MSG_MIDNIGHT_PALETTE, 4) 477 HANDLE_PALETTE(MSG_GRASSLAND_PALETTE, 5) 478 HANDLE_PALETTE(MSG_LIGHTNING_PALETTE, 6) 479 HANDLE_PALETTE(MSG_SPRING_PALETTE, 7) 480 HANDLE_PALETTE(MSG_HIGHCONTRAST_PALETTE, 8) 481 482 HANDLE_ITER(MSG_ITER_128, 128) 483 HANDLE_ITER(MSG_ITER_512, 512) 484 HANDLE_ITER(MSG_ITER_1024, 1024) 485 HANDLE_ITER(MSG_ITER_4096, 4096) 486 HANDLE_ITER(MSG_ITER_8192, 8192) 487 HANDLE_ITER(MSG_ITER_12288, 12288) 488 HANDLE_ITER(MSG_ITER_16384, 16384) 489 490 case B_ABOUT_REQUESTED: { 491 BAboutWindow* wind = new BAboutWindow("Mandelbrot", 492 "application/x-vnd.Haiku-Mandelbrot"); 493 494 const char* authors[] = { 495 "Augustin Cavalier <waddlesplash>", 496 "kerwizzy", 497 NULL 498 }; 499 wind->AddCopyright(2016, "Haiku, Inc."); 500 wind->AddAuthors(authors); 501 wind->Show(); 502 break; 503 } 504 505 default: 506 BWindow::MessageReceived(msg); 507 break; 508 } 509 } 510 #undef HANDLE_SET 511 #undef HANDLE_PALETTE 512 #undef HANDLE_ITER 513 514 515 bool 516 MandelbrotWindow::QuitRequested() 517 { 518 if (BWindow::QuitRequested()) { 519 be_app->PostMessage(B_QUIT_REQUESTED); 520 return true; 521 } 522 return false; 523 } 524 525 526 // #pragma mark - MandelbrotApp 527 528 529 class MandelbrotApp : public BApplication 530 { 531 public: 532 MandelbrotApp() 533 : BApplication("application/x-vnd.Haiku-Mandelbrot") {} 534 535 void ReadyToRun(); 536 bool QuitRequested() { return true; } 537 }; 538 539 540 void 541 MandelbrotApp::ReadyToRun() 542 { 543 MandelbrotWindow* wind = new MandelbrotWindow(BRect(0, 0, 640, 480)); 544 wind->CenterOnScreen(); 545 wind->Show(); 546 } 547 548 549 int 550 main(int argc, char* argv[]) 551 { 552 MandelbrotApp().Run(); 553 return 0; 554 } 555