#include "ozellikler_paneli.hpp" #include #include #include #include #include "uygulama_baglam.hpp" #include "ui_ortak.hpp" #include "fiko/ui/bilesen_ek.hpp" // ui::toastGoster (KYA kimlik kopyalandı geri bildirimi) #include "dosya_islemleri.hpp" #include "fiko/cekirdek/cekirdek.hpp" #include "fiko/cekirdek/nesne.hpp" #include "fiko/cekirdek/olay.hpp" #include "fiko/platform/pencere.hpp" #include "fiko/sahne/sahne.hpp" #include "fiko/sahne/sahne_islem.hpp" #include "imgui.h" #include "fiko/ui/agac_simge_kayit.hpp" #include "fiko/ui/pencere_cerceve.hpp" namespace fiko { // Tek yüz/kenar/nokta seçiliyken görünen görsel-override düzenleyici. // Yüz: renk + alfa + görünürlük anında render'a yansır (ogeRenkYenile). Kenar/nokta alfa // ve stil/malzeme saklanır (sonraki dalgalarda render'a bağlanacak). Undo: nesne-özellik paneliyle aynı kalıp — // karenin başında pusula al, aktifleşince sakla, bırakışta tek DuzenleIslem geçmişe. static void ogeGorunumBolumu(UygulamaBaglam& b, Sahne& sahne, Cekirdek& cekirdek) { const auto& secili = sahne.secililer(); if (secili.size() != 1 || secili[0].alt == 0) return; // tam olarak tek öğe const SecimKip kip = sahne.kip(); if (kip == SecimKip::Govde) return; const SecimOge oge = secili[0]; Dugum* d = sahne.bul(oge.govde); if (!d) return; static const char* kenarStilAd[] = {"Düz", "Kesik (Dash)", "Nokta (Dot)", "Çizgi-Nokta"}; static const char* noktaSekilAd[] = {"Nokta", "Kare", "Artı"}; ImGui::Separator(); ImGui::Text("%s Öğe Görünümü · #%u", kip == SecimKip::Yuz ? "🟦" : kip == SecimKip::Kenar ? "➖" : "•", oge.alt); // KYA kalıcı kimlik (ayar açıkken): 🏷 ad + kopyala — edit'te ordinal kaysa da SABİT (referans/makro). if (geometri::kaliciKimlikAcikMi()) { const std::string kyaAd = sahne.kimlikMetni(oge); if (!kyaAd.empty()) { ImGui::TextDisabled("🏷 %s", kyaAd.c_str()); ImGui::SameLine(); if (ImGui::SmallButton("📋##kyaOz")) { ImGui::SetClipboardText(kyaAd.c_str()); ui::toastGoster("Kalıcı kimlik kopyalandı", ui::ToastTip::Bilgi); } if (ImGui::IsItemHovered()) ImGui::SetTooltip("Kalıcı kimliği kopyala (edit sonrası da geçerli)"); } } // Karenin "öncesi" — herhangi bir widget yazmadan ÖNCE (undo tabanı). const DugumPusula ogeOnce = sahne.anlik(d->kimlik); bool renkDegisti = false, renkBitti = false, sifirlandi = false; // GPU re-upload tetikleyiciler bool alfaDegisti = false, alfaBitti = false, gorunurDegisti = false; OgeOzellik& o = (kip == SecimKip::Yuz) ? d->yuzOzellikYaz(oge.alt) : (kip == SecimKip::Kenar) ? d->kenarOzellikYaz(oge.alt) : d->noktaOzellikYaz(oge.alt); // ID çakışması önlenir: nesne-özellik panelinin widget'ları da "Renk"/"Görünür" gibi // etiketler kullanabilir → aynı ID → ImGui assert/uyarı. Öğe ordinali ile ayrık ID alanı. ImGui::PushID((int)oge.alt); auto undoIzle = [&]() { // her widget'tan sonra çağır: aktivasyon/bırakış izle if (ImGui::IsItemActivated()) b.ozellikEski = ogeOnce; if (ImGui::IsItemDeactivatedAfterEdit() && b.ozellikEski.kimlik == d->kimlik) cekirdek.uygula(std::make_unique(sahne, b.ozellikEski, sahne.anlik(d->kimlik))); }; // Renk — ColorEdit3'ün picker'ı AYRI popup penceresi olduğundan IsItemDeactivatedAfterEdit // GÜVENİLMEZ tetiklenir (item deaktivasyonu popup içinde yakalanmaz) → DuzenleIslem hiç // push edilmez → Ctrl+Z rengi geri almazdı. Çözüm: değer-değişti + sonraki-kare-commit. // İlk değişimde oturum tabanı (ogeOnce = düzenleme ÖNCESİ) saklanır; picker'da aktif // sürükleme bitince (item aktif değil + bu kare değişim yok) TEK DuzenleIslem geçmişe girer. // NoInputs: swatch yanındaki gereksiz RGB kutuları kaldırılır — salt picker açan renk karesi. static bool renkDuzenleniyor = false; // renk oturumu sürüyor (commit bekliyor) f32 renk[3] = {o.renk.x, o.renk.y, o.renk.z}; if (ImGui::ColorEdit3("Renk", renk, ImGuiColorEditFlags_NoInputs)) { if (!renkDuzenleniyor) { b.ozellikEski = ogeOnce; renkDuzenleniyor = true; } // oturum tabanı o.renk = {renk[0], renk[1], renk[2]}; renkDegisti = true; } if (renkDuzenleniyor && !renkDegisti && !ImGui::IsItemActive()) { // oturum bitti → commit renkDuzenleniyor = false; renkBitti = true; // bırakışta kesin son mesh (tam re-upload) if (b.ozellikEski.kimlik == d->kimlik) cekirdek.uygula(std::make_unique(sahne, b.ozellikEski, sahne.anlik(d->kimlik))); } if (ImGui::SliderFloat("Alfa", &o.alfa, 0.0f, 1.0f, "%.2f")) alfaDegisti = true; if (ImGui::IsItemDeactivatedAfterEdit()) alfaBitti = true; // sürükleme bitti → kesin son hal undoIzle(); if (ImGui::Checkbox("Görünür", &o.gorunur)) gorunurDegisti = true; undoIzle(); if (kip == SecimKip::Yuz) { ImGui::SliderFloat("Metalik", &o.metalik, 0.0f, 1.0f, "%.2f"); undoIzle(); ImGui::SliderFloat("Pürüz", &o.puruz, 0.0f, 1.0f, "%.2f"); undoIzle(); } else if (kip == SecimKip::Kenar) { int stil = (int)o.stil; if (ImGui::Combo("Stil", &stil, kenarStilAd, IM_ARRAYSIZE(kenarStilAd))) o.stil = (KenarStil)stil; undoIzle(); ImGui::SliderFloat("Kalınlık", &o.kalinlik, 0.5f, 8.0f, "%.1f"); undoIzle(); } else { // Nokta ImGui::SliderFloat("Boyut", &o.noktaBoyut, 1.0f, 20.0f, "%.1f"); undoIzle(); int sekil = (int)o.sekil; if (ImGui::Combo("Şekil", &sekil, noktaSekilAd, IM_ARRAYSIZE(noktaSekilAd))) o.sekil = (NoktaSekil)sekil; undoIzle(); } if (ImGui::SmallButton("Varsayılana dön")) { b.ozellikEski = ogeOnce; if (kip == SecimKip::Yuz) d->yuzOz.erase(oge.alt); else if (kip == SecimKip::Kenar) d->kenarOz.erase(oge.alt); else d->noktaOz.erase(oge.alt); sifirlandi = true; cekirdek.uygula(std::make_unique(sahne, b.ozellikEski, sahne.anlik(d->kimlik))); } ImGui::PopID(); // (int)oge.alt — ID çakışması koruması sonu // Debounce: renk SÜRÜKLERKEN throttle (~12/sn) → kare-başı tam mesh re-upload yükü yok; // bırakış (renkBitti) veya sıfırla → kesin son hal. "Varsayılana dön" tam re-mesh gerektirir // (override kaldırıldı → base renge dönmeli) → yenidenInsa. // Alfa/görünür yalnız YÜZ'de render'a yansır (kenar/nokta alfa Dalga 3/4). Renk her öğede // saklanır ama sahne mesh'i yüz-tabanlı → ogeRenkYenile yüz için anlamlı. const bool yuzYenile = kip == SecimKip::Yuz; static f64 sonYenile = 0.0; if (renkDegisti || (alfaDegisti && yuzYenile)) { const f64 t = ImGui::GetTime(); if (t - sonYenile > 0.08) { sahne.ogeRenkYenile(*d); sonYenile = t; } } if (renkBitti || (alfaBitti && yuzYenile) || (gorunurDegisti && yuzYenile)) sahne.ogeRenkYenile(*d); // kesin son hal (alfa bırakışı / görünür toggle anında) // "Varsayılana dön" → override kaldırıldı; YERİNDE ogeRenkYenile (ordinal-sabit, STEP/gövde // tabanına döner). yenidenInsa YOK: ithal gövdeyi yeniden üçgenler → renk karışır (bug). if (sifirlandi) sahne.ogeRenkYenile(*d); } // ---- Özellikler (ayrı yüzen pencere, ana pencerenin üstünde) ---- void ozelliklerPaneliCiz(UygulamaBaglam& b, float ekranG) { Cekirdek& cekirdek = b.cekirdek; Sahne& sahne = b.sahne; const auto seciliGovdeler = sahne.seciliGovdeler(); Dugum* aktif = seciliGovdeler.empty() ? nullptr : sahne.bul(seciliGovdeler.back()); if (b.panelOzellik) { if (!b.ozellikAcikOnceki) ImGui::SetNextWindowFocus(); // açılışta öne getir ImGui::SetNextWindowPos({ekranG - 340, 70}, ImGuiCond_FirstUseEver); // Merkezî iskelet: içeriğe göre (y=0) viewport'a cap'li yükseklik → ilk açılışta oturur. ui::Pencere pen{"Özellikler", &b.panelOzellik, {.boyut = {320, 0}, .pinDurum = &b.ozellikUstte}}; if (pen) { DokumanNesnesi* aktifNesne = aktif ? cekirdek.nesne(aktif->kimlik) : nullptr; if (aktifNesne) { ImGui::Text("%s %s — %s", tipSimge(aktif->tip), aktifNesne->ad().c_str(), tipAdi(aktif->tip)); ImGui::Separator(); // Özellikler kök nesnenin ozellikler()'inden gelir → her tür için otomatik, // pluginable. Boyut/renk yazınca yenidenInsaGerek işaretlenir (reaktif inşa). // Undo: widget'lar çağrılmadan ÖNCE alınan pusula = bu karenin "öncesi" // (Checkbox gibi tek-kare düzenlemelerde de eski değer doğru yakalanır). const DugumPusula anlikOnce = sahne.anlik(aktif->kimlik); for (Ozellik& o : aktifNesne->ozellikler(cekirdek.dunya())) { switch (o.tur) { case Ozellik::Tur::Sayi: if (o.saltOkunur) { ImGui::TextDisabled("%s: %.3f", o.ad.c_str(), o.sayiOku()); } else { f32 v = (f32)o.sayiOku(); if (ImGui::DragFloat(o.ad.c_str(), &v, 0.05f)) o.sayiYaz((f64)v); } break; case Ozellik::Tur::Vektor: { const Vec3f p = o.vektorOku(); f32 a[3] = {p.x, p.y, p.z}; if (ImGui::DragFloat3(o.ad.c_str(), a, 0.05f)) o.vektorYaz({a[0], a[1], a[2]}); break; } case Ozellik::Tur::Renk: { const Vec3f c = o.vektorOku(); f32 a[3] = {c.x, c.y, c.z}; if (ImGui::ColorEdit3(o.ad.c_str(), a)) o.vektorYaz({a[0], a[1], a[2]}); break; } case Ozellik::Tur::Bayrak: { bool bb = o.bayrakOku(); if (ImGui::Checkbox(o.ad.c_str(), &bb)) o.bayrakYaz(bb); break; } case Ozellik::Tur::Metin: { char tmp[128]; std::snprintf(tmp, sizeof(tmp), "%s", o.metinOku().c_str()); if (!o.saltOkunur && ImGui::InputText(o.ad.c_str(), tmp, sizeof(tmp))) o.metinYaz(tmp); break; } } // Undo: widget aktifleşince karenin "öncesi" pusulası saklanır; düzenleme // bitince (bırakış/Enter) tek DuzenleIslem geçmişe girer (ara kareler girmez). if (ImGui::IsItemActivated()) b.ozellikEski = anlikOnce; if (ImGui::IsItemDeactivatedAfterEdit() && b.ozellikEski.kimlik == aktif->kimlik) cekirdek.uygula(std::make_unique(sahne, b.ozellikEski, sahne.anlik(aktif->kimlik))); } // ---- Kalıtımsal UI özelliği: Ağaç Simgesi (her görünür obje devralır) ---- // Objenin kendi ağaç-simge override'ı (emoji/SVG/PNG). Boş → tip glifi. if (GorunumSaglayici* gv = cekirdek.gorunum(aktif->kimlik)) { ImGui::Separator(); const ui::Simge pv = ui::AgacSimgeKayit::global().ogeAl(aktif->kimlik); const float fh = ImGui::GetFrameHeight(); if (pv.doku) ImGui::Image(pv.doku, {fh, fh}); else { ImGui::AlignTextToFramePadding(); ImGui::TextUnformatted(!gv->agacSimgeOzel().empty() ? gv->agacSimgeOzel().c_str() : gv->agacSimgesi()); } ImGui::SameLine(); char sbuf[260]; std::snprintf(sbuf, sizeof(sbuf), "%s", gv->agacSimgeOzel().c_str()); ImGui::SetNextItemWidth(-(fh + ImGui::GetStyle().ItemSpacing.x + 2.0f)); if (ImGui::InputTextWithHint("##agacSimgeOzel", "Ağaç simgesi (emoji / .svg/.png)", sbuf, sizeof(sbuf))) { gv->agacSimgeOzelAyarla(sbuf); // obje = sahip ui::AgacSimgeKayit::global().ogeAyarla(aktif->kimlik, sbuf); // render önbelleği } ImGui::SameLine(); if (ImGui::Button("📁##agacSimgeDosya", {fh, fh})) { const std::string yol = resimDosyaSec((HWND)b.pencere.dogalTutamac()); if (!yol.empty()) { gv->agacSimgeOzelAyarla(yol); ui::AgacSimgeKayit::global().ogeAyarla(aktif->kimlik, yol); } } if (ImGui::IsItemHovered()) ImGui::SetTooltip("Simge dosyası seç (SVG / PNG)"); } } else { ImGui::TextDisabled("Seçili gövde yok."); } // ---- Öğe Görünümü (tek yüz/kenar/nokta seçiliyken) ---- // Seçili topoloji öğesinin per-öğe görsel override'ını düzenler; RENK anında // render'a yansır (ogeRenkYenile), diğer alanlar saklanır (sonraki dalgalar). ogeGorunumBolumu(b, sahne, cekirdek); } // if (pen) } // pen yıkıcısı End() çağırır b.ozellikAcikOnceki = b.panelOzellik; } // ---- Olay Denetçi (canlı olay zaman çizelgesi) ---- // Omurgadan geçen her mutasyon burada görünür: sıra/zaman/tür/varlık/özet. // Kayıtlar RAM halka tamponundan (OlayGunlugu::kayitlar) okunur, sıfır disk. void olayDenetciCiz(UygulamaBaglam& b) { Cekirdek& cekirdek = b.cekirdek; if (!b.panelOlay) return; ui::Pencere pen{"Olay Denetçi", &b.panelOlay, {.boyut = {520, 420}, .pinDurum = &b.olayUstte, .bayrak = 0}}; if (pen) { static bool otoKaydir = true; const std::vector& kayitlar = cekirdek.olaylar().kayitlar(); ImGui::Text("%zu olay", kayitlar.size()); ImGui::SameLine(); ImGui::TextDisabled("· sıra %llu", (unsigned long long)cekirdek.olaylar().sonrakiSira()); ImGui::SameLine(ImGui::GetWindowContentRegionMax().x - 160); if (ImGui::SmallButton("Diske Yaz")) { // artımlı + filtreli .fikolog cekirdek.olaylar().diskeYaz(b.olayLogYol); b.bilgiVer("Olay günlüğü yazıldı (.fikolog)"); } ImGui::SameLine(); ImGui::Checkbox("Takip", &otoKaydir); if (ImGui::BeginTable("olaylar", 4, ImGuiTableFlags_ScrollY | ImGuiTableFlags_RowBg | ImGuiTableFlags_BordersInnerV | ImGuiTableFlags_SizingFixedFit)) { ImGui::TableSetupScrollFreeze(0, 1); ImGui::TableSetupColumn("Sıra", ImGuiTableColumnFlags_WidthFixed, 44); ImGui::TableSetupColumn("Zaman", ImGuiTableColumnFlags_WidthFixed, 52); ImGui::TableSetupColumn("Tür", ImGuiTableColumnFlags_WidthFixed, 128); ImGui::TableSetupColumn("Özet", ImGuiTableColumnFlags_WidthStretch); ImGui::TableHeadersRow(); for (const OlayKaydi& k : kayitlar) { ImGui::TableNextRow(); ImGui::TableNextColumn(); ImGui::TextDisabled("%llu", (unsigned long long)k.sira); ImGui::TableNextColumn(); ImGui::TextDisabled("%.2f", k.zaman); ImGui::TableNextColumn(); ImGui::TextColored(olayRengi(k.tur), "%s", olayTuruAdi(k.tur)); ImGui::TableNextColumn(); if (k.varlik) { ImGui::TextDisabled("#%u", k.varlik); ImGui::SameLine(); } ImGui::TextUnformatted(k.ozet[0] ? k.ozet : "—"); } if (otoKaydir) ImGui::SetScrollHereY(1.0f); // en yeni olaya sabitle ImGui::EndTable(); } } // pen yıkıcısı End() çağırır } } // namespace fiko