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};
}
/**