#include "imgui.h" #if SPECIAL #include "main.h" #endif 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); #if 0 // 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++; } #endif } 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->UpdateKeyframes = true; State->UpdateFrame = true; } } if (IsItemDeactivated) { State->Interact_Active = interact_type_none; State->Interact_Modifier = 0; State->UpdateKeyframes = true; State->UpdateFrame = true; } } // 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 & 0x01)) 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->Block_Bezier_Index, ArrayLocation, InfoLocation->KeyframeCount))) { File_DeselectAllKeyframes(File, State, Memory, SortedCompArray, SortedLayerArray, SortedPropertyStart, SortedKeyframeArray); for (int p = 0; p < InfoLocation->KeyframeCount; p++) { int k = ArrayLocation[p]; bezier_point *Point = Bezier_LookupAddress(Memory, Property->Block_Bezier_Index, 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, sorted_property_array *InfoLocation, uint16 *ArrayLocation, ImVec2 Increment, ImVec2 TimelineAbsolutePos, ImVec2 GraphPos, int Frame_Offset, 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 < InfoLocation->KeyframeCount; p++) { int k = ArrayLocation[p]; bezier_point *PointAddress = Bezier_LookupAddress(Memory, Property->Block_Bezier_Index, k); v2 PointPos[3]; Bezier_Interact_Evaluate(State, PointAddress, PointPos); real32 LocalPos_Ratio_X = (PointPos[0].x + Frame_Offset) * 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, SortedCompArray, 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 & 0x01)) continue; int32 Frame_Offset = Layer->Frame_Offset; 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 < InfoLocation->KeyframeCount); Assert(InfoLocation->MaxYIndex < InfoLocation->KeyframeCount); 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->Block_Bezier_Index, ArrayLocation, InfoLocation->KeyframeCount); 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 < InfoLocation->KeyframeCount; p++) { int k = ArrayLocation[p]; if (InfoLocation->KeyframeCount == 1) int b = 0; int Idx = (p % 2); int NewIdx = Idx * 3; int OldIdx = (NewIdx == 3) ? 0 : 3; PointAddress[Idx] = Bezier_LookupAddress(Memory, Property->Block_Bezier_Index, k); v2 PointPos[3]; Bezier_Interact_Evaluate(State, PointAddress[Idx], PointPos, GraphZoomHeight, Y_Increment); PointPos[0].x += Frame_Offset; ImVec2 Keyframe_LocalPos[3] = { IV2(PointPos[0]), IV2(PointPos[0] + PointPos[1]), IV2(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 could be arbitrary. real32 DisplayOffset = 0; int LayerCount = SortedCompStart.LayerCount + SortedCompStart.FakeLayerCount; for (int i = 0; i < LayerCount; i++) { if (i != 0) int a = 0; 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; int32 Frame_Offset = Layer->Frame_Offset; real32 Vertical_Offset = SortEntry.SortedOffset + SortEntry.DisplayOffset; if (Layer->IsSelected & 0x01) Interact_Evaluate_Layer(Memory, State, Index_Physical, SortedCompStart, SortedLayerStart, &Frame_Start, &Frame_End, &Frame_Offset); ImVec2 Layer_LocalPos = ImVec2(Frame_Start + Frame_Offset, 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); if (SortEntry.IsFake) draw_list->AddRectFilled(Layer_ScreenPos_Min, Layer_ScreenPos_Max, IM_COL32(129, 50, 180, 90)); 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 if (SortEntry.IsFake) sprintf(buf, "%s (duplicate), (idx: %i so: %.1f di: %.1f", String->Char, Index_Physical, SortEntry.SortedOffset, SortEntry. DisplayOffset); else sprintf(buf, "%s, (idx: %i so: %.1f di: %.1f", String->Char, Index_Physical, SortEntry.SortedOffset, SortEntry. DisplayOffset); #else if (SortEntry.IsFake) sprintf(buf, "%s (duplicate)", String->Char); else sprintf(buf, "%s", String->Char); #endif if (UI->TimelinePercentZoomed.y <= 1.0f) { real32 TextX = (Layer_ScreenPos_Min.x > TimelineAbsolutePos.x) ? Layer_ScreenPos_Min.x : TimelineAbsolutePos.x; draw_list->AddText(ImVec2(TextX, Layer_ScreenPos_Min.y), 0xFFFFFFFF, buf); } if (Layer->IsSelected & 0x01) { 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 & 0x02) { // 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 & 0x01) && !(Layer->IsSelected & 0x02) && (!Layer->IsLocked || io.KeyCtrl)) { Layer_Select(Memory, State, Index_Physical); } } else if (!io.KeyShift) { Layer->IsSelected = 0x00; } } 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()) { if (Layer->IsLocked) { ImGui::SetMouseCursor(ImGuiMouseCursor_NotAllowed); } else { ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeEW); } } if (ImGui::IsItemActivated()) { if (!(Layer->IsSelected & 0x01) && !Layer->IsLocked) { 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 & 0x01); 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 & 0x01) { // NOTE(fox): Some data on the tree could be saved here. History_Action_Swap(Memory, F_Layers, sizeof(Layer->Frame_Start), &Layer->Frame_Start); History_Action_Swap(Memory, F_Layers, sizeof(Layer->Frame_End), &Layer->Frame_End); Interact_Evaluate_Layer(Memory, State, Index_Physical, SortedCompStart, SortedLayerStart, &Layer->Frame_Start, &Layer->Frame_End, &Layer->Frame_Offset); } } 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::IsItemHovered()) { if (Layer->IsLocked) { ImGui::SetMouseCursor(ImGuiMouseCursor_NotAllowed); } } if (ImGui::IsItemActivated()) { if (!(Layer->IsSelected & 0x01) && !Layer->IsLocked) { 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::MenuItem("Visible", NULL, Layer->IsVisible)) { bool32 Commit = false; int h = 0, z = 0, i = 0; bool32 Val = Layer->IsVisible ^ 1; while (Block_Loop(Memory, F_Layers, File->Layer_Count, &h, &z, &i)) { block_layer *CurLayer = (block_layer *)Memory_Block_AddressAtIndex(Memory, F_Layers, i); if (CurLayer->IsVisible != Val && CurLayer->IsSelected & 0x01) { if (!Commit) { History_Entry_Commit(Memory, "Toggle visibility"); Commit = true; } History_Action_Swap(Memory, F_Layers, sizeof(CurLayer->IsVisible), &CurLayer->IsVisible); CurLayer->IsVisible = Val; } } if (Commit) { History_Entry_End(Memory); State->UpdateFrame = true; } } if (ImGui::MenuItem("Lock", NULL, Layer->IsLocked)) { bool32 Commit = false; int h = 0, z = 0, i = 0; bool32 Val = Layer->IsLocked ^ 1; while (Block_Loop(Memory, F_Layers, File->Layer_Count, &h, &z, &i)) { block_layer *CurLayer = (block_layer *)Memory_Block_AddressAtIndex(Memory, F_Layers, i); if (CurLayer->IsLocked != Val && ((CurLayer->IsSelected & 0x01) || (Layer == CurLayer))) { if (!Commit) { History_Entry_Commit(Memory, "Toggle lock"); Commit = true; } History_Action_Swap(Memory, F_Layers, sizeof(CurLayer->IsLocked), &CurLayer->IsLocked); CurLayer->IsLocked = Val; } } 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")) { State->HotkeyInput = hotkey_duplicatelayer; } ImGui::Button("Layer Offset"); if (ImGui::IsItemActive()) { Layer->Frame_Offset -= 120; Layer->Frame_Start += 120; Layer->Frame_End += 120; State->UpdateFrame = true; } if (ImGui::MenuItem("Delete layer")) { State->HotkeyInput = hotkey_deletelayer; 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; bool32 Commit = false; 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 & 0x01) { if (!Commit) { History_Entry_Commit(Memory, "Change layer colors"); Commit = true; } History_Action_Swap(Memory, F_Layers, sizeof(Layer->ColIndex), &Layer->ColIndex); Layer->ColIndex = c; } } if (Commit) { History_Entry_End(Memory); } } 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)) { int h = 0, z = 0, i = 0; bool32 Commit = false; 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 & 0x01) { if (!Commit) { History_Entry_Commit(Memory, "Change layer blend mode"); Commit = true; } History_Action_Swap(Memory, F_Layers, sizeof(Layer->BlendMode), &Layer->BlendMode); Layer->BlendMode = (blend_mode)n; } } if (Commit) { History_Entry_End(Memory); } 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 (ImGui::IsKeyPressed(ImGuiKey_Escape)) { State->Interact_Active = interact_type_none; State->Interact_Modifier = 0; State->UpdateFrame = true; Memory->PurgeCache = true; } else 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_Layers, sizeof(Layer->Vertical_Offset), &Layer->Vertical_Offset); Layer->Vertical_Offset = SortEntry.SortedOffset; if (Layer->IsSelected & 0x01) { History_Action_Swap(Memory, F_Layers, sizeof(Layer->Frame_Offset), &Layer->Frame_Offset); Interact_Evaluate_Layer(Memory, State, Index_Physical, SortedCompStart, SortedLayerStart, &Layer->Frame_Start, &Layer->Frame_End, &Layer->Frame_Offset); } } 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 / 2); ImVec2 GraphMinBounds = GraphMinPos; ImVec2 GraphMaxBounds = GraphMinBounds + ImVec2(TimelineSizeWithBorder.x, Layer_ScreenSize.y); uint32 col = (Channel % 2) ? IM_COL32(50, 50, 50, 255) : IM_COL32(70, 70, 70, 255); draw_list->AddRectFilled(GraphMinBounds, GraphMaxBounds, col); ImGui_Timeline_DrawKeySheet(File, State, Memory, UI, io, draw_list, Property, InfoLocation, ArrayLocation, Increment, TimelineAbsolutePos, GraphPos, Frame_Offset, 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); sorted_layer_array TopLayerEntry = Precomp_SortedLayerStart[Precomp_SortedCompStart.LayerCount - 1]; real32 SmallestY = TopLayerEntry.SortedOffset + TopLayerEntry.DisplayOffset; real32 PrecompHeight = Precomp_SortedCompStart.DisplaySize; ImVec2 MinClipPos = ImVec2(Layer_ScreenPos_Min.x, Layer_ScreenPos_Max.y + (Channel * Increment.y * TimelineZoomSize.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 - Channel) * 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_Bar(project_data *File, project_state *State, memory *Memory, ui *UI, ImGuiIO io, real32 FontHeight, ImVec2 MouseDelta, ImVec2 TimelineSizeWithBorder, ImVec2 *ActivePercentOffset, ImVec2 *ActivePercentZoomed, ImVec2 TimelineSize, ImVec2 TimelineAbsolutePos) { 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); } } 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); ImVec2 MouseDelta = io.MouseDelta / TimelineSize; ImGui_Timeline_Bar(File, State, Memory, UI, io, FontHeight, MouseDelta, TimelineSizeWithBorder, ActivePercentOffset, ActivePercentZoomed, TimelineSize, TimelineAbsolutePos); 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); 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(); }