Browse Source

Split application class into a window manager, a window, and an input manager

master
C. J. Howard 1 year ago
parent
commit
ffda59dea6
41 changed files with 2128 additions and 1447 deletions
  1. +0
    -1
      CMakeLists.txt
  2. +124
    -0
      src/app/display.hpp
  3. +130
    -0
      src/app/input-manager.cpp
  4. +54
    -28
      src/app/input-manager.hpp
  5. +310
    -0
      src/app/sdl/sdl-input-manager.cpp
  6. +55
    -0
      src/app/sdl/sdl-input-manager.hpp
  7. +324
    -0
      src/app/sdl/sdl-window-manager.cpp
  8. +74
    -0
      src/app/sdl/sdl-window-manager.hpp
  9. +294
    -0
      src/app/sdl/sdl-window.cpp
  10. +76
    -0
      src/app/sdl/sdl-window.hpp
  11. +103
    -0
      src/app/window-events.hpp
  12. +30
    -0
      src/app/window-manager.cpp
  13. +84
    -0
      src/app/window-manager.hpp
  14. +26
    -0
      src/app/window.cpp
  15. +252
    -0
      src/app/window.hpp
  16. +0
    -818
      src/application.cpp
  17. +0
    -295
      src/application.hpp
  18. +15
    -10
      src/game/context.hpp
  19. +3
    -4
      src/game/controls.cpp
  20. +2
    -2
      src/game/fonts.cpp
  21. +5
    -5
      src/game/graphics.cpp
  22. +2
    -3
      src/game/menu.cpp
  23. +139
    -118
      src/game/state/boot.cpp
  24. +2
    -1
      src/game/state/boot.hpp
  25. +0
    -1
      src/game/state/controls-menu.cpp
  26. +1
    -2
      src/game/state/credits.cpp
  27. +0
    -1
      src/game/state/extras-menu.cpp
  28. +0
    -1
      src/game/state/gamepad-config-menu.cpp
  29. +6
    -7
      src/game/state/graphics-menu.cpp
  30. +0
    -1
      src/game/state/keyboard-config-menu.cpp
  31. +0
    -1
      src/game/state/language-menu.cpp
  32. +4
    -5
      src/game/state/main-menu.cpp
  33. +2
    -4
      src/game/state/nest-selection.cpp
  34. +3
    -5
      src/game/state/nuptial-flight.cpp
  35. +0
    -1
      src/game/state/options-menu.cpp
  36. +1
    -2
      src/game/state/pause-menu.cpp
  37. +0
    -1
      src/game/state/sound-menu.cpp
  38. +4
    -5
      src/game/state/splash.cpp
  39. +0
    -1
      src/game/world.cpp
  40. +0
    -121
      src/input/device-manager.cpp
  41. +3
    -3
      src/utility/state-machine.hpp

+ 0
- 1
CMakeLists.txt View File

@ -1,6 +1,5 @@
cmake_minimum_required(VERSION 3.25)
option(VERSION_STRING "Project version string" "0.0.0")
project(antkeeper VERSION ${VERSION_STRING} LANGUAGES CXX)

+ 124
- 0
src/app/display.hpp View File

@ -0,0 +1,124 @@
/*
* Copyright (C) 2023 Christopher J. Howard
*
* This file is part of Antkeeper source code.
*
* Antkeeper source code is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Antkeeper source code is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Antkeeper source code. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef ANTKEEPER_APP_DISPLAY_HPP
#define ANTKEEPER_APP_DISPLAY_HPP
#include "math/vector.hpp"
#include <string>
namespace app {
/**
* Virtual display.
*/
class display
{
public:
/**
* Sets the index of the display.
*
* @param index Index of the display.
*/
inline void set_index(int index) noexcept
{
this->index = index;
}
/**
* Sets the name of the display.
*
* @param name Name of the display.
*/
inline void set_name(const std::string& name) noexcept
{
this->name = name;
}
/**
* Sets the size of the display.
*
* @param size Size of the display, in display units.
*/
inline void set_size(const math::vector<int, 2>& size) noexcept
{
this->size = size;
}
/**
* Sets the refresh rate of the display.
*
* @param rate Refresh rate, in Hz.
*/
inline void set_refresh_rate(int rate) noexcept
{
this->refresh_rate = rate;
}
/**
* Sets the DPI of the display.
*
* @param dpi DPI.
*/
inline void set_dpi(float dpi) noexcept
{
this->dpi = dpi;
}
/// Returns the index of the display.
[[nodiscard]] inline int get_index() const noexcept
{
return index;
}
/// Returns the name of the display.
[[nodiscard]] inline const std::string& get_name() const noexcept
{
return name;
}
/// Returns the size of the display, in display units.
[[nodiscard]] inline const math::vector<int, 2>& get_size() const noexcept
{
return size;
}
/// Returns the refresh rate of the display, in Hz.
[[nodiscard]] inline int get_refresh_rate() const noexcept
{
return refresh_rate;
}
/// Returns the DPI of the display.
[[nodiscard]] inline float get_dpi() const noexcept
{
return dpi;
}
private:
int index;
std::string name;
math::vector<int, 2> size;
int refresh_rate;
float dpi;
};
} // namespace app
#endif // ANTKEEPER_APP_DISPLAY_HPP

+ 130
- 0
src/app/input-manager.cpp View File

@ -0,0 +1,130 @@
/*
* Copyright (C) 2023 Christopher J. Howard
*
* This file is part of Antkeeper source code.
*
* Antkeeper source code is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Antkeeper source code is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Antkeeper source code. If not, see <http://www.gnu.org/licenses/>.
*/
#include "app/input-manager.hpp"
#include "app/sdl/sdl-input-manager.hpp"
namespace app {
input_manager* input_manager::instance()
{
return new sdl_input_manager();
}
void input_manager::register_device(input::device& device)
{
switch (device.get_device_type())
{
case input::device_type::gamepad:
register_gamepad(static_cast<input::gamepad&>(device));
break;
case input::device_type::keyboard:
register_keyboard(static_cast<input::keyboard&>(device));
break;
case input::device_type::mouse:
register_mouse(static_cast<input::mouse&>(device));
break;
default:
//std::unreachable();
break;
}
}
void input_manager::register_gamepad(input::gamepad& device)
{
// Connect gamepad event signals to the event queue
subscriptions.emplace(&device, device.get_connected_channel().subscribe(event_queue));
subscriptions.emplace(&device, device.get_disconnected_channel().subscribe(event_queue));
subscriptions.emplace(&device, device.get_axis_moved_channel().subscribe(event_queue));
subscriptions.emplace(&device, device.get_button_pressed_channel().subscribe(event_queue));
subscriptions.emplace(&device, device.get_button_released_channel().subscribe(event_queue));
// Add gamepad to list of gamepads
gamepads.emplace(&device);
}
void input_manager::register_keyboard(input::keyboard& device)
{
// Connect keyboard event signals to the event queue
subscriptions.emplace(&device, device.get_connected_channel().subscribe(event_queue));
subscriptions.emplace(&device, device.get_disconnected_channel().subscribe(event_queue));
subscriptions.emplace(&device, device.get_key_pressed_channel().subscribe(event_queue));
subscriptions.emplace(&device, device.get_key_released_channel().subscribe(event_queue));
// Add keyboard to list of keyboards
keyboards.emplace(&device);
}
void input_manager::register_mouse(input::mouse& device)
{
// Connect mouse event signals to the event queue
subscriptions.emplace(&device, device.get_connected_channel().subscribe(event_queue));
subscriptions.emplace(&device, device.get_disconnected_channel().subscribe(event_queue));
subscriptions.emplace(&device, device.get_button_pressed_channel().subscribe(event_queue));
subscriptions.emplace(&device, device.get_button_released_channel().subscribe(event_queue));
subscriptions.emplace(&device, device.get_moved_channel().subscribe(event_queue));
subscriptions.emplace(&device, device.get_scrolled_channel().subscribe(event_queue));
// Add mouse to list of mice
mice.emplace(&device);
}
void input_manager::unregister_device(input::device& device)
{
subscriptions.erase(&device);
switch (device.get_device_type())
{
case input::device_type::gamepad:
unregister_gamepad(static_cast<input::gamepad&>(device));
break;
case input::device_type::keyboard:
unregister_keyboard(static_cast<input::keyboard&>(device));
break;
case input::device_type::mouse:
unregister_mouse(static_cast<input::mouse&>(device));
break;
default:
//std::unreachable();
break;
}
}
void input_manager::unregister_gamepad(input::gamepad& gamepad)
{
gamepads.erase(&gamepad);
}
void input_manager::unregister_keyboard(input::keyboard& keyboard)
{
keyboards.erase(&keyboard);
}
void input_manager::unregister_mouse(input::mouse& mouse)
{
mice.erase(&mouse);
}
} // namespace app

src/input/device-manager.hpp → src/app/input-manager.hpp View File

@ -17,8 +17,8 @@
* along with Antkeeper source code. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef ANTKEEPER_INPUT_DEVICE_MANAGER_HPP
#define ANTKEEPER_INPUT_DEVICE_MANAGER_HPP
#ifndef ANTKEEPER_APP_INPUT_MANAGER_HPP
#define ANTKEEPER_APP_INPUT_MANAGER_HPP
#include "input/device.hpp"
#include "input/gamepad.hpp"
@ -29,27 +29,34 @@
#include <memory>
#include <unordered_set>
namespace input {
namespace app {
/**
* Manages virtual input devices.
*/
class device_manager
class input_manager
{
public:
/**
* Registers an input device.
*
* @param device Input device to register.
* Allocates and returns an input manager.
*/
void register_device(device& device);
static input_manager* instance();
/// Destructs an input manager.
virtual ~input_manager() = default;
/**
* Unregisters an input device.
*
* @param device Input device to unregister.
* Processes input events.
*/
void unregister_device(device& device);
virtual void update() = 0;
/**
* Returns the event queue associated with registered input devices.
*/
[[nodiscard]] inline const ::event::queue& get_event_queue() const noexcept
{
return event_queue;
}
/**
* Returns the event queue associated with registered input devices.
@ -60,38 +67,57 @@ public:
}
/// Returns the set of registered gamepads.
[[nodiscard]] inline const std::unordered_set<gamepad*>& get_gamepads() noexcept
[[nodiscard]] inline const std::unordered_set<input::gamepad*>& get_gamepads() noexcept
{
return gamepads;
}
/// Returns the set of registered keyboards.
[[nodiscard]] inline const std::unordered_set<keyboard*>& get_keyboards() noexcept
[[nodiscard]] inline const std::unordered_set<input::keyboard*>& get_keyboards() noexcept
{
return keyboards;
}
/// Returns the set of registered mice.
[[nodiscard]] inline const std::unordered_set<mouse*>& get_mice() noexcept
[[nodiscard]] inline const std::unordered_set<input::mouse*>& get_mice() noexcept
{
return mice;
}
private:
void register_gamepad(gamepad& gamepad);
void register_keyboard(keyboard& keyboard);
void register_mouse(mouse& mouse);
void unregister_gamepad(gamepad& gamepad);
void unregister_keyboard(keyboard& keyboard);
void unregister_mouse(mouse& mouse);
protected:
/**
* Registers an input device.
*
* @param device Input device to register.
*/
/// @{
void register_device(input::device& device);
void register_gamepad(input::gamepad& device);
void register_keyboard(input::keyboard& device);
void register_mouse(input::mouse& device);
/// @}
/**
* Unregisters an input device.
*
* @param device Input device to unregister.
*/
/// @{
void unregister_device(input::device& device);
void unregister_gamepad(input::gamepad& device);
void unregister_keyboard(input::keyboard& device);
void unregister_mouse(input::mouse& device);
/// @}
::event::queue event_queue;
std::multimap<device*, std::shared_ptr<::event::subscription>> subscriptions;
std::unordered_set<gamepad*> gamepads;
std::unordered_set<keyboard*> keyboards;
std::unordered_set<mouse*> mice;
private:
std::multimap<input::device*, std::shared_ptr<::event::subscription>> subscriptions;
std::unordered_set<input::gamepad*> gamepads;
std::unordered_set<input::keyboard*> keyboards;
std::unordered_set<input::mouse*> mice;
};
} // namespace input
} // namespace app
#endif // ANTKEEPER_INPUT_DEVICE_MANAGER_HPP
#endif // ANTKEEPER_APP_INPUT_MANAGER_HPP

+ 310
- 0
src/app/sdl/sdl-input-manager.cpp View File

