From 1b5c622fc68b1ccc827280e4ca22ebd785bd12c6 Mon Sep 17 00:00:00 2001 From: Mauro Junior <45118493+jetrotal@users.noreply.github.com> Date: Sat, 27 Jul 2024 02:58:25 -0300 Subject: [PATCH] CommandStringPicMenu - Inline buttons the substrings "\/" are the tag that points to a link --- src/game_interpreter.cpp | 31 ++++++- src/game_windows.cpp | 35 ++++++++ src/window_selectable.cpp | 167 ++++++++++++++++++++++++++++++++++++++ src/window_selectable.h | 8 ++ 4 files changed, 240 insertions(+), 1 deletion(-) diff --git a/src/game_interpreter.cpp b/src/game_interpreter.cpp index 798b8bef37..ee5d681d98 100644 --- a/src/game_interpreter.cpp +++ b/src/game_interpreter.cpp @@ -5207,6 +5207,10 @@ bool Game_Interpreter::CommandStringPicMenu(const lcf::rpg::EventCommand& com) { const int output_var_current_item = ValueOrVariable(com.parameters[3], com.parameters[4]); const int output_var_input_state = ValueOrVariable(com.parameters[5], com.parameters[6]); + if (strpic_index <= 0) { + Output::Warning("CommandStringPicMenu: Requested invalid picture id ({})", strpic_index); + return true; + } auto& window_data = Main_Data::game_windows->GetWindow(strpic_index); if (!window_data.window) { @@ -5321,12 +5325,37 @@ bool Game_Interpreter::CommandStringPicMenu(const lcf::rpg::EventCommand& com) { auto pending_message = Main_Data::game_windows->GeneratePendingMessage(ToString(text_data.text)); const auto& lines = pending_message.GetLines(); + bool use_substring_mode = false; + + // Detect mode by checking for "\/" in the entire text + for (const auto& line : lines) { + if (line.find("\\/") != std::string::npos) { + use_substring_mode = true; + break; + } + } + + // Now apply the appropriate counting method for (const auto& line : lines) { std::stringstream ss(line); std::string sub_line; while (Utils::ReadLine(ss, sub_line)) { - max_item++; + if (use_substring_mode) { + size_t pos = 0; + int count = 0; + while ((pos = sub_line.find("\\/", pos)) != std::string::npos) { + count++; + if (count % 2 != 0) { // Check if count is odd + max_item++; + } + pos += 2; // Move past the found substring + } + } + else { + max_item++; // Increment once per sub_line in original mode + } + // Output::Warning("{}", sub_line); } } diff --git a/src/game_windows.cpp b/src/game_windows.cpp index 18ca11cdec..cdda12c741 100644 --- a/src/game_windows.cpp +++ b/src/game_windows.cpp @@ -248,6 +248,10 @@ void Game_Windows::Window_User::Refresh(bool& async_wait) { int x_max = 0; int y_max = 0; + bool is_drawing_rect = false; + Rect current_rect; + std::vector inline_rects = {}; + auto ProcessText = [&](ProcessTextMode mode) { for (size_t i = 0; i < data.texts.size(); ++i) { auto& font = fonts[i]; @@ -335,6 +339,26 @@ void Game_Windows::Window_User::Refresh(bool& async_wait) { text_index = pres.next; } break; + case '/': + { + if (mode == ProcessTextMode::TextDrawing) continue; + // Toggle rect drawing mode + if (!is_drawing_rect) { + // Start drawing a new rect + current_rect.x = x; + current_rect.y = y; + is_drawing_rect = true; + } + else { + // Finish the current rect + current_rect.width = x + 1 - current_rect.x; + if (current_rect.width > x_max && x_max != 0 ) current_rect.width -=1; + current_rect.height = y - current_rect.y; + inline_rects.push_back(current_rect); + is_drawing_rect = false; + } + } + break; } continue; } @@ -342,6 +366,14 @@ void Game_Windows::Window_User::Refresh(bool& async_wait) { line32 += static_cast(ch); } + // After the loop, check if there's an unfinished rect + if (is_drawing_rect) { + current_rect.width = x_max -1; + current_rect.height = y; + inline_rects.push_back(current_rect); + is_drawing_rect = false; + } + if (!line32.empty()) { if (mode == ProcessTextMode::TextDrawing) { Text::Draw(*window->GetContents(), x, y, *font, *system, text_color, Utils::EncodeUTF(line32)); @@ -389,6 +421,9 @@ void Game_Windows::Window_User::Refresh(bool& async_wait) { } window = std::make_unique(0, 0, data.width, data.height); + + if (!inline_rects.empty()) window->inline_rects = inline_rects; + if (!data.flags.border_margin) { window->SetBorderX(0); // FIXME: Figure out why 0 does not work here (bug in Window class) diff --git a/src/window_selectable.cpp b/src/window_selectable.cpp index d9f7dac770..8ae4efbab6 100644 --- a/src/window_selectable.cpp +++ b/src/window_selectable.cpp @@ -21,6 +21,7 @@ #include "input.h" #include "util_macro.h" #include "bitmap.h" +#include "output.h" constexpr int arrow_animation_frames = 20; @@ -99,6 +100,13 @@ void Window_Selectable::UpdateHelp() { // Update Cursor Rect void Window_Selectable::UpdateCursorRect() { + + bool cursor_is_inline = !inline_rects.empty() && index < static_cast(inline_rects.size()); + if (cursor_is_inline) { + InlineUpdateCursorRect(); + return; + } + int cursor_width = 0; int x = 0; if (index < 0) { @@ -128,6 +136,67 @@ void Window_Selectable::UpdateCursorRect() { SetCursorRect(Rect(x, y, cursor_width, item_height)); } +void Window_Selectable::InlineUpdateCursorRect() { + if (index < 0 || index >= static_cast(inline_rects.size())) { + SetCursorRect(Rect()); + return; + } + + const auto& rect = inline_rects[index]; + int cursor_width = rect.width + 9; + int x = rect.x - 4; + int y = rect.y; + + int height = this->height - border_y * 2; + int max_y = std::max_element(inline_rects.begin(), inline_rects.end(), + [](const auto& a, const auto& b) { return a.y + a.height < b.y + b.height; })->y + menu_item_height; + int max_oy = std::max(0, max_y - height); + + int target_oy = oy; + if (y < oy) { + target_oy = y; + scroll_dir = scroll_dir == 0 ? -1 : scroll_dir; + } + else if (y + menu_item_height > oy + height) { + target_oy = std::min(y + menu_item_height - height, max_oy); + scroll_dir = scroll_dir == 0 ? 1 : scroll_dir; + } + else { + scroll_dir = 0; + } + + if (scroll_dir != 0) { + scroll_progress = std::min(scroll_progress + 1, 4); + int scroll_amount = (menu_item_height * scroll_progress / 4 - menu_item_height * (scroll_progress - 1) / 4) * scroll_dir; + oy = std::clamp(oy + scroll_amount, 0, max_oy); + if (scroll_progress == 4) { + scroll_dir = 0; + scroll_progress = 0; + oy = target_oy; + } + } + else { + oy = target_oy; + } + + y -= oy; + if (border_x == 0) { + x += border_x; + cursor_width -= border_x * 2; + } + + //workaround to deal with the cursor appearing over the borders while scrolling + int output_y = scroll_dir == -1 ? y / 2 : y; + int output_height = scroll_dir == 1 ? menu_item_height / 2 : menu_item_height; + + SetCursorRect(Rect(x, output_y, cursor_width, output_height)); + + UpdateArrows(); + bool latest_rect_is_oob = inline_rects.back().y + menu_item_height != oy + height; + bool arrow_visible = (arrow_frame < arrow_animation_frames); + SetDownArrow(latest_rect_is_oob && arrow_visible); +} + void Window_Selectable::UpdateArrows() { bool show_up_arrow = (GetTopRow() > 0); bool show_down_arrow = (GetTopRow() < (GetRowMax() - GetPageRowMax())); @@ -143,6 +212,13 @@ void Window_Selectable::UpdateArrows() { // Update void Window_Selectable::Update() { Window_Base::Update(); + + bool cursor_is_inline = !inline_rects.empty() && index < static_cast(inline_rects.size()); + if (cursor_is_inline) { + InlineUpdate(); + return; + } + if (active && item_max > 0 && index >= 0) { if (scroll_dir != 0) { scroll_progress++; @@ -234,6 +310,97 @@ void Window_Selectable::Update() { UpdateArrows(); } +void Window_Selectable::InlineUpdate() { + if (active && !inline_rects.empty()) { + int old_index = index; + if (Input::IsRepeated(Input::DOWN) || Input::IsTriggered(Input::SCROLL_DOWN)) { + MoveIndexVertical(1); + } + if (Input::IsRepeated(Input::UP) || Input::IsTriggered(Input::SCROLL_UP)) { + MoveIndexVertical(-1); + } + if (Input::IsRepeated(Input::RIGHT)) { + MoveIndexHorizontal(1); + } + if (Input::IsRepeated(Input::LEFT)) { + MoveIndexHorizontal(-1); + } + if (index != old_index) { + Main_Data::game_system->SePlay(Main_Data::game_system->GetSystemSE(Main_Data::game_system->SFX_Cursor)); + if (active && help_window != NULL) { + UpdateHelp(); + } + } + } + InlineUpdateCursorRect(); + +} + +void Window_Selectable::MoveIndexHorizontal(int direction) { + int current_x = inline_rects[index].x; + int current_y = inline_rects[index].y; + int nearest_index = -1; + int min_distance = std::numeric_limits::max(); + + for (size_t i = 0; i < inline_rects.size(); ++i) { + if (i != index && inline_rects[i].y == current_y) { + if ((direction > 0 && inline_rects[i].x > current_x) || + (direction < 0 && inline_rects[i].x < current_x)) { + int distance = std::abs(inline_rects[i].x - current_x); + if (distance < min_distance) { + min_distance = distance; + nearest_index = i; + } + } + } + } + + if (nearest_index != -1) { + Main_Data::game_system->SePlay(Main_Data::game_system->GetSystemSE(Main_Data::game_system->SFX_Cursor)); + index = nearest_index; + } +} + +void Window_Selectable::MoveIndexVertical(int direction) { + int current_x = inline_rects[index].x; + int current_y = inline_rects[index].y; + int nearest_index = -1; + int min_y_distance = std::numeric_limits::max(); + int min_x = std::numeric_limits::max(); + + // First pass: find the minimum y distance in the correct direction + for (size_t i = 0; i < inline_rects.size(); ++i) { + if (i != index) { + if ((direction > 0 && inline_rects[i].y > current_y) || + (direction < 0 && inline_rects[i].y < current_y)) { + int y_distance = std::abs(inline_rects[i].y - current_y); + if (y_distance < min_y_distance) { + min_y_distance = y_distance; + } + } + } + } + + // Second pass: among buttons with minimum y distance, find the one with smallest x value + for (size_t i = 0; i < inline_rects.size(); ++i) { + if (i != index) { + if ((direction > 0 && inline_rects[i].y > current_y) || + (direction < 0 && inline_rects[i].y < current_y)) { + int y_distance = std::abs(inline_rects[i].y - current_y); + if (y_distance == min_y_distance && inline_rects[i].x < min_x) { + min_x = inline_rects[i].x; + nearest_index = i; + } + } + } + } + + if (nearest_index != -1) { + Main_Data::game_system->SePlay(Main_Data::game_system->GetSystemSE(Main_Data::game_system->SFX_Cursor)); + index = nearest_index; + } +} + // Set endless scrolling state void Window_Selectable::SetEndlessScrolling(bool state) { endless_scrolling = state; diff --git a/src/window_selectable.h b/src/window_selectable.h index 54e0bccdc2..1ba84dc885 100644 --- a/src/window_selectable.h +++ b/src/window_selectable.h @@ -30,6 +30,8 @@ class Window_Selectable: public Window_Base { public: Window_Selectable(int ix, int iy, int iwidth, int iheight); + std::vector inline_rects = {}; + /** * Creates the contents based on how many items * are currently in the window. @@ -76,6 +78,12 @@ class Window_Selectable: public Window_Base { virtual void UpdateCursorRect(); void Update() override; + void InlineUpdateCursorRect(); + void InlineUpdate(); + + void MoveIndexVertical(int direction); + void MoveIndexHorizontal(int direction); + virtual void UpdateHelp(); /**