#include "Config.h" #include #include #ifdef WIN #include #endif #include #if defined(LIN) # include "icon_exe.png.h" #endif #include #ifndef WIN #include #endif #ifdef MACOSX # include "common/macosx.h" #endif #include #include "Format.h" #include "Misc.h" #include "graphics/Graphics.h" #include "client/SaveInfo.h" #include "client/GameSave.h" #include "client/SaveFile.h" #include "gui/game/GameController.h" #include "gui/game/GameView.h" #include "gui/font/FontEditor.h" #include "gui/dialogues/ErrorMessage.h" #include "gui/dialogues/ConfirmPrompt.h" #include "gui/Style.h" #include "gui/interface/Engine.h" int desktopWidth = 1280, desktopHeight = 1024; SDL_Window * sdl_window; SDL_Renderer * sdl_renderer; SDL_Texture * sdl_texture; int scale = 1; bool fullscreen = false; bool altFullscreen = false; bool forceIntegerScaling = true; bool resizable = false; void StartTextInput() { SDL_StartTextInput(); } void StopTextInput() { SDL_StopTextInput(); } void SetTextInputRect(int x, int y, int w, int h) { SDL_Rect rect; rect.x = x; rect.y = y; rect.w = w; rect.h = h; SDL_SetTextInputRect(&rect); } void ClipboardPush(ByteString text) { SDL_SetClipboardText(text.c_str()); } ByteString ClipboardPull() { return ByteString(SDL_GetClipboardText()); } int GetModifiers() { return SDL_GetModState(); } void CalculateMousePosition(int *x, int *y) { int globalMx, globalMy; SDL_GetGlobalMouseState(&globalMx, &globalMy); int windowX, windowY; SDL_GetWindowPosition(sdl_window, &windowX, &windowY); if (x) *x = (globalMx - windowX) / scale; if (y) *y = (globalMy - windowY) / scale; } void blit(pixel * vid) { SDL_UpdateTexture(sdl_texture, NULL, vid, WINDOWW * sizeof (Uint32)); // need to clear the renderer if there are black edges (fullscreen, or resizable window) if (fullscreen || resizable) SDL_RenderClear(sdl_renderer); SDL_RenderCopy(sdl_renderer, sdl_texture, NULL, NULL); SDL_RenderPresent(sdl_renderer); } void RecreateWindow(); int SDLOpen() { if (SDL_Init(SDL_INIT_VIDEO) < 0) { fprintf(stderr, "Initializing SDL: %s\n", SDL_GetError()); return 1; } RecreateWindow(); int displayIndex = SDL_GetWindowDisplayIndex(sdl_window); if (displayIndex >= 0) { SDL_Rect rect; if (!SDL_GetDisplayUsableBounds(displayIndex, &rect)) { desktopWidth = rect.w; desktopHeight = rect.h; } } #ifdef LIN std::vector imageData; int imgw, imgh; if (PngDataToPixels(imageData, imgw, imgh, reinterpret_cast(icon_exe_png), icon_exe_png_size, false)) { SDL_Surface *icon = SDL_CreateRGBSurfaceFrom(&imageData[0], imgw, imgh, 32, imgw * sizeof(pixel), 0x00FF0000, 0x0000FF00, 0x000000FF, 0xFF000000); SDL_SetWindowIcon(sdl_window, icon); SDL_FreeSurface(icon); } #endif return 0; } void SDLSetScreen(int scale_, bool resizable_, bool fullscreen_, bool altFullscreen_, bool forceIntegerScaling_) { // bool changingScale = scale != scale_; bool changingFullscreen = fullscreen_ != fullscreen || (altFullscreen_ != altFullscreen && fullscreen); bool changingResizable = resizable != resizable_; scale = scale_; fullscreen = fullscreen_; altFullscreen = altFullscreen_; resizable = resizable_; forceIntegerScaling = forceIntegerScaling_; // Recreate the window when toggling fullscreen, due to occasional issues // Also recreate it when enabling resizable windows, to fix bugs on windows, // see https://github.com/jacob1/The-Powder-Toy/issues/24 if (changingFullscreen || (changingResizable && resizable && !fullscreen)) { RecreateWindow(); return; } if (changingResizable) SDL_RestoreWindow(sdl_window); SDL_SetWindowSize(sdl_window, WINDOWW * scale, WINDOWH * scale); SDL_RenderSetIntegerScale(sdl_renderer, forceIntegerScaling && fullscreen ? SDL_TRUE : SDL_FALSE); unsigned int flags = 0; if (fullscreen) flags = altFullscreen ? SDL_WINDOW_FULLSCREEN : SDL_WINDOW_FULLSCREEN_DESKTOP; SDL_SetWindowFullscreen(sdl_window, flags); if (fullscreen) SDL_RaiseWindow(sdl_window); SDL_SetWindowResizable(sdl_window, resizable ? SDL_TRUE : SDL_FALSE); } void RecreateWindow() { unsigned int flags = 0; if (fullscreen) flags = altFullscreen ? SDL_WINDOW_FULLSCREEN : SDL_WINDOW_FULLSCREEN_DESKTOP; if (resizable && !fullscreen) flags |= SDL_WINDOW_RESIZABLE; if (sdl_texture) SDL_DestroyTexture(sdl_texture); if (sdl_renderer) SDL_DestroyRenderer(sdl_renderer); if (sdl_window) { SDL_DestroyWindow(sdl_window); } sdl_window = SDL_CreateWindow(APPNAME, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, WINDOWW * scale, WINDOWH * scale, flags); sdl_renderer = SDL_CreateRenderer(sdl_window, -1, 0); SDL_RenderSetLogicalSize(sdl_renderer, WINDOWW, WINDOWH); if (forceIntegerScaling && fullscreen) SDL_RenderSetIntegerScale(sdl_renderer, SDL_TRUE); sdl_texture = SDL_CreateTexture(sdl_renderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, WINDOWW, WINDOWH); SDL_RaiseWindow(sdl_window); SDL_SetHint(SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH, "1"); //Uncomment this to enable resizing //SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "linear"); //SDL_SetWindowResizable(sdl_window, SDL_TRUE); } unsigned int GetTicks() { return SDL_GetTicks(); } int elapsedTime = 0, currentTime = 0, lastTime = 0, currentFrame = 0; unsigned int lastTick = 0; unsigned int lastFpsUpdate = 0; float fps = 0; ui::Engine * engine = NULL; bool showDoubleScreenDialog = false; float currentWidth, currentHeight; int mousex = 0, mousey = 0; int mouseButton = 0; bool mouseDown = false; bool calculatedInitialMouse = false, delay = false; bool hasMouseMoved = false; void EventProcess(SDL_Event event) { switch (event.type) { case SDL_QUIT: if (engine->GetFastQuit() || engine->CloseWindow()) engine->Exit(); break; case SDL_KEYDOWN: if (SDL_GetModState() & KMOD_GUI) { break; } if (!event.key.repeat && event.key.keysym.sym == 'q' && (event.key.keysym.mod&KMOD_CTRL)) engine->ConfirmExit(); else engine->onKeyPress(event.key.keysym.sym, event.key.keysym.scancode, event.key.repeat, event.key.keysym.mod&KMOD_SHIFT, event.key.keysym.mod&KMOD_CTRL, event.key.keysym.mod&KMOD_ALT); break; case SDL_KEYUP: if (SDL_GetModState() & KMOD_GUI) { break; } engine->onKeyRelease(event.key.keysym.sym, event.key.keysym.scancode, event.key.repeat, event.key.keysym.mod&KMOD_SHIFT, event.key.keysym.mod&KMOD_CTRL, event.key.keysym.mod&KMOD_ALT); break; case SDL_TEXTINPUT: if (SDL_GetModState() & KMOD_GUI) { break; } engine->onTextInput(ByteString(event.text.text).FromUtf8()); break; case SDL_TEXTEDITING: if (SDL_GetModState() & KMOD_GUI) { break; } engine->onTextEditing(ByteString(event.edit.text).FromUtf8(), event.edit.start); break; case SDL_MOUSEWHEEL: { // int x = event.wheel.x; int y = event.wheel.y; if (event.wheel.direction == SDL_MOUSEWHEEL_FLIPPED) { // x *= -1; y *= -1; } engine->onMouseWheel(mousex, mousey, y); // TODO: pass x? break; } case SDL_MOUSEMOTION: mousex = event.motion.x; mousey = event.motion.y; engine->onMouseMove(mousex, mousey); hasMouseMoved = true; break; case SDL_DROPFILE: engine->onFileDrop(event.drop.file); SDL_free(event.drop.file); break; case SDL_MOUSEBUTTONDOWN: // if mouse hasn't moved yet, sdl will send 0,0. We don't want that if (hasMouseMoved) { mousex = event.motion.x; mousey = event.motion.y; } mouseButton = event.button.button; engine->onMouseClick(event.motion.x, event.motion.y, mouseButton); mouseDown = true; #if !defined(NDEBUG) && !defined(DEBUG) SDL_CaptureMouse(SDL_TRUE); #endif break; case SDL_MOUSEBUTTONUP: // if mouse hasn't moved yet, sdl will send 0,0. We don't want that if (hasMouseMoved) { mousex = event.motion.x; mousey = event.motion.y; } mouseButton = event.button.button; engine->onMouseUnclick(mousex, mousey, mouseButton); mouseDown = false; #if !defined(NDEBUG) && !defined(DEBUG) SDL_CaptureMouse(SDL_FALSE); #endif break; case SDL_WINDOWEVENT: { switch (event.window.event) { case SDL_WINDOWEVENT_SHOWN: if (!calculatedInitialMouse) { //initial mouse coords, sdl won't tell us this if mouse hasn't moved CalculateMousePosition(&mousex, &mousey); engine->onMouseMove(mousex, mousey); calculatedInitialMouse = true; } break; // This event would be needed in certain glitchy cases of window resizing // But for all currently tested cases, it isn't needed /*case SDL_WINDOWEVENT_RESIZED: { float width = event.window.data1; float height = event.window.data2; currentWidth = width; currentHeight = height; // this "* scale" thing doesn't really work properly // currently there is a bug where input doesn't scale properly after resizing, only when double scale mode is active inputScaleH = (float)WINDOWW * scale / currentWidth; inputScaleV = (float)WINDOWH * scale / currentHeight; std::cout << "Changing input scale to " << inputScaleH << "x" << inputScaleV << std::endl; break; }*/ // This would send a mouse up event when focus is lost // Not even sdl itself will know when the mouse was released if it happens in another window // So it will ignore the next mouse down (after tpt is re-focused) and not send any events at all // This is more unintuitive than pretending the mouse is still down when it's not, so this code is commented out /*case SDL_WINDOWEVENT_FOCUS_LOST: if (mouseDown) { mouseDown = false; engine->onMouseUnclick(mousex, mousey, mouseButton); } break;*/ } break; } } } void EngineProcess() { double frameTimeAvg = 0.0f, correctedFrameTimeAvg = 0.0f; SDL_Event event; while(engine->Running()) { int frameStart = SDL_GetTicks(); if(engine->Broken()) { engine->UnBreak(); break; } event.type = 0; while (SDL_PollEvent(&event)) { EventProcess(event); event.type = 0; //Clear last event } if(engine->Broken()) { engine->UnBreak(); break; } engine->Tick(); engine->Draw(); if (scale != engine->Scale || fullscreen != engine->Fullscreen || altFullscreen != engine->GetAltFullscreen() || forceIntegerScaling != engine->GetForceIntegerScaling() || resizable != engine->GetResizable()) { SDLSetScreen(engine->Scale, engine->GetResizable(), engine->Fullscreen, engine->GetAltFullscreen(), engine->GetForceIntegerScaling()); } blit(engine->g->vid); int frameTime = SDL_GetTicks() - frameStart; frameTimeAvg = frameTimeAvg * 0.8 + frameTime * 0.2; float fpsLimit = ui::Engine::Ref().FpsLimit; if(fpsLimit > 2) { double offset = 1000.0 / fpsLimit - frameTimeAvg; if(offset > 0) SDL_Delay(Uint32(offset + 0.5)); } int correctedFrameTime = SDL_GetTicks() - frameStart; correctedFrameTimeAvg = correctedFrameTimeAvg * 0.95 + correctedFrameTime * 0.05; if (frameStart - lastFpsUpdate > 200) { engine->SetFps(1000.0 / correctedFrameTimeAvg); lastFpsUpdate = frameStart; } if (frameStart - lastTick > 100) { lastTick = frameStart; } if (showDoubleScreenDialog) { showDoubleScreenDialog = false; } } #ifdef DEBUG std::cout << "Breaking out of EngineProcess" << std::endl; #endif } int main(int argc, char * argv[]) { currentWidth = WINDOWW; currentHeight = WINDOWH; scale = 1; if (argc >= 3) { std::istringstream ss(argv[2]); int buf; if (ss >> buf) { scale = buf; } } resizable = false; fullscreen = false; altFullscreen = false; forceIntegerScaling = true; // TODO: maybe bind the maximum allowed scale to screen size somehow if(scale < 1 || scale > 10) scale = 1; SDLOpen(); StopTextInput(); ui::Engine::Ref().g = new Graphics(); ui::Engine::Ref().Scale = scale; ui::Engine::Ref().SetResizable(resizable); ui::Engine::Ref().Fullscreen = fullscreen; ui::Engine::Ref().SetAltFullscreen(altFullscreen); ui::Engine::Ref().SetForceIntegerScaling(forceIntegerScaling); engine = &ui::Engine::Ref(); engine->SetMaxSize(desktopWidth, desktopHeight); engine->Begin(WINDOWW, WINDOWH); engine->SetFastQuit(true); GameController * gameController = NULL; if (argc >= 2) { engine->ShowWindow(new FontEditor(argv[1])); } else { std::cerr << "path to font.cpp not supplied" << std::endl; exit(1); } EngineProcess(); ui::Engine::Ref().CloseWindow(); delete gameController; delete ui::Engine::Ref().g; if (SDL_GetWindowFlags(sdl_window) & SDL_WINDOW_OPENGL) { // * nvidia-460 egl registers callbacks with x11 that end up being called // after egl is unloaded unless we grab it here and release it after // sdl closes the display. this is an nvidia driver weirdness but // technically an sdl bug. glfw has this fixed: // https://github.com/glfw/glfw/commit/9e6c0c747be838d1f3dc38c2924a47a42416c081 SDL_GL_LoadLibrary(NULL); SDL_QuitSubSystem(SDL_INIT_VIDEO); SDL_GL_UnloadLibrary(); } SDL_Quit(); return 0; }