#include "my_imgui_internal_widgets.h" #include "imgui_ops.h" #include "imgui_helper_widgets.cpp" static void ImGui_DebugMemoryViewer(project_state *State) { ImGui::Begin("Memory viewer"); cache_entry *EntryArray = State->Render.Entry; char *Type[4] = { "unassigned", "comp", "source", "layer" }; int c = 0; while (EntryArray[c].CycleTime != 0) { ImGui::Text("Type - %s, Start - %i, Info - %i", Type[EntryArray[c].Type], EntryArray[c].Block_StartIndex, EntryArray[c].TypeInfo); c++; } ImGui::End(); } static void ImGui_File(project_data *File, project_state *State, memory *Memory, ImGuiIO io) { ImGui::Begin("Files"); ImGui::Text("Application average %.3f ms/frame (%.1f FPS)", 1000.0f / io.Framerate, io.Framerate); for (int c = 0; c < File->Comp_Count; c++) { block_composition *Comp = (block_composition *)Memory_Block_AddressAtIndex(Memory, F_Precomps, c); block_string *String = (block_string *)Memory_Block_AddressAtIndex(Memory, F_Strings, Comp->Name_String_Index); ImGui::Text(String->Char); } for (int c = 0; c < File->Source_Count; c++) { block_source *Source = (block_source *)Memory_Block_AddressAtIndex(Memory, F_Sources, c); block_string *String = (block_string *)Memory_Block_AddressAtIndex(Memory, F_Strings, Source->Path_String_Index); ImGui::Text(String->Char); } #if DEBUG for (int i = 0; i < Debug.Temp.WatchedProperties; i++) { if (Debug.Temp.DebugPropertyType[i] == d_float) { ImGui::Text("%s: %f", Debug.Temp.String[i], Debug.Temp.Val[i].f); } else if (Debug.Temp.DebugPropertyType[i] == d_int) { ImGui::Text("%s: %i", Debug.Temp.String[i], Debug.Temp.Val[i].i); } else if (Debug.Temp.DebugPropertyType[i] == d_uint) { ImGui::Text("%s: %u", Debug.Temp.String[i], Debug.Temp.Val[i].u); } } #endif ImGui::End(); } static void ImGui_Viewport(project_data *File, project_state *State, ui *UI, memory *Memory, ImGuiIO io, GLuint textureID) { bool open = true; ImGui::Begin("Viewport", &open, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse); if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) UI->FocusedWindow = focus_viewport; block_composition *MainComp = (block_composition *)Memory_Block_AddressAtIndex(Memory, F_Precomps, File->PrincipalCompIndex); block_layer *Layer = (block_layer *)Memory_Block_AddressAtIndex(Memory, F_Layers, 0); ImVec2 ViewportMin = ImGui::GetCursorScreenPos(); ImVec2 ViewportScale = ImGui::GetContentRegionAvail(); ImVec2 ViewportMax = ImVec2(ViewportMin.x + ViewportScale.x, ViewportMin.y + ViewportScale.y); if (State->Initializing) { UI->CompZoom = ImVec2(MainComp->Width, MainComp->Height); UI->CompPos = ImVec2(ViewportMin.x + ((ViewportMax.x - ViewportMin.x)/2 - UI->CompZoom.x/2), ViewportMin.y + ((ViewportMax.y - ViewportMin.y)/2 - UI->CompZoom.y/2)); } ImVec2 CompPosMin = ImVec2(UI->CompPos.x, UI->CompPos.y); ImVec2 CompPosMax = ImVec2(UI->CompPos.x + UI->CompZoom.x, UI->CompPos.y + UI->CompZoom.y); ImDrawList* draw_list = ImGui::GetWindowDrawList(); draw_list->AddRectFilled(ViewportMin, ViewportMax, IM_COL32(50, 50, 50, 255)); draw_list->AddRect(ViewportMin, ViewportMax, IM_COL32(255, 255, 255, 255)); draw_list->AddRect(CompPosMin, CompPosMax, IM_COL32(255, 255, 255, 55)); // Actual composition texture draw_list->PushClipRect(ViewportMin, ViewportMax, true); draw_list->AddImage((void *)(intptr_t)textureID, CompPosMin, CompPosMax); draw_list->PopClipRect(); // Interactions for dragging and zooming ImGui::SetCursorScreenPos(ViewportMin); ImGui::InvisibleButton("canvas", ViewportScale, ImGuiButtonFlags_MouseButtonLeft | ImGuiButtonFlags_MouseButtonRight); bool32 IsHovered = ImGui::IsItemHovered(); bool32 IsActive = ImGui::IsItemActive(); bool32 IsActivated = ImGui::IsItemActivated(); bool32 IsDeactivated = ImGui::IsItemDeactivated(); if (IsHovered && IsActivated && ImGui::IsMouseDown(ImGuiMouseButton_Left)) { // Point to zoom in on if Z is held UI->TempZoomRatio = ImGui_ScreenPointToCompUV(ViewportMin, UI->CompPos, UI->CompZoom, io.MousePos); // Layer selection /* if (!ImGui::IsKeyDown(ImGuiKey_Z) || !State->Pen.IsActive) { for (int i = File.NumberOfLayers - 1; i >= 0; i--) { project_layer *Layer = File.Layer[i]; if (!io.KeyShift) DeselectAllLayers(&File, State); v2 LayerUV = CompUVToLayerUV(Layer, &CompBuffer, UI->TempZoomRatio); if (TestUV(LayerUV) && !Layer->IsSelected) { SelectLayer(Layer, State, i); break; } } } */ } if (IsActive && ImGui::IsMouseDragging(ImGuiMouseButton_Right, -1.0f)) { UI->CompPos.x += io.MouseDelta.x; UI->CompPos.y += io.MouseDelta.y; } if (IsActive && ImGui::IsMouseDragging(ImGuiMouseButton_Left, -1.0f) && ImGui::IsKeyDown(ImGuiKey_Z)) { real32 Distance = io.MouseDelta.x + io.MouseDelta.y; UI->CompZoom.x += (Distance)*(real32)MainComp->Width/MainComp->Height; UI->CompZoom.y += (Distance); UI->CompPos.x -= ((Distance)*(real32)MainComp->Width/MainComp->Height)*UI->TempZoomRatio.x; UI->CompPos.y -= Distance*UI->TempZoomRatio.y; } ImGui::SetCursorScreenPos(ImVec2(ViewportMin.x, ViewportMin.y + ViewportScale.y - ImGui::GetFontSize()*1.5)); ImGui::Text("%.1f", 100.0f * (UI->CompZoom.x / MainComp->Width)); if (State->MsgTime > 0) { ImGui::SameLine(); ImGui::SetCursorPosX((ViewportScale.x / 5)*4); ImGui::Text(State->Msg); State->MsgTime--; } ImGui::End(); /* for (int i = 0; i < AmountOf(Layer->Property); i++) { ImGui::PushID(i); property_channel *Property = &Layer->Property[i]; ImGui::DragScalar(Property->Name, ImGuiDataType_Float, &Property->CurrentValue, Property->ScrubVal, &Property->MinVal, &Property->MaxVal, "%f"); if (ImGui::IsItemActive()) State->UpdateFrame = true; ImGui::PopID(); } */ } static void ImGui_TimelineHorizontalIncrementDraw(ui *UI, ImDrawList *draw_list, ImVec2 TimelineSizeWithBorder, ImVec2 TimelineAbsolutePos, block_composition MainComp) { uint32 LineColor = IM_COL32(200, 200, 200, 40); real32 TimelineZoomSize = TimelineSizeWithBorder.x / UI->TimelinePercentZoomed.x ; real32 TimelineMoveSize = TimelineSizeWithBorder.x * UI->TimelinePercentOffset.x / UI->TimelinePercentZoomed.x; Assert(TimelineZoomSize > 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*TimelineZoomSize, 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; } } } static void ImGui_Timeline_DrawPrecomp(project_data *File, project_state *State, memory *Memory, ui *UI, ImGuiIO io, ImDrawList *draw_list, uint16 CompIndex, ImVec2 Increment, ImVec2 TimelineAbsolutePos, ImVec2 TimelineMoveSize, ImVec2 TimelineZoomSize, ImVec2 TimelineSize, ImVec2 TimelineSizeWithBorder, real32 LayerIncrement, sorted_comp_info *SortedCompArray, sorted_layer *SortedLayerArray) { block_composition *Comp = (block_composition *)Memory_Block_AddressAtIndex(Memory, F_Precomps, CompIndex); sorted_comp_info SortedCompInfo = SortedCompArray[CompIndex]; sorted_layer *SortedLayerInfo = Layer_GetSortedArray(SortedLayerArray, SortedCompArray, CompIndex); ImGui::PushID(CompIndex); for (int i = 0; i < SortedCompInfo.LayerCount; i++) { ImGui::PushID(i); sorted_layer SortEntry = SortedLayerInfo[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 = Layer->Vertical_Offset; if (Layer->IsSelected) Layer_Interact_Evaluate(Memory, State, Index_Physical, SortedCompInfo, SortedLayerInfo, &Frame_Start, &Frame_End, &Vertical_Offset); 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; if (UI->BoxSelect) { 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 (Test) { if (!Layer->IsSelected) { Layer->IsSelected = true; } } 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(Memory, File->Layer_Count); State->MostRecentlySelectedLayer = i; Layer->IsSelected = true; } } if (ImGui::IsItemActive()) { if (ImGui::IsMouseDragging(ImGuiMouseButton_Left, -1)) { State->Interact_Active = interact_type_layer_timeadjust; ImVec2 DragDelta = ImGui::GetMouseDragDelta(); DragDelta = DragDelta + (ImVec2(UI->Warp_X, UI->Warp_Y) * TimelineSize); State->Interact_Offset[0] = (DragDelta.x / TimelineSizeWithBorder.x * UI->TimelinePercentZoomed.x) * Comp->Frame_Count; State->Interact_Offset[1] = b; DebugWatchVar("Offset1", &State->Interact_Offset[0], d_float); } } if (ImGui::IsItemDeactivated()) { if (State->Interact_Active == interact_type_layer_timeadjust) { for (int a = 0; a < File->Layer_Count; a++) { block_layer *Layer = (block_layer *)Memory_Block_AddressAtIndex(Memory, F_Layers, a); if (Layer->IsSelected) { Layer_Interact_Evaluate(Memory, State, Index_Physical, SortedCompInfo, SortedLayerInfo, &Layer->Frame_Start, &Layer->Frame_End, &Layer->Vertical_Offset); } } State->Interact_Active = interact_type_none; 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(Memory, File->Layer_Count); State->MostRecentlySelectedLayer = i; Layer->IsSelected = true; } } 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(UI->Warp_X, UI->Warp_Y) * TimelineSize); State->Interact_Offset[0] = (DragDelta.x / TimelineSizeWithBorder.x * UI->TimelinePercentZoomed.x) * Comp->Frame_Count; State->Interact_Offset[1] = (DragDelta.y / TimelineSizeWithBorder.y * UI->TimelinePercentZoomed.y) * LayerIncrement; /* if (UI->DragDelta_Prev.x != 0) { ImVec2 Offset_Old = (UI->DragDelta_Prev / TimelineSizeWithBorder * UI->TimelinePercentZoomed) * ImVec2(MainComp->Frame_Count, LayerIncrement); if ((int32)State->Interact_Offset[1] != (int32)Offset_Old.y) State->UpdateFrame = true; } UI->DragDelta_Prev = DragDelta; */ } } if (ImGui::IsItemDeactivated()) { if (State->Interact_Active == interact_type_layer_move) { for (int a = 0; a < File->Layer_Count; a++) { block_layer *Layer = (block_layer *)Memory_Block_AddressAtIndex(Memory, F_Layers, a); if (Layer->IsSelected) { Layer_Interact_Evaluate(Memory, State, Index_Physical, SortedCompInfo, SortedLayerInfo, &Layer->Frame_Start, &Layer->Frame_End, &Layer->Vertical_Offset); } } State->Interact_Active = interact_type_none; State->Interact_Offset[0] = 0; State->Interact_Offset[1] = 0; } } ImGui::PopID(); } // Check if any layers are precomps; we want to test hit detection for them _after_ the layers in front. for (int i = 0; i < SortedCompInfo.LayerCount; i++) { ImGui::PushID(i); sorted_layer SortEntry = SortedLayerInfo[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 = Layer->Vertical_Offset; if (Layer->IsSelected) Layer_Interact_Evaluate(Memory, State, Index_Physical, SortedCompInfo, SortedLayerInfo, &Frame_Start, &Frame_End, &Vertical_Offset); 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; if (Layer->IsPrecomp && Layer->Precomp_Toggled) { sorted_layer *Precomp_SortedLayerInfo = Layer_GetSortedArray(SortedLayerArray, SortedCompArray, Layer->Block_Source_Index); sorted_comp_info Precomp_SortedCompInfo = SortedCompArray[Layer->Block_Source_Index]; block_layer *Layer_Top = (block_layer *)Memory_Block_AddressAtIndex(Memory, F_Layers, Precomp_SortedLayerInfo[Precomp_SortedCompInfo.LayerCount - 1].Block_Layer_Index); block_layer *Layer_Bottom = (block_layer *)Memory_Block_AddressAtIndex(Memory, F_Layers, Precomp_SortedLayerInfo[0].Block_Layer_Index); real32 SmallestY = Layer_Top->Vertical_Offset; real32 LargestY = Layer_Bottom->Vertical_Offset; real32 PrecompHeight = LargestY - SmallestY + 1; 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); ImGui::PushClipRect(MinClipPos, MaxClipPos, true); draw_list->PushClipRect(MinClipPos, MaxClipPos, true); ImGui_Timeline_DrawPrecomp(File, State, Memory, UI, io, draw_list, Layer->Block_Source_Index, Increment, NestedTimelineAbsolutePos, TimelineMoveSize, TimelineZoomSize, TimelineSize, TimelineSizeWithBorder, LayerIncrement, SortedCompArray, SortedLayerArray); draw_list->PopClipRect(); ImGui::PopClipRect(); } ImGui::PopID(); } // TODO(fox): Draw calls are executed in reverse order, so we need another iteration to draw layers on top of precomps. // The ImDrawListSplitter API can probably do this without another iteration. for (int i = 0; i < SortedCompInfo.LayerCount; i++) { ImGui::PushID(i); sorted_layer SortEntry = SortedLayerInfo[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 = Layer->Vertical_Offset; if (Layer->IsSelected) Layer_Interact_Evaluate(Memory, State, Index_Physical, SortedCompInfo, SortedLayerInfo, &Frame_Start, &Frame_End, &Vertical_Offset); 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; ImU32 col = IM_COL32(255, 255, 255, 255); int NumberOfActiveProperties = 0; for (int h = 0; h < AmountOf(Layer->Property); h++) { property_channel *Property = &Layer->Property[h]; if (Property->Keyframe_Count) NumberOfActiveProperties++; } for (int h = 0; h < AmountOf(Layer->Property); h++) { property_channel *Property = &Layer->Property[h]; ImGui::PushID(Property); if (Property->Keyframe_Count) { property_info PropertyInfo = Property_GetInfo(Memory, Property); real32 Y_Increment = 1 / (PropertyInfo.MaxVal - PropertyInfo.MinVal); bezier_point *PointAddress[2] = {}; ImVec2 Keyframe_ScreenPos[2] = {}; ImVec2 Keyframe_ScreenPos_L[2] = {}; ImVec2 Keyframe_ScreenPos_R[2] = {}; for (int k = 0; k < (Property->Keyframe_Count + 1); k++) { int Idx = k % 2; PointAddress[Idx] = Bezier_Lookup(Memory, Property, k); bezier_point *Point = PointAddress[Idx]; ImVec2 Keyframe_LocalPos[3] = { V2(Point->Pos[0]), V2(Point->Pos[0] + Point->Pos[1]), V2(Point->Pos[0] + Point->Pos[2]) }; ImVec2 Keyframe_LocalPos_Ratio[3]; for (int b = 0; b < 3; b++) { Keyframe_LocalPos_Ratio[b] = (Keyframe_LocalPos[b] - ImVec2(0, PropertyInfo.MinVal)) * ImVec2(Increment.x, Y_Increment); } Keyframe_ScreenPos[Idx] = Layer_ScreenPos_Min + ((Keyframe_LocalPos_Ratio[0] - ImVec2(0, 0.5)) * TimelineZoomSize) + ImVec2(0, Layer_ScreenSize.y/2); Keyframe_ScreenPos_L[Idx] = Layer_ScreenPos_Min + ((Keyframe_LocalPos_Ratio[1] - ImVec2(0, 0.5)) * TimelineZoomSize) + ImVec2(0, Layer_ScreenSize.y/2); Keyframe_ScreenPos_R[Idx] = Layer_ScreenPos_Min + ((Keyframe_LocalPos_Ratio[2] - ImVec2(0, 0.5)) * TimelineZoomSize) + ImVec2(0, Layer_ScreenSize.y/2); draw_list->AddCircle(Keyframe_ScreenPos[Idx], 2, col, 16, 1); if (k != 0 && k != Property->Keyframe_Count) { if (PointAddress[0]->Type == interpolation_type_bezier && PointAddress[1]->Type == interpolation_type_bezier) { draw_list->AddBezierCubic(Keyframe_ScreenPos[!Idx], Keyframe_ScreenPos_R[!Idx], Keyframe_ScreenPos_L[Idx], Keyframe_ScreenPos[Idx], col, 1.0f, 0); } else { draw_list->AddLine(Keyframe_ScreenPos[0], Keyframe_ScreenPos[1], col, 1.0f); } } } } ImGui::PopID(); } draw_list->AddRectFilled(Layer_ScreenPos_Min, Layer_ScreenPos_Max, ImColor(Layer->Col[0], Layer->Col[1], Layer->Col[2], 1.0f)); draw_list->AddRect(Layer_ScreenPos_Min, Layer_ScreenPos_Max, ImColor(1.0f, 1.0f, 1.0f, 0.5f), 2); // block_string *String = (block_string *)Memory_Block_AddressAtIndex(Memory, F_Strings, Layer->Block_String_Index); // draw_list->AddText(Layer_ScreenPos_Min, 0xFFFFFFFF, String->Char); if (Layer->IsSelected) { 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); } ImGui::PopID(); } ImGui::PopID(); } static void ImGui_Timeline(project_data *File, project_state *State, memory *Memory, ui *UI, ImGuiIO io) { 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::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) UI->FocusedWindow = focus_timeline; 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); if (UI->TimelinePercentZoomed.x == 0) { UI->TimelinePercentZoomed = ImVec2(1, 1); } ImGui_TimelineHorizontalIncrementDraw(UI, draw_list, TimelineSizeWithBorder, TimelineAbsolutePos, *MainComp); ImVec2 TimelineZoomSize = TimelineSizeWithBorder / UI->TimelinePercentZoomed; ImVec2 TimelineMoveSize = TimelineSizeWithBorder * UI->TimelinePercentOffset / UI->TimelinePercentZoomed; ImVec2 Increment = ImVec2((real32)1 / MainComp->Frame_Count, (real32)1 / LayerIncrement); uint64 SortSize = (sizeof(sorted_comp_info) * File->Comp_Count) + (sizeof(sorted_layer) * File->Layer_Count); void *SortedArray = Memory_PushScratch(Memory, SortSize); Arbitrary_Zero((uint8 *)SortedArray, SortSize); sorted_comp_info *SortedCompArray = (sorted_comp_info *)SortedArray; sorted_layer *SortedLayerArray = (sorted_layer *)((uint8 *)SortedArray + (sizeof(sorted_comp_info) * File->Comp_Count)); Layer_SortAll(Memory, SortedLayerArray, SortedCompArray, File->Layer_Count, File->Comp_Count); ImGui_Timeline_DrawPrecomp(File, State, Memory, UI, io, draw_list, File->PrincipalCompIndex, Increment, TimelineAbsolutePos, TimelineMoveSize, TimelineZoomSize, TimelineSize, TimelineSizeWithBorder, LayerIncrement, SortedCompArray, SortedLayerArray); Memory_PopScratch(Memory, SortSize); ImVec2 MouseDelta = io.MouseDelta / TimelineSize; real32 BarHandleSize = FontHeight; real32 BarThickness = 50; real32 BarMinZoom = 0.01; real32 BarH_Pos = -TimelineSizeWithBorder.x * UI->TimelinePercentOffset.x; real32 BarH_Size = TimelineSizeWithBorder.x / (1 / UI->TimelinePercentZoomed.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 ((UI->TimelinePercentZoomed.x - MouseDelta.x) > BarMinZoom) { UI->TimelinePercentZoomed.x -= MouseDelta.x; UI->TimelinePercentOffset.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)) { UI->TimelinePercentOffset.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 ((UI->TimelinePercentZoomed.x + MouseDelta.x) > BarMinZoom) { UI->TimelinePercentZoomed.x += MouseDelta.x; } BarHeld = true; } if (BarHeld) { ImGui_WarpMouse(UI, io.MousePos, TimelineAbsolutePos, TimelineAbsolutePos + TimelineSizeWithBorder, 1); } Assert(UI->TimelinePercentZoomed.x > BarMinZoom); real32 BarV_MaxSize = TimelineSizeWithBorder.y - BarThickness/2; real32 BarV_Pos = -BarV_MaxSize * UI->TimelinePercentOffset.y; real32 BarV_Size = BarV_MaxSize / (1 / UI->TimelinePercentZoomed.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))) { UI->TimelinePercentZoomed.y -= MouseDelta.y; UI->TimelinePercentOffset.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) { UI->TimelinePercentOffset.y -= io.MouseWheel/10; } if (ImGui::IsItemActive() && ImGui::IsMouseDragging(ImGuiMouseButton_Left, -1)) { UI->TimelinePercentOffset.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))) { UI->TimelinePercentZoomed.y += MouseDelta.y; BarHeld = true; } UI->TimelinePercentZoomed.y = Max(UI->TimelinePercentZoomed.y, 0.01); if (BarHeld) { ImGui_WarpMouse(UI, 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 + TimelineMoveSize)) / TimelineZoomSize; if (io.KeyShift) { UI->TimelinePercentOffset.y += Increment*Direction; } else if (io.KeyCtrl) { UI->TimelinePercentOffset.x += Increment*Direction*0.3; } else { if (Direction == 1) { UI->TimelinePercentZoomed = UI->TimelinePercentZoomed - (UI->TimelinePercentZoomed * Increment); UI->TimelinePercentOffset = UI->TimelinePercentOffset - ((UI->TimelinePercentOffset * Increment) + Offset*Increment); } else { UI->TimelinePercentOffset = ((UI->TimelinePercentOffset + Offset*Increment) / (1.0f - Increment)); UI->TimelinePercentZoomed = (UI->TimelinePercentZoomed / (1.0f - Increment)); } } } if (IsItemActivated) { if (!io.KeyShift) Layer_DeselectAll(Memory, File->Layer_Count); UI->BoxSelect = true; } if (IsItemActive) { Assert(UI->BoxSelect); draw_list->AddRectFilled(io.MouseClickedPos[0], io.MousePos, IM_COL32(0, 0, 200, 50)); } if (!ImGui::IsMouseDown(ImGuiMouseButton_Left)) { UI->Warp_X = 0; UI->Warp_Y = 0; } if (IsItemDeactivated) { UI->BoxSelect = false; } draw_list->PopClipRect(); ImGui::PopClipRect(); ImGui::PopStyleVar(); ImGui::EndChild(); ImGui::PopStyleVar(2); ImGui::End(); } static void ImGui_ProcessInputs(project_data *File, project_state *State, memory *Memory, ImGuiIO io) { if (ImGui::IsKeyPressed(ImGuiKey_Q)) { State->IsRunning = false; } if (ImGui::IsKeyPressed(ImGuiKey_A)) { State->UpdateFrame = true; } if (ImGui::IsKeyPressed(ImGuiKey_T)) { for (int a = 0; a < File->Layer_Count; a++) { block_layer *Layer = (block_layer *)Memory_Block_AddressAtIndex(Memory, F_Layers, a); if (Layer->IsSelected && Layer->IsPrecomp) { Layer->Precomp_Toggled ^= 1; } } } #if DEBUG if (ImGui::IsKeyPressed(ImGuiKey_W)) { Debug.ToggleWindow ^= 1; } #endif } #if 0 real32 MaxVal_Y = -10000; real32 MinVal_Y = 10000; for (int b = 0; b < Property->NumberOfTotalKeyframes; b++) { keyframe *Keyframe = KeyframeLookup(Property, b); MaxVal_Y = (Keyframe->Value.f > MaxVal_Y) ? Keyframe->Value.f : MaxVal_Y; MinVal_Y = (Keyframe->Value.f < MinVal_Y) ? Keyframe->Value.f : MinVal_Y; } keyframe *FirstKeyframe = KeyframeLookup(Property, 0); keyframe *LastKeyframe = KeyframeLookup(Property, Property->NumberOfTotalKeyframes - 1); real32 MinVal_X = (Layer->BitmapInfo.FrameOffset + FirstKeyframe->FrameNumber); real32 MaxVal_X = (Layer->BitmapInfo.FrameOffset + LastKeyframe->FrameNumber); UI->Y_MaxVal = MaxVal_Y; UI->Y_MinVal = MinVal_Y; if (!UI->IsDragging) { UI->Display_Y_MinVal = UI->Y_MinVal; UI->Display_Y_MaxVal = UI->Y_MaxVal; } real32 Y_TimelinePercentZoomed = UI->Y_TimelinePercentZoomed; real32 Y_TimelinePercentOffset = UI->Y_TimelinePercentOffset; MaxVal_Y = UI->Display_Y_MaxVal; MinVal_Y = UI->Display_Y_MinVal; DebugWatchVar("offset: ", &Y_TimelinePercentOffset, d_float); DebugWatchVar("zoom: ", &Y_TimelinePercentZoomed, d_float); real32 Ratio_Graph_X = (MaxVal_X - MinVal_X) / File->NumberOfFrames; real32 TimelineZoomSize = TimelineSizeWithBorder.x / UI->TimelinePercentZoomed; real32 TimelineMoveSize = TimelineSizeWithBorder.x * UI->TimelinePercentOffset / UI->TimelinePercentZoomed; real32 Y_TimelineZoomSize = TimelineSizeWithBorder.y / Y_TimelinePercentZoomed; real32 Y_TimelineMoveSize = TimelineSizeWithBorder.y * Y_TimelinePercentOffset / Y_TimelinePercentZoomed; for (int b = 0; b < Property->NumberOfTotalKeyframes; b++) { ImGui::PushID(b); keyframe *Keyframe = KeyframeLookup(Property, b); // Only used for drawing the bezier. keyframe *NextKeyframe = (b != Property->NumberOfTotalKeyframes - 1) ? KeyframeLookup(Property, b + 1) : NULL; real32 Increment_X = (real32)1 / File->NumberOfFrames; real32 UI_FrameDistance = Increment_X*TimelineZoomSize; int32 Keyframe_X = (Layer->BitmapInfo.FrameOffset + Keyframe->FrameNumber); real32 Keyframe_Y = Keyframe->Value.f; real32 Ratio_X_Mid = (real32)Keyframe_X / File->NumberOfFrames; real32 Ratio_Y_Mid = (Keyframe_Y - MinVal_Y) / (MaxVal_Y - MinVal_Y); ImVec2 KeyframePos_Mid = ImVec2(TimelineAbsolutePos.x + TimelineMoveSize + Ratio_X_Mid*TimelineZoomSize, TimelineAbsolutePos.y + Y_TimelineMoveSize + (1.0f - Ratio_Y_Mid)*Y_TimelineZoomSize); ImGui::SetCursorScreenPos(KeyframePos_Mid); ImGui::Button("##keyframe", ImVec2(FontHeight, FontHeight)); if (ImGui::IsItemHovered() && ImGui::IsKeyPressed(ImGuiKey_R)) { UI->TempVal = Keyframe->Value.f; UI->TempVal_X = Keyframe->FrameNumber; } if (ImGui::IsItemActivated()) { UI->IsDragging = true; UI->TempVal = Keyframe->Value.f; UI->TempVal_X = Keyframe->FrameNumber; } if ((ImGui::IsItemActive() && ImGui::IsMouseDragging(ImGuiMouseButton_Left, -1))) { ImVec2 DragDelta = ImGui::GetMouseDragDelta(); DragDelta = DragDelta + (ImVec2(UI->Wrap_X, UI->Wrap_Y) * TimelineSize); DebugWatchVar("DragX", &DragDelta.x, d_float); DebugWatchVar("DragY", &DragDelta.y, d_float); DebugWatchVar("Wrap_X", &UI->Wrap_X, d_int); DebugWatchVar("Wrap_Y", &UI->Wrap_Y, d_int); real32 MouseDeltaRatio = -DragDelta.y / TimelineSizeWithBorder.y * Y_TimelinePercentZoomed; ImVec2 Increment = ImVec2(DragDelta.x / UI_FrameDistance, ((MaxVal_Y - MinVal_Y) * MouseDeltaRatio)); // The plus 0.5 * X_Direction is for making the frame jump happen // when the cursor is between two frames rather than when passing one. real32 X_Direction = (Increment.x > 0) ? fabsf(Increment.x) / Increment.x : 0; Keyframe->FrameNumber = UI->TempVal_X + (int32)(Increment.x + 0.5*X_Direction); Keyframe->Value.f = UI->TempVal + Increment.y; if (io.KeyShift) { bool32 RestrainAxis = (fabsf(DragDelta.x) > fabsf(DragDelta.y)); if (RestrainAxis) { Keyframe->Value.f = UI->TempVal; } else { Keyframe->FrameNumber = UI->TempVal_X; } } ImGui_WrapMouse(UI, io.MousePos, TimelineAbsolutePos, TimelineAbsolutePos + TimelineSizeWithBorder); } // TODO(fox): This is kind of a mess. I built the graph around the // ratios of the keyframes/timeline to make the bars straightforward, // meaning the position/offset have to be transformed into a new space // when a new min/max value is reached. if (ImGui::IsItemDeactivated()) { if ((UI->TempVal >= MaxVal_Y) || Keyframe->Value.f == UI->Y_MaxVal) { real32 Min = ((Ratio_Y_Mid <= 0.0f) && (UI->TempVal >= MaxVal_Y)) ? MinVal_Y : UI->Y_MinVal; real32 RealRatio_Y = (UI->Y_MaxVal - UI->Y_MinVal) / (MaxVal_Y - MinVal_Y); UI->Y_TimelinePercentZoomed = UI->Y_TimelinePercentZoomed / RealRatio_Y; UI->Y_TimelinePercentOffset = (1.0f/RealRatio_Y + UI->Y_TimelinePercentOffset/RealRatio_Y - 1.0f); } else if (UI->TempVal <= MinVal_Y || Keyframe->Value.f == UI->Y_MinVal) { real32 RealRatio_Y = (UI->Y_MinVal - MinVal_Y) / (MaxVal_Y - MinVal_Y); UI->Y_TimelinePercentOffset = UI->Y_TimelinePercentOffset / (1 - RealRatio_Y); UI->Y_TimelinePercentZoomed = UI->Y_TimelinePercentZoomed / (1 - RealRatio_Y); } UI->IsDragging = false; UI->Wrap_X = 0; UI->Wrap_Y = 0; } ImU32 col = ImGui::GetColorU32(ImGuiCol_ScrollbarGrab); ImVec2 Handle_Pos[2] = {}; if (Keyframe->Type == bezier) { ImVec2 Handle_Ratio[2] = {}; Handle_Ratio[0] = ImVec2((real32)(Keyframe_X + Keyframe->TangentLeft.x) / File->NumberOfFrames, (Keyframe_Y + Keyframe->TangentLeft.y - MinVal_Y) / (MaxVal_Y - MinVal_Y)); Handle_Ratio[1] = ImVec2((real32)(Keyframe_X + Keyframe->TangentRight.x) / File->NumberOfFrames, (Keyframe_Y + Keyframe->TangentRight.y - MinVal_Y) / (MaxVal_Y - MinVal_Y)); Handle_Pos[0] = ImVec2(TimelineAbsolutePos.x + TimelineMoveSize + Handle_Ratio[0].x*TimelineZoomSize, TimelineAbsolutePos.y + Y_TimelineMoveSize + (1.0f - Handle_Ratio[0].y)*Y_TimelineZoomSize); Handle_Pos[1] = ImVec2(TimelineAbsolutePos.x + TimelineMoveSize + Handle_Ratio[1].x*TimelineZoomSize, TimelineAbsolutePos.y + Y_TimelineMoveSize + (1.0f - Handle_Ratio[1].y)*Y_TimelineZoomSize); draw_list->AddLine(KeyframePos_Mid, Handle_Pos[0], col, 1.0f); draw_list->AddLine(KeyframePos_Mid, Handle_Pos[1], col, 1.0f); for (int i = 0; i < 2; i++) { ImGui::SetCursorScreenPos(Handle_Pos[i]); ImGui::Button((i == 0) ? "##keyframe_left" : "##keyframe_right", ImVec2(FontHeight, FontHeight)); v2 *Tangent = (i == 0) ? &Keyframe->TangentLeft : &Keyframe->TangentRight; if (ImGui::IsItemActivated()) { UI->IsDragging = true; UI->TempVal_X = Tangent->x; UI->TempVal = Tangent->y; } if ((ImGui::IsItemActive() && ImGui::IsMouseDragging(ImGuiMouseButton_Left, -1))) { ImVec2 DragDelta = ImGui::GetMouseDragDelta(); DragDelta = DragDelta + (ImVec2(UI->Wrap_X, UI->Wrap_Y) * TimelineSize); ImVec2 MouseDeltaRatio = (ImVec2(1, -1) * DragDelta) / TimelineSizeWithBorder * ImVec2(UI->TimelinePercentZoomed / Ratio_Graph_X, Y_TimelinePercentZoomed); real32 NewPos_X = ((MaxVal_X - MinVal_X) * MouseDeltaRatio.x); real32 NewPos_Y = (io.KeyShift) ? 0 : ((MaxVal_Y - MinVal_Y) * MouseDeltaRatio.y); *Tangent = V2(UI->TempVal_X, UI->TempVal) + V2(NewPos_X, NewPos_Y); ImGui_WrapMouse(UI, io.MousePos, TimelineAbsolutePos, TimelineAbsolutePos + TimelineSizeWithBorder); } if (ImGui::IsItemDeactivated()) { UI->IsDragging = false; UI->Wrap_X = 0; UI->Wrap_Y = 0; } } } if (NextKeyframe) { real32 Ratio_X_2 = (real32)(Layer->BitmapInfo.FrameOffset + NextKeyframe->FrameNumber) / File->NumberOfFrames; real32 Ratio_Y_2 = (NextKeyframe->Value.f - MinVal_Y) / (MaxVal_Y - MinVal_Y); ImVec2 NextKeyframePos = ImVec2(TimelineAbsolutePos.x + TimelineMoveSize + Ratio_X_2*TimelineZoomSize, TimelineAbsolutePos.y + Y_TimelineMoveSize + (1.0f - Ratio_Y_2)*Y_TimelineZoomSize); draw_list->AddLine(KeyframePos_Mid, NextKeyframePos, col, 1.0f); } ImGui::PopID(); } ImGui_TimelineIncrementDraw(File, UI, draw_list, TimelineSizeWithBorder, TimelineAbsolutePos, 0); ImGui_TimelineIncrementDraw2(File, draw_list, MaxVal_Y, MinVal_Y, Y_TimelinePercentZoomed, Y_TimelinePercentOffset, TimelineSizeWithBorder, TimelineAbsolutePos, 0); #if DEBUG draw_list->AddCircle(TimelineAbsolutePos + ImVec2(TimelineSizeWithBorder.x * 0.25, TimelineSizeWithBorder.y - 50), 2, IM_COL32(200, 000, 200, 200), 16, 1); draw_list->AddCircle(TimelineAbsolutePos + ImVec2(TimelineSizeWithBorder.x * 0.5, TimelineSizeWithBorder.y - 50), 2, IM_COL32(200, 000, 200, 200), 16, 1); draw_list->AddCircle(TimelineAbsolutePos + ImVec2(TimelineSizeWithBorder.x * 0.75, TimelineSizeWithBorder.y - 50), 2, IM_COL32(200, 000, 200, 200), 16, 1); #endif #endif #if 0 ImVec2 MouseDelta = io.MouseDelta / TimelineSize; real32 BarHandleSize = FontHeight; real32 BarThickness = 50; real32 BarMinZoom = 0.01; real32 BarH_Pos = -TimelineSizeWithBorder.x * UI->TimelinePercentOffset; real32 BarH_Size = TimelineSizeWithBorder.x / (1 / UI->TimelinePercentZoomed); // 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 ((UI->TimelinePercentZoomed - MouseDelta.x) > BarMinZoom) { UI->TimelinePercentZoomed -= MouseDelta.x; UI->TimelinePercentOffset -= 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)) { UI->TimelinePercentOffset -= 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 ((UI->TimelinePercentZoomed + MouseDelta.x) > BarMinZoom) { UI->TimelinePercentZoomed += MouseDelta.x; } BarHeld = true; } if (BarHeld) { ImGui_WrapMouse(UI, io.MousePos, TimelineAbsolutePos, TimelineAbsolutePos + TimelineSizeWithBorder, 1); } Assert(UI->TimelinePercentZoomed > BarMinZoom); real32 BarV_MaxSize = TimelineSizeWithBorder.y - BarThickness/2; real32 BarV_Pos = -BarV_MaxSize * Y_TimelinePercentOffset; real32 BarV_Size = BarV_MaxSize / (1 / Y_TimelinePercentZoomed); 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))) { UI->Y_TimelinePercentZoomed -= MouseDelta.y; UI->Y_TimelinePercentOffset -= MouseDelta.y; BarHeld = true; } ImGui::SetCursorScreenPos(BarV_PosUI + ImVec2(0, BarHandleSize)); ImGui::Button("##h-scrollbar", ImVec2(BarThickness, BarV_SizeUI)); if (ImGui::IsItemHovered() && io.MouseWheel) { UI->Y_TimelinePercentOffset -= io.MouseWheel/10; } if (ImGui::IsItemActive() && ImGui::IsMouseDragging(ImGuiMouseButton_Left, -1)) { UI->Y_TimelinePercentOffset -= 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))) { UI->Y_TimelinePercentZoomed += MouseDelta.y; BarHeld = true; } UI->Y_TimelinePercentZoomed = Max(UI->Y_TimelinePercentZoomed, 0.01); if (BarHeld) { ImGui_WrapMouse(UI, io.MousePos, TimelineAbsolutePos, TimelineAbsolutePos + TimelineSizeWithBorder, 2); } draw_list->PopClipRect(); ImGui::PopClipRect(); ImGui::PopStyleVar(); if (io.MouseWheel) { // NOTE(fox): Change this if any other action is added when hovering over the bar area. bool32 BarHovering_H = TestRectangle(TimelineAbsolutePos + ImVec2(0, TimelineSize.y - BarThickness), TimelineAbsolutePos + ImVec2(TimelineSize.x, TimelineSize.y), io.MousePos); bool32 BarHovering_V = TestRectangle(TimelineAbsolutePos + ImVec2(TimelineSize.x - BarThickness, 0), TimelineAbsolutePos + ImVec2(TimelineSize.x, TimelineSize.y), io.MousePos); if (BarHovering_H && io.MouseWheel) { UI->TimelinePercentOffset -= io.MouseWheel/15; } else if (BarHovering_V && io.MouseWheel) { UI->Y_TimelinePercentOffset -= io.MouseWheel/15; } else { real32 Increment = 0.1; bool32 Direction = (io.MouseWheel > 0) ? 1 : -1; real32 Offset = (io.MousePos.y - (TimelineAbsolutePos.y + Y_TimelineMoveSize)) / Y_TimelineZoomSize; real32 X_Offset = (io.MousePos.x - (TimelineAbsolutePos.x + TimelineMoveSize)) / TimelineZoomSize; DebugWatchVar("X Offset", &X_Offset, d_float); if (io.KeyShift) { UI->Y_TimelinePercentOffset += Increment*Direction; } else if (io.KeyCtrl) { UI->TimelinePercentOffset += Increment*Direction*0.3; } else { if (Direction == 1) { UI->Y_TimelinePercentZoomed -= (UI->Y_TimelinePercentZoomed * Increment); UI->Y_TimelinePercentOffset -= (UI->Y_TimelinePercentOffset * Increment) + Offset*Increment; UI->TimelinePercentZoomed -= (UI->TimelinePercentZoomed * Increment); UI->TimelinePercentOffset -= (UI->TimelinePercentOffset * Increment) + X_Offset*Increment; } else { UI->Y_TimelinePercentOffset = ((UI->Y_TimelinePercentOffset + Offset*Increment) / (1.0f - Increment)); UI->Y_TimelinePercentZoomed = (UI->Y_TimelinePercentZoomed / (1.0f - Increment)); UI->TimelinePercentOffset = ((UI->TimelinePercentOffset + X_Offset*Increment) / (1.0f - Increment)); UI->TimelinePercentZoomed = (UI->TimelinePercentZoomed / (1.0f - Increment)); } } } } // General timeline interaction ImGui::SetCursorScreenPos(TimelineAbsolutePos); ImGui::InvisibleButton("TimelineMoving", TimelineSizeWithBorder, ImGuiButtonFlags_MouseButtonLeft | ImGuiButtonFlags_MouseButtonRight); bool32 IsHovered = ImGui::IsItemHovered(); bool32 IsActive = ImGui::IsItemActive(); bool32 IsItemActivated = ImGui::IsItemActivated(); bool32 IsItemDeactivated = ImGui::IsItemDeactivated(); bool32 LeftClick = ImGui::IsMouseDown(ImGuiMouseButton_Left); bool32 RightClick = ImGui::IsMouseDown(ImGuiMouseButton_Right); if (IsActive) { if (LeftClick) { if (io.KeyCtrl && IsActive) { real32 LocalMousePos = ImGui::GetMousePos().x - TimelineStartingPos.x; real32 ZoomRatio = LocalMousePos / UI->TimelineZoom; File->CurrentFrame = (int32)(ZoomRatio + 0.5); State->UpdateFrame = true; State->UpdateKeyframes = true; } else { if (IsItemActivated) { if (!io.KeyShift) { // DeselectAllKeyframes(&State); // DeselectAllLayers(File, State); } UI->BoxStart = ImGui::GetMousePos(); UI->BoxSelectActive = true; } if (ImGui::IsMouseDragging(ImGuiMouseButton_Left, -1) ) { UI->BoxEnd = ImGui::GetMousePos(); draw_list->AddRectFilled(UI->BoxStart, UI->BoxEnd, IM_COL32(0, 0, 200, 50)); } } // Timeline zooming interaction } else if (RightClick && IsActive) { if (IsItemActivated) { real32 LocalMousePos = io.MousePos.x - WindowMinAbs.x - UI->TimelineSplit; UI->TempZoomRatioTimeline = LocalMousePos / TimelineSize.x; } if (ImGui::IsMouseDragging(ImGuiMouseButton_Right, -1) ) { UI->TimelineZoom += io.MouseDelta.x; } } } if (IsItemDeactivated) { UI->BoxStart = {0, 0}; UI->BoxEnd = {0, 0}; UI->BoxSelectActive = false; if (!io.KeyShift) DeselectAllLayers(File, State); } ImGui::EndChild(); ImGui::PopStyleVar(2); #endif #if 0 // 0 for timeline keyframe, 1 for graph keyframe, 2 for left graph handle, 3 for right graph handle static void ImGui_KeyframeDragging(project_data *File, project_state *State, ui *UI, property_channel *Property, int32 b, ImGuiIO io, int16 Type) { keyframe *Keyframe = KeyframeLookup(Property, b); if (ImGui::IsItemActive()) { if (!Keyframe->IsSelected && ImGui::IsItemActivated()) { if (!io.KeyShift) { temp_keyframe_list Bad = GetSelectedKeyframes(File); for (int i = 0; i < Bad.Amount; i++) Bad.SelectedKeyframe[i]->IsSelected = false; } Keyframe->IsSelected = true; State->RecentSelectionType = selection_keyframe; } if (ImGui::IsMouseDragging(ImGuiMouseButton_Left, -1)) { ImGui::SetMouseCursor(ImGuiMouseCursor_Hand); if (Type == 0 || Type == 1) { UI->DraggingKeyframeThreshold += io.MouseDelta.x; if (abs(UI->DraggingKeyframeThreshold) >= UI->TimelineZoom) { int16 Increment = UI->DraggingKeyframeThreshold/UI->TimelineZoom; // temp_keyframe_list Bad = GetSelectedKeyframes(File); // for (int b = 0; b < Bad.Amount; b++) { // keyframe *SelectedKeyframe = Bad.SelectedKeyframe[b]; if (!(Keyframe->FrameNumber == 0 && Increment == -1)) { Keyframe->FrameNumber += Increment; CheckKeyframeSort(Property, Increment, b); // SortAndCacheKeyframeAtFrame(SelectedKeyframe->FrameNumber, &File.LayerPTR[i]->Property[a], &Cache); ClampSurroundingKeyframeHandles(Property, b); } // } UI->DraggingKeyframeThreshold += -1*Increment*UI->TimelineZoom; State->UpdateFrame = true; State->UpdateKeyframes = true; // Cache.Frame[File.CurrentFrame].Cached = false; } } /* if (Type != 0) { if (Type == 1) { real32 IncrementsPerPixel = (Property->LocalMaxVal.f - Property->LocalMinVal.f)/Property->GraphLength; Keyframe->Value.f -= io.MouseDelta.y*IncrementsPerPixel; CalculatePropertyMinMax(Property); } if (Type == 2) { Keyframe->TangentLeft.x += io.MouseDelta.x/UI->TimelineZoom; Keyframe->TangentLeft.y -= io.MouseDelta.y; ClampKeyframeHandles(Property, b, 0); } if (Type == 3) { Keyframe->TangentRight.x += io.MouseDelta.x/UI->TimelineZoom; Keyframe->TangentRight.y -= io.MouseDelta.y; ClampKeyframeHandles(Property, b, 1); } State->UpdateFrame = true; State->UpdateKeyframes = true; } */ } } } static void ImGui_InteractSliderProperty(project_state *State, memory *Memory, property_channel *Property) { ImGui::DragScalar(Property->Name, ImGuiDataType_Float, &Property->CurrentValue.f, Property->ScrubVal.f, &Property->MinVal.f, &Property->MaxVal.f, "%f"); if (ImGui::IsItemActivated()) { State->InteractCache[0] = Property->CurrentValue.f; } if (ImGui::IsItemActive()) { State->UpdateFrame = true; } if (ImGui::IsItemDeactivatedAfterEdit()) { if (ImGui::IsKeyPressed(ImGuiKey_Escape)) { Property->CurrentValue.f = State->InteractCache[0]; } else { History_Entry_Commit(Memory, action_entry_default, "Tranforms interact"); History_Action_Change(Memory, &Property->CurrentValue.f, &State->InteractCache[0], &Property->CurrentValue.f, action_type_change_r32); History_Entry_End(Memory); } State->UpdateFrame = true; } } static void ImGui_DebugUndoTree(project_data *File, memory *Memory) { ImGui::SetNextWindowSize(ImVec2(200, 800)); ImGui::Begin("undotree"); for (int i = 0; i < Memory->Action.NumberOfEntries; i++) { action_entry Entry = Memory->Action.Entry[i]; bool32 CurrentPos = (i < Memory->Action.Index); ImGui::MenuItem(Entry.Name, NULL, CurrentPos); } ImGui::End(); } static void ImGui_DebugMemoryViewer(project_data *File, memory *Memory) { ImGui::SetNextWindowSize(ImVec2(800, 200)); ImGui::Begin("memoryviewer"); ImVec2 ViewportMin = ImGui::GetCursorScreenPos(); ImVec2 ViewportScale = ImGui::GetContentRegionAvail(); ImVec2 ViewportMax = ImVec2(ViewportMin.x + ViewportScale.x, ViewportMin.y + ViewportScale.y); memory_table *Table = &Memory->Slot[B_LoadedBitmaps]; real32 TotalMB = Table->Size/1024/1024; real32 LineAmount = 50; real32 IncrementMB = TotalMB / LineAmount; real32 ScreenIncrement = ViewportScale.x / LineAmount; real32 CubeHeight = ImGui::GetFontSize(); real32 TotalHeight = CubeHeight*4; ImDrawList* draw_list = ImGui::GetWindowDrawList(); draw_list->AddRectFilled(ViewportMin, ViewportMax, IM_COL32(50, 50, 50, 255)); for (float x = 0; x < LineAmount; x++) { uint32 LineColor = IM_COL32(200, 200, 200, 40); ImVec2 Min = ImVec2(ViewportMin.x + ScreenIncrement * x, ViewportMin.y); ImVec2 Max = ImVec2(Min.x + 2, ViewportMax.y); draw_list->AddLine(Min, Max, LineColor); } // CurrentPosition line { uint32 LineColor = IM_COL32(000, 100, 200, 200); real32 CurPosMB = (real32)Table->CurrentPosition/1024/1024; ImVec2 Min = ImVec2(ViewportMin.x + (CurPosMB/IncrementMB * ScreenIncrement), ViewportMin.y); ImVec2 Max = ImVec2(Min.x + 2, ViewportMax.y); draw_list->AddLine(Min, Max, LineColor); } for (uint32 i = 0; i < Table->NumberOfPointers; i++) { if (Memory->Bitmap[i].SourceOwner) { void *DataStart = Memory->Bitmap[i].Data; void *NextDataStart; int pp = 1; for (;;) { if (Memory->Bitmap[i+pp].SourceOwner) { NextDataStart = Memory->Bitmap[i+pp].Data; break; } pp++; } uint64 BytesBetween = (uint8 *)NextDataStart - (uint8 *)DataStart; source *Source = Memory->Bitmap[i].SourceOwner; real32 Pos = Memory_NormalizedPosition(&File->Source[0], File->NumberOfSources, sizeof(source), Source); ImVec4 col = ImColor::HSV(Pos, 0.3, 0.6, 1.0f); uint64 BitmapSize = Bitmap_CalcTotalBytes(Source->Info.Width, Source->Info.Height, Source->Info.BytesPerPixel); if (BitmapSize > BytesBetween && i != Table->NumberOfPointers - 1) { col = ImColor::HSV(Pos, 1.0, 1.0, 1.0f); } uint64 DataStart2 = (uint8 *)DataStart - (uint8 *)Table->Address; real32 StartReal = ((real32)DataStart2/1024/1024); ImVec2 Min = ImVec2(ViewportMin.x + (StartReal/IncrementMB * ScreenIncrement), ViewportMin.y + (ViewportScale.y / 2) - (TotalHeight / 2) + TotalHeight*Pos); real32 SizeReal = ((real32)BitmapSize/1024/1024); ImVec2 Size = ImVec2(SizeReal/IncrementMB * ScreenIncrement, CubeHeight); ImGui::SetCursorScreenPos(Min); ImGui::PushID(i); ImGui::PushStyleColor(ImGuiCol_Button, col); char buf2[256]; sprintf(buf2, "%i##mempos", Memory->Bitmap[i].Frame); ImGui::Button(buf2, Size); ImGui::PopStyleColor(); if (ImGui::IsItemHovered()) { char buf[1024]; sprintf(buf, "Source owner: %s\nSize: %.02f MB\n Frame number: %i\n Index: %i", Memory->Bitmap[i].SourceOwner->Path, SizeReal, Memory->Bitmap[i].Frame, i); ImGui::BeginTooltip(); ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f); ImGui::TextUnformatted(buf); ImGui::PopTextWrapPos(); ImGui::EndTooltip(); } ImGui::PopID(); ImVec2 Max = ImVec2(ViewportMax.x, Min.y + CubeHeight); // draw_list->AddRectFilled(Min, Max, IM_COL32(200, 200, 200, 255)); } } ImGui::SetCursorScreenPos(ImVec2(ViewportMin.x, ViewportMax.y - CubeHeight*2)); char buf[1024]; sprintf(buf, "Current index: %i\nAmount of pointers: %i", Table->PointerIndex, Table->NumberOfPointers); ImGui::Text(buf); ImGui::End(); } static bool32 FU; static void ImGui_TimelineIncrementDraw2(project_data *File, ImDrawList *draw_list, real32 MaxVal_Y, real32 MinVal_Y, real32 Y_TimelinePercentZoomed, real32 Y_TimelinePercentOffset, ImVec2 TimelineSizeWithBorder, ImVec2 TimelineAbsolutePos, bool32 IsText) { uint32 LineColor = IM_COL32(200, 200, 200, 40); real32 TimelineZoomSize = TimelineSizeWithBorder.y / Y_TimelinePercentZoomed; real32 TimelineMoveSize = TimelineSizeWithBorder.y * Y_TimelinePercentOffset / Y_TimelinePercentZoomed; Assert(TimelineZoomSize > 0.0f); real32 Fraction = (MinVal_Y > 0) ? MinVal_Y - (int32)MinVal_Y : (int32)MinVal_Y - MinVal_Y; real32 x = (int32)MinVal_Y; bool32 RightmostEdge = false; while (!RightmostEdge) { real32 Ratio_Y = (x - MinVal_Y) / (MaxVal_Y - MinVal_Y); ImVec2 Min = ImVec2(TimelineAbsolutePos.x, TimelineAbsolutePos.y + TimelineMoveSize + (1.0f - Ratio_Y)*TimelineZoomSize); ImVec2 Max = ImVec2(TimelineAbsolutePos.x + TimelineSizeWithBorder.x, Min.y + 2); if (Min.y > TimelineAbsolutePos.y) { draw_list->AddLine(Min, Max, LineColor); char buf2[6]; sprintf(buf2, "%.2f", x); draw_list->AddText(ImVec2(TimelineAbsolutePos.x, Min.y), IM_COL32(200, 200, 200, 130), buf2); x += 1.0f; } else { RightmostEdge = true; } } x = (int32)MinVal_Y; x -= 1.0f; bool32 LeftmostEdge = false; while (!LeftmostEdge) { real32 Ratio_Y = (x - MinVal_Y) / (MaxVal_Y - MinVal_Y); ImVec2 Min = ImVec2(TimelineAbsolutePos.x, TimelineAbsolutePos.y + TimelineMoveSize + (1.0f - Ratio_Y)*TimelineZoomSize); ImVec2 Max = ImVec2(TimelineAbsolutePos.x + TimelineSizeWithBorder.x, Min.y + 2); if (Min.y < TimelineAbsolutePos.y + TimelineSizeWithBorder.y) { draw_list->AddLine(Min, Max, LineColor); char buf2[6]; sprintf(buf2, "%.2f", x); draw_list->AddText(ImVec2(TimelineAbsolutePos.x, Min.y), IM_COL32(200, 200, 200, 130), buf2); x -= 1.0f; } else { LeftmostEdge = true; } } } static void ImGui_PropertiesPanel(project_data *File, project_state *State, ui *UI, memory *Memory, ImGuiIO io) { if (State->MostRecentlySelectedLayer > -1) { project_layer *Layer = File->Layer[State->MostRecentlySelectedLayer]; char buf[256]; sprintf(buf, "Properties: %s###Properties", Layer->Name); ImGui::Begin(buf); if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) UI->FocusedWindow = focus_properties; ImGui::Text("Transform"); for (int h = 0; h < AmountOf(Layer->Property); h++) { property_channel *Property = &Layer->Property[h]; ImGui::PushID(Property); if (ImGui::Button("K")) Keyframe_Insert(Property, Memory, File->CurrentFrame, Property->CurrentValue.f); ImGui::SameLine(); ImGui_InteractSliderProperty(State, Memory, Property); ImGui::PopID(); } for (int h = 0; h < Layer->NumberOfEffects; h++) { effect *Effect = Layer->Effect[h]; ImGui::PushID(Effect->ImGuiID); if (FU && h == 0) { int a = 0; } // this is stupid if (Effect->IsActive) ImGui::PushStyleColor(ImGuiCol_Button, ImGui::GetColorU32(ImGuiCol_ButtonHovered)); else ImGui::PushStyleColor(ImGuiCol_Button, ImGui::GetColorU32(ImGuiCol_Button)); if (ImGui::Button("V")) { History_Entry_Commit(Memory, action_entry_default, "Toggle effect"); History_Action_Change_SwapBool(Memory, &Effect->IsActive); History_Entry_End(Memory); State->UpdateFrame = true; } ImGui::SameLine(); ImGui::PopStyleColor(); ImGui::Button("R"); ImGui::SameLine(); ImGui::Selectable(Effect->Name, Effect->IsSelected); // NOTE(fox): The logic for effect dragging has to be after we've // done the UI for the rest of the effect! real32 Effect_Top = ImGui::GetCursorScreenPos().y; bool32 IsHovered = ImGui::IsItemHovered(); bool32 IsActive = ImGui::IsItemActive(); bool32 IsActivated = ImGui::IsItemActivated(); bool32 IsDeactivated = ImGui::IsItemDeactivated(); if (Effect->DisplayType == standard) { for (int i = 0; i < Effect->NumberOfProperties; i++) { property_channel *Property = &Effect->Property[i]; ImGui::PushID(Property); if (Property->VarType == type_real) { ImGui_InteractSliderProperty(State, Memory, Property); } if (Property->VarType == type_color) if (ImGui::ColorEdit4("color 1", &Property->CurrentValue.f, ImGuiColorEditFlags_Float)) State->UpdateFrame = true; if (Property->VarType == type_blendmode) { uint32 *item_current_idx = (uint32 *)&Property->CurrentValue.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::PopID(); } } else if (Effect->DisplayType == levels) { source *Source = Layer->Source; layer_bitmap_info *BitmapInfo = &Layer->BitmapInfo; if (!BitmapInfo->HistogramVals) { uint64 Size = Bitmap_CalcUnpackedBytes(Source->Info.Width, Source->Info.Height, Source->Info.BytesPerPixel); BitmapInfo->HistogramVals = AllocateMemory(Memory, (sizeof(uint32) * 5 * 256), P_MiscCache); Bitmap_CalcHistogram(BitmapInfo->HistogramVals, BitmapInfo->BitmapBuffer, Source->Info.BytesPerPixel, Size); } char *LevelsButtons[5] = { "All", "Red", "Green", "Blue", "Alpha" }; for (int i = 0; i < 5; i++) { if (ImGui::Button(LevelsButtons[i])) { BitmapInfo->LevelsSelector = i; } if (i != 4) { ImGui::SameLine(); } } real32 *Min, *Mid, *Max; if (BitmapInfo->LevelsSelector == 0) { Min = &Effect->Property[0].CurrentValue.f; Mid = &Effect->Property[1].CurrentValue.f; Max = &Effect->Property[2].CurrentValue.f; } else { Min = &Effect->Property[3].CurrentValue.col.E[BitmapInfo->LevelsSelector - 1]; Mid = &Effect->Property[4].CurrentValue.col.E[BitmapInfo->LevelsSelector - 1]; Max = &Effect->Property[5].CurrentValue.col.E[BitmapInfo->LevelsSelector - 1]; } if (BitmapInfo->LevelsSelector == 0) { ImGui::PushStyleColor(ImGuiCol_PlotHistogram, ImVec4(0.6f, 0.6f, 0.6f, 1.0f)); ImGui::PushStyleColor(ImGuiCol_PlotHistogramHovered, ImVec4(0.6f, 0.6f, 0.6f, 1.0f)); } else if (BitmapInfo->LevelsSelector == 1) { ImGui::PushStyleColor(ImGuiCol_PlotHistogram, ImVec4(0.9f, 0.6f, 0.6f, 1.0f)); ImGui::PushStyleColor(ImGuiCol_PlotHistogramHovered, ImVec4(0.9f, 0.6f, 0.6f, 1.0f)); } else if (BitmapInfo->LevelsSelector == 2) { ImGui::PushStyleColor(ImGuiCol_PlotHistogram, ImVec4(0.6f, 0.9f, 0.6f, 1.0f)); ImGui::PushStyleColor(ImGuiCol_PlotHistogramHovered, ImVec4(0.6f, 0.9f, 0.6f, 1.0f)); } else if (BitmapInfo->LevelsSelector == 3) { ImGui::PushStyleColor(ImGuiCol_PlotHistogram, ImVec4(0.6f, 0.6f, 0.9f, 1.0f)); ImGui::PushStyleColor(ImGuiCol_PlotHistogramHovered, ImVec4(0.6f, 0.6f, 0.9f, 1.0f)); } else if (BitmapInfo->LevelsSelector == 4) { ImGui::PushStyleColor(ImGuiCol_PlotHistogram, ImVec4(0.9f, 0.9f, 0.9f, 1.0f)); ImGui::PushStyleColor(ImGuiCol_PlotHistogramHovered, ImVec4(0.9f, 0.9f, 0.9f, 1.0f)); } float *values = (float *)BitmapInfo->HistogramVals + 256*BitmapInfo->LevelsSelector; int values_count = 256; int values_offset = 0; float scale_min = FLT_MIN; float scale_max = FLT_MAX; ImVec2 graph_size = ImVec2(0, 250); int stride = sizeof(float); // The default histogram is good enough for what we need. ImGui::PlotHistogram("##histo", values, values_count, values_offset, NULL, scale_min, scale_max, graph_size, stride); // TODO(fox): Figure out the proper way to represent these IDs. if (ImGui::SliderLevels("##one", "##two", "three", Mid, Min, Max)) State->UpdateFrame = true; ImGui::PopStyleColor(); ImGui::PopStyleColor(); if (State->UpdateFrame) { uint64 Size = Bitmap_CalcUnpackedBytes(Source->Info.Width, Source->Info.Height, Source->Info.BytesPerPixel); Bitmap_CalcHistogram(BitmapInfo->HistogramVals, BitmapInfo->BitmapBuffer, Source->Info.BytesPerPixel, Size); } ImGui::Button("K"); } ImGui::PopID(); if (IsActive) { if (!Effect->IsSelected && IsActivated) { Effect->IsSelected ^= 1; } if (ImGui::IsMouseDragging(ImGuiMouseButton_Left, -1)) { real32 Effect_Bottom = ImGui::GetCursorScreenPos().y; real32 Effect_Length = Effect_Bottom - Effect_Top; ImGui::SetMouseCursor(ImGuiMouseCursor_Hand); UI->DraggingEffectThreshold += io.MouseDelta.y; if (abs(UI->DraggingEffectThreshold) >= Effect_Length) { int16 Increment = UI->DraggingEffectThreshold/Effect_Length; effect *Effect = Layer->Effect[1]; Layer->Effect[1] = Layer->Effect[0]; Layer->Effect[0] = Effect; // History_Entry_Commit(Memory, action_entry_default, "Change effect order"); // History_Entry_End(Memory); FU = true; UI->DraggingEffectThreshold += -1*Increment*Effect_Length; State->UpdateFrame = true; State->UpdateKeyframes = true; } } } if (FU) { FU = false; break; } } } else { char buf[256]; sprintf(buf, "Properties: empty###Properties"); ImGui::Begin(buf); if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) UI->FocusedWindow = focus_properties; } ImGui::End(); } static void ImGui_Viewport(project_data File, project_state *State, ui *UI, memory *Memory, comp_buffer CompBuffer, ImGuiIO io, GLuint textureID) { bool open = true; ImGui::Begin("Viewport", &open, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse); if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) UI->FocusedWindow = focus_viewport; // Primarily taken from the Custom Rendering section of the demo ImVec2 ViewportMin = ImGui::GetCursorScreenPos(); ImVec2 ViewportScale = ImGui::GetContentRegionAvail(); ViewportScale.y -= ImGui::GetFontSize()*0.5; if (ViewportScale.x < 50.0f) ViewportScale.x = 50.0f; if (ViewportScale.y < 50.0f) ViewportScale.y = 50.0f; ImVec2 ViewportMax = ImVec2(ViewportMin.x + ViewportScale.x, ViewportMin.y + ViewportScale.y); if (UI->Initializing) { UI->CompZoom = ImVec2(CompBuffer.Width, CompBuffer.Height); UI->CompPos = ImVec2(ViewportMin.x + ((ViewportMax.x - ViewportMin.x)/2 - UI->CompZoom.x/2), ViewportMin.y + ((ViewportMax.y - ViewportMin.y)/2 - UI->CompZoom.y/2)); } ImDrawList* draw_list = ImGui::GetWindowDrawList(); draw_list->AddRectFilled(ViewportMin, ViewportMax, IM_COL32(50, 50, 50, 255)); draw_list->AddRect(ViewportMin, ViewportMax, IM_COL32(255, 255, 255, 255)); // Actual composition texture draw_list->PushClipRect(ViewportMin, ViewportMax, true); draw_list->AddImage((void *)(intptr_t)textureID, ImVec2(UI->CompPos.x, UI->CompPos.y), ImVec2(UI->CompPos.x + UI->CompZoom.x, UI->CompPos.y + UI->CompZoom.y)); draw_list->PopClipRect(); // UI+interaction for layer if (State->MostRecentlySelectedLayer > -1) { project_layer *Layer = File.Layer[State->MostRecentlySelectedLayer]; source *Source = Layer->Source; // Anchor point UI ImVec2 AUV = ImVec2(Layer->x.CurrentValue.f / CompBuffer.Width, Layer->y.CurrentValue.f / CompBuffer.Height); ImVec2 ScreenAP = ImVec2(UI->CompPos.x + AUV.x * UI->CompZoom.x, UI->CompPos.y + AUV.y * UI->CompZoom.y); draw_list->AddNgon(ScreenAP, 20, ImGui::GetColorU32(ImGuiCol_ScrollbarGrab), 8, 10.0f); // Mask UI if (Layer->NumberOfMasks) { for (int i = 0; i < Layer->NumberOfMasks; i++) { mask *Mask = &Layer->Mask[i]; ImGui::PushID(i); real32 PointSize = 40; for (int p = 0; p < Mask->NumberOfPoints; p++) { mask_point *Point0 = &Mask->Point[p]; mask_point *Point1 = &Mask->Point[p+1]; if (p+1 == Mask->NumberOfPoints) Point1 = &Mask->Point[0]; // NOTE(fox): I want to keep operations in local space under the v2 data // type and operations in screen space under ImVec2. v2 Point0_Pos = Point0->Pos; v2 Point0_Pos_Left = Point0_Pos + Point0->TangentLeft; v2 Point0_Pos_Right = Point0_Pos + Point0->TangentRight; v2 Point1_Pos = Point1->Pos; v2 Point1_Pos_Left = Point1_Pos + Point1->TangentLeft; v2 Point1_Pos_Right = Point1_Pos + Point1->TangentRight; ImVec2 Point0_ScreenPos = Layer_LocalToScreenSpace(Layer, UI, CompBuffer, Point0_Pos); ImVec2 Point0_ScreenPos_Left = Layer_LocalToScreenSpace(Layer, UI, CompBuffer, Point0_Pos_Left); ImVec2 Point0_ScreenPos_Right = Layer_LocalToScreenSpace(Layer, UI, CompBuffer, Point0_Pos_Right); ImVec2 Point1_ScreenPos = Layer_LocalToScreenSpace(Layer, UI, CompBuffer, Point1_Pos); ImVec2 Point1_ScreenPos_Left = Layer_LocalToScreenSpace(Layer, UI, CompBuffer, Point1_Pos_Left); // ImVec2 Point1_ScreenPos_Right = Layer_LocalToScreenSpace(Layer, UI, CompBuffer, Point1_Pos_Right); ImGui::PushID(p); ImU32 col = ImGui::GetColorU32(ImGuiCol_ScrollbarGrab); // The handle itself col = ImGui::GetColorU32(ImGuiCol_ButtonHovered); if (Point0->HandleBezier) { draw_list->AddNgon(Point0_ScreenPos_Left, 10, col, 8, 5.0f); draw_list->AddNgon(Point0_ScreenPos_Right, 10, col, 8, 5.0f); draw_list->AddLine(Point0_ScreenPos, Point0_ScreenPos_Left, col, 2.0f); draw_list->AddLine(Point0_ScreenPos, Point0_ScreenPos_Right, col, 2.0f); } draw_list->AddNgon(Point0_ScreenPos, 10, col, 8, 5.0f); int max = 1; if (Point0->HandleBezier && Point0->IsSelected) { max = 3; } for (int b = 0; b < max; b++) { ImGui::PushID(b); if (b == 0) { ImGui::SetCursorScreenPos(Point0_ScreenPos - ImVec2(PointSize/2, PointSize/2)); } else if (b == 1) { ImGui::SetCursorScreenPos(Point0_ScreenPos_Left - ImVec2(PointSize/2, PointSize/2)); } else { ImGui::SetCursorScreenPos(Point0_ScreenPos_Right - ImVec2(PointSize/2, PointSize/2)); } ImGui::InvisibleButton("##point", ImVec2(PointSize, PointSize), ImGuiButtonFlags_MouseButtonLeft); if (ImGui::IsItemHovered()) { ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeEW); } if (ImGui::IsItemHovered() && ImGui::IsKeyPressed(ImGuiKey_Backspace)) { Mask_DeletePoint(Memory, Mask, p); State->UpdateFrame = true; } if (ImGui::IsItemActivated() && b == 0) { if (p == 0 && State->Pen.IsActive) { History_Entry_Commit(Memory, action_entry_default, "Close mask path"); History_Action_Change_SwapBool(Memory, &State->Pen.IsActive); History_Action_Change_SwapBool(Memory, &Mask->IsClosed); // State->Pen.IsActive = false; // Mask->IsClosed = true; History_Entry_End(Memory); } else if (io.KeyAlt) { History_Entry_Commit(Memory, action_entry_default, "Switch handles on point"); History_Action_Change_SwapBool(Memory, &Point0->HandleBezier); History_Entry_End(Memory); } Point0->IsSelected = true; } if (ImGui::IsItemActive()) { ImVec2 MouseIncrement = io.MouseDelta * (ImVec2(CompBuffer.Width, CompBuffer.Height) / UI->CompZoom); if (b == 0) { Layer_CalcRotatedOffset(Layer, V2(MouseIncrement), V2(1, 1), &Point0->Pos.x, &Point0->Pos.y); } else if (b == 1) { Layer_CalcRotatedOffset(Layer, V2(MouseIncrement), V2(1, 1), &Point0->TangentLeft.x, &Point0->TangentLeft.y); } else { Layer_CalcRotatedOffset(Layer, V2(MouseIncrement), V2(1, 1), &Point0->TangentRight.x, &Point0->TangentRight.y); } State->UpdateFrame = true; } ImGui::PopID(); } // The bezier path if (Mask->NumberOfPoints == 1 || (p+1 == Mask->NumberOfPoints && !Mask->IsClosed)) { ImGui::PopID(); continue; } ImU32 col2 = ImGui::GetColorU32(ImGuiCol_Button); if (Point0->HandleBezier && Point1->HandleBezier) { draw_list->AddBezierCubic(Point0_ScreenPos, Point0_ScreenPos_Right, Point1_ScreenPos_Left, Point1_ScreenPos, col2, 6.0f, 0); } else if (Point0->HandleBezier) { draw_list->AddBezierCubic(Point0_ScreenPos, Point0_ScreenPos_Right, Point1_ScreenPos, Point1_ScreenPos, col2, 6.0f, 0); } else if (Point1->HandleBezier) { draw_list->AddBezierCubic(Point0_ScreenPos, Point0_ScreenPos, Point1_ScreenPos_Left, Point1_ScreenPos, col2, 6.0f, 0); } else { draw_list->AddLine(Point0_ScreenPos, Point1_ScreenPos, col2, 6.0f); } if (Point0->HandleBezier && Point1->HandleBezier) { if (ImGui::BezierInteractive(Point0_ScreenPos, Point0_ScreenPos_Right, Point1_ScreenPos_Left, Point1_ScreenPos) && State->Tool == tool_pen) { // Using a button like this may be kinda janky, but it gives us access // to all of ButtonBehavior and the ID system without having to write on top of ImGui's. ImGui::SetCursorScreenPos(io.MousePos - ImVec2(5,5)); ImGui::Button("maskbezier", ImVec2(10, 10)); if (ImGui::IsItemHovered()) { #if DEBUG // Code that basically mimics Mask_AddPointToCurve but visualized in screen space, for checking bugs. v2 LayerPoint = Layer_ScreenSpaceToLocal(Layer, UI, CompBuffer, ViewportMin, io.MousePos); real32 ratio = Bezier_CubicRatioOfPoint(Point0_Pos, Point0_Pos_Right, Point1_Pos_Left, Point1_Pos, LayerPoint); draw_list->AddNgon(io.MousePos, 2, col, 8, 5.0f); ImVec2 RatioLeft = ImGui::RatioToPoint(Point0_ScreenPos, Point0_ScreenPos_Right, ratio); ImVec2 RatioRight = ImGui::RatioToPoint(Point1_ScreenPos_Left, Point1_ScreenPos, ratio); ImVec2 RatioTop = ImGui::RatioToPoint(Point0_ScreenPos_Right, Point1_ScreenPos_Left, ratio); ImVec2 TangentLeft = ImGui::RatioToPoint(RatioLeft, RatioTop, ratio); ImVec2 TangentRight = ImGui::RatioToPoint(RatioTop, RatioRight, ratio); draw_list->AddLine(RatioLeft, RatioTop, col, 2.0f); draw_list->AddLine(RatioRight, RatioTop, col, 2.0f); draw_list->AddLine(TangentLeft, TangentRight, col, 2.0f); #endif } if (ImGui::IsItemActivated() && io.KeyCtrl) { v2 LayerPoint = Layer_ScreenSpaceToLocal(Layer, UI, CompBuffer, ViewportMin, io.MousePos); real32 ratio = Bezier_CubicRatioOfPoint(Point0_Pos, Point0_Pos_Right, Point1_Pos_Left, Point1_Pos, LayerPoint); Mask_AddPointToCurve(Memory, Mask, p, ratio); } } } else { if (ImGui::LineInteractive(Point0_ScreenPos, Point1_ScreenPos) && State->Tool == tool_pen) { ImGui::SetCursorScreenPos(io.MousePos - ImVec2(5,5)); ImGui::Button("maskline", ImVec2(10, 10)); if (ImGui::IsItemHovered()) { draw_list->AddNgon(io.MousePos, 2, col, 8, 5.0f); } if (ImGui::IsItemActivated() && io.KeyCtrl) { v2 LayerPoint = Layer_ScreenSpaceToLocal(Layer, UI, CompBuffer, ViewportMin, io.MousePos); Mask_AddPointToLine(Mask, p, LayerPoint); } } } ImGui::PopID(); } ImGui::PopID(); } } } // Interactions for dragging and zooming ImGui::SetCursorScreenPos(ViewportMin); real32 ButtonSize = 20; for (int t = 0; t < tool_count; t++) { ImGui::PushID(t); bool32 Selected = (State->Tool == t); if (ImGui::Selectable(ToolName[t], Selected, 0, ImVec2(ButtonSize*2, ButtonSize))) { State->Tool = (tool)t; } ImGui::PopID(); } ImGui::InvisibleButton("canvas", ViewportScale, ImGuiButtonFlags_MouseButtonLeft | ImGuiButtonFlags_MouseButtonRight); bool32 IsHovered = ImGui::IsItemHovered(); bool32 IsActive = ImGui::IsItemActive(); bool32 IsActivated = ImGui::IsItemActivated(); bool32 IsDeactivated = ImGui::IsItemDeactivated(); if (State->MostRecentlySelectedLayer > -1) { project_layer *Layer = File.Layer[State->MostRecentlySelectedLayer]; if (Layer->NumberOfMasks == 0) { if (IsActivated && ImGui::IsMouseDown(ImGuiMouseButton_Left)) { if (State->Tool == tool_pen && !State->Pen.IsActive) { State->Pen.IsActive = true; Layer->NumberOfMasks++; } } } if (State->Pen.IsActive && !ImGui::IsKeyDown(ImGuiKey_Z)) { if (IsActivated) { v2 LayerPos = Layer_ScreenSpaceToLocal(Layer, UI, CompBuffer, ViewportMin, io.MousePos); Mask_PushPoint(&Layer->Mask[Layer->NumberOfMasks-1], LayerPos); } if (IsActive) { mask *Mask = &Layer->Mask[Layer->NumberOfMasks-1]; mask_point *CurrentPoint = &Mask->Point[Mask->NumberOfPoints-1]; v2 CompUV = ImGui_ScreenPointToCompUV(ViewportMin, UI->CompPos, UI->CompZoom, io.MousePos); v2 LayerUV = CompUVToLayerUV(Layer, &CompBuffer, CompUV); v2 LayerPos = LayerUV * V2(Layer->Source->Info.Width, Layer->Source->Info.Height); v2 OffsetPos = CurrentPoint->Pos - LayerPos; CurrentPoint->HandleBezier = true; CurrentPoint->TangentRight = -OffsetPos; CurrentPoint->TangentLeft = OffsetPos; } // Escape can be pressed to exit point-adding mode or to delete the // mask if it was just created. if (ImGui::IsKeyPressed(ImGuiKey_Escape)) { mask *Mask = &Layer->Mask[Layer->NumberOfMasks-1]; if (IsActive && Mask->NumberOfPoints == 1) { Layer->NumberOfMasks--; Mask->NumberOfPoints = 0; State->Pen.IsActive = false; } else { History_Entry_Commit(Memory, action_entry_default, "Path adding exited"); History_Action_Change_SwapBool(Memory, &State->Pen.IsActive); History_Entry_End(Memory); } IsDeactivated = false; // just in case escape and mouse release happen simultaneously } mask *Mask = &Layer->Mask[Layer->NumberOfMasks-1]; if (IsDeactivated) { mask *Mask = &Layer->Mask[Layer->NumberOfMasks-1]; if (Mask->NumberOfPoints == 1) { // NOTE(fox): We have to use this slightly janky way of writing to // the history tree since we can only create entries/actions once we // know the user is committed to them. Might write an escapable // entry mode if we do this often. uint16 PreviousNumberOfMasks = Layer->NumberOfMasks - 1; uint16 PreviousNumberOfPoints = Mask->NumberOfPoints - 1; bool32 NotActive = false; History_Entry_Commit(Memory, action_entry_default, "Create mask"); History_Action_Change(Memory, &State->Pen.IsActive, &NotActive, &State->Pen.IsActive, action_type_change_u32); History_Action_Change(Memory, &Layer->NumberOfMasks, &PreviousNumberOfMasks, &Layer->NumberOfMasks, action_type_change_u16); History_Action_Change(Memory, &Mask->NumberOfPoints, &PreviousNumberOfPoints, &Mask->NumberOfPoints, action_type_change_u16); History_Entry_End(Memory); } else { uint16 PreviousNumberOfPoints = Mask->NumberOfPoints - 1; mask_point *CurrentPoint = &Mask->Point[Mask->NumberOfPoints-1]; History_Entry_Commit(Memory, action_entry_default, "Add point"); History_Action_Change(Memory, &Mask->NumberOfPoints, &PreviousNumberOfPoints, &Mask->NumberOfPoints, action_type_change_u16); History_Action_StoreData(Memory, &CurrentPoint, sizeof(mask_point)); History_Entry_End(Memory); } } if (State->Tool != tool_pen) { State->Pen.IsActive = false; } } } ImGui::OpenPopupOnItemClick("context", ImGuiPopupFlags_MouseButtonMiddle); if (ImGui::BeginPopup("context")) { if (ImGui::MenuItem("Scalar", NULL, false, InstructionMode != instruction_mode_scalar)) { InstructionMode = instruction_mode_scalar; State->UpdateFrame = true; } #if ARM if (ImGui::MenuItem("NEON", NULL, false, InstructionMode != instruction_mode_neon)) { InstructionMode = instruction_mode_neon; State->UpdateFrame = true; } #else if (ImGui::MenuItem("SSE", NULL, false, InstructionMode != instruction_mode_sse)) { InstructionMode = instruction_mode_sse; State->UpdateFrame = true; } if (ImGui::MenuItem("AVX2", NULL, false, InstructionMode != instruction_mode_avx)) { InstructionMode = instruction_mode_avx; State->UpdateFrame = true; } #endif ImGui::EndPopup(); } if (IsHovered && IsActivated && ImGui::IsMouseDown(ImGuiMouseButton_Left)) { // Point to zoom in on if Z is held UI->TempZoomRatio = ImGui_ScreenPointToCompUV(ViewportMin, UI->CompPos, UI->CompZoom, io.MousePos); // Layer selection if (!ImGui::IsKeyDown(ImGuiKey_Z) || !State->Pen.IsActive) { for (int i = File.NumberOfLayers - 1; i >= 0; i--) { project_layer *Layer = File.Layer[i]; if (!io.KeyShift) DeselectAllLayers(&File, State); v2 LayerUV = CompUVToLayerUV(Layer, &CompBuffer, UI->TempZoomRatio); if (TestUV(LayerUV) && !Layer->IsSelected) { SelectLayer(Layer, State, i); break; } } } } if (IsActive && ImGui::IsMouseDragging(ImGuiMouseButton_Right, -1.0f)) { UI->CompPos.x += io.MouseDelta.x; UI->CompPos.y += io.MouseDelta.y; } if (IsActive && ImGui::IsMouseDragging(ImGuiMouseButton_Left, -1.0f) && ImGui::IsKeyDown(ImGuiKey_Z)) { real32 Distance = io.MouseDelta.x + io.MouseDelta.y; UI->CompZoom.x += (Distance)*(real32)CompBuffer.Width/CompBuffer.Height; UI->CompZoom.y += (Distance); UI->CompPos.x -= ((Distance)*(real32)CompBuffer.Width/CompBuffer.Height)*UI->TempZoomRatio.x; UI->CompPos.y -= Distance*UI->TempZoomRatio.y; } ImGui::SetCursorScreenPos(ImVec2(ViewportMin.x, ViewportMin.y + ViewportScale.y - ImGui::GetFontSize()*1.5)); ImGui::Text("%.1f", 100.0f * (UI->CompZoom.x / CompBuffer.Width)); if (State->MsgTime > 0) { ImGui::SameLine(); ImGui::SetCursorPosX((ViewportScale.x / 5)*4); ImGui::Text(State->Msg); State->MsgTime--; } ImGui::End(); } // 1 for left, 2 for right, 3 for both static bool32 ImGui_SlidingLayer(project_layer *Layer, real32 *DraggingThreshold, real32 Delta, int16 TimelineZoom, int16 Side) { bool32 Result = 0; if (ImGui::IsItemActivated()) { // if (Side & 1) // Layer->StartFrame += Increment; // if (Side & 2) // Layer->EndFrame += Increment; // if (Side == 3) { } if (ImGui::IsItemActive() && ImGui::IsMouseDragging(ImGuiMouseButton_Left, -1)) { ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeEW); *DraggingThreshold += Delta; if (abs(*DraggingThreshold) >= TimelineZoom) { int16 Increment = *DraggingThreshold/TimelineZoom; if (Side & 1) Layer->StartFrame += Increment; if (Side & 2) Layer->EndFrame += Increment; if (Side == 3) { // TODO(fox): Make frame offset in keyframes local! IncrementKeyframesInLayer(Layer, Increment); if (Layer->Source->SourceType == source_type_video) { Layer->BitmapInfo.FrameOffset += Increment; } } *DraggingThreshold += -1*Increment*TimelineZoom; } Result = 1; } return Result; } static void ImGui_File(project_data *File, project_state *State, memory *Memory, ui *UI, ImGuiIO io) { ImGui::Begin("Files"); ImGui::Text("Application average %.3f ms/frame (%.1f FPS)", 1000.0f / io.Framerate, io.Framerate); #if DEBUG if (State->DemoButton) { if (ImGui::Button("Generate demo scene")) { CreateDemoScene(File, State, Memory); State->UpdateKeyframes = true; State->UpdateFrame = true; State->DemoButton = false; } } if (State->GridButton) { ImGui::SameLine(); if (ImGui::Button("Generate square grid")) { // CreateGrid(File, Memory); State->UpdateKeyframes = true; State->UpdateFrame = true; State->GridButton = false; } } #endif ImGui::Text("Sources:"); for (int i = 0; i < File->NumberOfSources; i++) { bool32 Test = false; if (File->SourceSelected == i) Test = true; ImGui::Selectable(File->Source[i].Path, Test); if (ImGui::IsItemClicked()) File->SourceSelected = i; if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) File->SourceSelected = i; ImGui::OpenPopupOnItemClick("sourcecontext", ImGuiPopupFlags_MouseButtonRight); } if (ImGui::BeginPopup("sourcecontext")) { if (ImGui::MenuItem("Create layer from source")) { Layer_CreateFromSource(File, State, Memory, &File->Source[File->SourceSelected]); } ImGui::EndPopup(); } static char Input[1024]; ImGui::InputTextWithHint("##sourceinput", "Input file path of source...", Input, STRING_SIZE); if (ImGui::IsItemDeactivated() && ImGui::IsKeyPressed(ImGuiKey_Enter)) { Source_Generate(File, State, Memory, Input); } #if DEBUG for (int i = 0; i < Debug.Temp.WatchedProperties; i++) { if (Debug.Temp.DebugPropertyType[i] == d_float) { ImGui::Text("%s: %f", Debug.Temp.String[i], Debug.Temp.Val[i].f); } else if (Debug.Temp.DebugPropertyType[i] == d_int) { ImGui::Text("%s: %i", Debug.Temp.String[i], Debug.Temp.Val[i].i); } else if (Debug.Temp.DebugPropertyType[i] == d_uint) { ImGui::Text("%s: %u", Debug.Temp.String[i], Debug.Temp.Val[i].u); } } Debug.Temp = {}; #endif ImGui::End(); } static void ImGui_EffectsPanel(project_data *File, project_state *State, memory *Memory, ui *UI, ImGuiIO io) { ImGui::Begin("Effects list", NULL); if (State->RerouteEffects) { ImGui::SetKeyboardFocusHere(); State->RerouteEffects = 0; } int value_changed = ImGui::InputText("Effect name...", State->filter.InputBuf, IM_ARRAYSIZE(State->filter.InputBuf), ImGuiInputTextFlags_CallbackCompletion, EffectConsoleCallback); if (Hacko) { if (!io.KeyShift) EffectSel++; else EffectSel--; Hacko = 0; } if (value_changed) { State->filter.Build(); EffectSel = -1; } // Enter conveniently deactivates the InputText field if (ImGui::IsItemDeactivated() && ImGui::IsKeyPressed(ImGuiKey_Enter)) { int32 p = 0; for (int32 i = 0; i < AmountOf(EffectList); i++) { if (State->filter.PassFilter(EffectList[i].Name)) { if (EffectSel == p && State->MostRecentlySelectedLayer != -1) { AddEffect(File->Layer[State->MostRecentlySelectedLayer], Memory, i); State->UpdateFrame = true; } p++; } } EffectSel = -1; } int32 p = 0; for (int32 i = 0; i < AmountOf(EffectList); i++) { if (State->filter.PassFilter(EffectList[i].Name)) { bool t = false; if (EffectSel == p) { t = true; } ImGui::Selectable(EffectList[i].Name, &t); if (ImGui::IsItemClicked()) { if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left) && State->MostRecentlySelectedLayer != -1) { AddEffect(File->Layer[State->MostRecentlySelectedLayer], Memory, i); State->UpdateFrame = true; } } p++; } } ImGui::End(); } static void ImGui_ProcessInputs(project_data *File, project_state *State, comp_buffer *CompBuffer, memory *Memory, ui *UI, ImGuiIO io) { if (io.KeysData[ImGuiKey_Q].Down) { State->IsRunning = false; } #if DEBUG // ImGui::SaveIniSettingsToDisk("asda"); #endif if (ImGui::IsKeyPressed(ImGuiKey_R)) { /* UI->Y_TimelinePercentZoomed = UI->Default_Y_TimelinePercentZoomed; UI->Y_TimelinePercentOffset = UI->Default_Y_TimelinePercentOffset; keyframe *Keyframe = KeyframeLookup(&File->Layer[0]->x, 0); Keyframe->Value.f = -10; Keyframe = KeyframeLookup(&File->Layer[0]->x, 1); Keyframe->Value.f = -5; Keyframe = KeyframeLookup(&File->Layer[0]->x, 2); Keyframe->Value.f = 0; Keyframe = KeyframeLookup(&File->Layer[0]->x, 3); Keyframe->Value.f = 5; Keyframe = KeyframeLookup(&File->Layer[0]->x, 4); Keyframe->Value.f = 10; */ } if (ImGui::IsKeyPressed(ImGuiKey_D)) { IncrementFrame(File, -1); State->UpdateFrame = true; State->UpdateKeyframes = true; } if (ImGui::IsKeyPressed(ImGuiKey_F)) { IncrementFrame(File, 1); State->UpdateFrame = true; State->UpdateKeyframes = true; } if (ImGui::IsKeyPressed(ImGuiKey_Z)) { if (io.KeyCtrl && io.KeyShift) { History_Redo(Memory); } else if (io.KeyCtrl) { History_Undo(Memory); } State->UpdateFrame = true; State->UpdateKeyframes = true; } if (ImGui::IsKeyPressed(ImGuiKey_V)) { if (State->Tool == tool_pen) { State->Tool = tool_default; } else { State->Tool = tool_pen; } } if (ImGui::IsKeyPressed(ImGuiKey_Space)) { if (io.KeyShift) { State->RerouteEffects = true; } else { State->IsPlaying ^= 1; } } if (State->IsPlaying && !IsRendering) { IncrementFrame(File, 1); State->UpdateFrame = true; State->UpdateKeyframes = true; } // if (ImGui::IsKeyPressed(ImGuiKey_R) && State->NumberOfSelectedLayers) // TransformsInteract(File, State, Memory, UI, sliding_rotation); if (ImGui::IsKeyPressed(ImGuiKey_S) && State->NumberOfSelectedLayers) TransformsInteract(File, State, Memory, UI, sliding_scale); if (ImGui::IsKeyPressed(ImGuiKey_G) && State->NumberOfSelectedLayers) TransformsInteract(File, State, Memory, UI, sliding_position); if (ImGui::IsKeyPressed(ImGuiKey_A) && State->NumberOfSelectedLayers) TransformsInteract(File, State, Memory, UI, sliding_anchorpoint); if (ImGui::IsKeyPressed(ImGuiKey_1)) LoadTestFootage(File, State, Memory); if (ImGui::IsKeyPressed(ImGuiKey_D) && io.KeyCtrl) { } if (ImGui::IsKeyPressed(ImGuiKey_Delete)) { switch (State->RecentSelectionType) { case selection_none: { } break; case selection_layer: { } break; case selection_effect: { } break; case selection_keyframe: { DeleteSelectedKeyframes(File, Memory); State->UpdateKeyframes = true; State->UpdateFrame = true; } case selection_maskpoint: { } case selection_source: { } break; } } if (State->IsInteracting) { ImVec2 MouseIncrement = io.MouseDelta * (ImVec2(CompBuffer->Width, CompBuffer->Height) / UI->CompZoom); project_layer *Layer = File->Layer[State->MostRecentlySelectedLayer]; switch (State->TransformsHotkeyInteract) { case sliding_position: { Layer->x.CurrentValue.f += MouseIncrement.x; Layer->y.CurrentValue.f += MouseIncrement.y; } break; case sliding_anchorpoint: { source *Source = Layer->Source; Layer->x.CurrentValue.f += MouseIncrement.x; Layer->y.CurrentValue.f += MouseIncrement.y; Layer_CalcRotatedOffset(Layer, V2(MouseIncrement), V2(Source->Info.Width, Source->Info.Height), &Layer->ax.CurrentValue.f, &Layer->ay.CurrentValue.f); } break; case sliding_rotation: { Layer->rotation.CurrentValue.f += MouseIncrement.x / 10.0; } break; case sliding_scale: { Layer->scale.CurrentValue.f += MouseIncrement.x / 200.0; } break; } if (ImGui::IsKeyPressed(ImGuiKey_Escape)) { switch (State->TransformsHotkeyInteract) { case sliding_position: { Layer->x.CurrentValue.f = State->InteractCache[0]; Layer->y.CurrentValue.f = State->InteractCache[1]; } break; case sliding_anchorpoint: { Layer->x.CurrentValue.f = State->InteractCache[0]; Layer->y.CurrentValue.f = State->InteractCache[1]; Layer->ax.CurrentValue.f = State->InteractCache[2]; Layer->ay.CurrentValue.f = State->InteractCache[3]; } break; case sliding_rotation: { Layer->rotation.CurrentValue.f = State->InteractCache[0]; } break; case sliding_scale: { Layer->scale.CurrentValue.f = State->InteractCache[0]; } break; } State->IsInteracting = false; State->UpdateFrame = true; } if (ImGui::IsMouseDown(ImGuiMouseButton_Left)) { History_Entry_Commit(Memory, action_entry_default, "Tranforms interact"); switch (State->TransformsHotkeyInteract) { case sliding_position: { History_Action_Change(Memory, &Layer->x.CurrentValue.f, &State->InteractCache[0], &Layer->x.CurrentValue.f, action_type_change_r32); History_Action_Change(Memory, &Layer->y.CurrentValue.f, &State->InteractCache[1], &Layer->y.CurrentValue.f, action_type_change_r32); } break; case sliding_anchorpoint: { History_Action_Change(Memory, &Layer->x.CurrentValue.f, &State->InteractCache[0], &Layer->x.CurrentValue.f, action_type_change_r32); History_Action_Change(Memory, &Layer->y.CurrentValue.f, &State->InteractCache[1], &Layer->y.CurrentValue.f, action_type_change_r32); History_Action_Change(Memory, &Layer->ax.CurrentValue.f, &State->InteractCache[2], &Layer->ax.CurrentValue.f, action_type_change_r32); History_Action_Change(Memory, &Layer->ay.CurrentValue.f, &State->InteractCache[3], &Layer->ay.CurrentValue.f, action_type_change_r32); } break; case sliding_rotation: { History_Action_Change(Memory, &Layer->rotation.CurrentValue.f, &State->InteractCache[0], &Layer->rotation.CurrentValue.f, action_type_change_r32); } break; case sliding_scale: { History_Action_Change(Memory, &Layer->scale.CurrentValue.f, &State->InteractCache[0], &Layer->scale.CurrentValue.f, action_type_change_r32); } break; } History_Entry_End(Memory); State->IsInteracting = false; } State->UpdateFrame = true; } if (!ImGui::IsMouseDown(ImGuiMouseButton_Left)) { UI->DraggingLayerThreshold = 0; UI->DraggingTimelineThreshold = 0; UI->DraggingKeyframeThreshold = 0; } } static char ImGuiPrefs[] = "[Window][DockSpaceViewport_11111111]" "\nPos=0,0" "\nSize=3200,1800" "\nCollapsed=0" "\n" "\n[Window][Debug##Default]" "\nPos=60,60" "\nSize=400,400" "\nCollapsed=0" "\n" "\n[Window][Viewport]" "\nPos=528,0" "\nSize=2168,1171" "\nCollapsed=0" "\nDockId=0x00000005,0" "\n" "\n[Window][###Properties]" "\nPos=0,0" "\nSize=526,1171" "\nCollapsed=0" "\nDockId=0x00000003,0" "\n" "\n[Window][Timeline]" "\nPos=0,1173" "\nSize=3200,627" "\nCollapsed=0" "\nDockId=0x00000002,0" "\n" "\n[Window][Dear ImGui Demo]" "\nPos=1881,692" "\nSize=550,680" "\nCollapsed=0" "\n" "\n[Window][Files]" "\nPos=2698,0" "\nSize=502,913" "\nCollapsed=0" "\nDockId=0x00000007,0" "\n" "\n[Window][Effects list]" "\nPos=2698,915" "\nSize=502,256" "\nCollapsed=0" "\nDockId=0x00000008,0" "\n" "\n[Docking][Data]" "\nDockSpace ID=0x8B93E3BD Window=0xA787BDB4 Pos=0,0 Size=3200,1800 Split=Y Selected=0x13926F0B" "\n DockNode ID=0x00000001 Parent=0x8B93E3BD SizeRef=3200,1171 Split=X Selected=0x13926F0B" "\n DockNode ID=0x00000003 Parent=0x00000001 SizeRef=526,1171 Selected=0xDBB8CEFA" "\n DockNode ID=0x00000004 Parent=0x00000001 SizeRef=2672,1171 Split=X Selected=0x13926F0B" "\n DockNode ID=0x00000005 Parent=0x00000004 SizeRef=2115,1171 CentralNode=1 Selected=0x13926F0B" "\n DockNode ID=0x00000006 Parent=0x00000004 SizeRef=502,1171 Split=Y Selected=0x86FA2F90" "\n DockNode ID=0x00000007 Parent=0x00000006 SizeRef=502,913 Selected=0x86FA2F90" "\n DockNode ID=0x00000008 Parent=0x00000006 SizeRef=502,256 Selected=0x812F222D" "\n DockNode ID=0x00000002 Parent=0x8B93E3BD SizeRef=3200,627 HiddenTabBar=1 Selected=0x0F18B61B"; /* // Graph window if (UI->NumberOfGraphsEnabled) { ui_graph Graph = UI->Graph[0]; ImGui::SetCursorScreenPos(ImVec2(ImGui::GetCursorScreenPos().x, TimelineAbsolutePos.y + TimelineSizeWithBorder.y - Graph.WindowYOffset)); ImVec2 GraphMin = ImVec2(TimelineAbsolutePos.x, ImGui::GetCursorScreenPos().y); ImVec2 GraphSize = ImVec2(TimelineSizeWithBorder.x, Graph.WindowYOffset); ImDrawList* draw_list = ImGui::GetWindowDrawList(); draw_list->AddRectFilled(GraphMin, GraphMin + GraphSize, IM_COL32(0, 0, 30, 50)); // draw_list->PushClipRect(GraphMin, GraphMin + GraphSize, true); // draw_list->PopClipRect(); ImVec2 LeftPos[2]; ImVec2 MidPos[2]; ImVec2 RightPos[2]; ImU32 col = ImGui::GetColorU32(ImGuiCol_ScrollbarGrab); for (int b = 0; b < Property->NumberOfTotalKeyframes; b++) { keyframe *Keyframe = KeyframeLookup(Property, b); // int32 Index = KeyframeMemoryToIndex(Property, b); ImGui::PushID(Keyframe); real32 MinVal = Property->LocalMinVal.f; real32 MaxVal = Property->LocalMaxVal.f; // Normalized ratio between the smallest and largest value real32 HandleYRatio = (Keyframe->Value.f - MaxVal) / (MaxVal - MinVal); real32 HandleYRatio_L = (Keyframe->Value.f + Keyframe->TangentLeft.y - MaxVal) / (MaxVal - MinVal); real32 HandleYRatio_R = (Keyframe->Value.f + Keyframe->TangentRight.y - MaxVal) / (MaxVal - MinVal); real32 LocalHandlePosX = UI->TimelineZoom*Keyframe->FrameNumber; real32 LocalHandlePosX_L = LocalHandlePosX + UI->TimelineZoom*Keyframe->TangentLeft.x; real32 LocalHandlePosX_R = LocalHandlePosX + UI->TimelineZoom*Keyframe->TangentRight.x; real32 HandlePosX = TimelineStartingPos.x + LocalHandlePosX - FontHeight*0.5; real32 HandlePosX_L = TimelineStartingPos.x + LocalHandlePosX_L - FontHeight*0.5; real32 HandlePosX_R = TimelineStartingPos.x + LocalHandlePosX_R - FontHeight*0.5; real32 LocalHandlePosY = HandleYRatio * Property->GraphLength; real32 LocalHandlePosY_L = HandleYRatio_L * Property->GraphLength; real32 LocalHandlePosY_R = HandleYRatio_R * Property->GraphLength; real32 HandlePosY = MinPos.y - LocalHandlePosY + Property->GraphYOffset; real32 HandlePosY_L = MinPos.y - LocalHandlePosY_L + Property->GraphYOffset; real32 HandlePosY_R = MinPos.y - LocalHandlePosY_R + Property->GraphYOffset; ImVec2 HandlePos = ImVec2(HandlePosX, HandlePosY); ImVec2 HandlePos_L = ImVec2(HandlePosX_L, HandlePosY_L); ImVec2 HandlePos_R = ImVec2(HandlePosX_R, HandlePosY_R); if (UI->BoxSelectActive && UI->BoxStart.y >= NextY) { if (IsRectTouching(UI->BoxStart, UI->BoxEnd, HandlePos, HandlePos + KeyframeSize)) { Keyframe->IsSelected = true; State->RecentSelectionType = selection_keyframe; } else if (!io.KeyShift) { Keyframe->IsSelected = false; } } ImGui::PushStyleColor(ImGuiCol_Button, col); ImGui::SetCursorScreenPos(ImVec2(HandlePosX - FontHeight*1.5, HandlePosY - FontHeight*1.5)); ImGui::Text("%.02f", Keyframe->Value.f); ImGui::SetCursorScreenPos(ImVec2(HandlePosX - FontHeight*1.0, HandlePosY - FontHeight*1.0)); ImGui::InvisibleButton("##keyframepoint", ImVec2(FontHeight*2, FontHeight*2), ImGuiButtonFlags_MouseButtonLeft | ImGuiButtonFlags_MouseButtonRight); draw_list->AddRect(ImVec2(HandlePosX - FontHeight*0.5, HandlePosY - FontHeight*0.5), ImVec2(HandlePosX + FontHeight*0.5, HandlePosY + FontHeight*0.5), ImGui::GetColorU32(ImGuiCol_ButtonHovered)); ImGui_KeyframeDragging(File, State, UI, Property, b, io, 1); if (Keyframe->IsSelected && Keyframe->Type == bezier) { ImGui::SetCursorScreenPos(ImVec2(HandlePosX_L, HandlePosY_L)); draw_list->AddCircle(ImVec2(HandlePosX_L, HandlePosY_L), 2, col, 16, 1); ImGui::Button("##keyframehandleleft", ImVec2(FontHeight, FontHeight)); ImGui_KeyframeDragging(File, State, UI, Property, b, io, 2); ImGui::SetCursorScreenPos(ImVec2(HandlePosX_R, HandlePosY_R)); ImGui::Button("##keyframehandleright", ImVec2(FontHeight, FontHeight)); ImGui_KeyframeDragging(File, State, UI, Property, b, io, 3); draw_list->AddLine(MidPos[b & 1], RightPos[b & 1], col, 1.0f); draw_list->AddLine(MidPos[b & 1], LeftPos[b & 1], col, 1.0f); } ImGui::PopStyleColor(); ImGui::PopID(); } // TODO(fox): Reformat this so it's all done in one loop. for (int b = 0; b < Property->NumberOfTotalKeyframes; b++) { keyframe *Keyframe = KeyframeLookup(Property, b); real32 MinVal = Property->LocalMinVal.f; real32 MaxVal = Property->LocalMaxVal.f; real32 HandleYRatio = (Keyframe->Value.f - MaxVal) / (MaxVal - MinVal); real32 HandleYRatio_L = (Keyframe->Value.f + Keyframe->TangentLeft.y - MaxVal) / (MaxVal - MinVal); real32 HandleYRatio_R = (Keyframe->Value.f + Keyframe->TangentRight.y - MaxVal) / (MaxVal - MinVal); real32 LocalHandlePosX = UI->TimelineZoom*Keyframe->FrameNumber; real32 LocalHandlePosX_L = LocalHandlePosX + UI->TimelineZoom*Keyframe->TangentLeft.x; real32 LocalHandlePosX_R = LocalHandlePosX + UI->TimelineZoom*Keyframe->TangentRight.x; real32 HandlePosX = TimelineStartingPos.x + LocalHandlePosX - FontHeight*0.5; real32 HandlePosX_L = TimelineStartingPos.x + LocalHandlePosX_L - FontHeight*0.5; real32 HandlePosX_R = TimelineStartingPos.x + LocalHandlePosX_R - FontHeight*0.5; real32 LocalHandlePosY = HandleYRatio * Property->GraphLength; real32 LocalHandlePosY_L = HandleYRatio_L * Property->GraphLength; real32 LocalHandlePosY_R = HandleYRatio_R * Property->GraphLength; real32 HandlePosY = MinPos.y - LocalHandlePosY + Property->GraphYOffset; real32 HandlePosY_L = MinPos.y - LocalHandlePosY_L + Property->GraphYOffset; real32 HandlePosY_R = MinPos.y - LocalHandlePosY_R + Property->GraphYOffset; ImVec2 HandlePos = ImVec2(HandlePosX, HandlePosY); ImVec2 HandlePos_L = ImVec2(HandlePosX_L, HandlePosY_L); ImVec2 HandlePos_R = ImVec2(HandlePosX_R, HandlePosY_R); MidPos[b & 1] = HandlePos; LeftPos[b & 1] = HandlePos_L; RightPos[b & 1] = HandlePos_R; if (b != 0) { if (b & 1) { if (Keyframe->Type == linear) draw_list->AddLine(MidPos[0], MidPos[1], col, 1.0f); else if (Keyframe->Type == bezier) draw_list->AddBezierCubic(MidPos[0], RightPos[0], LeftPos[1], MidPos[1], col, 1.0f, 8); } else { if (Keyframe->Type == linear) draw_list->AddLine(MidPos[1], MidPos[0], col, 1.0f); else if (Keyframe->Type == bezier) draw_list->AddBezierCubic(MidPos[1], RightPos[1], LeftPos[0], MidPos[0], col, 1.0f, 8); } } } // Horiziontal value lines // uint32 LineColor = IM_COL32(200, 200, 200, 40); // for (int i = 0; i < 10; i++) { // real32 YPos = MinPos.y + (UI->TimelineZoom/2 * i) + 5; // ImVec2 Min = ImVec2(TimelineStartingPos.x, YPos); // ImVec2 Max = ImVec2(TimelineStartingPos.x + TimelineSize.x, YPos); // draw_list->AddLine(Min, Max, LineColor); // } draw_list->PopClipRect(); // ImGui::SetCursorScreenPos(ImVec2(MinPos.x, MinPos.y)); // ImGui::Button("##SplitMove", ImVec2(TimelineBorderPadding.x, SidebarSizeWithBorder.y)); // if (ImGui::IsItemActive() && ImGui::IsMouseDragging(ImGuiMouseButton_Left, -1)) // { // UI->TimelineSplit += io.MouseDelta.x; // } ImGui::SetCursorScreenPos(ImVec2(MinPos.x, MinPos.y)); ImGui::InvisibleButton("AnimationCurves", ImVec2(TimelineSize.x - 20, GraphWindowHeight), ImGuiButtonFlags_MouseButtonLeft | ImGuiButtonFlags_MouseButtonRight); // NOTE(fox): I'm reusing this struct for the other // channels, so I'm OR'ing it. Also persists across layers. AnimationCurves.IsItemHovered |= ImGui::IsItemHovered(); AnimationCurves.IsItemActive |= ImGui::IsItemActive(); AnimationCurves.IsItemActivated |= ImGui::IsItemActivated(); AnimationCurves.IsItemDeactivated |= ImGui::IsItemDeactivated(); AnimationCurves.LeftClick |= ImGui::IsMouseDown(ImGuiMouseButton_Left); AnimationCurves.RightClick |= ImGui::IsMouseDown(ImGuiMouseButton_Right); if (AnimationCurves.IsItemHovered && AnimationCurves.IsItemActivated && ImGui::IsMouseDown(ImGuiMouseButton_Right)) { real32 LocalMousePos = io.MousePos.y - MinPos.y - Property->GraphYOffset; UI->TempZoomRatioGraph = LocalMousePos / Property->GraphLength; } // DebugWatchVar("LocalMousePos", &LocalMousePos, d_float); if (AnimationCurves.IsItemActive && ImGui::IsMouseDragging(ImGuiMouseButton_Right, -1)) { Property->GraphLength += io.MouseDelta.x; Property->GraphYOffset -= io.MouseDelta.x*UI->TempZoomRatioGraph; Property->GraphYOffset += io.MouseDelta.y; } } */ #if 0 // Timeline frame ticks ImGui::SetCursorScreenPos(TimelineStartingPos); if (UI->TimelineZoom > 10) { for (float x = 0; x < File->NumberOfFrames + 2; x += 1) { uint32 LineColor = IM_COL32(200, 200, 200, 40); ImVec2 Min = ImVec2(TimelineStartingPos.x + UI->TimelineZoom * x, TimelineStartingPos.y); ImVec2 Max = ImVec2(Min.x + 2, WindowMaxAbs.y); if (x == File->CurrentFrame) continue; draw_list->AddLine(Min, Max, LineColor); char buf2[6]; if (((uint32)x % File->FPS) == 0) sprintf(buf2, "%.2i:%.2i", (uint32)x / File->FPS, (uint32)x % File->FPS); else sprintf(buf2, ":%.2i", (uint32)x % File->FPS); draw_list->AddText(ImVec2(Min.x, TimelineAbsolutePos.y), IM_COL32(200, 200, 200, 130), buf2); } } #else #endif #if 0 // Playhead line uint32 LineColor = IM_COL32(200, 200, 200, 200); ImVec2 Min = PlayheadPos; ImVec2 Max = ImVec2(Min.x + 2, Min.y + TimelineSizeWithBorder.y + TopbarSize.y/2); draw_list->AddLine(Min, Max, LineColor); #endif #if 0 // Scrollbar real32 MaxScrollPos = UI->TimelineZoom * (File->NumberOfFrames + 1); real32 ScrollRatio = Normalize(-1 * (UI->ScrollXOffset / MaxScrollPos)); DebugWatchVar("ScrollXOffset: %.2f", &UI->ScrollXOffset, d_float); real32 BarRatio = (MaxZoom / UI->TimelineZoom); DebugWatchVar("BarRatio: %.2f", &BarRatio, d_float); DebugWatchVar("Timelinezoom: %.2f", &UI->TimelineZoom, d_float); // ImVec2 SMinP = TimelineAbsolutePos + ImVec2(ScrollRatio * TimelineSizeWithBorder.x, TimelineSize.y - 200); // ImVec2 SMaxP = TimelineAbsolutePos + ImVec2((ScrollRatio + BarRatio) * TimelineSizeWithBorder.x, TimelineSize.y + 200); // draw_list->AddRectFilled(SMinP, SMaxP, // IM_COL32(0, 200, 200, 50)); ImVec2 BarMinPos = ImVec2(ScrollRatio * TimelineSizeWithBorder.x, TimelineSize.y - 50); ImVec2 BarSize = ImVec2(BarRatio * TimelineSizeWithBorder.x, TimelineSize.y + 10); ImVec2 SideSize = ImVec2(FontHeight, 0); ImGui::SetCursorScreenPos(TimelineAbsolutePos + BarMinPos); ImGui::Button("##scrollbarleft", ImVec2(SideSize.x, BarSize.y)); ImGui::SetCursorScreenPos(TimelineAbsolutePos + BarMinPos + SideSize); ImGui::Button("##scrollbarhori", BarSize - SideSize*2); if (ImGui::IsItemActive() && ImGui::IsMouseDragging(ImGuiMouseButton_Left, -1)) { ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeEW); UI->ScrollXOffset -= io.MouseDelta.x / BarRatio; } ImGui::SetCursorScreenPos(TimelineAbsolutePos + BarMinPos + ImVec2(BarSize.x, 0) - SideSize); ImGui::Button("##scrollbarright", ImVec2(SideSize.x, BarSize.y)); if (ImGui::IsItemActivated()) { UI->TempZoomRatioTimeline = UI->TimelineZoom; } bool32 asda = false; if (ImGui::IsKeyPressed(ImGuiKey_F)) { asda = true; } if ((ImGui::IsItemActive() && ImGui::IsMouseDragging(ImGuiMouseButton_Left, -1)) || asda) { ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeEW); real32 test = asda ? 0.125 : (-io.MouseDelta.x / TimelineSize.x); real32 MouseDelta = TimelineSize.x * test; real32 ZoomAmount = MouseDelta / (TimelineSize.x * (1.0f - test)) * UI->TimelineZoom; UI->TimelineZoom += ZoomAmount/BarRatio; // if (asda) // UI->ScrollXOffset -= MouseDelta / BarRatio; } ImGui::SameLine(); #endif #if 0 for (int i = File->NumberOfLayers - 1; i >= 0; i--) { project_layer *Layer = File->Layer[i]; ImGui::PushID(i); ImGui::SetCursorScreenPos(ImVec2(SidebarStartingPos.x, ImGui::GetCursorScreenPos().y)); draw_list->PushClipRect(SidebarAbsolutePos, SidebarAbsolutePos + TimelineFullSize, true); if (Layer->IsSelected) { real32 Y = ImGui::GetCursorScreenPos().y; draw_list->AddRectFilled(ImVec2(SidebarAbsolutePos.x, Y), ImVec2(TimelineAbsolutePos.x + TimelineSize.x, Y + FontHeight + FramePadding.y*2), IM_COL32(255, 255, 255, 50)); } draw_list->PopClipRect(); ImGui::Button("V"); ImGui::SameLine(); ImGui::Button("I"); ImGui::SameLine(); ImGui::Text(Layer->Name); ImGui::SameLine(); ImGui::Button(BlendmodeNames[Layer->BlendMode]); ImGui::OpenPopupOnItemClick("blendmode_picker", ImGuiPopupFlags_MouseButtonLeft); if (ImGui::BeginPopup("blendmode_picker")) { for (int16 b = 0; b < AmountOf(BlendmodeNames); b++) { if (ImGui::MenuItem(BlendmodeNames[b], NULL, false, Layer->BlendMode != b)) { Layer->BlendMode = (blend_mode)b; State->UpdateFrame = true; } // using IsActivated here instead of above loop doesn't seem to // work; the popup gets closed instead if (ImGui::IsItemHovered() && io.KeyCtrl) { Layer->BlendMode = (blend_mode)b; State->UpdateFrame = true; } } ImGui::EndPopup(); } ImGui::SameLine(); ImGui::SetCursorScreenPos(ImVec2(SidebarStartingPos.x, ImGui::GetCursorScreenPos().y)); ImGui::Button("##mover", ImVec2(SidebarSizeWithBorder.x, FontHeight + FramePadding.y*2)); // Layer dragging interaction if (ImGui::IsItemActive()) { if (ImGui::IsItemActivated() && !Layer->IsSelected) { if (!io.KeyShift) DeselectAllLayers(File, State); SelectLayer(Layer, State, i); } if (ImGui::IsMouseDragging(ImGuiMouseButton_Left, -1) ) { ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeEW); UI->DraggingLayerThreshold -= io.MouseDelta.y; real32 Threshold = FontHeight + FramePadding.y*2; if (abs(UI->DraggingLayerThreshold) >= Threshold) { int16 Increment = UI->DraggingLayerThreshold/abs(UI->DraggingLayerThreshold); MoveLayersByIncrement(File, State, Increment); UI->DraggingLayerThreshold += -1*Increment*Threshold; State->UpdateFrame = true; // Cache.Frame[File->CurrentFrame].Cached = false; } } } // Properties gap ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0.0f, ItemSpacing.y * UI->KeyframeSpacing)); for (int a = 0; a < AmountOf(Layer->Property); a++) { if (Layer->Property[a].IsToggled) { property_channel *Property = &Layer->Property[a]; ImGui::PushID(Property); // if (Property->IsSelected) { // real32 Y = ImGui::GetCursorScreenPos().y; // draw_list->AddRectFilled(ImVec2(SidebarAbsolutePos.x, Y), // ImVec2(TimelineAbsolutePos.x, Y + FontHeight + FramePadding.y*2), // IM_COL32(100, 0, 255, 50)); // } ImGui::SetCursorScreenPos(ImVec2(SidebarStartingPos.x, ImGui::GetCursorScreenPos().y)); ImGui::Text(Property->Name); real32 YInit = ImGui::GetCursorScreenPos().y; ImGui::SameLine(); if (ImGui::Button("K")) Keyframe_Insert(Property, Memory, File->CurrentFrame, Property->CurrentValue.f); ImGui::SameLine(); if (ImGui::Button("G")) { UI->Graph[UI->NumberOfGraphsEnabled].ChannelViewed = Property; UI->NumberOfGraphsEnabled++; } ImGui::SetCursorScreenPos(ImVec2(ImGui::GetCursorScreenPos().x, YInit)); ImGui::PopID(); } } ImGui::PopStyleVar(); ImGui::PopID(); } #endif /* for (int i = File->NumberOfLayers - 1; i >= 0; i--) { // The actual layer bars project_layer *Layer = File->Layer[i]; ImGui::PushID(i); uint16 LayerTLSpan = Layer->EndFrame - Layer->StartFrame; // if (Layer->SourceType == video) { // video_source *Source = (video_source *)Layer->RenderInfo; // real32 XMin = TimelineMinX + UI->TimelineZoom*Source->VideoFrameOffset; // // real32 YMin = StartingCursorPosAbs.y + (FontHeight + FramePadding.y*2 + ItemSpacing.y)*i; // real32 YMin = ImGui::GetCursorScreenPos().y; // draw_list->AddRect(ImVec2(WindowMin.x, YMin), // ImVec2(WindowMaxAbs.x, YMin + FontHeight + FramePadding.y*2), // IM_COL32(255, 255, 255, 50), 2); // } ImGui::SetCursorScreenPos(ImVec2(TimelineStartingPos.x + UI->TimelineZoom*Layer->StartFrame, ImGui::GetCursorScreenPos().y)); ImGui::Button("##leftbound", ImVec2(0.5 * UI->TimelineZoom, 0)); ImGui::SameLine(); if (ImGui::IsItemHovered()) { ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeEW); } ImGui_SlidingLayer(Layer, &UI->DraggingKeyframeThreshold, io.MouseDelta.x, UI->TimelineZoom, 1); // TODO(fox): Investigate why this button doesn't get lined up with // leftbound in certain cases. (i.e. rotation property expanded with keyframes) ImGui::Button("##layer", ImVec2((LayerTLSpan * UI->TimelineZoom), 0)); ImGui::SameLine(); if (ImGui::IsItemClicked()) { if (!io.KeyShift) DeselectAllLayers(File, State); SelectLayer(Layer, State, i); } if (ImGui_SlidingLayer(Layer, &UI->DraggingLayerThreshold, io.MouseDelta.x, UI->TimelineZoom, 3)) { // TODO(fox): This will be removed once video caching is implemented. UI->TemporaryUpdateOverride = true; } ImGui::Button("##rightbound", ImVec2(0.5 * UI->TimelineZoom, 0)); if (ImGui::IsItemHovered()) { ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeEW); } ImGui_SlidingLayer(Layer, &UI->DraggingKeyframeThreshold, io.MouseDelta.x, UI->TimelineZoom, 2); for (int a = Layer->StartFrame; a < Layer->EndFrame; a++) { cached_bitmap *Bitmap = Cache_CheckBitmap(Layer->Source, &Layer->BitmapInfo, Memory, a); if (Bitmap) { ImVec2 MinPos = ImVec2(TimelineStartingPos.x + UI->TimelineZoom * a, ImGui::GetCursorScreenPos().y); draw_list->AddRect(MinPos, ImVec2(MinPos.x + UI->TimelineZoom, MinPos.y + 2.0f), ImColor(0, 255, 0, 255)); } } ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0.0f, ItemSpacing.y * UI->KeyframeSpacing)); ImGui::SetCursorPosY(ImGui::GetCursorPos().y + (ItemSpacing.y * UI->KeyframeSpacing / 2)); for (int a = 0; a < AmountOf(Layer->Property); a++) { if (Layer->Property[a].IsToggled) { real32 InitialY = ImGui::GetCursorScreenPos().y; ImGui::NewLine(); real32 NextY = ImGui::GetCursorScreenPos().y; property_channel *Property = &Layer->Property[a]; ImGui::PushID(Property); for (int b = 0; b < Layer->Property[a].NumberOfTotalKeyframes; b++) { keyframe *Keyframe = KeyframeLookup(Property, b); real32 KeyframeOrigin = TimelineStartingPos.x + UI->TimelineZoom*Keyframe->FrameNumber; ImVec2 KeyframePosition = ImVec2(KeyframeOrigin - FontHeight/2, InitialY); ImGui::PushID(Keyframe); ImGui::SetCursorScreenPos(KeyframePosition); // sadly ImGui::Selectable doesn't work here if (Keyframe->IsSelected) ImGui::PushStyleColor(ImGuiCol_Button, ImGui::GetColorU32(ImGuiCol_ButtonHovered)); ImGui::Button("##keyframe", ImVec2(FontHeight, FontHeight)); ImGui::SameLine(); if (Keyframe->IsSelected) ImGui::PopStyleColor(); if (UI->BoxSelectActive && UI->BoxStart.y < NextY) { if (IsRectTouching(UI->BoxStart, UI->BoxEnd, KeyframePosition, KeyframePosition + KeyframeSize)) { SelectKeyframe(File, Layer, Property, Keyframe); State->RecentSelectionType = selection_keyframe; } else if (!io.KeyShift) { Keyframe->IsSelected = false; } } ImGui_KeyframeDragging(File, State, UI, Property, b, io, 0); ImGui::PopID(); } ImGui::SetCursorScreenPos(ImVec2(ImGui::GetCursorScreenPos().x, NextY)); ImGui::PopID(); } } ImGui::SetCursorPosY(ImGui::GetCursorPos().y - (ItemSpacing.y * UI->KeyframeSpacing / 2)); ImGui::PopStyleVar(); ImGui::PopID(); } */ #if 0 // Old code: ImVec2 SideSize = ImVec2(FontHeight, 0); real32 BarYSize = 50; real32 BarYPos = TimelineSize.y - BarYSize; real32 BarXPos = -TimelineSizeWithBorder.x * UI->TimelinePercentOffset; real32 BarXSize = TimelineSizeWithBorder.x / (1 / UI->TimelinePercentZoomed); real32 MouseDelta = io.MouseDelta.x / TimelineSize.x; // A bit of manipulation to clip the bar handles to the edges if they go over. real32 LeftPos = (BarXPos > 0) ? BarXPos : 0; ImVec2 BarClippedLeftPos(LeftPos, BarYPos); real32 BarOffset = BarXPos - LeftPos; real32 BarClippedSize = (BarXSize + BarXPos > TimelineSizeWithBorder.x) ? TimelineSizeWithBorder.x - BarXPos : BarXSize + BarOffset; if (LeftPos == 0 && BarClippedSize > TimelineSizeWithBorder.x) BarClippedSize = TimelineSizeWithBorder.x; ImGui::SetCursorScreenPos(TimelineAbsolutePos + BarClippedLeftPos); ImGui::Button("##scrollbarleft", ImVec2(SideSize.x, BarYSize)); if ((ImGui::IsItemActive() && ImGui::IsMouseDragging(ImGuiMouseButton_Left, -1))) { UI->TimelinePercentZoomed -= MouseDelta; UI->TimelinePercentOffset -= MouseDelta; } ImGui::SetCursorScreenPos(TimelineAbsolutePos + BarClippedLeftPos + SideSize); ImGui::Button("##scrollbarhori", ImVec2(BarClippedSize, BarYSize) - SideSize*2); if (ImGui::IsItemActive() && ImGui::IsMouseDragging(ImGuiMouseButton_Left, -1)) { UI->TimelinePercentOffset -= MouseDelta; } ImGui::SetCursorScreenPos(TimelineAbsolutePos + BarClippedLeftPos + ImVec2(BarClippedSize, 0) - SideSize ); ImGui::Button("##scrollbarright", ImVec2(SideSize.x, BarYSize)); if ((ImGui::IsItemActive() && ImGui::IsMouseDragging(ImGuiMouseButton_Left, -1))) { UI->TimelinePercentZoomed += MouseDelta; } // New code: ImVec2 MouseDelta = io.MouseDelta / TimelineSize; real32 BarHandleSize = FontHeight; real32 BarThickness = 50; real32 BarH_XPos = -TimelineSizeWithBorder.x * UI->TimelinePercentOffset; real32 BarH_Size = TimelineSizeWithBorder.x / (1 / UI->TimelinePercentZoomed); // 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_XOffset = (BarH_XPos > 0) ? BarH_XPos : 0; ImVec2 BarH_PosUI = TimelineAbsolutePos + ImVec2(BarH_XOffset, TimelineSize.y - BarThickness); real32 BarH_SizeUI = (BarH_Size + BarH_XPos > TimelineSizeWithBorder.x) ? TimelineSizeWithBorder.x - BarH_XPos : BarH_Size + (BarH_XPos - BarH_XOffset); if (BarH_XOffset == 0 && BarH_SizeUI > TimelineSizeWithBorder.x) BarH_SizeUI = TimelineSizeWithBorder.x; BarH_SizeUI = BarH_SizeUI - BarHandleSize*2; ImGui::SetCursorScreenPos(BarH_PosUI); ImGui::Button("##scrollbarleft", ImVec2(BarHandleSize, BarThickness)); if ((ImGui::IsItemActive() && ImGui::IsMouseDragging(ImGuiMouseButton_Left, -1))) { UI->TimelinePercentZoomed -= MouseDelta.x; UI->TimelinePercentOffset -= MouseDelta.x; } ImGui::SetCursorScreenPos(BarH_PosUI + ImVec2(BarHandleSize, 0)); ImGui::Button("##scrollbarhori", ImVec2(BarH_SizeUI, BarThickness)); if (ImGui::IsItemActive() && ImGui::IsMouseDragging(ImGuiMouseButton_Left, -1)) { UI->TimelinePercentOffset -= MouseDelta.x; } 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))) { UI->TimelinePercentZoomed += MouseDelta.x; } #endif #endif