Support Twitch streaming, chroma key, errors, fixed modulation, node lifespan, fix bugs, many optimizations

This commit is contained in:
mid
2025-03-09 10:25:39 +02:00
parent 8f053bbdb1
commit 6fc29ba5f8
17 changed files with 1307 additions and 678 deletions

View File

@@ -28,93 +28,155 @@
#include<hi/linearity.h>
static void ShapeGrNode(GrNode *gn) {
if(gn->logical->type == CUTIHI_T('CPre','view')) {
gn->name = "Preview";
gn->sinks = {{"Video", GrNode::Port::Type::SAMPLE}};
} else if(gn->logical->type == CUTIHI_T('CMix','er ')) {
gn->name = "Mixer";
gn->sinks = {{"Sink 1", GrNode::Port::Type::SAMPLE}, {"Sink 2", GrNode::Port::Type::SAMPLE}};
gn->sources = {{"Audio", GrNode::Port::Type::SAMPLE}};
} else if(gn->logical->type == CUTIHI_T('CTex','t ')) {
gn->name = "Text";
gn->sinks = {{"Text", GrNode::Port::Type::TEXT}, {"Color", GrNode::Port::Type::COLOR}, {"DPI", GrNode::Port::Type::VEC1}};
gn->sources = {{"Sample", GrNode::Port::Type::SAMPLE}};
} else if(gn->logical->type == CUTIHI_T('CTim','e ')) {
gn->name = "Time";
gn->sinks = {};
gn->sources = {{"t", GrNode::Port::Type::VEC1}};
} else if(gn->logical->type == CUTIHI_T('CMod','ulat')) {
gn->name = "Modulate";
gn->sinks = {{"Sample", GrNode::Port::Type::SAMPLE}, {"Brightness", GrNode::Port::Type::VEC1}, {"Contrast", GrNode::Port::Type::VEC1}, {"Hue", GrNode::Port::Type::VEC1}};
gn->sources = {{"Sample", GrNode::Port::Type::SAMPLE}};
} else if(gn->logical->type == CUTIHI_T('CCns','tCol')) {
gn->name = "Constant";
gn->sinks = {{"Color", GrNode::Port::Type::COLOR}};
gn->sources = {{"Sample", GrNode::Port::Type::SAMPLE}};
} else if(gn->logical->type == CUTIHI_T('CEmb','ed ')) {
gn->name = "Embed";
gn->sinks = {
{"Frame", GrNode::Port::Type::SAMPLE},
{"Sub 1", GrNode::Port::Type::SAMPLE}, {" Pos", GrNode::Port::Type::VEC2}, {" Size", GrNode::Port::Type::VEC1},
{"Sub 2", GrNode::Port::Type::SAMPLE}, {" Pos", GrNode::Port::Type::VEC2}, {" Size", GrNode::Port::Type::VEC1},
{"Sub 3", GrNode::Port::Type::SAMPLE}, {" Pos", GrNode::Port::Type::VEC2}, {" Size", GrNode::Port::Type::VEC1}
};
gn->sources = {{"Sample", GrNode::Port::Type::SAMPLE}};
} else if(gn->logical->type == CUTIHI_T('CIma','ge ')) {
gn->name = "Image";
gn->sinks = {{"Filepath", GrNode::Port::Type::FILE_OPEN}};
gn->sources = {{"Sample", GrNode::Port::Type::SAMPLE}};
} else if(gn->logical->type == CUTIHI_T('CWin','dow ')) {
gn->name = "Window";
gn->sinks = {{"Name", GrNode::Port::Type::WINDOW_SOURCE}};
gn->sources = {{"Sample", GrNode::Port::Type::SAMPLE}};
} else if(gn->logical->type == CUTIHI_T('CInA','udio')) {
gn->name = "Microphone";
gn->sinks = {{"Source", GrNode::Port::Type::MIC_SOURCE}};
gn->sources = {{"Audio", GrNode::Port::Type::SAMPLE}};
} else if(gn->logical->type == CUTIHI_T('CExp','Wave')) {
gn->name = "Mux Wav";
gn->sinks = {{"Filename", GrNode::Port::Type::FILE_SAVE}, {"Audio", GrNode::Port::Type::SAMPLE}};
gn->sources = {};
} else if(gn->logical->type == CUTIHI_T('CMov','ie ')) {
gn->name = "Movie";
gn->sinks = {{"Filepath", GrNode::Port::Type::FILE_OPEN}, {"Time", GrNode::Port::Type::VEC1}};
gn->sources = {{"Sample", GrNode::Port::Type::SAMPLE}, {"Audio", GrNode::Port::Type::SAMPLE}};
} else if(gn->logical->type == CUTIHI_T('CEnc','GVP8')) {
gn->name = "Encode VP8";
gn->sinks = {{"Sample", GrNode::Port::Type::SAMPLE}};
gn->sources = {{"Bitstream"}};
} else if(gn->logical->type == CUTIHI_T('CEnc','GVP9')) {
gn->name = "Encode VP9";
gn->sinks = {{"Sample", GrNode::Port::Type::SAMPLE}};
gn->sources = {{"Bitstream"}};
} else if(gn->logical->type == CUTIHI_T('CExp','Webm')) {
gn->name = "Mux WebM";
gn->sinks = {{"Video Bitstream"}, {"Audio Bitstream"}, {"Filename", GrNode::Port::Type::FILE_SAVE}};
gn->sources = {};
} else if(gn->logical->type == CUTIHI_T('CKey','hook')) {
gn->name = "Keyhook";
gn->sinks = {{"Key", GrNode::Port::Type::VEC1}, {"Smooth Time", GrNode::Port::Type::VEC1}};
gn->sources = {{"Bool", GrNode::Port::Type::VEC1}};
} else if(gn->logical->type == CUTIHI_T('CEnc','Opus')) {
gn->name = "Encode Opus";
gn->sinks = {{"Audio", GrNode::Port::Type::SAMPLE}};
gn->sources = {{"Bitstream"}};
} else if(gn->logical->type == CUTIHI_T('CWeb','Cam ')) {
gn->name = "Live Digital Camera";
gn->sinks = {};
gn->sources = {{"Sample", GrNode::Port::Type::SAMPLE}};
} else if(gn->logical->type == CUTIHI_T('CCmp','nScl')) {
gn->name = "Scale";
gn->sinks = {{"Vector", GrNode::Port::Type::VEC4}, {"Sample", GrNode::Port::Type::SAMPLE}};
gn->sources = {{"Sample", GrNode::Port::Type::SAMPLE}};
static Frame *globaldis;
std::string node_name_from_id(uint64_t id) {
static std::unordered_map<uint64_t, std::string> NODE_ID_NAMES = {
{CUTIHI_T('CPre','view'), "Preview"},
{CUTIHI_T('CMix','er '), "Mixer"},
{CUTIHI_T('CTex','t '), "Text"},
{CUTIHI_T('CTim','e '), "Time"},
{CUTIHI_T('CMod','ulat'), "Modulate"},
{CUTIHI_T('CCns','tCol'), "Constant"},
{CUTIHI_T('CEmb','ed '), "Frame"},
{CUTIHI_T('CIma','ge '), "Image"},
{CUTIHI_T('CWin','dow '), "Window"},
{CUTIHI_T('CInA','udio'), "Microphone"},
{CUTIHI_T('CExp','Wave'), "Mux .wav"},
{CUTIHI_T('CMov','ie '), "Movie"},
{CUTIHI_T('CEnc','GVP8'), "Encode VP8"},
{CUTIHI_T('CEnc','GVP9'), "Encode VP9"},
{CUTIHI_T('CEnc','H264'), "Encode H264"},
{CUTIHI_T('CExp','Webm'), "Mux .webm"},
{CUTIHI_T('CKey','hook'), "Keyhook"},
{CUTIHI_T('CEnc','Opus'), "Encode Opus"},
{CUTIHI_T('CWeb','Cam '), "Live Digital Camera"},
{CUTIHI_T('CCmp','nScl'), "Scale"},
{CUTIHI_T('CChr','omaK'), "Chroma Key"},
{CUTIHI_T('CStr','RTMP'), "Stream RTMP"},
{CUTIHI_T('CEnc','AACL'), "Encode AAC-LC"},
};
auto nameit = NODE_ID_NAMES.find(id);
if(nameit == NODE_ID_NAMES.end()) {
char n[9] = {};
memcpy(n, &id, 8);
return "Unknown (" + std::string{n} + ")";
} else {
puts("Unknown node type.");
return nameit->second;
}
}
GrNode::Port::Port(wxString name) : Port(name, GrNode::Port::Type::NONE, false, 1, 1) {}
GrNode::Port::Port(wxString name, Type type) : Port(name, type, false, 1, 1) {}
GrNode::Port::Port(wxString name, Type type, bool separator) : Port(name, type, separator, 1, 1) {}
GrNode::Port::Port(wxString name, Type type, bool separator, float dragScalingX, float dragScalingY) : name(name), type(type), separator(separator) {
dragScaling[0] = dragScalingX;
dragScaling[1] = dragScalingY;
}
static void ShapeGrNode(GrNode *gn) {
gn->name = node_name_from_id(gn->logical->type);
if(gn->logical->type == CUTIHI_T('CPre','view')) {
gn->sinks = {{"Video", GrNode::Port::Type::SAMPLE}};
} else if(gn->logical->type == CUTIHI_T('CMix','er ')) {
gn->sources = {{"Audio", GrNode::Port::Type::SAMPLE}};
int numSinks = gn->logical->sinkCount;
while(numSinks > 0 && gn->logical->sinks[numSinks - 1].type == CUTIHI_VAL_NONE) {
numSinks--;
}
numSinks++;
gn->sinks.clear();
for(int s = 0; s < numSinks; s++) {
gn->sinks.push_back({std::string{"Sink "} + std::to_string(s + 1), GrNode::Port::Type::SAMPLE});
}
} else if(gn->logical->type == CUTIHI_T('CTex','t ')) {
gn->sinks = {{"Text", GrNode::Port::Type::TEXT}, {"Color", GrNode::Port::Type::COLOR}, {"DPI", GrNode::Port::Type::VEC1}, {"Font", GrNode::Port::Type::TEXT}};
gn->sources = {{"Sample", GrNode::Port::Type::SAMPLE}};
} else if(gn->logical->type == CUTIHI_T('CTim','e ')) {
gn->sinks = {};
gn->sources = {{"t", GrNode::Port::Type::VEC1}};
} else if(gn->logical->type == CUTIHI_T('CMod','ulat')) {
gn->sinks = {{"Sample", GrNode::Port::Type::SAMPLE}, {"Brightness", GrNode::Port::Type::VEC1, false, 0.01, 0}, {"Contrast", GrNode::Port::Type::VEC1, false, 0.01, 0}, {"Hue", GrNode::Port::Type::VEC1}};
gn->sources = {{"Sample", GrNode::Port::Type::SAMPLE}};
} else if(gn->logical->type == CUTIHI_T('CCns','tCol')) {
gn->sinks = {{"Color", GrNode::Port::Type::COLOR}, {"Resolution", GrNode::Port::Type::VEC2}};
gn->sources = {{"Sample", GrNode::Port::Type::SAMPLE}};
} else if(gn->logical->type == CUTIHI_T('CEmb','ed ')) {
gn->sources = {{"Sample", GrNode::Port::Type::SAMPLE}};
gn->sinks = {{"Frame", GrNode::Port::Type::SAMPLE, true}};
int numSubs = 0;
while(gn->logical->sinkCount > (1 + numSubs * 3 + 0) && gn->logical->sinks[1 + numSubs * 3 + 0].type != CUTIHI_VAL_NONE) {
numSubs++;
}
numSubs++;
for(int s = 0; s < numSubs; s++) {
gn->sinks.push_back({"Sub " + std::to_string(s + 1), GrNode::Port::Type::SAMPLE});
gn->sinks.push_back({" Pos", GrNode::Port::Type::VEC2});
gn->sinks.push_back({" Size", GrNode::Port::Type::VEC1, true});
}
} else if(gn->logical->type == CUTIHI_T('CIma','ge ')) {
gn->sinks = {{"Filepath", GrNode::Port::Type::FILE_OPEN}};
gn->sources = {{"Sample", GrNode::Port::Type::SAMPLE}};
} else if(gn->logical->type == CUTIHI_T('CWin','dow ')) {
gn->sinks = {{"Name", GrNode::Port::Type::WINDOW_SOURCE}};
gn->sources = {{"Sample", GrNode::Port::Type::SAMPLE}};
} else if(gn->logical->type == CUTIHI_T('CInA','udio')) {
gn->sinks = {{"Source", GrNode::Port::Type::MIC_SOURCE}};
gn->sources = {{"Audio", GrNode::Port::Type::SAMPLE}};
} else if(gn->logical->type == CUTIHI_T('CExp','Wave')) {
gn->sinks = {{"Filename", GrNode::Port::Type::FILE_SAVE}, {"Audio", GrNode::Port::Type::SAMPLE}};
gn->sources = {};
} else if(gn->logical->type == CUTIHI_T('CMov','ie ')) {
gn->sinks = {{"Filepath", GrNode::Port::Type::FILE_OPEN}, {"Time", GrNode::Port::Type::VEC1}};
gn->sources = {{"Sample", GrNode::Port::Type::SAMPLE}, {"Audio", GrNode::Port::Type::SAMPLE}};
} else if(gn->logical->type == CUTIHI_T('CEnc','GVP8')) {
gn->sinks = {{"Sample", GrNode::Port::Type::SAMPLE}};
gn->sources = {{"Bitstream"}};
} else if(gn->logical->type == CUTIHI_T('CEnc','GVP9')) {
gn->sinks = {{"Sample", GrNode::Port::Type::SAMPLE}};
gn->sources = {{"Bitstream"}};
} else if(gn->logical->type == CUTIHI_T('CEnc','H264')) {
gn->sinks = {{"Sample", GrNode::Port::Type::SAMPLE}};
gn->sources = {{"Bitstream"}};
} else if(gn->logical->type == CUTIHI_T('CExp','Webm')) {
gn->sinks = {{"Video Bitstream"}, {"Audio Bitstream"}, {"Filename", GrNode::Port::Type::FILE_SAVE}};
gn->sources = {};
} else if(gn->logical->type == CUTIHI_T('CKey','hook')) {
gn->sinks = {{"Key", GrNode::Port::Type::TEXT}, {"Smooth Time", GrNode::Port::Type::VEC1}};
gn->sources = {{"Bool", GrNode::Port::Type::VEC1}};
} else if(gn->logical->type == CUTIHI_T('CEnc','Opus')) {
gn->sinks = {{"Audio", GrNode::Port::Type::SAMPLE}};
gn->sources = {{"Bitstream"}};
} else if(gn->logical->type == CUTIHI_T('CWeb','Cam ')) {
gn->sinks = {};
gn->sources = {{"Sample", GrNode::Port::Type::SAMPLE}};
} else if(gn->logical->type == CUTIHI_T('CCmp','nScl')) {
gn->sinks = {{"Vector", GrNode::Port::Type::VEC4}, {"Sample", GrNode::Port::Type::SAMPLE}};
gn->sources = {{"Sample", GrNode::Port::Type::SAMPLE}};
} else if(gn->logical->type == CUTIHI_T('CChr','omaK')) {
gn->sinks = {{"Sample", GrNode::Port::Type::SAMPLE}, {"Color", GrNode::Port::Type::COLOR}};
gn->sources = {{"Sample", GrNode::Port::Type::SAMPLE}};
} else if(gn->logical->type == CUTIHI_T('CStr','RTMP')) {
gn->sinks = {{"Video Bitstream"}, {"Audio Bitstream"}, {"URL", GrNode::Port::Type::TEXT}};
gn->sources = {};
} else if(gn->logical->type == CUTIHI_T('CEnc','AACL')) {
gn->sinks = {{"Audio", GrNode::Port::Type::SAMPLE}};
gn->sources = {{"Bitstream"}};
}
gn->Fit();
}
Frame::Frame() : wxFrame(NULL, wxID_ANY, "Cuticle", wxDefaultPosition, {wxSystemSettings::GetMetric(wxSYS_SCREEN_X) / 2, wxSystemSettings::GetMetric(wxSYS_SCREEN_Y) / 2}) {
globaldis = this;
aui.SetManagedWindow(this);
viewer = new ImageViewer(this);
@@ -167,9 +229,7 @@ Frame::Frame() : wxFrame(NULL, wxID_ANY, "Cuticle", wxDefaultPosition, {wxSystem
}, f);
for(size_t n = 0; n < graph->backendNG->count; n++) {
GrNode *gn = *std::find_if(graph->gnodes.begin(), graph->gnodes.end(), [=](GrNode *gn){
return gn->logical == graph->backendNG->nodes[n];
});
GrNode *gn = graph->get_graphical(graph->backendNG->nodes[n]);
int32_t pos[2] = {gn->GetPosition().x, gn->GetPosition().y};
fwrite(pos, sizeof(pos), 1, f);
@@ -207,7 +267,6 @@ Frame::Frame() : wxFrame(NULL, wxID_ANY, "Cuticle", wxDefaultPosition, {wxSystem
gnode->logical = graph->backendNG->nodes[n];
ShapeGrNode(gnode);
gnode->Fit();
graph->gnodes.push_back(gnode);
}
@@ -238,10 +297,10 @@ Frame::Frame() : wxFrame(NULL, wxID_ANY, "Cuticle", wxDefaultPosition, {wxSystem
toolbar.durationEnable = new wxCheckBox(tlba, wxID_ANY, "");
toolbar.duration = new ctTimeCtrl(tlba, 120);
toolbar.duration->Bind(wxEVT_COMMAND_TEXT_UPDATED, [=](wxCommandEvent&){
CHi_SetDuration(graph->backendNG, toolbar.durationEnable->IsChecked() ? toolbar.duration->GetSeconds() : -1);
});
CHi_SetDuration(graph->backendNG, toolbar.duration->GetSeconds());
toolbar.durationEnable->SetValue(true);
toolbar.durationEnable->Bind(wxEVT_CHECKBOX, [=](wxCommandEvent&){
@@ -296,12 +355,23 @@ Frame::Frame() : wxFrame(NULL, wxID_ANY, "Cuticle", wxDefaultPosition, {wxSystem
});
};
graph->backendNG->eventOnError = +[](CHiNodeGraph *ng, CHiPubNode *n) {
wxTheApp->CallAfter([ng, n](){
auto frame = (Frame*) ng->ud;
GrNode *gn = frame->graph->get_graphical(n);
gn->Refresh();
});
};
tlba->Realize();
aui.SetFlags(wxAUI_MGR_LIVE_RESIZE | wxAUI_MGR_DEFAULT);
aui.Update();
Centre();
graph->CreatePreviewNode();
}
Frame::~Frame() {
@@ -313,7 +383,7 @@ bool GrNode::MouseOverPort(wxPoint point, bool &source, int &i) {
int p = (point.y - 26) / 20;
if((point.x >= 15 || p >= (int) sinks.size()) && (point.x < GetSize().x - 15 || p >= (int) sources.size())) return false;
int isSource = point.x >= GetSize().x - 10;
int isSource = point.x >= GetSize().x / 2;
source = isSource;
i = p;
@@ -329,11 +399,25 @@ void GrNode::MakeKeyframe(int sinkIdx) {
((Frame*) ng->GetParent())->timeline->Refresh();
}
static bool has_errors(CHiPubNode *pn) {
for(int i = 0; i < CUTIHI_MAX_ERRORS; i++) {
if(pn->errors.active[i]) return true;
}
return false;
}
GrNode::GrNode(NodeGraph *parent) : wxPanel(parent, wxID_ANY, {0, 0}, {175, 80}) {
Bind(wxEVT_PAINT, [this](wxPaintEvent &ev){
wxColour bgColor;
if(has_errors(logical)) {
bgColor = wxColour{168, 118, 118};
} else {
bgColor = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW);
}
wxPaintDC dc(this);
dc.SetBrush(wxBrush{wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)});
dc.SetPen(HasFocus() ? *wxRED_PEN : *wxBLACK_PEN);
dc.SetBrush(wxBrush{bgColor});
dc.SetPen(HasFocus() ? *wxGREY_PEN : *wxBLACK_PEN);
dc.DrawRoundedRectangle(5, 0, GetSize().x - 10, GetSize().y, 5);
dc.DrawText(name, GetSize().x / 2 - dc.GetTextExtent(name).x / 2, 2);
@@ -364,6 +448,10 @@ GrNode::GrNode(NodeGraph *parent) : wxPanel(parent, wxID_ANY, {0, 0}, {175, 80})
dc.DrawText(p.name, 15, (y += 20) - sz.y / 2);
dc.DrawCircle(5, y, 5);
if(p.separator) {
dc.DrawLine(5, y + 10, GetSize().x / 2, y + 10);
}
i++;
}
y = 13;
@@ -388,6 +476,10 @@ GrNode::GrNode(NodeGraph *parent) : wxPanel(parent, wxID_ANY, {0, 0}, {175, 80})
dc.DrawText(p.name, GetSize().x - sz.x - 15, (y += 20) - sz.y / 2);
dc.DrawCircle(GetSize().x - 6, y, 5);
if(p.separator) {
dc.DrawLine(GetSize().x / 2, y + 10, GetSize().x - 5, y + 10);
}
i++;
}
});
@@ -425,9 +517,38 @@ GrNode::GrNode(NodeGraph *parent) : wxPanel(parent, wxID_ANY, {0, 0}, {175, 80})
SetPosition(GetPosition() + neu - parent->dragPos);
parent->dragPos = neu;
}
} else if(wxGetMouseState().MiddleIsDown()) {
if(HasCapture()) {
wxPoint neu = ClientToScreen(ev.GetPosition());
wxPoint diff = neu - parent->dragPos;
parent->dragPos = neu;
CHiValue newv = *CHi_Crawl(&this->logical->sinks[sinkValueDragging]);
if(newv.type == CUTIHI_VAL_NONE) {
newv.type = CUTIHI_VAL_VEC4;
newv.data.vec4[0] = newv.data.vec4[1] = newv.data.vec4[2] = 0;
newv.data.vec4[3] = 1;
}
assert(newv.type == CUTIHI_VAL_VEC4);
newv.data.vec4[0] += diff.x * sinks[sinkValueDragging].dragScaling[0];
newv.data.vec4[1] += diff.y * sinks[sinkValueDragging].dragScaling[1];
pthread_mutex_lock(&this->logical->ng->mut);
CHi_ConfigureSink(this->logical, sinkValueDragging, newv);
pthread_mutex_unlock(&this->logical->ng->mut);
parent->Dirtify(this);
}
}
parent->Refresh();
SetFocus();
// Set focus if currently focused item is not a control/entry/editor
if(!dynamic_cast<wxControl*>(wxWindow::FindFocus())) {
SetFocus();
}
});
Bind(wxEVT_LEFT_UP, [parent, this](wxMouseEvent &ev){
if(HasCapture()) {
@@ -446,6 +567,7 @@ GrNode::GrNode(NodeGraph *parent) : wxPanel(parent, wxID_ANY, {0, 0}, {175, 80})
wxColourData data;
data.SetChooseFull(true);
data.SetChooseAlpha(true);
CHiValue *currentVal = CHi_Crawl(&this->logical->sinks[p]);
if(currentVal->type == CUTIHI_VAL_VEC4) {
@@ -458,7 +580,10 @@ GrNode::GrNode(NodeGraph *parent) : wxPanel(parent, wxID_ANY, {0, 0}, {175, 80})
}
wxColourDialog dlg(this, &data);
if(dlg.ShowModal() == wxID_OK) {
pthread_mutex_lock(&this->logical->ng->mut);
CHiValue newv;
newv.type = CUTIHI_VAL_VEC4;
newv.data.vec4[0] = dlg.GetColourData().GetColour().Red() / 255.f;
@@ -467,24 +592,34 @@ GrNode::GrNode(NodeGraph *parent) : wxPanel(parent, wxID_ANY, {0, 0}, {175, 80})
newv.data.vec4[3] = dlg.GetColourData().GetColour().Alpha() / 255.f;
CHi_ConfigureSink(this->logical, p, newv);
parent->Dirtify(this);
pthread_mutex_unlock(&this->logical->ng->mut);
}
} else if(sinks[p].type == Port::Type::FILE_OPEN) {
wxFileDialog dlg(this, wxFileSelectorPromptStr, wxEmptyString, wxEmptyString, wxFileSelectorDefaultWildcardStr, wxFD_OPEN | wxFD_PREVIEW);
if(dlg.ShowModal() == wxID_OK) {
pthread_mutex_lock(&this->logical->ng->mut);
CHiValue newv;
newv.type = CUTIHI_VAL_TEXT;
newv.data.text = strdup(dlg.GetPath().utf8_str());
CHi_ConfigureSink(this->logical, p, newv);
parent->Dirtify(this);
pthread_mutex_unlock(&this->logical->ng->mut);
}
} else if(sinks[p].type == Port::Type::FILE_SAVE) {
wxFileDialog dlg(this, wxFileSelectorPromptStr, wxEmptyString, wxEmptyString, wxFileSelectorDefaultWildcardStr, wxFD_SAVE | wxFD_PREVIEW | wxFD_OVERWRITE_PROMPT);
if(dlg.ShowModal() == wxID_OK) {
pthread_mutex_lock(&this->logical->ng->mut);
CHiValue newv;
newv.type = CUTIHI_VAL_TEXT;
newv.data.text = strdup(dlg.GetPath().utf8_str());
CHi_ConfigureSink(this->logical, p, newv);
parent->Dirtify(this);
pthread_mutex_unlock(&this->logical->ng->mut);
}
} else if(sinks[p].type >= Port::Type::VEC1 && sinks[p].type <= Port::Type::VEC4) {
auto ctrls = std::make_shared<std::vector<wxTextCtrl*>>();
@@ -494,6 +629,8 @@ GrNode::GrNode(NodeGraph *parent) : wxPanel(parent, wxID_ANY, {0, 0}, {175, 80})
if(ev.GetKeyCode() == WXK_RETURN) {
double d;
if(tc->GetValue().ToDouble(&d)) {
pthread_mutex_lock(&this->logical->ng->mut);
CHiValue newv = *CHi_Crawl(&this->logical->sinks[p]);
newv.type = CUTIHI_VAL_VEC4;
newv.data.vec4[i] = d;
@@ -505,6 +642,8 @@ GrNode::GrNode(NodeGraph *parent) : wxPanel(parent, wxID_ANY, {0, 0}, {175, 80})
CallAfter([tc](){tc->Destroy();});
parent->Dirtify(this);
pthread_mutex_unlock(&this->logical->ng->mut);
}
} else if(ev.GetKeyCode() == WXK_TAB) {
ctrls->operator[]((i + ctrls->size() + (wxGetKeyState(WXK_SHIFT) ? -1 : 1)) % ctrls->size())->SetFocus();
@@ -520,6 +659,8 @@ GrNode::GrNode(NodeGraph *parent) : wxPanel(parent, wxID_ANY, {0, 0}, {175, 80})
ctrl->SetValue(wxString{CHi_Crawl(&this->logical->sinks[p])->data.text});
ctrl->SetFocus();
ctrl->Bind(wxEVT_KILL_FOCUS, [=](wxFocusEvent &ev){
pthread_mutex_lock(&this->logical->ng->mut);
CHiValue newv;
newv.type = CUTIHI_VAL_TEXT;
char *c = (char*) malloc(ctrl->GetValue().Len() + 1);
@@ -528,9 +669,13 @@ GrNode::GrNode(NodeGraph *parent) : wxPanel(parent, wxID_ANY, {0, 0}, {175, 80})
CHi_ConfigureSink(this->logical, p, newv);
CallAfter([ctrl](){ctrl->Destroy();});
parent->Dirtify(this);
pthread_mutex_unlock(&this->logical->ng->mut);
});
ctrl->Bind(wxEVT_KEY_DOWN, [=](wxKeyEvent &ev){
if(ev.GetKeyCode() == WXK_RETURN) {
pthread_mutex_lock(&this->logical->ng->mut);
CHiValue newv;
newv.type = CUTIHI_VAL_TEXT;
char *c = (char*) malloc(ctrl->GetValue().Len() + 1);
@@ -539,6 +684,8 @@ GrNode::GrNode(NodeGraph *parent) : wxPanel(parent, wxID_ANY, {0, 0}, {175, 80})
CHi_ConfigureSink(this->logical, p, newv);
CallAfter([ctrl](){ctrl->Destroy();});
parent->Dirtify(this);
pthread_mutex_unlock(&this->logical->ng->mut);
} else ev.Skip();
});
} else if(sinks[p].type == Port::Type::MIC_SOURCE) {
@@ -551,11 +698,15 @@ GrNode::GrNode(NodeGraph *parent) : wxPanel(parent, wxID_ANY, {0, 0}, {175, 80})
wxSingleChoiceDialog dlg(this, "", "Choose Source", choices.size(), choices.data(), datae.data());
if(dlg.ShowModal() == wxID_OK) {
pthread_mutex_lock(&this->logical->ng->mut);
CHiValue newv;
newv.type = CUTIHI_VAL_TEXT;
newv.data.text = strdup(CHi_Microphone_GetSourceName((size_t) (uintptr_t) dlg.GetSelectionData()));
CHi_ConfigureSink(this->logical, p, newv);
parent->Dirtify(this);
pthread_mutex_unlock(&this->logical->ng->mut);
}
} else if(sinks[p].type == Port::Type::WINDOW_SOURCE) {
std::vector<const char*> choicesOrig;
@@ -570,11 +721,15 @@ GrNode::GrNode(NodeGraph *parent) : wxPanel(parent, wxID_ANY, {0, 0}, {175, 80})
wxSingleChoiceDialog dlg(this, "", "Choose Source", choices.size(), choices.data(), (void**) nullptr);
if(dlg.ShowModal() == wxID_OK) {
pthread_mutex_lock(&this->logical->ng->mut);
CHiValue newv;
newv.type = CUTIHI_VAL_TEXT;
newv.data.text = strdup(choicesOrig[dlg.GetSelection()]);
CHi_ConfigureSink(this->logical, p, newv);
parent->Dirtify(this);
pthread_mutex_unlock(&this->logical->ng->mut);
}
}
}
@@ -609,9 +764,13 @@ GrNode::GrNode(NodeGraph *parent) : wxPanel(parent, wxID_ANY, {0, 0}, {175, 80})
}
if(!isSource) {
pthread_mutex_lock(&daNode->ng->mut);
CHiValue val;
val.type = CUTIHI_VAL_NONE;
CHi_ConfigureSink(daNode, daPortIdx, val);
pthread_mutex_unlock(&daNode->ng->mut);
}
parent->Dirtify(this);
@@ -619,6 +778,27 @@ GrNode::GrNode(NodeGraph *parent) : wxPanel(parent, wxID_ANY, {0, 0}, {175, 80})
}
}
});
Bind(wxEVT_MIDDLE_DOWN, [parent, this](wxMouseEvent &ev){
bool isSource;
int p;
if(MouseOverPort(ev.GetPosition(), isSource, p)) {
if(!isSource && (sinks[p].type == GrNode::Port::Type::VEC1 || sinks[p].type == GrNode::Port::Type::VEC2 || sinks[p].type == GrNode::Port::Type::VEC3 || sinks[p].type == GrNode::Port::Type::VEC4)) {
CaptureMouse();
parent->dragPos = ClientToScreen(ev.GetPosition());
sinkValueDragging = p;
ev.StopPropagation();
}
}
});
Bind(wxEVT_MIDDLE_UP, [parent, this](wxMouseEvent &ev){
if(HasCapture()) {
ReleaseMouse();
ev.StopPropagation();
}
});
}
GrNode::~GrNode() {
@@ -719,8 +899,14 @@ void ImageViewer::SetImage(CHiImage *chim) {
ResizeImage(siez);
}
void ImageViewer::ResizeImage(float size) {
float w = size, h = (float) bufH / bufW * siez;
if(w <= 1 || h <= 1) {
return;
}
siez = size;
img.Rescale(siez, (float) bufH / bufW * siez);
img.Rescale(w, h);
bm = wxBitmap(img);
Refresh();
}
@@ -729,41 +915,46 @@ NodeGraph::NodeGraph(Frame *f) : wxPanel(f, wxID_ANY) {
backendNG = CHi_NewNodeGraph();
backendNG->ud = f;
{
GrNode *v = new GrNode(this);
v->logical = CHi_Preview();
ShapeGrNode(v);
CHi_RegisterNode(backendNG, v->logical);
gnodes.push_back(v);
}
Bind(wxEVT_CONTEXT_MENU, [this, f](wxContextMenuEvent &ev){
wxMenu *menuExports = new wxMenu();
int idEncodeH264 = menuExports->Append(wxID_ANY, "Encode H264", "")->GetId();
int idEncodeVp8 = menuExports->Append(wxID_ANY, "Encode VP8", "")->GetId();
int idEncodeVp9 = menuExports->Append(wxID_ANY, "Encode VP9", "")->GetId();
int idEncodeOpus = menuExports->Append(wxID_ANY, "Encode Opus", "")->GetId();
int idEncodeAAC = menuExports->Append(wxID_ANY, "Encode AAC-LC", "")->GetId();
int idMuxWebm = menuExports->Append(wxID_ANY, "Mux WebM", "")->GetId();
int idMuxWav = menuExports->Append(wxID_ANY, "Muv Wav", "")->GetId();
int idStreamRTMP = menuExports->Append(wxID_ANY, "Stream RTMP", "")->GetId();
wxMenu *menuRealtime = new wxMenu();
int idMicrophone = menuRealtime->Append(wxID_ANY, "Microphone", "")->GetId();
int idCamera = menuRealtime->Append(wxID_ANY, "Live Digital Camera", "")->GetId();
int idKeyhook = menuRealtime->Append(wxID_ANY, "Keyhook", "")->GetId();
int idWindow = menuRealtime->Append(wxID_ANY, "Window", "")->GetId();
wxMenu menu;
menu.Append(wxID_ANY, "Exports", menuExports);
menu.Append(wxID_ANY, "Realtime", menuRealtime);
menu.AppendSeparator();
int idConstant = menu.Append(wxID_ANY, "Constant", "")->GetId();
int idImage = menu.Append(wxID_ANY, "Image", "")->GetId();
int idMovie = menu.Append(wxID_ANY, "Movie", "")->GetId();
int idWindow = menu.Append(wxID_ANY, "Window", "")->GetId();
int idText = menu.Append(wxID_ANY, "Text", "")->GetId();
int idMicrophone = menu.Append(wxID_ANY, "Microphone", "")->GetId();
int idMixer = menu.Append(wxID_ANY, "Mixer", "")->GetId();
int idCamera = menu.Append(wxID_ANY, "Live Digital Camera", "")->GetId();
int idMixer = menu.Append(wxID_ANY, "Audio Mixer", "")->GetId();
int idTime = menu.Append(wxID_ANY, "Time", "")->GetId();
int idEmbed = menu.Append(wxID_ANY, "Embed", "")->GetId();
int idEmbed = menu.Append(wxID_ANY, "Frame", "")->GetId();
int idComponentScale = menu.Append(wxID_ANY, "Scale", "")->GetId();
int idModulate = menu.Append(wxID_ANY, "Modulate", "")->GetId();
int idKeyhook = menu.Append(wxID_ANY, "Keyhook (Live)", "")->GetId();
int idEncodeVp8 = menu.Append(wxID_ANY, "Encode VP8", "")->GetId();
int idEncodeVp9 = menu.Append(wxID_ANY, "Encode VP9", "")->GetId();
int idEncodeOpus = menu.Append(wxID_ANY, "Encode Opus", "")->GetId();
int idMuxWebm = menu.Append(wxID_ANY, "Mux WebM", "")->GetId();
int idMuxWav = menu.Append(wxID_ANY, "Muv Wav", "")->GetId();
int idChromaKey = menu.Append(wxID_ANY, "Chroma Key", "")->GetId();
wxPoint position = ScreenToClient(wxGetMousePosition());
menu.Bind(wxEVT_MENU, [=](wxCommandEvent &ev){
auto fn = [=](wxCommandEvent &ev) {
std::function<void()> after = [](){};
pthread_mutex_lock(&backendNG->mut);
GrNode *noed = nullptr;
if(ev.GetId() == idConstant) {
noed = new GrNode(this);
@@ -791,6 +982,9 @@ NodeGraph::NodeGraph(Frame *f) : wxPanel(f, wxID_ANY) {
float params[4] = {1};
CHi_SetExtrapolationMode(backendNG, noed->logical, portIdx, CUTIHI_EXTRAPOLATION_CONSTANT, params);
};
} else if(ev.GetId() == idEncodeH264) {
noed = new GrNode(this);
noed->logical = CHi_EncodeH264();
} else if(ev.GetId() == idEncodeVp9) {
noed = new GrNode(this);
noed->logical = CHi_EncodeVP9();
@@ -830,19 +1024,36 @@ NodeGraph::NodeGraph(Frame *f) : wxPanel(f, wxID_ANY) {
} else if(ev.GetId() == idKeyhook) {
noed = new GrNode(this);
noed->logical = CHi_Keyhook();
} else if(ev.GetId() == idChromaKey) {
noed = new GrNode(this);
noed->logical = CHi_ChromaKey();
} else if(ev.GetId() == idStreamRTMP) {
noed = new GrNode(this);
noed->logical = CHi_StreamRTMP();
} else if(ev.GetId() == idEncodeAAC) {
noed = new GrNode(this);
noed->logical = CHi_EncodeAAC();
}
if(noed) {
ShapeGrNode(noed);
noed->Fit();
noed->SetPosition(position);
CHi_RegisterNode(backendNG, noed->logical);
gnodes.push_back(noed);
after();
((Frame*) GetParent())->timeline->ResetRows();
}
});
pthread_mutex_unlock(&backendNG->mut);
};
menuExports->Bind(wxEVT_MENU, fn);
menuRealtime->Bind(wxEVT_MENU, fn);
menu.Bind(wxEVT_MENU, fn);
PopupMenu(&menu);
});
Bind(wxEVT_LEFT_DOWN, [this](wxMouseEvent &ev){
@@ -897,12 +1108,18 @@ NodeGraph::~NodeGraph() {
}
void NodeGraph::Alinken(Link l) {
pthread_mutex_lock(&backendNG->mut);
CHiValue newv;
newv.type = CUTIHI_VAL_LINKED;
newv.data.linked.to = l.output->logical;
newv.data.linked.idx = l.o;
if(!CHi_ConfigureSink(l.input->logical, l.i, newv)) {
int err = CHi_ConfigureSink(l.input->logical, l.i, newv);
pthread_mutex_unlock(&backendNG->mut);
if(!err) {
((Frame*) GetParent())->stba->SetStatusText("Uh oh! Hur-hur, there's a WAACKY cycle! Can't do, sorry friend.");
return;
}
@@ -914,6 +1131,8 @@ void NodeGraph::Alinken(Link l) {
}
}
ShapeGrNode(l.input);
links.push_back(l);
Dirtify(l.input);
@@ -921,7 +1140,7 @@ void NodeGraph::Alinken(Link l) {
}
void NodeGraph::Dirtify(GrNode *g) {
g->logical->clean = 0;
/*g->logical->clean = 0;
for(auto &it : links) {
if(it.output == g) {
Dirtify(it.input);
@@ -936,7 +1155,9 @@ void NodeGraph::Dirtify(GrNode *g) {
((Frame*) GetParent())->viewer->SetImage(val->data.sample);
}
}
}
}*/
CHi_Hysteresis(gnodes[0]->logical);
}
bool operator==(const NodeGraph::Link &l, const NodeGraph::Link &r) {
@@ -961,3 +1182,32 @@ bool NodeGraph::DetectCycles(GrNode *root) {
std::set<GrNode*> p{};
return dfs(this, p, root);
}
void NodeGraph::CreatePreviewNode() {
GrNode *v = new GrNode(this);
v->logical = CHi_Preview();
ShapeGrNode(v);
CHi_RegisterNode(backendNG, v->logical);
gnodes.push_back(v);
v->SetPosition(wxPoint{GetSize().x - v->GetSize().x - 8, 8});
// The Preview node is actually a NOOP
// Must inject our own function in Perform
v->logical->Perform = +[](CHiPubNode *preview){
CHiValue *val = CHi_Crawl(&preview->sinks[0]);
if(val->type == CUTIHI_VAL_SAMPLE && val->data.sample) {
globaldis->viewer->SetImage(val->data.sample);
}
return 1;
};
}
GrNode *NodeGraph::get_graphical(CHiPubNode *n) {
return *std::find_if(gnodes.begin(), gnodes.end(), [=](GrNode *gn){
return gn->logical == n;
});
}

View File

@@ -3,6 +3,7 @@
#include<vector>
#include<set>
#include<unordered_map>
#include<wx/frame.h>
#include<wx/panel.h>
#include<wx/aui/aui.h>
@@ -21,6 +22,8 @@ struct ImageViewer;
struct Timeline;
struct ctTimeCtrl;
std::string node_name_from_id(uint64_t id);
struct Frame : wxFrame {
wxAuiManager aui;
@@ -48,6 +51,14 @@ struct GrNode : wxPanel {
enum class Type {
NONE, FILE_OPEN, COLOR, VEC1, VEC2, VEC3, VEC4, TEXT, SAMPLE, FILE_SAVE, MIC_SOURCE, WINDOW_SOURCE
} type;
bool separator;
float dragScaling[2];
Port(wxString name);
Port(wxString name, Type type);
Port(wxString name, Type type, bool separator);
Port(wxString name, Type type, bool separator, float dragScalingX, float dragScalingY);
};
std::vector<Port> sinks;
std::vector<Port> sources;
@@ -56,6 +67,8 @@ struct GrNode : wxPanel {
CHiPubNode *logical;
int sinkValueDragging;
GrNode(NodeGraph*);
virtual ~GrNode();
@@ -120,6 +133,10 @@ struct NodeGraph : wxPanel {
void Dirtify(GrNode *g);
bool DetectCycles(GrNode*);
void CreatePreviewNode();
GrNode *get_graphical(CHiPubNode*);
};

View File

@@ -20,30 +20,113 @@ static T mod(T a, T b) {
#define ZERO_TIME_BASE 128
bool Timeline::MouseOverKF(wxPoint p, size_t &kfsIdxRet, size_t &kfIdxRet) {
auto f = (Frame*) GetParent();
Timeline::Row *Timeline::GetRow(int y) {
y = y / bmpKf.GetHeight();
int kfsIdx = p.y / bmpKf.GetHeight() - 1;
if(y == 0 || y > rows.size()) {
return nullptr;
}
if(kfsIdx < 0 || kfsIdx >= f->graph->backendNG->keyframesList.count) {
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, kfsIdx, t);
size_t idx = CHi_GetClosestKeyframe(f->graph->backendNG, kfs, t);
if(fabs(f->graph->backendNG->keyframesList.keyframes[kfsIdx]->times[idx] - t) > threshold) {
if(fabs(kfs->times[idx] - t) > threshold) {
return false;
}
kfsIdxRet = kfsIdx;
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};
@@ -53,7 +136,9 @@ Timeline::Timeline(struct Frame *parent) : wxPanel(parent, wxID_ANY) {
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) {
@@ -62,42 +147,39 @@ Timeline::Timeline(struct Frame *parent) : wxPanel(parent, wxID_ANY) {
});
Bind(wxEVT_LEFT_DOWN, [=](wxMouseEvent &ev){
Timeline::Row *row = GetRow(ev.GetPosition().y);
auto f = (Frame*) GetParent();
size_t kfsIdx, kfIdx;
if(MouseOverKF(ev.GetPosition(), kfsIdx, kfIdx)) {
captureMode = Timeline::CaptureMode::KF;
CaptureMouse();
mouseX = ev.GetX();
this->captureKfsIdx = kfsIdx;
this->captureKfIdx = kfIdx;
} else {
float t = (ev.GetX() + camX - ZERO_TIME_BASE) / (float) scale;
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
if(ev.ControlDown()) {
float minDist = FLT_MAX;
float newT = t;
for(size_t kfsIdx = 0; kfsIdx < f->graph->backendNG->keyframesList.count; kfsIdx++) {
size_t kfIdx = CHi_GetClosestKeyframe(f->graph->backendNG, kfsIdx, t);
float dist = fabs(f->graph->backendNG->keyframesList.keyframes[kfsIdx]->times[kfIdx] - t);
if(dist < minDist) {
minDist = dist;
newT = f->graph->backendNG->keyframesList.keyframes[kfsIdx]->times[kfIdx];
}
}
t = newT;
}
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){
@@ -105,6 +187,20 @@ Timeline::Timeline(struct Frame *parent) : wxPanel(parent, wxID_ANY) {
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();
@@ -112,9 +208,14 @@ Timeline::Timeline(struct Frame *parent) : wxPanel(parent, wxID_ANY) {
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) {
@@ -122,7 +223,7 @@ Timeline::Timeline(struct Frame *parent) : wxPanel(parent, wxID_ANY) {
float timeDiff = (float) diff / this->scale;
captureKfIdx = CHi_MoveKeyframeBy(f->graph->backendNG, f->graph->backendNG->keyframesList.keyframes[captureKfsIdx], captureKfIdx, timeDiff);
captureKfIdx = CHi_MoveKeyframeBy(f->graph->backendNG, captureKfs, captureKfIdx, timeDiff);
Refresh();
@@ -130,13 +231,13 @@ Timeline::Timeline(struct Frame *parent) : wxPanel(parent, wxID_ANY) {
}
mouseX = ev.GetX();
mouseY = ev.GetY();
} else {
// This is really baad..
size_t kfsIdx, kfIdx;
if(GetToolTipText() == "" && MouseOverKF(ScreenToClient(wxGetMousePosition()), kfsIdx, kfIdx)) {
CHiKeyframes *kfs = f->graph->backendNG->keyframesList.keyframes[kfsIdx];
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){
@@ -209,8 +310,9 @@ Timeline::Timeline(struct Frame *parent) : wxPanel(parent, wxID_ANY) {
Bind(wxEVT_CONTEXT_MENU, [=](wxContextMenuEvent &ev){
wxPoint position = ScreenToClient(wxGetMousePosition());
size_t kfsIdx, kfIdx;
if(MouseOverKF(ScreenToClient(wxGetMousePosition()), kfsIdx, kfIdx)) {
CHiKeyframes *kfs;
size_t kfIdx;
if(MouseOverKF(ScreenToClient(wxGetMousePosition()), kfs, kfIdx)) {
wxMenu menu;
int idDel = menu.Append(wxID_ANY, "Delete")->GetId();
@@ -218,7 +320,6 @@ Timeline::Timeline(struct Frame *parent) : wxPanel(parent, wxID_ANY) {
menu.Bind(wxEVT_MENU, [=](wxCommandEvent &ev){
if(ev.GetId() == idDel) {
auto f = (Frame*) GetParent();
auto kfs = f->graph->backendNG->keyframesList.keyframes[kfsIdx];
CHi_DeleteKeyframe(f->graph->backendNG, kfs, kfIdx);
@@ -242,31 +343,83 @@ void Timeline::Paint(wxPaintEvent &ev) {
dc.SetPen(wxPen{wxColour{160, 60, 60}});
wxColour defaultTextColor = dc.GetTextForeground();
dc.SetTextForeground(wxColour{160, 60, 60});
{
int x = CHi_Time_Get(frame->graph->backendNG) * scale - camX + ZERO_TIME_BASE;
dc.DrawLine(x, 0, x, GetSize().y);
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, x, 10);
dc.DrawText(wxString::Format("%gs", t), x + 4, 0);
dc.DrawLine(x, 0 - camY, x, 10 - camY);
dc.DrawText(wxString::Format("%gs", t), x + 4, 0 - camY);
t++;
}
auto kfsList = &frame->graph->backendNG->keyframesList;
for(size_t kfsIdx = 0; kfsIdx < kfsList->count; kfsIdx++) {
for(Timeline::Row &row : rows) {
CHiKeyframes *kfs = kfsList->keyframes[kfsIdx];
for(size_t kfIdx = 0; kfIdx < kfs->count; kfIdx++) {
wxBitmap &bmp = kfIdx == kfs->count - 1 && kfs->extrapolationMode != CUTIHI_EXTRAPOLATION_NONE ? bmpKfExtrap : bmpKf;
if(row.type == Timeline::Row::KEYFRAMES) {
CHiKeyframes *kfs = row.kfs;
dc.DrawBitmap(bmp, ZERO_TIME_BASE - camX + scale * kfs->times[kfIdx] - bmpKf.GetWidth() / 2, bmpKf.GetHeight() * (kfsIdx + 1));
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);
}
}

View File

@@ -5,13 +5,36 @@
#include<wx/panel.h>
struct Frame;
struct GrNode;
struct CHiKeyframes;
struct Timeline : wxPanel {
int64_t camX = 0;
int64_t camY = 0;
int64_t mouseX = 0;
int64_t mouseY = 0;
size_t captureKfsIdx, captureKfIdx;
CHiKeyframes *captureKfs;
size_t captureKfIdx;
struct Row {
enum Type {
KEYFRAMES,
NODE_LIFESPAN
};
int y, h;
Type type;
union {
CHiKeyframes *kfs;
GrNode *gn;
};
};
std::vector<Row> rows;
enum class CaptureMode {
CAM, KF
@@ -24,7 +47,11 @@ struct Timeline : wxPanel {
void Paint(wxPaintEvent&);
bool MouseOverKF(wxPoint p, size_t &kfsIdx, size_t &kfIdx);
Timeline::Row *GetRow(int y);
bool MouseOverKF(wxPoint p, CHiKeyframes* &kfs, size_t &kfIdx);
float SnapTime(float t);
void ResetRows();
};
#endif