@ -0,0 +1,310 @@
/*
* Copyright (C) 2023 Christopher J. Howard
*
* This file is part of Antkeeper source code.
*
* Antkeeper source code is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Antkeeper source code is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Antkeeper source code. If not, see <http://www.gnu.org/licenses/>.
*/
#include "app/sdl/sdl-input-manager.hpp"
#include "debug/log.hpp"
#include "math/map.hpp"
#include <SDL2/SDL.h>
#include <stdexcept>
namespace app {
sdl_input_manager::sdl_input_manager()
{
// Init SDL joystick and controller subsystems
debug::log::trace("Initializing SDL joystick and controller subsystems...");
if (SDL_InitSubSystem(SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER) != 0)
{
debug::log::error("Failed to initialize SDL joystick and controller subsytems: {}", SDL_GetError());
throw std::runtime_error("Failed to initialize SDL joystick and controller subsytems");
}
else
{
debug::log::trace("Initialized SDL joystick and controller subsystems");
}
// Register keyboard and mouse
register_keyboard(keyboard);
register_mouse(mouse);
// Generate keyboard and mouse device connected events
keyboard.connect();
mouse.connect();
}
sdl_input_manager::~sdl_input_manager()
{
// Quit SDL joystick and controller subsystems
debug::log::trace("Quitting SDL joystick and controller subsystems...");
SDL_QuitSubSystem(SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER);
debug::log::trace("Quit SDL joystick and controller subsystems...");
}
void sdl_input_manager::update()
{
// Active modifier keys
std::uint16_t sdl_key_mod = KMOD_NONE;
std::uint16_t modifier_keys = input::modifier_key::none;
// Gather SDL events from event queue
SDL_PumpEvents();
// Handle OS events
for (;;)
{
// Get next display or window event
SDL_Event event;
int status = SDL_PeepEvents(&event, 1, SDL_GETEVENT, SDL_FIRSTEVENT, SDL_LOCALECHANGED);
if (!status)
{
break;
}
else if (status < 0)
{
debug::log::error("Failed to peep SDL events: {}", SDL_GetError());
throw std::runtime_error("Failed to peep SDL events");
}
if (event.type == SDL_QUIT)
{
//...
}
}
// Handle keyboard, mouse, and gamepad events
for (;;)
{
// Get next display or window event
SDL_Event event;
int status = SDL_PeepEvents(&event, 1, SDL_GETEVENT, SDL_KEYDOWN, SDL_LASTEVENT);
if (!status)
{
break;
}
else if (status < 0)
{
debug::log::error("Failed to peep SDL events: {}", SDL_GetError());
throw std::runtime_error("Failed to peep SDL events");
}
switch (event.type)
{
[[likely]] case SDL_MOUSEMOTION:
{
mouse.move({event.motion.x, event.motion.y}, {event.motion.xrel, event.motion.yrel});
break;
}
case SDL_KEYDOWN:
case SDL_KEYUP:
{
// Get scancode of key
const input::scancode scancode = static_cast<input::scancode>(event.key.keysym.scancode);
// Rebuild modifier keys bit mask
if (event.key.keysym.mod != sdl_key_mod)
{
sdl_key_mod = event.key.keysym.mod;
modifier_keys = input::modifier_key::none;
if (sdl_key_mod & KMOD_LSHIFT)
modifier_keys |= input::modifier_key::left_shift;
if (sdl_key_mod & KMOD_RSHIFT)
modifier_keys |= input::modifier_key::right_shift;
if (sdl_key_mod & KMOD_LCTRL)
modifier_keys |= input::modifier_key::left_ctrl;
if (sdl_key_mod & KMOD_RCTRL)
modifier_keys |= input::modifier_key::right_ctrl;
if (sdl_key_mod & KMOD_LGUI)
modifier_keys |= input::modifier_key::left_gui;
if (sdl_key_mod & KMOD_RGUI)
modifier_keys |= input::modifier_key::right_gui;
if (sdl_key_mod & KMOD_NUM)
modifier_keys |= input::modifier_key::num_lock;
if (sdl_key_mod & KMOD_CAPS)
modifier_keys |= input::modifier_key::caps_lock;
if (sdl_key_mod & KMOD_SCROLL)
modifier_keys |= input::modifier_key::scroll_lock;
if (sdl_key_mod & KMOD_MODE)
modifier_keys |= input::modifier_key::alt_gr;
}
// Determine if event was generated from a key repeat
const bool repeat = event.key.repeat > 0;
if (event.type == SDL_KEYDOWN)
{
keyboard.press(scancode, repeat, modifier_keys);
}
else
{
keyboard.release(scancode, repeat, modifier_keys);
}
break;
}
case SDL_MOUSEWHEEL:
{
const float flip = (event.wheel.direction == SDL_MOUSEWHEEL_FLIPPED) ? -1.0f : 1.0f;
mouse.scroll({event.wheel.preciseX * flip, event.wheel.preciseY * flip});
break;
}
case SDL_MOUSEBUTTONDOWN:
{
mouse.press(static_cast<input::mouse_button>(event.button.button));
break;
}
case SDL_MOUSEBUTTONUP:
{
mouse.release(static_cast<input::mouse_button>(event.button.button));
break;
}
[[likely]] case SDL_CONTROLLERAXISMOTION:
{
if (event.caxis.axis != SDL_CONTROLLER_AXIS_INVALID)
{
if (auto it = gamepad_map.find(event.cdevice.which); it != gamepad_map.end())
{
// Map axis position onto `[-1, 1]`.
const float position = math::map
(
static_cast<float>(event.caxis.value),
static_cast<float>(std::numeric_limits<decltype(event.caxis.value)>::min()),
static_cast<float>(std::numeric_limits<decltype(event.caxis.value)>::max()),
-1.0f,
1.0f
);
// Generate gamepad axis moved event
it->second->move(static_cast<input::gamepad_axis>(event.caxis.axis), position);
}
}
break;
}
case SDL_CONTROLLERBUTTONDOWN:
{
if (event.cbutton.button != SDL_CONTROLLER_BUTTON_INVALID)
{
if (auto it = gamepad_map.find(event.cdevice.which); it != gamepad_map.end())
{
it->second->press(static_cast<input::gamepad_button>(event.cbutton.button));
}
}
break;
}
case SDL_CONTROLLERBUTTONUP:
{
if (event.cbutton.button != SDL_CONTROLLER_BUTTON_INVALID)
{
if (auto it = gamepad_map.find(event.cdevice.which); it != gamepad_map.end())
{
it->second->release(static_cast<input::gamepad_button>(event.cbutton.button));
}
}
break;
}
[[unlikely]] case SDL_CONTROLLERDEVICEADDED:
{
if (SDL_IsGameController(event.cdevice.which))
{
SDL_GameController* sdl_controller = SDL_GameControllerOpen(event.cdevice.which);
const char* controller_name = SDL_GameControllerNameForIndex(event.cdevice.which);
if (sdl_controller)
{
if (auto it = gamepad_map.find(event.cdevice.which); it != gamepad_map.end())
{
// Gamepad reconnected
debug::log::info("Reconnected gamepad \"{}\"", controller_name);
it->second->connect();
}
else
{
// Get gamepad GUID
SDL_Joystick* sdl_joystick = SDL_GameControllerGetJoystick(sdl_controller);
SDL_JoystickGUID sdl_guid = SDL_JoystickGetGUID(sdl_joystick);
// Copy into UUID struct
::uuid gamepad_uuid;
std::memcpy(gamepad_uuid.data.data(), sdl_guid.data, gamepad_uuid.data.size());
debug::log::info("Connected gamepad \"{}\" with UUID {}", controller_name, gamepad_uuid.string());
// Create new gamepad
input::gamepad* gamepad = new input::gamepad();
gamepad->set_uuid(gamepad_uuid);
// Add gamepad to gamepad map
gamepad_map[event.cdevice.which] = gamepad;
// Register gamepad
register_device(*gamepad);
// Generate gamepad connected event
gamepad->connect();
}
}
else
{
debug::log::error("Failed to connected gamepad \"{}\": {}", controller_name, SDL_GetError());
}
}
break;
}
[[unlikely]] case SDL_CONTROLLERDEVICEREMOVED:
{
SDL_GameController* sdl_controller = SDL_GameControllerFromInstanceID(event.cdevice.which);
if (sdl_controller)
{
const char* controller_name = SDL_GameControllerNameForIndex(event.cdevice.which);
SDL_GameControllerClose(sdl_controller);
if (auto it = gamepad_map.find(event.cdevice.which); it != gamepad_map.end())
{
it->second->disconnect();
}
debug::log::info("Disconnected gamepad \"{}\"", controller_name);
}
break;
}
default:
break;
}
}
// Flush event queue
this->event_queue.flush();
}
} // namespace app

+ 55
- 0
src/app/sdl/sdl-input-manager.hpp View File

@ -0,0 +1,55 @@
/*
* Copyright (C) 2023 Christopher J. Howard
*
* This file is part of Antkeeper source code.
*
* Antkeeper source code is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Antkeeper source code is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Antkeeper source code. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef ANTKEEPER_APP_SDL_INPUT_MANAGER_HPP
#define ANTKEEPER_APP_SDL_INPUT_MANAGER_HPP
#include "app/input-manager.hpp"
namespace app {
class sdl_window;
/**
*
*/
class sdl_input_manager: public input_manager
{
public:
/**
* Constructs an SDL input manager.
*/
sdl_input_manager();
/**
* Destructs an SDL input manager.
*/
virtual ~sdl_input_manager();
virtual void update();
private:
input::keyboard keyboard;
input::mouse mouse;
std::unordered_map<int, input::gamepad*> gamepad_map;
};
} // namespace app
#endif // ANTKEEPER_APP_SDL_INPUT_MANAGER_HPP

+ 324
- 0
src/app/sdl/sdl-window-manager.cpp View File

@ -0,0 +1,324 @@
/*
* Copyright (C) 2023 Christopher J. Howard
*
* This file is part of Antkeeper source code.
*
* Antkeeper source code is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Antkeeper source code is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Antkeeper source code. If not, see <http://www.gnu.org/licenses/>.
*/
#include "app/sdl/sdl-window-manager.hpp"
#include "app/sdl/sdl-window.hpp"
#include "debug/log.hpp"
#include "config.hpp"
#include <stdexcept>
namespace app {
sdl_window_manager::sdl_window_manager()
{
// Init SDL events and video subsystems
debug::log::trace("Initializing SDL events and video subsystems...");
if (SDL_InitSubSystem(SDL_INIT_EVENTS | SDL_INIT_VIDEO) != 0)
{
debug::log::fatal("Failed to initialize SDL events and video subsystems: {}", SDL_GetError());
throw std::runtime_error("Failed to initialize SDL events and video subsystems");
}
debug::log::trace("Initialized SDL events and video subsystems");
// Query displays
const int display_count = SDL_GetNumVideoDisplays();
if (display_count < 1)
{
debug::log::warning("No displays detected: {}", SDL_GetError());
}
else
{
debug::log::info("Display count: {}", display_count);
displays.resize(display_count);
for (int i = 0; i < display_count; ++i)
{
// Query display mode
SDL_DisplayMode display_mode;
if (SDL_GetDesktopDisplayMode(i, &display_mode) != 0)
{
debug::log::error("Failed to get mode of display {}: {}", i, SDL_GetError());
SDL_ClearError();
continue;
}
// Query display name
const char* display_name = SDL_GetDisplayName(i);
if (!display_name)
{
debug::log::warning("Failed to get name of display {}: {}", i, SDL_GetError());
SDL_ClearError();
display_name = "";
}
// Query display DPI
float display_dpi;
if (SDL_GetDisplayDPI(i, &display_dpi, nullptr, nullptr) != 0)
{
const float default_dpi = 96.0f;
debug::log::warning("Failed to get DPI of display {}: {}; Defaulting to {} DPI", i, SDL_GetError(), default_dpi);
SDL_ClearError();
}
// Update display properties
display& display = displays[i];
display.set_index(i);
display.set_name(display_name);
display.set_size({display_mode.w, display_mode.h});
display.set_refresh_rate(display_mode.refresh_rate);
display.set_dpi(display_dpi);
// Log display information
debug::log::info("Display {} name: \"{}\"; resolution: {}x{}; refresh rate: {}Hz; DPI: {}", i, display_name, display_mode.w, display_mode.h, display_mode.refresh_rate, display_dpi);
}
}
// Load OpenGL library
debug::log::trace("Loading OpenGL library...");
if (SDL_GL_LoadLibrary(nullptr) != 0)
{
debug::log::fatal("Failed to load OpenGL library: {}", SDL_GetError());
throw std::runtime_error("Failed to load OpenGL library");
}
debug::log::trace("Loaded OpenGL library");
// Set OpenGL-related window creation hints
SDL_GL_SetAttribute(SDL_GL_ACCELERATED_VISUAL, 1);
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, config::opengl_version_major);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, config::opengl_version_minor);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_FORWARD_COMPATIBLE_FLAG);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
SDL_GL_SetAttribute(SDL_GL_RED_SIZE, config::opengl_min_red_size);
SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, config::opengl_min_green_size);
SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, config::opengl_min_blue_size);
SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, config::opengl_min_alpha_size);
SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, config::opengl_min_depth_size);
SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, config::opengl_min_stencil_size);
}
sdl_window_manager::~sdl_window_manager()
{
// Quit SDL video subsystem
debug::log::trace("Quitting SDL video subsystem...");
SDL_QuitSubSystem(SDL_INIT_VIDEO);
debug::log::trace("Quit SDL video subsystem");
}
window* sdl_window_manager::create_window
(
const std::string& title,
const math::vector<int, 2>& windowed_position,
const math::vector<int, 2>& windowed_size,
bool maximized,
bool fullscreen,
bool v_sync
)
{
// Create new window
app::sdl_window* window = new app::sdl_window
(
title,
windowed_position,
windowed_size,
maximized,
fullscreen,
v_sync
);
// Map internal SDL window to window
window_map[window->internal_window] = window;
return window;
}
void sdl_window_manager::update()
{
// Gather SDL events from event queue
SDL_PumpEvents();
for (;;)
{
// Get next window or display event
SDL_Event event;
int status = SDL_PeepEvents(&event, 1, SDL_GETEVENT, SDL_DISPLAYEVENT, SDL_SYSWMEVENT);
if (!status)
{
break;
}
else if (status < 0)
{
debug::log::error("Failed to peep SDL events: {}", SDL_GetError());
throw std::runtime_error("Failed to peep SDL events");
}
// Handle event
if (event.type == SDL_WINDOWEVENT)
{
switch (event.window.event)
{
case SDL_WINDOWEVENT_SIZE_CHANGED:
{
// Get window
SDL_Window* internal_window = SDL_GetWindowFromID(event.window.windowID);
app::sdl_window* window = get_window(internal_window);
// Update window state
window->size = {event.window.data1, event.window.data2};
const auto window_flags = SDL_GetWindowFlags(internal_window);
if (!(window_flags & SDL_WINDOW_MAXIMIZED) && !(window_flags & SDL_WINDOW_FULLSCREEN))
{
window->windowed_size = window->size;
}
SDL_GL_GetDrawableSize(internal_window, &window->viewport_size.x(), &window->viewport_size.y());
window->rasterizer->context_resized(window->viewport_size.x(), window->viewport_size.y());
// Publish window resized event
window->resized_publisher.publish({window, window->size});
break;
}
case SDL_WINDOWEVENT_MOVED:
{
// Get window
SDL_Window* internal_window = SDL_GetWindowFromID(event.window.windowID);
app::sdl_window* window = get_window(internal_window);
// Update window state
window->position = {event.window.data1, event.window.data2};
const auto window_flags = SDL_GetWindowFlags(internal_window);
if (!(window_flags & SDL_WINDOW_MAXIMIZED) && !(window_flags & SDL_WINDOW_FULLSCREEN))
{
window->windowed_position = window->position;
}
// Publish window moved event
window->moved_publisher.publish({window, window->position});
break;
}
case SDL_WINDOWEVENT_FOCUS_GAINED:
{
// Get window
SDL_Window* internal_window = SDL_GetWindowFromID(event.window.windowID);
app::sdl_window* window = get_window(internal_window);
// Publish window focus gained event
window->focus_changed_publisher.publish({window, true});
break;
}
case SDL_WINDOWEVENT_FOCUS_LOST:
{
// Get window
SDL_Window* internal_window = SDL_GetWindowFromID(event.window.windowID);
app::sdl_window* window = get_window(internal_window);
// Publish window focus lost event
window->focus_changed_publisher.publish({window, false});
break;
}
case SDL_WINDOWEVENT_MAXIMIZED:
{
// Get window
SDL_Window* internal_window = SDL_GetWindowFromID(event.window.windowID);
app::sdl_window* window = get_window(internal_window);
// Update window state
window->maximized = true;
// Publish window maximized event
window->maximized_publisher.publish({window});
break;
}
case SDL_WINDOWEVENT_RESTORED:
{
// Get window
SDL_Window* internal_window = SDL_GetWindowFromID(event.window.windowID);
app::sdl_window* window = get_window(internal_window);
// Update window state
window->maximized = false;
// Publish window restored event
window->restored_publisher.publish({window});
break;
}
case SDL_WINDOWEVENT_MINIMIZED:
{
// Get window
SDL_Window* internal_window = SDL_GetWindowFromID(event.window.windowID);
app::sdl_window* window = get_window(internal_window);
// Publish window minimized event
window->minimized_publisher.publish({window});
break;
}
[[unlikely]] case SDL_WINDOWEVENT_CLOSE:
{
// Get window
SDL_Window* internal_window = SDL_GetWindowFromID(event.window.windowID);
app::sdl_window* window = get_window(internal_window);
// Publish window closed event
window->closed_publisher.publish({window});
break;
}
default:
break;
}
}
else if (event.type == SDL_DISPLAYEVENT)
{
}
}
}
sdl_window* sdl_window_manager::get_window(SDL_Window* internal_window)
{
sdl_window* window = nullptr;
if (auto i = window_map.find(internal_window); i != window_map.end())
{
window = i->second;
}
else
{
throw std::runtime_error("SDL window unrecognized by SDL window manager");
}
return window;
}
std::size_t sdl_window_manager::get_display_count() const
{
return displays.size();
}
const display& sdl_window_manager::get_display(std::size_t index) const
{
return displays[index];
}
} // namespace app

+ 74
- 0
src/app/sdl/sdl-window-manager.hpp View File

@ -0,0 +1,74 @@
/*
* Copyright (C) 2023 Christopher J. Howard
*
* This file is part of Antkeeper source code.
*
* Antkeeper source code is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Antkeeper source code is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Antkeeper source code. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef ANTKEEPER_APP_SDL_WINDOW_MANAGER_HPP
#define ANTKEEPER_APP_SDL_WINDOW_MANAGER_HPP
#include "app/window-manager.hpp"
#include "app/display.hpp"
#include <SDL2/SDL.h>
#include <unordered_map>
#include <vector>
namespace app {
class sdl_window;
/**
*
*/
class sdl_window_manager: public window_manager
{
public:
/**
* Constructs an SDL window manager.
*/
sdl_window_manager();
/**
* Destructs an SDL window manager.
*/
virtual ~sdl_window_manager();
virtual void update();
/// @copydoc window::window()
[[nodiscard]] virtual window* create_window
(
const std::string& title,
const math::vector<int, 2>& windowed_position,
const math::vector<int, 2>& windowed_size,
bool maximized,
bool fullscreen,
bool v_sync
);
[[nodiscard]] virtual std::size_t get_display_count() const;
[[nodiscard]] virtual const display& get_display(std::size_t index) const;
private:
sdl_window* get_window(SDL_Window* internal_window);
std::vector<display> displays;
std::unordered_map<SDL_Window*, app::sdl_window*> window_map;
};
} // namespace app
#endif // ANTKEEPER_APP_SDL_WINDOW_MANAGER_HPP

