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