#include "imgui/imgui.h" #include "imgui_ops.h" // 0 for timeline keyframe, 1 for graph keyframe, 2 for left graph handle, 3 for right graph handle internal void ImGui_KeyframeDragging(project_data *File, project_state *State, ui *UI, property_channel *Property, int32 b, ImGuiIO io, int16 Type) { keyframe *Keyframe = KeyframeLookupMemory(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; } } } } internal void ImGui_PropertiesPanel(project_data *File, project_state *State, ui *UI, memory *Memory) { 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")) ManualKeyframeInsertF(Property, Memory, File->CurrentFrame, Property->CurrentValue.f); ImGui::SameLine(); if (ImGui::DragScalar(Property->Name, ImGuiDataType_Float, &Property->CurrentValue.f, Property->ScrubVal.f, &Property->MinVal.f, &Property->MaxVal.f, "%f")) { State->UpdateFrame = true; } ImGui::PopID(); } for (int h = 0; h < Layer->NumberOfEffects; h++) { effect *Effect = Layer->Effect[h]; ImGui::Button("V"); ImGui::SameLine(); ImGui::Text(Effect->Name); for (int i = 0; i < Effect->NumberOfProperties; i++) { property_channel *Property = &Effect->Property[i]; ImGui::PushID(Property); if (Property->VarType == type_real) ImGui::DragScalar(Property->Name, ImGuiDataType_Float, &Property->CurrentValue.f, 0.005f, &Property->MaxVal.f, &Property->MaxVal.f, "%f"); 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 { char buf[256]; sprintf(buf, "Properties: empty###Properties"); ImGui::Begin(buf); if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) UI->FocusedWindow = focus_properties; } ImGui::End(); } internal v2 CalculateAnchorPointUV(project_layer *Layer, pixel_buffer *Buffer); internal void ImGui_Viewport(project_data File, project_state *State, ui *UI, pixel_buffer CompBuffer, ImGuiIO io, GLuint textureID) { ImGui::Begin("Viewport"); 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(); 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)); ImGui::InvisibleButton("canvas", ViewportScale, ImGuiButtonFlags_MouseButtonLeft | ImGuiButtonFlags_MouseButtonRight); bool32 IsHovered = ImGui::IsItemHovered(); bool32 IsActive = ImGui::IsItemActive(); bool32 IsActivated = ImGui::IsItemActivated(); if (IsHovered && IsActivated && ImGui::IsMouseDown(ImGuiMouseButton_Left)) { v2 LocalMousePos = (V2(io.MousePos) - V2(ViewportMin)); v2 LocalCompPos = V2(UI->CompPos) - V2(ViewportMin); v2 MouseScreenUV = LocalMousePos - LocalCompPos; UI->TempZoomRatio = MouseScreenUV / V2(UI->CompZoom); // AKA actual normalized UV of comp if (!ImGui::IsKeyDown(ImGuiKey_Z)) { for (int i = File.NumberOfLayers - 1; i >= 0; i--) { if (!io.KeyShift) DeselectAllLayers(&File, State); if (TestPointInLayer(File.Layer[i], &CompBuffer, UI->TempZoomRatio) && !File.Layer[i]->IsSelected) { SelectLayer(File.Layer[i], 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; } 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)); if (State->MostRecentlySelectedLayer > -1) { project_layer *Layer = File.Layer[State->MostRecentlySelectedLayer]; 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); } draw_list->PopClipRect(); 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 internal bool32 ImGui_SlidingLayer(project_layer *Layer, real32 *DraggingThreshold, real32 Delta, int16 TimelineZoom, int16 Side) { bool32 Result = 0; if (ImGui::IsItemActive() && ImGui::IsMouseDragging(ImGuiMouseButton_Left, -1)) { ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeEW); *DraggingThreshold += Delta; if (abs(*DraggingThreshold) >= TimelineZoom) { int16 Increment = *DraggingThreshold/TimelineZoom; // TODO(fox): Properly handle the start and end points wrapping. if (!(Increment < 0 && Layer->StartFrame == 0 && Side & 1)) { if (Side & 1) Layer->StartFrame += Increment; if (Side & 2) Layer->EndFrame += Increment; if (Side == 3) { IncrementKeyframesInLayer(Layer, Increment); if (Layer->SourceType == source_video) { video_source *Source = (video_source *)Layer->RenderInfo; Source->VideoFrameOffset += Increment; } } } *DraggingThreshold += -1*Increment*TimelineZoom; } Result = 1; } return Result; } internal void AddSource(project_data *File, memory *Memory, char * = NULL); internal 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 (ImGui::Button("Add source")) { AddSource(File, Memory); } if (State->DemoButton) { ImGui::SameLine(); if (ImGui::Button("Generate demo scene")) { CreateDemoScene(File, Memory); State->UpdateKeyframes = true; State->UpdateFrame = true; State->DemoButton = false; } } for (int16 i = 0; i < File->NumberOfSources; i++) { ImGui::PushID(i); ImGui::InputText("##source", File->Source[i], STRING_SIZE); ImGui::SameLine(); if (ImGui::Button("Create Layer")) { CreateLayerFromSource(File, State, Memory, File->Source[i]); } ImGui::PopID(); } #if DEBUG for (int i = 0; i < Debug.WatchedProperties; i++) { if (Debug.DebugPropertyType[i] == d_float) { ImGui::Text("%s: %f", Debug.String[i], Debug.Val[i].f); } else if (Debug.DebugPropertyType[i] == d_int) { ImGui::Text("%s: %i", Debug.String[i], Debug.Val[i].i); } else if (Debug.DebugPropertyType[i] == d_uint) { ImGui::Text("%s: %u", Debug.String[i], Debug.Val[i].u); } } #endif ImGui::End(); } internal 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.0f) WindowSize.x = 50.0f; // prevent crashing if the window gets too small if (WindowSize.y < 50.0f) WindowSize.y = 50.0f; // (still crashes) ImVec2 WindowMinAbs = ImGui::GetWindowPos(); ImVec2 WindowMaxAbs = WindowMinAbs + WindowSize; ImVec2 ButtonSize = ImVec2(FontHeight*2, FontHeight*2); real32 TopbarHeight = FontHeight*4; ImVec2 TopbarMax = ImVec2(WindowMaxAbs.x, WindowMinAbs.y + TopbarHeight); ImVec2 TimelineBorderPadding = ImVec2(FontHeight, FontHeight); ImVec2 TopbarSize = ImVec2(WindowSize.x, TopbarHeight); ImVec2 TopbarButtonSize = ImVec2(TopbarHeight, TopbarHeight); // NOTE(fox): StartingPos values include X and Y scroll, primarily used for // the keyframes/layers. Absolute doesn't include scroll, primarily used // for the clip rects. ImVec2 SidebarSize = ImVec2(UI->TimelineSplit, WindowSize.y - TopbarHeight); ImVec2 SidebarSizeWithBorder = SidebarSize - TimelineBorderPadding*2; ImVec2 SidebarAbsolutePos = WindowMinAbs + ImVec2(0, TopbarSize.y) + TimelineBorderPadding; ImVec2 SidebarStartingPos = SidebarAbsolutePos + ImVec2(0, UI->ScrollYOffset); ImVec2 TimelineSize = ImVec2(WindowSize.x - SidebarSize.x, SidebarSize.y); ImVec2 TimelineSizeWithBorder = TimelineSize - TimelineBorderPadding*2; ImVec2 TimelineAbsolutePos = WindowMinAbs + ImVec2(SidebarSize.x, TopbarSize.y) + TimelineBorderPadding; ImVec2 TimelineStartingPos = SidebarStartingPos + ImVec2(SidebarSize.x + UI->ScrollXOffset, 0); // Timeline and sidebar size including the padding between them ImVec2 TimelineFullSize = TimelineSizeWithBorder + SidebarSizeWithBorder + ImVec2(TimelineBorderPadding.x*2, 0); ImVec2 KeyframeSize = ImVec2(FontHeight, FontHeight); ImVec2 PlayheadPos = ImVec2(TimelineStartingPos.x + UI->TimelineZoom * File->CurrentFrame, WindowMinAbs.y + TopbarSize.y/2); // NOTE(fox): The InvisibleButton hitbox that handles mouse inputs on the // graph occludes the hitbox that handles box drag selection, so I'm using // this struct to carry over the state from the former to the latter. imgui_buttonstate AnimationCurves = {}; if (UI->Initializing) { UI->TimelineZoom = TimelineSizeWithBorder.x / (File->NumberOfFrames + 1); } ImDrawList* draw_list = ImGui::GetWindowDrawList(); draw_list->AddRectFilled(WindowMinAbs, WindowMaxAbs, IM_COL32(255, 255, 255, 50)); draw_list->AddRectFilled(WindowMinAbs, TopbarMax, IM_COL32(255, 255, 255, 50)); // ImGui::BeginChild("Topbar", TopbarSize, true, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoScrollbar); ImGui::Button("V", TopbarButtonSize); ImGui::SameLine(); ImGui::Button("V", TopbarButtonSize); ImGui::SameLine(); ImGui::Button("V", TopbarButtonSize); ImGui::SameLine(); ImGui::SetCursorScreenPos(PlayheadPos); ImGui::Button("P", ButtonSize); if (ImGui::IsItemActive()) { if (ImGui::IsMouseDragging(ImGuiMouseButton_Left, -1)) { UI->DraggingKeyframeThreshold += io.MouseDelta.x; if (abs(UI->DraggingKeyframeThreshold) >= UI->TimelineZoom) { int16 Increment = UI->DraggingKeyframeThreshold/UI->TimelineZoom; if (File->CurrentFrame <= 0 && Increment < File->StartFrame) File->CurrentFrame = 0; else if (File->CurrentFrame >= File->EndFrame && Increment > File->EndFrame) { File->CurrentFrame = File->EndFrame; } else { File->CurrentFrame += Increment; } State->UpdateFrame = true; State->UpdateKeyframes = true; UI->DraggingKeyframeThreshold += -1*Increment*UI->TimelineZoom; } } } ImGui::EndChild(); /// ImGui::BeginChild("Sidebar", SidebarSize, true, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoScrollbar); ImGui::SetCursorScreenPos(SidebarStartingPos); ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ItemSpacing); ImGui::PushClipRect(SidebarAbsolutePos, SidebarAbsolutePos + SidebarSizeWithBorder, true); 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::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")) ManualKeyframeInsertF(Property, Memory, File->CurrentFrame, Property->CurrentValue.f); ImGui::SameLine(); if (ImGui::Button("G")) { SwitchBool(Property->IsGraphToggled); // TODO(fox): Make system to init things like these automatically? if (!Property->GraphLength) { Property->GraphLength = 150; Property->GraphYOffset = (Property->GraphWindowHeight - Property->GraphLength)/2; } } ImGui::SetCursorScreenPos(ImVec2(ImGui::GetCursorScreenPos().x, YInit)); if (Property->IsGraphToggled) { ImGui::Dummy(ImVec2(5, Property->GraphWindowHeight)); } ImGui::PopID(); } } ImGui::PopStyleVar(); ImGui::PopID(); } ImGui::PopClipRect(); /// Split size adjuster ImGui::SetCursorScreenPos(ImVec2(WindowMinAbs.x + UI->TimelineSplit - TimelineBorderPadding.x, TimelineAbsolutePos.y)); ImGui::InvisibleButton("##SplitMove", ImVec2(TimelineBorderPadding.x, SidebarSizeWithBorder.y), ImGuiButtonFlags_MouseButtonLeft); if (ImGui::IsItemActive() && ImGui::IsMouseDragging(ImGuiMouseButton_Left, -1)) { UI->TimelineSplit += io.MouseDelta.x; } ImGui::PopStyleVar(); ImGui::EndChild(); ImGui::SameLine(); /// ImGui::BeginChild("Timeline", TimelineSize, true, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoScrollbar); ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0.0f, ItemSpacing.y)); ImGui::SetCursorScreenPos(TimelineStartingPos); ImGui::PushClipRect(TimelineAbsolutePos, TimelineAbsolutePos + TimelineSizeWithBorder, true); draw_list->PushClipRect(TimelineAbsolutePos, TimelineAbsolutePos + TimelineSizeWithBorder, true); 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); 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 = KeyframeLookupMemory(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)); if (Property->IsGraphToggled) { uint16 GraphWindowHeight = File->Layer[i]->Property[a].GraphWindowHeight; real32 GraphWindowLocalYMin = ImGui::GetCursorPosY(); ImDrawList* draw_list = ImGui::GetWindowDrawList(); real32 ScreenY = NextY; ImVec2 MinPos = ImVec2(TimelineAbsolutePos.x, ScreenY); ImVec2 MaxPos = ImVec2(TimelineAbsolutePos.x + TimelineSizeWithBorder.x, ScreenY + GraphWindowHeight); draw_list->AddRectFilled(MinPos, MaxPos, IM_COL32(00, 00, 30, 65)); draw_list->PushClipRect(MinPos, MaxPos, true); 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 = KeyframeLookupMemory(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 = KeyframeLookupIndex(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; } } ImGui::PopID(); } } ImGui::SetCursorPosY(ImGui::GetCursorPos().y - (ItemSpacing.y * UI->KeyframeSpacing / 2)); ImGui::PopStyleVar(); ImGui::PopID(); } // 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); } } draw_list->PopClipRect(); ImGui::PopClipRect(); // 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); ImGui::PopStyleVar(); // 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 || AnimationCurves.IsItemActive) { 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 || AnimationCurves.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; ImGui::SetScrollX(ImGui::GetScrollMaxX() * UI->TempZoomRatioTimeline); } } } if (IsItemDeactivated || AnimationCurves.IsItemDeactivated) { UI->BoxStart = {0, 0}; UI->BoxEnd = {0, 0}; UI->BoxSelectActive = false; } ImGui::EndChild(); ImGui::PopStyleVar(2); if (IsRectTouching(WindowMinAbs, WindowMaxAbs, io.MousePos, io.MousePos + 1)) { if (io.KeyCtrl && io.MouseWheel) { real32 ZoomAmount = io.MouseWheel*8; real32 LocalMousePos = ImGui::GetMousePos().x - TimelineStartingPos.x; real32 ZoomRatio = LocalMousePos / UI->TimelineZoom; UI->TimelineZoom += ZoomAmount; UI->ScrollXOffset -= ZoomAmount*ZoomRatio; } else { UI->ScrollXOffset += io.MouseWheelH*8; UI->ScrollYOffset += io.MouseWheel*8; } } ImGui::End(); } internal void ImGui_ProcessInputs(project_data *File, project_state *State, pixel_buffer *CompBuffer, memory *Memory, ui *UI, ImGuiIO io) { if (io.KeysData[ImGuiKey_Q].Down) State->IsRunning = false; 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_Space)) { SwitchBool(State->IsPlaying); } if (State->IsPlaying && !IsRendering) { IncrementFrame(File, 1); State->UpdateFrame = true; State->UpdateKeyframes = true; } if (ImGui::IsKeyPressed(ImGuiKey_R) && State->NumberOfSelectedLayers) TransformsInteract(File, State, UI, sliding_rotation); if (ImGui::IsKeyPressed(ImGuiKey_S) && State->NumberOfSelectedLayers) TransformsInteract(File, State, UI, sliding_scale); if (ImGui::IsKeyPressed(ImGuiKey_G) && State->NumberOfSelectedLayers) TransformsInteract(File, State, UI, sliding_position); if (ImGui::IsKeyPressed(ImGuiKey_A) && State->NumberOfSelectedLayers) TransformsInteract(File, State, UI, sliding_anchorpoint); 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; } break; } } #if DEBUG if (ImGui::IsKeyPressed(ImGuiKey_E)) { SwitchBool(AVXEnabled); State->UpdateFrame = true; } if (ImGui::IsKeyPressed(ImGuiKey_M)) { Debug.Markers[Debug.MarkerIndex] = File->CurrentFrame; Debug.MarkerIndex++; } #endif bool32 Ended = ImGui::IsMouseDown(ImGuiMouseButton_Left); if (State->IsInteracting) { ImVec2 MouseIncrement = io.MouseDelta * (ImVec2(CompBuffer->Width, CompBuffer->Height) / UI->CompZoom); switch (State->TransformsHotkeyInteract) { case sliding_position: { InteractProperty(0, File, State, Ended, MouseIncrement.x, Memory); InteractProperty(1, File, State, Ended, MouseIncrement.y, Memory); } break; case sliding_anchorpoint: { InteractProperty(2, File, State, Ended, MouseIncrement.x, Memory); InteractProperty(3, File, State, Ended, MouseIncrement.y, Memory); } break; case sliding_rotation: { InteractProperty(4, File, State, Ended, MouseIncrement.x / 10.0, Memory); } break; case sliding_scale: { InteractProperty(5, File, State, Ended, MouseIncrement.x / 200.0, Memory); } break; } } if (!ImGui::IsMouseDown(ImGuiMouseButton_Left)) { UI->DraggingLayerThreshold = 0; UI->DraggingTimelineThreshold = 0; UI->DraggingKeyframeThreshold = 0; } } global_variable char ImGuiPrefs[] = "[Window][DockSpaceViewport_11111111]" "\nSize=3153,1837" "\nCollapsed=0" "\n" "\n[Window][Debug##Default]" "\nPos=60,60" "\nSize=400,400" "\nCollapsed=0" "\n" "\n[Window][Viewport]" "\nPos=528,0" "\nSize=2121,1208" "\nCollapsed=0" "\nDockId=0x00000005,0" "\n" "\n[Window][###Properties]" "\nSize=526,1208" "\nCollapsed=0" "\nDockId=0x00000003,0" "\n" "\n[Window][Timeline]" "\nPos=0,1210" "\nSize=3153,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=2651,0" "\nSize=502,1208" "\nCollapsed=0" "\nDockId=0x00000006,0" "\n" "\n[Docking][Data]" "\nDockSpace ID=0x8B93E3BD Pos=0,0 Size=3153,1837 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 Selected=0x86FA2F90" "\n DockNode ID=0x00000002 Parent=0x8B93E3BD SizeRef=3200,627 HiddenTabBar=1 Selected=0x0F18B61B" "\n";