+ 294
- 0
src/app/sdl/sdl-window.cpp View File

@ -0,0 +1,294 @@
/*
* Copyright (C) 2023 Christopher J. Howard
*
* This file is part of Antkeeper source code.
*
* Antkeeper source code is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Antkeeper source code is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Antkeeper source code. If not, see <http://www.gnu.org/licenses/>.
*/
#include "app/sdl/sdl-window.hpp"
#include "config.hpp"
#include "debug/log.hpp"
#include <glad/glad.h>
#include <stdexcept>
namespace app {
sdl_window::sdl_window
(
const std::string& title,
const math::vector<int, 2>& windowed_position,
const math::vector<int, 2>& windowed_size,
bool maximized,
bool fullscreen,
bool v_sync
)
{
// Determine SDL window creation flags
Uint32 window_flags = SDL_WINDOW_ALLOW_HIGHDPI | SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE;
if (maximized)
{
window_flags |= SDL_WINDOW_MAXIMIZED;
}
if (fullscreen)
{
window_flags |= SDL_WINDOW_FULLSCREEN_DESKTOP;
}
// Create SDL window
debug::log::trace("Creating SDL window...");
internal_window = SDL_CreateWindow
(
title.c_str(),
windowed_position.x(),
windowed_position.y(),
windowed_size.x(),
windowed_size.y(),
window_flags
);
if (!internal_window)
{
debug::log::fatal("Failed to create SDL window: {}", SDL_GetError());
throw std::runtime_error("Failed to create SDL window");
}
debug::log::trace("Created SDL window");
// Create OpenGL context
debug::log::trace("Creating OpenGL context...");
internal_context = SDL_GL_CreateContext(internal_window);
if (!internal_context)
{
debug::log::fatal("Failed to create OpenGL context: {}", SDL_GetError());
throw std::runtime_error("Failed to create OpenGL context");
}
debug::log::trace("Created OpenGL context");
// Query OpenGL context info
int opengl_context_version_major = -1;
int opengl_context_version_minor = -1;
int opengl_context_red_size = -1;
int opengl_context_green_size = -1;
int opengl_context_blue_size = -1;
int opengl_context_alpha_size = -1;
int opengl_context_depth_size = -1;
int opengl_context_stencil_size = -1;
SDL_GL_GetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, &opengl_context_version_major);
SDL_GL_GetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, &opengl_context_version_minor);
SDL_GL_GetAttribute(SDL_GL_RED_SIZE, &opengl_context_red_size);
SDL_GL_GetAttribute(SDL_GL_GREEN_SIZE, &opengl_context_green_size);
SDL_GL_GetAttribute(SDL_GL_BLUE_SIZE, &opengl_context_blue_size);
SDL_GL_GetAttribute(SDL_GL_ALPHA_SIZE, &opengl_context_alpha_size);
SDL_GL_GetAttribute(SDL_GL_DEPTH_SIZE, &opengl_context_depth_size);
SDL_GL_GetAttribute(SDL_GL_STENCIL_SIZE, &opengl_context_stencil_size);
// Log OpenGL context info
debug::log::info
(
"OpenGL context version: {}.{}; format: R{}G{}B{}A{}D{}S{}",
opengl_context_version_major,
opengl_context_version_minor,
opengl_context_red_size,
opengl_context_green_size,
opengl_context_blue_size,
opengl_context_alpha_size,
opengl_context_depth_size,
opengl_context_stencil_size
);
// Compare OpenGL context version with requested version
if (opengl_context_version_major != config::opengl_version_major ||
opengl_context_version_minor != config::opengl_version_minor)
{
debug::log::warning("Requested OpenGL context version {}.{} but got version {}.{}", config::opengl_version_major, config::opengl_version_minor, opengl_context_version_major, opengl_context_version_minor);
}
// Compare OpenGL context format with requested format
if (opengl_context_red_size < config::opengl_min_red_size ||
opengl_context_green_size < config::opengl_min_green_size ||
opengl_context_blue_size < config::opengl_min_blue_size ||
opengl_context_alpha_size < config::opengl_min_alpha_size ||
opengl_context_depth_size < config::opengl_min_depth_size ||
opengl_context_stencil_size < config::opengl_min_stencil_size)
{
debug::log::warning
(
"OpenGL context format (R{}G{}B{}A{}D{}S{}) does not meet minimum requested 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,
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
);
}
// Load OpenGL functions via GLAD
debug::log::trace("Loading OpenGL functions...");
if (!gladLoadGLLoader((GLADloadproc)SDL_GL_GetProcAddress))
{
debug::log::fatal("Failed to load OpenGL functions", SDL_GetError());
throw std::runtime_error("Failed to load OpenGL functions");
}
debug::log::trace("Loaded OpenGL functions");
// Log OpenGL information
debug::log::info
(
"OpenGL vendor: {}; renderer: {}; version: {}; shading language version: {}",
reinterpret_cast<const char*>(glGetString(GL_VENDOR)),
reinterpret_cast<const char*>(glGetString(GL_RENDERER)),
reinterpret_cast<const char*>(glGetString(GL_VERSION)),
reinterpret_cast<const char*>(glGetString(GL_SHADING_LANGUAGE_VERSION))
);
// Fill window with color
//glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
swap_buffers();
// Enable or disable v-sync
set_v_sync(v_sync);
// Update window state
this->title = title;
this->windowed_position = windowed_position;
this->windowed_size = windowed_size;
this->maximized = maximized;
this->fullscreen = fullscreen;
SDL_GetWindowPosition(internal_window, &this->position.x(), &this->position.y());
SDL_GetWindowSize(internal_window, &this->size.x(), &this->size.y());
SDL_GetWindowMinimumSize(internal_window, &this->minimum_size.x(), &this->minimum_size.y());
SDL_GetWindowMaximumSize(internal_window, &this->maximum_size.x(), &this->maximum_size.y());
SDL_GL_GetDrawableSize(internal_window, &this->viewport_size.x(), &this->viewport_size.y());
// Allocate rasterizer
this->rasterizer = new gl::rasterizer();
}
sdl_window::~sdl_window()
{
// Destruct rasterizer
delete rasterizer;
// Destruct the OpenGL context
SDL_GL_DeleteContext(internal_context);
// Destruct the SDL window
SDL_DestroyWindow(internal_window);
}
void sdl_window::set_title(const std::string& title)
{
SDL_SetWindowTitle(internal_window, title.c_str());
this->title = title;
}
void sdl_window::set_position(const math::vector<int, 2>& position)
{
SDL_SetWindowPosition(internal_window, position.x(), position.y());
}
void sdl_window::set_size(const math::vector<int, 2>& size)
{
SDL_SetWindowSize(internal_window, size.x(), size.y());
}
void sdl_window::set_minimum_size(const math::vector<int, 2>& size)
{
SDL_SetWindowMinimumSize(internal_window, size.x(), size.y());
this->minimum_size = size;
}
void sdl_window::set_maximum_size(const math::vector<int, 2>& size)
{
SDL_SetWindowMaximumSize(internal_window, size.x(), size.y());
this->maximum_size = size;
}
void sdl_window::set_maximized(bool maximized)
{
if (maximized)
{
SDL_MaximizeWindow(internal_window);
}
else
{
SDL_RestoreWindow(internal_window);
}
}
void sdl_window::set_fullscreen(bool fullscreen)
{
SDL_SetWindowFullscreen(internal_window, (fullscreen) ? SDL_WINDOW_FULLSCREEN_DESKTOP : 0);
this->fullscreen = fullscreen;
}
void sdl_window::set_v_sync(bool v_sync)
{
if (v_sync)
{
debug::log::trace("Enabling adaptive v-sync...");
if (SDL_GL_SetSwapInterval(-1) != 0)
{
debug::log::error("Failed to enable adaptive v-sync: {}", SDL_GetError());
debug::log::trace("Enabling synchronized v-sync...");
if (SDL_GL_SetSwapInterval(1) != 0)
{
debug::log::error("Failed to enable synchronized v-sync: {}", SDL_GetError());
v_sync = false;
}
else
{
debug::log::debug("Enabled synchronized v-sync");
}
}
else
{
debug::log::debug("Enabled adaptive v-sync");
}
}
else
{
debug::log::trace("Disabling v-sync...");
if (SDL_GL_SetSwapInterval(0) != 0)
{
debug::log::error("Failed to disable v-sync: {}", SDL_GetError());
v_sync = true;
}
else
{
debug::log::debug("Disabled v-sync");
}
}
this->v_sync = v_sync;
}
void sdl_window::make_current()
{
SDL_GL_MakeCurrent(internal_window, internal_context);
}
void sdl_window::swap_buffers()
{
SDL_GL_SwapWindow(internal_window);
}
} // namespace app

+ 76
- 0
src/app/sdl/sdl-window.hpp View File

@ -0,0 +1,76 @@
/*
* Copyright (C) 2023 Christopher J. Howard
*
* This file is part of Antkeeper source code.
*
* Antkeeper source code is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Antkeeper source code is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Antkeeper source code. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef ANTKEEPER_APP_SDL_WINDOW_HPP
#define ANTKEEPER_APP_SDL_WINDOW_HPP
#include "app/window.hpp"
#include <SDL2/SDL.h>
namespace app {
/**
*
*/
class sdl_window: public window
{
public:
virtual ~sdl_window();
virtual void set_title(const std::string& title);
virtual void set_position(const math::vector<int, 2>& position);
virtual void set_size(const math::vector<int, 2>& size);
virtual void set_minimum_size(const math::vector<int, 2>& size);
virtual void set_maximum_size(const math::vector<int, 2>& size);
virtual void set_maximized(bool maximized);
virtual void set_fullscreen(bool fullscreen);
virtual void set_v_sync(bool v_sync);
virtual void make_current();
virtual void swap_buffers();
[[nodiscard]] inline virtual gl::rasterizer* get_rasterizer() noexcept
{
return rasterizer;
}
private:
friend class sdl_window_manager;
sdl_window
(
const std::string& title,
const math::vector<int, 2>& windowed_position,
const math::vector<int, 2>& windowed_size,
bool maximized,
bool fullscreen,
bool v_sync
);
sdl_window(const sdl_window&) = delete;
sdl_window(sdl_window&&) = delete;
sdl_window& operator=(const sdl_window&) = delete;
sdl_window& operator=(sdl_window&&) = delete;
SDL_Window* internal_window;
SDL_GLContext internal_context;
gl::rasterizer* rasterizer;
};
} // namespace app
#endif // ANTKEEPER_APP_SDL_WINDOW_HPP

+ 103
- 0
src/app/window-events.hpp View File

@ -0,0 +1,103 @@
/*
* Copyright (C) 2023 Christopher J. Howard
*
* This file is part of Antkeeper source code.
*
* Antkeeper source code is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Antkeeper source code is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Antkeeper source code. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef ANTKEEPER_APP_WINDOW_EVENTS_HPP
#define ANTKEEPER_APP_WINDOW_EVENTS_HPP
#include "math/vector.hpp"
namespace app {
class window;
/**
* Event generated when a window has been requested to close.
*/
struct window_closed_event
{
/// Pointer to the window that has been requested to close.
window* window;
};
/**
* Event generated when a window has gained or lost focus.
*/
struct window_focus_changed_event
{
/// Pointer to the window that has gained or lost focus.
window* window;
/// `true` if the window is in focus, `false` otherwise.
bool in_focus;
};
/**
* Event generated when a window has been moved.
*/
struct window_moved_event
{
/// Pointer to the window that has been moved.
window* window;
/// Position of the window, in display units.
math::vector<int, 2> position;
};
/**
* Event generated when a window has been maximized.
*/
struct window_maximized_event
{
/// Pointer to the window that has been maximized.
window* window;
};
/**
* Event generated when a window has been minimized.
*/
struct window_minimized_event
{
/// Pointer to the window that has been minimized.
window* window;
};
/**
* Event generated when a window has been restored.
*/
struct window_restored_event
{
/// Pointer to the window that has been restored.
window* window;
};
/**
* Event generated when a window has been resized.
*/
struct window_resized_event
{
/// Pointer to the window that has been resized.
window* window;
/// Window size, in display units.
math::vector<int, 2> size;
};
} // namespace app
#endif // ANTKEEPER_APP_WINDOW_EVENTS_HPP

+ 30
- 0
src/app/window-manager.cpp View File

@ -0,0 +1,30 @@
/*
* Copyright (C) 2023 Christopher J. Howard
*
* This file is part of Antkeeper source code.
*
* Antkeeper source code is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Antkeeper source code is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Antkeeper source code. If not, see <http://www.gnu.org/licenses/>.
*/
#include "app/window-manager.hpp"
#include "app/sdl/sdl-window-manager.hpp"
namespace app {
window_manager* window_manager::instance()
{
return new sdl_window_manager();
}
} // namespace app

+ 84
- 0
src/app/window-manager.hpp View File

@ -0,0 +1,84 @@
/*
* Copyright (C) 2023 Christopher J. Howard
*
* This file is part of Antkeeper source code.
*
* Antkeeper source code is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Antkeeper source code is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Antkeeper source code. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef ANTKEEPER_APP_WINDOW_MANAGER_HPP
#define ANTKEEPER_APP_WINDOW_MANAGER_HPP
#include "app/display.hpp"
#include "app/window.hpp"
#include "math/vector.hpp"
#include <string>
namespace app {
/**
*
*/
class window_manager
{
public:
/**
* Allocates and returns a window manager.
*/
static window_manager* instance();
/// Destructs a window manager.
virtual ~window_manager() = default;
/**
* Updates all managed windows. This should be called once per frame.
*/
virtual void update() = 0;
/**
* Constructs a window.
*
* @param title Title of the window.
* @param windowed_position Windowed (non-maximized, non-fullscreen) position of the window, in display units.
* @param windowed_size Windowed (non-maximized, non-fullscreen) size of the window, in display units.
* @param maximized `true` if the window should start maximized, `false` otherwise.
* @param fullscreen `true` if the window should start fullscreen, `false` otherwise.
* @param v_sync `true` if v-sync should be enabled, `false` otherwise.
*/
[[nodiscard]] virtual window* create_window
(
const std::string& title,
const math::vector<int, 2>& windowed_position,
const math::vector<int, 2>& windowed_size,
bool maximized,
bool fullscreen,
bool v_sync
) = 0;
/// Returns the number of available displays.
[[nodiscard]] virtual std::size_t get_display_count() const = 0;
/**
* Returns the display with the given index.
*
* @param index Index of a display.
*
* @return Display with the given index.
*/
[[nodiscard]] virtual const display& get_display(std::size_t index) const = 0;
};
} // namespace app
#endif // ANTKEEPER_APP_WINDOW_MANAGER_HPP

+ 26
- 0
src/app/window.cpp View File

@ -0,0 +1,26 @@
/*
* Copyright (C) 2023 Christopher J. Howard
*
* This file is part of Antkeeper source code.
*
* Antkeeper source code is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Antkeeper source code is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Antkeeper source code. If not, see <http://www.gnu.org/licenses/>.
*/
#include "app/window.hpp"
namespace app {
} // namespace app

+ 252
- 0
src/app/window.hpp View File

