prosperon/source/engine/thirdparty/imgui/imgui_neo_sequencer.cpp

1377 lines
46 KiB
C++

//
// Created by Matty on 2022-01-28.
//
#define IMGUI_DEFINE_MATH_OPERATORS
#include "imgui_neo_sequencer.h"
#include "imgui_internal.h"
#include "imgui_neo_internal.h"
#include <unordered_map>
namespace ImGui
{
// Internal state, used for deletion of old keyframes.
struct ImGuiNeoTimelineKeyframes
{
ImGuiID TimelineID;
ImVector<int32_t> KeyframesToDelete;
};
// Internal struct holding how many times was keyframe on certain frame rendered, used as offset for duplicates
struct ImGuiNeoKeyframeDuplicate
{
int32_t Frame;
uint32_t Count;
};
enum class SelectionState
{
Idle, // Doing nothing related
Selecting, // Selecting selection
Dragging // Dragging selection
};
struct ImGuiNeoSequencerInternalData
{
ImVec2 TopLeftCursor = {0, 0}; // Cursor on top of whole widget
ImVec2 TopBarStartCursor = {0, 0}; // Cursor on top, below Zoom slider
ImVec2 StartValuesCursor = {0, 0}; // Cursor on top of values
ImVec2 ValuesCursor = {0, 0}; // Current cursor position, used for values drawing
ImVec2 Size = {0, 0}; // Size of whole sequencer
ImVec2 TopBarSize = {0, 0}; // Size of top bar without Zoom
FrameIndexType StartFrame = 0;
FrameIndexType EndFrame = 0;
FrameIndexType OffsetFrame = 0; // Offset from start
float ValuesWidth = 32.0f; // Width of biggest label in timeline, used for offset of timeline
float FilledHeight = 0.0f; // Height of whole sequencer
float Zoom = 1.0f;
ImGuiID Id;
ImGuiID LastSelectedTimeline = 0;
ImGuiID SelectedTimeline = 0;
bool LastTimelineOpenned = false;
ImVector<ImGuiID> TimelineStack;
ImVector<ImGuiID> GroupStack;
FrameIndexType CurrentFrame = 0;
bool HoldingCurrentFrame = false; // Are we draging current frame?
ImVec4 CurrentFrameColor; // Color of current frame, we have to save it because we render on EndNeoSequencer, but process at BeginneoSequencer
bool HoldingZoomSlider = false;
//Selection
ImVector<ImGuiID> Selection; // Contains ids of keyframes
ImVec2 SelectionMouseStart = {0, 0};
SelectionState StateOfSelection = SelectionState::Idle;
ImVec2 DraggingMouseStart = {0, 0};
bool StartDragging = true;
ImVector<int32_t> DraggingSelectionStart; // Contains start values of all selection elements
bool DraggingEnabled = true;
bool SelectionEnabled = true;
bool IsSelectionRightClicked = false;
//Last keyframe data
bool IsLastKeyframeHovered = false;
bool IsLastKeyframeSelected = false;
bool IsLastKeyframeRightClicked = false;
//Deletion
bool DeleteDataDirty = false;
bool DeleteEnabled = true;
ImVector<ImGuiNeoTimelineKeyframes> SelectionData;
};
static ImGuiNeoSequencerStyle style; // NOLINT(cert-err58-cpp)
//Global context stuff
static bool inSequencer = false;
// Height of timeline right now
static float currentTimelineHeight = 0.0f;
// Current active sequencer
static ImGuiID currentSequencer;
// Current timeline depth, used for offset of label
static uint32_t currentTimelineDepth = 0;
static ImVector<ImGuiColorMod> sequencerColorStack;
// Data of all sequencers, this is main c++ part and I should create C alternative or use imgui ImVector or something
static std::unordered_map<ImGuiID, ImGuiNeoSequencerInternalData> sequencerData;
static ImVector<ImGuiNeoKeyframeDuplicate> keyframeDuplicates;
///////////// STATIC HELPERS ///////////////////////
static float getPerFrameWidth(ImGuiNeoSequencerInternalData& context)
{
return GetPerFrameWidth(context.Size.x, context.ValuesWidth, context.EndFrame, context.StartFrame,
context.Zoom);
}
static float getKeyframePositionX(FrameIndexType frame, ImGuiNeoSequencerInternalData& context)
{
const auto perFrameWidth = getPerFrameWidth(context);
return (float) (frame - context.OffsetFrame - context.StartFrame) * perFrameWidth;
}
static float getWorkTimelineWidth(ImGuiNeoSequencerInternalData& context)
{
const auto perFrameWidth = getPerFrameWidth(context);
return context.Size.x - context.ValuesWidth - perFrameWidth;
}
// Dont pull frame from context, its used for dragging
static ImRect getCurrentFrameBB(FrameIndexType frame, ImGuiNeoSequencerInternalData& context)
{
const auto& imStyle = GetStyle();
const auto width = style.CurrentFramePointerSize * GetIO().FontGlobalScale;
const auto cursor =
context.TopBarStartCursor + ImVec2{context.ValuesWidth + imStyle.FramePadding.x - width / 2.0f, 0};
const auto currentFrameCursor = cursor + ImVec2{getKeyframePositionX(frame, context), 0};
float pointerHeight = style.CurrentFramePointerSize * 2.5f;
ImRect rect{currentFrameCursor, currentFrameCursor + ImVec2{width, pointerHeight * GetIO().FontGlobalScale}};
return rect;
}
static void processCurrentFrame(FrameIndexType* frame, ImGuiNeoSequencerInternalData& context)
{
auto pointerRect = getCurrentFrameBB(*frame, context);
pointerRect.Min -= ImVec2{2.0f, 2.0f};
pointerRect.Max += ImVec2{2.0f, 2.0f};
const auto& imStyle = GetStyle();
const auto timelineXmin = context.TopBarStartCursor.x + context.ValuesWidth + imStyle.FramePadding.x;
const ImVec2 timelineXRange = {
timelineXmin, //min
timelineXmin + context.Size.x - context.ValuesWidth
};
const auto hovered = ItemHoverable(pointerRect, GetCurrentWindow()->GetID("##_top_selector_neo"), 0);
context.CurrentFrameColor = GetStyleNeoSequencerColorVec4(ImGuiNeoSequencerCol_FramePointer);
if (hovered)
{
context.CurrentFrameColor = GetStyleNeoSequencerColorVec4(ImGuiNeoSequencerCol_FramePointerHovered);
}
if (context.HoldingCurrentFrame)
{
if (IsMouseDragging(ImGuiMouseButton_Left, 0.0f))
{
const auto mousePosX = GetMousePos().x;
const auto v = mousePosX - timelineXRange.x;// Subtract min
const auto normalized = v / getWorkTimelineWidth(context); //Divide by width to remap to 0 - 1 range
const auto clamped = ImClamp(normalized, 0.0f, 1.0f);
const auto viewSize = (float) (context.EndFrame - context.StartFrame) / context.Zoom;
const auto frameViewVal = (float) context.StartFrame + (clamped * (float) viewSize);
const auto finalFrame = (FrameIndexType) round(frameViewVal) + context.OffsetFrame;
context.CurrentFrameColor = GetStyleNeoSequencerColorVec4(ImGuiNeoSequencerCol_FramePointerPressed);
*frame = finalFrame;
}
if (!IsMouseDown(ImGuiMouseButton_Left))
{
context.HoldingCurrentFrame = false;
context.CurrentFrameColor = GetStyleNeoSequencerColorVec4(ImGuiNeoSequencerCol_FramePointer);
}
}
if (hovered && IsMouseDown(ImGuiMouseButton_Left) && !context.HoldingCurrentFrame)
{
context.HoldingCurrentFrame = true;
context.CurrentFrameColor = GetStyleNeoSequencerColorVec4(ImGuiNeoSequencerCol_FramePointerPressed);
}
context.CurrentFrame = *frame;
}
static void finishPreviousTimeline(ImGuiNeoSequencerInternalData& context)
{
context.ValuesCursor = {context.TopBarStartCursor.x, context.ValuesCursor.y};
currentTimelineHeight = 0.0f;
}
static ImColor getKeyframeColor(ImGuiNeoSequencerInternalData& context, bool hovered, bool inSelection)
{
if (inSelection)
{
return ColorConvertFloat4ToU32(GetStyleNeoSequencerColorVec4(ImGuiNeoSequencerCol_KeyframeSelected));
}
return hovered ?
ColorConvertFloat4ToU32(
GetStyleNeoSequencerColorVec4(ImGuiNeoSequencerCol_KeyframeHovered)) :
ColorConvertFloat4ToU32(GetStyleNeoSequencerColorVec4(ImGuiNeoSequencerCol_Keyframe));
}
static void addKeyframeToDeleteData(int32_t value, ImGuiNeoSequencerInternalData& context, const ImGuiID timelineId)
{
bool foundTimeline = false;
for (auto&& val: context.SelectionData)
{
if (val.TimelineID == timelineId)
{
foundTimeline = true;
if (!val.KeyframesToDelete.contains(value))
val.KeyframesToDelete.push_back(value);
break;
}
}
if (!foundTimeline)
{
context.SelectionData.push_back({});
auto& data = context.SelectionData.back();
data.TimelineID = timelineId;
data.KeyframesToDelete.push_back(value);
}
}
static bool
getKeyframeInSelection(int32_t value, ImGuiID id, ImGuiNeoSequencerInternalData& context, const ImRect bb)
{
//TODO(matej.vrba): This is kinda slow, it works for smaller data sample, but for bigger sample it should be changed to hashset
const ImGuiID timelineId = context.TimelineStack.back();
if (context.DeleteDataDirty && context.Selection.contains(id))
{
addKeyframeToDeleteData(value, context, timelineId);
}
if (context.StateOfSelection != SelectionState::Selecting)
{
return context.Selection.contains(id);
}
ImRect sel = {context.SelectionMouseStart, GetMousePos()};
if (sel.Min.y > sel.Max.y)
{
ImVec2 tmp = sel.Min;
sel.Min = sel.Max;
sel.Max = tmp;
}
if (sel.Min.x > sel.Max.x)
{
float tmp = sel.Min.x;
sel.Min.x = sel.Max.x;
sel.Max.x = tmp;
}
const bool overlaps = bb.Overlaps(sel);
const bool forceRemove = IsKeyDown(style.ModRemoveKey);
const bool forceAdd = IsKeyDown(style.ModAddKey);
auto removeKeyframe = [&]()
{
for (auto&& val: context.SelectionData)
{
if (val.TimelineID == timelineId)
{
val.KeyframesToDelete.find_erase(value);
break;
}
}
context.Selection.find_erase(id);
};
if (overlaps)
{
if (forceRemove)
{
removeKeyframe();
return context.Selection.contains(id);
} else
{
if (!context.Selection.contains(id))
{
addKeyframeToDeleteData(value, context, timelineId);
context.Selection.push_back(id);
}
}
} else
{
if (!forceRemove && !forceAdd)
{
removeKeyframe();
} else
{
return context.Selection.contains(id);
}
}
return overlaps;
}
static ImGuiID getKeyframeID(int32_t* frame)
{
return GetCurrentWindow()->GetID(frame);
}
static bool createKeyframe(int32_t* frame)
{
const auto& imStyle = GetStyle();
auto& context = sequencerData[currentSequencer];
const auto timelineOffset = getKeyframePositionX(*frame, context);
float offset = 0.0f;
for (auto&& duplicateData: keyframeDuplicates)
{
if (duplicateData.Frame == *frame)
{
offset = (float) duplicateData.Count * style.CollidedKeyframeOffset;
duplicateData.Count++;
}
}
if (offset < style.CollidedKeyframeOffset)
{
keyframeDuplicates.push_back({});
keyframeDuplicates.back().Frame = *frame;
keyframeDuplicates.back().Count = 1;
}
const auto pos = ImVec2{context.StartValuesCursor.x + imStyle.FramePadding.x, context.ValuesCursor.y} +
ImVec2{timelineOffset + context.ValuesWidth + offset, 0};
const auto bbPos = pos - ImVec2{currentTimelineHeight / 2, 0};
const ImRect bb = {bbPos, bbPos + ImVec2{currentTimelineHeight, currentTimelineHeight}};
const auto drawList = ImGui::GetWindowDrawList();
const ImGuiID id = getKeyframeID(frame);
bool hovered = ItemHoverable(bb, id, 0);
if (context.SelectionEnabled && context.Selection.contains(id) &&
(context.StateOfSelection != SelectionState::Selecting))
{
// process dragging
if (bb.Contains(GetMousePos()) && IsMouseClicked(ImGuiMouseButton_Left) &&
context.StateOfSelection != SelectionState::Dragging &&
context.DraggingEnabled)
{
//Start dragging
context.StartDragging = true;
}
if (context.StateOfSelection == SelectionState::Dragging)
{
ImGuiID* it = context.Selection.find(id);
int32_t index = context.Selection.index_from_ptr(it);
if (context.DraggingSelectionStart.size() < index + 1 || context.DraggingSelectionStart[index] == -1)
{
if (context.DraggingSelectionStart.size() < index + 1)
{
context.DraggingSelectionStart.resize(index + 1, -1);
}
context.DraggingSelectionStart[index] = *frame;
}
float mouseDelta = GetMousePos().x - context.DraggingMouseStart.x;
auto offsetA = int32_t(
mouseDelta / (context.Size.x / (float) context.EndFrame - (float) context.StartFrame));
*frame = context.DraggingSelectionStart[index] + offsetA;
}
}
const bool inSelection = getKeyframeInSelection(*frame, id, context, bb);
context.IsLastKeyframeSelected = inSelection;
if (timelineOffset >= 0.0f)
{
ImColor color = getKeyframeColor(context, hovered, inSelection);
drawList->AddCircleFilled(pos + ImVec2{0, currentTimelineHeight / 2.f}, currentTimelineHeight / 3.0f,
color, 4);
}
context.IsLastKeyframeHovered = hovered;
context.IsLastKeyframeRightClicked = hovered && IsMouseClicked(ImGuiMouseButton_Right);
if (context.Selection.contains(id) && context.IsLastKeyframeRightClicked)
{
context.IsSelectionRightClicked = true;
}
return true;
}
static uint32_t idCounter = 0;
static char idBuffer[16];
const char* generateID()
{
idBuffer[0] = '#';
idBuffer[1] = '#';
memset(idBuffer + 2, 0, 14);
snprintf(idBuffer + 2, 14, "%o", idCounter++);
return &idBuffer[0];
}
void resetID()
{
idCounter = 0;
}
static void renderCurrentFrame(ImGuiNeoSequencerInternalData& context)
{
const auto bb = getCurrentFrameBB(context.CurrentFrame, context);
const auto drawList = ImGui::GetWindowDrawList();
RenderNeoSequencerCurrentFrame(
GetStyleNeoSequencerColorVec4(ImGuiNeoSequencerCol_FramePointerLine),
context.CurrentFrameColor,
bb,
context.Size.y - context.TopBarSize.y,
style.CurrentFrameLineWidth,
drawList
);
}
static float calculateZoomBarHeight()
{
const auto& imStyle = GetStyle();
return GetFontSize() * style.ZoomHeightScale + imStyle.FramePadding.y * 2.0f;
}
static void
processAndRenderZoom(ImGuiNeoSequencerInternalData& context, const ImVec2& cursor, bool allowEditingLength,
FrameIndexType* start,
FrameIndexType* end)
{
const auto& imStyle = GetStyle();
ImGuiWindow* window = GetCurrentWindow();
const auto zoomHeight = calculateZoomBarHeight();
auto* drawList = GetWindowDrawList();
//Input width
const auto inputWidth = CalcTextSize("123456").x;
const auto inputWidthWithPadding = inputWidth + imStyle.ItemSpacing.x;
const auto cursorV = allowEditingLength ? cursor + ImVec2{inputWidthWithPadding, 0}
: cursor;
const auto size = allowEditingLength ?
context.Size.x - 2 * inputWidthWithPadding :
context.Size.x;
const ImRect bb{cursorV, cursorV + ImVec2{size, zoomHeight}};
const auto zoomBarEndWithSpacing = ImVec2{bb.Max.x + imStyle.ItemSpacing.x, bb.Min.y};
FrameIndexType startFrameVal = *start;
FrameIndexType endFrameVal = *end;
if (allowEditingLength)
{
const float sideOffset = imStyle.ItemSpacing.x / 2.0f;
auto prevWindowCursor = window->DC.CursorPos;
window->DC.CursorPos = cursor;
window->DC.CursorPos.x += sideOffset;
PushItemWidth(inputWidth);
InputScalar("##input_start_frame", ImGuiDataType_U32, &startFrameVal, NULL, NULL, "%i",
allowEditingLength ? 0 : ImGuiInputTextFlags_ReadOnly);
window->DC.CursorPos = ImVec2{zoomBarEndWithSpacing.x, cursor.y};
window->DC.CursorPos.x -= sideOffset;
PushItemWidth(inputWidth);
InputScalar("##input_end_frame", ImGuiDataType_U32, &endFrameVal, NULL, NULL, "%i",
allowEditingLength ? 0 : ImGuiInputTextFlags_ReadOnly);
window->DC.CursorPos = prevWindowCursor;
}
//if (startFrameVal < 0)
// startFrameVal = (int32_t) *start;
//
//if (endFrameVal < 0)
// endFrameVal = (int32_t) *end;
if (endFrameVal <= startFrameVal)
endFrameVal = (int32_t) *end;
*start = startFrameVal;
*end = endFrameVal;
//drawList->AddText(startFrameTextCursor + ImVec2{frameNumberBorderSize.x, 0} - ImVec2{numberTextWidth,0},IM_COL32_WHITE,numberText);
//Background
drawList->AddRectFilled(bb.Min, bb.Max,
ColorConvertFloat4ToU32(GetStyleNeoSequencerColorVec4(ImGuiNeoSequencerCol_ZoomBarBg)),
10.0f);
const auto baseWidth = bb.GetSize().x -
imStyle.ItemInnerSpacing.x; //There is just half spacing applied, doing it normally makes big gap on sides
const auto sliderHeight = bb.GetSize().y - imStyle.ItemInnerSpacing.y;
const auto sliderWidth = baseWidth / context.Zoom;
const auto sliderMin = bb.Min + imStyle.ItemInnerSpacing / 2.0f;
//const auto sliderMax = bb.Max - imStyle.ItemInnerSpacing / 2.0f;
const auto sliderMaxWidth = baseWidth;
const auto totalFrames = (*end - *start);
const auto singleFrameWidthOffset = sliderMaxWidth / (float) totalFrames;
const auto zoomSliderOffset = singleFrameWidthOffset * (float) context.OffsetFrame;
const auto sliderStart = sliderMin + ImVec2{zoomSliderOffset, 0};
const float sideSize = sliderHeight;
const ImRect finalSliderBB{sliderStart, sliderStart + ImVec2{sliderWidth, sliderHeight}};
const ImRect finalSliderInteractBB = {finalSliderBB.Min + ImVec2{sideSize, 0},
finalSliderBB.Max - ImVec2{sideSize, 0}};
const auto viewWidth = (uint32_t) ((float) totalFrames / context.Zoom);
const bool hovered = ItemHoverable(bb, GetCurrentWindow()->GetID("##zoom_slider"), 0);
if (hovered)
{
SetKeyOwner(ImGuiKey_MouseWheelY, GetItemID());
const float currentScroll = GetIO().MouseWheel;
context.Zoom = ImClamp(context.Zoom + float(currentScroll) * 0.3f, 1.0f, (float) viewWidth);
const auto newZoomWidth = (FrameIndexType) ceil((float) totalFrames / (context.Zoom));
if (*start + context.OffsetFrame + newZoomWidth > *end)
context.OffsetFrame = ImMax(0U, totalFrames - viewWidth);
}
if (context.HoldingZoomSlider)
{
if (IsMouseDragging(ImGuiMouseButton_Left, 0.01f))
{
const auto currentX = GetMousePos().x;
const auto v = currentX - bb.Min.x;// Subtract min
const auto normalized = v / bb.GetWidth(); //Divide by width to remap to 0 - 1 range
const auto sliderWidthNormalized = 1.0f / context.Zoom;
const auto singleFrameWidthOffsetNormalized = singleFrameWidthOffset / bb.GetWidth();
FrameIndexType finalFrame = (FrameIndexType) ((float) (normalized - sliderWidthNormalized / 2.0f) /
singleFrameWidthOffsetNormalized);
if (normalized - sliderWidthNormalized / 2.0f < 0.0f)
{
finalFrame = 0;
}
if (normalized + sliderWidthNormalized / 2.0f > 1.0f)
{
finalFrame = totalFrames - viewWidth;
}
context.OffsetFrame = finalFrame;
}
if (!IsMouseDown(ImGuiMouseButton_Left))
{
context.HoldingZoomSlider = false;
}
}
if (hovered && IsMouseDown(ImGuiMouseButton_Left))
{
context.HoldingZoomSlider = true;
}
const auto res = ItemAdd(finalSliderInteractBB, 0);
const auto viewStart = *start + context.OffsetFrame;
const auto viewEnd = viewStart + viewWidth;
if (res)
{
auto sliderColor = GetStyleNeoSequencerColorVec4(ImGuiNeoSequencerCol_ZoomBarSlider);
if (IsItemHovered())
{
sliderColor = GetStyleNeoSequencerColorVec4(ImGuiNeoSequencerCol_ZoomBarSliderHovered);
}
//Render bar
drawList->AddRectFilled(finalSliderBB.Min, finalSliderBB.Max, ColorConvertFloat4ToU32(sliderColor), 10.0f);
const auto sliderCenter = finalSliderBB.GetCenter();
char overlayTextBuffer[128];
snprintf(overlayTextBuffer, sizeof(overlayTextBuffer), "%i - %i", viewStart, viewEnd);
const auto overlaySize = CalcTextSize(overlayTextBuffer);
drawList->AddText(sliderCenter - overlaySize / 2.0f, IM_COL32_WHITE, overlayTextBuffer);
}
}
static void processSelection(ImGuiNeoSequencerInternalData& context)
{
context.DeleteDataDirty = false;
if (context.StartDragging)
{
context.StateOfSelection = SelectionState::Dragging;
context.DraggingMouseStart = GetMousePos();
context.StartDragging = false;
return;
}
const auto windowWorkRect = GetCurrentWindow()->ClipRect;
const auto sequencerWorkRect = ImRect{
context.TopBarStartCursor + ImVec2{context.ValuesWidth, context.TopBarSize.y},
context.TopBarStartCursor + context.Size - ImVec2{0, context.TopBarSize.y}};
if (IsMouseDown(ImGuiMouseButton_Left) && windowWorkRect.Contains(GetMousePos()) &&
sequencerWorkRect.Contains(GetMousePos()))
{
// Not dragging yet
switch (context.StateOfSelection)
{
case SelectionState::Idle:
{
if (!IsMouseClicked(ImGuiMouseButton_Left)) return;
context.SelectionMouseStart = GetMousePos();
context.StateOfSelection = SelectionState::Selecting;
break;
}
case SelectionState::Selecting:
{
break;
}
case SelectionState::Dragging:
{
break;
}
}
} else
{
switch (context.StateOfSelection)
{
case SelectionState::Idle:
{
break;
}
case SelectionState::Selecting:
{
context.SelectionMouseStart = {0, 0};
context.StateOfSelection = SelectionState::Idle;
break;
}
case SelectionState::Dragging:
{
context.DraggingSelectionStart.resize(0);
context.StateOfSelection = SelectionState::Idle;
context.DraggingMouseStart = {0, 0};
context.DeleteDataDirty = true;
for (auto&& t: context.SelectionData)
t.KeyframesToDelete.resize(0);
break;
}
}
}
}
static void renderSelection(ImGuiNeoSequencerInternalData& context)
{
if (context.StateOfSelection != SelectionState::Selecting)
{
return;
}
const ImVec2 currentMousePosition = GetMousePos();
auto* drawList = GetWindowDrawList();
ImRect sel{context.SelectionMouseStart,
currentMousePosition};
if (sel.Min.y > sel.Max.y)
{
ImVec2 tmp = sel.Min;
sel.Min = sel.Max;
sel.Max = tmp;
}
if (sel.Min.x > sel.Max.x)
{
float tmp = sel.Min.x;
sel.Min.x = sel.Max.x;
sel.Max.x = tmp;
}
if (sel.GetArea() < 32.0f)
return;
// Inner
drawList->AddRectFilled(
context.SelectionMouseStart,
currentMousePosition,
ColorConvertFloat4ToU32(style.Colors[ImGuiNeoSequencerCol_Selection])
);
// border
drawList->AddRect(
context.SelectionMouseStart,
currentMousePosition,
ColorConvertFloat4ToU32(style.Colors[ImGuiNeoSequencerCol_SelectionBorder]),
0.0f,
0,
0.5f
);
}
static bool groupBehaviour(const ImGuiID id, bool* open, const ImVec2 labelSize)
{
auto& context = sequencerData[currentSequencer];
ImGuiWindow* window = GetCurrentWindow();
const bool closable = open != nullptr;
auto drawList = ImGui::GetWindowDrawList();
const float arrowWidth = drawList->_Data->FontSize;
const ImVec2 arrowSize = {arrowWidth, arrowWidth};
const ImRect arrowBB = {
context.ValuesCursor,
context.ValuesCursor + arrowSize
};
const ImVec2 groupBBMin = {context.ValuesCursor + ImVec2{arrowSize.x, 0.0f}};
const ImRect groupBB = {
groupBBMin,
groupBBMin + labelSize
};
const ImGuiID arrowID = window->GetID(generateID());
const auto addArrowRes = ItemAdd(arrowBB, arrowID);
if (addArrowRes)
{
if (IsItemClicked() && closable)
(*open) = !(*open);
}
const auto addGroupRes = ItemAdd(groupBB, id);
if (addGroupRes)
{
if (IsItemClicked())
{
context.LastSelectedTimeline = context.SelectedTimeline;
context.SelectedTimeline = context.SelectedTimeline == id ? 0 : id;
}
}
const float width = groupBB.Max.x - arrowBB.Min.x;
context.ValuesWidth = std::max(context.ValuesWidth, width); // Make left panel wide enough
return addGroupRes && addArrowRes;
}
static bool timelineBehaviour(const ImGuiID id, const ImVec2 labelSize)
{
auto& context = sequencerData[currentSequencer];
//ImGuiWindow *window = GetCurrentWindow();
const ImRect groupBB = {
context.ValuesCursor,
context.ValuesCursor + labelSize
};
const auto addGroupRes = ItemAdd(groupBB, id);
if (addGroupRes)
{
if (IsItemClicked())
{
context.LastSelectedTimeline = context.SelectedTimeline;
context.SelectedTimeline = context.SelectedTimeline == id ? 0 : id;
}
}
const float width = groupBB.Max.x - groupBB.Min.x;
context.ValuesWidth = std::max(context.ValuesWidth, width); // Make left panel wide enough
return addGroupRes;
}
////////////////////////////////////
const ImVec4& GetStyleNeoSequencerColorVec4(ImGuiNeoSequencerCol idx)
{
return GetNeoSequencerStyle().Colors[idx];
}
ImGuiNeoSequencerStyle& GetNeoSequencerStyle()
{
return style;
}
bool
BeginNeoSequencer(const char* idin, FrameIndexType* frame, FrameIndexType* startFrame, FrameIndexType* endFrame,
const ImVec2& size,
ImGuiNeoSequencerFlags flags)
{
IM_ASSERT(!inSequencer && "Called when while in other NeoSequencer, that won't work, call End!");
IM_ASSERT(*startFrame < *endFrame && "Start frame must be smaller than end frame");
static char childNameStorage[64];
snprintf(childNameStorage, sizeof(childNameStorage), "##%s_child_wrapper", idin);
const bool openChild = BeginChild(childNameStorage);
if (!openChild)
{
EndChild();
return openChild;
}
//ImGuiContext &g = *GImGui;
ImGuiWindow* window = GetCurrentWindow();
const auto& imStyle = GetStyle();
//auto &neoStyle = GetNeoSequencerStyle();
if (inSequencer)
return false;
if (window->SkipItems)
return false;
const auto drawList = GetWindowDrawList();
const auto cursor = GetCursorScreenPos();
const auto area = ImGui::GetContentRegionAvail();
const auto cursorBasePos = GetCursorScreenPos() + window->Scroll;
PushID(idin);
const auto id = window->IDStack[window->IDStack.size() - 1];
inSequencer = true;
auto& context = sequencerData[id];
context.Id = id;
auto realSize = ImFloor(size);
if (realSize.x <= 0.0f)
realSize.x = ImMax(4.0f, area.x);
if (realSize.y <= 0.0f)
realSize.y = ImMax(4.0f, context.FilledHeight);
const bool showZoom = !(flags & ImGuiNeoSequencerFlags_HideZoom);
const bool headerAlwaysVisible = (flags & ImGuiNeoSequencerFlags_AlwaysShowHeader);
context.SelectionEnabled = (flags & ImGuiNeoSequencerFlags_EnableSelection);
context.DraggingEnabled = context.SelectionEnabled && (flags & ImGuiNeoSequencerFlags_Selection_EnableDragging);
context.DeleteEnabled = context.SelectionEnabled && (flags & ImGuiNeoSequencerFlags_Selection_EnableDeletion);
context.TopLeftCursor = headerAlwaysVisible ? cursorBasePos : cursor;
// If Zoom is shown, we offset it by height of Zoom bar + padding
context.TopBarStartCursor = showZoom ? context.TopLeftCursor +
ImVec2{0, calculateZoomBarHeight()}
: context.TopLeftCursor;
context.StartFrame = *startFrame;
context.EndFrame = *endFrame;
context.Size = realSize;
context.TopBarSize = ImVec2(context.Size.x, style.TopBarHeight);
if (context.TopBarSize.y <= 0.0f)
context.TopBarSize.y = CalcTextSize("100").y + imStyle.FramePadding.y * 2.0f;
currentSequencer = window->IDStack[window->IDStack.size() - 1];
auto backgroundSize = context.Size;
const float topCut = abs(context.TopLeftCursor.y - cursor.y);
backgroundSize.y = backgroundSize.y - (topCut);
RenderNeoSequencerBackground(GetStyleNeoSequencerColorVec4(ImGuiNeoSequencerCol_Bg), context.TopLeftCursor,
backgroundSize,
drawList, style.SequencerRounding);
RenderNeoSequencerTopBarBackground(GetStyleNeoSequencerColorVec4(ImGuiNeoSequencerCol_TopBarBg),
context.TopBarStartCursor, context.TopBarSize,
drawList, style.SequencerRounding);
RenderNeoSequencerTopBarOverlay(context.Zoom, context.ValuesWidth, context.StartFrame, context.EndFrame,
context.OffsetFrame,
context.TopBarStartCursor, context.TopBarSize, drawList,
style.TopBarShowFrameLines, style.TopBarShowFrameTexts, style.MaxSizePerTick);
if (showZoom)
processAndRenderZoom(context, context.TopLeftCursor, flags & ImGuiNeoSequencerFlags_AllowLengthChanging,
startFrame, endFrame);
if (context.Size.y < context.FilledHeight)
context.Size.y = context.FilledHeight;
context.FilledHeight = context.TopBarSize.y + style.TopBarSpacing +
(showZoom ? calculateZoomBarHeight() : 0.0f);
context.StartValuesCursor = cursor + ImVec2{0, context.TopBarSize.y + style.TopBarSpacing};
if (showZoom)
context.StartValuesCursor = context.StartValuesCursor + ImVec2{0, calculateZoomBarHeight()};
context.ValuesCursor = context.StartValuesCursor;
processCurrentFrame(frame, context);
//if (enableSelection)
//processSelection(context);
const auto clipMin = context.TopBarStartCursor + ImVec2(0, context.TopBarSize.y);
drawList->PushClipRect(clipMin,
clipMin + backgroundSize - ImVec2(0, context.TopBarSize.y) -
ImVec2{0, GetFontSize() * style.ZoomHeightScale}, true);
return true;
}
void EndNeoSequencer()
{
IM_ASSERT(inSequencer && "Called end sequencer when BeginSequencer didnt return true or wasn't called at all!");
IM_ASSERT(sequencerData.count(currentSequencer) != 0 && "Ended sequencer has no context!");
auto& context = sequencerData[currentSequencer];
IM_ASSERT(context.TimelineStack.empty() && "Missmatch in timeline Begin / End");
if (context.SelectionEnabled)
processSelection(context);
context.LastSelectedTimeline = context.SelectedTimeline;
context.IsSelectionRightClicked = false;
if (context.SelectionEnabled)
renderSelection(context);
renderCurrentFrame(context);
inSequencer = false;
const ImVec2 min = {0, 0};
context.Size.y = context.FilledHeight;
const auto max = context.Size;
ItemSize({min, max});
PopID();
resetID();
EndChild();
}
IMGUI_API bool BeginNeoGroup(const char* label, bool* open)
{
return BeginNeoTimeline(label, nullptr, 0, open, ImGuiNeoTimelineFlags_Group);
}
IMGUI_API void EndNeoGroup()
{
return EndNeoTimeLine();
}
#ifdef __cplusplus
bool
BeginNeoTimeline(const char* label, std::vector<int32_t>& keyframes, bool* open, ImGuiNeoTimelineFlags flags)
{
std::vector<int32_t*> c_keyframes{keyframes.size()};
for (uint32_t i = 0; i < keyframes.size(); i++)
c_keyframes[i] = &keyframes[i];
return BeginNeoTimeline(label, c_keyframes.data(), c_keyframes.size(), open, flags);
}
#endif
void PushNeoSequencerStyleColor(ImGuiNeoSequencerCol idx, ImU32 col)
{
ImGuiColorMod backup;
backup.Col = idx;
backup.BackupValue = style.Colors[idx];
sequencerColorStack.push_back(backup);
style.Colors[idx] = ColorConvertU32ToFloat4(col);
}
void PushNeoSequencerStyleColor(ImGuiNeoSequencerCol idx, const ImVec4& col)
{
ImGuiColorMod backup;
backup.Col = idx;
backup.BackupValue = style.Colors[idx];
sequencerColorStack.push_back(backup);
style.Colors[idx] = col;
}
void PopNeoSequencerStyleColor(int count)
{
while (count > 0)
{
ImGuiColorMod& backup = sequencerColorStack.back();
style.Colors[backup.Col] = backup.BackupValue;
sequencerColorStack.pop_back();
count--;
}
}
void SetSelectedTimeline(const char* timelineLabel)
{
IM_ASSERT(inSequencer && "Not in active sequencer!");
auto& context = sequencerData[currentSequencer];
ImGuiWindow* window = GetCurrentWindow();
ImGuiID timelineID = 0;
if (timelineLabel)
{
timelineID = window->GetID(timelineLabel);
}
context.LastSelectedTimeline = context.SelectedTimeline;
context.SelectedTimeline = timelineID;
}
bool IsNeoTimelineSelected(ImGuiNeoTimelineIsSelectedFlags flags)
{
IM_ASSERT(inSequencer && "Not in active sequencer!");
auto& context = sequencerData[currentSequencer];
IM_ASSERT(!context.TimelineStack.empty() && "No active timelines are present!");
const bool newly = flags & ImGuiNeoTimelineIsSelectedFlags_NewlySelected;
const auto openTimeline = context.TimelineStack[context.TimelineStack.size() - 1];
if (!newly)
{
return context.SelectedTimeline == openTimeline;
}
return (context.SelectedTimeline != context.LastSelectedTimeline) &&
context.SelectedTimeline == openTimeline;
}
bool BeginNeoTimelineEx(const char* label, bool* open, ImGuiNeoTimelineFlags flags)
{
IM_ASSERT(inSequencer && "Not in active sequencer!");
const bool closable = open != nullptr;
auto& context = sequencerData[currentSequencer];
const auto& imStyle = GetStyle();
ImGuiWindow* window = GetCurrentWindow();
const ImGuiID id = window->GetID(label);
auto labelSize = CalcTextSize(label);
labelSize.y += imStyle.FramePadding.y * 2 + style.ItemSpacing.y * 2;
labelSize.x += imStyle.FramePadding.x * 2 + style.ItemSpacing.x * 2 +
(float) currentTimelineDepth * style.DepthItemSpacing;
bool isGroup = flags & ImGuiNeoTimelineFlags_Group && closable;
bool addRes = false;
if (isGroup)
{
labelSize.x += imStyle.ItemSpacing.x + GetFontSize();
addRes = groupBehaviour(id, open, labelSize);
} else
{
addRes = timelineBehaviour(id, labelSize);
}
if (currentTimelineDepth > 0)
{
context.ValuesCursor = {context.TopBarStartCursor.x, context.ValuesCursor.y};
}
currentTimelineHeight = labelSize.y;
context.FilledHeight += currentTimelineHeight;
const auto result = !closable || (*open);
context.LastTimelineOpenned = result;
if (addRes)
{
RenderNeoTimelane(id == context.SelectedTimeline,
context.ValuesCursor + ImVec2{context.ValuesWidth, 0},
ImVec2{context.Size.x - context.ValuesWidth, currentTimelineHeight},
GetStyleNeoSequencerColorVec4(ImGuiNeoSequencerCol_SelectedTimeline));
ImVec4 color = GetStyleColorVec4(ImGuiCol_Text);
if (IsItemHovered()) color.w *= 0.7f;
RenderNeoTimelineLabel(label,
context.ValuesCursor + imStyle.FramePadding +
ImVec2{(float) currentTimelineDepth * style.DepthItemSpacing, 0},
labelSize,
color,
isGroup,
isGroup && (*open));
}
if (result)
context.TimelineStack.push_back(id);
if (isGroup)
{ // Group requires special behaviour if its closed
context.ValuesCursor.y += currentTimelineHeight;
if (result)
{
currentTimelineDepth++;
context.GroupStack.push_back(id);
}
}
keyframeDuplicates.resize(0);
return result;
}
bool BeginNeoTimeline(const char* label, FrameIndexType** keyframes, uint32_t keyframeCount, bool* open,
ImGuiNeoTimelineFlags flags)
{
if (!BeginNeoTimelineEx(label, open, flags))
return false;
for (uint32_t i = 0; i < keyframeCount; i++)
{
NeoKeyframe(keyframes[i]);
}
return true;
}
void EndNeoTimeLine()
{
IM_ASSERT(inSequencer && "Not in active sequencer!");
auto& context = sequencerData[currentSequencer];
const auto& imStyle = GetStyle();
IM_ASSERT(context.TimelineStack.size() > 0 && "Timeline stack push/pop missmatch!");
context.ValuesCursor.x += imStyle.FramePadding.x + (float) currentTimelineDepth * style.DepthItemSpacing;
context.ValuesCursor.y += currentTimelineHeight;
finishPreviousTimeline(context);
if (!context.TimelineStack.empty() && !context.GroupStack.empty() &&
context.TimelineStack.back() == context.GroupStack.back())
{
currentTimelineDepth--;
context.GroupStack.pop_back();
}
context.TimelineStack.pop_back();
}
void NeoKeyframe(int32_t* value)
{
IM_ASSERT(inSequencer && "Not in active sequencer!");
auto& context = sequencerData[currentSequencer];
IM_ASSERT(!context.TimelineStack.empty() && "Not in timeline!");
createKeyframe(value);
}
bool IsNeoKeyframeHovered()
{
IM_ASSERT(inSequencer && "Not in active sequencer!");
auto& context = sequencerData[currentSequencer];
return context.IsLastKeyframeHovered;
}
bool IsNeoKeyframeSelected()
{
IM_ASSERT(inSequencer && "Not in active sequencer!");
auto& context = sequencerData[currentSequencer];
return context.IsLastKeyframeSelected;
}
bool IsNeoKeyframeRightClicked()
{
IM_ASSERT(inSequencer && "Not in active sequencer!");
auto& context = sequencerData[currentSequencer];
return context.IsLastKeyframeRightClicked;
}
void NeoClearSelection()
{
IM_ASSERT(inSequencer && "Not in active sequencer!");
auto& context = sequencerData[currentSequencer];
context.Selection.resize(0);
context.SelectionData.resize(0);
}
bool NeoIsSelecting()
{
IM_ASSERT(inSequencer && "Not in active sequencer!");
auto& context = sequencerData[currentSequencer];
return context.StateOfSelection == SelectionState::Selecting;
}
bool NeoHasSelection()
{
IM_ASSERT(inSequencer && "Not in active sequencer!");
auto& context = sequencerData[currentSequencer];
return !context.Selection.empty();
}
bool NeoIsDraggingSelection()
{
IM_ASSERT(inSequencer && "Not in active sequencer!");
auto& context = sequencerData[currentSequencer];
return context.StateOfSelection == SelectionState::Dragging;
}
uint32_t GetNeoKeyframeSelectionSize()
{
IM_ASSERT(inSequencer && "Not in active sequencer!");
auto& context = sequencerData[currentSequencer];
if (!context.DeleteEnabled)
return 0;
IM_ASSERT(!context.TimelineStack.empty() && "Not in timeline!");
const ImGuiID timelineId = context.TimelineStack.back();
for (auto&& deleteSelection: context.SelectionData)
{
if (deleteSelection.TimelineID == timelineId)
return deleteSelection.KeyframesToDelete.size();
}
return 0;
}
void GetNeoKeyframeSelection(FrameIndexType * selection)
{
IM_ASSERT(inSequencer && "Not in active sequencer!");
auto& context = sequencerData[currentSequencer];
if (!context.DeleteEnabled)
return;
IM_ASSERT(!context.TimelineStack.empty() && "Not in timeline!");
const ImGuiID timelineId = context.TimelineStack.back();
for (auto&& deleteSelection: context.SelectionData)
{
if (deleteSelection.TimelineID == timelineId)
{
for (int32_t i = 0; i < deleteSelection.KeyframesToDelete.size(); i++)
{
selection[i] = deleteSelection.KeyframesToDelete[i];
}
return;
}
}
}
bool IsNeoKeyframeSelectionRightClicked()
{
IM_ASSERT(inSequencer && "Not in active sequencer!");
auto& context = sequencerData[currentSequencer];
return context.IsSelectionRightClicked;
}
bool NeoCanDeleteSelection()
{
IM_ASSERT(inSequencer && "Not in active sequencer!");
auto& context = sequencerData[currentSequencer];
return context.DeleteEnabled && NeoHasSelection() && !NeoIsSelecting() && !NeoIsDraggingSelection();
}
}
ImGuiNeoSequencerStyle::ImGuiNeoSequencerStyle()
{
Colors[ImGuiNeoSequencerCol_Bg] = ImVec4{0.31f, 0.31f, 0.31f, 1.00f};
Colors[ImGuiNeoSequencerCol_TopBarBg] = ImVec4{0.22f, 0.22f, 0.22f, 0.84f};
Colors[ImGuiNeoSequencerCol_SelectedTimeline] = ImVec4{0.98f, 0.706f, 0.322f, 0.88f};
Colors[ImGuiNeoSequencerCol_TimelinesBg] = Colors[ImGuiNeoSequencerCol_TopBarBg];
Colors[ImGuiNeoSequencerCol_TimelineBorder] = Colors[ImGuiNeoSequencerCol_Bg] * ImVec4{0.5f, 0.5f, 0.5f, 1.0f};
Colors[ImGuiNeoSequencerCol_FramePointer] = ImVec4{0.98f, 0.24f, 0.24f, 0.50f};
Colors[ImGuiNeoSequencerCol_FramePointerHovered] = ImVec4{0.98f, 0.15f, 0.15f, 1.00f};
Colors[ImGuiNeoSequencerCol_FramePointerPressed] = ImVec4{0.98f, 0.08f, 0.08f, 1.00f};
Colors[ImGuiNeoSequencerCol_Keyframe] = ImVec4{0.59f, 0.59f, 0.59f, 0.50f};
Colors[ImGuiNeoSequencerCol_KeyframeHovered] = ImVec4{0.98f, 0.39f, 0.36f, 1.00f};
Colors[ImGuiNeoSequencerCol_KeyframePressed] = ImVec4{0.98f, 0.39f, 0.36f, 1.00f};
Colors[ImGuiNeoSequencerCol_KeyframeSelected] = ImVec4{0.32f, 0.23f, 0.98f, 1.00f};
Colors[ImGuiNeoSequencerCol_FramePointerLine] = ImVec4{0.98f, 0.98f, 0.98f, 0.8f};
Colors[ImGuiNeoSequencerCol_ZoomBarBg] = ImVec4{0.59f, 0.59f, 0.59f, 0.90f};
Colors[ImGuiNeoSequencerCol_ZoomBarSlider] = ImVec4{0.8f, 0.8f, 0.8f, 0.60f};
Colors[ImGuiNeoSequencerCol_ZoomBarSliderHovered] = ImVec4{0.98f, 0.98f, 0.98f, 0.80f};
Colors[ImGuiNeoSequencerCol_ZoomBarSliderEnds] = ImVec4{0.59f, 0.59f, 0.59f, 0.90f};
Colors[ImGuiNeoSequencerCol_ZoomBarSliderEndsHovered] = ImVec4{0.93f, 0.93f, 0.93f, 0.93f};
Colors[ImGuiNeoSequencerCol_SelectionBorder] = ImVec4{0.98f, 0.706f, 0.322f, 0.61f};
Colors[ImGuiNeoSequencerCol_Selection] = ImVec4{0.98f, 0.706f, 0.322f, 0.33f};
}