Move to EBML-based project file format
This commit is contained in:
261
ui/frame.cpp
261
ui/frame.cpp
@@ -22,13 +22,26 @@
|
||||
#include"timeline.h"
|
||||
#include<functional>
|
||||
#include<algorithm>
|
||||
#include<eebie/writer.h>
|
||||
|
||||
#define SSE_MATHFUN_WITH_CODE
|
||||
#include<hi/kumb.h>
|
||||
|
||||
#include<hi/linearity.h>
|
||||
|
||||
static Frame *globaldis;
|
||||
extern Frame *globaldis;
|
||||
|
||||
// The Preview node is actually a NOOP
|
||||
// Must inject our own function when either loading or creating projects
|
||||
static int INJECTED_PREVIEW_FUNC(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;
|
||||
};
|
||||
|
||||
std::string node_name_from_id(uint64_t id) {
|
||||
static std::unordered_map<uint64_t, std::string> NODE_ID_NAMES = {
|
||||
@@ -224,76 +237,40 @@ Frame::Frame() : wxFrame(NULL, wxID_ANY, "Cuticle", wxDefaultPosition, {wxSystem
|
||||
wxFileDialog save{this, "Save", "", "", "Cuticle Project (*.ctc)|*.ctc", wxFD_SAVE | wxFD_OVERWRITE_PROMPT};
|
||||
|
||||
if(save.ShowModal() == wxID_OK) {
|
||||
FILE *f = fopen(save.GetPath().mb_str(), "wb");
|
||||
struct UD {
|
||||
FILE *f;
|
||||
Frame *frame;
|
||||
} ud;
|
||||
|
||||
CHi_NodeGraphSave(graph->backendNG, +[](void *ud, const void *data, size_t len){
|
||||
auto f = (FILE*) ud;
|
||||
|
||||
return fwrite(data, 1, len, f);
|
||||
}, f);
|
||||
|
||||
for(size_t n = 0; n < graph->backendNG->count; 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);
|
||||
wxString path = save.GetPath();
|
||||
if(!path.EndsWith(".ctc")) {
|
||||
path += ".ctc";
|
||||
}
|
||||
|
||||
fclose(f);
|
||||
ud.f = fopen(path.mb_str(), "wb");
|
||||
ud.frame = this;
|
||||
|
||||
CHi_NodeGraphSave(graph->backendNG, +[](void *udPtr, const void *data, size_t len){
|
||||
auto &ud = *(UD*) udPtr;
|
||||
return fwrite(data, 1, len, ud.f);
|
||||
}, +[](void *udPtr, EBMLWriter *ebml, CHiPubNode *node){
|
||||
auto &ud = *(UD*) udPtr;
|
||||
|
||||
GrNode *gn = ud.frame->graph->get_graphical(node);
|
||||
|
||||
int32_t pos[2] = {gn->GetPosition().x, gn->GetPosition().y};
|
||||
ebml_writer_push(ebml, 0x5000);
|
||||
ebml_writer_put(ebml, 0x5001, EBML_BINARY, (EBMLPrimitive) {.binary = {.ptr = (uint8_t*) &pos, sizeof(pos)}});
|
||||
ebml_writer_pop(ebml);
|
||||
}, &ud);
|
||||
|
||||
fclose(ud.f);
|
||||
}
|
||||
} else if(ev.GetId() == wxID_OPEN) {
|
||||
wxFileDialog load{this, "Load", "", "", "Cuticle Project (*.ctc)|*.ctc", wxFD_OPEN | wxFD_FILE_MUST_EXIST};
|
||||
|
||||
if(load.ShowModal() == wxID_OK) {
|
||||
FILE *f = fopen(load.GetPath().mb_str(), "rb");
|
||||
|
||||
if(CHi_NodeGraphLoad(graph->backendNG, +[](void *ud, void *data, size_t len){
|
||||
auto f = (FILE*) ud;
|
||||
|
||||
return fread(data, 1, len, f);
|
||||
}, f) == 0) {
|
||||
|
||||
for(GrNode *gnode : graph->gnodes) {
|
||||
gnode->Destroy();
|
||||
}
|
||||
|
||||
graph->gnodes.clear();
|
||||
graph->links.clear();
|
||||
|
||||
for(size_t n = 0; n < graph->backendNG->count; n++) {
|
||||
GrNode *gnode = new GrNode(graph);
|
||||
|
||||
int32_t pos[2];
|
||||
fread(pos, sizeof(pos), 1, f);
|
||||
|
||||
gnode->SetPosition({pos[0], pos[1]});
|
||||
|
||||
gnode->logical = graph->backendNG->nodes[n];
|
||||
|
||||
ShapeGrNode(gnode);
|
||||
|
||||
graph->gnodes.push_back(gnode);
|
||||
}
|
||||
|
||||
for(size_t n = 0; n < graph->backendNG->count; n++) {
|
||||
CHiPubNode *node = graph->backendNG->nodes[n];
|
||||
|
||||
for(size_t s = 0; s < node->sinkCount; s++) {
|
||||
if(node->sinks[s].type == CUTIHI_VAL_LINKED) {
|
||||
graph->links.push_back(NodeGraph::Link{graph->gnodes[n], s, *std::find_if(graph->gnodes.begin(), graph->gnodes.end(), [=](GrNode *gn){ return gn->logical == node->sinks[s].data.linked.to; }), node->sinks[s].data.linked.idx});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
toolbar.duration->SetSeconds(std::max(0.f, graph->backendNG->duration));
|
||||
|
||||
if(graph->backendNG->duration <= 0) {
|
||||
toolbar.durationEnable->SetValue(false);
|
||||
toolbar.duration->Enable(false);
|
||||
}
|
||||
}
|
||||
|
||||
fclose(f);
|
||||
LoadProject(load.GetPath().utf8_string());
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -347,27 +324,6 @@ Frame::Frame() : wxFrame(NULL, wxID_ANY, "Cuticle", wxDefaultPosition, {wxSystem
|
||||
tlba->AddControl(toolbar.duration);
|
||||
tlba->AddControl(toolbar.btnPerform);
|
||||
|
||||
graph->backendNG->eventOnStopComplete = +[](CHiNodeGraph *ng){
|
||||
wxTheApp->CallAfter([ng](){
|
||||
wxButton *btn = ((Frame*) ng->ud)->toolbar.btnPerform;
|
||||
btn->Enable();
|
||||
btn->SetLabel("Compile");
|
||||
|
||||
auto tlba = ((Frame*) ng->ud)->tlba;
|
||||
tlba->EnableTool(wxID_SAVE, true);
|
||||
tlba->EnableTool(wxID_OPEN, true);
|
||||
});
|
||||
};
|
||||
|
||||
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);
|
||||
@@ -375,6 +331,7 @@ Frame::Frame() : wxFrame(NULL, wxID_ANY, "Cuticle", wxDefaultPosition, {wxSystem
|
||||
|
||||
Centre();
|
||||
|
||||
LoadProject("");
|
||||
graph->CreatePreviewNode();
|
||||
}
|
||||
|
||||
@@ -382,6 +339,101 @@ Frame::~Frame() {
|
||||
aui.UnInit();
|
||||
}
|
||||
|
||||
void Frame::LoadProject(std::string filename) {
|
||||
struct UD {
|
||||
FILE *f;
|
||||
Frame *frame;
|
||||
std::unordered_map<uint64_t, wxPoint> nodePositions;
|
||||
} ud = {};
|
||||
|
||||
if(filename != "") {
|
||||
ud.f = fopen(filename.c_str(), "rb");
|
||||
}
|
||||
ud.frame = this;
|
||||
|
||||
if(filename == "" || CHi_NodeGraphLoad(graph->backendNG, +[](void *udPtr, void *data, size_t len){
|
||||
auto &ud = *(UD*) udPtr;
|
||||
return fread(data, 1, len, ud.f);
|
||||
}, +[](void *udPtr, uint64_t nodeIdx, uint64_t elId, const void *data, size_t len){
|
||||
auto &ud = *(UD*) udPtr;
|
||||
if(elId == 0x5001) {
|
||||
auto pos = (const int32_t*) data;
|
||||
ud.nodePositions.emplace(nodeIdx, wxPoint{pos[0], pos[1]});
|
||||
}
|
||||
}, &ud) == 0) {
|
||||
|
||||
for(GrNode *gnode : graph->gnodes) {
|
||||
gnode->Destroy();
|
||||
}
|
||||
|
||||
graph->gnodes.clear();
|
||||
graph->links.clear();
|
||||
|
||||
for(size_t n = 0; n < graph->backendNG->count; n++) {
|
||||
GrNode *gnode = new GrNode(graph);
|
||||
|
||||
gnode->SetPosition(ud.nodePositions[n]);
|
||||
|
||||
gnode->logical = graph->backendNG->nodes[n];
|
||||
|
||||
ShapeGrNode(gnode);
|
||||
|
||||
if(gnode->logical->type == CUTIHI_T('CPre', 'view')) {
|
||||
gnode->logical->Perform = INJECTED_PREVIEW_FUNC;
|
||||
}
|
||||
|
||||
graph->gnodes.push_back(gnode);
|
||||
}
|
||||
|
||||
for(size_t n = 0; n < graph->backendNG->count; n++) {
|
||||
CHiPubNode *node = graph->backendNG->nodes[n];
|
||||
|
||||
for(size_t s = 0; s < node->sinkCount; s++) {
|
||||
if(node->sinks[s].linked.to) {
|
||||
graph->links.push_back(NodeGraph::Link{graph->gnodes[n], s, *std::find_if(graph->gnodes.begin(), graph->gnodes.end(), [=](GrNode *gn){ return gn->logical == node->sinks[s].linked.to; }), node->sinks[s].linked.idx});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
toolbar.duration->SetSeconds(std::max(0.f, graph->backendNG->duration));
|
||||
|
||||
if(graph->backendNG->duration <= 0) {
|
||||
toolbar.durationEnable->SetValue(false);
|
||||
toolbar.duration->Enable(false);
|
||||
}
|
||||
|
||||
timeline->ResetRows();
|
||||
|
||||
graph->backendNG->eventOnStopComplete = +[](CHiNodeGraph *ng){
|
||||
wxTheApp->CallAfter([ng](){
|
||||
wxButton *btn = ((Frame*) ng->ud)->toolbar.btnPerform;
|
||||
btn->Enable();
|
||||
btn->SetLabel("Compile");
|
||||
|
||||
auto tlba = ((Frame*) ng->ud)->tlba;
|
||||
tlba->EnableTool(wxID_SAVE, true);
|
||||
tlba->EnableTool(wxID_OPEN, true);
|
||||
});
|
||||
};
|
||||
|
||||
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();
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
if(ud.f) {
|
||||
fclose(ud.f);
|
||||
}
|
||||
|
||||
// Force everything in the graph to be computed so the preview immediately shows up
|
||||
CHi_Hysteresis(graph->backendNG);
|
||||
}
|
||||
|
||||
bool GrNode::MouseOverPort(wxPoint point, bool &source, int &i) {
|
||||
if(point.y < 26 || point.x < 0 || point.x > GetSize().x) return false;
|
||||
|
||||
@@ -400,7 +452,7 @@ void GrNode::MakeKeyframe(int sinkIdx) {
|
||||
|
||||
CHi_MakeKeyframe(ng->backendNG, this->logical, sinkIdx);
|
||||
|
||||
((Frame*) ng->GetParent())->timeline->Refresh();
|
||||
globaldis->timeline->ResetRows();
|
||||
}
|
||||
|
||||
static bool has_errors(CHiPubNode *pn) {
|
||||
@@ -1048,7 +1100,7 @@ NodeGraph::NodeGraph(Frame *f) : wxPanel(f, wxID_ANY) {
|
||||
|
||||
after();
|
||||
|
||||
((Frame*) GetParent())->timeline->ResetRows();
|
||||
globaldis->timeline->ResetRows();
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&backendNG->mut);
|
||||
@@ -1114,17 +1166,16 @@ 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;
|
||||
CHiValue newv = {};
|
||||
newv.linked.to = l.output->logical;
|
||||
newv.linked.idx = l.o;
|
||||
|
||||
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.");
|
||||
globaldis->stba->SetStatusText("Uh oh! Hur-hur, there's a WAACKY cycle! Can't do, sorry friend.");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1161,7 +1212,7 @@ void NodeGraph::Dirtify(GrNode *g) {
|
||||
}
|
||||
}*/
|
||||
|
||||
CHi_Hysteresis(gnodes[0]->logical);
|
||||
CHi_Hysteresis(backendNG);
|
||||
}
|
||||
|
||||
bool operator==(const NodeGraph::Link &l, const NodeGraph::Link &r) {
|
||||
@@ -1197,17 +1248,23 @@ void NodeGraph::CreatePreviewNode() {
|
||||
|
||||
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);
|
||||
v->logical->Perform = INJECTED_PREVIEW_FUNC;
|
||||
}
|
||||
|
||||
void NodeGraph::DestroyNode(GrNode *gn) {
|
||||
for(auto it = links.begin(); it != links.end();) {
|
||||
if(it->input == gn || it->output == gn) {
|
||||
it = links.erase(it);
|
||||
} else {
|
||||
it++;
|
||||
}
|
||||
|
||||
return 1;
|
||||
};
|
||||
}
|
||||
|
||||
gnodes.erase(std::find(gnodes.begin(), gnodes.end(), gn));
|
||||
|
||||
wxTheApp->CallAfter([=](){
|
||||
gn->Destroy();
|
||||
});
|
||||
}
|
||||
|
||||
GrNode *NodeGraph::get_graphical(CHiPubNode *n) {
|
||||
|
||||
@@ -43,6 +43,8 @@ struct Frame : wxFrame {
|
||||
|
||||
Frame();
|
||||
virtual ~Frame();
|
||||
|
||||
void LoadProject(std::string filename);
|
||||
};
|
||||
|
||||
struct GrNode : wxPanel {
|
||||
@@ -136,6 +138,8 @@ struct NodeGraph : wxPanel {
|
||||
|
||||
void CreatePreviewNode();
|
||||
|
||||
void DestroyNode(GrNode*);
|
||||
|
||||
GrNode *get_graphical(CHiPubNode*);
|
||||
};
|
||||
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
#include"frame.h"
|
||||
|
||||
Frame *globaldis;
|
||||
|
||||
struct App : wxApp {
|
||||
virtual bool OnInit() {
|
||||
(new Frame())->Show(true);
|
||||
@@ -9,4 +11,4 @@ struct App : wxApp {
|
||||
}
|
||||
};
|
||||
|
||||
wxIMPLEMENT_APP(App);
|
||||
wxIMPLEMENT_APP(App);
|
||||
|
||||
@@ -10,6 +10,8 @@
|
||||
|
||||
#include"frame.h"
|
||||
|
||||
extern Frame *globaldis;
|
||||
|
||||
static wxBitmap bmpKf;
|
||||
static wxBitmap bmpKfExtrap;
|
||||
|
||||
@@ -38,7 +40,7 @@ bool Timeline::MouseOverKF(wxPoint p, CHiKeyframes* &kfs, size_t &kfIdxRet) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto f = (Frame*) GetParent();
|
||||
auto f = globaldis;
|
||||
|
||||
kfs = row->kfs;
|
||||
|
||||
@@ -59,7 +61,7 @@ bool Timeline::MouseOverKF(wxPoint p, CHiKeyframes* &kfs, size_t &kfIdxRet) {
|
||||
void Timeline::ResetRows() {
|
||||
rows.clear();
|
||||
|
||||
auto frame = (Frame*) GetParent();
|
||||
auto frame = globaldis;
|
||||
|
||||
auto kfsList = &frame->graph->backendNG->keyframesList;
|
||||
|
||||
@@ -92,13 +94,15 @@ void Timeline::ResetRows() {
|
||||
|
||||
y += row.h;
|
||||
}
|
||||
|
||||
Refresh();
|
||||
}
|
||||
|
||||
float Timeline::SnapTime(float t) {
|
||||
float minDist = FLT_MAX;
|
||||
float newT = t;
|
||||
|
||||
auto f = (Frame*) GetParent();
|
||||
auto f = globaldis;
|
||||
|
||||
std::vector<float> times;
|
||||
for(Timeline::Row &row : rows) {
|
||||
@@ -149,7 +153,7 @@ 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();
|
||||
auto f = globaldis;
|
||||
|
||||
float t = (ev.GetX() + camX - ZERO_TIME_BASE) / (float) scale;
|
||||
if(ev.ControlDown()) {
|
||||
@@ -196,14 +200,14 @@ Timeline::Timeline(struct Frame *parent) : wxPanel(parent, wxID_ANY) {
|
||||
t = SnapTime(t);
|
||||
}
|
||||
|
||||
row->gn->logical->lifespan.end = t;
|
||||
row->gn->logical->lifespan.end = t < 0 ? 0 : t;
|
||||
|
||||
Refresh();
|
||||
}
|
||||
});
|
||||
|
||||
Bind(wxEVT_MOTION, [=](wxMouseEvent &ev){
|
||||
auto f = (Frame*) GetParent();
|
||||
auto f = globaldis;
|
||||
|
||||
if(HasCapture()) {
|
||||
if(captureMode == Timeline::CaptureMode::CAM) {
|
||||
@@ -319,7 +323,7 @@ Timeline::Timeline(struct Frame *parent) : wxPanel(parent, wxID_ANY) {
|
||||
|
||||
menu.Bind(wxEVT_MENU, [=](wxCommandEvent &ev){
|
||||
if(ev.GetId() == idDel) {
|
||||
auto f = (Frame*) GetParent();
|
||||
auto f = globaldis;
|
||||
|
||||
CHi_DeleteKeyframe(f->graph->backendNG, kfs, kfIdx);
|
||||
|
||||
@@ -337,7 +341,7 @@ Timeline::Timeline(struct Frame *parent) : wxPanel(parent, wxID_ANY) {
|
||||
}
|
||||
|
||||
void Timeline::Paint(wxPaintEvent &ev) {
|
||||
auto frame = (Frame*) GetParent();
|
||||
auto frame = globaldis;
|
||||
|
||||
wxPaintDC dc{this};
|
||||
|
||||
@@ -405,6 +409,10 @@ void Timeline::Paint(wxPaintEvent &ev) {
|
||||
|
||||
if(end == 0) {
|
||||
end = gn->logical->ng->duration;
|
||||
if(end == -1) {
|
||||
// Hack :)
|
||||
end = 3600 * 10;
|
||||
}
|
||||
}
|
||||
|
||||
start *= scale;
|
||||
|
||||
Reference in New Issue
Block a user