#include"timeline.h" #include #include #include #include #include #include"frame.h" static wxBitmap bmpKf; static wxBitmap bmpKfExtrap; template static T mod(T a, T b) { return (a % b + b) % b; } #define ZERO_TIME_BASE 128 Timeline::Row *Timeline::GetRow(int y) { y = y / bmpKf.GetHeight(); if(y == 0 || y > rows.size()) { return nullptr; } y--; return &rows.at(y); } bool Timeline::MouseOverKF(wxPoint p, CHiKeyframes* &kfs, size_t &kfIdxRet) { Timeline::Row *row = GetRow(p.y); if(!row || row->type != Timeline::Row::KEYFRAMES) { return false; } auto f = (Frame*) GetParent(); kfs = row->kfs; float t = (p.x + camX - ZERO_TIME_BASE) / (float) scale; float threshold = bmpKf.GetWidth() / (float) scale / 2; size_t idx = CHi_GetClosestKeyframe(f->graph->backendNG, kfs, t); if(fabs(kfs->times[idx] - t) > threshold) { return false; } kfIdxRet = idx; return true; } void Timeline::ResetRows() { rows.clear(); auto frame = (Frame*) GetParent(); auto kfsList = &frame->graph->backendNG->keyframesList; int y = bmpKf.GetHeight(); for(size_t kfsIdx = 0; kfsIdx < kfsList->count; kfsIdx++) { rows.emplace_back(); Timeline::Row &row = rows.back(); row.type = Timeline::Row::KEYFRAMES; row.kfs = kfsList->keyframes[kfsIdx]; row.y = y; row.h = bmpKf.GetHeight(); y += row.h; } for(GrNode *gn : frame->graph->gnodes) { rows.emplace_back(); Timeline::Row &row = rows.back(); row.type = Timeline::Row::NODE_LIFESPAN; row.gn = gn; row.y = y; row.h = bmpKf.GetHeight(); y += row.h; } } float Timeline::SnapTime(float t) { float minDist = FLT_MAX; float newT = t; auto f = (Frame*) GetParent(); std::vector times; for(Timeline::Row &row : rows) { if(row.type == Timeline::Row::NODE_LIFESPAN) { times.push_back(row.gn->logical->lifespan.start); times.push_back(row.gn->logical->lifespan.end); } else if(row.type == Timeline::Row::KEYFRAMES) { for(size_t kfIdx = 0; kfIdx < row.kfs->count; kfIdx++) { times.push_back(row.kfs->times[kfIdx]); } } } if(times.size() == 0) { return t; } std::sort(times.begin(), times.end()); auto it = std::lower_bound(times.begin(), times.end(), t); if(it == times.end() || (it != times.begin() && (*it - t) >= (t - *std::prev(it)))) { it = std::prev(it); } return *it; } Timeline::Timeline(struct Frame *parent) : wxPanel(parent, wxID_ANY) { bmpKf = wxBitmap{"keyframe.bmp", wxBITMAP_TYPE_ANY}; bmpKfExtrap = wxBitmap{"keyframe_extrap.bmp", wxBITMAP_TYPE_ANY}; Bind(wxEVT_PAINT, &Timeline::Paint, this); Bind(wxEVT_MIDDLE_DOWN, [=](wxMouseEvent &ev){ captureMode = Timeline::CaptureMode::CAM; CaptureMouse(); mouseX = ev.GetX(); mouseY = ev.GetY(); }); Bind(wxEVT_MIDDLE_UP, [=](wxMouseEvent &ev){ if(HasCapture() && captureMode == Timeline::CaptureMode::CAM) { ReleaseMouse(); } }); Bind(wxEVT_LEFT_DOWN, [=](wxMouseEvent &ev){ Timeline::Row *row = GetRow(ev.GetPosition().y); auto f = (Frame*) GetParent(); float t = (ev.GetX() + camX - ZERO_TIME_BASE) / (float) scale; if(ev.ControlDown()) { t = SnapTime(t); } if(!row) { // Snap to closest keyframe, in all keyframes CHi_Time_Set(f->graph->backendNG, t < 0 ? 0 : t); Refresh(); f->graph->Dirtify(f->graph->gnodes[0]); } else if(row->type == Timeline::Row::KEYFRAMES) { CHiKeyframes *kfs; size_t kfIdx; if(MouseOverKF(ev.GetPosition(), kfs, kfIdx)) { captureMode = Timeline::CaptureMode::KF; CaptureMouse(); mouseX = ev.GetX(); mouseY = ev.GetY(); this->captureKfs = kfs; this->captureKfIdx = kfIdx; } } else if(row->type == Timeline::Row::NODE_LIFESPAN) { row->gn->logical->lifespan.start = t; Refresh(); } }); Bind(wxEVT_LEFT_UP, [=](wxMouseEvent &ev){ if(HasCapture() && captureMode == Timeline::CaptureMode::KF) { ReleaseMouse(); } }); Bind(wxEVT_RIGHT_DOWN, [=](wxMouseEvent &ev){ Timeline::Row *row = GetRow(ev.GetPosition().y); if(row && row->type == Timeline::Row::NODE_LIFESPAN) { float t = (ev.GetX() + camX - ZERO_TIME_BASE) / (float) scale; if(ev.ControlDown()) { t = SnapTime(t); } row->gn->logical->lifespan.end = t; Refresh(); } }); Bind(wxEVT_MOTION, [=](wxMouseEvent &ev){ auto f = (Frame*) GetParent(); if(HasCapture()) { if(captureMode == Timeline::CaptureMode::CAM) { camX += mouseX - ev.GetX(); camY += mouseY - ev.GetY(); if(camX < 0) { camX = 0; } if(camY < 0) { camY = 0; } Refresh(); } else if(captureMode == Timeline::CaptureMode::KF) { int64_t diff = ev.GetX() - mouseX; float timeDiff = (float) diff / this->scale; captureKfIdx = CHi_MoveKeyframeBy(f->graph->backendNG, captureKfs, captureKfIdx, timeDiff); Refresh(); f->graph->Dirtify(f->graph->gnodes[0]); } mouseX = ev.GetX(); mouseY = ev.GetY(); } else { // This is really baad.. CHiKeyframes *kfs; size_t kfIdx; if(GetToolTipText() == "" && MouseOverKF(ScreenToClient(wxGetMousePosition()), kfs, kfIdx)) { CHiPubNode *node = kfs->node; auto it = std::find_if(f->graph->gnodes.begin(), f->graph->gnodes.end(), [=](GrNode *g){ return g->logical == node; }); assert(it != f->graph->gnodes.end()); auto &sinks = (*it)->sinks; for(size_t i = 0; i < node->sinkCount; i++) { if(node->sinks[i].type == CUTIHI_VAL_KEYED && node->sinks[i].data.keyed == kfs) { CHiValueRaw *val = &node->sinks[i].data.keyed->values[kfIdx]; switch(sinks[i].type) { case GrNode::Port::Type::TEXT: case GrNode::Port::Type::FILE_OPEN: case GrNode::Port::Type::FILE_SAVE: SetToolTip(wxString{val->text}); break; case GrNode::Port::Type::VEC1: SetToolTip(wxString::Format("%g", val->vec4[0])); break; case GrNode::Port::Type::VEC2: SetToolTip(wxString::Format("(%g, %g)", val->vec4[0], val->vec4[1])); break; case GrNode::Port::Type::VEC3: SetToolTip(wxString::Format("(%g, %g, %g)", val->vec4[0], val->vec4[1], val->vec4[2])); break; case GrNode::Port::Type::VEC4: case GrNode::Port::Type::COLOR: SetToolTip(wxString::Format("(%g, %g, %g, %g)", val->vec4[0], val->vec4[1], val->vec4[2], val->vec4[3])); break; default: SetToolTip(" "); } break; } } } else { SetToolTip(nullptr); } } }); Bind(wxEVT_MOUSEWHEEL, [=](wxMouseEvent &ev){ int delta = ev.GetWheelRotation() / ev.GetWheelDelta(); while(delta > 0) { scale *= 2; if(scale > 800) { scale = 800; } delta--; } while(delta < 0) { scale /= 2; if(scale < 25) { scale = 25; } delta++; } Refresh(); }); Bind(wxEVT_CONTEXT_MENU, [=](wxContextMenuEvent &ev){ wxPoint position = ScreenToClient(wxGetMousePosition()); CHiKeyframes *kfs; size_t kfIdx; if(MouseOverKF(ScreenToClient(wxGetMousePosition()), kfs, kfIdx)) { wxMenu menu; int idDel = menu.Append(wxID_ANY, "Delete")->GetId(); menu.Bind(wxEVT_MENU, [=](wxCommandEvent &ev){ if(ev.GetId() == idDel) { auto f = (Frame*) GetParent(); CHi_DeleteKeyframe(f->graph->backendNG, kfs, kfIdx); Refresh(); } }); PopupMenu(&menu); } }); SetMinSize(wxSize{0, 64}); //SetSize(wxSize{GetSize().x, std::max(GetMinSize().y, GetSize().y)}); Fit(); } void Timeline::Paint(wxPaintEvent &ev) { auto frame = (Frame*) GetParent(); wxPaintDC dc{this}; dc.SetPen(wxPen{wxColour{160, 60, 60}}); wxColour defaultTextColor = dc.GetTextForeground(); dc.SetTextForeground(wxColour{160, 60, 60}); { float curTime = CHi_Time_Get(frame->graph->backendNG); int x = curTime * scale - camX + ZERO_TIME_BASE; if(x >= ZERO_TIME_BASE) { dc.DrawLine(x, 0, x, GetSize().y); } unsigned int H = (unsigned int) std::floor(curTime / 3600); unsigned int M = (unsigned int) std::floor(curTime / 60) % 60; float S = ::fmodf(curTime, 60); wxString s; if(H == 0 && M == 0) { s = wxString::Format("%.03f", S); } else if(H == 0) { s = wxString::Format("%02u:%.03f", M, S); } else { s = wxString::Format("%02u:%02u:%.03f", M, S); } wxCoord w, h; dc.GetTextExtent(s, &w, &h); dc.DrawText(s, x + 2, GetSize().y - h); } dc.SetTextForeground(defaultTextColor); dc.SetPen(wxPen{wxSystemSettings::GetColour(wxSYS_COLOUR_INACTIVECAPTIONTEXT)}); float t = std::ceil((float) camX / scale); for(int64_t x = ZERO_TIME_BASE + mod(-camX, scale); x < GetSize().x; x += scale) { dc.DrawLine(x, 0 - camY, x, 10 - camY); dc.DrawText(wxString::Format("%gs", t), x + 4, 0 - camY); t++; } for(Timeline::Row &row : rows) { if(row.type == Timeline::Row::KEYFRAMES) { CHiKeyframes *kfs = row.kfs; for(size_t kfIdx = 0; kfIdx < kfs->count; kfIdx++) { wxBitmap &bmp = kfIdx == kfs->count - 1 && kfs->extrapolationMode != CUTIHI_EXTRAPOLATION_NONE ? bmpKfExtrap : bmpKf; dc.DrawBitmap(bmp, ZERO_TIME_BASE - camX + scale * kfs->times[kfIdx] - bmpKf.GetWidth() / 2, row.y - camY); } } else if(row.type == Timeline::Row::NODE_LIFESPAN) { GrNode *gn = row.gn; float start = gn->logical->lifespan.start; float end = gn->logical->lifespan.end; if(end == 0) { end = gn->logical->ng->duration; } start *= scale; end *= scale; int32_t x1 = ZERO_TIME_BASE + std::max(0, start - camX); int32_t x2 = ZERO_TIME_BASE + std::max(0, end - camX); int32_t y = row.y - camY; int32_t h = row.h; dc.SetBrush(*wxLIGHT_GREY_BRUSH); dc.DrawRectangle(x1, y, x2 - x1, h); dc.DrawText(node_name_from_id(gn->logical->type), 0, y); } } }