summaryrefslogtreecommitdiff
path: root/src/imgui_ui_timeline.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/imgui_ui_timeline.cpp')
-rw-r--r--src/imgui_ui_timeline.cpp1070
1 files changed, 1070 insertions, 0 deletions
diff --git a/src/imgui_ui_timeline.cpp b/src/imgui_ui_timeline.cpp
new file mode 100644
index 0000000..3a89641
--- /dev/null
+++ b/src/imgui_ui_timeline.cpp
@@ -0,0 +1,1070 @@
+static void
+ImGui_Timeline_BGElements(project_data *File, project_state *State, memory *Memory, ui *UI, ImDrawList *draw_list, ImVec2 TimelineSizeWithBorder, ImVec2 TimelineAbsolutePos, block_composition MainComp,
+ ImVec2 TimelineZoomSize, ImVec2 TimelineMoveSize)
+{
+ // start/end regions
+ uint32 PreviewAreaColor = IM_COL32(128, 128, 128, 30);
+ ImVec2 Region_Min = ImVec2(TimelineAbsolutePos.x + TimelineMoveSize.x + ((real32)MainComp.Frame_Start / MainComp.Frame_Count)*TimelineZoomSize.x, TimelineAbsolutePos.y);
+ ImVec2 Region_Max = ImVec2(Region_Min.x + ((real32)(MainComp.Frame_End - MainComp.Frame_Start) / MainComp.Frame_Count)*TimelineZoomSize.x, Region_Min.y + TimelineSizeWithBorder.y);
+ draw_list->AddRectFilled(Region_Min, Region_Max, PreviewAreaColor);
+ // cache bar
+ cache_entry *EntryArray = State->Render.Entry;
+ int c = 0;
+ int count = Memory->EntryCount;
+ while (count != 0) {
+ if (EntryArray[c].IsCached) {
+ if (EntryArray[c].Type == cache_entry_type_comp &&
+ EntryArray[c].TypeInfo == File->PrincipalCompIndex) {
+ int Frame = EntryArray[c].TypeInfo_Sub;
+ ImVec2 Region_Min = ImVec2(TimelineAbsolutePos.x + TimelineMoveSize.x + ((real32)Frame / MainComp.Frame_Count)*TimelineZoomSize.x, TimelineAbsolutePos.y);
+ ImVec2 Region_Max = ImVec2(Region_Min.x + ((real32)(1) / MainComp.Frame_Count)*TimelineZoomSize.x, Region_Min.y + 2.0f);
+ draw_list->AddRectFilled(Region_Min, Region_Max, IM_COL32(0, 0, 128, 100));
+ }
+ count--;
+ }
+ c++;
+ }
+}
+
+static void
+ImGui_Timeline_HorizontalIncrementDraw(project_state *State, ui *UI, ImDrawList *draw_list, ImVec2 TimelineSizeWithBorder, ImVec2 TimelineAbsolutePos, block_composition MainComp,
+ ImVec2 TimelineZoomSize, ImVec2 TimelineMoveSize)
+{
+ uint32 LineColor = IM_COL32(200, 200, 200, 40);
+ uint32 PlayheadColor = IM_COL32(000, 000, 200, 160);
+ uint32 PreviewAreaColor = IM_COL32(128, 128, 128, 60);
+
+ Assert(TimelineZoomSize.x > 0.0f);
+
+ real32 x = 0;
+ bool32 RightmostEdge = false;
+ real32 Increment = (real32)1 / MainComp.Frame_Count;
+ if (UI->TimelinePercentZoomed.x > 0.90)
+ Increment = (real32)MainComp.FPS / MainComp.Frame_Count;
+ else if (UI->TimelinePercentZoomed.x > 0.40)
+ Increment *= 2;
+
+ while (!RightmostEdge) {
+ ImVec2 Min = ImVec2(TimelineAbsolutePos.x + TimelineMoveSize.x + x*TimelineZoomSize.x, TimelineAbsolutePos.y);
+ ImVec2 Max = ImVec2(Min.x + 2, TimelineAbsolutePos.y + TimelineSizeWithBorder.y);
+ if (Min.x < TimelineAbsolutePos.x + TimelineSizeWithBorder.x) {
+ draw_list->AddLine(Min, Max, LineColor);
+ char buf2[6];
+ uint32 FrameNumber = (uint32)(x * MainComp.Frame_Count) % MainComp.FPS;
+ if (FrameNumber != 0)
+ sprintf(buf2, ":%.2i", FrameNumber);
+ else
+ sprintf(buf2, "%.2i:00", (uint32)(x * MainComp.Frame_Count) / MainComp.FPS);
+ draw_list->AddText(ImVec2(Min.x, TimelineAbsolutePos.y), IM_COL32(200, 200, 200, 130), buf2);
+ x += Increment;
+ if (x > 1.0f)
+ RightmostEdge = true;
+ } else {
+ RightmostEdge = true;
+ }
+ }
+
+ ImVec2 Min = ImVec2(TimelineAbsolutePos.x + TimelineMoveSize.x + ((real32)State->Frame_Current / MainComp.Frame_Count)*TimelineZoomSize.x, TimelineAbsolutePos.y);
+ ImVec2 Max = ImVec2(Min.x + 2, TimelineAbsolutePos.y + TimelineSizeWithBorder.y);
+ draw_list->AddLine(Min, Max, PlayheadColor);
+
+ real32 Size = ImGui::GetFontSize() * 2;
+ ImGui::SetCursorScreenPos(Min - ImVec2(Size / 2, 0));
+ ImGui::Button("##playhead", ImVec2(Size, Size));
+ bool32 IsHovered = ImGui::IsItemHovered();
+ bool32 IsItemActive = ImGui::IsItemActive();
+ bool32 IsItemActivated = ImGui::IsItemActivated();
+ bool32 IsItemDeactivated = ImGui::IsItemDeactivated();
+ if (IsItemActivated) {
+ State->Interact_Active = interact_type_timeline_scrub;
+ State->Interact_Offset[0] = State->Frame_Current;
+ }
+ if (IsItemActive) {
+ ImVec2 DragDelta = ImGui::GetMouseDragDelta();
+ int32 NewCurrent = (int32)(State->Interact_Offset[0] + (DragDelta.x / TimelineZoomSize.x * MainComp.Frame_Count) + 0.5);
+ if (NewCurrent != State->Frame_Current) {
+ State->Frame_Current = NewCurrent;
+ State->UpdateFrame = true;
+ }
+ }
+ if (IsItemDeactivated) {
+ State->Interact_Active = interact_type_none;
+ State->Interact_Modifier = 0;
+ }
+}
+
+// TODO(fox): Bring back graph info!
+static void
+ImGui_Timeline_GraphInfo(project_data *File, project_state *State, memory *Memory, ui *UI, ImGuiIO io,
+ sorted_comp_array *SortedCompArray, sorted_layer_array *SortedLayerArray,
+ sorted_property_array *SortedPropertyStart, uint16 *SortedKeyframeArray)
+{
+ bool open = true;
+ ImGui::Begin("Graph info");
+
+ int h = 0, c = 0, i = 0;
+ while (Block_Loop(Memory, F_Layers, File->Layer_Count, &h, &c, &i))
+ {
+ block_layer *Layer = (block_layer *)Memory_Block_AddressAtIndex(Memory, F_Layers, i);
+ if (!Layer->IsSelected)
+ continue;
+
+ block_string *String = (block_string *)Memory_Block_AddressAtIndex(Memory, F_Strings, Layer->Block_String_Index);
+ ImGui::Text(String->Char);
+
+ ImGui::PushID(i);
+ for (int h = 0; h < AmountOf(Layer->Property); h++) {
+ property_channel *Property = &Layer->Property[h];
+ if (Property->Block_Bezier_Count) {
+ sorted_property_array *InfoLocation = Property_GetSortedInfo(SortedPropertyStart, i, h);
+ uint16 *ArrayLocation = Property_GetSortedArray(SortedKeyframeArray, i, h);
+ ImGui::PushID(Property);
+ if (ImGui::Selectable(DefaultChannel[h], Property_IsGraphSelected(Memory, Property, ArrayLocation))) {
+ File_DeselectAllKeyframes(File, State, Memory, SortedCompArray, SortedLayerArray, SortedPropertyStart, SortedKeyframeArray);
+ for (int p = 0; p < Property->Keyframe_Count; p++) {
+ int k = ArrayLocation[p];
+ bezier_point *Point = Bezier_LookupAddress(Memory, Property, k);
+ Point->IsSelected = true;
+ }
+ State->RecentSelectionType = selection_type_keyframe;
+ }
+ ImGui::PopID();
+ }
+ }
+
+ ImGui::PopID();
+ }
+
+ ImGui::End();
+}
+
+static void
+ImGui_Timeline_DrawKeySheet(project_data *File, project_state *State, memory *Memory, ui *UI, ImGuiIO io, ImDrawList *draw_list, property_channel *Property, uint16 *ArrayLocation,
+ ImVec2 Increment, ImVec2 TimelineAbsolutePos, ImVec2 GraphPos, ImVec2 TimelineMoveSize, ImVec2 TimelineZoomSize,
+ ImVec2 TimelineSize, ImVec2 TimelineSizeWithBorder, real32 LayerIncrement,
+ sorted_comp_array *SortedCompArray, sorted_layer_array *SortedLayerArray, sorted_property_array *SortedPropertyStart, uint16 *SortedKeyframeArray)
+{
+ ImGui::PushID(Property);
+
+ for (int p = 0; p < Property->Keyframe_Count; p++) {
+ int k = ArrayLocation[p];
+ bezier_point *PointAddress = Bezier_LookupAddress(Memory, Property, k);
+
+ v2 PointPos[3];
+ Bezier_Interact_Evaluate(State, PointAddress, PointPos);
+
+ real32 LocalPos_Ratio_X = PointPos[0].x * Increment.x;
+ real32 Keyframe_ScreenPos_X = GraphPos.x + TimelineMoveSize.x + LocalPos_Ratio_X * TimelineZoomSize.x;
+
+ ImVec2 Keyframe_ScreenPos(Keyframe_ScreenPos_X, GraphPos.y);
+
+ if (State->BoxSelect) {
+ if (ImGui_TestBoxSelection_Point(Keyframe_ScreenPos, io, &PointAddress->IsSelected))
+ State->RecentSelectionType = selection_type_keyframe;
+ }
+
+ ImVec2 ButtonSize(16, 16);
+
+ ImGui::PushID(p);
+
+ ImU32 PointCol = (PointAddress->IsSelected) ? ImColor(0.8f, 0.5f, 0.0f, 1.0f) : ImColor(0.1f, 0.1f, 0.1f, 1.0f);
+ ImU32 LineCol = (PointAddress->IsSelected) ? ImColor(0.8f, 0.5f, 0.5f, 1.0f) : ImColor(0.4f, 0.4f, 0.4f, 1.0f);
+ ImGui::SetCursorScreenPos(Keyframe_ScreenPos - (ButtonSize * 0.5));
+ ImGui::InvisibleButton("##keyframemover", ButtonSize, ImGuiMouseButton_Left);
+ bool32 IsHovered = ImGui::IsItemHovered();
+ bool32 IsItemActive = ImGui::IsItemActive();
+ bool32 IsItemActivated = ImGui::IsItemActivated();
+ bool32 IsItemDeactivated = ImGui::IsItemDeactivated();
+ bool32 LeftClick = ImGui::IsMouseDown(ImGuiMouseButton_Left);
+ bool32 RightClick = ImGui::IsMouseDown(ImGuiMouseButton_Right);
+
+ if (IsHovered) {
+ PointCol = ImColor(1.0f, 0.8f, 0.8f, 1.0f);
+ ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
+ }
+
+ if (IsItemActivated) {
+ PointAddress->IsSelected = 1;
+ }
+
+ if (IsItemActive) {
+ if (State->Interact_Active == interact_type_none) {
+ State->Interact_Offset[2] = io.MousePos.x;
+ State->Interact_Offset[3] = io.MousePos.y;
+ State->Interact_Active = interact_type_keyframe_move;
+ State->Interact_Modifier = 1; // X axis movement only
+ } else {
+ Assert(State->Interact_Active == interact_type_keyframe_move);
+ ImGui_WarpMouse(State, io.MousePos, TimelineAbsolutePos, TimelineAbsolutePos + TimelineSizeWithBorder, 1);
+ ImVec2 DragDelta = io.MousePos - ImVec2(State->Interact_Offset[2], State->Interact_Offset[3]);
+ DragDelta = DragDelta + (ImVec2(State->Warp_X, State->Warp_Y) * TimelineSize);
+ if (io.MouseDelta.x || io.MouseDelta.y) {
+ State->UpdateFrame = true;
+ }
+ if (State->Interact_Active == interact_type_keyframe_move) {
+ State->Interact_Offset[0] = (DragDelta.x / TimelineZoomSize.x) / Increment.x;
+ State->Interact_Offset[1] = DragDelta.y;
+ }
+ }
+ }
+
+ if (IsItemDeactivated) {
+ Keyframe_Commit(File, State, Memory, SortedCompArray, SortedLayerArray, SortedPropertyStart, SortedKeyframeArray);
+ }
+
+ draw_list->AddCircleFilled(Keyframe_ScreenPos, 4, PointCol);
+
+ ImGui::PopID();
+ }
+ ImGui::PopID();
+}
+
+static void
+ImGui_Timeline_DrawGraph(project_data *File, project_state *State, memory *Memory, ui *UI, ImGuiIO io, ImDrawList *draw_list,
+ ImVec2 Increment, ImVec2 TimelineAbsolutePos, ImVec2 TimelineMoveSize, ImVec2 TimelineZoomSize,
+ ImVec2 TimelineSize, ImVec2 TimelineSizeWithBorder, real32 LayerIncrement,
+ sorted_comp_array *SortedCompArray, sorted_layer_array *SortedLayerArray,
+ sorted_property_array *SortedPropertyStart, uint16 *SortedKeyframeArray)
+{
+ // I'm using the draw splitter here to be able to draw the dots on top of the graph lines.
+ State->Test.Split(draw_list, 2);
+
+ for (int c = 0; c < File->Comp_Count; c++) {
+ sorted_comp_array SortedCompStart = SortedCompArray[c];
+ sorted_layer_array *SortedLayerStart = Sorted_GetLayerStart(SortedLayerArray, &SortedCompStart, c);
+ for (int i = 0; i < SortedCompStart.LayerCount; i++) {
+ sorted_layer_array *SortedLayer = &SortedLayerStart[i];
+ block_layer *Layer = (block_layer *)Memory_Block_AddressAtIndex(Memory, F_Layers, SortedLayer->Block_Layer_Index);
+ if (!Layer->IsSelected)
+ continue;
+
+ int32 Frame_Start = Layer->Frame_Start;
+
+ ImGui::PushID(i);
+
+ if ((State->Interact_Active == interact_type_keyframe_move ||
+ State->Interact_Active == interact_type_keyframe_rotate ||
+ State->Interact_Active == interact_type_keyframe_scale))
+ {
+ ImGui_WarpMouse(State, io.MousePos, TimelineAbsolutePos, TimelineAbsolutePos + TimelineSizeWithBorder);
+ ImVec2 DragDelta = io.MousePos - ImVec2(State->Interact_Offset[2], State->Interact_Offset[3]);
+ DragDelta = DragDelta + (ImVec2(State->Warp_X, State->Warp_Y) * TimelineSize);
+ if (io.MouseDelta.x || io.MouseDelta.y) {
+ State->UpdateFrame = true;
+ }
+ if (State->Interact_Active == interact_type_keyframe_move) {
+ // The Y increment varies between graphs, so we have to do it in the Bezier_EvaluateValue call.
+ State->Interact_Offset[0] = (DragDelta.x / TimelineZoomSize.x) / Increment.x;
+ State->Interact_Offset[1] = DragDelta.y;
+ } else if (State->Interact_Active == interact_type_keyframe_scale) {
+ State->Interact_Offset[0] = (DragDelta.x / TimelineSizeWithBorder.x * UI->TimelinePercentZoomed.x) / Increment.x;
+ } else if (State->Interact_Active == interact_type_keyframe_rotate) {
+ State->Interact_Offset[0] = (DragDelta.x / TimelineZoomSize.x);
+ /*
+ real32 Slope_Old = (Keyframe_ScreenPos.y - State->Interact_Offset[3]) / (Keyframe_ScreenPos.x - State->Interact_Offset[2]);
+ real32 Slope_New = (Keyframe_ScreenPos.y - io.MousePos.y) / (Keyframe_ScreenPos.x - io.MousePos.x);
+ State->Interact_Offset[0] = atan((Slope_Old - Slope_New) / (1 + Slope_Old * Slope_New));
+ */
+ }
+ ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeAll);
+ }
+
+ sorted_property_array *InfoLocation = SortedPropertyStart + SortedLayer->SortedPropertyStart;
+ uint16 *ArrayLocation = SortedKeyframeArray + SortedLayer->SortedKeyframeStart;
+ int h1 = 0, c1 = 0, p = 0;
+ property_channel *Property = NULL;
+ while (Layer_LoopChannels(State, Memory, &InfoLocation, &ArrayLocation, Layer, &Property, NULL, &h1, &c1, &p))
+ {
+ ImGui::PushID(Property);
+ if (Property->Block_Bezier_Count) {
+ real32 MinY, MaxY;
+ Property_MinMax_Y(Memory, State, Property, InfoLocation, &MinY, &MaxY, 0);
+ Assert(InfoLocation->MinYIndex < Property->Keyframe_Count);
+ Assert(InfoLocation->MaxYIndex < Property->Keyframe_Count);
+ Assert(MaxY >= MinY);
+ real32 Y_Increment = (MaxY - MinY) ? (1 / (MaxY - MinY)) : 0.5;
+
+ real32 GraphScale = 0;
+ real32 GraphPos = 0;
+ if ((1 / Y_Increment) < 5) {
+ GraphScale = 0.2;
+ GraphPos = 0.3;
+ } else if ((1 / Y_Increment) > 700) {
+ GraphScale = 0.6;
+ GraphPos = 0.06;
+ } else {
+ GraphScale = 0.4;
+ GraphPos = 0.2;
+ }
+
+
+ real32 GraphMoveHeight = TimelineMoveSize.y + (TimelineZoomSize.y * GraphPos);
+ real32 GraphZoomHeight = TimelineZoomSize.y * GraphScale;
+
+ bool32 IsGraphSelected = Property_IsGraphSelected(Memory, Property, ArrayLocation);
+ uint32 GraphCol = IsGraphSelected ? IM_COL32(255, 180, 150, 255) : IM_COL32(255, 255, 255, 70);
+
+ bezier_point *PointAddress[2] = {};
+ ImVec2 Keyframe_ScreenPos[6] = {};
+ for (int p = 0; p < Property->Keyframe_Count; p++) {
+ int k = ArrayLocation[p];
+ if (Property->Keyframe_Count == 1)
+ int b = 0;
+ int Idx = (p % 2);
+ int NewIdx = Idx * 3;
+ int OldIdx = (NewIdx == 3) ? 0 : 3;
+ PointAddress[Idx] = Bezier_LookupAddress(Memory, Property, k);
+
+ v2 PointPos[3];
+ Bezier_Interact_Evaluate(State, PointAddress[Idx], PointPos, GraphZoomHeight, Y_Increment);
+
+ ImVec2 Keyframe_LocalPos[3] = { V2(PointPos[0]), V2(PointPos[0] + PointPos[1]), V2(PointPos[0] + PointPos[2]) };
+
+ ImVec2 Keyframe_LocalPos_Ratio[3];
+ for (int b = 0; b < 3; b++) {
+ Keyframe_LocalPos_Ratio[b] = (Keyframe_LocalPos[b] - ImVec2(0, MinY)) * ImVec2(Increment.x, Y_Increment);
+ Keyframe_ScreenPos[NewIdx + b] = TimelineAbsolutePos + ImVec2(TimelineMoveSize.x, GraphMoveHeight) + ((ImVec2(1, -1) * Keyframe_LocalPos_Ratio[b] + ImVec2(0, 1)) * ImVec2(TimelineZoomSize.x, GraphZoomHeight));
+ }
+
+ if (State->BoxSelect) {
+ if (ImGui_TestBoxSelection_Point(Keyframe_ScreenPos[NewIdx], io, &PointAddress[Idx]->IsSelected))
+ State->RecentSelectionType = selection_type_keyframe;
+ }
+
+ State->Test.SetCurrentChannel(draw_list, 1);
+
+ ImVec2 ButtonSize(16, 16);
+
+ ImGui::PushID(k);
+ int Max = PointAddress[Idx]->IsSelected ? 2 : 0;
+ for (int b = Max; b >= 0; b--) {
+ ImU32 PointCol = ((PointAddress[Idx]->IsSelected - 1) == b) ? ImColor(0.8f, 0.5f, 0.0f, 1.0f) : ImColor(0.1f, 0.1f, 0.1f, 1.0f);
+ ImU32 LineCol = ((PointAddress[Idx]->IsSelected - 1) == b) ? ImColor(0.8f, 0.5f, 0.5f, 1.0f) : ImColor(0.4f, 0.4f, 0.4f, 1.0f);
+ ImGui::PushID(b);
+ ImGui::SetCursorScreenPos(Keyframe_ScreenPos[NewIdx + b] - (ButtonSize * 0.5));
+ ImGui::InvisibleButton("##keyframemover", ButtonSize, ImGuiMouseButton_Left);
+ bool32 IsHovered = ImGui::IsItemHovered();
+ bool32 IsItemActive = ImGui::IsItemActive();
+ bool32 IsItemActivated = ImGui::IsItemActivated();
+ bool32 IsItemDeactivated = ImGui::IsItemDeactivated();
+ bool32 LeftClick = ImGui::IsMouseDown(ImGuiMouseButton_Left);
+ bool32 RightClick = ImGui::IsMouseDown(ImGuiMouseButton_Right);
+
+ if (IsHovered) {
+ PointCol = ImColor(1.0f, 0.8f, 0.8f, 1.0f);
+ ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
+ }
+
+ if (IsItemActivated) {
+ PointAddress[Idx]->IsSelected = b+1;
+ }
+
+ if (b != 0 && PointAddress[Idx]->IsSelected)
+ draw_list->AddLine(Keyframe_ScreenPos[NewIdx], Keyframe_ScreenPos[NewIdx + b], LineCol, 2.0f);
+
+ if (b == 0) {
+ draw_list->AddCircleFilled(Keyframe_ScreenPos[NewIdx + b], 4, PointCol);
+ if (IsGraphSelected) {
+ char buf[8];
+ sprintf(buf, "%.2f", Keyframe_LocalPos[0].y);
+ draw_list->AddText(Keyframe_ScreenPos[NewIdx + b], 0xFFFFFFFF, buf);
+ }
+ } else {
+ draw_list->AddCircle(Keyframe_ScreenPos[NewIdx + b], 6, PointCol, 0, 2);
+ }
+
+ ImGui::PopID();
+ }
+ ImGui::PopID();
+
+ State->Test.SetCurrentChannel(draw_list, 0);
+
+ if (p != 0) {
+ if (PointAddress[0]->Type == interpolation_type_bezier && PointAddress[1]->Type == interpolation_type_bezier) {
+ draw_list->AddBezierCubic(Keyframe_ScreenPos[OldIdx], Keyframe_ScreenPos[OldIdx + 2],
+ Keyframe_ScreenPos[NewIdx + 1], Keyframe_ScreenPos[NewIdx], GraphCol, 1.0f, 0);
+ } else {
+ draw_list->AddLine(Keyframe_ScreenPos[0], Keyframe_ScreenPos[3], GraphCol, 1.0f);
+ }
+ }
+ }
+ }
+ ImGui::PopID();
+ }
+ ImGui::PopID();
+ }
+ }
+ State->Test.Merge(draw_list);
+}
+
+
+static void
+ImGui_Timeline_DrawPrecomp(project_data *File, project_state *State, memory *Memory, ui *UI, ImGuiIO io, ImDrawList *draw_list, int16 RecursionIdx[MAX_PRECOMP_RECURSIONS], uint32 Recursions,
+ ImVec2 Increment, ImVec2 TimelineAbsolutePos, ImVec2 TimelineMoveSize, ImVec2 TimelineZoomSize,
+ ImVec2 TimelineSize, ImVec2 TimelineSizeWithBorder, real32 LayerIncrement,
+ sorted_comp_array *SortedCompArray, sorted_layer_array *SortedLayerArray, sorted_property_array *SortedPropertyStart, uint16 *SortedKeyframeArray)
+{
+ uint16 CompIndex = 0;
+ if (RecursionIdx[Recursions] == -1) {
+ CompIndex = File->PrincipalCompIndex;
+ } else {
+ block_layer *Layer = (block_layer *)Memory_Block_AddressAtIndex(Memory, F_Layers, RecursionIdx[Recursions]);
+ CompIndex = Layer->Block_Source_Index;
+ }
+ block_composition *Comp = (block_composition *)Memory_Block_AddressAtIndex(Memory, F_Precomps, CompIndex);
+ sorted_comp_array SortedCompStart = SortedCompArray[CompIndex];
+ sorted_layer_array *SortedLayerStart = Sorted_GetLayerStart(SortedLayerArray, SortedCompArray, CompIndex);
+
+ ImGui::PushID(CompIndex);
+
+ // NOTE(fox): Layer sorting pre-calculates UI positioning, so the order layers are drawn is arbitrary.
+ real32 DisplayOffset = 0;
+ for (int i = SortedCompStart.LayerCount - 1; i >= 0; i--)
+ {
+ sorted_layer_array SortEntry = SortedLayerStart[i];
+ uint32 Index_Physical = SortEntry.Block_Layer_Index;
+ block_layer *Layer = (block_layer *)Memory_Block_AddressAtIndex(Memory, F_Layers, Index_Physical);
+
+ int32 Frame_Start = Layer->Frame_Start;
+ int32 Frame_End = Layer->Frame_End;
+ real32 Vertical_Offset = SortEntry.SortedOffset + DisplayOffset;
+
+ Layer_Evaluate_Display(State, Memory, Layer, SortedPropertyStart, SortedKeyframeArray, SortedLayerArray, SortedCompArray, SortedLayerStart, i, &DisplayOffset);
+
+ if (Layer->IsSelected)
+ Interact_Evaluate_Layer(Memory, State, Index_Physical, SortedCompStart, SortedLayerStart, &Frame_Start, &Frame_End);
+
+ ImVec2 Layer_LocalPos = ImVec2(Frame_Start, Vertical_Offset);
+ ImVec2 Layer_LocalSize = ImVec2(Frame_End - Frame_Start, Layer->Vertical_Height);
+
+ ImVec2 Layer_LocalPos_Ratio = (Layer_LocalPos * Increment);
+ ImVec2 Layer_LocalSize_Ratio = Layer_LocalSize * Increment;
+ ImVec2 Layer_ScreenPos_Min = TimelineAbsolutePos + TimelineMoveSize + (Layer_LocalPos_Ratio * TimelineZoomSize);
+ ImVec2 Layer_ScreenPos_Max = TimelineAbsolutePos + TimelineMoveSize + ((Layer_LocalPos_Ratio + Layer_LocalSize_Ratio) * TimelineZoomSize);
+ ImVec2 Layer_ScreenSize = Layer_ScreenPos_Max - Layer_ScreenPos_Min;
+
+ // UI
+
+ ImU32 LayerColor = 0;
+ ImU32 BorderColor = 0;
+ LayerColor = ImColor(UI->LayerColors[Layer->ColIndex]);
+ BorderColor = ImColor(0.2, 0.2, 0.2, 1.0f);
+
+ draw_list->AddRectFilled(Layer_ScreenPos_Min, Layer_ScreenPos_Max, LayerColor);
+ draw_list->AddRect(Layer_ScreenPos_Min, Layer_ScreenPos_Max, BorderColor, 2);
+ block_string *String = (block_string *)Memory_Block_AddressAtIndex(Memory, F_Strings, Layer->Block_String_Index);
+ char buf[128];
+#if DEBUG
+ sprintf(buf, "%s, %i", String->Char, Index_Physical);
+#else
+ sprintf(buf, "%s", String->Char);
+#endif
+ if (UI->TimelinePercentZoomed.y <= 1.0f)
+ draw_list->AddText(Layer_ScreenPos_Min, 0xFFFFFFFF, buf);
+
+ if (Layer->IsSelected == 1) {
+ draw_list->AddRectFilled(Layer_ScreenPos_Min, Layer_ScreenPos_Max, ImColor(0.25f, 0.25f, 0.25f, 0.5f), 2);
+ draw_list->AddRect(Layer_ScreenPos_Min, Layer_ScreenPos_Max, ImColor(1.0f, 1.0f, 1.0f, 0.5f), 2);
+ } else if (Layer->IsSelected == 2) {
+ // draw_list->AddRectFilled(Layer_ScreenPos_Min, Layer_ScreenPos_Max, ImColor(0.25f, 0.25f, 0.25f, 0.5f), 2);
+ draw_list->AddRect(Layer_ScreenPos_Min, Layer_ScreenPos_Max, ImColor(0.0f, 0.9f, 0.0f, 0.9f), 4);
+ }
+
+ //
+
+ // Main interaction
+
+ ImGui::PushID(i);
+ if (State->TimelineMode == timeline_mode_default) {
+
+ if (State->BoxSelect && State->TimelineMode == timeline_mode_default) {
+ bool32 Test = 0;
+ if (io.MouseClickedPos[0].y < io.MousePos.y)
+ Test = (Layer_ScreenPos_Min.y >= io.MouseClickedPos[0].y && Layer_ScreenPos_Min.y <= io.MousePos.y);
+ else
+ Test = (Layer_ScreenPos_Max.y <= io.MouseClickedPos[0].y && Layer_ScreenPos_Max.y >= io.MousePos.y);
+
+ if (io.MouseClickedPos[0].x < io.MousePos.x)
+ Test &= (Layer_ScreenPos_Max.x >= io.MouseClickedPos[0].x && Layer_ScreenPos_Min.x <= io.MousePos.x);
+ else
+ Test &= (Layer_ScreenPos_Min.x <= io.MouseClickedPos[0].x && Layer_ScreenPos_Max.x >= io.MousePos.x);
+
+ if (Test) {
+ if (!Layer->IsSelected) {
+ Layer_Select(Memory, State, Index_Physical);
+ }
+ } else if (!io.KeyShift) {
+ Layer->IsSelected = false;
+ }
+ }
+
+
+ ImVec2 ResizeSize = ImVec2(Increment.x * 0.45 * TimelineZoomSize.x, Layer_ScreenSize.y);
+ ImVec2 ResizePos[2] = { Layer_ScreenPos_Min, ImVec2(Layer_ScreenPos_Max.x - ResizeSize.x, Layer_ScreenPos_Min.y) };
+ for (int b = 0; b < 2; b++) {
+ ImGui::PushID(b);
+ ImGui::SetCursorScreenPos(ResizePos[b]);
+ ImGui::Button("##layer_resize", ResizeSize);
+
+ if (ImGui::IsItemHovered()) {
+ ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeEW);
+ }
+ if (ImGui::IsItemActivated()) {
+ if (!Layer->IsSelected) {
+ if (!io.KeyShift) Layer_DeselectAll(File, State, Memory);
+ Layer_Select(Memory, State, Index_Physical);
+ }
+ }
+ if (ImGui::IsItemActive()) {
+ if (ImGui::IsMouseDragging(ImGuiMouseButton_Left, -1)) {
+ Assert(Layer->IsSelected);
+ State->Interact_Active = interact_type_layer_timeadjust;
+ ImVec2 DragDelta = ImGui::GetMouseDragDelta();
+ DragDelta = DragDelta + (ImVec2(State->Warp_X, State->Warp_Y) * TimelineSize);
+
+ State->Interact_Offset[0] = (DragDelta.x / TimelineSizeWithBorder.x * UI->TimelinePercentZoomed.x) * Comp->Frame_Count;
+ State->Interact_Offset[1] = b;
+ }
+ }
+ if (ImGui::IsItemDeactivated()) {
+ if (State->Interact_Active == interact_type_layer_timeadjust) {
+ History_Entry_Commit(Memory, "Adjust layer timing");
+ int h = 0, c = 0, i = 0;
+ while (Block_Loop(Memory, F_Layers, File->Layer_Count, &h, &c, &i)) {
+ block_layer *Layer = (block_layer *)Memory_Block_AddressAtIndex(Memory, F_Layers, i);
+ if (Layer->IsSelected) {
+ // NOTE(fox): Some data on the tree could be saved here.
+ History_Action_Swap(Memory, F_File, sizeof(Layer->Frame_Start), &Layer->Frame_Start);
+ History_Action_Swap(Memory, F_File, sizeof(Layer->Frame_End), &Layer->Frame_End);
+ Interact_Evaluate_Layer(Memory, State, Index_Physical, SortedCompStart, SortedLayerStart, &Layer->Frame_Start, &Layer->Frame_End);
+ }
+ }
+ History_Entry_End(Memory);
+ State->Interact_Active = interact_type_none;
+ State->Interact_Modifier = 0;
+ State->Interact_Offset[0] = 0;
+ State->Interact_Offset[1] = 0;
+ }
+ }
+
+ ImGui::PopID();
+ }
+
+ ImGui::SetCursorScreenPos(Layer_ScreenPos_Min);
+ ImGui::InvisibleButton("##layer_mid", Layer_ScreenSize, ImGuiMouseButton_Left);
+
+ if (ImGui::IsItemActivated()) {
+ if (!Layer->IsSelected) {
+ if (!io.KeyShift) Layer_DeselectAll(File, State, Memory);
+ Layer_Select(Memory, State, Index_Physical);
+ Layer_Select_RecurseUp(Memory, State, Index_Physical, RecursionIdx, Recursions);
+ }
+ }
+
+ ImGui::OpenPopupOnItemClick("layerpopup", ImGuiPopupFlags_MouseButtonRight);
+ if (ImGui::BeginPopup("layerpopup")) {
+ if (ImGui::Selectable("Visible")) {
+ bool32 Commit = false;
+ int h = 0, z = 0, i = 0;
+ while (Block_Loop(Memory, F_Layers, File->Layer_Count, &h, &z, &i)) {
+ block_layer *Layer = (block_layer *)Memory_Block_AddressAtIndex(Memory, F_Layers, i);
+ if (Layer->IsSelected) {
+ if (!Commit) {
+ History_Entry_Commit(Memory, "Toggle visibility");
+ Commit = true;
+ }
+ History_Action_Swap(Memory, F_File, sizeof(Layer->IsVisible), &Layer->IsVisible);
+ Layer->IsVisible ^= 1;
+ }
+ }
+ if (Commit) {
+ History_Entry_End(Memory);
+ State->UpdateFrame = true;
+ }
+ }
+ if (ImGui::MenuItem("Pre-compose layer")) {
+ Precomp_UICreateButton(File, State, Memory, CompIndex, SortedCompStart, SortedLayerStart);
+ State->UpdateKeyframes = true;
+ }
+ if (ImGui::MenuItem("Duplicate layer")) {
+ Precomp_UIDuplicate(File, State, Memory, CompIndex, SortedCompStart, SortedLayerStart);
+ }
+ if (ImGui::MenuItem("Delete layer")) {
+ Precomp_UICreateButton(File, State, Memory, CompIndex, SortedCompStart, SortedLayerStart);
+ State->UpdateKeyframes = true;
+ }
+ if (ImGui::BeginMenu("Layer color"))
+ {
+ ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0.0f, 0.0f));
+ for (int c = 0; c < AmountOf(UI->LayerColors); c++) {
+ ImGui::PushID(c);
+ ImGui::PushStyleColor(ImGuiCol_Button, UI->LayerColors[c]);
+ real32 Size = ImGui::GetFontSize() * 2;
+ ImVec2 ColSize(Size, Size);
+ if (ImGui::Button("##test", ColSize)) {
+ int h = 0, z = 0, i = 0;
+ while (Block_Loop(Memory, F_Layers, File->Layer_Count, &h, &z, &i)) {
+ block_layer *Layer = (block_layer *)Memory_Block_AddressAtIndex(Memory, F_Layers, i);
+ if (Layer->IsSelected) {
+ Layer->ColIndex = c;
+ }
+ }
+ }
+ if ((c+1) % 4) { ImGui::SameLine(); }
+ ImGui::PopStyleColor();
+ ImGui::PopID();
+ }
+ ImGui::PopStyleVar();
+ ImGui::EndMenu();
+ }
+ uint32 *item_current_idx = (uint32 *)&Layer->BlendMode; // Here we store our selection data as an index.
+ if (ImGui::BeginListBox("Blend mode"))
+ {
+ for (int n = 0; n < IM_ARRAYSIZE(BlendmodeNames); n++)
+ {
+ const bool is_selected = (*item_current_idx == n);
+ if (ImGui::Selectable(BlendmodeNames[n], is_selected)) {
+ *item_current_idx = n;
+ State->UpdateFrame = true;
+ }
+
+ // Set the initial focus when opening the combo (scrolling + keyboard navigation focus)
+ if (is_selected)
+ ImGui::SetItemDefaultFocus();
+ }
+ ImGui::EndListBox();
+ }
+ ImGui::EndPopup();
+ }
+
+ if (ImGui::IsItemActive()) {
+ if (ImGui::IsMouseDragging(ImGuiMouseButton_Left, -1)) {
+ if (State->Interact_Active == interact_type_none) {
+ State->Interact_Active = interact_type_layer_move;
+ // TODO(fox): Selected layers inside precomps will have interactions doubled,
+ // so I'm forcing interaction to only be with members of the same precomp.
+ // Could be made more intuitive later.
+ Layer_RecursiveDeselect(Memory, SortedCompArray, SortedLayerArray, Layer->Block_Composition_Index, File->PrincipalCompIndex);
+ }
+ ImVec2 DragDelta = ImGui::GetMouseDragDelta();
+ DragDelta = DragDelta + (ImVec2(State->Warp_X, State->Warp_Y) * TimelineSize);
+
+ ImVec2 Offset_Old = ImVec2(State->Interact_Offset[0], State->Interact_Offset[1]);
+ ImVec2 Offset_New = (DragDelta / TimelineSizeWithBorder * UI->TimelinePercentZoomed) * ImVec2(Comp->Frame_Count, -LayerIncrement);
+
+ // if (((int32)Offset_Old.x != (int32)Offset_New.x) || ((int32)Offset_Old.y != (int32)Offset_New.y))
+ State->UpdateFrame = true;
+
+ State->Interact_Offset[0] = Offset_New.x;
+ State->Interact_Offset[1] = Offset_New.y;
+ }
+ }
+
+ if (ImGui::IsItemDeactivated()) {
+ if (State->Interact_Active == interact_type_layer_move) {
+ History_Entry_Commit(Memory, "Move layers");
+ for (int i = 0; i < SortedCompStart.LayerCount; i++)
+ {
+ sorted_layer_array SortEntry = SortedLayerStart[i];
+ uint32 Index_Physical = SortEntry.Block_Layer_Index;
+ block_layer *Layer = (block_layer *)Memory_Block_AddressAtIndex(Memory, F_Layers, Index_Physical);
+ // NOTE(fox): Some data on the tree could be saved here.
+ History_Action_Swap(Memory, F_File, sizeof(Layer->Vertical_Offset), &Layer->Vertical_Offset);
+ Layer->Vertical_Offset = SortEntry.SortedOffset;
+ if (Layer->IsSelected) {
+ History_Action_Swap(Memory, F_File, sizeof(Layer->Frame_Start), &Layer->Frame_Start);
+ History_Action_Swap(Memory, F_File, sizeof(Layer->Frame_End), &Layer->Frame_End);
+ Interact_Evaluate_Layer(Memory, State, Index_Physical, SortedCompStart, SortedLayerStart, &Layer->Frame_Start, &Layer->Frame_End);
+ }
+ }
+ State->Interact_Active = interact_type_none;
+ State->Interact_Modifier = 0;
+ History_Entry_End(Memory);
+ }
+ }
+
+ // Keyframe view
+ uint32 Channel = 0;
+
+ sorted_property_array *InfoLocation = SortedPropertyStart + SortedLayerStart->SortedPropertyStart;
+ uint16 *ArrayLocation = SortedKeyframeArray + SortedLayerStart->SortedKeyframeStart;
+ int h = 0, c = 0, p = 0;
+ property_channel *Property = NULL;
+ block_effect *Effect = NULL;
+ while (Layer_LoopChannels(State, Memory, &InfoLocation, &ArrayLocation, Layer, &Property, &Effect, &h, &c, &p))
+ {
+ if (Property->IsToggled) {
+ ImVec2 GraphMinPos = ImVec2(TimelineAbsolutePos.x, Layer_ScreenPos_Min.y + Layer_ScreenSize.y + (Layer_ScreenSize.y * Channel));
+ ImVec2 GraphPos = GraphMinPos + ImVec2(0, Layer_ScreenSize.y);
+ ImVec2 GraphMinBounds = GraphMinPos + ImVec2(0, Layer_ScreenSize.y * 0.5);
+ ImVec2 GraphMaxBounds = GraphMinBounds + ImVec2(TimelineSizeWithBorder.x, Layer_ScreenSize.y);
+ uint32 col = (Channel % 2) ? IM_COL32(50, 50, 50, 255) : IM_COL32(50, 50, 50, 128);
+ draw_list->AddRectFilled(GraphMinBounds, GraphMaxBounds, col);
+ ImGui_Timeline_DrawKeySheet(File, State, Memory, UI, io, draw_list, Property, ArrayLocation,
+ Increment, TimelineAbsolutePos, GraphPos, TimelineMoveSize, TimelineZoomSize,
+ TimelineSize, TimelineSizeWithBorder, LayerIncrement,
+ SortedCompArray, SortedLayerArray, SortedPropertyStart, SortedKeyframeArray);
+ Channel++;
+ }
+ }
+ }
+
+ //
+
+ // Precomp recursion
+
+ if (Layer->IsPrecomp && Layer->Precomp_Toggled) {
+
+ sorted_layer_array *Precomp_SortedLayerStart = Sorted_GetLayerStart(SortedLayerArray, SortedCompArray, Layer->Block_Source_Index);
+ sorted_comp_array Precomp_SortedCompStart = SortedCompArray[Layer->Block_Source_Index];
+
+ block_layer *Layer_Top = (block_layer *)Memory_Block_AddressAtIndex(Memory, F_Layers, Precomp_SortedLayerStart[Precomp_SortedCompStart.LayerCount - 1].Block_Layer_Index);
+ block_layer *Layer_Bottom = (block_layer *)Memory_Block_AddressAtIndex(Memory, F_Layers, Precomp_SortedLayerStart[0].Block_Layer_Index);
+ real32 SmallestY = Layer_Top->Vertical_Offset;
+ real32 LargestY = Layer_Bottom->Vertical_Offset;
+ // Layer_Evaluate_Display(Layer_Top, SortedLayerArray, SortedCompArray, Precomp_SortedLayerStart, Precomp_SortedCompStart.LayerCount - 1, &SmallestY);
+ // Layer_Evaluate_Display(Layer_Bottom, SortedLayerArray, SortedCompArray, Precomp_SortedLayerStart, 0, &LargestY);
+ real32 PrecompHeight = LargestY - SmallestY + 2;
+
+ ImVec2 MinClipPos = ImVec2(Layer_ScreenPos_Min.x, Layer_ScreenPos_Max.y);
+ ImVec2 MaxClipPos = ImVec2(Layer_ScreenPos_Max.x, MinClipPos.y + (PrecompHeight * Increment.y * TimelineZoomSize.y));
+ draw_list->AddRectFilled(MinClipPos, MaxClipPos, ImColor(0.2f, 0.2f, 0.2f, 1.0f));
+
+
+ ImVec2 Layer_LocalPos_Screen = Layer_LocalPos_Ratio * TimelineZoomSize;
+ ImVec2 NestedTimelineAbsolutePos = TimelineAbsolutePos + Layer_LocalPos_Screen - ImVec2(0, SmallestY * Increment.y * TimelineZoomSize.y) + ImVec2(0, Layer_ScreenSize.y * 1.5);
+
+ ImGui::PushClipRect(MinClipPos, MaxClipPos, true);
+ draw_list->PushClipRect(MinClipPos, MaxClipPos, true);
+ uint32 RecursionsCount = Recursions+1;
+ RecursionIdx[RecursionsCount] = Index_Physical;
+ ImGui_Timeline_DrawPrecomp(File, State, Memory, UI, io, draw_list, RecursionIdx, RecursionsCount,
+ Increment, NestedTimelineAbsolutePos, TimelineMoveSize, TimelineZoomSize,
+ TimelineSize, TimelineSizeWithBorder, LayerIncrement,
+ SortedCompArray, SortedLayerArray, SortedPropertyStart, SortedKeyframeArray);
+ draw_list->PopClipRect();
+ ImGui::PopClipRect();
+
+ }
+
+ //
+
+ ImGui::PopID();
+ }
+
+ ImGui::PopID();
+}
+
+static void
+ImGui_Timeline(project_data *File, project_state *State, memory *Memory, ui *UI, ImGuiIO io,
+ sorted_comp_array *SortedCompArray, sorted_layer_array *SortedLayerArray, sorted_property_array *SortedPropertyStart, uint16 *SortedKeyframeArray)
+{
+ ImVec2 FramePadding = ImGui::GetStyle().FramePadding;
+ ImVec2 ItemSpacing = ImGui::GetStyle().ItemSpacing;
+ ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0));
+ ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0)); // makes setting up the layout easier
+ ImGui::Begin("Timeline", NULL);
+
+ if (ImGui::IsWindowHovered(ImGuiFocusedFlags_ChildWindows)) {
+ State->FocusedWindow = focus_timeline;
+ }
+
+ // if (State->TimelineMode == timeline_mode_graph)
+ // ImGui_GraphInfo(File, State, Memory, UI, io, SortedPropertyStart, SortedKeyframeArray);
+
+ real32 FontHeight = ImGui::GetFontSize();
+
+ ImVec2 WindowSize = ImGui::GetWindowSize();
+
+ if (WindowSize.x < 50 || WindowSize.y < 50) {
+ ImGui::PopStyleVar(2);
+ ImGui::End();
+ return;
+ }
+
+ block_composition *MainComp = (block_composition *)Memory_Block_AddressAtIndex(Memory, F_Precomps, File->PrincipalCompIndex);
+
+ ImVec2 WindowMinAbs = ImGui::GetWindowPos();
+ ImVec2 WindowMaxAbs = WindowMinAbs + WindowSize;
+
+ ImVec2 ButtonSize = ImVec2(FontHeight*2, FontHeight*2);
+
+ ImVec2 TimelineBorderPadding = ImVec2(FontHeight, FontHeight);
+
+ ImVec2 TimelineSize = ImVec2(WindowSize.x, WindowSize.y);
+ ImVec2 TimelineSizeWithBorder = TimelineSize - TimelineBorderPadding*2;
+ ImVec2 TimelineAbsolutePos = WindowMinAbs + TimelineBorderPadding;
+
+ ImVec2 KeyframeSize = ImVec2(FontHeight, FontHeight);
+
+ int32 FrameCount = MainComp->Frame_Count;
+
+ ImDrawList* draw_list = ImGui::GetWindowDrawList();
+ draw_list->AddRectFilled(WindowMinAbs, WindowMaxAbs,
+ IM_COL32(255, 255, 255, 50));
+
+
+ ImGui::BeginChild("Timeline", TimelineSize, true, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoScrollbar);
+
+ ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0.0f, ItemSpacing.y));
+
+ ImGui::PushClipRect(TimelineAbsolutePos, TimelineAbsolutePos + TimelineSizeWithBorder, true);
+ draw_list->PushClipRect(TimelineAbsolutePos, TimelineAbsolutePos + TimelineSizeWithBorder, true);
+
+ real32 LayerIncrement = 40;
+
+ ImVec2 Val_Min(0, 0);
+ ImVec2 Val_Max(40, LayerIncrement);
+
+ // DebugWatchVar("Selection", &State->RecentSelectionType, d_int);
+
+ // ImVec2 *ActivePercentZoomed = (UI->TimelineMode != timeline_mode_graph) ? &UI->TimelinePercentZoomed : &UI->GraphPercentZoomed;
+ // ImVec2 *ActivePercentOffset = (UI->TimelineMode != timeline_mode_graph) ? &UI->TimelinePercentOffset : &UI->GraphPercentOffset;
+ ImVec2 *ActivePercentZoomed = &UI->TimelinePercentZoomed;
+ ImVec2 *ActivePercentOffset = &UI->TimelinePercentOffset;
+
+ if (ActivePercentZoomed->x == 0) {
+ *ActivePercentZoomed = ImVec2(1, 1);
+ }
+
+ ImVec2 ActiveZoomSize = TimelineSizeWithBorder / *ActivePercentZoomed;
+ ImVec2 ActiveMoveSize = TimelineSizeWithBorder * *ActivePercentOffset / *ActivePercentZoomed;
+
+ ImVec2 TimelineZoomSize = TimelineSizeWithBorder / UI->TimelinePercentZoomed;
+ ImVec2 TimelineMoveSize = TimelineSizeWithBorder * UI->TimelinePercentOffset / UI->TimelinePercentZoomed;
+
+ // DebugWatchVar("TimelineY: ", &TimelineMoveSize.y, d_float);
+
+ ImVec2 Increment = ImVec2((real32)1 / MainComp->Frame_Count, (real32)1 / LayerIncrement);
+
+ ImGui_Timeline_BGElements(File, State, Memory, UI, draw_list, TimelineSizeWithBorder, TimelineAbsolutePos, *MainComp, TimelineZoomSize, TimelineMoveSize);
+
+ int16 RecursionIdx[MAX_PRECOMP_RECURSIONS] = {};
+ int32 Recursions = 0;
+ RecursionIdx[0] = -1;
+ ImGui_Timeline_DrawPrecomp(File, State, Memory, UI, io, draw_list, RecursionIdx, Recursions,
+ Increment, TimelineAbsolutePos, TimelineMoveSize, TimelineZoomSize,
+ TimelineSize, TimelineSizeWithBorder, LayerIncrement,
+ SortedCompArray, SortedLayerArray, SortedPropertyStart, SortedKeyframeArray);
+
+ if (State->TimelineMode == timeline_mode_graph) {
+
+ if (UI->GraphMoveSize.y == 0) {
+ UI->GraphZoomSize = ImVec2(1, UI->TimelinePercentZoomed.y );
+ UI->GraphMoveSize = ImVec2(0, -UI->TimelinePercentOffset.y );
+ }
+
+ ImVec2 ZoomDifference = (UI->TimelinePercentZoomed / UI->GraphZoomSize);
+ ImVec2 MoveDifference = (UI->TimelinePercentOffset + (UI->GraphMoveSize));
+ // DebugWatchVar("zoomdif: ", &ZoomDifference.y, d_float);
+ // DebugWatchVar("movedif: ", &MoveDifference.y, d_float);
+ ImVec2 GraphZoomSize = TimelineSizeWithBorder / ZoomDifference;
+ ImVec2 GraphMoveSize = TimelineSizeWithBorder * (MoveDifference) / UI->TimelinePercentZoomed;
+ // DebugWatchVar("zoomsize: ", &GraphZoomSize.y, d_float);
+ // DebugWatchVar("movesize: ", &GraphMoveSize.y, d_float);
+
+ draw_list->AddRectFilled(WindowMinAbs, WindowMaxAbs,
+ IM_COL32(50, 50, 50, 230));
+ ImGui_Timeline_DrawGraph(File, State, Memory, UI, io, draw_list,
+ Increment, TimelineAbsolutePos, GraphMoveSize, GraphZoomSize,
+ TimelineSize, TimelineSizeWithBorder, LayerIncrement,
+ SortedCompArray, SortedLayerArray,
+ SortedPropertyStart, SortedKeyframeArray);
+ }
+
+ ImGui_Timeline_HorizontalIncrementDraw(State, UI, draw_list, TimelineSizeWithBorder, TimelineAbsolutePos, *MainComp, TimelineZoomSize, TimelineMoveSize);
+
+
+ ImVec2 MouseDelta = io.MouseDelta / TimelineSize;
+
+ real32 BarHandleSize = FontHeight;
+ real32 BarThickness = 50;
+ real32 BarMinZoom = 0.01;
+
+ real32 BarH_Pos = -TimelineSizeWithBorder.x * ActivePercentOffset->x;
+ real32 BarH_Size = TimelineSizeWithBorder.x / (1 / ActivePercentZoomed->x);
+
+ // I use "UI" to denote the size/position after clipping the bar so that it
+ // doesn't go out of bounds and the handles are always selectable at the edges.
+
+ real32 BarH_Offset = Max(BarH_Pos, 0);
+
+ real32 BarH_SizeUI = (BarH_Size + BarH_Pos > TimelineSizeWithBorder.x) ?
+ TimelineSizeWithBorder.x - BarH_Pos :
+ BarH_Size + (BarH_Pos - BarH_Offset);
+
+ if (BarH_Offset == 0 && BarH_SizeUI > TimelineSizeWithBorder.x)
+ BarH_SizeUI = TimelineSizeWithBorder.x;
+
+ BarH_SizeUI = BarH_SizeUI - BarHandleSize*2;
+
+ BarH_SizeUI = Max(BarH_SizeUI, FontHeight*4);
+
+ BarH_Offset = Min(BarH_Offset, TimelineSize.x - BarH_SizeUI - BarHandleSize*4);
+ ImVec2 BarH_PosUI = TimelineAbsolutePos + ImVec2(BarH_Offset, TimelineSize.y - BarThickness);
+ bool32 BarHeld = false;
+
+ ImGui::SetCursorScreenPos(BarH_PosUI);
+ ImGui::Button("##scrollbarleft", ImVec2(BarHandleSize, BarThickness));
+
+ if ((ImGui::IsItemActive() && ImGui::IsMouseDragging(ImGuiMouseButton_Left, -1)))
+ {
+ if ((ActivePercentZoomed->x - MouseDelta.x) > BarMinZoom) {
+ ActivePercentZoomed->x -= MouseDelta.x;
+ ActivePercentOffset->x -= MouseDelta.x;
+ }
+ BarHeld = true;
+ }
+
+ ImGui::SetCursorScreenPos(BarH_PosUI + ImVec2(BarHandleSize, 0));
+ ImGui::Button("##scrollbarhori", ImVec2(BarH_SizeUI, BarThickness));
+
+ if (ImGui::IsItemActive() && ImGui::IsMouseDragging(ImGuiMouseButton_Left, -1))
+ {
+ ActivePercentOffset->x -= MouseDelta.x;
+ BarHeld = true;
+ }
+
+ ImGui::SetCursorScreenPos(BarH_PosUI + ImVec2(BarHandleSize, 0) + ImVec2(BarH_SizeUI, 0));
+ ImGui::Button("##scrollbarright", ImVec2(BarHandleSize, BarThickness));
+
+ if ((ImGui::IsItemActive() && ImGui::IsMouseDragging(ImGuiMouseButton_Left, -1)))
+ {
+ if ((ActivePercentZoomed->x + MouseDelta.x) > BarMinZoom) {
+ ActivePercentZoomed->x += MouseDelta.x;
+ }
+ BarHeld = true;
+ }
+
+ if (BarHeld) {
+ ImGui_WarpMouse(State, io.MousePos, TimelineAbsolutePos, TimelineAbsolutePos + TimelineSizeWithBorder, 1);
+ }
+
+ Assert(ActivePercentZoomed->x > BarMinZoom);
+
+ real32 BarV_MaxSize = TimelineSizeWithBorder.y - BarThickness/2;
+ real32 BarV_Pos = -BarV_MaxSize * ActivePercentOffset->y;
+ real32 BarV_Size = BarV_MaxSize / (1 / ActivePercentZoomed->y);
+ BarV_Size = Max(BarV_Size, FontHeight*4);
+
+ real32 BarV_Offset = Max(BarV_Pos, 0);
+
+ real32 BarV_SizeUI = (BarV_Size + BarV_Pos > BarV_MaxSize) ?
+ BarV_MaxSize - BarV_Pos :
+ BarV_Size + (BarV_Pos - BarV_Offset);
+
+ if (BarV_Offset == 0 && BarV_SizeUI > BarV_MaxSize)
+ BarV_SizeUI = BarV_MaxSize;
+
+ BarV_SizeUI = BarV_SizeUI - BarHandleSize*2;
+
+ BarV_SizeUI = Max(BarV_SizeUI, FontHeight*4);
+
+ BarV_Offset = Min(BarV_Offset, BarV_MaxSize - BarV_SizeUI - BarHandleSize*4);
+ ImVec2 BarV_PosUI = TimelineAbsolutePos + ImVec2(TimelineSize.x - BarThickness, BarV_Offset);
+ BarHeld = false;
+
+ ImGui::SetCursorScreenPos(BarV_PosUI);
+ ImGui::Button("##h-scrollbarleft", ImVec2(BarThickness, BarHandleSize));
+
+ if ((ImGui::IsItemActive() && ImGui::IsMouseDragging(ImGuiMouseButton_Left, -1)))
+ {
+ ActivePercentZoomed->y -= MouseDelta.y;
+ ActivePercentOffset->y -= MouseDelta.y;
+ BarHeld = true;
+ }
+
+ ImGui::SetCursorScreenPos(BarV_PosUI + ImVec2(0, BarHandleSize));
+ ImGui::Button("##h-scrollbar", ImVec2(BarThickness, BarV_SizeUI));
+
+ if (ImGui::IsItemHovered() && io.MouseWheel)
+ {
+ ActivePercentOffset->y -= io.MouseWheel/10;
+ }
+
+ if (ImGui::IsItemActive() && ImGui::IsMouseDragging(ImGuiMouseButton_Left, -1))
+ {
+ ActivePercentOffset->y -= MouseDelta.y;
+ BarHeld = true;
+ }
+
+ ImGui::SetCursorScreenPos(BarV_PosUI + ImVec2(0, BarHandleSize) + ImVec2(0, BarV_SizeUI));
+ ImGui::Button("##h-scrollbarright", ImVec2(BarThickness, BarHandleSize));
+
+ if ((ImGui::IsItemActive() && ImGui::IsMouseDragging(ImGuiMouseButton_Left, -1)))
+ {
+ ActivePercentZoomed->y += MouseDelta.y;
+ BarHeld = true;
+ }
+
+ ActivePercentZoomed->y = Max(ActivePercentZoomed->y, 0.01);
+
+ if (BarHeld) {
+ ImGui_WarpMouse(State, io.MousePos, TimelineAbsolutePos, TimelineAbsolutePos + TimelineSizeWithBorder, 2);
+ }
+
+ ImGui::SetCursorScreenPos(TimelineAbsolutePos);
+ ImGui::InvisibleButton("TimelineMoving", TimelineSizeWithBorder, ImGuiButtonFlags_MouseButtonLeft | ImGuiButtonFlags_MouseButtonRight);
+ // ImGui::Button("TimelineMoving", TimelineSizeWithBorder);
+ bool32 IsHovered = ImGui::IsItemHovered();
+ bool32 IsItemActive = ImGui::IsItemActive();
+ bool32 IsItemActivated = ImGui::IsItemActivated();
+ bool32 IsItemDeactivated = ImGui::IsItemDeactivated();
+ bool32 LeftClick = ImGui::IsMouseDown(ImGuiMouseButton_Left);
+ bool32 RightClick = ImGui::IsMouseDown(ImGuiMouseButton_Right);
+
+ if (IsHovered && io.MouseWheel) {
+ real32 Increment = 0.1;
+ bool32 Direction = (io.MouseWheel > 0) ? 1 : -1;
+ ImVec2 Offset = (io.MousePos - (TimelineAbsolutePos + ActiveMoveSize)) / ActiveZoomSize;
+ if (io.KeyShift) {
+ ActivePercentOffset->y += Increment*Direction;
+ } else if (io.KeyCtrl) {
+ ActivePercentOffset->x += Increment*Direction*0.3;
+ } else {
+ if (Direction == 1) {
+ *ActivePercentZoomed = *ActivePercentZoomed - (*ActivePercentZoomed * Increment);
+ *ActivePercentOffset = *ActivePercentOffset - ((*ActivePercentOffset * Increment) + Offset*Increment);
+ } else {
+ *ActivePercentOffset = ((*ActivePercentOffset + Offset*Increment) / (1.0f - Increment));
+ *ActivePercentZoomed = (*ActivePercentZoomed / (1.0f - Increment));
+ }
+ }
+ }
+
+ if (LeftClick) {
+ if (IsItemActivated) {
+ if (!io.KeyShift && State->TimelineMode == timeline_mode_default) Layer_DeselectAll(File, State, Memory);
+ if (State->Interact_Active == interact_type_keyframe_move ||
+ State->Interact_Active == interact_type_keyframe_rotate ||
+ State->Interact_Active == interact_type_keyframe_scale) {
+ Keyframe_Commit(File, State, Memory, SortedCompArray, SortedLayerArray, SortedPropertyStart, SortedKeyframeArray);
+ }
+ State->BoxSelect = true;
+ }
+ if (IsItemActive) {
+ Assert(State->BoxSelect);
+ draw_list->AddRectFilled(io.MouseClickedPos[0], io.MousePos,
+ IM_COL32(0, 0, 200, 50));
+ }
+ } else {
+ State->Warp_X = 0;
+ State->Warp_Y = 0;
+ }
+
+ if (IsItemDeactivated) {
+ State->BoxSelect = false;
+ }
+
+ draw_list->PopClipRect();
+ ImGui::PopClipRect();
+
+ ImGui::PopStyleVar();
+
+
+ ImGui::EndChild();
+
+ ImGui::PopStyleVar(2);
+
+ ImGui::End();
+}