diff --git a/CMakeLists.txt b/CMakeLists.txt
index e01a1f4..0b41e73 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -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)
diff --git a/src/app/display.hpp b/src/app/display.hpp
new file mode 100644
index 0000000..476f32a
--- /dev/null
+++ b/src/app/display.hpp
@@ -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 .
+ */
+
+#ifndef ANTKEEPER_APP_DISPLAY_HPP
+#define ANTKEEPER_APP_DISPLAY_HPP
+
+#include "math/vector.hpp"
+#include
+
+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& 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& 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 size;
+ int refresh_rate;
+ float dpi;
+};
+
+} // namespace app
+
+#endif // ANTKEEPER_APP_DISPLAY_HPP
diff --git a/src/app/input-manager.cpp b/src/app/input-manager.cpp
new file mode 100644
index 0000000..907cf38
--- /dev/null
+++ b/src/app/input-manager.cpp
@@ -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 .
+ */
+
+#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(device));
+ break;
+
+ case input::device_type::keyboard:
+ register_keyboard(static_cast(device));
+ break;
+
+ case input::device_type::mouse:
+ register_mouse(static_cast(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(device));
+ break;
+
+ case input::device_type::keyboard:
+ unregister_keyboard(static_cast(device));
+ break;
+
+ case input::device_type::mouse:
+ unregister_mouse(static_cast(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
diff --git a/src/input/device-manager.hpp b/src/app/input-manager.hpp
similarity index 52%
rename from src/input/device-manager.hpp
rename to src/app/input-manager.hpp
index 65ec939..048172b 100644
--- a/src/input/device-manager.hpp
+++ b/src/app/input-manager.hpp
@@ -17,8 +17,8 @@
* along with Antkeeper source code. If not, see .
*/
-#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
#include
-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& get_gamepads() noexcept
+ [[nodiscard]] inline const std::unordered_set& get_gamepads() noexcept
{
return gamepads;
}
/// Returns the set of registered keyboards.
- [[nodiscard]] inline const std::unordered_set& get_keyboards() noexcept
+ [[nodiscard]] inline const std::unordered_set& get_keyboards() noexcept
{
return keyboards;
}
/// Returns the set of registered mice.
- [[nodiscard]] inline const std::unordered_set& get_mice() noexcept
+ [[nodiscard]] inline const std::unordered_set& 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> subscriptions;
- std::unordered_set gamepads;
- std::unordered_set keyboards;
- std::unordered_set mice;
+
+private:
+ std::multimap> subscriptions;
+ std::unordered_set gamepads;
+ std::unordered_set keyboards;
+ std::unordered_set mice;
};
-} // namespace input
+} // namespace app
-#endif // ANTKEEPER_INPUT_DEVICE_MANAGER_HPP
+#endif // ANTKEEPER_APP_INPUT_MANAGER_HPP
diff --git a/src/app/sdl/sdl-input-manager.cpp b/src/app/sdl/sdl-input-manager.cpp
new file mode 100644
index 0000000..d63ff64
--- /dev/null
+++ b/src/app/sdl/sdl-input-manager.cpp
@@ -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 .
+ */
+
+#include "app/sdl/sdl-input-manager.hpp"
+#include "debug/log.hpp"
+#include "math/map.hpp"
+#include
+#include
+
+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(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(event.button.button));
+ break;
+ }
+
+ case SDL_MOUSEBUTTONUP:
+ {
+ mouse.release(static_cast(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(event.caxis.value),
+ static_cast(std::numeric_limits::min()),
+ static_cast(std::numeric_limits::max()),
+ -1.0f,
+ 1.0f
+ );
+
+ // Generate gamepad axis moved event
+ it->second->move(static_cast(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(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(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
diff --git a/src/app/sdl/sdl-input-manager.hpp b/src/app/sdl/sdl-input-manager.hpp
new file mode 100644
index 0000000..8457fe9
--- /dev/null
+++ b/src/app/sdl/sdl-input-manager.hpp
@@ -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 .
+ */
+
+#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 gamepad_map;
+};
+
+} // namespace app
+
+#endif // ANTKEEPER_APP_SDL_INPUT_MANAGER_HPP
diff --git a/src/app/sdl/sdl-window-manager.cpp b/src/app/sdl/sdl-window-manager.cpp
new file mode 100644
index 0000000..17e310a
--- /dev/null
+++ b/src/app/sdl/sdl-window-manager.cpp
@@ -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 .
+ */
+
+#include "app/sdl/sdl-window-manager.hpp"
+#include "app/sdl/sdl-window.hpp"
+#include "debug/log.hpp"
+#include "config.hpp"
+#include
+
+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& windowed_position,
+ const math::vector& 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
diff --git a/src/app/sdl/sdl-window-manager.hpp b/src/app/sdl/sdl-window-manager.hpp
new file mode 100644
index 0000000..9aaa8df
--- /dev/null
+++ b/src/app/sdl/sdl-window-manager.hpp
@@ -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 .
+ */
+
+#ifndef ANTKEEPER_APP_SDL_WINDOW_MANAGER_HPP
+#define ANTKEEPER_APP_SDL_WINDOW_MANAGER_HPP
+
+#include "app/window-manager.hpp"
+#include "app/display.hpp"
+#include
+#include
+#include
+
+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& windowed_position,
+ const math::vector& 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 displays;
+ std::unordered_map window_map;
+};
+
+} // namespace app
+
+#endif // ANTKEEPER_APP_SDL_WINDOW_MANAGER_HPP
diff --git a/src/app/sdl/sdl-window.cpp b/src/app/sdl/sdl-window.cpp
new file mode 100644
index 0000000..a456cb6
--- /dev/null
+++ b/src/app/sdl/sdl-window.cpp
@@ -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 .
+ */
+
+#include "app/sdl/sdl-window.hpp"
+#include "config.hpp"
+#include "debug/log.hpp"
+#include
+#include
+
+namespace app {
+
+sdl_window::sdl_window
+(
+ const std::string& title,
+ const math::vector& windowed_position,
+ const math::vector& 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(glGetString(GL_VENDOR)),
+ reinterpret_cast(glGetString(GL_RENDERER)),
+ reinterpret_cast(glGetString(GL_VERSION)),
+ reinterpret_cast(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& position)
+{
+ SDL_SetWindowPosition(internal_window, position.x(), position.y());
+}
+
+void sdl_window::set_size(const math::vector& size)
+{
+ SDL_SetWindowSize(internal_window, size.x(), size.y());
+}
+
+void sdl_window::set_minimum_size(const math::vector& size)
+{
+ SDL_SetWindowMinimumSize(internal_window, size.x(), size.y());
+ this->minimum_size = size;
+}
+
+void sdl_window::set_maximum_size(const math::vector& 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
diff --git a/src/app/sdl/sdl-window.hpp b/src/app/sdl/sdl-window.hpp
new file mode 100644
index 0000000..909e830
--- /dev/null
+++ b/src/app/sdl/sdl-window.hpp
@@ -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 .
+ */
+
+#ifndef ANTKEEPER_APP_SDL_WINDOW_HPP
+#define ANTKEEPER_APP_SDL_WINDOW_HPP
+
+#include "app/window.hpp"
+#include
+
+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& position);
+ virtual void set_size(const math::vector& size);
+ virtual void set_minimum_size(const math::vector& size);
+ virtual void set_maximum_size(const math::vector& 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& windowed_position,
+ const math::vector& 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
diff --git a/src/app/window-events.hpp b/src/app/window-events.hpp
new file mode 100644
index 0000000..5205041
--- /dev/null
+++ b/src/app/window-events.hpp
@@ -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 .
+ */
+
+#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 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 size;
+};
+
+} // namespace app
+
+#endif // ANTKEEPER_APP_WINDOW_EVENTS_HPP
diff --git a/src/app/window-manager.cpp b/src/app/window-manager.cpp
new file mode 100644
index 0000000..fa4040e
--- /dev/null
+++ b/src/app/window-manager.cpp
@@ -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 .
+ */
+
+#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
diff --git a/src/app/window-manager.hpp b/src/app/window-manager.hpp
new file mode 100644
index 0000000..012b830
--- /dev/null
+++ b/src/app/window-manager.hpp
@@ -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 .
+ */
+
+#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
+
+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& windowed_position,
+ const math::vector& 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
diff --git a/src/app/window.cpp b/src/app/window.cpp
new file mode 100644
index 0000000..4ac8546
--- /dev/null
+++ b/src/app/window.cpp
@@ -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 .
+ */
+
+#include "app/window.hpp"
+
+namespace app {
+
+
+
+} // namespace app
diff --git a/src/app/window.hpp b/src/app/window.hpp
new file mode 100644
index 0000000..b007e1f
--- /dev/null
+++ b/src/app/window.hpp
@@ -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 .
+ */
+
+#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
+
+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& position) = 0;
+
+ /**
+ * Changes the size of the window.
+ *
+ * @param size Size of the window, in display units.
+ */
+ virtual void set_size(const math::vector& 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& 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& 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& get_windowed_position() const noexcept
+ {
+ return windowed_position;
+ }
+
+ /// Returns the current position of the window, in display units.
+ [[nodiscard]] inline const math::vector& 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& get_windowed_size() const noexcept
+ {
+ return windowed_size;
+ }
+
+ /// Returns the current size of the window, in display units.
+ [[nodiscard]] inline const math::vector& get_size() const noexcept
+ {
+ return size;
+ }
+
+ /// Returns the minimum size of the window, in display units.
+ [[nodiscard]] inline const math::vector& get_minimum_size() const noexcept
+ {
+ return minimum_size;
+ }
+
+ /// Returns the maximum size of the window, in display units.
+ [[nodiscard]] inline const math::vector& 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& 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& get_closed_channel() noexcept
+ {
+ return closed_publisher.channel();
+ }
+
+ /// Returns the channel through which window focus changed events are published.
+ [[nodiscard]] inline event::channel& get_focus_changed_channel() noexcept
+ {
+ return focus_changed_publisher.channel();
+ }
+
+ /// Returns the channel through which window maximized events are published.
+ [[nodiscard]] inline event::channel& get_maximized_channel() noexcept
+ {
+ return maximized_publisher.channel();
+ }
+
+ /// Returns the channel through which window minimized events are published.
+ [[nodiscard]] inline event::channel& get_minimized_channel() noexcept
+ {
+ return minimized_publisher.channel();
+ }
+
+ /// Returns the channel through which window moved events are published.
+ [[nodiscard]] inline event::channel& get_moved_channel() noexcept
+ {
+ return moved_publisher.channel();
+ }
+
+ /// Returns the channel through which window resized events are published.
+ [[nodiscard]] inline event::channel& get_resized_channel() noexcept
+ {
+ return resized_publisher.channel();
+ }
+
+ /// Returns the channel through which window restored events are published.
+ [[nodiscard]] inline event::channel& get_restored_channel() noexcept
+ {
+ return restored_publisher.channel();
+ }
+
+protected:
+ friend class window_manager;
+
+ std::string title;
+ math::vector windowed_position;
+ math::vector position;
+ math::vector windowed_size;
+ math::vector size;
+ math::vector minimum_size;
+ math::vector maximum_size;
+ math::vector viewport_size;
+ bool maximized;
+ bool fullscreen;
+ bool v_sync;
+
+ event::publisher closed_publisher;
+ event::publisher focus_changed_publisher;
+ event::publisher maximized_publisher;
+ event::publisher minimized_publisher;
+ event::publisher moved_publisher;
+ event::publisher resized_publisher;
+ event::publisher restored_publisher;
+};
+
+} // namespace app
+
+#endif // ANTKEEPER_APP_WINDOW_HPP
diff --git a/src/application.cpp b/src/application.cpp
deleted file mode 100644
index 4961836..0000000
--- a/src/application.cpp
+++ /dev/null
@@ -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 .
- */
-
-#include "application.hpp"
-#include "config.hpp"
-#include "debug/log.hpp"
-#include "input/scancode.hpp"
-#include "math/map.hpp"
-#include
-#include
-#include
-#include
-
-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(glGetString(GL_VENDOR)),
- reinterpret_cast(glGetString(GL_RENDERER)),
- reinterpret_cast(glGetString(GL_VERSION)),
- reinterpret_cast(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(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(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(sdl_event.button.button));
- break;
- }
-
- case SDL_MOUSEBUTTONUP:
- {
- mouse.release(static_cast(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(sdl_event.caxis.value),
- static_cast(std::numeric_limits::min()),
- static_cast(std::numeric_limits::max()),
- -1.0f,
- 1.0f
- );
-
- // Generate gamepad axis moved event
- it->second->move(static_cast(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(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(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(sdl_event.window.data1);
- event.size.y() = static_cast(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(sdl_window_drawable_w);
- event.viewport_size.y() = static_cast(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(sdl_event.window.data1);
- event.position.y() = static_cast(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);
- // }
-}
diff --git a/src/application.hpp b/src/application.hpp
deleted file mode 100644
index fd60632..0000000
--- a/src/application.hpp
+++ /dev/null
@@ -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 .
- */
-
-#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
-#include
-#include
-
-// 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& 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& 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& get_window_moved_channel() noexcept
- {
- return window_moved_publisher.channel();
- }
-
- /// Returns the channel through which window resized events are published.
- [[nodiscard]] inline event::channel& get_window_resized_channel() noexcept
- {
- return window_resized_publisher.channel();
- }
-
- /// Returns the channel through which window maximized events are published.
- [[nodiscard]] inline event::channel& get_window_maximized_channel() noexcept
- {
- return window_maximized_publisher.channel();
- }
-
- /// Returns the channel through which window restored events are published.
- [[nodiscard]] inline event::channel& get_window_restored_channel() noexcept
- {
- return window_restored_publisher.channel();
- }
-
- /// Returns the channel through which window minimized events are published.
- [[nodiscard]] inline event::channel& 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 gamepad_map;
-
- event::publisher window_closed_publisher;
- event::publisher window_focus_changed_publisher;
- event::publisher window_moved_publisher;
- event::publisher window_resized_publisher;
- event::publisher window_maximized_publisher;
- event::publisher window_restored_publisher;
- event::publisher 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
diff --git a/src/game/context.hpp b/src/game/context.hpp
index 98de396..9a30fba 100644
--- a/src/game/context.hpp
+++ b/src/game/context.hpp
@@ -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* 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 state_machine;
std::function 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> function_queue;
// Parallel processes
std::unordered_map> processes;
- /// Interface for window management and input events
- application* app;
-
// Controls
input::mapper input_mapper;
std::forward_list> control_subscriptions;
diff --git a/src/game/controls.cpp b/src/game/controls.cpp
index 71e9015..1618f41 100644
--- a/src/game/controls.cpp
+++ b/src/game/controls.cpp
@@ -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();
// 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)
{
diff --git a/src/game/fonts.cpp b/src/game/fonts.cpp
index 4dff569..ab09de0 100644
--- a/src/game/fonts.cpp
+++ b/src/game/fonts.cpp
@@ -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("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())
diff --git a/src/game/graphics.cpp b/src/game/graphics.cpp
index 9c8910b..c20df9d 100644
--- a/src/game/graphics.cpp
+++ b/src/game/graphics.cpp
@@ -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(viewport_size.x() * ctx.render_scale + 0.5f), static_cast(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(viewport_size.x() * ctx.render_scale + 0.5f), static_cast(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 frame = std::make_shared();
@@ -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());
}
}
}
diff --git a/src/game/menu.cpp b/src/game/menu.cpp
index 70fa0f0..19a5e3f 100644
--- a/src/game/menu.cpp
+++ b/src/game/menu.cpp
@@ -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(event.x - viewport[0] / 2);
const float y = static_cast((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(event.x - viewport[0] / 2);
const float y = static_cast((viewport[1] - event.y + 1) - viewport[1] / 2);
diff --git a/src/game/state/boot.cpp b/src/game/state/boot.cpp
index 32215d1..e900376 100644
--- a/src/game/state/boot.cpp
+++ b/src/game/state/boot.cpp
@@ -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>(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>("settings.cfg");
- if (!ctx.settings)
- {
- debug::log::info("Settings not found");
- ctx.settings = new dict();
- }
-}
-
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>("settings.cfg");
+ if (!ctx.settings)
+ {
+ debug::log::info("Settings not found");
+ ctx.settings = new dict();
+ }
+}
+
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(display.get_size().x() * windowed_size_scale);
+ window_h = static_cast(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*>(&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(viewport_size[0]) / static_cast(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(viewport_size[0]), static_cast(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("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("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(event.viewport_size.x()) * -0.5f;
- const float clip_right = static_cast(event.viewport_size.x()) * 0.5f;
- const float clip_top = static_cast(event.viewport_size.y()) * -0.5f;
- const float clip_bottom = static_cast(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
+ // (
+ // [&](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(viewport_size.x()) * -0.5f;
+ // const float clip_right = static_cast(viewport_size.x()) * 0.5f;
+ // const float clip_top = static_cast(viewport_size.y()) * -0.5f;
+ // const float clip_bottom = static_cast(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();
diff --git a/src/game/state/boot.hpp b/src/game/state/boot.hpp
index 21d0495..6523b43 100644
--- a/src/game/state/boot.hpp
+++ b/src/game/state/boot.hpp
@@ -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();
diff --git a/src/game/state/controls-menu.cpp b/src/game/state/controls-menu.cpp
index 52d9874..91c852a 100644
--- a/src/game/state/controls-menu.cpp
+++ b/src/game/state/controls-menu.cpp
@@ -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"
diff --git a/src/game/state/credits.cpp b/src/game/state/credits.cpp
index 27351f4..4eff5fd 100644
--- a/src/game/state/credits.cpp
+++ b/src/game/state/credits.cpp
@@ -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);
diff --git a/src/game/state/extras-menu.cpp b/src/game/state/extras-menu.cpp
index 4b53452..32c4060 100644
--- a/src/game/state/extras-menu.cpp
+++ b/src/game/state/extras-menu.cpp
@@ -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"
diff --git a/src/game/state/gamepad-config-menu.cpp b/src/game/state/gamepad-config-menu.cpp
index d6a3d2c..ee5ad3c 100644
--- a/src/game/state/gamepad-config-menu.cpp
+++ b/src/game/state/gamepad-config-menu.cpp
@@ -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"
diff --git a/src/game/state/graphics-menu.cpp b/src/game/state/graphics-menu.cpp
index a168d72..90be37e 100644
--- a/src/game/state/graphics-menu.cpp
+++ b/src/game/state/graphics-menu.cpp
@@ -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(ctx.anti_aliasing_method);
const float font_scale = ctx.font_scale;
const bool dyslexia_font = ctx.dyslexia_font;
diff --git a/src/game/state/keyboard-config-menu.cpp b/src/game/state/keyboard-config-menu.cpp
index 3a73e89..0e2fb9f 100644
--- a/src/game/state/keyboard-config-menu.cpp
+++ b/src/game/state/keyboard-config-menu.cpp
@@ -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"
diff --git a/src/game/state/language-menu.cpp b/src/game/state/language-menu.cpp
index 3cc4682..e55c8ba 100644
--- a/src/game/state/language-menu.cpp
+++ b/src/game/state/language-menu.cpp
@@ -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"
diff --git a/src/game/state/main-menu.cpp b/src/game/state/main-menu.cpp
index fba3c28..b02b614 100644
--- a/src/game/state/main-menu.cpp
+++ b/src/game/state/main-menu.cpp
@@ -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&>(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::out_cubic, false, std::bind(&application::close, ctx.app));
+ ctx.fade_transition->transition(config::quit_fade_out_duration, false, ease::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(viewport_size[0]) / static_cast(viewport_size[1]);
float fov = math::vertical_fov(math::radians(100.0f), aspect_ratio);
diff --git a/src/game/state/nest-selection.cpp b/src/game/state/nest-selection.cpp
index 7b4fba4..d702944 100644
--- a/src/game/state/nest-selection.cpp
+++ b/src/game/state/nest-selection.cpp
@@ -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(viewport_size[0]) / static_cast(viewport_size[1]);
// Init first person camera rig parameters
diff --git a/src/game/state/nuptial-flight.cpp b/src/game/state/nuptial-flight.cpp
index 88ca82e..18332bf 100644
--- a/src/game/state/nuptial-flight.cpp
+++ b/src/game/state/nuptial-flight.cpp
@@ -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(viewport_size[0]) / static_cast(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 =
diff --git a/src/game/state/options-menu.cpp b/src/game/state/options-menu.cpp
index 3f482d9..8221e8b 100644
--- a/src/game/state/options-menu.cpp
+++ b/src/game/state/options-menu.cpp
@@ -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"
diff --git a/src/game/state/pause-menu.cpp b/src/game/state/pause-menu.cpp
index 5e295a4..7e628d5 100644
--- a/src/game/state/pause-menu.cpp
+++ b/src/game/state/pause-menu.cpp
@@ -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::out_cubic, false, std::bind(&application::close, ctx.app));
+ ctx.fade_transition->transition(config::quit_fade_out_duration, false, ease::out_cubic, false, [&ctx](){ctx.closed=true;});
};
// Build list of menu select callbacks
diff --git a/src/game/state/sound-menu.cpp b/src/game/state/sound-menu.cpp
index 405bb76..27fdf85 100644
--- a/src/game/state/sound-menu.cpp
+++ b/src/game/state/sound-menu.cpp
@@ -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"
diff --git a/src/game/state/splash.cpp b/src/game/state/splash.cpp
index e262e97..fdbd7e9 100644
--- a/src/game/state/splash.cpp
+++ b/src/game/state/splash.cpp
@@ -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");
}
diff --git a/src/game/world.cpp b/src/game/world.cpp
index b38cd75..e803ed4 100644
--- a/src/game/world.cpp
+++ b/src/game/world.cpp
@@ -17,7 +17,6 @@
* along with Antkeeper source code. If not, see .
*/
-#include "application.hpp"
#include "color/color.hpp"
#include "config.hpp"
#include "debug/log.hpp"
diff --git a/src/input/device-manager.cpp b/src/input/device-manager.cpp
deleted file mode 100644
index 27a2553..0000000
--- a/src/input/device-manager.cpp
+++ /dev/null
@@ -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 .
- */
-
-#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(device));
- break;
-
- case device_type::keyboard:
- register_keyboard(static_cast(device));
- break;
-
- case device_type::mouse:
- register_mouse(static_cast(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(device));
- break;
-
- case device_type::keyboard:
- unregister_keyboard(static_cast(device));
- break;
-
- case device_type::mouse:
- unregister_mouse(static_cast(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
diff --git a/src/state-machine.hpp b/src/utility/state-machine.hpp
similarity index 87%
rename from src/state-machine.hpp
rename to src/utility/state-machine.hpp
index 9f749c4..985397d 100644
--- a/src/state-machine.hpp
+++ b/src/utility/state-machine.hpp
@@ -17,8 +17,8 @@
* along with Antkeeper source code. If not, see .
*/
-#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
#include
@@ -36,4 +36,4 @@ using state_machine = std::stack>;
} // namespace hsm
-#endif // ANTKEEPER_HSM_STATE_MACHINE_HPP
+#endif // ANTKEEPER_UTILITY_HSM_STATE_MACHINE_HPP