@@ -175,193 +175,239 @@ static bool Sonic06Button(ImVec2 pos, const char* label, bool& hovered, const Im
175175 return pressed;
176176}
177177
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+
178322static void DrawStageMapSelector ()
179323{
180324 auto & io = ImGui::GetIO ();
181325 auto & res = io.DisplaySize ;
326+ auto & config = s_StageMapSelectorConfig;
182327
183328 auto * pMode = App::s_pApp->m_pDoc ->GetDocMode <Sonicteam::StageSelectMode>();
184- if (!pMode)
185- return ;
329+ if (!pMode) return ;
186330
187331 DrawBackgroundDev ();
188332
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+
210352 auto & items = pMode->m_CurrentStageMap ->m_vpStageMap ;
211353 const int itemCount = static_cast <int >(items.size ());
212354 int currentIdx = pMode->m_CurrentStageMapIndex .get ();
213355
214- // Input handling (Mouse)
356+ // Handle input
215357 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);
226359
227360 // 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 );
230363
231364 // Draw visible items
232365 for (int i = start_idx; i < end_idx; ++i) {
233366 const auto & item = items[i];
234367 if (!item) continue ;
235368
236369 const bool isSelected = (i == currentIdx);
237- const int visible_index = i - start_idx;
370+ const int visible_index = i - start_idx;
238371 bool hovered = false ;
239372
240373 // Calculate button position
241374 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 )
244378 };
379+
380+ // Format item name
245381 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
249384 }
250- if (isSelected)
251- {
385+ if (isSelected) {
252386 item_name = " >> " + item_name;
253387 }
254388
389+ // Draw button
255390 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)) {
258393 currentIdx = i;
259394 pMode->m_CurrentStageMapIndex = i;
260395 guest_stack_var<DevMessage> vMsgRec (0x10002 , 0x5A , 1 );
261396 GuestToHostFunction<void >(sub_824A8FF0, pMsgRec, vMsgRec.get ());
262397 }
263398
264- // Tooltip with stage information
399+ // Show tooltip if hovered and in custom mode
265400 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 );
345402 }
346-
347-
348403 }
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 ();
357404
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+ }
364409}
410+ // DrawStageMapSelector
365411
366412static void DrawDevTitle () {
367413
0 commit comments