@ -0,0 +1,252 @@
/*
* Copyright (C) 2023 Christopher J. Howard
*
* This file is part of Antkeeper source code.
*
* Antkeeper source code is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Antkeeper source code is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Antkeeper source code. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef ANTKEEPER_APP_WINDOW_HPP
#define ANTKEEPER_APP_WINDOW_HPP
#include "math/vector.hpp"
#include "event/publisher.hpp"
#include "app/window-events.hpp"
#include "gl/rasterizer.hpp"
#include <string>
namespace app {
class window_manager;
/**
*
*/
class window
{
public:
/**
* Constructs a window.
*/
window() = default;
/**
* Destructs a window.
*/
virtual ~window() = default;
/**
* Changes the title of the window.
*
* @param title Window title.
*/
virtual void set_title(const std::string& title) = 0;
/**
* Changes the position of the window.
*
* @param position Position of the window, in display units.
*/
virtual void set_position(const math::vector<int, 2>& position) = 0;
/**
* Changes the size of the window.
*
* @param size Size of the window, in display units.
*/
virtual void set_size(const math::vector<int, 2>& size) = 0;
/**
* Sets the minimum size of the window.
*
* @param size Minimum size of the window, in display units.
*/
virtual void set_minimum_size(const math::vector<int, 2>& size) = 0;
/**
* Sets the maximum size of the window.
*
* @param size Maximum size of the window, in display units.
*/
virtual void set_maximum_size(const math::vector<int, 2>& size) = 0;
/**
* Maximizes or unmaximizes the window.
*
* @param maximized `true` if the window should be maximized, `false` otherwise.
*/
virtual void set_maximized(bool maximized) = 0;
/**
* Enables or disables fullscreen mode.
*
* @param fullscreen `true` if the window should be in fullscreen mode, `false` otherwise.
*/
virtual void set_fullscreen(bool fullscreen) = 0;
/**
* Enables or disables v-sync.
*
* @param v_sync `true` if the v-sync should be enabled, `false` otherwise.
*/
virtual void set_v_sync(bool v_sync) = 0;
/**
* Makes the window's graphics context current.
*/
virtual void make_current() = 0;
/**
* Swaps the front and back buffers of the window's graphics context.
*/
virtual void swap_buffers() = 0;
/// Returns the title of the window.
[[nodiscard]] inline const std::string& get_title() const noexcept
{
return title;
}
/// Returns the windowed (non-maximized, non-fullscreen) position of the window, in display units.
[[nodiscard]] inline const math::vector<int, 2>& get_windowed_position() const noexcept
{
return windowed_position;
}
/// Returns the current position of the window, in display units.
[[nodiscard]] inline const math::vector<int, 2>& get_position() const noexcept
{
return position;
}
/// Returns the windowed (non-maximized, non-fullscreen) size of the window, in display units.
[[nodiscard]] inline const math::vector<int, 2>& get_windowed_size() const noexcept
{
return windowed_size;
}
/// Returns the current size of the window, in display units.
[[nodiscard]] inline const math::vector<int, 2>& get_size() const noexcept
{
return size;
}
/// Returns the minimum size of the window, in display units.
[[nodiscard]] inline const math::vector<int, 2>& get_minimum_size() const noexcept
{
return minimum_size;
}
/// Returns the maximum size of the window, in display units.
[[nodiscard]] inline const math::vector<int, 2>& get_maximum_size() const noexcept
{
return minimum_size;
}
/// Returns the current size of the window's drawable viewport, in pixels.
[[nodiscard]] inline const math::vector<int, 2>& get_viewport_size() const noexcept
{
return viewport_size;
}
/// Returns `true` if the window is maximized, `false` otherwise.
[[nodiscard]] inline bool is_maximized() const noexcept
{
return maximized;
}
/// Returns `true` if the window is in fullscreen mode, `false` otherwise.
[[nodiscard]] inline bool is_fullscreen() const noexcept
{
return fullscreen;
}
/// Returns `true` if the v-sync is enabled, `false` otherwise.
[[nodiscard]] inline bool get_v_sync() const noexcept
{
return v_sync;
}
/// Returns the rasterizer associated with this window.
[[nodiscard]] virtual gl::rasterizer* get_rasterizer() noexcept = 0;
/// Returns the channel through which window closed events are published.
[[nodiscard]] inline event::channel<window_closed_event>& get_closed_channel() noexcept
{
return closed_publisher.channel();
}
/// Returns the channel through which window focus changed events are published.
[[nodiscard]] inline event::channel<window_focus_changed_event>& get_focus_changed_channel() noexcept
{
return focus_changed_publisher.channel();
}
/// Returns the channel through which window maximized events are published.
[[nodiscard]] inline event::channel<window_maximized_event>& get_maximized_channel() noexcept
{
return maximized_publisher.channel();
}
/// Returns the channel through which window minimized events are published.
[[nodiscard]] inline event::channel<window_minimized_event>& get_minimized_channel() noexcept
{
return minimized_publisher.channel();
}
/// Returns the channel through which window moved events are published.
[[nodiscard]] inline event::channel<window_moved_event>& get_moved_channel() noexcept
{
return moved_publisher.channel();
}
/// Returns the channel through which window resized events are published.
[[nodiscard]] inline event::channel<window_resized_event>& get_resized_channel() noexcept
{
return resized_publisher.channel();
}
/// Returns the channel through which window restored events are published.
[[nodiscard]] inline event::channel<window_restored_event>& get_restored_channel() noexcept
{
return restored_publisher.channel();
}
protected:
friend class window_manager;
std::string title;
math::vector<int, 2> windowed_position;
math::vector<int, 2> position;
math::vector<int, 2> windowed_size;
math::vector<int, 2> size;
math::vector<int, 2> minimum_size;
math::vector<int, 2> maximum_size;
math::vector<int, 2> viewport_size;
bool maximized;
bool fullscreen;
bool v_sync;
event::publisher<window_closed_event> closed_publisher;
event::publisher<window_focus_changed_event> focus_changed_publisher;
event::publisher<window_maximized_event> maximized_publisher;
event::publisher<window_minimized_event> minimized_publisher;
event::publisher<window_moved_event> moved_publisher;
event::publisher<window_resized_event> resized_publisher;
event::publisher<window_restored_event> restored_publisher;
};
} // namespace app
#endif // ANTKEEPER_APP_WINDOW_HPP

+ 0
- 818
src/application.cpp View File

