#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, char *Name) { ImGui::DragScalar(Name, ImGuiDataType_Float, &Property->CurrentValue, Property->ScrubVal, &Property->MinVal, &Property->MaxVal, "%f"); if (ImGui::IsItemActive()) { State->UpdateFrame = true; } /* if (ImGui::IsItemActivated()) { State->InteractCache[0] = Property->CurrentValue.f; } 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, 0); 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)) State->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(); char *Name = DefaultChannel[h]; ImGui_InteractSliderProperty(State, Memory, Property, Name); ImGui::PopID(); } for (int i = 0; i < Layer->Block_Effect_Count; i++) { block_effect Effect = *(block_effect *)Memory_Block_AddressAtIndex(Memory, F_Effects, Layer->Block_Effect_Index[i]); header_effect *EffectHeader = Effect_EntryFromID(State, Effect.ID); ImGui::PushID(Effect.Index); ImGui::Text(EffectHeader->Name); ImGui::PopID(); if (EffectHeader->DisplayType == effect_display_type_standard) { for (int c = 0; c < EffectHeader->Property_Count; c++) { header_property ChannelHeader = State->Property[EffectHeader->PropertyStartIndex + c]; property_channel *Property = (property_channel *)Memory_Block_AddressAtIndex(Memory, F_Properties, Effect.Block_Property_Index[c]); ImGui::PushID(Property); if (ChannelHeader.DisplayType == property_display_type_standard) { ImGui_InteractSliderProperty(State, Memory, Property, ChannelHeader.Name); } else if (ChannelHeader.DisplayType == property_display_type_color) { property_channel *Property1 = (property_channel *)Memory_Block_AddressAtIndex(Memory, F_Properties, Effect.Block_Property_Index[c+1]); property_channel *Property2 = (property_channel *)Memory_Block_AddressAtIndex(Memory, F_Properties, Effect.Block_Property_Index[c+2]); property_channel *Property3 = (property_channel *)Memory_Block_AddressAtIndex(Memory, F_Properties, Effect.Block_Property_Index[c+3]); real32 *Color[4] = { &Property->CurrentValue, &Property1->CurrentValue, &Property2->CurrentValue, &Property3->CurrentValue }; if (ImGui::ColorEdit4("color", (real32 *)Color, ImGuiColorEditFlags_Float)) State->UpdateFrame = true; c += 3; } else if (ChannelHeader.DisplayType == property_display_type_blendmode) { uint32 *item_current_idx = (uint32 *)&(Property->CurrentValue); // 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(); } } else { Assert(0); } ImGui::PopID(); } } else { Assert(0); } } if (Layer->IsPrecomp) { block_composition *Comp = (block_composition *)Memory_Block_AddressAtIndex(Memory, F_Precomps, Layer->Block_Source_Index); ImGui::DragScalar("Width", ImGuiDataType_U16, &Comp->Width); if (ImGui::IsItemActive()) { State->UpdateFrame = true; } ImGui::DragScalar("Height", ImGuiDataType_U16, &Comp->Height); if (ImGui::IsItemActive()) { State->UpdateFrame = true; } } ImGui::End(); } else { ImGui::Begin("Properties: empty###Properties"); if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) State->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); State->UpdateKeyframes = true; } 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(); } #if STABLE static void ImGui_SD_Thumbnail(project_data *File, project_state *State, ui *UI, memory *Memory, ImGuiIO io, sorted_comp_info *SortedCompArray, sorted_layer *SortedLayerArray, uint16 *SourceArray, uint16 SourceCount) { ImGui::Begin("SD gallery"); ImDrawList* draw_list = ImGui::GetWindowDrawList(); ImVec2 ViewportMin = ImGui::GetCursorScreenPos(); ImVec2 ViewportScale = ImGui::GetContentRegionAvail(); ImVec2 ViewportMax = ImVec2(ViewportMin.x + ViewportScale.x, ViewportMin.y + ViewportScale.y); if (!SourceCount || ViewportScale.x < 50) { ImGui::End(); return; } int test[16]; int count = 0; // draw_list->AddImage((void *)(intptr_t)textureID, ViewportMin, ViewportMax); uint32 T_Height = 128; real32 RowPercent = (real32)ViewportScale.x / T_Height; uint32 PerRow = (uint32)RowPercent; uint32 UI_Size = T_Height; bool32 Active = false; for (int i = 0; i < SourceCount; i++) { int32 Y = i / PerRow; int32 X = i % PerRow; block_source *Source = (block_source *)Memory_Block_AddressAtIndex(Memory, F_Sources, SourceArray[i]); // ImGui::Text("Count: %i", Source->RelativeTimestamp); real32 Ratio = (real32)Source->Width / Source->Height; uint32 T_Width = (uint32)(Ratio * T_Height); if (Source->ThumbnailTex == 0) { Source_DumpThumbnail(Memory, Source, T_Width, T_Height); } // draw_list->AddRect(ImgMin, ImgMax, IM_COL32(255, 255, 255, 64)); ImVec2 ImgMin = ViewportMin + ImVec2(X * UI_Size, Y * UI_Size); ImGui::SetCursorScreenPos(ImgMin); real32 ClosestSpacing = 0.2; bool32 Wide = (Source->Width > Source->Height); // ImVec2 Min = (Wide) ? ImVec2(0,ClosestSpacing) : ImVec2(ClosestSpacing,0); // ImVec2 Max = (Wide) ? ImVec2(1-ClosestSpacing,1) : ImVec2(1,1-ClosestSpacing); ImVec2 Min(0.2, 0.2); ImVec2 Max(0.8, 0.8); ImGui::ImageButton((void *)(intptr_t)Source->ThumbnailTex, ImVec2(UI_Size, UI_Size), Min, Max, 12); // ImGui::ImageButton(user_texture_id, & sizeconst ImVec2& = ImVec2(0, 0), = ImVec2(1,1), int frame_padding = -1, const ImVec4& bg_col = ImVec4(0,0,0,0), const ImVec4& tint_col = ImVec4(1,1,1,1)); // <0 frame_padding uses default frame padding settings. 0 for no padding // ImVec2 ImgMax = ImgMin + ImVec2(UI_Size * Ratio, UI_Size); // ImGui::Button("##thumbnail", ImVec2(UI_Size, UI_Size)); // draw_list->AddImage((void *)(intptr_t)Source->ThumbnailTex, ImgMin, ImgMax); if (ImGui::IsItemHovered()) { State->PreviewSource = SourceArray[i]; State->UpdateFrame = true; Active = true; block_source *Source1 = (block_source *)Memory_Block_AddressAtIndex(Memory, F_Sources, 1); block_source *Source2 = (block_source *)Memory_Block_AddressAtIndex(Memory, F_Sources, 2); block_source *Source0 = (block_source *)Memory_Block_AddressAtIndex(Memory, F_Sources, 0); real32 a = 0; } if (ImGui::IsItemClicked() || ImGui::IsItemClicked(ImGuiMouseButton_Right)) { if (!io.KeyShift && !Source->IsSelected) { Source_DeselectAll(File, Memory); } Source->IsSelected = 1; } ImGui::OpenPopupOnItemClick("temptosource", ImGuiPopupFlags_MouseButtonRight); } if (State->PreviewSource != -1 && !Active) { State->PreviewSource = -1; State->UpdateFrame = true; } if (ImGui::BeginPopup("temptosource")) { if (ImGui::MenuItem("Create layer from source")) { Source_UICreateButton(File, State, Memory, SortedCompArray, SortedLayerArray); State->UpdateKeyframes = true; } ImGui::EndPopup(); } ImGui::End(); } static void ImGui_SD_Prompt(project_data *File, project_state *State, ui *UI, memory *Memory, ImGuiIO io, sorted_comp_info *SortedCompArray, sorted_layer *SortedLayerArray) { sorted_comp_info *SortedCompInfo = &SortedCompArray[File->PrincipalCompIndex]; sorted_layer *SortedLayerInfo = Layer_GetSortedArray(SortedLayerArray, SortedCompArray,File->PrincipalCompIndex); ImGui::Begin("SD prompt input"); sd_state *SD = &File->UI.SD; int Size = ImGui::GetFontSize(); ImGui::PushStyleColor(ImGuiCol_Button, IM_COL32(200, 80, 0, 255)); if (ImGui::Button("Generate!", ImVec2(Size*8, Size*2))) { if (!State->CurlActive) { if (SD->Mode) { block_layer *Layer = NULL; for (int i = SortedCompInfo->LayerCount - 1; i >= 0; i--) { sorted_layer SortEntry = SortedLayerInfo[i]; uint32 Index_Physical = SortEntry.Block_Layer_Index; block_layer *TestLayer = (block_layer *)Memory_Block_AddressAtIndex(Memory, F_Layers, Index_Physical); if (TestLayer->IsSelected) { Layer = TestLayer; break; } } if (Layer) { block_source *Source = (block_source *)Memory_Block_AddressAtIndex(Memory, F_Sources, Layer->Block_Source_Index); void *BitmapAddress; if (Source->Type == source_type_principal) { block_source *Source = (block_source *)Memory_Block_AddressAtIndex(Memory, F_Sources, Layer->Block_Source_Index); BitmapAddress = Memory_Block_AddressAtIndex(Memory, F_PrincipalBitmaps, Source->Bitmap_Index, 0); } else if (Source->Type == source_type_file) { cache_entry *Entry = Memory_Cache_Search(State, Memory, cache_entry_type_source, Layer->Block_Source_Index, 0); Assert(Entry->IsCached); BitmapAddress = Memory_Block_Bitmap_AddressAtIndex(Memory, Entry->Block_StartIndex); } uint64 Size = Source->Width * Source->Height * Source->BytesPerPixel; int32 len = 0; uint8 *PNGBitmap = stbi_write_png_to_mem((uint8 *)BitmapAddress, Source->Width * Source->BytesPerPixel, Source->Width, Source->Height, Source->BytesPerPixel, &len); Assert(PNGBitmap); uint64 EncodedEstimateSize = base64_encode_size((size_t)Size); uint8 *EncodedOutput = (uint8 *)Memory_PushScratch(Memory, EncodedEstimateSize); uint64 EncodedTrueSize = 0; base64_encode((uint8 *)PNGBitmap, len, EncodedOutput, (size_t *)&EncodedTrueSize); Assert(EncodedOutput[EncodedTrueSize] == '\0'); // printf("%s", EncodedOutput); STBIW_FREE(PNGBitmap); SD_AssembleJSON(SD, (char *)State->JSONPayload, EncodedOutput); Memory_PopScratch(Memory, EncodedEstimateSize); State->CurlActive = -1; } } else { SD_AssembleJSON(SD, State->JSONPayload); // SD_AssembleJSON(SD, (char *)State->Dump2); State->CurlActive = -1; } } } ImGui::PopStyleColor(); if (State->CurlActive) { ImGui::SameLine(); ImGui::Text("Est. time: %.1f sec, %.2f", State->SDTimeEstimate, State->SDPercentDone*100); } ImGui::InputText("Address", SD->ServerAddress, SD_LEN_ADDRESS); if (SD->ServerAddress[0] == '\0' && SD->Prompt[0] == '\0') { sprintf(SD->Prompt, "%s", "highres"); sprintf(SD->NegPrompt, "%s", "nsfw, \ntext, \ncropped, \nworst quality, \nlow quality, \nnormal quality, \njpeg artifacts, \nsignature, \nwatermark"); sprintf(SD->ServerAddress, "%s", "http://127.0.0.1:7860"); } if (ImGui::Selectable("txt2img", !SD->Mode)) SD->Mode = 0; if (ImGui::Selectable("img2img", SD->Mode)) SD->Mode = 1; 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"); if (SD->Mode) { ImGui::SliderFloat("Denoising strength", &SD->DenoisingStrength, 0, 1, "%.2f"); } ImGui::SliderInt("Batch size", &SD->BatchSize, 1, 8, "%i"); ImGui::InputInt("Seed", &SD->Seed); ImGui::End(); } #endif 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) { State->InteractTransformMode = 1; } if (State->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) State->InteractTransformMode = 1; else State->InteractTransformMode = 2; } // Scale part if (State->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 (State->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 (!State->InteractTransformMode && ImGui::IsMouseClicked(ImGuiMouseButton_Left) && InBounds && !OtherActions) State->InteractTransformMode = 3; if (State->InteractTransformMode == 3) { Interact->Position.x += (real32)io.MouseDelta.x/CompScale.x; Interact->Position.y += (real32)io.MouseDelta.y/CompScale.y; } if (State->InteractTransformMode) { if (io.MouseDelta.x || io.MouseDelta.y) State->UpdateFrame = true; if (!ImGui::IsMouseDown(ImGuiMouseButton_Left)) State->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; if (!io.KeyCtrl) 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) { if (io.KeyCtrl) { layer_transforms T = Layer_GetTransforms(Layer); Transform_ApplyInteractive(*(interact_transform *)&State->Interact_Offset[0], &T.x, &T.y, &T.rotation, &T.scale); property_channel *Property[4] = { &Layer->x, &Layer->y, &Layer->rotation, &Layer->scale }; real32 Val[4] = { T.x, T.y, T.rotation, T.scale }; for (int a = 0; a < 4; a++) { if (Property[a]->CurrentValue != Val[a]) { History_Entry_Commit(Memory, "Add keyframe"); bezier_point Point = { 1, {(real32)State->Frame_Current, Val[a], -1, 0, 1, 0}, interpolation_type_linear, 0, {0, 0, 0}, 0 }; Bezier_Add(Memory, Property[a], Point); History_Entry_End(Memory); } } } else { 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); } } } if (!io.KeyCtrl) 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)) State->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 1 bool32 IsActive = ImGui::IsItemActive(); bool32 IsActivated = ImGui::IsItemActivated(); bool32 IsDeactivated = ImGui::IsItemDeactivated(); #else bool32 IsActive = ImGui::IsKeyDown(ImGuiKey_3); bool32 IsActivated = ImGui::IsKeyPressed(ImGuiKey_3); bool32 IsDeactivated = ImGui::IsKeyReleased(ImGuiKey_3); #endif if (IsHovered && IsActivated && !ImGui::IsMouseDown(ImGuiMouseButton_Right)) { // Point to zoom in on if Z is held State->TempZoomRatio = ImGui_ScreenPointToCompUV(ViewportMin, UI->CompPos, UI->CompZoom, io.MousePos); if (State->Tool == tool_brush && State->Interact_Active != interact_type_brush) { sorted_layer *SortedLayerInfo = Layer_GetSortedArray(SortedLayerArray, SortedCompArray, File->PrincipalCompIndex); sorted_comp_info SortedCompInfo = SortedCompArray[File->PrincipalCompIndex]; if (!io.KeyCtrl) { 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->IsPrecomp) { block_source *Source = (block_source *)Memory_Block_AddressAtIndex(Memory, F_Sources, Layer->Block_Source_Index); if (Layer->IsSelected && Source->Type == source_type_principal) { Arbitrary_Zero((uint8 *)State->Brush.TransientBitmap, 2048*2048*4); State->Interact_Active = interact_type_brush; State->Brush.LayerToPaint_Index = i; break; } } } } if (State->Brush.LayerToPaint_Index == -1) { Arbitrary_Zero((uint8 *)State->Brush.TransientBitmap, 2048*2048*4); State->Interact_Active = interact_type_brush; Layer_DeselectAll(File, State, Memory); 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); 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, State->Brush.TransientBitmap, ViewportMin, MousePos); } } else if (IsActivated) { ImVec2 MousePos = io.MousePos; Brush_Render(State, UI, T_Layer, MainComp, Source, State->Brush.TransientBitmap, ViewportMin, MousePos); } State->UpdateFrame = true; } if (IsDeactivated) { block_layer *Layer = (block_layer *)Memory_Block_AddressAtIndex(Memory, F_Layers, State->Brush.LayerToPaint_Index); 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, 0); rectangle RenderRegion = { 0, 0, Source->Width, Source->Height }; direct_info Info = { (real32)Source->Width, (real32)Source->Height, (real32)Source->BytesPerPixel, (real32)Source->Width * Source->BytesPerPixel, Bitmap_ByteInfo(Source->BytesPerPixel), blend_normal, RenderRegion, State->Brush.TransientBitmap, 1}; Render_Main((void *)&Info, SourceBitmapAddress, render_type_notransform_swap, State->Brush.CacheBounds); uint64 BitmapSize = Source->Width * Source->Height * Source->BytesPerPixel; // History_Entry_Commit(Memory, "Paintbrush stroke"); // History_Action_BitmapPaint(Memory, BitmapSize, SourceBitmapAddress, State->Brush.TransientBitmap, Source->BytesPerPixel); // History_Entry_End(Memory); State->Brush.LayerToPaint_Index = -1; State->Interact_Active = interact_type_none; State->UpdateFrame = true; } } real32 Distance = 0; if (IsActive) { if (ImGui::IsMouseDragging(ImGuiMouseButton_Left, -1.0f)) Distance = io.MouseDelta.x + io.MouseDelta.y; if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) Distance = 200; } if (Distance && ImGui::IsKeyDown(ImGuiKey_Z)) { if (io.KeyShift) Distance *= -1; UI->CompZoom.x += (Distance)*(real32)MainComp->Width/MainComp->Height; UI->CompZoom.y += (Distance); UI->CompPos.x -= ((Distance)*(real32)MainComp->Width/MainComp->Height)*State->TempZoomRatio.x; UI->CompPos.y -= Distance*State->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_Timeline_BGElements(project_state *State, ui *UI, ImDrawList *draw_list, ImVec2 TimelineSizeWithBorder, ImVec2 TimelineAbsolutePos, block_composition MainComp, ImVec2 TimelineZoomSize, ImVec2 TimelineMoveSize) { uint32 PreviewAreaColor = IM_COL32(128, 128, 128, 30); ImVec2 Region_Min = ImVec2(TimelineAbsolutePos.x + TimelineMoveSize.x + ((real32)MainComp.Frame_Start / MainComp.Frame_Count)*TimelineZoomSize.x, TimelineAbsolutePos.y); ImVec2 Region_Max = ImVec2(Region_Min.x + ((real32)(MainComp.Frame_End - MainComp.Frame_Start) / MainComp.Frame_Count)*TimelineZoomSize.x, Region_Min.y + TimelineSizeWithBorder.y); draw_list->AddRectFilled(Region_Min, Region_Max, PreviewAreaColor); } static void ImGui_Timeline_HorizontalIncrementDraw(project_state *State, ui *UI, ImDrawList *draw_list, ImVec2 TimelineSizeWithBorder, ImVec2 TimelineAbsolutePos, block_composition MainComp, ImVec2 TimelineZoomSize, ImVec2 TimelineMoveSize) { uint32 LineColor = IM_COL32(200, 200, 200, 40); uint32 PlayheadColor = IM_COL32(000, 000, 200, 160); uint32 PreviewAreaColor = IM_COL32(128, 128, 128, 60); Assert(TimelineZoomSize.x > 0.0f); real32 x = 0; bool32 RightmostEdge = false; real32 Increment = (real32)1 / MainComp.Frame_Count; if (UI->TimelinePercentZoomed.x > 0.90) Increment = (real32)MainComp.FPS / MainComp.Frame_Count; else if (UI->TimelinePercentZoomed.x > 0.40) Increment *= 2; while (!RightmostEdge) { ImVec2 Min = ImVec2(TimelineAbsolutePos.x + TimelineMoveSize.x + x*TimelineZoomSize.x, TimelineAbsolutePos.y); ImVec2 Max = ImVec2(Min.x + 2, TimelineAbsolutePos.y + TimelineSizeWithBorder.y); if (Min.x < TimelineAbsolutePos.x + TimelineSizeWithBorder.x) { draw_list->AddLine(Min, Max, LineColor); char buf2[6]; uint32 FrameNumber = (uint32)(x * MainComp.Frame_Count) % MainComp.FPS; if (FrameNumber != 0) sprintf(buf2, ":%.2i", FrameNumber); else sprintf(buf2, "%.2i:00", (uint32)(x * MainComp.Frame_Count) / MainComp.FPS); draw_list->AddText(ImVec2(Min.x, TimelineAbsolutePos.y), IM_COL32(200, 200, 200, 130), buf2); x += Increment; if (x > 1.0f) RightmostEdge = true; } else { RightmostEdge = true; } } ImVec2 Min = ImVec2(TimelineAbsolutePos.x + TimelineMoveSize.x + ((real32)State->Frame_Current / MainComp.Frame_Count)*TimelineZoomSize.x, TimelineAbsolutePos.y); ImVec2 Max = ImVec2(Min.x + 2, TimelineAbsolutePos.y + TimelineSizeWithBorder.y); draw_list->AddLine(Min, Max, PlayheadColor); real32 Size = ImGui::GetFontSize() * 2; ImGui::SetCursorScreenPos(Min - ImVec2(Size / 2, 0)); ImGui::Button("##playhead", ImVec2(Size, Size)); bool32 IsHovered = ImGui::IsItemHovered(); bool32 IsItemActive = ImGui::IsItemActive(); bool32 IsItemActivated = ImGui::IsItemActivated(); bool32 IsItemDeactivated = ImGui::IsItemDeactivated(); if (IsItemActivated) { State->Interact_Active = interact_type_timeline_scrub; State->Interact_Offset[0] = State->Frame_Current; } if (IsItemActive) { ImVec2 DragDelta = ImGui::GetMouseDragDelta(); int32 NewCurrent = (int32)(State->Interact_Offset[0] + (DragDelta.x / TimelineZoomSize.x * MainComp.Frame_Count) + 0.5); if (NewCurrent != State->Frame_Current) { State->Frame_Current = NewCurrent; State->UpdateFrame = true; } } if (IsItemDeactivated) { State->Interact_Active = interact_type_none; } } 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 = Property_GetSortedInfo(SortedPropertyInfo, i, h); uint16 *ArrayLocation = Property_GetSortedArray(SortedPropertyArray, i, h); 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 (State->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. State->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(State, io.MousePos, TimelineAbsolutePos, TimelineAbsolutePos + TimelineSizeWithBorder); ImVec2 DragDelta = io.MousePos - ImVec2(State->Interact_Offset[2], State->Interact_Offset[3]); DragDelta = DragDelta + (ImVec2(State->Warp_X, State->Warp_Y) * TimelineSize); if (io.MouseDelta.x || io.MouseDelta.y) { State->UpdateFrame = true; } if (State->Interact_Active == interact_type_keyframe_move) { // The Y increment varies between graphs, so we have to do it in the Bezier_EvaluateValue call. State->Interact_Offset[0] = (DragDelta.x / TimelineZoomSize.x) / Increment.x; State->Interact_Offset[1] = DragDelta.y; } else if (State->Interact_Active == interact_type_keyframe_scale) { State->Interact_Offset[0] = (DragDelta.x / TimelineSizeWithBorder.x * UI->TimelinePercentZoomed.x) / Increment.x; } else if (State->Interact_Active == interact_type_keyframe_rotate) { State->Interact_Offset[0] = (DragDelta.x / TimelineZoomSize.x); /* real32 Slope_Old = (Keyframe_ScreenPos.y - State->Interact_Offset[3]) / (Keyframe_ScreenPos.x - State->Interact_Offset[2]); real32 Slope_New = (Keyframe_ScreenPos.y - io.MousePos.y) / (Keyframe_ScreenPos.x - io.MousePos.x); State->Interact_Offset[0] = atan((Slope_Old - Slope_New) / (1 + Slope_Old * Slope_New)); */ } } for (int h = 0; h < AmountOf(Layer->Property); h++) { property_channel *Property = &Layer->Property[h]; sorted_property_info *InfoLocation = Property_GetSortedInfo(SortedPropertyInfo, i, h); uint16 *ArrayLocation = Property_GetSortedArray(SortedPropertyArray, i, h); 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 (State->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; } } State->Test.SetCurrentChannel(draw_list, 1); ImVec2 ButtonSize(16, 16); ImGui::PushID(k); int Max = PointAddress[Idx]->IsSelected ? 2 : 0; for (int b = Max; b >= 0; b--) { ImU32 PointCol = ((PointAddress[Idx]->IsSelected - 1) == b) ? ImColor(0.8f, 0.5f, 0.0f, 1.0f) : ImColor(0.1f, 0.1f, 0.1f, 1.0f); ImU32 LineCol = ((PointAddress[Idx]->IsSelected - 1) == b) ? ImColor(0.8f, 0.5f, 0.5f, 1.0f) : ImColor(0.4f, 0.4f, 0.4f, 1.0f); ImGui::PushID(b); ImGui::SetCursorScreenPos(Keyframe_ScreenPos[NewIdx + b] - (ButtonSize * 0.5)); ImGui::InvisibleButton("##keyframemover", ButtonSize, ImGuiMouseButton_Left); bool32 IsHovered = ImGui::IsItemHovered(); bool32 IsItemActive = ImGui::IsItemActive(); bool32 IsItemActivated = ImGui::IsItemActivated(); bool32 IsItemDeactivated = ImGui::IsItemDeactivated(); bool32 LeftClick = ImGui::IsMouseDown(ImGuiMouseButton_Left); bool32 RightClick = ImGui::IsMouseDown(ImGuiMouseButton_Right); if (IsHovered) PointCol = ImColor(1.0f, 0.8f, 0.8f, 1.0f); 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(); State->Test.SetCurrentChannel(draw_list, 0); if (p != 0) { if (PointAddress[0]->Type == interpolation_type_bezier && PointAddress[1]->Type == interpolation_type_bezier) { draw_list->AddBezierCubic(Keyframe_ScreenPos[OldIdx], Keyframe_ScreenPos[OldIdx + 2], Keyframe_ScreenPos[NewIdx + 1], Keyframe_ScreenPos[NewIdx], GraphCol, 1.0f, 0); } else { draw_list->AddLine(Keyframe_ScreenPos[0], Keyframe_ScreenPos[3], GraphCol, 1.0f); } } } } ImGui::PopID(); } ImGui::PopID(); } State->Test.Merge(draw_list); } static void ImGui_Timeline_DrawPrecomp(project_data *File, project_state *State, memory *Memory, ui *UI, ImGuiIO io, ImDrawList *draw_list, 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 (State->TimelineMode == timeline_mode_default) { if (State->BoxSelect && State->TimelineMode == timeline_mode_default) { bool32 Test = 0; if (io.MouseClickedPos[0].y < io.MousePos.y) Test = (Layer_ScreenPos_Min.y >= io.MouseClickedPos[0].y && Layer_ScreenPos_Min.y <= io.MousePos.y); else Test = (Layer_ScreenPos_Max.y <= io.MouseClickedPos[0].y && Layer_ScreenPos_Max.y >= io.MousePos.y); if (io.MouseClickedPos[0].x < io.MousePos.x) Test &= (Layer_ScreenPos_Max.x >= io.MouseClickedPos[0].x && Layer_ScreenPos_Min.x <= io.MousePos.x); else Test &= (Layer_ScreenPos_Min.x <= io.MouseClickedPos[0].x && Layer_ScreenPos_Max.x >= io.MousePos.x); if (Test) { if (!Layer->IsSelected) { Layer_Select(Memory, State, Index_Physical); } } else if (!io.KeyShift) { Layer->IsSelected = false; } } ImVec2 ResizeSize = ImVec2(Increment.x * 0.45 * TimelineZoomSize.x, Layer_ScreenSize.y); ImVec2 ResizePos[2] = { Layer_ScreenPos_Min, ImVec2(Layer_ScreenPos_Max.x - ResizeSize.x, Layer_ScreenPos_Min.y) }; for (int b = 0; b < 2; b++) { ImGui::PushID(b); ImGui::SetCursorScreenPos(ResizePos[b]); ImGui::Button("##layer_resize", ResizeSize); if (ImGui::IsItemHovered()) { ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeEW); } if (ImGui::IsItemActivated()) { if (!Layer->IsSelected) { if (!io.KeyShift) Layer_DeselectAll(File, State, Memory); Layer_Select(Memory, State, Index_Physical); } } if (ImGui::IsItemActive()) { if (ImGui::IsMouseDragging(ImGuiMouseButton_Left, -1)) { Assert(Layer->IsSelected); State->Interact_Active = interact_type_layer_timeadjust; ImVec2 DragDelta = ImGui::GetMouseDragDelta(); DragDelta = DragDelta + (ImVec2(State->Warp_X, State->Warp_Y) * TimelineSize); State->Interact_Offset[0] = (DragDelta.x / TimelineSizeWithBorder.x * UI->TimelinePercentZoomed.x) * Comp->Frame_Count; State->Interact_Offset[1] = b; // 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::Selectable("Visible")) { bool32 Commit = false; int h = 0, z = 0, i = 0; while (Block_Loop(Memory, F_Layers, File->Layer_Count, &h, &z, &i)) { block_layer *Layer = (block_layer *)Memory_Block_AddressAtIndex(Memory, F_Layers, i); if (Layer->IsSelected) { if (!Commit) { History_Entry_Commit(Memory, "Toggle visibility"); Commit = true; } History_Action_Swap(Memory, F_File, sizeof(Layer->IsVisible), &Layer->IsVisible); Layer->IsVisible ^= 1; } } if (Commit) { History_Entry_End(Memory); State->UpdateFrame = true; } } if (ImGui::MenuItem("Pre-compose layer")) { Precomp_UICreateButton(File, State, Memory, CompIndex, SortedCompInfo, SortedLayerInfo); State->UpdateKeyframes = true; } if (ImGui::MenuItem("Duplicate layer")) { Precomp_UIDuplicate(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); if (ImGui::Button("##test", ColSize)) { int h = 0, z = 0, i = 0; while (Block_Loop(Memory, F_Layers, File->Layer_Count, &h, &z, &i)) { block_layer *Layer = (block_layer *)Memory_Block_AddressAtIndex(Memory, F_Layers, i); if (Layer->IsSelected) { Layer->ColIndex = c; } } } if ((c+1) % 4) { ImGui::SameLine(); } ImGui::PopStyleColor(); ImGui::PopID(); } ImGui::PopStyleVar(); ImGui::EndMenu(); } uint32 *item_current_idx = (uint32 *)&Layer->BlendMode; // Here we store our selection data as an index. if (ImGui::BeginListBox("Blend mode")) { for (int n = 0; n < IM_ARRAYSIZE(BlendmodeNames); n++) { const bool is_selected = (*item_current_idx == n); if (ImGui::Selectable(BlendmodeNames[n], is_selected)) { *item_current_idx = n; State->UpdateFrame = true; } // Set the initial focus when opening the combo (scrolling + keyboard navigation focus) if (is_selected) ImGui::SetItemDefaultFocus(); } ImGui::EndListBox(); } ImGui::EndPopup(); } if (ImGui::IsItemActive()) { if (ImGui::IsMouseDragging(ImGuiMouseButton_Left, -1)) { if (State->Interact_Active == interact_type_none) { State->Interact_Active = interact_type_layer_move; // TODO(fox): Selected layers inside precomps will have interactions doubled, // so I'm forcing interaction to only be with members of the same precomp. // Could be made more intuitive later. Layer_RecursiveDeselect(Memory, SortedCompArray, SortedLayerArray, Layer->Block_Composition_Index, File->PrincipalCompIndex); } ImVec2 DragDelta = ImGui::GetMouseDragDelta(); DragDelta = DragDelta + (ImVec2(State->Warp_X, State->Warp_Y) * TimelineSize); ImVec2 Offset_Old = ImVec2(State->Interact_Offset[0], State->Interact_Offset[1]); ImVec2 Offset_New = (DragDelta / TimelineSizeWithBorder * UI->TimelinePercentZoomed) * ImVec2(Comp->Frame_Count, -LayerIncrement); // if (((int32)Offset_Old.x != (int32)Offset_New.x) || ((int32)Offset_Old.y != (int32)Offset_New.y)) State->UpdateFrame = true; State->Interact_Offset[0] = Offset_New.x; State->Interact_Offset[1] = Offset_New.y; } } if (ImGui::IsItemDeactivated()) { if (State->Interact_Active == interact_type_layer_move) { History_Entry_Commit(Memory, "Move layers"); for (int i = 0; i < 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 = Property_GetSortedInfo(SortedPropertyInfo, i, h); uint16 *ArrayLocation = Property_GetSortedArray(SortedPropertyArray, i, h); ImVec2 GraphPos(TimelineAbsolutePos.x, Layer_ScreenPos_Min.y + (Layer_ScreenSize.y * 2) + (Layer_ScreenSize.y * Channel)); 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 (State->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)) State->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_BGElements(State, UI, draw_list, TimelineSizeWithBorder, TimelineAbsolutePos, *MainComp, TimelineZoomSize, TimelineMoveSize); ImGui_Timeline_DrawPrecomp(File, State, Memory, UI, io, draw_list, File->PrincipalCompIndex, Increment, TimelineAbsolutePos, TimelineMoveSize, TimelineZoomSize, TimelineSize, TimelineSizeWithBorder, LayerIncrement, SortedCompArray, SortedLayerArray, SortedPropertyInfo, SortedPropertyArray); if (State->TimelineMode == timeline_mode_graph) { if (UI->GraphMoveSize.y == 0) { UI->GraphZoomSize = ImVec2(1, UI->TimelinePercentZoomed.y ); UI->GraphMoveSize = ImVec2(0, -UI->TimelinePercentOffset.y ); } ImVec2 ZoomDifference = (UI->TimelinePercentZoomed / UI->GraphZoomSize); ImVec2 MoveDifference = (UI->TimelinePercentOffset + (UI->GraphMoveSize)); // DebugWatchVar("zoomdif: ", &ZoomDifference.y, d_float); // DebugWatchVar("movedif: ", &MoveDifference.y, d_float); ImVec2 GraphZoomSize = TimelineSizeWithBorder / ZoomDifference; ImVec2 GraphMoveSize = TimelineSizeWithBorder * (MoveDifference) / UI->TimelinePercentZoomed; // DebugWatchVar("zoomsize: ", &GraphZoomSize.y, d_float); // DebugWatchVar("movesize: ", &GraphMoveSize.y, d_float); draw_list->AddRectFilled(WindowMinAbs, WindowMaxAbs, IM_COL32(50, 50, 50, 230)); ImGui_Timeline_DrawGraph(File, State, Memory, UI, io, draw_list, Increment, TimelineAbsolutePos, GraphMoveSize, GraphZoomSize, TimelineSize, TimelineSizeWithBorder, LayerIncrement, SortedPropertyInfo, SortedPropertyArray); } ImGui_Timeline_HorizontalIncrementDraw(State, UI, draw_list, TimelineSizeWithBorder, TimelineAbsolutePos, *MainComp, TimelineZoomSize, TimelineMoveSize); ImVec2 MouseDelta = io.MouseDelta / TimelineSize; real32 BarHandleSize = FontHeight; real32 BarThickness = 50; real32 BarMinZoom = 0.01; real32 BarH_Pos = -TimelineSizeWithBorder.x * ActivePercentOffset->x; real32 BarH_Size = TimelineSizeWithBorder.x / (1 / ActivePercentZoomed->x); // I use "UI" to denote the size/position after clipping the bar so that it // doesn't go out of bounds and the handles are always selectable at the edges. real32 BarH_Offset = Max(BarH_Pos, 0); real32 BarH_SizeUI = (BarH_Size + BarH_Pos > TimelineSizeWithBorder.x) ? TimelineSizeWithBorder.x - BarH_Pos : BarH_Size + (BarH_Pos - BarH_Offset); if (BarH_Offset == 0 && BarH_SizeUI > TimelineSizeWithBorder.x) BarH_SizeUI = TimelineSizeWithBorder.x; BarH_SizeUI = BarH_SizeUI - BarHandleSize*2; BarH_SizeUI = Max(BarH_SizeUI, FontHeight*4); BarH_Offset = Min(BarH_Offset, TimelineSize.x - BarH_SizeUI - BarHandleSize*4); ImVec2 BarH_PosUI = TimelineAbsolutePos + ImVec2(BarH_Offset, TimelineSize.y - BarThickness); bool32 BarHeld = false; ImGui::SetCursorScreenPos(BarH_PosUI); ImGui::Button("##scrollbarleft", ImVec2(BarHandleSize, BarThickness)); if ((ImGui::IsItemActive() && ImGui::IsMouseDragging(ImGuiMouseButton_Left, -1))) { if ((ActivePercentZoomed->x - MouseDelta.x) > BarMinZoom) { ActivePercentZoomed->x -= MouseDelta.x; ActivePercentOffset->x -= MouseDelta.x; } BarHeld = true; } ImGui::SetCursorScreenPos(BarH_PosUI + ImVec2(BarHandleSize, 0)); ImGui::Button("##scrollbarhori", ImVec2(BarH_SizeUI, BarThickness)); if (ImGui::IsItemActive() && ImGui::IsMouseDragging(ImGuiMouseButton_Left, -1)) { ActivePercentOffset->x -= MouseDelta.x; BarHeld = true; } ImGui::SetCursorScreenPos(BarH_PosUI + ImVec2(BarHandleSize, 0) + ImVec2(BarH_SizeUI, 0)); ImGui::Button("##scrollbarright", ImVec2(BarHandleSize, BarThickness)); if ((ImGui::IsItemActive() && ImGui::IsMouseDragging(ImGuiMouseButton_Left, -1))) { if ((ActivePercentZoomed->x + MouseDelta.x) > BarMinZoom) { ActivePercentZoomed->x += MouseDelta.x; } BarHeld = true; } if (BarHeld) { ImGui_WarpMouse(State, io.MousePos, TimelineAbsolutePos, TimelineAbsolutePos + TimelineSizeWithBorder, 1); } Assert(ActivePercentZoomed->x > BarMinZoom); real32 BarV_MaxSize = TimelineSizeWithBorder.y - BarThickness/2; real32 BarV_Pos = -BarV_MaxSize * ActivePercentOffset->y; real32 BarV_Size = BarV_MaxSize / (1 / ActivePercentZoomed->y); BarV_Size = Max(BarV_Size, FontHeight*4); real32 BarV_Offset = Max(BarV_Pos, 0); real32 BarV_SizeUI = (BarV_Size + BarV_Pos > BarV_MaxSize) ? BarV_MaxSize - BarV_Pos : BarV_Size + (BarV_Pos - BarV_Offset); if (BarV_Offset == 0 && BarV_SizeUI > BarV_MaxSize) BarV_SizeUI = BarV_MaxSize; BarV_SizeUI = BarV_SizeUI - BarHandleSize*2; BarV_SizeUI = Max(BarV_SizeUI, FontHeight*4); BarV_Offset = Min(BarV_Offset, BarV_MaxSize - BarV_SizeUI - BarHandleSize*4); ImVec2 BarV_PosUI = TimelineAbsolutePos + ImVec2(TimelineSize.x - BarThickness, BarV_Offset); BarHeld = false; ImGui::SetCursorScreenPos(BarV_PosUI); ImGui::Button("##h-scrollbarleft", ImVec2(BarThickness, BarHandleSize)); if ((ImGui::IsItemActive() && ImGui::IsMouseDragging(ImGuiMouseButton_Left, -1))) { ActivePercentZoomed->y -= MouseDelta.y; ActivePercentOffset->y -= MouseDelta.y; BarHeld = true; } ImGui::SetCursorScreenPos(BarV_PosUI + ImVec2(0, BarHandleSize)); ImGui::Button("##h-scrollbar", ImVec2(BarThickness, BarV_SizeUI)); if (ImGui::IsItemHovered() && io.MouseWheel) { ActivePercentOffset->y -= io.MouseWheel/10; } if (ImGui::IsItemActive() && ImGui::IsMouseDragging(ImGuiMouseButton_Left, -1)) { ActivePercentOffset->y -= MouseDelta.y; BarHeld = true; } ImGui::SetCursorScreenPos(BarV_PosUI + ImVec2(0, BarHandleSize) + ImVec2(0, BarV_SizeUI)); ImGui::Button("##h-scrollbarright", ImVec2(BarThickness, BarHandleSize)); if ((ImGui::IsItemActive() && ImGui::IsMouseDragging(ImGuiMouseButton_Left, -1))) { ActivePercentZoomed->y += MouseDelta.y; BarHeld = true; } ActivePercentZoomed->y = Max(ActivePercentZoomed->y, 0.01); if (BarHeld) { ImGui_WarpMouse(State, io.MousePos, TimelineAbsolutePos, TimelineAbsolutePos + TimelineSizeWithBorder, 2); } ImGui::SetCursorScreenPos(TimelineAbsolutePos); ImGui::InvisibleButton("TimelineMoving", TimelineSizeWithBorder, ImGuiButtonFlags_MouseButtonLeft | ImGuiButtonFlags_MouseButtonRight); // ImGui::Button("TimelineMoving", TimelineSizeWithBorder); bool32 IsHovered = ImGui::IsItemHovered(); bool32 IsItemActive = ImGui::IsItemActive(); bool32 IsItemActivated = ImGui::IsItemActivated(); bool32 IsItemDeactivated = ImGui::IsItemDeactivated(); bool32 LeftClick = ImGui::IsMouseDown(ImGuiMouseButton_Left); bool32 RightClick = ImGui::IsMouseDown(ImGuiMouseButton_Right); if (IsHovered && io.MouseWheel) { real32 Increment = 0.1; bool32 Direction = (io.MouseWheel > 0) ? 1 : -1; ImVec2 Offset = (io.MousePos - (TimelineAbsolutePos + ActiveMoveSize)) / ActiveZoomSize; if (io.KeyShift) { ActivePercentOffset->y += Increment*Direction; } else if (io.KeyCtrl) { ActivePercentOffset->x += Increment*Direction*0.3; } else { if (Direction == 1) { *ActivePercentZoomed = *ActivePercentZoomed - (*ActivePercentZoomed * Increment); *ActivePercentOffset = *ActivePercentOffset - ((*ActivePercentOffset * Increment) + Offset*Increment); } else { *ActivePercentOffset = ((*ActivePercentOffset + Offset*Increment) / (1.0f - Increment)); *ActivePercentZoomed = (*ActivePercentZoomed / (1.0f - Increment)); } } } if (LeftClick) { if (IsItemActivated) { if (!io.KeyShift && State->TimelineMode == timeline_mode_default) Layer_DeselectAll(File, State, Memory); if (State->Interact_Active == interact_type_keyframe_move || State->Interact_Active == interact_type_keyframe_rotate || State->Interact_Active == interact_type_keyframe_scale) { History_Entry_Commit(Memory, "Move 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 = Property_GetSortedArray(SortedPropertyArray, i, h); 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; State->Interact_Modifier = 0; } State->BoxSelect = true; } if (IsItemActive) { Assert(State->BoxSelect); draw_list->AddRectFilled(io.MouseClickedPos[0], io.MousePos, IM_COL32(0, 0, 200, 50)); } } else { State->Warp_X = 0; State->Warp_Y = 0; } if (IsItemDeactivated) { State->BoxSelect = false; } draw_list->PopClipRect(); ImGui::PopClipRect(); ImGui::PopStyleVar(); ImGui::EndChild(); ImGui::PopStyleVar(2); ImGui::End(); } struct key_entry { ImGuiKey_ KeyIndex; char *Name; char *ShiftName; uint32 Sector; ImVec2 Offset; real32 WidthRatio; }; struct shortcut_entry { ImGuiKey_ Key; ImGuiModFlags_ Mods; char *Name; }; #if 0 static key_entry KeyEntries[] { { ImGuiKey_Tab, "Tab", "\0", 0, ImVec2(0, 1), 1.5f }, { ImGuiKey_LeftArrow, "<-", "\0", 2, ImVec2(0, 5), 1.0f }, { ImGuiKey_RightArrow, "->", "", 2, ImVec2(2, 5), 1.0f }, { ImGuiKey_UpArrow, "/\\", "", 2, ImVec2(1, 4), 1.0f }, { ImGuiKey_DownArrow, "\\/", "", 2, ImVec2(1, 5), 1.0f }, { ImGuiKey_PageUp, "Pg Up", "", 2, ImVec2(2, 0), 1.0f }, { ImGuiKey_PageDown, "Pg Dn", "", 2, ImVec2(2, 1), 1.0f }, { ImGuiKey_Home, "Home", "", 2, ImVec2(1, 0), 1.0f }, { ImGuiKey_End, "End", "", 2, ImVec2(0, 1), 1.0f }, { ImGuiKey_Insert, "Insert", "", 2, ImVec2(0, 0), 1.0f }, { ImGuiKey_Delete, "Delete", "", 2, ImVec2(0, 1), 1.0f }, { ImGuiKey_Backspace, "Backspace", "", 0, ImVec2(13, 1), 2.0f }, { ImGuiKey_Space, "Space", "", 0, ImVec2(4, 3), 6.5f }, { ImGuiKey_Enter, "Enter", "", 0, ImVec2(2, 12), 1.5f}, { ImGuiKey_Escape, "Esc", "", 1, ImVec2(0, 0), 1.0f }, { ImGuiKey_LeftCtrl, { ImGuiKey_LeftShift, "L_Shift", "", 0, { ImGuiKey_LeftAlt, { ImGuiKey_LeftSuper, { ImGuiKey_RightCtrl, { ImGuiKey_RightShift, { ImGuiKey_RightAlt, { ImGuiKey_RightSuper, { ImGuiKey_Menu, { ImGuiKey_0, "0", ")", 0, ImVec2(10, 0), 1.0f }, { ImGuiKey_1, "1", "!", 0, ImVec2(1, 0), 1.0f }, { ImGuiKey_2, "2", "@", 0, ImVec2(2, 0), 1.0f }, { ImGuiKey_3, "3", "$", 0, ImVec2(3, 0), 1.0f }, { ImGuiKey_4, "4", "%", 0, ImVec2(4, 0), 1.0f }, { ImGuiKey_5, "5", "%", 0, ImVec2(5, 0), 1.0f }, { ImGuiKey_6, "6", "^", 0, ImVec2(6, 0), 1.0f }, { ImGuiKey_7, "7", "&", 0, ImVec2(7, 0), 1.0f }, { ImGuiKey_8, "8", "*", 0, ImVec2(8, 0), 1.0f }, { ImGuiKey_9, "9", "(", 0, ImVec2(9, 0), 1.0f }, { ImGuiKey_A, "a", "A", 0, ImVec2(1, 2), 1.0f }, { ImGuiKey_B, "b", "B", 0, ImVec2(5, 3), 1.0f }, { ImGuiKey_C, "c", "C", 0, ImVec2(3, 3), 1.0f }, { ImGuiKey_D, "d", "D", 0, ImVec2(3, 2), 1.0f }, { ImGuiKey_E, "e", "E", 0, ImVec2(0, 2), 1.0f }, { ImGuiKey_F, "f", "F", 0, ImVec2(4, 2), 1.0f }, { ImGuiKey_G, "g", "G", 0, ImVec2(5, 2), 1.0f }, { ImGuiKey_H, "h", "H", 0, ImVec2(6, 2), 1.0f }, { ImGuiKey_I, "i", "I", 0, ImVec2(0, 7), 1.0f }, { ImGuiKey_J, "j", "J", 0, ImVec2(7, 2), 1.0f }, { ImGuiKey_K, "k", "K", 0, ImVec2(8, 2), 1.0f }, { ImGuiKey_L, "l", "L", 0, ImVec2(9, 2), 1.0f }, { ImGuiKey_M, "m", "M", 0, ImVec2(7, 3), 1.0f }, { ImGuiKey_N, "n", "N", 0, ImVec2(6, 3), 1.0f }, { ImGuiKey_O, "o", "O", 0, ImVec2(8, 1), 1.0f }, { ImGuiKey_P, "p", "P", 0, ImVec2(9, 1), 1.0f }, { ImGuiKey_Q, "q", "Q", 0, ImVec2(1, 1), 1.0f }, { ImGuiKey_R, "r", "R", 0, ImVec2(3, 1), 1.0f }, { ImGuiKey_S, "s", "S", 0, ImVec2(2, 1), 1.0f }, { ImGuiKey_T, "t", "T", 0, ImVec2(4, 1), 1.0f }, { ImGuiKey_U, "u", "U", 0, ImVec2(6, 1), 1.0f }, { ImGuiKey_V, "v", "V", 0, ImVec2(4, 3), 1.0f }, { ImGuiKey_W, "w", "W", 0, ImVec2(1, 1), 1.0f }, { ImGuiKey_X, "x", "X", 0, ImVec2(2, 3), 1.0f }, { ImGuiKey_Y, "y", "Y", 0, ImVec2(0, 5), 1.0f }, { ImGuiKey_Z, "z", "Z", 0, ImVec2(1, 3), 1.0f }, { ImGuiKey_F1, "F1", "", 1, ImVec2(2, 0), 1.0f }, { ImGuiKey_F2, "F2", "", 1, ImVec2(3, 0), 1.0f }, { ImGuiKey_F3, "F3", "", 1, ImVec2(4, 0), 1.0f }, { ImGuiKey_F4, "F4", "", 1, ImVec2(5, 0), 1.0f }, { ImGuiKey_F5, "F5", "", 1, ImVec2(6.5, 0), 1.0f }, { ImGuiKey_F6, "F6", "", 1, ImVec2(7.5, 0), 1.0f }, { ImGuiKey_F7, "F7", "", 1, ImVec2(8.5, 0), 1.0f }, { ImGuiKey_F8, "F8", "", 1, ImVec2(9.5, 0), 1.0f }, { ImGuiKey_F9, "F9", "", 1, ImVec2(11, 0), 1.0f }, { ImGuiKey_F10 "F10","", 1, ImVec2(12, 0), 1.0f }, { ImGuiKey_F11 "F11","", 1, ImVec2(13, 0), 1.0f }, { ImGuiKey_F12 "F12","", 1, ImVec2(14, 0), 1.0f }, { ImGuiKey_Apostrophe, "\'", "\"", 1, ImVec2(11, 2), 1.0f }, { ImGuiKey_Comma, ",", "<", 0, ImVec2(8, 3), 1.0f }, { ImGuiKey_Minus, "-", "_", 0, ImVec2(11, 1), 1.0f}; { ImGuiKey_Period, ".", ">", 0, ImVec2(9, 3), 1.0f }, { ImGuiKey_Slash, "/", "?", 0, ImVec2(10, 3), 1.0f }, { ImGuiKey_Semicolon, ";", ":", 0, ImVec2(10, 2), 1.0f }, { ImGuiKey_Equal, "=", "+", 0, ImVec2(12, 1), 1.0f }, { ImGuiKey_LeftBracket, "[", "{", 0, ImVec2(0, 10), 1.0f }, { ImGuiKey_Backslash, "\\","|", 0, ImVec2(0, 12), 1.5f }, { ImGuiKey_RightBracket,"]", "}", 0, ImVec2(0, 11), 1.0f }, { ImGuiKey_GraveAccent, "`", "~", 0, ImVec2(0, 0), 1.0f }, { ImGuiKey_CapsLock, "Caps lock", "", 1, ImVec2(0, 2), 1.75f }; { ImGuiKey_ScrollLock, { ImGuiKey_NumLock, { ImGuiKey_PrintScreen, { ImGuiKey_Pause, { ImGuiKey_Keypad0, { ImGuiKey_Keypad1, { ImGuiKey_Keypad2, { ImGuiKey_Keypad3, { ImGuiKey_Keypad4, { ImGuiKey_Keypad5, { ImGuiKey_Keypad6, { ImGuiKey_Keypad7, { ImGuiKey_Keypad8, { ImGuiKey_Keypad9, { ImGuiKey_KeypadDecimal, { ImGuiKey_KeypadDivide, { ImGuiKey_KeypadMultiply, { ImGuiKey_KeypadSubtract, { ImGuiKey_KeypadAdd, { ImGuiKey_KeypadEnter, { ImGuiKey_KeypadEqual, }; #endif static shortcut_entry ShortcutArray[] { { ImGuiKey_1, ImGuiModFlags_None, "Enable debug UI" }, { ImGuiKey_2, ImGuiModFlags_None, "Toggle precomp view" } }; static ImVec2 SectorOffset[] = { ImVec2(0, 1.5), ImVec2(0, 0) }; static void ImGui_Popups(project_data *File, project_state *State, ui *UI, memory *Memory, ImGuiIO io) { switch (State->ImGuiPopups) { case popup_none: { } break; case popup_saveas: { ImGui::OpenPopup("Save as"); ImVec2 Size(300, 300); Assert(0); ImGui::SetNextWindowPos(ImVec2(300, 800)); ImGui::SetNextWindowSize(Size); ImGui::SetKeyboardFocusHere(); } break; case popup_keybinds: { ImGui::OpenPopup("Keybinds"); ImGui::SetNextWindowPos(ImVec2(300, 800)); ImGui::SetNextWindowSize(ImVec2(500, 300)); ImGui::SetKeyboardFocusHere(); } break; default: { Assert(0); } } State->ImGuiPopups = popup_none; if (ImGui::BeginPopupModal("Save as")) { ImGui::Text("Destination path..."); ImGui::InputText("File", State->Filename, 512); if (ImGui::Button("Save file")) { ImGui::Text("Saving..."); File_SaveAs(File, State, Memory, State->Filename); ImGui::CloseCurrentPopup(); } if (ImGui::IsKeyPressed(ImGuiKey_Escape)) { ImGui::CloseCurrentPopup(); } ImGui::EndPopup(); } if (ImGui::BeginPopupModal("Keybinds")) { #if 0 real32 KeySize = 20; for (int k = 0; k < AmountOf(KeyEntries); k++) { key_entry KeyEntry = KeyArray[k]; ImVec2 Offset_ScreenPos = ImVec2(KeySize, KeySize) * (SectorOffset[KeyEntry.Sector] + } #endif } } 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; State->UpdateKeyframes = true; } if (ImGui::IsKeyPressed(ImGuiKey_E)) { if (!io.KeyShift) { 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; State->UpdateKeyframes = true; } else { State->Brush.EraseMode ^= 1; } } if (ImGui::IsKeyPressed(ImGuiKey_X)) { if (State->Interact_Active == interact_type_keyframe_move) { if (State->Interact_Modifier != 1) State->Interact_Modifier = 1; else State->Interact_Modifier = 0; } else { 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; } // NOTE(fox): File data not tracked on undo tree! if (ImGui::IsKeyPressed(ImGuiKey_N)) { block_composition *MainComp = (block_composition *)Memory_Block_AddressAtIndex(Memory, F_Precomps, File->PrincipalCompIndex); if (MainComp->Frame_Start < State->Frame_Current) MainComp->Frame_End = State->Frame_Current; } if (ImGui::IsKeyPressed(ImGuiKey_B)) { block_composition *MainComp = (block_composition *)Memory_Block_AddressAtIndex(Memory, F_Precomps, File->PrincipalCompIndex); if (MainComp->Frame_End > State->Frame_Current) MainComp->Frame_Start = State->Frame_Current; } if (ImGui::IsKeyPressed(ImGuiKey_Tab)) { State->TimelineMode = (State->TimelineMode == timeline_mode_default) ? timeline_mode_graph : timeline_mode_default; UI->GraphZoomSize = ImVec2(0, 0); UI->GraphMoveSize = ImVec2(0, 0); } if (!io.KeyCtrl) { if (State->FocusedWindow == focus_timeline) { if (State->TimelineMode == timeline_mode_default) { if (ImGui::IsKeyPressed(ImGuiKey_G)) { Layer_ToggleChannel(File, Memory, 0); Layer_ToggleChannel(File, Memory, 1); } else if (ImGui::IsKeyPressed(ImGuiKey_A)) { Layer_ToggleChannel(File, Memory, 2); Layer_ToggleChannel(File, Memory, 3); } else if (ImGui::IsKeyPressed(ImGuiKey_R)) { Layer_ToggleChannel(File, Memory, 4); } else if (ImGui::IsKeyPressed(ImGuiKey_S)) { Layer_ToggleChannel(File, Memory, 5); } else if (ImGui::IsKeyPressed(ImGuiKey_T)) { if (io.KeyShift) { Layer_ToggleChannel(File, Memory, 6); } else { Layer_ToggleChannel(File, Memory, 7); } } } else if (State->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; } } } else if (State->FocusedWindow == focus_viewport) { if (ImGui::IsKeyPressed(ImGuiKey_T)) { State->HotkeyInput = hotkey_transform; } } } 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) ) { if (io.KeyShift) { State->RerouteEffects = true; } else { 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 (io.KeyShift && ImGui::IsKeyPressed(ImGuiKey_Slash)) { State->ImGuiPopups = popup_keybinds; } #if DEBUG if (ImGui::IsKeyPressed(ImGuiKey_3)) { State->ImGuiPopups = popup_keybinds; } if (ImGui::IsKeyPressed(ImGuiKey_F)) { sprintf(State->DummyName, "test2"); File_Open(File, State, Memory, State->DummyName); State->UpdateFrame = true; } if (ImGui::IsKeyPressed(ImGuiKey_0)) { Debug.ReloadUI ^= 1; } 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->Filename[0] == '\0') { State->ImGuiPopups = popup_saveas; } else { File_SaveAs(File, State, Memory, State->Filename); } } } if (ImGui::IsKeyPressed(ImGuiKey_C)) { State->HotkeyInput = hotkey_copy; } if (ImGui::IsKeyPressed(ImGuiKey_V)) { State->HotkeyInput = hotkey_paste; } 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 (State->Filename[0] == '\0') State->ImGuiPopups = popup_saveas; else File_SaveAs(File, State, Memory, State->Filename); } if (ImGui::MenuItem("Save as", "Ctrl+Shift+S")) { State->ImGuiPopups = popup_saveas; } if (ImGui::BeginMenu("Open file")) { ImGui::InputText("Filename", State->DummyName, 512); if (ImGui::IsItemDeactivated() && ImGui::IsKeyPressed(ImGuiKey_Enter)) { if (File_Open(File, State, Memory, State->DummyName)) { State->UpdateFrame = true; } else { PostMsg(State, "File not found."); } } ImGui::EndMenu(); } 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(); } ImGui::EndMenu(); } if (ImGui::BeginMenu("Window")) { #if STABLE if (ImGui::Selectable("Stable Diffusion tools", UI->StableEnabled)) UI->StableEnabled ^= 1; ImGui::EndMenu(); #endif } ImGui::EndMenuBar(); } ImGui::End(); } static void ImGui_EffectsPanel(project_data *File, project_state *State, memory *Memory, ui *UI, ImGuiIO io) { ImGui::Begin("Effects list", NULL); if (State->RerouteEffects) { ImGui::SetKeyboardFocusHere(); State->RerouteEffects = 0; } int value_changed = ImGui::InputText("Effect name...", State->filter.InputBuf, IM_ARRAYSIZE(State->filter.InputBuf), ImGuiInputTextFlags_CallbackCompletion, EffectConsoleCallback); if (Hacko) { if (!io.KeyShift) EffectSel++; else EffectSel--; Hacko = 0; } if (value_changed) { State->filter.Build(); EffectSel = -1; } // Enter conveniently deactivates the InputText field if (ImGui::IsItemDeactivated() && ImGui::IsKeyPressed(ImGuiKey_Enter)) { int32 p = 0; for (int32 i = 0; i < State->Playhead_Effect; i++) { header_effect *EffectHeader = &State->Effect[i]; if (State->filter.PassFilter(EffectHeader->Name)) { if (EffectSel == p && State->MostRecentlySelectedLayer != -1) { Assert(0); // AddEffect(File->Layer[State->MostRecentlySelectedLayer], Memory, i); State->UpdateFrame = true; } p++; } } EffectSel = -1; } int32 p = 0; for (int32 i = 0; i < State->Playhead_Effect; i++) { header_effect *EffectHeader = &State->Effect[i]; if (State->filter.PassFilter(EffectHeader->Name)) { bool t = false; if (EffectSel == p) { t = true; } ImGui::Selectable(EffectHeader->Name, &t); if (ImGui::IsItemClicked()) { if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left) && State->MostRecentlySelectedLayer != -1) { Assert(0); State->UpdateFrame = true; } } p++; } } ImGui::End(); }