Keishiki/Keishiki/UI.cpp
lachrymaLF 3edd2ac5c5 asdf
2024-08-08 22:24:24 -04:00

2311 lines
122 KiB
C++

#include "UI.h"
#include "Graphics.h"
#include "VisualTrack.h"
#include <imgui.h>
#include <imgui_internal.h>
#include <misc/cpp/imgui_stdlib.h>
#include "backends/imgui_impl_sdl3.h"
#include "imgui_impl_bgfx.h"
#include <implot.h>
#include <algorithm>
#define STB_IMAGE_WRITE_IMPLEMENTATION
#include <stb_image_write.h>
#include <chrono>
#include <fmt/chrono.h>
#include <SDL3/SDL_dialog.h>
namespace {
const K::Resource::Resource<K::Resource::Type::K_R_Still> *logo;
// Panels
bool draw_viewport = true,
draw_layer = true,
draw_comp = true,
draw_interpolation = true,
draw_assets = true;
constexpr f32 row_height = 26.0f;
// Viewport
K::VisualTrack bg{};
bool playing;
u64 last_frame_tick;
bgfx::TextureHandle save = BGFX_INVALID_HANDLE;
K::Byte *save_buffer;
K::CompositionState *comp_save_called = nullptr;
u32 ready_frame;
enum DeleteRequest {
K_DR_Selected_Compositions,
K_DR_Selected_Layers,
K_DR_Selected_Keys,
K_DR_Selected_Nodes,
K_DR_Make_Subcomposition,
K_DR_Count
};
bool delete_requests[K_DR_Count]{};
}
namespace ImGui {
inline ImTextureID toId(bgfx::TextureHandle _handle, uint8_t _flags, uint8_t _mip)
{
union { struct { bgfx::TextureHandle handle; uint8_t flags; uint8_t mip; } s; ImTextureID id; } tex{};
tex.s.handle = _handle;
tex.s.flags = _flags;
tex.s.mip = _mip;
return tex.id;
}
}
namespace K::UI {
void SetLogoView() {
bgfx::setViewRect(Graphics::K_VIEW_LOGO, 0, 0, app_state.window_width, app_state.window_height);
float view[16];
bx::mtxTranslate(view, 0.f, 0.f, 1.0f);
float proj[16];
bx::mtxOrtho(proj, 0.0f, app_state.window_width, 0.0f, app_state.window_height, 0.1f, 100.0f, 0.f, bgfx::getCaps()->homogeneousDepth);
bgfx::setViewTransform(K::Graphics::K_VIEW_LOGO, view, proj);
}
void AddTransformLayer(CompositionState& s, const String& name) {
auto l = Layer{
VisualTrack{},
name,
Graphics::K_V_AlphaOver,
0UL,
s.frame_max
};
l.track.AddUniform("u_resolution", ShaderGraph::ExpandVariant(ShaderGraph::T_XY), K_U_Exposed);
l.track.uniforms[0].val = ShaderGraph::T_Map<ShaderGraph::T_XY>::type{static_cast<f32>(s.width), static_cast<f32>(s.height)};
K::VisualTrack::ExposeUniform(s, l.track.uniforms.back());
l.track.AddUniform("u_opacity", ShaderGraph::ExpandVariant(ShaderGraph::T_Float), K_U_Exposed);
l.track.uniforms[1].val = ShaderGraph::T_Map<ShaderGraph::T_Float>::type(1.0f);
K::VisualTrack::ExposeUniform(s, l.track.uniforms.back());
l.track.AddUniform("u_rot", ShaderGraph::ExpandVariant(ShaderGraph::T_Float), K_U_Exposed);
l.track.uniforms[2].val = ShaderGraph::T_Map<ShaderGraph::T_Float>::type(.0f);
K::VisualTrack::ExposeUniform(s, l.track.uniforms.back());
l.track.AddUniform("u_scale", ShaderGraph::ExpandVariant(ShaderGraph::T_XY), K_U_Exposed);
l.track.uniforms[3].val = ShaderGraph::T_Map<ShaderGraph::T_XY>::type{1.0f, 1.0f};
K::VisualTrack::ExposeUniform(s, l.track.uniforms.back());
l.track.AddUniform("u_translate", ShaderGraph::ExpandVariant(ShaderGraph::T_XY), K_U_Exposed);
l.track.uniforms[4].val = ShaderGraph::T_Map<ShaderGraph::T_XY>::type{.0f, .0f};
K::VisualTrack::ExposeUniform(s, l.track.uniforms.back());
l.track.shader = "void main() {\n"
"\tfloat angle = -u_rot * M_PI / 180.0f;\n"
"\tvec2 uv = vec2(v_texcoord0.x, 1.0f - v_texcoord0.y) - .5f;\n"
"\tuv = uv * u_resolution;\n"
"\tuv = uv - u_translate;\n"
"\tuv = vec2(cos(angle)*uv.x - sin(angle)*uv.y, sin(angle)*uv.x + cos(angle)*uv.y);\n"
"\tuv = uv / s_texColor_dims;\n"
"\tuv = uv / u_scale;\n"
"\tuv = uv + .5f;\n\n"
"\tvec4 tx = texture2D(s_texColor, uv);\n"
"\tgl_FragColor = vec4(tx.rgb, tx.a * u_opacity);\n"
"}\n";
l.track.AddSampler("s_texColor");
l.track.Compile();
s.layers.push_back(std::move(l));
}
void MainMenuBar() {
if (ImGui::BeginMainMenuBar()) {
if (ImGui::BeginMenu("File")) {
if (ImGui::MenuItem("Open Project", "")) {
Resource::ReadProject();
}
if (ImGui::MenuItem("Save Project", "CTRL+S")) {
Resource::SaveCurrentProject();
}
ImGui::EndMenu();
}
if (ImGui::BeginMenu("Edit")) {
// if (ImGui::MenuItem("Undo", "CTRL+Z")) {}
// if (ImGui::MenuItem("Redo", "CTRL+Y", false, false)) {} // Disabled item
// ImGui::Separator();
// if (ImGui::MenuItem("Cut", "CTRL+X")) {}
// if (ImGui::MenuItem("Copy", "CTRL+C")) {}
// if (ImGui::MenuItem("Paste", "CTRL+V")) {}
ImGui::EndMenu();
}
if (ImGui::BeginMenu("View")) {
if (ImGui::MenuItem("Assets", "", &draw_assets)) {}
if (ImGui::MenuItem("Viewport", "", &draw_viewport)) {}
if (ImGui::MenuItem("Composition", "", &draw_comp)) {}
if (ImGui::MenuItem("Layer", "", &draw_layer)) {}
if (ImGui::MenuItem("Interpolation", "", &draw_interpolation)) {}
ImGui::EndMenu();
}
}
ImGui::EndMainMenuBar();
}
void DrawPlugboardVariableEditWidget(Plugboard::T_Map<Plugboard::T_Count>::type& var) {
std::visit([](auto&& arg) {
using T = std::decay_t<decltype(arg)>;
if constexpr (std::is_same_v<T, Plugboard::T_Map<Plugboard::T_Float>::type>)
ImGui::DragFloat("##", &arg, 0.005f);
else if constexpr (std::is_same_v<T, Plugboard::T_Map<Plugboard::T_Int>::type>)
ImGui::DragInt("##", &arg, 0.005f);
else if constexpr (std::is_same_v<T, Plugboard::T_Map<Plugboard::T_RGBA>::type>)
ImGui::DragFloat4("##", &arg.r, 0.005f);
else if constexpr (std::is_same_v<T, Plugboard::T_Map<Plugboard::T_XY>::type>)
ImGui::DragFloat2("##", &arg.x, 0.005f);
else if constexpr (std::is_same_v<T, Plugboard::T_Map<Plugboard::T_XYZ>::type>)
ImGui::DragFloat3("##", &arg.x, 0.005f);
}, var);
}
template <Plugboard::Node N>
void PlugboardNodeDrawSockets(CompositionState& s, N& n, ImGuiStyle& style,
bool& dragging_on_socket, ImVec2& source, Dict<Plugboard::ConnectInfo, Plugboard::LinksFromSource, Plugboard::ConnectInfoHasher>& links_pos, bool dummy = true) {
i32 row = 1;
for (u32 out = 0; out < n.out.size(); out++, row++) {
ImGui::PushID(row);
ImGui::TableNextRow(ImGuiTableRowFlags_None, row_height);
ImGui::TableNextColumn();
ImGui::TableNextColumn();
if (dummy)
ImGui::Dummy({100.0f - ImGui::CalcTextSize(n.out[out].name.c_str()).x - style.ItemSpacing.x, 0.0f});
ImGui::SameLine();
ImGui::TextUnformatted(n.out[out].name.c_str());
ImGui::TableNextColumn();
ImGui::RadioButton("##Out", false);
if (ImGui::IsItemActive()) {
dragging_on_socket = true;
source = ImGui::GetCursorScreenPos() + ImVec2{ ImGui::GetFrameHeight() / 2, -ImGui::GetFrameHeight() / 2 - style.ItemInnerSpacing.y};
}
if (ImGui::BeginDragDropSource(ImGuiDragDropFlags_None)) {
Plugboard::ConnectInfo d{ &n, out };
ImGui::SetDragDropPayload("K_PLUG_OUT_TO_IN", &d, sizeof(d));
ImGui::EndDragDropSource();
}
if (ImGui::BeginDragDropTarget()) {
if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload("K_PLUG_IN_TO_OUT")) {
Plugboard::ConnectInfo in = *(const Plugboard::ConnectInfo*)payload->Data;
Plugboard::Connect(in.p, in.index, &n, out);
s.plugboard.RecollectChains();
}
ImGui::EndDragDropTarget();
}
ImGui::PopID();
// Update link info if needed
if (!n.out[out].outgoing.empty()) {\
links_pos[{&n, out}].source = ImGui::GetCursorScreenPos() + ImVec2{ImGui::GetFrameHeight() / 2,
-ImGui::GetFrameHeight() / 2 -
style.ItemInnerSpacing.y};
}
}
for (u32 in = 0; in < n.in.size(); in++, row++) {
ImGui::PushID(row);
ImGui::TableNextRow(ImGuiTableRowFlags_None, row_height);
ImGui::TableNextColumn();
ImGui::RadioButton("##In", false);
if (ImGui::BeginDragDropSource(ImGuiDragDropFlags_None)) {
Plugboard::ConnectInfo d{ &n, in };
ImGui::SetDragDropPayload("K_PLUG_IN_TO_OUT", &d, sizeof(d));
ImGui::EndDragDropSource();
}
if (ImGui::BeginDragDropTarget()) {
if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload("K_PLUG_OUT_TO_IN")) {
Plugboard::ConnectInfo out = *(const Plugboard::ConnectInfo*)payload->Data;
Plugboard::Connect(&n, in, out.p, out.index);
s.plugboard.RecollectChains();
}
ImGui::EndDragDropTarget();
}
auto socket_link_pos = ImGui::GetCursorScreenPos() + ImVec2{ ImGui::GetFrameHeight() / 2, -ImGui::GetFrameHeight() / 2 - style.ItemInnerSpacing.y};
ImGui::TableNextColumn();
ImGui::TextUnformatted(n.in[in].name.c_str());
// Update link info
std::visit([&links_pos, &socket_link_pos, &dragging_on_socket, &source, &s](auto&& arg) {
using T = std::decay_t<decltype(arg)>;
if constexpr (std::is_same_v<T, Plugboard::ConnectInfo>) {
if (ImGui::IsItemActive())
links_pos[arg].sinks.push_back(ImGui::GetMousePos());
else
links_pos[arg].sinks.push_back(socket_link_pos);
}
else if (ImGui::IsItemActive()) {
dragging_on_socket = true;
source = socket_link_pos;
}
if constexpr (std::is_same_v<T, Plugboard::T_Map<Plugboard::Type::T_Count>::type>) {
ImGui::SameLine();
DrawPlugboardVariableEditWidget(arg);
}
}, n.in[in].value);
ImGui::TableNextColumn();
ImGui::PopID();
}
}
template <Plugboard::Node N>
void PlugboardDrawNode(CompositionState& s, N& n, ImVec2& view_pos, ImGuiIO& io, ImGuiStyle& style,
bool& dragging_on_socket, ImVec2& source, Dict<Plugboard::ConnectInfo, Plugboard::LinksFromSource, Plugboard::ConnectInfoHasher>& links_pos) {
Plugboard::NodeInstanceP ptr{&n};
bool selected = std::ranges::find(s.plugboard.selected_nodes, ptr) != s.plugboard.selected_nodes.end();
const auto& pos = n.pos;
const auto& name = n.name;
ImGui::SetCursorPos(view_pos + pos);
ImGui::PushID(&n);
ImGui::PushStyleColor(ImGuiCol_ChildBg, selected ? 0xAA202040 : 0xAA080813);
static ImVec2 old_pos{};
if (ImGui::BeginChild(name, {}, ImGuiChildFlags_AlwaysAutoResize | ImGuiChildFlags_AutoResizeX | ImGuiChildFlags_AutoResizeY | ImGuiChildFlags_Border)) {
if (ImGui::BeginTable(name, 3)) {
ImGui::TableNextRow(ImGuiTableRowFlags_None, row_height);
ImGui::TableNextColumn();
ImGui::TableNextColumn();
ImGui::Button(name, {100.0f , 0.0f});
if (ImGui::BeginPopupContextItem()) {
if (!s.plugboard.selected_nodes.empty() && ImGui::Button("Delete")) {
delete_requests[K_DR_Selected_Nodes] = true;
ImGui::CloseCurrentPopup();
}
ImGui::EndPopup();
}
if (ImGui::IsItemClicked()) {
old_pos = pos;
if (io.KeyCtrl && !selected) {
s.plugboard.selected_nodes.push_back(ptr);
}
else if (!selected) {
s.plugboard.selected_nodes.clear();
s.plugboard.selected_nodes.push_back(ptr);
}
}
if (ImGui::IsItemActive()) {
n.pos = old_pos + ImGui::GetMouseDragDelta();
}
ImGui::TableNextColumn();
if constexpr (requires (N node) { { node.Draw() } -> std::same_as<void>; }) {
ImGui::TableNextRow();
ImGui::TableNextColumn();
ImGui::TableNextColumn();
n.Draw();
ImGui::TableNextColumn();
}
PlugboardNodeDrawSockets<N>(s, n, style, dragging_on_socket, source, links_pos);
ImGui::EndTable();
}
}
ImGui::PopStyleColor();
ImGui::EndChild();
ImGui::PopID();
}
void TogglePlay() {
playing = !playing;
if (playing)
last_frame_tick = app_state.current_time;
}
void Viewport(CompositionState& s) {
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2{});
if (ImGui::Begin("Viewport", nullptr, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse)) {
ImGuiIO& io = ImGui::GetIO();
static bool do_bg = true, dragging = false;
static f32 size = 1.0f, angle = 0;
static ImVec2 pos{}, old = pos;
if (ImGui::Shortcut(ImGuiKey_Space))
TogglePlay();
app_state.render_view = Graphics::K_VIEW_COMP_COMPOSITE;
i32 result_index = s.GetFrame(s.current_frame);
bgfx::TextureHandle present = BGFX_INVALID_HANDLE;
if (do_bg) {
bg.GetFrame(s, s.render_fb, s.width, s.height, s.proj, s.view, s.transform);
Graphics::Composite(s.composite_fb[1 - result_index],
s.render, s.composite[result_index], Graphics::K_V_AlphaOver, s.width, s.height,
s.proj, s.transform);
present = s.composite[1 - result_index];
}
else
present = s.composite[result_index];
ImTextureID idx = ImGui::toId(present, 1, 0);
if (idx != nullptr) {
ImVec2 image_size = {static_cast<f32>(s.width) * size, static_cast<f32>(s.height) * size};
ImDrawList* draw_list = ImGui::GetWindowDrawList();
static auto rot = [](const ImVec2& v, float cos_a, float sin_a) {
return ImVec2(v.x * cos_a - v.y * sin_a, v.x * sin_a + v.y * cos_a);
};
ImVec2 center = ImGui::GetCursorScreenPos() + ImGui::GetContentRegionAvail() * .5f + pos;
f32 cos_a = std::cosf(angle / 180.0f * std::numbers::pi);
f32 sin_a = std::sinf(angle / 180.0f * std::numbers::pi);
ImVec2 quad[4] = {
center + rot(ImVec2(-image_size.x * 0.5f, -image_size.y * 0.5f), cos_a, sin_a),
center + rot(ImVec2(+image_size.x * 0.5f, -image_size.y * 0.5f), cos_a, sin_a),
center + rot(ImVec2(+image_size.x * 0.5f, +image_size.y * 0.5f), cos_a, sin_a),
center + rot(ImVec2(-image_size.x * 0.5f, +image_size.y * 0.5f), cos_a, sin_a)
};
ImVec2 uvs[4] = {
ImVec2(0.0f, 0.0f),
ImVec2(1.0f, 0.0f),
ImVec2(1.0f, 1.0f),
ImVec2(0.0f, 1.0f)
};
draw_list->AddImageQuad(idx, quad[0], quad[1], quad[2], quad[3], uvs[0], uvs[1], uvs[2], uvs[3], IM_COL32_WHITE);
}
/* ImGui::BeginDisabled(comp_save_called != nullptr);
if (ImGui::Button("Save to frame.png")) {
bgfx::blit(Graphics::K_VIEW_COMP_COMPOSITE + view_count, save, 0, 0, composite[layers_done % 2]);
ready_frame = bgfx::readTexture(save, save_buffer);
comp_save_called = &s;
}
ImGui::EndDisabled();*/
if (ImGui::IsWindowHovered()) {
if (io.MouseWheel != 0.0f) {
size += io.MouseWheel * 0.25f;
size = std::max(size, 0.25f);
}
if (io.MouseClicked[ImGuiMouseButton_Middle]) {
dragging = true;
old = pos;
}
if (io.MouseDoubleClicked[ImGuiMouseButton_Middle]) {
size = 1.0f;
pos.x = 0.0f;
pos.y = 0.0f;
old = pos;
}
}
if (dragging) {
pos = old + ImGui::GetMouseDragDelta(ImGuiMouseButton_Middle);
}
if (io.MouseReleased[ImGuiMouseButton_Middle]) {
dragging = false;
}
ImGui::SetCursorPosY(ImGui::GetWindowHeight() - 25.0f);
ImGui::PushStyleColor(ImGuiCol_ChildBg, 0x99000000);
if (ImGui::BeginChild("Controls")) {
ImGui::Text("%ux%u@%.1f", s.width, s.height, s.fps);
ImGui::SameLine();
f32 percentage = size * 100.0f;
ImGui::SetNextItemWidth(110.0f);
if (ImGui::InputFloat("View Scale", &percentage, 15.0f, 25.0f, "%.2f%%")) {
size = std::max(.25f, percentage / 100.0f);
}
ImGui::SameLine();
ImGui::SetNextItemWidth(50.0f);
ImGui::DragFloat("View Angle", &angle, .05f, 0.0f, 0.0f, "%.1f");
ImGui::SameLine();
ImGui::Checkbox("Transparency Grid", &do_bg);
}
ImGui::EndChild();
ImGui::PopStyleColor();
}
ImGui::End();
ImGui::PopStyleVar();
}
constexpr bool TimelineFrameInView(f32 view_left, f32 view_right, i64 frame, u64 frame_max) {
f32 p = static_cast<f32>(frame) / static_cast<f32>(frame_max + 1);
return p >= view_left && p < view_right;
}
constexpr f32 TimelineFrameToScreenView(f32 view_left, f32 view_amt, f32 view_width, i64 frame, u64 frame_max) {
return (static_cast<f32>(frame) / static_cast<f32>(frame_max + 1) - view_left) / view_amt * view_width;
}
constexpr u64 TimelineScreenViewToFrame(f32 view_left, f32 view_amt, f32 view_width, f32 view, u64 frame_max) {
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 CompositionState &s;
Plugboard::ConnectInfo &connected_v;
};
void Composition(CompositionState& s) {
if (!ImGui::Begin("Composition", nullptr, ImGuiWindowFlags_NoScrollbar)) {
ImGui::End();
return;
}
ImGuiIO& io = ImGui::GetIO();
static bool show_curve_editor = false;
if (ImGui::Shortcut(ImGuiKey_Space))
TogglePlay();
static f32 nodes_view_scale = 1.0f;
static ImVec2 nodes_view_pos{}, nodes_old_view_pos{}, nodes_begin{};
static u32 node_current = 0;
if (ImGui::Button("Add")) {
s.plugboard.nodes.Store(static_cast<Plugboard::K_P_Nodes>(node_current), ImVec2{-nodes_view_pos.x + 70.0f, -nodes_view_pos.y + 70.0f});
}
ImGui::SameLine();
ImGui::SetNextItemWidth(150.0f);
static Dict<Plugboard::ConnectInfo, Plugboard::LinksFromSource, Plugboard::ConnectInfoHasher> links_pos;
if (ImGui::BeginCombo("##Node", s.plugboard.nodes.GetName(static_cast<Plugboard::K_P_Nodes>(node_current)), ImGuiComboFlags_None)) {
for (u32 n = 0; n < Plugboard::K_P_Count; n++) {
const bool is_selected = (node_current == n);
if (ImGui::Selectable(s.plugboard.nodes.GetName(static_cast<Plugboard::K_P_Nodes>(n)), is_selected))
node_current = n;
if (is_selected)
ImGui::SetItemDefaultFocus();
}
ImGui::EndCombo();
}
ImGui::SameLine();
if (ImGui::Button(playing ? "Pause" : "Play"))
TogglePlay();
ImGui::SameLine();
ImGui::Text("Frame %lu / %lu", s.current_frame, s.frame_max);
ImGui::SameLine();
const bool no_selection = s.selected.empty();
static String name{};
if (ImGui::Button("Add Layer") && !name.empty())
AddTransformLayer(s, name);
ImGui::SameLine();
ImGui::SetNextItemWidth(250.0f);
ImGui::InputText("##LayerName", &name);
ImGui::SameLine();
ImGui::Checkbox("Chain Editor", &show_curve_editor);
ImRect drag_rect{io.MousePos, io.MousePos};
drag_rect.Add(io.MouseClickedPos[ImGuiMouseButton_Left]);
ImGuiStyle style = ImGui::GetStyle();
bool dragging_on_socket{};
ImVec2 drag_source{};
ImGui::PushStyleColor(ImGuiCol_ChildBg, 0xFF100500);
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)) {
ImGuiWindow *window = ImGui::GetCurrentWindow();
ImVec2 window_pos = ImGui::GetWindowPos();
nodes_begin = window_pos;
if (ImGui::BeginDragDropTargetCustom(window->ContentRegionRect, window->ID)) {
if (const ImGuiPayload *payload = ImGui::AcceptDragDropPayload("K_PLUG_IN_TO_OUT")) {
Plugboard::ConnectInfo in = *(const Plugboard::ConnectInfo *) payload->Data;
Plugboard::Disconnect(in.p, in.index);
}
ImGui::EndDragDropTarget();
}
// Grid
const f32 grid_inc = 20.0f;
const f32 w = window->ContentRegionRect.GetWidth(), h = window->ContentRegionRect.GetHeight();
for (f32 x = std::fmodf(nodes_view_pos.x, grid_inc); x < w; x += grid_inc)
window->DrawList->AddLine(window_pos + ImVec2(x, 0.0f), window_pos + ImVec2(x, h), 0xFF222222, 1.0f);
for (f32 y = std::fmodf(nodes_view_pos.y, grid_inc); y < h; y += grid_inc)
window->DrawList->AddLine(window_pos + ImVec2(0.0f, y), window_pos + ImVec2(w, y), 0xFF222222, 1.0f);
// why are we re-getting positions on each frame?
// because it's just convenient because sometimes we would be dragging on a socket!
links_pos.clear();
if (ImGui::IsMouseClicked(ImGuiMouseButton_Left)) {
nodes_old_view_pos = nodes_view_pos;
}
if (ImGui::IsItemActive()) {
nodes_view_pos = nodes_old_view_pos + ImGui::GetMouseDragDelta();
}
std::apply([&](auto&&... args) {
(([&](auto&& arg){
for (auto& node : arg) {
PlugboardDrawNode<std::decay_t<decltype(node)>>(s, node, nodes_view_pos, io, style, dragging_on_socket,
drag_source, links_pos);
}
}(args)), ...);
}, s.plugboard.nodes.nodes);
ImGui::SetCursorPos({});
ImGui::PushStyleColor(ImGuiCol_ChildBg, 0x33FFFFFF);
if (ImGui::BeginChild("Composition In", {},
ImGuiChildFlags_AlwaysAutoResize | ImGuiChildFlags_AutoResizeX |
ImGuiChildFlags_AutoResizeY)) {
ImGui::PopStyleColor();
if (ImGui::BeginTable("Composition In", 3)) {
PlugboardNodeDrawSockets(s, s.plugboard.in_instance, style, dragging_on_socket, drag_source,
links_pos, false);
ImGui::EndTable();
}
}
ImGui::EndChild();
}
ImGui::PopStyleColor();
ImGui::EndChild();
ImGui::SameLine(0.0f, 0.5f);
// Timeline setup
f32 table_left = ImGui::GetCursorScreenPos().x;
ImVec2 tl_init_pos, tl_end_begin;
f32 view_width,
tl_width,
view_height,
control_left,
control_right;
static f32 view_left = 0.0f,
view_left_old = view_left,
view_right = 1.0f,
view_right_old = view_right;
f32 view_amt = view_right - view_left, mouse_tl_x;
f32 fr_step;
// Mouse Handlers
// Drag playhead/pan
static bool tl_clear_selection_request = false;
bool tl_clear_selection_request_this_frame = tl_clear_selection_request;
static bool bg_drag_select_active = false;
static Vector<Plugboard::ChainSegment*> keys_selected_by_bg_drag{};
auto tl_bg_handler = [&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;
static bool middle_dragging = false;
ImGui::InvisibleButton("##TL_BG", ImVec2{ w == 0.0f ? view_width + style.CellPadding.x * 2 : w, h == 0.0f ? row_height : h});
if (ImGui::IsItemClicked()) {
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)) {
pan_x = mouse_tl_x;
view_left_old = view_left;
view_right_old = view_right;
middle_dragging = true;
}
if (middle_dragging && ImGui::IsKeyDown(ImGuiKey_MouseMiddle)) {
ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
f32 shift_amt = (mouse_tl_x - pan_x) / view_width * view_amt;
view_left = view_left_old - shift_amt;
view_right = view_right_old - shift_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;
}
}
if (ImGui::IsMouseReleased(ImGuiMouseButton_Middle))
middle_dragging = false;
if (ImGui::IsKeyPressed(ImGuiKey_Delete) && ImGui::IsItemHovered()) {
delete_requests[K_DR_Selected_Keys] = true;
}
};
constexpr static f32 knob_width = 8.0f;
constexpr static f32 kf_tab_width = 12.0f;
auto kf_drag_btn_handler = [&io, &drag_rect](
Plugboard::ChainSel *key_loop_target, bool dragging, Plugboard::ChainSel& chain, i32 frame, bool is_sel,
Vector<u32>::iterator sel_it, const Plugboard::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 Plugboard::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++;
}
chain.segments.insert(pos, {frame, static_cast<f32>(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::ranges::find(keys_selected_by_bg_drag,
&(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);
}
}
};
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0, 0));
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2{});
ImGui::PushStyleVar(ImGuiStyleVar_ItemInnerSpacing, ImVec2{});
ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, ImVec2{2, 0});
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);
ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_NoSort, 140.0f);
ImGui::TableSetupColumn("Blending", ImGuiTableColumnFlags_NoSort, 120.0f);
ImGui::TableSetupColumn("##Enable", ImGuiTableColumnFlags_NoSort);
ImGui::TableSetupColumn("##Timeline",
ImGuiTableColumnFlags_NoSort | ImGuiTableColumnFlags_WidthStretch);
ImGui::TableNextRow(ImGuiTableRowFlags_Headers, view_height);
for (int column = 0; column < 4; column++) {
ImGui::TableSetColumnIndex(column);
ImGui::TableHeader(ImGui::TableGetColumnName(column));
}
// Timeline controls
ImGui::TableSetColumnIndex(4);
auto *window = ImGui::GetCurrentWindow();
tl_init_pos = ImGui::GetCursorScreenPos();
view_width = ImGui::GetColumnWidth() - 10.0f,
tl_width = view_width - knob_width,
view_height = ImGui::TableGetHeaderRowHeight(),
control_left = tl_init_pos.x,
control_right = tl_init_pos.x + view_width;
mouse_tl_x = io.MousePos.x - tl_init_pos.x;
fr_step = 1.0f / static_cast<f32>(s.frame_max + 1) * view_width / view_amt;
ImGui::SetCursorScreenPos({tl_init_pos.x - knob_width / 2.0f + view_width * view_left, tl_init_pos.y});
ImGui::InvisibleButton("##TL_L", ImVec2{knob_width, view_height});
if (ImGui::IsItemHovered()) {
ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeEW);
}
if (ImGui::IsItemClicked(ImGuiMouseButton_Left)) {
view_left_old = view_left;
}
if (ImGui::IsItemActive()) {
view_left = std::clamp(view_left_old + ImGui::GetMouseDragDelta().x / tl_width, 0.0f, view_right - 2.0f / static_cast<f32>(s.frame_max));
}
ImGui::SameLine();
ImGui::InvisibleButton("##TL_INTERVAL", ImVec2{std::max(2.0f, tl_width * view_amt), view_height});
if (ImGui::IsItemHovered()) {
ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeEW);
}
if (ImGui::IsItemClicked(ImGuiMouseButton_Left)) {
view_left_old = view_left;
view_right_old = view_right;
}
if (ImGui::IsItemActive()) {
view_left = std::clamp(view_left_old + ImGui::GetMouseDragDelta().x / tl_width, 0.0f, 1.0f - view_amt);
view_right = view_left + view_amt;
}
ImGui::SameLine();
ImGui::InvisibleButton("##TL_R", ImVec2{knob_width, view_height});
if (ImGui::IsItemHovered()) {
ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeEW);
}
if (ImGui::IsItemClicked(ImGuiMouseButton_Left)) {
view_right_old = view_right;
}
if (ImGui::IsItemActive()) {
view_right = std::clamp(view_right_old + ImGui::GetMouseDragDelta().x / tl_width, view_left + 2.0f / static_cast<f32>(s.frame_max), 1.0f);
}
window->DrawList->AddLine({tl_init_pos.x + static_cast<f32>(s.current_frame) / static_cast<f32>(s.frame_max + 1) * view_width, tl_init_pos.y},
{tl_init_pos.x + static_cast<f32>(s.current_frame) / static_cast<f32>(s.frame_max + 1) * view_width, tl_init_pos.y + view_height},
0x773333FF);
window->DrawList->AddRect({tl_init_pos.x - knob_width / 2.0f, tl_init_pos.y}, {tl_init_pos.x + knob_width / 2.0f, tl_init_pos.y + view_height},
0x22FFFFFF);
window->DrawList->AddRect({tl_init_pos.x + view_width - knob_width / 2.0f, tl_init_pos.y},
{tl_init_pos.x + view_width + knob_width / 2.0f, tl_init_pos.y + view_height}, 0x22FFFFFF);
window->DrawList->AddRectFilled({tl_init_pos.x + view_width * view_left - knob_width / 2.0f, tl_init_pos.y},
{tl_init_pos.x + view_width * view_left + knob_width / 2.0f, tl_init_pos.y + view_height},
0xAAFFFFFF, 2.0f);
window->DrawList->AddRectFilled({tl_init_pos.x + view_width * view_right - knob_width / 2.0f, tl_init_pos.y},
{tl_init_pos.x + view_width * view_right + knob_width / 2.0f, tl_init_pos.y + view_height},
0xAAFFFFFF, 2.0f);
window->DrawList->AddRectFilled(
{tl_init_pos.x + view_width * view_left, tl_init_pos.y + view_height * .2f},
{tl_init_pos.x + view_width * view_right, tl_init_pos.y + view_height * .8f}, 0x33FFFFFF, 2.0f);
ImGui::SetCursorScreenPos(tl_init_pos);
ImGui::InvisibleButton("##TL_BG", {view_width, view_height}); // block bg interaction
ImGui::SameLine();
ImGui::TableHeader(""); // TL header
// Main rows
i32 move_from = -1, move_to = -1;
bool after{};
// KF controls
static bool started_dragging = false, dragging = false;
if (started_dragging) {
dragging = true;
}
if (ImGui::IsMouseReleased(ImGuiMouseButton_Left)) {
dragging = false;
}
for (u32 i = 0; i < s.layers.size(); i++) {
auto& current_layer = s.layers[i];
const bool selected = std::ranges::find(s.selected, i) != s.selected.end(),
disabled = std::ranges::find(s.disabled, i) != s.disabled.end();
ImGui::PushID(static_cast<i32>(i));
ImGui::TableNextRow(ImGuiTableRowFlags_None, row_height);
ImGui::TableSetColumnIndex(0);
ImGui::Text("%u", i);
ImGui::TableSetColumnIndex(1);
static auto base_flags = ImGuiTreeNodeFlags_SpanFullWidth | ImGuiTreeNodeFlags_OpenOnArrow |
ImGuiTreeNodeFlags_AllowOverlap | ImGuiTreeNodeFlags_FramePadding;
auto flags = base_flags;
if (selected)
flags |= ImGuiTreeNodeFlags_Selected;
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, {0.0f, (row_height - ImGui::GetTextLineHeight()) / 2.0f});
bool layer_open = ImGui::TreeNodeEx(current_layer.name.c_str(), flags);
if (ImGui::BeginPopupContextItem()) {
if (!s.selected.empty()) {
if (ImGui::Button("Delete")) {
delete_requests[K_DR_Selected_Layers] = true;
ImGui::CloseCurrentPopup();
}
if (ImGui::Button("Make Subcomposition")) {
delete_requests[K_DR_Make_Subcomposition] = true;
ImGui::CloseCurrentPopup();
}
}
ImGui::EndPopup();
}
if (ImGui::IsItemClicked() && !ImGui::IsItemToggledOpen()) {
if (io.KeyCtrl) {
if (selected)
std::erase(s.selected, i);
else
s.selected.push_back(i);
}
else if (io.KeyShift) {
// TODO
}
else {
s.selected.clear();
if (!selected)
s.selected.push_back(i);
}
s.active = selected ? -1 : static_cast<i32>(i);
}
auto source_flags =
ImGuiDragDropFlags_SourceNoDisableHover | ImGuiDragDropFlags_SourceNoHoldToOpenOthers;
if (!no_selection) source_flags |= ImGuiDragDropFlags_SourceNoPreviewTooltip;
if ((no_selection || selected) && ImGui::BeginDragDropSource(source_flags)) {
if (s.selected.empty()) ImGui::Text("Swap with #%u %s", i, current_layer.name.c_str());
ImGui::SetDragDropPayload("K_COMP_REORDER", &i, sizeof(u32));
ImGui::EndDragDropSource();
}
if ((no_selection || !selected) && ImGui::BeginDragDropTarget()) {
if (!no_selection) {
after = io.MousePos.y - ImGui::GetWindowPos().y >
ImGui::GetCursorPosY() - row_height / 2.0f;
if (after)
ImGui::SetTooltip("After #%u %s", i, current_layer.name.c_str());
else
ImGui::SetTooltip("Before #%u %s", i, current_layer.name.c_str());
}
if (const ImGuiPayload *payload = ImGui::AcceptDragDropPayload("K_COMP_REORDER")) {
move_from = *(const int *) payload->Data;
move_to = static_cast<i32>(i);
}
ImGui::EndDragDropTarget();
}
ImGui::TableSetColumnIndex(2);
ImGui::SetNextItemWidth(-FLT_MIN);
if (ImGui::BeginCombo("##Blending", Graphics::BlendingToString[current_layer.mode])) {
for (i32 b = Graphics::K_V_AlphaOver; b != Graphics::K_V_Count; b++) {
const bool is_selected = (static_cast<Graphics::Blending>(b) == current_layer.mode);
if (ImGui::Selectable(Graphics::BlendingToString[b], is_selected))
current_layer.mode = static_cast<Graphics::Blending>(b);
if (is_selected)
ImGui::SetItemDefaultFocus();
}
ImGui::EndCombo();
}
ImGui::TableSetColumnIndex(3);
bool cp = !disabled;
ImGui::Checkbox("##Enable", &cp);
if (cp == disabled) {
if (disabled)
std::erase(s.disabled, i);
else
s.disabled.push_back(i);
}
// Timeline
ImGui::TableSetColumnIndex(4);
f32 init_y = ImGui::GetCursorScreenPos().y;
if (!show_curve_editor) {
const static f32 layer_bound_width = 5.0f;
const bool l_le_right_extr =
static_cast<f32>(current_layer.in) / static_cast<f32>(s.frame_max + 1) <= view_right,
l_in = l_le_right_extr &&
static_cast<f32>(current_layer.in) / static_cast<f32>(s.frame_max + 1) >= view_left,
l_ge_left_extr =
static_cast<f32>(current_layer.out) / static_cast<f32>(s.frame_max + 1) >= view_left,
l_out = l_ge_left_extr &&
static_cast<f32>(current_layer.out) / static_cast<f32>(s.frame_max + 1) <=
view_right;
f32 in_pos = TimelineFrameToScreenView(view_left, view_amt, view_width, current_layer.in,
s.frame_max),
out_pos = TimelineFrameToScreenView(view_left, view_amt, view_width, current_layer.out,
s.frame_max);
ImGui::PushID(0);
if (l_in) {
ImGui::SetCursorPosX(ImGui::GetCursorPosX() + in_pos);
static f32 l_in_old;
ImGui::PushStyleColor(ImGuiCol_Button, 0xFF666666);
ImGui::Button("##Layer_L", {layer_bound_width, row_height});
if (ImGui::IsItemClicked()) {
l_in_old = in_pos;
}
if (ImGui::IsItemHovered()) {
ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeEW);
}
if (ImGui::IsItemActive()) {
current_layer.in = std::min(TimelineScreenViewToFrame(view_left, view_amt, view_width,
std::clamp(
ImGui::GetMouseDragDelta().x +
l_in_old, 0.0f, view_width),
s.frame_max), current_layer.out);
}
ImGui::PopStyleColor();
ImGui::SameLine(0.0f, 0.0f);
}
if (l_le_right_extr && l_ge_left_extr) {
ImGui::Button(current_layer.name.c_str(),
{std::min(out_pos + fr_step, view_width) - static_cast<f32>(l_in) * in_pos -
layer_bound_width * static_cast<f32>(l_in + l_out), row_height});
static u32 in_old, out_old;
if (ImGui::IsItemClicked()) {
in_old = current_layer.in;
out_old = current_layer.out;
}
if (ImGui::IsItemActive()) {
i32 off = static_cast<i32>(std::trunc(ImGui::GetMouseDragDelta().x / fr_step));
off = std::max(-static_cast<i32>(in_old), off);
current_layer.in = in_old + off;
current_layer.out = out_old + off;
}
}
if (l_out) {
static f32 r_out_old;
ImGui::SameLine(0.0f, 0.0f);
ImGui::PushStyleColor(ImGuiCol_Button, 0xFF666666);
ImGui::Button("##Layer_R", {layer_bound_width, row_height});
if (ImGui::IsItemClicked()) {
r_out_old = out_pos;
}
if (ImGui::IsItemHovered()) {
ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeEW);
}
if (ImGui::IsItemActive()) {
current_layer.out = std::max(TimelineScreenViewToFrame(view_left, view_amt, view_width,
std::clamp(
ImGui::GetMouseDragDelta().x +
r_out_old, 0.0f, view_width),
s.frame_max), current_layer.in);
}
ImGui::PopStyleColor();
}
ImGui::SetCursorScreenPos(ImVec2{tl_init_pos.x, init_y});
tl_bg_handler();
ImGui::PopID();
}
if (layer_open) {
i32 j = 0;
for (auto &u: current_layer.track.uniforms) {
if (!(u.status & K_U_Exposed))
continue;
ImGui::PushID(j);
ImGui::TableNextRow(ImGuiTableRowFlags_None, row_height);
ImGui::TableSetColumnIndex(0);
ImGui::RadioButton("##Socket", false);
if (ImGui::BeginDragDropSource(ImGuiDragDropFlags_None)) {
ImGui::SetDragDropPayload("K_PLUG_IN_TO_OUT", &u.connection,
sizeof(u.connection));
ImGui::EndDragDropSource();
}
if (ImGui::BeginDragDropTarget()) {
if (const ImGuiPayload *payload = ImGui::AcceptDragDropPayload("K_PLUG_OUT_TO_IN")) {
auto out = *(const Plugboard::ConnectInfo *) payload->Data;
Plugboard::Connect(u.connection.p, u.connection.index, out.p, out.index);
s.plugboard.RecollectChains();
}
ImGui::EndDragDropTarget();
}
auto& connected_v = std::visit([&u](auto&& arg) -> auto& { return arg->in[u.connection.index].value; }, u.connection.p);
std::visit([&io, &u, &dragging_on_socket, &drag_source, &s, &style](auto &&arg) {
using T = std::decay_t<decltype(arg)>;
if constexpr (std::is_same_v<T, Plugboard::ConnectInfo>) {
if (ImGui::IsItemActive())
links_pos[arg].sinks.push_back(io.MousePos);
else
links_pos[arg].sinks.push_back(ImGui::GetCursorScreenPos() +
ImVec2{ImGui::GetFrameHeight() / 2,
-ImGui::GetFrameHeight() / 2 -
style.ItemInnerSpacing.y});
}
else if (ImGui::IsItemActive()) {
if (ImGui::IsKeyPressed(ImGuiKey_LeftShift)) {
auto& c = s.plugboard.nodes.Store(Plugboard::Chain{});
c.extra.chain.segments.push_back({static_cast<i32>(s.current_frame), std::get<Plugboard::T_Map<Plugboard::T_Float>::type>(arg), Plugboard::InterpolationExtra{Plugboard::K_I_Linear}});
c.extra.chain.selected.push_back(0);
Plugboard::Connect(u.connection.p, u.connection.index, &c, 0);
Plugboard::Connect(&c, 0, &s.plugboard.in_instance, 0);
auto& nodes = s.plugboard.show_nodes[u.connection.index];
nodes.clear();
nodes.push_back({&c, 0});
}
else {
dragging_on_socket = true;
drag_source = ImGui::GetCursorScreenPos() + ImVec2{ImGui::GetFrameHeight() / 2,
-ImGui::GetFrameHeight() / 2 -
style.ItemInnerSpacing.y};
}
}
}, connected_v);
ImGui::TableSetColumnIndex(1);
auto u_flags = ImGuiTreeNodeFlags_SpanFullWidth | ImGuiTreeNodeFlags_NoTreePushOnOpen | ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_FramePadding | ImGuiTreeNodeFlags_DefaultOpen;
if (connected_v.index() != 1)
u_flags |= ImGuiTreeNodeFlags_Leaf;
bool uniform_open = ImGui::TreeNodeEx(u.name.c_str(), u_flags);
ImGui::TableSetColumnIndex(2);
ImGui::SetNextItemWidth(-FLT_MIN);
if (auto *v = std::get_if<Plugboard::T_Map<Plugboard::T_Count>::type>(&connected_v))
DrawPlugboardVariableEditWidget(*v);
ImGui::TableSetColumnIndex(3);
ImGui::TableSetColumnIndex(4);
if (!show_curve_editor) {
auto p = ImGui::GetCursorScreenPos(); // restored later
// plot uniform if connected
ImPlot::PushStyleVar(ImPlotStyleVar_PlotPadding, {0.0f, 0.0f});
ImPlot::PushStyleVar(ImPlotStyleVar_FitPadding, {.1f, .1f});
ImPlot::PushStyleColor(ImPlotCol_FrameBg, 0);
ImPlot::PushStyleColor(ImPlotCol_PlotBg, 0);
ImPlot::PushStyleColor(ImPlotCol_PlotBorder, 0);
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 info{
.begin = view_left * static_cast<f32>(s.frame_max + 1),
.end = view_right * static_cast<f32>(s.frame_max + 1),
.samples = 100,
.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);
CompositionState ss = i.s;
ss.current_frame = static_cast<u64>(std::round(x));
bool good;
Vector<Plugboard::ConnectInfo> info;
return {x, std::get<Plugboard::T_Map<Plugboard::T_Float>::type>(
Plugboard::ConvertValue(Plugboard::Eval(ss, i.connected_v),
Plugboard::T_Float, good))};
}, &info, info.samples);
ImPlot::EndPlot();
}
else {
ImGui::TextUnformatted("Constant");
}
ImPlot::PopStyleColor(3);
ImPlot::PopStyleVar(2);
ImGui::SetCursorScreenPos(p);
tl_bg_handler();
}
if (uniform_open && connected_v.index() == 1) {
for (const auto& info: s.plugboard.show_nodes[u.connection.index]) {
ImGui::TableNextRow(ImGuiTableRowFlags_None, row_height);
ImGui::TableSetColumnIndex(0);
ImGui::TableSetColumnIndex(1);
ImGui::Indent();
auto n_flags = ImGuiTreeNodeFlags_SpanFullWidth |
ImGuiTreeNodeFlags_NoTreePushOnOpen | ImGuiTreeNodeFlags_Leaf |
ImGuiTreeNodeFlags_FramePadding;
auto chain_sel_it = std::ranges::find(s.plugboard.selected_nodes, info.p);
if (chain_sel_it != s.plugboard.selected_nodes.end())
n_flags |= ImGuiTreeNodeFlags_Selected;
ImGui::TreeNodeEx(std::visit([](auto&& arg){ return arg->name; }, info.p), n_flags);
if (ImGui::IsItemClicked()) {
if (chain_sel_it != s.plugboard.selected_nodes.end())
s.plugboard.selected_nodes.erase(chain_sel_it);
else
s.plugboard.selected_nodes.push_back(info.p);
}
ImGui::TableSetColumnIndex(2);
auto& [chain, chain_copy] = std::get<Plugboard::Chain*>(info.p)->extra;
if (tl_clear_selection_request)
chain.selected.clear();
bool m_empty = chain.segments.empty(), m_has_before = !m_empty && chain.segments.begin()->frame < s.current_frame;
ImGui::BeginDisabled(m_empty || !m_has_before);
if (ImGui::Button("<")) {
s.current_frame = std::prev(std::lower_bound(chain.segments.begin(), chain.segments.end(),
s.current_frame,
[](const Plugboard::ChainSegment &a, i32 b) {
return a.frame < b;
}))->frame;
}
ImGui::EndDisabled();
ImGui::SameLine();
f32 v = std::get<Plugboard::T_Map<Plugboard::T_Float>::type>(
Plugboard::Eval(s, info));
if (ImGui::DragFloat("##value", &v, 0.005f)) {
if (std::ranges::find(s.plugboard.selected_nodes, info.p) == s.plugboard.selected_nodes.end())
s.plugboard.selected_nodes.push_back(info.p);
auto l = std::lower_bound(chain.segments.begin(), chain.segments.end(), s.current_frame,
[](const Plugboard::ChainSegment& a, i32 b) { return a.frame < b; });
if (l != chain.segments.end() && l->frame == static_cast<i32>(s.current_frame))
l->value = v;
else {
auto d = std::distance(chain.segments.begin(), l);
for (auto& sel : chain.selected)
if (sel >= d)
sel++;
chain.selected.push_back(d);
chain.segments.insert(l, {static_cast<i32>(s.current_frame), v, Plugboard::InterpolationExtra{
m_has_before ? std::prev(l)->interp.interp : Plugboard::K_I_Linear
}});
}
}
ImGui::SameLine();
ImGui::BeginDisabled(m_empty || chain.segments.back().frame <= s.current_frame);
if (ImGui::Button(">"))
s.current_frame = std::upper_bound(chain.segments.begin(), chain.segments.end(), s.current_frame,
[](i32 a, const Plugboard::ChainSegment& b) { return a < b.frame; })->frame;
ImGui::EndDisabled();
ImGui::TableSetColumnIndex(3);
ImGui::Text("%lu", chain.segments.size());
ImGui::TableSetColumnIndex(4);
if (!show_curve_editor) {
ImVec2 begin_tl = ImGui::GetCursorScreenPos();
if (started_dragging)
chain_copy = chain;
auto key_loop_target = &chain;
if (dragging) {
chain.segments.clear();
chain.selected.clear();
key_loop_target = &chain_copy;
}
for (u32 ii = 0; ii < key_loop_target->segments.size(); ii++) {
const auto &[k, val, segment] = (key_loop_target->segments)[ii];
ImGui::PushID(k);
auto sel_it = std::ranges::find(key_loop_target->selected, ii);
bool is_sel = sel_it != key_loop_target->selected.end();
i32 frame = k;
if (dragging && is_sel) {
frame += static_cast<i32>(ImGui::GetMouseDragDelta().x / tl_width *
view_amt *
static_cast<f32>(s.frame_max + 1));
}
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};
ImGui::SetCursorScreenPos(draw_pos);
ImGui::PushStyleColor(ImGuiCol_Button, 0x00000000);
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(1.0f, (row_height - ImGui::GetTextLineHeight()) / 2.0f));
ImGui::Button((const char*)(is_sel ? u8"\u25c8" : u8"\u25c7"), {kf_tab_width, row_height * .8f});
ImGui::PopStyleVar();
ImGui::PopStyleColor();
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});
ImGui::PopID();
}
for (auto it = chain.segments.begin(); it != chain.segments.end(); it++) {
auto nit = std::next(it);
if (nit != chain.segments.end() && it->value != nit->value) {
window->DrawList->AddRectFilled(
{TimelineFrameToScreenView(view_left, view_amt, view_width,
it->frame,
s.frame_max) + begin_tl.x,
begin_tl.y + row_height / 2.0f - 2.0f},
{TimelineFrameToScreenView(view_left, view_amt, view_width,
nit->frame,
s.frame_max) + begin_tl.x,
begin_tl.y + row_height / 2.0f + 2.0f},
std::ranges::find(chain.selected,
std::distance(chain.segments.begin(), it)) !=
chain.selected.end() &&
std::ranges::find(chain.selected,
std::distance(chain.segments.begin(), nit)) !=
chain.selected.end() ? 0x88FFAAAA : 0x44AAAAAA);
}
}
ImGui::SetCursorScreenPos(begin_tl);
tl_bg_handler();
}
}
}
ImGui::PopID();
j++;
}
ImGui::TreePop();
}
else for (auto& u: current_layer.track.uniforms) {
if (!(u.status & K_U_Exposed))
continue;
std::visit([table_left, &init_y](auto &&arg) {
using T = std::decay_t<decltype(arg)>;
if constexpr (std::is_same_v<T, Plugboard::ConnectInfo>)
links_pos[arg].sinks.push_back( // make nodes connect to collapsed layer row
{table_left, init_y + row_height / 2.0f});
}, std::visit([&u](auto&& arg) -> auto& { return arg->in[u.connection.index].value; }, u.connection.p));
}
ImGui::PopStyleVar(); // FramePadding
ImGui::PopID();
}
if (started_dragging && dragging)
started_dragging = false;
tl_end_begin = {tl_init_pos.x, ImGui::GetCursorScreenPos().y};
// Handle layer reordering
if (move_from != -1 && move_to != -1) { // do layer move
if (no_selection)
std::swap(s.layers[move_to], s.layers[move_from]);
else {
const u32 n = s.selected.size();
u32 inserted = 0;
u32 target = move_to + after, popped_before_target = 0;
for (auto it = s.selected.rbegin(); it != s.selected.rend(); it++, inserted++) {
u32 sel = *it;
if (sel > target)
sel += inserted; // note that we iterate backwards, so cur index is always right if < target
Layer l = s.layers[sel];
s.layers.erase(s.layers.begin() + sel);
if (sel < move_to) popped_before_target++;
s.layers.emplace(s.layers.begin() + target - popped_before_target, l);
}
s.selected.clear();
for (u32 j = 0; j < n; j++)
s.selected.push_back(target - popped_before_target + j);
}
}
ImGui::EndTable();
}
ImGui::PopStyleVar(4);
ImGui::SetCursorScreenPos(nodes_begin);
if (ImGui::BeginChild("Node Connection Overlay", {table_left - ImGui::GetWindowPos().x, 0.0f}, false, ImGuiWindowFlags_NoInputs)) {
auto *window = ImGui::GetCurrentWindow();
for (auto &[_, link]: links_pos)
for (auto &sink: link.sinks)
window->DrawList->AddBezierCubic(link.source,
{link.source.x + 60.0f, link.source.y},
{sink.x - 60.0f, sink.y}, sink, 0xFFFFFFFF,
2.0f);
if (dragging_on_socket)
window->DrawList->AddLine(io.MousePos, drag_source, 0xFFFFFFFF, 2.0f);
}
ImGui::EndChild();
ImGui::SetCursorScreenPos(tl_end_begin);
if (ImGui::BeginChild("TL Bottom Unfilled Drag Overlay")) {
tl_bg_handler(ImGui::GetContentRegionAvail().y);
}
ImGui::EndChild();
ImVec2 tl_grid_cursor_pos = {tl_init_pos.x, tl_init_pos.y + view_height};
if (show_curve_editor) {
ImGui::SetCursorScreenPos(tl_grid_cursor_pos);
ImGui::PushStyleColor(ImGuiCol_ChildBg, 0xFF111111);
if (ImGui::BeginChild("Curve Editor", ImGui::GetContentRegionAvail())) {
ImGui::SetCursorScreenPos(tl_grid_cursor_pos);
ImPlot::PushStyleVar(ImPlotStyleVar_PlotPadding, {0.0f, 0.0f});
ImPlot::PushStyleVar(ImPlotStyleVar_FitPadding, {.1f, .1f});
ImPlot::PushStyleColor(ImPlotCol_FrameBg, 0);
ImPlot::PushStyleColor(ImPlotCol_PlotBg, 0);
ImPlot::PushStyleColor(ImPlotCol_PlotBorder, 0);
if (ImPlot::BeginPlot("uniform", {view_width, ImGui::GetContentRegionAvail().y}, ImPlotFlags_NoTitle | ImPlotFlags_NoMenus | ImPlotFlags_NoBoxSelect | ImPlotFlags_NoMouseText | ImPlotFlags_NoFrame | ImPlotFlags_NoInputs)) {
static bool started_dragging = false, dragging = false;
ImPlot::SetupAxis(ImAxis_X1, "time", ImPlotAxisFlags_NoHighlight | ImPlotAxisFlags_NoLabel | ImPlotAxisFlags_NoGridLines);
auto flags = ImPlotAxisFlags_NoHighlight | ImPlotAxisFlags_NoLabel | ImPlotAxisFlags_Opposite | ImPlotAxisFlags_NoTickLabels | ImPlotAxisFlags_NoTickMarks;
if (!dragging)
flags |= ImPlotAxisFlags_AutoFit;
ImPlot::SetupAxis(ImAxis_Y1, "val", flags);
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();
if (started_dragging) {
dragging = true;
}
if (ImGui::IsMouseReleased(ImGuiMouseButton_Left)) {
dragging = false;
}
u32 keys = 0;
for (const auto& n : s.plugboard.selected_nodes) {
if (!std::holds_alternative<Plugboard::Chain*>(n))
continue;
ImGui::PushID(&n);
auto& [chain, chain_copy] = std::get<Plugboard::Chain*>(n)->extra;
Plugboard::ConnectInfo i {n, 0}; // it's a chain -- 0 is out
if (started_dragging)
chain_copy = chain;
auto key_loop_target = &chain;
if (dragging) {
chain.segments.clear();
chain.selected.clear();
key_loop_target = &chain_copy;
}
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::ranges::find(key_loop_target->selected, ii);
bool is_sel = sel_it != key_loop_target->selected.end();
bool is_nxt_sel = ii + 1 < key_loop_target->segments.size() && std::ranges::find(key_loop_target->selected, ii + 1) != key_loop_target->selected.end();
i32 frame = k;
f64 v = val;
f64 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 = mouse_pos.x - mouse_clicked_pos.x;
drag_off_y = mouse_pos.y - mouse_clicked_pos.y;
if (dragging && is_sel) {
frame += static_cast<i32>(std::round(drag_off_x));
v += drag_off_y;
}
f64 x = frame, y = v;
ImVec2 k_pos = ImPlot::PlotToPixels(x, y);
const bool is_last = ii + 1 == key_loop_target->segments.size();
if (is_last)
ImPlot::Annotation(x, y, {1.0f, 1.0f, 1.0f, 1.0f}, {15.0f, 0.0f}, true);
else
ImPlot::Annotation(x, y, {1.0f, 1.0f, 1.0f, 1.0f}, {15.0f, 0.0f}, true, "%.2f, %.2f, %s", x, y, Plugboard::K_Interpolation_Names[segment.interp]);
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 == Plugboard::K_I_CubicBezier && !is_last) {
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 += static_cast<i32>(std::round(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;
static f32 cap_l, cap_r;
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];
auto cp1 = segment.v[2] + 1.0f;
auto d_sqrt = std::sqrt(cp1 * cp1 - 4.0f * segment.v[2] * segment.v[2]);
cap_l = (cp1 - d_sqrt) / 2.0f;
cap_r = (cp1 + d_sqrt) / 2.0f;
}
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)), segment.v[2] >= 0.0f ? 0.0f : cap_l, cap_r);
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];
auto d_sqrt = std::sqrt(4.0f * segment.v[0] - 3.0f * segment.v[0] * segment.v[0]);
cap_l = (segment.v[0] - d_sqrt) / 2.0f;
cap_r = (segment.v[0] + d_sqrt) / 2.0f;
}
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)), cap_l, segment.v[0] <= 1.0f ? 1.0f : cap_r);
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(s.plugboard.nodes.GetName(static_cast<Plugboard::K_P_Nodes>(n.index())), [](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);
CompositionState ss = i.s;
ss.current_frame = static_cast<u64>(x);
bool good;
Vector<Plugboard::ConnectInfo> info;
return {x, std::get<Plugboard::T_Map<Plugboard::T_Float>::type>(
Plugboard::ConvertValue(Plugboard::Eval(ss, i.connected_v),
Plugboard::T_Float, good))};
}, &info, info.samples);
ImGui::PopID();
}
ImPlot::EndPlot();
if (started_dragging && dragging)
started_dragging = false;
}
ImPlot::PopStyleColor(3);
ImPlot::PopStyleVar(2);
ImGui::SetCursorScreenPos(ImGui::GetCursorStartPos());
tl_bg_handler(ImGui::GetContentRegionAvail().y, ImGui::GetContentRegionAvail().x);
if (tl_clear_selection_request)
for (auto& p : s.plugboard.selected_nodes)
if (auto *n = std::get_if<Plugboard::Chain*>(&p)){
auto& [chain, _] = (*n)->extra;
chain.selected.clear();
}
}
ImGui::EndChild();
ImGui::PopStyleColor();
}
ImGui::SetCursorScreenPos(tl_grid_cursor_pos);
if (ImGui::BeginChild("TL Grid Overlay", ImGui::GetContentRegionAvail(), ImGuiChildFlags_None, ImGuiWindowFlags_NoInputs)) {
auto window = ImGui::GetCurrentWindow();
// Frame Grid
static f32 frame_grid_min_width = 20.0f;
f32 grid_step =
fr_step >= frame_grid_min_width ? fr_step : std::ceil(frame_grid_min_width / fr_step) * fr_step;
for (f32 fr = TimelineFrameToScreenView(view_left, view_amt, view_width,
std::ceil(view_left * static_cast<f32>(s.frame_max + 1)),
s.frame_max) + tl_init_pos.x;
fr < tl_init_pos.x + view_width; fr += grid_step)
window->DrawList->AddLine({fr, tl_init_pos.y},
{fr, ImGui::GetWindowSize().y + tl_init_pos.y}, 0x11FFFFFF, 1.0f);
// Playhead
if (TimelineFrameInView(view_left, view_right, s.current_frame, s.frame_max)) {
f32 fr = TimelineFrameToScreenView(view_left, view_amt, view_width, s.current_frame, s.frame_max) +
tl_init_pos.x;
window->DrawList->AddLine(
{fr, tl_init_pos.y},
{fr, ImGui::GetWindowSize().y + tl_init_pos.y}, 0x773333FF, 1.0f);
window->DrawList->AddRectFilled(
{fr, tl_init_pos.y},
{fr + fr_step, ImGui::GetWindowSize().y + tl_init_pos.y}, 0x223333FF, 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) && mouse_tl_x >= 0 && mouse_tl_x < view_width) {
if (io.MouseWheel != 0.0f) {
if (ImGui::IsKeyDown(ImGuiKey_LeftCtrl)) {
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;
}
}
if (show_curve_editor) {
if (ImGui::IsKeyDown(ImGuiKey_LeftShift)) {
}
else {
}
}
}
}
if (tl_clear_selection_request_this_frame && tl_clear_selection_request)
tl_clear_selection_request = false;
}
ImGui::EndChild();
ImGui::End();
}
void Layer(CompositionState& s) {
struct TextFilters { static int VariableName(ImGuiInputTextCallbackData *data) {
return !(data->EventChar < 256 &&
((data->EventChar >= 'a' && data->EventChar <= 'z') ||
(data->EventChar >= 'A' && data->EventChar <= 'Z')||
(data->EventChar >= '0' && data->EventChar <= '9') ||
data->EventChar == '_'));
} };
if (ImGui::Begin("Layer")) {
if (s.active == -1)
ImGui::TextUnformatted("No active layer.");
else {
ImGui::Text("Editing Layer %u: %s", s.active, s.layers[s.active].name.c_str());
if (ImGui::BeginTabBar("Props", ImGuiTabBarFlags_None)) {
if (ImGui::BeginTabItem("Uniforms")) {
static i32 type_current = 0;
static String name{};
if (ImGui::Button("Add Uniform") && !name.empty() && !isdigit(name[0]) && !(name[0] == 'f' && name.length() == 1)) {
s.layers[s.active].track.AddUniform("u_" + name, ExpandVariant(static_cast<ShaderGraph::Type>(type_current)), K_U_Exposed);
VisualTrack::ExposeUniform(s, s.layers[s.active].track.uniforms.back());
}
ImGui::SameLine();
ImGui::SetNextItemWidth(-80.0f);
ImGui::InputText("##UniformName", &name, ImGuiInputTextFlags_CallbackCharFilter, TextFilters::VariableName);
ImGui::SameLine();
ImGui::SetNextItemWidth(-FLT_MIN);
ImGui::Combo("##Type", &type_current, ShaderGraph::Type_To_Str, ShaderGraph::T_Count);
if (ImGui::BeginTable("Uniforms", 4, ImGuiTableFlags_Borders)) {
ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_NoSort | ImGuiTableColumnFlags_WidthFixed);
ImGui::TableSetupColumn("Type", ImGuiTableColumnFlags_NoSort | ImGuiTableColumnFlags_WidthFixed);
ImGui::TableSetupColumn("Value", ImGuiTableColumnFlags_NoSort | ImGuiTableColumnFlags_WidthStretch);
ImGui::TableSetupColumn("##del", ImGuiTableColumnFlags_NoSort | ImGuiTableColumnFlags_WidthFixed);
ImGui::TableHeadersRow();
i32 i = 0;
for (auto it = s.layers[s.active].track.uniforms.begin(); it != s.layers[s.active].track.uniforms.end();) {
if (!(it->status & K_U_Exposed)) {
it++;
continue;
}
ImGui::TableNextRow();
ImGui::PushID(i++);
ImGui::TableNextColumn();
ImGui::TextUnformatted(it->name.c_str());
ImGui::TableNextColumn();
ImGui::TextUnformatted(ShaderGraph::Type_To_Str[it->val.index()]);
ImGui::TableNextColumn();
ImGui::SetNextItemWidth(-FLT_MIN);
std::visit([&it](auto&& n) {
std::visit([](auto&& arg) {
using T = std::decay_t<decltype(arg)>;
if constexpr (std::is_same_v<T, Plugboard::T_Map<Plugboard::Type::T_Count>::type>)
DrawPlugboardVariableEditWidget(arg);
else
ImGui::Text("Plugged into %s!", std::visit([](auto&& d) { return d->name; }, arg.p));
}, n->in[it->connection.index].value);
}, it->connection.p);
ImGui::TableNextColumn();
if (ImGui::Button("X")) {
bgfx::destroy(it->handle);
it = s.layers[s.active].track.uniforms.erase(it);
}
else it++;
ImGui::PopID();
}
ImGui::EndTable();
}
ImGui::EndTabItem();
}
if (ImGui::BeginTabItem("Samplers")) {
static String name{};
if (ImGui::Button("Add Sampler") && !name.empty() && !isdigit(name[0])) {
s.layers[s.active].track.AddSampler("s_" + name);
}
ImGui::SameLine();
ImGui::InputText("##SamplerName", &name, ImGuiInputTextFlags_CallbackCharFilter, TextFilters::VariableName);
if (ImGui::BeginTable("Samplers", 4, ImGuiTableFlags_Borders)) {
ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_NoSort | ImGuiTableColumnFlags_WidthStretch);
ImGui::TableSetupColumn("Dims via", ImGuiTableColumnFlags_NoSort | ImGuiTableColumnFlags_WidthFixed);
ImGui::TableSetupColumn("Source", ImGuiTableColumnFlags_NoSort);
ImGui::TableSetupColumn("##del", ImGuiTableColumnFlags_NoSort | ImGuiTableColumnFlags_WidthFixed);
ImGui::TableHeadersRow();
i32 i = 0;
for (auto it = s.layers[s.active].track.samplers.begin(); it != s.layers[s.active].track.samplers.end();) {
ImGui::TableNextRow();
ImGui::PushID(i++);
ImGui::TableNextColumn();
ImGui::TextUnformatted(it->name.c_str());
ImGui::TableNextColumn();
ImGui::Text("%s%s", it->name.c_str(), "_dims");
ImGui::TableNextColumn();
const char *r_name = std::visit([](auto&& arg) {
using T = std::decay_t<decltype(arg)>;
if constexpr (std::is_same_v<T, Resource::Resource<Resource::K_R_Still>*>) {
return arg->filename.c_str();
}
else if constexpr (std::is_same_v<T, CompositionState*>) {
return arg->name.c_str();
}
}, it->resource);
u32 id = 0;
if (ImGui::BeginCombo("##source", r_name)) {
ImGui::TextUnformatted("----Resources----");
for (auto& [file, res_v] : Resource::resources)
if (auto *res = std::get_if<Resource::Resource<Resource::K_R_Still>>(&res_v)) {
ImGui::PushID(id++);
const bool is_selected = r_name == res->filename.c_str(); // lol
if (ImGui::Selectable(file.c_str(), is_selected)) {
it->resource = res;
it->dims->val = ShaderGraph::XY{static_cast<f32>(res->w), static_cast<f32>(res->h)};
if (std::visit([](auto&& arg){ return arg != nullptr; }, it->frame->connection.p))
VisualTrack::HideUniform(s, *it->frame);
}
if (is_selected)
ImGui::SetItemDefaultFocus();
ImGui::PopID();
}
ImGui::TextUnformatted("----Compositions----");
for (auto& comp : app_state.project.compositions) {
ImGui::PushID(id++);
const bool is_selected = r_name == comp.name.c_str(); // lol
if (ImGui::Selectable(comp.name.c_str(), is_selected)) {
// im sorry... todo refactor
auto dfs = [&s](CompositionState& c){
auto iter = [&s](CompositionState& c, auto& this_lambda) {
if (&s == &c)
return false;
else for (auto& layer : c.layers)
for (auto &sampler: layer.track.samplers) {
if (auto *res = std::get_if<CompositionState *>(
&sampler.resource)) {
if (this_lambda(**res, this_lambda) == false)
return false;
}
}
return true;
};
return iter(c, iter);
};
if (dfs(comp)) {
it->resource = &comp;
it->dims->val = ShaderGraph::XY{static_cast<f32>(comp.width),
static_cast<f32>(comp.height)};
it->frame->val = ShaderGraph::T_Map<ShaderGraph::T_Int>::type(0);
if (std::visit([](auto &&arg) { return arg == nullptr; },
it->frame->connection.p))
VisualTrack::ExposeUniform(s, *it->frame);
}
else
Log(K_L_Warning, "Dependency cycle detected! Composition will not be set for sampler.");
}
if (is_selected)
ImGui::SetItemDefaultFocus();
ImGui::PopID();
}
ImGui::EndCombo();
}
ImGui::TableNextColumn();
if (ImGui::Button("X")) {
bgfx::destroy(it->handle);
it = s.layers[s.active].track.samplers.erase(it);
}
else it++;
ImGui::PopID();
}
ImGui::EndTable();
}
ImGui::EndTabItem();
}
ImGui::EndTabBar();
}
ImGui::InputTextMultiline("##source", &s.layers[s.active].track.shader, ImVec2(-FLT_MIN, ImGui::GetContentRegionAvail().y - 30.0f), ImGuiInputTextFlags_AllowTabInput);
if (ImGui::Button("Submit Shader"))
s.layers[s.active].track.Compile();
}
}
ImGui::End();
}
void Interpolation(CompositionState& s) {
if (ImGui::Begin("Interpolation", nullptr, ImGuiWindowFlags_NoScrollbar)) {
ImGuiIO& io = ImGui::GetIO();
static Plugboard::K_Interpolation type_current = Plugboard::K_I_CubicBezier;
static const ImVec2 p1 { 0.0f, 0.0f}, p4 {1.0f, 1.0f};
static ImVec2 p2{1.0f, 0.0f}, p3{0.0f, 1.0f}, p2_old = p2, p3_old = p3;
if (ImGui::Button("Apply"))
for (auto& layer : s.layers)
for (auto& u : layer.track.uniforms) {
if (!(u.status & K_U_Exposed))
continue;
for (auto& n : s.plugboard.show_nodes[u.connection.index]) {
auto& [chain, _] = std::get<Plugboard::Chain*>(n.p)->extra;
for (auto it = chain.segments.begin(); it != chain.segments.end(); it++) {
auto nit = std::next(it);
if (nit != chain.segments.end() && std::ranges::find(chain.selected, std::distance(chain.segments.begin(), it)) != chain.selected.end() &&
std::ranges::find(chain.selected, std::distance(chain.segments.begin(), nit)) != chain.selected.end()) {
it->interp = {type_current, {p2.x, p2.y, p3.x, p3.y}};
}
}
}
}
ImGui::SameLine();
ImGui::Combo("##Type", (i32*)&type_current, Plugboard::K_Interpolation_Names, Plugboard::K_I_Count);
f32 w_window = std::min(ImGui::GetContentRegionAvail().x, ImGui::GetContentRegionAvail().y);
if (type_current == Plugboard::K_I_CubicBezier) {
f32 y_max = std::max(std::max(p2.y, p3.y), p4.y),
y_min = std::min(std::min(p1.y, p2.y), p3.y),
y_range = y_max - y_min;
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, {10.0f, 10.0f});
if (ImGui::BeginChild("Canvas", {w_window, w_window}, ImGuiChildFlags_Border, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse)) {
f32 w = ImGui::GetContentRegionAvail().x;
ImVec2 pos = ImGui::GetCursorScreenPos();
auto *dl = ImGui::GetWindowDrawList();
ImVec2 tp1 = pos + ImVec2{p1.x * w, ((1.0f - p1.y) - (1.0f - y_max)) * w / y_range},
tp4 = pos + ImVec2{p4.x * w, ((1.0f - p4.y) - (1.0f - y_max)) * w / y_range},
tp2 = pos + ImVec2{p2.x * w, ((1.0f - p2.y) - (1.0f - y_max)) * w / y_range},
tp3 = pos + ImVec2{p3.x * w, ((1.0f - p3.y) - (1.0f - y_max)) * w / y_range};
dl->AddBezierCubic(tp1, tp2, tp3, tp4, 0xFFFFFFFF, 2.0f);
dl->AddLine(tp1, tp2, 0xAAFFFFFF, 1.5f);
dl->AddLine(tp3, tp4, 0xAAFFFFFF, 1.5f);
dl->AddLine(tp1, {tp1.x + w, tp1.y}, 0x44FFFFFF, 2.0f);
dl->AddLine(tp4, {tp4.x - w, tp4.y}, 0x44FFFFFF, 2.0f);
dl->AddText(tp1 + ImVec2{10.0f, -20.0f}, 0xFFFFFFFF, "0.0");
dl->AddText(tp4 + ImVec2{-30.0f, 10.0f}, 0xFFFFFFFF, "1.0");
f32 x = std::clamp((io.MousePos.x - pos.x) / w, 0.0f, 1.0f);
f32 y = Graphics::InjectingCubicBezierFromX(p2.x, p2.y, p3.x, p3.y, x);
dl->AddLine(pos + ImVec2{0, ((1.0f - y) - (1.0f - y_max)) * w / y_range}, pos + ImVec2{w, ((1.0f - y) - (1.0f - y_max)) * w / y_range}, 0xBB5555FF, 1.0f);
dl->AddLine(pos + ImVec2{x * w, 0.0f}, pos + ImVec2{x * w, w}, 0x44FF5555, 1.0f);
ImGui::SetCursorScreenPos({tp1.x, ((1.0f - y) - (1.0f - y_max)) * w / y_range + pos.y});
ImGui::Text("%.2f, %.2f", x, y);
ImGui::SetCursorPos(ImVec2{0.0f, 20.0f});
ImGui::Text("%.2f", y_max);
ImGui::SetCursorPos(ImVec2{w * .8f, w - 15.0f,});
ImGui::Text("%.2f", y_min);
static f32 y_range_old{};
ImVec2 size = {15.0f, 15.0f};
ImGui::SetCursorScreenPos(tp2 - size / 2.0f);
ImVec2 off = io.MousePos - io.MouseClickedPos[0];
ImGui::Button("##P2", size);
if (ImGui::IsItemClicked()) {
y_range_old = y_range;
p2_old = p2;
}
if (ImGui::IsItemActive()) {
p2 = p2_old + ImVec2{off.x / w, off.y / -w * y_range_old};
p2.x = std::clamp(p2.x, 0.0f, 1.0f);
}
ImGui::SetCursorScreenPos(tp3 - size / 2.0f);
ImGui::Button("##P3", size);
if (ImGui::IsItemClicked()) {
y_range_old = y_range;
p3_old = p3;
}
if (ImGui::IsItemActive()) {
p3 = p3_old + ImVec2{off.x / w, off.y / -w * y_range_old};
p3.x = std::clamp(p3.x, 0.0f, 1.0f);
}
}
ImGui::PopStyleVar(1);
ImGui::EndChild();
}
else {
ImPlot::PushStyleVar(ImPlotStyleVar_PlotPadding, {0.0f, 0.0f});
ImPlot::PushStyleVar(ImPlotStyleVar_FitPadding, {.1f, .1f});
ImPlot::PushStyleColor(ImPlotCol_FrameBg, 0);
ImPlot::PushStyleColor(ImPlotCol_PlotBg, 0);
ImPlot::PushStyleColor(ImPlotCol_PlotBorder, 0);
if (ImPlot::BeginPlot("uniform", {w_window, w_window}, ImPlotFlags_CanvasOnly | ImPlotFlags_NoInputs)) {
ImPlot::SetupAxis(ImAxis_X1, "time", ImPlotAxisFlags_NoLabel | ImPlotAxisFlags_NoHighlight | ImPlotAxisFlags_AutoFit);
ImPlot::SetupAxis(ImAxis_Y1, "val", ImPlotAxisFlags_Opposite | ImPlotAxisFlags_NoLabel | ImPlotAxisFlags_NoHighlight | ImPlotAxisFlags_AutoFit);
ImPlot::SetupFinish();
constexpr static u32 samples = 100;
if (type_current == Plugboard::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, Plugboard::EvalInterpolation(t, {.interp = type_current, .v = {p2.x,p2.y, p3.x, p3.y}})};
}, nullptr, samples);
ImPlot::EndPlot();
}
ImPlot::PopStyleColor(3);
ImPlot::PopStyleVar(2);
}
}
ImGui::End();
}
void Assets() {
if (ImGui::Begin("Assets", &draw_assets)) {
if (ImGui::Button("New Composition"))
ImGui::OpenPopup("New Composition");
ImVec2 center = ImGui::GetMainViewport()->GetCenter();
ImGui::SetNextWindowPos(center, ImGuiCond_Appearing, ImVec2(0.5f, 0.5f));
if (ImGui::BeginPopupModal("New Composition", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) {
static String name("Comp");
ImGui::InputText("Name", &name);
static i32 frames = 100;
if (ImGui::InputInt("Frames", &frames, 1))
frames = std::max(frames, 1);
static f32 fps = 30.0f;
if (ImGui::InputFloat("FPS", &fps, 1.0f, 5.0f, "%.1f"))
fps = std::clamp(fps, 1.0f, 120.0f);
static i32 dims[2] = { 1280, 550 };
if (ImGui::InputInt2("Dimensions", dims)) {
dims[0] = std::max(1, dims[0]);
dims[1] = std::max(1, dims[1]);
}
if (ImGui::Button("OK", ImVec2(120, 0)) && !name.empty()) {
app_state.project.compositions.insert({name, 0, static_cast<u64>(frames), fps, static_cast<u32>(dims[0]), static_cast<u32>(dims[1])})->Setup();
ImGui::CloseCurrentPopup();
}
ImGui::SetItemDefaultFocus();
ImGui::SameLine();
if (ImGui::Button("Cancel", ImVec2(120, 0))) {
name.clear();
ImGui::CloseCurrentPopup();
}
ImGui::EndPopup();
}
if (ImGui::BeginTable("Compositions", 4, ImGuiTableFlags_Borders)) {
ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthStretch);
ImGui::TableSetupColumn("Frames", ImGuiTableColumnFlags_WidthFixed);
ImGui::TableSetupColumn("Dimensions", ImGuiTableColumnFlags_WidthFixed);
ImGui::TableSetupColumn("FPS", ImGuiTableColumnFlags_WidthFixed);
ImGui::TableHeadersRow();
for (auto& comp : app_state.project.compositions) {
ImGui::TableNextRow();
ImGui::PushID(&comp);
ImGui::TableNextColumn();
bool sel = app_state.project.inspecting_composition == &comp;
if (ImGui::Selectable(comp.name.c_str(), sel, ImGuiSelectableFlags_SpanAllColumns) && !sel) {
app_state.project.inspecting_composition = &comp;
bg.uniforms[0].val = ShaderGraph::XYZ{
static_cast<f32>(comp.width),
static_cast<f32>(comp.height),
0.0f };
}
if (sel && ImGui::BeginPopupContextItem()) {
if (ImGui::Button("Delete")) {
delete_requests[K_DR_Selected_Compositions] = true;
ImGui::CloseCurrentPopup();
}
ImGui::EndPopup();
}
ImGui::TableNextColumn();
ImGui::Text("%lu", comp.frame_max);
ImGui::TableNextColumn();
ImGui::Text("%ux%u", comp.width, comp.height);
ImGui::TableNextColumn();
ImGui::Text("%.1f", comp.fps);
ImGui::PopID();
}
ImGui::EndTable();
}
if (ImGui::Button("Load")) {
Resource::LoadFile();
}
if (ImGui::BeginTable("Assets", 3, ImGuiTableFlags_Borders)) {
ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthStretch);
ImGui::TableSetupColumn("Type", ImGuiTableColumnFlags_WidthFixed);
ImGui::TableSetupColumn("Modified", ImGuiTableColumnFlags_WidthFixed);
ImGui::TableHeadersRow();
i32 i = 0;
for (auto& [name, res] : Resource::resources) {
ImGui::TableNextRow();
ImGui::PushID(i++);
ImGui::TableNextColumn();
ImGui::Text("%s", name.c_str());
std::visit([](auto&& arg){
using T = std::decay_t<decltype(arg)>;
ImGui::TableNextColumn();
if constexpr (std::is_same_v<T, Resource::Resource<Resource::K_R_Still>>) {
ImGui::Text("Still");
}
else if constexpr (std::is_same_v<T, Resource::Resource<Resource::K_R_ShaderProgram>>) {
ImGui::Text("Shader");
}
else if constexpr (std::is_same_v<T, Resource::Resource<Resource::K_R_StillSequence>>) {
ImGui::Text("Sequence");
}
else if constexpr (std::is_same_v<T, Resource::Resource<Resource::K_R_VideoAudio>>) {
ImGui::Text("Video");
}
else if constexpr (std::is_same_v<T, Resource::Resource<Resource::K_R_AudioOnly>>) {
ImGui::Text("Audio");
}
ImGui::TableNextColumn();
if constexpr (std::is_same_v<T, Resource::Resource<Resource::K_R_ShaderProgram>>) {
ImGui::Text("%s", fmt::format("{:%Y-%m-%d %H:%M:%S}", std::chrono::floor<std::chrono::seconds>(std::chrono::file_clock::to_sys(arg.frag_last_updated))).c_str());
}
else {
ImGui::Text("%s", fmt::format("{:%Y-%m-%d %H:%M:%S}", std::chrono::floor<std::chrono::seconds>(std::chrono::file_clock::to_sys(arg.last_updated))).c_str());
}
}, res);
ImGui::PopID();
}
ImGui::EndTable();
}
}
ImGui::End();
}
void ProcessDeleteRequests(CompositionState& s) {
if (delete_requests[K_DR_Selected_Compositions]) {
decltype(app_state.project.compositions)::iterator it = app_state.project.compositions.end();
// comp deletions should be rare, so this should be fine -- change to cache if this becomes a problem
for (auto itt = app_state.project.compositions.begin(); itt != app_state.project.compositions.end(); itt++) {
if (&(*itt) == &s) {
it = itt;
continue;
}
for (auto& layer : itt->layers)
for (auto& sampler : layer.track.samplers) {
sampler.resource = Resource::fallback_still;
sampler.dims->val = ShaderGraph::T_Map<ShaderGraph::T_XY>::type{
static_cast<f32>(Resource::fallback_still->w),
static_cast<f32>(Resource::fallback_still->h)
};
layer.track.HideUniform(s, *sampler.frame);
}
}
it->Destroy();
if (app_state.project.inspecting_composition == &(*it))
app_state.project.inspecting_composition = nullptr;
app_state.project.compositions.erase(it);
delete_requests[K_DR_Selected_Compositions] = false;
}
if (delete_requests[K_DR_Make_Subcomposition]) {
auto itt = app_state.project.compositions.emplace();
CompositionState& new_comp = *itt;
new_comp.name = "subcomp";
new_comp.fps = s.fps;
new_comp.width = s.width;
new_comp.height = s.height;
new_comp.Setup();
u32 index_in_new_comp = 0;
u64 frame_min = -1, frame_max = 0;
for (auto& j : s.selected) {
new_comp.layers.push_back(s.layers[j]);
for (auto& u : new_comp.layers.back().track.uniforms)
if (u.status & K_U_Exposed) {
VisualTrack::ExposeUniform(new_comp, u);
}
if (std::ranges::find(s.disabled, j) != s.disabled.end())
new_comp.disabled.push_back(index_in_new_comp);
frame_min = std::min(frame_min, s.layers[j].in);
frame_max = std::max(frame_max, s.layers[j].out);
index_in_new_comp++;
}
new_comp.frame_max = frame_max - frame_min;
for (auto& layer : new_comp.layers)
layer.in -= frame_min;
// delete layers from old comp
if (std::ranges::find(s.selected, s.active) != s.selected.end()) // unset active if active is selected
s.active = -1;
u32 deleted = 0, before_active = 0; // stupid counting tricks
const auto sz = s.layers.size();
for (u32 j = 0; j < sz; j++) { // im sure this isn't the best way to go about this
if (std::ranges::find(s.selected, j) != s.selected.end()) {
for (auto &u: s.layers[j - deleted].track.uniforms)
VisualTrack::HideUniform(s, u);
s.layers.erase(s.layers.begin() + j - deleted);
std::erase(s.disabled, j);
if (static_cast<i32>(j) < s.active)
before_active++;
deleted++;
}
else if (auto it = std::ranges::find(s.disabled, j); it != s.disabled.end())
*it -= deleted;
}
s.selected.clear();
if (s.active != -1)
s.active -= static_cast<i32>(before_active);
AddTransformLayer(s, "subcomp");
auto& sampler = s.layers.back().track.samplers.back();
sampler.resource = &new_comp;
sampler.dims->val = ShaderGraph::XY{static_cast<f32>(s.width),
static_cast<f32>(s.height)};
sampler.frame->val = ShaderGraph::T_Map<ShaderGraph::T_Int>::type(0);
VisualTrack::ExposeUniform(s, *sampler.frame);
delete_requests[K_DR_Make_Subcomposition] = false;
}
if (delete_requests[K_DR_Selected_Layers]) {
if (std::ranges::find(s.selected, s.active) != s.selected.end()) // unset active if active is selected
s.active = -1;
u32 deleted = 0, before_active = 0; // stupid counting tricks
const u32 sz = s.layers.size();
for (u32 j = 0; j < sz; j++) { // im sure this isn't the best way to go about this
if (std::ranges::find(s.selected, j) != s.selected.end()) {
s.layers[j - deleted].track.Clear();
for (auto &u: s.layers[j - deleted].track.uniforms)
VisualTrack::HideUniform(s, u);
s.layers.erase(s.layers.begin() + j - deleted);
std::erase(s.disabled, j);
if (static_cast<i32>(j) < s.active)
before_active++;
deleted++;
}
else if (auto it = std::ranges::find(s.disabled, j); it != s.disabled.end())
*it -= deleted;
}
s.selected.clear();
if (s.active != -1)
s.active -= static_cast<i32>(before_active);
delete_requests[K_DR_Selected_Layers] = false;
}
if (delete_requests[K_DR_Selected_Nodes]) {
for (auto p: s.plugboard.selected_nodes) {
std::visit([&s](auto &&arg) {
for (u32 i = 0; i < arg->in.size(); i++)
Plugboard::Disconnect(arg, i);
for (auto &socket: arg->out)
for (Plugboard::ConnectInfo &connection: socket.outgoing)
Plugboard::Disconnect(connection.p, connection.index);
s.plugboard.nodes.Remove(arg);
}, p);
s.plugboard.RecollectChains();
}
s.plugboard.selected_nodes.clear();
delete_requests[K_DR_Selected_Nodes] = false;
}
if (delete_requests[K_DR_Selected_Keys]) {
for (auto& chain : s.plugboard.selected_nodes) {
if (auto *c = std::get_if<Plugboard::Chain*>(&chain)) {
auto& [segments, selected] = (*c)->extra.chain;
std::ranges::sort(selected, std::ranges::greater{});
for (u32 i : selected)
segments.erase(segments.begin() + i);
selected.clear();
}
}
delete_requests[K_DR_Selected_Keys] = false;
}
}
void Draw() {
ImGui_ImplSDL3_NewFrame();
ImGui_Implbgfx_NewFrame();
ImGui::NewFrame();
Graphics::DrawTextureImageAlpha(K::Graphics::K_VIEW_LOGO, logo, app_state.window_width - logo->w + 10, 0, .7f);
ImGui::DockSpaceOverViewport(0, ImGui::GetMainViewport(), ImGuiDockNodeFlags_PassthruCentralNode);
ImGui::ShowDemoWindow();
MainMenuBar();
if (draw_assets) Assets();
if (app_state.project.inspecting_composition != nullptr) {
auto &comp = *app_state.project.inspecting_composition;
if (playing) {
if (static_cast<f32>(app_state.current_time - last_frame_tick) >= 1000.0f / comp.fps) {
f32 delta = static_cast<f32>(app_state.current_time - last_frame_tick);
f32 frames_passed = delta / (1000.0f / comp.fps);
comp.current_frame = (comp.current_frame + static_cast<u64>(std::floor(frames_passed))) % (comp.frame_max + 1);
last_frame_tick = app_state.current_time - static_cast<u64>(std::fmod(delta, 1000.0f / comp.fps));
}
}
if (draw_viewport) Viewport(comp);
if (draw_layer) Layer(comp);
if (draw_comp) Composition(comp);
if (draw_interpolation) Interpolation(comp);
if (comp_save_called != nullptr && ready_frame <= app_state.bgfx_frame) {
stbi_write_png("frame.png", static_cast<i32>(comp_save_called->width),
static_cast<i32>(comp_save_called->height), 4, save_buffer,
static_cast<i32>(comp_save_called->width) * 4);
comp_save_called = nullptr;
}
ProcessDeleteRequests(comp);
}
ImGui::Render();
ImGui_Implbgfx_RenderDrawLists(ImGui::GetDrawData());
const bgfx::Stats *stat = bgfx::getStats();
const bgfx::Caps *caps = bgfx::getCaps();
bgfx::dbgTextClear();
bgfx::dbgTextPrintf(0, app_state.window_height/16 - 8, 0xf8,
" lachrymal.net :: %s Renderer :: %u/%u Views :: %u FBs :: %u Samplers :: Blit %s :: %s :: %u FPS",
caps->supported & BGFX_CAPS_RENDERER_MULTITHREADED ? "Multithreaded" : "Singlethreaded",
app_state.render_view, caps->limits.maxViews, caps->limits.maxFrameBuffers,
caps->limits.maxTextureSamplers, caps->supported & BGFX_CAPS_TEXTURE_BLIT ? "OK" : "NO",
SDL_GetCurrentVideoDriver(),
stat->cpuTimerFreq / stat->cpuTimeFrame);
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_MICRO_VERSION,
BGFX_API_VERSION, ImGui::GetVersion(), bgfx::getRendererName(bgfx::getRendererType()));
app_state.bgfx_frame = bgfx::frame();
}
void Init(SDL_Window *window) {
bgfx::setDebug(BGFX_DEBUG_TEXT);
ImGui::CreateContext();
ImPlot::CreateContext();
ImGuiIO& io = ImGui::GetIO();
io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
io.ConfigFlags |= ImGuiConfigFlags_DockingEnable;
ImVector<ImWchar> ranges;
ImFontGlyphRangesBuilder builder;
builder.AddChar(0x25C7);
builder.AddChar(0x25C8);
builder.AddRanges(io.Fonts->GetGlyphRangesDefault());
builder.AddRanges(io.Fonts->GetGlyphRangesJapanese());
builder.AddRanges(io.Fonts->GetGlyphRangesChineseFull());
builder.AddRanges(io.Fonts->GetGlyphRangesKorean());
builder.BuildRanges(&ranges);
ImFont* font = io.Fonts->AddFontFromFileTTF("SourceHanSans-Regular.ttc", std::floor(16 * app_state.dpi_scale), nullptr, ranges.Data);
font->Scale /= app_state.dpi_scale;
io.Fonts->Build();
ImGuiStyle& style = ImGui::GetStyle();
style.WindowBorderSize = 0.0f;
style.DockingSeparatorSize = 1.0f;
style.ScrollbarRounding = 0.0f;
style.TabRounding = 0.0f;
style.ScrollbarSize = 8.0f;
style.FramePadding.y = 2.0f;
ImGui_Implbgfx_Init(Graphics::K_VIEW_UI);
bgfx::setViewClear(Graphics::K_VIEW_UI, BGFX_CLEAR_COLOR);
SetLogoView();
switch (bgfx::getRendererType()) {
case bgfx::RendererType::Noop:
case bgfx::RendererType::Direct3D11:
case bgfx::RendererType::Direct3D12:
ImGui_ImplSDL3_InitForD3D(window);
break;
case bgfx::RendererType::Metal:
ImGui_ImplSDL3_InitForMetal(window);
break;
case bgfx::RendererType::OpenGL:
ImGui_ImplSDL3_InitForOpenGL(window, nullptr);
break;
case bgfx::RendererType::Vulkan:
ImGui_ImplSDL3_InitForVulkan(window);
break;
default:
Log(K_L_Error, "Unsupported Renderer");
}
bg.pg = Resource::Load<Resource::K_R_ShaderProgram>("Checkerboard")->pg;
bg.AddUniform("f_hw", ShaderGraph::Type::T_XYZ, K_U_Inactive);
logo = Resource::Load<Resource::K_R_Still>("Keishiki.png");
}
void Shutdown() {
bgfx::destroy(bg.uniforms[0].handle);
bg.uniforms.erase(bg.uniforms.begin());
for (auto& comp : app_state.project.compositions) {
for (auto& layer : comp.layers)
layer.track.Clear();
comp.Destroy();
}
ImGui_Implbgfx_Shutdown();
ImGui_ImplSDL3_Shutdown();
ImPlot::DestroyContext();
ImGui::DestroyContext();
}
}