Skip to content

Commit

Permalink
gui: dopesheet / keyframe management
Browse files Browse the repository at this point in the history
  • Loading branch information
hanatos committed Oct 6, 2023
1 parent 142cc89 commit b54146f
Show file tree
Hide file tree
Showing 6 changed files with 253 additions and 38 deletions.
23 changes: 22 additions & 1 deletion src/gui/api.h
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,10 @@ dt_gui_dr_prev()
if(vkdt.graph_dev.frame_cnt != 1)
{
vkdt.graph_dev.frame = vkdt.state.anim_frame = 0; // reset to beginning
vkdt.state.anim_no_keyframes = 0; // (re-)enable keyframes
vkdt.graph_dev.runflags = s_graph_run_all; // reprocess first frame
vkdt.state.anim_no_keyframes = 0; // (re-)enable keyframes
dt_graph_apply_keyframes(&vkdt.graph_dev); // rerun once
dt_gui_dr_anim_stop();
}
else
{ // backtrack to last image in lighttable collection
Expand Down Expand Up @@ -244,6 +246,25 @@ dt_gui_dr_leave_fullscreen_view()
dt_gui_dr_toggle_fullscreen_view();
}

static inline void
dt_gui_dr_show_dopesheet()
{
vkdt.wstate.dopesheet_view = 1.0;
}

static inline void
dt_gui_dr_hide_dopesheet()
{
vkdt.wstate.dopesheet_view = 0.0;
}

static inline void
dt_gui_dr_toggle_dopesheet()
{
if(vkdt.wstate.dopesheet_view > 0) vkdt.wstate.dopesheet_view = 0.0;
else vkdt.wstate.dopesheet_view = 1.0;
}

static inline void
dt_gui_lt_duplicate()
{
Expand Down
1 change: 1 addition & 0 deletions src/gui/flat.mk
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ GUI_H=gui/gui.h\
gui/api.h\
gui/api.hh\
gui/hotkey.hh\
gui/widget_dopesheet.hh\
gui/widget_draw.hh\
gui/widget_export.hh\
gui/widget_filebrowser.hh\
Expand Down
1 change: 1 addition & 0 deletions src/gui/gui.h
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ typedef struct dt_gui_wstate_t

int fullscreen_view; // darkroom mode without panels
int history_view; // darkroom mode with left panel shown (history view)
float dopesheet_view; // darkroom mode dopesheet, stores adaptive size (0 means collapsed)

int have_joystick; // found and enabled a joystick (disable via gui/disable_joystick in config)
int pentablet_enabled; // 1 if the stylus is in proxmity of the pen tablet
Expand Down
85 changes: 61 additions & 24 deletions src/gui/render_darkroom.cc
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ extern "C"
#include "gui/render_view.hh"
#include "gui/hotkey.hh"
#include "gui/api.hh"
#include "gui/widget_dopesheet.hh"
#include "gui/widget_draw.hh"
#include "gui/widget_thumbnail.hh"
#include "gui/widget_image.hh"
Expand All @@ -27,6 +28,7 @@ static ImHotKey::HotKey hk_darkroom[] = {
{"next image", "switch to next image in folder", {ImGuiKey_Space}},
{"prev image", "switch to previous image in folder", {ImGuiKey_Backspace}},
{"fullscreen", "show/hide side panels for fullscreen", {ImGuiKey_Tab}},
{"dopesheet", "show/hide keyframe overview", {ImGuiKey_LeftCtrl, ImGuiKey_D}},
{"rate 0", "assign zero stars", {ImGuiKey_0}},
{"rate 1", "assign one star", {ImGuiKey_1}},
{"rate 2", "assign two stars", {ImGuiKey_2}},
Expand Down Expand Up @@ -54,18 +56,19 @@ enum hotkey_names_t
s_hotkey_next_image = 8,
s_hotkey_prev_image = 9,
s_hotkey_fullscreen = 10,
s_hotkey_rate_0 = 11,
s_hotkey_rate_1 = 12,
s_hotkey_rate_2 = 13,
s_hotkey_rate_3 = 14,
s_hotkey_rate_4 = 15,
s_hotkey_rate_5 = 16,
s_hotkey_label_1 = 17,
s_hotkey_label_2 = 18,
s_hotkey_label_3 = 19,
s_hotkey_label_4 = 20,
s_hotkey_label_5 = 21,
s_hotkey_reload_shaders = 22,
s_hotkey_dopesheet = 11,
s_hotkey_rate_0 = 12,
s_hotkey_rate_1 = 13,
s_hotkey_rate_2 = 14,
s_hotkey_rate_3 = 15,
s_hotkey_rate_4 = 16,
s_hotkey_rate_5 = 17,
s_hotkey_label_1 = 18,
s_hotkey_label_2 = 19,
s_hotkey_label_3 = 20,
s_hotkey_label_4 = 21,
s_hotkey_label_5 = 22,
s_hotkey_reload_shaders = 23,
};

// used to communictate between the gui helper functions
Expand Down Expand Up @@ -155,7 +158,7 @@ void render_darkroom()
const float *axes = vkdt.wstate.have_joystick ? glfwGetJoystickAxes(GLFW_JOYSTICK_1, &axes_cnt) : 0;
{ // center image view
int win_x = vkdt.state.center_x, win_y = vkdt.state.center_y;
int win_w = vkdt.state.center_wd, win_h = vkdt.state.center_ht;
int win_w = vkdt.state.center_wd, win_h = vkdt.state.center_ht - vkdt.wstate.dopesheet_view;
{ // draw image properties
ImGui::SetNextWindowPos (ImGui::GetMainViewport()->Pos, ImGuiCond_Always);
ImGui::SetNextWindowSize(ImVec2(win_w+2*win_x, win_y), ImGuiCond_Always);
Expand All @@ -176,11 +179,10 @@ void render_darkroom()
}
ImGui::End();
}
int border = 0;
ImGui::SetNextWindowPos (ImVec2(
ImGui::GetMainViewport()->Pos.x + win_x - border,
ImGui::GetMainViewport()->Pos.y + win_y - border), ImGuiCond_Always);
ImGui::SetNextWindowSize(ImVec2(win_w+2*border, win_h+2*border), ImGuiCond_Always);
ImGui::GetMainViewport()->Pos.x + win_x,
ImGui::GetMainViewport()->Pos.y + win_y), ImGuiCond_Always);
ImGui::SetNextWindowSize(ImVec2(win_w, win_h), ImGuiCond_Always);
ImGui::SetNextWindowViewport(ImGui::GetMainViewport()->ID);
ImGui::PushStyleColor(ImGuiCol_WindowBg, gamma(ImVec4(0.5, 0.5, 0.5, 1.0)));
ImGui::Begin("darkroom center", 0, ImGuiWindowFlags_NoTitleBar |
Expand Down Expand Up @@ -271,6 +273,25 @@ void render_darkroom()
ImGui::PopStyleColor();
} // end center view

