#include "my_imgui_internal_widgets.h" #include "imgui_ops.h" #include "imgui_helper_widgets.cpp" static void ImGui_PropertyInteract_Slider(project_state *State, memory *Memory, property_channel *Property, ImGuiIO &io, ImVec2 WindowMinAbs, ImVec2 WindowMaxAbs, memory_table_list Table) { if (ImGui::IsItemActive()) { State->UpdateFrame = true; ImGui_WarpMouse(State, io.MousePos, WindowMinAbs, WindowMaxAbs, 1); } if (ImGui::IsItemActivated()) { State->Interact_Offset[0] = Property->CurrentValue; State->Interact_Active = interact_type_slider_scrub; } if (ImGui::IsItemDeactivatedAfterEdit()) { // Pressing Esc while dragging a slider conveniently stops the input in // ImGui, so all we need to do is set it back: if (ImGui::IsKeyPressed(ImGuiKey_Escape)) { Property->CurrentValue = State->Interact_Offset[0]; } else if (!Property->Keyframe_Count) { History_Entry_Commit(Memory, "Property interact"); real32 Temp = Property->CurrentValue; Property->CurrentValue = State->Interact_Offset[0]; History_Action_Swap(Memory, Table, sizeof(Property->CurrentValue), &Property->CurrentValue); Property->CurrentValue = Temp; History_Entry_End(Memory); } State->Interact_Active = interact_type_none; State->UpdateFrame = true; State->Warp_X = 0; State->Warp_Y = 0; } } static void ImGui_RGBAModeSwitch(project_state *State, memory *Memory, ImGuiIO io, uint32 *Channel) { char *Names[5] = {"All", "R", "G", "B", "A" }; if (ImGui::BeginListBox("RGB")) { for (int i = 0; i < 5; i++) { if (ImGui::Selectable(Names[i], (*Channel == i))) { *Channel = i; } } ImGui::EndListBox(); } } static void ImGui_CurvesUI(project_state *State, memory *Memory, ImGuiIO io, block_effect *Effect, property_channel *PropertyStart, uint16 *SortedPointStart) { real32 Padding = ImGui::GetFontSize()*6; ImVec2 ViewportMin = ImGui::GetCursorScreenPos() + Padding/6; ImVec2 ViewportScale = ImGui::GetContentRegionAvail(); ViewportScale.y = ViewportScale.x = ViewportScale.x - Padding; // square seems nice ImVec2 ViewportMax = ViewportMin + ViewportScale; 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)); real32 PointSize = 40; ImU32 col = ImGui::GetColorU32(ImGuiCol_Text); ImU32 col_light = ImGui::GetColorU32(ImGuiCol_TextDisabled); ImVec2 Point_ScreenPos[4]; // ocd? draw_list->PushClipRect(ViewportMin + 2, ViewportMax - 2, true); for (real32 i = 0.25; i < 1.0; i += 0.25) { ImVec2 Horizontal = ViewportMin + ViewportScale * ImVec2(0, i); ImVec2 Vertical = ViewportMin + ViewportScale * ImVec2(i, 0); draw_list->AddLine(Horizontal, Horizontal + ImVec2(ViewportScale.x, 0), col_light, 1.0f); draw_list->AddLine(Vertical, Vertical + ImVec2(0, ViewportScale.y), col_light, 1.0f); } draw_list->PopClipRect(); #if 0 real32 LUT[5][256] = {}; for (int a = 0; a < 5; a++) { int Num = (a == 0) ? *NumberOfPoints_Main : NumberOfPoints_Col->E[a-1]; v2 *CurvePoint = (v2 *)(FileEffectAddress + (a * (sizeof(v2) * 10))); for (int i = 0; i < Num; i++) { v2 Point_P1 = CurvePoint[i]; v2 Point_P2 = CurvePoint[i + 1]; v2 Point_P0 = (i != 0) ? CurvePoint[i - 1] : V2(0, 0); v2 Point_P3 = (i != (Num - 2)) ? CurvePoint[i + 2] : V2(1, 1); v2 m1 = (Point_P2 - Point_P0) / (2 * Tau); v2 m2 = (Point_P3 - Point_P1) / (2 * Tau); CurvesSolver(LUT[a], Point_P1, Point_P2, m1, m2, i); } if (CurvePoint[0].x > 0.0f) { real32 Count_Start = 0; real32 Count_End = (CurvePoint[0].x * 255); real32 Count = Count_Start; while (Count < Count_End) { LUT[a][(uint32)Count] = LUT[a][(uint32)Count_End]; Count++; } } if (CurvePoint[Num-1].x < 1.0f) { real32 Count_Start = (CurvePoint[Num-1].x * 255) - 0.5; real32 Count_End = 255; real32 Count = Count_Start; while (Count < Count_End) { LUT[a][(uint32)Count] = LUT[a][(uint32)Count_Start]; Count++; } } for (int i = 0; i < Num; i++) { if (CurvePoint[i].y == 1.0f) LUT[a][255] = 1.0f; } } #endif real32 *Num = &Effect->ExtraData[0]; uint32 *SelectedChannel = (uint32 *)&Effect->ExtraData[5]; v2 Pos = {}; bool32 AddPoint = 0; for (uint32 i = 0; i < *(uint32 *)Num; i += 1) { v2 Point_P1 = Effect_V2(Memory, Effect, SortedPointStart[i]); v2 Point_P2 = Effect_V2(Memory, Effect, SortedPointStart[i + 1]); v2 Point_P0 = (i != 0) ? Effect_V2(Memory, Effect, SortedPointStart[i - 1]) : V2(0, 0); v2 Point_P3 = (i != (*Num - 2)) ? Effect_V2(Memory, Effect, SortedPointStart[i + 2]) : V2(1, 1); ImVec2 Point_P0_ScreenPos = ViewportMin + (ImVec2(Point_P0.x, 1.0f - Point_P0.y) * ViewportScale); ImVec2 Point_P1_ScreenPos = ViewportMin + (ImVec2(Point_P1.x, 1.0f - Point_P1.y) * ViewportScale); ImVec2 Point_P2_ScreenPos = ViewportMin + (ImVec2(Point_P2.x, 1.0f - Point_P2.y) * ViewportScale); ImVec2 Point_P3_ScreenPos = ViewportMin + (ImVec2(Point_P3.x, 1.0f - Point_P3.y) * ViewportScale); ImGui::PushID(&PropertyStart[SortedPointStart[i]]); draw_list->AddNgon(Point_P1_ScreenPos, 2, col, 8, 5.0f); ImGui::SetCursorScreenPos(Point_P1_ScreenPos - ImVec2(PointSize/2, PointSize/2)); ImGui::InvisibleButton("##point", ImVec2(PointSize, PointSize), ImGuiButtonFlags_MouseButtonLeft); if (ImGui::IsItemHovered()) { ImGui::SetMouseCursor(ImGuiMouseCursor_Hand); } if (ImGui::IsItemActivated()) { if (io.KeyCtrl && *Num != 2) { property_channel *Property_X = Effect_Property(Memory, Effect, SortedPointStart[i]); Property_X->Identifier = -1; property_channel *Property_Y = Effect_Property(Memory, Effect, SortedPointStart[i]+1); Property_Y->Identifier = -1; Effect->ExtraData[*SelectedChannel]--; } } if (ImGui::IsItemActive()) { property_channel *Property_X = Effect_Property(Memory, Effect, SortedPointStart[i]); property_channel *Property_Y = Effect_Property(Memory, Effect, SortedPointStart[i]+1); v2 Point = V2(((io.MousePos - ViewportMin) / ViewportScale)); Point.y = 1.0f - Point.y; Point.x = Normalize(Point.x); Point.y = Normalize(Point.y); Property_X->CurrentValue = Point.x; Property_Y->CurrentValue = Point.y; State->UpdateFrame = true; } if (i == (*Num - 1)) { ImGui::PopID(); break; } // Conversion from Catmull-Rom curves to Bezier curves for display, // referencing https://pomax.github.io/bezierinfo/#catmullconv ImVec2 bez_m1 = (Point_P2_ScreenPos - Point_P0_ScreenPos) / (6 * Tau); ImVec2 bez_m2 = (Point_P3_ScreenPos - Point_P1_ScreenPos) / (6 * Tau); ImVec2 Point_Bez[4]; Point_Bez[0] = Point_P1_ScreenPos; Point_Bez[1] = Point_P1_ScreenPos + bez_m1; Point_Bez[2] = Point_P2_ScreenPos - bez_m2; Point_Bez[3] = Point_P2_ScreenPos; draw_list->PushClipRect(ViewportMin, ViewportMax, true); draw_list->AddBezierCubic(Point_Bez[0], Point_Bez[1], Point_Bez[2], Point_Bez[3], col, 1.0f, 0); if (ImGui::BezierInteractive(Point_Bez[0], Point_Bez[1], Point_Bez[2], Point_Bez[3]) && io.MousePos.x > (Point_P1_ScreenPos.x + PointSize/2) && io.MousePos.x < (Point_P2_ScreenPos.x - PointSize/2)) { ImGui::SetCursorScreenPos(io.MousePos - ImVec2(5,5)); ImGui::Button("pointclick", ImVec2(10, 10)); if (ImGui::IsItemActivated()) { Pos = V2(((io.MousePos - ViewportMin) / ViewportScale)); Pos.y = 1.0f - Pos.y; Pos.x = Normalize(Pos.x); Pos.y = Normalize(Pos.y); AddPoint = true; } } if (i == 0) draw_list->AddLine(ImVec2(ViewportMin.x, Point_Bez[0].y), Point_Bez[0], col, 1.0f); if (i == (*Num - 2)) draw_list->AddLine(ImVec2(ViewportMax.x, Point_Bez[3].y), Point_Bez[3], col, 1.0f); draw_list->PopClipRect(); #if 0 for (int x = 0; x < 256; x++) { v2 Point = V2((real32)x/256, LUT[*ChannelIndex][x]); ImVec2 Point_ScreenPos = ViewportMin + (ImVec2(Point.x, 1.0f - Point.y) * ViewportScale); draw_list->AddNgon(Point_ScreenPos, 1, col, 8, 5.0f); } #endif draw_list->AddNgon(Point_P1_ScreenPos, 2, col, 8, 5.0f); ImGui::PopID(); } if (AddPoint) { int x = 0; for (;;) { property_channel *Property_X = Effect_Property(Memory, Effect, x); if (Property_X->Identifier == -1) { Property_X->CurrentValue = Pos.x; Property_X->Identifier = *SelectedChannel; property_channel *Property_Y = Effect_Property(Memory, Effect, x+1); Assert(Property_Y->Identifier == -1); Property_Y->CurrentValue = Pos.y; Property_Y->Identifier = *SelectedChannel; x = MAX_PROPERTIES_PER_EFFECT; } if (x > MAX_PROPERTIES_PER_EFFECT) break; x++; } Effect->ExtraData[*SelectedChannel]++; AddPoint = false; } // ImVec2 ButtonPos = ImGui::GetCursorScreenPos(); ImGui::SetCursorScreenPos(ViewportMin); ImGui::InvisibleButton("canvas", ViewportScale, ImGuiButtonFlags_MouseButtonLeft | ImGuiButtonFlags_MouseButtonRight); bool32 IsHovered = ImGui::IsItemHovered(); bool32 IsActive = ImGui::IsItemActive(); bool32 IsActivated = ImGui::IsItemActivated(); bool32 IsDeactivated = ImGui::IsItemDeactivated(); ImVec2 EndPos = ImGui::GetCursorScreenPos(); ImGui::SetCursorScreenPos(ViewportMin + ImVec2(ViewportScale.x + 20, 0)); ImGui_RGBAModeSwitch(State, Memory, io, SelectedChannel); ImGui::SetCursorScreenPos(EndPos); } static void ImGui_PropertiesPanel(project_data *File, project_state *State, ui *UI, memory *Memory, ImGuiIO io, sorted_comp_info *SortedCompArray, sorted_layer *SortedLayerArray, sorted_property_info *SortedPropertyInfo, uint16 *SortedPropertyArray) { bool32 Display = 1; block_layer *Layer = NULL; sorted_layer *SortedLayer = NULL; if (State->MostRecentlySelectedLayer > -1) { Layer = (block_layer *)Memory_Block_AddressAtIndex(Memory, F_Layers, State->MostRecentlySelectedLayer, 0); sorted_comp_info SortedCompInfo = SortedCompArray[Layer->Block_Composition_Index]; sorted_layer *SortedLayerInfo = Layer_GetSortedArray(SortedLayerArray, &SortedCompInfo, Layer->Block_Composition_Index); SortedLayer = &SortedLayerInfo[State->MostRecentlySelectedLayer]; if (!Layer->Occupied) Display = 0; } else { Display = 0; } if (Display) { block_string *String = (block_string *)Memory_Block_AddressAtIndex(Memory, F_Strings, Layer->Block_String_Index); char buf[256]; sprintf(buf, "Properties: %s###Properties", String->Char); ImGui::Begin(buf); if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) State->FocusedWindow = focus_properties; ImVec2 WindowSize = ImGui::GetWindowSize(); ImVec2 WindowMinAbs = ImGui::GetWindowPos(); ImVec2 WindowMaxAbs = WindowMinAbs + WindowSize; ImGui::Text("Transform"); sorted_property_info *InfoLocation = SortedPropertyInfo + SortedLayer->SortedPropertyStart; uint16 *ArrayLocation = SortedPropertyArray + SortedLayer->SortedKeyframeStart; int h = 0, c = 0, p = 0; property_channel *Property = NULL; block_effect *Effect = NULL; while (Layer_LoopChannels(State, Memory, &InfoLocation, &ArrayLocation, Layer, &Property, &Effect, &h, &c, &p)) { ImGui::PushID(Property); if ((h - 1) < AmountOf(Layer->Property) && c == 0) { if (ImGui::Button("K")) { Property_AddKeyframe(Memory, F_Layers, Property, State->Frame_Current, ArrayLocation); } ImGui::SameLine(); #if DEBUG char size[64]; sprintf(size, "%s, %i", DefaultChannel[h-1], Property->Keyframe_Count); char *Name = size; #else char *Name = DefaultChannel[h-1]; #endif ImGui::DragScalar(Name, ImGuiDataType_Float, &Property->CurrentValue, Property->ScrubVal, &Property->MinVal, &Property->MaxVal, "%f"); ImGui_PropertyInteract_Slider(State, Memory, Property, io, WindowMinAbs, WindowMaxAbs, F_Layers); } else { Assert(Effect); header_effect *EffectHeader = Effect_EntryFromID(State, Effect->ID); header_property ChannelHeader = State->Property[EffectHeader->PropertyStartIndex + c - 1]; #if DEBUG char size[64]; sprintf(size, "%s, %i", ChannelHeader.Name, Property->Keyframe_Count); char *Name = size; #else char *Name = ChannelHeader.Name; #endif if ((c - 1) == 0) { ImGui::PushID(Effect->Index); #if DEBUG ImGui::Text("%s, %i", EffectHeader->Name, Effect->Index); #else ImGui::Text(EffectHeader->Name); #endif ImGui::PopID(); } if (EffectHeader->DisplayType == effect_display_type_standard) { if (ChannelHeader.DisplayType == property_display_type_standard) { if (ImGui::Button("K")) { Property_AddKeyframe(Memory, F_Properties, Property, State->Frame_Current, ArrayLocation); } ImGui::SameLine(); ImGui::DragScalar(Name, ImGuiDataType_Float, &Property->CurrentValue, Property->ScrubVal, &Property->MinVal, &Property->MaxVal, "%f"); ImGui_PropertyInteract_Slider(State, Memory, Property, io, WindowMinAbs, WindowMaxAbs, F_Properties); } else if (ChannelHeader.DisplayType == property_display_type_color) { if (ImGui::Button("K")) { Property_AddKeyframe(Memory, F_Properties, Property, State->Frame_Current, ArrayLocation); } ImGui::SameLine(); ImGui::DragScalar(Name, ImGuiDataType_Float, &Property->CurrentValue, Property->ScrubVal, &Property->MinVal, &Property->MaxVal, "%f"); ImGui_PropertyInteract_Slider(State, Memory, Property, io, WindowMinAbs, WindowMaxAbs, F_Properties); // if (c == 3) { // ImGui::ColorEdit4("col", Col, ImGuiColorEditFlags_Float); // } } else { Assert(0); } } else if (EffectHeader->DisplayType == effect_display_type_curves) { #if DEBUG ImGui::Text("Points (RGBA): %.02f, Points (indiv): %.02f, %.02f, %.02f, %.02f", Effect->ExtraData[0], Effect->ExtraData[1], Effect->ExtraData[2], Effect->ExtraData[3], Effect->ExtraData[4]); #endif if (Property->Identifier == -1) { Effect_Curves_Init(Effect, Property); } uint16 SortedPointStart[MAX_PROPERTIES_PER_EFFECT/5]; uint32 VisibleChannel = *(uint32 *)&Effect->ExtraData[5]; Effect_Curves_Sort(Memory, Effect, SortedPointStart, VisibleChannel); ImGui_CurvesUI(State, Memory, io, Effect, Property, SortedPointStart); c = EffectHeader->Property_Count; // Causes this loop to only iterate once. } else if (EffectHeader->DisplayType == effect_display_type_levels) { ImGui::Text("Levels!"); uint32 VisibleChannel = *(uint32 *)&Effect->ExtraData[0]; real32 *P_Left = 0, *P_Mid = 0, *P_Right = 0; if (VisibleChannel == 0) { property_channel *Property0 = (property_channel *)Memory_Block_AddressAtIndex(Memory, F_Properties, Effect->Block_Property_Index[0]); property_channel *Property1 = (property_channel *)Memory_Block_AddressAtIndex(Memory, F_Properties, Effect->Block_Property_Index[1]); property_channel *Property2 = (property_channel *)Memory_Block_AddressAtIndex(Memory, F_Properties, Effect->Block_Property_Index[2]); P_Left = &Property0->CurrentValue; P_Mid = &Property1->CurrentValue; P_Right = &Property2->CurrentValue; } else { property_channel *Property0 = (property_channel *)Memory_Block_AddressAtIndex(Memory, F_Properties, Effect->Block_Property_Index[3+(VisibleChannel-1)]); property_channel *Property1 = (property_channel *)Memory_Block_AddressAtIndex(Memory, F_Properties, Effect->Block_Property_Index[7+(VisibleChannel-1)]); property_channel *Property2 = (property_channel *)Memory_Block_AddressAtIndex(Memory, F_Properties, Effect->Block_Property_Index[11+(VisibleChannel-1)]); P_Left = &Property0->CurrentValue; P_Mid = &Property1->CurrentValue; P_Right = &Property2->CurrentValue; } ImGui::SliderLevels("1", "2,", "3", (void *)P_Mid, (void *)P_Left, (void *)P_Right); if (ImGui::IsItemActive()) { State->UpdateFrame = true; } ImGui_RGBAModeSwitch(State, Memory, io, (uint32 *)&Effect->ExtraData[0]); c = EffectHeader->Property_Count; } else { Assert(0); } } ImGui::PopID(); } #if 0 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(); 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); // NOTE(fox): We might have to make a system for processing create/delete // calls after the UI if things like the Count get incremented and invalid // data is read. if (ChannelHeader.DisplayType == property_display_type_standard) { if (ImGui::Button("K")) { uint16 *ArrayLocation = Property_GetSortedArray(SortedPropertyArray, State->MostRecentlySelectedLayer, h); Property_AddKeyframe(Memory, Property, State->Frame_Current, ArrayLocation); } ImGui::SameLine(); ImGui::DragScalar(ChannelHeader.Name, ImGuiDataType_Float, &Property->CurrentValue, Property->ScrubVal, &Property->MinVal, &Property->MaxVal, "%f"); ImGui_PropertyInteract_Slider(State, Memory, Property, io, WindowMinAbs, WindowMaxAbs, F_Properties); } else if (ChannelHeader.DisplayType == property_display_type_color) { header_property LastHeader = State->Property[EffectHeader->PropertyStartIndex + c + 3]; Assert(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 | ImGuiColorEditFlags_NoInputs)) { } if (ImGui::IsItemActivated()) { // State->Interact_Offset[0] = Property->CurrentValue; // State->Interact_Offset[1] = Property1->CurrentValue; // State->Interact_Offset[2] = Property2->CurrentValue; // State->Interact_Offset[3] = Property3->CurrentValue; State->Interact_Active = interact_type_slider_scrub; } if (ImGui::IsItemActive()) { State->UpdateFrame = true; Property->CurrentValue = Color[0]; Property1->CurrentValue = Color[1]; Property2->CurrentValue = Color[2]; Property3->CurrentValue = Color[3]; } if (ImGui::IsItemDeactivatedAfterEdit()) { if (ImGui::IsKeyPressed(ImGuiKey_Escape)) { Property->CurrentValue = State->Interact_Offset[0]; } else if (!Property->Keyframe_Count) { // History_Entry_Commit(Memory, "Property interact"); // real32 Temp = Property->CurrentValue; // Property->CurrentValue = State->Interact_Offset[0]; // History_Action_Swap(Memory, Table, sizeof(Property->CurrentValue), &Property->CurrentValue); // Property->CurrentValue = Temp; // History_Entry_End(Memory); } State->Interact_Active = interact_type_none; 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); } } #endif 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, uint16 *SortedPropertyArray) { 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->Interact_Modifier = 0; 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 == 1) { 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 }; uint16 *ArrayLocation = Property_GetSortedArray(SortedPropertyArray, State->MostRecentlySelectedLayer, h); Bezier_Add(Memory, F_Layers, Property[a], Point, ArrayLocation); 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->Interact_Modifier = 0; 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 == 1) { 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, uint16 *SortedPropertyArray) { bool open = true; ImGui::Begin("Viewport", &open, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse); if (ImGui::IsWindowHovered(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)); real32 FontSize = ImGui::GetFontSize(); ImGui::SetCursorScreenPos(ImVec2(ViewportMax.x - FontSize*2, ViewportMin.y + ViewportScale.y - FontSize*3.0)); ImGui::PushStyleColor(ImGuiCol_Button, IM_COL32(0, 0, 0, 80)); if (ImGui::Button("?")) State->ImGuiPopups = popup_keybinds; ImGui::PopStyleColor(); // 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, SortedPropertyArray); } } // 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 = (File->Layer_Count == 1) ? 11 : 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); v2 PrincipalCompUV = ImGui_ScreenPointToCompUV(ViewportMin, UI->CompPos, UI->CompZoom, io.MousePos); v2 LayerPos = Layer_TraverseForPoint(File, State, Memory, PrincipalCompUV, SortedCompArray, SortedLayerArray); Brush_Render(State, UI, T_Layer, MainComp, Source, State->Brush.TransientBitmap, ViewportMin, LayerPos); } } else if (IsActivated) { ImVec2 MousePos = io.MousePos; v2 PrincipalCompUV = ImGui_ScreenPointToCompUV(ViewportMin, UI->CompPos, UI->CompZoom, io.MousePos); v2 LayerPos = Layer_TraverseForPoint(File, State, Memory, PrincipalCompUV, SortedCompArray, SortedLayerArray); Brush_Render(State, UI, T_Layer, MainComp, Source, State->Brush.TransientBitmap, ViewportMin, LayerPos); } 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->Interact_Modifier = 0; State->UpdateFrame = true; } } if (ImGui::IsKeyDown(ImGuiKey_Z) && ImGui::IsWindowHovered()) { if (IsActive) ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeAll); else ImGui::SetMouseCursor(ImGuiMouseCursor_Hand); } 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 - FontSize*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; State->Interact_Modifier = 0; } } static void ImGui_GraphInfo(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) { bool open = true; ImGui::Begin("Graph info"); int h = 0, c = 0, i = 0; while (Block_Loop(Memory, F_Layers, File->Layer_Count, &h, &c, &i)) { block_layer *Layer = (block_layer *)Memory_Block_AddressAtIndex(Memory, F_Layers, i); if (!Layer->IsSelected) continue; block_string *String = (block_string *)Memory_Block_AddressAtIndex(Memory, F_Strings, Layer->Block_String_Index); ImGui::Text(String->Char); ImGui::PushID(i); for (int h = 0; h < AmountOf(Layer->Property); h++) { property_channel *Property = &Layer->Property[h]; if (Property->Block_Bezier_Count) { sorted_property_info *InfoLocation = Property_GetSortedInfo(SortedPropertyInfo, i, h); uint16 *ArrayLocation = Property_GetSortedArray(SortedPropertyArray, i, h); ImGui::PushID(Property); if (ImGui::Selectable(DefaultChannel[h], InfoLocation->IsGraphSelected)) { Property_DeselectAll(File, State, Memory, SortedCompArray, SortedLayerArray, SortedPropertyInfo, SortedPropertyArray); for (int p = 0; p < Property->Keyframe_Count; p++) { int k = ArrayLocation[p]; bezier_point *Point = Bezier_LookupAddress(Memory, Property, k); Point->IsSelected = true; } State->RecentSelectionType = selection_type_keyframe; } ImGui::PopID(); } } ImGui::PopID(); } ImGui::End(); } static void ImGui_Timeline_DrawKeySheet(project_data *File, project_state *State, memory *Memory, ui *UI, ImGuiIO io, ImDrawList *draw_list, property_channel *Property, uint16 *ArrayLocation, ImVec2 Increment, ImVec2 TimelineAbsolutePos, ImVec2 GraphPos, ImVec2 TimelineMoveSize, ImVec2 TimelineZoomSize, ImVec2 TimelineSize, ImVec2 TimelineSizeWithBorder, real32 LayerIncrement, sorted_comp_info *SortedCompArray, sorted_layer *SortedLayerArray, sorted_property_info *SortedPropertyInfo, uint16 *SortedPropertyArray) { 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 = GraphPos.x + TimelineMoveSize.x + LocalPos_Ratio_X * TimelineZoomSize.x; ImVec2 Keyframe_ScreenPos(Keyframe_ScreenPos_X, GraphPos.y); if (State->BoxSelect) { if (ImGui_TestBoxSelection_Point(Keyframe_ScreenPos, io, &PointAddress->IsSelected)) State->RecentSelectionType = selection_type_keyframe; } ImVec2 ButtonSize(16, 16); ImGui::PushID(p); ImU32 PointCol = (PointAddress->IsSelected) ? ImColor(0.8f, 0.5f, 0.0f, 1.0f) : ImColor(0.1f, 0.1f, 0.1f, 1.0f); ImU32 LineCol = (PointAddress->IsSelected) ? ImColor(0.8f, 0.5f, 0.5f, 1.0f) : ImColor(0.4f, 0.4f, 0.4f, 1.0f); ImGui::SetCursorScreenPos(Keyframe_ScreenPos - (ButtonSize * 0.5)); ImGui::InvisibleButton("##keyframemover", ButtonSize, ImGuiMouseButton_Left); bool32 IsHovered = ImGui::IsItemHovered(); bool32 IsItemActive = ImGui::IsItemActive(); bool32 IsItemActivated = ImGui::IsItemActivated(); bool32 IsItemDeactivated = ImGui::IsItemDeactivated(); bool32 LeftClick = ImGui::IsMouseDown(ImGuiMouseButton_Left); bool32 RightClick = ImGui::IsMouseDown(ImGuiMouseButton_Right); if (IsHovered) { PointCol = ImColor(1.0f, 0.8f, 0.8f, 1.0f); ImGui::SetMouseCursor(ImGuiMouseCursor_Hand); } if (IsItemActivated) { PointAddress->IsSelected = 1; } if (IsItemActive) { if (State->Interact_Active == interact_type_none) { State->Interact_Offset[2] = io.MousePos.x; State->Interact_Offset[3] = io.MousePos.y; State->Interact_Active = interact_type_keyframe_move; State->Interact_Modifier = 1; // X axis movement only } else { Assert(State->Interact_Active == interact_type_keyframe_move); ImGui_WarpMouse(State, io.MousePos, TimelineAbsolutePos, TimelineAbsolutePos + TimelineSizeWithBorder, 1); ImVec2 DragDelta = io.MousePos - ImVec2(State->Interact_Offset[2], State->Interact_Offset[3]); DragDelta = DragDelta + (ImVec2(State->Warp_X, State->Warp_Y) * TimelineSize); if (io.MouseDelta.x || io.MouseDelta.y) { State->UpdateFrame = true; } if (State->Interact_Active == interact_type_keyframe_move) { State->Interact_Offset[0] = (DragDelta.x / TimelineZoomSize.x) / Increment.x; State->Interact_Offset[1] = DragDelta.y; } } } if (IsItemDeactivated) { Bezier_Commit(File, State, Memory, SortedCompArray, SortedLayerArray, SortedPropertyInfo, SortedPropertyArray); } draw_list->AddCircleFilled(Keyframe_ScreenPos, 4, PointCol); ImGui::PopID(); } ImGui::PopID(); } static void ImGui_Timeline_DrawGraph(project_data *File, project_state *State, memory *Memory, ui *UI, ImGuiIO io, ImDrawList *draw_list, ImVec2 Increment, ImVec2 TimelineAbsolutePos, ImVec2 TimelineMoveSize, ImVec2 TimelineZoomSize, ImVec2 TimelineSize, ImVec2 TimelineSizeWithBorder, real32 LayerIncrement, sorted_comp_info *SortedCompArray, sorted_layer *SortedLayerArray, 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); for (int c = 0; c < File->Comp_Count; c++) { sorted_comp_info SortedCompInfo = SortedCompArray[c]; sorted_layer *SortedLayerInfo = Layer_GetSortedArray(SortedLayerArray, &SortedCompInfo, c); for (int i = 0; i < SortedCompInfo.LayerCount; i++) { sorted_layer *SortedLayer = &SortedLayerInfo[i]; block_layer *Layer = (block_layer *)Memory_Block_AddressAtIndex(Memory, F_Layers, SortedLayer->Block_Layer_Index); if (!Layer->IsSelected) continue; int32 Frame_Start = Layer->Frame_Start; ImGui::PushID(i); if ((State->Interact_Active == interact_type_keyframe_move || State->Interact_Active == interact_type_keyframe_rotate || State->Interact_Active == interact_type_keyframe_scale)) { ImGui_WarpMouse(State, io.MousePos, TimelineAbsolutePos, TimelineAbsolutePos + TimelineSizeWithBorder); ImVec2 DragDelta = io.MousePos - ImVec2(State->Interact_Offset[2], State->Interact_Offset[3]); DragDelta = DragDelta + (ImVec2(State->Warp_X, State->Warp_Y) * TimelineSize); if (io.MouseDelta.x || io.MouseDelta.y) { State->UpdateFrame = true; } if (State->Interact_Active == interact_type_keyframe_move) { // The Y increment varies between graphs, so we have to do it in the Bezier_EvaluateValue call. State->Interact_Offset[0] = (DragDelta.x / TimelineZoomSize.x) / Increment.x; State->Interact_Offset[1] = DragDelta.y; } else if (State->Interact_Active == interact_type_keyframe_scale) { State->Interact_Offset[0] = (DragDelta.x / TimelineSizeWithBorder.x * UI->TimelinePercentZoomed.x) / Increment.x; } else if (State->Interact_Active == interact_type_keyframe_rotate) { State->Interact_Offset[0] = (DragDelta.x / TimelineZoomSize.x); /* real32 Slope_Old = (Keyframe_ScreenPos.y - State->Interact_Offset[3]) / (Keyframe_ScreenPos.x - State->Interact_Offset[2]); real32 Slope_New = (Keyframe_ScreenPos.y - io.MousePos.y) / (Keyframe_ScreenPos.x - io.MousePos.x); State->Interact_Offset[0] = atan((Slope_Old - Slope_New) / (1 + Slope_Old * Slope_New)); */ } ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeAll); } sorted_property_info *InfoLocation = SortedPropertyInfo + SortedLayer->SortedPropertyStart; uint16 *ArrayLocation = SortedPropertyArray + SortedLayer->SortedKeyframeStart; int h1 = 0, c1 = 0, p = 0; property_channel *Property = NULL; while (Layer_LoopChannels(State, Memory, &InfoLocation, &ArrayLocation, Layer, &Property, NULL, &h1, &c1, &p)) { ImGui::PushID(Property); if (Property->Block_Bezier_Count) { real32 MinY, MaxY; Property_MinMax_Y(Memory, State, Property, InfoLocation, &MinY, &MaxY, 0); Assert(InfoLocation->MinYIndex < Property->Keyframe_Count); Assert(InfoLocation->MaxYIndex < Property->Keyframe_Count); Assert(MaxY >= MinY); real32 Y_Increment = (MaxY - MinY) ? (1 / (MaxY - MinY)) : 0.5; real32 GraphScale = 0; real32 GraphPos = 0; if ((1 / Y_Increment) < 5) { GraphScale = 0.2; GraphPos = 0.3; } else if ((1 / Y_Increment) > 700) { GraphScale = 0.6; GraphPos = 0.06; } else { GraphScale = 0.4; GraphPos = 0.2; } real32 GraphMoveHeight = TimelineMoveSize.y + (TimelineZoomSize.y * GraphPos); real32 GraphZoomHeight = TimelineZoomSize.y * GraphScale; 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]; if (Property->Keyframe_Count == 1) int b = 0; int Idx = (p % 2); int NewIdx = Idx * 3; int OldIdx = (NewIdx == 3) ? 0 : 3; PointAddress[Idx] = Bezier_LookupAddress(Memory, Property, k); v2 PointPos[3]; Bezier_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) { if (ImGui_TestBoxSelection_Point(Keyframe_ScreenPos[NewIdx], io, &PointAddress[Idx]->IsSelected)) State->RecentSelectionType = selection_type_keyframe; } State->Test.SetCurrentChannel(draw_list, 1); ImVec2 ButtonSize(16, 16); ImGui::PushID(k); int Max = PointAddress[Idx]->IsSelected ? 2 : 0; for (int b = Max; b >= 0; b--) { ImU32 PointCol = ((PointAddress[Idx]->IsSelected - 1) == b) ? ImColor(0.8f, 0.5f, 0.0f, 1.0f) : ImColor(0.1f, 0.1f, 0.1f, 1.0f); ImU32 LineCol = ((PointAddress[Idx]->IsSelected - 1) == b) ? ImColor(0.8f, 0.5f, 0.5f, 1.0f) : ImColor(0.4f, 0.4f, 0.4f, 1.0f); ImGui::PushID(b); ImGui::SetCursorScreenPos(Keyframe_ScreenPos[NewIdx + b] - (ButtonSize * 0.5)); ImGui::InvisibleButton("##keyframemover", ButtonSize, ImGuiMouseButton_Left); bool32 IsHovered = ImGui::IsItemHovered(); bool32 IsItemActive = ImGui::IsItemActive(); bool32 IsItemActivated = ImGui::IsItemActivated(); bool32 IsItemDeactivated = ImGui::IsItemDeactivated(); bool32 LeftClick = ImGui::IsMouseDown(ImGuiMouseButton_Left); bool32 RightClick = ImGui::IsMouseDown(ImGuiMouseButton_Right); if (IsHovered) { PointCol = ImColor(1.0f, 0.8f, 0.8f, 1.0f); ImGui::SetMouseCursor(ImGuiMouseCursor_Hand); } if (IsItemActivated) { PointAddress[Idx]->IsSelected = b+1; } if (b != 0 && PointAddress[Idx]->IsSelected) draw_list->AddLine(Keyframe_ScreenPos[NewIdx], Keyframe_ScreenPos[NewIdx + b], LineCol, 2.0f); if (b == 0) { draw_list->AddCircleFilled(Keyframe_ScreenPos[NewIdx + b], 4, PointCol); if (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, int16 RecursionIdx[MAX_PRECOMP_RECURSIONS], uint32 Recursions, 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) { uint16 CompIndex = 0; if (RecursionIdx[Recursions] == -1) { CompIndex = File->PrincipalCompIndex; } else { block_layer *Layer = (block_layer *)Memory_Block_AddressAtIndex(Memory, F_Layers, RecursionIdx[Recursions]); CompIndex = Layer->Block_Source_Index; } block_composition *Comp = (block_composition *)Memory_Block_AddressAtIndex(Memory, F_Precomps, CompIndex); sorted_comp_info SortedCompInfo = SortedCompArray[CompIndex]; sorted_layer *SortedLayerInfo = Layer_GetSortedArray(SortedLayerArray, SortedCompArray, CompIndex); ImGui::PushID(CompIndex); // NOTE(fox): Layer sorting pre-calculates UI positioning, so the order layers are drawn is arbitrary. real32 DisplayOffset = 0; for (int i = 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(State, Memory, Layer, SortedPropertyInfo, SortedPropertyArray, 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[128]; #if DEBUG sprintf(buf, "%s, %i", String->Char, Index_Physical); #else sprintf(buf, "%s", String->Char); #endif if (UI->TimelinePercentZoomed.y <= 1.0f) draw_list->AddText(Layer_ScreenPos_Min, 0xFFFFFFFF, buf); if (Layer->IsSelected == 1) { draw_list->AddRectFilled(Layer_ScreenPos_Min, Layer_ScreenPos_Max, ImColor(0.25f, 0.25f, 0.25f, 0.5f), 2); draw_list->AddRect(Layer_ScreenPos_Min, Layer_ScreenPos_Max, ImColor(1.0f, 1.0f, 1.0f, 0.5f), 2); } else if (Layer->IsSelected == 2) { // draw_list->AddRectFilled(Layer_ScreenPos_Min, Layer_ScreenPos_Max, ImColor(0.25f, 0.25f, 0.25f, 0.5f), 2); draw_list->AddRect(Layer_ScreenPos_Min, Layer_ScreenPos_Max, ImColor(0.0f, 0.9f, 0.0f, 0.9f), 4); } // // Main interaction ImGui::PushID(i); if (State->TimelineMode == timeline_mode_default) { if (State->BoxSelect && State->TimelineMode == timeline_mode_default) { bool32 Test = 0; if (io.MouseClickedPos[0].y < io.MousePos.y) Test = (Layer_ScreenPos_Min.y >= io.MouseClickedPos[0].y && Layer_ScreenPos_Min.y <= io.MousePos.y); else Test = (Layer_ScreenPos_Max.y <= io.MouseClickedPos[0].y && Layer_ScreenPos_Max.y >= io.MousePos.y); if (io.MouseClickedPos[0].x < io.MousePos.x) Test &= (Layer_ScreenPos_Max.x >= io.MouseClickedPos[0].x && Layer_ScreenPos_Min.x <= io.MousePos.x); else Test &= (Layer_ScreenPos_Min.x <= io.MouseClickedPos[0].x && Layer_ScreenPos_Max.x >= io.MousePos.x); if (Test) { if (!Layer->IsSelected) { Layer_Select(Memory, State, Index_Physical); } } else if (!io.KeyShift) { Layer->IsSelected = false; } } ImVec2 ResizeSize = ImVec2(Increment.x * 0.45 * TimelineZoomSize.x, Layer_ScreenSize.y); ImVec2 ResizePos[2] = { Layer_ScreenPos_Min, ImVec2(Layer_ScreenPos_Max.x - ResizeSize.x, Layer_ScreenPos_Min.y) }; for (int b = 0; b < 2; b++) { ImGui::PushID(b); ImGui::SetCursorScreenPos(ResizePos[b]); ImGui::Button("##layer_resize", ResizeSize); if (ImGui::IsItemHovered()) { ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeEW); } if (ImGui::IsItemActivated()) { if (!Layer->IsSelected) { if (!io.KeyShift) Layer_DeselectAll(File, State, Memory); Layer_Select(Memory, State, Index_Physical); } } if (ImGui::IsItemActive()) { if (ImGui::IsMouseDragging(ImGuiMouseButton_Left, -1)) { Assert(Layer->IsSelected); State->Interact_Active = interact_type_layer_timeadjust; ImVec2 DragDelta = ImGui::GetMouseDragDelta(); DragDelta = DragDelta + (ImVec2(State->Warp_X, State->Warp_Y) * TimelineSize); State->Interact_Offset[0] = (DragDelta.x / TimelineSizeWithBorder.x * UI->TimelinePercentZoomed.x) * Comp->Frame_Count; State->Interact_Offset[1] = b; } } if (ImGui::IsItemDeactivated()) { if (State->Interact_Active == interact_type_layer_timeadjust) { History_Entry_Commit(Memory, "Adjust layer timing"); int h = 0, c = 0, i = 0; while (Block_Loop(Memory, F_Layers, File->Layer_Count, &h, &c, &i)) { block_layer *Layer = (block_layer *)Memory_Block_AddressAtIndex(Memory, F_Layers, i); if (Layer->IsSelected) { // NOTE(fox): Some data on the tree could be saved here. History_Action_Swap(Memory, F_File, sizeof(Layer->Frame_Start), &Layer->Frame_Start); History_Action_Swap(Memory, F_File, sizeof(Layer->Frame_End), &Layer->Frame_End); 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_Modifier = 0; State->Interact_Offset[0] = 0; State->Interact_Offset[1] = 0; } } ImGui::PopID(); } ImGui::SetCursorScreenPos(Layer_ScreenPos_Min); ImGui::InvisibleButton("##layer_mid", Layer_ScreenSize, ImGuiMouseButton_Left); if (ImGui::IsItemActivated()) { if (!Layer->IsSelected) { if (!io.KeyShift) Layer_DeselectAll(File, State, Memory); Layer_Select(Memory, State, Index_Physical); Layer_Select_RecurseUp(Memory, State, Index_Physical, RecursionIdx, Recursions); } } ImGui::OpenPopupOnItemClick("layerpopup", ImGuiPopupFlags_MouseButtonRight); if (ImGui::BeginPopup("layerpopup")) { if (ImGui::Selectable("Visible")) { bool32 Commit = false; int h = 0, z = 0, i = 0; while (Block_Loop(Memory, F_Layers, File->Layer_Count, &h, &z, &i)) { block_layer *Layer = (block_layer *)Memory_Block_AddressAtIndex(Memory, F_Layers, i); if (Layer->IsSelected) { if (!Commit) { History_Entry_Commit(Memory, "Toggle visibility"); Commit = true; } History_Action_Swap(Memory, F_File, sizeof(Layer->IsVisible), &Layer->IsVisible); Layer->IsVisible ^= 1; } } if (Commit) { History_Entry_End(Memory); State->UpdateFrame = true; } } if (ImGui::MenuItem("Pre-compose layer")) { Precomp_UICreateButton(File, State, Memory, CompIndex, 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; State->Interact_Modifier = 0; History_Entry_End(Memory); } } // Keyframe view uint32 Channel = 0; sorted_property_info *InfoLocation = SortedPropertyInfo + SortedLayerInfo->SortedPropertyStart; uint16 *ArrayLocation = SortedPropertyArray + SortedLayerInfo->SortedKeyframeStart; int h = 0, c = 0, p = 0; property_channel *Property = NULL; block_effect *Effect = NULL; while (Layer_LoopChannels(State, Memory, &InfoLocation, &ArrayLocation, Layer, &Property, &Effect, &h, &c, &p)) { if (Property->IsToggled) { ImVec2 GraphMinPos = ImVec2(TimelineAbsolutePos.x, Layer_ScreenPos_Min.y + Layer_ScreenSize.y + (Layer_ScreenSize.y * Channel)); ImVec2 GraphPos = GraphMinPos + ImVec2(0, Layer_ScreenSize.y); ImVec2 GraphMinBounds = GraphMinPos + ImVec2(0, Layer_ScreenSize.y * 0.5); ImVec2 GraphMaxBounds = GraphMinBounds + ImVec2(TimelineSizeWithBorder.x, Layer_ScreenSize.y); uint32 col = (Channel % 2) ? IM_COL32(50, 50, 50, 255) : IM_COL32(50, 50, 50, 128); draw_list->AddRectFilled(GraphMinBounds, GraphMaxBounds, col); ImGui_Timeline_DrawKeySheet(File, State, Memory, UI, io, draw_list, Property, ArrayLocation, Increment, TimelineAbsolutePos, GraphPos, TimelineMoveSize, TimelineZoomSize, TimelineSize, TimelineSizeWithBorder, LayerIncrement, SortedCompArray, SortedLayerArray, SortedPropertyInfo, SortedPropertyArray); 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); uint32 RecursionsCount = Recursions+1; RecursionIdx[RecursionsCount] = Index_Physical; ImGui_Timeline_DrawPrecomp(File, State, Memory, UI, io, draw_list, RecursionIdx, RecursionsCount, Increment, NestedTimelineAbsolutePos, TimelineMoveSize, TimelineZoomSize, TimelineSize, TimelineSizeWithBorder, LayerIncrement, SortedCompArray, SortedLayerArray, 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) { ImVec2 FramePadding = ImGui::GetStyle().FramePadding; ImVec2 ItemSpacing = ImGui::GetStyle().ItemSpacing; ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0)); ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0)); // makes setting up the layout easier ImGui::Begin("Timeline", NULL); if (ImGui::IsWindowHovered(ImGuiFocusedFlags_ChildWindows)) { State->FocusedWindow = focus_timeline; } // if (State->TimelineMode == timeline_mode_graph) // ImGui_GraphInfo(File, State, Memory, UI, io, SortedPropertyInfo, SortedPropertyArray); real32 FontHeight = ImGui::GetFontSize(); ImVec2 WindowSize = ImGui::GetWindowSize(); if (WindowSize.x < 50 || WindowSize.y < 50) { ImGui::PopStyleVar(2); ImGui::End(); return; } block_composition *MainComp = (block_composition *)Memory_Block_AddressAtIndex(Memory, F_Precomps, File->PrincipalCompIndex); ImVec2 WindowMinAbs = ImGui::GetWindowPos(); ImVec2 WindowMaxAbs = WindowMinAbs + WindowSize; ImVec2 ButtonSize = ImVec2(FontHeight*2, FontHeight*2); ImVec2 TimelineBorderPadding = ImVec2(FontHeight, FontHeight); ImVec2 TimelineSize = ImVec2(WindowSize.x, WindowSize.y); ImVec2 TimelineSizeWithBorder = TimelineSize - TimelineBorderPadding*2; ImVec2 TimelineAbsolutePos = WindowMinAbs + TimelineBorderPadding; ImVec2 KeyframeSize = ImVec2(FontHeight, FontHeight); int32 FrameCount = MainComp->Frame_Count; ImDrawList* draw_list = ImGui::GetWindowDrawList(); draw_list->AddRectFilled(WindowMinAbs, WindowMaxAbs, IM_COL32(255, 255, 255, 50)); ImGui::BeginChild("Timeline", TimelineSize, true, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoScrollbar); ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0.0f, ItemSpacing.y)); ImGui::PushClipRect(TimelineAbsolutePos, TimelineAbsolutePos + TimelineSizeWithBorder, true); draw_list->PushClipRect(TimelineAbsolutePos, TimelineAbsolutePos + TimelineSizeWithBorder, true); real32 LayerIncrement = 40; ImVec2 Val_Min(0, 0); ImVec2 Val_Max(40, LayerIncrement); // DebugWatchVar("Selection", &State->RecentSelectionType, d_int); // ImVec2 *ActivePercentZoomed = (UI->TimelineMode != timeline_mode_graph) ? &UI->TimelinePercentZoomed : &UI->GraphPercentZoomed; // ImVec2 *ActivePercentOffset = (UI->TimelineMode != timeline_mode_graph) ? &UI->TimelinePercentOffset : &UI->GraphPercentOffset; ImVec2 *ActivePercentZoomed = &UI->TimelinePercentZoomed; ImVec2 *ActivePercentOffset = &UI->TimelinePercentOffset; if (ActivePercentZoomed->x == 0) { *ActivePercentZoomed = ImVec2(1, 1); } ImVec2 ActiveZoomSize = TimelineSizeWithBorder / *ActivePercentZoomed; ImVec2 ActiveMoveSize = TimelineSizeWithBorder * *ActivePercentOffset / *ActivePercentZoomed; ImVec2 TimelineZoomSize = TimelineSizeWithBorder / UI->TimelinePercentZoomed; ImVec2 TimelineMoveSize = TimelineSizeWithBorder * UI->TimelinePercentOffset / UI->TimelinePercentZoomed; // DebugWatchVar("TimelineY: ", &TimelineMoveSize.y, d_float); ImVec2 Increment = ImVec2((real32)1 / MainComp->Frame_Count, (real32)1 / LayerIncrement); ImGui_Timeline_BGElements(State, UI, draw_list, TimelineSizeWithBorder, TimelineAbsolutePos, *MainComp, TimelineZoomSize, TimelineMoveSize); int16 RecursionIdx[MAX_PRECOMP_RECURSIONS] = {}; int32 Recursions = 0; RecursionIdx[0] = -1; ImGui_Timeline_DrawPrecomp(File, State, Memory, UI, io, draw_list, RecursionIdx, Recursions, Increment, TimelineAbsolutePos, TimelineMoveSize, TimelineZoomSize, TimelineSize, TimelineSizeWithBorder, LayerIncrement, SortedCompArray, SortedLayerArray, 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, SortedCompArray, SortedLayerArray, 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) { Bezier_Commit(File, State, Memory, SortedCompArray, SortedLayerArray, SortedPropertyInfo, SortedPropertyArray); } 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(); } #include "keybinds.h" static void ImGui_Key_GetUIInfo(key_entry KeyEntry, real32 KeySize, ImVec2 *Offset_ScreenPos, ImVec2 *KeyScreenSize) { ImVec2 Extra(0, 0); if (KeyEntry.Sector == 0) { if (KeyEntry.Offset.x != 0) { if (KeyEntry.Offset.y == 1) { Extra.x += 0.5; } if (KeyEntry.Offset.y == 2) { Extra.x += 0.75; } if (KeyEntry.Offset.y == 3) { Extra.x += 1.5; } } } *Offset_ScreenPos = ImVec2(KeySize, KeySize) * (SectorOffset[KeyEntry.Sector] + Extra + KeyEntry.Offset); *KeyScreenSize = (KeyEntry.WidthRatio > 0.0f) ? ImVec2(KeySize * KeyEntry.WidthRatio, KeySize) : ImVec2(KeySize, KeySize * -KeyEntry.WidthRatio); } static void ImGui_KeybindUI(project_data *File, project_state *State, ui *UI, memory *Memory, ImGuiIO io) { real32 KeySize = ImGui::GetFontSize()*2; real32 KeyboardWidth = KeySize * 23.5; real32 KeyboardHeight = KeySize * 7; ImVec2 WindowSize = ImGui::GetWindowSize(); ImVec2 WindowMinAbs = ImGui::GetWindowPos(); ImVec2 WindowMaxAbs = WindowMinAbs + WindowSize; ImVec2 KeyboardPos((WindowSize.x - KeyboardWidth) / 2, KeySize*2); ImDrawList* draw_list = ImGui::GetWindowDrawList(); ImVec2 SectorOffset[4] = { ImVec2(0, 1.25), ImVec2(0,0), ImVec2(15.25, 1.25), ImVec2(19.5, 1.25) }; ImGui::PushStyleColor(ImGuiCol_Button, IM_COL32(190, 0, 50, 180)); if (ImGui::Button("X")) ImGui::CloseCurrentPopup(); ImGui::PopStyleColor(); State->Split_KeybindUI.Split(draw_list, 2); State->Split_KeybindUI.SetCurrentChannel(draw_list, 1); // keyboard ImGui::PushStyleColor(ImGuiCol_Button, IM_COL32(0, 0, 0, 180)); for (int k = 0; k < AmountOf(KeyEntries); k++) { key_entry KeyEntry = KeyEntries[k]; ImVec2 Offset_ScreenPos(0,0); ImVec2 KeyScreenSize(0,0); ImGui_Key_GetUIInfo(KeyEntry, KeySize, &Offset_ScreenPos, &KeyScreenSize); if (KeyEntry.Name[0] != '\0') { ImGui::PushID(k); ImGui::SetCursorScreenPos(WindowMinAbs + KeyboardPos + Offset_ScreenPos); ImGui::Button(KeyEntry.Name, KeyScreenSize); ImGui::PopID(); } } ImGui::PopStyleColor(); State->Split_KeybindUI.SetCurrentChannel(draw_list, 0); // list ImVec2 SubwindowMinPos(WindowMinAbs + KeyboardPos + ImVec2(0, KeyboardHeight + KeySize)); ImVec2 SubwindowSize(KeyboardWidth, WindowSize.y - KeyboardHeight - KeySize*4); ImGui::SetCursorScreenPos(SubwindowMinPos); ImGui::BeginChild("Keybinds info", SubwindowSize); key_mode CurrentKeyMode = (key_mode)9999; for (int a = 0; a < AmountOf(ShortcutArray); a++) { shortcut_entry ShortcutEntry = ShortcutArray[a]; // header info if (CurrentKeyMode != ShortcutEntry.Mode) { ImGui::Dummy(ImVec2(1, KeySize)); CurrentKeyMode = ShortcutEntry.Mode; ImVec2 Size = ImGui::CalcTextSize(KeyModeTitles[CurrentKeyMode]); ImGui::SetCursorPosX(((SubwindowSize.x / 2) - (Size.x / 2))); ImGui::Text(KeyModeTitles[CurrentKeyMode]); ImGui::SetCursorPosX((SubwindowSize.x / 2) - (ImGui::CalcTextSize("-----").x / 2)); ImGui::TextColored(ImColor(UI->LayerColors[CurrentKeyMode]), "-----"); ImGui::Dummy(ImVec2(1, KeySize/4)); while (ShortcutEntry.Key == ImGuiKey_None) { ImVec2 Size = ImGui::CalcTextSize(ShortcutEntry.Name); ImGui::SetCursorPosX(((SubwindowSize.x / 2) - (Size.x / 2))); ImGui::Text(ShortcutEntry.Name); a++; ShortcutEntry = ShortcutArray[a]; } ImGui::Dummy(ImVec2(1, KeySize/2)); } // shortcut text + key Assert(ShortcutEntry.Key != ImGuiKey_None); key_entry KeyEntry = KeyEntries[ShortcutEntry.Key - ImGuiKey_Tab]; real32 Padding = KeySize; ImGui::Dummy(ImVec2(Padding, 1)); ImGui::SameLine(); ImGui::Text(ShortcutEntry.Name); ImGui::SameLine(); char buf[64]; if (ShortcutEntry.Mods == Mod_None) { sprintf(buf, "%s", KeyEntry.Name); } else if (ShortcutEntry.Mods == Mod_Shift) { sprintf(buf, "%s", KeyEntry.ShiftName); } else { sprintf(buf, "%s + %s", KeyModText[ShortcutEntry.Mods], KeyEntry.Name); } ImVec2 Size = ImGui::CalcTextSize(buf); ImGui::SetCursorPosX(SubwindowSize.x - Size.x - Padding*1.5); ImGui::Text(buf); // indicator on keyboard ImVec2 Offset_ScreenPos(0,0); ImVec2 KeyScreenSize(0,0); ImGui_Key_GetUIInfo(KeyEntry, KeySize, &Offset_ScreenPos, &KeyScreenSize); real32 ModeSliverSize = KeySize / key_mode_count; Offset_ScreenPos.x += (ShortcutEntry.Mode * ModeSliverSize); KeyScreenSize = ImVec2(ModeSliverSize, KeyScreenSize.y); ImVec2 MinPos = WindowMinAbs + KeyboardPos + Offset_ScreenPos; draw_list->AddRectFilled(MinPos, MinPos + KeyScreenSize, UI->LayerColors[CurrentKeyMode]); } ImGui::EndChild(); if (ImGui::IsKeyPressed(ImGuiKey_Escape)) { ImGui::CloseCurrentPopup(); } ImGui::EndPopup(); State->Split_KeybindUI.Merge(draw_list); } 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"); ImGui::SetKeyboardFocusHere(); } break; case popup_keybinds: { ImGui::OpenPopup("Keybinds"); 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")) { ImGui_KeybindUI(File, State, UI, Memory, io); } } 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_U)) { State->HotkeyInput = hotkey_togglechannels; } if (ImGui::IsKeyPressed(ImGuiKey_X)) { if (State->TimelineMode == timeline_mode_graph && 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_Y)) { if (State->TimelineMode == timeline_mode_graph && State->Interact_Active == interact_type_keyframe_move) { if (State->Interact_Modifier != 2) State->Interact_Modifier = 2; else State->Interact_Modifier = 0; } } 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)) { if (io.KeyShift) { 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; } else { 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) { // NOTE(fox): Checking IsWindowHovered seems to be all we need to do to // make per-window hotkeys work; setting it as the focused window causes // problems with popups. 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->Interact_Modifier = 0; 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; State->MostRecentlySelectedLayer = 0; } 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) { Effect_Add(File, State, 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) { Effect_Add(File, State, Memory, i); State->UpdateFrame = true; } } p++; } } ImGui::End(); } static char ImGuiPrefs[] = "[Window][DockSpaceViewport_11111111]\n" "Pos=0,0\n" "Size=3840,2160\n" "Collapsed=0\n" "\n" "[Window][Debug##Default]\n" "Pos=122,442\n" "Size=400,400\n" "Collapsed=0\n" "\n" "[Window][Viewport]\n" "Pos=443,34\n" "Size=2872,1565\n" "Collapsed=0\n" "DockId=0x00000010,0\n" "\n" "[Window][###Properties]\n" "Pos=0,34\n" "Size=441,1565\n" "Collapsed=0\n" "DockId=0x0000000B,0\n" "\n" "[Window][Timeline]\n" "Pos=0,1601\n" "Size=3840,559\n" "Collapsed=0\n" "DockId=0x0000000A,0\n" "\n" "[Window][Dear ImGui Demo]\n" "Pos=2677,34\n" "Size=523,437\n" "Collapsed=0\n" "DockId=0x00000011,1\n" "\n" "[Window][Files]\n" "Pos=3317,604\n" "Size=523,743\n" "Collapsed=0\n" "DockId=0x00000007,0\n" "\n" "[Window][Effects list]\n" "Pos=3317,1349\n" "Size=523,250\n" "Collapsed=0\n" "DockId=0x00000008,0\n" "\n" "[Window][Graph editor]\n" "Pos=0,949\n" "Size=3200,526\n" "Collapsed=0\n" "DockId=0x00000009,0\n" "\n" "[Window][undotree]\n" "Pos=2677,473\n" "Size=523,572\n" "Collapsed=0\n" "DockId=0x00000007,2\n" "\n" "[Window][memoryviewer]\n" "Pos=50,273\n" "Size=800,200\n" "Collapsed=0\n" "\n" "[Window][Example: Custom rendering]\n" "Pos=758,789\n" "Size=485,414\n" "Collapsed=0\n" "\n" "[Window][Memory viewer]\n" "Pos=2677,473\n" "Size=523,572\n" "Collapsed=0\n" "DockId=0x00000007,1\n" "\n" "[Window][Graph info]\n" "Pos=2838,1265\n" "Size=235,353\n" "Collapsed=0\n" "\n" "[Window][Properties]\n" "Pos=0,34\n" "Size=495,1056\n" "Collapsed=0\n" "DockId=0x0000000F,0\n" "\n" "[Window][Colors]\n" "Pos=3317,34\n" "Size=523,568\n" "Collapsed=0\n" "DockId=0x00000011,0\n" "\n" "[Window][Menu]\n" "Pos=0,0\n" "Size=3840,32\n" "Collapsed=0\n" "DockId=0x0000000D,0\n" "\n" "[Window][Stable Diffusion]\n" "Pos=2206,684\n" "Size=421,462\n" "Collapsed=0\n" "\n" "[Window][SD prompt input]\n" "Pos=2677,473\n" "Size=523,541\n" "Collapsed=0\n" "DockId=0x00000007,2\n" "\n" "[Window][Example: Console]\n" "Pos=747,851\n" "Size=520,600\n" "Collapsed=0\n" "\n" "[Window][SD gallery]\n" "Pos=0,718\n" "Size=441,557\n" "Collapsed=0\n" "DockId=0x0000000C,0\n" "\n" "[Window][Save as]\n" "Pos=1782,1058\n" "Size=300,116\n" "Collapsed=0\n" "\n" "[Window][Keybinds]\n" "Pos=1573,769\n" "Size=750,639\n" "Collapsed=0\n" "\n" "[Table][0x861D378E,3]\n" "Column 0 Weight=1.0000\n" "Column 1 Weight=1.0000\n" "Column 2 Weight=1.0000\n" "\n" "[Table][0x1F146634,3]\n" "RefScale=13\n" "Column 0 Width=63\n" "Column 1 Width=63\n" "Column 2 Width=63\n" "\n" "[Table][0x64418101,3]\n" "RefScale=13\n" "Column 0 Width=63\n" "Column 1 Width=63\n" "Column 2 Width=63\n" "\n" "[Table][0xC9935533,3]\n" "Column 0 Weight=1.0000\n" "Column 1 Weight=1.0000\n" "Column 2 Weight=1.0000\n" "\n" "[Docking][Data]\n" "DockSpace ID=0x8B93E3BD Window=0xA787BDB4 Pos=0,0 Size=3840,2160 Split=Y Selected=0x13926F0B\n" " DockNode ID=0x0000000D Parent=0x8B93E3BD SizeRef=3200,32 HiddenTabBar=1 Selected=0xA57AB2C6\n" " DockNode ID=0x0000000E Parent=0x8B93E3BD SizeRef=3200,1299 Split=Y\n" " DockNode ID=0x00000001 Parent=0x0000000E SizeRef=3200,1205 Split=X Selected=0x13926F0B\n" " DockNode ID=0x00000003 Parent=0x00000001 SizeRef=441,1171 Split=Y Selected=0xDBB8CEFA\n" " DockNode ID=0x0000000B Parent=0x00000003 SizeRef=521,425 Selected=0xDBB8CEFA\n" " DockNode ID=0x0000000C Parent=0x00000003 SizeRef=521,347 Selected=0x56290987\n" " DockNode ID=0x00000004 Parent=0x00000001 SizeRef=1690,1171 Split=X Selected=0x13926F0B\n" " DockNode ID=0x00000005 Parent=0x00000004 SizeRef=1165,1171 Split=X Selected=0x13926F0B\n" " DockNode ID=0x0000000F Parent=0x00000005 SizeRef=495,856 Selected=0x199AB496\n" " DockNode ID=0x00000010 Parent=0x00000005 SizeRef=2199,856 CentralNode=1 Selected=0x13926F0B\n" " DockNode ID=0x00000006 Parent=0x00000004 SizeRef=523,1171 Split=Y Selected=0x86FA2F90\n" " DockNode ID=0x00000011 Parent=0x00000006 SizeRef=483,437 Selected=0xBF7DFDC9\n" " DockNode ID=0x00000012 Parent=0x00000006 SizeRef=483,766 Split=Y Selected=0x59A2A092\n" " DockNode ID=0x00000007 Parent=0x00000012 SizeRef=523,572 Selected=0x86FA2F90\n" " DockNode ID=0x00000008 Parent=0x00000012 SizeRef=523,192 Selected=0x812F222D\n" " DockNode ID=0x00000002 Parent=0x0000000E SizeRef=3200,559 Split=Y Selected=0x0F18B61B\n" " DockNode ID=0x00000009 Parent=0x00000002 SizeRef=3250,526 Selected=0xA1F22F4D\n" " DockNode ID=0x0000000A Parent=0x00000002 SizeRef=3250,323 HiddenTabBar=1 Selected=0x0F18B61B\n" "\0";