/* * Copyright 2016, Haiku, Inc. All rights reserved. * Distributed under the terms of the MIT license. * * Authors: * Augustin Cavalier * kerwizzy */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "FractalEngine.h" #undef B_TRANSLATION_CONTEXT #define B_TRANSLATION_CONTEXT "MandelbrotWindow" #define MANDELBROT_VIEW_REFRESH_FPS 10 // #pragma mark - FractalView //#define TRACE_MANDELBROT_VIEW #ifdef TRACE_MANDELBROT_VIEW # include # define TRACE(x...) printf(x) #else # define TRACE(x...) #endif class FractalView : public BView { public: FractalView(); ~FractalView(); virtual void AttachedToWindow(); virtual void FrameResized(float, float); virtual void Pulse(); virtual void MouseDown(BPoint where); virtual void MouseMoved(BPoint where, uint32 mode, const BMessage*); virtual void MouseUp(BPoint where); virtual void MessageReceived(BMessage* msg); virtual void Draw(BRect updateRect); void ResetPosition(); void SetLocationFromFrame(double frameX, double frameY); void ZoomFractal(double originX, double originY, double zoomFactor); void ZoomFractalFromFrame(double frameOriginX, double frameOriginY, double zoomFactor); void ImportBitsAndInvalidate(); void RedrawFractal(); void UpdateSize(); void CreateDisplayBitmap(uint16 width, uint16 height); void StartSave(); void WriteImage(entry_ref*, char*); void EndSave(); FractalEngine* fFractalEngine; enum { MSG_START_SAVE, MSG_WRITE_IMAGE }; private: BRect GetDragFrame(); BPoint fSelectStart; BPoint fSelectEnd; bool fSelecting; uint32 fMouseButtons; BBitmap* fDisplayBitmap; double fLocationX; double fLocationY; double fSize; BFilePanel* fSavePanel; bool fSaving; }; FractalView::FractalView() : BView(NULL, B_WILL_DRAW | B_FRAME_EVENTS | B_PULSE_NEEDED), fFractalEngine(NULL), fSelecting(false), fDisplayBitmap(NULL), fLocationX(0), fLocationY(0), fSize(0.005), fSavePanel(NULL), fSaving(false) { SetHighColor(make_color(255, 255, 255, 255)); } FractalView::~FractalView() { delete fDisplayBitmap; } void FractalView::ResetPosition() { fLocationX = 0; fLocationY = 0; fSize = 0.005; } void FractalView::AttachedToWindow() { fFractalEngine = new FractalEngine(this, Window()); fFractalEngine->Run(); TRACE("Attached to window\n"); } void FractalView::FrameResized(float, float) { TRACE("Frame Resize\n"); UpdateSize(); } void FractalView::UpdateSize() { TRACE("Update Size\n"); BMessage msg(FractalEngine::MSG_RESIZE); uint16 width = (uint16)Frame().Width(); uint16 height = (uint16)Frame().Height(); msg.AddUInt16("width", width); msg.AddUInt16("height", height); CreateDisplayBitmap(width, height); msg.AddPointer("bitmap", fDisplayBitmap); fFractalEngine->PostMessage(&msg); // Create the new buffer } void FractalView::CreateDisplayBitmap(uint16 width,uint16 height) { delete fDisplayBitmap; fDisplayBitmap = NULL; TRACE("width %u height %u\n",width,height); BRect rect(0, 0, width, height); fDisplayBitmap = new BBitmap(rect, B_RGB24); } BRect FractalView::GetDragFrame() { BRect dragZone = BRect(std::min(fSelectStart.x, fSelectEnd.x), std::min(fSelectStart.y, fSelectEnd.y), std::max(fSelectStart.x, fSelectEnd.x), std::max(fSelectStart.y, fSelectEnd.y)), frame = Frame(); float width = dragZone.Width(), height = width * (frame.Height() / frame.Width()); float x1 = fSelectStart.x, y1 = fSelectStart.y, x2, y2; if (fSelectStart.x < fSelectEnd.x) x2 = x1 + width; else x2 = x1 - width; if (fSelectStart.y < fSelectEnd.y) y2 = y1 + height; else y2 = y1 - height; return BRect(x1, y1, x2, y2); } void FractalView::MouseDown(BPoint where) { fSelecting = true; fSelectStart = where; fMouseButtons = 0; Window()->CurrentMessage()->FindInt32("buttons", (int32*)&fMouseButtons); } void FractalView::MouseMoved(BPoint where, uint32 mode, const BMessage*) { if (fSelecting) { fSelectEnd = where; Invalidate(); } } void FractalView::SetLocationFromFrame(double frameX,double frameY) { BRect frame = Frame(); fLocationX = ((frameX - frame.Width() / 2) * fSize + fLocationX); fLocationY = ((frameY - frame.Height() / 2) * -fSize + fLocationY); // -fSize because is in raster coordinates (y swapped) } void FractalView::ZoomFractalFromFrame(double frameOriginX, double frameOriginY, double zoomFactor) { BRect frame = Frame(); ZoomFractal((frameOriginX - frame.Width() / 2) * fSize + fLocationX, (frameOriginY - frame.Height() / 2) * -fSize + fLocationY, zoomFactor); } void FractalView::ZoomFractal(double originX, double originY, double zoomFactor) { double deltaX = originX - fLocationX; double deltaY = originY - fLocationY; TRACE("oX %g oY %g zoom %g\n", originX, originY, zoomFactor); deltaX /= zoomFactor; deltaY /= zoomFactor; fLocationX = originX - deltaX; fLocationY = originY - deltaY; fSize /= zoomFactor; } void FractalView::MouseUp(BPoint where) { BRect frame = Frame(); fSelecting = false; if (fabs(fSelectStart.x - where.x) > 4) { fSelectEnd = where; BRect dragFrame = GetDragFrame(); BPoint lt = dragFrame.LeftTop(); float centerX = lt.x + dragFrame.Width() / 2, centerY = lt.y + dragFrame.Height() / 2; SetLocationFromFrame(centerX, centerY); fSize = std::fabs((dragFrame.Width() * fSize) / frame.Width()); } else { if (fMouseButtons & B_PRIMARY_MOUSE_BUTTON) { SetLocationFromFrame(where.x, where.y); ZoomFractal(fLocationX, fLocationY, 2); } else { ZoomFractal(fLocationX, fLocationY, 0.5); } } RedrawFractal(); } void FractalView::MessageReceived(BMessage* msg) { switch (msg->what) { case B_MOUSE_WHEEL_CHANGED: { float change = msg->FindFloat("be:wheel_delta_y"); BPoint where; GetMouse(&where, NULL); double zoomFactor; if (change < 0) zoomFactor = 3.0/2.0; else zoomFactor = 2.0/3.0; ZoomFractalFromFrame(where.x, where.y, zoomFactor); RedrawFractal(); break; } case FractalEngine::MSG_BUFFER_CREATED: TRACE("Got buffer created msg.\n"); ImportBitsAndInvalidate(); RedrawFractal(); break; case FractalEngine::MSG_RENDER_COMPLETE: TRACE("Got render complete msg.\n"); Window()->SetPulseRate(0); ImportBitsAndInvalidate(); break; case MSG_WRITE_IMAGE: { delete fSavePanel; fSavePanel = NULL; entry_ref dirRef; char* name; msg->FindRef("directory", &dirRef); msg->FindString((const char*)"name", (const char**) &name); WriteImage(&dirRef, name); break; } case B_CANCEL: // image is frozen before the FilePanel is shown EndSave(); break; default: BView::MessageReceived(msg); break; } } void FractalView::Pulse() { ImportBitsAndInvalidate(); } void FractalView::ImportBitsAndInvalidate() { if (fSaving) { TRACE("Not importing bits because saving.\n"); return; } TRACE("Importing bits...\n"); fFractalEngine->WriteToBitmap(fDisplayBitmap); Invalidate(); } void FractalView::RedrawFractal() { Window()->SetPulseRate(1000000 / MANDELBROT_VIEW_REFRESH_FPS); BMessage message(FractalEngine::MSG_RENDER); message.AddDouble("locationX", fLocationX); message.AddDouble("locationY", fLocationY); message.AddDouble("size", fSize); fFractalEngine->PostMessage(&message); } void FractalView::Draw(BRect updateRect) { DrawBitmap(fDisplayBitmap, updateRect, updateRect); if (fSelecting) StrokeRect(GetDragFrame()); } void FractalView::StartSave() { TRACE("Got to start save\n"); fSaving = true; BMessenger messenger(this); BMessage message(MSG_WRITE_IMAGE); fSavePanel = new BFilePanel(B_SAVE_PANEL, &messenger, 0, 0, false, &message); BString* filename = new BString(); filename->SetToFormat("%g-%g-%g.png", fLocationX, fLocationY, fSize); fSavePanel->SetSaveText(filename->String()); fSavePanel->Show(); } void FractalView::WriteImage(entry_ref* dirRef, char* name) { TRACE("Got to write save handler\n"); BFile file; BDirectory parentDir(dirRef); parentDir.CreateFile(name, &file); // Write the screenshot bitmap to the file BBitmapStream stream(fDisplayBitmap); BTranslatorRoster* roster = BTranslatorRoster::Default(); roster->Translate(&stream, NULL, NULL, &file, B_PNG_FORMAT, B_TRANSLATOR_BITMAP); BNodeInfo info(&file); if (info.InitCheck() == B_OK) info.SetType("image/png"); BBitmap* bitmap; stream.DetachBitmap(&bitmap); // The stream takes over ownership of the bitmap // unfreeze the image, image was frozen before invoke of FilePanel EndSave(); } void FractalView::EndSave() { fSaving = false; ImportBitsAndInvalidate(); } // #pragma mark - MandelbrotWindow class MandelbrotWindow : public BWindow { public: enum { MSG_MANDELBROT_SET = 'MndW', MSG_BURNINGSHIP_SET, MSG_TRICORN_SET, MSG_JULIA_SET, MSG_ORBITTRAP_SET, MSG_MULTIBROT_SET, MSG_ROYAL_PALETTE, MSG_DEEPFROST_PALETTE, MSG_FROST_PALETTE, MSG_FIRE_PALETTE, MSG_MIDNIGHT_PALETTE, MSG_GRASSLAND_PALETTE, MSG_LIGHTNING_PALETTE, MSG_SPRING_PALETTE, MSG_HIGHCONTRAST_PALETTE, MSG_ITER_128, MSG_ITER_512, MSG_ITER_1024, MSG_ITER_4096, MSG_ITER_8192, MSG_ITER_12288, MSG_ITER_16384, MSG_SUBSAMPLING_1, MSG_SUBSAMPLING_2, MSG_SUBSAMPLING_3, MSG_SUBSAMPLING_4, MSG_TOGGLE_FULLSCREEN }; MandelbrotWindow(BRect frame); ~MandelbrotWindow() {} void ToggleFullscreen(); virtual void DispatchMessage(BMessage* message, BHandler* target); virtual void MessageReceived(BMessage* msg); virtual bool QuitRequested(); bool fFullScreen; BMenuBar* fMenuBar; BRect fWindowFrame; private: FractalView* fFractalView; }; MandelbrotWindow::MandelbrotWindow(BRect frame) : BWindow(frame, B_TRANSLATE_SYSTEM_NAME("Mandelbrot"), B_TITLED_WINDOW_LOOK, B_NORMAL_WINDOW_FEEL, 0L), fFractalView(new FractalView) { fFullScreen = false; fMenuBar = new BMenuBar("MenuBar"); BMenu* setMenu; BMenu* paletteMenu; BMenu* iterMenu; BMenu* subsamplingMenu; BLayoutBuilder::Menu<>(fMenuBar) .AddMenu(B_TRANSLATE("File")) .AddItem(B_TRANSLATE("Save as image" B_UTF8_ELLIPSIS), FractalView::MSG_START_SAVE, 'S') .AddSeparator() .AddItem(B_TRANSLATE("About"), B_ABOUT_REQUESTED) .AddItem(B_TRANSLATE("Quit"), B_QUIT_REQUESTED, 'Q') .End() .AddMenu(B_TRANSLATE("View")) .AddItem(B_TRANSLATE("Full screen"), MSG_TOGGLE_FULLSCREEN, B_RETURN) .End() .AddMenu(B_TRANSLATE("Set")) .GetMenu(setMenu) .AddItem(B_TRANSLATE("Mandelbrot"), MSG_MANDELBROT_SET) .AddItem(B_TRANSLATE("Burning Ship"), MSG_BURNINGSHIP_SET) .AddItem(B_TRANSLATE("Tricorn"), MSG_TRICORN_SET) .AddItem(B_TRANSLATE("Julia"), MSG_JULIA_SET) .AddItem(B_TRANSLATE("Orbit Trap"), MSG_ORBITTRAP_SET) .AddItem(B_TRANSLATE("Multibrot"), MSG_MULTIBROT_SET) .End() .AddMenu(B_TRANSLATE("Palette")) .GetMenu(paletteMenu) .AddItem(B_TRANSLATE("Royal"), MSG_ROYAL_PALETTE) .AddItem(B_TRANSLATE("Deepfrost"), MSG_DEEPFROST_PALETTE) .AddItem(B_TRANSLATE("Frost"), MSG_FROST_PALETTE) .AddItem(B_TRANSLATE("Fire"), MSG_FIRE_PALETTE) .AddItem(B_TRANSLATE("Midnight"), MSG_MIDNIGHT_PALETTE) .AddItem(B_TRANSLATE("Grassland"), MSG_GRASSLAND_PALETTE) .AddItem(B_TRANSLATE("Lightning"), MSG_LIGHTNING_PALETTE) .AddItem(B_TRANSLATE("Spring"), MSG_SPRING_PALETTE) .AddItem(B_TRANSLATE("High contrast"), MSG_HIGHCONTRAST_PALETTE) .End() .AddMenu(B_TRANSLATE("Iterations")) .GetMenu(iterMenu) .AddItem("128", MSG_ITER_128) .AddItem("512", MSG_ITER_512) .AddItem("1024", MSG_ITER_1024) .AddItem("4096", MSG_ITER_4096) .AddItem("8192", MSG_ITER_8192) .AddItem("12288", MSG_ITER_12288) .AddItem("16384", MSG_ITER_16384) .End() .AddMenu(B_TRANSLATE("Subsampling")) .GetMenu(subsamplingMenu) .AddItem(B_TRANSLATE("1 (none)"), MSG_SUBSAMPLING_1) .AddItem("4", MSG_SUBSAMPLING_2) .AddItem("9", MSG_SUBSAMPLING_3) .AddItem("16", MSG_SUBSAMPLING_4) .End() .End(); setMenu->SetRadioMode(true); setMenu->FindItem(MSG_MANDELBROT_SET)->SetMarked(true); paletteMenu->SetRadioMode(true); paletteMenu->FindItem(MSG_ROYAL_PALETTE)->SetMarked(true); iterMenu->SetRadioMode(true); iterMenu->FindItem(MSG_ITER_1024)->SetMarked(true); subsamplingMenu->SetRadioMode(true); subsamplingMenu->FindItem(MSG_SUBSAMPLING_2)->SetMarked(true); BLayoutBuilder::Group<>(this, B_VERTICAL, 0) .SetInsets(0) .Add(fMenuBar) .Add(fFractalView) .End(); } void MandelbrotWindow::ToggleFullscreen() { BRect frame; fFullScreen = !fFullScreen; if (fFullScreen) { TRACE("Enabling fullscreen\n"); BScreen screen; fWindowFrame = Frame(); frame = screen.Frame(); frame.top -= fMenuBar->Bounds().Height() + 1; SetFlags(Flags() | B_NOT_RESIZABLE | B_NOT_MOVABLE); Activate(); // make the window frontmost } else { TRACE("Disabling fullscreen\n"); frame = fWindowFrame; SetFlags(Flags() & ~(B_NOT_RESIZABLE | B_NOT_MOVABLE)); } MoveTo(frame.left, frame.top); ResizeTo(frame.Width(), frame.Height()); Layout(false); } #define HANDLE_SET(uiwhat, id) \ case uiwhat: { \ BMessage msg(FractalEngine::MSG_CHANGE_SET); \ msg.AddUInt8("set", id); \ fFractalView->fFractalEngine->PostMessage(&msg); \ fFractalView->ResetPosition(); \ fFractalView->RedrawFractal(); \ break; \ } #define HANDLE_PALETTE(uiwhat, id) \ case uiwhat: { \ BMessage msg(FractalEngine::MSG_SET_PALETTE); \ msg.AddUInt8("palette", id); \ fFractalView->fFractalEngine->PostMessage(&msg); \ fFractalView->RedrawFractal(); \ break; \ } #define HANDLE_ITER(uiwhat, id) \ case uiwhat: { \ BMessage msg(FractalEngine::MSG_SET_ITERATIONS); \ msg.AddUInt16("iterations", id); \ fFractalView->fFractalEngine->PostMessage(&msg); \ fFractalView->RedrawFractal(); \ break; \ } #define HANDLE_SUBSAMPLING(uiwhat, id) \ case uiwhat: { \ BMessage msg(FractalEngine::MSG_SET_SUBSAMPLING); \ msg.AddUInt8("subsampling", id); \ fFractalView->fFractalEngine->PostMessage(&msg); \ fFractalView->RedrawFractal(); \ break; \ } void MandelbrotWindow::DispatchMessage(BMessage* message, BHandler* target) { const char* bytes; int32 modifierKeys; if ((message->what == B_KEY_DOWN || message->what == B_UNMAPPED_KEY_DOWN) && message->FindString("bytes", &bytes) == B_OK && message->FindInt32("modifiers", &modifierKeys) == B_OK) { if (bytes[0] == B_FUNCTION_KEY) { // Matches WebPositive fullscreen key (F11) int32 key; if (message->FindInt32("key", &key) == B_OK) { switch (key) { case B_F11_KEY: { ToggleFullscreen(); break; } default: break; } } } } BWindow::DispatchMessage(message, target); } void MandelbrotWindow::MessageReceived(BMessage* msg) { switch (msg->what) { HANDLE_SET(MSG_MANDELBROT_SET, 0) HANDLE_SET(MSG_BURNINGSHIP_SET, 1) HANDLE_SET(MSG_TRICORN_SET, 2) HANDLE_SET(MSG_JULIA_SET, 3) HANDLE_SET(MSG_ORBITTRAP_SET, 4) HANDLE_SET(MSG_MULTIBROT_SET, 5) HANDLE_PALETTE(MSG_ROYAL_PALETTE, 0) HANDLE_PALETTE(MSG_DEEPFROST_PALETTE, 1) HANDLE_PALETTE(MSG_FROST_PALETTE, 2) HANDLE_PALETTE(MSG_FIRE_PALETTE, 3) HANDLE_PALETTE(MSG_MIDNIGHT_PALETTE, 4) HANDLE_PALETTE(MSG_GRASSLAND_PALETTE, 5) HANDLE_PALETTE(MSG_LIGHTNING_PALETTE, 6) HANDLE_PALETTE(MSG_SPRING_PALETTE, 7) HANDLE_PALETTE(MSG_HIGHCONTRAST_PALETTE, 8) HANDLE_ITER(MSG_ITER_128, 128) HANDLE_ITER(MSG_ITER_512, 512) HANDLE_ITER(MSG_ITER_1024, 1024) HANDLE_ITER(MSG_ITER_4096, 4096) HANDLE_ITER(MSG_ITER_8192, 8192) HANDLE_ITER(MSG_ITER_12288, 12288) HANDLE_ITER(MSG_ITER_16384, 16384) HANDLE_SUBSAMPLING(MSG_SUBSAMPLING_1, 1) HANDLE_SUBSAMPLING(MSG_SUBSAMPLING_2, 2) HANDLE_SUBSAMPLING(MSG_SUBSAMPLING_3, 3) HANDLE_SUBSAMPLING(MSG_SUBSAMPLING_4, 4) case FractalView::MSG_START_SAVE: { fFractalView->StartSave(); break; } case MSG_TOGGLE_FULLSCREEN: ToggleFullscreen(); break; case B_ABOUT_REQUESTED: { BAboutWindow* wind = new BAboutWindow("Mandelbrot", "application/x-vnd.Haiku-Mandelbrot"); const char* authors[] = { "Augustin Cavalier ", "kerwizzy", NULL }; wind->AddCopyright(2016, "Haiku, Inc."); wind->AddAuthors(authors); wind->Show(); break; } case B_KEY_DOWN: { int8 val; if (msg->FindInt8("byte", &val) == B_OK && val == B_ESCAPE && fFullScreen) ToggleFullscreen(); break; } default: BWindow::MessageReceived(msg); break; } } #undef HANDLE_SET #undef HANDLE_PALETTE #undef HANDLE_ITER #undef HANDLE_SUBSAMPLING bool MandelbrotWindow::QuitRequested() { if (BWindow::QuitRequested()) { be_app->PostMessage(B_QUIT_REQUESTED); return true; } return false; } // #pragma mark - MandelbrotApp class MandelbrotApp : public BApplication { public: MandelbrotApp() : BApplication("application/x-vnd.Haiku-Mandelbrot") {} void ReadyToRun(); bool QuitRequested() { return true; } }; void MandelbrotApp::ReadyToRun() { MandelbrotWindow* wind = new MandelbrotWindow(BRect(0, 0, 640, 480)); wind->CenterOnScreen(); wind->Show(); } int main(int argc, char* argv[]) { MandelbrotApp().Run(); return 0; }