From dc2b46baadaa9c1fb381f06ecbcbfb42148407a2 Mon Sep 17 00:00:00 2001 From: anxiousintrovert <82425472+anxiousintrovert@users.noreply.github.com> Date: Sat, 17 Oct 2026 00:04:39 -0500 Subject: [PATCH 1/4] Complete mouse handling for title, Journal, and save prompts --- port/src/battlemenu/main_ring.cpp | 3 +- port/src/menu_journal_chest.cpp | 14 ++++ port/src/menu_journal_chest.hpp | 5 +- port/src/menu_mouse.cpp | 87 +++++++++++++++++------ port/src/menu_mouse.hpp | 3 + port/src/tests/menu_save_pointer_test.cpp | 68 ++++++++++++++++++ port/src/title/titleloop.cpp | 79 ++++++++++++++++++-- 7 files changed, 230 insertions(+), 29 deletions(-) create mode 100644 port/src/tests/menu_save_pointer_test.cpp diff --git a/port/src/battlemenu/main_ring.cpp b/port/src/battlemenu/main_ring.cpp index 8bfa0c193..d82b89570 100644 --- a/port/src/battlemenu/main_ring.cpp +++ b/port/src/battlemenu/main_ring.cpp @@ -707,4 +707,5 @@ bool TakeBattleMenuLoadRequest() { } ingame_load_pending = false; return true; -} \ No newline at end of file +} +void BattleMenuIconPosition(int index, float &x, float &y) { GetSafeIconPos(index, x, y); } diff --git a/port/src/menu_journal_chest.cpp b/port/src/menu_journal_chest.cpp index 66af05442..800cab93f 100644 --- a/port/src/menu_journal_chest.cpp +++ b/port/src/menu_journal_chest.cpp @@ -131,6 +131,20 @@ void Open() { s_cursor.SnapTo(48.0f, static_cast(kLeftStartY - 2)); } +void HandlePointer(float x, float y) { + if (x >= 84 && x < 248 && y >= kLeftStartY - 4 && + y < kLeftStartY - 4 + TRACKER_TOWN_COUNT * kRowSpacing) { + const int town = static_cast((y - (kLeftStartY - 4)) / kRowSpacing); + if (town != s_selected_town) { + s_selected_town = town; + ComMenuSePlay(MENU_SOUND_CURSOR); + } + } else if (x >= 180 && x < 460 && y >= 100 && y < 128) { + s_filter_miracle_only = !s_filter_miracle_only; + ComMenuSePlay(MENU_SOUND_CONFIRM); + } +} + bool HandleKey() { if (GamePad.Down(PAD_CIRCLE)) { ComMenuSePlay(MENU_SOUND_REFUSE); diff --git a/port/src/menu_journal_chest.hpp b/port/src/menu_journal_chest.hpp index 5e95d9d13..66c63ce5e 100644 --- a/port/src/menu_journal_chest.hpp +++ b/port/src/menu_journal_chest.hpp @@ -13,10 +13,13 @@ void Open(); /// Handles gamepad input while the Chest Tracker is active. Returns true if the user exited back to the Journal menu. bool HandleKey(); +// Select a settlement or toggle the filter at the displayed pointer position. +void HandlePointer(float x, float y); + /// Renders the Chest Tracker dashboard using the UI framework. void Draw(); /// Appends the Chest Tracker's localization keys and default English strings to `out`. void AppendStrings(std::vector>& out); -} // namespace JournalChest \ No newline at end of file +} // namespace JournalChest diff --git a/port/src/menu_mouse.cpp b/port/src/menu_mouse.cpp index ef839f56a..4cb848215 100644 --- a/port/src/menu_mouse.cpp +++ b/port/src/menu_mouse.cpp @@ -4,6 +4,7 @@ #include #include "battlemenu.hpp" +#include "clsmes.hpp" #include "itemdata.hpp" #include "dngstatusdata.hpp" #include "editpartsinfo.hpp" @@ -13,6 +14,7 @@ #include "menu_draw.hpp" #include "menu_inventory.hpp" #include "menu_manual.hpp" +#include "menu_journal_chest.hpp" #include "weapon_buildup.hpp" #include "menu_misc.hpp" #include "menu_option.hpp" @@ -53,23 +55,10 @@ int RingIconCount() { return BtlMenuMode == BATTLE_MENU_MODE_DUNGEON ? 8 : 9; } -// Matches port/src/battlemenu.cpp GetSafeIconPos() so icons >= orig_max have valid hitboxes. -void GetRingIconPos(int index, float &out_x, float &out_y) { - const int orig_max = (BtlMenuMode == BATTLE_MENU_MODE_DUNGEON) ? 7 : 8; - if (index < orig_max) { - out_x = NorMenuIcon[index].x; - out_y = NorMenuIcon[index].y; - } else { - const int diff = index - (orig_max - 1); - out_x = NorMenuIcon[orig_max - 1].x - (16.0f * diff); - out_y = NorMenuIcon[orig_max - 1].y + (40.0f * diff); - } -} - // The row of one ring icon: its icon and the label beside it, where the brackets stand. Rect RingRow(int icon) { float ix = 0.0f, iy = 0.0f; - GetRingIconPos(icon, ix, iy); + BattleMenuIconPosition(icon, ix, iy); int left = static_cast(ix) - 18; int top = static_cast(iy) - 8; return Rect{left, top, left + 250, top + 34}; @@ -329,6 +318,10 @@ void SaveMouse() { } MenuPointerTake take = g_save.Take(320.0f, 240.0f); MenuPointerShow(&g_save); + if ((take.clicked & 2) != 0) { + MenuPointerPress(kInputCircle); + return; + } bool slots = SaveMenu.key_no == SAVE_KEY_FILE_SELECT; if (slots) { int count = SaveSlotRows(SaveSlots, SaveMenu.access_kind == SAVE_ACCESS_SAVE); @@ -349,12 +342,32 @@ void SaveMouse() { } else { bool confirm = SaveMenu.key_no == SAVE_KEY_LOAD_DECIDE || SaveMenu.key_no == SAVE_KEY_SAVE_DECIDE || SaveMenu.key_no == SAVE_KEY_AFTER_ENDING; - int x = SaveMenu.key_no == SAVE_KEY_AFTER_ENDING ? 196 : 246; - int y = SaveMenu.key_no == SAVE_KEY_AFTER_ENDING ? 140 : 156; - int hit = take.pointing ? YesNoHit(x, y, g_save.x, g_save.y) : -1; - if (confirm && hit >= 0) { - if ((take.clicked & 1) != 0) MenuPointerPress(hit == 0 ? kInputCross : kInputCircle); - if ((take.clicked & 2) != 0) MenuPointerPress(kInputCircle); + if (!take.pointing || (take.clicked & 1) == 0) return; + const auto &mes = CommonMenuMes2; + if (confirm) { + // These are inline Cross/Yes and Circle/No prompts in a centred message, + // not the dungeon's vertical yes/no plate. Follow the laid-out glyphs. + for (int i = 0; i < mes.win_line_num; ++i) { + const auto &line = mes.win_line[i]; + if (line.code != -0x2F8 && line.code != -0x2FA) continue; + float left = mes.text_x + line.x; + float top = mes.text_y + line.y; + float right = mes.text_x + mes.text_width; + for (int j = i + 1; j < mes.win_line_num && mes.win_line[j].y == line.y; ++j) { + if (mes.win_line[j].code == -0x2F8 || mes.win_line[j].code == -0x2FA) { + right = mes.text_x + mes.win_line[j].x; + break; + } + } + if (g_save.x >= left && g_save.x < right && g_save.y >= top && g_save.y < top + mes.char_height) { + MenuPointerPress(line.code == -0x2F8 ? kInputCross : kInputCircle); + break; + } + } + } else if (SaveMenu.key_no == SAVE_KEY_END_SAVE || SaveMenu.key_no == SAVE_KEY_END_SAVE_ENDING || + SaveMenu.key_no == SAVE_KEY_ALERT) { + // Informational messages have no choice: a click anywhere acknowledges them. + MenuPointerPress(kInputCross); } } } @@ -989,6 +1002,35 @@ bool WeaponMouse(const MenuPointerTake &take) { } } +// The new journal categories and tracker use their displayed row positions. +void JournalMouse(const MenuPointerTake &take) { + if ((take.clicked & 2) != 0) { + MenuPointerPress(kInputCircle); + return; + } + if (!take.pointing || (take.clicked & 1) == 0) return; + const float x = g_battle.x, y = g_battle.y; + if (ManualMenu.mode == 100) { + JournalChest::HandlePointer(x, y); + } else if (ManualMenu.mode == MANUAL_MODE_SELECT && ManualMenu.selection_level == -2) { + if (x >= 135 && x < 355 && y >= 96 && y < 236) { + ManualMenu.category = static_cast((y - 96) / 28); + MenuPointerPress(kInputCross); + } + } else if (ManualMenu.mode == MANUAL_MODE_SELECT && ManualMenu.selection_level == -1) { + constexpr int counts[] = {5, 5, 4, 3}; + const auto &text = CommonMenuMes3; + if (ManualMenu.category >= 0 && ManualMenu.category < 4 && text.char_height > 0 && + x >= text.text_x && x < text.text_x + text.char_width * text.text_columns && + y >= text.text_y && y < text.text_y + counts[ManualMenu.category] * text.char_height) { + ManualMenu.entry = static_cast((y - text.text_y) / text.char_height); + MenuPointerPress(kInputCross); + } + } else if (ManualMenu.mode == MANUAL_MODE_READING && x >= 0 && x < 640 && y >= 0 && y < 480) { + MenuPointerPress(x < 160 ? kInputLeft : kInputCross); + } +} + void BattleMouse() { if (!g_battle.IsOpen()) { g_battle.Open(); @@ -1021,6 +1063,11 @@ void BattleMouse() { case BTLMENU_STATE_CHARA: AlliesMouse(take); break; + case BTLMENU_STATE_MANUAL: + case BTLMENU_STATE_MANUAL_OPEN: + // Like Georama, the manual keeps its OPEN state after the slide finishes. + if (BtlEffectFlag == BTLEFFECT_NONE) JournalMouse(take); + break; case BTLMENU_STATE_MOVE: MoveMouse(take); break; diff --git a/port/src/menu_mouse.hpp b/port/src/menu_mouse.hpp index 59a32dd57..b7644ff71 100644 --- a/port/src/menu_mouse.hpp +++ b/port/src/menu_mouse.hpp @@ -6,3 +6,6 @@ void MenuMouseNoteBattleMenu(); // Draws what the mouse adds to the pause menu's own picture: the question asked before an item is // thrown away. Called at the end of BattleMenuDraw. void MenuMouseDrawOverlay(); + +// The same animated icon coordinates used by the renderer and pointer hit testing. +void BattleMenuIconPosition(int index, float &x, float &y); diff --git a/port/src/tests/menu_save_pointer_test.cpp b/port/src/tests/menu_save_pointer_test.cpp new file mode 100644 index 000000000..c1a28aeff --- /dev/null +++ b/port/src/tests/menu_save_pointer_test.cpp @@ -0,0 +1,68 @@ +#include + +#include "clsmes.hpp" +#include "memcard.hpp" +#include "menu_pointer.hpp" +#include "menu_save.hpp" +#include "platform/input.hpp" + +namespace { +void Open(int state) { + InputSetScriptedDevices({}); + SaveMenu.texture_ready = 1; + SaveMenu.key_no = state; + MenuMouseUpdate(); +} + +int Click(unsigned button) { + InputKeyboardMouse mouse; + mouse.mouse_buttons = button; + InputSetScriptedDevices(mouse); + MenuMouseUpdate(); + int result = MenuPointerSyntheticDown(); + InputSetScriptedDevices({}); + SaveMenu.texture_ready = 0; + MenuMouseUpdate(); + return result; +} + +void Prompt(int left) { + // The initial pointer fingertip is (346, 254). Put the inline choices on + // the second message row, well away from the old hard-coded vertical plate. + auto &mes = CommonMenuMes2; + mes.text_x = left; + mes.text_y = 220; + mes.text_width = 200; + mes.char_height = 24; + mes.win_line_num = 2; + mes.win_line[0] = {-0x2F8, 0, 24, 0, 0}; + mes.win_line[1] = {-0x2FA, 80, 24, 0, 0}; +} +} + +TEST(MenuSavePointer, LoadYesFollowsCentredInlinePrompt) { + Prompt(300); + Open(SAVE_KEY_LOAD_DECIDE); + EXPECT_EQ(Click(1), kInputCross); +} + +TEST(MenuSavePointer, SaveNoFollowsCentredInlinePrompt) { + Prompt(200); + Open(SAVE_KEY_SAVE_DECIDE); + EXPECT_EQ(Click(1), kInputCircle); +} + +TEST(MenuSavePointer, CompletedMessageCanBeDismissedAnywhere) { + // Deliberately place the message away from the initial pointer. + CommonMenuMes2.text_x = CommonMenuMes2.text_y = 0; + CommonMenuMes2.text_columns = 1; + CommonMenuMes2.text_rows = 1; + Open(SAVE_KEY_END_SAVE); + EXPECT_EQ(Click(1), kInputCross); +} + +TEST(MenuSavePointer, BackDoesNotRequireHoveringAChoice) { + Prompt(0); + Open(SAVE_KEY_LOAD_DECIDE); + EXPECT_EQ(Click(2), kInputCircle); +} diff --git a/port/src/title/titleloop.cpp b/port/src/title/titleloop.cpp index f8460c16b..c537cd3fb 100644 --- a/port/src/title/titleloop.cpp +++ b/port/src/title/titleloop.cpp @@ -15,6 +15,7 @@ #include "main.hpp" #include "memcard.hpp" #include "menu_save.hpp" +#include "menu_pointer.hpp" #include "mglib.hpp" #include "rect.hpp" #include "savedata.hpp" @@ -42,6 +43,57 @@ int OpeningBookKey(); void OpeningBookDraw(); void TiPlayVolSE(int group, int no, int voice, float volume); +namespace { +MenuPointer title_pointer; + +struct TitlePointerAction { + bool start = false; + bool confirm = false; + bool back = false; + bool moved = false; +}; + +TitlePointerAction TakeTitlePointer() { + TitlePointerAction action; + if (CProcess.no == TITLE_STEP_OPTION_INIT || CProcess.no == TITLE_STEP_OPTION || + CProcess.no == TITLE_STEP_SAVE_INIT || CProcess.no == TITLE_STEP_SAVE) { + // The child screen owns pointer capture, including a button held through the fade. + title_pointer.Abandon(); + return action; + } + if (CProcess.no != TITLE_STEP_LOGO && CProcess.no != TITLE_STEP_MENU) { + title_pointer.Close(); + return action; + } + if (!title_pointer.IsOpen()) { + title_pointer.Open(); + } + auto take = title_pointer.Take(225.0f, CProcess.no == TITLE_STEP_MENU ? CCursol.GetPos() : 312.0f); + MenuPointerShow(&title_pointer); + if (!take.pointing || !take.moved) { + return action; + } + const float x = title_pointer.x; + const float y = title_pointer.y - (SCREEN_HEIGHT - 448) / 2; + if (CProcess.no == TITLE_STEP_LOGO) { + action.start = (take.clicked & 1) && x >= 64 && x < 576 && y >= 296 && y < 360; + } else { + action.back = (take.clicked & 2) != 0; + if (x >= 193 && x < 449 && y >= 280 && y < 376) { + int row = static_cast((y - 280) / 32); + if (CCursol.select != row) { + CCursol.select = row; + TiPlayVolSE(MIDI_PORT_UNK_D, 122, 24, 1.0f); + } + action.moved = true; + action.confirm = (take.clicked & 1) != 0; + opcnt = 0; + } + } + return action; +} +} // namespace + PORT_OVERRIDE int TitleLoop() { sceVu0FVECTOR pos; sceVu0FMATRIX matrix; @@ -52,6 +104,7 @@ PORT_OVERRIDE int TitleLoop() { FCamera.GetCameraMatrix(matrix); MGSetViewMatrix(matrix, pos); Cloud__2.Step(); + const auto pointer = TakeTitlePointer(); switch (CProcess.no) { case TITLE_STEP_FADE_IN: @@ -84,7 +137,7 @@ PORT_OVERRIDE int TitleLoop() { CProcess.no = TITLE_STEP_LOGO; } - if (GamePad.Down(PAD_START)) { + if (GamePad.Down(PAD_START) || pointer.start) { CProcess.no = TITLE_STEP_LOGO; } @@ -97,7 +150,7 @@ PORT_OVERRIDE int TitleLoop() { CSprite.Move(); - if (GamePad.Down(PAD_START)) { + if (GamePad.Down(PAD_START) || pointer.start) { Logo.motion_type.state.time = 116.0f; Fade1 = 128; CSprite.x[0] = 700.0f; @@ -126,7 +179,7 @@ PORT_OVERRIDE int TitleLoop() { CLogo.Move(); } - if (Fade1 > 127 && GamePad.Down(PAD_START)) { + if (Fade1 > 127 && (GamePad.Down(PAD_START) || pointer.start)) { TiPlayVolSE(MIDI_PORT_UNK_D, 122, 25, 1.0f); CProcess.no = TITLE_STEP_MENU; opcnt = 0; @@ -147,7 +200,7 @@ PORT_OVERRIDE int TitleLoop() { case TITLE_STEP_MENU: CLogo.Move(); - if (CCursol.Move()) { + if (CCursol.Move() || pointer.moved || pointer.back) { if (GamePad.Down(PAD_UP)) { CCursol.select--; opcnt = 0; @@ -180,13 +233,13 @@ PORT_OVERRIDE int TitleLoop() { break; } - if (GamePad.Down(PAD_START) || GamePad.Down(PAD_CROSS)) { + if (GamePad.Down(PAD_START) || GamePad.Down(PAD_CROSS) || pointer.confirm) { TiPlayVolSE(MIDI_PORT_SE_DEFAULT, 38, 20, 1.0f); CProcess.no = TITLE_STEP_MENU_BLINK; opcnt = 0; } - if (GamePad.Down(PAD_CIRCLE)) { + if (GamePad.Down(PAD_CIRCLE) || pointer.back) { Fade1 = Fade2 = Fade3 = Fade4 = 0; CSprite.Init(); CLogo.Init(); @@ -497,7 +550,10 @@ PORT_OVERRIDE void TitleDraw() { RECT rect = {0, 0, 32, 32}; - DrawObjectVibe(225, CCursol.GetPos(), TexManager.GetTexture("icon01", -1), rect, 128, CFade.Get(128)); + float hand_x = 225; + float hand_y = CCursol.GetPos(); + MenuPointerHand(hand_x, hand_y); + DrawObjectVibe(hand_x, hand_y, TexManager.GetTexture("icon01", -1), rect, 128, CFade.Get(128)); setbilinear(1); } } @@ -520,5 +576,14 @@ PORT_OVERRIDE void TitleDraw() { break; } + if (CProcess.no == TITLE_STEP_LOGO && title_pointer.pointing) { + float hand_x = 0, hand_y = 0; + if (MenuPointerHand(hand_x, hand_y)) { + RECT rect = {0, 0, 32, 32}; + setbilinear(0); + DrawObjectVibe(hand_x, hand_y, TexManager.GetTexture("icon01", -1), rect, 128, 128); + setbilinear(1); + } + } TitlePortFeedback(35); } From 21e3976787953b6ec99038de1d5346d12f4cf22a Mon Sep 17 00:00:00 2001 From: anxiousintrovert <82425472+anxiousintrovert@users.noreply.github.com> Date: Sat, 17 Oct 2026 00:14:18 -0500 Subject: [PATCH 2/4] Fix centred save prompt hitboxes and ending acknowledgement --- port/src/menu_mouse.cpp | 7 +++++-- port/src/tests/menu_save_pointer_test.cpp | 18 ++++++++++++++++++ 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/port/src/menu_mouse.cpp b/port/src/menu_mouse.cpp index 4cb848215..f1d5e2863 100644 --- a/port/src/menu_mouse.cpp +++ b/port/src/menu_mouse.cpp @@ -319,7 +319,7 @@ void SaveMouse() { MenuPointerTake take = g_save.Take(320.0f, 240.0f); MenuPointerShow(&g_save); if ((take.clicked & 2) != 0) { - MenuPointerPress(kInputCircle); + MenuPointerPress(SaveMenu.key_no == SAVE_KEY_END_SAVE_ENDING ? kInputCross : kInputCircle); return; } bool slots = SaveMenu.key_no == SAVE_KEY_FILE_SELECT; @@ -351,7 +351,10 @@ void SaveMouse() { const auto &line = mes.win_line[i]; if (line.code != -0x2F8 && line.code != -0x2FA) continue; float left = mes.text_x + line.x; - float top = mes.text_y + line.y; + const int row_offset = mes.fukidashi == 0 && mes.centre_rows != 0 + ? (mes.char_height * (mes.rows - mes.text_rows)) >> 1 + : 0; + float top = mes.text_y + row_offset + line.y; float right = mes.text_x + mes.text_width; for (int j = i + 1; j < mes.win_line_num && mes.win_line[j].y == line.y; ++j) { if (mes.win_line[j].code == -0x2F8 || mes.win_line[j].code == -0x2FA) { diff --git a/port/src/tests/menu_save_pointer_test.cpp b/port/src/tests/menu_save_pointer_test.cpp index c1a28aeff..31fc65cde 100644 --- a/port/src/tests/menu_save_pointer_test.cpp +++ b/port/src/tests/menu_save_pointer_test.cpp @@ -34,6 +34,8 @@ void Prompt(int left) { mes.text_y = 220; mes.text_width = 200; mes.char_height = 24; + mes.fukidashi = 0; + mes.centre_rows = 0; mes.win_line_num = 2; mes.win_line[0] = {-0x2F8, 0, 24, 0, 0}; mes.win_line[1] = {-0x2FA, 80, 24, 0, 0}; @@ -66,3 +68,19 @@ TEST(MenuSavePointer, BackDoesNotRequireHoveringAChoice) { Open(SAVE_KEY_LOAD_DECIDE); EXPECT_EQ(Click(2), kInputCircle); } + +TEST(MenuSavePointer, LoadYesIncludesRenderedRowCentring) { + Prompt(300); + CommonMenuMes2.text_y = 200; + CommonMenuMes2.char_height = 20; + CommonMenuMes2.rows = 4; + CommonMenuMes2.text_rows = 2; + CommonMenuMes2.centre_rows = 1; + Open(SAVE_KEY_LOAD_DECIDE); + EXPECT_EQ(Click(1), kInputCross); +} + +TEST(MenuSavePointer, EndingCompletionBackAcknowledgesWithCross) { + Open(SAVE_KEY_END_SAVE_ENDING); + EXPECT_EQ(Click(2), kInputCross); +} From 61a23672b537b0d40c6d8dc25729b48744a8ff90 Mon Sep 17 00:00:00 2001 From: anxiousintrovert <82425472+anxiousintrovert@users.noreply.github.com> Date: Sat, 17 Oct 2026 08:47:46 -0500 Subject: [PATCH 3/4] Support menu pointers in door and object item pickers --- port/include/stubs/menu_save.hpp | 3 + port/src/menu_event_pointer.cpp | 98 ++++++++++++++++++++++++++++++++ port/src/menu_event_pointer.hpp | 6 ++ port/src/menu_mouse.cpp | 8 +++ 4 files changed, 115 insertions(+) create mode 100644 port/src/menu_event_pointer.cpp create mode 100644 port/src/menu_event_pointer.hpp diff --git a/port/include/stubs/menu_save.hpp b/port/include/stubs/menu_save.hpp index 2978a16b1..1843be532 100644 --- a/port/include/stubs/menu_save.hpp +++ b/port/include/stubs/menu_save.hpp @@ -1,5 +1,8 @@ #pragma once +// Keep the item picker's game logic available to the port's pointer wrapper. +#define EventItemSelectLoop PortEventItemSelectLoop + // memcard.cpp's SaveMenuFunc table names the save menu's steps, which this unit defines static: // the PS2 link makes them global (object_fixups.json, globalize_symbols). Each gets a global // symbol under the name the table references, which also keeps clang from dropping the static diff --git a/port/src/menu_event_pointer.cpp b/port/src/menu_event_pointer.cpp new file mode 100644 index 000000000..f0724fb87 --- /dev/null +++ b/port/src/menu_event_pointer.cpp @@ -0,0 +1,98 @@ +#include "menu_event_pointer.hpp" + +#include "menu_pointer.hpp" +#include "menu_save.hpp" +#include "platform/input.hpp" + +/** + * Runs the original item picker selection and drawing logic. + */ +int PortEventItemSelectLoop(int *result); + +/** The item picker's selection and transition state. */ +extern MINI_MENU_INFO MiniMenu; +/** The item board's logical screen origin. */ +extern float EventBoardPos[2]; +/** The animated vertical origin of the first item row. */ +extern int EventItemMoveY; +/** The item picker's animated hand position. */ +extern float MiniCur[2]; +/** Whether the item picker textures are ready. */ +extern int MiniEventTexReadFlag; + +namespace { + +/** The pointer owned by the door/object item picker. */ +MenuPointer event_pointer; +/** Whether the item picker remained active after its last update. */ +bool event_active = false; + +/** + * Finds an occupied item cell within the animated board's visible clip. + */ +int ItemHit(float x, float y) { + const float left = EventBoardPos[0] + 28.0f; + const float top = EventBoardPos[1] + 56.0f; + if (x < left || x >= left + 210.0f || y < top || y >= top + 80.0f) { + return -1; + } + for (int index = 0; index < MiniMenu.event_item_num; ++index) { + const float cell_left = left + (index % 5) * 42.0f; + const float cell_top = static_cast(EventItemMoveY + (index / 5) * 40); + if (x >= cell_left && x < cell_left + 42.0f && y >= cell_top && y < cell_top + 40.0f) { + return index; + } + } + return -1; +} + +} // namespace + +/** + * Tracks the item picker's lifetime while preserving its original item handling. + */ +int EventItemSelectLoop(int *result) { + const int done = PortEventItemSelectLoop(result); + event_active = done == 0; + return done; +} + +bool EventItemPointerUpdate() { + const bool active = event_active; + event_active = false; + if (!active) { + return !event_pointer.Close(); + } + if (!event_pointer.IsOpen()) { + event_pointer.Open(); + } + const MenuPointerTake take = event_pointer.Take(MiniCur[0], MiniCur[1]); + MenuPointerShow(&event_pointer); + if (!MiniEventTexReadFlag) { + return true; + } + if (MiniMenu.state == MINI_MENU_REFUSED) { + if ((take.clicked & 3) != 0) { + MenuPointerPress(kInputCircle); + } + return true; + } + if (MiniMenu.state != MINI_MENU_CHOOSING) { + return true; + } + if ((take.clicked & 2) != 0) { + MenuPointerPress(kInputCircle); + return true; + } + const int hit = take.pointing ? ItemHit(event_pointer.x, event_pointer.y) : -1; + if (hit >= 0 && take.moved && take.wheel == 0.0f) { + MiniMenu.cursor = hit; + if ((take.clicked & 1) != 0) { + MenuPointerPress(kInputCross); + } + } + if (take.pointing && take.wheel != 0.0f) { + MenuPointerPress(take.wheel > 0.0f ? kInputUp : kInputDown); + } + return true; +} diff --git a/port/src/menu_event_pointer.hpp b/port/src/menu_event_pointer.hpp new file mode 100644 index 000000000..35d240fb6 --- /dev/null +++ b/port/src/menu_event_pointer.hpp @@ -0,0 +1,6 @@ +#pragma once + +/** + * Handles pointer input while the door/object item picker owns the menu. + */ +bool EventItemPointerUpdate(); diff --git a/port/src/menu_mouse.cpp b/port/src/menu_mouse.cpp index f1d5e2863..c1a49553f 100644 --- a/port/src/menu_mouse.cpp +++ b/port/src/menu_mouse.cpp @@ -1,4 +1,5 @@ #include "menu_pointer.hpp" +#include "menu_event_pointer.hpp" #include #include @@ -1097,6 +1098,13 @@ void MenuMouseUpdate() { bool battle = g_battle_drawn; g_battle_drawn = false; + if (EventItemPointerUpdate()) { + g_battle.Abandon(); + g_save.Abandon(); + g_save_open = false; + return; + } + bool save = SaveMenu.texture_ready != 0 && SaveMenu.key_no != SAVE_KEY_FADE_OUT; if (save) { g_battle.Abandon(); From d2f10ed75bd615489dfa9bda9ddc5cb84e08e599 Mon Sep 17 00:00:00 2001 From: anxiousintrovert <82425472+anxiousintrovert@users.noreply.github.com> Date: Sun, 18 Oct 2026 09:03:08 -0500 Subject: [PATCH 4/4] Draw the menu pointer on Journal reading pages --- port/src/menu_mouse.cpp | 4 ++++ port/src/menu_mouse.hpp | 5 +++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/port/src/menu_mouse.cpp b/port/src/menu_mouse.cpp index c1a49553f..e147fca4a 100644 --- a/port/src/menu_mouse.cpp +++ b/port/src/menu_mouse.cpp @@ -1140,6 +1140,10 @@ void MenuMouseUpdate() { } void MenuMouseDrawOverlay() { + if ((BattleMenuFlag == BTLMENU_STATE_MANUAL || BattleMenuFlag == BTLMENU_STATE_MANUAL_OPEN) && + ManualMenu.mode == MANUAL_MODE_READING && g_battle.IsOpen() && g_battle.pointing) { + DrawMenuObjectVibe(0, 0, 1, 0x40); + } if (!g_discard.active) { return; } diff --git a/port/src/menu_mouse.hpp b/port/src/menu_mouse.hpp index b7644ff71..95a1cf58b 100644 --- a/port/src/menu_mouse.hpp +++ b/port/src/menu_mouse.hpp @@ -3,8 +3,9 @@ // The pause menu was drawn this tick, so its screen takes the mouse next tick. void MenuMouseNoteBattleMenu(); -// Draws what the mouse adds to the pause menu's own picture: the question asked before an item is -// thrown away. Called at the end of BattleMenuDraw. +/** + * Draws the Journal reading pointer and item-discard confirmation over the pause menu. + */ void MenuMouseDrawOverlay(); // The same animated icon coordinates used by the renderer and pointer hit testing.