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
This commit is contained in:
kichikuou
2026-04-19 09:42:20 +09:00
parent 2a99c75e4d
commit 1655c8a8e0
6 changed files with 53 additions and 15 deletions
+2
View File
@@ -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);
+2 -2
View File
@@ -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),
+4 -3
View File
@@ -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),
+37 -7
View File
@@ -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;
+2
View File
@@ -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
+6 -3
View File
@@ -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);