#include "fiko/ui/zaman_tarayici.hpp" #include #include #include "fiko/ui/animasyon.hpp" #include "fiko/ui/bilesen.hpp" #include "fiko/ui/tema.hpp" #include "imgui_internal.h" namespace fiko::ui { namespace { constexpr float PI_ = 3.14159265f; // Metni verilen ankraja hizalı çizer (yatay: 0 sol,0.5 orta,1 sağ). void metinCiz(ImDrawList* dl, ImVec2 nokta, float boyut, ImU32 renk, const char* metin, float ankrajX = 0.5f) { ImFont* f = ImGui::GetFont(); const ImVec2 mb = f->CalcTextSizeA(boyut, FLT_MAX, 0.0f, metin); dl->AddText(f, boyut, {nokta.x - mb.x * ankrajX, nokta.y}, renk, metin); } // Küçük göz ikonu (bastırılı düğüm göstergesi). Açık göz veya çizili kapalı göz. void gozIkonu(ImDrawList* dl, ImVec2 m, float r, ImU32 renk, bool kapali) { // badem (iki yay) + iris dl->PathArcTo(m, r, PI_ * 0.15f, PI_ * 0.85f, 12); dl->PathStroke(renk, 0, 1.4f); dl->PathArcTo(m, r, PI_ + PI_ * 0.15f, PI_ + PI_ * 0.85f, 12); dl->PathStroke(renk, 0, 1.4f); if (!kapali) dl->AddCircleFilled(m, r * 0.42f, renk); if (kapali) dl->AddLine({m.x - r, m.y - r * 0.8f}, {m.x + r, m.y + r * 0.8f}, renk, 1.6f); } } // namespace ZamanOlay zamanTarayici(const char* kimlik, const ZamanDugum* dugumler, int sayi, int* aktifIndeks) { ZamanOlay olay; if (sayi <= 0 || !dugumler || !aktifIndeks) return olay; const Tema& t = tema(); const float gen = ImGui::GetContentRegionAvail().x; const float yk = 76.0f; const ImVec2 p = ImGui::GetCursorScreenPos(); ImGui::PushID(kimlik); ImGui::InvisibleButton("##ztarayici", {gen, yk}); const bool hover = ImGui::IsItemHovered(); const bool aktif = ImGui::IsItemActive(); // sol tuş basılı (sürükleme) const bool basildi = ImGui::IsItemActivated(); // bu kare basıldı const bool birakildi = ImGui::IsItemDeactivated(); // bu kare bırakıldı const bool ciftTik = ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left) && hover; const bool sagTik = ImGui::IsItemHovered() && ImGui::IsMouseClicked(ImGuiMouseButton_Right); const ImGuiID id = ImGui::GetItemID(); ImGui::PopID(); const int son = sayi - 1; const float rayY = p.y + 22.0f; // ana hat yüksekliği (düğüm merkezi) const float phY = p.y + 6.0f; // playhead tutamağı üstte const float ray0 = p.x + 22.0f, ray1 = p.x + gen - 22.0f; auto nx = [&](int i) { return sayi == 1 ? p.x + gen * 0.5f : ray0 + (ray1 - ray0) * (float)i / (float)son; }; // Sürekli x → kesirli index (playhead sürükleme için). auto xToIdx = [&](float x) { if (sayi == 1) return 0.0f; return ImClamp((x - ray0) / (ray1 - ray0) * (float)son, 0.0f, (float)son); }; // ---- Etkileşim durumu (ImGuiStorage) ---- ImGuiStorage* depo = ImGui::GetStateStorage(); const ImGuiID kSurukTur = id + 1; // 0 yok, 1 playhead scrub, 2 düğüm reorder const ImGuiID kSurukKay = id + 2; // reorder: sürüklenen düğüm indexi const ImGuiID kBasX = id + 3; // basma X'i (yatay eşik için, *1000 int) const ImGuiID kBasIdx = id + 4; // basılan düğüm indexi const ImVec2 fare = ImGui::GetMousePos(); // Fare altındaki düğüm (yakınlık toleransı). int hoverIdx = -1; if (hover || aktif) { float enYakin = 18.0f; for (int i = 0; i < sayi; ++i) { const float d = std::fabs(fare.x - nx(i)); if (d < enYakin && std::fabs(fare.y - rayY) < 22.0f) { enYakin = d; hoverIdx = i; } } } // Playhead tutamağına yakın mı (üst şerit)? const float phX = nx(ImClamp(*aktifIndeks, 0, son)); const bool phHover = hover && std::fabs(fare.x - phX) < 14.0f && fare.y < rayY - 4.0f; // ---- Basma: scrub mı reorder mı? ---- if (basildi) { depo->SetInt(kBasX, (int)(fare.x * 1000.0f)); depo->SetInt(kBasIdx, hoverIdx); if (phHover || (hoverIdx < 0 && fare.y < rayY - 2.0f)) { depo->SetInt(kSurukTur, 1); // playhead sürükle } else if (hoverIdx >= 0) { depo->SetInt(kSurukTur, 0); // henüz belirsiz — eşik aşılınca reorder depo->SetInt(kSurukKay, hoverIdx); } else { depo->SetInt(kSurukTur, 0); } } int surukTur = depo->GetInt(kSurukTur, 0); int surukKay = depo->GetInt(kSurukKay, -1); // Sürükleme sırasında: yatay eşik aşılırsa düğüm reorder moduna geç. if (aktif && surukTur == 0 && surukKay >= 0) { const float basX = depo->GetInt(kBasX, 0) / 1000.0f; if (std::fabs(fare.x - basX) > 6.0f) { surukTur = 2; depo->SetInt(kSurukTur, 2); } } // ---- Playhead scrub ---- if (aktif && surukTur == 1) { const int yeni = (int)std::round(xToIdx(fare.x)); if (yeni != *aktifIndeks) { *aktifIndeks = yeni; olay.tarayici = yeni; } } // ---- Reorder bırakma hedefi ---- int hedefIdx = -1; // ekleme göstergesi konumu (0..sayi) if (aktif && surukTur == 2 && surukKay >= 0) { const float f = xToIdx(fare.x) + 0.5f; hedefIdx = ImClamp((int)f, 0, sayi); } if (birakildi) { if (surukTur == 2 && surukKay >= 0) { // hedefIdx ekleme yuvası; kaynak çıktıktan sonraki nihai index'e çevir. float f2 = xToIdx(fare.x) + 0.5f; int yuva = ImClamp((int)f2, 0, sayi); int nihai = yuva > surukKay ? yuva - 1 : yuva; if (nihai != surukKay) { olay.siraKaynak = surukKay; olay.siraHedef = nihai; } } else if (surukTur < 2) { // Sürükleme değil → tık. Düğüme tık = o noktaya sar (rollback). const int basIdx = depo->GetInt(kBasIdx, -1); if (basIdx >= 0 && basIdx == hoverIdx) { if (basIdx != *aktifIndeks) { *aktifIndeks = basIdx; olay.tarayici = basIdx; } } } depo->SetInt(kSurukTur, 0); depo->SetInt(kSurukKay, -1); depo->SetInt(kBasIdx, -1); } // Çift-tık / sağ-tık: bastır (suppress) toggle. if ((ciftTik || sagTik) && hoverIdx >= 0) olay.bastirToggle = hoverIdx; // ========================= ÇİZİM ========================= ImDrawList* dl = ImGui::GetWindowDrawList(); ImFont* f = ImGui::GetFont(); const int aktifI = ImClamp(*aktifIndeks, 0, son); const float sx = nx(aktifI); // --- Ana hat: geçmiş neon (sol) + gelecek/ghost kesikli (sağ) --- dl->AddLine({nx(0), rayY}, {sx, rayY}, u32(t.vurgu, 0.20f * t.glowSiddet), 6.0f); dl->AddLine({nx(0), rayY}, {sx, rayY}, u32(t.vurgu, 0.9f), 2.2f); if (aktifI < son) { // ghost hattı: kesikli (dash) — "geleceğe uzanan" his const float x0 = sx, x1 = nx(son); const float dash = 7.0f, bosluk = 5.0f; for (float x = x0; x < x1; x += dash + bosluk) dl->AddLine({x, rayY}, {ImMin(x + dash, x1), rayY}, u32(t.metinSoluk, 0.40f), 1.5f); } // --- Düğümler --- for (int i = 0; i < sayi; ++i) { const ZamanDugum& d = dugumler[i]; const float x = nx(i); const bool s = i == aktifI; const bool h = i == hoverIdx; const bool gecmis = i <= aktifI; const bool ghost = i > aktifI; // rollback sonrası hayalet const bool suruk = surukTur == 2 && i == surukKay; // hover/aktif → yay ile büyüme const float hedefBuyu = (h ? 1.0f : 0.0f) + (s ? 0.6f : 0.0f); const float buyu = yay(id + 1000 + (ImGuiID)i, hedefBuyu, 260.0f, 20.0f); float r = 5.5f + 2.5f * buyu; float alfa = ghost ? 0.42f : 1.0f; // hayalet düğümler soluk if (d.bastirili) alfa *= 0.55f; if (suruk) alfa *= 0.35f; // sürüklenen orijinal soluk ImVec4 renk = s ? t.vurgu2 : gecmis ? t.vurgu : t.metinSoluk; if (d.hata) renk = t.hata; // seçili nabız halkası if (s && !suruk) dl->AddCircleFilled({x, rayY}, r + 4.0f * nabiz(1.2f), u32(t.vurgu2, 0.20f * alfa)); // hover glow halkası if (h && !suruk) dl->AddCircleFilled({x, rayY}, r + 5.0f, u32(t.vurgu, 0.18f * t.glowSiddet)); // düğüm gövdesi: simge varsa yuvarlak zemin + simge, yoksa nokta dl->AddCircleFilled({x, rayY}, r, u32(karisim(t.panel, renk, 0.60f), alfa)); dl->AddCircle({x, rayY}, r, u32(renk, 0.95f * alfa), 0, s ? 2.2f : 1.5f); if (!d.simge.bosMu()) simgeCiz(dl, d.simge, {x, rayY}, r * 1.3f, u32(t.metin, alfa)); if (d.hata) dl->AddCircle({x, rayY}, r + 3.0f, u32(t.hata, 0.6f * nabiz(2.0f)), 0, 1.5f); // bastırılı göstergesi (küçük göz-kapalı, düğümün sağ-altı) if (d.bastirili) gozIkonu(dl, {x + r + 3.0f, rayY + r + 1.0f}, 3.5f, u32(t.uyari, 0.85f), true); // etiket if (d.etiket) { const bool one = s || h; float ea = ghost ? 0.55f : 1.0f; if (d.bastirili) ea *= 0.6f; metinCiz(dl, {x, rayY + r + 6.0f}, one ? 12.0f : 11.0f, u32(one ? t.metin : t.metinSoluk, ea), d.etiket); } // hover etiket balonu (üstte, animasyonlu) if (h && d.etiket && buyu > 0.15f) { const ImVec2 tb = f->CalcTextSizeA(12.0f, FLT_MAX, 0.0f, d.etiket); const float by = rayY - r - 10.0f - 4.0f * buyu; const ImVec2 mn{x - tb.x * 0.5f - 6.0f, by - tb.y - 3.0f}; const ImVec2 mx{x + tb.x * 0.5f + 6.0f, by + 3.0f}; dl->AddRectFilled(mn, mx, u32(t.panel, 0.96f), 4.0f); dl->AddRect(mn, mx, u32(t.vurgu, 0.5f * buyu), 4.0f, 0, 1.0f); metinCiz(dl, {x, by - tb.y}, 12.0f, u32(t.metin), d.etiket); } } // --- Reorder ekleme göstergesi (insertion indicator) --- if (surukTur == 2 && hedefIdx >= 0) { float ix; if (hedefIdx <= 0) ix = nx(0) - 10.0f; else if (hedefIdx >= sayi) ix = nx(son) + 10.0f; else ix = (nx(hedefIdx - 1) + nx(hedefIdx)) * 0.5f; dl->AddLine({ix, rayY - 16.0f}, {ix, rayY + 16.0f}, u32(t.vurgu2), 2.5f); dl->AddTriangleFilled({ix - 4.0f, rayY - 16.0f}, {ix + 4.0f, rayY - 16.0f}, {ix, rayY - 10.0f}, u32(t.vurgu2)); dl->AddTriangleFilled({ix - 4.0f, rayY + 16.0f}, {ix + 4.0f, rayY + 16.0f}, {ix, rayY + 10.0f}, u32(t.vurgu2)); // sürüklenen düğümün hayaleti fare hizasında if (surukKay >= 0 && dugumler[surukKay].etiket) metinCiz(dl, {fare.x, rayY - 34.0f}, 12.0f, u32(t.vurgu2), dugumler[surukKay].etiket); } // --- Playhead (rollback bar) --- // Sürüklerken parlaklık artışı (yay). const float phVurgu = yay(id + 5, (phHover || surukTur == 1) ? 1.0f : 0.0f, 240.0f, 22.0f); // dikey rollback çubuğu (playhead X'inden aşağı) dl->AddLine({sx, phY + 4.0f}, {sx, rayY + 20.0f}, u32(t.vurgu2, 0.35f + 0.4f * phVurgu), 1.5f + phVurgu); // tutamak: aşağı bakan üçgen + halka const float th = 7.0f + 2.0f * phVurgu; if (phVurgu > 0.02f) dl->AddCircleFilled({sx, phY + 4.0f}, th + 4.0f, u32(t.vurgu2, 0.20f * phVurgu)); dl->AddTriangleFilled({sx - th, phY - 2.0f}, {sx + th, phY - 2.0f}, {sx, phY + 8.0f}, u32(karisim(t.vurgu2, t.metin, 0.15f))); dl->AddTriangle({sx - th, phY - 2.0f}, {sx + th, phY - 2.0f}, {sx, phY + 8.0f}, u32(t.vurgu2, 0.9f), 1.4f); return olay; } // ============================================================================ // Animasyon Zaman Şeridi (sürekli) // ============================================================================ SeritOlay animasyonSeridi(const char* kimlik, SeritDurum& d, float toplamSureS, const SeritRozet* rozetler, int rozetSayi, int kareSayi) { SeritOlay olay; const Tema& t = tema(); ImGui::PushID(kimlik); d.hizIndeks = ImClamp(d.hizIndeks, 0, seritHizSayi - 1); d.dongu = ImClamp(d.dongu, 0, 2); if (kareSayi < 2) kareSayi = 2; const float hiz = seritHizlar[d.hizIndeks]; const float kare = 1.0f / (float)kareSayi; auto atla = [&](float z) { z = ImClamp(z, 0.0f, 1.0f); if (z != d.zaman) { d.zaman = z; olay.degisti = true; } olay.kesin = true; }; // ---- Transport satırı ---- if (ImGui::SmallButton("⏮")) { d.oynat = false; d.geri = false; atla(0.0f); } ImGui::SetItemTooltip("Başa sar"); ImGui::SameLine(0, 3); if (ImGui::SmallButton("◁")) { d.oynat = false; atla((std::round(d.zaman / kare) - 1.0f) * kare); } ImGui::SetItemTooltip("Bir kare geri (←)"); ImGui::SameLine(0, 3); if (ImGui::SmallButton(d.oynat ? "⏸" : "▶")) { if (!d.oynat && d.dongu == 0 && d.zaman >= 1.0f) d.zaman = 0.0f; // bitti → baştan d.oynat = !d.oynat; d.geri = false; } ImGui::SetItemTooltip(d.oynat ? "Duraklat" : "Oynat"); ImGui::SameLine(0, 3); if (ImGui::SmallButton("▷")) { d.oynat = false; atla((std::round(d.zaman / kare) + 1.0f) * kare); } ImGui::SetItemTooltip("Bir kare ileri (→)"); ImGui::SameLine(0, 8); char hizEt[16]; ImFormatString(hizEt, sizeof(hizEt), "%g×", hiz); ImGui::SetNextItemWidth(62.0f); if (ImGui::BeginCombo("##serithiz", hizEt)) { for (int i = 0; i < seritHizSayi; ++i) { ImFormatString(hizEt, sizeof(hizEt), "%g×", seritHizlar[i]); if (ImGui::Selectable(hizEt, i == d.hizIndeks)) d.hizIndeks = i; } ImGui::EndCombo(); } ImGui::SetItemTooltip("Gerçek-zaman çarpanı (1× = gerçek süre)"); ImGui::SameLine(0, 3); static const char* kDonguSimge[3] = {"➡", "🔁", "🔃"}; static const char* kDonguAd[3] = {"Tek geçiş", "Döngü", "Ping-pong"}; if (ImGui::SmallButton(kDonguSimge[d.dongu])) { d.dongu = (d.dongu + 1) % 3; d.geri = false; } ImGui::SetItemTooltip("Döngü kipi: %s", kDonguAd[d.dongu]); ImGui::SameLine(0, 8); ImGui::TextColored(t.vurgu, "t = %.2f s", d.zaman * toplamSureS); ImGui::SameLine(0, 6); ImGui::TextColored(t.metinSoluk, "%.2f× gerçek zaman", hiz); // ---- Oynatma ilerlemesi (gerçek-zaman çarpanı; kare atlamasız sürekli) ---- if (d.oynat && toplamSureS > 1e-9f) { const float dz = ImGui::GetIO().DeltaTime * (seritHizlar[d.hizIndeks]) / toplamSureS; float z = d.zaman + (d.geri ? -dz : dz); if (d.dongu == 2) { // ping-pong: uçlarda yansıt if (z > 1.0f) { z = 2.0f - z; d.geri = true; } if (z < 0.0f) { z = -z; d.geri = false; } } else if (d.dongu == 1) { // döngü: sar if (z > 1.0f) z -= 1.0f; } else if (z >= 1.0f) { // tek geçiş: sonda dur z = 1.0f; d.oynat = false; olay.kesin = true; } d.zaman = ImClamp(z, 0.0f, 1.0f); olay.degisti = true; } // ---- Şerit gövdesi: rozet bandı + ray ---- const float gen = ImGui::GetContentRegionAvail().x; const float rozetH = 15.0f, rayH = 20.0f; const ImVec2 p = ImGui::GetCursorScreenPos(); ImGui::InvisibleButton("##seritray", {gen, rozetH + rayH}); const bool hover = ImGui::IsItemHovered(); const bool aktif = ImGui::IsItemActive(); const bool basildi = ImGui::IsItemActivated(); const bool birakildi = ImGui::IsItemDeactivated(); const ImGuiID id = ImGui::GetItemID(); const float x0 = p.x + 5.0f, x1 = p.x + gen - 5.0f; const float rayUst = p.y + rozetH, raySon = p.y + rozetH + rayH; const float rayOrta = rayUst + rayH * 0.5f; auto tx = [&](float tt) { return x0 + (x1 - x0) * ImClamp(tt, 0.0f, 1.0f); }; auto xt = [&](float x) { return x1 > x0 ? ImClamp((x - x0) / (x1 - x0), 0.0f, 1.0f) : 0.0f; }; const ImVec2 fare = ImGui::GetMousePos(); // Rozet hover (üst bant öncelikli). int rozHover = -1; if ((hover || aktif) && rozetler && fare.y < rayUst + 3.0f) { float enYakin = 8.0f; for (int i = 0; i < rozetSayi; ++i) { const float dd = std::fabs(fare.x - tx(rozetler[i].t)); if (dd < enYakin) { enYakin = dd; rozHover = i; } } } // Basma: rozet tıkı mı, scrub mı? (ImGuiStorage: 1 = scrub sürüyor) ImGuiStorage* depo = ImGui::GetStateStorage(); const ImGuiID kScrub = id + 1; if (basildi) { if (rozHover >= 0) { depo->SetInt(kScrub, 0); d.oynat = false; atla(rozetler[rozHover].t); // rozete tık → o ana atla } else { depo->SetInt(kScrub, 1); } } if (aktif && depo->GetInt(kScrub, 0) == 1) { // Kare-hassas scrub: fare konumu kare ızgarasına oturur. const float z = ImClamp(std::round(xt(fare.x) / kare) * kare, 0.0f, 1.0f); if (z != d.zaman) { d.zaman = z; d.oynat = false; olay.degisti = true; } } if (birakildi && depo->GetInt(kScrub, 0) == 1) { depo->SetInt(kScrub, 0); olay.degisti = olay.kesin = true; // bırakınca kesin son kare } // Ok tuşları (şerit hover'dayken): kare ileri/geri. if (hover && !aktif) { if (ImGui::IsKeyPressed(ImGuiKey_LeftArrow)) { d.oynat = false; atla((std::round(d.zaman / kare) - 1.0f) * kare); } if (ImGui::IsKeyPressed(ImGuiKey_RightArrow)) { d.oynat = false; atla((std::round(d.zaman / kare) + 1.0f) * kare); } } // ---- Çizim ---- ImDrawList* dl = ImGui::GetWindowDrawList(); const float sx = tx(d.zaman); // Ray zemini + dolan kısım (neon degrade) + kenarlık. const float rr = 4.0f; dl->AddRectFilled({x0, rayUst + 3.0f}, {x1, raySon - 3.0f}, u32(t.cerceve, 0.9f), rr); if (sx > x0 + 1.0f) { dl->AddRectFilledMultiColor({x0, rayUst + 3.0f}, {sx, raySon - 3.0f}, u32(t.vurgu, 0.28f), u32(t.vurgu, 0.65f), u32(t.vurgu, 0.65f), u32(t.vurgu, 0.28f)); dl->AddRectFilled({x0, rayUst + 3.0f}, {sx, raySon - 3.0f}, u32(t.vurgu, 0.10f * t.glowSiddet), rr); } dl->AddRect({x0, rayUst + 3.0f}, {x1, raySon - 3.0f}, u32(t.kenar), rr, 0, 1.0f); // Rozetler: bant üstünde elmas işaret + raya inen ince çizgi (bölüm işareti). for (int i = 0; i < rozetSayi; ++i) { const SeritRozet& rz = rozetler[i]; const float x = tx(rz.t); const bool h = i == rozHover; const ImVec4 rc = rz.renk.w > 0.0f ? rz.renk : t.vurgu2; const float buyu = yay(id + 100 + (ImGuiID)i, h ? 1.0f : 0.0f, 260.0f, 20.0f); const float r = 3.6f + 1.8f * buyu; const float my = p.y + rozetH * 0.5f; dl->AddLine({x, my + r}, {x, raySon - 4.0f}, u32(rc, 0.30f + 0.35f * buyu), 1.0f); if (h) dl->AddCircleFilled({x, my}, r + 4.0f, u32(rc, 0.20f * t.glowSiddet)); dl->AddQuadFilled({x, my - r}, {x + r, my}, {x, my + r}, {x - r, my}, u32(karisim(t.panel, rc, 0.75f), 0.95f)); dl->AddQuad({x, my - r}, {x + r, my}, {x, my + r}, {x - r, my}, u32(rc), 1.3f); } if (rozHover >= 0 && rozetler[rozHover].etiket && rozetler[rozHover].etiket[0]) ImGui::SetTooltip("%s\nTıkla → bu ana atla", rozetler[rozHover].etiket); // Playhead: dikey çizgi + tutamak üçgeni (vurgu2 neon). const bool phYakin = hover && std::fabs(fare.x - sx) < 10.0f; const float ph = yay(id + 7, (phYakin || aktif) ? 1.0f : 0.0f, 240.0f, 22.0f); dl->AddLine({sx, rayUst + 1.0f}, {sx, raySon - 1.0f}, u32(t.vurgu2, 0.55f + 0.4f * ph), 1.8f + ph); const float th = 5.0f + 1.5f * ph; if (ph > 0.02f) dl->AddCircleFilled({sx, raySon - 2.0f}, th + 4.0f, u32(t.vurgu2, 0.18f * ph)); dl->AddTriangleFilled({sx - th, raySon + 1.0f}, {sx + th, raySon + 1.0f}, {sx, raySon - th}, u32(karisim(t.vurgu2, t.metin, 0.15f))); ImGui::PopID(); return olay; } } // namespace fiko::ui