Support Twitch streaming, chroma key, errors, fixed modulation, node lifespan, fix bugs, many optimizations
This commit is contained in:
488
ui/frame.cpp
488
ui/frame.cpp
@@ -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;
|
||||
});
|
||||
}
|
||||
|
||||
17
ui/frame.h
17
ui/frame.h
@@ -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*);
|
||||
};
|
||||
|
||||
|
||||
|
||||
263
ui/timeline.cpp
263
ui/timeline.cpp
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user