From c9ecc15246c2f24fcbe10a349229e5a3bacc5960 Mon Sep 17 00:00:00 2001 From: "C. J. Howard" Date: Tue, 7 Feb 2023 00:37:55 +0800 Subject: [PATCH] Improve display management --- src/app/display-events.hpp | 61 +++++++ src/app/display-orientation.hpp | 50 ++++++ src/app/display.hpp | 91 ++++++++-- src/app/sdl/sdl-window-manager.cpp | 248 ++++++++++++++++++++++---- src/app/sdl/sdl-window-manager.hpp | 1 + src/game/state/boot.cpp | 13 +- src/geom/primitive/hyperrectangle.hpp | 2 +- 7 files changed, 411 insertions(+), 55 deletions(-) create mode 100644 src/app/display-events.hpp create mode 100644 src/app/display-orientation.hpp diff --git a/src/app/display-events.hpp b/src/app/display-events.hpp new file mode 100644 index 0000000..f343a8c --- /dev/null +++ b/src/app/display-events.hpp @@ -0,0 +1,61 @@ +/* + * 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_EVENTS_HPP +#define ANTKEEPER_APP_DISPLAY_EVENTS_HPP + +#include "app/display-orientation.hpp" + +namespace app { + +class display; + +/** + * Event generated when a display has been connected. + */ +struct display_connected_event +{ + /// Pointer to the display that has been connected. + const display* display; +}; + +/** + * Event generated when a display has been disconnected. + */ +struct display_disconnected_event +{ + /// Pointer to the display that has been disconnected. + const display* display; +}; + +/** + * Event generated when the orientation of a display has changed. + */ +struct display_orientation_changed_event +{ + /// Pointer to the display that has had it's orientation changed. + const display* display; + + /// Orientation of the display. + display_orientation orientation; +}; + +} // namespace app + +#endif // ANTKEEPER_APP_DISPLAY_EVENTS_HPP diff --git a/src/app/display-orientation.hpp b/src/app/display-orientation.hpp new file mode 100644 index 0000000..4378061 --- /dev/null +++ b/src/app/display-orientation.hpp @@ -0,0 +1,50 @@ +/* + * 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_ORIENTATION_HPP +#define ANTKEEPER_APP_DISPLAY_ORIENTATION_HPP + +#include + +namespace app { + +/** + * Display orientations. + */ +enum class display_orientation: std::uint8_t +{ + /// Display orientation unknown. + unknown, + + /// Display is in landscape mode, with the right side up, relative to portrait mode. + landscape, + + /// Display is in landscape mode, with the left side up, relative to portrait mode. + landscape_flipped, + + /// Display is in portrait mode. + portrait, + + /// Display is in portrait mode, upside down. + portrait_flipped +}; + +} // namespace app + +#endif // ANTKEEPER_APP_DISPLAY_ORIENTATION_HPP diff --git a/src/app/display.hpp b/src/app/display.hpp index 476f32a..bd646f1 100644 --- a/src/app/display.hpp +++ b/src/app/display.hpp @@ -20,7 +20,10 @@ #ifndef ANTKEEPER_APP_DISPLAY_HPP #define ANTKEEPER_APP_DISPLAY_HPP -#include "math/vector.hpp" +#include "app/display-orientation.hpp" +#include "app/display-events.hpp" +#include "geom/primitive/rectangle.hpp" +#include "event/publisher.hpp" #include namespace app { @@ -52,13 +55,21 @@ public: } /** - * Sets the size of the display. + * Sets the bounds of the display. * - * @param size Size of the display, in display units. + * @param bounds Bounds of the display, in display units. */ - inline void set_size(const math::vector& size) noexcept + inline void set_bounds(const geom::primitive::rectangle& bounds) noexcept { - this->size = size; + this->bounds = bounds; + } + + /** + * Sets the usable bounds of the display, which excludes areas reserved by the OS for things like menus or docks. + */ + inline void set_usable_bounds(const geom::primitive::rectangle& bounds) noexcept + { + this->usable_bounds = bounds; } /** @@ -80,9 +91,19 @@ public: { this->dpi = dpi; } + + /** + * Sets the orientation of the display. + * + * @param orientation Display orientation. + */ + inline void set_orientation(display_orientation orientation) noexcept + { + this->orientation = orientation; + } /// Returns the index of the display. - [[nodiscard]] inline int get_index() const noexcept + [[nodiscard]] inline const int& get_index() const noexcept { return index; } @@ -93,30 +114,76 @@ public: return name; } - /// Returns the size of the display, in display units. - [[nodiscard]] inline const math::vector& get_size() const noexcept + /// Returns the bounds of the display, in display units. + [[nodiscard]] inline const geom::primitive::rectangle& get_bounds() const noexcept + { + return bounds; + } + + /// Returns the usable bounds of the display, which excludes areas reserved by the OS for things like menus or docks, in display units. + [[nodiscard]] inline const geom::primitive::rectangle& get_usable_bounds() const noexcept { - return size; + return usable_bounds; } /// Returns the refresh rate of the display, in Hz. - [[nodiscard]] inline int get_refresh_rate() const noexcept + [[nodiscard]] inline const int& get_refresh_rate() const noexcept { return refresh_rate; } /// Returns the DPI of the display. - [[nodiscard]] inline float get_dpi() const noexcept + [[nodiscard]] inline const float& get_dpi() const noexcept { return dpi; } + /// Returns the current orientation of the display. + [[nodiscard]] inline const display_orientation& get_orientation() const noexcept + { + return orientation; + } + + /// Returns `true` if the display is connected, `false` otherwise. + [[nodiscard]] inline const bool& is_connected() const noexcept + { + return connected; + } + + /// Returns the channel through which display connected events are published. + [[nodiscard]] inline event::channel& get_connected_channel() noexcept + { + return connected_publisher.channel(); + } + + /// Returns the channel through which display disconnected events are published. + [[nodiscard]] inline event::channel& get_disconnected_channel() noexcept + { + return disconnected_publisher.channel(); + } + + /// Returns the channel through which display orientation changed events are published. + [[nodiscard]] inline event::channel& get_orientation_changed_channel() noexcept + { + return orientation_changed_publisher.channel(); + } + private: + friend class window_manager; + friend class sdl_window_manager; + int index; std::string name; - math::vector size; + geom::primitive::rectangle bounds; + geom::primitive::rectangle usable_bounds; int refresh_rate; float dpi; + display_orientation orientation; + bool connected; + + event::publisher connected_publisher; + event::publisher disconnected_publisher; + event::publisher orientation_changed_publisher; }; } // namespace app diff --git a/src/app/sdl/sdl-window-manager.cpp b/src/app/sdl/sdl-window-manager.cpp index 17e310a..8205409 100644 --- a/src/app/sdl/sdl-window-manager.cpp +++ b/src/app/sdl/sdl-window-manager.cpp @@ -44,48 +44,19 @@ sdl_window_manager::sdl_window_manager() } else { + // Allocate displays + displays.resize(display_count); 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); + // Update display state + update_display(i); // 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); + display& display = displays[i]; + const auto display_resolution = display.get_bounds().size(); + debug::log::info("Display {} name: \"{}\"; resolution: {}x{}; refresh rate: {}Hz; DPI: {}", i, display.get_name(), display_resolution.x(), display_resolution.y(), display.get_refresh_rate(), display.get_dpi()); } } @@ -190,6 +161,9 @@ void sdl_window_manager::update() 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()); + // Log window resized event + debug::log::debug("Window {} resized to {}x{}", event.window.windowID, event.window.data1, event.window.data2); + // Publish window resized event window->resized_publisher.publish({window, window->size}); break; @@ -209,6 +183,9 @@ void sdl_window_manager::update() window->windowed_position = window->position; } + // Log window moved event + debug::log::debug("Window {} moved to ({}, {})", event.window.windowID, event.window.data1, event.window.data2); + // Publish window moved event window->moved_publisher.publish({window, window->position}); break; @@ -220,6 +197,9 @@ void sdl_window_manager::update() SDL_Window* internal_window = SDL_GetWindowFromID(event.window.windowID); app::sdl_window* window = get_window(internal_window); + // Log window focus gained event + debug::log::debug("Window {} gained focus", event.window.windowID); + // Publish window focus gained event window->focus_changed_publisher.publish({window, true}); break; @@ -231,6 +211,9 @@ void sdl_window_manager::update() SDL_Window* internal_window = SDL_GetWindowFromID(event.window.windowID); app::sdl_window* window = get_window(internal_window); + // Log window focus lost event + debug::log::debug("Window {} lost focus", event.window.windowID); + // Publish window focus lost event window->focus_changed_publisher.publish({window, false}); break; @@ -245,6 +228,9 @@ void sdl_window_manager::update() // Update window state window->maximized = true; + // Log window focus gained event + debug::log::debug("Window {} maximized", event.window.windowID); + // Publish window maximized event window->maximized_publisher.publish({window}); break; @@ -259,6 +245,9 @@ void sdl_window_manager::update() // Update window state window->maximized = false; + // Log window restored event + debug::log::debug("Window {} restored", event.window.windowID); + // Publish window restored event window->restored_publisher.publish({window}); break; @@ -270,6 +259,9 @@ void sdl_window_manager::update() SDL_Window* internal_window = SDL_GetWindowFromID(event.window.windowID); app::sdl_window* window = get_window(internal_window); + // Log window focus gained event + debug::log::debug("Window {} minimized", event.window.windowID); + // Publish window minimized event window->minimized_publisher.publish({window}); break; @@ -281,6 +273,9 @@ void sdl_window_manager::update() SDL_Window* internal_window = SDL_GetWindowFromID(event.window.windowID); app::sdl_window* window = get_window(internal_window); + // Log window closed event + debug::log::debug("Window {} closed", event.window.windowID); + // Publish window closed event window->closed_publisher.publish({window}); break; @@ -292,7 +287,107 @@ void sdl_window_manager::update() } else if (event.type == SDL_DISPLAYEVENT) { - + switch (event.display.event) + { + case SDL_DISPLAYEVENT_CONNECTED: + if (event.display.display < displays.size()) + { + // Get previously connected display + display& display = displays[event.display.display]; + + // Update display state + display.connected = true; + + // Log display (re)connected event + debug::log::info("Reconnected display {}", event.display.display); + + // Publish display (re)connected event + display.connected_publisher.publish({&display}); + } + else if (event.display.display == displays.size()) + { + // Allocate new display + displays.resize(displays.size() + 1); + display& display = displays[event.display.display]; + + // Update display state + update_display(event.display.display); + + // Log display info + const auto display_resolution = display.get_bounds().size(); + debug::log::info("Connected display {}; name: \"{}\"; resolution: {}x{}; refresh rate: {}Hz; DPI: {}", event.display.display, display.get_name(), display_resolution.x(), display_resolution.y(), display.get_refresh_rate(), display.get_dpi()); + + // Publish display connected event + display.connected_publisher.publish({&display}); + } + else + { + debug::log::error("Index of connected display ({}) out of range", event.display.display); + } + break; + + case SDL_DISPLAYEVENT_DISCONNECTED: + if (event.display.display < displays.size()) + { + // Get display + display& display = displays[event.display.display]; + + // Update display state + display.connected = false; + + // Log display disconnected event + debug::log::info("Disconnected display {}", event.display.display); + + // Publish display disconnected event + display.disconnected_publisher.publish({&display}); + } + else + { + debug::log::error("Index of disconnected display ({}) out of range", event.display.display); + } + break; + + case SDL_DISPLAYEVENT_ORIENTATION: + if (event.display.display < displays.size()) + { + // Get display + display& display = displays[event.display.display]; + + // Update display state + switch (event.display.data1) + { + case SDL_ORIENTATION_LANDSCAPE: + display.set_orientation(display_orientation::landscape); + break; + case SDL_ORIENTATION_LANDSCAPE_FLIPPED: + display.set_orientation(display_orientation::landscape_flipped); + break; + case SDL_ORIENTATION_PORTRAIT: + display.set_orientation(display_orientation::portrait); + break; + case SDL_ORIENTATION_PORTRAIT_FLIPPED: + display.set_orientation(display_orientation::portrait_flipped); + break; + default: + display.set_orientation(display_orientation::unknown); + break; + } + + // Log display orientation changed event + debug::log::info("Display {} orientation changed", event.display.display); + + // Publish display orientation changed event + display.orientation_changed_publisher.publish({&display, display.get_orientation()}); + } + else + { + debug::log::error("Index of orientation-changed display ({}) out of range", event.display.display); + } + break; + + default: + break; + } } } } @@ -321,4 +416,83 @@ const display& sdl_window_manager::get_display(std::size_t index) const return displays[index]; } +void sdl_window_manager::update_display(int sdl_display_index) +{ + // Query display mode + SDL_DisplayMode sdl_display_mode; + if (SDL_GetDesktopDisplayMode(sdl_display_index, &sdl_display_mode) != 0) + { + debug::log::error("Failed to get mode of display {}: {}", sdl_display_index, SDL_GetError()); + SDL_ClearError(); + sdl_display_mode = {0, 0, 0, 0, nullptr}; + } + + // Query display name + const char* sdl_display_name = SDL_GetDisplayName(sdl_display_index); + if (!sdl_display_name) + { + debug::log::warning("Failed to get name of display {}: {}", sdl_display_index, SDL_GetError()); + SDL_ClearError(); + sdl_display_name = nullptr; + } + + // Query display bounds + SDL_Rect sdl_display_bounds; + if (SDL_GetDisplayBounds(sdl_display_index, &sdl_display_bounds) != 0) + { + debug::log::warning("Failed to get bounds of display {}: {}", sdl_display_index, SDL_GetError()); + SDL_ClearError(); + sdl_display_bounds = {0, 0, sdl_display_mode.w, sdl_display_mode.h}; + } + + // Query display usable bounds + SDL_Rect sdl_display_usable_bounds; + if (SDL_GetDisplayUsableBounds(sdl_display_index, &sdl_display_usable_bounds) != 0) + { + debug::log::warning("Failed to get usable bounds of display {}: {}", sdl_display_index, SDL_GetError()); + SDL_ClearError(); + sdl_display_usable_bounds = sdl_display_bounds; + } + + // Query display DPI + float sdl_display_dpi; + if (SDL_GetDisplayDPI(sdl_display_index, &sdl_display_dpi, nullptr, nullptr) != 0) + { + debug::log::warning("Failed to get DPI of display {}: {}", sdl_display_index, SDL_GetError()); + SDL_ClearError(); + sdl_display_dpi = 0.0f; + } + + // Query display orientation + SDL_DisplayOrientation sdl_display_orientation = SDL_GetDisplayOrientation(sdl_display_index); + + // Update display properties + display& display = displays[sdl_display_index]; + display.set_index(static_cast(sdl_display_index)); + display.set_name(sdl_display_name ? sdl_display_name : std::string()); + display.set_bounds({{sdl_display_bounds.x, sdl_display_bounds.y}, {sdl_display_bounds.x + sdl_display_bounds.w, sdl_display_bounds.y + sdl_display_bounds.h}}); + display.set_usable_bounds({{sdl_display_usable_bounds.x, sdl_display_usable_bounds.y}, {sdl_display_usable_bounds.x + sdl_display_usable_bounds.w, sdl_display_usable_bounds.y + sdl_display_usable_bounds.h}}); + display.set_refresh_rate(sdl_display_mode.refresh_rate); + display.set_dpi(sdl_display_dpi); + switch (sdl_display_orientation) + { + case SDL_ORIENTATION_LANDSCAPE: + display.set_orientation(display_orientation::landscape); + break; + case SDL_ORIENTATION_LANDSCAPE_FLIPPED: + display.set_orientation(display_orientation::landscape_flipped); + break; + case SDL_ORIENTATION_PORTRAIT: + display.set_orientation(display_orientation::portrait); + break; + case SDL_ORIENTATION_PORTRAIT_FLIPPED: + display.set_orientation(display_orientation::portrait_flipped); + break; + default: + display.set_orientation(display_orientation::unknown); + break; + } + display.connected = true; +} + } // namespace app diff --git a/src/app/sdl/sdl-window-manager.hpp b/src/app/sdl/sdl-window-manager.hpp index 9aaa8df..3b10875 100644 --- a/src/app/sdl/sdl-window-manager.hpp +++ b/src/app/sdl/sdl-window-manager.hpp @@ -64,6 +64,7 @@ public: private: sdl_window* get_window(SDL_Window* internal_window); + void update_display(int sdl_display_index); std::vector displays; std::unordered_map window_map; diff --git a/src/game/state/boot.cpp b/src/game/state/boot.cpp index e900376..e70c448 100644 --- a/src/game/state/boot.cpp +++ b/src/game/state/boot.cpp @@ -359,12 +359,15 @@ void boot::setup_window() if (resize) { const app::display& display = ctx.window_manager->get_display(0); + const auto& usable_bounds = display.get_usable_bounds(); + const auto usable_bounds_center = usable_bounds.center(); - 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; + const float default_windowed_scale = 1.0f / 1.2f; + + window_w = static_cast((usable_bounds.max.x() - usable_bounds.min.x()) * default_windowed_scale); + window_h = static_cast((usable_bounds.max.y() - usable_bounds.min.y()) * default_windowed_scale); + window_x = usable_bounds_center.x() - window_w / 2; + window_y = usable_bounds_center.y() - window_h / 2; } // Construct window diff --git a/src/geom/primitive/hyperrectangle.hpp b/src/geom/primitive/hyperrectangle.hpp index bc18592..6d1b18f 100644 --- a/src/geom/primitive/hyperrectangle.hpp +++ b/src/geom/primitive/hyperrectangle.hpp @@ -76,7 +76,7 @@ struct hyperrectangle /// Returns the center position of the hyperrectangle. constexpr vector_type center() const noexcept { - return (min + max) * T{0.5}; + return (min + max) / T{2}; } /**