@ -1,818 +0,0 @@
/*
* Copyright (C) 2023 Christopher J. Howard
*
* This file is part of Antkeeper source code.
*
* Antkeeper source code is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Antkeeper source code is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Antkeeper source code. If not, see <http://www.gnu.org/licenses/>.
*/
#include "application.hpp"
#include "config.hpp"
#include "debug/log.hpp"
#include "input/scancode.hpp"
#include "math/map.hpp"
#include <SDL2/SDL.h>
#include <glad/glad.h>
#include <stdexcept>
#include <utility>
application::application
(
const std::string& window_title,
int window_x,
int window_y,
int window_w,
int window_h,
bool maximized,
bool fullscreen,
bool v_sync
):
closed(false),
maximized(false),
fullscreen(true),
v_sync(false),
cursor_visible(true),
display_size({0, 0}),
display_dpi(0.0f),
windowed_position({-1, -1}),
windowed_size({-1, -1}),
viewport_size({-1, -1}),
mouse_position({0, 0}),
sdl_window(nullptr),
sdl_gl_context(nullptr)
{
// Log SDL info
// SDL_version sdl_compiled_version;
// SDL_version sdl_linked_version;
// SDL_VERSION(&sdl_compiled_version);
// SDL_GetVersion(&sdl_linked_version);
// debug::log::info
// (
// "SDL compiled version: {}.{}.{}; linked version: {}.{}.{}",
// sdl_compiled_version.major,
// sdl_compiled_version.minor,
// sdl_compiled_version.patch,
// sdl_linked_version.major,
// sdl_linked_version.minor,
// sdl_linked_version.patch
// );
// Init SDL events and video subsystems
debug::log::trace("Initializing SDL events and video subsystems...");
if (SDL_InitSubSystem(SDL_INIT_EVENTS | SDL_INIT_VIDEO) != 0)
{
debug::log::fatal("Failed to initialize SDL events and video subsystems: {}", SDL_GetError());
throw std::runtime_error("Failed to initialize SDL events and video subsystems");
}
debug::log::trace("Initialized SDL events and video subsystems");
// Query displays
debug::log::trace("Querying displays...");
const int sdl_display_count = SDL_GetNumVideoDisplays();
if (sdl_display_count < 1)
{
debug::log::fatal("No displays detected: {}", SDL_GetError());
throw std::runtime_error("No displays detected");
}
debug::log::info("Display count: {}", sdl_display_count);
for (int i = 0; i < sdl_display_count; ++i)
{
// Query display mode
SDL_DisplayMode sdl_display_mode;
if (SDL_GetDesktopDisplayMode(i, &sdl_display_mode) != 0)
{
debug::log::error("Failed to get mode of display {}: {}", i, SDL_GetError());
SDL_ClearError();
continue;
}
// Query display name
const char* sdl_display_name = SDL_GetDisplayName(i);
if (!sdl_display_name)
{
debug::log::warning("Failed to get name of display {}: {}", i, SDL_GetError());
SDL_ClearError();
sdl_display_name = "";
}
// Query display DPI
float sdl_display_dpi;
if (SDL_GetDisplayDPI(i, &sdl_display_dpi, nullptr, nullptr) != 0)
{
const float default_dpi = 96.0f;
debug::log::warning("Failed to get DPI of display {}: {}; Defaulting to {} DPI", i, SDL_GetError(), default_dpi);
SDL_ClearError();
}
// Log display information
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);
}
debug::log::trace("Queried displays");
// Detect display dimensions
SDL_DisplayMode sdl_desktop_display_mode;
if (SDL_GetDesktopDisplayMode(0, &sdl_desktop_display_mode) != 0)
{
debug::log::fatal("Failed to detect desktop display mode: {}", SDL_GetError());
throw std::runtime_error("Failed to detect desktop display mode");
}
display_size = {sdl_desktop_display_mode.w, sdl_desktop_display_mode.h};
// Detect display DPI
if (SDL_GetDisplayDPI(0, &display_dpi, nullptr, nullptr) != 0)
{
debug::log::fatal("Failed to detect display DPI: {}", SDL_GetError());
throw std::runtime_error("Failed to detect display DPI");
}
// Log display properties
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);
// Load OpenGL library
debug::log::trace("Loading OpenGL library...");
if (SDL_GL_LoadLibrary(nullptr) != 0)
{
debug::log::fatal("Failed to load OpenGL library: {}", SDL_GetError());
throw std::runtime_error("Failed to load OpenGL library");
}
debug::log::trace("Loaded OpenGL library");
// Set OpenGL-related window creation hints
SDL_GL_SetAttribute(SDL_GL_ACCELERATED_VISUAL, 1);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, config::opengl_version_major);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, config::opengl_version_minor);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_FORWARD_COMPATIBLE_FLAG);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
SDL_GL_SetAttribute(SDL_GL_RED_SIZE, config::opengl_min_red_size);
SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, config::opengl_min_green_size);
SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, config::opengl_min_blue_size);
SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, config::opengl_min_alpha_size);
SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, config::opengl_min_depth_size);
SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, config::opengl_min_stencil_size);
Uint32 sdl_window_flags = SDL_WINDOW_ALLOW_HIGHDPI | SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE;
if (fullscreen)
{
sdl_window_flags |= SDL_WINDOW_FULLSCREEN_DESKTOP;
}
if (maximized)
{
sdl_window_flags |= SDL_WINDOW_MAXIMIZED;
}
if (window_x == -1 && window_y == -1)
{
window_x = SDL_WINDOWPOS_CENTERED;
window_y = SDL_WINDOWPOS_CENTERED;
}
if (window_w <= 0 || window_h <= 0)
{
window_w = sdl_desktop_display_mode.w / 2;
window_h = sdl_desktop_display_mode.h / 2;
}
// Create a hidden fullscreen window
debug::log::trace("Creating window...");
sdl_window = SDL_CreateWindow
(
window_title.c_str(),
window_x,
window_y,
window_w,
window_h,
sdl_window_flags
);
if (!sdl_window)
{
debug::log::fatal("Failed to create {}x{} window: {}", display_size[0], display_size[1], SDL_GetError());
throw std::runtime_error("Failed to create SDL window");
}
debug::log::trace("Created window");
if (window_x != SDL_WINDOWPOS_CENTERED && window_y != SDL_WINDOWPOS_CENTERED)
{
this->windowed_position = {window_x, window_y};
}
this->windowed_size = {window_w, window_h};
this->maximized = maximized;
this->fullscreen = fullscreen;
// Set hard window minimum size
SDL_SetWindowMinimumSize(sdl_window, 160, 120);
// Create OpenGL context
debug::log::trace("Creating OpenGL {}.{} context...", config::opengl_version_major, config::opengl_version_minor);
sdl_gl_context = SDL_GL_CreateContext(sdl_window);
if (!sdl_gl_context)
{
debug::log::fatal("Failed to create OpenGL context: {}", SDL_GetError());
throw std::runtime_error("Failed to create OpenGL context");
}
// Log version of created OpenGL context
int opengl_context_version_major = -1;
int opengl_context_version_minor = -1;
SDL_GL_GetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, &opengl_context_version_major);
SDL_GL_GetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, &opengl_context_version_minor);
debug::log::info("Created OpenGL {}.{} context", opengl_context_version_major, opengl_context_version_minor);
// Compare OpenGL context version with requested version
if (opengl_context_version_major != config::opengl_version_major ||
opengl_context_version_minor != config::opengl_version_minor)
{
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);
}
// Log format of OpenGL context default framebuffer
int opengl_context_red_size = -1;
int opengl_context_green_size = -1;
int opengl_context_blue_size = -1;
int opengl_context_alpha_size = -1;
int opengl_context_depth_size = -1;
int opengl_context_stencil_size = -1;
SDL_GL_GetAttribute(SDL_GL_RED_SIZE, &opengl_context_red_size);
SDL_GL_GetAttribute(SDL_GL_GREEN_SIZE, &opengl_context_green_size);
SDL_GL_GetAttribute(SDL_GL_BLUE_SIZE, &opengl_context_blue_size);
SDL_GL_GetAttribute(SDL_GL_ALPHA_SIZE, &opengl_context_alpha_size);
SDL_GL_GetAttribute(SDL_GL_DEPTH_SIZE, &opengl_context_depth_size);
SDL_GL_GetAttribute(SDL_GL_STENCIL_SIZE, &opengl_context_stencil_size);
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);
// Compare OpenGL context default framebuffer format with request format
if (opengl_context_red_size < config::opengl_min_red_size ||
opengl_context_green_size < config::opengl_min_green_size ||
opengl_context_blue_size < config::opengl_min_blue_size ||
opengl_context_alpha_size < config::opengl_min_alpha_size ||
opengl_context_depth_size < config::opengl_min_depth_size ||
opengl_context_stencil_size < config::opengl_min_stencil_size)
{
debug::log::warning
(
"OpenGL context format (R{}G{}B{}A{}D{}S{}) does not meet minimum requested 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,
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
);
}
// Load OpenGL functions via GLAD
debug::log::trace("Loading OpenGL functions...");
if (!gladLoadGLLoader((GLADloadproc)SDL_GL_GetProcAddress))
{
debug::log::fatal("Failed to load OpenGL functions", SDL_GetError());
throw std::runtime_error("Failed to load OpenGL functions");
}
debug::log::trace("Loaded OpenGL functions");
// Log OpenGL context information
debug::log::info
(
"OpenGL vendor: {}; renderer: {}; version: {}; shading language version: {}",
reinterpret_cast<const char*>(glGetString(GL_VENDOR)),
reinterpret_cast<const char*>(glGetString(GL_RENDERER)),
reinterpret_cast<const char*>(glGetString(GL_VERSION)),
reinterpret_cast<const char*>(glGetString(GL_SHADING_LANGUAGE_VERSION))
);
// Clear window color
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
swap_buffers();
// Set v-sync mode
set_v_sync(v_sync);
// Update viewport size
SDL_GL_GetDrawableSize(sdl_window, &viewport_size[0], &viewport_size[1]);
// Init SDL joystick and controller subsystems
debug::log::trace("Initializing SDL joystick and controller subsystems...");
if (SDL_InitSubSystem(SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER) != 0)
{
debug::log::error("Failed to initialize SDL joystick and controller subsytems: {}", SDL_GetError());
}
else
{
debug::log::trace("Initialized SDL joystick and controller subsystems");
}
// Setup rasterizer
rasterizer = new gl::rasterizer();
rasterizer->context_resized(viewport_size[0], viewport_size[1]);
// Register keyboard and mouse with input device manager
device_manager.register_device(keyboard);
device_manager.register_device(mouse);
// Generate keyboard and mouse device connected events
keyboard.connect();
mouse.connect();
// Connect gamepads
process_events();
}
application::~application()
{
// Destroy the OpenGL context
SDL_GL_DeleteContext(sdl_gl_context);
// Destroy the SDL window
SDL_DestroyWindow(sdl_window);
// Shutdown SDL
SDL_QuitSubSystem(SDL_INIT_VIDEO | SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER);
SDL_Quit();
}
void application::close()
{
closed = true;
}
void application::set_title(const std::string& title)
{
SDL_SetWindowTitle(sdl_window, title.c_str());
}
void application::set_cursor_visible(bool visible)
{
SDL_ShowCursor((visible) ? SDL_ENABLE : SDL_DISABLE);
cursor_visible = visible;
}
void application::set_relative_mouse_mode(bool enabled)
{
if (enabled)
{
SDL_GetMouseState(&mouse_position[0], &mouse_position[1]);
SDL_ShowCursor(SDL_DISABLE);
SDL_SetRelativeMouseMode(SDL_TRUE);
}
else
{
SDL_SetRelativeMouseMode(SDL_FALSE);
SDL_WarpMouseInWindow(sdl_window, mouse_position[0], mouse_position[1]);
if (cursor_visible)
{
SDL_ShowCursor(SDL_ENABLE);
}
}
}
void application::resize_window(int width, int height)
{
int x = (display_size[0] >> 1) - (width >> 1);
int y = (display_size[1] >> 1) - (height >> 1);
// Resize and center window
SDL_SetWindowPosition(sdl_window, x, y);
SDL_SetWindowSize(sdl_window, width, height);
}
void application::set_fullscreen(bool fullscreen)
{
if (this->fullscreen != fullscreen)
{
this->fullscreen = fullscreen;
if (fullscreen)
{
SDL_SetWindowFullscreen(sdl_window, SDL_WINDOW_FULLSCREEN_DESKTOP);
}
else
{
SDL_SetWindowFullscreen(sdl_window, 0);
}
}
}
void application::set_v_sync(bool v_sync)
{
if (v_sync)
{
debug::log::trace("Enabling adaptive v-sync...");
if (SDL_GL_SetSwapInterval(-1) != 0)
{
debug::log::error("Failed to enable adaptive v-sync: {}", SDL_GetError());
debug::log::trace("Enabling synchronized v-sync...");
if (SDL_GL_SetSwapInterval(1) != 0)
{
debug::log::error("Failed to enable synchronized v-sync: {}", SDL_GetError());
}
else
{
this->v_sync = v_sync;
debug::log::debug("Enabled synchronized v-sync");
}
}
else
{
this->v_sync = v_sync;
debug::log::debug("Enabled adaptive v-sync");
}
}
else
{
debug::log::trace("Disabling v-sync...");
if (SDL_GL_SetSwapInterval(0) != 0)
{
debug::log::error("Failed to disable v-sync: {}", SDL_GetError());
}
else
{
this->v_sync = v_sync;
debug::log::debug("Disabled v-sync");
}
}
}
void application::set_window_opacity(float opacity)
{
SDL_SetWindowOpacity(sdl_window, opacity);
}
void application::swap_buffers()
{
SDL_GL_SwapWindow(sdl_window);
}
void application::show_window()
{
SDL_ShowWindow(sdl_window);
//SDL_GL_MakeCurrent(sdl_window, sdl_gl_context);
}
void application::hide_window()
{
SDL_HideWindow(sdl_window);
}
void application::add_game_controller_mappings(const void* mappings, std::size_t size)
{
debug::log::trace("Adding SDL game controller mappings...");
int mapping_count = SDL_GameControllerAddMappingsFromRW(SDL_RWFromConstMem(mappings, static_cast<int>(size)), 0);
if (mapping_count == -1)
{
debug::log::error("Failed to add SDL game controller mappings: {}", SDL_GetError());
}
else
{
debug::log::debug("Added {} SDL game controller mappings", mapping_count);
}
}
void application::process_events()
{
// Active modifier keys
std::uint16_t sdl_key_mod = KMOD_NONE;
std::uint16_t modifier_keys = input::modifier_key::none;
// Mouse motion event accumulators
// bool mouse_motion = false;
// std::int32_t mouse_x;
// std::int32_t mouse_y;
// std::int32_t mouse_dx = 0;
// std::int32_t mouse_dy = 0;
SDL_Event sdl_event;
while (SDL_PollEvent(&sdl_event))
{
switch (sdl_event.type)
{
[[likely]] case SDL_MOUSEMOTION:
{
// More than one mouse motion event is often generated per frame, and may be a source of lag.
// Mouse events can be accumulated here to prevent excessive function calls and allocations
// mouse_motion = true;
// mouse_x = sdl_event.motion.x;
// mouse_y = sdl_event.motion.y;
// mouse_dx += sdl_event.motion.xrel;
// mouse_dy += sdl_event.motion.yrel;
mouse.move({sdl_event.motion.x, sdl_event.motion.y}, {sdl_event.motion.xrel, sdl_event.motion.yrel});
break;
}
case SDL_KEYDOWN:
case SDL_KEYUP:
{
// Get scancode of key
const input::scancode scancode = static_cast<input::scancode>(sdl_event.key.keysym.scancode);
// Rebuild modifier keys bit mask
if (sdl_event.key.keysym.mod != sdl_key_mod)
{
sdl_key_mod = sdl_event.key.keysym.mod;
modifier_keys = input::modifier_key::none;
if (sdl_key_mod & KMOD_LSHIFT)
modifier_keys |= input::modifier_key::left_shift;
if (sdl_key_mod & KMOD_RSHIFT)
modifier_keys |= input::modifier_key::right_shift;
if (sdl_key_mod & KMOD_LCTRL)
modifier_keys |= input::modifier_key::left_ctrl;
if (sdl_key_mod & KMOD_RCTRL)
modifier_keys |= input::modifier_key::right_ctrl;
if (sdl_key_mod & KMOD_LGUI)
modifier_keys |= input::modifier_key::left_gui;
if (sdl_key_mod & KMOD_RGUI)
modifier_keys |= input::modifier_key::right_gui;
if (sdl_key_mod & KMOD_NUM)
modifier_keys |= input::modifier_key::num_lock;
if (sdl_key_mod & KMOD_CAPS)
modifier_keys |= input::modifier_key::caps_lock;
if (sdl_key_mod & KMOD_SCROLL)
modifier_keys |= input::modifier_key::scroll_lock;
if (sdl_key_mod & KMOD_MODE)
modifier_keys |= input::modifier_key::alt_gr;
}
// Determine if event was generated from a key repeat
const bool repeat = sdl_event.key.repeat > 0;
if (sdl_event.type == SDL_KEYDOWN)
{
keyboard.press(scancode, repeat, modifier_keys);
}
else
{
keyboard.release(scancode, repeat, modifier_keys);
}
break;
}
case SDL_MOUSEWHEEL:
{
const float flip = (sdl_event.wheel.direction == SDL_MOUSEWHEEL_FLIPPED) ? -1.0f : 1.0f;
mouse.scroll({sdl_event.wheel.preciseX * flip, sdl_event.wheel.preciseY * flip});
break;
}
case SDL_MOUSEBUTTONDOWN:
{
mouse.press(static_cast<input::mouse_button>(sdl_event.button.button));
break;
}
case SDL_MOUSEBUTTONUP:
{
mouse.release(static_cast<input::mouse_button>(sdl_event.button.button));
break;
}
[[likely]] case SDL_CONTROLLERAXISMOTION:
{
if (sdl_event.caxis.axis != SDL_CONTROLLER_AXIS_INVALID)
{
if (auto it = gamepad_map.find(sdl_event.cdevice.which); it != gamepad_map.end())
{
// Map axis position onto `[-1, 1]`.
const float position = math::map
(
static_cast<float>(sdl_event.caxis.value),
static_cast<float>(std::numeric_limits<decltype(sdl_event.caxis.value)>::min()),
static_cast<float>(std::numeric_limits<decltype(sdl_event.caxis.value)>::max()),
-1.0f,
1.0f
);
// Generate gamepad axis moved event
it->second->move(static_cast<input::gamepad_axis>(sdl_event.caxis.axis), position);
}
}
break;
}
case SDL_CONTROLLERBUTTONDOWN:
{
if (sdl_event.cbutton.button != SDL_CONTROLLER_BUTTON_INVALID)
{
if (auto it = gamepad_map.find(sdl_event.cdevice.which); it != gamepad_map.end())
{
it->second->press(static_cast<input::gamepad_button>(sdl_event.cbutton.button));
}
}
break;
}
case SDL_CONTROLLERBUTTONUP:
{
if (sdl_event.cbutton.button != SDL_CONTROLLER_BUTTON_INVALID)
{
if (auto it = gamepad_map.find(sdl_event.cdevice.which); it != gamepad_map.end())
{
it->second->release(static_cast<input::gamepad_button>(sdl_event.cbutton.button));
}
}
break;
}
case SDL_WINDOWEVENT:
{
switch (sdl_event.window.event)
{
case SDL_WINDOWEVENT_SIZE_CHANGED:
{
// Query SDL window parameters
SDL_Window* sdl_window = SDL_GetWindowFromID(sdl_event.window.windowID);
const auto sdl_window_flags = SDL_GetWindowFlags(sdl_window);
int sdl_window_drawable_w = 0;
int sdl_window_drawable_h = 0;
SDL_GL_GetDrawableSize(sdl_window, &sdl_window_drawable_w, &sdl_window_drawable_h);
// Build window resized event
input::event::window_resized event;
event.window = nullptr;
event.size.x() = static_cast<std::int32_t>(sdl_event.window.data1);
event.size.y() = static_cast<std::int32_t>(sdl_event.window.data2);
event.maximized = sdl_window_flags & SDL_WINDOW_MAXIMIZED;
event.fullscreen = sdl_window_flags & SDL_WINDOW_FULLSCREEN;
event.viewport_size.x() = static_cast<std::int32_t>(sdl_window_drawable_w);
event.viewport_size.y() = static_cast<std::int32_t>(sdl_window_drawable_h);
// Update windowed size
if (!event.maximized && !event.fullscreen)
{
windowed_size = event.size;
}
// Update GL context size
rasterizer->context_resized(event.viewport_size.x(), event.viewport_size.y());
// Publish window resized event
window_resized_publisher.publish(event);
break;
}
case SDL_WINDOWEVENT_MOVED:
{
// Query SDL window parameters
SDL_Window* sdl_window = SDL_GetWindowFromID(sdl_event.window.windowID);
const auto sdl_window_flags = SDL_GetWindowFlags(sdl_window);
// Build window moved event
input::event::window_moved event;
event.window = nullptr;
event.position.x() = static_cast<std::int32_t>(sdl_event.window.data1);
event.position.y() = static_cast<std::int32_t>(sdl_event.window.data2);
event.maximized = sdl_window_flags & SDL_WINDOW_MAXIMIZED;
event.fullscreen = sdl_window_flags & SDL_WINDOW_FULLSCREEN;
// Update windowed position
if (!event.maximized && !event.fullscreen)
{
windowed_position = event.position;
}
// Publish window moved event
window_moved_publisher.publish(event);
break;
}
case SDL_WINDOWEVENT_FOCUS_GAINED:
// Build and publish window focused gained event
window_focus_changed_publisher.publish({nullptr, true});
break;
case SDL_WINDOWEVENT_FOCUS_LOST:
// Build and publish window focused lost event
window_focus_changed_publisher.publish({nullptr, false});
break;
case SDL_WINDOWEVENT_MAXIMIZED:
// Update window maximized
maximized = true;
// Build and publish window maximized event
window_maximized_publisher.publish({nullptr});
break;
case SDL_WINDOWEVENT_RESTORED:
// Update window maximized
maximized = false;
// Build and publish window restored event
window_restored_publisher.publish({nullptr});
break;
case SDL_WINDOWEVENT_MINIMIZED:
// Build and publish window minimized event
window_minimized_publisher.publish({nullptr});
break;
[[unlikely]] case SDL_WINDOWEVENT_CLOSE:
// Build and publish window closed event
window_closed_publisher.publish({nullptr});
break;
default:
break;
}
break;
}
[[unlikely]] case SDL_CONTROLLERDEVICEADDED:
{
if (SDL_IsGameController(sdl_event.cdevice.which))
{
SDL_GameController* sdl_controller = SDL_GameControllerOpen(sdl_event.cdevice.which);
const char* controller_name = SDL_GameControllerNameForIndex(sdl_event.cdevice.which);
if (sdl_controller)
{
if (auto it = gamepad_map.find(sdl_event.cdevice.which); it != gamepad_map.end())
{
// Gamepad reconnected
debug::log::info("Reconnected gamepad \"{}\"", controller_name);
it->second->connect();
}
else
{
// Get gamepad GUID
SDL_Joystick* sdl_joystick = SDL_GameControllerGetJoystick(sdl_controller);
SDL_JoystickGUID sdl_guid = SDL_JoystickGetGUID(sdl_joystick);
// Copy into UUID struct
::uuid gamepad_uuid;
std::memcpy(gamepad_uuid.data.data(), sdl_guid.data, gamepad_uuid.data.size());
debug::log::info("Connected gamepad \"{}\" with UUID {}", controller_name, gamepad_uuid.string());
// Create new gamepad
input::gamepad* gamepad = new input::gamepad();
gamepad->set_uuid(gamepad_uuid);
// Add gamepad to gamepad map
gamepad_map[sdl_event.cdevice.which] = gamepad;
// Register gamepad with device manager
device_manager.register_device(*gamepad);
// Generate gamepad connected event
gamepad->connect();
}
}
else
{
debug::log::error("Failed to connected gamepad \"{}\": {}", controller_name, SDL_GetError());
}
}
break;
}
[[unlikely]] case SDL_CONTROLLERDEVICEREMOVED:
{
SDL_GameController* sdl_controller = SDL_GameControllerFromInstanceID(sdl_event.cdevice.which);
if (sdl_controller)
{
const char* controller_name = SDL_GameControllerNameForIndex(sdl_event.cdevice.which);
SDL_GameControllerClose(sdl_controller);
if (auto it = gamepad_map.find(sdl_event.cdevice.which); it != gamepad_map.end())
{
it->second->disconnect();
}
debug::log::info("Disconnected gamepad \"{}\"", controller_name);
}
break;
}
[[unlikely]] case SDL_QUIT:
{
debug::log::info("Quit requested");
close();
break;
}
default:
break;
}
}
// Process accumulated mouse motion events
// if (mouse_motion)
// {
// mouse.move(mouse_x, mouse_y, mouse_dx, mouse_dy);
// }
}

+ 0
- 295
src/application.hpp View File

