@@ -175,193 +175,239 @@ static bool Sonic06Button(ImVec2 pos, const char* label, bool& hovered, const Im
175
175
return pressed;
176
176
}
177
177
178
+ // DrawStageMapSelector
179
+ struct StageMapSelectorConfig {
180
+ // Layout
181
+ float windowWidthRatio = 0 .8f ;
182
+ float buttonHeightRatio = 0 .05f ;
183
+ float buttonSpacing = 5 .0f ;
184
+ int defaultVisibleItems = 9 ;
185
+ float itemsScaleHeightRatio = 0 .05f ;
186
+
187
+ // Positioning
188
+ ImVec2 basePosRatio = { 0 .15f , 0 .45f };
189
+ ImVec2 buttonOffset = { 10 .0f , 0.0 };
190
+
191
+ // Colors
192
+ ImU32 tooltipBgColor = IM_COL32(0 , 0 , 0 , 180 );
193
+ ImU32 tooltipBorderColor = IM_COL32(50 , 150 , 255 , 200 );
194
+ ImU32 tooltipNameColor = IM_COL32(255 , 255 , 255 , 255 );
195
+ ImU32 tooltipTextColor = IM_COL32(200 , 200 , 255 , 255 );
196
+
197
+ // Tooltip
198
+ float tooltipPadding = 15 .0f ;
199
+ float tooltipSeparatorWidth = 5 .0f ;
200
+ float tooltipFontSize = 0 .0f ; // 0 means use current font size
201
+
202
+ // Mouse cursor
203
+ float cursorRadius = 7 .5f ;
204
+ ImU32 cursorColor = IM_COL32(0 , 150 , 255 , 220 );
205
+ ImU32 cursorOutlineColor = IM_COL32(255 , 255 , 255 , 180 );
206
+ };
207
+
208
+ static StageMapSelectorConfig s_StageMapSelectorConfig;
209
+
210
+ static void HandleStageMapSelectorInput (Sonicteam::StageSelectMode* pMode, uintptr_t pMsgRec)
211
+ {
212
+ auto & io = ImGui::GetIO ();
213
+
214
+ // Mouse wheel scrolling
215
+ if (abs (io.MouseWheel ) > ScrollAmount) {
216
+ guest_stack_var<DevMessage> vMsgRec (0x10001 , io.MouseWheel > 0 ? 0 : 0xB4 , 130 );
217
+ GuestToHostFunction<void >(sub_824A8FF0, pMsgRec, vMsgRec.get ());
218
+ }
219
+
220
+ // Right click handling
221
+ if (ImGui::IsMouseClicked (ImGuiMouseButton_Right)) {
222
+ guest_stack_var<DevMessage> vMsgRec (0x10002 , 0 , 0 );
223
+ GuestToHostFunction<void >(sub_824A8FF0, pMsgRec, vMsgRec.get ());
224
+ }
225
+ }
226
+
227
+ static void DrawStageMapTooltip (stdx::vector<xpointer<Sonicteam::StageMap>>& stages)
228
+ {
229
+ if (stages.empty ()) return ;
230
+
231
+ auto & io = ImGui::GetIO ();
232
+ auto & config = s_StageMapSelectorConfig;
233
+ ImDrawList* draw_list = ImGui::GetForegroundDrawList ();
234
+
235
+ const float font_size = config.tooltipFontSize > 0 ? config.tooltipFontSize : ImGui::GetFontSize ();
236
+ const float line_spacing = 2 .0f ;
237
+
238
+ // Calculate tooltip size
239
+ float max_name_width = 0 .0f ;
240
+ float max_text_width = 0 .0f ;
241
+ float total_height = 0 .0f ;
242
+ std::vector<std::pair<std::string, std::string>> entries;
243
+
244
+ for (auto & stage : stages) {
245
+ if (!stage.ptr .get ()) continue ;
246
+
247
+ std::string nameUtf8 = ConvertShiftJISToUTF8 (stage->m_Name .c_str ());
248
+ std::string textUtf8 = ConvertShiftJISToUTF8 (stage->m_Text .c_str ());
249
+ entries.emplace_back (nameUtf8, textUtf8);
250
+
251
+ const ImVec2 name_size = g_mnewRodinFont->CalcTextSizeA (font_size, FLT_MAX, 0 .0f , nameUtf8.c_str ());
252
+ const ImVec2 text_size = g_mnewRodinFont->CalcTextSizeA (font_size, FLT_MAX, 0 .0f , textUtf8.c_str ());
253
+
254
+ max_name_width = ImMax (max_name_width, name_size.x );
255
+ max_text_width = ImMax (max_text_width, text_size.x );
256
+ total_height += ImMax (name_size.y , text_size.y ) + line_spacing;
257
+ }
258
+
259
+ const float total_width = max_name_width + config.tooltipSeparatorWidth + max_text_width +
260
+ config.tooltipPadding * 2 ;
261
+ total_height += config.tooltipPadding * 2 - line_spacing;
262
+
263
+ // Position tooltip
264
+ ImVec2 mouse_pos = GetMousePos ();
265
+ ImVec2 tooltip_pos = ImVec2 (
266
+ ImClamp (mouse_pos.x - total_width * 0 .5f , 10 .0f , io.DisplaySize .x - total_width - 10 .0f ),
267
+ ImClamp (mouse_pos.y - total_height - 20 .0f , 10 .0f , io.DisplaySize .y - total_height - 10 .0f )
268
+ );
269
+
270
+ if (tooltip_pos.y <= 10 ) {
271
+ tooltip_pos.x += io.DisplaySize .x * 0 .040f ;
272
+ }
273
+
274
+ // Draw background
275
+ const ImVec2 tooltip_min = tooltip_pos;
276
+ const ImVec2 tooltip_max = ImVec2 (tooltip_pos.x + total_width, tooltip_pos.y + total_height);
277
+ draw_list->AddRectFilled (tooltip_min, tooltip_max, config.tooltipBgColor , 5 .0f );
278
+ draw_list->AddRect (tooltip_min, tooltip_max, config.tooltipBorderColor , 5 .0f , 0 , 1 .5f );
279
+
280
+ // Draw text entries
281
+ float current_y = tooltip_pos.y + config.tooltipPadding ;
282
+ for (const auto & [name, text] : entries) {
283
+ const float name_height = g_mnewRodinFont->CalcTextSizeA (font_size, FLT_MAX, 0 .0f , name.c_str ()).y ;
284
+ const float text_height = g_mnewRodinFont->CalcTextSizeA (font_size, FLT_MAX, 0 .0f , text.c_str ()).y ;
285
+ const float line_height = ImMax (name_height, text_height);
286
+
287
+ // Draw name
288
+ draw_list->AddText (
289
+ g_mnewRodinFont,
290
+ font_size,
291
+ ImVec2 (tooltip_pos.x + config.tooltipPadding , current_y),
292
+ config.tooltipNameColor ,
293
+ name.c_str ()
294
+ );
295
+
296
+ // Draw text
297
+ draw_list->AddText (
298
+ g_mnewRodinFont,
299
+ font_size,
300
+ ImVec2 (tooltip_pos.x + config.tooltipPadding + max_name_width + config.tooltipSeparatorWidth , current_y),
301
+ config.tooltipTextColor ,
302
+ text.c_str ()
303
+ );
304
+
305
+ current_y += line_height + line_spacing;
306
+ }
307
+ }
308
+
309
+ static void DrawStageMapCursor ()
310
+ {
311
+ auto & config = s_StageMapSelectorConfig;
312
+ ImVec2 mouse_pos = GetMousePos ();
313
+
314
+ ImGui::GetBackgroundDrawList ()->AddCircleFilled (
315
+ mouse_pos, config.cursorRadius , config.cursorColor , 12
316
+ );
317
+ ImGui::GetBackgroundDrawList ()->AddCircle (
318
+ mouse_pos, config.cursorRadius , config.cursorOutlineColor , 12 , 1 .5f
319
+ );
320
+ }
321
+
178
322
static void DrawStageMapSelector ()
179
323
{
180
324
auto & io = ImGui::GetIO ();
181
325
auto & res = io.DisplaySize ;
326
+ auto & config = s_StageMapSelectorConfig;
182
327
183
328
auto * pMode = App::s_pApp->m_pDoc ->GetDocMode <Sonicteam::StageSelectMode>();
184
- if (!pMode)
185
- return ;
329
+ if (!pMode) return ;
186
330
187
331
DrawBackgroundDev ();
188
332
189
- if (Config::DevTitle == EDevTitleMenu::Custom) DrawHUD ({ 0 , 0 }, res, g_mnewRodinFont, pMode->m_StageMapName .get ());
190
-
191
- float WINDOW_WIDTH = 0 .8f * res.x ;
192
- float BUTTON_HEIGHT = 0 .05f * res.y ;
193
- float BUTTON_SPACING = 5 .0f ;
194
- int VISIBLE_ITEMS = 9 ;
195
- float ITEMS_SCALE_HEIGHT = 0 .05f * Video::s_viewportHeight;
196
- float WINDOW_HEIGHT = (BUTTON_HEIGHT + BUTTON_SPACING) * VISIBLE_ITEMS + ITEMS_SCALE_HEIGHT;
197
-
198
- ImVec2 base_pos = {
199
- (res.x - WINDOW_WIDTH) * 0 .15f ,
200
- (res.y - WINDOW_HEIGHT) * 0 .45f
201
- };
202
-
203
- if (Config::DevTitle == EDevTitleMenu::True)
204
- {
205
- VISIBLE_ITEMS = 18 ;
206
- base_pos.x = 0 ;
207
- base_pos.y = 0 ;
208
- }
209
-
333
+ // Draw HUD if in custom mode
334
+ if (Config::DevTitle == EDevTitleMenu::Custom) {
335
+ DrawHUD ({ 0 , 0 }, res, g_mnewRodinFont, pMode->m_StageMapName .get ());
336
+ }
337
+
338
+ // Calculate layout
339
+ int visibleItems = config.defaultVisibleItems ;
340
+ ImVec2 base_pos = {
341
+ (res.x - config.windowWidthRatio * res.x ) * config.basePosRatio .x ,
342
+ (res.y - (config.buttonHeightRatio * res.y + config.buttonSpacing ) * visibleItems +
343
+ config.itemsScaleHeightRatio * Video::s_viewportHeight) * config.basePosRatio .y
344
+ };
345
+
346
+ // Adjust for dev mode
347
+ if (Config::DevTitle == EDevTitleMenu::True) {
348
+ visibleItems = 18 ;
349
+ base_pos = { 0 , 0 };
350
+ }
351
+
210
352
auto & items = pMode->m_CurrentStageMap ->m_vpStageMap ;
211
353
const int itemCount = static_cast <int >(items.size ());
212
354
int currentIdx = pMode->m_CurrentStageMapIndex .get ();
213
355
214
- // Input handling (Mouse)
356
+ // Handle input
215
357
auto pMsgRec = reinterpret_cast <uintptr_t >(static_cast <Sonicteam::SoX::MessageReceiver*>(pMode));
216
-
217
- if (abs (io.MouseWheel ) > ScrollAmount) {
218
- guest_stack_var<DevMessage> vMsgRec (0x10001 , io.MouseWheel > 0 ? 0 : 0xB4 , 130 );
219
- GuestToHostFunction<void >(sub_824A8FF0, pMsgRec, vMsgRec.get ());
220
- }
221
-
222
- if (ImGui::IsMouseClicked (ImGuiMouseButton_Right)) {
223
- guest_stack_var<DevMessage> vMsgRec (0x10002 , 0 , 0 );
224
- GuestToHostFunction<void >(sub_824A8FF0, pMsgRec, vMsgRec.get ());
225
- }
358
+ HandleStageMapSelectorInput (pMode, pMsgRec);
226
359
227
360
// Calculate visible range
228
- const int start_idx = std::max (0 , currentIdx - VISIBLE_ITEMS / 2 );
229
- const int end_idx = std::min (itemCount, start_idx + VISIBLE_ITEMS );
361
+ const int start_idx = std::max (0 , currentIdx - visibleItems / 2 );
362
+ const int end_idx = std::min (itemCount, start_idx + visibleItems );
230
363
231
364
// Draw visible items
232
365
for (int i = start_idx; i < end_idx; ++i) {
233
366
const auto & item = items[i];
234
367
if (!item) continue ;
235
368
236
369
const bool isSelected = (i == currentIdx);
237
- const int visible_index = i - start_idx;
370
+ const int visible_index = i - start_idx;
238
371
bool hovered = false ;
239
372
240
373
// Calculate button position
241
374
const ImVec2 button_pos = {
242
- base_pos.x + 10 ,
243
- base_pos.y + 40 + visible_index * (BUTTON_HEIGHT + BUTTON_SPACING)
375
+ base_pos.x + config.buttonOffset .x ,
376
+ base_pos.y + config.buttonOffset .y + visible_index *
377
+ (config.buttonHeightRatio * res.y + config.buttonSpacing )
244
378
};
379
+
380
+ // Format item name
245
381
std::string item_name = item->m_Name .c_str ();
246
- if (item->m_vpStageMap .size () > 1 )
247
- {
248
- item_name = " [" + item_name + " ]" ; // mark as group
382
+ if (item->m_vpStageMap .size () > 1 ) {
383
+ item_name = " [" + item_name + " ]" ; // Mark as group
249
384
}
250
- if (isSelected)
251
- {
385
+ if (isSelected) {
252
386
item_name = " >> " + item_name;
253
387
}
254
388
389
+ // Draw button
255
390
if (Sonic06Button (button_pos, item_name.c_str (), hovered,
256
- ImVec2 (WINDOW_WIDTH - 20 , BUTTON_HEIGHT), isSelected,CenterFlag::Y))
257
- {
391
+ ImVec2 (config. windowWidthRatio * res. x - 20 , config. buttonHeightRatio * res. y ),
392
+ isSelected, CenterFlag::Y)) {
258
393
currentIdx = i;
259
394
pMode->m_CurrentStageMapIndex = i;
260
395
guest_stack_var<DevMessage> vMsgRec (0x10002 , 0x5A , 1 );
261
396
GuestToHostFunction<void >(sub_824A8FF0, pMsgRec, vMsgRec.get ());
262
397
}
263
398
264
- // Tooltip with stage information
399
+ // Show tooltip if hovered and in custom mode
265
400
if (hovered && item->m_vpStageMap .size () > 0 && Config::DevTitle == EDevTitleMenu::Custom) {
266
- ImDrawList* draw_list = ImGui::GetForegroundDrawList ();
267
- const ImVec2 mouse_pos = GetMousePos ();
268
- const float font_size = ImGui::GetFontSize ();
269
- const float line_spacing = 2 .0f ;
270
-
271
-
272
- float max_name_width = 0 .0f ;
273
- float max_text_width = 0 .0f ;
274
- float total_height = 0 .0f ;
275
- std::vector<std::pair<std::string, std::string>> entries;
276
-
277
- for (int j = 0 ; j < item->m_vpStageMap .size (); ++j) {
278
- const auto & stage = item->m_vpStageMap [j];
279
- if (!stage.ptr .get ()) continue ;
280
-
281
- std::string nameUtf8 = ConvertShiftJISToUTF8 (stage->m_Name .c_str ());
282
- std::string textUtf8 = ConvertShiftJISToUTF8 (stage->m_Text .c_str ());
283
- entries.emplace_back (nameUtf8, textUtf8);
284
-
285
- // Measure each part separately using the actual rendering font
286
- const ImVec2 name_size = g_mnewRodinFont->CalcTextSizeA (font_size, FLT_MAX, 0 .0f , nameUtf8.c_str ());
287
- const ImVec2 text_size = g_mnewRodinFont->CalcTextSizeA (font_size, FLT_MAX, 0 .0f , textUtf8.c_str ());
288
-
289
- max_name_width = ImMax (max_name_width, name_size.x );
290
- max_text_width = ImMax (max_text_width, text_size.x );
291
- total_height += ImMax (name_size.y , text_size.y ) + line_spacing;
292
- }
293
-
294
- const float text_padding = 10 .0f ;
295
- const float separator_width = 5 .0f ;
296
- const float tooltip_padding = 15 .0f ;
297
- const float total_width = max_name_width + separator_width + max_text_width + tooltip_padding * 2 ;
298
- total_height += tooltip_padding * 2 - line_spacing;
299
-
300
- // Position tooltip (ensure it stays on screen)
301
- ImVec2 tooltip_pos = ImVec2 (
302
- ImClamp (mouse_pos.x - total_width * 0 .5f , 10 .0f , io.DisplaySize .x - total_width - 10 .0f ),
303
- ImClamp (mouse_pos.y - total_height - 20 .0f , 10 .0f , io.DisplaySize .y - total_height - 10 .0f )
304
- );
305
- if (tooltip_pos.y <= 10 )
306
- {
307
- tooltip_pos.x += io.DisplaySize .x * 0.040 ;
308
- }
309
-
310
- // Draw background
311
- const ImVec2 tooltip_min = tooltip_pos;
312
- const ImVec2 tooltip_max = ImVec2 (tooltip_pos.x + total_width, tooltip_pos.y + total_height);
313
- draw_list->AddRectFilled (tooltip_min, tooltip_max, IM_COL32 (0 , 0 , 0 , 180 ), 5 .0f );
314
- draw_list->AddRect (tooltip_min, tooltip_max, IM_COL32 (50 , 150 , 255 , 200 ), 5 .0f , 0 , 1 .5f );
315
-
316
- // Draw text entries
317
- float current_y = tooltip_pos.y + tooltip_padding;
318
- for (const auto & [name, text] : entries) {
319
- // Calculate this line's height
320
- const float name_height = g_mnewRodinFont->CalcTextSizeA (font_size, FLT_MAX, 0 .0f , name.c_str ()).y ;
321
- const float text_height = g_mnewRodinFont->CalcTextSizeA (font_size, FLT_MAX, 0 .0f , text.c_str ()).y ;
322
- const float line_height = ImMax (name_height, text_height);
323
-
324
- // Draw name (left-aligned)
325
- draw_list->AddText (
326
- g_mnewRodinFont,
327
- font_size,
328
- ImVec2 (tooltip_pos.x + tooltip_padding, current_y),
329
- IM_COL32 (255 , 255 , 255 , 255 ),
330
- name.c_str ()
331
- );
332
-
333
- // Draw text (right-aligned)
334
- const float text_width = g_mnewRodinFont->CalcTextSizeA (font_size, FLT_MAX, 0 .0f , text.c_str ()).x ;
335
- draw_list->AddText (
336
- g_mnewRodinFont,
337
- font_size,
338
- ImVec2 (tooltip_pos.x + tooltip_padding + max_name_width + separator_width, current_y),
339
- IM_COL32 (200 , 200 , 255 , 255 ),
340
- text.c_str ()
341
- );
342
-
343
- current_y += line_height + line_spacing;
344
- }
401
+ DrawStageMapTooltip (item->m_vpStageMap );
345
402
}
346
-
347
-
348
403
}
349
-
350
- // Do not draw mouse, if not custom
351
- if (Config::DevTitle != EDevTitleMenu::Custom) return ;
352
- // Draw mouse position indicator
353
- const float dot_radius = 7 .5f ;
354
- const ImU32 dot_color = IM_COL32 (0 , 150 , 255 , 220 );
355
- const ImU32 outline_color = IM_COL32 (255 , 255 , 255 , 180 );
356
- ImVec2 mouse_pos = GetMousePos ();
357
404
358
- ImGui::GetBackgroundDrawList ()->AddCircleFilled (
359
- mouse_pos, dot_radius, dot_color, 12
360
- );
361
- ImGui::GetBackgroundDrawList ()->AddCircle (
362
- mouse_pos, dot_radius, outline_color, 12 , 1 .5f
363
- );
405
+ // Draw mouse cursor in custom mode
406
+ if (Config::DevTitle == EDevTitleMenu::Custom) {
407
+ DrawStageMapCursor ();
408
+ }
364
409
}
410
+ // DrawStageMapSelector
365
411
366
412
static void DrawDevTitle () {
367
413
0 commit comments