From ffda59dea6d5610b85ab1734bc695c0cad1694c2 Mon Sep 17 00:00:00 2001 From: "C. J. Howard" Date: Mon, 6 Feb 2023 11:37:19 +0800 Subject: [PATCH] Split application class into a window manager, a window, and an input manager --- CMakeLists.txt | 1 - src/app/display.hpp | 124 +++ src/app/input-manager.cpp | 130 +++ .../input-manager.hpp} | 82 +- src/app/sdl/sdl-input-manager.cpp | 310 +++++++ src/app/sdl/sdl-input-manager.hpp | 55 ++ src/app/sdl/sdl-window-manager.cpp | 324 +++++++ src/app/sdl/sdl-window-manager.hpp | 74 ++ src/app/sdl/sdl-window.cpp | 294 +++++++ src/app/sdl/sdl-window.hpp | 76 ++ src/app/window-events.hpp | 103 +++ src/app/window-manager.cpp | 30 + src/app/window-manager.hpp | 84 ++ src/app/window.cpp | 26 + src/app/window.hpp | 252 ++++++ src/application.cpp | 818 ------------------ src/application.hpp | 295 ------- src/game/context.hpp | 25 +- src/game/controls.cpp | 7 +- src/game/fonts.cpp | 4 +- src/game/graphics.cpp | 10 +- src/game/menu.cpp | 5 +- src/game/state/boot.cpp | 257 +++--- src/game/state/boot.hpp | 3 +- src/game/state/controls-menu.cpp | 1 - src/game/state/credits.cpp | 3 +- src/game/state/extras-menu.cpp | 1 - src/game/state/gamepad-config-menu.cpp | 1 - src/game/state/graphics-menu.cpp | 13 +- src/game/state/keyboard-config-menu.cpp | 1 - src/game/state/language-menu.cpp | 1 - src/game/state/main-menu.cpp | 9 +- src/game/state/nest-selection.cpp | 6 +- src/game/state/nuptial-flight.cpp | 8 +- src/game/state/options-menu.cpp | 1 - src/game/state/pause-menu.cpp | 3 +- src/game/state/sound-menu.cpp | 1 - src/game/state/splash.cpp | 9 +- src/game/world.cpp | 1 - src/input/device-manager.cpp | 121 --- src/{ => utility}/state-machine.hpp | 6 +- 41 files changed, 2128 insertions(+), 1447 deletions(-) create mode 100644 src/app/display.hpp create mode 100644 src/app/input-manager.cpp rename src/{input/device-manager.hpp => app/input-manager.hpp} (52%) create mode 100644 src/app/sdl/sdl-input-manager.cpp create mode 100644 src/app/sdl/sdl-input-manager.hpp create mode 100644 src/app/sdl/sdl-window-manager.cpp create mode 100644 src/app/sdl/sdl-window-manager.hpp create mode 100644 src/app/sdl/sdl-window.cpp create mode 100644 src/app/sdl/sdl-window.hpp create mode 100644 src/app/window-events.hpp create mode 100644 src/app/window-manager.cpp create mode 100644 src/app/window-manager.hpp create mode 100644 src/app/window.cpp create mode 100644 src/app/window.hpp delete mode 100644 src/application.cpp delete mode 100644 src/application.hpp delete mode 100644 src/input/device-manager.cpp rename src/{ => utility}/state-machine.hpp (87%) 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