diff options
Diffstat (limited to 'src/imgui_ui_timeline.cpp')
-rw-r--r-- | src/imgui_ui_timeline.cpp | 1070 |
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(); +} |