💿🐜 Antkeeper source code https://antkeeper.com
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

818 lines
25 KiB

  1. /*
  2. * Copyright (C) 2023 Christopher J. Howard
  3. *
  4. * This file is part of Antkeeper source code.
  5. *
  6. * Antkeeper source code is free software: you can redistribute it and/or modify
  7. * it under the terms of the GNU General Public License as published by
  8. * the Free Software Foundation, either version 3 of the License, or
  9. * (at your option) any later version.
  10. *
  11. * Antkeeper source code is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. * GNU General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU General Public License
  17. * along with Antkeeper source code. If not, see <http://www.gnu.org/licenses/>.
  18. */
  19. #include "application.hpp"
  20. #include "config.hpp"
  21. #include "debug/log.hpp"
  22. #include "input/scancode.hpp"
  23. #include "math/map.hpp"
  24. #include <SDL2/SDL.h>
  25. #include <glad/glad.h>
  26. #include <stdexcept>
  27. #include <utility>
  28. application::application
  29. (
  30. const std::string& window_title,
  31. int window_x,
  32. int window_y,
  33. int window_w,
  34. int window_h,
  35. bool maximized,
  36. bool fullscreen,
  37. bool v_sync
  38. ):
  39. closed(false),
  40. maximized(false),
  41. fullscreen(true),
  42. v_sync(false),
  43. cursor_visible(true),
  44. display_size({0, 0}),
  45. display_dpi(0.0f),
  46. windowed_position({-1, -1}),
  47. windowed_size({-1, -1}),
  48. viewport_size({-1, -1}),
  49. mouse_position({0, 0}),
  50. sdl_window(nullptr),
  51. sdl_gl_context(nullptr)
  52. {
  53. // Log SDL info
  54. // SDL_version sdl_compiled_version;
  55. // SDL_version sdl_linked_version;
  56. // SDL_VERSION(&sdl_compiled_version);
  57. // SDL_GetVersion(&sdl_linked_version);
  58. // debug::log::info
  59. // (
  60. // "SDL compiled version: {}.{}.{}; linked version: {}.{}.{}",
  61. // sdl_compiled_version.major,
  62. // sdl_compiled_version.minor,
  63. // sdl_compiled_version.patch,
  64. // sdl_linked_version.major,
  65. // sdl_linked_version.minor,
  66. // sdl_linked_version.patch
  67. // );
  68. // Init SDL events and video subsystems
  69. debug::log::trace("Initializing SDL events and video subsystems...");
  70. if (SDL_InitSubSystem(SDL_INIT_EVENTS | SDL_INIT_VIDEO) != 0)
  71. {
  72. debug::log::fatal("Failed to initialize SDL events and video subsystems: {}", SDL_GetError());
  73. throw std::runtime_error("Failed to initialize SDL events and video subsystems");
  74. }
  75. debug::log::trace("Initialized SDL events and video subsystems");
  76. // Query displays
  77. debug::log::trace("Querying displays...");
  78. const int sdl_display_count = SDL_GetNumVideoDisplays();
  79. if (sdl_display_count < 1)
  80. {
  81. debug::log::fatal("No displays detected: {}", SDL_GetError());
  82. throw std::runtime_error("No displays detected");
  83. }
  84. debug::log::info("Display count: {}", sdl_display_count);
  85. for (int i = 0; i < sdl_display_count; ++i)
  86. {
  87. // Query display mode
  88. SDL_DisplayMode sdl_display_mode;
  89. if (SDL_GetDesktopDisplayMode(i, &sdl_display_mode) != 0)
  90. {
  91. debug::log::error("Failed to get mode of display {}: {}", i, SDL_GetError());
  92. SDL_ClearError();
  93. continue;
  94. }
  95. // Query display name
  96. const char* sdl_display_name = SDL_GetDisplayName(i);
  97. if (!sdl_display_name)
  98. {
  99. debug::log::warning("Failed to get name of display {}: {}", i, SDL_GetError());
  100. SDL_ClearError();
  101. sdl_display_name = "";
  102. }
  103. // Query display DPI
  104. float sdl_display_dpi;
  105. if (SDL_GetDisplayDPI(i, &sdl_display_dpi, nullptr, nullptr) != 0)
  106. {
  107. const float default_dpi = 96.0f;
  108. debug::log::warning("Failed to get DPI of display {}: {}; Defaulting to {} DPI", i, SDL_GetError(), default_dpi);
  109. SDL_ClearError();
  110. }
  111. // Log display information
  112. debug::log::info("Display {} name: \"{}\"; resolution: {}x{}; refresh rate: {}Hz; DPI: {}", i, sdl_display_name, sdl_display_mode.w, sdl_display_mode.h, sdl_display_mode.refresh_rate, sdl_display_dpi);
  113. }
  114. debug::log::trace("Queried displays");
  115. // Detect display dimensions
  116. SDL_DisplayMode sdl_desktop_display_mode;
  117. if (SDL_GetDesktopDisplayMode(0, &sdl_desktop_display_mode) != 0)
  118. {
  119. debug::log::fatal("Failed to detect desktop display mode: {}", SDL_GetError());
  120. throw std::runtime_error("Failed to detect desktop display mode");
  121. }
  122. display_size = {sdl_desktop_display_mode.w, sdl_desktop_display_mode.h};
  123. // Detect display DPI
  124. if (SDL_GetDisplayDPI(0, &display_dpi, nullptr, nullptr) != 0)
  125. {
  126. debug::log::fatal("Failed to detect display DPI: {}", SDL_GetError());
  127. throw std::runtime_error("Failed to detect display DPI");
  128. }
  129. // Log display properties
  130. debug::log::info("Detected {}x{}@{}Hz display with {} DPI", sdl_desktop_display_mode.w, sdl_desktop_display_mode.h, sdl_desktop_display_mode.refresh_rate, display_dpi);
  131. // Load OpenGL library
  132. debug::log::trace("Loading OpenGL library...");
  133. if (SDL_GL_LoadLibrary(nullptr) != 0)
  134. {
  135. debug::log::fatal("Failed to load OpenGL library: {}", SDL_GetError());
  136. throw std::runtime_error("Failed to load OpenGL library");
  137. }
  138. debug::log::trace("Loaded OpenGL library");
  139. // Set OpenGL-related window creation hints
  140. SDL_GL_SetAttribute(SDL_GL_ACCELERATED_VISUAL, 1);
  141. SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, config::opengl_version_major);
  142. SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, config::opengl_version_minor);
  143. SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_FORWARD_COMPATIBLE_FLAG);
  144. SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
  145. SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
  146. SDL_GL_SetAttribute(SDL_GL_RED_SIZE, config::opengl_min_red_size);
  147. SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, config::opengl_min_green_size);
  148. SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, config::opengl_min_blue_size);
  149. SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, config::opengl_min_alpha_size);
  150. SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, config::opengl_min_depth_size);
  151. SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, config::opengl_min_stencil_size);
  152. Uint32 sdl_window_flags = SDL_WINDOW_ALLOW_HIGHDPI | SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE;
  153. if (fullscreen)
  154. {
  155. sdl_window_flags |= SDL_WINDOW_FULLSCREEN_DESKTOP;
  156. }
  157. if (maximized)
  158. {
  159. sdl_window_flags |= SDL_WINDOW_MAXIMIZED;
  160. }
  161. if (window_x == -1 && window_y == -1)
  162. {
  163. window_x = SDL_WINDOWPOS_CENTERED;
  164. window_y = SDL_WINDOWPOS_CENTERED;
  165. }
  166. if (window_w <= 0 || window_h <= 0)
  167. {
  168. window_w = sdl_desktop_display_mode.w / 2;
  169. window_h = sdl_desktop_display_mode.h / 2;
  170. }
  171. // Create a hidden fullscreen window
  172. debug::log::trace("Creating window...");
  173. sdl_window = SDL_CreateWindow
  174. (
  175. window_title.c_str(),
  176. window_x,
  177. window_y,
  178. window_w,
  179. window_h,
  180. sdl_window_flags
  181. );
  182. if (!sdl_window)
  183. {
  184. debug::log::fatal("Failed to create {}x{} window: {}", display_size[0], display_size[1], SDL_GetError());
  185. throw std::runtime_error("Failed to create SDL window");
  186. }
  187. debug::log::trace("Created window");
  188. if (window_x != SDL_WINDOWPOS_CENTERED && window_y != SDL_WINDOWPOS_CENTERED)
  189. {
  190. this->windowed_position = {window_x, window_y};
  191. }
  192. this->windowed_size = {window_w, window_h};
  193. this->maximized = maximized;
  194. this->fullscreen = fullscreen;
  195. // Set hard window minimum size
  196. SDL_SetWindowMinimumSize(sdl_window, 160, 120);
  197. // Create OpenGL context
  198. debug::log::trace("Creating OpenGL {}.{} context...", config::opengl_version_major, config::opengl_version_minor);
  199. sdl_gl_context = SDL_GL_CreateContext(sdl_window);
  200. if (!sdl_gl_context)
  201. {
  202. debug::log::fatal("Failed to create OpenGL context: {}", SDL_GetError());
  203. throw std::runtime_error("Failed to create OpenGL context");
  204. }
  205. // Log version of created OpenGL context
  206. int opengl_context_version_major = -1;
  207. int opengl_context_version_minor = -1;
  208. SDL_GL_GetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, &opengl_context_version_major);
  209. SDL_GL_GetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, &opengl_context_version_minor);
  210. debug::log::info("Created OpenGL {}.{} context", opengl_context_version_major, opengl_context_version_minor);
  211. // Compare OpenGL context version with requested version
  212. if (opengl_context_version_major != config::opengl_version_major ||
  213. opengl_context_version_minor != config::opengl_version_minor)
  214. {
  215. debug::log::warning("Requested OpenGL {}.{} context but created OpenGL {}.{} context", config::opengl_version_major, config::opengl_version_minor, opengl_context_version_major, opengl_context_version_minor);
  216. }
  217. // Log format of OpenGL context default framebuffer
  218. int opengl_context_red_size = -1;
  219. int opengl_context_green_size = -1;
  220. int opengl_context_blue_size = -1;
  221. int opengl_context_alpha_size = -1;
  222. int opengl_context_depth_size = -1;
  223. int opengl_context_stencil_size = -1;
  224. SDL_GL_GetAttribute(SDL_GL_RED_SIZE, &opengl_context_red_size);
  225. SDL_GL_GetAttribute(SDL_GL_GREEN_SIZE, &opengl_context_green_size);
  226. SDL_GL_GetAttribute(SDL_GL_BLUE_SIZE, &opengl_context_blue_size);
  227. SDL_GL_GetAttribute(SDL_GL_ALPHA_SIZE, &opengl_context_alpha_size);
  228. SDL_GL_GetAttribute(SDL_GL_DEPTH_SIZE, &opengl_context_depth_size);
  229. SDL_GL_GetAttribute(SDL_GL_STENCIL_SIZE, &opengl_context_stencil_size);
  230. debug::log::info("OpenGL context format: R{}G{}B{}A{}D{}S{}", opengl_context_red_size, opengl_context_green_size, opengl_context_blue_size, opengl_context_alpha_size, opengl_context_depth_size, opengl_context_stencil_size);
  231. // Compare OpenGL context default framebuffer format with request format
  232. if (opengl_context_red_size < config::opengl_min_red_size ||
  233. opengl_context_green_size < config::opengl_min_green_size ||
  234. opengl_context_blue_size < config::opengl_min_blue_size ||
  235. opengl_context_alpha_size < config::opengl_min_alpha_size ||
  236. opengl_context_depth_size < config::opengl_min_depth_size ||
  237. opengl_context_stencil_size < config::opengl_min_stencil_size)
  238. {
  239. debug::log::warning
  240. (
  241. "OpenGL context format (R{}G{}B{}A{}D{}S{}) does not meet minimum requested format (R{}G{}B{}A{}D{}S{})",
  242. opengl_context_red_size, opengl_context_green_size, opengl_context_blue_size, opengl_context_alpha_size, opengl_context_depth_size, opengl_context_stencil_size,
  243. config::opengl_min_red_size, config::opengl_min_green_size, config::opengl_min_blue_size, config::opengl_min_alpha_size, config::opengl_min_depth_size, config::opengl_min_stencil_size
  244. );
  245. }
  246. // Load OpenGL functions via GLAD
  247. debug::log::trace("Loading OpenGL functions...");
  248. if (!gladLoadGLLoader((GLADloadproc)SDL_GL_GetProcAddress))
  249. {
  250. debug::log::fatal("Failed to load OpenGL functions", SDL_GetError());
  251. throw std::runtime_error("Failed to load OpenGL functions");
  252. }
  253. debug::log::trace("Loaded OpenGL functions");
  254. // Log OpenGL context information
  255. debug::log::info
  256. (
  257. "OpenGL vendor: {}; renderer: {}; version: {}; shading language version: {}",
  258. reinterpret_cast<const char*>(glGetString(GL_VENDOR)),
  259. reinterpret_cast<const char*>(glGetString(GL_RENDERER)),
  260. reinterpret_cast<const char*>(glGetString(GL_VERSION)),
  261. reinterpret_cast<const char*>(glGetString(GL_SHADING_LANGUAGE_VERSION))
  262. );
  263. // Clear window color
  264. glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
  265. glClear(GL_COLOR_BUFFER_BIT);
  266. swap_buffers();
  267. // Set v-sync mode
  268. set_v_sync(v_sync);
  269. // Update viewport size
  270. SDL_GL_GetDrawableSize(sdl_window, &viewport_size[0], &viewport_size[1]);
  271. // Init SDL joystick and controller subsystems
  272. debug::log::trace("Initializing SDL joystick and controller subsystems...");
  273. if (SDL_InitSubSystem(SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER) != 0)
  274. {
  275. debug::log::error("Failed to initialize SDL joystick and controller subsytems: {}", SDL_GetError());
  276. }
  277. else
  278. {
  279. debug::log::trace("Initialized SDL joystick and controller subsystems");
  280. }
  281. // Setup rasterizer
  282. rasterizer = new gl::rasterizer();
  283. rasterizer->context_resized(viewport_size[0], viewport_size[1]);
  284. // Register keyboard and mouse with input device manager
  285. device_manager.register_device(keyboard);
  286. device_manager.register_device(mouse);
  287. // Generate keyboard and mouse device connected events
  288. keyboard.connect();
  289. mouse.connect();
  290. // Connect gamepads
  291. process_events();
  292. }
  293. application::~application()
  294. {
  295. // Destroy the OpenGL context
  296. SDL_GL_DeleteContext(sdl_gl_context);
  297. // Destroy the SDL window
  298. SDL_DestroyWindow(sdl_window);
  299. // Shutdown SDL
  300. SDL_QuitSubSystem(SDL_INIT_VIDEO | SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER);
  301. SDL_Quit();
  302. }
  303. void application::close()
  304. {
  305. closed = true;
  306. }
  307. void application::set_title(const std::string& title)
  308. {
  309. SDL_SetWindowTitle(sdl_window, title.c_str());
  310. }
  311. void application::set_cursor_visible(bool visible)
  312. {
  313. SDL_ShowCursor((visible) ? SDL_ENABLE : SDL_DISABLE);
  314. cursor_visible = visible;
  315. }
  316. void application::set_relative_mouse_mode(bool enabled)
  317. {
  318. if (enabled)
  319. {
  320. SDL_GetMouseState(&mouse_position[0], &mouse_position[1]);
  321. SDL_ShowCursor(SDL_DISABLE);
  322. SDL_SetRelativeMouseMode(SDL_TRUE);
  323. }
  324. else
  325. {
  326. SDL_SetRelativeMouseMode(SDL_FALSE);
  327. SDL_WarpMouseInWindow(sdl_window, mouse_position[0], mouse_position[1]);
  328. if (cursor_visible)
  329. {
  330. SDL_ShowCursor(SDL_ENABLE);
  331. }
  332. }
  333. }
  334. void application::resize_window(int width, int height)
  335. {
  336. int x = (display_size[0] >> 1) - (width >> 1);
  337. int y = (display_size[1] >> 1) - (height >> 1);
  338. // Resize and center window
  339. SDL_SetWindowPosition(sdl_window, x, y);
  340. SDL_SetWindowSize(sdl_window, width, height);
  341. }
  342. void application::set_fullscreen(bool fullscreen)
  343. {
  344. if (this->fullscreen != fullscreen)
  345. {
  346. this->fullscreen = fullscreen;
  347. if (fullscreen)
  348. {
  349. SDL_SetWindowFullscreen(sdl_window, SDL_WINDOW_FULLSCREEN_DESKTOP);
  350. }
  351. else
  352. {
  353. SDL_SetWindowFullscreen(sdl_window, 0);
  354. }
  355. }
  356. }
  357. void application::set_v_sync(bool v_sync)
  358. {
  359. if (v_sync)
  360. {
  361. debug::log::trace("Enabling adaptive v-sync...");
  362. if (SDL_GL_SetSwapInterval(-1) != 0)
  363. {
  364. debug::log::error("Failed to enable adaptive v-sync: {}", SDL_GetError());
  365. debug::log::trace("Enabling synchronized v-sync...");
  366. if (SDL_GL_SetSwapInterval(1) != 0)
  367. {
  368. debug::log::error("Failed to enable synchronized v-sync: {}", SDL_GetError());
  369. }
  370. else
  371. {
  372. this->v_sync = v_sync;
  373. debug::log::debug("Enabled synchronized v-sync");
  374. }
  375. }
  376. else
  377. {
  378. this->v_sync = v_sync;
  379. debug::log::debug("Enabled adaptive v-sync");
  380. }
  381. }
  382. else
  383. {
  384. debug::log::trace("Disabling v-sync...");
  385. if (SDL_GL_SetSwapInterval(0) != 0)
  386. {
  387. debug::log::error("Failed to disable v-sync: {}", SDL_GetError());
  388. }
  389. else
  390. {
  391. this->v_sync = v_sync;
  392. debug::log::debug("Disabled v-sync");
  393. }
  394. }
  395. }
  396. void application::set_window_opacity(float opacity)
  397. {
  398. SDL_SetWindowOpacity(sdl_window, opacity);
  399. }
  400. void application::swap_buffers()
  401. {
  402. SDL_GL_SwapWindow(sdl_window);
  403. }
  404. void application::show_window()
  405. {
  406. SDL_ShowWindow(sdl_window);
  407. //SDL_GL_MakeCurrent(sdl_window, sdl_gl_context);
  408. }
  409. void application::hide_window()
  410. {
  411. SDL_HideWindow(sdl_window);
  412. }
  413. void application::add_game_controller_mappings(const void* mappings, std::size_t size)
  414. {
  415. debug::log::trace("Adding SDL game controller mappings...");
  416. int mapping_count = SDL_GameControllerAddMappingsFromRW(SDL_RWFromConstMem(mappings, static_cast<int>(size)), 0);
  417. if (mapping_count == -1)
  418. {
  419. debug::log::error("Failed to add SDL game controller mappings: {}", SDL_GetError());
  420. }
  421. else
  422. {
  423. debug::log::debug("Added {} SDL game controller mappings", mapping_count);
  424. }
  425. }
  426. void application::process_events()
  427. {
  428. // Active modifier keys
  429. std::uint16_t sdl_key_mod = KMOD_NONE;
  430. std::uint16_t modifier_keys = input::modifier_key::none;
  431. // Mouse motion event accumulators
  432. // bool mouse_motion = false;
  433. // std::int32_t mouse_x;
  434. // std::int32_t mouse_y;
  435. // std::int32_t mouse_dx = 0;
  436. // std::int32_t mouse_dy = 0;
  437. SDL_Event sdl_event;
  438. while (SDL_PollEvent(&sdl_event))
  439. {
  440. switch (sdl_event.type)
  441. {
  442. [[likely]] case SDL_MOUSEMOTION:
  443. {
  444. // More than one mouse motion event is often generated per frame, and may be a source of lag.
  445. // Mouse events can be accumulated here to prevent excessive function calls and allocations
  446. // mouse_motion = true;
  447. // mouse_x = sdl_event.motion.x;
  448. // mouse_y = sdl_event.motion.y;
  449. // mouse_dx += sdl_event.motion.xrel;
  450. // mouse_dy += sdl_event.motion.yrel;
  451. mouse.move({sdl_event.motion.x, sdl_event.motion.y}, {sdl_event.motion.xrel, sdl_event.motion.yrel});
  452. break;
  453. }
  454. case SDL_KEYDOWN:
  455. case SDL_KEYUP:
  456. {
  457. // Get scancode of key
  458. const input::scancode scancode = static_cast<input::scancode>(sdl_event.key.keysym.scancode);
  459. // Rebuild modifier keys bit mask
  460. if (sdl_event.key.keysym.mod != sdl_key_mod)
  461. {
  462. sdl_key_mod = sdl_event.key.keysym.mod;
  463. modifier_keys = input::modifier_key::none;
  464. if (sdl_key_mod & KMOD_LSHIFT)
  465. modifier_keys |= input::modifier_key::left_shift;
  466. if (sdl_key_mod & KMOD_RSHIFT)
  467. modifier_keys |= input::modifier_key::right_shift;
  468. if (sdl_key_mod & KMOD_LCTRL)
  469. modifier_keys |= input::modifier_key::left_ctrl;
  470. if (sdl_key_mod & KMOD_RCTRL)
  471. modifier_keys |= input::modifier_key::right_ctrl;
  472. if (sdl_key_mod & KMOD_LGUI)
  473. modifier_keys |= input::modifier_key::left_gui;
  474. if (sdl_key_mod & KMOD_RGUI)
  475. modifier_keys |= input::modifier_key::right_gui;
  476. if (sdl_key_mod & KMOD_NUM)
  477. modifier_keys |= input::modifier_key::num_lock;
  478. if (sdl_key_mod & KMOD_CAPS)
  479. modifier_keys |= input::modifier_key::caps_lock;
  480. if (sdl_key_mod & KMOD_SCROLL)
  481. modifier_keys |= input::modifier_key::scroll_lock;
  482. if (sdl_key_mod & KMOD_MODE)
  483. modifier_keys |= input::modifier_key::alt_gr;
  484. }
  485. // Determine if event was generated from a key repeat
  486. const bool repeat = sdl_event.key.repeat > 0;
  487. if (sdl_event.type == SDL_KEYDOWN)
  488. {
  489. keyboard.press(scancode, repeat, modifier_keys);
  490. }
  491. else
  492. {
  493. keyboard.release(scancode, repeat, modifier_keys);
  494. }
  495. break;
  496. }
  497. case SDL_MOUSEWHEEL:
  498. {
  499. const float flip = (sdl_event.wheel.direction == SDL_MOUSEWHEEL_FLIPPED) ? -1.0f : 1.0f;
  500. mouse.scroll({sdl_event.wheel.preciseX * flip, sdl_event.wheel.preciseY * flip});
  501. break;
  502. }
  503. case SDL_MOUSEBUTTONDOWN:
  504. {
  505. mouse.press(static_cast<input::mouse_button>(sdl_event.button.button));
  506. break;
  507. }
  508. case SDL_MOUSEBUTTONUP:
  509. {
  510. mouse.release(static_cast<input::mouse_button>(sdl_event.button.button));
  511. break;
  512. }
  513. [[likely]] case SDL_CONTROLLERAXISMOTION:
  514. {
  515. if (sdl_event.caxis.axis != SDL_CONTROLLER_AXIS_INVALID)
  516. {
  517. if (auto it = gamepad_map.find(sdl_event.cdevice.which); it != gamepad_map.end())
  518. {
  519. // Map axis position onto `[-1, 1]`.
  520. const float position = math::map
  521. (
  522. static_cast<float>(sdl_event.caxis.value),
  523. static_cast<float>(std::numeric_limits<decltype(sdl_event.caxis.value)>::min()),
  524. static_cast<float>(std::numeric_limits<decltype(sdl_event.caxis.value)>::max()),
  525. -1.0f,
  526. 1.0f
  527. );
  528. // Generate gamepad axis moved event
  529. it->second->move(static_cast<input::gamepad_axis>(sdl_event.caxis.axis), position);
  530. }
  531. }
  532. break;
  533. }
  534. case SDL_CONTROLLERBUTTONDOWN:
  535. {
  536. if (sdl_event.cbutton.button != SDL_CONTROLLER_BUTTON_INVALID)
  537. {
  538. if (auto it = gamepad_map.find(sdl_event.cdevice.which); it != gamepad_map.end())
  539. {
  540. it->second->press(static_cast<input::gamepad_button>(sdl_event.cbutton.button));
  541. }
  542. }
  543. break;
  544. }
  545. case SDL_CONTROLLERBUTTONUP:
  546. {
  547. if (sdl_event.cbutton.button != SDL_CONTROLLER_BUTTON_INVALID)
  548. {
  549. if (auto it = gamepad_map.find(sdl_event.cdevice.which); it != gamepad_map.end())
  550. {
  551. it->second->release(static_cast<input::gamepad_button>(sdl_event.cbutton.button));
  552. }
  553. }
  554. break;
  555. }
  556. case SDL_WINDOWEVENT:
  557. {
  558. switch (sdl_event.window.event)
  559. {
  560. case SDL_WINDOWEVENT_SIZE_CHANGED:
  561. {
  562. // Query SDL window parameters
  563. SDL_Window* sdl_window = SDL_GetWindowFromID(sdl_event.window.windowID);
  564. const auto sdl_window_flags = SDL_GetWindowFlags(sdl_window);
  565. int sdl_window_drawable_w = 0;
  566. int sdl_window_drawable_h = 0;
  567. SDL_GL_GetDrawableSize(sdl_window, &sdl_window_drawable_w, &sdl_window_drawable_h);
  568. // Build window resized event
  569. input::event::window_resized event;
  570. event.window = nullptr;
  571. event.size.x() = static_cast<std::int32_t>(sdl_event.window.data1);
  572. event.size.y() = static_cast<std::int32_t>(sdl_event.window.data2);
  573. event.maximized = sdl_window_flags & SDL_WINDOW_MAXIMIZED;
  574. event.fullscreen = sdl_window_flags & SDL_WINDOW_FULLSCREEN;
  575. event.viewport_size.x() = static_cast<std::int32_t>(sdl_window_drawable_w);
  576. event.viewport_size.y() = static_cast<std::int32_t>(sdl_window_drawable_h);
  577. // Update windowed size
  578. if (!event.maximized && !event.fullscreen)
  579. {
  580. windowed_size = event.size;
  581. }
  582. // Update GL context size
  583. rasterizer->context_resized(event.viewport_size.x(), event.viewport_size.y());
  584. // Publish window resized event
  585. window_resized_publisher.publish(event);
  586. break;
  587. }
  588. case SDL_WINDOWEVENT_MOVED:
  589. {
  590. // Query SDL window parameters
  591. SDL_Window* sdl_window = SDL_GetWindowFromID(sdl_event.window.windowID);
  592. const auto sdl_window_flags = SDL_GetWindowFlags(sdl_window);
  593. // Build window moved event
  594. input::event::window_moved event;
  595. event.window = nullptr;
  596. event.position.x() = static_cast<std::int32_t>(sdl_event.window.data1);
  597. event.position.y() = static_cast<std::int32_t>(sdl_event.window.data2);
  598. event.maximized = sdl_window_flags & SDL_WINDOW_MAXIMIZED;
  599. event.fullscreen = sdl_window_flags & SDL_WINDOW_FULLSCREEN;
  600. // Update windowed position
  601. if (!event.maximized && !event.fullscreen)
  602. {
  603. windowed_position = event.position;
  604. }
  605. // Publish window moved event
  606. window_moved_publisher.publish(event);
  607. break;
  608. }
  609. case SDL_WINDOWEVENT_FOCUS_GAINED:
  610. // Build and publish window focused gained event
  611. window_focus_changed_publisher.publish({nullptr, true});
  612. break;
  613. case SDL_WINDOWEVENT_FOCUS_LOST:
  614. // Build and publish window focused lost event
  615. window_focus_changed_publisher.publish({nullptr, false});
  616. break;
  617. case SDL_WINDOWEVENT_MAXIMIZED:
  618. // Update window maximized
  619. maximized = true;
  620. // Build and publish window maximized event
  621. window_maximized_publisher.publish({nullptr});
  622. break;
  623. case SDL_WINDOWEVENT_RESTORED:
  624. // Update window maximized
  625. maximized = false;
  626. // Build and publish window restored event
  627. window_restored_publisher.publish({nullptr});
  628. break;
  629. case SDL_WINDOWEVENT_MINIMIZED:
  630. // Build and publish window minimized event
  631. window_minimized_publisher.publish({nullptr});
  632. break;
  633. [[unlikely]] case SDL_WINDOWEVENT_CLOSE:
  634. // Build and publish window closed event
  635. window_closed_publisher.publish({nullptr});
  636. break;
  637. default:
  638. break;
  639. }
  640. break;
  641. }
  642. [[unlikely]] case SDL_CONTROLLERDEVICEADDED:
  643. {
  644. if (SDL_IsGameController(sdl_event.cdevice.which))
  645. {
  646. SDL_GameController* sdl_controller = SDL_GameControllerOpen(sdl_event.cdevice.which);
  647. const char* controller_name = SDL_GameControllerNameForIndex(sdl_event.cdevice.which);
  648. if (sdl_controller)
  649. {
  650. if (auto it = gamepad_map.find(sdl_event.cdevice.which); it != gamepad_map.end())
  651. {
  652. // Gamepad reconnected
  653. debug::log::info("Reconnected gamepad \"{}\"", controller_name);
  654. it->second->connect();
  655. }
  656. else
  657. {
  658. // Get gamepad GUID
  659. SDL_Joystick* sdl_joystick = SDL_GameControllerGetJoystick(sdl_controller);
  660. SDL_JoystickGUID sdl_guid = SDL_JoystickGetGUID(sdl_joystick);
  661. // Copy into UUID struct
  662. ::uuid gamepad_uuid;
  663. std::memcpy(gamepad_uuid.data.data(), sdl_guid.data, gamepad_uuid.data.size());
  664. debug::log::info("Connected gamepad \"{}\" with UUID {}", controller_name, gamepad_uuid.string());
  665. // Create new gamepad
  666. input::gamepad* gamepad = new input::gamepad();
  667. gamepad->set_uuid(gamepad_uuid);
  668. // Add gamepad to gamepad map
  669. gamepad_map[sdl_event.cdevice.which] = gamepad;
  670. // Register gamepad with device manager
  671. device_manager.register_device(*gamepad);
  672. // Generate gamepad connected event
  673. gamepad->connect();
  674. }
  675. }
  676. else
  677. {
  678. debug::log::error("Failed to connected gamepad \"{}\": {}", controller_name, SDL_GetError());
  679. }
  680. }
  681. break;
  682. }
  683. [[unlikely]] case SDL_CONTROLLERDEVICEREMOVED:
  684. {
  685. SDL_GameController* sdl_controller = SDL_GameControllerFromInstanceID(sdl_event.cdevice.which);
  686. if (sdl_controller)
  687. {
  688. const char* controller_name = SDL_GameControllerNameForIndex(sdl_event.cdevice.which);
  689. SDL_GameControllerClose(sdl_controller);
  690. if (auto it = gamepad_map.find(sdl_event.cdevice.which); it != gamepad_map.end())
  691. {
  692. it->second->disconnect();
  693. }
  694. debug::log::info("Disconnected gamepad \"{}\"", controller_name);
  695. }
  696. break;
  697. }
  698. [[unlikely]] case SDL_QUIT:
  699. {
  700. debug::log::info("Quit requested");
  701. close();
  702. break;
  703. }
  704. default:
  705. break;
  706. }
  707. }
  708. // Process accumulated mouse motion events
  709. // if (mouse_motion)
  710. // {
  711. // mouse.move(mouse_x, mouse_y, mouse_dx, mouse_dy);
  712. // }
  713. }