if(vkdt.wstate.dopesheet_view > 0.0f)
{ // draw dopesheet
int win_x = vkdt.state.center_x, win_y = vkdt.state.center_y + vkdt.state.center_ht - vkdt.wstate.dopesheet_view;
int win_w = vkdt.state.center_wd, win_h = vkdt.wstate.dopesheet_view;
ImGui::SetNextWindowPos (ImVec2(
ImGui::GetMainViewport()->Pos.x + win_x,
ImGui::GetMainViewport()->Pos.y + win_y), ImGuiCond_Always);
ImGui::SetNextWindowSize(ImVec2(win_w, win_h), ImGuiCond_Always);
ImGui::SetNextWindowViewport(ImGui::GetMainViewport()->ID);
ImGui::PushStyleColor(ImGuiCol_WindowBg, gamma(ImVec4(0.5, 0.5, 0.5, 1.0)));
ImGui::Begin("darkroom dopesheet", 0, ImGuiWindowFlags_NoTitleBar |
ImGuiWindowFlags_NoMove |
ImGuiWindowFlags_NoResize |
ImGuiWindowFlags_NoBackground);
dt_dopesheet();
ImGui::End();
ImGui::PopStyleColor();
}

if(!vkdt.wstate.fullscreen_view && vkdt.wstate.history_view)
{ // left panel
ImGui::SetNextWindowPos (ImVec2(
Expand Down Expand Up @@ -445,6 +466,8 @@ void render_darkroom()
if(ImGui::SliderInt("frame", &vkdt.state.anim_frame, 0, vkdt.state.anim_max_frame))
{
vkdt.graph_dev.frame = vkdt.state.anim_frame;
vkdt.state.anim_no_keyframes = 0; // (re-)enable keyframes
dt_graph_apply_keyframes(&vkdt.graph_dev); // rerun once
vkdt.graph_dev.runflags = s_graph_run_record_cmd_buf | s_graph_run_wait_done;
}
if(ImGui::IsItemHovered())
Expand Down Expand Up @@ -504,15 +527,26 @@ void render_darkroom()
vkdt.graph_dev.frame_rate = frame_rate; // conv to double
dt_graph_history_global(&vkdt.graph_dev);
}
if(ImGui::Button("force downloading all outputs", ImVec2(-1, 0)))
if(vkdt.graph_dev.frame_cnt != 1)
{
vkdt.graph_dev.runflags = s_graph_run_download_sink;
if(vkdt.wstate.dopesheet_view == 0.0f && ImGui::Button("show dopesheet", ImVec2(-1, 0)))
{
dt_gui_dr_show_dopesheet();
}
else if(vkdt.wstate.dopesheet_view > 0.0f && ImGui::Button("hide dopesheet", ImVec2(-1, 0)))
{
dt_gui_dr_hide_dopesheet();
}
if(ImGui::Button("force downloading all outputs", ImVec2(-1, 0)))
{
vkdt.graph_dev.runflags = s_graph_run_download_sink;
}
if(ImGui::IsItemHovered())
dt_gui_set_tooltip("this is useful if an animated graph has output modules\n"
"attached to it. for instance this allows you to trigger\n"
"writing of intermediate results of an optimisation from the gui.\n"
"only works when the animation is stopped.");
}
if(ImGui::IsItemHovered())
dt_gui_set_tooltip("this is useful if an animated graph has output modules\n"
"attached to it. for instance this allows you to trigger\n"
"writing of intermediate results of an optimisation from the gui.\n"
"only works when the animation is stopped.");
}

if(ImGui::CollapsingHeader("presets"))
Expand Down Expand Up @@ -561,6 +595,9 @@ void render_darkroom()
case s_hotkey_fullscreen:
dt_gui_dr_toggle_fullscreen_view();
break;
case s_hotkey_dopesheet:
dt_gui_dr_toggle_dopesheet();
break;
case s_hotkey_rate_0: dt_gui_rate_0(); break;
case s_hotkey_rate_1: dt_gui_rate_1(); break;
case s_hotkey_rate_2: dt_gui_rate_2(); break;
Expand Down
150 changes: 150 additions & 0 deletions src/gui/widget_dopesheet.hh
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
#pragma once
#include "imgui_internal.h"

namespace {

inline void
dt_draw_quad(float u, float v, float size, uint32_t col)
{
float c[] = {
0, 1, 1, 0,
0, -1, -1, 0};
float x[20];
for(int i=0;i<4;i++)
{
x[2*i+0] = u + size * c[2*i+0];
x[2*i+1] = v + size * c[2*i+1];
}
ImGui::GetWindowDrawList()->AddConvexPolyFilled((ImVec2 *)x, 4, col);
ImGui::GetWindowDrawList()->AddPolyline( (ImVec2 *)x, 4, 0xff000000, true, .1*size);
}

inline int screen_to_frame(float sx, dt_graph_t *g, ImRect bb)
{
return CLAMP((int)((sx - bb.Min[0])/(bb.Max[0]-bb.Min[0]) * g->frame_cnt + 0.5), 0, g->frame_cnt-1);
}

inline float frame_to_screen(int f, dt_graph_t *g, ImRect bb)
{
return CLAMP(bb.Min[0] + (bb.Max[0]-bb.Min[0]) * f / (float)g->frame_cnt, bb.Min[0], bb.Max[0]);
}

inline float
dt_draw_param_line(
dt_module_t *mod,
int p)
{
ImGuiWindow* window = ImGui::GetCurrentWindow();
if (window->SkipItems) return 0.0f;

ImGui::PushID(p + 200*(mod-mod->graph->module));
const ImGuiID id = window->GetID("#image");
ImGui::PopID();

int win_w = vkdt.state.center_wd;
int ht = vkdt.wstate.fontsize; // line height = font_size + 2*FramePadding.y ?
const ImVec2 cp = ImVec2((int)window->DC.CursorPos[0], (int)window->DC.CursorPos[1]);
const ImRect bb(ImVec2(cp[0]+(int)(0.12*win_w), cp[1]), ImVec2(cp[0] + win_w, cp[1] + ht));

static int drag_k = -1, drag_mod = -1;

int have_keys = 0;
for(int k=0;k<mod->keyframe_cnt;k++)
{
if(mod->keyframe[k].param == mod->so->param[p]->name)
{
if(!have_keys)
{
ImGui::ItemSize(bb);
if (!ImGui::ItemAdd(bb, id)) return 0.0f;
if(ImGui::IsItemClicked(0))
{ // left click: move animation time
// TODO: api functions! create/delete keyframe, set animation time
vkdt.graph_dev.frame = vkdt.state.anim_frame = screen_to_frame(ImGui::GetMousePos().x, mod->graph, bb);
vkdt.state.anim_no_keyframes = 0; // (re-)enable keyframes
dt_graph_apply_keyframes(&vkdt.graph_dev); // rerun once
vkdt.graph_dev.runflags = s_graph_run_record_cmd_buf | s_graph_run_wait_done;
}
ImGui::SameLine();
char text[60];
window->DrawList->AddRectFilled(bb.Min, bb.Max, ImGui::GetColorU32(ImGuiCol_Button));
int len = snprintf(text, sizeof(text), "%" PRItkn " %" PRItkn " %" PRItkn, dt_token_str(mod->name), dt_token_str(mod->inst), dt_token_str(mod->so->param[p]->name));
window->DrawList->AddText(cp, ImGui::GetColorU32(ImGuiCol_Text), text, text+len);
have_keys = 1;
}
float x = frame_to_screen(mod->keyframe[k].frame, mod->graph, bb);
float y = (bb.Min[1]+bb.Max[1])/2.0;
dt_draw_quad(x, y, ht/2.0, ImGui::GetColorU32(ImGuiCol_PlotHistogram));
const ImRect bbk(ImVec2(x-ht/2.0, y-ht/2.0), ImVec2(x+ht/2.0, y+ht/2.0));
ImGui::ItemSize(bbk);
ImGui::PushID(k + 30*p + 1000*(mod-mod->graph->module));
const ImGuiID idk = window->GetID("#image");
ImGui::PopID();
if (!ImGui::ItemAdd(bbk, idk)) continue;
if(ImGui::IsItemHovered())
{ // TODO: make this at least respect length (mod->so->param[p]->{type, cnt}
dt_gui_set_tooltip("%" PRItkn " %f\nright click to delete\nleft click and drag to move",
dt_token_str(mod->so->param[p]->name), *((float*)mod->keyframe[k].data));
}
if(ImGui::IsItemClicked(0))
{ // set state: dragging keyframe k
drag_k = k;
drag_mod = mod-mod->graph->module;
}
if(ImGui::IsKeyReleased(ImGuiKey_MouseLeft) && drag_k == k && drag_mod == mod-mod->graph->module)
{ // drag finished
mod->keyframe[k].frame = screen_to_frame(ImGui::GetMousePos().x, mod->graph, bb);
drag_k = drag_mod = -1;
}
if(ImGui::IsItemClicked(1))
{ // right click: delete this keyframe by copying the last one over it and decreasing keyframe_cnt
mod->keyframe[k--] = mod->keyframe[--mod->keyframe_cnt];
}
ImGui::SameLine();
}
}
if(have_keys)
{
const float x[4] = {
frame_to_screen(mod->graph->frame, mod->graph, bb), bb.Min[1],
frame_to_screen(mod->graph->frame, mod->graph, bb), bb.Max[1] };
ImGui::GetWindowDrawList()->AddPolyline((ImVec2 *)x, 2, 0xff000000, false, .1*ht);
ImGui::NewLine();
return ht;
}
return 0.0f;
}

inline float
dt_dopesheet_module(dt_graph_t *g, uint32_t modid)
{ // draw all parameters of a module
dt_module_t *mod = g->module + modid;
if(mod->keyframe_cnt == 0) return 0.0f; // no keyframes to draw
float size = 0.0f;
for(int p=0;p<mod->so->num_params;p++)
size += dt_draw_param_line(mod, p);
return size;
}

} // end anonymous namespace

inline void
dt_dopesheet()
{ // draw all modules connected on the graph in same order as right panel in darkroom mode
dt_graph_t *graph = &vkdt.graph_dev;
dt_module_t *const arr = graph->module;
const int arr_cnt = graph->num_modules;
uint32_t modid[100], cnt = 0;
#define TRAVERSE_POST \
assert(cnt < sizeof(modid)/sizeof(modid[0]));\
modid[cnt++] = curr;
#include "pipe/graph-traverse.inc"
float size = 0.0;
for(int m=cnt-1;m>=0;m--)
size += dt_dopesheet_module(graph, modid[m]);
// this is unfortunately not reliable since we skip drawing and everything
// in case a line cannot be seen on screen.
// vkdt.wstate.dopesheet_view = MIN(0.5*vkdt.state.center_ht, size);
(void)size;
vkdt.wstate.dopesheet_view = 0.2*vkdt.state.center_ht; // fixed size
}
Loading

0 comments on commit b54146f

Please sign in to comment.