#include "arama_menu.hpp" #include "fiko/core/metin.hpp" // icerirTr/sadeTr — Türkçe duyarsız arama #include #include #include #include #include #include #include #include "ayar_kalici.hpp" // ayarOku/ayarYaz — kullanım sayacı kalıcılığı #include "fiko/ui/animasyon.hpp" #include "fiko/ui/bilesen.hpp" // glowCerceve #include "fiko/ui/simge.hpp" // ui::Simge / simgeCiz #include "fiko/ui/tema.hpp" // tema / u32 / karisim #include "fiko/ui/web_skin.hpp" // webSkinGridCiz — merkezî ızgara (Ayarlar → Pencere) #include "imgui_internal.h" // BringWindowToDisplayFront namespace fiko { namespace { using namespace fiko::ui; // ---- Komut kullanım öğrenmesi (bağlama göre son+sık) — bkz. arama_menu.hpp ---- // Anahtar: "baglam\x1fgrup|ad". Kalıcı biçim: "anahtar:sayi:sira;anahtar:sayi:sira". // sayi/sira SONDA (rfind) → ad içinde ':' geçse bile ayrışır. Değerde '=' ve '\n' yok // → ayar_kalici (satır başına anahtar=değer, ilk '='de böler) güvenle taşır. struct Kullanim { ::fiko::u32 sayi = 0; ::fiko::u64 sira = 0; // monotonik kullanım sırası → MRU ("son kullandığın") }; ::fiko::u64 g_sira = 0; // bir sonraki kullanım damgası (yüklemede en büyüğün üstüne kurulur) std::map& kullanimTablo() { static std::map tablo; static bool yuklendi = false; if (yuklendi) return tablo; yuklendi = true; const std::string s = ayarOku("dm.komut_kullanim", ""); std::size_t i = 0; while (i < s.size()) { const std::size_t son = s.find(';', i); const std::string kayit = s.substr(i, son == std::string::npos ? son : son - i); const std::size_t iSira = kayit.rfind(':'); if (iSira != std::string::npos && iSira > 0) { const std::size_t iSayi = kayit.rfind(':', iSira - 1); if (iSayi != std::string::npos && iSayi > 0) { Kullanim k; k.sayi = (::fiko::u32)std::strtoul(kayit.c_str() + iSayi + 1, nullptr, 10); k.sira = (::fiko::u64)std::strtoull(kayit.c_str() + iSira + 1, nullptr, 10); if (k.sayi) { tablo[kayit.substr(0, iSayi)] = k; if (k.sira >= g_sira) g_sira = k.sira + 1; // damga çakışmasın (MRU bozulur) } } } if (son == std::string::npos) break; i = son + 1; } return tablo; } std::string kullanimAnahtar(const std::string& baglam, const std::string& grup, const std::string& ad) { return baglam + "\x1f" + grup + "|" + ad; } // Kalıcı biçim ayırıcıları kayıtta geçerse dosya bozulur → o kaydı hiç yazma. bool anahtarGuvenli(const std::string& s) { return s.find(';') == std::string::npos && s.find('=') == std::string::npos && s.find('\n') == std::string::npos; } std::string& baglamRef() { static std::string b = "genel"; return b; } // Eşanlamlı sözlüğü: kullanıcı İngilizce/CAD jargonu yazınca TÜRKÇE komut bulunsun // ("fillet" → "Kenar Yuvarlat (3B)"). takma = kullanıcının yazdığı, hedef = komut // etiketinde geçen parça. Filtre takma adın ÖNEKİ ise eşleşir ("fil" de tutar). // Tek yön yeter: komut adları zaten Türkçe (isimlendirme kuralı). struct Esanlam { const char* takma; const char* hedef; }; constexpr Esanlam kEsanlamlar[] = { {"fillet", "Yuvarlat"}, {"round", "Yuvarlat"}, {"chamfer", "Pah"}, {"bspline", "Spline"}, {"egri", "Spline"}, {"bevel", "Pah"}, {"extrude", "Pad"}, {"pocket", "Cep"}, {"hole", "Delik"}, {"drill", "Delik"}, {"shell", "Boşalt"}, {"draft", "Çekme Açısı"}, {"mirror", "Ayna"}, {"pattern", "Dizi"}, {"array", "Dizi"}, {"union", "Birleştir"}, {"subtract", "Çıkar"}, {"intersect", "Kesişim"}, {"measure", "Ölçüm"}, {"section", "Kesit"}, {"import", "İçe Aktar"}, {"export", "Dışa Aktar"},{"revolve", "Shaft"}, {"thickness", "Kalınlık"},{"curvature", "Kurvatür"},{"box", "Kutu"}, {"cylinder", "Silindir"}, {"sphere", "Küre"}, {"cone", "Koni"}, {"line", "Çizgi"}, {"weight", "Ağırlık"}, {"mass", "Ağırlık"}, {"volume", "Ağırlık"}, {"push", "İt/Çek"}, {"pull", "İt/Çek"}, {"move", "Taşı"}, {"delete", "Kaldır"}, {"remove", "Kaldır"}, {"hide", "Gizle"}, {"show", "Göster"}, {"zebra", "Zebra"}, {"diff", "CAD Fark"}, {"compare", "CAD Fark"}, }; // Türkçe duyarsız alt-dizi araması. Eskiden ASCII-only tolower'dı → "çekme"/"Ç" // eşleşmiyordu; merkezi fiko::icerirTr hem büyük/küçük hem ASCII-katlama yapar // ("cekme" de "Çekme Açısı"nı bulur). bool icerirCI(const std::string& metin, const char* aranan) { return icerirTr(metin, aranan ? aranan : ""); } // "fil" → "fillet" gibi ÖNEK eşleşmesi (Türkçe duyarsız, ASCII katlamalı). bool onekCI(const char* tam, const char* onek) { const std::string t = sadeTr(tam ? tam : ""), o = sadeTr(onek ? onek : ""); return t.compare(0, o.size(), o) == 0; } // Menü öğesi filtreye uyuyor mu: etiket VEYA ipucu VEYA eşanlamlı köprüsü. // ipucu da aranır → "kalıp çıkışı" yazınca Çekme Açısı çıkar (niyetle arama). bool ogeEslesir(const AramaMenuOge& o, const char* filtre) { if (!filtre || !*filtre) return true; if (icerirCI(o.etiket, filtre)) return true; if (!o.ipucu.empty() && icerirCI(o.ipucu, filtre)) return true; for (const Esanlam& e : kEsanlamlar) if (onekCI(e.takma, filtre) && icerirCI(o.etiket, e.hedef)) return true; return false; } constexpr float GEN = 244.0f; // pencere genişliği (px) constexpr float SATIR = 26.0f; // liste satır yüksekliği constexpr float BASLIK = 34.0f; // arama kutusu bandı constexpr int MAKS_GORUNEN = 9; // fazlası kaydırılır } // namespace bool& menuOgrenmeAktif() { static bool b = true; return b; } // AyarSistemi void komutOgrenmeSifirla() { kullanimTablo().clear(); g_sira = 0; ayarYaz("dm.komut_kullanim", ""); } void dmBaglamAyarla(const std::string& baglam) { baglamRef() = baglam; } const std::string& dmBaglam() { return baglamRef(); } void komutKullanimArtir(const std::string& baglam, const std::string& grup, const std::string& ad) { if (!menuOgrenmeAktif()) return; // öğrenme kapalı → sayaç hiç tutulmaz if (grup.empty() || ad.empty() || baglam.empty()) return; const std::string anah = kullanimAnahtar(baglam, grup, ad); if (!anahtarGuvenli(anah)) return; auto& tablo = kullanimTablo(); Kullanim& k = tablo[anah]; ++k.sayi; k.sira = g_sira++; // Diske hemen yaz: komut çalıştırma kullanıcı-hızındadır (sıcak yol DEĞİL), dosya // küçük. Çökme/kapanışta öğrenilenler kaybolmasın. std::string s; for (const auto& [anahtar, kul] : tablo) { if (!s.empty()) s += ';'; s += anahtar; s += ':'; s += std::to_string(kul.sayi); s += ':'; s += std::to_string(kul.sira); } ayarYaz("dm.komut_kullanim", s); } std::vector komutOgrenilen(const std::string& baglam, int maksSon, int maksSik) { if (!menuOgrenmeAktif()) return {}; const auto& tablo = kullanimTablo(); // Bir kovadaki kayıtları topla: (sira, sayi, grup, ad). const auto topla = [&tablo](const std::string& kova) { std::vector> v; const std::string onek = kova + "\x1f"; for (const auto& [anahtar, kul] : tablo) { if (anahtar.compare(0, onek.size(), onek) != 0) continue; const std::string kalan = anahtar.substr(onek.size()); const std::size_t bar = kalan.find('|'); if (bar == std::string::npos) continue; v.push_back({kul.sira, kul.sayi, kalan.substr(0, bar), kalan.substr(bar + 1)}); } return v; }; // Bu bağlamda hiç geçmiş yoksa "genel" kovasına düş → menü ilk günden boş kalmasın. auto kayitlar = topla(baglam); if (kayitlar.empty() && baglam != "genel") kayitlar = topla("genel"); std::vector sonuc; // 1) SON kullanılanlar: sira (damga) büyükten küçüğe. std::sort(kayitlar.begin(), kayitlar.end(), [](const auto& a, const auto& b) { return std::get<0>(a) > std::get<0>(b); }); for (const auto& k : kayitlar) { if ((int)sonuc.size() >= maksSon) break; sonuc.push_back({std::get<2>(k), std::get<3>(k), true}); } // 2) SIK kullanılanlar: sayı büyükten küçüğe, MRU'da geçenler hariç. std::sort(kayitlar.begin(), kayitlar.end(), [](const auto& a, const auto& b) { return std::get<1>(a) > std::get<1>(b); }); int sik = 0; for (const auto& k : kayitlar) { if (sik >= maksSik) break; const std::string& g = std::get<2>(k); const std::string& a = std::get<3>(k); bool var = false; for (const OgrenilenKomut& o : sonuc) if (o.grup == g && o.ad == a) { var = true; break; } if (var) continue; sonuc.push_back({g, a, false}); ++sik; } return sonuc; } bool komutSonCalisan(std::string& grup, std::string& ad) { if (!menuOgrenmeAktif()) return false; const auto& tablo = kullanimTablo(); const std::string* enSonAnahtar = nullptr; ::fiko::u64 enBuyuk = 0; bool var = false; for (const auto& [anahtar, kul] : tablo) if (!var || kul.sira > enBuyuk) { enBuyuk = kul.sira; enSonAnahtar = &anahtar; var = true; } if (!var) return false; // Anahtar: "baglam\x1fgrup|ad" → bağlamı at, grup/ad'ı ayır. const std::size_t ayrac = enSonAnahtar->find('\x1f'); if (ayrac == std::string::npos) return false; const std::string kalan = enSonAnahtar->substr(ayrac + 1); const std::size_t bar = kalan.find('|'); if (bar == std::string::npos) return false; grup = kalan.substr(0, bar); ad = kalan.substr(bar + 1); return true; } void aramaMenuAc(AramaMenuDurum& d, ImVec2 konum, std::vector ogeler) { d.acik = true; d.konum = konum; d.ogeler = std::move(ogeler); d.filtre[0] = '\0'; // İlk ETKİN öğeye konumlan: liste soluk başlıkla (ör. DM "3 yüz seçili" özeti) // başlayabilir → secili=0 kalsaydı açılıştaki ilk Enter ölü olurdu. // DİKKAT: secili normalde `gorunur` dizisini indeksler (bkz. aramaMenuCiz), ogeler'i // değil. Burada geçerli çünkü filtre AZ ÖNCE temizlendi → gorunur = 0,1,2… (özdeş). d.secili = 0; for (std::size_t i = 0; i < d.ogeler.size(); ++i) if (d.ogeler[i].etkin) { d.secili = (int)i; break; } d.yeniAcildi = true; d.ustPanel = nullptr; // bant/kapanış açılıştan sonra çağıran tarafından bağlanır d.ustPanelYuk = 0.0f; d.kapatIstegi = false; d.kapanis = nullptr; } void aramaMenuCiz(AramaMenuDurum& d, ImGuiIO& io) { if (!d.acik) return; const Tema& t = tema(); // Açılış fade+scale pop (kullanıcı: bol animasyon). Kimlik sabit — tek örnek. static const ImGuiID animId = ImHashStr("##dm_arama_menu_anim"); if (d.yeniAcildi) animSifirla(animId, 0.0f); const float a = animf(animId, 1.0f, 18.0f); const float olcek = 0.90f + 0.10f * a; // ---- Filtrelenmiş görünür indeksler (kapasite korunur → sıcak yolda tahsis yok) ---- std::vector& gorunur = d.gorunurTmp; gorunur.clear(); for (int i = 0; i < (int)d.ogeler.size(); ++i) if (ogeEslesir(d.ogeler[i], d.filtre)) gorunur.push_back(i); const int gn = (int)gorunur.size(); if (d.secili >= gn) d.secili = gn > 0 ? gn - 1 : 0; if (d.secili < 0) d.secili = 0; const int gosterilen = gn < MAKS_GORUNEN ? gn : MAKS_GORUNEN; const float listeYuk = (float)(gosterilen > 0 ? gosterilen : 1) * SATIR; // Üst bant (grafik özellik satırı) yalnız filtre BOŞKEN görünür; yazınca kaybolur. const float ustYuk = (d.ustPanel && d.filtre[0] == '\0') ? d.ustPanelYuk : 0.0f; const float yuk = (BASLIK + ustYuk + listeYuk + 8.0f) * olcek; const float gen = GEN * olcek; // ---- Konum: imleç sağ-altı, viewport'a kelepçele ---- ImGuiViewport* vp = ImGui::GetMainViewport(); if (ImGuiViewport* v = ImGui::FindViewportByID(io.MouseHoveredViewport)) vp = v; const float vMinX = vp->Pos.x + 4.0f, vMinY = vp->Pos.y + 4.0f; const float vMaxX = vp->Pos.x + vp->Size.x - 4.0f, vMaxY = vp->Pos.y + vp->Size.y - 4.0f; float px = d.konum.x + 12.0f, py = d.konum.y + 8.0f; if (px + gen > vMaxX) px = d.konum.x - 12.0f - gen; if (px < vMinX) px = vMinX; if (py + yuk > vMaxY) py = vMaxY - yuk; if (py < vMinY) py = vMinY; const ImVec2 winMin{px, py}, winMax{px + gen, py + yuk}; // ---- Şeffaf zeminli overlay pencere (kendi OS penceresi yok) ---- ImGui::SetNextWindowViewport(vp->ID); ImGui::SetNextWindowPos(winMin); ImGui::SetNextWindowSize({gen, yuk}); ImGui::SetNextWindowBgAlpha(0.0f); if (d.yeniAcildi) ImGui::SetNextWindowFocus(); ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f); ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f); ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0)); ImGui::Begin("##dm_arama_menu", nullptr, ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoDocking | ImGuiWindowFlags_NoScrollWithMouse | ImGuiWindowFlags_NoFocusOnAppearing); ImGui::PopStyleVar(3); ImGui::BringWindowToDisplayFront(ImGui::GetCurrentWindow()); ImDrawList* dl = ImGui::GetWindowDrawList(); // Zemin: glow çerçeve + panel + ince cyan kenar (radyal pill ile aynı dil). glowCerceve(dl, winMin, winMax, t.vurgu, t.yuvarlak, 0.5f * a); dl->AddRectFilled(winMin, winMax, ui::u32(t.panel, 0.97f * a), t.yuvarlak); // Merkezî pencere ızgarası (Fikri: sağ menü de gridli — adım/göster Ayarlar → Pencere, // webSkin ile AYNI kaynak; menü fade'i alfa çarpanıyla). ui::webSkinGridCiz(dl, winMin, winMax, a); dl->AddRect(winMin, winMax, ui::u32(t.vurgu, 0.45f * a), t.yuvarlak, 0, 1.2f); int calistir = -1; // bu kare seçilen orijinal öğe indeksi bool kapat = false; // ---- Arama kutusu ---- ImGui::SetCursorScreenPos({winMin.x + 8.0f, winMin.y + 6.0f}); ImGui::SetNextItemWidth(gen - 16.0f); ImGui::PushStyleColor(ImGuiCol_FrameBg, ui::u32(karisim(t.panel, t.vurgu, 0.08f), a)); ImGui::PushStyleColor(ImGuiCol_Text, ui::u32(t.metin, a)); if (d.yeniAcildi) ImGui::SetKeyboardFocusHere(); ImGui::InputTextWithHint("##dmara", "🔍 Ara…", d.filtre, sizeof(d.filtre)); const bool kutuAktif = ImGui::IsItemActive(); const ImGuiID filtreId = ImGui::GetItemID(); ImGui::PopStyleColor(2); // ---- Üst bant: filtre boşken kutunun hemen altında kompakt özellik satırı (DM) ---- if (ustYuk > 0.0f) { ImGui::SetCursorScreenPos({winMin.x + 8.0f, winMin.y + BASLIK * olcek}); // SetCursorScreenPos pencere sınırını "uzatır" ama tek başına BÜYÜTMEZ; ustPanel() // hiçbir öğe göndermezse (ör. salt-okunur lisans kipinde grafik özellik bandı // kendini çizmez) ImGui "submit an item e.g. Dummy()" uyarısı basar. Sıfır boyutlu // Dummy sınırı geçerli kılar ve görsel olarak hiçbir şeyi değiştirmez. ImGui::Dummy(ImVec2(0.0f, 0.0f)); d.ustPanel(); if (d.kapatIstegi) { d.kapatIstegi = false; kapat = true; } // bant: içerik bayat → kapat const float ayY = winMin.y + (BASLIK + ustYuk) * olcek - 2.0f; dl->AddLine({winMin.x + 6.0f, ayY}, {winMax.x - 6.0f, ayY}, ui::u32(t.vurgu, 0.25f * a)); } // ---- Klavye gezinme (kutu odaktayken de çalışır — tek-satır ↑↓ boşta) ---- // Bant widget'ı (KYA kutusu/alfa barı — filtre kutusu DEĞİL) bu VEYA ÖNCEKİ kare aktifse // bastır: InputText Enter'ı aynı karede deaktive eder (ActiveId=0 düşer), yalnız // IsAnyItemActive bakmak Enter karesini kaçırır → ActiveIdPreviousFrame ile yakala. // Filtre kutusu muaf (Enter=çalıştır eski davranışı birebir korunur). ImGuiContext& gctx = *GImGui; const bool bantMesgul = (gctx.ActiveId != 0 && gctx.ActiveId != filtreId) || (gctx.ActiveIdPreviousFrame != 0 && gctx.ActiveIdPreviousFrame != filtreId); if (gn > 0 && !bantMesgul) { if (ImGui::IsKeyPressed(ImGuiKey_DownArrow)) d.secili = (d.secili + 1) % gn; if (ImGui::IsKeyPressed(ImGuiKey_UpArrow)) d.secili = (d.secili - 1 + gn) % gn; if (ImGui::IsKeyPressed(ImGuiKey_Enter) || ImGui::IsKeyPressed(ImGuiKey_KeypadEnter)) { const AramaMenuOge& o = d.ogeler[gorunur[d.secili]]; if (o.etkin) calistir = gorunur[d.secili]; } } // Açık popup (ör. banttaki renk seçici) varken Esc/dış-tık menüyü kapatmasın — // önce popup kapanır, ikinci basış/tık menüyü kapatır. Bant girişi yazılırken // Esc yalnız girişten çıkar (bantMesgul), menüyü kapatmaz. const bool popupAcik = ImGui::IsPopupOpen("", ImGuiPopupFlags_AnyPopupId | ImGuiPopupFlags_AnyPopupLevel); if (!popupAcik && !bantMesgul && ImGui::IsKeyPressed(ImGuiKey_Escape)) kapat = true; // ---- Liste ---- const float listeY0 = winMin.y + (BASLIK + ustYuk) * olcek; const int bas = (d.secili >= MAKS_GORUNEN) ? (d.secili - MAKS_GORUNEN + 1) : 0; // kaydırma const ImVec2 fare = io.MousePos; for (int r = 0; r < gosterilen; ++r) { const int gi = bas + r; if (gi >= gn) break; const AramaMenuOge& o = d.ogeler[gorunur[gi]]; const ImVec2 sMin{winMin.x + 4.0f, listeY0 + (float)r * SATIR}; const ImVec2 sMax{winMax.x - 4.0f, sMin.y + SATIR - 1.0f}; const bool fareUst = fare.x >= sMin.x && fare.x <= sMax.x && fare.y >= sMin.y && fare.y <= sMax.y && o.etkin; if (fareUst) d.secili = gi; // hover = seçim (klavye ile birleşik) const bool vurgu = (gi == d.secili) && o.etkin; if (vurgu) { dl->AddRectFilled(sMin, sMax, ui::u32(karisim(t.panel, t.vurgu, 0.28f), a), t.yuvarlak * 0.5f); dl->AddLine({sMin.x + 1.0f, sMin.y + 2.0f}, {sMin.x + 1.0f, sMax.y - 2.0f}, ui::u32(t.vurgu, 0.95f * a), 2.0f); // sol vurgu şeridi } const ImU32 mRenk = ui::u32(o.etkin ? (vurgu ? t.metin : karisim(t.metinSoluk, t.metin, 0.5f)) : t.metinSoluk, a * (o.etkin ? 1.0f : 0.55f)); const float orta = (sMin.y + sMax.y) * 0.5f; if (!o.simge.empty()) simgeCiz(dl, ui::Simge(o.simge.c_str()), {sMin.x + 15.0f, orta}, 15.0f * olcek, mRenk); ImFont* f = ImGui::GetFont(); const float fs = 14.0f * olcek; dl->AddText(f, fs, {sMin.x + 30.0f, orta - fs * 0.5f}, mRenk, o.etiket.c_str()); if (!o.ipucu.empty()) { // sağda soluk ipucu (kısayol/açıklama) const ImVec2 ib = f->CalcTextSizeA(fs * 0.85f, FLT_MAX, 0.0f, o.ipucu.c_str()); dl->AddText(f, fs * 0.85f, {sMax.x - ib.x - 8.0f, orta - fs * 0.42f}, ui::u32(t.metinSoluk, 0.7f * a), o.ipucu.c_str()); } if (fareUst && ImGui::IsMouseClicked(ImGuiMouseButton_Left)) calistir = gorunur[gi]; } if (gn == 0) { // hiç eşleşme ImFont* f = ImGui::GetFont(); dl->AddText(f, 13.0f * olcek, {winMin.x + 14.0f, listeY0 + 5.0f}, ui::u32(t.metinSoluk, a), "eşleşme yok"); } // Pencerenin TAMAMEN dışına sol/sağ tık → kapat (InputText yakalamaz; popup açıkken değil). if (!popupAcik && (ImGui::IsMouseClicked(ImGuiMouseButton_Left) || ImGui::IsMouseClicked(ImGuiMouseButton_Right)) && (fare.x < winMin.x || fare.x > winMax.x || fare.y < winMin.y || fare.y > winMax.y)) kapat = true; ImGui::End(); (void)kutuAktif; d.yeniAcildi = false; // Kapanışta bant oturumlarını kesinleştir (renk/alfa undo kaybolmasın) — komuttan ÖNCE. const auto kapanisiYap = [&d] { d.ustPanel = nullptr; if (d.kapanis) { auto k = std::move(d.kapanis); d.kapanis = nullptr; k(); } }; // Eylem: pencere kapatıldıktan SONRA çalıştır (komut kendi penceresini açabilir). if (calistir >= 0) { AramaMenuOge oge = d.ogeler[calistir]; // kopya (calistir listeyi değiştirebilir) d.acik = false; d.ogeler.clear(); kapanisiYap(); if (oge.calistir) oge.calistir(); return; } if (kapat) { d.acik = false; d.ogeler.clear(); kapanisiYap(); } } } // namespace fiko