From 1655c8a8e06ab8ec9f1226ff59b42f7eddaba099 Mon Sep 17 00:00:00 2001 From: kichikuou Date: Sat, 21 Mar 2026 14:11:48 +0900 Subject: [PATCH] PartsEngine: Implement pass_cursor flag This implements SetPassCursor/GetPartsPassCursor HLL functions. The pass_cursor flag controls whether the cursor "passes through" a component to reach parts behind it. Key changes to input processing: - Iteration order changed from back-to-front to front-to-back for proper cursor consumption - MOUSE_ENTER / MOUSE_LEAVE now fire for all parts regardless of clickable - Click hit test uses clickable || !pass_cursor pre-filter - pass_cursor=false parts consume the cursor, preventing hover and click events from reaching parts behind them --- include/parts.h | 2 ++ src/hll/GUIEngine.c | 4 ++-- src/hll/PartsEngine.c | 7 +++--- src/parts/input.c | 44 ++++++++++++++++++++++++++++++++------ src/parts/parts_internal.h | 2 ++ src/parts/save.c | 9 +++++--- 6 files changed, 53 insertions(+), 15 deletions(-) diff --git a/include/parts.h b/include/parts.h index 42b2650..bb5ff93 100644 --- a/include/parts.h +++ b/include/parts.h @@ -162,6 +162,8 @@ bool PE_SetPartsConstructionSurfaceArea(int parts_no, int x, int y, int w, int h // input.c void PE_UpdateInputState(int passed_time); +void PE_SetPassCursor(int parts_no, bool pass); +bool PE_GetPartsPassCursor(int parts_no); void PE_SetClickable(int parts_no, bool clickable); bool PE_GetPartsClickable(int parts_no); void PE_SetPartsGroupDecideOnCursor(int group_no, bool decide_on_cursor); diff --git a/src/hll/GUIEngine.c b/src/hll/GUIEngine.c index 0c86581..fcac5c7 100644 --- a/src/hll/GUIEngine.c +++ b/src/hll/GUIEngine.c @@ -918,7 +918,7 @@ HLL_LIBRARY(GUIEngine, HLL_EXPORT(Parts_SetShow, PE_SetShow), HLL_EXPORT(Parts_SetAlpha, PE_SetAlpha), HLL_EXPORT(Parts_SetPartsDrawFilter, PE_SetPartsDrawFilter), - HLL_TODO_EXPORT(Parts_SetPassCursor, GUIEngine_Parts_SetPassCursor), + HLL_EXPORT(Parts_SetPassCursor, PE_SetPassCursor), HLL_EXPORT(Parts_SetClickable, PE_SetClickable), HLL_EXPORT(Parts_SetSpeedupRateByMessageSkip, PE_SetSpeedupRateByMessageSkip), HLL_TODO_EXPORT(Parts_SetResetTimerByChangeInputStatus, GUIEngine_Parts_SetResetTimerByChangeInputStatus), @@ -945,7 +945,7 @@ HLL_LIBRARY(GUIEngine, HLL_EXPORT(Parts_GetPartsShow, PE_GetPartsShow), HLL_EXPORT(Parts_GetPartsAlpha, PE_GetPartsAlpha), HLL_EXPORT(Parts_GetPartsDrawFilter, PE_GetPartsDrawFilter), - HLL_TODO_EXPORT(Parts_GetPartsPassCursor, GUIEngine_Parts_GetPartsPassCursor), + HLL_EXPORT(Parts_GetPartsPassCursor, PE_GetPartsPassCursor), HLL_EXPORT(Parts_GetPartsClickable, PE_GetPartsClickable), HLL_TODO_EXPORT(Parts_GetPartsSpeedupRateByMessageSkip, GUIEngine_Parts_GetPartsSpeedupRateByMessageSkip), HLL_TODO_EXPORT(Parts_GetResetTimerByChangeInputStatus, GUIEngine_Parts_GetResetTimerByChangeInputStatus), diff --git a/src/hll/PartsEngine.c b/src/hll/PartsEngine.c index cc8f70b..8ee99b1 100644 --- a/src/hll/PartsEngine.c +++ b/src/hll/PartsEngine.c @@ -143,7 +143,6 @@ static int PartsEngine_PartsFunc(int func_id, struct page **array_int, } HLL_WARN_UNIMPLEMENTED(, void, PartsEngine, StopSoundWithoutSystemSound); -HLL_WARN_UNIMPLEMENTED(, void, PartsEngine, Parts_SetPassCursor, int Number, bool Pass); static void PartsEngine_PreLink(void); @@ -242,6 +241,7 @@ HLL_LIBRARY(PartsEngine, HLL_EXPORT(SetPartsDrawFilter, PE_SetPartsDrawFilter), HLL_EXPORT(SetAddColor, PE_SetAddColor), HLL_EXPORT(SetMultiplyColor, PE_SetMultiplyColor), + HLL_EXPORT(SetPassCursor, PE_SetPassCursor), HLL_EXPORT(SetClickable, PE_SetClickable), HLL_EXPORT(SetSpeedupRateByMessageSkip, PE_SetSpeedupRateByMessageSkip), HLL_TODO_EXPORT(SetResetTimerByChangeInputStatus, PartsEngine_SetResetTimerByChangeInputStatus), @@ -252,6 +252,7 @@ HLL_LIBRARY(PartsEngine, HLL_EXPORT(GetPartsAlpha, PE_GetPartsAlpha), HLL_TODO_EXPORT(GetAddColor, PartsEngine_GetAddColor), HLL_TODO_EXPORT(GetMultiplyColor, PartsEngine_GetMultiplyColor), + HLL_EXPORT(GetPartsPassCursor, PE_GetPartsPassCursor), HLL_EXPORT(GetPartsClickable, PE_GetPartsClickable), HLL_TODO_EXPORT(GetPartsSpeedupRateByMessageSkip, PartsEngine_GetPartsSpeedupRateByMessageSkip), HLL_TODO_EXPORT(GetResetTimerByChangeInputStatus, PartsEngine_GetResetTimerByChangeInputStatus), @@ -644,7 +645,7 @@ HLL_LIBRARY(PartsEngine, HLL_TODO_EXPORT(Parts_CreateParts3DLayerPluginID, PartsEngine_Parts_CreateParts3DLayerPluginID), HLL_TODO_EXPORT(Parts_GetParts3DLayerPluginID, PartsEngine_Parts_GetParts3DLayerPluginID), HLL_TODO_EXPORT(Parts_ReleaseParts3DLayerPluginID, PartsEngine_Parts_ReleaseParts3DLayerPluginID), - HLL_EXPORT(Parts_SetPassCursor, PartsEngine_Parts_SetPassCursor), + HLL_EXPORT(Parts_SetPassCursor, PE_SetPassCursor), HLL_EXPORT(Parts_SetClickable, PE_SetClickable), HLL_TODO_EXPORT(Parts_SetResetTimerByChangeInputStatus, PartsEngine_Parts_SetResetTimerByChangeInputStatus), HLL_TODO_EXPORT(Parts_SetDrag, PartsEngine_Parts_SetDrag), @@ -653,7 +654,7 @@ HLL_LIBRARY(PartsEngine, HLL_EXPORT(Parts_SetOnCursorShowLinkPartsNumber, PE_SetOnCursorShowLinkPartsNumber), HLL_TODO_EXPORT(Parts_SetSoundNumber, PartsEngine_Parts_SetSoundNumber), HLL_EXPORT(Parts_SetPartsPixelDecide, PE_SetPartsPixelDecide), - HLL_TODO_EXPORT(Parts_GetPartsPassCursor, PartsEngine_Parts_GetPartsPassCursor), + HLL_EXPORT(Parts_GetPartsPassCursor, PE_GetPartsPassCursor), HLL_EXPORT(Parts_GetPartsClickable, PE_GetPartsClickable), HLL_TODO_EXPORT(Parts_GetResetTimerByChangeInputStatus, PartsEngine_Parts_GetResetTimerByChangeInputStatus), HLL_TODO_EXPORT(Parts_GetPartsDrag, PartsEngine_Parts_GetPartsDrag), diff --git a/src/parts/input.c b/src/parts/input.c index 919849d..821b673 100644 --- a/src/parts/input.c +++ b/src/parts/input.c @@ -42,19 +42,25 @@ static bool parts_hittest(struct parts *parts, int state, Point pos) return SDL_PointInRect(&pos, &hitbox); } -static void parts_update_mouse(struct parts *parts, Point cur_pos, bool cur_clicking) +static void parts_update_mouse(struct parts *parts, Point cur_pos, bool cur_clicking, + bool *hover_consumed, bool *click_consumed) { // Always use DEFAULT state hitbox regardless of the current display state. - bool is_hovered = parts_hittest(parts, PARTS_STATE_DEFAULT, cur_pos); + bool is_hovered = parts_hittest(parts, PARTS_STATE_DEFAULT, cur_pos) + && !*hover_consumed; bool was_hovered = parts->is_hovered; parts->is_hovered = is_hovered; + // !pass_cursor parts consume the cursor for parts behind them + if (is_hovered && !parts->pass_cursor) + *hover_consumed = true; + if (parts->linked_from >= 0 && is_hovered != was_hovered) { parts_dirty(parts_get(parts->linked_from)); } - if (!parts_began_click || !parts->clickable) + if (!parts_began_click) return; if (is_hovered && !was_hovered) { @@ -71,9 +77,18 @@ static void parts_update_mouse(struct parts *parts, Point cur_pos, bool cur_clic return; } - // click down: just remember the parts number for later + bool click_eligible = parts->clickable || !parts->pass_cursor; + if (!click_eligible || *click_consumed) { + if (!was_hovered) + audio_play_sound(parts->on_cursor_sound); + parts_set_state(parts, PARTS_STATE_HOVERED); + return; + } + + // click down: first eligible part captures the click if (cur_clicking && !prev_clicking) { click_down_parts = parts->no; + *click_consumed = true; // KEY_TRIGGER message fires on press transition parts_msg_push(parts->no, parts->delegate_index, @@ -96,7 +111,8 @@ static void parts_update_mouse(struct parts *parts, Point cur_pos, bool cur_clic } // click event: only if the click down event had same parts number - if (prev_clicking && !cur_clicking && click_down_parts == parts->no) { + if (parts->clickable && prev_clicking && !cur_clicking + && click_down_parts == parts->no) { audio_play_sound(parts->on_click_sound); clicked_parts = parts->no; @@ -113,9 +129,13 @@ void PE_UpdateInputState(possibly_unused int passed_time) bool cur_clicking = key_is_down(VK_LBUTTON); mouse_get_pos(&cur_pos.x, &cur_pos.y); + bool hover_consumed = false; + bool click_consumed = false; struct parts *parts; - PARTS_LIST_FOREACH(parts) { - parts_update_mouse(parts, cur_pos, cur_clicking); + // Iterate front-to-back (highest z first) for proper cursor consumption + PARTS_LIST_FOREACH_REVERSE(parts) { + parts_update_mouse(parts, cur_pos, cur_clicking, + &hover_consumed, &click_consumed); } if (prev_clicking && !cur_clicking) { @@ -128,6 +148,16 @@ void PE_UpdateInputState(possibly_unused int passed_time) prev_clicking = cur_clicking; } +void PE_SetPassCursor(int parts_no, bool pass) +{ + parts_get(parts_no)->pass_cursor = !!pass; +} + +bool PE_GetPartsPassCursor(int parts_no) +{ + return parts_get(parts_no)->pass_cursor; +} + void PE_SetClickable(int parts_no, bool clickable) { parts_get(parts_no)->clickable = !!clickable; diff --git a/src/parts/parts_internal.h b/src/parts/parts_internal.h index 03ef111..1fdeb4c 100644 --- a/src/parts/parts_internal.h +++ b/src/parts/parts_internal.h @@ -364,6 +364,7 @@ struct parts { int delegate_index; int sprite_deform; bool clickable; + bool pass_cursor; int on_cursor_sound; int on_click_sound; int origin_mode; @@ -379,6 +380,7 @@ struct parts { }; #define PARTS_LIST_FOREACH(iter) TAILQ_FOREACH(iter, &parts_list, parts_list_entry) +#define PARTS_LIST_FOREACH_REVERSE(iter) TAILQ_FOREACH_REVERSE(iter, &parts_list, parts_list, parts_list_entry) #define PARTS_FOREACH_CHILD(iter, parent) TAILQ_FOREACH(iter, &parent->children, child_list_entry) // parts.c diff --git a/src/parts/save.c b/src/parts/save.c index 87af1bf..bd7c2cf 100644 --- a/src/parts/save.c +++ b/src/parts/save.c @@ -532,8 +532,10 @@ static void save_parts(struct iarray_writer *w, struct parts *parts) iarray_write(w, parts->alpha_clipper_parts_no); // TODO: once the Rance 9 save format stabilizes, bump save version // and save unconditionally - if (parts_multi_controller) + if (parts_multi_controller) { iarray_write(w, parts->controller_no); + iarray_write(w, parts->pass_cursor); + } unsigned motion_count_pos = iarray_writer_pos(w); iarray_write(w, 0); // size of motion list @@ -575,9 +577,10 @@ static void load_parts(struct iarray_reader *r, int version) parts->alpha_clipper_parts_no = iarray_read(r); // TODO: once the Rance 9 save format stabilizes, bump save version // and load based on version check - if (parts_multi_controller) + if (parts_multi_controller) { parts->controller_no = iarray_read(r); - + parts->pass_cursor = iarray_read(r); + } int motion_count = iarray_read(r); for (int i = 0; i < motion_count; i++) { struct parts_motion *motion = load_parts_motion(r);