#include "fiko/ui/isaret_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 = 26.0f; // orta iptal dairesi constexpr float R_DIS = 104.0f; // halka dışı yarıçapı constexpr float ESIK = 30.0f; // commit için min sürükleme mesafesi (px) constexpr float DWELL = 0.20f; // hareketsiz kalınca halka belirir (sn) constexpr float DWELL_HIZ = 6.0f; // dwell'de kabul edilen küçük titreme (px) // Pusula açıları/sırası/yarı-açısı → fiko/ui/sektor.hpp (PUSULA_ACI/SIRA/YARI); // işaret menüsü + araç halkası (radyal) ortak kullanır. struct IsaretDurum { bool acik = false; ImVec2 merkez{}; // aç noktası (sağ-tık dünyada) ImVec2 sonFare{}; // dwell titreme ölçümü float dwellSay = 0.0f; // hareketsiz geçen süre bool halkaGorunur = false; // dwell aşılınca true → görsel çizilir bool yeniAcildi = false; // bu kare açıldı → açılış karesindeki sağ-bırakmayı yut ImGuiID viewport = 0; bool kendiPencere = false; ImVec2 sonBoyut{}; }; std::unordered_map g_durumlar; ImGuiID kimlikHash(const char* kimlik) { return ImHashStr(kimlik); } // Öğeleri pusula yönlerine dağıtır: yon>=0 sabitlenir, -1'ler boş yönlere sırayla // serpilir. slot[yon] = öğe indeksi (-1 boş). Dönüş: dolu slot sayısı. int yonleriDoldur(const IsaretOge* ogeler, int sayi, int slot[8]) { for (int i = 0; i < 8; ++i) slot[i] = -1; // Önce sabit yönler. for (int i = 0; i < sayi; ++i) if (ogeler[i].yon >= 0 && ogeler[i].yon < 8 && slot[ogeler[i].yon] < 0) slot[ogeler[i].yon] = i; // Sonra otomatikler — boş ilk yuvaya (PUSULA_SIRA: K, D, G, B öncelikli okunur sıra). int si = 0; for (int i = 0; i < sayi; ++i) { if (ogeler[i].yon >= 0 && ogeler[i].yon < 8) continue; while (si < 8 && slot[PUSULA_SIRA[si]] >= 0) ++si; if (si >= 8) break; slot[PUSULA_SIRA[si]] = i; } int dolu = 0; for (int i = 0; i < 8; ++i) if (slot[i] >= 0) ++dolu; return dolu; } // Fare deltasından en yakın DOLU pusula yönünü bulur (eşik aşıldıysa). -1: yok. int enYakinYon(float dx, float dy, const int slot[8]) { const float fAci = std::atan2(dy, dx); int enIyi = -1; float enKucuk = FLT_MAX; for (int y = 0; y < 8; ++y) { if (slot[y] < 0) continue; float fark = fAci - PUSULA_ACI[y]; while (fark > PI) fark -= 2 * PI; while (fark < -PI) fark += 2 * PI; fark = std::fabs(fark); if (fark < enKucuk) { enKucuk = fark; enIyi = y; } } // yalnız ~yarım dilim (22.5°) içindeyse kesin snap; değilse yine en yakını al return enIyi; } // Bir pusula pastası çizer (yön merkezli ±22.5° dilim) — neon dolgu + kontur. void dilimCiz(ImDrawList* dl, ImVec2 m, float ic, float dis, float aciOrta, ImU32 dolgu, ImU32 kontur, float konturKalinlik) { const float yari = PUSULA_YARI; // ±22.5° - boşluk const float a0 = aciOrta - yari, a1 = aciOrta + yari; const int seg = ImMax(6, (int)((a1 - a0) * 24.0f)); dl->PathClear(); dl->PathArcTo(m, dis, a0, a1, seg); dl->PathArcTo(m, ic, a1, a0, seg); dl->PathFillConcave(dolgu); dl->PathArcTo(m, dis, a0, a1, seg); dl->PathStroke(kontur, 0, konturKalinlik); } void dilimGlow(ImDrawList* dl, ImVec2 m, float dis, float aciOrta, const ImVec4& renk, float siddet) { const float yari = PUSULA_YARI; const float a0 = aciOrta - yari, a1 = aciOrta + yari; const int seg = ImMax(6, (int)((a1 - a0) * 24.0f)); for (int i = 1; i <= 3; ++i) { dl->PathArcTo(m, dis + (float)i * 2.0f, a0, a1, seg); dl->PathStroke(u32(renk, 0.18f * siddet * tema().glowSiddet / (float)i), 0, 2.5f); } } } // namespace void isaretAc(const char* kimlik, ImVec2 konum) { const ImGuiID id = kimlikHash(kimlik); IsaretDurum& d = g_durumlar[id]; d.acik = true; d.merkez = konum; d.sonFare = konum; d.dwellSay = 0.0f; d.halkaGorunur = true; // tık ile açılır → halka HEMEN görünsün (dwell bekleme yok) d.yeniAcildi = true; // menü sağ-tık BIRAKIŞIYLA (Tik jesti) açılır → o karedeki // IsMouseReleased(Right) commit'i tetiklemesin (anında kapanma). d.sonBoyut = {}; // Radyal ile aynı tuval seçimi: fare altındaki viewport'a şeffaf tuvalle bağlan; // halka SIĞMIYORSA kendi OS penceresi + cam panel fallback. 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_DIS + 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.kendiPencere = !sigiyor; d.viewport = sigiyor ? hedef->ID : 0; animSifirla(id, 0.0f); // halka giriş animasyonu } void isaretKapat(const char* kimlik) { g_durumlar[kimlikHash(kimlik)].acik = false; } bool isaretAcikMi(const char* kimlik) { auto it = g_durumlar.find(kimlikHash(kimlik)); return it != g_durumlar.end() && it->second.acik; } IsaretSecim isaretCiz(const char* kimlik, const IsaretOge* ogeler, int sayi) { IsaretSecim secim; const ImGuiID id = kimlikHash(kimlik); auto it = g_durumlar.find(id); if (it == g_durumlar.end() || !it->second.acik || sayi <= 0) return secim; IsaretDurum& d = it->second; const Tema& t = tema(); int slot[8]; yonleriDoldur(ogeler, sayi, slot); 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); // ---- Dwell (hareketsiz kalma) izleme → halka fade-in ---- const float df = std::sqrt((fare.x - d.sonFare.x) * (fare.x - d.sonFare.x) + (fare.y - d.sonFare.y) * (fare.y - d.sonFare.y)); if (df > DWELL_HIZ) { d.dwellSay = 0.0f; // hareket → dwell sıfırla d.sonFare = fare; } else { d.dwellSay += ImGui::GetIO().DeltaTime; if (d.dwellSay >= DWELL) d.halkaGorunur = true; } // Eşik aştıysa kullanıcı zaten hedefe niyetli → izi hep göster (kas hafızası). // Sürükleme sırasında hedef yön (görsel vurgulama + commit adayı). const int aktifYon = (uzaklik >= ESIK) ? enYakinYon(dx, dy, slot) : -1; const float acilim = yay(id, d.halkaGorunur ? 1.0f : 0.0f, 260.0f, 20.0f); const bool gorsel = acilim > 0.01f; const float kapsamYari = d.kendiPencere ? R_DIS + 18.0f : R_DIS + 44.0f; // ---- Tuval seçimi (radyal_menu ile birebir) ---- 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("##isaret_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 zemini yalnız halka görünürken + ayrık pencerede. ImGuiViewportP* vpp = (ImGuiViewportP*)ImGui::GetWindowViewport(); const bool ayrikPencere = vpp && vpp->Window == ImGui::GetCurrentWindow(); if (ayrikPencere && gorsel) { 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, t.koyu ? 0.30f : 0.14f); dl->AddRectFilled(t0, t1, u32(t.panel, 0.50f * acilim), kose); dl->AddRect(t0, t1, u32(t.vurgu, 0.45f * acilim), kose, 0, 1.5f); glowCerceve(dl, {t0.x + 2, t0.y + 2}, {t1.x - 2, t1.y - 2}, t.vurgu, kose, 0.5f * acilim); 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 float rDis = R_DIS * ImClamp(acilim, 0.0f, 1.15f); const float rIc = R_IC * ImClamp(acilim, 0.0f, 1.15f); // ---- Halka görseli (dwell'de belirir) — radyal dilim kalıbı ---- if (gorsel) { for (int y = 0; y < 8; ++y) { if (slot[y] < 0) continue; const IsaretOge& o = ogeler[slot[y]]; const bool aktif = (y == aktifYon); const float aci = PUSULA_ACI[y]; ImVec4 dolgu = karisim(t.panel, t.vurgu, aktif ? 0.55f : 0.16f); dolgu.w = 0.94f * acilim; if (!o.etkin) dolgu = {t.panel.x, t.panel.y, t.panel.z, 0.72f * acilim}; const ImVec4 kontur = o.etkin ? t.vurgu : t.metinSoluk; if (aktif && o.etkin) dilimGlow(dl, m, rDis, aci, t.vurgu, 1.0f); dilimCiz(dl, m, rIc + 4.0f, rDis, aci, u32(dolgu), u32(kontur, aktif ? 0.9f : 0.30f), aktif ? 2.2f : 1.2f); const float rIkon = (rIc + 4.0f + rDis) * 0.5f; const ImVec2 ikonM{m.x + std::cos(aci) * rIkon, m.y + std::sin(aci) * rIkon - 6.0f}; const ImU32 mRenk = u32(o.etkin ? (aktif ? t.metin : karisim(t.metin, t.metinSoluk, 0.25f)) : t.metinSoluk, acilim); 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); } } } // ---- Kas hafızası izi: imleçten aç noktasına neon çizgi + aktif yön vurgusu ---- if (uzaklik > 6.0f) { // İnce ışıyan iz (katmanlı → glow hissi). const ImVec4 izRenk = (aktifYon >= 0) ? t.vurgu : t.metinSoluk; for (int i = 3; i >= 1; --i) dl->AddLine(m, fare, u32(izRenk, 0.12f * (float)i * t.glowSiddet), (float)i * 2.0f); dl->AddLine(m, fare, u32(izRenk, uzaklik >= ESIK ? 0.95f : 0.5f), 2.0f); // İmleç ucunda küçük yön göstergesi. dl->AddCircleFilled(fare, uzaklik >= ESIK ? 4.5f : 3.0f, u32(izRenk, uzaklik >= ESIK ? 1.0f : 0.6f)); if (aktifYon >= 0) dl->AddCircle(fare, 7.0f, u32(t.vurgu, 0.7f), 0, 1.5f); } // ---- Orta daire: aktif öğe adı / iptal ---- const bool esikAlti = uzaklik < ESIK; dl->AddCircleFilled(m, ImMax(rIc, 10.0f), u32(karisim(t.panel, esikAlti ? t.hata : t.vurgu, esikAlti ? 0.30f : 0.12f), gorsel ? 0.96f : 0.85f)); dl->AddCircle(m, ImMax(rIc, 10.0f), u32(esikAlti ? t.hata : t.vurgu, 0.5f), 0, 1.5f); { const char* ortaMetin = esikAlti ? "✕" : nullptr; if (!esikAlti && aktifYon >= 0) ortaMetin = ogeler[slot[aktifYon]].etiket; if (ortaMetin) { ImFont* f = ImGui::GetFont(); const float fs = esikAlti ? 15.0f : 11.0f; const ImVec2 mb = f->CalcTextSizeA(fs, FLT_MAX, 0.0f, ortaMetin); dl->AddText(f, fs, {m.x - mb.x * 0.5f, m.y - mb.y * 0.5f}, u32(esikAlti ? t.hata : t.metin), ortaMetin); } } // ---- Etkileşim: iki commit yolu ---- // (1) Maya jesti: sağ-tık BIRAKIŞI + yön (yalnız in-window; overlay'de io // release gelmez → tık modu kullanılır). // (2) Tık modu (radyal gibi): halka görünürken sol-tıkla öğe seç / merkez-X / // dışarı kapat. Overlay'de sol tık SeffafPencere mandalından okunur (io'ya // İLETİLMEZ → arkadaki pencere tıklanmaz); overlay DIŞINA io tıkı = kapat. const bool solTik = overlay ? SeffafPencere::tekil().solTikAl() : ImGui::IsMouseClicked(ImGuiMouseButton_Left); const bool sagTikOverlay = overlay && SeffafPencere::tekil().sagTikAl(); // Açılış karesinde sağ buton zaten bırakılmış (Tik jesti) → o kareyi yut, yoksa // menü anında iptal olurdu. Sonraki karelerde gerçek flick-bırakışı commit eder. const bool sagBirakildi = !overlay && !d.yeniAcildi && ImGui::IsMouseReleased(ImGuiMouseButton_Right); d.yeniAcildi = false; const bool disTik = overlay && ImGui::IsMouseClicked(ImGuiMouseButton_Left); const bool iptalTus = ImGui::IsKeyPressed(ImGuiKey_Escape); bool kapat = false; if (sagBirakildi) { // Maya jesti: yön fısırtısı secim.commit = true; secim.secili = (uzaklik >= ESIK && aktifYon >= 0 && ogeler[slot[aktifYon]].etkin) ? slot[aktifYon] : -1; // eşik altı → iptal kapat = true; } else if (solTik) { // tık modu if (gorsel && aktifYon >= 0 && uzaklik >= rIc && uzaklik <= rDis + 10.0f && ogeler[slot[aktifYon]].etkin) { secim.commit = true; secim.secili = slot[aktifYon]; // halka öğesine tık → komut çalışır kapat = true; } else { // merkez X veya dışarı tık → iptal/kapat secim.commit = true; secim.secili = -1; kapat = true; } } else if (sagTikOverlay || disTik || iptalTus) { secim.commit = true; secim.secili = -1; kapat = true; } 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