#include "my_imgui_internal_widgets.h" #include "imgui_ops.h" #include "imgui_helper_widgets.cpp" static void ImGui_InteractSliderProperty(project_state *State, memory *Memory, property_channel *Property) { ImGui::DragScalar(Property->Name, ImGuiDataType_Float, &Property->CurrentValue, Property->ScrubVal, &Property->MinVal, &Property->MaxVal, "%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_PropertiesPanel(project_data *File, project_state *State, ui *UI, memory *Memory, ImGuiIO io) { bool32 Display = 1; block_layer *Layer = NULL; if (State->MostRecentlySelectedLayer > -1) { Layer = (block_layer *)Memory_Block_AddressAtIndex(Memory, F_Layers, State->MostRecentlySelectedLayer); if (!Layer->Occupied) Display = 0; } else { Display = 0; } if (Display) { block_string *String = (block_string *)Memory_Block_AddressAtIndex(Memory, F_Strings, Layer->Block_String_Index); char buf[256]; sprintf(buf, "Properties: %s###Properties", String->Char); 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")) { History_Entry_Commit(Memory, "Add keyframe"); bezier_point Point = { 1, {(real32)State->Frame_Current, Property->CurrentValue, -1, 0, 1, 0}, interpolation_type_linear, 0, {0, 0, 0}, 0 }; Bezier_Add(Memory, Property, Point); History_Entry_End(Memory); } ImGui::SameLine(); ImGui_InteractSliderProperty(State, Memory, Property); ImGui::PopID(); } ImGui::End(); } else { ImGui::Begin("Properties: empty###Properties"); if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) UI->FocusedWindow = focus_properties; ImGui::End(); } } #if DEBUG static void ImGui_DebugMemoryViewer(memory *Memory, project_state *State) { ImGui::Begin("Memory viewer"); ImVec2 ViewportMin = ImGui::GetCursorScreenPos(); ImVec2 ViewportScale = ImGui::GetContentRegionAvail(); ImVec2 ViewportMax = ImVec2(ViewportMin.x + ViewportScale.x, ViewportMin.y + ViewportScale.y); // ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0)); // ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0)); cache_entry *EntryArray = State->Render.Entry; char *Type[4] = { "unassigned", "comp", "source", "layer" }; uint32 Blocks_Total = Memory->Slot[B_CachedBitmaps].Size / BitmapBlockSize; uint32 PerRow = sqrt(Blocks_Total); real32 BlockSize = ViewportScale.x / PerRow; for (int c = 0; c < Memory->EntryCount; c++) { ImGui::PushID(c); cache_entry Entry = EntryArray[c]; uint32 BlockSpan; if (c+1 != Memory->EntryCount) { cache_entry NextEntry = EntryArray[c+1]; BlockSpan = NextEntry.Block_StartIndex - Entry.Block_StartIndex; } else { BlockSpan = 1; } ImVec2 ButtonPos = ViewportMin + ImVec2((Entry.Block_StartIndex % PerRow) * BlockSize, BlockSize * (Entry.Block_StartIndex / PerRow)); ImVec2 ButtonSize = ImVec2(BlockSpan * BlockSize, BlockSize); ImGui::SetCursorScreenPos(ButtonPos); char size[20]; sprintf(size, "%lu##uimemoryblock", EntryArray[c].CycleTime); if (ButtonPos.x + ButtonSize.x > ViewportMax.x) { real32 ButtonSizeSplit = ViewportMax.x - ButtonPos.x; ImGui::Button(size, ImVec2(ButtonSizeSplit, ButtonSize.y)); ImVec2 ButtonPos2 = ImVec2(ViewportMin.x, ButtonPos.y + BlockSize); ImGui::SetCursorScreenPos(ButtonPos2); ImGui::Button("##uimemoryblockpad", ImVec2(ButtonSize.x - ButtonSizeSplit, ButtonSize.y)); } else { ImGui::Button(size, ButtonSize); } if (ImGui::IsItemHovered()) { ImGui::BeginTooltip(); ImGui::Text("Type - %s, Start - %i, Info - %i, %i", Type[EntryArray[c].Type], EntryArray[c].Block_StartIndex, EntryArray[c].TypeInfo, EntryArray[c].TypeInfo_Sub); ImGui::EndTooltip(); } ImGui::PopID(); } // ImGui::PopStyleVar(2); ImGui::End(); } static void ImGui_DebugUndoTree(memory *Memory, project_state *State) { ImGui::Begin("undotree"); for (int i = 0; i < Memory->History.NumberOfEntries; i++) { history_entry Entry = Memory->History.Entry[i]; bool32 CurrentPos = (i < Memory->History.EntryPlayhead); ImGui::MenuItem(Entry.Name, NULL, CurrentPos); } ImGui::End(); } #endif static void ImGui_File(project_data *File, project_state *State, memory *Memory, ImGuiIO io, sorted_comp_info *SortedCompArray, sorted_layer *SortedLayerArray) { ImGui::Begin("Files"); ImGui::Text("Application average %.3f ms/frame (%.1f FPS)", 1000.0f / io.Framerate, io.Framerate); if (ImGui::IsKeyPressed(ImGuiKey_Backspace)) { /* uint64 SortSize = (sizeof(uint16) * File->Comp_Count); void *SortedArray = Memory_PushScratch(Memory, SortSize); uint16 *SelectedSourceIndex = (uint16 *)SortedArray; int SelectedSourceCount = 0; int h = 0, c = 0, i = 0; int SourceCount = File->Source_Count; while (Block_Loop(Memory, F_Sources, SourceCount, &h, &c, &i)) { block_source *Source = (block_source *)Memory_Block_AddressAtIndex(Memory, F_Sources, i); if (Source->IsSelected) { SelectedSourceIndex[SelectedSourceCount] = i; SelectedSourceCount++; } } h = 0, c = 0, i = 0; int LayerCount = File->Layer_Count; while (Block_Loop(Memory, F_Layers, LayerCount, &h, &c, &i)) { block_layer *Layer = (block_layer *)Memory_Block_AddressAtIndex(Memory, F_Layers, i); for (int b = 0; b < SelectedSourceCount; b++) { if (SelectedSourceIndex[b] == Layer->Block_Source_Index) { } } } Memory_PopScratch(Memory, SortSize); */ /* bool32 CommitAction = 0; while (Block_Loop(Memory, F_Sources, SourceCount, &h, &c, &i)) { block_source *Source = (block_source *)Memory_Block_AddressAtIndex(Memory, F_Sources, i); if (Source->IsSelected) { if (!CommitAction) { History_Entry_Commit(Memory, "Delete source"); CommitAction = 1; } Source_Delete(File, Memory, i); } } if (CommitAction) History_Entry_End(Memory); */ } 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); } int h = 0, c = 0, i = 0; while (Block_Loop(Memory, F_Sources, File->Source_Count, &h, &c, &i)) { block_source *Source = (block_source *)Memory_Block_AddressAtIndex(Memory, F_Sources, i); block_string *String = (block_string *)Memory_Block_AddressAtIndex(Memory, F_Strings, Source->Path_String_Index); ImGui::Selectable(String->Char, Source->IsSelected); if (ImGui::IsItemClicked() || ImGui::IsItemClicked(ImGuiMouseButton_Right)) { if (!io.KeyShift && !Source->IsSelected) { Source_DeselectAll(File, Memory); } Source->IsSelected = 1; } ImGui::OpenPopupOnItemClick("sourcecontext", ImGuiPopupFlags_MouseButtonRight); } if (ImGui::BeginPopup("sourcecontext")) { if (ImGui::MenuItem("Create layer from source")) { Source_UICreateButton(File, State, Memory, SortedCompArray, SortedLayerArray); } ImGui::EndPopup(); } // if (!ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) // Source_DeselectAll(File, Memory); #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_StableDiffusion(project_data *File, project_state *State, ui *UI, memory *Memory, ImGuiIO io) { ImGui::Begin("SD prompt input"); sd_state *SD = &State->SD; int Size = ImGui::GetFontSize(); ImGui::PushStyleColor(ImGuiCol_Button, IM_COL32(200, 80, 0, 255)); if (ImGui::Button("Generate!", ImVec2(Size*8, Size*2))) { /* block_layer *Layer = (block_layer *)Memory_Block_AddressAtIndex(Memory, F_Layers, 0); block_source *Source = (block_source *)Memory_Block_AddressAtIndex(Memory, F_Sources, Layer->Block_Source_Index); cache_entry *Entry = Memory_Cache_Search(State, Memory, cache_entry_type_source, Layer->Block_Source_Index, 0); Assert(Entry->IsCached); void *BitmapAddress = Memory_Block_Bitmap_AddressAtIndex(Memory, Entry->Block_StartIndex); uint64 Size = Source->Width * Source->Height * Source->BytesPerPixel; uint64 EncodedLength = 0; uint64 EncodedAllocSize = base64_encode_size(Size); void *EncodedOutput = Memory_PushScratch(Memory, EncodedSize); uint64 EncodedTrueSize; base64_encode((uint8 *)BitmapAddress, Size, (uint8 *)EncodedOutput, &EncodedTrueSize); Memory_PopScratch(Memory, EncodedSize); */ SD_Txt2Txt(SD); } ImGui::InputText("Address", SD->ServerAddress, SD_LEN_ADDRESS); if (State->Initializing && (SD->ServerAddress[0] == '\0')) { sprintf(SD->Prompt, "%s", "1girl, looking at viewer, smile, hakurei reimu, cowboy shot"); sprintf(SD->NegPrompt, "%s", "nsfw, bad anatomy, bad hands, text, error, missing fingers, extra digit, fewer digits, cropped, worst quality, low quality, normal quality, jpeg artifacts, signature, watermark"); sprintf(SD->ServerAddress, "%s", "http://127.0.0.1:7860"); } ImGui::PopStyleColor(); ImGui::InputTextMultiline("Prompt", SD->Prompt, SD_LEN_PROMPT); ImGui::InputTextMultiline("Negative prompt", SD->NegPrompt, SD_LEN_PROMPT); ImGui::SliderInt("Steps", &SD->Steps, 0, 150); ImGui::SliderInt("Width", &SD->Width, 64, 2048, "%i px"); if (ImGui::IsItemDeactivatedAfterEdit()) { SD->Width = SD->Width + (64 - (SD->Width % 64)); } ImGui::SliderInt("Height", &SD->Height, 64, 2048, "%i px"); if (ImGui::IsItemDeactivatedAfterEdit()) { SD->Height = SD->Height + (64 - (SD->Height % 64)); } ImGui::SliderFloat("CFG scale", &SD->CFG, 1, 30, "%.2f"); ImGui::SliderFloat("Denoising strength", &SD->DenoisingStrength, 0, 1, "%.2f"); ImGui::InputInt("Seed", &SD->Seed); ImGui::End(); } static void ImGui_ColorPanel(project_data *File, project_state *State, ui *UI, memory *Memory, ImGuiIO io) { ImGuiStyle& style = ImGui::GetStyle(); ImGui::Begin("Colors"); ImGuiColorEditFlags flags_primary = ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags_Float; ImGuiColorEditFlags flags_picker = ImGuiColorEditFlags_PickerHueBar | ImGuiColorEditFlags_AlphaBar | ImGuiColorEditFlags_NoSmallPreview | ImGuiColorEditFlags_NoSidePreview | ImGuiColorEditFlags_DisplayRGB | ImGuiColorEditFlags_DisplayHSV | ImGuiColorEditFlags_DisplayHex; // Dim window if it's not active so there's not a big saturation square in // the corner of my vision while I'm editing. Personal preference. real32 AlphaMult = (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) ? 1.0f : 0.3f; ImGui::PushStyleVar(ImGuiStyleVar_Alpha, style.Alpha * AlphaMult); ImGui::ColorPicker4("##maincolorpicker", &UI->Color.r, flags_primary | flags_picker); ImGui::PopStyleVar(); if (ImGui::ColorButton("##primarycolor", *(ImVec4*)&UI->Color.r, flags_primary, ImVec2(20, 20))) { v4 Temp = UI->Color; UI->Color = UI->AltColor; UI->AltColor = Temp; } if (ImGui::ColorButton("##secondarycolor", *(ImVec4*)&UI->AltColor.r, flags_primary, ImVec2(20, 20))) { v4 Temp = UI->Color; UI->Color = UI->AltColor; UI->AltColor = Temp; } if (State->Tool == tool_brush) { real32 BrushSizeMin = 0; real32 BrushSizeMax = 1024; real32 BrushHardnessMin = 0.5; real32 BrushHardnessMax = 100; real32 BrushSpacingMin = 0.1; real32 BrushSpacingMax = 100; if (ImGui::DragScalar("Size", ImGuiDataType_Float, &State->Brush.Size, 1, &BrushSizeMin, &BrushSizeMax, "%.3f")) { Brush_CalcBitmapAlphaFromSize(Memory, &State->Brush, 4); State_BindBrushTexture(Memory, &State->Brush, 4); } if (ImGui::DragScalar("Hardness", ImGuiDataType_Float, &State->Brush.Hardness, 1, &BrushHardnessMin, &BrushHardnessMax, "%.3f", ImGuiSliderFlags_Logarithmic)) { Brush_CalcBitmapAlphaFromSize(Memory, &State->Brush, 4); State_BindBrushTexture(Memory, &State->Brush, 4); } if (ImGui::DragScalar("Spacing", ImGuiDataType_Float, &State->Brush.Spacing, 1, &BrushSpacingMin, &BrushSpacingMax, "%.3f", ImGuiSliderFlags_Logarithmic)) { Brush_CalcBitmapAlphaFromSize(Memory, &State->Brush, 4); State_BindBrushTexture(Memory, &State->Brush, 4); } ImGui::Button(BrushNames[State->Brush.Type]); ImGui::OpenPopupOnItemClick("brush_picker", ImGuiPopupFlags_MouseButtonLeft); if (ImGui::BeginPopup("brush_picker")) { for (int16 b = 0; b < brush_amount; b++) { if (ImGui::MenuItem(BrushNames[b], NULL, false, State->Brush.Type != b)) { State->Brush.Type = (brush_type)b; } } ImGui::EndPopup(); } } ImGui::End(); } static void ImGui_Toolbar(project_state *State, ImDrawList *draw_list) { ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0)); ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0)); // ImGui::PushStyleColor(ImGuiCol_Button, IM_COL32(50, 50, 50, 0)); real32 IconSize = ImGui::GetFontSize() * 4; int ToolCount = (int)tool_count; ImVec2 ButtonSize(IconSize, IconSize); ImVec2 WindowSize(IconSize, IconSize * ToolCount); ImGui::BeginChild("Toolbar", WindowSize, true, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoScrollbar); for (int i = 0; i < ToolCount; i++) { ImGui::PushID(i); // draw_list->AddImage((void *)(intptr_t)State->ToolIconTex[i], Min, Max); if ((int)State->Tool == i) { ImVec2 Min = ImGui::GetCursorScreenPos(); ImVec2 Max = Min + ButtonSize; draw_list->AddRectFilled(Min, Max, IM_COL32(255, 255, 255, 128)); } ImGui::Button(ToolName[i], ButtonSize); if (ImGui::IsItemActivated()) { State->Tool = (tool)i; } ImGui::PopID(); } ImGui::EndChild(); // ImGui::PopStyleColor(); ImGui::PopStyleVar(2); } static void ImGui_RenderUIBrush(project_state *State, memory *Memory, ImVec2 ViewportMin, ImVec2 ViewportMax, ImVec2 CompZoom, ImGuiIO io, uint16 Width, uint16 Height) { ImDrawList* draw_list = ImGui::GetWindowDrawList(); if (State->Tool == tool_brush) { if (ImGui::IsKeyPressed(ImGuiKey_ModAlt, false)) { State->Brush.UIPos = io.MousePos; } ImVec2 CompScale = CompZoom / ImVec2(Width, Height); ImVec2 BrushSize = CompScale * State->Brush.Size; ImVec2 MinBounds = State->Brush.UIPos - BrushSize/2; ImVec2 MaxBounds = MinBounds + BrushSize; if (io.KeyAlt) { draw_list->PushClipRect(ViewportMin, ViewportMax, true); draw_list->AddImage((void *)(intptr_t)State->Brush.GLTexture, MinBounds, MaxBounds, ImVec2(0, 0), ImVec2(1, 1), 1); draw_list->PopClipRect(); ImGui::SetCursorScreenPos(State->Brush.UIPos); char buf[256]; sprintf(buf, "Size: %.1f, Hardness: %.1f", State->Brush.Size, State->Brush.Hardness); ImGui::Text(buf); if (io.MouseDelta.x || io.MouseDelta.y) { ImVec2 Delta = io.MouseDelta; State->Brush.Size += Delta.x; State->Brush.Hardness += Delta.y*State->Brush.Hardness/100; if (State->Brush.Size < 0) State->Brush.Size = 0; if (State->Brush.Size > 1024) State->Brush.Size = 1024; if (State->Brush.Hardness < 0.5) State->Brush.Hardness = 0.5; if (State->Brush.Hardness > 100) State->Brush.Hardness = 100; Brush_CalcBitmapAlphaFromSize(Memory, &State->Brush, 4); State_BindBrushTexture(Memory, &State->Brush, 4); } } } } static void ImGui_TransformUI(project_data *File, project_state *State, memory *Memory, ui *UI, ImDrawList *draw_list, ImGuiIO &io, interact_transform *Interact, ImVec2 ViewportMin, uint32 CompWidth, uint32 CompHeight) { v2 InteractMin = Interact->Min + Interact->Position; v2 InteractMax = Interact->Max + Interact->Position; v2 BoxLength = InteractMax - InteractMin; v2 Center = InteractMax - (BoxLength/2); real32 Point0X = Center.x - InteractMin.x; real32 Point0Y = Center.y - InteractMin.y; real32 Rad = Interact->Radians; v2 XAxis = (Point0X * Interact->Scale)*V2(cos(Rad), sin(Rad)); v2 YAxis = (Point0Y * -Interact->Scale)*V2(sin(Rad), -cos(Rad)); // Points are clockwise starting from the top left. real32 X0 = -XAxis.x - YAxis.x + Center.x; real32 Y0 = -XAxis.y - YAxis.y + Center.y; real32 X1 = X0 + XAxis.x*2; real32 Y1 = Y0 + XAxis.y*2; real32 X2 = X1 + YAxis.x*2; real32 Y2 = Y1 + YAxis.y*2; real32 X3 = X2 - XAxis.x*2; real32 Y3 = Y2 - XAxis.y*2; // Midway points. real32 Mid_X0 = X0 + XAxis.x; real32 Mid_Y0 = Y0 + XAxis.y; real32 Mid_X1 = X1 + YAxis.x; real32 Mid_Y1 = Y1 + YAxis.y; real32 Mid_X2 = X2 - XAxis.x; real32 Mid_Y2 = Y2 - XAxis.y; real32 Mid_X3 = X3 - YAxis.x; real32 Mid_Y3 = Y3 - YAxis.y; ImVec2 CompScale = UI->CompZoom / ImVec2(CompWidth, CompHeight); ImVec2 P[4]; P[0] = ImVec2(X0, Y0)*CompScale + UI->CompPos; P[1] = ImVec2(X1, Y1)*CompScale + UI->CompPos; P[2] = ImVec2(X2, Y2)*CompScale + UI->CompPos; P[3] = ImVec2(X3, Y3)*CompScale + UI->CompPos; ImVec2 Mid_P[4]; Mid_P[0] = ImVec2(Mid_X0, Mid_Y0)*CompScale + UI->CompPos; Mid_P[1] = ImVec2(Mid_X1, Mid_Y1)*CompScale + UI->CompPos; Mid_P[2] = ImVec2(Mid_X2, Mid_Y2)*CompScale + UI->CompPos; Mid_P[3] = ImVec2(Mid_X3, Mid_Y3)*CompScale + UI->CompPos; ImU32 wcol = ImGui::GetColorU32(ImGuiCol_Text); draw_list->AddLine(P[0], P[1], wcol, 2.0f); draw_list->AddLine(P[1], P[2], wcol, 2.0f); draw_list->AddLine(P[2], P[3], wcol, 2.0f); draw_list->AddLine(P[3], P[0], wcol, 2.0f); v2 XAxis2 = (BoxLength*CompScale.x)*V2(cos(Rad), sin(Rad)); v2 YAxis2 = (BoxLength*CompScale.y)*V2(sin(Rad), -cos(Rad)); v2 XAxisPerp = (1.0f / LengthSq(XAxis))*XAxis; v2 YAxisPerp = (1.0f / LengthSq(YAxis))*YAxis; // real32 LocalX = ((io.MousePos.x - UI->CompPos.x) - Center.x) ; // real32 LocalY = ((io.MousePos.y - UI->CompPos.y) - Center.y) ; layer_transforms BoxTransforms = { Center.x, Center.y, 0.5, 0.5, (real32)(Interact->Radians / (PI / 180)), Interact->Scale }; v2 LayerPoint = Transform_ScreenSpaceToLocal(BoxTransforms, CompWidth, CompHeight, BoxLength.x, BoxLength.y, UI->CompPos, UI->CompZoom, ViewportMin, io.MousePos); real32 U = LayerPoint.x / BoxLength.x; real32 V = LayerPoint.y / BoxLength.y; ImVec2 ScaleHandleSize(50, 50); bool32 OtherActions = ImGui::IsKeyDown(ImGuiKey_Z); // First do the halfway scale points, since they don't need UVs considered: for (int i = 0; i < 4; i++) { ImGui::SetCursorScreenPos(Mid_P[i] - ScaleHandleSize/2); ImGui::PushID(i); ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImGui::ColorConvertFloat4ToU32(ImVec4(0.6f, 0.0f, 0.3f, 1.0f))); ImGui::Button("##ScaleMids", ScaleHandleSize); ImGui::PopStyleColor(); if (ImGui::IsItemActivated() && !OtherActions) { UI->InteractTransformMode = 1; } if (UI->InteractTransformMode == 1 && ImGui::IsItemActive()) { uint32 side = i; if (side == 0) { Interact->Scale -= io.MouseDelta.y / BoxLength.y; Interact->Position.y += io.MouseDelta.y / 2; } else if (side == 1) { Interact->Scale += io.MouseDelta.x / BoxLength.x; Interact->Position.x += io.MouseDelta.x / 2; } else if (side == 2) { Interact->Scale += io.MouseDelta.y / BoxLength.y; Interact->Position.y += io.MouseDelta.y / 2; } else if (side == 3) { Interact->Scale -= io.MouseDelta.x / BoxLength.x; Interact->Position.x += io.MouseDelta.x / 2; } } ImGui::PopID(); } bool32 InBounds = false; // Scale if cursor is on button within the UV, rotate if outside UV, and position if a non-button is dragged. if (U >= 0.0f && U <= 1.0f && V >= 0.0f && V <= 1.0f) { ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImGui::ColorConvertFloat4ToU32(ImVec4(0.6f, 0.0f, 0.3f, 1.0f))); InBounds = true; } for (int i = 0; i < 4; i++) { ImGui::SetCursorScreenPos(P[i] - ScaleHandleSize/2); ImGui::PushID(i); ImGui::Button("##ScaleRotateCorners", ScaleHandleSize); if (ImGui::IsItemActivated() && !OtherActions) { if (InBounds) UI->InteractTransformMode = 1; else UI->InteractTransformMode = 2; } // Scale part if (UI->InteractTransformMode == 1 && ImGui::IsItemActive()) { // TODO(fox): Corner dragging scale only works in the X // axis. Mostly feels right when dragged how you expect, // but I'll fix it if someone complains. uint32 side = i; if (side == 0) { Interact->Scale -= io.MouseDelta.x / BoxLength.x; Interact->Position.x += io.MouseDelta.x / 2; Interact->Position.y += io.MouseDelta.x*(BoxLength.y/BoxLength.x) / 2; } else if (side == 1) { Interact->Scale += io.MouseDelta.x / BoxLength.x; Interact->Position.x += io.MouseDelta.x / 2; Interact->Position.y -= io.MouseDelta.x*(BoxLength.y/BoxLength.x) / 2; } else if (side == 2) { Interact->Scale += io.MouseDelta.x / BoxLength.x; Interact->Position.x += io.MouseDelta.x / 2; Interact->Position.y += io.MouseDelta.x*(BoxLength.y/BoxLength.x) / 2; } else if (side == 3) { Interact->Scale -= io.MouseDelta.x / BoxLength.x; Interact->Position.x += io.MouseDelta.x / 2; Interact->Position.y -= io.MouseDelta.x*(BoxLength.y/BoxLength.x) / 2; } } // Rotation part if (UI->InteractTransformMode == 2 && ImGui::IsItemActive()) { real32 LocalX = (io.MousePos.x - UI->CompPos.x)/CompScale.x - InteractMin.x - (BoxLength.x/2); real32 LocalY = (io.MousePos.y - UI->CompPos.y)/CompScale.y - InteractMin.y - (BoxLength.y/2); real32 Slope_Mouse = LocalY/LocalX; real32 Slope_Corner = 0; real32 Slope_Flipped = 0; real32 Dot = 0; // TODO(fox) learn basic geometry to do this properly // We find the angle between the direction of whichever corner the // mouse is grabbing (Slope_Corner) and the mouse's current // position (Slope_Mouse) to get ExtraRadians. The calculation only // works between -90 and 90, so I take the dot product of the // opposite edge of the corner and add the extra degrees when it's negative. v2 SlopeDot = V2(BoxLength.x, BoxLength.y); // top left clockwise uint32 side = i; if (side == 0) { Slope_Corner = BoxLength.y / BoxLength.x; Slope_Flipped = -BoxLength.x / BoxLength.y; Dot = LocalX * -SlopeDot.x + LocalY * -SlopeDot.y; } else if (side == 1) { Slope_Corner = -BoxLength.y / BoxLength.x; Slope_Flipped = BoxLength.x / BoxLength.y; Dot = LocalX * SlopeDot.x + LocalY * -SlopeDot.y; } else if (side == 2) { Slope_Corner = BoxLength.y / BoxLength.x; Slope_Flipped = -BoxLength.x / BoxLength.y; Dot = LocalX * SlopeDot.x + LocalY * SlopeDot.y; } else if (side == 3) { Slope_Corner = -BoxLength.y / BoxLength.x; Slope_Flipped = BoxLength.x / BoxLength.y; Dot = LocalX * -SlopeDot.x + LocalY * SlopeDot.y; } Interact->Radians = atan((Slope_Mouse - Slope_Corner) / (1 + Slope_Mouse * Slope_Corner)); real32 ExtraRadians2 = atan((Slope_Mouse - Slope_Flipped) / (1 + Slope_Mouse * Slope_Flipped)); if (Dot < 0) { if (Interact->Radians < 0) { Interact->Radians = (90 * (PI / 180)) + ExtraRadians2; } else { Interact->Radians = (-90 * (PI / 180)) + ExtraRadians2; } } } ImGui::PopID(); } if (!UI->InteractTransformMode && ImGui::IsMouseClicked(ImGuiMouseButton_Left) && InBounds && !OtherActions) UI->InteractTransformMode = 3; if (UI->InteractTransformMode == 3) { Interact->Position.x += (real32)io.MouseDelta.x/CompScale.x; Interact->Position.y += (real32)io.MouseDelta.y/CompScale.y; } if (UI->InteractTransformMode) { if (io.MouseDelta.x || io.MouseDelta.y) State->UpdateFrame = true; if (!ImGui::IsMouseDown(ImGuiMouseButton_Left)) UI->InteractTransformMode = 0; } if (ImGui::IsKeyPressed(ImGuiKey_Escape)) { State->Interact_Active = interact_type_none; State->UpdateFrame = true; } // Second condition so you don't have to reach for Enter. if (ImGui::IsKeyPressed(ImGuiKey_Enter) || (ImGui::IsMouseClicked(ImGuiMouseButton_Left) && io.KeyCtrl)) { int h = 0, c = 0, i = 0; History_Entry_Commit(Memory, "Transform layers"); while (Block_Loop(Memory, F_Layers, File->Layer_Count, &h, &c, &i)) { block_layer *Layer = (block_layer *)Memory_Block_AddressAtIndex(Memory, F_Layers, i); if (Layer->IsSelected) { History_Action_Swap(Memory, F_File, sizeof(Layer->x.CurrentValue), &Layer->x.CurrentValue); History_Action_Swap(Memory, F_File, sizeof(Layer->y.CurrentValue), &Layer->y.CurrentValue); History_Action_Swap(Memory, F_File, sizeof(Layer->scale.CurrentValue), &Layer->scale.CurrentValue); History_Action_Swap(Memory, F_File, sizeof(Layer->rotation.CurrentValue), &Layer->rotation.CurrentValue); Transform_ApplyInteractive(*(interact_transform *)&State->Interact_Offset[0], &Layer->x.CurrentValue, &Layer->y.CurrentValue, &Layer->rotation.CurrentValue, &Layer->scale.CurrentValue); } } History_Entry_End(Memory); State->Interact_Active = interact_type_none; State->UpdateFrame = true; } if (InBounds == true) { ImGui::PopStyleColor(); } } static void ImGui_LayerViewportUI(project_state *State, memory *Memory, ui *UI, ImDrawList *draw_list, block_composition *MainComp, uint32 CompIndex, block_layer *ParentLayer[4], uint32 Recursions, sorted_comp_info *SortedCompArray, sorted_layer *SortedLayerArray) { sorted_comp_info *SortedCompInfo = &SortedCompArray[CompIndex]; sorted_layer *SortedLayerInfo = Layer_GetSortedArray(SortedLayerArray, SortedCompArray, CompIndex); ImU32 wcol = ImGui::GetColorU32(ImGuiCol_Text); for (int i = 0; i < SortedCompInfo->LayerCount; 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); if (Layer->IsPrecomp) { ParentLayer[Recursions] = Layer; ImGui_LayerViewportUI(State, Memory, UI, draw_list, MainComp, Layer->Block_Source_Index, ParentLayer, Recursions + 1, SortedCompArray, SortedLayerArray); } if (Layer->IsSelected) { uint32 Width = 0, Height = 0; if (!Layer->IsPrecomp) { block_source *Source = (block_source *)Memory_Block_AddressAtIndex(Memory, F_Sources, Layer->Block_Source_Index); Width = Source->Width; Height = Source->Height; } else { block_composition *Comp = (block_composition *)Memory_Block_AddressAtIndex(Memory, F_Precomps, Layer->Block_Source_Index); Width = Comp->Width; Height = Comp->Height; } v2 Point[5] = { V2(Width*Layer->ax.CurrentValue, Height*Layer->ay.CurrentValue), V2(0, 0), V2(Width, 0), V2(0, Height), V2(Width, Height) }; layer_transforms T = Layer_GetTransforms(Layer); if (State->Interact_Active == interact_type_viewport_transform && Layer->IsSelected) { Transform_ApplyInteractive(*(interact_transform *)&State->Interact_Offset[0], &T.x, &T.y, &T.rotation, &T.scale); } v2 NewPos[5]; for (int i = 0; i < 5; i++) { NewPos[i] = TransformPoint(T, Width, Height, Point[i]); } int i = 0; while (i < Recursions) { T = Layer_GetTransforms(ParentLayer[i]); block_composition *Comp = (block_composition *)Memory_Block_AddressAtIndex(Memory, F_Precomps, ParentLayer[i]->Block_Source_Index); Width = Comp->Width; Height = Comp->Height; for (int i = 0; i < 5; i++) { NewPos[i] = TransformPoint(T, Width, Height, NewPos[i]); } i++; } ImVec2 ScreenPoint[5]; for (int i = 0; i < 5; i++) { v2 CompUV = NewPos[i] / V2(MainComp->Width, MainComp->Height); ScreenPoint[i] = ImVec2(UI->CompPos.x + CompUV.x * UI->CompZoom.x, UI->CompPos.y + CompUV.y * UI->CompZoom.y); } draw_list->AddNgon(ScreenPoint[0], 20, wcol, 8, 10.0f); draw_list->AddLine(ScreenPoint[1], ScreenPoint[2], wcol, 2.0f); draw_list->AddLine(ScreenPoint[2], ScreenPoint[4], wcol, 2.0f); draw_list->AddLine(ScreenPoint[1], ScreenPoint[3], wcol, 2.0f); draw_list->AddLine(ScreenPoint[3], ScreenPoint[4], wcol, 2.0f); } /* if (Layer->IsSelected) { uint32 Width = 0, Height = 0; if (!Layer->IsPrecomp) { block_source *Source = (block_source *)Memory_Block_AddressAtIndex(Memory, F_Sources, Layer->Block_Source_Index); Width = Source->Width; Height = Source->Height; } else { block_composition *Comp = (block_composition *)Memory_Block_AddressAtIndex(Memory, F_Precomps, Layer->Block_Source_Index); Width = Comp->Width; Height = Comp->Height; } // Anchor point UI v2 CenterPoint = V2(Width*Layer->ax.CurrentValue, Height*Layer->ay.CurrentValue); ImVec2 ScreenAP = Layer_LocalToScreenSpace(State, Memory, Layer, UI, File->PrincipalCompIndex, CenterPoint); draw_list->AddNgon(ScreenAP, 20, wcol, 8, 10.0f); // Bounding box UI ImVec2 P1 = Layer_LocalToScreenSpace(State, Memory, Layer, UI, File->PrincipalCompIndex, V2(0, 0)); ImVec2 P2 = Layer_LocalToScreenSpace(State, Memory, Layer, UI, File->PrincipalCompIndex, V2(Width, 0)); ImVec2 P3 = Layer_LocalToScreenSpace(State, Memory, Layer, UI, File->PrincipalCompIndex, V2(0, Height)); ImVec2 P4 = Layer_LocalToScreenSpace(State, Memory, Layer, UI, File->PrincipalCompIndex, V2(Width, Height)); draw_list->AddLine(P1, P2, wcol, 2.0f); draw_list->AddLine(P2, P4, wcol, 2.0f); draw_list->AddLine(P1, P3, wcol, 2.0f); draw_list->AddLine(P3, P4, wcol, 2.0f); } */ } } static void ImGui_Viewport(project_data *File, project_state *State, ui *UI, memory *Memory, ImGuiIO io, GLuint textureID, sorted_comp_info *SortedCompArray, sorted_layer *SortedLayerArray) { 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); 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(); // UI+interaction for layer if (State->MostRecentlySelectedLayer > -1) { block_layer *ParentLayer[4]; ImGui_LayerViewportUI(State, Memory, UI, draw_list, MainComp, File->PrincipalCompIndex, ParentLayer, 0, SortedCompArray, SortedLayerArray); if (State->Interact_Active == interact_type_viewport_transform) { ImGui_TransformUI(File, State, Memory, UI, draw_list, io, (interact_transform *)&State->Interact_Offset[0], ViewportMin, MainComp->Width, MainComp->Height); } } // Interactions for dragging and zooming ImGui::SetCursorScreenPos(ViewportMin); ImGui::InvisibleButton("canvas", ViewportScale, ImGuiButtonFlags_MouseButtonLeft | ImGuiButtonFlags_MouseButtonRight); bool32 IsHovered = ImGui::IsItemHovered(); #if 0 bool32 IsActive = ImGui::IsItemActive(); bool32 IsActivated = ImGui::IsItemActivated(); #else bool32 IsActive = ImGui::IsKeyDown(ImGuiKey_3); bool32 IsActivated = ImGui::IsKeyPressed(ImGuiKey_3); #endif bool32 IsDeactivated = ImGui::IsItemDeactivated(); if (IsHovered && IsActivated && !ImGui::IsMouseDown(ImGuiMouseButton_Right)) { // Point to zoom in on if Z is held UI->TempZoomRatio = ImGui_ScreenPointToCompUV(ViewportMin, UI->CompPos, UI->CompZoom, io.MousePos); if (State->Tool == tool_brush) { sorted_layer *SortedLayerInfo = Layer_GetSortedArray(SortedLayerArray, SortedCompArray, File->PrincipalCompIndex); sorted_comp_info SortedCompInfo = SortedCompArray[File->PrincipalCompIndex]; for (int i = SortedCompInfo.LayerCount - 1; i >= 0; 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); block_source *Source = (block_source *)Memory_Block_AddressAtIndex(Memory, F_Sources, Layer->Block_Source_Index); if (Layer->IsSelected && Source->Type == source_type_principal) { State->Interact_Active = interact_type_brush; State->Brush.LayerToPaint_Index = Layer->Block_Source_Index; } } if (State->Brush.LayerToPaint_Index == -1) { State->Interact_Active = interact_type_brush; Layer_DeselectAll(File, State, Memory); Arbitrary_Zero((uint8 *)State->Brush.TransientBitmap, 2048*2048*4); History_Entry_Commit(Memory,"Paint new layer"); uint16 i = Source_Generate_Blank(File, State, Memory, MainComp->Width, MainComp->Height, MainComp->BytesPerPixel); block_layer *Layer = Layer_Init(File, Memory); block_source *Source = (block_source *)Memory_Block_AddressAtIndex(Memory, F_Sources, Layer->Block_Source_Index); State->Brush.LayerToPaint_Index = Memory_Block_LazyIndexAtAddress(Memory, F_Layers, (void *)Layer); Layer->Block_Source_Index = i; Layer->x.CurrentValue = MainComp->Width / 2; Layer->y.CurrentValue = MainComp->Height / 2; int TopOffset = 0; sorted_comp_info SortedCompInfo = SortedCompArray[File->PrincipalCompIndex]; if (SortedCompInfo.LayerCount) { sorted_layer *SortedLayerInfo = Layer_GetSortedArray(SortedLayerArray, SortedCompArray, File->PrincipalCompIndex); block_layer *TopLayer = (block_layer *)Memory_Block_AddressAtIndex(Memory, F_Layers, SortedLayerInfo[SortedCompInfo.LayerCount - 1].Block_Layer_Index); TopOffset = TopLayer->Vertical_Offset; } Layer->Vertical_Offset = TopOffset - 1; Layer->Frame_Start = MainComp->Frame_Start; Layer->Frame_End = MainComp->Frame_End; Layer->IsSelected = true; History_Entry_End(Memory); State->UpdateFrame = true; } } // Layer selection if (!ImGui::IsKeyDown(ImGuiKey_Z) && State->Tool == tool_default && State->Interact_Active == interact_type_none) { int32 Selection = Layer_TestSelection(Memory, State, UI, SortedCompArray, SortedLayerArray, File->PrincipalCompIndex); if (!io.KeyShift && State->Interact_Active == interact_type_none) Layer_DeselectAll(File, State, Memory); if (Selection != -1) Layer_Select(Memory, State, Selection); } } /* if (State->Interact_Active == interact_type_viewport_transform) { interact_transform *Interact = (interact_transform *)&State->Interact_Offset[0]; ImVec2 DragDelta = io.MousePos - Interact->OGPos; Interact->Position = V2(DragDelta.x, DragDelta.y); if (io.MouseDelta.x || io.MouseDelta.y) State->UpdateFrame = true; } */ if (IsActive && ImGui::IsMouseDragging(ImGuiMouseButton_Right, -1.0f)) { UI->CompPos.x += io.MouseDelta.x; UI->CompPos.y += io.MouseDelta.y; } bool32 OtherActions = ImGui::IsKeyDown(ImGuiKey_Z) || ImGui::IsMouseDown(ImGuiMouseButton_Right); if (State->Tool == tool_brush && State->Interact_Active == interact_type_brush) { Assert(State->Brush.LayerToPaint_Index != -1); if (IsActive && !OtherActions) { block_layer *Layer = (block_layer *)Memory_Block_AddressAtIndex(Memory, F_Layers, State->Brush.LayerToPaint_Index); layer_transforms T_Layer = Layer_GetTransforms(Layer); block_source *Source = (block_source *)Memory_Block_AddressAtIndex(Memory, F_Sources, Layer->Block_Source_Index); void *SourceBitmapAddress = Memory_Block_AddressAtIndex(Memory, F_PrincipalBitmaps, Source->Bitmap_Index); ImVec2 MouseDelta = io.MouseDelta; real32 Delta = MouseDelta.x + MouseDelta.y; if (Delta != 0.0f) { real32 DeltaDistance = sqrt(MouseDelta.x * MouseDelta.x + MouseDelta.y * MouseDelta.y); real32 DeltaSlope = MouseDelta.y / MouseDelta.x; for (real32 i = 0; i < DeltaDistance; i += State->Brush.Spacing) { ImVec2 MousePos = ImGui_Brush_CalcMousePos(State, io, MouseDelta, i, DeltaDistance, DeltaSlope); Brush_Render(State, UI, T_Layer, MainComp, Source, SourceBitmapAddress, ViewportMin, MousePos); } } else if (IsActivated) { ImVec2 MousePos = io.MousePos; Brush_Render(State, UI, T_Layer, MainComp, Source, SourceBitmapAddress, ViewportMin, MousePos); } // Memory_Cache_Invalidate(State, Memory, cache_entry_type_comp, Layer->Block_Composition_Index, 0); State->UpdateFrame = true; } if (IsDeactivated) { State->Brush.LayerToPaint_Index = -1; State->Interact_Active = interact_type_none; } } 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::SetCursorScreenPos(ViewportMin); ImGui_Toolbar(State, draw_list); ImGui_RenderUIBrush(State, Memory, ViewportMin, ViewportMax, UI->CompPos, io, MainComp->Width, MainComp->Height); 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(project_state *State, ui *UI, ImDrawList *draw_list, ImVec2 TimelineSizeWithBorder, ImVec2 TimelineAbsolutePos, block_composition MainComp, ImVec2 TimelineZoomSize, ImVec2 TimelineMoveSize) { uint32 LineColor = IM_COL32(200, 200, 200, 40); uint32 PlayheadColor = IM_COL32(000, 000, 200, 160); Assert(TimelineZoomSize.x > 0.0f); real32 x = 0; bool32 RightmostEdge = false; real32 Increment = (real32)1 / MainComp.Frame_Count; if (UI->TimelinePercentZoomed.x > 0.90) Increment = (real32)MainComp.FPS / MainComp.Frame_Count; else if (UI->TimelinePercentZoomed.x > 0.40) Increment *= 2; while (!RightmostEdge) { ImVec2 Min = ImVec2(TimelineAbsolutePos.x + TimelineMoveSize.x + x*TimelineZoomSize.x, TimelineAbsolutePos.y); ImVec2 Max = ImVec2(Min.x + 2, TimelineAbsolutePos.y + TimelineSizeWithBorder.y); if (Min.x < TimelineAbsolutePos.x + TimelineSizeWithBorder.x) { draw_list->AddLine(Min, Max, LineColor); char buf2[6]; uint32 FrameNumber = (uint32)(x * MainComp.Frame_Count) % MainComp.FPS; if (FrameNumber != 0) sprintf(buf2, ":%.2i", FrameNumber); else sprintf(buf2, "%.2i:00", (uint32)(x * MainComp.Frame_Count) / MainComp.FPS); draw_list->AddText(ImVec2(Min.x, TimelineAbsolutePos.y), IM_COL32(200, 200, 200, 130), buf2); x += Increment; if (x > 1.0f) RightmostEdge = true; } else { RightmostEdge = true; } } ImVec2 Min = ImVec2(TimelineAbsolutePos.x + TimelineMoveSize.x + ((real32)State->Frame_Current / MainComp.Frame_Count)*TimelineZoomSize.x, TimelineAbsolutePos.y); ImVec2 Max = ImVec2(Min.x + 2, TimelineAbsolutePos.y + TimelineSizeWithBorder.y); draw_list->AddLine(Min, Max, PlayheadColor); } static void ImGui_GraphInfo(project_data *File, project_state *State, memory *Memory, ui *UI, ImGuiIO io, sorted_property_info *SortedPropertyInfo, uint16 *SortedPropertyArray) { bool open = true; ImGui::Begin("Graph info", &open, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse); int h = 0, c = 0, i = 0; while (Block_Loop(Memory, F_Layers, File->Layer_Count, &h, &c, &i)) { block_layer *Layer = (block_layer *)Memory_Block_AddressAtIndex(Memory, F_Layers, i); if (!Layer->IsSelected) continue; block_string *String = (block_string *)Memory_Block_AddressAtIndex(Memory, F_Strings, Layer->Block_String_Index); ImGui::Text(String->Char); ImGui::PushID(i); for (int h = 0; h < AmountOf(Layer->Property); h++) { property_channel *Property = &Layer->Property[h]; if (Property->Block_Bezier_Count) { sorted_property_info *InfoLocation = SortedPropertyInfo + (i * 7) + h; uint16 *ArrayLocation = SortedPropertyArray + (i * 7 * MAX_KEYFRAMES_PER_BLOCK) + (h * MAX_KEYFRAMES_PER_BLOCK); ImGui::PushID(Property); if (ImGui::Selectable(Property->Name, InfoLocation->IsGraphSelected)) { Property_DeselectAll(File, Memory, SortedPropertyArray); bezier_point *FirstPointAddress = Bezier_LookupAddress(Memory, Property, ArrayLocation[0]); FirstPointAddress->IsSelected = true; } ImGui::PopID(); } } ImGui::PopID(); } ImGui::End(); } static void ImGui_Timeline_DrawKeySheet(project_data *File, project_state *State, memory *Memory, ui *UI, ImGuiIO io, ImDrawList *draw_list, property_channel *Property, uint16 *ArrayLocation, ImVec2 Increment, ImVec2 TimelineAbsolutePos, ImVec2 TimelineMoveSize, ImVec2 TimelineZoomSize, ImVec2 TimelineSize, ImVec2 TimelineSizeWithBorder, real32 LayerIncrement) { ImGui::PushID(Property); for (int p = 0; p < Property->Keyframe_Count; p++) { int k = ArrayLocation[p]; bezier_point *PointAddress = Bezier_LookupAddress(Memory, Property, k); v2 PointPos[3]; Bezier_EvaluateValue(State, PointAddress, PointPos); real32 LocalPos_Ratio_X = PointPos[0].x * Increment.x; real32 Keyframe_ScreenPos_X = TimelineAbsolutePos.x + TimelineMoveSize.x + LocalPos_Ratio_X * TimelineZoomSize.x; ImVec2 Keyframe_ScreenPos(Keyframe_ScreenPos_X, TimelineAbsolutePos.y); if (UI->BoxSelect) { real32 Y_Top = (io.MouseClickedPos[0].y < io.MousePos.y) ? io.MouseClickedPos[0].y : io.MousePos.y; real32 Y_Bottom = (io.MouseClickedPos[0].y > io.MousePos.y) ? io.MouseClickedPos[0].y : io.MousePos.y; real32 X_Left = (io.MouseClickedPos[0].x < io.MousePos.x) ? io.MouseClickedPos[0].x : io.MousePos.x; real32 X_Right = (io.MouseClickedPos[0].x > io.MousePos.x) ? io.MouseClickedPos[0].x : io.MousePos.x; if (Keyframe_ScreenPos.y >= Y_Top && Keyframe_ScreenPos.y <= Y_Bottom && Keyframe_ScreenPos.x >= X_Left && Keyframe_ScreenPos.x <= X_Right) { if (!PointAddress->IsSelected) { PointAddress->IsSelected = 1; } } else if (!io.KeyShift) { PointAddress->IsSelected = 0; } } ImVec2 ButtonSize(16, 16); ImGui::PushID(p); ImU32 PointCol = (PointAddress->IsSelected) ? ImColor(0.8f, 0.5f, 0.0f, 1.0f) : ImColor(0.1f, 0.1f, 0.1f, 1.0f); ImU32 LineCol = (PointAddress->IsSelected) ? ImColor(0.8f, 0.5f, 0.5f, 1.0f) : ImColor(0.4f, 0.4f, 0.4f, 1.0f); ImGui::SetCursorScreenPos(Keyframe_ScreenPos - (ButtonSize * 0.5)); ImGui::InvisibleButton("##keyframemover", ButtonSize, ImGuiMouseButton_Left); bool32 IsHovered = ImGui::IsItemHovered(); bool32 IsItemActive = ImGui::IsItemActive(); bool32 IsItemActivated = ImGui::IsItemActivated(); bool32 IsItemDeactivated = ImGui::IsItemDeactivated(); bool32 LeftClick = ImGui::IsMouseDown(ImGuiMouseButton_Left); bool32 RightClick = ImGui::IsMouseDown(ImGuiMouseButton_Right); if (IsHovered) PointCol = ImColor(1.0f, 0.8f, 0.8f, 1.0f); if (IsItemActivated) { PointAddress->IsSelected = 1; } draw_list->AddCircleFilled(Keyframe_ScreenPos, 4, PointCol); ImGui::PopID(); } ImGui::PopID(); } static void ImGui_Timeline_DrawGraph(project_data *File, project_state *State, memory *Memory, ui *UI, ImGuiIO io, ImDrawList *draw_list, ImVec2 Increment, ImVec2 TimelineAbsolutePos, ImVec2 TimelineMoveSize, ImVec2 TimelineZoomSize, ImVec2 TimelineSize, ImVec2 TimelineSizeWithBorder, real32 LayerIncrement, sorted_property_info *SortedPropertyInfo, uint16 *SortedPropertyArray) { // I'm using the draw splitter here to be able to draw the dots on top of the graph lines. UI->Test.Split(draw_list, 2); int h = 0, c = 0, i = 0; while (Block_Loop(Memory, F_Layers, File->Layer_Count, &h, &c, &i)) { block_layer *Layer = (block_layer *)Memory_Block_AddressAtIndex(Memory, F_Layers, i); if (!Layer->IsSelected) continue; int32 Frame_Start = Layer->Frame_Start; ImGui::PushID(i); if ((State->Interact_Active == interact_type_keyframe_move || State->Interact_Active == interact_type_keyframe_rotate || State->Interact_Active == interact_type_keyframe_scale)) { ImGui_WarpMouse(UI, io.MousePos, TimelineAbsolutePos, TimelineAbsolutePos + TimelineSizeWithBorder); ImVec2 DragDelta = io.MousePos - ImVec2(State->Interact_Offset[2], State->Interact_Offset[3]); DragDelta = DragDelta + (ImVec2(UI->Warp_X, UI->Warp_Y) * TimelineSize); if (io.MouseDelta.x || io.MouseDelta.y) { State->UpdateFrame = true; } if (State->Interact_Active == interact_type_keyframe_move) { // The Y increment varies between graphs, so we have to do it in the Bezier_EvaluateValue call. State->Interact_Offset[0] = (DragDelta.x / TimelineZoomSize.x) / Increment.x; State->Interact_Offset[1] = DragDelta.y; } else if (State->Interact_Active == interact_type_keyframe_scale) { State->Interact_Offset[0] = (DragDelta.x / TimelineSizeWithBorder.x * UI->TimelinePercentZoomed.x) / Increment.x; } else if (State->Interact_Active == interact_type_keyframe_rotate) { State->Interact_Offset[0] = (DragDelta.x / TimelineZoomSize.x); /* real32 Slope_Old = (Keyframe_ScreenPos.y - State->Interact_Offset[3]) / (Keyframe_ScreenPos.x - State->Interact_Offset[2]); real32 Slope_New = (Keyframe_ScreenPos.y - io.MousePos.y) / (Keyframe_ScreenPos.x - io.MousePos.x); State->Interact_Offset[0] = atan((Slope_Old - Slope_New) / (1 + Slope_Old * Slope_New)); */ } } for (int h = 0; h < AmountOf(Layer->Property); h++) { property_channel *Property = &Layer->Property[h]; sorted_property_info *InfoLocation = SortedPropertyInfo + (i * 7) + h; uint16 *ArrayLocation = SortedPropertyArray + (i * 7 * MAX_KEYFRAMES_PER_BLOCK) + (h * MAX_KEYFRAMES_PER_BLOCK); ImGui::PushID(Property); if (Property->Block_Bezier_Count) { real32 MinY, MaxY; Property_MinMax_Y(Memory, State, Property, InfoLocation, &MinY, &MaxY, 0); real32 Y_Increment = 1 / (MaxY - MinY); real32 GraphScale = 0; real32 GraphPos = 0; if ((1 / Y_Increment) < 5) { GraphScale = 0.2; GraphPos = 0.3; } else if ((1 / Y_Increment) > 700) { GraphScale = 0.6; GraphPos = 0.06; } else { GraphScale = 0.4; GraphPos = 0.2; } real32 GraphMoveHeight = TimelineMoveSize.y + (TimelineZoomSize.y * GraphPos); real32 GraphZoomHeight = TimelineZoomSize.y * GraphScale; uint32 GraphCol = InfoLocation->IsGraphSelected ? IM_COL32(255, 180, 150, 255) : IM_COL32(255, 255, 255, 70); bezier_point *PointAddress[2] = {}; ImVec2 Keyframe_ScreenPos[6] = {}; for (int p = 0; p < Property->Keyframe_Count; p++) { int k = ArrayLocation[p]; int Idx = (p % 2); int NewIdx = Idx * 3; int OldIdx = (NewIdx == 3) ? 0 : 3; PointAddress[Idx] = Bezier_LookupAddress(Memory, Property, k); v2 PointPos[3]; Bezier_EvaluateValue(State, PointAddress[Idx], PointPos, GraphZoomHeight, Y_Increment); ImVec2 Keyframe_LocalPos[3] = { V2(PointPos[0]), V2(PointPos[0] + PointPos[1]), V2(PointPos[0] + PointPos[2]) }; ImVec2 Keyframe_LocalPos_Ratio[3]; for (int b = 0; b < 3; b++) { Keyframe_LocalPos_Ratio[b] = (Keyframe_LocalPos[b] - ImVec2(0, MinY)) * ImVec2(Increment.x, Y_Increment); Keyframe_ScreenPos[NewIdx + b] = TimelineAbsolutePos + ImVec2(TimelineMoveSize.x, GraphMoveHeight) + ((ImVec2(1, -1) * Keyframe_LocalPos_Ratio[b] + ImVec2(0, 1)) * ImVec2(TimelineZoomSize.x, GraphZoomHeight)); } if (UI->BoxSelect) { real32 Y_Top = (io.MouseClickedPos[0].y < io.MousePos.y) ? io.MouseClickedPos[0].y : io.MousePos.y; real32 Y_Bottom = (io.MouseClickedPos[0].y > io.MousePos.y) ? io.MouseClickedPos[0].y : io.MousePos.y; real32 X_Left = (io.MouseClickedPos[0].x < io.MousePos.x) ? io.MouseClickedPos[0].x : io.MousePos.x; real32 X_Right = (io.MouseClickedPos[0].x > io.MousePos.x) ? io.MouseClickedPos[0].x : io.MousePos.x; if (Keyframe_ScreenPos[NewIdx].y >= Y_Top && Keyframe_ScreenPos[NewIdx].y <= Y_Bottom && Keyframe_ScreenPos[NewIdx].x >= X_Left && Keyframe_ScreenPos[NewIdx].x <= X_Right) { if (!PointAddress[Idx]->IsSelected) { PointAddress[Idx]->IsSelected = 1; } } else if (!io.KeyShift) { PointAddress[Idx]->IsSelected = 0; } } UI->Test.SetCurrentChannel(draw_list, 1); ImVec2 ButtonSize(16, 16); ImGui::PushID(k); int Max = PointAddress[Idx]->IsSelected ? 2 : 0; for (int b = Max; b >= 0; b--) { ImU32 PointCol = ((PointAddress[Idx]->IsSelected - 1) == b) ? ImColor(0.8f, 0.5f, 0.0f, 1.0f) : ImColor(0.1f, 0.1f, 0.1f, 1.0f); ImU32 LineCol = ((PointAddress[Idx]->IsSelected - 1) == b) ? ImColor(0.8f, 0.5f, 0.5f, 1.0f) : ImColor(0.4f, 0.4f, 0.4f, 1.0f); ImGui::PushID(b); ImGui::SetCursorScreenPos(Keyframe_ScreenPos[NewIdx + b] - (ButtonSize * 0.5)); ImGui::InvisibleButton("##keyframemover", ButtonSize, ImGuiMouseButton_Left); bool32 IsHovered = ImGui::IsItemHovered(); bool32 IsItemActive = ImGui::IsItemActive(); bool32 IsItemActivated = ImGui::IsItemActivated(); bool32 IsItemDeactivated = ImGui::IsItemDeactivated(); bool32 LeftClick = ImGui::IsMouseDown(ImGuiMouseButton_Left); bool32 RightClick = ImGui::IsMouseDown(ImGuiMouseButton_Right); if (IsHovered) PointCol = ImColor(1.0f, 0.8f, 0.8f, 1.0f); if (IsItemActivated) { PointAddress[Idx]->IsSelected = b+1; } if (b != 0 && PointAddress[Idx]->IsSelected) draw_list->AddLine(Keyframe_ScreenPos[NewIdx], Keyframe_ScreenPos[NewIdx + b], LineCol, 2.0f); if (b == 0) { draw_list->AddCircleFilled(Keyframe_ScreenPos[NewIdx + b], 4, PointCol); if (InfoLocation->IsGraphSelected) { char buf[8]; sprintf(buf, "%.2f", Keyframe_LocalPos[0].y); draw_list->AddText(Keyframe_ScreenPos[NewIdx + b], 0xFFFFFFFF, buf); } } else { draw_list->AddCircle(Keyframe_ScreenPos[NewIdx + b], 6, PointCol, 0, 2); } ImGui::PopID(); } ImGui::PopID(); UI->Test.SetCurrentChannel(draw_list, 0); if (p != 0) { if (PointAddress[0]->Type == interpolation_type_bezier && PointAddress[1]->Type == interpolation_type_bezier) { draw_list->AddBezierCubic(Keyframe_ScreenPos[OldIdx], Keyframe_ScreenPos[OldIdx + 2], Keyframe_ScreenPos[NewIdx + 1], Keyframe_ScreenPos[NewIdx], GraphCol, 1.0f, 0); } else { draw_list->AddLine(Keyframe_ScreenPos[0], Keyframe_ScreenPos[3], GraphCol, 1.0f); } } } } ImGui::PopID(); } ImGui::PopID(); } UI->Test.Merge(draw_list); } 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, sorted_property_info *SortedPropertyInfo, uint16 *SortedPropertyArray) { 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); // Layers are drawn from top to bottom, so we can account for opened precomps/keyframes // without needing another loop. real32 DisplayOffset = 0; for (int i = SortedCompInfo.LayerCount - 1; i >= 0; 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 = SortEntry.SortedOffset + DisplayOffset; Layer_Evaluate_Display(Layer, SortedLayerArray, SortedCompArray, SortedLayerInfo, i, &DisplayOffset); if (Layer->IsSelected) Layer_Interact_Evaluate(Memory, State, Index_Physical, SortedCompInfo, SortedLayerInfo, &Frame_Start, &Frame_End); ImVec2 Layer_LocalPos = ImVec2(Frame_Start, Vertical_Offset); ImVec2 Layer_LocalSize = ImVec2(Frame_End - Frame_Start, Layer->Vertical_Height); ImVec2 Layer_LocalPos_Ratio = (Layer_LocalPos * Increment); ImVec2 Layer_LocalSize_Ratio = Layer_LocalSize * Increment; ImVec2 Layer_ScreenPos_Min = TimelineAbsolutePos + TimelineMoveSize + (Layer_LocalPos_Ratio * TimelineZoomSize); ImVec2 Layer_ScreenPos_Max = TimelineAbsolutePos + TimelineMoveSize + ((Layer_LocalPos_Ratio + Layer_LocalSize_Ratio) * TimelineZoomSize); ImVec2 Layer_ScreenSize = Layer_ScreenPos_Max - Layer_ScreenPos_Min; // UI ImU32 LayerColor = 0; ImU32 BorderColor = 0; LayerColor = ImColor(UI->LayerColors[Layer->ColIndex]); BorderColor = ImColor(0.2, 0.2, 0.2, 1.0f); draw_list->AddRectFilled(Layer_ScreenPos_Min, Layer_ScreenPos_Max, LayerColor); draw_list->AddRect(Layer_ScreenPos_Min, Layer_ScreenPos_Max, BorderColor, 2); block_string *String = (block_string *)Memory_Block_AddressAtIndex(Memory, F_Strings, Layer->Block_String_Index); char buf[21]; sprintf(buf, "%.2f, %.2f", Layer->Vertical_Offset, SortEntry.SortedOffset); // draw_list->AddText(Layer_ScreenPos_Min, 0xFFFFFFFF, buf); if (UI->TimelinePercentZoomed.y <= 1.0f) 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); } // // Main interaction ImGui::PushID(i); if (UI->TimelineMode == timeline_mode_default) { if (UI->BoxSelect && UI->TimelineMode == timeline_mode_default) { bool32 Test = 0; if (io.MouseClickedPos[0].y < io.MousePos.y) Test = (Layer_ScreenPos_Min.y >= io.MouseClickedPos[0].y && Layer_ScreenPos_Min.y <= io.MousePos.y); else Test = (Layer_ScreenPos_Max.y <= io.MouseClickedPos[0].y && Layer_ScreenPos_Max.y >= io.MousePos.y); if (io.MouseClickedPos[0].x < io.MousePos.x) Test &= (Layer_ScreenPos_Max.x >= io.MouseClickedPos[0].x && Layer_ScreenPos_Min.x <= io.MousePos.x); else Test &= (Layer_ScreenPos_Min.x <= io.MouseClickedPos[0].x && Layer_ScreenPos_Max.x >= io.MousePos.x); if (Test) { if (!Layer->IsSelected) { Layer_Select(Memory, State, Index_Physical); } } else if (!io.KeyShift) { Layer->IsSelected = false; } } ImVec2 ResizeSize = ImVec2(Increment.x * 0.45 * TimelineZoomSize.x, Layer_ScreenSize.y); ImVec2 ResizePos[2] = { Layer_ScreenPos_Min, ImVec2(Layer_ScreenPos_Max.x - ResizeSize.x, Layer_ScreenPos_Min.y) }; for (int b = 0; b < 2; b++) { ImGui::PushID(b); ImGui::SetCursorScreenPos(ResizePos[b]); ImGui::Button("##layer_resize", ResizeSize); if (ImGui::IsItemHovered()) { ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeEW); } if (ImGui::IsItemActivated()) { if (!Layer->IsSelected) { if (!io.KeyShift) Layer_DeselectAll(File, State, Memory); Layer_Select(Memory, State, Index_Physical); } } if (ImGui::IsItemActive()) { if (ImGui::IsMouseDragging(ImGuiMouseButton_Left, -1)) { Assert(Layer->IsSelected); State->Interact_Active = interact_type_layer_timeadjust; ImVec2 DragDelta = ImGui::GetMouseDragDelta(); DragDelta = DragDelta + (ImVec2(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) { History_Entry_Commit(Memory, "Adjust layer timing"); int h = 0, c = 0, i = 0; while (Block_Loop(Memory, F_Layers, File->Layer_Count, &h, &c, &i)) { block_layer *Layer = (block_layer *)Memory_Block_AddressAtIndex(Memory, F_Layers, i); if (Layer->IsSelected) { // NOTE(fox): Some data on the tree could be saved here. History_Action_Swap(Memory, F_File, sizeof(Layer->Frame_Start), &Layer->Frame_Start); History_Action_Swap(Memory, F_File, sizeof(Layer->Frame_End), &Layer->Frame_End); Layer_Interact_Evaluate(Memory, State, Index_Physical, SortedCompInfo, SortedLayerInfo, &Layer->Frame_Start, &Layer->Frame_End); } } History_Entry_End(Memory); 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(File, State, Memory); Layer_Select(Memory, State, Index_Physical); } } ImGui::OpenPopupOnItemClick("layerpopup", ImGuiPopupFlags_MouseButtonRight); if (ImGui::BeginPopup("layerpopup")) { if (ImGui::MenuItem("Pre-compose layer")) { Precomp_UICreateButton(File, State, Memory, CompIndex, SortedCompInfo, SortedLayerInfo); } if (ImGui::BeginMenu("Layer color")) { ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0.0f, 0.0f)); for (int c = 0; c < AmountOf(UI->LayerColors); c++) { ImGui::PushID(c); ImGui::PushStyleColor(ImGuiCol_Button, UI->LayerColors[c]); real32 Size = ImGui::GetFontSize() * 2; ImVec2 ColSize(Size, Size); ImGui::Button("##test", ColSize); if ((c+1) % 4) { ImGui::SameLine(); } ImGui::PopStyleColor(); ImGui::PopID(); } ImGui::PopStyleVar(); ImGui::EndMenu(); } ImGui::EndPopup(); } if (ImGui::IsItemActive()) { if (ImGui::IsMouseDragging(ImGuiMouseButton_Left, -1)) { if (State->Interact_Active == interact_type_none) { State->Interact_Active = interact_type_layer_move; // TODO(fox): Selected layers inside precomps will have interactions doubled, // so I'm forcing interaction to only be with members of the same precomp. // Could be made more intuitive later. Layer_RecursiveDeselect(Memory, SortedCompArray, SortedLayerArray, Layer->Block_Composition_Index, File->PrincipalCompIndex); } ImVec2 DragDelta = ImGui::GetMouseDragDelta(); DragDelta = DragDelta + (ImVec2(UI->Warp_X, UI->Warp_Y) * TimelineSize); ImVec2 Offset_Old = ImVec2(State->Interact_Offset[0], State->Interact_Offset[1]); ImVec2 Offset_New = (DragDelta / TimelineSizeWithBorder * UI->TimelinePercentZoomed) * ImVec2(Comp->Frame_Count, -LayerIncrement); // if (((int32)Offset_Old.x != (int32)Offset_New.x) || ((int32)Offset_Old.y != (int32)Offset_New.y)) State->UpdateFrame = true; State->Interact_Offset[0] = Offset_New.x; State->Interact_Offset[1] = Offset_New.y; } } if (ImGui::IsItemDeactivated()) { if (State->Interact_Active == interact_type_layer_move) { History_Entry_Commit(Memory, "Move layers"); for (int i = 0; i < SortedCompInfo.LayerCount; 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); // NOTE(fox): Some data on the tree could be saved here. History_Action_Swap(Memory, F_File, sizeof(Layer->Vertical_Offset), &Layer->Vertical_Offset); Layer->Vertical_Offset = SortEntry.SortedOffset; if (Layer->IsSelected) { History_Action_Swap(Memory, F_File, sizeof(Layer->Frame_Start), &Layer->Frame_Start); History_Action_Swap(Memory, F_File, sizeof(Layer->Frame_End), &Layer->Frame_End); Layer_Interact_Evaluate(Memory, State, Index_Physical, SortedCompInfo, SortedLayerInfo, &Layer->Frame_Start, &Layer->Frame_End); } } State->Interact_Active = interact_type_none; History_Entry_End(Memory); } } // Keyframe view uint32 Channel = 0; for (int h = 0; h < AmountOf(Layer->Property); h++) { property_channel *Property = &Layer->Property[h]; if (Property->IsToggled) { // sorted_property_info *InfoLocation = SortedPropertyInfo + (i * 7) + h; ImVec2 GraphPos(TimelineAbsolutePos.x, Layer_ScreenPos_Min.y + (Layer_ScreenSize.y * 2) + (Layer_ScreenSize.y * Channel)); uint16 *ArrayLocation = SortedPropertyArray + (i * 7 * MAX_KEYFRAMES_PER_BLOCK) + (h * MAX_KEYFRAMES_PER_BLOCK); ImGui_Timeline_DrawKeySheet(File, State, Memory, UI, io, draw_list, Property, ArrayLocation, Increment, GraphPos, TimelineMoveSize, TimelineZoomSize, TimelineSize, TimelineSizeWithBorder, LayerIncrement); Channel++; } } } // // Precomp recursion 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; // Layer_Evaluate_Display(Layer_Top, SortedLayerArray, SortedCompArray, Precomp_SortedLayerInfo, Precomp_SortedCompInfo.LayerCount - 1, &SmallestY); // Layer_Evaluate_Display(Layer_Bottom, SortedLayerArray, SortedCompArray, Precomp_SortedLayerInfo, 0, &LargestY); real32 PrecompHeight = LargestY - SmallestY + 2; ImVec2 MinClipPos = ImVec2(Layer_ScreenPos_Min.x, Layer_ScreenPos_Max.y); ImVec2 MaxClipPos = ImVec2(Layer_ScreenPos_Max.x, MinClipPos.y + (PrecompHeight * Increment.y * TimelineZoomSize.y)); draw_list->AddRectFilled(MinClipPos, MaxClipPos, ImColor(0.2f, 0.2f, 0.2f, 1.0f)); ImVec2 Layer_LocalPos_Screen = Layer_LocalPos_Ratio * TimelineZoomSize; ImVec2 NestedTimelineAbsolutePos = TimelineAbsolutePos + Layer_LocalPos_Screen - ImVec2(0, SmallestY * Increment.y * TimelineZoomSize.y) + ImVec2(0, Layer_ScreenSize.y * 1.5); ImGui::PushClipRect(MinClipPos, MaxClipPos, true); draw_list->PushClipRect(MinClipPos, MaxClipPos, true); ImGui_Timeline_DrawPrecomp(File, State, Memory, UI, io, draw_list, Layer->Block_Source_Index, Increment, NestedTimelineAbsolutePos, TimelineMoveSize, TimelineZoomSize, TimelineSize, TimelineSizeWithBorder, LayerIncrement, SortedCompArray, SortedLayerArray, SortedPropertyInfo, SortedPropertyArray); draw_list->PopClipRect(); ImGui::PopClipRect(); } // ImGui::PopID(); } ImGui::PopID(); } static void ImGui_Timeline(project_data *File, project_state *State, memory *Memory, ui *UI, ImGuiIO io, sorted_comp_info *SortedCompArray, sorted_layer *SortedLayerArray, sorted_property_info *SortedPropertyInfo, uint16 *SortedPropertyArray) { if (UI->TimelineMode == timeline_mode_graph) ImGui_GraphInfo(File, State, Memory, UI, io, SortedPropertyInfo, SortedPropertyArray); 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); // ImVec2 *ActivePercentZoomed = (UI->TimelineMode != timeline_mode_graph) ? &UI->TimelinePercentZoomed : &UI->GraphPercentZoomed; // ImVec2 *ActivePercentOffset = (UI->TimelineMode != timeline_mode_graph) ? &UI->TimelinePercentOffset : &UI->GraphPercentOffset; ImVec2 *ActivePercentZoomed = &UI->TimelinePercentZoomed; ImVec2 *ActivePercentOffset = &UI->TimelinePercentOffset; if (ActivePercentZoomed->x == 0) { *ActivePercentZoomed = ImVec2(1, 1); } ImVec2 ActiveZoomSize = TimelineSizeWithBorder / *ActivePercentZoomed; ImVec2 ActiveMoveSize = TimelineSizeWithBorder * *ActivePercentOffset / *ActivePercentZoomed; ImVec2 TimelineZoomSize = TimelineSizeWithBorder / UI->TimelinePercentZoomed; ImVec2 TimelineMoveSize = TimelineSizeWithBorder * UI->TimelinePercentOffset / UI->TimelinePercentZoomed; DebugWatchVar("TimelineY: ", &TimelineMoveSize.y, d_float); ImVec2 Increment = ImVec2((real32)1 / MainComp->Frame_Count, (real32)1 / LayerIncrement); ImGui_Timeline_DrawPrecomp(File, State, Memory, UI, io, draw_list, File->PrincipalCompIndex, Increment, TimelineAbsolutePos, TimelineMoveSize, TimelineZoomSize, TimelineSize, TimelineSizeWithBorder, LayerIncrement, SortedCompArray, SortedLayerArray, SortedPropertyInfo, SortedPropertyArray); if (UI->TimelineMode == timeline_mode_graph) { if (UI->GraphMoveSize.y == 0) { UI->GraphZoomSize = ImVec2(1, UI->TimelinePercentZoomed.y ); UI->GraphMoveSize = ImVec2(0, -UI->TimelinePercentOffset.y ); } ImVec2 ZoomDifference = (UI->TimelinePercentZoomed / UI->GraphZoomSize); ImVec2 MoveDifference = (UI->TimelinePercentOffset + (UI->GraphMoveSize)); DebugWatchVar("zoomdif: ", &ZoomDifference.y, d_float); DebugWatchVar("movedif: ", &MoveDifference.y, d_float); ImVec2 GraphZoomSize = TimelineSizeWithBorder / ZoomDifference; ImVec2 GraphMoveSize = TimelineSizeWithBorder * (MoveDifference) / UI->TimelinePercentZoomed; DebugWatchVar("zoomsize: ", &GraphZoomSize.y, d_float); DebugWatchVar("movesize: ", &GraphMoveSize.y, d_float); draw_list->AddRectFilled(WindowMinAbs, WindowMaxAbs, IM_COL32(50, 50, 50, 230)); ImGui_Timeline_DrawGraph(File, State, Memory, UI, io, draw_list, Increment, TimelineAbsolutePos, GraphMoveSize, GraphZoomSize, TimelineSize, TimelineSizeWithBorder, LayerIncrement, SortedPropertyInfo, SortedPropertyArray); } ImGui_TimelineHorizontalIncrementDraw(State, UI, draw_list, TimelineSizeWithBorder, TimelineAbsolutePos, *MainComp, TimelineZoomSize, TimelineMoveSize); ImVec2 MouseDelta = io.MouseDelta / TimelineSize; real32 BarHandleSize = FontHeight; real32 BarThickness = 50; real32 BarMinZoom = 0.01; real32 BarH_Pos = -TimelineSizeWithBorder.x * ActivePercentOffset->x; real32 BarH_Size = TimelineSizeWithBorder.x / (1 / ActivePercentZoomed->x); // I use "UI" to denote the size/position after clipping the bar so that it // doesn't go out of bounds and the handles are always selectable at the edges. real32 BarH_Offset = Max(BarH_Pos, 0); real32 BarH_SizeUI = (BarH_Size + BarH_Pos > TimelineSizeWithBorder.x) ? TimelineSizeWithBorder.x - BarH_Pos : BarH_Size + (BarH_Pos - BarH_Offset); if (BarH_Offset == 0 && BarH_SizeUI > TimelineSizeWithBorder.x) BarH_SizeUI = TimelineSizeWithBorder.x; BarH_SizeUI = BarH_SizeUI - BarHandleSize*2; BarH_SizeUI = Max(BarH_SizeUI, FontHeight*4); BarH_Offset = Min(BarH_Offset, TimelineSize.x - BarH_SizeUI - BarHandleSize*4); ImVec2 BarH_PosUI = TimelineAbsolutePos + ImVec2(BarH_Offset, TimelineSize.y - BarThickness); bool32 BarHeld = false; ImGui::SetCursorScreenPos(BarH_PosUI); ImGui::Button("##scrollbarleft", ImVec2(BarHandleSize, BarThickness)); if ((ImGui::IsItemActive() && ImGui::IsMouseDragging(ImGuiMouseButton_Left, -1))) { if ((ActivePercentZoomed->x - MouseDelta.x) > BarMinZoom) { ActivePercentZoomed->x -= MouseDelta.x; ActivePercentOffset->x -= MouseDelta.x; } BarHeld = true; } ImGui::SetCursorScreenPos(BarH_PosUI + ImVec2(BarHandleSize, 0)); ImGui::Button("##scrollbarhori", ImVec2(BarH_SizeUI, BarThickness)); if (ImGui::IsItemActive() && ImGui::IsMouseDragging(ImGuiMouseButton_Left, -1)) { ActivePercentOffset->x -= MouseDelta.x; BarHeld = true; } ImGui::SetCursorScreenPos(BarH_PosUI + ImVec2(BarHandleSize, 0) + ImVec2(BarH_SizeUI, 0)); ImGui::Button("##scrollbarright", ImVec2(BarHandleSize, BarThickness)); if ((ImGui::IsItemActive() && ImGui::IsMouseDragging(ImGuiMouseButton_Left, -1))) { if ((ActivePercentZoomed->x + MouseDelta.x) > BarMinZoom) { ActivePercentZoomed->x += MouseDelta.x; } BarHeld = true; } if (BarHeld) { ImGui_WarpMouse(UI, io.MousePos, TimelineAbsolutePos, TimelineAbsolutePos + TimelineSizeWithBorder, 1); } Assert(ActivePercentZoomed->x > BarMinZoom); real32 BarV_MaxSize = TimelineSizeWithBorder.y - BarThickness/2; real32 BarV_Pos = -BarV_MaxSize * ActivePercentOffset->y; real32 BarV_Size = BarV_MaxSize / (1 / ActivePercentZoomed->y); BarV_Size = Max(BarV_Size, FontHeight*4); real32 BarV_Offset = Max(BarV_Pos, 0); real32 BarV_SizeUI = (BarV_Size + BarV_Pos > BarV_MaxSize) ? BarV_MaxSize - BarV_Pos : BarV_Size + (BarV_Pos - BarV_Offset); if (BarV_Offset == 0 && BarV_SizeUI > BarV_MaxSize) BarV_SizeUI = BarV_MaxSize; BarV_SizeUI = BarV_SizeUI - BarHandleSize*2; BarV_SizeUI = Max(BarV_SizeUI, FontHeight*4); BarV_Offset = Min(BarV_Offset, BarV_MaxSize - BarV_SizeUI - BarHandleSize*4); ImVec2 BarV_PosUI = TimelineAbsolutePos + ImVec2(TimelineSize.x - BarThickness, BarV_Offset); BarHeld = false; ImGui::SetCursorScreenPos(BarV_PosUI); ImGui::Button("##h-scrollbarleft", ImVec2(BarThickness, BarHandleSize)); if ((ImGui::IsItemActive() && ImGui::IsMouseDragging(ImGuiMouseButton_Left, -1))) { ActivePercentZoomed->y -= MouseDelta.y; ActivePercentOffset->y -= MouseDelta.y; BarHeld = true; } ImGui::SetCursorScreenPos(BarV_PosUI + ImVec2(0, BarHandleSize)); ImGui::Button("##h-scrollbar", ImVec2(BarThickness, BarV_SizeUI)); if (ImGui::IsItemHovered() && io.MouseWheel) { ActivePercentOffset->y -= io.MouseWheel/10; } if (ImGui::IsItemActive() && ImGui::IsMouseDragging(ImGuiMouseButton_Left, -1)) { ActivePercentOffset->y -= MouseDelta.y; BarHeld = true; } ImGui::SetCursorScreenPos(BarV_PosUI + ImVec2(0, BarHandleSize) + ImVec2(0, BarV_SizeUI)); ImGui::Button("##h-scrollbarright", ImVec2(BarThickness, BarHandleSize)); if ((ImGui::IsItemActive() && ImGui::IsMouseDragging(ImGuiMouseButton_Left, -1))) { ActivePercentZoomed->y += MouseDelta.y; BarHeld = true; } ActivePercentZoomed->y = Max(ActivePercentZoomed->y, 0.01); if (BarHeld) { ImGui_WarpMouse(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 + ActiveMoveSize)) / ActiveZoomSize; if (io.KeyShift) { ActivePercentOffset->y += Increment*Direction; } else if (io.KeyCtrl) { ActivePercentOffset->x += Increment*Direction*0.3; } else { if (Direction == 1) { *ActivePercentZoomed = *ActivePercentZoomed - (*ActivePercentZoomed * Increment); *ActivePercentOffset = *ActivePercentOffset - ((*ActivePercentOffset * Increment) + Offset*Increment); } else { *ActivePercentOffset = ((*ActivePercentOffset + Offset*Increment) / (1.0f - Increment)); *ActivePercentZoomed = (*ActivePercentZoomed / (1.0f - Increment)); } } } if (LeftClick) { if (IsItemActivated) { if (!io.KeyShift && UI->TimelineMode == timeline_mode_default) Layer_DeselectAll(File, State, Memory); if (State->Interact_Active == interact_type_keyframe_move || State->Interact_Active == interact_type_keyframe_rotate || State->Interact_Active == interact_type_keyframe_scale) { // bool32 CommitAction = 0; // if (!CommitAction) { // History_Entry_Commit(Memory, "Delete source"); // CommitAction = 1; History_Entry_Commit(Memory, "Add keyframe"); int h = 0, c = 0, i = 0; while (Block_Loop(Memory, F_Layers, File->Layer_Count, &h, &c, &i)) { block_layer *Layer = (block_layer *)Memory_Block_AddressAtIndex(Memory, F_Layers, i); if (!Layer->IsSelected) continue; for (int h = 0; h < AmountOf(Layer->Property); h++) { uint16 *ArrayLocation = SortedPropertyArray + (i * 7 * MAX_KEYFRAMES_PER_BLOCK) + (h * MAX_KEYFRAMES_PER_BLOCK); property_channel *Property = &Layer->Property[h]; for (int p = 0; p < Property->Keyframe_Count; p++) { int k = ArrayLocation[p]; bezier_point *PointAddress = Bezier_LookupAddress(Memory, Property, k); if (PointAddress->IsSelected) { v2 NewPos[3]; Bezier_EvaluateValue(State, PointAddress, NewPos); History_Action_Swap(Memory, F_Bezier, sizeof(PointAddress->Pos), &PointAddress->Pos); PointAddress->Pos[0] = NewPos[0]; PointAddress->Pos[1] = NewPos[1]; PointAddress->Pos[2] = NewPos[2]; } } } } History_Entry_End(Memory); State->Interact_Offset[0] = 0; State->Interact_Offset[1] = 0; State->Interact_Offset[2] = 0; State->Interact_Offset[3] = 0; State->Interact_Active = interact_type_none; } UI->BoxSelect = true; } if (IsItemActive) { Assert(UI->BoxSelect); draw_list->AddRectFilled(io.MouseClickedPos[0], io.MousePos, IM_COL32(0, 0, 200, 50)); } } else { 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, ui *UI, memory *Memory, ImGuiIO io) { if (ImGui::IsKeyPressed(ImGuiKey_Q)) { State->IsRunning = false; } if (ImGui::IsKeyPressed(ImGuiKey_W)) { block_composition *MainComp = (block_composition *)Memory_Block_AddressAtIndex(Memory, F_Precomps, File->PrincipalCompIndex); State->Frame_Current = ((State->Frame_Current - 1) < 0) ? 0 : State->Frame_Current - 1; State->UpdateFrame = true; } if (ImGui::IsKeyPressed(ImGuiKey_E)) { block_composition *MainComp = (block_composition *)Memory_Block_AddressAtIndex(Memory, F_Precomps, File->PrincipalCompIndex); State->Frame_Current = ((State->Frame_Current + 1) >= MainComp->Frame_Count) ? 0 : State->Frame_Current + 1; State->UpdateFrame = true; } if (ImGui::IsKeyPressed(ImGuiKey_X)) { v4 Temp = UI->Color; UI->Color = UI->AltColor; UI->AltColor = Temp; } if (ImGui::IsKeyPressed(ImGuiKey_V)) { State->Tool = tool_default; } if (ImGui::IsKeyPressed(ImGuiKey_B)) { State->Tool = tool_brush; } if (ImGui::IsKeyPressed(ImGuiKey_Tab)) { UI->TimelineMode = (UI->TimelineMode == timeline_mode_default) ? timeline_mode_graph : timeline_mode_default; UI->GraphZoomSize = ImVec2(0, 0); UI->GraphMoveSize = ImVec2(0, 0); } if (UI->TimelineMode == timeline_mode_graph) { if (ImGui::IsKeyPressed(ImGuiKey_G)) { State->Interact_Offset[2] = io.MousePos.x; State->Interact_Offset[3] = io.MousePos.y; State->Interact_Active = interact_type_keyframe_move; } else if (ImGui::IsKeyPressed(ImGuiKey_R)) { // State->Interact_Offset[2] = io.MousePos.x; // State->Interact_Offset[3] = io.MousePos.y; // State->Interact_Active = interact_type_keyframe_rotate; } else if (ImGui::IsKeyPressed(ImGuiKey_S)) { State->Interact_Offset[2] = io.MousePos.x; State->Interact_Offset[3] = io.MousePos.y; State->Interact_Active = interact_type_keyframe_scale; } } if (ImGui::IsKeyPressed(ImGuiKey_Escape)) { if (State->Interact_Active == interact_type_keyframe_move || State->Interact_Active == interact_type_keyframe_rotate || State->Interact_Active == interact_type_keyframe_scale) { State->Interact_Offset[0] = 0; State->Interact_Offset[1] = 0; State->Interact_Offset[2] = 0; State->Interact_Offset[3] = 0; State->Interact_Active = interact_type_none; State->UpdateFrame = true; } } if (ImGui::IsKeyPressed(ImGuiKey_Space)) { State->IsPlaying ^= 1; } if (ImGui::IsKeyPressed(ImGuiKey_2)) { int h = 0, c = 0, i = 0; while (Block_Loop(Memory, F_Layers, File->Layer_Count, &h, &c, &i)) { block_layer *Layer = (block_layer *)Memory_Block_AddressAtIndex(Memory, F_Layers, i); if (Layer->IsSelected && Layer->IsPrecomp) { Layer->Precomp_Toggled ^= 1; } } } if (ImGui::IsKeyPressed(ImGuiKey_Delete)) { bool32 CommitAction = 0; int h = 0, c = 0, i = 0; int LayerCount = File->Layer_Count; while (Block_Loop(Memory, F_Layers, LayerCount, &h, &c, &i)) { block_layer *Layer = (block_layer *)Memory_Block_AddressAtIndex(Memory, F_Layers, i); if (Layer->IsSelected) { if (!CommitAction) { History_Entry_Commit(Memory, "Delete source"); CommitAction = 1; } Layer_Delete(File, Memory, i); } } if (CommitAction) { History_Entry_End(Memory); State->UpdateFrame = true; State->MostRecentlySelectedLayer = -1; } } #if DEBUG if (ImGui::IsKeyPressed(ImGuiKey_1)) { Debug.ToggleWindow ^= 1; } #endif bool32 mod_key = io.ConfigMacOSXBehaviors ? io.KeySuper : io.KeyCtrl; if (mod_key) { /* if (ImGui::IsKeyPressed(ImGuiKey_S)) { if (io.KeyShift) { State->ImGuiPopups = popup_saveas; } else { if (State->Context[0].Filename[0] == '\0') { State->ImGuiPopups = popup_saveas; } else { if (File_SaveAs(State, Memory, 0, State->Context[0].Filename)) { PostMsg(State, "File saved!"); } else { PostMsg(State, "File save failed..."); } } } } if (ImGui::IsKeyPressed(ImGuiKey_N)) { if (io.KeyShift) State->ImGuiPopups = popup_newfile; else State->ImGuiPopups = popup_newlayer; } */ if (ImGui::IsKeyPressed(ImGuiKey_Z)) { if (io.KeyShift) { History_Redo(Memory); State->UpdateFrame = true; } else { History_Undo(Memory); State->UpdateFrame = true; } } } } static void ImGui_Menu(project_data *File, project_state *State, ui *UI, memory *Memory, ImGuiIO io) { bool open = true; ImGui::Begin("Menu", &open, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_MenuBar); if (ImGui::BeginMenuBar()) { if (ImGui::BeginMenu("File")) { if (ImGui::MenuItem("Save", "Ctrl+S")) { // if (File_SaveAs(State, Memory, 0, State->Context[0].Filename)) { if (0) { PostMsg(State, "File saved!"); } else { PostMsg(State, "File save failed..."); } } /* if (ImGui::MenuItem("New project file", "Ctrl+Shift+N")) { State->ImGuiPopups = popup_newfile; } if (ImGui::BeginMenu("Open file")) { ImGui::InputText("Filename", State->DummyName, 512); if (ImGui::IsItemDeactivated() && ImGui::IsKeyPressed(ImGuiKey_Enter)) { File_Open(State, Memory, State->DummyName); State->Context[Index].UpdateFrame = true; } ImGui::EndMenu(); } if (ImGui::MenuItem("Save as", "Ctrl+Shift+S")) { State->ImGuiPopups = popup_saveas; } */ ImGui::EndMenu(); } if (ImGui::BeginMenu("Layer")) { if (ImGui::BeginMenu("Import source from file")) { ImGui::InputText("Path to image", State->DummyName2, 512); if (ImGui::IsItemDeactivated() && ImGui::IsKeyPressed(ImGuiKey_Enter)) { int SourceIndex = Source_Generate(File, State, Memory, (void *)State->DummyName2); State->UpdateFrame = true; } ImGui::EndMenu(); } /* if (ImGui::MenuItem("New layer", "Ctrl+N")) { State->ImGuiPopups = popup_newlayer; } if (ImGui::BeginMenu("Layer from image path")) { ImGui::InputText("Path to image", State->DummyName2, 512); if (ImGui::IsItemDeactivated() && ImGui::IsKeyPressed(ImGuiKey_Enter)) { Layer_CreateFromFile(State, Memory, Index, State->DummyName2); Layer_DeselectAll(State, File, &Context->UI); layer_sorted Sorts = Layer_GetSortedList(State, File); file_layer *Layer = (file_layer *)Sorts.Location[File->NumberOfLayers - 1]; Layer_Select(Layer, &Context->UI, File->NumberOfLayers - 1); State->Context[Index].UpdateFrame = true; } ImGui::EndMenu(); } if (ImGui::MenuItem("New adjustment layer", "Ctrl+Shift+Y")) { Layer_CreateAdjustment(State, Memory, Index); } if (ImGui::MenuItem("Duplicate layer", "Ctrl+Shift+Y")) { if (State->Context[Index].UI.MostRecentlySelectedLayer > -1) Layer_Duplicate(State, Memory, Index, State->Context[Index].UI.MostRecentlySelectedLayer); } */ ImGui::EndMenu(); } ImGui::EndMenuBar(); } ImGui::End(); } static void ImGui_Popups(project_state *State, ui *UI, memory *Memory, ImGuiIO io) { } #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, sorted_comp_info *SortedCompArray, sorted_layer *SortedLayerArray) { 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_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