2311 lines
122 KiB
C++
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 = ∁
|
|
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 == ∁
|
|
if (ImGui::Selectable(comp.name.c_str(), sel, ImGuiSelectableFlags_SpanAllColumns) && !sel) {
|
|
app_state.project.inspecting_composition = ∁
|
|
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();
|
|
}
|
|
}
|