#include "fiko/ui/agac_gorunum.hpp" #include #include #include #include #include #include #include "fiko/ui/animasyon.hpp" #include "fiko/ui/tema.hpp" #include "imgui_internal.h" namespace fiko::ui { namespace { // Açık düğümleri derinlik-öncelikli sırayla düz listeye toplar. `yol`: kökten bu // düğüme kadar her seviyede "son kardeş miydi" — CATIA kılavuz çizgilerinin ara // seviyelerde doğru kesilmesi için (son-kardeş atanın altında dikey çizgi çizilmez). void duzlestir(const std::vector& dugumler, const std::unordered_map& acik, int derinlik, std::vector& yol, std::vector& cikti) { const int n = (int)dugumler.size(); for (int i = 0; i < n; ++i) { const AgacDugum& d = dugumler[i]; const bool son = i == n - 1; yol.push_back(son); cikti.push_back({&d, derinlik, son, yol}); // yol'un anlık kopyası satırla taşınır const bool haveCocuk = !d.cocuklar.empty(); if (haveCocuk) { auto it = acik.find(d.id); // Kullanıcı override'ı (acikDurum_) varsa o; yoksa düğümün varsayılanı // (feature → varsayılanKapali=true: sonuç düğümü nest'i ilk görünümde gizli). const bool aciksa = it == acik.end() ? !d.varsayilanKapali : it->second; if (aciksa) duzlestir(d.cocuklar, acik, derinlik + 1, yol, cikti); } yol.pop_back(); } } // ASCII büyük/küçük duyarsız alt-dize konumu (yoksa npos) — filtre vurgusu için. std::size_t altDizeBul(const std::string& metin, const std::string& aranan) { if (aranan.empty() || metin.size() < aranan.size()) return std::string::npos; for (std::size_t i = 0; i + aranan.size() <= metin.size(); ++i) { std::size_t j = 0; while (j < aranan.size() && std::tolower((unsigned char)metin[i + j]) == std::tolower((unsigned char)aranan[j])) ++j; if (j == aranan.size()) return i; } return std::string::npos; } // Tip-bazlı simge rengi (CATIA renk dili): her arketip belirgin bir tonla çizilir // → kullanıcı göz taramasıyla aradığı öğeyi (eskiz yeşil, katı mavi, feature turuncu…) // hızlı bulur. Sabit tonlar (tema-bağımsız) — "bulunabilirlik" için kasıtlı. ImVec4 arketipIkonRenk(const Tema& t, Arketip a) { switch (a) { case Arketip::Govde: return {0.42f, 0.68f, 1.00f, 1}; // mavi — katı gövde case Arketip::Sketch: return {0.42f, 0.90f, 0.52f, 1}; // yeşil — eskiz case Arketip::Feature: return {1.00f, 0.66f, 0.28f, 1}; // turuncu — feature case Arketip::Datum: return {0.60f, 0.80f, 0.72f, 1}; // soluk yeşil-gri — referans case Arketip::Tel: return {0.28f, 0.86f, 0.86f, 1}; // teal — wireframe case Arketip::Yuzey: return {0.80f, 0.86f, 0.34f, 1}; // lime — açık yüzey case Arketip::CamOp: return {1.00f, 0.46f, 0.34f, 1}; // kırmızı-turuncu — işleme op case Arketip::CamTakim: return {0.72f, 0.78f, 0.84f, 1}; // çelik — takım case Arketip::Analiz: return {0.80f, 0.50f, 1.00f, 1}; // mor — analiz case Arketip::Isik: return {1.00f, 0.85f, 0.35f, 1}; // sarı — ışık case Arketip::Konum: return {0.55f, 0.80f, 1.00f, 1}; // açık mavi — görünüm case Arketip::Olcum: return {0.35f, 0.82f, 0.96f, 1}; // cyan — ölçüm case Arketip::Malzeme: return {0.86f, 0.68f, 0.44f, 1}; // tan — malzeme case Arketip::Grup: return {1.00f, 0.80f, 0.35f, 1}; // altın — klasör / gövde kabı case Arketip::Parametre: return {0.95f, 0.55f, 0.95f, 1}; // magenta — ƒ(x) case Arketip::AltDetay: return t.metinSoluk; default: return karisim(t.metinSoluk, t.metin, 0.8f); // Genel } } // Kapsül rozet (badge) çizer; sağ-kenar X'ini döner. renk=0 → tema.vurgu. float rozetCiz(ImDrawList* dl, const Tema& t, const AgacRozet& rz, ImVec2 solOrta, float alfaCarpan) { const bool glifVar = rz.glif && rz.glif[0]; const float tw = rz.metin.empty() ? 0.0f : ImGui::CalcTextSize(rz.metin.c_str()).x; const float ic = (glifVar ? 13.0f : 0.0f) + tw + (glifVar && tw > 0 ? 4.0f : 0.0f); const float pad = 5.0f; const float ph = ImGui::GetTextLineHeight() * 0.74f; const ImVec2 a{solOrta.x, solOrta.y - ph * 0.5f}; const ImVec2 b{solOrta.x + ic + pad * 2.0f, solOrta.y + ph * 0.5f}; const ImU32 rc = rz.renk ? (ImU32)rz.renk : u32(t.vurgu); const ImU32 rgb = rc & 0x00FFFFFFu; const float af = alfaCarpan; dl->AddRectFilled(a, b, rgb | ((ImU32)(58.0f * af) << 24), t.yuvarlak * 0.6f); dl->AddRect(a, b, rgb | ((ImU32)(150.0f * af) << 24), t.yuvarlak * 0.6f, 0, 1.0f); float x = a.x + pad; if (glifVar) { simgeCiz(dl, Simge(rz.glif), {x + 6.0f, solOrta.y}, 11.0f, rgb | ((ImU32)(255.0f * af) << 24)); x += 13.0f; } if (!rz.metin.empty()) dl->AddText({x, solOrta.y - ImGui::GetTextLineHeight() * 0.5f}, rgb | ((ImU32)(235.0f * af) << 24), rz.metin.c_str()); return b.x; } // CATIA [+]/[−] genişlet kutusu ÇİZİMİ (kılavuz çizgisi üstünde). Etkileşim (tık→ // aç/kapat) caller'da kalır → sunum/etkileşim ayrımı. void genisletKutusuCiz(ImDrawList* dl, const Tema& t, ImVec2 merkez, bool aciksa, bool hover) { const float kt = 4.5f; // kutu yarı-boyutu const ImU32 kbg = u32(t.panel, 0.96f); const ImU32 kkenar = u32(hover ? t.vurgu : t.metinSoluk, 0.95f); const ImU32 kisaret = u32(hover ? t.vurgu : t.metin, 0.95f); const ImVec2 ka{merkez.x - kt, merkez.y - kt}, kb{merkez.x + kt, merkez.y + kt}; dl->AddRectFilled(ka, kb, kbg, 1.5f); dl->AddRect(ka, kb, kkenar, 1.5f, 0, 1.0f); dl->AddLine({ka.x + 2.0f, merkez.y}, {kb.x - 2.0f, merkez.y}, kisaret, 1.3f); // − if (!aciksa) // kapalı → + (dikey çizgi ekle) dl->AddLine({merkez.x, ka.y + 2.0f}, {merkez.x, kb.y - 2.0f}, kisaret, 1.3f); } // Görünürlük göz simgesi ÇİZİMİ (badem+iris açık / çizik kapalı). Etkileşim caller'da. void gozCiz(ImDrawList* dl, const Tema& t, ImVec2 merkez, bool gorunur, bool hover) { const ImU32 gr = u32(gorunur ? (hover ? t.vurgu : t.metin) : t.metinSoluk, gorunur ? 1.0f : 0.6f); dl->AddCircle(merkez, 6.0f, gr, 12, 1.3f); if (gorunur) dl->AddCircleFilled(merkez, 2.4f, gr); else dl->AddLine({merkez.x - 6, merkez.y + 6}, {merkez.x + 6, merkez.y - 6}, gr, 1.3f); } // Satır zemini ÇİZİMİ: seçili → neon gradient şerit + sol vurgu çubuğu; hover → soluk. void satirZeminCiz(ImDrawList* dl, const Tema& t, ImVec2 satMin, ImVec2 satMax, ImVec2 dolguMax, float sa, float h) { if (sa > 0.01f) { const int v0 = dl->VtxBuffer.Size; dl->AddRectFilled(satMin, dolguMax, u32(karisim(t.panel, t.vurgu, 0.30f * sa)), t.yuvarlak * 0.5f); ImGui::ShadeVertsLinearColorGradientKeepAlpha(dl, v0, dl->VtxBuffer.Size, satMin, dolguMax, u32(t.vurgu, 0.28f * sa), u32(t.vurgu2, 0.10f * sa)); dl->AddRectFilled(satMin, {satMin.x + 2.5f, satMax.y}, u32(t.vurgu, 0.9f * sa)); } else if (h > 0.01f) { dl->AddRectFilled(satMin, dolguMax, u32(t.metin, 0.05f * h), t.yuvarlak * 0.5f); } } // CATIA hiyerarşi kılavuz çizgileri (├ ─ └). Ara seviyede dikey YALNIZ ata sürüyorsa // (yol[lv]); yoksa son-çocukların altında sarkan yanlış çizgiler oluşurdu. void kilavuzCizgileriCiz(ImDrawList* dl, const Tema& t, const DuzSatir& s, ImVec2 satMin, ImVec2 satMax, float orta, float gozGen, float girinti, float okBoyut) { if (s.derinlik <= 0) return; const ImU32 cizgi = u32(t.kenar, 0.55f); for (int lv = 1; lv <= s.derinlik; ++lv) { const float lx = satMin.x + gozGen + (lv - 1) * girinti + okBoyut; if (lv == s.derinlik) { const float alt = s.sonKardes ? orta : satMax.y; // └/├ köşe dl->AddLine({lx, satMin.y}, {lx, alt}, cizgi, 1.0f); dl->AddLine({lx, orta}, {lx + girinti - okBoyut, orta}, cizgi, 1.0f); } else if (lv < (int)s.yol.size() && !s.yol[lv]) { dl->AddLine({lx, satMin.y}, {lx, satMax.y}, cizgi, 1.0f); // ata sürüyor → dikey geç } } } } // namespace namespace { // Hedefin atalarını aç (kaydir: kapalı dal içindeki düğüm görünür olsun). bool atalariAc(const std::vector& v, std::uint64_t hedef, std::unordered_map& acik) { for (const AgacDugum& d : v) { if (d.id == hedef) return true; if (!d.cocuklar.empty() && atalariAc(d.cocuklar, hedef, acik)) { acik[d.id] = true; return true; } } return false; } } // namespace void AgacGorunum::secimTemizle() { secili_.clear(); seciliKume_.clear(); } void AgacGorunum::sec(std::uint64_t id, bool coklu) { if (!coklu) { secili_.clear(); seciliKume_.clear(); } auto it = std::find(secili_.begin(), secili_.end(), id); if (it == secili_.end()) { secili_.push_back(id); seciliKume_.insert(id); } else if (coklu) { secili_.erase(it); seciliKume_.erase(id); } // ctrl-tık: seçiliyi düşür (toggle) sonSecim_ = id; } void AgacGorunum::secTopluAyarla(const std::vector& idler) { secili_ = idler; // benzersiz olduğu garanti (caller s_harita anahtarları) → find gereksiz seciliKume_.clear(); seciliKume_.reserve(idler.size()); for (std::uint64_t id : idler) seciliKume_.insert(id); } bool AgacGorunum::seciliMi(std::uint64_t id) const { return seciliKume_.count(id) != 0; } void AgacGorunum::hepsiniAc(bool ac) { hepsiniAcKomut_ = ac ? 1 : 2; } void AgacGorunum::altAgaciAc(std::uint64_t id, bool ac) { // Hedef düğümü bul, sonra o düğüm + tüm torunlarının açık durumunu ata. std::function isaretle = [&](const AgacDugum& d) { if (!d.cocuklar.empty()) { acikDurum_[d.id] = ac; for (const auto& c : d.cocuklar) isaretle(c); } }; std::function&)> bul = [&](const std::vector& v) -> bool { for (const auto& d : v) { if (d.id == id) { acikDurum_[d.id] = ac; isaretle(d); return true; } if (bul(d.cocuklar)) return true; } return false; }; bul(kokler); duzKirli_ = true; // görünür satır kümesi değişti → yeniden düzleştir } bool AgacGorunum::acikMi(std::uint64_t id) const { auto it = acikDurum_.find(id); return it == acikDurum_.end() ? true : it->second; // varsayılan açık } void AgacGorunum::kaydir(std::uint64_t id) { kaydirId_ = id; } void AgacGorunum::renameBaslat(std::uint64_t id, const char* mevcutAd) { rename_ = id; renameOdak_ = true; std::strncpy(renameTampon_, mevcutAd ? mevcutAd : "", sizeof(renameTampon_) - 1); renameTampon_[sizeof(renameTampon_) - 1] = 0; } AgacOlaylari AgacGorunum::ciz(const char* kimlik) { AgacOlaylari olay; const Tema& t = tema(); // "Hepsini aç/kapat" komutu: düzleştirmeden önce tüm bilinen id'leri işaretle. if (hepsiniAcKomut_) { const bool ac = hepsiniAcKomut_ == 1; std::function&)> gez = [&](const std::vector& v) { for (auto& d : v) if (!d.cocuklar.empty()) { acikDurum_[d.id] = ac; gez(d.cocuklar); } }; gez(kokler); hepsiniAcKomut_ = 0; duzKirli_ = true; // açıklık toptan değişti → yeniden düzleştir } // Kaydırma hedefi: kapalı ataları AÇ (düzleştirmede satır görünsün). if (kaydirId_) { atalariAc(kokler, kaydirId_, acikDurum_); duzKirli_ = true; } // Ağacı görünür satırlara düzleştir — PERF: yalnız içerik/açıklık değişince // (yenile() VEYA ok toggle VEYA hepsiniAc). satirCache_ üye buffer'da tutulur; // DuzSatir.d işaretçileri kokler yeniden kurulana dek geçerli (caller yenile() der). if (duzKirli_) { satirCache_.clear(); satirCache_.reserve(64); std::vector yol; duzlestir(kokler, acikDurum_, 0, yol, satirCache_); duzKirli_ = false; } std::vector& satirlar = satirCache_; // Sahne-içi (P2) zoom: yaziOlcek satır/girinti geometrisini ölçekler; metin ise iç // child'a SetWindowFontScale ile büyür (aşağıda). yaziOlcek=1 → hiçbir değişiklik. const float olc = yaziOlcek > 0.01f ? yaziOlcek : 1.0f; const float satirY = (satirYuksekligi > 0 ? satirYuksekligi : ImGui::GetTextLineHeight() + ImGui::GetStyle().FramePadding.y * 2.0f) * olc; const float girinti = 16.0f * olc; // seviye başına const float okBoyut = 9.0f * olc; // üçgen genişlik/2 const float gozGen = (gorunurlukSutunu ? 22.0f : 0.0f) * olc; ImGui::PushID(kimlik); // ---- Sol kaydırma çubuğu: içerik kendi kaydırmalı child'ında (built-in bar // gizli); dikey scrollbar sol oluğa ScrollbarEx ile çizilir. Hem pencere hem // 3B-overlay modunda çalışır (dış container'a dokunmadan). ---- const float sbW = solScrollbar ? ImGui::GetStyle().ScrollbarSize : 0.0f; const ImVec2 disMin = ImGui::GetCursorScreenPos(); const ImVec2 disAvail = ImGui::GetContentRegionAvail(); if (sbW > 0.0f) { ImGui::SetCursorScreenPos({disMin.x + sbW, disMin.y}); ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4{0, 0, 0, 0}); ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2{0, 0}); ImGui::BeginChild("##agacIcerik", {disAvail.x - sbW, disAvail.y}, false, ImGuiWindowFlags_NoScrollbar); if (olc != 1.0f) ImGui::SetWindowFontScale(olc); // sahne-içi zoom: metin ölçeği if (scrollUygula_) { ImGui::SetScrollY(yeniScroll_); scrollUygula_ = false; } if (panBasa_) { ImGui::SetScrollY(0.0f); panBasa_ = false; panDikey_ = 0.0f; } if (panDikey_ != 0.0f) { ImGui::SetScrollY(ImGui::GetScrollY() + panDikey_); panDikey_ = 0.0f; } } ImDrawList* dl = ImGui::GetWindowDrawList(); const float x0 = ImGui::GetCursorScreenPos().x; const float tamGen = ImGui::GetContentRegionAvail().x; // Kaydırma hedefini uygula: satır indeksi → pencere ortasına getir. if (kaydirId_) { for (int r = 0; r < (int)satirlar.size(); ++r) if (satirlar[r].d->id == kaydirId_) { ImGui::SetScrollY(std::max(0.0f, r * satirY - ImGui::GetWindowHeight() * 0.5f)); break; } kaydirId_ = 0; } rollbackAday_ = 0; // rollback sürükleme adayı (bu kare imleç-altı feature) rollbackSonAlt_ = 0.0f; // en alttaki feature'ın alt-y'si (bu kare) ImGuiListClipper clipper; clipper.Begin((int)satirlar.size(), satirY); while (clipper.Step()) { for (int r = clipper.DisplayStart; r < clipper.DisplayEnd; ++r) { const DuzSatir& s = satirlar[r]; const AgacDugum& d = *s.d; const bool cocukVar = !d.cocuklar.empty(); const bool secili = seciliMi(d.id); const ImVec2 satMin = ImGui::GetCursorScreenPos(); const ImVec2 satMax{satMin.x + tamGen, satMin.y + satirY}; const float orta = satMin.y + satirY * 0.5f; // CATIA tık-geçişi: fare bu satırın (tam-genişlik) dikdörtgeni üzerinde mi. // Geometrik test (clip=pencere) → boş alan/satır-arası ayrımı; overlay modu // yalnız satır üstünde 3B'yi bastırır (main.cpp vpHover). if (ImGui::IsMouseHoveringRect(satMin, satMax)) olay.fareSatirUstunde = true; // Rollback sürükleme: imleç-altı feature satırını aday yap + alt kenarına bırakış // önizleme çizgisi. rollbackSonAlt_ sıralı en-alt feature'ın altını izler (iptal ayrımı). if (rollbackSuruk_ && d.arketip == Arketip::Feature) { rollbackSonAlt_ = satMax.y; const float my = ImGui::GetMousePos().y; if (my >= satMin.y && my < satMax.y) { rollbackAday_ = d.id; dl->AddLine({satMin.x + 2.0f, satMax.y - 1.0f}, {satMax.x - 2.0f, satMax.y - 1.0f}, IM_COL32(255, 170, 60, 255), 2.5f); } } const float ic = satMin.x + gozGen + s.derinlik * girinti; // içerik sol kenarı (ok) // TAM 64-bit id ile benzersiz scope (eski 32-bit katlama + r → ImGui "same id" // çakışması yapabiliyordu; pointer formu x64'te 64 bitin tamamını kullanır). ImGui::PushID(reinterpret_cast(static_cast(d.id))); // --- tüm satır: seçim/hover hedefi --- // AllowOverlap: satır tam-genişlik buton; üstündeki ok/göz/rename/hızlı-eylem // öğeleri (SONRA submit edilir) tıklanabilsin (yoksa satır butonu hepsini yutar // → ok daralt/genişlet ve rename çalışmaz). ImGui::SetCursorScreenPos(satMin); ImGui::SetNextItemAllowOverlap(); const bool tiklandi = ImGui::InvisibleButton("##sat", {tamGen, satirY}); const ImGuiID satId = ImGui::GetItemID(); const bool hover = ImGui::IsItemHovered(); // --- Sürükle-bırak: satırı kaynak yap + üzerine bırakmayı hedefle. Hedef // satırın hangi bölgesine bırakıldığına göre niyet: üst %30 → öncesine, // alt %30 → sonrasına, orta → içine (reparent). İndikatör (çizgi/kutu) DrawList // ile; gerçek ust/sira yazımı + döngü koruması caller'da (olay olarak döner). --- if (ImGui::BeginDragDropSource(ImGuiDragDropFlags_SourceNoHoldToOpenOthers)) { ImGui::SetDragDropPayload("FIKO_AGAC_DUGUM", &d.id, sizeof(d.id)); ImGui::TextUnformatted(d.etiket.c_str()); // sürükleme önizlemesi ImGui::EndDragDropSource(); } if (ImGui::BeginDragDropTarget()) { const float oran = (ImGui::GetMousePos().y - satMin.y) / satirY; const int konum = oran < 0.30f ? -1 : (oran > 0.70f ? 1 : 0); if (konum == 0) // içine → satır çerçevesi vurgusu dl->AddRect(satMin, {satMax.x, satMax.y}, u32(t.vurgu, 0.95f), t.yuvarlak * 0.5f, 0, 2.0f); else { // önce/sonra → kenar çizgisi + tutamak noktası const float ly = konum < 0 ? satMin.y : satMax.y; dl->AddLine({satMin.x, ly}, {satMax.x, ly}, u32(t.vurgu), 2.5f); dl->AddCircleFilled({satMin.x + 3.0f, ly}, 3.5f, u32(t.vurgu)); } if (const ImGuiPayload* p = ImGui::AcceptDragDropPayload("FIKO_AGAC_DUGUM")) { olay.surukleBirak = true; olay.surukleKaynak = *reinterpret_cast(p->Data); olay.surukleHedef = d.id; olay.surukleKonum = konum; } ImGui::EndDragDropTarget(); } // Harici hover (sahne→ağaç): fare bu satırda değilken de hover-stili göster. const bool disHov = (disHover != 0 && d.id == disHover); const float h = animf(satId, (hover || disHov) ? 1.0f : 0.0f, 16.0f); const float sa = animf(satId + 1, secili ? 1.0f : 0.0f, 16.0f); // Vurgu dolgusu YAZI-genişliğinde (tam satır değil): metnin bittiği yere kadar // (ok/çip/ikon dahil), CATIA saydam yüzen ağaçta metni sarar. Hit-test (InvisibleButton, // IsMouseHoveringRect, fareSatirUstunde) tam satır kalır → yalnız GÖRSEL dolgu daralır. float metinBasX = ic + okBoyut * 2 + 2; // etiket başlangıcı (bkz. cx, aşağıda) if (d.renk.w > 0.01f) metinBasX += 15; // tip renk çipi if (!d.simge.bosMu()) metinBasX += 18; // tip ikonu float dolguSagX = metinBasX + ImGui::CalcTextSize(d.etiket.c_str()).x + 6.0f; if (dolguSagX > satMax.x) dolguSagX = satMax.x; const ImVec2 dolguMax{dolguSagX, satMax.y}; satirZeminCiz(dl, t, satMin, satMax, dolguMax, sa, h); // seçim/hover zemini // --- bağlantı (girinti) çizgileri (CATIA kılavuz: ├ ─ └) --- if (baglantiCizgileri) kilavuzCizgileriCiz(dl, t, s, satMin, satMax, orta, gozGen, girinti, okBoyut); // --- genişlet/daralt [+]/[−] kutusu (CATIA tarzı, kılavuz çizgisi üstünde) --- if (cocukVar) { auto it = acikDurum_.find(d.id); const bool aciksa = it == acikDurum_.end() ? !d.varsayilanKapali : it->second; const ImVec2 okMerkez{ic + okBoyut, orta}; ImGui::SetCursorScreenPos({ic, orta - okBoyut}); if (ImGui::InvisibleButton("##ok", {okBoyut * 2, okBoyut * 2})) { acikDurum_[d.id] = !aciksa; duzKirli_ = true; // dal aç/kapat → görünür satır kümesi değişti } genisletKutusuCiz(dl, t, okMerkez, aciksa, ImGui::IsItemHovered()); } float cx = ic + (cocukVar ? okBoyut * 2 + 2 : okBoyut * 2 + 2); // --- tip renk çipi --- if (d.renk.w > 0.01f) { dl->AddRectFilled({cx, orta - 5}, {cx + 10, orta + 5}, u32(d.renk), 2.0f); dl->AddRect({cx, orta - 5}, {cx + 10, orta + 5}, u32(t.kenar, 0.8f), 2.0f); cx += 15; } // --- tip ikonu (renkli: arketip tonu + durum override) --- if (!d.simge.bosMu()) { ImVec4 ir = arketipIkonRenk(t, d.arketip); if (d.durum == Durum::Hata) ir = t.hata; // hatalı → kırmızı else if (d.durum == Durum::Bastik) ir = t.metinSoluk; // bastırılmış → soluk else if (secili) ir = karisim(ir, t.metin, 0.35f); // seçili → tona doğru parlat const float ia = (d.gorunur ? 1.0f : 0.5f) * (d.soluk ? 0.7f : 1.0f); simgeCiz(dl, d.simge, {cx + 7, orta}, 14.0f, u32(ir, ia)); cx += 18; } // --- etiket (VEYA inline rename input) --- if (renameEtkin && rename_ == d.id) { ImGui::SetCursorScreenPos({cx, satMin.y + ImGui::GetStyle().FramePadding.y * 0.5f}); ImGui::SetNextItemWidth(satMax.x - cx - 6); if (renameOdak_) ImGui::SetKeyboardFocusHere(); // odak isteği; AKTİF olunca düşür ImGui::PushStyleColor(ImGuiCol_FrameBg, t.cerceve); const bool bitti = ImGui::InputText( "##rename", renameTampon_, sizeof(renameTampon_), ImGuiInputTextFlags_EnterReturnsTrue | ImGuiInputTextFlags_AutoSelectAll); ImGui::PopStyleColor(); const bool aktif = ImGui::IsItemActive(); if (bitti) { // Enter → onayla if (renameTampon_[0]) { olay.yenidenAdlandi = true; olay.adId = d.id; olay.yeniAd = renameTampon_; } rename_ = 0; renameOdak_ = false; } else if (renameOdak_) { // Odak isteği yapıldı; item aktifleşene kadar AÇIK tut (ilk kare IsItemActive // henüz false — eskiden burada erken kapanıyordu). Aktif olunca bayrağı düşür. if (aktif) renameOdak_ = false; } else if (!aktif) { rename_ = 0; // odaklıyken başka yere tıklandı → iptal } } else { // Saydam overlay (3B üstünde yüzen ağaç): metin parlak sahne üstünde de // okunsun diye 1px ince gölge (abartısız kontur). Panel opak olsa da zararsız. const ImVec2 etkPos{cx, orta - ImGui::GetTextLineHeight() * 0.5f}; // Filtre eşleşmesi → eşleşen alt-dize arkasına sarı vurgu bandı (görsel). if (!filtre.empty()) { const std::size_t mp = altDizeBul(d.etiket, filtre); if (mp != std::string::npos) { const float ox = ImGui::CalcTextSize(d.etiket.c_str(), d.etiket.c_str() + mp).x; const float ow = ImGui::CalcTextSize(d.etiket.c_str() + mp, d.etiket.c_str() + mp + filtre.size()).x; const float th = ImGui::GetTextLineHeight(); dl->AddRectFilled({etkPos.x + ox - 1.0f, etkPos.y}, {etkPos.x + ox + ow + 1.0f, etkPos.y + th}, u32(t.uyari, 0.32f), 2.0f); } } float alfa = d.gorunur ? 1.0f : 0.45f; if (d.soluk || d.arketip == Arketip::AltDetay) alfa *= 0.62f; // referans/nested → soluk ImVec4 taban = secili ? t.metin : karisim(t.metinSoluk, t.metin, 0.6f + 0.4f * h); if (d.durum == Durum::Hata) taban = karisim(taban, t.hata, 0.75f); // hatalı → kırmızımsı ad else if (d.durum == Durum::Bastik) taban = t.metinSoluk; dl->AddText({etkPos.x + 1.0f, etkPos.y + 1.0f}, IM_COL32(0, 0, 0, d.gorunur ? 150 : 80), d.etiket.c_str()); dl->AddText(etkPos, u32(taban, alfa), d.etiket.c_str()); if (d.kalin) // ImGui'de gerçek bold font yoksa 0.6px offset ikinci geçiş = sahte-bold dl->AddText({etkPos.x + 0.6f, etkPos.y}, u32(taban, alfa), d.etiket.c_str()); const float etkGen = ImGui::CalcTextSize(d.etiket.c_str()).x; if (d.altiCizili) { // aktif gövde ("In Work Object") const float uy = orta + ImGui::GetTextLineHeight() * 0.5f + 1.0f; dl->AddLine({cx, uy}, {cx + etkGen, uy}, u32(t.vurgu2, 0.9f), 1.5f); } if (d.durum == Durum::Bastik) // bastırılmış → üstü çizili dl->AddLine({cx, orta}, {cx + etkGen, orta}, u32(t.metinSoluk, 0.8f), 1.0f); } // --- zengin satır: durum simgesi + rozetler + mini-bilgi (rename/nested hariç) --- // Veri-odaklı (AgacDugum alanlarından); tip-özel çizim fonksiyonu YOK. Etiketin // hemen sağına dizilir; mini-bilgi satır sağına hizalanır (hover'da hızlı-eylem // belirince gizlenir → çakışma yok). Parametre satırı miniBilgi'de DEĞERİ gösterir // (agacMiniBilgi "= 25 mm") → kullanıcı ağaçta parametre değerini doğrudan görür. if (rename_ != d.id && d.arketip != Arketip::AltDetay) { float sagX = cx + ImGui::CalcTextSize(d.etiket.c_str()).x + 8.0f; if (d.durum != Durum::Normal) { // Durum simgesi: hata ⚠(kırmızı,sabit) · bayat ⟳(sarı,nabız) · hesaplanıyor ⏳(vurgu, // nabız) · bastırılmış ⏸(soluk,sabit). Nabız = dikkat çeken canlı durumlar. const char* dg = d.durum == Durum::Hata ? "⚠" : d.durum == Durum::Bayat ? "⟳" : d.durum == Durum::Hesaplaniyor ? "⏳" : "⏸"; // Bastik const ImVec4 dc = d.durum == Durum::Hata ? t.hata : d.durum == Durum::Bayat ? t.uyari : d.durum == Durum::Hesaplaniyor ? t.vurgu : t.metinSoluk; // Bastik const bool nabizli = d.durum == Durum::Bayat || d.durum == Durum::Hesaplaniyor; const float da = nabizli ? (0.55f + 0.45f * nabiz(1.3f)) : 1.0f; simgeCiz(dl, Simge(dg), {sagX + 7.0f, orta}, 13.0f, u32(dc, da)); // Hover'da durum tooltip'i (hata → sebep miniBilgi'de; burada kısa etiket). if (ImGui::IsMouseHoveringRect({sagX, orta - 8}, {sagX + 16, orta + 8})) { const char* aciklama = d.durum == Durum::Hata ? "Hata — recompute başarısız" : d.durum == Durum::Bayat ? "Bayat — güncelleme gerekiyor" : d.durum == Durum::Hesaplaniyor ? "Hesaplanıyor…" : "Bastırılmış (Suppress)"; ImGui::SetTooltip("%s", aciklama); } sagX += 19.0f; } for (const AgacRozet& rz : d.rozetler) { const float ra = rz.nabiz ? (0.55f + 0.45f * nabiz(1.3f)) : 1.0f; sagX = rozetCiz(dl, t, rz, {sagX, orta}, ra) + 4.0f; } const bool hizliBelirdi = !hizliEylemler.empty() && ImGui::IsMouseHoveringRect(satMin, satMax); if (!d.miniBilgi.empty() && !hizliBelirdi) { const float w = ImGui::CalcTextSize(d.miniBilgi.c_str()).x; const float mx = satMax.x - 6.0f - w; if (mx > sagX + 6.0f) // etiket/rozetle çakışmıyorsa çiz dl->AddText({mx, orta - ImGui::GetTextLineHeight() * 0.5f}, u32(t.metinSoluk, 0.72f), d.miniBilgi.c_str()); } } // --- Rollback çubuğu (Onshape tarzı yatay bar): rollback noktası feature'ının ALTINDA // turuncu çizgi + sol tutamaç üçgeni. Buradan sonrakiler geçici devre dışı (görsel işaret). --- if (d.rollbackCizgi) { const ImU32 rc = IM_COL32(255, 170, 60, 235); const float ry = satMax.y - 1.0f; dl->AddLine({satMin.x + 2.0f, ry}, {satMax.x - 2.0f, ry}, rc, 2.0f); dl->AddTriangleFilled({satMin.x + 1.0f, ry - 4.0f}, {satMin.x + 1.0f, ry + 4.0f}, {satMin.x + 6.0f, ry}, rc); // sol tutamaç // Sürükle tutamağı: sol üçgen üstünde görünmez buton → tutup dikey sürükle = rollback taşı. // AllowOverlap ile satır butonunun üstünde önceliklidir (satır seçimini yutmaz). ImGui::SetCursorScreenPos({satMin.x, ry - 6.0f}); ImGui::SetNextItemAllowOverlap(); ImGui::InvisibleButton("##rbTut", {16.0f, 12.0f}); if (ImGui::IsItemActivated()) rollbackSuruk_ = true; if (ImGui::IsItemHovered() && !rollbackSuruk_) ImGui::SetTooltip("Sürükle: rollback noktasını taşı"); } // --- görünürlük 👁 (sol sütun) --- if (gorunurlukSutunu) { ImGui::SetCursorScreenPos({satMin.x + 3, orta - 8}); if (ImGui::InvisibleButton("##goz", {16, 16})) { olay.gorunurlukDegisti = true; olay.gorunurlukId = d.id; olay.yeniGorunur = !d.gorunur; } gozCiz(dl, t, {satMin.x + 11, orta}, d.gorunur, ImGui::IsItemHovered()); } // --- hızlı eylemler (etiketin HEMEN sağında; imleç satır üzerindeyken belirir) --- // Manuel rect testi: buton kendisi hover'ı çalınca satır-hover sönmesin. if (!hizliEylemler.empty() && rename_ != d.id && ImGui::IsMouseHoveringRect(satMin, satMax)) { int gorunenSayi = 0; for (int e = 0; e < (int)hizliEylemler.size() && e < 32; ++e) if (d.hizliMaske & (1u << e)) ++gorunenSayi; if (gorunenSayi) { // İçerik-bitişik: buton şeridi etiket metninin hemen sağına konumlanır // (sabit sağ-kenar hizası yok), genişliği yalnız buton sayısı kadar (auto-fit). const float seritGen = gorunenSayi * 18.0f; const float etiketSon = cx + ImGui::CalcTextSize(d.etiket.c_str()).x; float baslaX = etiketSon + 8.0f; const float enSag = satMax.x - 4.0f - seritGen; // satır sonunu aşma if (baslaX > enSag) baslaX = enSag; // Okunurluk: etiket şerit altında kalmasın → içerik-genişliğinde zemin. dl->AddRectFilled({baslaX - 2, satMin.y + 1}, {baslaX + seritGen + 2, satMax.y - 1}, u32(t.panel, 0.88f), t.yuvarlak * 0.5f); float ex = baslaX; for (int e = 0; e < (int)hizliEylemler.size() && e < 32; ++e) { if (!(d.hizliMaske & (1u << e))) continue; const AgacHizliEylem& he = hizliEylemler[e]; ImGui::SetCursorScreenPos({ex, orta - 8}); ImGui::PushID(1000 + e); if (ImGui::InvisibleButton("##he", {16, 16})) { olay.hizliEylem = true; olay.hizliEylemId = he.eylemId; olay.hizliSatirId = d.id; } const bool eHover = ImGui::IsItemHovered(); if (eHover && he.ipucu) ImGui::SetTooltip("%s", he.ipucu); simgeCiz(dl, he.simge, {ex + 8, orta}, 13.0f, u32(eHover ? t.vurgu : t.metinSoluk, eHover ? 1.0f : 0.85f)); ImGui::PopID(); ex += 18; } } } // --- etkileşim sonuçları (ok/göz butonlarından SONRA; öncelik onlarda) --- if (hover) { olay.hoverId = d.id; // Etiket panel genişliğini aşıyorsa (kırpık) tam adı + mini-bilgiyi tooltip'te göster. if (rename_ != d.id) { const float tw = ImGui::CalcTextSize(d.etiket.c_str()).x; if (cx + tw + 6.0f > satMax.x) { if (!d.miniBilgi.empty()) ImGui::SetTooltip("%s\n%s", d.etiket.c_str(), d.miniBilgi.c_str()); else ImGui::SetTooltip("%s", d.etiket.c_str()); } } } if (tiklandi) { const ImGuiIO& io = ImGui::GetIO(); if (io.KeyShift && sonSecim_ && sonSecim_ != d.id) { // CATIA shift-aralık: çapa (sonSecim_) → tıklanan arası düz-listede seç. // Çapa KORUNUR (art arda shift-tık aralığı büyütür/küçültür). int ia = -1, ib = -1; for (int k = 0; k < (int)satirlar.size(); ++k) { if (satirlar[k].d->id == sonSecim_) ia = k; if (satirlar[k].d->id == d.id) ib = k; } if (ia >= 0 && ib >= 0) { if (ia > ib) { const int tmp = ia; ia = ib; ib = tmp; } if (!io.KeyCtrl) secili_.clear(); for (int k = ia; k <= ib; ++k) { const std::uint64_t rid = satirlar[k].d->id; if (std::find(secili_.begin(), secili_.end(), rid) == secili_.end()) secili_.push_back(rid); } } else { sec(d.id, io.KeyCtrl); } } else { sec(d.id, io.KeyCtrl); // tek/ctrl-tık → çapayı günceller } olay.secimDegisti = true; } if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left) && hover) { olay.ciftTiklandi = true; olay.ciftTikId = d.id; if (renameEtkin && renameCiftTikla && cocukVar == false) renameBaslat(d.id, d.etiket.c_str()); } if (ImGui::IsMouseClicked(ImGuiMouseButton_Right) && hover) { if (!secili) { sec(d.id, false); olay.secimDegisti = true; } olay.baglamIstendi = true; olay.baglamId = d.id; olay.baglamKonum = ImGui::GetMousePos(); } ImGui::PopID(); ImGui::SetCursorScreenPos({x0, satMax.y}); // Satır içeriği drawlist + manuel SetCursorScreenPos ile mutlak çizildi; // clipper sınırını gerçek bir öğeyle büyüt (sıfır-boyut Dummy) → ImGui'nin // "SetCursorPos ile sınır büyütme, öğe submit et" uyarısı kalkar. Satır // yüksekliği zaten InvisibleButton ile kaplandığından yerleşim değişmez. ImGui::Dummy(ImVec2(0.0f, 0.0f)); } } clipper.End(); // Rollback çubuğu bırakıldı → olayı yay: feature üstünde = O feature'a sar; tüm feature'ların // altında = iptal (hedefId 0). İkisi de değilse (ilk feature üstü boşluk) olay yok (no-op). if (rollbackSuruk_ && !ImGui::IsMouseDown(ImGuiMouseButton_Left)) { rollbackSuruk_ = false; if (rollbackAday_ != 0) { olay.rollbackTasindi = true; olay.rollbackHedefId = rollbackAday_; } else if (rollbackSonAlt_ > 0.0f && ImGui::GetMousePos().y > rollbackSonAlt_) { olay.rollbackTasindi = true; // hepsinin altı → iptal (hedefId 0) } } // ---- Klavye navigasyonu (ağaç odaktayken): ↑↓ gez · Home/End uç · →aç/in · // ←kapat/ata · F2 rename · Enter etkinleştir. Düz-liste (satirlar) üzerinde tek-seçim // taşır + görünür alana kaydırır. rename modunda pasif (InputText tuşları yer). ---- // Ağaç klavye odağında mı → caller Delete'i GLOBAL (gövde sil) yerine ağaç seçimine yönlendirir // (feature/grup/sketch/parametre de silinebilsin + çoklu). odaktayken Delete → silIstendi olayı. const bool agacOdakli = !satirlar.empty() && ImGui::IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows); olay.odakta = agacOdakli; if (agacOdakli && rename_ == 0 && !secili_.empty() && ImGui::IsKeyPressed(ImGuiKey_Delete)) olay.silIstendi = true; // PERF: satır-arama (O(N)) + tuş dalları YALNIZ bir nav tuşuna basıldığında koşar; boşta // karede (tuş yok) hiç iş yapılmaz (büyük ağaçta odaktayken her-kare O(N) taramayı önler). const bool navTus = ImGui::IsKeyPressed(ImGuiKey_DownArrow) || ImGui::IsKeyPressed(ImGuiKey_UpArrow) || ImGui::IsKeyPressed(ImGuiKey_Home) || ImGui::IsKeyPressed(ImGuiKey_End) || ImGui::IsKeyPressed(ImGuiKey_RightArrow) || ImGui::IsKeyPressed(ImGuiKey_LeftArrow) || ImGui::IsKeyPressed(ImGuiKey_F2) || ImGui::IsKeyPressed(ImGuiKey_Enter) || ImGui::IsKeyPressed(ImGuiKey_KeypadEnter); if (navTus && !satirlar.empty() && rename_ == 0 && ImGui::IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows)) { int cur = -1; const std::uint64_t aktifId = sonSecim_ ? sonSecim_ : (secili_.empty() ? 0 : secili_.back()); for (int r = 0; r < (int)satirlar.size(); ++r) if (satirlar[r].d->id == aktifId) { cur = r; break; } const auto tasi = [&](int idx) { if (idx < 0) idx = 0; if (idx > (int)satirlar.size() - 1) idx = (int)satirlar.size() - 1; const std::uint64_t rid = satirlar[idx].d->id; sec(rid, false); // tek-seçim (secili_+kume+sonSecim_) olay.secimDegisti = true; kaydir(rid); // görünür alana getir (kapalı ataları da açar) }; if (ImGui::IsKeyPressed(ImGuiKey_DownArrow)) tasi(cur < 0 ? 0 : cur + 1); else if (ImGui::IsKeyPressed(ImGuiKey_UpArrow)) tasi(cur < 0 ? 0 : cur - 1); else if (ImGui::IsKeyPressed(ImGuiKey_Home)) tasi(0); else if (ImGui::IsKeyPressed(ImGuiKey_End)) tasi((int)satirlar.size() - 1); else if (cur >= 0 && ImGui::IsKeyPressed(ImGuiKey_RightArrow)) { const AgacDugum& d = *satirlar[cur].d; if (!d.cocuklar.empty()) { if (!acikMi(d.id)) { acikDurum_[d.id] = true; duzKirli_ = true; } // kapalı → aç else tasi(cur + 1); // açık → ilk çocuğa in } } else if (cur >= 0 && ImGui::IsKeyPressed(ImGuiKey_LeftArrow)) { const AgacDugum& d = *satirlar[cur].d; if (!d.cocuklar.empty() && acikMi(d.id)) { acikDurum_[d.id] = false; duzKirli_ = true; } else { // yaprak/kapalı → ataya git (düz-listede önceki daha sığ satır) const int myd = satirlar[cur].derinlik; for (int k = cur - 1; k >= 0; --k) if (satirlar[k].derinlik < myd) { tasi(k); break; } } } else if (cur >= 0 && renameEtkin && ImGui::IsKeyPressed(ImGuiKey_F2)) { renameBaslat(satirlar[cur].d->id, satirlar[cur].d->etiket.c_str()); } else if (cur >= 0 && (ImGui::IsKeyPressed(ImGuiKey_Enter) || ImGui::IsKeyPressed(ImGuiKey_KeypadEnter))) { olay.ciftTiklandi = true; // Enter = çift-tık (aktifleştir/pencere aç) olay.ciftTikId = satirlar[cur].d->id; } } // ---- Sol scrollbar'ı çiz (içerik child'ının scroll durumundan) ---- if (sbW > 0.0f) { const float scrollY = ImGui::GetScrollY(); const float scrollMaxY = ImGui::GetScrollMaxY(); const float gorunenYuk = disAvail.y; const float icerikYuk = gorunenYuk + scrollMaxY; // toplam içerik yüksekliği ImGui::EndChild(); ImGui::PopStyleVar(); ImGui::PopStyleColor(); if (scrollMaxY > 0.0f) { // yalnız taşma varsa çubuk görünür const ImRect bb{{disMin.x, disMin.y}, {disMin.x + sbW, disMin.y + disAvail.y}}; ImS64 scroll = (ImS64)(scrollY + 0.5f); if (ImGui::ScrollbarEx(bb, ImGui::GetID("##agacSolSB"), ImGuiAxis_Y, &scroll, (ImS64)gorunenYuk, (ImS64)icerikYuk, ImDrawFlags_RoundCornersAll)) { yeniScroll_ = (float)scroll; scrollUygula_ = true; // sonraki karede içerik child'ına uygula } // Scrollbar oluğu ağaç SATIRI değil → caller'ın "fare tüketiyor" testine girmez; // hover/aktifken işaretle ki 3B-overlay modunda arkadaki sahne orbit/seçim ALMASIN. if (ImGui::IsItemHovered() || ImGui::IsItemActive()) olay.fareSatirUstunde = true; } // Boş satır bandı da ağaç tüketsin diye içerik bölgesini işaretle (layout). // Kompakt yoğunlukta içerik bu yüksekliğe ulaşmayabilir → sınırı gerçek bir // öğeyle büyüt (sıfır-boyut Dummy), yoksa ImGui "SetCursorPos ile sınır // büyütme" hata denetimi tetiklenir (satır 627'deki kalıpla aynı). ImGui::SetCursorScreenPos({disMin.x, disMin.y + disAvail.y}); ImGui::Dummy(ImVec2(0.0f, 0.0f)); } ImGui::PopID(); olay.secili = secili_; (void)okBoyut; return olay; } } // namespace fiko::ui