#include "fiko/ui/radyal_menu.hpp" #include #include #include #include #include "fiko/ui/animasyon.hpp" #include "fiko/ui/bilesen.hpp" #include "fiko/ui/efekt.hpp" #include "fiko/ui/seffaf_pencere.hpp" #include "fiko/ui/sektor.hpp" #include "fiko/ui/tema.hpp" #include "imgui_internal.h" namespace fiko::ui { namespace { constexpr float PI = 3.14159265f; constexpr float R_IC = 30.0f; // orta iptal dairesi constexpr float R_DIS = 106.0f; // ana halka dışı constexpr float R_ALT = 168.0f; // alt halka dışı constexpr float BOSLUK = 0.030f; // sektörler arası açısal boşluk (rad) struct RadyalDurum { bool acik = false; ImVec2 merkez{}; int aktifAna = -1; // alt halkası açık öğe ImGuiID viewport = 0; // sığıyorsa bağlanılan viewport (şeffaf tuval) bool kendiPencere = false; // sığmıyorsa: kendi OS penceresi + cam panel zemin ImVec2 sonBoyut{}; // SetWindowRgn yalnız boyut değişince yenilenir }; std::unordered_map g_durumlar; ImGuiID kimlikHash(const char* kimlik) { return ImHashStr(kimlik); } // sektorCiz / sektorGlow → fiko/ui/sektor.hpp (radyal + işaret + araç halkası paylaşır). // Bir halkadaki öğeleri çizer ve fare etkileşimini değerlendirir. // aciBas/aciSpan: halkanın kapladığı açı aralığı. Dönen: hover indeksi (-1 yok). int halkaCiz(ImDrawList* dl, ImVec2 m, const RadyalOge* ogeler, int sayi, float ic, float dis, float aciBas, float aciSpan, float acilim, int vurgulu) { const Tema& t = tema(); const ImVec2 fare = ImGui::GetMousePos(); const float dx = fare.x - m.x, dy = fare.y - m.y; const float uzaklik = std::sqrt(dx * dx + dy * dy); float fAci = std::atan2(dy, dx); const float dilim = aciSpan / (float)sayi; int hover = -1; if (uzaklik >= ic && uzaklik <= dis) { // açıyı halka aralığına normalize et float g = fAci - aciBas; while (g < 0) g += 2 * PI; while (g >= 2 * PI) g -= 2 * PI; if (g <= aciSpan) { const int aday = (int)(g / dilim); if (aday >= 0 && aday < sayi && ogeler[aday].etkin) hover = aday; } } for (int i = 0; i < sayi; ++i) { const float a0 = aciBas + dilim * (float)i + BOSLUK * 0.5f; const float a1 = aciBas + dilim * (float)(i + 1) - BOSLUK * 0.5f; const bool h = i == hover; const bool secilmis = i == vurgulu; const RadyalOge& o = ogeler[i]; ImVec4 dolgu = karisim(t.panel, t.vurgu, secilmis ? 0.45f : (h ? 0.55f : 0.16f)); dolgu.w = 0.94f; if (!o.etkin) dolgu = {t.panel.x, t.panel.y, t.panel.z, 0.72f}; const ImVec4 kontur = o.etkin ? t.vurgu : t.metinSoluk; if (h || secilmis) sektorGlow(dl, m, dis, a0, a1, t.vurgu, h ? 1.0f : 0.55f); sektorCiz(dl, m, ic, dis, a0, a1, u32(dolgu), u32(kontur, (h || secilmis) ? 0.9f : 0.30f), h ? 2.2f : 1.2f); // simge + etiket (sektör orta açısında) const float orta = (a0 + a1) * 0.5f; const float rIkon = (ic + dis) * 0.5f; const ImVec2 ikonM{m.x + std::cos(orta) * rIkon, m.y + std::sin(orta) * rIkon - 6.0f}; const ImU32 mRenk = u32(o.etkin ? (h ? t.metin : karisim(t.metin, t.metinSoluk, 0.25f)) : t.metinSoluk); if (!o.simge.bosMu()) simgeCiz(dl, o.simge, ikonM, 20.0f * acilim, mRenk); if (o.etiket) { ImFont* f = ImGui::GetFont(); const float fs = 12.0f; const ImVec2 mb = f->CalcTextSizeA(fs, FLT_MAX, 0.0f, o.etiket); dl->AddText(f, fs, {ikonM.x - mb.x * 0.5f, ikonM.y + 12.0f}, mRenk, o.etiket); } // çocuk işareti: dış kenarda küçük ok if (o.cocukSayi > 0) { const ImVec2 okM{m.x + std::cos(orta) * (dis - 7.0f), m.y + std::sin(orta) * (dis - 7.0f)}; dl->AddCircleFilled(okM, 2.5f, u32(t.vurgu2, o.etkin ? 0.9f : 0.3f)); } } return hover; } } // namespace void radyalAc(const char* kimlik, ImVec2 konum) { const ImGuiID id = kimlikHash(kimlik); RadyalDurum& d = g_durumlar[id]; d.acik = true; d.aktifAna = -1; // Tercih: fare altındaki viewport'a şeffaf tuvalle bağlan (OS penceresi doğmaz). // Halka oraya SIĞMIYORSA kaydırma yapılmaz — menü tam tıklanan yerde kendi // OS penceresinde açılır; zemin cam panel + yuvarlatılmış pencere bölgesi olur. ImGuiViewport* hedef = ImGui::GetMainViewport(); if (ImGuiViewport* v = ImGui::FindViewportByID(ImGui::GetIO().MouseHoveredViewport)) hedef = v; else for (ImGuiViewport* aday : ImGui::GetPlatformIO().Viewports) if (konum.x >= aday->Pos.x && konum.x < aday->Pos.x + aday->Size.x && konum.y >= aday->Pos.y && konum.y < aday->Pos.y + aday->Size.y) { hedef = aday; break; } const float K = R_ALT + 44.0f; const bool sigiyor = konum.x - K >= hedef->Pos.x && konum.y - K >= hedef->Pos.y && konum.x + K <= hedef->Pos.x + hedef->Size.x && konum.y + K <= hedef->Pos.y + hedef->Size.y; d.merkez = konum; d.kendiPencere = !sigiyor; d.viewport = sigiyor ? hedef->ID : 0; d.sonBoyut = {}; animSifirla(id, 0.0f); // ana halka giriş animasyonu animSifirla(id + 1, 0.0f); // alt halka animasyonu } void radyalKapat(const char* kimlik) { g_durumlar[kimlikHash(kimlik)].acik = false; } bool radyalAcikMi(const char* kimlik) { auto it = g_durumlar.find(kimlikHash(kimlik)); return it != g_durumlar.end() && it->second.acik; } RadyalSecim radyalCiz(const char* kimlik, const RadyalOge* ogeler, int sayi) { RadyalSecim secim; const ImGuiID id = kimlikHash(kimlik); auto it = g_durumlar.find(id); if (it == g_durumlar.end() || !it->second.acik || sayi <= 0) return secim; RadyalDurum& d = it->second; const Tema& t = tema(); const float acilim = yay(id, 1.0f, 260.0f, 20.0f); const float rDis = R_DIS * ImClamp(acilim, 0.0f, 1.15f); const float rIc = R_IC * ImClamp(acilim, 0.0f, 1.15f); const bool altVar = d.aktifAna >= 0 && ogeler[d.aktifAna].cocukSayi > 0; const float altAcilim = altVar ? yay(id + 1, 1.0f, 260.0f, 20.0f) : 0.0f; // Kendi penceresinde tuval sıkı ve SABİT boyutludur (overlay yeniden kurulmasın). const float kapsamYari = d.kendiPencere ? R_ALT + 18.0f : (altVar ? R_ALT : R_DIS) + 44.0f; // Tuval seçimi: // 1) Sığan durum → mevcut viewport'a bağlı şeffaf ImGui penceresi. // 2) Sığmayan + D3D interop → SeffafPencere (gerçek per-pixel alfa, DComp). // 3) Sığmayan + interop yok → ImGui penceresi + cam panel zemin (fallback). const bool overlay = d.kendiPencere && SeffafPencere::destekleniyor() && SeffafPencere::tekil().hazirla((int)(kapsamYari * 2), (int)(kapsamYari * 2)); ImDrawList* dl = nullptr; if (overlay) { SeffafPencere& sp = SeffafPencere::tekil(); if (!sp.gorunurMu()) sp.goster(d.merkez.x - kapsamYari, d.merkez.y - kapsamYari); dl = sp.kareBasla(); } else { if (!d.kendiPencere && ImGui::FindViewportByID(d.viewport)) ImGui::SetNextWindowViewport(d.viewport); ImGui::SetNextWindowPos({d.merkez.x - kapsamYari, d.merkez.y - kapsamYari}); ImGui::SetNextWindowSize({kapsamYari * 2, kapsamYari * 2}); ImGui::SetNextWindowBgAlpha(0.0f); ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f); ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f); ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0)); ImGui::Begin("##radyal_tuval", nullptr, ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoDocking | ImGuiWindowFlags_NoScrollWithMouse | ImGuiWindowFlags_NoNav); ImGui::PopStyleVar(3); ImGui::BringWindowToDisplayFront(ImGui::GetCurrentWindow()); dl = ImGui::GetWindowDrawList(); // ---- Cam panel fallback: zemin + köşeleri OS seviyesinde kırp ---- // KORUMA: yalnız viewport GERÇEKTEN bu tuvale aitse (başka pencereye merge // edilmediyse) uygulanır; aksi halde SetWindowRgn ana pencereyi kırpardı. ImGuiViewportP* vpp = (ImGuiViewportP*)ImGui::GetWindowViewport(); const bool ayrikPencere = vpp && vpp->Window == ImGui::GetCurrentWindow(); if (ayrikPencere) { const ImVec2 t0 = ImGui::GetWindowPos(); const ImVec2 t1{t0.x + kapsamYari * 2, t0.y + kapsamYari * 2}; const float kose = 22.0f; Efektler::aurora(dl, t0, t1, tema().koyu ? 0.30f : 0.14f); dl->AddRectFilled(t0, t1, u32(tema().panel, 0.55f), kose); dl->AddRect(t0, t1, u32(tema().vurgu, 0.45f), kose, 0, 1.5f); glowCerceve(dl, {t0.x + 2, t0.y + 2}, {t1.x - 2, t1.y - 2}, tema().vurgu, kose, 0.5f); const ImVec2 boyut = ImGui::GetWindowSize(); if (vpp->PlatformHandleRaw && (boyut.x != d.sonBoyut.x || boyut.y != d.sonBoyut.y)) { d.sonBoyut = boyut; const int bg = (int)(boyut.x + 0.5f), by = (int)(boyut.y + 0.5f); SetWindowRgn((HWND)vpp->PlatformHandleRaw, CreateRoundRectRgn(0, 0, bg + 1, by + 1, (int)(kose * 2), (int)(kose * 2)), TRUE); } } } const ImVec2 m = d.merkez; const ImVec2 fare = ImGui::GetMousePos(); const float dx = fare.x - m.x, dy = fare.y - m.y; const float uzaklik = std::sqrt(dx * dx + dy * dy); // Ana halka (tam çember, üstten başlar) const float bas = -PI * 0.5f; const int anaHover = halkaCiz(dl, m, ogeler, sayi, rIc + 4.0f, rDis, bas, 2 * PI, acilim, d.aktifAna); // Alt halka: aktif ananın sektör ortası etrafına yayılır int altHover = -1; if (altVar) { const RadyalOge& ana = ogeler[d.aktifAna]; const float dilim = 2 * PI / (float)sayi; const float orta = bas + dilim * ((float)d.aktifAna + 0.5f); const float span = ImMin(0.62f * (float)ana.cocukSayi, 2 * PI * 0.8f); const float altDis = R_DIS + (R_ALT - R_DIS) * ImClamp(altAcilim, 0.0f, 1.1f); altHover = halkaCiz(dl, m, ana.cocuklar, ana.cocukSayi, rDis + 5.0f, altDis, orta - span * 0.5f, span, altAcilim, -1); } // Orta daire: hover edilen öğenin adı / iptal const bool ortaHover = uzaklik < rIc; dl->AddCircleFilled(m, rIc, u32(karisim(t.panel, ortaHover ? t.hata : t.vurgu, ortaHover ? 0.35f : 0.10f), 0.96f)); dl->AddCircle(m, rIc, u32(ortaHover ? t.hata : t.vurgu, 0.5f), 0, 1.5f); { const char* ortaMetin = "✕"; if (!ortaHover) { if (altHover >= 0) ortaMetin = ogeler[d.aktifAna].cocuklar[altHover].etiket; else if (anaHover >= 0) ortaMetin = ogeler[anaHover].etiket; else if (d.aktifAna >= 0) ortaMetin = ogeler[d.aktifAna].etiket; } if (ortaMetin) { ImFont* f = ImGui::GetFont(); const float fs = ortaHover ? 16.0f : 11.0f; ImVec2 mb = f->CalcTextSizeA(fs, FLT_MAX, 0.0f, ortaMetin); if (mb.x > rIc * 1.8f) { // sığmıyorsa küçült mb.x *= 9.0f / fs; mb.y *= 9.0f / fs; dl->AddText(f, 9.0f, {m.x - mb.x * 0.5f, m.y - mb.y * 0.5f}, u32(t.metin), ortaMetin); } else { dl->AddText(f, fs, {m.x - mb.x * 0.5f, m.y - mb.y * 0.5f}, u32(ortaHover ? t.hata : t.metin), ortaMetin); } } } // ---- Etkileşim ---- // Overlay modunda tıklar SeffafPencere WndProc mandallarından gelir (io'ya // iletilmez → arkadaki pencere tıklanmaz). ImGui io'daki gerçek tık ise // overlay DIŞINDAKİ bir pencereye yapılmıştır → menüyü kapatır. const bool solTik = overlay ? SeffafPencere::tekil().solTikAl() : ImGui::IsMouseClicked(ImGuiMouseButton_Left); const bool sagTik = SeffafPencere::tekil().sagTikAl() || ImGui::IsMouseClicked(ImGuiMouseButton_Right); const bool disTik = overlay && ImGui::IsMouseClicked(ImGuiMouseButton_Left); const float etkinDis = altVar ? R_ALT : rDis; bool kapat = false; if (ImGui::IsKeyPressed(ImGuiKey_Escape) || sagTik || disTik) kapat = true; if (solTik) { if (ortaHover) { kapat = true; } else if (altHover >= 0) { secim.ana = d.aktifAna; secim.cocuk = altHover; kapat = true; } else if (anaHover >= 0) { if (ogeler[anaHover].cocukSayi > 0) { if (d.aktifAna == anaHover) { d.aktifAna = -1; // aynı öğe: alt halkayı kapat } else { d.aktifAna = anaHover; animSifirla(id + 1, 0.0f); } } else { secim.ana = anaHover; kapat = true; } } else if (uzaklik > etkinDis + 18.0f) { kapat = true; // dışarı tık } } if (kapat) d.acik = false; if (overlay) { SeffafPencere& sp = SeffafPencere::tekil(); sp.kareBitir(); if (kapat) sp.gizle(); } else { ImGui::End(); } return secim; } } // namespace fiko::ui