/* =========================================================================== * Sago Multi Scrambler Puzzle Copyright (C) 2022 Poul Sander This program 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 2 of the License, or (at your option) any later version. This program 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 this program. If not, see http://www.gnu.org/licenses/ Source information and contacts persons can be found at https://github.com/sago007/saland =========================================================================== */ #include "ImageSelectState.hpp" #include #include #include "globals.hpp" #include "PuzzleSingleImageState.hpp" #include "sago_common.hpp" #include "SagoImGui.hpp" #include "config.hpp" static void DrawRect(SDL_Renderer* target, int topx, int topy, int height, int width, const std::string& name) { const int size = 32; SDL_Rect bounds_ns = {topx, topy+size, width, height-2*size}; //bounds for south SDL_Rect bounds_e = {topx, topy, width-size, height}; const sago::SagoSprite& n = globalData.spriteHolder->GetSprite(name+"n"); const sago::SagoSprite& s = globalData.spriteHolder->GetSprite(name+"s"); const sago::SagoSprite& e = globalData.spriteHolder->GetSprite(name+"e"); const sago::SagoSprite& w = globalData.spriteHolder->GetSprite(name+"w"); const sago::SagoSprite& fill = globalData.spriteHolder->GetSprite(name+"fill"); for (int i = 1; i < width/size; ++i) { n.DrawBounded(target, SDL_GetTicks(), topx+i*size, topy, bounds_e); for (int j = 1; j < height/size; ++j) { w.DrawBounded(target, SDL_GetTicks(), topx, topy+j*size, bounds_ns); fill.Draw(target, SDL_GetTicks(),topx+i*size, topy+j*size); e.DrawBounded(target, SDL_GetTicks(), topx+width-size, topy+j*size, bounds_ns); } s.DrawBounded(target, SDL_GetTicks(), topx+i*size, topy+height-size, bounds_e); } //Corners const sago::SagoSprite& nw = globalData.spriteHolder->GetSprite(name+"nw"); const sago::SagoSprite& ne = globalData.spriteHolder->GetSprite(name+"ne"); const sago::SagoSprite& se = globalData.spriteHolder->GetSprite(name+"se"); const sago::SagoSprite& sw = globalData.spriteHolder->GetSprite(name+"sw"); nw.Draw(target, SDL_GetTicks(), topx, topy); ne.Draw(target, SDL_GetTicks(), topx+width-size, topy); se.Draw(target, SDL_GetTicks(), topx+width-size, topy+height-size); sw.Draw(target, SDL_GetTicks(), topx, topy+height-size); } static void DrawRectWhite(SDL_Renderer* target, int topx, int topy, int height, int width) { std::string name = "ui_rect_white_"; DrawRect(target, topx, topy, height, width, name); } static void DrawRectYellow(SDL_Renderer* target, int topx, int topy, int height, int width) { std::string name = "ui_rect_yellow_"; DrawRect(target, topx, topy, height, width, name); } ImageSelectState::ImageSelectState() { // Set up logical resolution for scalability // Using 1280x720 as the base logical resolution logicalResize = sago::SagoLogicalResize(1280, 720); logicalResize.SetPhysicalSize(globalData.xsize, globalData.ysize); } ImageSelectState::~ImageSelectState() { } bool ImageSelectState::IsActive() { return isActive; } void ImageSelectState::ProcessInput(const SDL_Event& event, bool& processed) { if (event.type == SDL_WINDOWEVENT) { if (event.window.event == SDL_WINDOWEVENT_SIZE_CHANGED) { logicalResize.SetPhysicalSize(globalData.xsize, globalData.ysize); } } ImGui_ImplSDL2_ProcessEvent(&event); } void ImageSelectState::Draw(SDL_Renderer* target) { // Logical coordinates for the layout const int logical_width = 1280; const int logical_height = 720; const int number_of_images_per_page = 6; const int frame_size = 300; const int frame_spacing = 20; const int frame_border = 10; const int number_of_columns = 3; const int top_x_logical = (logical_width - (3 * frame_size + 2 * frame_spacing)) / 2; const int top_y_logical = (logical_height - (2 * frame_size + frame_spacing)) / 2; for (int i = 0; i < number_of_images_per_page; ++i) { int x = i % number_of_columns; int y = i / number_of_columns; // Calculate logical rectangle for this frame SDL_Rect frame_logical; frame_logical.x = top_x_logical + x * (frame_size + frame_spacing); frame_logical.y = top_y_logical + y * (frame_size + frame_spacing); frame_logical.w = frame_size; frame_logical.h = frame_size; // Convert entire rectangle to physical coordinates SDL_Rect frame_physical = frame_logical; logicalResize.LogicalToPhysical(frame_physical); // Calculate border in physical coordinates SDL_Rect border_logical = {0, 0, frame_border, frame_border}; SDL_Rect border_physical = border_logical; logicalResize.LogicalToPhysical(border_physical); int border_physical_w = border_physical.w; int border_physical_h = border_physical.h; DrawRectYellow(target, frame_physical.x, frame_physical.y, frame_physical.h, frame_physical.w); size_t image_number = i + firstImage; if (image_number < imageHolders.size()) { imageHolders[image_number].Draw(target, frame_physical.x + border_physical_w, frame_physical.y + border_physical_h, frame_physical.w - 2 * border_physical_w, frame_physical.h - 2 * border_physical_h); } if (image_number < imageNameFields.size()) { int text_x_physical, text_y_physical; logicalResize.LogicalToPhysical( frame_logical.x + frame_size / 2, frame_logical.y + frame_size - 5, text_x_physical, text_y_physical); imageNameFields[image_number].Draw(target, text_x_physical, text_y_physical, sago::SagoTextField::Alignment::center, sago::SagoTextField::VerticalAlignment::bottom); } // Draw favorite indicator with sprites if (image_number < imageList.size()) { std::string absolutePath = std::filesystem::absolute(imageList[image_number]).string(); bool isFavorite = IsFavorite(absolutePath); int icon_x_physical, icon_y_physical; logicalResize.LogicalToPhysical( frame_logical.x + frame_size - 40, frame_logical.y + 10, icon_x_physical, icon_y_physical); // Draw the checkbox sprite const sago::SagoSprite& checkbox = globalData.spriteHolder->GetSprite("i_level_check_box"); checkbox.Draw(target, SDL_GetTicks(), icon_x_physical, icon_y_physical); // If favorite, draw the check mark on top if (isFavorite) { const sago::SagoSprite& check = globalData.spriteHolder->GetSprite("i_level_check"); check.Draw(target, SDL_GetTicks(), icon_x_physical, icon_y_physical); } // Create an invisible ImGui button overlay for clicking ImGui::SetNextWindowPos(ImVec2(icon_x_physical, icon_y_physical)); ImGui::SetNextWindowSize(ImVec2(0, 0)); ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0)); ImGui::Begin(("##favorite" + std::to_string(image_number)).c_str(), nullptr, ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoBackground); // Invisible button with the size of the icon ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0)); ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.2f, 0.2f, 0.2f, 0.3f)); ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(0.3f, 0.3f, 0.3f, 0.5f)); ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0, 0)); if (ImGui::Button("##toggle", ImVec2(30, 30))) { if (isFavorite) { RemoveFavorite(absolutePath); } else { AddFavorite(absolutePath); } } ImGui::PopStyleVar(); ImGui::PopStyleColor(3); if (ImGui::IsItemHovered()) { ImGui::SetTooltip(isFavorite ? "Remove from favorites" : "Add to favorites"); } ImGui::End(); ImGui::PopStyleVar(); } } ImGui::BeginMainMenuBar(); if (ImGui::BeginMenu("File")) { if (ImGui::MenuItem("Close")) { isActive = false; } ImGui::EndMenu(); } if (ImGui::BeginMenu("View")) { if (ImGui::MenuItem("Show Only Favorites", nullptr, showOnlyFavorites)) { showOnlyFavorites = !showOnlyFavorites; // Reload the image list with the new filter imageList.clear(); imageNameFields.clear(); imageHolders.clear(); firstImage = 0; Init(); } ImGui::EndMenu(); } ImGui::EndMainMenuBar(); ImGui::Begin("Page control"); if (ImGui::Button("Next page")) { firstImage += number_of_images_per_page; } if (ImGui::Button("Previous page")) { if (firstImage < number_of_images_per_page) { firstImage = 0; } else { firstImage -= number_of_images_per_page; } } ImGui::End(); } void ImageSelectState::Update() { // If the mouse button is released, make bMouseUp equal true if ( !(SDL_GetMouseState(nullptr, nullptr)&SDL_BUTTON(1)) ) { globalData.mouseUp=true; } if (SDL_GetMouseState(nullptr,nullptr)&SDL_BUTTON(1) && globalData.mouseUp) { globalData.mouseUp = false; // Don't process clicks if ImGui wants to capture the mouse (e.g., clicking on buttons) if (ImGui::GetIO().WantCaptureMouse) { return; } // Convert physical mouse coordinates to logical coordinates int mouse_x_logical, mouse_y_logical; logicalResize.PhysicalToLogical(globalData.mousex, globalData.mousey, mouse_x_logical, mouse_y_logical); const int logical_width = 1280; const int logical_height = 720; if (mouse_x_logical > 0 && mouse_x_logical < logical_width && mouse_y_logical > 0 && mouse_y_logical < logical_height) { const int number_of_images_per_page = 6; const int frame_size = 300; const int frame_spacing = 20; const int number_of_columns = 3; const int top_x_logical = (logical_width - (3 * frame_size + 2 * frame_spacing)) / 2; const int top_y_logical = (logical_height - (2 * frame_size + frame_spacing)) / 2; for (int i = 0; i < number_of_images_per_page; ++i) { int x = i % number_of_columns; int y = i / number_of_columns; size_t image_number = i + firstImage; int rect_x_logical = top_x_logical + x * (frame_size + frame_spacing); int rect_y_logical = top_y_logical + y * (frame_size + frame_spacing); if (mouse_x_logical > rect_x_logical && mouse_x_logical < rect_x_logical + frame_size && mouse_y_logical > rect_y_logical && mouse_y_logical < rect_y_logical + frame_size) { if (image_number < imageList.size()) { printf("Clicked on image %s\n", imageList[image_number].c_str()); PuzzleSingleImageState psi; psi.LoadPictureFromFile(imageList[image_number], globalData.screen); RunGameState(psi); globalData.mouseUp=true; return; } } } } } } /** * @brief Compares two characters ignoring case * * @param a * @param b * @return true if the characters are equal ignoring case * @return false if the characters are not equal ignoring case */ static bool ichar_equals(char a, char b) { return std::tolower(static_cast(a)) == std::tolower(static_cast(b)); } /** * @brief Checks if the filename has the given extension. The comparison is case insensitive. * * @param filename The filename to check * @param extension The extension to check for (example ".jpg") * @return true if the filename has the given extension * @return false if the filename does not have the given extension */ static bool HasExtension(const std::string& filename, const std::string& extension) { if (filename.size() < extension.size()) { return false; } return std::equal(extension.rbegin(), extension.rend(), filename.rbegin(), ichar_equals); } static void setFontText(const sago::SagoDataHolder* holder, sago::SagoTextField& field, const char* text) { field.SetHolder(holder); field.SetFont("freeserif"); field.SetColor({255,255,255,255}); field.SetFontSize(24); field.SetOutline(1, {0,0,0,255}); field.SetText(text); } void ImageSelectState::Init() { if (!folder.empty()) { for (const auto& entry : std::filesystem::directory_iterator(folder)) { if (entry.is_regular_file() && (HasExtension(entry.path().string(), ".jpg") || HasExtension(entry.path().string(), ".jpeg") || HasExtension(entry.path().string(), ".png") ) ) { std::string imagePath = entry.path().string(); std::string absolutePath = std::filesystem::absolute(imagePath).string(); // If showOnlyFavorites is enabled, skip non-favorite images if (showOnlyFavorites && !IsFavorite(absolutePath)) { continue; } std::cout << entry.path() << std::endl; imageList.push_back(imagePath); sago::SagoTextField field; setFontText(globalData.dataHolder, field, entry.path().filename().string().c_str()); imageNameFields.push_back(std::move(field)); imageHolders.emplace_back(); imageHolders.back().LoadPictureFromFileLazy(imagePath); } } } }