Skip to content

Commit 2a9fa33

Browse files
GWToolbox Botclaude
andcommitted
fix(breakout): spawn buttons centred and non-overlapping
New breakout buttons defaulted to a fixed position and stacked on top of each other, forcing the user to drag every newly-enabled button into place. Keep an internal registry of each shown breakout button's live rect. When a button first appears without a saved position, place it in the middle of the screen and cascade it until it clears every other breakout button, so they can't spawn overlapping. Saved positions are still restored as before. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 25bea3f commit 2a9fa33

2 files changed

Lines changed: 57 additions & 2 deletions

File tree

GWToolboxdll/ToolboxUIElement.cpp

Lines changed: 55 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
600638
void 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

GWToolboxdll/ToolboxUIElement.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,8 @@ class ToolboxUIElement : public ToolboxModule {
5454
bool lock_breakout_button = false;
5555
std::array<float, 2> breakout_pos = {60.f, 60.f};
5656
bool pending_breakout_pos = false;
57+
// Runtime-only: set once the button has been positioned (from saved settings or a computed default)
58+
bool breakout_pos_set = false;
5759

5860
// Mobile-mode layout settings (separate from normal-mode settings above)
5961
bool mobile_lock_move = false;

0 commit comments

Comments
 (0)