@ -1,295 +0,0 @@
/*
* Copyright (C) 2023 Christopher J. Howard
*
* This file is part of Antkeeper source code.
*
* Antkeeper source code is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Antkeeper source code is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Antkeeper source code. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef ANTKEEPER_APPLICATION_HPP
#define ANTKEEPER_APPLICATION_HPP
#include "event/publisher.hpp"
#include "gl/rasterizer.hpp"
#include "input/device-manager.hpp"
#include "input/event.hpp"
#include "input/gamepad.hpp"
#include "input/keyboard.hpp"
#include "input/mouse.hpp"
#include "utility/fundamental-types.hpp"
#include <list>
#include <memory>
#include <unordered_map>
// Forward declarations
typedef struct SDL_Window SDL_Window;
typedef void* SDL_GLContext;
/**
*
*/
class application
{
public:
/**
* Constructs and initializes an application.
*/
application
(
const std::string& window_title,
int window_x,
int window_y,
int window_w,
int window_h,
bool maximized,
bool fullscreen,
bool v_sync
);
/**
* Destructs an application.
*/
~application();
/**
* Requests the application to close.
*/
void close();
/**
* Sets the application window's title.
*
* @param title Window title.
*/
void set_title(const std::string& title);
/**
* Sets the cursor visibility.
*
* @param visible `true` if the cursor should be visible, `false` otherwise.
*/
void set_cursor_visible(bool visible);
/**
* Enables or disables relative mouse mode, in which only relative mouse movement events are generated.
*
* @param enabled `true` if relative mouse mode should be enabled, `false` otherwise.
*/
void set_relative_mouse_mode(bool enabled);
/**
* Resizes the application window.
*
* @param width Width of the window, in pixels.
* @param height Height of the window, in pixels.
*/
void resize_window(int width, int height);
/**
* Puts the application window into either fullscreen or window mode.
*
* @param fullscreen `true` if the window should be fullscreen, `false` if it should be window.
*/
void set_fullscreen(bool fullscreen);
/**
* Enables or disables v-sync mode.
*
* @param v_sync `true` if v-sync should be enabled, `false` otherwise.
*/
void set_v_sync(bool v_sync);
void set_window_opacity(float opacity);
void swap_buffers();
void show_window();
void hide_window();
void add_game_controller_mappings(const void* mappings, std::size_t size);
/// Returns the dimensions of the current display.
[[nodiscard]] const int2& get_display_size() const;
/// Returns the DPI of the display.
[[nodiscard]] float get_display_dpi() const;
/// Returns the position of the window when not maximized or fullscreen.
[[nodiscard]] const int2& get_windowed_position() const;
/// Returns the dimensions of the window when not maximized or fullscreen.
[[nodiscard]] const int2& get_windowed_size() const;
/// Returns the dimensions of the window's drawable viewport.
[[nodiscard]] const int2& get_viewport_size() const;
/// Returns `true` if the window is maximized, `false` otherwise.
[[nodiscard]] bool is_maximized() const;
/// Returns `true` if the window is in fullscreen mode, `false` otherwise.
[[nodiscard]] bool is_fullscreen() const;
/// Returns `true` if the v-sync is enabled, `false` otherwise.
[[nodiscard]] bool get_v_sync() const;
/// Returns the rasterizer for the window.
[[nodiscard]] gl::rasterizer* get_rasterizer();
void process_events();
[[nodiscard]] bool was_closed() const;
/**
* Returns the input device manager.
*/
/// @{
[[nodiscard]] inline const input::device_manager& get_device_manager() const noexcept
{
return device_manager;
}
[[nodiscard]] inline input::device_manager& get_device_manager() noexcept
{
return device_manager;
}
/// @}
/// Returns the channel through which window closed events are published.
[[nodiscard]] inline event::channel<input::event::window_closed>& get_window_closed_channel() noexcept
{
return window_closed_publisher.channel();
}
/// Returns the channel through which window focus changed events are published.
[[nodiscard]] inline event::channel<input::event::window_focus_changed>& get_window_focus_changed_channel() noexcept
{
return window_focus_changed_publisher.channel();
}
/// Returns the channel through which window moved events are published.
[[nodiscard]] inline event::channel<input::event::window_moved>& get_window_moved_channel() noexcept
{
return window_moved_publisher.channel();
}
/// Returns the channel through which window resized events are published.
[[nodiscard]] inline event::channel<input::event::window_resized>& get_window_resized_channel() noexcept
{
return window_resized_publisher.channel();
}
/// Returns the channel through which window maximized events are published.
[[nodiscard]] inline event::channel<input::event::window_maximized>& get_window_maximized_channel() noexcept
{
return window_maximized_publisher.channel();
}
/// Returns the channel through which window restored events are published.
[[nodiscard]] inline event::channel<input::event::window_restored>& get_window_restored_channel() noexcept
{
return window_restored_publisher.channel();
}
/// Returns the channel through which window minimized events are published.
[[nodiscard]] inline event::channel<input::event::window_minimized>& get_window_minimized_channel() noexcept
{
return window_minimized_publisher.channel();
}
private:
void window_moved();
void window_resized();
bool closed;
bool maximized;
bool fullscreen;
bool v_sync;
bool cursor_visible;
int2 display_size;
float display_dpi;
int2 windowed_position;
int2 windowed_size;
int2 viewport_size;
int2 mouse_position;
SDL_Window* sdl_window;
SDL_GLContext sdl_gl_context;
gl::rasterizer* rasterizer;
// Input devices
input::device_manager device_manager;
input::keyboard keyboard;
input::mouse mouse;
std::unordered_map<int, input::gamepad*> gamepad_map;
event::publisher<input::event::window_closed> window_closed_publisher;
event::publisher<input::event::window_focus_changed> window_focus_changed_publisher;
event::publisher<input::event::window_moved> window_moved_publisher;
event::publisher<input::event::window_resized> window_resized_publisher;
event::publisher<input::event::window_maximized> window_maximized_publisher;
event::publisher<input::event::window_restored> window_restored_publisher;
event::publisher<input::event::window_minimized> window_minimized_publisher;
};
inline const int2& application::get_display_size() const
{
return display_size;
}
inline float application::get_display_dpi() const
{
return display_dpi;
}
inline const int2& application::get_windowed_position() const
{
return windowed_position;
}
inline const int2& application::get_windowed_size() const
{
return windowed_size;
}
inline const int2& application::get_viewport_size() const
{
return viewport_size;
}
inline bool application::is_maximized() const
{
return maximized;
}
inline bool application::is_fullscreen() const
{
return fullscreen;
}
inline bool application::get_v_sync() const
{
return v_sync;
}
inline gl::rasterizer* application::get_rasterizer()
{
return rasterizer;
}
inline bool application::was_closed() const
{
return closed;
}
#endif // ANTKEEPER_APPLICATION_HPP

+ 15
- 10
src/game/context.hpp View File

@ -21,7 +21,8 @@
#define ANTKEEPER_GAME_CONTEXT_HPP
#include "animation/tween.hpp"
#include "application.hpp"
#include "app/window-manager.hpp"
#include "app/input-manager.hpp"
#include "debug/performance-sampler.hpp"
#include "entity/id.hpp"
#include "entity/registry.hpp"
@ -43,7 +44,7 @@
#include "render/material.hpp"
#include "resources/json.hpp"
#include "scene/scene.hpp"
#include "state-machine.hpp"
#include "utility/state-machine.hpp"
#include "type/bitmap-font.hpp"
#include "type/typeface.hpp"
#include "utility/dict.hpp"
@ -64,8 +65,6 @@
// Forward declarations
class animator;
class application;
class resource_manager;
class screen_transition;
class timeline;
@ -126,23 +125,29 @@ struct context
// Configuration
dict<std::uint32_t>* settings;
/// Hierarchichal state machine
// Window creation, events, and management
app::window_manager* window_manager;
app::window* window;
bool closed;
std::shared_ptr<::event::subscription> window_closed_subscription;
// Input devices and events
app::input_manager* input_manager;
// Hierarchichal state machine
hsm::state_machine<game::state::base> state_machine;
std::function<void()> resume_callback;
/// Debugging
// Debugging
debug::performance_sampler performance_sampler;
debug::cli* cli;
/// Queue for scheduling "next frame" function calls
// Queue for scheduling "next frame" function calls
std::queue<std::function<void()>> function_queue;
// Parallel processes
std::unordered_map<std::string, std::function<void(double, double)>> processes;
/// Interface for window management and input events
application* app;
// Controls
input::mapper input_mapper;
std::forward_list<std::shared_ptr<::event::subscription>> control_subscriptions;

+ 3
- 4
src/game/controls.cpp View File

@ -20,7 +20,6 @@
#include "game/controls.hpp"
#include "resources/resource-manager.hpp"
#include "resources/json.hpp"
#include "application.hpp"
#include "game/component/transform.hpp"
#include "game/component/constraint/constraint.hpp"
#include "game/component/constraint-stack.hpp"
@ -147,8 +146,8 @@ void apply_control_profile(game::context& ctx, const json& profile)
}
// Get keyboard and mouse devices
input::keyboard* keyboard = ctx.app->get_device_manager().get_keyboards().front();
input::mouse* mouse = ctx.app->get_device_manager().get_mice().front();
input::keyboard* keyboard = ctx.input_manager->get_keyboards().front();
input::mouse* mouse = ctx.input_manager->get_mice().front();
// Find profile gamepad device
input::gamepad* gamepad = nullptr;
@ -159,7 +158,7 @@ void apply_control_profile(game::context& ctx, const json& profile)
const std::string uuid_string = gamepad_element->get<std::string>();
// Find gamepad with matching UUID
for (input::gamepad* device: ctx.app->get_device_manager().get_gamepads())
for (input::gamepad* device: ctx.input_manager->get_gamepads())
{
if (device->get_uuid().string() == uuid_string)
{

+ 2
- 2
src/game/fonts.cpp View File

@ -18,7 +18,6 @@
*/
#include "game/fonts.hpp"
#include "application.hpp"
#include "type/type.hpp"
#include "resources/resource-manager.hpp"
#include "gl/texture-wrapping.hpp"
@ -126,7 +125,8 @@ void load_fonts(game::context& ctx)
gl::shader_program* bitmap_font_shader = ctx.resource_manager->load<gl::shader_program>("bitmap-font.glsl");
// Point size to pixel size conversion factor
const float pt_to_px = (ctx.app->get_display_dpi() / 72.0f) * ctx.font_scale;
const float dpi = ctx.window_manager->get_display(0).get_dpi();
const float pt_to_px = (dpi / 72.0f) * ctx.font_scale;
// Build debug font
if (auto it = ctx.typefaces.find("monospace"); it != ctx.typefaces.end())

+ 5
- 5
src/game/graphics.cpp View File

@ -45,7 +45,7 @@ void create_framebuffers(game::context& ctx)
debug::log::trace("Creating framebuffers...");
// Calculate render resolution
const int2& viewport_size = ctx.app->get_viewport_size();
const int2& viewport_size = ctx.window->get_viewport_size();
ctx.render_resolution = {static_cast<int>(viewport_size.x() * ctx.render_scale + 0.5f), static_cast<int>(viewport_size.y() * ctx.render_scale + 0.5f)};
// Create HDR framebuffer (32F color, 32F depth)
@ -128,7 +128,7 @@ void change_render_resolution(game::context& ctx, float scale)
ctx.render_scale = scale;
// Recalculate render resolution
const int2& viewport_size = ctx.app->get_viewport_size();
const int2& viewport_size = ctx.window->get_viewport_size();
ctx.render_resolution = {static_cast<int>(viewport_size.x() * ctx.render_scale + 0.5f), static_cast<int>(viewport_size.y() * ctx.render_scale + 0.5f)};
// Resize HDR framebuffer and attachments
@ -171,7 +171,7 @@ void save_screenshot(game::context& ctx)
debug::log::debug("Saving screenshot to \"{}\"...", screenshot_filepath_string);
// Get viewport dimensions
const int2& viewport_size = ctx.app->get_viewport_size();
const int2& viewport_size = ctx.window->get_viewport_size();
// Allocate screenshot image
std::shared_ptr<image> frame = std::make_shared<image>();
@ -231,7 +231,7 @@ void reroute_framebuffers(game::context& ctx)
else
{
ctx.common_final_pass->set_framebuffer(ctx.ldr_framebuffer_a);
ctx.fxaa_pass->set_framebuffer(&ctx.rasterizer->get_default_framebuffer());
ctx.fxaa_pass->set_framebuffer(&ctx.window->get_rasterizer()->get_default_framebuffer());
}
}
else
@ -242,7 +242,7 @@ void reroute_framebuffers(game::context& ctx)
}
else
{
ctx.common_final_pass->set_framebuffer(&ctx.rasterizer->get_default_framebuffer());
ctx.common_final_pass->set_framebuffer(&ctx.window->get_rasterizer()->get_default_framebuffer());
}
}
}

+ 2
- 3
src/game/menu.cpp View File

@ -19,7 +19,6 @@
#include "game/menu.hpp"
#include "scene/text.hpp"
#include "application.hpp"
#include "animation/animation.hpp"
#include "animation/animator.hpp"
#include "animation/ease.hpp"
@ -384,7 +383,7 @@ void setup_controls(game::context& ctx)
max_x += padding;
max_y += padding;
const auto& viewport = ctx.app->get_viewport_dimensions();
const auto& viewport = ctx.window->get_viewport_size();
const float x = static_cast<float>(event.x - viewport[0] / 2);
const float y = static_cast<float>((viewport[1] - event.y + 1) - viewport[1] / 2);
@ -430,7 +429,7 @@ void setup_controls(game::context& ctx)
max_x += padding;
max_y += padding;
const auto& viewport = ctx.app->get_viewport_dimensions();
const auto& viewport = ctx.window->get_viewport_size();
const float x = static_cast<float>(event.x - viewport[0] / 2);
const float y = static_cast<float>((viewport[1] - event.y + 1) - viewport[1] / 2);

+ 139
- 118
src/game/state/boot.cpp View File

