@@ -597,13 +597,54 @@ void ToolboxUIElement::ShowVisibleRadio()
597597 ImGui::PopID ();
598598}
599599
600+ namespace {
601+ // Live rects of currently-shown breakout buttons, keyed by their ImGui window id.
602+ // Lets a newly-shown button pick a spot near the screen centre that doesn't overlap the others.
603+ std::unordered_map<std::string, ImRect> breakout_button_rects;
604+
605+ bool BreakoutRectsOverlap (const ImRect& a, const ImRect& b)
606+ {
607+ return a.Min .x < b.Max .x && a.Max .x > b.Min .x && a.Min .y < b.Max .y && a.Max .y > b.Min .y ;
608+ }
609+
610+ // Pick a position starting from the centre of the screen, cascading until it clears every other breakout button.
611+ ImVec2 GetDefaultBreakoutPos (const char * window_id, const ImVec2& size)
612+ {
613+ const ImGuiViewport* vp = ImGui::GetMainViewport ();
614+ const ImVec2 start = {vp->WorkPos .x + (vp->WorkSize .x - size.x ) * 0 .5f , vp->WorkPos .y + (vp->WorkSize .y - size.y ) * 0 .5f };
615+ const ImVec2 max = {vp->WorkPos .x + vp->WorkSize .x , vp->WorkPos .y + vp->WorkSize .y };
616+ ImVec2 pos = start;
617+ for (int i = 0 ; i < 256 ; i++) {
618+ const ImRect candidate = {pos, {pos.x + size.x , pos.y + size.y }};
619+ bool overlaps = false ;
620+ for (const auto & [id, rect] : breakout_button_rects) {
621+ if (id != window_id && BreakoutRectsOverlap (candidate, rect)) {
622+ overlaps = true ;
623+ break ;
624+ }
625+ }
626+ if (!overlaps) break ;
627+ pos.x += size.x + 6 .f ;
628+ if (pos.x + size.x > max.x ) {
629+ pos.x = start.x ;
630+ pos.y += size.y + 6 .f ;
631+ if (pos.y + size.y > max.y ) pos.y = vp->WorkPos .y ;
632+ }
633+ }
634+ return pos;
635+ }
636+ }
637+
600638void ToolboxUIElement::DrawBreakoutButton (IDirect3DDevice9*)
601639{
602- if (!show_breakout_button) return ;
603-
604640 char window_id[256 ];
605641 snprintf (window_id, sizeof (window_id), " %s##breakout_btn" , Name ());
606642
643+ if (!show_breakout_button) {
644+ breakout_button_rects.erase (window_id);
645+ return ;
646+ }
647+
607648 ImGuiWindowFlags flags = ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoNav;
608649
609650 if (!ToolboxSettings::move_all && lock_breakout_button) {
@@ -613,6 +654,16 @@ void ToolboxUIElement::DrawBreakoutButton(IDirect3DDevice9*)
613654 if (pending_breakout_pos) {
614655 ImGui::SetNextWindowPos ({breakout_pos[0 ], breakout_pos[1 ]}, ImGuiCond_Always);
615656 pending_breakout_pos = false ;
657+ breakout_pos_set = true ;
658+ }
659+ else if (!breakout_pos_set) {
660+ // Brand-new button: default to the middle of the screen, nudged so it doesn't land on another button.
661+ const float est = ImGui::GetFrameHeight () + 16 .f ;
662+ const ImVec2 pos = GetDefaultBreakoutPos (window_id, {est, est});
663+ ImGui::SetNextWindowPos (pos, ImGuiCond_Always);
664+ breakout_pos[0 ] = pos.x ;
665+ breakout_pos[1 ] = pos.y ;
666+ breakout_pos_set = true ;
616667 }
617668
618669 ImGui::PushStyleVar (ImGuiStyleVar_WindowPadding, {6 .f , 6 .f });
@@ -666,9 +717,11 @@ void ToolboxUIElement::DrawBreakoutButton(IDirect3DDevice9*)
666717 ImGui::PopStyleVar (2 );
667718
668719 // Keep breakout_pos current so SaveSettings captures the right position even without a live ImGui context.
720+ // Also record the live rect so other breakout buttons can avoid overlapping this one.
669721 if (const auto bw = ImGui::FindWindowByName (window_id)) {
670722 breakout_pos[0 ] = bw->Pos .x ;
671723 breakout_pos[1 ] = bw->Pos .y ;
724+ breakout_button_rects[window_id] = ImRect (bw->Pos , {bw->Pos .x + bw->Size .x , bw->Pos .y + bw->Size .y });
672725 }
673726}
674727
0 commit comments