diff --git a/source/code/HelpGamepadState.cpp b/source/code/HelpGamepadState.cpp index 480a130..4aebbd6 100644 --- a/source/code/HelpGamepadState.cpp +++ b/source/code/HelpGamepadState.cpp @@ -67,7 +67,7 @@ void HelpGamepadState::ProcessInput(const SDL_Event& event, bool& processed) { #define OFFSETX (-512+globalData.xsize/2) -static void RenderDrawLine(SDL_Renderer * renderer, int x1, int y1, int x2, int y2) { +static void RenderDrawLine(SDL_Renderer* renderer, int x1, int y1, int x2, int y2) { globalData.logicalResize.LogicalToPhysical(&x1, &y1); globalData.logicalResize.LogicalToPhysical(&x2, &y2); SDL_RenderDrawLine(renderer, x1, y1, x2, y2); diff --git a/source/code/HelpHowtoState.cpp b/source/code/HelpHowtoState.cpp index 53716c3..d39c663 100644 --- a/source/code/HelpHowtoState.cpp +++ b/source/code/HelpHowtoState.cpp @@ -131,7 +131,7 @@ HelpHowtoState::~HelpHowtoState() { const double PI =3.141592653589793238463; -static void RenderDrawLine(SDL_Renderer * renderer, int x1, int y1, int x2, int y2) { +static void RenderDrawLine(SDL_Renderer* renderer, int x1, int y1, int x2, int y2) { globalData.logicalResize.LogicalToPhysical(&x1, &y1); globalData.logicalResize.LogicalToPhysical(&x2, &y2); SDL_RenderDrawLine(renderer, x1, y1, x2, y2); diff --git a/source/code/SagoImGui.cpp b/source/code/SagoImGui.cpp index f76bf22..df3d404 100644 --- a/source/code/SagoImGui.cpp +++ b/source/code/SagoImGui.cpp @@ -29,7 +29,8 @@ void InitImGui(SDL_Window* window, SDL_Renderer* renderer, int width, int height // Setup Dear ImGui context IMGUI_CHECKVERSION(); ImGui::CreateContext(); - ImGuiIO& io = ImGui::GetIO(); (void)io; + ImGuiIO& io = ImGui::GetIO(); + (void)io; io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; // Enable Gamepad Controls io.DisplaySize.x = static_cast(width); diff --git a/source/code/highscore.cpp b/source/code/highscore.cpp index ff92447..1bf08e2 100644 --- a/source/code/highscore.cpp +++ b/source/code/highscore.cpp @@ -58,7 +58,8 @@ Highscore::Highscore(const std::string& type, double speed) : filename(type+".js json j = json::parse(readFileContent); try { j.at("highscore").get_to(table); - } catch (json::exception& e) { + } + catch (json::exception& e) { std::cerr << "Failed to read highscore " << filename << " due to formatting errors. Resetting the file. Reason: " << e.what() << "\n"; table.clear(); diff --git a/source/code/main.cpp b/source/code/main.cpp index af7e2ef..c9bea2d 100644 --- a/source/code/main.cpp +++ b/source/code/main.cpp @@ -1761,7 +1761,7 @@ int runGame(Gametype gametype, int level) { bool pressed = false; int x = 0; int y = 0; - + theGame.GetBrickCoordinateFromMouse(pressed, mousex, mousey, x, y); if (pressed) { a.action = BlockGameAction::Action::MOUSE_DOWN; diff --git a/source/code/puzzlehandler.cpp b/source/code/puzzlehandler.cpp index 1205946..fba55b9 100644 --- a/source/code/puzzlehandler.cpp +++ b/source/code/puzzlehandler.cpp @@ -96,7 +96,8 @@ void LoadClearData() { json j = json::parse(readFileContent); try { j.at("cleared").get_to(puzzleCleared); - } catch (json::exception& e) { + } + catch (json::exception& e) { std::cerr << "Failed to read \"" << puzzleSavePath << "\". File will be regenerated. Reason: " << e.what() << "\n"; puzzleCleared.clear(); } @@ -180,8 +181,7 @@ void EditorAddPuzzle(size_t level) { if (nrOfPuzzles >= maxNrOfPuzzleStages) { return; } - if (level >= maxNrOfPuzzleStages) - { + if (level >= maxNrOfPuzzleStages) { level = 0; } memmove( puzzleLevels[level+1], puzzleLevels[level], sizeof(puzzleLevels[0])*(maxNrOfPuzzleStages-level-1) ); diff --git a/source/code/sago/SagoLogicalResize.hpp b/source/code/sago/SagoLogicalResize.hpp index 2d9074f..53db267 100644 --- a/source/code/sago/SagoLogicalResize.hpp +++ b/source/code/sago/SagoLogicalResize.hpp @@ -29,30 +29,99 @@ SOFTWARE. namespace sago { -class SagoLogicalResize -{ +/** + * @brief Provides coordinate transformation between logical and physical screen coordinates. + * + * This class helps create resolution-independent layouts by defining coordinates in a fixed + * "logical" resolution and automatically converting them to the actual "physical" screen size. + * It maintains aspect ratio by adding letterboxing/pillarboxing margins when needed. + * + * Key concepts: + * - Logical coordinates: Fixed coordinate system you design for (e.g., 1920x1080) + * - Physical coordinates: Actual screen/window size (e.g., 1280x720, 3840x2160, etc.) + * - Scale factor: Calculated to fit logical size into physical size while preserving aspect ratio + * - Margins: Black bars added to maintain aspect ratio (letterboxing/pillarboxing) + * - Tile alignment: As long as tiles are resized with LogicalToPhysical, they will align correctly. No gaps or overlaps. + * + * Example usage: + * @code + * // Design your UI for 1920x1080 + * SagoLogicalResize resize(1920, 1080); + * resize.SetPhysicalSize(window_width, window_height); + * + * // Draw at logical position (100, 100) + * int phys_x, phys_y; + * resize.LogicalToPhysical(100, 100, phys_x, phys_y); + * DrawSprite(renderer, phys_x, phys_y); + * + * // Handle mouse input + * int log_x, log_y; + * resize.PhysicalToLogical(mouse_x, mouse_y, log_x, log_y); + * if (log_x > 100 && log_x < 200) { ... } + * @endcode + */ +class SagoLogicalResize { public: - SagoLogicalResize() : logical_width_(1), logical_height_(1), physical_width_(1), physical_height_(1) { SetScaleFactor(); } + /** + * @brief Default constructor with minimal 1x1 logical size. + */ + SagoLogicalResize() : logical_width_(1), logical_height_(1), physical_width_(1), physical_height_(1) { + SetScaleFactor(); + } + + /** + * @brief Constructor with specified logical resolution. + * @param logical_width The width of the logical coordinate system (minimum 1) + * @param logical_height The height of the logical coordinate system (minimum 1) + */ SagoLogicalResize(int logical_width, int logical_height) - : logical_width_(std::max(1, logical_width)), logical_height_(std::max(1, logical_height)), physical_width_(1), physical_height_(1) { SetScaleFactor(); } + : logical_width_(std::max(1, logical_width)), logical_height_(std::max(1, logical_height)), physical_width_(1), physical_height_(1) { + SetScaleFactor(); + } - void SetPhysicalSize(int physical_width, int physical_height) - { + /** + * @brief Updates the physical (actual) screen size and recalculates scaling. + * + * Call this when the window is resized or when initializing the physical display size. + * This recalculates the scale factor and margins to fit the logical size into the physical size. + * + * @param physical_width The actual width of the screen/window (minimum 1) + * @param physical_height The actual height of the screen/window (minimum 1) + */ + void SetPhysicalSize(int physical_width, int physical_height) { //Physical size must be at least 1. Less than 1 is not drawn anyway and it prevents division by zero. physical_width_ = std::max(1, physical_width); physical_height_ = std::max(1, physical_height); SetScaleFactor(); } - void LogicalToPhysical(int logical_x, int logical_y, int &physical_x, int &physical_y) const - { + /** + * @brief Converts logical coordinates to physical screen coordinates. + * + * This applies the scale factor and adds the appropriate margins. + * Use this to convert positions where you want to draw elements on screen. + * + * @param logical_x The x coordinate in logical space + * @param logical_y The y coordinate in logical space + * @param physical_x Output: the x coordinate in physical screen space + * @param physical_y Output: the y coordinate in physical screen space + */ + void LogicalToPhysical(int logical_x, int logical_y, int& physical_x, int& physical_y) const { physical_x = logical_x; physical_y = logical_y; LogicalToPhysical(&physical_x, &physical_y); } - void LogicalToPhysical(int *x, int *y) const - { + /** + * @brief Converts logical coordinates to physical screen coordinates (pointer version). + * + * This applies the scale factor and adds the appropriate margins. + * Null pointers are safely ignored. + * + * @param x Pointer to x coordinate (will be converted in place), can be null + * @param y Pointer to y coordinate (will be converted in place), can be null + */ + void LogicalToPhysical(int* x, int* y) const { if (x) { *x = *x * scale_factor_ + left_margin_; } @@ -61,8 +130,19 @@ public: } } - void LogicalToPhysical(SDL_Rect &inout) const - { + /** + * @brief Converts a logical rectangle to physical screen coordinates. + * + * This converts both the position AND size of a rectangle. The rectangle + * is transformed in place. Use this for converting areas/regions rather than + * just single points. + * + * Note: This properly handles the size conversion by converting the bottom-right + * corner and calculating the new width/height from the difference. + * + * @param inout The rectangle in logical coordinates (input), converted to physical coordinates (output) + */ + void LogicalToPhysical(SDL_Rect& inout) const { SDL_Rect input = inout; LogicalToPhysical(&inout.x, &inout.y); LogicalToPhysical(input.x + input.w + 1, input.y + input.h + 1, inout.w, inout.h); @@ -70,37 +150,70 @@ public: inout.h -= inout.y - 1; } - void PhysicalToLogical(int physical_x, int physical_y, int &logical_x, int &logical_y) const - { + /** + * @brief Converts physical screen coordinates to logical coordinates. + * + * Use this to convert mouse input or other physical positions back to your + * logical coordinate system for hit detection and input handling. + * This removes margins and applies inverse scaling. + * + * @param physical_x The x coordinate in physical screen space + * @param physical_y The y coordinate in physical screen space + * @param logical_x Output: the x coordinate in logical space + * @param logical_y Output: the y coordinate in logical space + */ + void PhysicalToLogical(int physical_x, int physical_y, int& logical_x, int& logical_y) const { logical_x = (physical_x - left_margin_) / scale_factor_; logical_y = (physical_y - top_margin_) / scale_factor_; } - int GetTopMargin() const - { + /** + * @brief Gets the top margin (letterboxing/pillarboxing offset). + * + * This is the black bar size at the top when the aspect ratio requires vertical margins. + * Useful for debugging or custom rendering that needs to know the drawable area. + * + * @return The top margin in physical pixels + */ + int GetTopMargin() const { return top_margin_; } - int GetLeftMargin() const - { + /** + * @brief Gets the left margin (letterboxing/pillarboxing offset). + * + * This is the black bar size on the left when the aspect ratio requires horizontal margins. + * Useful for debugging or custom rendering that needs to know the drawable area. + * + * @return The left margin in physical pixels + */ + int GetLeftMargin() const { return left_margin_; } private: - void SetScaleFactor() - { + /** + * @brief Calculates the scale factor and margins based on current logical and physical sizes. + * + * The scale factor is chosen to fit the logical size into the physical size while + * maintaining aspect ratio. The margins center the content on screen. + * + * Scale factor = min(physical_width/logical_width, physical_height/logical_height) + * This ensures the content fits in both dimensions without distortion. + */ + void SetScaleFactor() { scale_factor_ = std::min(physical_width_ / logical_width_, physical_height_ / logical_height_); left_margin_ = (physical_width_ - logical_width_ * scale_factor_) / 2; top_margin_ = (physical_height_ - logical_height_ * scale_factor_) / 2; } - double scale_factor_; - int top_margin_; - int left_margin_; - double logical_width_; - double logical_height_; - double physical_width_; - double physical_height_; + double scale_factor_; ///< Multiplier to convert logical size to physical size + int top_margin_; ///< Vertical offset for centering (letterboxing) + int left_margin_; ///< Horizontal offset for centering (pillarboxing) + double logical_width_; ///< Width of the logical coordinate system + double logical_height_; ///< Height of the logical coordinate system + double physical_width_; ///< Actual width of the screen/window + double physical_height_; ///< Actual height of the screen/window }; } // namespace sago \ No newline at end of file diff --git a/source/code/stageclearhandler.cpp b/source/code/stageclearhandler.cpp index 5b6d4bd..98011a4 100644 --- a/source/code/stageclearhandler.cpp +++ b/source/code/stageclearhandler.cpp @@ -85,7 +85,8 @@ void LoadStageClearStages() { json j = json::parse(readFileContent); try { j.at("stages").get_to(stages); - } catch (json::exception& e) { + } + catch (json::exception& e) { std::cerr << "Failed to read highscore " << stageClearSaveName << " due to formatting errors. Resetting the file. Reason: " << e.what() << "\n"; stages.clear(); @@ -98,7 +99,8 @@ void LoadStageClearStages() { json j = json::parse(readFileContent); try { j.at("stages").get_to(parScores); - } catch (json::exception& e) { + } + catch (json::exception& e) { std::cerr << "Failed to read par times. Reason: " << e.what() << "\n"; } stages.resize(nrOfStageLevels); diff --git a/source/code/themes.cpp b/source/code/themes.cpp index 64c0ff7..5a3645b 100644 --- a/source/code/themes.cpp +++ b/source/code/themes.cpp @@ -138,7 +138,7 @@ static size_t current_theme = 0; void ThemesFillMissingFields(Theme& theme) { - if (theme.background.name.empty() || !ThemesBackgroundExists(theme.background.name) ){ + if (theme.background.name.empty() || !ThemesBackgroundExists(theme.background.name) ) { //If the theme does not define a background then use the standard. theme.background.name = "standard"; }