427 lines
		
	
	
		
			9.7 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			427 lines
		
	
	
		
			9.7 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| #include"timeline.h"
 | |
| 
 | |
| #include<wx/menu.h>
 | |
| #include<wx/dcclient.h>
 | |
| #include<wx/settings.h>
 | |
| 
 | |
| #include<algorithm>
 | |
| 
 | |
| #include<float.h>
 | |
| 
 | |
| #include"frame.h"
 | |
| 
 | |
| static wxBitmap bmpKf;
 | |
| static wxBitmap bmpKfExtrap;
 | |
| 
 | |
| template<typename T>
 | |
| 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<float> 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<int64_t>(-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<int32_t>(0, start - camX);
 | |
| 			int32_t x2 = ZERO_TIME_BASE + std::max<int32_t>(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);
 | |
| 		}
 | |
| 		
 | |
| 	}
 | |
| }
 | 
