Move to Git

This commit is contained in:
mid
2024-06-26 20:47:26 +03:00
commit 18da1dabcd
34 changed files with 5999 additions and 0 deletions

838
ui/frame.cpp Normal file
View File

@@ -0,0 +1,838 @@
#include"frame.h"
#include<wx/menu.h>
#include<wx/dcclient.h>
#include<wx/colourdata.h>
#include<wx/colordlg.h>
#include<wx/filedlg.h>
#include<wx/textctrl.h>
#include<wx/wrapsizer.h>
#include<wx/stattext.h>
#include"timectrl.h"
#include<algorithm>
#include<hi/mode.h>
#include<memory>
#include<tmmintrin.h>
#include<wx/settings.h>
#include"hi/microphone.h"
#include<wx/choicdlg.h>
#include<wx/app.h>
#include<thread>
#include<chrono>
#include"timeline.h"
#include<functional>
#include<algorithm>
#define SSE_MATHFUN_WITH_CODE
#include<hi/kumb.h>
#include<hi/linearity.h>
Frame::Frame() : wxFrame(NULL, wxID_ANY, "Cuticle", wxDefaultPosition, {wxSystemSettings::GetMetric(wxSYS_SCREEN_X) / 2, wxSystemSettings::GetMetric(wxSYS_SCREEN_Y) / 2}) {
aui.SetManagedWindow(this);
viewer = new ImageViewer(this);
graph = new NodeGraph(this);
compsets = new CompositionSettings(this);
timeline = new Timeline(this);
auto info = wxAuiPaneInfo().Center().Caption("Preview").BestSize(GetSize().x, GetSize().y * 3 / 4);
aui.AddPane(viewer, info);
info = wxAuiPaneInfo().Bottom().Caption("Node Graph").BestSize(GetSize().x, GetSize().y * 1 / 4);
aui.AddPane(graph, info);
info = wxAuiPaneInfo().Right().Caption("Composition");
aui.AddPane(compsets, info);
info = wxAuiPaneInfo().Bottom().Row(2).Caption("Timeline");
aui.AddPane(timeline, info);
stba = CreateStatusBar();
tlba = CreateToolBar();
static wxBitmap modeImgs[] = {
[CUTIHI_MODE_LIVE] = wxBitmap{"tlml.bmp", wxBITMAP_TYPE_ANY},
[CUTIHI_MODE_OFFLINE] = wxBitmap{"tlmo.bmp", wxBITMAP_TYPE_ANY}
};
int modeTool = tlba->AddTool(wxID_ANY, "Mode", wxBitmap{"tlml.bmp", wxBITMAP_TYPE_ANY})->GetId();
CHi_SetMode(CUTIHI_MODE_LIVE);
tlba->Bind(wxEVT_COMMAND_TOOL_CLICKED, [=](wxCommandEvent &ev){
if(ev.GetId() == modeTool) {
CHi_SetMode((CHiMode) ((CHi_GetMode() + 1) % 2));
tlba->SetToolNormalBitmap(modeTool, modeImgs[CHi_GetMode()]);
}
});
tlba->Realize();
aui.SetFlags(wxAUI_MGR_LIVE_RESIZE | wxAUI_MGR_DEFAULT);
aui.Update();
Centre();
}
Frame::~Frame() {
aui.UnInit();
}
bool GrNode::MouseOverPort(wxPoint point, bool &source, int &i) {
if(point.y < 26 || point.x < 0 || point.x > GetSize().x) return false;
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;
source = isSource;
i = p;
return true;
}
void GrNode::MakeKeyframe(int sinkIdx) {
auto ng = (NodeGraph*) GetParent();
CHi_MakeKeyframe(ng->backendNG, this->logical, sinkIdx);
((Frame*) ng->GetParent())->timeline->Refresh();
}
GrNode::GrNode(NodeGraph *parent) : wxPanel(parent, wxID_ANY, {0, 0}, {175, 80}) {
Bind(wxEVT_PAINT, [this](wxPaintEvent &ev){
wxPaintDC dc(this);
dc.SetBrush(wxBrush{wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)});
dc.SetPen(HasFocus() ? *wxRED_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);
bool hoverIsSource;
int hoverI;
bool hover = MouseOverPort(ScreenToClient(wxGetMousePosition()), hoverIsSource, hoverI);
int y = 13;
int i = 0;
for(Port &p : sinks) {
wxColour col = p.type == GrNode::Port::Type::FILE_OPEN ? wxColour{255, 0, 0}
: p.type == GrNode::Port::Type::FILE_SAVE ? wxColour{255, 0, 0}
: p.type == GrNode::Port::Type::COLOR ? wxColour{0, 0, 255}
: p.type == GrNode::Port::Type::VEC2 ? wxColour{0, 255, 0}
: p.type == GrNode::Port::Type::TEXT ? wxColour{255, 255, 0}
: p.type == GrNode::Port::Type::WINDOW_SOURCE ? wxColour{255, 255, 0}
: p.type == GrNode::Port::Type::SAMPLE ? wxColour{252, 111, 255}
: p.type == GrNode::Port::Type::MIC_SOURCE ? wxColour{255, 255, 255}
: wxColour{128, 128, 128};
if(hover && !hoverIsSource && i == hoverI) {
col.Set(std::min(col.Red() + 50, 255), std::min(col.Green() + 50, 255), std::min(col.Blue() + 50, 255));
}
wxSize sz = dc.GetTextExtent(p.name);
dc.SetBrush(wxBrush{col});
dc.DrawText(p.name, 15, (y += 20) - sz.y / 2);
dc.DrawCircle(5, y, 5);
i++;
}
y = 13;
i = 0;
for(Port &p : sources) {
wxColour col = p.type == GrNode::Port::Type::FILE_OPEN ? wxColour{255, 0, 0}
: p.type == GrNode::Port::Type::FILE_SAVE ? wxColour{255, 0, 0}
: p.type == GrNode::Port::Type::COLOR ? wxColour{0, 0, 255}
: p.type == GrNode::Port::Type::VEC2 ? wxColour{0, 255, 0}
: p.type == GrNode::Port::Type::TEXT ? wxColour{255, 255, 0}
: p.type == GrNode::Port::Type::WINDOW_SOURCE ? wxColour{255, 255, 0}
: p.type == GrNode::Port::Type::SAMPLE ? wxColour{252, 111, 255}
: p.type == GrNode::Port::Type::MIC_SOURCE ? wxColour{255, 255, 255}
: wxColour{128, 128, 128};
if(hover && hoverIsSource && i == hoverI) {
col.Set(std::min(col.Red() + 50, 255), std::min(col.Green() + 50, 255), std::min(col.Blue() + 50, 255));
}
wxSize sz = dc.GetTextExtent(p.name);
dc.SetBrush(wxBrush{col});
dc.DrawText(p.name, GetSize().x - sz.x - 15, (y += 20) - sz.y / 2);
dc.DrawCircle(GetSize().x - 6, y, 5);
i++;
}
});
Bind(wxEVT_LEFT_DOWN, [parent, this](wxMouseEvent &ev){
SetFocus();
parent->Refresh();
bool isSource;
int p;
if(MouseOverPort(ev.GetPosition(), isSource, p)) {
if(parent->attacheeNode && parent->attacheePortIsSource != isSource) {
if(parent->attacheePortIsSource) {
parent->Alinken({this, p, parent->attacheeNode, parent->attacheePort});
} else {
parent->Alinken({parent->attacheeNode, parent->attacheePort, this, p});
}
parent->attacheeNode = NULL;
} else {
parent->attacheeNode = this;
parent->attacheePort = p;
parent->attacheePortIsSource = isSource;
}
} else {
CaptureMouse();
parent->attacheeNode = NULL;
parent->dragged = this;
parent->dragPos = ClientToScreen(ev.GetPosition());
}
});
Bind(wxEVT_MOTION, [parent, this](wxMouseEvent &ev){
if(wxGetMouseState().LeftIsDown()) {
if(HasCapture()) {
wxPoint neu = ClientToScreen(ev.GetPosition());
SetPosition(GetPosition() + neu - parent->dragPos);
parent->dragPos = neu;
}
}
parent->Refresh();
SetFocus();
});
Bind(wxEVT_LEFT_UP, [parent, this](wxMouseEvent &ev){
if(HasCapture()) {
ReleaseMouse();
parent->dragged = NULL;
}
});
Bind(wxEVT_LEFT_DCLICK, [parent, this](wxMouseEvent &ev){
if(ev.GetPosition().y >= 26) {
parent->attacheeNode = NULL;
int p = (ev.GetPosition().y - 26) / 20;
if(p >= (int) sinks.size()) return;
if(sinks[p].type == Port::Type::COLOR) {
wxColourData data;
data.SetChooseFull(true);
CHiValue *currentVal = CHi_Crawl(&this->logical->sinks[p]);
if(currentVal->type == CUTIHI_VAL_VEC4) {
data.SetColour({
(uint8_t) (currentVal->data.vec4[0] * 255.f),
(uint8_t) (currentVal->data.vec4[1] * 255.f),
(uint8_t) (currentVal->data.vec4[2] * 255.f),
(uint8_t) (currentVal->data.vec4[3] * 255.f),
});
}
wxColourDialog dlg(this, &data);
if(dlg.ShowModal() == wxID_OK) {
CHiValue newv;
newv.type = CUTIHI_VAL_VEC4;
newv.data.vec4[0] = dlg.GetColourData().GetColour().Red() / 255.f;
newv.data.vec4[1] = dlg.GetColourData().GetColour().Green() / 255.f;
newv.data.vec4[2] = dlg.GetColourData().GetColour().Blue() / 255.f;
newv.data.vec4[3] = dlg.GetColourData().GetColour().Alpha() / 255.f;
CHi_ConfigureSink(this->logical, p, newv);
parent->Dirtify(this);
}
} 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) {
CHiValue newv;
newv.type = CUTIHI_VAL_TEXT;
newv.data.text = strdup(dlg.GetPath().utf8_str());
CHi_ConfigureSink(this->logical, p, newv);
parent->Dirtify(this);
}
} 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) {
CHiValue newv;
newv.type = CUTIHI_VAL_TEXT;
newv.data.text = strdup(dlg.GetPath().utf8_str());
CHi_ConfigureSink(this->logical, p, newv);
parent->Dirtify(this);
}
} else if(sinks[p].type >= Port::Type::VEC1 && sinks[p].type <= Port::Type::VEC4) {
auto ctrls = std::make_shared<std::vector<wxTextCtrl*>>();
for(int i = 0; i <= (int) sinks[p].type - (int) Port::Type::VEC1; i++) {
wxTextCtrl *tc = new wxTextCtrl(GetParent(), wxID_ANY, wxString::Format("%f", this->logical->sinks[p].data.vec4[i]), GetParent()->ScreenToClient(ClientToScreen({5 + 60 * i, (p + 1) * 20})));
tc->Bind(wxEVT_KEY_DOWN, [=](wxKeyEvent &ev){
if(ev.GetKeyCode() == WXK_RETURN) {
double d;
if(tc->GetValue().ToDouble(&d)) {
CHiValue newv = *CHi_Crawl(&this->logical->sinks[p]);
newv.type = CUTIHI_VAL_VEC4;
newv.data.vec4[i] = d;
CHi_ConfigureSink(this->logical, p, newv);
auto it = std::find(ctrls->begin(), ctrls->end(), tc);
ctrls->operator[]((it - ctrls->begin() + 1) % ctrls->size())->SetFocus();
ctrls->erase(it);
CallAfter([tc](){tc->Destroy();});
parent->Dirtify(this);
}
} else if(ev.GetKeyCode() == WXK_TAB) {
ctrls->operator[]((i + ctrls->size() + (wxGetKeyState(WXK_SHIFT) ? -1 : 1)) % ctrls->size())->SetFocus();
parent->Dirtify(this);
} else ev.Skip();
});
ctrls->push_back(tc);
}
ctrls->operator[](0)->SetFocus();
} else if(sinks[p].type == Port::Type::TEXT) {
wxTextCtrl *ctrl = new wxTextCtrl(GetParent(), wxID_ANY, this->logical->sinks[p].data.text, GetParent()->ScreenToClient(ClientToScreen({5, (p + 1) * 26})));
ctrl->SetValue(wxString{CHi_Crawl(&this->logical->sinks[p])->data.text});
ctrl->SetFocus();
ctrl->Bind(wxEVT_KILL_FOCUS, [=](wxFocusEvent &ev){
CHiValue newv;
newv.type = CUTIHI_VAL_TEXT;
char *c = (char*) malloc(ctrl->GetValue().Len() + 1);
memcpy(c, ctrl->GetValue().c_str(), ctrl->GetValue().Len() + 1);
newv.data.text = c;
CHi_ConfigureSink(this->logical, p, newv);
CallAfter([ctrl](){ctrl->Destroy();});
parent->Dirtify(this);
});
ctrl->Bind(wxEVT_KEY_DOWN, [=](wxKeyEvent &ev){
if(ev.GetKeyCode() == WXK_RETURN) {
CHiValue newv;
newv.type = CUTIHI_VAL_TEXT;
char *c = (char*) malloc(ctrl->GetValue().Len() + 1);
memcpy(c, ctrl->GetValue().c_str(), ctrl->GetValue().Len() + 1);
newv.data.text = c;
CHi_ConfigureSink(this->logical, p, newv);
CallAfter([ctrl](){ctrl->Destroy();});
parent->Dirtify(this);
} else ev.Skip();
});
} else if(sinks[p].type == Port::Type::MIC_SOURCE) {
std::vector<wxString> choices;
std::vector<void*> datae;
for(size_t i = CHi_Microphone_GetNextSource(-1); i < CHi_Microphone_GetSourceCount(); i = CHi_Microphone_GetNextSource(i)) {
choices.push_back(wxString::FromUTF8(CHi_Microphone_GetSourceName(i)));
datae.push_back((void*) (uintptr_t) i);
}
wxSingleChoiceDialog dlg(this, "", "Choose Source", choices.size(), choices.data(), datae.data());
if(dlg.ShowModal() == wxID_OK) {
CHiValue newv;
newv.type = CUTIHI_VAL_VEC4;
newv.data.vec4[0] = (size_t) (uintptr_t) dlg.GetSelectionData();
CHi_ConfigureSink(this->logical, p, newv);
parent->Dirtify(this);
}
} else if(sinks[p].type == Port::Type::WINDOW_SOURCE) {
std::vector<const char*> choicesOrig;
std::vector<wxString> choices;
for(size_t i = CHi_Window_GetNextSource(-1); i < CHi_Window_GetSourceCount(); i = CHi_Window_GetNextSource(i)) {
auto name = CHi_Window_GetSourceName(i);
choicesOrig.push_back(name);
choices.push_back(wxString::FromUTF8(name));
}
wxSingleChoiceDialog dlg(this, "", "Choose Source", choices.size(), choices.data(), (void**) nullptr);
if(dlg.ShowModal() == wxID_OK) {
CHiValue newv;
newv.type = CUTIHI_VAL_TEXT;
newv.data.text = strdup(choicesOrig[dlg.GetSelection()]);
CHi_ConfigureSink(this->logical, p, newv);
parent->Dirtify(this);
}
}
}
});
Bind(wxEVT_KEY_DOWN, [=](wxKeyEvent &ev){
if(ev.GetKeyCode() == 'I') {
bool isSource;
int p;
if(MouseOverPort(ev.GetPosition(), isSource, p) && !isSource) {
MakeKeyframe(p);
}
} else if(ev.GetKeyCode() == WXK_DELETE) {
bool isSource;
int p;
if(MouseOverPort(ev.GetPosition(), isSource, p)) {
CHiPubNode *daNode = this->logical;
int daPortIdx = p;
for(auto it = parent->links.begin(); it != parent->links.end(); it++) {
auto &link = *it;
if((isSource ? link.output : link.input) == this && (isSource ? link.o : link.i) == p) {
parent->links.erase(it);
if(isSource) {
daNode = link.input->logical;
daPortIdx = link.i;
isSource = false;
}
break;
}
}
if(!isSource) {
CHiValue val;
val.type = CUTIHI_VAL_NONE;
CHi_ConfigureSink(daNode, daPortIdx, val);
}
parent->Dirtify(this);
parent->Refresh();
}
}
});
}
GrNode::~GrNode() {
}
void GrNode::Fit() {
SetSize(GetSize().x, (std::max(sinks.size(), sources.size()) + 1) * 23);
}
ImageViewer::ImageViewer(Frame *f) : wxPanel(f, wxID_ANY) {
Bind(wxEVT_PAINT, [this](wxPaintEvent &ev){
if(bm.IsOk()) {
wxPaintDC dc(this);
dc.DrawBitmap(bm, pos);
}
});
Bind(wxEVT_MIDDLE_DOWN, [this](wxMouseEvent &ev){
CaptureMouse();
drag = ev.GetPosition();
});
Bind(wxEVT_MOTION, [this](wxMouseEvent &ev){
if(HasCapture()) {
pos += ev.GetPosition() - drag;
drag = ev.GetPosition();
Refresh();
}
});
Bind(wxEVT_MIDDLE_UP, [this](wxMouseEvent &ev){
if(HasCapture()) {
ReleaseMouse();
}
});
Bind(wxEVT_MOUSEWHEEL, [this](wxMouseEvent &ev){
img.SetData((unsigned char*) buf, bufW, bufH, true);
ResizeImage(siez + 25 * ev.GetWheelDelta() / ev.GetWheelRotation());
});
}
__attribute__((optimize("O3"))) static uint8_t *bgra64torgb24(uint8_t *orig, size_t stride, size_t w, size_t h) {
auto T0 = wxGetUTCTimeUSec();
uint8_t *ret = (uint8_t*) _mm_malloc(w * h * 3 + 16, 16);
#pragma omp parallel for
for(size_t y = 0; y < h; y++) {
uint8_t *temp = orig + stride * y;
uint8_t *dest = ret + 3 * w * y;
for(size_t x = 0; x < (w & ~15); x += 16, temp += 128, dest += 48) {
__m128i z[8] = {};
for(int zi = 0; zi < 8; zi++) {
z[zi] = _mm_loadu_si128((__m128i*) temp + zi);
z[zi] = apply_gamma_epi16(z[zi], _mm_set_ps(1, 1 / 2.2f, 1 / 2.2f, 1 / 2.2f));
}
__m128i a = _mm_shuffle_epi8(z[0], _mm_set_epi8(-128, -128, -128, -128, -128, -128, -128, -128, -128, -128, 9, 11, 13, 1, 3, 5));
__m128i b = _mm_shuffle_epi8(z[1], _mm_set_epi8(-128, -128, -128, -128, 9, 11, 13, 1, 3, 5, -128, -128, -128, -128, -128, -128));
__m128i c = _mm_shuffle_epi8(z[2], _mm_set_epi8(13, 1, 3, 5, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128));
__m128i d = _mm_shuffle_epi8(z[2], _mm_set_epi8(-128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, 9, 11));
__m128i e = _mm_shuffle_epi8(z[3], _mm_set_epi8(-128, -128, -128, -128, -128, -128, -128, -128, 9, 11, 13, 1, 3, 5, -128, -128));
__m128i f = _mm_shuffle_epi8(z[4], _mm_set_epi8(-128, -128, 9, 11, 13, 1, 3, 5, -128, -128, -128, -128, -128, -128, -128, -128));
__m128i g = _mm_shuffle_epi8(z[5], _mm_set_epi8(3, 5, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128));
__m128i h = _mm_shuffle_epi8(z[5], _mm_set_epi8(-128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, 9, 11, 13, 1));
__m128i i = _mm_shuffle_epi8(z[6], _mm_set_epi8(-128, -128, -128, -128, -128, -128, 9, 11, 13, 1, 3, 5, -128, -128, -128, -128));
__m128i j = _mm_shuffle_epi8(z[7], _mm_set_epi8(9, 11, 13, 1, 3, 5, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128));
_mm_storeu_si128((__m128i*) dest + 0, _mm_or_si128(_mm_or_si128(a, b), c));
_mm_storeu_si128((__m128i*) dest + 1, _mm_or_si128(_mm_or_si128(_mm_or_si128(d, e), f), g));
_mm_storeu_si128((__m128i*) dest + 2, _mm_or_si128(_mm_or_si128(h, i), j));
}
for(size_t x = w & ~15; x < w; x++, temp += 8, dest += 3) {
uint64_t s = *(uint64_t*) temp;
dest[0] = powf(((s >> 40) & 0xFF) / 255.f, 1 / 2.2f) * 255.f;
dest[1] = powf(((s >> 24) & 0xFF) / 255.f, 1 / 2.2f) * 255.f;
dest[2] = powf(((s >> 8) & 0xFF) / 255.f, 1 / 2.2f) * 255.f;
}
}
auto T1 = wxGetUTCTimeUSec();
printf("%f\n", (T1 - T0).ToDouble() / 1000);
return ret;
}
void ImageViewer::SetImage(CHiImage *chim) {
if(!chim) return;
if(buf) _mm_free(buf);
buf = bgra64torgb24((uint8_t*) chim->data16, chim->stride, bufW = chim->width, bufH = chim->height);
img.SetData((unsigned char*) buf, chim->width, chim->height, true);
ResizeImage(siez);
}
void ImageViewer::ResizeImage(float size) {
siez = size;
img.Rescale(siez, (float) bufH / bufW * siez);
bm = wxBitmap(img);
Refresh();
}
NodeGraph::NodeGraph(Frame *f) : wxPanel(f, wxID_ANY) {
backendNG = CHi_NewNodeGraph();
backendNG->ud = f;
{
GrNode *v = new GrNode(this);
v->logical = CHi_Preview();
v->name = "Preview";
v->sinks = {{"Video", GrNode::Port::Type::SAMPLE}};
CHi_RegisterNode(backendNG, v->logical);
gnodes.push_back(v);
}
Bind(wxEVT_CONTEXT_MENU, [this, f](wxContextMenuEvent &ev){
wxMenu menu;
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 idTime = menu.Append(wxID_ANY, "Time", "")->GetId();
int idEmbed = menu.Append(wxID_ANY, "Embed", "")->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();
wxPoint position = ScreenToClient(wxGetMousePosition());
menu.Bind(wxEVT_MENU, [=](wxCommandEvent &ev){
std::function<void()> after = [](){};
GrNode *noed = nullptr;
if(ev.GetId() == idConstant) {
noed = new GrNode(this);
noed->logical = CHi_ConstantSample();
printf("%p\n", noed->logical->sinks[0].data.vec4);
noed->name = "Constant";
noed->sinks = {{"Color", GrNode::Port::Type::COLOR}};
noed->sources = {{"Sample", GrNode::Port::Type::SAMPLE}};
} else if(ev.GetId() == idImage) {
noed = new GrNode(this);
noed->logical = CHi_Image();
noed->name = "Image";
noed->sinks = {{"Filepath", GrNode::Port::Type::FILE_OPEN}};
noed->sources = {{"Sample", GrNode::Port::Type::SAMPLE}};
} else if(ev.GetId() == idEmbed) {
noed = new GrNode(this);
noed->logical = CHi_Embed();
noed->name = "Embed";
noed->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}
};
noed->sources = {{"Sample", GrNode::Port::Type::SAMPLE}};
} else if(ev.GetId() == idModulate) {
noed = new GrNode(this);
noed->logical = CHi_Modulate();
noed->name = "Modulate";
noed->sinks = {{"Sample", GrNode::Port::Type::SAMPLE}, {"Brightness", GrNode::Port::Type::VEC1}, {"Contrast", GrNode::Port::Type::VEC1}, {"Hue", GrNode::Port::Type::VEC1}};
noed->sources = {{"Sample", GrNode::Port::Type::SAMPLE}};
} else if(ev.GetId() == idMovie) {
noed = new GrNode(this);
noed->logical = CHi_Movie();
noed->name = "Movie";
noed->sinks = {{"Filepath", GrNode::Port::Type::FILE_OPEN}, {"Time", GrNode::Port::Type::VEC1}};
noed->sources = {{"Sample", GrNode::Port::Type::SAMPLE}, {"Audio", GrNode::Port::Type::SAMPLE}};
after = [=](){
size_t portIdx = std::distance(noed->sinks.begin(), std::find_if(noed->sinks.begin(), noed->sinks.end(), [](GrNode::Port& p) -> bool {
return p.name == "Time";
}));
CHi_MakeKeyframe(backendNG, noed->logical, portIdx);
float params[4] = {1};
CHi_SetExtrapolationMode(backendNG, noed->logical, portIdx, CUTIHI_EXTRAPOLATION_CONSTANT, params);
};
} else if(ev.GetId() == idEncodeVp9) {
noed = new GrNode(this);
noed->logical = CHi_EncodeVP9();
noed->name = "Encode VP9";
noed->sinks = {{"Sample", GrNode::Port::Type::SAMPLE}};
noed->sources = {{"Bitstream"}};
} else if(ev.GetId() == idEncodeVp8) {
noed = new GrNode(this);
noed->logical = CHi_EncodeVP8();
noed->name = "Encode VP8";
noed->sinks = {{"Sample", GrNode::Port::Type::SAMPLE}};
noed->sources = {{"Bitstream"}};
} else if(ev.GetId() == idMuxWebm) {
noed = new GrNode(this);
noed->logical = CHi_MuxWebm();
noed->name = "Mux WebM";
noed->sinks = {{"Video Bitstream"}, {"Audio Bitstream"}, {"Filename", GrNode::Port::Type::FILE_SAVE}};
noed->sources = {};
} else if(ev.GetId() == idWindow) {
noed = new GrNode(this);
noed->logical = CHi_Window();
noed->name = "Window";
noed->sinks = {{"Name", GrNode::Port::Type::WINDOW_SOURCE}};
noed->sources = {{"Sample", GrNode::Port::Type::SAMPLE}};
} else if(ev.GetId() == idText) {
noed = new GrNode(this);
noed->logical = CHi_Text();
noed->name = "Text";
noed->sinks = {{"Text", GrNode::Port::Type::TEXT}, {"Color", GrNode::Port::Type::COLOR}, {"DPI", GrNode::Port::Type::VEC1}};
noed->sources = {{"Sample", GrNode::Port::Type::SAMPLE}};
} else if(ev.GetId() == idTime) {
noed = new GrNode(this);
noed->logical = CHi_Time();
noed->name = "Time";
noed->sinks = {};
noed->sources = {{"t", GrNode::Port::Type::VEC1}};
} else if(ev.GetId() == idMicrophone) {
noed = new GrNode(this);
noed->logical = CHi_Microphone();
noed->name = "Microphone";
noed->sinks = {{"Source", GrNode::Port::Type::MIC_SOURCE}};
noed->sources = {{"Audio", GrNode::Port::Type::SAMPLE}};
} else if(ev.GetId() == idMixer) {
noed = new GrNode(this);
noed->logical = CHi_Mixer();
noed->name = "Mixer";
noed->sinks = {{"Sink 1", GrNode::Port::Type::SAMPLE}, {"Sink 2", GrNode::Port::Type::SAMPLE}};
noed->sources = {{"Audio", GrNode::Port::Type::SAMPLE}};
} else if(ev.GetId() == idMuxWav) {
noed = new GrNode(this);
noed->logical = CHi_ExportWav();
noed->name = "Mux Wav";
noed->sinks = {{"Filename", GrNode::Port::Type::FILE_SAVE}, {"Audio", GrNode::Port::Type::SAMPLE}};
noed->sources = {};
} else if(ev.GetId() == idEncodeOpus) {
noed = new GrNode(this);
noed->logical = CHi_EncodeOpus();
noed->name = "Encode Opus";
noed->sinks = {{"Audio", GrNode::Port::Type::SAMPLE}};
noed->sources = {{"Bitstream"}};
} else if(ev.GetId() == idCamera) {
noed = new GrNode(this);
noed->logical = CHi_Camera();
noed->name = "Live Digital Camera";
noed->sinks = {};
noed->sources = {{"Sample", GrNode::Port::Type::SAMPLE}};
} else if(ev.GetId() == idComponentScale) {
noed = new GrNode(this);
noed->logical = CHi_ComponentScale();
noed->name = "Scale";
noed->sinks = {{"Vector", GrNode::Port::Type::VEC4}, {"Sample", GrNode::Port::Type::SAMPLE}};
noed->sources = {{"Sample", GrNode::Port::Type::SAMPLE}};
} else if(ev.GetId() == idKeyhook) {
noed = new GrNode(this);
noed->logical = CHi_Keyhook();
noed->name = "Keyhook";
noed->sinks = {{"Key", GrNode::Port::Type::VEC1}, {"Smooth Time", GrNode::Port::Type::VEC1}};
noed->sources = {{"Bool", GrNode::Port::Type::VEC1}};
}
if(noed) {
noed->Fit();
noed->SetPosition(position);
CHi_RegisterNode(backendNG, noed->logical);
gnodes.push_back(noed);
after();
}
});
PopupMenu(&menu);
});
Bind(wxEVT_LEFT_DOWN, [this](wxMouseEvent &ev){
SetFocusIgnoringChildren();
Refresh();
});
Bind(wxEVT_LEFT_UP, [this](wxMouseEvent &ev){
if(attacheeNode) attacheeNode = NULL;
});
Bind(wxEVT_MIDDLE_DOWN, [this](wxMouseEvent &ev){
dragged = nullptr;
dragPos = ClientToScreen(ev.GetPosition());
CaptureMouse();
});
Bind(wxEVT_MIDDLE_UP, [this](wxMouseEvent &ev){
if(HasCapture()) {
ReleaseMouse();
}
});
Bind(wxEVT_MOTION, [this](wxMouseEvent &ev) {
Refresh();
if(HasCapture()) {
wxPoint neu = ClientToScreen(ev.GetPosition());
for(auto gr : gnodes) {
gr->SetPosition(gr->GetPosition() + neu - dragPos);
}
dragPos = neu;
}
});
Bind(wxEVT_PAINT, [this](wxPaintEvent &ev) {
wxPaintDC dc(this);
wxPoint p[2];
for(Link l : links) {
p[0] = l.input->GetPosition() + wxPoint{0, 33 + 20 * l.i};
p[1] = l.output->GetPosition() + wxPoint{l.output->GetSize().x, 33 + 20 * l.o};
dc.DrawSpline(2, p);
}
if(attacheeNode) {
p[0] = attacheeNode->GetPosition() + wxPoint{attacheePortIsSource ? attacheeNode->GetSize().x : 0, 33 + 20 * attacheePort};
p[1] = ScreenToClient(wxGetMousePosition());
dc.DrawSpline(2, p);
}
});
}
NodeGraph::~NodeGraph() {
}
void NodeGraph::Alinken(Link l) {
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)) {
((Frame*) GetParent())->stba->SetStatusText("Uh oh! Hur-hur, there's a WAACKY cycle! Can't do, sorry friend.");
return;
}
for(auto it = links.begin(); it != links.end(); it++) {
if((*it).input == l.input && (*it).i == l.i) {
links.erase(it);
break;
}
}
links.push_back(l);
Dirtify(l.input);
Refresh();
}
void NodeGraph::Dirtify(GrNode *g) {
g->logical->clean = 0;
for(auto &it : links) {
if(it.output == g) {
Dirtify(it.input);
}
}
if(g == gnodes[0]) {
if(CHi_Hysteresis(g->logical)) {
CHiValue *val = CHi_Crawl(&g->logical->sinks[0]);
if(val->type == CUTIHI_VAL_SAMPLE && val->data.sample) {
((Frame*) GetParent())->viewer->SetImage(val->data.sample);
}
}
}
}
bool operator==(const NodeGraph::Link &l, const NodeGraph::Link &r) {
return l.input == r.input && l.i == r.i && l.output == r.output && l.o == r.o;
}
static bool dfs(NodeGraph *ng, std::set<GrNode*> &p, GrNode *g) {
p.insert(g);
bool cyclic = false;
for(const NodeGraph::Link &l : ng->links) {
if(l.output == g && (std::find(p.begin(), p.end(), g) != p.end() || dfs(ng, p, l.input))) {
cyclic = true;
break;
}
}
p.erase(std::find(p.begin(), p.end(), g));
return cyclic;
}
bool NodeGraph::DetectCycles(GrNode *root) {
std::set<GrNode*> p{};
return dfs(this, p, root);
}
CompositionSettings::CompositionSettings(Frame *parent) : wxPanel(parent, wxID_ANY) {
auto sz = new wxBoxSizer(wxVERTICAL);
sz->Add(this->durationEnable = new wxCheckBox(this, wxID_ANY, "Duration"), 0, wxALIGN_CENTER);
sz->Add(this->duration = new ctTimeCtrl(this, 120), 0, wxEXPAND);
durationEnable->SetValue(true);
durationEnable->Bind(wxEVT_CHECKBOX, [=](wxCommandEvent&){
duration->Enable(durationEnable->GetValue());
});
sz->Add(this->btnPerform = new wxButton(this, wxID_ANY, "Compile"), 0, wxEXPAND);
btnPerform->Bind(wxEVT_BUTTON, [=](wxCommandEvent &ev){
if(btnPerform->GetLabel() == "Kill") {
CHi_StopCompilation(parent->graph->backendNG);
btnPerform->Disable();
} else {
CHi_SetDuration(parent->graph->backendNG, durationEnable->IsChecked() ? duration->GetSeconds() : -1);
CHi_BeginCompilation(parent->graph->backendNG);
btnPerform->SetLabel("Kill");
std::thread{[=](){
while(parent->graph->backendNG->compilationStatus == CUTIHI_COMP_RUNNING) {
parent->CallAfter([=](){
float t = CHi_Time_Get(parent->graph->backendNG);
parent->stba->SetStatusText(wxString::Format("%02i:%02i:%06.03fs", (int) (t / 3600), (int) (t / 60), fmodf(t, 60)));
});
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
parent->CallAfter([=](){
parent->stba->SetStatusText("Compilation has ended.");
});
}}.detach();
}
});
parent->graph->backendNG->eventOnStopComplete = +[](CHiNodeGraph *ng){
wxTheApp->CallAfter([ng](){
wxButton *btn = ((Frame*) ng->ud)->compsets->btnPerform;
btn->Enable();
btn->SetLabel("Compile");
});
};
SetSizerAndFit(sz);
}

131
ui/frame.h Normal file
View File

@@ -0,0 +1,131 @@
#ifndef _CUTICLE_FRAME_H
#define _CUTICLE_FRAME_H
#include<vector>
#include<set>
#include<wx/frame.h>
#include<wx/panel.h>
#include<wx/aui/aui.h>
#include<wx/button.h>
#include<wx/spinctrl.h>
#include<wx/statusbr.h>
#include<wx/toolbar.h>
#include<wx/checkbox.h>
#include<hi/node.h>
#include<hi/img.h>
struct NodeGraph;
struct ImageViewer;
struct CompositionSettings;
struct Timeline;
struct Frame : wxFrame {
wxAuiManager aui;
ImageViewer *viewer;
NodeGraph *graph;
CompositionSettings *compsets;
Timeline *timeline;
wxStatusBar *stba;
wxToolBar *tlba;
Frame();
virtual ~Frame();
};
struct GrNode : wxPanel {
struct Port {
wxString name;
enum class Type {
NONE, FILE_OPEN, COLOR, VEC1, VEC2, VEC3, VEC4, TEXT, SAMPLE, FILE_SAVE, MIC_SOURCE, WINDOW_SOURCE
} type;
};
std::vector<Port> sinks;
std::vector<Port> sources;
wxString name;
CHiPubNode *logical;
GrNode(NodeGraph*);
virtual ~GrNode();
bool MouseOverPort(wxPoint p, bool &source, int &i);
void MakeKeyframe(int i);
virtual void Fit() override;
};
struct ctTimeCtrl;
struct CompositionSettings : wxPanel {
ctTimeCtrl *duration;
wxCheckBox *durationEnable;
wxButton *btnPerform;
CompositionSettings(Frame*);
virtual ~CompositionSettings() = default;
};
struct ImageViewer : wxPanel {
wxPoint pos;
wxImage img;
wxBitmap bm;
size_t bufW, bufH;
uint8_t *buf = nullptr;
float siez = 512;
wxPoint drag;
ImageViewer(Frame*);
virtual ~ImageViewer() = default;
void SetImage(CHiImage *img);
void ResizeImage(float);
};
struct NodeGraph : wxPanel {
struct Link {
GrNode *input;
int i;
GrNode *output;
int o;
struct Comparator {
bool operator ()(const Link &a, const Link &b) {
if(a.input != b.input) return a.input < b.input;
else if(a.i != b.i) return a.i < b.i;
else if(a.output != b.output) return a.output < b.output;
else return a.o < b.o;
}
};
};
GrNode *attacheeNode = NULL;
int attacheePort;
int attacheePortIsSource;
GrNode *dragged = NULL;
wxPoint dragPos;
CHiNodeGraph *backendNG = NULL;
std::vector<GrNode*> gnodes;
std::vector<Link> links;
NodeGraph(Frame*);
virtual ~NodeGraph();
void Alinken(Link l);
void Dirtify(GrNode *g);
bool DetectCycles(GrNode*);
};
#endif

12
ui/main.cpp Normal file
View File

@@ -0,0 +1,12 @@
#include<wx/wx.h>
#include"frame.h"
struct App : wxApp {
virtual bool OnInit() {
(new Frame())->Show(true);
return true;
}
};
wxIMPLEMENT_APP(App);

46
ui/textctrl.cpp Normal file
View File

@@ -0,0 +1,46 @@
#include"timectrl.h"
ctTimeCtrl::ctTimeCtrl(wxWindow *parent, double seconds) : wxTextCtrl(parent, wxID_ANY) {
SetSeconds(seconds);
Bind(wxEVT_CHAR, [this](wxKeyEvent& ev){
if(ev.GetKeyCode() == WXK_LEFT || ev.GetKeyCode() == WXK_RIGHT) ev.Skip();
else if(ev.GetKeyCode() >= '0' && ev.GetKeyCode() <= '9') {
uint32_t c = GetValue()[GetInsertionPoint()].GetValue();
if(c >= '0' && c <= '9') {
// SetValue calls EVT_TEXT
wxString neu = GetValue().Clone();
neu.SetChar(GetInsertionPoint(), ev.GetUnicodeKey());
SetValue(neu);
CallAfter([this](){
SetInsertionPoint(GetInsertionPoint() + 1);
});
}
} else if(GetInsertionPoint() < (long) GetValue().Length() && GetValue()[GetInsertionPoint()].GetValue() == ev.GetUnicodeKey()) {
CallAfter([this](){
SetInsertionPoint(GetInsertionPoint() + 1);
});
}
});
Bind(wxEVT_TEXT, [this](wxCommandEvent &ev){
long h, m;
double s;
GetValue().ToCLong(&h);
GetValue().Mid(3).ToCLong(&m);
GetValue().Mid(6).ToCDouble(&s);
this->seconds = (h * 60 + m) * 60 + s;
});
}
double ctTimeCtrl::GetSeconds() {
return seconds;
}
void ctTimeCtrl::SetSeconds(double seconds) {
this->seconds = seconds;
ChangeValue(wxString::Format("%02i:%02i:%02.3g", (int) (seconds / 3600), (int) (seconds / 60), fmodf(seconds, 60)));
}

14
ui/timectrl.h Normal file
View File

@@ -0,0 +1,14 @@
#pragma once
#include<wx/textctrl.h>
struct ctTimeCtrl : wxTextCtrl {
ctTimeCtrl(wxWindow *parent, double seconds);
virtual ~ctTimeCtrl() = default;
double GetSeconds();
void SetSeconds(double);
private:
double seconds;
};

273
ui/timeline.cpp Normal file
View File

@@ -0,0 +1,273 @@
#include"timeline.h"
#include<wx/menu.h>
#include<wx/dcclient.h>
#include<wx/settings.h>
#include<algorithm>
#include<float.h>
#include"frame.h"
static wxBitmap bmpKf;
static wxBitmap bmpKfExtrap;
template<typename T>
static T mod(T a, T b) {
return (a % b + b) % b;
}
#define ZERO_TIME_BASE 128
bool Timeline::MouseOverKF(wxPoint p, size_t &kfsIdxRet, size_t &kfIdxRet) {
auto f = (Frame*) GetParent();
int kfsIdx = p.y / bmpKf.GetHeight() - 1;
if(kfsIdx < 0 || kfsIdx >= f->graph->backendNG->keyframesList.count) {
return false;
}
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);
if(fabs(f->graph->backendNG->keyframesList.keyframes[kfsIdx]->times[idx] - t) > threshold) {
return false;
}
kfsIdxRet = kfsIdx;
kfIdxRet = idx;
return true;
}
Timeline::Timeline(struct Frame *parent) : wxPanel(parent, wxID_ANY) {
bmpKf = wxBitmap{"keyframe.bmp", wxBITMAP_TYPE_ANY};
bmpKfExtrap = wxBitmap{"keyframe_extrap.bmp", wxBITMAP_TYPE_ANY};
Bind(wxEVT_PAINT, &Timeline::Paint, this);
Bind(wxEVT_MIDDLE_DOWN, [=](wxMouseEvent &ev){
captureMode = Timeline::CaptureMode::CAM;
CaptureMouse();
mouseX = ev.GetX();
});
Bind(wxEVT_MIDDLE_UP, [=](wxMouseEvent &ev){
if(HasCapture() && captureMode == Timeline::CaptureMode::CAM) {
ReleaseMouse();
}
});
Bind(wxEVT_LEFT_DOWN, [=](wxMouseEvent &ev){
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;
// 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]);
}
});
Bind(wxEVT_LEFT_UP, [=](wxMouseEvent &ev){
if(HasCapture() && captureMode == Timeline::CaptureMode::KF) {
ReleaseMouse();
}
});
Bind(wxEVT_MOTION, [=](wxMouseEvent &ev){
auto f = (Frame*) GetParent();
if(HasCapture()) {
if(captureMode == Timeline::CaptureMode::CAM) {
camX += mouseX - ev.GetX();
if(camX < 0) {
camX = 0;
}
Refresh();
} else if(captureMode == Timeline::CaptureMode::KF) {
int64_t diff = ev.GetX() - mouseX;
float timeDiff = (float) diff / this->scale;
captureKfIdx = CHi_MoveKeyframeBy(f->graph->backendNG, f->graph->backendNG->keyframesList.keyframes[captureKfsIdx], captureKfIdx, timeDiff);
Refresh();
f->graph->Dirtify(f->graph->gnodes[0]);
}
mouseX = ev.GetX();
} else {
// This is really baad..
size_t kfsIdx, kfIdx;
if(GetToolTipText() == "" && MouseOverKF(ScreenToClient(wxGetMousePosition()), kfsIdx, kfIdx)) {
CHiKeyframes *kfs = f->graph->backendNG->keyframesList.keyframes[kfsIdx];
CHiPubNode *node = kfs->node;
auto it = std::find_if(f->graph->gnodes.begin(), f->graph->gnodes.end(), [=](GrNode *g){
return g->logical == node;
});
assert(it != f->graph->gnodes.end());
auto &sinks = (*it)->sinks;
for(size_t i = 0; i < node->sinkCount; i++) {
if(node->sinks[i].type == CUTIHI_VAL_KEYED && node->sinks[i].data.keyed == kfs) {
CHiValueRaw *val = &node->sinks[i].data.keyed->values[kfIdx];
switch(sinks[i].type) {
case GrNode::Port::Type::TEXT:
case GrNode::Port::Type::FILE_OPEN:
case GrNode::Port::Type::FILE_SAVE:
SetToolTip(wxString{val->text});
break;
case GrNode::Port::Type::VEC1:
SetToolTip(wxString::Format("%g", val->vec4[0]));
break;
case GrNode::Port::Type::VEC2:
SetToolTip(wxString::Format("(%g, %g)", val->vec4[0], val->vec4[1]));
break;
case GrNode::Port::Type::VEC3:
SetToolTip(wxString::Format("(%g, %g, %g)", val->vec4[0], val->vec4[1], val->vec4[2]));
break;
case GrNode::Port::Type::VEC4:
case GrNode::Port::Type::COLOR:
SetToolTip(wxString::Format("(%g, %g, %g, %g)", val->vec4[0], val->vec4[1], val->vec4[2], val->vec4[3]));
break;
default:
SetToolTip(" ");
}
break;
}
}
} else {
SetToolTip(nullptr);
}
}
});
Bind(wxEVT_MOUSEWHEEL, [=](wxMouseEvent &ev){
int delta = ev.GetWheelRotation() / ev.GetWheelDelta();
while(delta > 0) {
scale *= 2;
if(scale > 800) {
scale = 800;
}
delta--;
}
while(delta < 0) {
scale /= 2;
if(scale < 25) {
scale = 25;
}
delta++;
}
Refresh();
});
Bind(wxEVT_CONTEXT_MENU, [=](wxContextMenuEvent &ev){
wxPoint position = ScreenToClient(wxGetMousePosition());
size_t kfsIdx, kfIdx;
if(MouseOverKF(ScreenToClient(wxGetMousePosition()), kfsIdx, kfIdx)) {
wxMenu menu;
int idDel = menu.Append(wxID_ANY, "Delete")->GetId();
menu.Bind(wxEVT_MENU, [=](wxCommandEvent &ev){
if(ev.GetId() == idDel) {
auto f = (Frame*) GetParent();
auto kfs = f->graph->backendNG->keyframesList.keyframes[kfsIdx];
CHi_DeleteKeyframe(f->graph->backendNG, kfs, kfIdx);
Refresh();
}
});
PopupMenu(&menu);
}
});
SetMinSize(wxSize{0, 64});
//SetSize(wxSize{GetSize().x, std::max(GetMinSize().y, GetSize().y)});
Fit();
}
void Timeline::Paint(wxPaintEvent &ev) {
auto frame = (Frame*) GetParent();
wxPaintDC dc{this};
dc.SetPen(wxPen{wxColour{160, 60, 60}});
{
int x = CHi_Time_Get(frame->graph->backendNG) * scale - camX + ZERO_TIME_BASE;
dc.DrawLine(x, 0, x, GetSize().y);
}
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);
t++;
}
auto kfsList = &frame->graph->backendNG->keyframesList;
for(size_t kfsIdx = 0; kfsIdx < kfsList->count; kfsIdx++) {
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;
dc.DrawBitmap(bmp, ZERO_TIME_BASE - camX + scale * kfs->times[kfIdx] - bmpKf.GetWidth() / 2, bmpKf.GetHeight() * (kfsIdx + 1));
}
}
}

30
ui/timeline.h Normal file
View File

@@ -0,0 +1,30 @@
#ifndef _CUTICLE_TIMELINE_H
#define _CUTICLE_TIMELINE_H
#include<wx/frame.h>
#include<wx/panel.h>
struct Frame;
struct Timeline : wxPanel {
int64_t camX = 0;
int64_t mouseX = 0;
size_t captureKfsIdx, captureKfIdx;
enum class CaptureMode {
CAM, KF
} captureMode;
int scale = 100;
Timeline(struct Frame *parent);
virtual ~Timeline() = default;
void Paint(wxPaintEvent&);
bool MouseOverKF(wxPoint p, size_t &kfsIdx, size_t &kfIdx);
};
#endif