good graph editor

This commit is contained in:
lachrymaLF 2024-06-20 03:59:11 -04:00
parent d639ee8b9b
commit b80a0db027
4 changed files with 281 additions and 122 deletions

View file

@ -130,13 +130,13 @@ namespace K {
const bgfx::Stats *stat = bgfx::getStats();
const bgfx::Caps *caps = bgfx::getCaps();
bgfx::dbgTextClear();
bgfx::dbgTextPrintf(0, app_state.window_height/16 - 4, 0xf8,
bgfx::dbgTextPrintf(0, app_state.window_height/16 - 8, 0xf8,
" lachrymal.net :: %s Renderer :: Max Views %u :: Max FBs %u :: Max Texture Samplers %u :: Blitting %s :: %u FPS",
caps->supported & BGFX_CAPS_RENDERER_MULTITHREADED ? "Multithreaded" : "Singlethreaded",
caps->limits.maxViews, caps->limits.maxFrameBuffers,
caps->limits.maxTextureSamplers, caps->supported & BGFX_CAPS_TEXTURE_BLIT ? "OK" : "NO",
stat->cpuTimerFreq / stat->cpuTimeFrame);
bgfx::dbgTextPrintf(0, app_state.window_height/16 - 3, 0xf8,
bgfx::dbgTextPrintf(0, app_state.window_height/16 - 7, 0xf8,
" Project Keishiki :: %s :: Build %s %s :: SDL %i.%i.%i :: bgfx 1.%i :: Dear ImGui %s :: %s",
BX_COMPILER_NAME, __DATE__, __TIME__, SDL_MAJOR_VERSION, SDL_MINOR_VERSION, SDL_PATCHLEVEL,
BGFX_API_VERSION, ImGui::GetVersion(), bgfx::getRendererName(bgfx::getRendererType()));

View file

@ -403,6 +403,13 @@ namespace K::UI {
return std::clamp(static_cast<u64>(std::round((view / view_width * view_amt + view_left) * static_cast<f32>(frame_max + 1))), static_cast<u64>(0), frame_max);
}
struct PlotInfo {
f32 begin, end;
i32 samples;
const CompState &s;
PlugboardGraph::ConnectInfo &connected_v;
};
void Composition(CompState& s) {
if (!ImGui::Begin("Composition", &draw_comp, ImGuiWindowFlags_NoScrollbar)) {
ImGui::End();
@ -481,8 +488,9 @@ namespace K::UI {
ImGui::Checkbox("Chain Editor", &show_curve_editor);
ImGuiIO& io = ImGui::GetIO();
ImRect drag_rect{io.MousePos, io.MousePos};
drag_rect.Add(io.MouseClickedPos[ImGuiMouseButton_Left]);
ImGuiStyle style = ImGui::GetStyle();
ImVec2 avail = ImGui::GetContentRegionAvail();
static f32 view_scale = 1.0f;
static ImVec2 view_pos{}, old_view_pos{}, nodes_begin{};
@ -491,8 +499,8 @@ namespace K::UI {
ImVec2 drag_source{};
ImGui::PushStyleColor(ImGuiCol_ChildBg, 0xFF100500);
ImGui::SetNextWindowSizeConstraints({avail.x * .1f, -1}, { avail.x * .8f, -1 });
if (ImGui::BeginChild("Nodes", {avail.x * .4f, avail.y}, ImGuiChildFlags_ResizeX, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoScrollbar)) {
ImGui::SetNextWindowSizeConstraints({ImGui::GetContentRegionAvail().x * .1f, -1}, { ImGui::GetContentRegionAvail().x * .8f, -1 });
if (ImGui::BeginChild("Nodes", {ImGui::GetContentRegionAvail().x * .4f, ImGui::GetContentRegionAvail().y}, ImGuiChildFlags_ResizeX, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoScrollbar)) {
static u32 selected;
ImGuiWindow *window = ImGui::GetCurrentWindow();
@ -548,6 +556,7 @@ namespace K::UI {
ImGui::SameLine(0.0f, 0.5f);
// Timeline setup
f32 table_left = ImGui::GetCursorScreenPos().x;
ImVec2 tl_init_pos, tl_end_begin;
f32 view_width,
@ -562,17 +571,27 @@ namespace K::UI {
f32 view_amt = view_right - view_left, mouse_tl_x;
f32 fr_step;
static Vector<PlugboardGraph::NodeInstance*> selected_chains{};
// Mouse Handlers
// Drag playhead/pan
static bool tl_clear_selection_request = false;
bool tl_clear_selection_request_this_frame = tl_clear_selection_request;
auto draw_tl_bg_drag_area = [&view_width, &style, &view_amt, &s, &mouse_tl_x](f32 h = 0.0f) {
static bool bg_drag_select_active = false;
static Vector<PlugboardNodes::ChainSegment*> keys_selected_by_bg_drag{};
auto draw_tl_bg_drag_area = [&view_width, &style, &view_amt, &s, &mouse_tl_x, &io](f32 h = 0.0f, f32 w = 0.0f) {
static f32 pan_x, view_left_old, view_right_old;
ImGui::InvisibleButton("##TL_BG", ImVec2{ view_width + style.CellPadding.x * 2, h == 0.0f ? row_height + style.CellPadding.y : h});
ImGui::InvisibleButton("##TL_BG", ImVec2{ w == 0.0f ? view_width + style.CellPadding.x * 2 : w, h == 0.0f ? row_height + style.CellPadding.y : h});
if (ImGui::IsItemClicked()) {
tl_clear_selection_request = true;
tl_clear_selection_request = !(io.KeyCtrl || io.KeyShift);
bg_drag_select_active = !io.KeyShift;
}
if (ImGui::IsMouseReleased(ImGuiMouseButton_Left)) {
bg_drag_select_active = false;
keys_selected_by_bg_drag.clear();
}
if (ImGui::IsItemActive()) {
if (io.KeyShift)
s.current_frame = TimelineScreenViewToFrame(view_left, view_amt, view_width, std::clamp(mouse_tl_x, 0.0f, view_width), s.frame_max);
}
if (ImGui::IsItemClicked(ImGuiMouseButton_Middle)) {
@ -596,25 +615,60 @@ namespace K::UI {
}
};
// Zoom
auto tl_scroll_zoom = [&mouse_tl_x, &view_width, &io, &view_amt]() {
if (ImGui::IsWindowHovered() && ImGui::IsKeyDown(ImGuiKey_LeftCtrl) && mouse_tl_x >= 0 && mouse_tl_x < view_width && io.MouseWheel != 0.0f) {
f32 center = mouse_tl_x / view_width * view_amt + view_left;
f32 new_view_amt = std::clamp(view_amt - 0.05f * io.MouseWheel, 0.05f, 1.0f);
view_left = center - new_view_amt / 2.0f - (mouse_tl_x / view_width - .5f) * new_view_amt;
view_right = center + new_view_amt / 2.0f - (mouse_tl_x / view_width - .5f) * new_view_amt;
if (view_left < 0.0f) {
view_right -= view_left;
view_left = 0.0f;
constexpr static f32 knob_width = 8.0f;
constexpr static f32 kf_tab_width = 6.0f;
auto kf_drag_btn_handler = [&io, &drag_rect](
PlugboardNodes::ChainSel *key_loop_target, bool dragging, PlugboardNodes::ChainSel& chain, i32 frame, bool is_sel,
Vector<u32>::iterator sel_it, const PlugboardNodes::InterpolationExtra& segment, f64 v, u32 ii, bool& started_dragging, const ImVec2& k_pos) {
if (dragging) {
auto pos = std::lower_bound(chain.segments.begin(), chain.segments.end(), frame,
[](const PlugboardNodes::ChainSegment &a, i32 b) {
return a.frame < b;
});
auto pos_index = std::distance(chain.segments.begin(), pos);
if (pos != chain.segments.end() && pos->frame == frame) { // overlapping kf's
if (is_sel)
pos->value = static_cast<f32>(v);
} else {
for (auto &sel: chain.selected) {
if (sel >= pos_index)
sel++;
}
else if (view_right > 1.0f) {
view_left -= (view_right - 1.0f);
view_right = 1.0f;
chain.segments.emplace(pos, frame, v, segment);
}
if (is_sel)
chain.selected.push_back(pos_index);
} else {
if (ImGui::IsItemClicked()) {
started_dragging = true; // setup dragging & start on next frame
if (!is_sel || ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) {
if (!io.KeyCtrl)
chain.selected.clear();
chain.selected.push_back(ii);
}
}
if (!is_sel && bg_drag_select_active &&
drag_rect.Contains(k_pos)) {
keys_selected_by_bg_drag.push_back(
&(key_loop_target->segments)[ii]); // This sucks pretty bad...
chain.selected.push_back(ii);
}
auto bg_drag_sel_it = std::find(keys_selected_by_bg_drag.begin(),
keys_selected_by_bg_drag.end(),
&(key_loop_target->segments)[ii]);
if (is_sel && bg_drag_sel_it != keys_selected_by_bg_drag.end() &&
!drag_rect.Contains(k_pos)) {
chain.selected.erase(sel_it);
keys_selected_by_bg_drag.erase(bg_drag_sel_it);
}
}
};
constexpr static f32 knob_width = 8.0f;
if (ImGui::BeginTable("Layers", 5, ImGuiTableFlags_BordersInner | ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_RowBg | ImGuiTableFlags_ScrollY)) {
ImGui::TableSetupScrollFreeze(0, 1); // Make top row always visible
ImGui::TableSetupColumn("#", ImGuiTableColumnFlags_NoSort | ImGuiTableColumnFlags_IndentDisable);
@ -704,7 +758,6 @@ namespace K::UI {
ImGui::TableHeader("");
// Main rows
tl_scroll_zoom();
i32 move_from = -1, move_to = -1;
bool after{};
@ -929,7 +982,7 @@ namespace K::UI {
ImGui::SetNextItemWidth(-FLT_MIN);
if (connected_v.index() == 0) {
if (connected_v.index() == 0)
std::visit([](auto&& arg) {
using T = std::decay_t<decltype(arg)>;
if constexpr (std::is_same_v<T, PlugboardGraph::T_Map<PlugboardGraph::T_Float>::type>)
@ -943,7 +996,7 @@ namespace K::UI {
else if constexpr (std::is_same_v<T, PlugboardGraph::T_Map<PlugboardGraph::T_XYZ>::type>)
ImGui::DragFloat3("##", &arg.x, 0.005f);
}, std::get<0>(connected_v));
}
ImGui::TableSetColumnIndex(3);
ImGui::TableSetColumnIndex(4);
auto p = ImGui::GetCursorScreenPos();
@ -957,11 +1010,10 @@ namespace K::UI {
if (connected_v.index() == 1 && ImPlot::BeginPlot("uniform", {view_width, row_height}, ImPlotFlags_CanvasOnly | ImPlotFlags_NoFrame | ImPlotFlags_NoInputs)) {
ImPlot::SetupAxis(ImAxis_X1, "time", ImPlotAxisFlags_NoDecorations | ImPlotAxisFlags_NoHighlight);
ImPlot::SetupAxis(ImAxis_Y1, "val", ImPlotAxisFlags_NoDecorations | ImPlotAxisFlags_NoHighlight | ImPlotAxisFlags_AutoFit);
struct PlotInfo { f32 begin, end; i32 samples; const CompState &s; PlugboardGraph::ConnectInfo &connected_v; };
struct PlotInfo info {
.begin = view_left * static_cast<f32>(s.frame_max + 1),
.end = view_right * static_cast<f32>(s.frame_max + 1),
.samples = 200,
.samples = 100,
.s = s,
.connected_v = std::get<1>(connected_v)
};
@ -994,10 +1046,19 @@ namespace K::UI {
ImGui::TableSetColumnIndex(0);
ImGui::TableSetColumnIndex(1);
ImGui::Indent();
ImGui::TreeNodeEx(info.p->node->name.c_str(),
ImGuiTreeNodeFlags_SpanFullWidth |
auto n_flags = ImGuiTreeNodeFlags_SpanFullWidth |
ImGuiTreeNodeFlags_NoTreePushOnOpen | ImGuiTreeNodeFlags_Leaf |
ImGuiTreeNodeFlags_FramePadding);
ImGuiTreeNodeFlags_FramePadding;
auto chain_sel_it = std::find(selected_chains.begin(), selected_chains.end(), info.p);
if (chain_sel_it != selected_chains.end())
n_flags |= ImGuiTreeNodeFlags_Selected;
ImGui::TreeNodeEx(info.p->node->name.c_str(), n_flags);
if (ImGui::IsItemClicked()) {
if (chain_sel_it != selected_chains.end())
selected_chains.erase(chain_sel_it);
else
selected_chains.push_back(info.p);
}
ImGui::TableSetColumnIndex(2);
auto& [chain, chain_copy] = *std::any_cast<PlugboardNodes::ChainExtra>(&info.p->extra);
@ -1022,6 +1083,9 @@ namespace K::UI {
f32 v = std::get<PlugboardGraph::T_Map<PlugboardGraph::T_Float>::type>(
PlugboardGraph::Eval(s, info));
if (ImGui::DragFloat("##value", &v, 0.005f)) {
if (std::find(selected_chains.begin(), selected_chains.end(), info.p) == selected_chains.end())
selected_chains.push_back(info.p);
auto l = std::lower_bound(chain.segments.begin(), chain.segments.end(), s.current_frame,
[](const PlugboardNodes::ChainSegment& a, i32 b) { return a.frame < b; });
if (l != chain.segments.end() && l->frame == static_cast<i32>(s.current_frame))
@ -1052,7 +1116,6 @@ namespace K::UI {
if (started_dragging)
chain_copy = chain;
const static f32 kf_tab_width = 6.0f;
auto key_loop_target = &chain;
if (dragging) {
chain.segments.clear();
@ -1071,48 +1134,17 @@ namespace K::UI {
frame += static_cast<i32>(ImGui::GetMouseDragDelta().x / tl_width * view_amt *
static_cast<f32>(s.frame_max + 1));
}
ImGui::SetCursorScreenPos(
{TimelineFrameToScreenView(view_left, view_amt, view_width, frame,
ImVec2 draw_pos = {TimelineFrameToScreenView(view_left, view_amt, view_width, frame,
s.frame_max) + begin_tl.x - kf_tab_width / 2.0f,
begin_tl.y});
begin_tl.y};
ImGui::SetCursorScreenPos(draw_pos);
ImGui::Button(is_sel ? "s" : "k", {kf_tab_width, row_height * .8f});
if (dragging) {
auto pos = std::lower_bound(chain.segments.begin(), chain.segments.end(), frame,
[](const PlugboardNodes::ChainSegment& a, i32 b) {
return a.frame < b;
});
kf_drag_btn_handler(key_loop_target, dragging, chain, frame, is_sel,
sel_it, segment, val, ii, started_dragging, {draw_pos.x + kf_tab_width / 2.0f, draw_pos.y});
auto pos_index = std::distance(chain.segments.begin(), pos);
if (pos != chain.segments.end() && pos->frame == frame) { // overlapping kf's
if (is_sel)
pos->value = val;
}
else {
for (auto &sel: chain.selected) {
if (sel >= pos_index)
sel++;
}
chain.segments.emplace(pos, frame, val, segment);
}
if (is_sel)
chain.selected.push_back(pos_index);
}
else {
if (ImGui::IsItemClicked()) {
started_dragging = true; // setup dragging & start on next frame
if (!is_sel || ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) {
if (!io.KeyCtrl)
chain.selected.clear();
chain.selected.push_back(ii);
}
}
}
ImGui::PopID();
}
for (auto it = chain.segments.begin(); it != chain.segments.end(); it++) {
@ -1184,7 +1216,7 @@ namespace K::UI {
}
ImGui::SetCursorScreenPos(nodes_begin);
if (ImGui::BeginChild("Overlay", {table_left - ImGui::GetWindowPos().x, 0.0f}, false, ImGuiWindowFlags_NoInputs)) {
if (ImGui::BeginChild("Node Connection Overlay", {table_left - ImGui::GetWindowPos().x, 0.0f}, false, ImGuiWindowFlags_NoInputs)) {
auto *window = ImGui::GetCurrentWindow();
for (auto &[_, link]: s.plugboard.links_pos)
for (auto &sink: link.sinks)
@ -1201,7 +1233,6 @@ namespace K::UI {
ImGui::SetCursorScreenPos(tl_end_begin);
if (ImGui::BeginChild("TL Bottom Unfilled Drag Overlay")) {
draw_tl_bg_drag_area(ImGui::GetContentRegionAvail().y);
tl_scroll_zoom();
}
ImGui::EndChild();
@ -1210,8 +1241,6 @@ namespace K::UI {
ImGui::SetCursorScreenPos(tl_grid_cursor_pos);
ImGui::PushStyleColor(ImGuiCol_ChildBg, 0xFF111111);
if (ImGui::BeginChild("Curve Editor", ImGui::GetContentRegionAvail())) {
draw_tl_bg_drag_area(ImGui::GetContentRegionAvail().y);
tl_scroll_zoom();
ImGui::SetCursorScreenPos(tl_grid_cursor_pos);
ImPlot::PushStyleVar(ImPlotStyleVar_PlotPadding, {0.0f, 0.0f});
@ -1220,48 +1249,148 @@ namespace K::UI {
ImPlot::PushStyleColor(ImPlotCol_PlotBg, 0);
ImPlot::PushStyleColor(ImPlotCol_PlotBorder, 0);
if (ImPlot::BeginPlot("uniform", {view_width, ImGui::GetContentRegionAvail().y}, ImPlotFlags_CanvasOnly | ImPlotFlags_NoFrame | ImPlotFlags_NoInputs)) {
ImPlot::SetupAxis(ImAxis_X1, "time", ImPlotAxisFlags_NoDecorations | ImPlotAxisFlags_NoHighlight);
ImPlot::SetupAxis(ImAxis_Y1, "val", ImPlotAxisFlags_NoDecorations | ImPlotAxisFlags_NoHighlight | ImPlotAxisFlags_AutoFit);
// struct PlotInfo { f32 begin, end; i32 samples; const CompState &s; PlugboardGraph::ConnectInfo &connected_v; };
// struct PlotInfo info {
// .begin = view_left * static_cast<f32>(s.frame_max + 1),
// .end = view_right * static_cast<f32>(s.frame_max + 1),
// .samples = 200,
// .s = s,
// .connected_v = std::get<1>(connected_v)
// };
// ImPlot::SetupAxisLimits(ImAxis_X1, info.begin, info.end, ImGuiCond_Always);
// ImPlot::SetupFinish();
// ImPlot::PlotLineG("v", [](int idx, void* user_data) -> ImPlotPoint {
// PlotInfo i = *(PlotInfo*)user_data;
// f32 x = i.begin + (i.end - i.begin) * static_cast<f32>(idx) / static_cast<f32>(i.samples);
// CompState ss = i.s;
// ss.current_frame = static_cast<u64>(std::round(x));
// bool good;
// Vector<PlugboardGraph::ConnectInfo> info;
// return {x, std::get<PlugboardGraph::T_Map<PlugboardGraph::T_Float>::type>(PlugboardGraph::ConvertValue(PlugboardGraph::Eval(ss, i.connected_v, &info), PlugboardGraph::T_Float, good))};
// }, &info, info.samples);
static float xs1[100], ys1[100];
for (int i = 0; i < 100; ++i) {
xs1[i] = i * 0.01f;
ys1[i] = xs1[i] + 0.1f * ((float)rand() / (float)RAND_MAX);
ImPlot::SetupAxis(ImAxis_X1, "time", ImPlotAxisFlags_NoHighlight | ImPlotAxisFlags_NoLabel | ImPlotAxisFlags_NoGridLines);
ImPlot::SetupAxis(ImAxis_Y1, "val", ImPlotAxisFlags_NoHighlight | ImPlotAxisFlags_NoLabel | ImPlotAxisFlags_Opposite | ImPlotAxisFlags_NoTickLabels | ImPlotAxisFlags_NoTickMarks | ImPlotAxisFlags_AutoFit);
const f32 begin = view_left * static_cast<f32>(s.frame_max + 1), end = view_right * static_cast<f32>(s.frame_max + 1);
ImPlot::SetupAxisLimits(ImAxis_X1, begin, end, ImGuiCond_Always);
ImPlot::SetupFinish();
static bool started_dragging = false, dragging = false;
if (started_dragging) {
dragging = true;
}
static float xs2[50], ys2[50];
for (int i = 0; i < 50; i++) {
xs2[i] = 0.25f + 0.2f * ((float)rand() / (float)RAND_MAX);
ys2[i] = 0.75f + 0.2f * ((float)rand() / (float)RAND_MAX);
if (ImGui::IsMouseReleased(ImGuiMouseButton_Left)) {
dragging = false;
}
u32 keys = 0;
for (const auto& n : selected_chains) {
PlugboardGraph::ConnectInfo i {n, 0}; // it's a chain -- 0 is out
auto& [chain, chain_copy] = *std::any_cast<PlugboardNodes::ChainExtra>(&n->extra);
if (started_dragging)
chain_copy = chain;
auto key_loop_target = &chain;
if (dragging) {
chain.segments.clear();
chain.selected.clear();
key_loop_target = &chain_copy;
}
ImPlot::PlotScatter("Data 1", xs1, ys1, 100);
ImPlot::PushStyleVar(ImPlotStyleVar_FillAlpha, 0.25f);
ImPlot::SetNextMarkerStyle(ImPlotMarker_Square, 6, ImPlot::GetColormapColor(1), IMPLOT_AUTO, ImPlot::GetColormapColor(1));
ImPlot::PlotScatter("Data 2", xs2, ys2, 50);
ImPlot::PopStyleVar();
for (u32 ii = 0; ii < key_loop_target->segments.size(); ii++) {
auto& [k, val, segment] = (key_loop_target->segments)[ii];
ImGui::PushID(keys++);
auto sel_it = std::find(key_loop_target->selected.begin(), key_loop_target->selected.end(), ii);
bool is_sel = sel_it != key_loop_target->selected.end();
bool is_nxt_sel = ii + 1 < key_loop_target->segments.size() && std::find(key_loop_target->selected.begin(), key_loop_target->selected.end(), ii + 1) != key_loop_target->selected.end();
i32 frame = k;
f64 v = val;
i32 drag_off_x; // data coords
f64 drag_off_y; // data coords
ImPlotPoint mouse_pos = ImPlot::PixelsToPlot(io.MousePos),
mouse_clicked_pos = ImPlot::PixelsToPlot(io.MouseClickedPos[ImGuiMouseButton_Left]);
drag_off_x = static_cast<i32>(std::round(mouse_pos.x - mouse_clicked_pos.x));
drag_off_y = mouse_pos.y - mouse_clicked_pos.y;
if (dragging && is_sel) {
frame += drag_off_x;
v += drag_off_y;
}
f64 x = frame, y = v;
ImVec2 k_pos = ImPlot::PlotToPixels(x, y);
ImPlot::Annotation(x, y, {1.0f, 1.0f, 1.0f, 1.0f}, {15.0f, 0.0f}, true);
ImGui::SetCursorScreenPos(k_pos - ImVec2{10.0f / 2.0f, 10.0f / 2.0f});
ImGui::PushStyleColor(ImGuiCol_Button, is_sel ? 0xFFFFFF88 : 0xFFAA7777);
ImGui::Button("##k", {10.0f, 10.0f});
ImGui::PopStyleColor();
kf_drag_btn_handler(key_loop_target, dragging, chain, frame, is_sel,
sel_it, segment, v, ii, started_dragging, k_pos);
if (segment.interp == PlugboardNodes::K_I_CubicBezier && ii + 1 < key_loop_target->segments.size()) {
const auto& [k_nxt, val_nxt, _] = (key_loop_target->segments)[ii + 1];
i32 frame_nxt = k_nxt;
f64 v_nxt = val_nxt;
if (dragging && is_nxt_sel) {
frame_nxt += drag_off_x;
v_nxt += drag_off_y;
}
f64 p2x = std::lerp(frame, frame_nxt, segment.v[0]), p2y = std::lerp(v, v_nxt, segment.v[1]),
p3x = std::lerp(frame, frame_nxt, segment.v[2]), p3y = std::lerp(v, v_nxt, segment.v[3]);
ImPlot::Annotation(p2x, p2y, {1.0f, 1.0f, 1.0f, 1.0f}, {15.0f, 0.0f}, true);
ImPlot::Annotation(p3x, p3y, {1.0f, 1.0f, 1.0f, 1.0f}, {15.0f, 0.0f}, true);
static f32 x_tmp, y_tmp;
ImGui::SetCursorScreenPos(ImPlot::PlotToPixels(p2x, p2y) - ImVec2{10.0f / 2.0f, 10.0f / 2.0f});
ImGui::Button("##p2", {10.0f, 10.0f});
if (ImGui::IsItemClicked()) {
x_tmp = segment.v[0];
y_tmp = segment.v[1];
}
if (ImGui::IsItemActive()) {
segment.v[0] = std::clamp(x_tmp + static_cast<f32>(drag_off_x) / (static_cast<f32>(frame_nxt) - static_cast<f32>(frame)), 0.0f, 1.0f);
segment.v[1] = y_tmp + static_cast<f32>(drag_off_y / (v_nxt - v));
}
ImGui::SetCursorScreenPos(ImPlot::PlotToPixels(p3x, p3y) - ImVec2{10.0f / 2.0f, 10.0f / 2.0f});
ImGui::Button("##p3", {10.0f, 10.0f});
if (ImGui::IsItemClicked()) {
x_tmp = segment.v[2];
y_tmp = segment.v[3];
}
if (ImGui::IsItemActive()) {
segment.v[2] = std::clamp(x_tmp + static_cast<f32>(drag_off_x) / (static_cast<f32>(frame_nxt) - static_cast<f32>(frame)), 0.0f, 1.0f);
segment.v[3] = y_tmp + static_cast<f32>(drag_off_y / (v_nxt - v));
}
f64 xs[2] = {static_cast<f64>(frame), p2x},
ys[2] = {v, p2y};
ImPlot::PlotLine("cb tangent", xs, ys, 2);
xs[0] = p3x; xs[1] = static_cast<f64>(frame_nxt);
ys[0] = p3y; ys[1] = v_nxt;
ImPlot::PlotLine("cb tangent", xs, ys, 2);
}
ImGui::PopID();
}
struct PlotInfo info{
.begin = begin,
.end = end,
.samples = 100,
.s = s,
.connected_v = i
};
ImPlot::PlotLineG("v", [](int idx, void *user_data) -> ImPlotPoint {
PlotInfo i = *(PlotInfo *) user_data;
f32 x = i.begin + (i.end - i.begin) * static_cast<f32>(idx) / static_cast<f32>(i.samples);
CompState ss = i.s;
ss.current_frame = static_cast<u64>(x);
bool good;
Vector<PlugboardGraph::ConnectInfo> info;
return {x, std::get<PlugboardGraph::T_Map<PlugboardGraph::T_Float>::type>(
PlugboardGraph::ConvertValue(PlugboardGraph::Eval(ss, i.connected_v, &info),
PlugboardGraph::T_Float, good))};
}, &info, info.samples);
}
ImPlot::EndPlot();
if (started_dragging && dragging)
started_dragging = false;
}
ImPlot::PopStyleColor(3);
ImPlot::PopStyleVar(2);
ImGui::SetCursorScreenPos(ImGui::GetCursorStartPos());
draw_tl_bg_drag_area(ImGui::GetContentRegionAvail().y, ImGui::GetContentRegionAvail().x);
}
ImGui::EndChild();
ImGui::PopStyleColor();
@ -1298,6 +1427,24 @@ namespace K::UI {
{fr, tl_init_pos.y + row_height},
{fr, ImGui::GetWindowSize().y + tl_init_pos.y}, 0x773333FF, 1.0f);
}
if (bg_drag_select_active) {
window->DrawList->AddRectFilled(io.MouseClickedPos[ImGuiMouseButton_Left], io.MousePos, 0x55AA5555);
window->DrawList->AddRect(io.MouseClickedPos[ImGuiMouseButton_Left], io.MousePos, 0xAAAA7777);
}
if (window->Rect().Contains(io.MousePos) && ImGui::IsKeyDown(ImGuiKey_LeftCtrl) && mouse_tl_x >= 0 && mouse_tl_x < view_width && io.MouseWheel != 0.0f) {
f32 center = mouse_tl_x / view_width * view_amt + view_left;
f32 new_view_amt = std::clamp(view_amt - 0.05f * io.MouseWheel, 0.05f, 1.0f);
view_left = center - new_view_amt / 2.0f - (mouse_tl_x / view_width - .5f) * new_view_amt;
view_right = center + new_view_amt / 2.0f - (mouse_tl_x / view_width - .5f) * new_view_amt;
if (view_left < 0.0f) {
view_right -= view_left;
view_left = 0.0f;
}
else if (view_right > 1.0f) {
view_left -= (view_right - 1.0f);
view_right = 1.0f;
}
}
}
ImGui::EndChild();
@ -1517,10 +1664,24 @@ namespace K::UI {
ImPlot::SetupAxis(ImAxis_Y1, "val", ImPlotAxisFlags_Opposite | ImPlotAxisFlags_NoLabel | ImPlotAxisFlags_NoHighlight | ImPlotAxisFlags_AutoFit);
ImPlot::SetupFinish();
constexpr static u32 samples = 100;
if (type_current == PlugboardNodes::K_I_CubicBezier) {
f64 p2x = p2.x, p2y = p2.y, p3x = p3.x, p3y= p3.y;
if (ImPlot::DragPoint(0, &p2x, &p2y, {1.0f, 1.0f, 1.0f, 1.0f})) {
p2.x = static_cast<f32>(std::clamp(p2x, 0.0, 1.0));
p2.y = static_cast<f32>(p2y);
}
if (ImPlot::DragPoint(1, &p3x, &p3y, {1.0f, 1.0f, 1.0f, 1.0f})) {
p3.x = static_cast<f32>(std::clamp(p3x, 0.0, 1.0));
p3.y = static_cast<f32>(p3y);
}
}
ImPlot::PlotLineG("v", [](i32 idx, void* user_data) {
f32 t = static_cast<f32>(idx) / static_cast<f32>(samples);
return ImPlotPoint{t, PlugboardNodes::EvalInterpolation(t, {.interp = type_current})};
return ImPlotPoint{t, PlugboardNodes::EvalInterpolation(t, {.interp = type_current, .v = {p2.x,p2.y, p3.x, p3.y}})};
}, nullptr, samples);
ImPlot::EndPlot();
}
ImPlot::PopStyleColor(3);

View file

@ -99,7 +99,7 @@ namespace K::PlugboardNodes {
struct InterpolationExtra {
K_Interpolation interp;
f32 v[4];
f32 v[4]; // currently used only for bezier tangent points -- between [0,1]^2 fitted to segment
};
constexpr f32 EvalInterpolation(f32 x, const InterpolationExtra& extra) {

View file

@ -5,7 +5,6 @@
- copy & paste
- Undo's
- Once this is done we can move the plugboard loop detection/chain finding out of the eval loop
- Graph editor
- Node groups
- Simple export
@ -18,8 +17,7 @@
- Data models
- Dump and read back state, (de)serialization!!!
- Pre-compose/Layer Groups (jokes -- can be completely UI side)
- Resource
- Deletion
- Deleting layers/nodes/keys/etc upkeep
- Motion blur
- Text (idea: index-based evaluation in plugboard)
- Shapes (idea: embed glisp :mmtroll:, need to inquire -- still a lot of friction for simple shapes if we don't also get the glisp gizmos)