@ -22,7 +22,6 @@
#include "animation/ease.hpp"
#include "animation/screen-transition.hpp"
#include "animation/timeline.hpp"
#include "application.hpp"
#include "color/color.hpp"
#include "config.hpp"
#include "debug/cli.hpp"
@ -109,49 +108,11 @@ boot::boot(game::context& ctx, int argc, char** argv):
// Boot process
debug::log::trace("Booting up...");
// Parse command line arguments
parse_arguments(argc, argv);
// Setup resource management
setup_resources();
// Load settings
load_settings();
// Default window settings
std::string window_title = config::application_name;
int window_x = -1;
int window_y = -1;
int window_w = -1;
int window_h = -1;
bool maximized = true;
bool fullscreen = true;
bool v_sync = true;
// Read window settings
read_or_write_setting(ctx, "window_title"_fnv1a32, window_title);
read_or_write_setting(ctx, "window_x"_fnv1a32, window_x);
read_or_write_setting(ctx, "window_y"_fnv1a32, window_y);
read_or_write_setting(ctx, "window_w"_fnv1a32, window_w);
read_or_write_setting(ctx, "window_h"_fnv1a32, window_h);
read_or_write_setting(ctx, "maximized"_fnv1a32, maximized);
read_or_write_setting(ctx, "fullscreen"_fnv1a32, fullscreen);
read_or_write_setting(ctx, "v_sync"_fnv1a32, v_sync);
// Allocate application
ctx.app = new application
(
window_title,
window_x,
window_y,
window_w,
window_h,
maximized,
fullscreen,
v_sync
);
setup_window();
setup_input();
load_strings();
setup_rendering();
setup_audio();
@ -163,6 +124,7 @@ boot::boot(game::context& ctx, int argc, char** argv):
setup_ui();
setup_debugging();
setup_loop();
ctx.active_ecoregion = nullptr;
debug::log::trace("Boot up complete");
@ -180,10 +142,10 @@ boot::~boot()
debug::log::trace("Booting down...");
// Update window settings
const auto& windowed_position = ctx.app->get_windowed_position();
const auto& windowed_size = ctx.app->get_windowed_size();
const bool maximized = ctx.app->is_maximized();
const bool fullscreen = ctx.app->is_fullscreen();
const auto& windowed_position = ctx.window->get_windowed_position();
const auto& windowed_size = ctx.window->get_windowed_size();
const bool maximized = ctx.window->is_maximized();
const bool fullscreen = ctx.window->is_fullscreen();
(*ctx.settings)["window_x"_fnv1a32] = windowed_position.x();
(*ctx.settings)["window_y"_fnv1a32] = windowed_position.y();
(*ctx.settings)["window_w"_fnv1a32] = windowed_size.x();
@ -194,11 +156,12 @@ boot::~boot()
// Save settings
ctx.resource_manager->save<dict<std::uint32_t>>(ctx.settings, "settings.cfg");
shutdown_audio();
// Destruct input and window managers
delete ctx.input_manager;
delete ctx.window_manager;
// Close application
delete ctx.app;
ctx.app = nullptr;
// Shut down audio
shutdown_audio();
debug::log::trace("Boot down complete");
}
@ -261,16 +224,6 @@ void boot::parse_arguments(int argc, char** argv)
}
}
void boot::load_settings()
{
ctx.settings = ctx.resource_manager->load<dict<std::uint32_t>>("settings.cfg");
if (!ctx.settings)
{
debug::log::info("Settings not found");
ctx.settings = new dict<std::uint32_t>();
}
}
void boot::setup_resources()
{
// Setup resource manager
@ -363,18 +316,85 @@ void boot::setup_resources()
ctx.resource_manager->include("/");
}
void boot::load_settings()
{
ctx.settings = ctx.resource_manager->load<dict<std::uint32_t>>("settings.cfg");
if (!ctx.settings)
{
debug::log::info("Settings not found");
ctx.settings = new dict<std::uint32_t>();
}
}
void boot::setup_window()
{
// debug::log::trace("Setting up window...");
// Construct window manager
ctx.window_manager = app::window_manager::instance();
// Default window settings
std::string window_title = config::application_name;
int window_x = -1;
int window_y = -1;
int window_w = -1;
int window_h = -1;
bool maximized = true;
bool fullscreen = true;
bool v_sync = true;
// Read window settings
bool resize = false;
read_or_write_setting(ctx, "window_title"_fnv1a32, window_title);
read_or_write_setting(ctx, "window_x"_fnv1a32, window_x);
read_or_write_setting(ctx, "window_y"_fnv1a32, window_y);
if (!read_or_write_setting(ctx, "window_w"_fnv1a32, window_w) ||
!read_or_write_setting(ctx, "window_h"_fnv1a32, window_h))
{
resize = true;
}
read_or_write_setting(ctx, "maximized"_fnv1a32, maximized);
read_or_write_setting(ctx, "fullscreen"_fnv1a32, fullscreen);
read_or_write_setting(ctx, "v_sync"_fnv1a32, v_sync);
// application* app = ctx.app;
// If window size not set, resize and reposition relative to default display
if (resize)
{
const app::display& display = ctx.window_manager->get_display(0);
const float windowed_size_scale = 1.0f / 1.2f;
window_w = static_cast<int>(display.get_size().x() * windowed_size_scale);
window_h = static_cast<int>(display.get_size().y() * windowed_size_scale);
window_x = display.get_size().x() / 2 - window_w / 2;
window_y = display.get_size().y() / 2 - window_h / 2;
}
// Construct window
ctx.window = ctx.window_manager->create_window
(
window_title,
{window_x, window_y},
{window_w, window_h},
maximized,
fullscreen,
v_sync
);
// ctx.app->get_rasterizer()->set_clear_color(0.0f, 0.0f, 0.0f, 1.0f);
// ctx.app->get_rasterizer()->clear_framebuffer(true, false, false);
// app->show_window();
// ctx.app->swap_buffers();
// Restrict window size
ctx.window->set_minimum_size({160, 144});
// debug::log::trace("Set up window");
// Setup window closed callback
ctx.window_closed_subscription = ctx.window->get_closed_channel().subscribe
(
[&](const auto& event)
{
ctx.closed = true;
}
);
}
void boot::setup_input()
{
// Construct input manager
ctx.input_manager = app::input_manager::instance();
}
void boot::load_strings()
@ -409,7 +429,7 @@ void boot::load_strings()
// Change window title
const std::string window_title = get_string(ctx, "application_title"_fnv1a32);
ctx.app->set_title(window_title);
ctx.window->set_title(window_title);
// Update window title setting
(*ctx.settings)["window_title"_fnv1a32] = window_title;
@ -431,9 +451,6 @@ void boot::setup_rendering()
read_or_write_setting(ctx, "anti_aliasing_method"_fnv1a32, *reinterpret_cast<std::underlying_type_t<render::anti_aliasing_method>*>(&ctx.anti_aliasing_method));
read_or_write_setting(ctx, "shadow_map_resolution"_fnv1a32, ctx.shadow_map_resolution);
// Get rasterizer from application
ctx.rasterizer = ctx.app->get_rasterizer();
// Create framebuffers
game::graphics::create_framebuffers(ctx);
@ -446,21 +463,21 @@ void boot::setup_rendering()
// Setup common render passes
{
// Construct bloom pass
ctx.bloom_pass = new render::bloom_pass(ctx.rasterizer, ctx.resource_manager);
ctx.bloom_pass = new render::bloom_pass(ctx.window->get_rasterizer(), ctx.resource_manager);
ctx.bloom_pass->set_source_texture(ctx.hdr_color_texture);
ctx.bloom_pass->set_mip_chain_length(0);
ctx.bloom_pass->set_filter_radius(0.005f);
ctx.common_final_pass = new render::final_pass(ctx.rasterizer, ctx.ldr_framebuffer_a, ctx.resource_manager);
ctx.common_final_pass = new render::final_pass(ctx.window->get_rasterizer(), ctx.ldr_framebuffer_a, ctx.resource_manager);
ctx.common_final_pass->set_color_texture(ctx.hdr_color_texture);
ctx.common_final_pass->set_bloom_texture(ctx.bloom_pass->get_bloom_texture());
ctx.common_final_pass->set_bloom_weight(0.04f);
ctx.common_final_pass->set_blue_noise_texture(blue_noise_map);
ctx.fxaa_pass = new render::fxaa_pass(ctx.rasterizer, &ctx.rasterizer->get_default_framebuffer(), ctx.resource_manager);
ctx.fxaa_pass = new render::fxaa_pass(ctx.window->get_rasterizer(), &ctx.window->get_rasterizer()->get_default_framebuffer(), ctx.resource_manager);
ctx.fxaa_pass->set_source_texture(ctx.ldr_color_texture_a);
ctx.resample_pass = new render::resample_pass(ctx.rasterizer, &ctx.rasterizer->get_default_framebuffer(), ctx.resource_manager);
ctx.resample_pass = new render::resample_pass(ctx.window->get_rasterizer(), &ctx.window->get_rasterizer()->get_default_framebuffer(), ctx.resource_manager);
ctx.resample_pass->set_source_texture(ctx.ldr_color_texture_b);
ctx.resample_pass->set_enabled(false);
@ -473,11 +490,11 @@ void boot::setup_rendering()
// Setup UI compositor
{
ctx.ui_clear_pass = new render::clear_pass(ctx.rasterizer, &ctx.rasterizer->get_default_framebuffer());
ctx.ui_clear_pass = new render::clear_pass(ctx.window->get_rasterizer(), &ctx.window->get_rasterizer()->get_default_framebuffer());
ctx.ui_clear_pass->set_cleared_buffers(false, true, false);
ctx.ui_clear_pass->set_clear_depth(-1.0f);
ctx.ui_material_pass = new render::material_pass(ctx.rasterizer, &ctx.rasterizer->get_default_framebuffer(), ctx.resource_manager);
ctx.ui_material_pass = new render::material_pass(ctx.window->get_rasterizer(), &ctx.window->get_rasterizer()->get_default_framebuffer(), ctx.resource_manager);
ctx.ui_material_pass->set_fallback_material(ctx.fallback_material);
ctx.ui_compositor = new render::compositor();
@ -487,12 +504,12 @@ void boot::setup_rendering()
// Setup underground compositor
{
ctx.underground_clear_pass = new render::clear_pass(ctx.rasterizer, ctx.hdr_framebuffer);
ctx.underground_clear_pass = new render::clear_pass(ctx.window->get_rasterizer(), ctx.hdr_framebuffer);
ctx.underground_clear_pass->set_cleared_buffers(true, true, false);
ctx.underground_clear_pass->set_clear_color({1, 0, 1, 0});
ctx.underground_clear_pass->set_clear_depth(-1.0f);
ctx.underground_material_pass = new render::material_pass(ctx.rasterizer, ctx.hdr_framebuffer, ctx.resource_manager);
ctx.underground_material_pass = new render::material_pass(ctx.window->get_rasterizer(), ctx.hdr_framebuffer, ctx.resource_manager);
ctx.underground_material_pass->set_fallback_material(ctx.fallback_material);
ctx.underground_compositor = new render::compositor();
@ -506,27 +523,27 @@ void boot::setup_rendering()
// Setup surface compositor
{
ctx.surface_shadow_map_clear_pass = new render::clear_pass(ctx.rasterizer, ctx.shadow_map_framebuffer);
ctx.surface_shadow_map_clear_pass = new render::clear_pass(ctx.window->get_rasterizer(), ctx.shadow_map_framebuffer);
ctx.surface_shadow_map_clear_pass->set_cleared_buffers(false, true, false);
ctx.surface_shadow_map_clear_pass->set_clear_depth(1.0f);
ctx.surface_shadow_map_pass = new render::shadow_map_pass(ctx.rasterizer, ctx.resource_manager);
ctx.surface_shadow_map_pass = new render::shadow_map_pass(ctx.window->get_rasterizer(), ctx.resource_manager);
ctx.surface_clear_pass = new render::clear_pass(ctx.rasterizer, ctx.hdr_framebuffer);
ctx.surface_clear_pass = new render::clear_pass(ctx.window->get_rasterizer(), ctx.hdr_framebuffer);
ctx.surface_clear_pass->set_cleared_buffers(false, true, true);
ctx.surface_clear_pass->set_clear_depth(-1.0f);
ctx.sky_pass = new render::sky_pass(ctx.rasterizer, ctx.hdr_framebuffer, ctx.resource_manager);
ctx.sky_pass = new render::sky_pass(ctx.window->get_rasterizer(), ctx.hdr_framebuffer, ctx.resource_manager);
ctx.sky_pass->set_enabled(false);
ctx.sky_pass->set_magnification(3.0f);
ctx.ground_pass = new render::ground_pass(ctx.rasterizer, ctx.hdr_framebuffer, ctx.resource_manager);
ctx.ground_pass = new render::ground_pass(ctx.window->get_rasterizer(), ctx.hdr_framebuffer, ctx.resource_manager);
ctx.ground_pass->set_enabled(false);
ctx.surface_material_pass = new render::material_pass(ctx.rasterizer, ctx.hdr_framebuffer, ctx.resource_manager);
ctx.surface_material_pass = new render::material_pass(ctx.window->get_rasterizer(), ctx.hdr_framebuffer, ctx.resource_manager);
ctx.surface_material_pass->set_fallback_material(ctx.fallback_material);
ctx.surface_outline_pass = new render::outline_pass(ctx.rasterizer, ctx.hdr_framebuffer, ctx.resource_manager);
ctx.surface_outline_pass = new render::outline_pass(ctx.window->get_rasterizer(), ctx.hdr_framebuffer, ctx.resource_manager);
ctx.surface_outline_pass->set_outline_width(0.25f);
ctx.surface_outline_pass->set_outline_color(float4{1.0f, 1.0f, 1.0f, 1.0f});
@ -685,7 +702,7 @@ void boot::setup_scenes()
debug::log::trace("Setting up scenes...");
// Get default framebuffer
const auto& viewport_size = ctx.app->get_viewport_size();
const auto& viewport_size = ctx.window->get_viewport_size();
const float viewport_aspect_ratio = static_cast<float>(viewport_size[0]) / static_cast<float>(viewport_size[1]);
// Setup UI camera
@ -883,7 +900,7 @@ void boot::setup_entities()
void boot::setup_systems()
{
const auto& viewport_size = ctx.app->get_viewport_size();
const auto& viewport_size = ctx.window->get_viewport_size();
float4 viewport = {0.0f, 0.0f, static_cast<float>(viewport_size[0]), static_cast<float>(viewport_size[1])};
// Setup terrain system
@ -960,19 +977,19 @@ void boot::setup_systems()
void boot::setup_controls()
{
// Load SDL game controller mappings database
debug::log::trace("Loading SDL game controller mappings...");
file_buffer* game_controller_db = ctx.resource_manager->load<file_buffer>("gamecontrollerdb.txt");
if (!game_controller_db)
{
debug::log::error("Failed to load SDL game controller mappings");
}
else
{
ctx.app->add_game_controller_mappings(game_controller_db->data(), game_controller_db->size());
debug::log::trace("Loaded SDL game controller mappings");
// debug::log::trace("Loading SDL game controller mappings...");
// file_buffer* game_controller_db = ctx.resource_manager->load<file_buffer>("gamecontrollerdb.txt");
// if (!game_controller_db)
// {
// debug::log::error("Failed to load SDL game controller mappings");
// }
// else
// {
// ctx.app->add_game_controller_mappings(game_controller_db->data(), game_controller_db->size());
// debug::log::trace("Loaded SDL game controller mappings");
ctx.resource_manager->unload("gamecontrollerdb.txt");
}
// ctx.resource_manager->unload("gamecontrollerdb.txt");
// }
// Load controls
debug::log::trace("Loading controls...");
@ -1023,10 +1040,10 @@ void boot::setup_controls()
(
[&ctx = this->ctx](const auto& event)
{
bool fullscreen = !ctx.app->is_fullscreen();
bool fullscreen = !ctx.window->is_fullscreen();
// Toggle fullscreen
ctx.app->set_fullscreen(fullscreen);
ctx.window->set_fullscreen(fullscreen);
// Update fullscreen setting
(*ctx.settings)["fullscreen"_fnv1a32] = fullscreen;
@ -1055,7 +1072,7 @@ void boot::setup_controls()
// Map and enable window controls
ctx.window_controls.add_mapping(ctx.fullscreen_control, input::key_mapping(nullptr, input::scancode::f11, false));
ctx.window_controls.add_mapping(ctx.screenshot_control, input::key_mapping(nullptr, input::scancode::f12, false));
ctx.window_controls.connect(ctx.app->get_device_manager().get_event_queue());
ctx.window_controls.connect(ctx.input_manager->get_event_queue());
// Set activation threshold for menu navigation controls to mitigate drifting gamepad axes
auto menu_control_threshold = [](float x) -> bool
@ -1104,20 +1121,22 @@ void boot::setup_ui()
}
// Setup UI resize handler
ctx.ui_resize_subscription = ctx.app->get_window_resized_channel().subscribe
(
[&](const auto& event)
{
const float clip_left = static_cast<float>(event.viewport_size.x()) * -0.5f;
const float clip_right = static_cast<float>(event.viewport_size.x()) * 0.5f;
const float clip_top = static_cast<float>(event.viewport_size.y()) * -0.5f;
const float clip_bottom = static_cast<float>(event.viewport_size.y()) * 0.5f;
const float clip_near = ctx.ui_camera->get_clip_near();
const float clip_far = ctx.ui_camera->get_clip_far();
// ctx.ui_resize_subscription = ctx.window_manager->get_event_queue().subscribe<app::window_resized_event>
// (
// [&](const auto& event)
// {
// const auto& viewport_size = event.window->get_viewport_size();
ctx.ui_camera->set_orthographic(clip_left, clip_right, clip_top, clip_bottom, clip_near, clip_far);
}
);
// const float clip_left = static_cast<float>(viewport_size.x()) * -0.5f;
// const float clip_right = static_cast<float>(viewport_size.x()) * 0.5f;
// const float clip_top = static_cast<float>(viewport_size.y()) * -0.5f;
// const float clip_bottom = static_cast<float>(viewport_size.y()) * 0.5f;
// const float clip_near = ctx.ui_camera->get_clip_near();
// const float clip_far = ctx.ui_camera->get_clip_far();
// ctx.ui_camera->set_orthographic(clip_left, clip_right, clip_top, clip_bottom, clip_near, clip_far);
// }
// );
}
void boot::setup_debugging()
@ -1152,8 +1171,8 @@ void boot::setup_loop()
ctx.ui_scene->update_tweens();
// Process events
ctx.app->process_events();
ctx.app->get_device_manager().get_event_queue().flush();
ctx.window_manager->update();
ctx.input_manager->update();
// Process function queue
while (!ctx.function_queue.empty())
@ -1204,14 +1223,16 @@ void boot::setup_loop()
[&ctx = this->ctx](double alpha)
{
ctx.render_system->draw(alpha);
ctx.app->swap_buffers();
ctx.window->swap_buffers();
}
);
}
void boot::loop()
{
while (!ctx.app->was_closed())
ctx.closed = false;
while (!ctx.closed)
{
// Execute main loop
ctx.loop.tick();

+ 2
- 1
src/game/state/boot.hpp View File

@ -51,8 +51,9 @@ private:
void parse_arguments(int argc, char** argv);
void setup_resources();
void load_settings();
void load_strings();
void setup_window();
void setup_input();
void load_strings();
void setup_rendering();
void setup_audio();
void setup_scenes();

+ 0
- 1
src/game/state/controls-menu.cpp View File

@ -21,7 +21,6 @@
#include "game/state/keyboard-config-menu.hpp"
#include "game/state/gamepad-config-menu.hpp"
#include "game/state/options-menu.hpp"
#include "application.hpp"
#include "scene/text.hpp"
#include "debug/log.hpp"
#include "game/menu.hpp"

+ 1
- 2
src/game/state/credits.cpp View File

@ -23,7 +23,6 @@
#include "animation/ease.hpp"
#include "animation/animation.hpp"
#include "animation/animator.hpp"
#include "application.hpp"
#include "scene/text.hpp"
#include "debug/log.hpp"
#include "game/strings.hpp"
@ -94,7 +93,7 @@ credits::credits(game::context& ctx):
}
}
);
ctx.input_mapper.connect(ctx.app->get_device_manager().get_event_queue());
ctx.input_mapper.connect(ctx.input_manager->get_event_queue());
ctx.ui_scene->add_object(&credits_text);

+ 0
- 1
src/game/state/extras-menu.cpp View File

@ -20,7 +20,6 @@
#include "game/state/extras-menu.hpp"
#include "game/state/main-menu.hpp"
#include "game/state/credits.hpp"
#include "application.hpp"
#include "scene/text.hpp"
#include "debug/log.hpp"
#include "game/fonts.hpp"

+ 0
- 1
src/game/state/gamepad-config-menu.cpp View File

@ -20,7 +20,6 @@
#include "game/state/gamepad-config-menu.hpp"
#include "game/state/controls-menu.hpp"
#include "game/context.hpp"
#include "application.hpp"
#include "scene/text.hpp"
#include "debug/log.hpp"
#include "resources/resource-manager.hpp"

+ 6
- 7
src/game/state/graphics-menu.cpp View File

@ -19,7 +19,6 @@
#include "game/state/graphics-menu.hpp"
#include "game/state/options-menu.hpp"
#include "application.hpp"
#include "scene/text.hpp"
#include "debug/log.hpp"
#include "game/fonts.hpp"
@ -88,9 +87,9 @@ graphics_menu::graphics_menu(game::context& ctx):
// Construct menu item callbacks
auto toggle_fullscreen_callback = [this, &ctx]()
{
bool fullscreen = !ctx.app->is_fullscreen();
bool fullscreen = !ctx.window->is_fullscreen();
ctx.app->set_fullscreen(fullscreen);
ctx.window->set_fullscreen(fullscreen);
this->update_value_text_content();
@ -151,12 +150,12 @@ graphics_menu::graphics_menu(game::context& ctx):
auto toggle_v_sync_callback = [this, &ctx]()
{
bool v_sync = !ctx.app->get_v_sync();
bool v_sync = !ctx.window->get_v_sync();
// Update v-sync setting
(*ctx.settings)["v_sync"_fnv1a32] = v_sync;
ctx.app->set_v_sync(v_sync);
ctx.window->set_v_sync(v_sync);
this->update_value_text_content();
game::menu::align_text(ctx);
@ -373,9 +372,9 @@ graphics_menu::~graphics_menu()
void graphics_menu::update_value_text_content()
{
const bool fullscreen = ctx.app->is_fullscreen();
const bool fullscreen = ctx.window->is_fullscreen();
const float render_scale = ctx.render_scale;
const bool v_sync = ctx.app->get_v_sync();
const bool v_sync = ctx.window->get_v_sync();
const int aa_method_index = static_cast<int>(ctx.anti_aliasing_method);
const float font_scale = ctx.font_scale;
const bool dyslexia_font = ctx.dyslexia_font;

+ 0
- 1
src/game/state/keyboard-config-menu.cpp View File

@ -19,7 +19,6 @@
#include "game/state/keyboard-config-menu.hpp"
#include "game/state/controls-menu.hpp"
#include "application.hpp"
#include "scene/text.hpp"
#include "debug/log.hpp"
#include "resources/resource-manager.hpp"

+ 0
- 1
src/game/state/language-menu.cpp View File

@ -19,7 +19,6 @@
#include "game/state/language-menu.hpp"
#include "game/state/options-menu.hpp"
#include "application.hpp"
#include "scene/text.hpp"
#include "debug/log.hpp"
#include "game/fonts.hpp"

+ 4
- 5
src/game/state/main-menu.cpp View File

@ -34,7 +34,6 @@
#include "animation/animator.hpp"
#include "animation/screen-transition.hpp"
#include "animation/ease.hpp"
#include "application.hpp"
#include "config.hpp"
#include "physics/light/exposure.hpp"
#include "game/component/model.hpp"
@ -67,7 +66,7 @@ main_menu::main_menu(game::context& ctx, bool fade_in):
const auto& title_aabb = static_cast<const geom::aabb<float>&>(title_text.get_local_bounds());
float title_w = title_aabb.max_point.x() - title_aabb.min_point.x();
float title_h = title_aabb.max_point.y() - title_aabb.min_point.y();
title_text.set_translation({std::round(-title_w * 0.5f), std::round(-title_h * 0.5f + (ctx.app->get_viewport_size().y() / 3.0f) / 2.0f), 0.0f});
title_text.set_translation({std::round(-title_w * 0.5f), std::round(-title_h * 0.5f + (ctx.window->get_viewport_size().y() / 3.0f) / 2.0f), 0.0f});
title_text.update_tweens();
// Add title text to UI
@ -110,7 +109,7 @@ main_menu::main_menu(game::context& ctx, bool fade_in):
game::menu::update_text_color(ctx);
game::menu::update_text_font(ctx);
game::menu::align_text(ctx, true, false, (-ctx.app->get_viewport_size().y() / 3.0f) / 2.0f);
game::menu::align_text(ctx, true, false, (-ctx.window->get_viewport_size().y() / 3.0f) / 2.0f);
game::menu::update_text_tweens(ctx);
game::menu::add_text_to_ui(ctx);
game::menu::setup_animations(ctx);
@ -208,7 +207,7 @@ main_menu::main_menu(game::context& ctx, bool fade_in):
game::menu::fade_out(ctx, nullptr);
// Fade to black then quit
ctx.fade_transition->transition(config::quit_fade_out_duration, false, ease<float>::out_cubic, false, std::bind(&application::close, ctx.app));
ctx.fade_transition->transition(config::quit_fade_out_duration, false, ease<float>::out_cubic, false, [&ctx](){ctx.closed=true;});
};
// Build list of menu select callbacks
@ -264,7 +263,7 @@ main_menu::main_menu(game::context& ctx, bool fade_in):
const float ev100_sunny16 = physics::light::ev::from_settings(16.0f, 1.0f / 100.0f, 100.0f);
ctx.surface_camera->set_exposure(ev100_sunny16);
const auto& viewport_size = ctx.app->get_viewport_size();
const auto& viewport_size = ctx.window->get_viewport_size();
const float aspect_ratio = static_cast<float>(viewport_size[0]) / static_cast<float>(viewport_size[1]);
float fov = math::vertical_fov(math::radians(100.0f), aspect_ratio);

+ 2
- 4
src/game/state/nest-selection.cpp View File

@ -40,14 +40,12 @@
#include "animation/ease.hpp"
#include "resources/resource-manager.hpp"
#include "game/world.hpp"
#include "application.hpp"
#include "render/passes/clear-pass.hpp"
#include "render/passes/ground-pass.hpp"
#include "state-machine.hpp"
#include "utility/state-machine.hpp"
#include "config.hpp"
#include "math/interpolation.hpp"
#include "physics/light/exposure.hpp"
#include "application.hpp"
#include "input/mouse.hpp"
#include "math/projection.hpp"
@ -126,7 +124,7 @@ nest_selection::nest_selection(game::context& ctx):
const float ev100_sunny16 = physics::light::ev::from_settings(16.0f, 1.0f / 100.0f, 100.0f);
ctx.surface_camera->set_exposure(ev100_sunny16);
const auto& viewport_size = ctx.app->get_viewport_size();
const auto& viewport_size = ctx.window->get_viewport_size();
const float aspect_ratio = static_cast<float>(viewport_size[0]) / static_cast<float>(viewport_size[1]);
// Init first person camera rig parameters

+ 3
- 5
src/game/state/nuptial-flight.cpp View File

@ -43,15 +43,13 @@
#include "animation/ease.hpp"
#include "resources/resource-manager.hpp"
#include "game/world.hpp"
#include "application.hpp"
#include "render/passes/clear-pass.hpp"
#include "render/passes/ground-pass.hpp"
#include "state-machine.hpp"
#include "utility/state-machine.hpp"
#include "config.hpp"
#include "math/interpolation.hpp"
#include "physics/light/exposure.hpp"
#include "color/color.hpp"
#include "application.hpp"
#include "input/mouse.hpp"
namespace game {
@ -100,7 +98,7 @@ nuptial_flight::nuptial_flight(game::context& ctx):
const float ev100_sunny16 = physics::light::ev::from_settings(16.0f, 1.0f / 100.0f, 100.0f);
ctx.surface_camera->set_exposure(ev100_sunny16);
const auto& viewport_size = ctx.app->get_viewport_size();
const auto& viewport_size = ctx.window->get_viewport_size();
const float aspect_ratio = static_cast<float>(viewport_size[0]) / static_cast<float>(viewport_size[1]);
// Init camera rig params
@ -613,7 +611,7 @@ void nuptial_flight::enable_controls()
auto [mouse_x, mouse_y] = ctx.app->get_mouse()->get_current_position();
// Get window viewport size
const auto viewport_size = ctx.app->viewport_size();
const auto viewport_size = ctx.window->get_viewport_size();
// Transform mouse coordinates from window space to NDC space
const float2 mouse_ndc =

+ 0
- 1
src/game/state/options-menu.cpp View File

@ -28,7 +28,6 @@
#include "animation/ease.hpp"
#include "animation/animation.hpp"
#include "animation/animator.hpp"
#include "application.hpp"
#include "scene/text.hpp"
#include "debug/log.hpp"
#include "game/strings.hpp"

+ 1
- 2
src/game/state/pause-menu.cpp View File

@ -25,7 +25,6 @@
#include "animation/ease.hpp"
#include "animation/animation.hpp"
#include "animation/animator.hpp"
#include "application.hpp"
#include "scene/text.hpp"
#include "debug/log.hpp"
#include "animation/screen-transition.hpp"
@ -171,7 +170,7 @@ pause_menu::pause_menu(game::context& ctx):
// Fade out to black then quit
ctx.fade_transition_color->set_value({0, 0, 0});
ctx.fade_transition->transition(config::quit_fade_out_duration, false, ease<float>::out_cubic, false, std::bind(&application::close, ctx.app));
ctx.fade_transition->transition(config::quit_fade_out_duration, false, ease<float>::out_cubic, false, [&ctx](){ctx.closed=true;});
};
// Build list of menu select callbacks

+ 0
- 1
src/game/state/sound-menu.cpp View File

@ -19,7 +19,6 @@
#include "game/state/sound-menu.hpp"
#include "game/state/options-menu.hpp"
#include "application.hpp"
#include "scene/text.hpp"
#include "debug/log.hpp"
#include "game/menu.hpp"

+ 4
- 5
src/game/state/splash.cpp View File

@ -23,7 +23,6 @@
#include "animation/animation.hpp"
#include "animation/animator.hpp"
#include "animation/ease.hpp"
#include "application.hpp"
#include "render/passes/clear-pass.hpp"
#include "game/context.hpp"
#include "debug/log.hpp"
@ -144,9 +143,9 @@ splash::splash(game::context& ctx):
[&ctx = this->ctx]()
{
// Black out screen
ctx.rasterizer->set_clear_color(0.0f, 0.0f, 0.0f, 1.0f);
ctx.rasterizer->clear_framebuffer(true, false, false);
ctx.app->swap_buffers();
ctx.window->get_rasterizer()->set_clear_color(0.0f, 0.0f, 0.0f, 1.0f);
ctx.window->get_rasterizer()->clear_framebuffer(true, false, false);
ctx.window->swap_buffers();
// Change to main menu state
ctx.state_machine.pop();
@ -156,7 +155,7 @@ splash::splash(game::context& ctx):
}
}
);
ctx.input_mapper.connect(ctx.app->get_device_manager().get_event_queue());
ctx.input_mapper.connect(ctx.input_manager->get_event_queue());
debug::log::trace("Entered splash state");
}

+ 0
- 1
src/game/world.cpp View File

@ -17,7 +17,6 @@
* along with Antkeeper source code. If not, see <http://www.gnu.org/licenses/>.
*/
#include "application.hpp"
#include "color/color.hpp"
#include "config.hpp"
#include "debug/log.hpp"

+ 0
- 121
src/input/device-manager.cpp View File

@ -1,121 +0,0 @@
/*
* Copyright (C) 2023 Christopher J. Howard
*
* This file is part of Antkeeper source code.
*
* Antkeeper source code is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Antkeeper source code is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Antkeeper source code. If not, see <http://www.gnu.org/licenses/>.
*/
#include "input/device-manager.hpp"
namespace input {
void device_manager::register_device(device& device)
{
subscriptions.emplace(&device, device.get_connected_channel().subscribe(event_queue));
subscriptions.emplace(&device, device.get_disconnected_channel().subscribe(event_queue));
switch (device.get_device_type())
{
case device_type::gamepad:
register_gamepad(static_cast<gamepad&>(device));
break;
case device_type::keyboard:
register_keyboard(static_cast<keyboard&>(device));
break;
case device_type::mouse:
register_mouse(static_cast<mouse&>(device));
break;
default:
//std::unreachable();
break;
}
}
void device_manager::unregister_device(device& device)
{
subscriptions.erase(&device);
switch (device.get_device_type())
{
case device_type::gamepad:
unregister_gamepad(static_cast<gamepad&>(device));
break;
case device_type::keyboard:
unregister_keyboard(static_cast<keyboard&>(device));
break;
case device_type::mouse:
unregister_mouse(static_cast<mouse&>(device));
break;
default:
//std::unreachable();
break;
}
}
void device_manager::register_gamepad(gamepad& gamepad)
{
// Connect gamepad event signals to the event queue
subscriptions.emplace(&gamepad, gamepad.get_axis_moved_channel().subscribe(event_queue));
subscriptions.emplace(&gamepad, gamepad.get_button_pressed_channel().subscribe(event_queue));
subscriptions.emplace(&gamepad, gamepad.get_button_released_channel().subscribe(event_queue));
// Add gamepad to list of gamepads
gamepads.emplace(&gamepad);
}
void device_manager::register_keyboard(keyboard& keyboard)
{
// Connect keyboard event signals to the event queue
subscriptions.emplace(&keyboard, keyboard.get_key_pressed_channel().subscribe(event_queue));
subscriptions.emplace(&keyboard, keyboard.get_key_released_channel().subscribe(event_queue));
// Add keyboard to list of keyboards
keyboards.emplace(&keyboard);
}
void device_manager::register_mouse(mouse& mouse)
{
// Connect mouse event signals to the event queue
subscriptions.emplace(&mouse, mouse.get_button_pressed_channel().subscribe(event_queue));
subscriptions.emplace(&mouse, mouse.get_button_released_channel().subscribe(event_queue));
subscriptions.emplace(&mouse, mouse.get_moved_channel().subscribe(event_queue));
subscriptions.emplace(&mouse, mouse.get_scrolled_channel().subscribe(event_queue));
// Add mouse to list of mice
mice.emplace(&mouse);
}
void device_manager::unregister_gamepad(gamepad& gamepad)
{
gamepads.erase(&gamepad);
}
void device_manager::unregister_keyboard(keyboard& keyboard)
{
keyboards.erase(&keyboard);
}
void device_manager::unregister_mouse(mouse& mouse)
{
mice.erase(&mouse);
}
} // namespace input

src/state-machine.hpp → src/utility/state-machine.hpp View File

@ -17,8 +17,8 @@
* along with Antkeeper source code. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef ANTKEEPER_HSM_STATE_MACHINE_HPP
#define ANTKEEPER_HSM_STATE_MACHINE_HPP
#ifndef ANTKEEPER_UTILITY_HSM_STATE_MACHINE_HPP
#define ANTKEEPER_UTILITY_HSM_STATE_MACHINE_HPP
#include <memory>
#include <stack>
@ -36,4 +36,4 @@ using state_machine = std::stack>;
} // namespace hsm
#endif // ANTKEEPER_HSM_STATE_MACHINE_HPP
#endif // ANTKEEPER_UTILITY_HSM_STATE_MACHINE_HPP

Loading…
Cancel
Save