void MainWindow::newFrame() { if (lastUIState != GetUIState()) { lastUIState = GetUIState(); if (lastUIState == UISTATE_INGAME && g_Config.bFullScreen && !QApplication::overrideCursor() && !g_Config.bShowTouchControls) QApplication::setOverrideCursor(QCursor(Qt::BlankCursor)); if (lastUIState != UISTATE_INGAME && g_Config.bFullScreen && QApplication::overrideCursor()) QApplication::restoreOverrideCursor(); updateMenus(); } std::unique_lock<std::mutex> lock(msgMutex_); while (!msgQueue_.empty()) { MainWindowMsg msg = msgQueue_.front(); msgQueue_.pop(); switch (msg) { case MainWindowMsg::BOOT_DONE: bootDone(); break; case MainWindowMsg::WINDOW_TITLE_CHANGED: std::unique_lock<std::mutex> lock(titleMutex_); setWindowTitle(QString::fromUtf8(newWindowTitle_.c_str())); break; } } }
void Core_RunLoop() { while ((GetUIState() != UISTATE_INGAME || !PSP_IsInited()) && GetUIState() != UISTATE_EXIT) { time_update(); #if defined(USING_WIN_UI) double startTime = time_now_d(); UpdateRunLoop(); // Simple throttling to not burn the GPU in the menu. time_update(); double diffTime = time_now_d() - startTime; int sleepTime = (int)(1000.0 / 60.0) - (int)(diffTime * 1000.0); if (sleepTime > 0) Sleep(sleepTime); if (!windowHidden) { GL_SwapBuffers(); } #else UpdateRunLoop(); #endif } while (!coreState && GetUIState() == UISTATE_INGAME) { time_update(); UpdateRunLoop(); #if defined(USING_WIN_UI) if (!windowHidden && !Core_IsStepping()) { GL_SwapBuffers(); } #endif } }
void MainWindow::newFrame() { if (lastUIState != GetUIState()) { lastUIState = GetUIState(); if (lastUIState == UISTATE_INGAME && g_Config.bFullScreen && !QApplication::overrideCursor() && !g_Config.bShowTouchControls) QApplication::setOverrideCursor(QCursor(Qt::BlankCursor)); if (lastUIState != UISTATE_INGAME && g_Config.bFullScreen && QApplication::overrideCursor()) QApplication::restoreOverrideCursor(); updateMenus(); } }
void WindowsHost::PollControllers() { bool doPad = true; for (auto iter = this->input.begin(); iter != this->input.end(); iter++) { auto device = *iter; if (!doPad && device->IsPad()) continue; if (device->UpdateState() == InputDevice::UPDATESTATE_SKIP_PAD) doPad = false; } float scaleFactor_x = g_dpi_scale_x * 0.1 * g_Config.fMouseSensitivity; float scaleFactor_y = g_dpi_scale_y * 0.1 * g_Config.fMouseSensitivity; float mx = std::max(-1.0f, std::min(1.0f, g_mouseDeltaX * scaleFactor_x)); float my = std::max(-1.0f, std::min(1.0f, g_mouseDeltaY * scaleFactor_y)); AxisInput axisX, axisY; axisX.axisId = JOYSTICK_AXIS_MOUSE_REL_X; axisX.deviceId = DEVICE_ID_MOUSE; axisX.value = mx; axisY.axisId = JOYSTICK_AXIS_MOUSE_REL_Y; axisY.deviceId = DEVICE_ID_MOUSE; axisY.value = my; // Disabled by default, needs a workaround to map to psp keys. if (g_Config.bMouseControl){ if (GetUIState() == UISTATE_INGAME || g_Config.bMapMouse) { if (fabsf(mx) > 0.01f) NativeAxis(axisX); if (fabsf(my) > 0.01f) NativeAxis(axisY); } } g_mouseDeltaX *= g_Config.fMouseSmoothing; g_mouseDeltaY *= g_Config.fMouseSmoothing; }
void Core_RunLoop(GraphicsContext *ctx, InputState *input_state) { graphicsContext = ctx; while ((GetUIState() != UISTATE_INGAME || !PSP_IsInited()) && GetUIState() != UISTATE_EXIT) { time_update(); #if defined(USING_WIN_UI) double startTime = time_now_d(); UpdateRunLoop(input_state); // Simple throttling to not burn the GPU in the menu. time_update(); double diffTime = time_now_d() - startTime; int sleepTime = (int)(1000.0 / 60.0) - (int)(diffTime * 1000.0); if (sleepTime > 0) Sleep(sleepTime); if (!windowHidden) { ctx->SwapBuffers(); } #else UpdateRunLoop(input_state); #endif } while (!coreState && GetUIState() == UISTATE_INGAME) { time_update(); UpdateRunLoop(input_state); #if defined(USING_WIN_UI) if (!windowHidden && !Core_IsStepping()) { ctx->SwapBuffers(); // Keep the system awake for longer than normal for cutscenes and the like. const double now = time_now_d(); if (now < lastActivity + ACTIVITY_IDLE_TIMEOUT) { // Only resetting it ever prime number seconds in case the call is expensive. // Using a prime number to ensure there's no interaction with other periodic events. if (now - lastKeepAwake > 89.0 || now < lastKeepAwake) { SetThreadExecutionState(ES_SYSTEM_REQUIRED | ES_DISPLAY_REQUIRED); lastKeepAwake = now; } } } #endif } }
void CorrectCursor() { bool autoHide = g_Config.bFullScreen && !mouseButtonDown && GetUIState() == UISTATE_INGAME; if (autoHide && hideCursor) { while (cursorCounter >= 0) { cursorCounter = ShowCursor(FALSE); } } else { hideCursor = !autoHide; if (cursorCounter < 0) { cursorCounter = ShowCursor(TRUE); SetCursor(LoadCursor(NULL, IDC_ARROW)); } } }
void UpdateRunLoop() { if (windowHidden && g_Config.bPauseWhenMinimized) { sleep_ms(16); return; } NativeUpdate(input_state); { lock_guard guard(input_state.lock); EndInputState(&input_state); } if (GetUIState() != UISTATE_EXIT) { NativeRender(); } }
inline void MapWindow::RenderRasp(Canvas &canvas) { if (rasp_store == nullptr) return; const WeatherUIState &state = GetUIState().weather; if (rasp_renderer && state.map != (int)rasp_renderer->GetParameter()) { #ifndef ENABLE_OPENGL const ScopeLock protect(mutex); #endif rasp_renderer.reset(); } if (state.map < 0) return; if (!rasp_renderer) { #ifndef ENABLE_OPENGL const ScopeLock protect(mutex); #endif rasp_renderer.reset(new RaspRenderer(*rasp_store, state.map)); } rasp_renderer->SetTime(state.time); { QuietOperationEnvironment operation; rasp_renderer->Update(Calculated().date_time_local, operation); } const auto &terrain_settings = GetMapSettings().terrain; if (rasp_renderer->Generate(render_projection, terrain_settings)) rasp_renderer->Draw(canvas, render_projection); }
void GlueMapWindow::DrawMapScale(Canvas &canvas, const PixelRect &rc, const MapWindowProjection &projection) const { if (!projection.IsValid()) return; StaticString<80> buffer; fixed map_width = projection.GetScreenWidthMeters(); const Font &font = *look.overlay_font; canvas.Select(font); FormatUserMapScale(map_width, buffer.buffer(), true); PixelSize text_size = canvas.CalcTextSize(buffer); const PixelScalar text_padding_x = Layout::GetTextPadding(); const PixelScalar height = font.GetCapitalHeight() + Layout::GetTextPadding(); PixelScalar x = 0; look.map_scale_left_icon.Draw(canvas, 0, rc.bottom - height); x += look.map_scale_left_icon.GetSize().cx; canvas.DrawFilledRectangle(x, rc.bottom - height, x + 2 * text_padding_x + text_size.cx, rc.bottom, COLOR_WHITE); canvas.SetBackgroundTransparent(); canvas.SetTextColor(COLOR_BLACK); x += text_padding_x; canvas.DrawText(x, rc.bottom - font.GetAscentHeight() - Layout::Scale(1), buffer); x += text_padding_x + text_size.cx; look.map_scale_right_icon.Draw(canvas, x, rc.bottom - height); buffer.clear(); if (GetMapSettings().auto_zoom_enabled) buffer = _T("AUTO "); switch (follow_mode) { case FOLLOW_SELF: break; case FOLLOW_PAN: buffer += _T("PAN "); break; } const UIState &ui_state = GetUIState(); if (ui_state.auxiliary_enabled) { buffer += ui_state.panel_name; buffer += _T(" "); } if (Basic().gps.replay) buffer += _T("REPLAY "); else if (Basic().gps.simulator) { buffer += _("Simulator"); buffer += _T(" "); } if (GetComputerSettings().polar.ballast_timer_active) buffer.AppendFormat( _T("BALLAST %d LITERS "), (int)GetComputerSettings().polar.glide_polar_task.GetBallastLitres()); if (weather != nullptr && weather->GetParameter() > 0) { const TCHAR *label = weather->ItemLabel(weather->GetParameter()); if (label != nullptr) buffer += label; } if (!buffer.empty()) { int y = rc.bottom - height; TextInBoxMode mode; mode.vertical_position = TextInBoxMode::VerticalPosition::ABOVE; mode.shape = LabelShape::OUTLINED; TextInBox(canvas, buffer, 0, y, mode, rc, nullptr); } }
void BlackberryMain::runMain() { bool running = true; while (running && !g_quitRequested) { input_state.mouse_valid = false; input_state.accelerometer_valid = false; while (true) { // Handle Blackberry events bps_event_t *event = NULL; bps_get_event(&event, 0); if (event == NULL) break; // Ran out of events int domain = bps_event_get_domain(event); if (domain == screen_get_domain()) { handleInput(screen_event_get_event(event)); } else if (domain == navigator_get_domain()) { switch(bps_event_get_code(event)) { case NAVIGATOR_INVOKE_TARGET: { const navigator_invoke_invocation_t *invoke = navigator_invoke_event_get_invocation(event); if(invoke) { boot_filename = navigator_invoke_invocation_get_uri(invoke)+7; // Remove file:// } } break; case NAVIGATOR_ORIENTATION: sensor_remap_coordinates(navigator_event_get_orientation_angle(event)); break; case NAVIGATOR_BACK: case NAVIGATOR_SWIPE_DOWN: NativeKey(KeyInput(DEVICE_ID_KEYBOARD, NKCODE_ESCAPE, KEY_DOWN)); break; case NAVIGATOR_EXIT: return; } } else if (domain == sensor_get_domain()) { if (SENSOR_ACCELEROMETER_READING == bps_event_get_code(event)) { sensor_event_get_xyz(event, &(input_state.acc.y), &(input_state.acc.x), &(input_state.acc.z)); AxisInput axis; axis.deviceId = DEVICE_ID_ACCELEROMETER; axis.flags = 0; axis.axisId = JOYSTICK_AXIS_ACCELEROMETER_X; axis.value = input_state.acc.x; NativeAxis(axis); axis.axisId = JOYSTICK_AXIS_ACCELEROMETER_Y; axis.value = input_state.acc.y; NativeAxis(axis); axis.axisId = JOYSTICK_AXIS_ACCELEROMETER_Z; axis.value = input_state.acc.z; NativeAxis(axis); } } } UpdateInputState(&input_state); NativeUpdate(input_state); // Work in Progress // Currently: Render to HDMI port (eg. 1080p) when in game. Render to device when in menu. // Idea: Render to all displays. Controls go to internal, game goes to external(s). if (GetUIState() == UISTATE_INGAME && !emulating) { emulating = true; switchDisplay(screen_emu); if (g_Config.iShowFPSCounter == 4) { int options = SCREEN_DEBUG_STATISTICS; screen_set_window_property_iv(screen_win[0], SCREEN_PROPERTY_DEBUG, &options); } } else if (GetUIState() != UISTATE_INGAME && emulating) { emulating = false; switchDisplay(screen_ui); } NativeRender(); EndInputState(&input_state); time_update(); // This handles VSync if (emulating) eglSwapBuffers(egl_disp[screen_emu], egl_surf[screen_emu]); else eglSwapBuffers(egl_disp[screen_ui], egl_surf[screen_ui]); } }
void CorrectCursor() { bool autoHide = ((g_Config.bFullScreen && !mouseButtonDown) || (g_Config.bMouseControl && trapMouse)) && GetUIState() == UISTATE_INGAME; if (autoHide && (hideCursor || g_Config.bMouseControl)) { while (cursorCounter >= 0) { cursorCounter = ShowCursor(FALSE); } if (g_Config.bMouseConfine) { RECT rc; GetClientRect(hwndDisplay, &rc); ClientToScreen(hwndDisplay, reinterpret_cast<POINT*>(&rc.left)); ClientToScreen(hwndDisplay, reinterpret_cast<POINT*>(&rc.right)); ClipCursor(&rc); } } else { hideCursor = !autoHide; if (cursorCounter < 0) { cursorCounter = ShowCursor(TRUE); SetCursor(LoadCursor(NULL, IDC_ARROW)); ClipCursor(NULL); } } }
unsigned int WINAPI TheThread(void *) { _InterlockedExchange(&emuThreadReady, THREAD_INIT); setCurrentThreadName("Emu"); // And graphics... host = new WindowsHost(MainWindow::GetHInstance(), MainWindow::GetHWND(), MainWindow::GetDisplayHWND()); host->SetWindowTitle(nullptr); // Convert the command-line arguments to Unicode, then to proper UTF-8 // (the benefit being that we don't have to pollute the UI project with win32 ifdefs and lots of Convert<whatever>To<whatever>). // This avoids issues with PPSSPP inadvertently destroying paths with Unicode glyphs // (using the ANSI args resulted in Japanese/Chinese glyphs being turned into question marks, at least for me..). // -TheDax std::vector<std::wstring> wideArgs = GetWideCmdLine(); std::vector<std::string> argsUTF8; for (auto& string : wideArgs) { argsUTF8.push_back(ConvertWStringToUTF8(string)); } std::vector<const char *> args; for (auto& string : argsUTF8) { args.push_back(string.c_str()); } bool performingRestart = NativeIsRestarting(); NativeInit(static_cast<int>(args.size()), &args[0], "1234", "1234", nullptr); host->UpdateUI(); GraphicsContext *graphicsContext = nullptr; std::string error_string; if (!host->InitGraphics(&error_string, &graphicsContext)) { // Before anything: are we restarting right now? if (performingRestart) { // Okay, switching graphics didn't work out. Probably a driver bug - fallback to restart. // This happens on NVIDIA when switching OpenGL -> Vulkan. g_Config.Save(); W32Util::ExitAndRestart(); } I18NCategory *err = GetI18NCategory("Error"); Reporting::ReportMessage("Graphics init error: %s", error_string.c_str()); const char *defaultErrorVulkan = "Failed initializing graphics. Try upgrading your graphics drivers.\n\nWould you like to try switching to OpenGL?\n\nError message:"; const char *defaultErrorOpenGL = "Failed initializing graphics. Try upgrading your graphics drivers.\n\nWould you like to try switching to DirectX 9?\n\nError message:"; const char *defaultErrorDirect3D9 = "Failed initializing graphics. Try upgrading your graphics drivers and directx 9 runtime.\n\nWould you like to try switching to OpenGL?\n\nError message:"; const char *genericError; int nextBackend = GPU_BACKEND_DIRECT3D9; switch (g_Config.iGPUBackend) { case GPU_BACKEND_DIRECT3D9: nextBackend = GPU_BACKEND_OPENGL; genericError = err->T("GenericDirect3D9Error", defaultErrorDirect3D9); break; case GPU_BACKEND_VULKAN: nextBackend = GPU_BACKEND_OPENGL; genericError = err->T("GenericVulkanError", defaultErrorVulkan); break; case GPU_BACKEND_OPENGL: default: nextBackend = GPU_BACKEND_DIRECT3D9; genericError = err->T("GenericOpenGLError", defaultErrorOpenGL); break; } std::string full_error = StringFromFormat("%s\n\n%s", genericError, error_string.c_str()); std::wstring title = ConvertUTF8ToWString(err->T("GenericGraphicsError", "Graphics Error")); bool yes = IDYES == MessageBox(0, ConvertUTF8ToWString(full_error).c_str(), title.c_str(), MB_ICONERROR | MB_YESNO); ERROR_LOG(BOOT, full_error.c_str()); if (yes) { // Change the config to the alternative and restart. g_Config.iGPUBackend = nextBackend; g_Config.Save(); W32Util::ExitAndRestart(); } // No safe way out without graphics. ExitProcess(1); } NativeInitGraphics(graphicsContext); NativeResized(); INFO_LOG(BOOT, "Done."); _dbg_update_(); if (coreState == CORE_POWERDOWN) { INFO_LOG(BOOT, "Exit before core loop."); goto shutdown; } _InterlockedExchange(&emuThreadReady, THREAD_CORE_LOOP); if (g_Config.bBrowse) PostMessage(MainWindow::GetHWND(), WM_COMMAND, ID_FILE_LOAD, 0); Core_EnableStepping(FALSE); while (GetUIState() != UISTATE_EXIT) { // We're here again, so the game quit. Restart Core_Run() which controls the UI. // This way they can load a new game. if (!Core_IsActive()) UpdateUIState(UISTATE_MENU); Core_Run(graphicsContext); } shutdown: _InterlockedExchange(&emuThreadReady, THREAD_SHUTDOWN); NativeShutdownGraphics(); // NativeShutdown deletes the graphics context through host->ShutdownGraphics(). NativeShutdown(); _InterlockedExchange(&emuThreadReady, THREAD_END); return 0; }
DisplayMode GetDisplayMode() const { return GetUIState().display_mode; }
int main(int argc, char *argv[]) { glslang::InitializeProcess(); #if PPSSPP_PLATFORM(RPI) bcm_host_init(); #endif putenv((char*)"SDL_VIDEO_CENTERED=1"); SDL_SetHint(SDL_HINT_VIDEO_MINIMIZE_ON_FOCUS_LOSS, "0"); if (VulkanMayBeAvailable()) { printf("Vulkan might be available.\n"); } else { printf("Vulkan is not available.\n"); } int set_xres = -1; int set_yres = -1; bool portrait = false; bool set_ipad = false; float set_dpi = 1.0f; float set_scale = 1.0f; // Produce a new set of arguments with the ones we skip. int remain_argc = 1; const char *remain_argv[256] = { argv[0] }; Uint32 mode = 0; for (int i = 1; i < argc; i++) { if (!strcmp(argv[i],"--fullscreen")) mode |= SDL_WINDOW_FULLSCREEN_DESKTOP; else if (set_xres == -2) set_xres = parseInt(argv[i]); else if (set_yres == -2) set_yres = parseInt(argv[i]); else if (set_dpi == -2) set_dpi = parseFloat(argv[i]); else if (set_scale == -2) set_scale = parseFloat(argv[i]); else if (!strcmp(argv[i],"--xres")) set_xres = -2; else if (!strcmp(argv[i],"--yres")) set_yres = -2; else if (!strcmp(argv[i],"--dpi")) set_dpi = -2; else if (!strcmp(argv[i],"--scale")) set_scale = -2; else if (!strcmp(argv[i],"--ipad")) set_ipad = true; else if (!strcmp(argv[i],"--portrait")) portrait = true; else { remain_argv[remain_argc++] = argv[i]; } } std::string app_name; std::string app_name_nice; std::string version; bool landscape; NativeGetAppInfo(&app_name, &app_name_nice, &landscape, &version); bool joystick_enabled = true; if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER | SDL_INIT_AUDIO) < 0) { fprintf(stderr, "Failed to initialize SDL with joystick support. Retrying without.\n"); joystick_enabled = false; if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO) < 0) { fprintf(stderr, "Unable to initialize SDL: %s\n", SDL_GetError()); return 1; } } // TODO: How do we get this into the GraphicsContext? #ifdef USING_EGL if (EGL_Open()) return 1; #endif // Get the video info before doing anything else, so we don't get skewed resolution results. // TODO: support multiple displays correctly SDL_DisplayMode displayMode; int should_be_zero = SDL_GetCurrentDisplayMode(0, &displayMode); if (should_be_zero != 0) { fprintf(stderr, "Could not get display mode: %s\n", SDL_GetError()); return 1; } g_DesktopWidth = displayMode.w; g_DesktopHeight = displayMode.h; SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8); SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8); SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8); SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24); SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8); SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); SDL_GL_SetSwapInterval(1); // Is resolution is too low to run windowed if (g_DesktopWidth < 480 * 2 && g_DesktopHeight < 272 * 2) { mode |= SDL_WINDOW_FULLSCREEN_DESKTOP; } if (mode & SDL_WINDOW_FULLSCREEN_DESKTOP) { pixel_xres = g_DesktopWidth; pixel_yres = g_DesktopHeight; g_Config.bFullScreen = true; } else { // set a sensible default resolution (2x) pixel_xres = 480 * 2 * set_scale; pixel_yres = 272 * 2 * set_scale; if (portrait) { std::swap(pixel_xres, pixel_yres); } g_Config.bFullScreen = false; } set_dpi = 1.0f / set_dpi; if (set_ipad) { pixel_xres = 1024; pixel_yres = 768; } if (!landscape) { std::swap(pixel_xres, pixel_yres); } if (set_xres > 0) { pixel_xres = set_xres; } if (set_yres > 0) { pixel_yres = set_yres; } float dpi_scale = 1.0f; if (set_dpi > 0) { dpi_scale = set_dpi; } dp_xres = (float)pixel_xres * dpi_scale; dp_yres = (float)pixel_yres * dpi_scale; // Mac / Linux char path[2048]; const char *the_path = getenv("HOME"); if (!the_path) { struct passwd* pwd = getpwuid(getuid()); if (pwd) the_path = pwd->pw_dir; } strcpy(path, the_path); if (path[strlen(path)-1] != '/') strcat(path, "/"); NativeInit(remain_argc, (const char **)remain_argv, path, "/tmp", nullptr); // Use the setting from the config when initing the window. if (g_Config.bFullScreen) mode |= SDL_WINDOW_FULLSCREEN_DESKTOP; int x = SDL_WINDOWPOS_UNDEFINED_DISPLAY(getDisplayNumber()); int y = SDL_WINDOWPOS_UNDEFINED; pixel_in_dps_x = (float)pixel_xres / dp_xres; pixel_in_dps_y = (float)pixel_yres / dp_yres; g_dpi_scale_x = dp_xres / (float)pixel_xres; g_dpi_scale_y = dp_yres / (float)pixel_yres; g_dpi_scale_real_x = g_dpi_scale_x; g_dpi_scale_real_y = g_dpi_scale_y; printf("Pixels: %i x %i\n", pixel_xres, pixel_yres); printf("Virtual pixels: %i x %i\n", dp_xres, dp_yres); GraphicsContext *graphicsContext = nullptr; SDL_Window *window = nullptr; std::string error_message; if (g_Config.iGPUBackend == (int)GPUBackend::OPENGL) { SDLGLGraphicsContext *ctx = new SDLGLGraphicsContext(); if (ctx->Init(window, x, y, mode, &error_message) != 0) { printf("GL init error '%s'\n", error_message.c_str()); } graphicsContext = ctx; } else if (g_Config.iGPUBackend == (int)GPUBackend::VULKAN) { SDLVulkanGraphicsContext *ctx = new SDLVulkanGraphicsContext(); if (!ctx->Init(window, x, y, mode, &error_message)) { printf("Vulkan init error '%s' - falling back to GL\n", error_message.c_str()); g_Config.iGPUBackend = (int)GPUBackend::OPENGL; SetGPUBackend((GPUBackend)g_Config.iGPUBackend); delete ctx; SDLGLGraphicsContext *glctx = new SDLGLGraphicsContext(); glctx->Init(window, x, y, mode, &error_message); graphicsContext = glctx; } else { graphicsContext = ctx; } } bool useEmuThread = g_Config.iGPUBackend == (int)GPUBackend::OPENGL; SDL_SetWindowTitle(window, (app_name_nice + " " + PPSSPP_GIT_VERSION).c_str()); // Since we render from the main thread, there's nothing done here, but we call it to avoid confusion. if (!graphicsContext->InitFromRenderThread(&error_message)) { printf("Init from thread error: '%s'\n", error_message.c_str()); } #ifdef MOBILE_DEVICE SDL_ShowCursor(SDL_DISABLE); #endif if (!useEmuThread) { NativeInitGraphics(graphicsContext); NativeResized(); } SDL_AudioSpec fmt, ret_fmt; memset(&fmt, 0, sizeof(fmt)); fmt.freq = 44100; fmt.format = AUDIO_S16; fmt.channels = 2; fmt.samples = 2048; fmt.callback = &mixaudio; fmt.userdata = (void *)0; if (SDL_OpenAudio(&fmt, &ret_fmt) < 0) { ELOG("Failed to open audio: %s", SDL_GetError()); } else { if (ret_fmt.samples != fmt.samples) // Notify, but still use it ELOG("Output audio samples: %d (requested: %d)", ret_fmt.samples, fmt.samples); if (ret_fmt.freq != fmt.freq || ret_fmt.format != fmt.format || ret_fmt.channels != fmt.channels) { ELOG("Sound buffer format does not match requested format."); ELOG("Output audio freq: %d (requested: %d)", ret_fmt.freq, fmt.freq); ELOG("Output audio format: %d (requested: %d)", ret_fmt.format, fmt.format); ELOG("Output audio channels: %d (requested: %d)", ret_fmt.channels, fmt.channels); ELOG("Provided output format does not match requirement, turning audio off"); SDL_CloseAudio(); } } // Audio must be unpaused _after_ NativeInit() SDL_PauseAudio(0); if (joystick_enabled) { joystick = new SDLJoystick(); } else { joystick = nullptr; } EnableFZ(); int framecount = 0; bool mouseDown = false; if (useEmuThread) { EmuThreadStart(graphicsContext); } graphicsContext->ThreadStart(); while (true) { SDL_Event event; while (SDL_PollEvent(&event)) { float mx = event.motion.x * g_dpi_scale_x; float my = event.motion.y * g_dpi_scale_y; switch (event.type) { case SDL_QUIT: g_QuitRequested = 1; break; #if !defined(MOBILE_DEVICE) case SDL_WINDOWEVENT: switch (event.window.event) { case SDL_WINDOWEVENT_RESIZED: { Uint32 window_flags = SDL_GetWindowFlags(window); bool fullscreen = (window_flags & SDL_WINDOW_FULLSCREEN); pixel_xres = event.window.data1; pixel_yres = event.window.data2; dp_xres = (float)pixel_xres * dpi_scale; dp_yres = (float)pixel_yres * dpi_scale; NativeResized(); // Set variable here in case fullscreen was toggled by hotkey g_Config.bFullScreen = fullscreen; // Hide/Show cursor correctly toggling fullscreen if (lastUIState == UISTATE_INGAME && fullscreen && !g_Config.bShowTouchControls) { SDL_ShowCursor(SDL_DISABLE); } else if (lastUIState != UISTATE_INGAME || !fullscreen) { SDL_ShowCursor(SDL_ENABLE); } break; } default: break; } break; #endif case SDL_KEYDOWN: { if (event.key.repeat > 0) { break;} int k = event.key.keysym.sym; KeyInput key; key.flags = KEY_DOWN; auto mapped = KeyMapRawSDLtoNative.find(k); if (mapped == KeyMapRawSDLtoNative.end() || mapped->second == NKCODE_UNKNOWN) { break; } key.keyCode = mapped->second; key.deviceId = DEVICE_ID_KEYBOARD; NativeKey(key); break; } case SDL_KEYUP: { if (event.key.repeat > 0) { break;} int k = event.key.keysym.sym; KeyInput key; key.flags = KEY_UP; auto mapped = KeyMapRawSDLtoNative.find(k); if (mapped == KeyMapRawSDLtoNative.end() || mapped->second == NKCODE_UNKNOWN) { break; } key.keyCode = mapped->second; key.deviceId = DEVICE_ID_KEYBOARD; NativeKey(key); break; } case SDL_TEXTINPUT: { int pos = 0; int c = u8_nextchar(event.text.text, &pos); KeyInput key; key.flags = KEY_CHAR; key.keyCode = c; key.deviceId = DEVICE_ID_KEYBOARD; NativeKey(key); break; } case SDL_MOUSEBUTTONDOWN: switch (event.button.button) { case SDL_BUTTON_LEFT: { mouseDown = true; TouchInput input; input.x = mx; input.y = my; input.flags = TOUCH_DOWN | TOUCH_MOUSE; input.id = 0; NativeTouch(input); KeyInput key(DEVICE_ID_MOUSE, NKCODE_EXT_MOUSEBUTTON_1, KEY_DOWN); NativeKey(key); } break; case SDL_BUTTON_RIGHT: { KeyInput key(DEVICE_ID_MOUSE, NKCODE_EXT_MOUSEBUTTON_2, KEY_DOWN); NativeKey(key); } break; } break; case SDL_MOUSEWHEEL: { KeyInput key; key.deviceId = DEVICE_ID_MOUSE; if (event.wheel.y > 0) { key.keyCode = NKCODE_EXT_MOUSEWHEEL_UP; } else { key.keyCode = NKCODE_EXT_MOUSEWHEEL_DOWN; } key.flags = KEY_DOWN; NativeKey(key); // SDL2 doesn't consider the mousewheel a button anymore // so let's send the KEY_UP right away. // Maybe KEY_UP alone will suffice? key.flags = KEY_UP; NativeKey(key); } case SDL_MOUSEMOTION: if (mouseDown) { TouchInput input; input.x = mx; input.y = my; input.flags = TOUCH_MOVE | TOUCH_MOUSE; input.id = 0; NativeTouch(input); } break; case SDL_MOUSEBUTTONUP: switch (event.button.button) { case SDL_BUTTON_LEFT: { mouseDown = false; TouchInput input; input.x = mx; input.y = my; input.flags = TOUCH_UP | TOUCH_MOUSE; input.id = 0; NativeTouch(input); KeyInput key(DEVICE_ID_MOUSE, NKCODE_EXT_MOUSEBUTTON_1, KEY_UP); NativeKey(key); } break; case SDL_BUTTON_RIGHT: { KeyInput key(DEVICE_ID_MOUSE, NKCODE_EXT_MOUSEBUTTON_2, KEY_UP); NativeKey(key); } break; } break; default: if (joystick) { joystick->ProcessInput(event); } break; } } if (g_QuitRequested) break; const uint8_t *keys = SDL_GetKeyboardState(NULL); if (emuThreadState == (int)EmuThreadState::DISABLED) { UpdateRunLoop(); } if (g_QuitRequested) break; #if !defined(MOBILE_DEVICE) if (lastUIState != GetUIState()) { lastUIState = GetUIState(); if (lastUIState == UISTATE_INGAME && g_Config.bFullScreen && !g_Config.bShowTouchControls) SDL_ShowCursor(SDL_DISABLE); if (lastUIState != UISTATE_INGAME && g_Config.bFullScreen) SDL_ShowCursor(SDL_ENABLE); } #endif if (framecount % 60 == 0) { // glsl_refresh(); // auto-reloads modified GLSL shaders once per second. } if (emuThreadState != (int)EmuThreadState::DISABLED) { if (!graphicsContext->ThreadFrame()) break; } graphicsContext->SwapBuffers(); ToggleFullScreenIfFlagSet(window); time_update(); framecount++; } if (useEmuThread) { EmuThreadStop(); while (emuThreadState != (int)EmuThreadState::STOPPED) { // Need to keep eating frames to allow the EmuThread to exit correctly. graphicsContext->ThreadFrame(); } EmuThreadJoin(); } delete joystick; if (!useEmuThread) { NativeShutdownGraphics(); } graphicsContext->Shutdown(); graphicsContext->ThreadEnd(); graphicsContext->ShutdownFromRenderThread(); NativeShutdown(); delete graphicsContext; SDL_PauseAudio(1); SDL_CloseAudio(); SDL_Quit(); #if PPSSPP_PLATFORM(RPI) bcm_host_deinit(); #endif glslang::FinalizeProcess(); ILOG("Leaving main"); return 0; }
unsigned int WINAPI TheThread(void *) { _InterlockedExchange(&emuThreadReady, THREAD_INIT); setCurrentThreadName("Emu"); // And graphics... // Native overwrites host. Can't allow that. Host *oldHost = host; // Convert the command-line arguments to Unicode, then to proper UTF-8 // (the benefit being that we don't have to pollute the UI project with win32 ifdefs and lots of Convert<whatever>To<whatever>). // This avoids issues with PPSSPP inadvertently destroying paths with Unicode glyphs // (using the ANSI args resulted in Japanese/Chinese glyphs being turned into question marks, at least for me..). // -TheDax std::vector<std::wstring> wideArgs = GetWideCmdLine(); std::vector<std::string> argsUTF8; for (auto& string : wideArgs) { argsUTF8.push_back(ConvertWStringToUTF8(string)); } std::vector<const char *> args; for (auto& string : argsUTF8) { args.push_back(string.c_str()); } NativeInit(static_cast<int>(args.size()), &args[0], "1234", "1234", "1234"); Host *nativeHost = host; host = oldHost; host->UpdateUI(); //Check Colour depth HDC dc = GetDC(NULL); u32 colour_depth = GetDeviceCaps(dc, BITSPIXEL); ReleaseDC(NULL, dc); if (colour_depth != 32){ MessageBox(0, L"Please switch your display to 32-bit colour mode", L"OpenGL Error", MB_OK); ExitProcess(1); } std::string error_string; if (!host->InitGraphics(&error_string)) { Reporting::ReportMessage("Graphics init error: %s", error_string.c_str()); std::string full_error = StringFromFormat( "Failed initializing OpenGL. Try upgrading your graphics drivers.\n\nError message:\n\n%s", error_string.c_str()); MessageBox(0, ConvertUTF8ToWString(full_error).c_str(), L"OpenGL Error", MB_OK | MB_ICONERROR); ERROR_LOG(BOOT, full_error.c_str()); // No safe way out without OpenGL. ExitProcess(1); } NativeInitGraphics(); NativeResized(); INFO_LOG(BOOT, "Done."); _dbg_update_(); if (coreState == CORE_POWERDOWN) { INFO_LOG(BOOT, "Exit before core loop."); goto shutdown; } _InterlockedExchange(&emuThreadReady, THREAD_CORE_LOOP); if (g_Config.bBrowse) PostMessage(MainWindow::GetHWND(), WM_COMMAND, ID_FILE_LOAD, 0); Core_EnableStepping(FALSE); while (GetUIState() != UISTATE_EXIT) { // We're here again, so the game quit. Restart Core_Run() which controls the UI. // This way they can load a new game. if (!Core_IsActive()) UpdateUIState(UISTATE_MENU); Core_Run(); } shutdown: _InterlockedExchange(&emuThreadReady, THREAD_SHUTDOWN); NativeShutdownGraphics(); host->ShutdownSound(); host = nativeHost; NativeShutdown(); host = oldHost; host->ShutdownGraphics(); _InterlockedExchange(&emuThreadReady, THREAD_END); return 0; }
void GlueMapWindow::DrawMapScale(Canvas &canvas, const PixelRect &rc, const MapWindowProjection &projection) const { StaticString<80> buffer; fixed map_width = projection.GetScreenWidthMeters(); canvas.Select(Fonts::map_bold); FormatUserMapScale(map_width, buffer.buffer(), true); PixelSize text_size = canvas.CalcTextSize(buffer); const PixelScalar text_padding_x = Layout::Scale(2); const PixelScalar height = Fonts::map_bold.GetCapitalHeight() + Layout::Scale(2); PixelScalar x = 0; look.map_scale_left_icon.Draw(canvas, 0, rc.bottom - height); x += look.map_scale_left_icon.GetSize().cx; canvas.DrawFilledRectangle(x, rc.bottom - height, x + 2 * text_padding_x + text_size.cx, rc.bottom, COLOR_WHITE); canvas.SetBackgroundTransparent(); canvas.SetTextColor(COLOR_BLACK); x += text_padding_x; canvas.text(x, rc.bottom - Fonts::map_bold.GetAscentHeight() - Layout::Scale(1), buffer); x += text_padding_x + text_size.cx; look.map_scale_right_icon.Draw(canvas, x, rc.bottom - height); buffer.clear(); if (GetMapSettings().auto_zoom_enabled) buffer = _T("AUTO "); switch (follow_mode) { case FOLLOW_SELF: break; case FOLLOW_PAN: buffer += _T("PAN "); break; } const UIState &ui_state = GetUIState(); if (ui_state.auxiliary_enabled) { buffer += ui_state.panel_name; buffer += _T(" "); } if (Basic().gps.replay) buffer += _T("REPLAY "); else if (Basic().gps.simulator) { buffer += _("Simulator"); buffer += _T(" "); } if (GetComputerSettings().polar.ballast_timer_active) buffer.AppendFormat( _T("BALLAST %d LITERS "), (int)GetComputerSettings().polar.glide_polar_task.GetBallastLitres()); if (weather != NULL && weather->GetParameter() > 0) { const TCHAR *label = weather->ItemLabel(weather->GetParameter()); if (label != NULL) buffer += label; } if (!buffer.empty()) { int y = rc.bottom - height; canvas.Select(Fonts::title); canvas.SetBackgroundOpaque(); canvas.SetBackgroundColor(COLOR_WHITE); canvas.text(0, y - canvas.CalcTextSize(buffer).cy, buffer); } }
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { switch (message) { case WM_CREATE: if (!DoesVersionMatchWindows(6, 0, 0, 0, true)) { // Remove the D3D11 choice on versions below XP RemoveMenu(GetMenu(hWnd), ID_OPTIONS_DIRECT3D11, MF_BYCOMMAND); } break; case WM_GETMINMAXINFO: { MINMAXINFO *minmax = reinterpret_cast<MINMAXINFO *>(lParam); RECT rc = { 0 }; bool portrait = g_Config.IsPortrait(); rc.right = portrait ? 272 : 480; rc.bottom = portrait ? 480 : 272; AdjustWindowRect(&rc, WS_OVERLAPPEDWINDOW, TRUE); minmax->ptMinTrackSize.x = rc.right - rc.left; minmax->ptMinTrackSize.y = rc.bottom - rc.top; } return 0; case WM_ACTIVATE: { bool pause = true; if (wParam == WA_ACTIVE || wParam == WA_CLICKACTIVE) { WindowsRawInput::GainFocus(); if (!IsIconic(GetHWND())) { InputDevice::GainFocus(); } g_activeWindow = WINDOW_MAINWINDOW; pause = false; } if (!noFocusPause && g_Config.bPauseOnLostFocus && GetUIState() == UISTATE_INGAME) { if (pause != Core_IsStepping()) { // != is xor for bools if (disasmWindow[0]) SendMessage(disasmWindow[0]->GetDlgHandle(), WM_COMMAND, IDC_STOPGO, 0); else Core_EnableStepping(pause); } } if (wParam == WA_ACTIVE || wParam == WA_CLICKACTIVE) { NativeMessageReceived("got_focus", ""); hasFocus = true; trapMouse = true; } if (wParam == WA_INACTIVE) { NativeMessageReceived("lost_focus", ""); WindowsRawInput::LoseFocus(); InputDevice::LoseFocus(); hasFocus = false; trapMouse = false; } } break; case WM_ERASEBKGND: // This window is always covered by DisplayWindow. No reason to erase. return 1; case WM_MOVE: SavePosition(); break; case WM_ENTERSIZEMOVE: inResizeMove = true; break; case WM_EXITSIZEMOVE: inResizeMove = false; HandleSizeChange(SIZE_RESTORED); break; case WM_SIZE: switch (wParam) { case SIZE_RESTORED: case SIZE_MAXIMIZED: if (g_IgnoreWM_SIZE) { return DefWindowProc(hWnd, message, wParam, lParam); } else if (!inResizeMove) { HandleSizeChange(wParam); } if (hasFocus) { InputDevice::GainFocus(); } break; case SIZE_MINIMIZED: Core_NotifyWindowHidden(true); if (!g_Config.bPauseWhenMinimized) { NativeMessageReceived("window minimized", "true"); } InputDevice::LoseFocus(); break; default: break; } break; case WM_TIMER: // Hack: Take the opportunity to also show/hide the mouse cursor in fullscreen mode. switch (wParam) { case TIMER_CURSORUPDATE: CorrectCursor(); return 0; case TIMER_CURSORMOVEUPDATE: hideCursor = true; KillTimer(hWnd, TIMER_CURSORMOVEUPDATE); return 0; } break; // For some reason, need to catch this here rather than in DisplayProc. case WM_MOUSEWHEEL: { int wheelDelta = (short)(wParam >> 16); KeyInput key; key.deviceId = DEVICE_ID_MOUSE; if (wheelDelta < 0) { key.keyCode = NKCODE_EXT_MOUSEWHEEL_DOWN; wheelDelta = -wheelDelta; } else { key.keyCode = NKCODE_EXT_MOUSEWHEEL_UP; } // There's no separate keyup event for mousewheel events, let's pass them both together. // This also means it really won't work great for key mapping :( Need to build a 1 frame delay or something. key.flags = KEY_DOWN | KEY_UP | KEY_HASWHEELDELTA | (wheelDelta << 16); NativeKey(key); } break; case WM_COMMAND: { if (!MainThread_Ready()) return DefWindowProc(hWnd, message, wParam, lParam); MainWindowMenu_Process(hWnd, wParam); } break; case WM_USER_TOGGLE_FULLSCREEN: ToggleFullscreen(hwndMain, wParam ? true : false); break; case WM_INPUT: return WindowsRawInput::Process(hWnd, wParam, lParam); // TODO: Could do something useful with WM_INPUT_DEVICE_CHANGE? // Not sure why we are actually getting WM_CHAR even though we use RawInput, but alright.. case WM_CHAR: return WindowsRawInput::ProcessChar(hWnd, wParam, lParam); case WM_DEVICECHANGE: #ifndef _M_ARM DinputDevice::CheckDevices(); #endif return DefWindowProc(hWnd, message, wParam, lParam); case WM_VERYSLEEPY_MSG: switch (wParam) { case VERYSLEEPY_WPARAM_SUPPORTED: return TRUE; case VERYSLEEPY_WPARAM_GETADDRINFO: { VerySleepy_AddrInfo *info = (VerySleepy_AddrInfo *)lParam; const u8 *ptr = (const u8 *)info->addr; std::string name; if (MIPSComp::jit && MIPSComp::jit->DescribeCodePtr(ptr, name)) { swprintf_s(info->name, L"Jit::%S", name.c_str()); return TRUE; } if (gpu && gpu->DescribeCodePtr(ptr, name)) { swprintf_s(info->name, L"GPU::%S", name.c_str()); return TRUE; } } return FALSE; default: return FALSE; } break; case WM_DROPFILES: { if (!MainThread_Ready()) return DefWindowProc(hWnd, message, wParam, lParam); HDROP hdrop = (HDROP)wParam; int count = DragQueryFile(hdrop,0xFFFFFFFF,0,0); if (count != 1) { MessageBox(hwndMain,L"You can only load one file at a time",L"Error",MB_ICONINFORMATION); } else { TCHAR filename[512]; if (DragQueryFile(hdrop, 0, filename, 512) != 0) { const std::string utf8_filename = ReplaceAll(ConvertWStringToUTF8(filename), "\\", "/"); NativeMessageReceived("boot", utf8_filename.c_str()); Core_EnableStepping(false); } } } break; case WM_CLOSE: InputDevice::StopPolling(); WindowsRawInput::Shutdown(); return DefWindowProc(hWnd,message,wParam,lParam); case WM_DESTROY: KillTimer(hWnd, TIMER_CURSORUPDATE); KillTimer(hWnd, TIMER_CURSORMOVEUPDATE); PostQuitMessage(0); break; case WM_USER + 1: if (disasmWindow[0]) disasmWindow[0]->NotifyMapLoaded(); if (memoryWindow[0]) memoryWindow[0]->NotifyMapLoaded(); if (disasmWindow[0]) disasmWindow[0]->UpdateDialog(); SetForegroundWindow(hwndMain); break; case WM_USER_SAVESTATE_FINISH: SetCursor(LoadCursor(0, IDC_ARROW)); break; case WM_USER_UPDATE_UI: TranslateMenus(hwndMain, menu); // Update checked status immediately for accelerators. UpdateMenus(); break; case WM_USER_WINDOW_TITLE_CHANGED: UpdateWindowTitle(); break; case WM_USER_BROWSE_BOOT_DONE: BrowseAndBootDone(); break; case WM_USER_BROWSE_BG_DONE: BrowseBackgroundDone(); break; case WM_USER_RESTART_EMUTHREAD: NativeSetRestarting(); InputDevice::StopPolling(); MainThread_Stop(); coreState = CORE_POWERUP; UpdateUIState(UISTATE_MENU); MainThread_Start(g_Config.iGPUBackend == (int)GPUBackend::OPENGL); InputDevice::BeginPolling(); break; case WM_MENUSELECT: // Called when a menu is opened. Also when an item is selected, but meh. UpdateMenus(true); WindowsRawInput::NotifyMenu(); trapMouse = false; break; case WM_EXITMENULOOP: // Called when menu is closed. trapMouse = true; break; // Turn off the screensaver. // Note that if there's a screensaver password, this simple method // doesn't work on Vista or higher. case WM_SYSCOMMAND: { switch (wParam) { case SC_SCREENSAVE: return 0; case SC_MONITORPOWER: return 0; } return DefWindowProc(hWnd, message, wParam, lParam); } default: return DefWindowProc(hWnd, message, wParam, lParam); } return 0; }
LRESULT CALLBACK DisplayProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { static bool firstErase = true; switch (message) { case WM_ACTIVATE: if (wParam == WA_ACTIVE || wParam == WA_CLICKACTIVE) { g_activeWindow = WINDOW_MAINWINDOW; } break; case WM_SIZE: break; case WM_SETFOCUS: break; case WM_ERASEBKGND: if (firstErase) { firstErase = false; // Paint black on first erase while OpenGL stuff is loading return DefWindowProc(hWnd, message, wParam, lParam); } // Then never erase, let the OpenGL drawing take care of everything. return 1; // Poor man's touch - mouse input. We send the data asynchronous touch events for minimal latency. case WM_LBUTTONDOWN: if (!touchHandler.hasTouch() || (GetMessageExtraInfo() & MOUSEEVENTF_MASK_PLUS_PENTOUCH) != MOUSEEVENTF_FROMTOUCH_NOPEN) { // Hack: Take the opportunity to show the cursor. mouseButtonDown = true; float x = GET_X_LPARAM(lParam) * g_dpi_scale_x; float y = GET_Y_LPARAM(lParam) * g_dpi_scale_y; WindowsRawInput::SetMousePos(x, y); TouchInput touch; touch.id = 0; touch.flags = TOUCH_DOWN; touch.x = x; touch.y = y; NativeTouch(touch); SetCapture(hWnd); // Simulate doubleclick, doesn't work with RawInput enabled static double lastMouseDown; double now = real_time_now(); if ((now - lastMouseDown) < 0.001 * GetDoubleClickTime()) { if (!g_Config.bShowTouchControls && !g_Config.bMouseControl && GetUIState() == UISTATE_INGAME && g_Config.bFullscreenOnDoubleclick) { SendToggleFullscreen(!g_Config.bFullScreen); } lastMouseDown = 0.0; } else { lastMouseDown = real_time_now(); } } break; case WM_MOUSEMOVE: if (!touchHandler.hasTouch() || (GetMessageExtraInfo() & MOUSEEVENTF_MASK_PLUS_PENTOUCH) != MOUSEEVENTF_FROMTOUCH_NOPEN) { // Hack: Take the opportunity to show the cursor. mouseButtonDown = (wParam & MK_LBUTTON) != 0; int cursorX = GET_X_LPARAM(lParam); int cursorY = GET_Y_LPARAM(lParam); if (abs(cursorX - prevCursorX) > 1 || abs(cursorY - prevCursorY) > 1) { hideCursor = false; SetTimer(hwndMain, TIMER_CURSORMOVEUPDATE, CURSORUPDATE_MOVE_TIMESPAN_MS, 0); } prevCursorX = cursorX; prevCursorY = cursorY; float x = (float)cursorX * g_dpi_scale_x; float y = (float)cursorY * g_dpi_scale_y; WindowsRawInput::SetMousePos(x, y); if (wParam & MK_LBUTTON) { TouchInput touch; touch.id = 0; touch.flags = TOUCH_MOVE; touch.x = x; touch.y = y; NativeTouch(touch); } } break; case WM_LBUTTONUP: if (!touchHandler.hasTouch() || (GetMessageExtraInfo() & MOUSEEVENTF_MASK_PLUS_PENTOUCH) != MOUSEEVENTF_FROMTOUCH_NOPEN) { // Hack: Take the opportunity to hide the cursor. mouseButtonDown = false; float x = (float)GET_X_LPARAM(lParam) * g_dpi_scale_x; float y = (float)GET_Y_LPARAM(lParam) * g_dpi_scale_y; WindowsRawInput::SetMousePos(x, y); TouchInput touch; touch.id = 0; touch.flags = TOUCH_UP; touch.x = x; touch.y = y; NativeTouch(touch); ReleaseCapture(); } break; case WM_TOUCH: { touchHandler.handleTouchEvent(hWnd, message, wParam, lParam); return 0; } default: return DefWindowProc(hWnd, message, wParam, lParam); } return 0; }
LRESULT CALLBACK DisplayProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { // Only apply a factor > 1 in windowed mode. int factor = !IsZoomed(GetHWND()) && !g_Config.bFullScreen && IsWindowSmall() ? 2 : 1; static bool firstErase = true; switch (message) { case WM_ACTIVATE: if (wParam == WA_ACTIVE || wParam == WA_CLICKACTIVE) { g_activeWindow = WINDOW_MAINWINDOW; } break; case WM_SIZE: break; case WM_SETFOCUS: break; case WM_ERASEBKGND: if (firstErase) { firstErase = false; // Paint black on first erase while OpenGL stuff is loading return DefWindowProc(hWnd, message, wParam, lParam); } // Then never erase, let the OpenGL drawing take care of everything. return 1; // Poor man's touch - mouse input. We send the data both as an input_state pointer, // and as asynchronous touch events for minimal latency. case WM_LBUTTONDOWN: if (!touchHandler.hasTouch() || (GetMessageExtraInfo() & MOUSEEVENTF_MASK_PLUS_PENTOUCH) != MOUSEEVENTF_FROMTOUCH_NOPEN) { // Hack: Take the opportunity to show the cursor. mouseButtonDown = true; { lock_guard guard(input_state.lock); input_state.mouse_valid = true; input_state.pointer_down[0] = true; input_state.pointer_x[0] = GET_X_LPARAM(lParam) * factor; input_state.pointer_y[0] = GET_Y_LPARAM(lParam) * factor; } TouchInput touch; touch.id = 0; touch.flags = TOUCH_DOWN; touch.x = input_state.pointer_x[0]; touch.y = input_state.pointer_y[0]; NativeTouch(touch); SetCapture(hWnd); // Simulate doubleclick, doesn't work with RawInput enabled static double lastMouseDown; double now = real_time_now(); if ((now - lastMouseDown) < 0.001 * GetDoubleClickTime()) { if (!g_Config.bShowTouchControls && GetUIState() == UISTATE_INGAME) { PostMessage(hwndMain, WM_USER_TOGGLE_FULLSCREEN, 0, 0); } lastMouseDown = 0.0; } else { lastMouseDown = real_time_now(); } } break; case WM_MOUSEMOVE: if (!touchHandler.hasTouch() || (GetMessageExtraInfo() & MOUSEEVENTF_MASK_PLUS_PENTOUCH) != MOUSEEVENTF_FROMTOUCH_NOPEN) { // Hack: Take the opportunity to show the cursor. mouseButtonDown = (wParam & MK_LBUTTON) != 0; int cursorX = GET_X_LPARAM(lParam); int cursorY = GET_Y_LPARAM(lParam); if (abs(cursorX - prevCursorX) > 1 || abs(cursorY - prevCursorY) > 1) { hideCursor = false; SetTimer(hwndMain, TIMER_CURSORMOVEUPDATE, CURSORUPDATE_MOVE_TIMESPAN_MS, 0); } prevCursorX = cursorX; prevCursorY = cursorY; { lock_guard guard(input_state.lock); input_state.pointer_x[0] = GET_X_LPARAM(lParam) * factor; input_state.pointer_y[0] = GET_Y_LPARAM(lParam) * factor; } if (wParam & MK_LBUTTON) { TouchInput touch; touch.id = 0; touch.flags = TOUCH_MOVE; touch.x = input_state.pointer_x[0]; touch.y = input_state.pointer_y[0]; NativeTouch(touch); } } break; case WM_LBUTTONUP: if (!touchHandler.hasTouch() || (GetMessageExtraInfo() & MOUSEEVENTF_MASK_PLUS_PENTOUCH) != MOUSEEVENTF_FROMTOUCH_NOPEN) { // Hack: Take the opportunity to hide the cursor. mouseButtonDown = false; { lock_guard guard(input_state.lock); input_state.pointer_down[0] = false; input_state.pointer_x[0] = GET_X_LPARAM(lParam) * factor; input_state.pointer_y[0] = GET_Y_LPARAM(lParam) * factor; } TouchInput touch; touch.id = 0; touch.flags = TOUCH_UP; touch.x = input_state.pointer_x[0]; touch.y = input_state.pointer_y[0]; NativeTouch(touch); ReleaseCapture(); } break; case WM_TOUCH: { touchHandler.handleTouchEvent(hWnd, message, wParam, lParam); return 0; } default: return DefWindowProc(hWnd, message, wParam, lParam); } return 0; }
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { switch (message) { case WM_CREATE: break; case WM_GETMINMAXINFO: { MINMAXINFO *minmax = reinterpret_cast<MINMAXINFO *>(lParam); RECT rc = { 0 }; bool portrait = g_Config.IsPortrait(); rc.right = portrait ? 272 : 480; rc.bottom = portrait ? 480 : 272; AdjustWindowRect(&rc, WS_OVERLAPPEDWINDOW, TRUE); minmax->ptMinTrackSize.x = rc.right - rc.left; minmax->ptMinTrackSize.y = rc.bottom - rc.top; } return 0; case WM_ACTIVATE: { bool pause = true; if (wParam == WA_ACTIVE || wParam == WA_CLICKACTIVE) { WindowsRawInput::GainFocus(); InputDevice::GainFocus(); g_activeWindow = WINDOW_MAINWINDOW; pause = false; } if (!noFocusPause && g_Config.bPauseOnLostFocus && GetUIState() == UISTATE_INGAME) { if (pause != Core_IsStepping()) { // != is xor for bools if (disasmWindow[0]) SendMessage(disasmWindow[0]->GetDlgHandle(), WM_COMMAND, IDC_STOPGO, 0); else Core_EnableStepping(pause); } } if (wParam == WA_ACTIVE) { NativeMessageReceived("got_focus", ""); } if (wParam == WA_INACTIVE) { NativeMessageReceived("lost_focus", ""); WindowsRawInput::LoseFocus(); InputDevice::LoseFocus(); } } break; case WM_ERASEBKGND: // This window is always covered by DisplayWindow. No reason to erase. return 1; case WM_MOVE: SavePosition(); break; case WM_SIZE: switch (wParam) { case SIZE_RESTORED: case SIZE_MAXIMIZED: if (g_IgnoreWM_SIZE) { return DefWindowProc(hWnd, message, wParam, lParam); } else { SavePosition(); Core_NotifyWindowHidden(false); if (!g_Config.bPauseWhenMinimized) { NativeMessageReceived("window minimized", "false"); } int width = 0, height = 0; RECT rc; GetClientRect(hwndMain, &rc); width = rc.right - rc.left; height = rc.bottom - rc.top; // Moves the internal display window to match the inner size of the main window. MoveWindow(hwndDisplay, 0, 0, width, height, TRUE); // Setting pixelWidth to be too small could have odd consequences. if (width >= 4 && height >= 4) { // The framebuffer manager reads these once per frame, hopefully safe enough.. should really use a mutex or some // much better mechanism. PSP_CoreParameter().pixelWidth = width; PSP_CoreParameter().pixelHeight = height; } UpdateRenderResolution(); if (UpdateScreenScale(width, height, IsWindowSmall())) { NativeMessageReceived("gpu resized", ""); } // Don't save the window state if fullscreen. if (!g_Config.bFullScreen) { g_WindowState = wParam; } } break; case SIZE_MINIMIZED: Core_NotifyWindowHidden(true); if (!g_Config.bPauseWhenMinimized) { NativeMessageReceived("window minimized", "true"); } break; default: break; } break; case WM_TIMER: // Hack: Take the opportunity to also show/hide the mouse cursor in fullscreen mode. switch (wParam) { case TIMER_CURSORUPDATE: CorrectCursor(); return 0; case TIMER_CURSORMOVEUPDATE: hideCursor = true; KillTimer(hWnd, TIMER_CURSORMOVEUPDATE); return 0; } break; // For some reason, need to catch this here rather than in DisplayProc. case WM_MOUSEWHEEL: { int wheelDelta = (short)(wParam >> 16); KeyInput key; key.deviceId = DEVICE_ID_MOUSE; if (wheelDelta < 0) { key.keyCode = NKCODE_EXT_MOUSEWHEEL_DOWN; wheelDelta = -wheelDelta; } else { key.keyCode = NKCODE_EXT_MOUSEWHEEL_UP; } // There's no separate keyup event for mousewheel events, let's pass them both together. // This also means it really won't work great for key mapping :( Need to build a 1 frame delay or something. key.flags = KEY_DOWN | KEY_UP | KEY_HASWHEELDELTA | (wheelDelta << 16); NativeKey(key); } break; case WM_COMMAND: { if (!EmuThread_Ready()) return DefWindowProc(hWnd, message, wParam, lParam); MainWindowMenu_Process(hWnd, wParam); } break; case WM_USER_TOGGLE_FULLSCREEN: ToggleFullscreen(hwndMain, !g_Config.bFullScreen); break; case WM_INPUT: return WindowsRawInput::Process(hWnd, wParam, lParam); // TODO: Could do something useful with WM_INPUT_DEVICE_CHANGE? // Not sure why we are actually getting WM_CHAR even though we use RawInput, but alright.. case WM_CHAR: return WindowsRawInput::ProcessChar(hWnd, wParam, lParam); case WM_VERYSLEEPY_MSG: switch (wParam) { case VERYSLEEPY_WPARAM_SUPPORTED: return TRUE; case VERYSLEEPY_WPARAM_GETADDRINFO: { VerySleepy_AddrInfo *info = (VerySleepy_AddrInfo *)lParam; const u8 *ptr = (const u8 *)info->addr; std::string name; if (MIPSComp::jit && MIPSComp::jit->DescribeCodePtr(ptr, name)) { swprintf_s(info->name, L"Jit::%S", name.c_str()); return TRUE; } if (gpu && gpu->DescribeCodePtr(ptr, name)) { swprintf_s(info->name, L"GPU::%S", name.c_str()); return TRUE; } } return FALSE; default: return FALSE; } break; case WM_DROPFILES: { if (!EmuThread_Ready()) return DefWindowProc(hWnd, message, wParam, lParam); HDROP hdrop = (HDROP)wParam; int count = DragQueryFile(hdrop,0xFFFFFFFF,0,0); if (count != 1) { MessageBox(hwndMain,L"You can only load one file at a time",L"Error",MB_ICONINFORMATION); } else { TCHAR filename[512]; DragQueryFile(hdrop,0,filename,512); TCHAR *type = filename+_tcslen(filename)-3; NativeMessageReceived("boot", ConvertWStringToUTF8(filename).c_str()); Core_EnableStepping(false); } } break; case WM_CLOSE: EmuThread_Stop(); InputDevice::StopPolling(); WindowsRawInput::Shutdown(); return DefWindowProc(hWnd,message,wParam,lParam); case WM_DESTROY: KillTimer(hWnd, TIMER_CURSORUPDATE); KillTimer(hWnd, TIMER_CURSORMOVEUPDATE); PostQuitMessage(0); break; case WM_USER + 1: if (disasmWindow[0]) disasmWindow[0]->NotifyMapLoaded(); if (memoryWindow[0]) memoryWindow[0]->NotifyMapLoaded(); if (disasmWindow[0]) disasmWindow[0]->UpdateDialog(); SetForegroundWindow(hwndMain); break; case WM_USER_SAVESTATE_FINISH: SetCursor(LoadCursor(0, IDC_ARROW)); break; case WM_USER_UPDATE_UI: TranslateMenus(hwndMain, menu); break; case WM_USER_UPDATE_SCREEN: ShowScreenResolution(); break; case WM_USER_WINDOW_TITLE_CHANGED: UpdateWindowTitle(); break; case WM_USER_BROWSE_BOOT_DONE: BrowseAndBootDone(); break; case WM_MENUSELECT: // Unfortunately, accelerate keys (hotkeys) shares the same enabled/disabled states // with corresponding menu items. UpdateMenus(); WindowsRawInput::NotifyMenu(); break; // Turn off the screensaver. // Note that if there's a screensaver password, this simple method // doesn't work on Vista or higher. case WM_SYSCOMMAND: { switch (wParam) { case SC_SCREENSAVE: return 0; case SC_MONITORPOWER: return 0; } return DefWindowProc(hWnd, message, wParam, lParam); } default: return DefWindowProc(hWnd, message, wParam, lParam); } return 0; }
int main(int argc, char *argv[]) { #ifdef RPI bcm_host_init(); #endif putenv((char*)"SDL_VIDEO_CENTERED=1"); std::string app_name; std::string app_name_nice; bool landscape; NativeGetAppInfo(&app_name, &app_name_nice, &landscape); net::Init(); if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_JOYSTICK | SDL_INIT_AUDIO) < 0) { fprintf(stderr, "Unable to initialize SDL: %s\n", SDL_GetError()); return 1; } #ifdef __APPLE__ // Make sure to request a somewhat modern GL context at least - the // latest supported by MacOSX (really, really sad...) // Requires SDL 2.0 // We really should upgrade to SDL 2.0 soon. //SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); //SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); //SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 2); #endif #ifdef USING_EGL if (EGL_Open()) return 1; #endif // Get the video info before doing anything else, so we don't get skewed resolution results. // TODO: support multiple displays correctly SDL_DisplayMode displayMode; int should_be_zero = SDL_GetCurrentDisplayMode(0, &displayMode); if (should_be_zero != 0) { fprintf(stderr, "Could not get display mode: %s\n", SDL_GetError()); return 1; } g_DesktopWidth = displayMode.w; g_DesktopHeight = displayMode.h; SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8); SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8); SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8); SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24); SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8); SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); SDL_GL_SetSwapInterval(1); Uint32 mode; #ifdef USING_GLES2 mode = SDL_WINDOW_OPENGL | SDL_WINDOW_FULLSCREEN; #else mode = SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE; #endif int set_xres = -1; int set_yres = -1; bool portrait = false; bool set_ipad = false; float set_dpi = 1.0f; float set_scale = 1.0f; for (int i = 1; i < argc; i++) { if (!strcmp(argv[i],"--fullscreen")) mode |= SDL_WINDOW_FULLSCREEN_DESKTOP; if (set_xres == -2) { set_xres = parseInt(argv[i]); } else if (set_yres == -2) { set_yres = parseInt(argv[i]); } if (set_dpi == -2) set_dpi = parseFloat(argv[i]); if (set_scale == -2) set_scale = parseFloat(argv[i]); if (!strcmp(argv[i],"--xres")) set_xres = -2; if (!strcmp(argv[i],"--yres")) set_yres = -2; if (!strcmp(argv[i],"--dpi")) set_dpi = -2; if (!strcmp(argv[i],"--scale")) set_scale = -2; if (!strcmp(argv[i],"--ipad")) set_ipad = true; if (!strcmp(argv[i],"--portrait")) portrait = true; } // Is resolution is too low to run windowed if (g_DesktopWidth < 480 * 2 && g_DesktopHeight < 272 * 2) { mode |= SDL_WINDOW_FULLSCREEN_DESKTOP; } if (mode & SDL_WINDOW_FULLSCREEN_DESKTOP) { pixel_xres = g_DesktopWidth; pixel_yres = g_DesktopHeight; #ifdef PPSSPP g_Config.bFullScreen = true; #endif } else { // set a sensible default resolution (2x) pixel_xres = 480 * 2 * set_scale; pixel_yres = 272 * 2 * set_scale; if (portrait) { std::swap(pixel_xres, pixel_yres); } #ifdef PPSSPP g_Config.bFullScreen = false; #endif } set_dpi = 1.0f / set_dpi; if (set_ipad) { pixel_xres = 1024; pixel_yres = 768; } if (!landscape) { std::swap(pixel_xres, pixel_yres); } if (set_xres > 0) { pixel_xres = set_xres; } if (set_yres > 0) { pixel_yres = set_yres; } float dpi_scale = 1.0f; if (set_dpi > 0) { dpi_scale = set_dpi; } dp_xres = (float)pixel_xres * dpi_scale; dp_yres = (float)pixel_yres * dpi_scale; g_Screen = SDL_CreateWindow(app_name_nice.c_str(), SDL_WINDOWPOS_UNDEFINED_DISPLAY(getDisplayNumber()),\ SDL_WINDOWPOS_UNDEFINED, pixel_xres, pixel_yres, mode); if (g_Screen == NULL) { fprintf(stderr, "SDL_CreateWindow failed: %s\n", SDL_GetError()); SDL_Quit(); return 2; } SDL_GLContext glContext = SDL_GL_CreateContext(g_Screen); if (glContext == NULL) { fprintf(stderr, "SDL_GL_CreateContext failed: %s\n", SDL_GetError()); SDL_Quit(); return 2; } #ifdef USING_EGL EGL_Init(); #endif #ifdef PPSSPP SDL_SetWindowTitle(g_Screen, (app_name_nice + " " + PPSSPP_GIT_VERSION).c_str()); #endif #ifdef MOBILE_DEVICE SDL_ShowCursor(SDL_DISABLE); #endif #ifndef USING_GLES2 if (GLEW_OK != glewInit()) { printf("Failed to initialize glew!\n"); return 1; } if (GLEW_VERSION_2_0) { printf("OpenGL 2.0 or higher.\n"); } else { printf("Sorry, this program requires OpenGL 2.0.\n"); return 1; } #endif #ifdef _MSC_VER // VFSRegister("temp/", new DirectoryAssetReader("E:\\Temp\\")); TCHAR path[MAX_PATH]; SHGetFolderPath(NULL, CSIDL_APPDATA, NULL, 0, path); PathAppend(path, (app_name + "\\").c_str()); #else // Mac / Linux char path[2048]; const char *the_path = getenv("HOME"); if (!the_path) { struct passwd* pwd = getpwuid(getuid()); if (pwd) the_path = pwd->pw_dir; } strcpy(path, the_path); if (path[strlen(path)-1] != '/') strcat(path, "/"); #endif #ifdef _WIN32 NativeInit(argc, (const char **)argv, path, "D:\\", "BADCOFFEE"); #else NativeInit(argc, (const char **)argv, path, "/tmp", "BADCOFFEE"); #endif pixel_in_dps = (float)pixel_xres / dp_xres; g_dpi_scale = dp_xres / (float)pixel_xres; printf("Pixels: %i x %i\n", pixel_xres, pixel_yres); printf("Virtual pixels: %i x %i\n", dp_xres, dp_yres); NativeInitGraphics(); NativeResized(); SDL_AudioSpec fmt, ret_fmt; memset(&fmt, 0, sizeof(fmt)); fmt.freq = 44100; fmt.format = AUDIO_S16; fmt.channels = 2; fmt.samples = 2048; fmt.callback = &mixaudio; fmt.userdata = (void *)0; if (SDL_OpenAudio(&fmt, &ret_fmt) < 0) { ELOG("Failed to open audio: %s", SDL_GetError()); } else { if (ret_fmt.samples != fmt.samples) // Notify, but still use it ELOG("Output audio samples: %d (requested: %d)", ret_fmt.samples, fmt.samples); if (ret_fmt.freq != fmt.freq || ret_fmt.format != fmt.format || ret_fmt.channels != fmt.channels) { ELOG("Sound buffer format does not match requested format."); ELOG("Output audio freq: %d (requested: %d)", ret_fmt.freq, fmt.freq); ELOG("Output audio format: %d (requested: %d)", ret_fmt.format, fmt.format); ELOG("Output audio channels: %d (requested: %d)", ret_fmt.channels, fmt.channels); ELOG("Provided output format does not match requirement, turning audio off"); SDL_CloseAudio(); } } // Audio must be unpaused _after_ NativeInit() SDL_PauseAudio(0); #ifndef _WIN32 joystick = new SDLJoystick(); #endif EnableFZ(); int framecount = 0; float t = 0; float lastT = 0; uint32_t pad_buttons = 0; // legacy pad buttons while (true) { input_state.accelerometer_valid = false; input_state.mouse_valid = true; SDL_Event event; while (SDL_PollEvent(&event)) { float mx = event.motion.x * g_dpi_scale; float my = event.motion.y * g_dpi_scale; switch (event.type) { case SDL_QUIT: g_QuitRequested = 1; break; #if !defined(MOBILE_DEVICE) case SDL_WINDOWEVENT: switch (event.window.event) { case SDL_WINDOWEVENT_RESIZED: { Uint32 window_flags = SDL_GetWindowFlags(g_Screen); bool fullscreen = (window_flags & SDL_WINDOW_FULLSCREEN); pixel_xres = event.window.data1; pixel_yres = event.window.data2; dp_xres = (float)pixel_xres * dpi_scale; dp_yres = (float)pixel_yres * dpi_scale; NativeResized(); #if defined(PPSSPP) // Set variable here in case fullscreen was toggled by hotkey g_Config.bFullScreen = fullscreen; // Hide/Show cursor correctly toggling fullscreen if (lastUIState == UISTATE_INGAME && fullscreen && !g_Config.bShowTouchControls) { SDL_ShowCursor(SDL_DISABLE); } else if (lastUIState != UISTATE_INGAME || !fullscreen) { SDL_ShowCursor(SDL_ENABLE); } #endif break; } break; } #endif case SDL_KEYDOWN: { int k = event.key.keysym.sym; KeyInput key; key.flags = KEY_DOWN; key.keyCode = KeyMapRawSDLtoNative.find(k)->second; key.deviceId = DEVICE_ID_KEYBOARD; NativeKey(key); for (int i = 0; i < ARRAY_SIZE(legacyKeyMap); i++) { if (legacyKeyMap[i] == key.keyCode) pad_buttons |= 1 << i; } break; } case SDL_KEYUP: { int k = event.key.keysym.sym; KeyInput key; key.flags = KEY_UP; key.keyCode = KeyMapRawSDLtoNative.find(k)->second; key.deviceId = DEVICE_ID_KEYBOARD; NativeKey(key); for (int i = 0; i < ARRAY_SIZE(legacyKeyMap); i++) { if (legacyKeyMap[i] == key.keyCode) pad_buttons &= ~(1 << i); } break; } case SDL_TEXTINPUT: { int pos = 0; int c = u8_nextchar(event.text.text, &pos); KeyInput key; key.flags = KEY_CHAR; key.keyCode = c; key.deviceId = DEVICE_ID_KEYBOARD; NativeKey(key); break; } case SDL_MOUSEBUTTONDOWN: switch (event.button.button) { case SDL_BUTTON_LEFT: { input_state.pointer_x[0] = mx; input_state.pointer_y[0] = my; input_state.pointer_down[0] = true; input_state.mouse_valid = true; TouchInput input; input.x = mx; input.y = my; input.flags = TOUCH_DOWN | TOUCH_MOUSE; input.id = 0; NativeTouch(input); KeyInput key(DEVICE_ID_MOUSE, NKCODE_EXT_MOUSEBUTTON_1, KEY_DOWN); NativeKey(key); } break; case SDL_BUTTON_RIGHT: { KeyInput key(DEVICE_ID_MOUSE, NKCODE_EXT_MOUSEBUTTON_2, KEY_DOWN); NativeKey(key); } break; } break; case SDL_MOUSEWHEEL: { KeyInput key; key.deviceId = DEVICE_ID_MOUSE; if (event.wheel.y > 0) { key.keyCode = NKCODE_EXT_MOUSEWHEEL_UP; } else { key.keyCode = NKCODE_EXT_MOUSEWHEEL_DOWN; } key.flags = KEY_DOWN; NativeKey(key); // SDL2 doesn't consider the mousewheel a button anymore // so let's send the KEY_UP right away. // Maybe KEY_UP alone will suffice? key.flags = KEY_UP; NativeKey(key); } case SDL_MOUSEMOTION: if (input_state.pointer_down[0]) { input_state.pointer_x[0] = mx; input_state.pointer_y[0] = my; input_state.mouse_valid = true; TouchInput input; input.x = mx; input.y = my; input.flags = TOUCH_MOVE | TOUCH_MOUSE; input.id = 0; NativeTouch(input); } break; case SDL_MOUSEBUTTONUP: switch (event.button.button) { case SDL_BUTTON_LEFT: { input_state.pointer_x[0] = mx; input_state.pointer_y[0] = my; input_state.pointer_down[0] = false; input_state.mouse_valid = true; //input_state.mouse_buttons_up = 1; TouchInput input; input.x = mx; input.y = my; input.flags = TOUCH_UP | TOUCH_MOUSE; input.id = 0; NativeTouch(input); KeyInput key(DEVICE_ID_MOUSE, NKCODE_EXT_MOUSEBUTTON_1, KEY_UP); NativeKey(key); } break; case SDL_BUTTON_RIGHT: { KeyInput key(DEVICE_ID_MOUSE, NKCODE_EXT_MOUSEBUTTON_2, KEY_UP); NativeKey(key); } break; } break; default: #ifndef _WIN32 joystick->ProcessInput(event); #endif break; } } if (g_QuitRequested) break; const uint8 *keys = SDL_GetKeyboardState(NULL); SimulateGamepad(keys, &input_state); input_state.pad_buttons = pad_buttons; UpdateInputState(&input_state, true); #ifdef PPSSPP UpdateRunLoop(); #else NativeUpdate(input_state); NativeRender(); #endif if (g_QuitRequested) break; #if defined(PPSSPP) && !defined(MOBILE_DEVICE) if (lastUIState != GetUIState()) { lastUIState = GetUIState(); if (lastUIState == UISTATE_INGAME && g_Config.bFullScreen && !g_Config.bShowTouchControls) SDL_ShowCursor(SDL_DISABLE); if (lastUIState != UISTATE_INGAME && g_Config.bFullScreen) SDL_ShowCursor(SDL_ENABLE); } #endif if (framecount % 60 == 0) { // glsl_refresh(); // auto-reloads modified GLSL shaders once per second. } #ifdef USING_EGL eglSwapBuffers(g_eglDisplay, g_eglSurface); #else if (!keys[SDLK_TAB] || t - lastT >= 1.0/60.0) { SDL_GL_SwapWindow(g_Screen); lastT = t; } #endif ToggleFullScreenIfFlagSet(); time_update(); t = time_now(); framecount++; } #ifndef _WIN32 delete joystick; #endif // Faster exit, thanks to the OS. Remove this if you want to debug shutdown // The speed difference is only really noticable on Linux. On Windows you do notice it though #ifndef MOBILE_DEVICE exit(0); #endif NativeShutdownGraphics(); SDL_PauseAudio(1); SDL_CloseAudio(); NativeShutdown(); #ifdef USING_EGL EGL_Close(); #endif SDL_GL_DeleteContext(glContext); SDL_Quit(); net::Shutdown(); #ifdef RPI bcm_host_deinit(); #endif exit(0); return 0; }
bool ButtonLabel::ExpandMacros(const TCHAR *In, TCHAR *OutBuffer, size_t Size) { // ToDo, check Buffer Size bool invalid = false; CopyString(OutBuffer, In, Size); if (_tcsstr(OutBuffer, _T("$(")) == NULL) return false; if (_tcsstr(OutBuffer, _T("$(CheckAirspace)"))) { if (airspace_database.empty()) invalid = true; ReplaceInString(OutBuffer, _T("$(CheckAirspace)"), _T(""), Size); } invalid |= ExpandTaskMacros(OutBuffer, Size, Calculated(), GetComputerSettings()); ExpandTrafficMacros(OutBuffer, Size); if (_tcsstr(OutBuffer, _T("$(CheckFLARM)"))) { if (!Basic().flarm.status.available) invalid = true; ReplaceInString(OutBuffer, _T("$(CheckFLARM)"), _T(""), Size); } if (_tcsstr(OutBuffer, _T("$(CheckCircling)"))) { if (!Calculated().circling) invalid = true; ReplaceInString(OutBuffer, _T("$(CheckCircling)"), _T(""), Size); } if (_tcsstr(OutBuffer, _T("$(CheckVega)"))) { if (devVarioFindVega()== NULL) invalid = true; ReplaceInString(OutBuffer, _T("$(CheckVega)"), _T(""), Size); } if (_tcsstr(OutBuffer, _T("$(CheckReplay)"))) { if (CommonInterface::MovementDetected()) invalid = true; ReplaceInString(OutBuffer, _T("$(CheckReplay)"), _T(""), Size); } if (_tcsstr(OutBuffer, _T("$(CheckWaypointFile)"))) { invalid |= way_points.IsEmpty(); ReplaceInString(OutBuffer, _T("$(CheckWaypointFile)"), _T(""), Size); } if (_tcsstr(OutBuffer, _T("$(CheckLogger)"))) { invalid |= Basic().gps.replay; ReplaceInString(OutBuffer, _T("$(CheckLogger)"), _T(""), Size); } if (_tcsstr(OutBuffer, _T("$(CheckNet)"))) { #ifndef HAVE_HTTP invalid = true; #endif ReplaceInString(OutBuffer, _T("$(CheckNet)"), _T(""), Size); } if (_tcsstr(OutBuffer, _T("$(CheckTerrain)"))) { if (!Calculated().terrain_valid) invalid = true; ReplaceInString(OutBuffer, _T("$(CheckTerrain)"), _T(""), Size); } CondReplaceInString(logger != nullptr && logger->IsLoggerActive(), OutBuffer, _T("$(LoggerActive)"), _("Stop"), _("Start"), Size); if (_tcsstr(OutBuffer, _T("$(SnailTrailToggleName)"))) { switch (GetMapSettings().trail.length) { case TrailSettings::Length::OFF: ReplaceInString(OutBuffer, _T("$(SnailTrailToggleName)"), _("Long"), Size); break; case TrailSettings::Length::LONG: ReplaceInString(OutBuffer, _T("$(SnailTrailToggleName)"), _("Short"), Size); break; case TrailSettings::Length::SHORT: ReplaceInString(OutBuffer, _T("$(SnailTrailToggleName)"), _("Full"), Size); break; case TrailSettings::Length::FULL: ReplaceInString(OutBuffer, _T("$(SnailTrailToggleName)"), _("Off"), Size); break; } } if (_tcsstr(OutBuffer, _T("$(AirSpaceToggleName)"))) { ReplaceInString(OutBuffer, _T("$(AirSpaceToggleName)"), GetMapSettings().airspace.enable ? _("Off") : _("On"), Size); } if (_tcsstr(OutBuffer, _T("$(TerrainTopologyToggleName)"))) { char val = 0; if (GetMapSettings().topography_enabled) val++; if (GetMapSettings().terrain.enable) val += (char)2; switch (val) { case 0: ReplaceInString(OutBuffer, _T("$(TerrainTopologyToggleName)"), _("Topography On"), Size); break; case 1: ReplaceInString(OutBuffer, _T("$(TerrainTopologyToggleName)"), _("Terrain On"), Size); break; case 2: ReplaceInString(OutBuffer, _T("$(TerrainTopologyToggleName)"), _("Terrain + Topography"), Size); break; case 3: ReplaceInString(OutBuffer, _T("$(TerrainTopologyToggleName)"), _("Terrain Off"), Size); break; } } if (_tcsstr(OutBuffer, _T("$(TerrainTopographyToggleName)"))) { char val = 0; if (GetMapSettings().topography_enabled) val++; if (GetMapSettings().terrain.enable) val += (char)2; switch (val) { case 0: ReplaceInString(OutBuffer, _T("$(TerrainTopographyToggleName)"), _("Topography On"), Size); break; case 1: ReplaceInString(OutBuffer, _T("$(TerrainTopographyToggleName)"), _("Terrain On"), Size); break; case 2: ReplaceInString(OutBuffer, _T("$(TerrainTopographyToggleName)"), _("Terrain + Topography"), Size); break; case 3: ReplaceInString(OutBuffer, _T("$(TerrainTopographyToggleName)"), _("Terrain Off"), Size); break; } } CondReplaceInString(CommonInterface::main_window->GetFullScreen(), OutBuffer, _T("$(FullScreenToggleActionName)"), _("Off"), _("On"), Size); CondReplaceInString(GetMapSettings().auto_zoom_enabled, OutBuffer, _T("$(ZoomAutoToggleActionName)"), _("Manual"), _("Auto"), Size); CondReplaceInString(GetMapSettings().topography_enabled, OutBuffer, _T("$(TopologyToggleActionName)"), _("Off"), _("On"), Size); CondReplaceInString(GetMapSettings().topography_enabled, OutBuffer, _T("$(TopographyToggleActionName)"), _("Off"), _("On"), Size); CondReplaceInString(GetMapSettings().terrain.enable, OutBuffer, _T("$(TerrainToggleActionName)"), _("Off"), _("On"), Size); CondReplaceInString(GetMapSettings().airspace.enable, OutBuffer, _T("$(AirspaceToggleActionName)"), _("Off"), _("On"), Size); if (_tcsstr(OutBuffer, _T("$(MapLabelsToggleActionName)"))) { static const TCHAR *const labels[] = { N_("All"), N_("Task & Landables"), N_("Task"), N_("None") }; static constexpr unsigned int n = ARRAY_SIZE(labels); unsigned int i = (unsigned)GetMapSettings().waypoint.label_selection; ReplaceInString(OutBuffer, _T("$(MapLabelsToggleActionName)"), gettext(labels[(i + 1) % n]), Size); } CondReplaceInString(GetComputerSettings().task.auto_mc, OutBuffer, _T("$(MacCreadyToggleActionName)"), _("Manual"), _("Auto"), Size); CondReplaceInString(GetUIState().auxiliary_enabled, OutBuffer, _T("$(AuxInfoToggleActionName)"), _("Off"), _("On"), Size); CondReplaceInString(GetUIState().force_display_mode == DisplayMode::CIRCLING, OutBuffer, _T("$(DispModeClimbShortIndicator)"), _T("(*)"), _T(""), Size); CondReplaceInString(GetUIState().force_display_mode == DisplayMode::CRUISE, OutBuffer, _T("$(DispModeCruiseShortIndicator)"), _T("(*)"), _T(""), Size); CondReplaceInString(GetUIState().force_display_mode == DisplayMode::NONE, OutBuffer, _T("$(DispModeAutoShortIndicator)"), _T("(*)"), _T(""), Size); CondReplaceInString(GetUIState().force_display_mode == DisplayMode::FINAL_GLIDE, OutBuffer, _T("$(DispModeFinalShortIndicator)"), _T("(*)"), _T(""), Size); CondReplaceInString(GetMapSettings().airspace.altitude_mode == AirspaceDisplayMode::ALLON, OutBuffer, _T("$(AirspaceModeAllShortIndicator)"), _T("(*)"), _T(""), Size); CondReplaceInString(GetMapSettings().airspace.altitude_mode == AirspaceDisplayMode::CLIP, OutBuffer, _T("$(AirspaceModeClipShortIndicator)"), _T("(*)"), _T(""), Size); CondReplaceInString(GetMapSettings().airspace.altitude_mode == AirspaceDisplayMode::AUTO, OutBuffer, _T("$(AirspaceModeAutoShortIndicator)"), _T("(*)"), _T(""), Size); CondReplaceInString(GetMapSettings().airspace.altitude_mode == AirspaceDisplayMode::ALLBELOW, OutBuffer, _T("$(AirspaceModeBelowShortIndicator)"), _T("(*)"), _T(""), Size); CondReplaceInString(GetMapSettings().airspace.altitude_mode == AirspaceDisplayMode::ALLOFF, OutBuffer, _T("$(AirspaceModeAllOffIndicator)"), _T("(*)"), _T(""), Size); CondReplaceInString(GetMapSettings().trail.length == TrailSettings::Length::OFF, OutBuffer, _T("$(SnailTrailOffShortIndicator)"), _T("(*)"), _T(""), Size); CondReplaceInString(GetMapSettings().trail.length == TrailSettings::Length::SHORT, OutBuffer, _T("$(SnailTrailShortShortIndicator)"), _T("(*)"), _T(""), Size); CondReplaceInString(GetMapSettings().trail.length == TrailSettings::Length::LONG, OutBuffer, _T("$(SnailTrailLongShortIndicator)"), _T("(*)"), _T(""), Size); CondReplaceInString(GetMapSettings().trail.length == TrailSettings::Length::FULL, OutBuffer, _T("$(SnailTrailFullShortIndicator)"), _T("(*)"), _T(""), Size); CondReplaceInString(!GetMapSettings().airspace.enable, OutBuffer, _T("$(AirSpaceOffShortIndicator)"), _T("(*)"), _T(""), Size); CondReplaceInString(GetMapSettings().airspace.enable, OutBuffer, _T("$(AirSpaceOnShortIndicator)"), _T("(*)"), _T(""), Size); CondReplaceInString(CommonInterface::GetUISettings().traffic.enable_gauge, OutBuffer, _T("$(FlarmDispToggleActionName)"), _("Off"), _("On"), Size); CondReplaceInString(GetMapSettings().auto_zoom_enabled, OutBuffer, _T("$(ZoomAutoToggleActionName)"), _("Manual"), _("Auto"), Size); if (_tcsstr(OutBuffer, _T("$(NextPageName)"))) { TCHAR label[30]; const PageLayout &page = CommonInterface::GetUISettings().pages.pages[Pages::NextIndex()]; page.MakeTitle(CommonInterface::GetUISettings().info_boxes, label, true); ReplaceInString(OutBuffer, _T("$(NextPageName)"), label, Size); } return invalid; }
// Some platforms, like Android, do not call this function but handle things on their own. void Core_Run() { #if defined(_DEBUG) host->UpdateDisassembly(); #endif #if !defined(USING_QT_UI) || defined(MOBILE_DEVICE) while (true) #endif { reswitch: if (GetUIState() != UISTATE_INGAME) { CoreStateProcessed(); if (GetUIState() == UISTATE_EXIT) { return; } Core_RunLoop(); #if defined(USING_QT_UI) && !defined(MOBILE_DEVICE) return; #else continue; #endif } switch (coreState) { case CORE_RUNNING: // enter a fast runloop Core_RunLoop(); break; // We should never get here on Android. case CORE_STEPPING: singleStepPending = false; CoreStateProcessed(); // Check if there's any pending savestate actions. SaveState::Process(); if (coreState == CORE_POWERDOWN) { return; } // wait for step command.. #if defined(USING_QT_UI) || defined(_DEBUG) host->UpdateDisassembly(); host->UpdateMemView(); host->SendCoreWait(true); #endif m_hStepEvent.wait(m_hStepMutex); #if defined(USING_QT_UI) || defined(_DEBUG) host->SendCoreWait(false); #endif #if defined(USING_QT_UI) && !defined(MOBILE_DEVICE) if (coreState != CORE_STEPPING) return; #endif // No step pending? Let's go back to the wait. if (!singleStepPending || coreState != CORE_STEPPING) { if (coreState == CORE_POWERDOWN) { return; } goto reswitch; } Core_SingleStep(); // update disasm dialog #if defined(USING_QT_UI) || defined(_DEBUG) host->UpdateDisassembly(); host->UpdateMemView(); #endif break; case CORE_POWERUP: case CORE_POWERDOWN: case CORE_ERROR: // Exit loop!! CoreStateProcessed(); return; case CORE_NEXTFRAME: return; } } }
bool InCirclingMode() const { return GetUIState().display_mode == DisplayMode::CIRCLING; }
void ClsFlatButton::PaintControl( ClsDC *pDC ) { // Get the client rectangle. ClsRect rc = GetClientRect(); // Any room to render in? if ( ! rc.IsEmpty()) { // Create an off-screen buffer. ClsBufferDC dc( *pDC, rc ); // Snapshot the DC. int sDC = dc.SaveDC(); // Render the frame. if ( ! m_bXPStyle ) RenderFrame( &dc, rc ); else { // Only when were hot or down. COLORREF crFg, crBg; if (( IsHot() || IsDown()) && IsWindowEnabled()) { crFg = XPColors.GetXPColor( ClsXPColors::XPC_OUTER_SELECTION ); crBg = XPColors.GetXPColor( IsDown() ? ClsXPColors::XPC_INNER_CHECKED_SELECTED : ClsXPColors::XPC_INNER_SELECTION ); } else { // Default colors... crFg = ( IsDefault() && IsWindowEnabled() && ! m_bXPDefault ) ? XPColors.GetXPColor( ClsXPColors::XPC_OUTER_SELECTION ) : XPColors.GetXPColor( ClsXPColors::XPC_IMAGE_DISABLED ); crBg = ::GetSysColor( COLOR_BTNFACE ); } // Render rectangle. dc.OutlinedRectangle( rc, crFg, crBg ); } // Determine rendering flags. DWORD dwFlags = 0; if ( ! IsWindowEnabled()) dwFlags |= ClsDrawTools::CDSF_DISABLED; if ( IsDown() && ! ThemingAPI.IsThemingEnabled()) dwFlags |= ClsDrawTools::CDSF_DOWNPRESSED; if ( GetUIState() & UISF_HIDEACCEL ) dwFlags |= ClsDrawTools::CDSF_HIDEACCEL; if ( IsHot()) dwFlags |= ClsDrawTools::CDSF_HOT; // Do we have any images? if ( m_hImages ) { // Copy the client rectangle. ClsRect rcimg( rc ); // We need to know the size of the images // in the image list. int cx, cy; ::ImageList_GetIconSize( m_hImages, &cx, &cy ); // Determine the place at which we render the images. rcimg.Offset( ::GetSystemMetrics( SM_CXFRAME ), 0 ); rcimg.Right() = rcimg.Left() + cx; // Adjust label rectangle. rc.Left() += cx + 4; // Render the image. if ( ! m_bXPStyle ) ClsDrawTools::RenderBitmap( dc, m_hImages, ( IsHot() || IsDown()) ? m_aImageIndex[ FIIF_HOT ] : m_aImageIndex[ FIIF_NORMAL ], rcimg, dwFlags ); else ClsDrawTools::RenderXPBitmap( dc, m_hImages, ( IsHot() || IsDown()) ? m_aImageIndex[ FIIF_HOT ] : m_aImageIndex[ FIIF_NORMAL ], rcimg, dwFlags ); } // Render the caption. ClsString str( m_hWnd ); // Anything to render? if ( str.GetStringLength()) { // Deflate the label rectangle. rc.Deflate( 3, 3 ); // Do we have the focus? if ( HasFocus() && ! ( GetUIState() & UISF_HIDEFOCUS ) && ! m_bPanelHeader ) dc.DrawFocusRect( rc ); // Setup the font to use. ClsFont font; GetFont( font ); ClsSelector sel( &dc, font ); // Render transparently. dc.SetBkMode( TRANSPARENT ); // We must not use the disabled flag if we are // rendering XP style... if ( m_bXPStyle ) dwFlags &= ~( ClsDrawTools::CDSF_DISABLED | ClsDrawTools::CDSF_DOWNPRESSED ); // Set text color. COLORREF cr = GetSysColor( IsWindowEnabled() ? COLOR_BTNTEXT : COLOR_GRAYTEXT ); if ( IsDown() && m_bXPStyle ) cr = XPColors.GetXPColor( ClsXPColors::XPC_TEXT_BACKGROUND ); if ( IsHot() || IsDown()) cr = m_crHotLabelColor == CLR_NONE ? cr : m_crHotLabelColor; else cr = m_crLabelColor == CLR_NONE ? cr : m_crLabelColor; dc.SetTextColor( cr ); // Render the caption. ClsDrawTools::RenderText( dc, str, rc, dwFlags ); } // Restore device context. dc.RestoreDC( sDC ); } }
bool MainUI::event(QEvent *e) { TouchInput input; QList<QTouchEvent::TouchPoint> touchPoints; switch(e->type()) { case QEvent::TouchBegin: case QEvent::TouchUpdate: case QEvent::TouchEnd: touchPoints = static_cast<QTouchEvent *>(e)->touchPoints(); foreach (const QTouchEvent::TouchPoint &touchPoint, touchPoints) { switch (touchPoint.state()) { case Qt::TouchPointStationary: break; case Qt::TouchPointPressed: case Qt::TouchPointReleased: input_state.pointer_down[touchPoint.id()] = (touchPoint.state() == Qt::TouchPointPressed); input_state.pointer_x[touchPoint.id()] = touchPoint.pos().x() * g_dpi_scale * xscale; input_state.pointer_y[touchPoint.id()] = touchPoint.pos().y() * g_dpi_scale * yscale; input.x = touchPoint.pos().x() * g_dpi_scale * xscale; input.y = touchPoint.pos().y() * g_dpi_scale * yscale; input.flags = (touchPoint.state() == Qt::TouchPointPressed) ? TOUCH_DOWN : TOUCH_UP; input.id = touchPoint.id(); NativeTouch(input); break; case Qt::TouchPointMoved: input_state.pointer_x[touchPoint.id()] = touchPoint.pos().x() * g_dpi_scale * xscale; input_state.pointer_y[touchPoint.id()] = touchPoint.pos().y() * g_dpi_scale * yscale; input.x = touchPoint.pos().x() * g_dpi_scale * xscale; input.y = touchPoint.pos().y() * g_dpi_scale * yscale; input.flags = TOUCH_MOVE; input.id = touchPoint.id(); NativeTouch(input); break; default: break; } } break; case QEvent::MouseButtonDblClick: if (!g_Config.bShowTouchControls || GetUIState() != UISTATE_INGAME) emit doubleClick(); break; case QEvent::MouseButtonPress: case QEvent::MouseButtonRelease: input_state.pointer_down[0] = (e->type() == QEvent::MouseButtonPress); input_state.pointer_x[0] = ((QMouseEvent*)e)->pos().x() * g_dpi_scale * xscale; input_state.pointer_y[0] = ((QMouseEvent*)e)->pos().y() * g_dpi_scale * yscale; input.x = ((QMouseEvent*)e)->pos().x() * g_dpi_scale * xscale; input.y = ((QMouseEvent*)e)->pos().y() * g_dpi_scale * yscale; input.flags = (e->type() == QEvent::MouseButtonPress) ? TOUCH_DOWN : TOUCH_UP; input.id = 0; NativeTouch(input); break; case QEvent::MouseMove: input_state.pointer_x[0] = ((QMouseEvent*)e)->pos().x() * g_dpi_scale * xscale; input_state.pointer_y[0] = ((QMouseEvent*)e)->pos().y() * g_dpi_scale * yscale; input.x = ((QMouseEvent*)e)->pos().x() * g_dpi_scale * xscale; input.y = ((QMouseEvent*)e)->pos().y() * g_dpi_scale * yscale; input.flags = TOUCH_MOVE; input.id = 0; NativeTouch(input); break; case QEvent::Wheel: NativeKey(KeyInput(DEVICE_ID_MOUSE, ((QWheelEvent*)e)->delta()<0 ? NKCODE_EXT_MOUSEWHEEL_DOWN : NKCODE_EXT_MOUSEWHEEL_UP, KEY_DOWN)); break; case QEvent::KeyPress: NativeKey(KeyInput(DEVICE_ID_KEYBOARD, KeyMapRawQttoNative.find(((QKeyEvent*)e)->key())->second, KEY_DOWN)); break; case QEvent::KeyRelease: NativeKey(KeyInput(DEVICE_ID_KEYBOARD, KeyMapRawQttoNative.find(((QKeyEvent*)e)->key())->second, KEY_UP)); break; default: return QWidget::event(e); } e->accept(); return true; }
void GlueMapWindow::DrawMapScale(Canvas &canvas, const PixelRect &rc, const MapWindowProjection &projection) const { RenderMapScale(canvas, projection, rc, look.overlay); if (!projection.IsValid()) return; StaticString<80> buffer; buffer.clear(); if (GetMapSettings().auto_zoom_enabled) buffer = _T("AUTO "); switch (follow_mode) { case FOLLOW_SELF: break; case FOLLOW_PAN: buffer += _T("PAN "); break; } const UIState &ui_state = GetUIState(); if (ui_state.auxiliary_enabled) { buffer += ui_state.panel_name; buffer += _T(" "); } if (Basic().gps.replay) buffer += _T("REPLAY "); else if (Basic().gps.simulator) { buffer += _("Simulator"); buffer += _T(" "); } if (GetComputerSettings().polar.ballast_timer_active) buffer.AppendFormat( _T("BALLAST %d LITERS "), (int)GetComputerSettings().polar.glide_polar_task.GetBallastLitres()); if (rasp_renderer != nullptr) { const TCHAR *label = rasp_renderer->GetLabel(); if (label != nullptr) buffer += gettext(label); } if (!buffer.empty()) { const Font &font = *look.overlay.overlay_font; canvas.Select(font); const unsigned height = font.GetCapitalHeight() + Layout::GetTextPadding(); int y = rc.bottom - height; TextInBoxMode mode; mode.vertical_position = TextInBoxMode::VerticalPosition::ABOVE; mode.shape = LabelShape::OUTLINED; TextInBox(canvas, buffer, 0, y, mode, rc, nullptr); } }