#include "imgui/imgui.h" #include "my_imgui_internal_widgets.h" #include "imgui_ops.h" // 0 for timeline keyframe, 1 for graph keyframe, 2 for left graph handle, 3 for right graph handle static void ImGui_KeyframeDragging(project_data *File, project_state *State, ui *UI, property_channel *Property, int32 b, ImGuiIO io, int16 Type) { keyframe *Keyframe = KeyframeLookupMemory(Property, b); if (ImGui::IsItemActive()) { if (!Keyframe->IsSelected && ImGui::IsItemActivated()) { if (!io.KeyShift) { temp_keyframe_list Bad = GetSelectedKeyframes(File); for (int i = 0; i < Bad.Amount; i++) Bad.SelectedKeyframe[i]->IsSelected = false; } Keyframe->IsSelected = true; State->RecentSelectionType = selection_keyframe; } if (ImGui::IsMouseDragging(ImGuiMouseButton_Left, -1)) { ImGui::SetMouseCursor(ImGuiMouseCursor_Hand); if (Type == 0 || Type == 1) { UI->DraggingKeyframeThreshold += io.MouseDelta.x; if (abs(UI->DraggingKeyframeThreshold) >= UI->TimelineZoom) { int16 Increment = UI->DraggingKeyframeThreshold/UI->TimelineZoom; // temp_keyframe_list Bad = GetSelectedKeyframes(File); // for (int b = 0; b < Bad.Amount; b++) { // keyframe *SelectedKeyframe = Bad.SelectedKeyframe[b]; if (!(Keyframe->FrameNumber == 0 && Increment == -1)) { Keyframe->FrameNumber += Increment; CheckKeyframeSort(Property, Increment, b); // SortAndCacheKeyframeAtFrame(SelectedKeyframe->FrameNumber, &File.LayerPTR[i]->Property[a], &Cache); ClampSurroundingKeyframeHandles(Property, b); } // } UI->DraggingKeyframeThreshold += -1*Increment*UI->TimelineZoom; State->UpdateFrame = true; State->UpdateKeyframes = true; // Cache.Frame[File.CurrentFrame].Cached = false; } } if (Type != 0) { if (Type == 1) { real32 IncrementsPerPixel = (Property->LocalMaxVal.f - Property->LocalMinVal.f)/Property->GraphLength; Keyframe->Value.f -= io.MouseDelta.y*IncrementsPerPixel; CalculatePropertyMinMax(Property); } if (Type == 2) { Keyframe->TangentLeft.x += io.MouseDelta.x/UI->TimelineZoom; Keyframe->TangentLeft.y -= io.MouseDelta.y; ClampKeyframeHandles(Property, b, 0); } if (Type == 3) { Keyframe->TangentRight.x += io.MouseDelta.x/UI->TimelineZoom; Keyframe->TangentRight.y -= io.MouseDelta.y; ClampKeyframeHandles(Property, b, 1); } State->UpdateFrame = true; State->UpdateKeyframes = true; } } } } static void ImGui_InteractSliderProperty(project_state *State, memory *Memory, property_channel *Property) { ImGui::DragScalar(Property->Name, ImGuiDataType_Float, &Property->CurrentValue.f, Property->ScrubVal.f, &Property->MinVal.f, &Property->MaxVal.f, "%f"); if (ImGui::IsItemActivated()) { Action_Entry_Begin(Memory, action_entry_default, "Tranforms interact"); State->InteractCache[0] = Property->CurrentValue.f; } if (ImGui::IsItemActive()) { State->UpdateFrame = true; } if (ImGui::IsItemDeactivated()) { Action_Change_Commit(Memory, &Property->CurrentValue.f, &State->InteractCache[0], &Property->CurrentValue.f, action_change_r32); Action_Entry_End(Memory); State->UpdateFrame = true; } } // Returns a normalized UV position of the composition static v2 ImGui_ScreenPointToCompUV(ImVec2 ViewportMin, ImVec2 CompPos, ImVec2 CompZoom, ImVec2 MousePos) { ImVec2 LocalMousePos = MousePos - ViewportMin; ImVec2 LocalCompPos = CompPos - ViewportMin; ImVec2 MouseScreenUV = LocalMousePos - LocalCompPos; ImVec2 Result = MouseScreenUV / CompZoom; return V2(Result); } static void ImGui_DebugUndoTree(project_data *File, memory *Memory) { ImGui::SetNextWindowSize(ImVec2(200, 800)); ImGui::Begin("undotree"); for (int i = 0; i < Memory->Action.NumberOfEntries; i++) { action_entry Entry = Memory->Action.Entry[i]; bool32 CurrentPos = (i < Memory->Action.Index); ImGui::MenuItem(Entry.Name, NULL, CurrentPos); } ImGui::End(); } static void ImGui_DebugMemoryViewer(project_data *File, memory *Memory) { ImGui::SetNextWindowSize(ImVec2(800, 200)); ImGui::Begin("memoryviewer"); ImVec2 ViewportMin = ImGui::GetCursorScreenPos(); ImVec2 ViewportScale = ImGui::GetContentRegionAvail(); ImVec2 ViewportMax = ImVec2(ViewportMin.x + ViewportScale.x, ViewportMin.y + ViewportScale.y); memory_table *Table = &Memory->Slot[B_LoadedBitmaps]; real32 TotalMB = Table->Size/1024/1024; real32 LineAmount = 50; real32 IncrementMB = TotalMB / LineAmount; real32 ScreenIncrement = ViewportScale.x / LineAmount; real32 CubeHeight = ImGui::GetFontSize(); real32 TotalHeight = CubeHeight*4; ImDrawList* draw_list = ImGui::GetWindowDrawList(); draw_list->AddRectFilled(ViewportMin, ViewportMax, IM_COL32(50, 50, 50, 255)); for (float x = 0; x < LineAmount; x++) { uint32 LineColor = IM_COL32(200, 200, 200, 40); ImVec2 Min = ImVec2(ViewportMin.x + ScreenIncrement * x, ViewportMin.y); ImVec2 Max = ImVec2(Min.x + 2, ViewportMax.y); draw_list->AddLine(Min, Max, LineColor); } // CurrentPosition line { uint32 LineColor = IM_COL32(000, 100, 200, 200); real32 CurPosMB = (real32)Table->CurrentPosition/1024/1024; ImVec2 Min = ImVec2(ViewportMin.x + (CurPosMB/IncrementMB * ScreenIncrement), ViewportMin.y); ImVec2 Max = ImVec2(Min.x + 2, ViewportMax.y); draw_list->AddLine(Min, Max, LineColor); } for (uint32 i = 0; i < Table->NumberOfPointers; i++) { if (Memory->Bitmap[i].SourceOwner) { void *DataStart = Memory->Bitmap[i].Data; void *NextDataStart; int pp = 1; for (;;) { if (Memory->Bitmap[i+pp].SourceOwner) { NextDataStart = Memory->Bitmap[i+pp].Data; break; } pp++; } uint64 BytesBetween = (uint8 *)NextDataStart - (uint8 *)DataStart; source *Source = Memory->Bitmap[i].SourceOwner; real32 Pos = Memory_NormalizedPosition(&File->Source[0], File->NumberOfSources, sizeof(source), Source); ImVec4 col = ImColor::HSV(Pos, 0.3, 0.6, 1.0f); uint64 BitmapSize = Bitmap_CalcTotalBytes(Source->Info.Width, Source->Info.Height, Source->Info.BytesPerPixel); if (BitmapSize > BytesBetween && i != Table->NumberOfPointers - 1) { col = ImColor::HSV(Pos, 1.0, 1.0, 1.0f); } uint64 DataStart2 = (uint8 *)DataStart - (uint8 *)Table->Address; real32 StartReal = ((real32)DataStart2/1024/1024); ImVec2 Min = ImVec2(ViewportMin.x + (StartReal/IncrementMB * ScreenIncrement), ViewportMin.y + (ViewportScale.y / 2) - (TotalHeight / 2) + TotalHeight*Pos); real32 SizeReal = ((real32)BitmapSize/1024/1024); ImVec2 Size = ImVec2(SizeReal/IncrementMB * ScreenIncrement, CubeHeight); ImGui::SetCursorScreenPos(Min); ImGui::PushID(i); ImGui::PushStyleColor(ImGuiCol_Button, col); char buf2[256]; sprintf(buf2, "%i##mempos", Memory->Bitmap[i].Frame); ImGui::Button(buf2, Size); ImGui::PopStyleColor(); if (ImGui::IsItemHovered()) { char buf[1024]; sprintf(buf, "Source owner: %s\nSize: %.02f MB\n Frame number: %i\n Index: %i", Memory->Bitmap[i].SourceOwner->Path, SizeReal, Memory->Bitmap[i].Frame, i); ImGui::BeginTooltip(); ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f); ImGui::TextUnformatted(buf); ImGui::PopTextWrapPos(); ImGui::EndTooltip(); } ImGui::PopID(); ImVec2 Max = ImVec2(ViewportMax.x, Min.y + CubeHeight); // draw_list->AddRectFilled(Min, Max, IM_COL32(200, 200, 200, 255)); } } ImGui::SetCursorScreenPos(ImVec2(ViewportMin.x, ViewportMax.y - CubeHeight*2)); char buf[1024]; sprintf(buf, "Current index: %i\nAmount of pointers: %i", Table->PointerIndex, Table->NumberOfPointers); ImGui::Text(buf); ImGui::End(); } static void ImGui_PropertiesPanel(project_data *File, project_state *State, ui *UI, memory *Memory) { if (State->MostRecentlySelectedLayer > -1) { project_layer *Layer = File->Layer[State->MostRecentlySelectedLayer]; char buf[256]; sprintf(buf, "Properties: %s###Properties", Layer->Name); ImGui::Begin(buf); if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) UI->FocusedWindow = focus_properties; ImGui::Text("Transform"); for (int h = 0; h < AmountOf(Layer->Property); h++) { property_channel *Property = &Layer->Property[h]; ImGui::PushID(Property); if (ImGui::Button("K")) ManualKeyframeInsertF(Property, Memory, File->CurrentFrame, Property->CurrentValue.f); ImGui::SameLine(); ImGui_InteractSliderProperty(State, Memory, Property); ImGui::PopID(); } for (int h = 0; h < Layer->NumberOfEffects; h++) { effect *Effect = Layer->Effect[h]; ImGui::PushID(Effect); // this is stupid if (Effect->IsActive) ImGui::PushStyleColor(ImGuiCol_Button, ImGui::GetColorU32(ImGuiCol_ButtonHovered)); else ImGui::PushStyleColor(ImGuiCol_Button, ImGui::GetColorU32(ImGuiCol_Button)); if (ImGui::Button("V")) { Effect->IsActive ^= 1; State->UpdateFrame = true; } ImGui::SameLine(); ImGui::PopStyleColor(); ImGui::Button("R"); ImGui::SameLine(); ImGui::Text(Effect->Name); if (Effect->DisplayType == standard) { for (int i = 0; i < Effect->NumberOfProperties; i++) { property_channel *Property = &Effect->Property[i]; ImGui::PushID(Property); if (Property->VarType == type_real) { ImGui_InteractSliderProperty(State, Memory, Property); } if (Property->VarType == type_color) if (ImGui::ColorEdit4("color 1", &Property->CurrentValue.f, ImGuiColorEditFlags_Float)) State->UpdateFrame = true; if (Property->VarType == type_blendmode) { uint32 *item_current_idx = (uint32 *)&Property->CurrentValue.blendmode; // Here we store our selection data as an index. if (ImGui::BeginListBox("Blend mode")) { for (int n = 0; n < IM_ARRAYSIZE(BlendmodeNames); n++) { const bool is_selected = (*item_current_idx == n); if (ImGui::Selectable(BlendmodeNames[n], is_selected)) { *item_current_idx = n; State->UpdateFrame = true; } // Set the initial focus when opening the combo (scrolling + keyboard navigation focus) if (is_selected) ImGui::SetItemDefaultFocus(); } ImGui::EndListBox(); } } ImGui::PopID(); } } else if (Effect->DisplayType == levels) { source *Source = Layer->Source; layer_bitmap_info *BitmapInfo = &Layer->BitmapInfo; if (!BitmapInfo->HistogramVals) { uint64 Size = Bitmap_CalcUnpackedBytes(Source->Info.Width, Source->Info.Height, Source->Info.BytesPerPixel); BitmapInfo->HistogramVals = AllocateMemory(Memory, (sizeof(uint32) * 5 * 256), P_MiscCache); Bitmap_CalcHistogram(BitmapInfo->HistogramVals, BitmapInfo->BitmapBuffer, Source->Info.BytesPerPixel, Size); } char *LevelsButtons[5] = { "All", "Red", "Green", "Blue", "Alpha" }; for (int i = 0; i < 5; i++) { if (ImGui::Button(LevelsButtons[i])) { BitmapInfo->LevelsSelector = i; } if (i != 4) { ImGui::SameLine(); } } real32 *Min, *Mid, *Max; if (BitmapInfo->LevelsSelector == 0) { Min = &Effect->Property[0].CurrentValue.f; Mid = &Effect->Property[1].CurrentValue.f; Max = &Effect->Property[2].CurrentValue.f; } else { Min = &Effect->Property[3].CurrentValue.col.E[BitmapInfo->LevelsSelector - 1]; Mid = &Effect->Property[4].CurrentValue.col.E[BitmapInfo->LevelsSelector - 1]; Max = &Effect->Property[5].CurrentValue.col.E[BitmapInfo->LevelsSelector - 1]; } if (BitmapInfo->LevelsSelector == 0) { ImGui::PushStyleColor(ImGuiCol_PlotHistogram, ImVec4(0.6f, 0.6f, 0.6f, 1.0f)); ImGui::PushStyleColor(ImGuiCol_PlotHistogramHovered, ImVec4(0.6f, 0.6f, 0.6f, 1.0f)); } else if (BitmapInfo->LevelsSelector == 1) { ImGui::PushStyleColor(ImGuiCol_PlotHistogram, ImVec4(0.9f, 0.6f, 0.6f, 1.0f)); ImGui::PushStyleColor(ImGuiCol_PlotHistogramHovered, ImVec4(0.9f, 0.6f, 0.6f, 1.0f)); } else if (BitmapInfo->LevelsSelector == 2) { ImGui::PushStyleColor(ImGuiCol_PlotHistogram, ImVec4(0.6f, 0.9f, 0.6f, 1.0f)); ImGui::PushStyleColor(ImGuiCol_PlotHistogramHovered, ImVec4(0.6f, 0.9f, 0.6f, 1.0f)); } else if (BitmapInfo->LevelsSelector == 3) { ImGui::PushStyleColor(ImGuiCol_PlotHistogram, ImVec4(0.6f, 0.6f, 0.9f, 1.0f)); ImGui::PushStyleColor(ImGuiCol_PlotHistogramHovered, ImVec4(0.6f, 0.6f, 0.9f, 1.0f)); } else if (BitmapInfo->LevelsSelector == 4) { ImGui::PushStyleColor(ImGuiCol_PlotHistogram, ImVec4(0.9f, 0.9f, 0.9f, 1.0f)); ImGui::PushStyleColor(ImGuiCol_PlotHistogramHovered, ImVec4(0.9f, 0.9f, 0.9f, 1.0f)); } float *values = (float *)BitmapInfo->HistogramVals + 256*BitmapInfo->LevelsSelector; int values_count = 256; int values_offset = 0; float scale_min = FLT_MIN; float scale_max = FLT_MAX; ImVec2 graph_size = ImVec2(0, 250); int stride = sizeof(float); // The default histogram is good enough for what we need. ImGui::PlotHistogram("##histo", values, values_count, values_offset, NULL, scale_min, scale_max, graph_size, stride); // TODO(fox): Figure out the proper way to represent these IDs. if (ImGui::SliderLevels("##one", "##two", "three", Mid, Min, Max)) State->UpdateFrame = true; ImGui::PopStyleColor(); ImGui::PopStyleColor(); if (State->UpdateFrame) { uint64 Size = Bitmap_CalcUnpackedBytes(Source->Info.Width, Source->Info.Height, Source->Info.BytesPerPixel); Bitmap_CalcHistogram(BitmapInfo->HistogramVals, BitmapInfo->BitmapBuffer, Source->Info.BytesPerPixel, Size); } ImGui::Button("K"); } ImGui::PopID(); } } else { char buf[256]; sprintf(buf, "Properties: empty###Properties"); ImGui::Begin(buf); if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) UI->FocusedWindow = focus_properties; } ImGui::End(); } static void ImGui_Viewport(project_data File, project_state *State, ui *UI, comp_buffer CompBuffer, ImGuiIO io, GLuint textureID) { bool open = true; ImGui::Begin("Viewport", &open, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse); if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) UI->FocusedWindow = focus_viewport; // Primarily taken from the Custom Rendering section of the demo ImVec2 ViewportMin = ImGui::GetCursorScreenPos(); ImVec2 ViewportScale = ImGui::GetContentRegionAvail(); ViewportScale.y -= ImGui::GetFontSize(); if (ViewportScale.x < 50.0f) ViewportScale.x = 50.0f; if (ViewportScale.y < 50.0f) ViewportScale.y = 50.0f; ImVec2 ViewportMax = ImVec2(ViewportMin.x + ViewportScale.x, ViewportMin.y + ViewportScale.y); if (UI->Initializing) { UI->CompZoom = ImVec2(CompBuffer.Width, CompBuffer.Height); UI->CompPos = ImVec2(ViewportMin.x + ((ViewportMax.x - ViewportMin.x)/2 - UI->CompZoom.x/2), ViewportMin.y + ((ViewportMax.y - ViewportMin.y)/2 - UI->CompZoom.y/2)); } ImDrawList* draw_list = ImGui::GetWindowDrawList(); draw_list->AddRectFilled(ViewportMin, ViewportMax, IM_COL32(50, 50, 50, 255)); draw_list->AddRect(ViewportMin, ViewportMax, IM_COL32(255, 255, 255, 255)); // Actual composition texture draw_list->PushClipRect(ViewportMin, ViewportMax, true); draw_list->AddImage((void *)(intptr_t)textureID, ImVec2(UI->CompPos.x, UI->CompPos.y), ImVec2(UI->CompPos.x + UI->CompZoom.x, UI->CompPos.y + UI->CompZoom.y)); draw_list->PopClipRect(); // UI+interaction for layer if (State->MostRecentlySelectedLayer > -1) { project_layer *Layer = File.Layer[State->MostRecentlySelectedLayer]; source *Source = Layer->Source; // Anchor point UI ImVec2 AUV = ImVec2(Layer->x.CurrentValue.f / CompBuffer.Width, Layer->y.CurrentValue.f / CompBuffer.Height); ImVec2 ScreenAP = ImVec2(UI->CompPos.x + AUV.x * UI->CompZoom.x, UI->CompPos.y + AUV.y * UI->CompZoom.y); draw_list->AddNgon(ScreenAP, 20, ImGui::GetColorU32(ImGuiCol_ScrollbarGrab), 8, 10.0f); // Mask UI if (Layer->NumberOfMasks) { for (int i = 0; i < Layer->NumberOfMasks; i++) { mask *Mask = &Layer->Mask[i]; ImGui::PushID(i); real32 PointSize = 40; for (int p = 0; p < Mask->NumberOfPoints; p++) { mask_point *Point0 = &Mask->Point[p]; mask_point *Point1 = &Mask->Point[p+1]; if (p+1 == Mask->NumberOfPoints) Point1 = &Mask->Point[0]; // NOTE(fox): I want to keep operations in local space under the v2 data // type and operations in screen space under ImVec2. v2 Point0_Pos = Point0->Pos; v2 Point0_Pos_Left = Point0_Pos + Point0->TangentLeft; v2 Point0_Pos_Right = Point0_Pos + Point0->TangentRight; v2 Point1_Pos = Point1->Pos; v2 Point1_Pos_Left = Point1_Pos + Point1->TangentLeft; v2 Point1_Pos_Right = Point1_Pos + Point1->TangentRight; ImVec2 Point0_ScreenPos = Layer_LocalToScreenSpace(Layer, UI, CompBuffer, Point0_Pos); ImVec2 Point0_ScreenPos_Left = Layer_LocalToScreenSpace(Layer, UI, CompBuffer, Point0_Pos_Left); ImVec2 Point0_ScreenPos_Right = Layer_LocalToScreenSpace(Layer, UI, CompBuffer, Point0_Pos_Right); ImVec2 Point1_ScreenPos = Layer_LocalToScreenSpace(Layer, UI, CompBuffer, Point1_Pos); ImVec2 Point1_ScreenPos_Left = Layer_LocalToScreenSpace(Layer, UI, CompBuffer, Point1_Pos_Left); ImVec2 Point1_ScreenPos_Right = Layer_LocalToScreenSpace(Layer, UI, CompBuffer, Point1_Pos_Right); ImGui::PushID(p); ImU32 col = ImGui::GetColorU32(ImGuiCol_ScrollbarGrab); // The handle itself if (Point0->IsSelected) { col = ImGui::GetColorU32(ImGuiCol_ButtonHovered); draw_list->AddNgon(Point0_ScreenPos_Left, 10, col, 8, 5.0f); draw_list->AddNgon(Point0_ScreenPos_Right, 10, col, 8, 5.0f); draw_list->AddLine(Point0_ScreenPos, Point0_ScreenPos_Left, col, 2.0f); draw_list->AddLine(Point0_ScreenPos, Point0_ScreenPos_Right, col, 2.0f); } draw_list->AddNgon(Point0_ScreenPos, 10, col, 8, 5.0f); int max = 1; if (Point0->HandleBezier && Point0->IsSelected) { max = 3; } for (int b = 0; b < max; b++) { ImGui::PushID(b); if (b == 0) { ImGui::SetCursorScreenPos(Point0_ScreenPos - ImVec2(PointSize/2, PointSize/2)); } else if (b == 1) { ImGui::SetCursorScreenPos(Point0_ScreenPos_Left - ImVec2(PointSize/2, PointSize/2)); } else { ImGui::SetCursorScreenPos(Point0_ScreenPos_Right - ImVec2(PointSize/2, PointSize/2)); } ImGui::InvisibleButton("##point", ImVec2(PointSize, PointSize), ImGuiButtonFlags_MouseButtonLeft); if (ImGui::IsItemHovered()) { ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeEW); } if (ImGui::IsItemActivated() && b == 0) { Point0->IsSelected = 1; } if (ImGui::IsItemActive()) { // TODO(fox): Combine this with the anchor point code. ImVec2 MouseIncrement = io.MouseDelta * (ImVec2(CompBuffer.Width, CompBuffer.Height) / UI->CompZoom); real32 Rad = (Layer->rotation.CurrentValue.f * (PI / 180)); real32 s = Layer->scale.CurrentValue.f; v2 XAxis = V2(cos(Rad), sin(Rad)) * (MouseIncrement.x / s); v2 YAxis = V2(sin(Rad), -cos(Rad)) * (MouseIncrement.y / -s); if (b == 0) { Point0->Pos.x += XAxis.x; Point0->Pos.y -= XAxis.y; Point0->Pos.x -= YAxis.x; Point0->Pos.y += YAxis.y; } else if (b == 1) { Point0->TangentLeft.x += XAxis.x; Point0->TangentLeft.y -= XAxis.y; Point0->TangentLeft.x -= YAxis.x; Point0->TangentLeft.y += YAxis.y; } else { Point0->TangentRight.x += XAxis.x; Point0->TangentRight.y -= XAxis.y; Point0->TangentRight.x -= YAxis.x; Point0->TangentRight.y += YAxis.y; } State->UpdateFrame = true; } ImGui::PopID(); } // The bezier path if (Mask->NumberOfPoints == 1) { ImGui::PopID(); continue; } ImU32 col2 = ImGui::GetColorU32(ImGuiCol_Button); if (Point0->HandleBezier || Point1->HandleBezier) { draw_list->AddBezierCubic(Point0_ScreenPos, Point0_ScreenPos_Right, Point1_ScreenPos_Left, Point1_ScreenPos, col2, 6.0f, 0); } else { draw_list->AddLine(Point0_ScreenPos, Point1_ScreenPos, col2, 6.0f); } if (Point0->HandleBezier && Point1->HandleBezier) { if (ImGui::BezierInteractive(Point0_ScreenPos, Point0_ScreenPos_Right, Point1_ScreenPos_Left, Point1_ScreenPos) && State->Tool == tool_pen) { // Using a button like this may be kinda janky, but it gives us access // to all of ButtonBehavior and the ID system without having to write on top of ImGui's. ImGui::SetCursorScreenPos(io.MousePos - ImVec2(5,5)); ImGui::Button("maskbezier", ImVec2(10, 10)); if (ImGui::IsItemHovered()) { #if DEBUG v2 LayerPoint = Layer_ScreenSpaceToLocal(Layer, UI, CompBuffer, ViewportMin, io.MousePos); real32 ratio = Bezier_CubicRatioOfPoint(Point0_Pos, Point0_Pos_Right, Point1_Pos_Left, Point1_Pos, LayerPoint); draw_list->AddNgon(io.MousePos, 2, col, 8, 5.0f); ImVec2 RatioLeft = ImGui::RatioToPoint(Point0_ScreenPos, Point0_ScreenPos_Right, ratio); ImVec2 RatioRight = ImGui::RatioToPoint(Point1_ScreenPos_Left, Point1_ScreenPos, ratio); ImVec2 RatioTop = ImGui::RatioToPoint(Point0_ScreenPos_Right, Point1_ScreenPos_Left, ratio); ImVec2 TangentLeft = ImGui::RatioToPoint(RatioLeft, RatioTop, ratio); ImVec2 TangentRight = ImGui::RatioToPoint(RatioTop, RatioRight, ratio); draw_list->AddLine(RatioLeft, RatioTop, col, 2.0f); draw_list->AddLine(RatioRight, RatioTop, col, 2.0f); draw_list->AddLine(TangentLeft, TangentRight, col, 2.0f); #endif } if (ImGui::IsItemActivated() && io.KeyCtrl) { v2 LayerPoint = Layer_ScreenSpaceToLocal(Layer, UI, CompBuffer, ViewportMin, io.MousePos); real32 ratio = Bezier_CubicRatioOfPoint(Point0_Pos, Point0_Pos_Right, Point1_Pos_Left, Point1_Pos, LayerPoint); Mask_AddPointToCurve(Mask, p, ratio); } } } else { if (ImGui::LineInteractive(Point0_ScreenPos, Point1_ScreenPos) && State->Tool == tool_pen) { ImGui::SetCursorScreenPos(io.MousePos - ImVec2(5,5)); ImGui::Button("maskline", ImVec2(10, 10)); if (ImGui::IsItemHovered()) { draw_list->AddNgon(io.MousePos, 2, col, 8, 5.0f); } if (ImGui::IsItemActivated() && io.KeyCtrl) { v2 LayerPoint = Layer_ScreenSpaceToLocal(Layer, UI, CompBuffer, ViewportMin, io.MousePos); Mask_AddPointToLine(Mask, p, LayerPoint); } } } ImGui::PopID(); } ImGui::PopID(); } } } // Interactions for dragging and zooming ImGui::SetCursorScreenPos(ViewportMin); real32 ButtonSize = 20; for (int t = 0; t < tool_count; t++) { ImGui::PushID(t); bool32 Selected = (State->Tool == t); if (ImGui::Selectable("##tool", Selected, 0, ImVec2(ButtonSize, ButtonSize))) { State->Tool = (tool)t; } ImGui::PopID(); } ImGui::InvisibleButton("canvas", ViewportScale, ImGuiButtonFlags_MouseButtonLeft | ImGuiButtonFlags_MouseButtonRight); bool32 IsHovered = ImGui::IsItemHovered(); bool32 IsActive = ImGui::IsItemActive(); bool32 IsActivated = ImGui::IsItemActivated(); /* if (State->MostRecentlySelectedLayer > -1) { project_layer *Layer = File.Layer[State->MostRecentlySelectedLayer]; if (IsActivated && ImGui::IsMouseDown(ImGuiMouseButton_Left)) { if (State->Tool == tool_pen && !State->Pen.IsActive) { State->Pen.IsActive = true; Layer->NumberOfMasks++; } } if (State->Pen.IsActive && !ImGui::IsKeyDown(ImGuiKey_Z)) { if (IsActivated) { v2 LayerPos = Layer_ScreenSpaceToLocal(Layer, UI, CompBuffer, ViewportMin, io.MousePos); Mask_PushPoint(&Layer->Mask[Layer->NumberOfMasks-1], LayerPos); } if (IsActive) { mask *Mask = &Layer->Mask[Layer->NumberOfMasks-1]; mask_point *CurrentPoint = &Mask->Point[Mask->NumberOfPoints-1]; v2 CompUV = ImGui_ScreenPointToCompUV(ViewportMin, UI->CompPos, UI->CompZoom, io.MousePos); v2 LayerUV = CompUVToLayerUV(Layer, &CompBuffer, CompUV); v2 LayerPos = LayerUV * V2(Layer->Source->Info.Width, Layer->Source->Info.Height); v2 OffsetPos = CurrentPoint->Pos - LayerPos; CurrentPoint->TangentRight = -OffsetPos; CurrentPoint->TangentLeft = OffsetPos; } if (State->Tool != tool_pen) { State->Pen.IsActive = false; } } } */ ImGui::OpenPopupOnItemClick("context", ImGuiPopupFlags_MouseButtonMiddle); if (ImGui::BeginPopup("context")) { if (ImGui::MenuItem("Scalar", NULL, false, InstructionMode != instruction_mode_scalar)) { InstructionMode = instruction_mode_scalar; State->UpdateFrame = true; } if (ImGui::MenuItem("SSE", NULL, false, InstructionMode != instruction_mode_sse)) { InstructionMode = instruction_mode_sse; State->UpdateFrame = true; } if (ImGui::MenuItem("AVX2", NULL, false, InstructionMode != instruction_mode_avx)) { InstructionMode = instruction_mode_avx; State->UpdateFrame = true; } ImGui::EndPopup(); } if (IsHovered && IsActivated && ImGui::IsMouseDown(ImGuiMouseButton_Left)) { // Point to zoom in on if Z is held UI->TempZoomRatio = ImGui_ScreenPointToCompUV(ViewportMin, UI->CompPos, UI->CompZoom, io.MousePos); DebugWatchVar("MouseScreenUV", &UI->TempZoomRatio.x, d_float); DebugWatchVar("MouseScreenUV", &UI->TempZoomRatio.y, d_float); // Layer selection if (!ImGui::IsKeyDown(ImGuiKey_Z) || !State->Pen.IsActive) { for (int i = File.NumberOfLayers - 1; i >= 0; i--) { project_layer *Layer = File.Layer[i]; if (!io.KeyShift) DeselectAllLayers(&File, State); v2 LayerUV = CompUVToLayerUV(Layer, &CompBuffer, UI->TempZoomRatio); if (TestUV(LayerUV) && !Layer->IsSelected) { SelectLayer(Layer, State, i); break; } } } } if (IsActive && ImGui::IsMouseDragging(ImGuiMouseButton_Right, -1.0f)) { UI->CompPos.x += io.MouseDelta.x; UI->CompPos.y += io.MouseDelta.y; } if (IsActive && ImGui::IsMouseDragging(ImGuiMouseButton_Left, -1.0f) && ImGui::IsKeyDown(ImGuiKey_Z)) { real32 Distance = io.MouseDelta.x + io.MouseDelta.y; UI->CompZoom.x += (Distance)*(real32)CompBuffer.Width/CompBuffer.Height; UI->CompZoom.y += (Distance); UI->CompPos.x -= ((Distance)*(real32)CompBuffer.Width/CompBuffer.Height)*UI->TempZoomRatio.x; UI->CompPos.y -= Distance*UI->TempZoomRatio.y; } ImGui::Text("%.1f", 100.0f * (UI->CompZoom.x / CompBuffer.Width)); if (State->MsgTime > 0) { ImGui::SameLine(); ImGui::SetCursorPosX((ViewportScale.x / 5)*4); ImGui::Text(State->Msg); State->MsgTime--; } ImGui::End(); } // 1 for left, 2 for right, 3 for both static bool32 ImGui_SlidingLayer(project_layer *Layer, real32 *DraggingThreshold, real32 Delta, int16 TimelineZoom, int16 Side) { bool32 Result = 0; if (ImGui::IsItemActive() && ImGui::IsMouseDragging(ImGuiMouseButton_Left, -1)) { ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeEW); *DraggingThreshold += Delta; if (abs(*DraggingThreshold) >= TimelineZoom) { int16 Increment = *DraggingThreshold/TimelineZoom; // TODO(fox): Properly handle the start and end points wrapping. if (!(Increment < 0 && Layer->StartFrame == 0 && Side & 1)) { if (Side & 1) Layer->StartFrame += Increment; if (Side & 2) Layer->EndFrame += Increment; if (Side == 3) { IncrementKeyframesInLayer(Layer, Increment); if (Layer->Source->SourceType == source_type_video) { Layer->BitmapInfo.FrameOffset += Increment; } } } *DraggingThreshold += -1*Increment*TimelineZoom; } Result = 1; } return Result; } static void ImGui_File(project_data *File, project_state *State, memory *Memory, ui *UI, ImGuiIO io) { ImGui::Begin("Files"); ImGui::Text("Application average %.3f ms/frame (%.1f FPS)", 1000.0f / io.Framerate, io.Framerate); if (State->DemoButton) { if (ImGui::Button("Generate demo scene")) { // CreateDemoScene(File, Memory); State->UpdateKeyframes = true; State->UpdateFrame = true; State->DemoButton = false; } } if (State->GridButton) { ImGui::SameLine(); if (ImGui::Button("Generate square grid")) { // CreateGrid(File, Memory); State->UpdateKeyframes = true; State->UpdateFrame = true; State->GridButton = false; } } ImGui::Text("Sources:"); for (int i = 0; i < File->NumberOfSources; i++) { bool32 Test = false; if (File->SourceSelected == i) Test = true; ImGui::Selectable(File->Source[i].Path, Test); if (ImGui::IsItemClicked()) File->SourceSelected = i; if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) File->SourceSelected = i; ImGui::OpenPopupOnItemClick("sourcecontext", ImGuiPopupFlags_MouseButtonRight); } if (ImGui::BeginPopup("sourcecontext")) { if (ImGui::MenuItem("Create layer from source")) { Layer_CreateFromSource(File, State, Memory, &File->Source[File->SourceSelected]); } ImGui::EndPopup(); } // static char Input[1024]; // ImGui::InputText("##sourceinput", Input, STRING_SIZE); // ImGui::SameLine(); // if (ImGui::Button("Create Layer")) { // // AddSource(File, State, Memory, Input); // } #if DEBUG for (int i = 0; i < Debug.Temp.WatchedProperties; i++) { if (Debug.Temp.DebugPropertyType[i] == d_float) { ImGui::Text("%s: %f", Debug.Temp.String[i], Debug.Temp.Val[i].f); } else if (Debug.Temp.DebugPropertyType[i] == d_int) { ImGui::Text("%s: %i", Debug.Temp.String[i], Debug.Temp.Val[i].i); } else if (Debug.Temp.DebugPropertyType[i] == d_uint) { ImGui::Text("%s: %u", Debug.Temp.String[i], Debug.Temp.Val[i].u); } } Debug.Temp = {}; #endif ImGui::End(); } static void ImGui_EffectsPanel(project_data *File, project_state *State, memory *Memory, ui *UI, ImGuiIO io) { ImGui::Begin("Effects list", NULL); if (State->RerouteEffects) { ImGui::SetKeyboardFocusHere(); State->RerouteEffects = 0; } int value_changed = ImGui::InputText("Effect name...", State->filter.InputBuf, IM_ARRAYSIZE(State->filter.InputBuf), ImGuiInputTextFlags_CallbackCompletion, EffectConsoleCallback); if (Hacko) { if (!io.KeyShift) EffectSel++; else EffectSel--; Hacko = 0; } if (value_changed) { State->filter.Build(); EffectSel = -1; } // Enter conveniently deactivates the InputText field if (ImGui::IsItemDeactivated() && ImGui::IsKeyPressed(ImGuiKey_Enter)) { int32 p = 0; for (int32 i = 0; i < AmountOf(EffectList); i++) { if (State->filter.PassFilter(EffectList[i].Name)) { if (EffectSel == p && State->MostRecentlySelectedLayer != -1) { AddEffect(File->Layer[State->MostRecentlySelectedLayer], Memory, i); } p++; } } EffectSel = -1; } int32 p = 0; for (int32 i = 0; i < AmountOf(EffectList); i++) { if (State->filter.PassFilter(EffectList[i].Name)) { bool t = false; if (EffectSel == p) { t = true; } ImGui::Selectable(EffectList[i].Name, &t); if (ImGui::IsItemClicked()) { if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left) && State->MostRecentlySelectedLayer != -1) { AddEffect(File->Layer[State->MostRecentlySelectedLayer], Memory, i); } } p++; } } ImGui::End(); } static void ImGui_Timeline(project_data *File, project_state *State, memory *Memory, ui *UI, ImGuiIO io) { ImVec2 FramePadding = ImGui::GetStyle().FramePadding; ImVec2 ItemSpacing = ImGui::GetStyle().ItemSpacing; ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0)); ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0)); // makes setting up the layout easier ImGui::Begin("Timeline", NULL); if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) UI->FocusedWindow = focus_timeline; real32 FontHeight = ImGui::GetFontSize(); ImVec2 WindowSize = ImGui::GetWindowSize(); if (WindowSize.x < 50.0f) WindowSize.x = 50.0f; // prevent crashing if the window gets too small if (WindowSize.y < 50.0f) WindowSize.y = 50.0f; // (still crashes) ImVec2 WindowMinAbs = ImGui::GetWindowPos(); ImVec2 WindowMaxAbs = WindowMinAbs + WindowSize; ImVec2 ButtonSize = ImVec2(FontHeight*2, FontHeight*2); real32 TopbarHeight = FontHeight*4; ImVec2 TopbarMax = ImVec2(WindowMaxAbs.x, WindowMinAbs.y + TopbarHeight); ImVec2 TimelineBorderPadding = ImVec2(FontHeight, FontHeight); ImVec2 TopbarSize = ImVec2(WindowSize.x, TopbarHeight); ImVec2 TopbarButtonSize = ImVec2(TopbarHeight, TopbarHeight); // NOTE(fox): StartingPos values include X and Y scroll, primarily used for // the keyframes/layers. Absolute doesn't include scroll, primarily used // for the clip rects. ImVec2 SidebarSize = ImVec2(UI->TimelineSplit, WindowSize.y - TopbarHeight); ImVec2 SidebarSizeWithBorder = SidebarSize - TimelineBorderPadding*2; ImVec2 SidebarAbsolutePos = WindowMinAbs + ImVec2(0, TopbarSize.y) + TimelineBorderPadding; ImVec2 SidebarStartingPos = SidebarAbsolutePos + ImVec2(0, UI->ScrollYOffset); ImVec2 TimelineSize = ImVec2(WindowSize.x - SidebarSize.x, SidebarSize.y); ImVec2 TimelineSizeWithBorder = TimelineSize - TimelineBorderPadding*2; ImVec2 TimelineAbsolutePos = WindowMinAbs + ImVec2(SidebarSize.x, TopbarSize.y) + TimelineBorderPadding; ImVec2 TimelineStartingPos = SidebarStartingPos + ImVec2(SidebarSize.x + UI->ScrollXOffset, 0); // Timeline and sidebar size including the padding between them ImVec2 TimelineFullSize = TimelineSizeWithBorder + SidebarSizeWithBorder + ImVec2(TimelineBorderPadding.x*2, 0); ImVec2 KeyframeSize = ImVec2(FontHeight, FontHeight); ImVec2 PlayheadPos = ImVec2(TimelineStartingPos.x + UI->TimelineZoom * File->CurrentFrame, WindowMinAbs.y + TopbarSize.y/2); // NOTE(fox): The InvisibleButton hitbox that handles mouse inputs on the // graph occludes the hitbox that handles box drag selection, so I'm using // this struct to carry over the state from the former to the latter. imgui_buttonstate AnimationCurves = {}; if (UI->Initializing) { UI->TimelineZoom = TimelineSizeWithBorder.x / (File->NumberOfFrames + 1); } ImDrawList* draw_list = ImGui::GetWindowDrawList(); draw_list->AddRectFilled(WindowMinAbs, WindowMaxAbs, IM_COL32(255, 255, 255, 50)); draw_list->AddRectFilled(WindowMinAbs, TopbarMax, IM_COL32(255, 255, 255, 50)); // ImGui::BeginChild("Topbar", TopbarSize, true, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoScrollbar); // ImGui::Text /* ImGui::Button("V", TopbarButtonSize); ImGui::SameLine(); ImGui::Button("V", TopbarButtonSize); ImGui::SameLine(); ImGui::Button("V", TopbarButtonSize); ImGui::SameLine(); */ ImGui::SetCursorScreenPos(PlayheadPos); ImGui::Button("P", ButtonSize); if (ImGui::IsItemActive()) { if (ImGui::IsMouseDragging(ImGuiMouseButton_Left, -1)) { UI->DraggingKeyframeThreshold += io.MouseDelta.x; if (abs(UI->DraggingKeyframeThreshold) >= UI->TimelineZoom) { int16 Increment = UI->DraggingKeyframeThreshold/UI->TimelineZoom; if (File->CurrentFrame <= 0 && Increment < File->StartFrame) File->CurrentFrame = 0; else if (File->CurrentFrame >= File->EndFrame && Increment > File->EndFrame) { File->CurrentFrame = File->EndFrame; } else { File->CurrentFrame += Increment; } State->UpdateFrame = true; State->UpdateKeyframes = true; UI->DraggingKeyframeThreshold += -1*Increment*UI->TimelineZoom; } } } ImGui::EndChild(); /// ImGui::BeginChild("Sidebar", SidebarSize, true, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoScrollbar); ImGui::SetCursorScreenPos(SidebarStartingPos); ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ItemSpacing); ImGui::PushClipRect(SidebarAbsolutePos, SidebarAbsolutePos + SidebarSizeWithBorder, true); for (int i = File->NumberOfLayers - 1; i >= 0; i--) { project_layer *Layer = File->Layer[i]; ImGui::PushID(i); ImGui::SetCursorScreenPos(ImVec2(SidebarStartingPos.x, ImGui::GetCursorScreenPos().y)); draw_list->PushClipRect(SidebarAbsolutePos, SidebarAbsolutePos + TimelineFullSize, true); if (Layer->IsSelected) { real32 Y = ImGui::GetCursorScreenPos().y; draw_list->AddRectFilled(ImVec2(SidebarAbsolutePos.x, Y), ImVec2(TimelineAbsolutePos.x + TimelineSize.x, Y + FontHeight + FramePadding.y*2), IM_COL32(255, 255, 255, 50)); } draw_list->PopClipRect(); ImGui::Button("V"); ImGui::SameLine(); ImGui::Button("I"); ImGui::SameLine(); ImGui::Text(Layer->Name); ImGui::SameLine(); ImGui::Button(BlendmodeNames[Layer->BlendMode]); ImGui::OpenPopupOnItemClick("blendmode_picker", ImGuiPopupFlags_MouseButtonLeft); if (ImGui::BeginPopup("blendmode_picker")) { for (int16 b = 0; b < AmountOf(BlendmodeNames); b++) { if (ImGui::MenuItem(BlendmodeNames[b], NULL, false, Layer->BlendMode != b)) { Layer->BlendMode = (blend_mode)b; State->UpdateFrame = true; } // using IsActivated here instead of above loop doesn't seem to // work; the popup gets closed instead if (ImGui::IsItemHovered() && io.KeyCtrl) { Layer->BlendMode = (blend_mode)b; State->UpdateFrame = true; } } ImGui::EndPopup(); } ImGui::SameLine(); ImGui::SetCursorScreenPos(ImVec2(SidebarStartingPos.x, ImGui::GetCursorScreenPos().y)); ImGui::Button("##mover", ImVec2(SidebarSizeWithBorder.x, FontHeight + FramePadding.y*2)); // Layer dragging interaction if (ImGui::IsItemActive()) { if (ImGui::IsItemActivated() && !Layer->IsSelected) { if (!io.KeyShift) DeselectAllLayers(File, State); SelectLayer(Layer, State, i); } if (ImGui::IsMouseDragging(ImGuiMouseButton_Left, -1) ) { ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeEW); UI->DraggingLayerThreshold -= io.MouseDelta.y; real32 Threshold = FontHeight + FramePadding.y*2; if (abs(UI->DraggingLayerThreshold) >= Threshold) { int16 Increment = UI->DraggingLayerThreshold/abs(UI->DraggingLayerThreshold); MoveLayersByIncrement(File, State, Increment); UI->DraggingLayerThreshold += -1*Increment*Threshold; State->UpdateFrame = true; // Cache.Frame[File->CurrentFrame].Cached = false; } } } // Properties gap ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0.0f, ItemSpacing.y * UI->KeyframeSpacing)); for (int a = 0; a < AmountOf(Layer->Property); a++) { if (Layer->Property[a].IsToggled) { property_channel *Property = &Layer->Property[a]; ImGui::PushID(Property); // if (Property->IsSelected) { // real32 Y = ImGui::GetCursorScreenPos().y; // draw_list->AddRectFilled(ImVec2(SidebarAbsolutePos.x, Y), // ImVec2(TimelineAbsolutePos.x, Y + FontHeight + FramePadding.y*2), // IM_COL32(100, 0, 255, 50)); // } ImGui::SetCursorScreenPos(ImVec2(SidebarStartingPos.x, ImGui::GetCursorScreenPos().y)); ImGui::Text(Property->Name); real32 YInit = ImGui::GetCursorScreenPos().y; ImGui::SameLine(); if (ImGui::Button("K")) ManualKeyframeInsertF(Property, Memory, File->CurrentFrame, Property->CurrentValue.f); ImGui::SameLine(); if (ImGui::Button("G")) { Property->IsGraphToggled ^= 1; // TODO(fox): Make system to init things like these automatically? if (!Property->GraphLength) { Property->GraphLength = 150; Property->GraphYOffset = (Property->GraphWindowHeight - Property->GraphLength)/2; } } ImGui::SetCursorScreenPos(ImVec2(ImGui::GetCursorScreenPos().x, YInit)); if (Property->IsGraphToggled) { ImGui::Dummy(ImVec2(5, Property->GraphWindowHeight)); } ImGui::PopID(); } } ImGui::PopStyleVar(); ImGui::PopID(); } ImGui::PopClipRect(); /// Split size adjuster ImGui::SetCursorScreenPos(ImVec2(WindowMinAbs.x + UI->TimelineSplit - TimelineBorderPadding.x, TimelineAbsolutePos.y)); ImGui::InvisibleButton("##SplitMove", ImVec2(TimelineBorderPadding.x, SidebarSizeWithBorder.y), ImGuiButtonFlags_MouseButtonLeft); if (ImGui::IsItemHovered()) { ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeEW); } if (ImGui::IsItemActive() && ImGui::IsMouseDragging(ImGuiMouseButton_Left, -1)) { UI->TimelineSplit += io.MouseDelta.x; } ImGui::PopStyleVar(); ImGui::EndChild(); ImGui::SameLine(); /// ImGui::BeginChild("Timeline", TimelineSize, true, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoScrollbar); ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0.0f, ItemSpacing.y)); ImGui::SetCursorScreenPos(TimelineStartingPos); ImGui::PushClipRect(TimelineAbsolutePos, TimelineAbsolutePos + TimelineSizeWithBorder, true); draw_list->PushClipRect(TimelineAbsolutePos, TimelineAbsolutePos + TimelineSizeWithBorder, true); for (int i = File->NumberOfLayers - 1; i >= 0; i--) { // The actual layer bars project_layer *Layer = File->Layer[i]; ImGui::PushID(i); uint16 LayerTLSpan = Layer->EndFrame - Layer->StartFrame; // if (Layer->SourceType == video) { // video_source *Source = (video_source *)Layer->RenderInfo; // real32 XMin = TimelineMinX + UI->TimelineZoom*Source->VideoFrameOffset; // // real32 YMin = StartingCursorPosAbs.y + (FontHeight + FramePadding.y*2 + ItemSpacing.y)*i; // real32 YMin = ImGui::GetCursorScreenPos().y; // draw_list->AddRect(ImVec2(WindowMin.x, YMin), // ImVec2(WindowMaxAbs.x, YMin + FontHeight + FramePadding.y*2), // IM_COL32(255, 255, 255, 50), 2); // } ImGui::SetCursorScreenPos(ImVec2(TimelineStartingPos.x + UI->TimelineZoom*Layer->StartFrame, ImGui::GetCursorScreenPos().y)); ImGui::Button("##leftbound", ImVec2(0.5 * UI->TimelineZoom, 0)); ImGui::SameLine(); if (ImGui::IsItemHovered()) { ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeEW); } ImGui_SlidingLayer(Layer, &UI->DraggingKeyframeThreshold, io.MouseDelta.x, UI->TimelineZoom, 1); // TODO(fox): Investigate why this button doesn't get lined up with // leftbound in certain cases. (i.e. rotation property expanded with keyframes) ImGui::Button("##layer", ImVec2((LayerTLSpan * UI->TimelineZoom), 0)); ImGui::SameLine(); if (ImGui::IsItemClicked()) { if (!io.KeyShift) DeselectAllLayers(File, State); SelectLayer(Layer, State, i); } if (ImGui_SlidingLayer(Layer, &UI->DraggingLayerThreshold, io.MouseDelta.x, UI->TimelineZoom, 3)) { // TODO(fox): This will be removed once video caching is implemented. UI->TemporaryUpdateOverride = true; } ImGui::Button("##rightbound", ImVec2(0.5 * UI->TimelineZoom, 0)); if (ImGui::IsItemHovered()) { ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeEW); } ImGui_SlidingLayer(Layer, &UI->DraggingKeyframeThreshold, io.MouseDelta.x, UI->TimelineZoom, 2); for (int a = Layer->StartFrame; a < Layer->EndFrame; a++) { cached_bitmap *Bitmap = Cache_CheckBitmap(Layer->Source, &Layer->BitmapInfo, Memory, a); if (Bitmap) { ImVec2 MinPos = ImVec2(TimelineStartingPos.x + UI->TimelineZoom * a, ImGui::GetCursorScreenPos().y); draw_list->AddRect(MinPos, ImVec2(MinPos.x + UI->TimelineZoom, MinPos.y + 2.0f), ImColor(0, 255, 0, 255)); } } ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0.0f, ItemSpacing.y * UI->KeyframeSpacing)); ImGui::SetCursorPosY(ImGui::GetCursorPos().y + (ItemSpacing.y * UI->KeyframeSpacing / 2)); for (int a = 0; a < AmountOf(Layer->Property); a++) { if (Layer->Property[a].IsToggled) { real32 InitialY = ImGui::GetCursorScreenPos().y; ImGui::NewLine(); real32 NextY = ImGui::GetCursorScreenPos().y; property_channel *Property = &Layer->Property[a]; ImGui::PushID(Property); for (int b = 0; b < Layer->Property[a].NumberOfTotalKeyframes; b++) { keyframe *Keyframe = KeyframeLookupMemory(Property, b); real32 KeyframeOrigin = TimelineStartingPos.x + UI->TimelineZoom*Keyframe->FrameNumber; ImVec2 KeyframePosition = ImVec2(KeyframeOrigin - FontHeight/2, InitialY); ImGui::PushID(Keyframe); ImGui::SetCursorScreenPos(KeyframePosition); // sadly ImGui::Selectable doesn't work here if (Keyframe->IsSelected) ImGui::PushStyleColor(ImGuiCol_Button, ImGui::GetColorU32(ImGuiCol_ButtonHovered)); ImGui::Button("##keyframe", ImVec2(FontHeight, FontHeight)); ImGui::SameLine(); if (Keyframe->IsSelected) ImGui::PopStyleColor(); if (UI->BoxSelectActive && UI->BoxStart.y < NextY) { if (IsRectTouching(UI->BoxStart, UI->BoxEnd, KeyframePosition, KeyframePosition + KeyframeSize)) { SelectKeyframe(File, Layer, Property, Keyframe); State->RecentSelectionType = selection_keyframe; } else if (!io.KeyShift) { Keyframe->IsSelected = false; } } ImGui_KeyframeDragging(File, State, UI, Property, b, io, 0); ImGui::PopID(); } ImGui::SetCursorScreenPos(ImVec2(ImGui::GetCursorScreenPos().x, NextY)); if (Property->IsGraphToggled) { uint16 GraphWindowHeight = File->Layer[i]->Property[a].GraphWindowHeight; real32 GraphWindowLocalYMin = ImGui::GetCursorPosY(); ImDrawList* draw_list = ImGui::GetWindowDrawList(); real32 ScreenY = NextY; ImVec2 MinPos = ImVec2(TimelineAbsolutePos.x, ScreenY); ImVec2 MaxPos = ImVec2(TimelineAbsolutePos.x + TimelineSizeWithBorder.x, ScreenY + GraphWindowHeight); draw_list->AddRectFilled(MinPos, MaxPos, IM_COL32(00, 00, 30, 65)); draw_list->PushClipRect(MinPos, MaxPos, true); ImVec2 LeftPos[2]; ImVec2 MidPos[2]; ImVec2 RightPos[2]; ImU32 col = ImGui::GetColorU32(ImGuiCol_ScrollbarGrab); for (int b = 0; b < Property->NumberOfTotalKeyframes; b++) { keyframe *Keyframe = KeyframeLookupMemory(Property, b); // int32 Index = KeyframeMemoryToIndex(Property, b); ImGui::PushID(Keyframe); real32 MinVal = Property->LocalMinVal.f; real32 MaxVal = Property->LocalMaxVal.f; // Normalized ratio between the smallest and largest value real32 HandleYRatio = (Keyframe->Value.f - MaxVal) / (MaxVal - MinVal); real32 HandleYRatio_L = (Keyframe->Value.f + Keyframe->TangentLeft.y - MaxVal) / (MaxVal - MinVal); real32 HandleYRatio_R = (Keyframe->Value.f + Keyframe->TangentRight.y - MaxVal) / (MaxVal - MinVal); real32 LocalHandlePosX = UI->TimelineZoom*Keyframe->FrameNumber; real32 LocalHandlePosX_L = LocalHandlePosX + UI->TimelineZoom*Keyframe->TangentLeft.x; real32 LocalHandlePosX_R = LocalHandlePosX + UI->TimelineZoom*Keyframe->TangentRight.x; real32 HandlePosX = TimelineStartingPos.x + LocalHandlePosX - FontHeight*0.5; real32 HandlePosX_L = TimelineStartingPos.x + LocalHandlePosX_L - FontHeight*0.5; real32 HandlePosX_R = TimelineStartingPos.x + LocalHandlePosX_R - FontHeight*0.5; real32 LocalHandlePosY = HandleYRatio * Property->GraphLength; real32 LocalHandlePosY_L = HandleYRatio_L * Property->GraphLength; real32 LocalHandlePosY_R = HandleYRatio_R * Property->GraphLength; real32 HandlePosY = MinPos.y - LocalHandlePosY + Property->GraphYOffset; real32 HandlePosY_L = MinPos.y - LocalHandlePosY_L + Property->GraphYOffset; real32 HandlePosY_R = MinPos.y - LocalHandlePosY_R + Property->GraphYOffset; ImVec2 HandlePos = ImVec2(HandlePosX, HandlePosY); ImVec2 HandlePos_L = ImVec2(HandlePosX_L, HandlePosY_L); ImVec2 HandlePos_R = ImVec2(HandlePosX_R, HandlePosY_R); if (UI->BoxSelectActive && UI->BoxStart.y >= NextY) { if (IsRectTouching(UI->BoxStart, UI->BoxEnd, HandlePos, HandlePos + KeyframeSize)) { Keyframe->IsSelected = true; State->RecentSelectionType = selection_keyframe; } else if (!io.KeyShift) { Keyframe->IsSelected = false; } } ImGui::PushStyleColor(ImGuiCol_Button, col); ImGui::SetCursorScreenPos(ImVec2(HandlePosX - FontHeight*1.5, HandlePosY - FontHeight*1.5)); ImGui::Text("%.02f", Keyframe->Value.f); ImGui::SetCursorScreenPos(ImVec2(HandlePosX - FontHeight*1.0, HandlePosY - FontHeight*1.0)); ImGui::InvisibleButton("##keyframepoint", ImVec2(FontHeight*2, FontHeight*2), ImGuiButtonFlags_MouseButtonLeft | ImGuiButtonFlags_MouseButtonRight); draw_list->AddRect(ImVec2(HandlePosX - FontHeight*0.5, HandlePosY - FontHeight*0.5), ImVec2(HandlePosX + FontHeight*0.5, HandlePosY + FontHeight*0.5), ImGui::GetColorU32(ImGuiCol_ButtonHovered)); ImGui_KeyframeDragging(File, State, UI, Property, b, io, 1); if (Keyframe->IsSelected && Keyframe->Type == bezier) { ImGui::SetCursorScreenPos(ImVec2(HandlePosX_L, HandlePosY_L)); draw_list->AddCircle(ImVec2(HandlePosX_L, HandlePosY_L), 2, col, 16, 1); ImGui::Button("##keyframehandleleft", ImVec2(FontHeight, FontHeight)); ImGui_KeyframeDragging(File, State, UI, Property, b, io, 2); ImGui::SetCursorScreenPos(ImVec2(HandlePosX_R, HandlePosY_R)); ImGui::Button("##keyframehandleright", ImVec2(FontHeight, FontHeight)); ImGui_KeyframeDragging(File, State, UI, Property, b, io, 3); draw_list->AddLine(MidPos[b & 1], RightPos[b & 1], col, 1.0f); draw_list->AddLine(MidPos[b & 1], LeftPos[b & 1], col, 1.0f); } ImGui::PopStyleColor(); ImGui::PopID(); } // TODO(fox): Reformat this so it's all done in one loop. for (int b = 0; b < Property->NumberOfTotalKeyframes; b++) { keyframe *Keyframe = KeyframeLookupMemory(Property, b); real32 MinVal = Property->LocalMinVal.f; real32 MaxVal = Property->LocalMaxVal.f; real32 HandleYRatio = (Keyframe->Value.f - MaxVal) / (MaxVal - MinVal); real32 HandleYRatio_L = (Keyframe->Value.f + Keyframe->TangentLeft.y - MaxVal) / (MaxVal - MinVal); real32 HandleYRatio_R = (Keyframe->Value.f + Keyframe->TangentRight.y - MaxVal) / (MaxVal - MinVal); real32 LocalHandlePosX = UI->TimelineZoom*Keyframe->FrameNumber; real32 LocalHandlePosX_L = LocalHandlePosX + UI->TimelineZoom*Keyframe->TangentLeft.x; real32 LocalHandlePosX_R = LocalHandlePosX + UI->TimelineZoom*Keyframe->TangentRight.x; real32 HandlePosX = TimelineStartingPos.x + LocalHandlePosX - FontHeight*0.5; real32 HandlePosX_L = TimelineStartingPos.x + LocalHandlePosX_L - FontHeight*0.5; real32 HandlePosX_R = TimelineStartingPos.x + LocalHandlePosX_R - FontHeight*0.5; real32 LocalHandlePosY = HandleYRatio * Property->GraphLength; real32 LocalHandlePosY_L = HandleYRatio_L * Property->GraphLength; real32 LocalHandlePosY_R = HandleYRatio_R * Property->GraphLength; real32 HandlePosY = MinPos.y - LocalHandlePosY + Property->GraphYOffset; real32 HandlePosY_L = MinPos.y - LocalHandlePosY_L + Property->GraphYOffset; real32 HandlePosY_R = MinPos.y - LocalHandlePosY_R + Property->GraphYOffset; ImVec2 HandlePos = ImVec2(HandlePosX, HandlePosY); ImVec2 HandlePos_L = ImVec2(HandlePosX_L, HandlePosY_L); ImVec2 HandlePos_R = ImVec2(HandlePosX_R, HandlePosY_R); MidPos[b & 1] = HandlePos; LeftPos[b & 1] = HandlePos_L; RightPos[b & 1] = HandlePos_R; if (b != 0) { if (b & 1) { if (Keyframe->Type == linear) draw_list->AddLine(MidPos[0], MidPos[1], col, 1.0f); else if (Keyframe->Type == bezier) draw_list->AddBezierCubic(MidPos[0], RightPos[0], LeftPos[1], MidPos[1], col, 1.0f, 8); } else { if (Keyframe->Type == linear) draw_list->AddLine(MidPos[1], MidPos[0], col, 1.0f); else if (Keyframe->Type == bezier) draw_list->AddBezierCubic(MidPos[1], RightPos[1], LeftPos[0], MidPos[0], col, 1.0f, 8); } } } // Horiziontal value lines // uint32 LineColor = IM_COL32(200, 200, 200, 40); // for (int i = 0; i < 10; i++) { // real32 YPos = MinPos.y + (UI->TimelineZoom/2 * i) + 5; // ImVec2 Min = ImVec2(TimelineStartingPos.x, YPos); // ImVec2 Max = ImVec2(TimelineStartingPos.x + TimelineSize.x, YPos); // draw_list->AddLine(Min, Max, LineColor); // } draw_list->PopClipRect(); // ImGui::SetCursorScreenPos(ImVec2(MinPos.x, MinPos.y)); // ImGui::Button("##SplitMove", ImVec2(TimelineBorderPadding.x, SidebarSizeWithBorder.y)); // if (ImGui::IsItemActive() && ImGui::IsMouseDragging(ImGuiMouseButton_Left, -1)) // { // UI->TimelineSplit += io.MouseDelta.x; // } ImGui::SetCursorScreenPos(ImVec2(MinPos.x, MinPos.y)); ImGui::InvisibleButton("AnimationCurves", ImVec2(TimelineSize.x - 20, GraphWindowHeight), ImGuiButtonFlags_MouseButtonLeft | ImGuiButtonFlags_MouseButtonRight); // NOTE(fox): I'm reusing this struct for the other // channels, so I'm OR'ing it. Also persists across layers. AnimationCurves.IsItemHovered |= ImGui::IsItemHovered(); AnimationCurves.IsItemActive |= ImGui::IsItemActive(); AnimationCurves.IsItemActivated |= ImGui::IsItemActivated(); AnimationCurves.IsItemDeactivated |= ImGui::IsItemDeactivated(); AnimationCurves.LeftClick |= ImGui::IsMouseDown(ImGuiMouseButton_Left); AnimationCurves.RightClick |= ImGui::IsMouseDown(ImGuiMouseButton_Right); if (AnimationCurves.IsItemHovered && AnimationCurves.IsItemActivated && ImGui::IsMouseDown(ImGuiMouseButton_Right)) { real32 LocalMousePos = io.MousePos.y - MinPos.y - Property->GraphYOffset; UI->TempZoomRatioGraph = LocalMousePos / Property->GraphLength; } // DebugWatchVar("LocalMousePos", &LocalMousePos, d_float); if (AnimationCurves.IsItemActive && ImGui::IsMouseDragging(ImGuiMouseButton_Right, -1)) { Property->GraphLength += io.MouseDelta.x; Property->GraphYOffset -= io.MouseDelta.x*UI->TempZoomRatioGraph; Property->GraphYOffset += io.MouseDelta.y; } } ImGui::PopID(); } } ImGui::SetCursorPosY(ImGui::GetCursorPos().y - (ItemSpacing.y * UI->KeyframeSpacing / 2)); ImGui::PopStyleVar(); ImGui::PopID(); } // Timeline frame ticks ImGui::SetCursorScreenPos(TimelineStartingPos); if (UI->TimelineZoom > 10) { for (float x = 0; x < File->NumberOfFrames + 2; x += 1) { uint32 LineColor = IM_COL32(200, 200, 200, 40); ImVec2 Min = ImVec2(TimelineStartingPos.x + UI->TimelineZoom * x, TimelineStartingPos.y); ImVec2 Max = ImVec2(Min.x + 2, WindowMaxAbs.y); if (x == File->CurrentFrame) continue; draw_list->AddLine(Min, Max, LineColor); } } draw_list->PopClipRect(); ImGui::PopClipRect(); // Playhead line uint32 LineColor = IM_COL32(200, 200, 200, 200); ImVec2 Min = PlayheadPos; ImVec2 Max = ImVec2(Min.x + 2, Min.y + TimelineSizeWithBorder.y + TopbarSize.y/2); draw_list->AddLine(Min, Max, LineColor); ImGui::PopStyleVar(); // General timeline interaction ImGui::SetCursorScreenPos(TimelineAbsolutePos); ImGui::InvisibleButton("TimelineMoving", TimelineSizeWithBorder, ImGuiButtonFlags_MouseButtonLeft | ImGuiButtonFlags_MouseButtonRight); bool32 IsHovered = ImGui::IsItemHovered(); bool32 IsActive = ImGui::IsItemActive(); bool32 IsItemActivated = ImGui::IsItemActivated(); bool32 IsItemDeactivated = ImGui::IsItemDeactivated(); bool32 LeftClick = ImGui::IsMouseDown(ImGuiMouseButton_Left); bool32 RightClick = ImGui::IsMouseDown(ImGuiMouseButton_Right); if (IsActive || AnimationCurves.IsItemActive) { if (LeftClick) { if (io.KeyCtrl && IsActive) { real32 LocalMousePos = ImGui::GetMousePos().x - TimelineStartingPos.x; real32 ZoomRatio = LocalMousePos / UI->TimelineZoom; File->CurrentFrame = (int32)(ZoomRatio + 0.5); State->UpdateFrame = true; State->UpdateKeyframes = true; } else { if (IsItemActivated || AnimationCurves.IsItemActivated) { if (!io.KeyShift) { // DeselectAllKeyframes(&State); // DeselectAllLayers(File, State); } UI->BoxStart = ImGui::GetMousePos(); UI->BoxSelectActive = true; } if (ImGui::IsMouseDragging(ImGuiMouseButton_Left, -1) ) { UI->BoxEnd = ImGui::GetMousePos(); draw_list->AddRectFilled(UI->BoxStart, UI->BoxEnd, IM_COL32(0, 0, 200, 50)); } } // Timeline zooming interaction } else if (RightClick && IsActive) { if (IsItemActivated) { real32 LocalMousePos = io.MousePos.x - WindowMinAbs.x - UI->TimelineSplit; UI->TempZoomRatioTimeline = LocalMousePos / TimelineSize.x; } if (ImGui::IsMouseDragging(ImGuiMouseButton_Right, -1) ) { UI->TimelineZoom += io.MouseDelta.x; ImGui::SetScrollX(ImGui::GetScrollMaxX() * UI->TempZoomRatioTimeline); } } } if (IsItemDeactivated || AnimationCurves.IsItemDeactivated) { UI->BoxStart = {0, 0}; UI->BoxEnd = {0, 0}; UI->BoxSelectActive = false; if (!io.KeyShift) DeselectAllLayers(File, State); } ImGui::EndChild(); ImGui::PopStyleVar(2); if (IsRectTouching(WindowMinAbs, WindowMaxAbs, io.MousePos, io.MousePos + 1)) { if (io.KeyCtrl && io.MouseWheel) { real32 ZoomAmount = io.MouseWheel*16; real32 LocalMousePos = ImGui::GetMousePos().x - TimelineStartingPos.x; real32 ZoomRatio = LocalMousePos / UI->TimelineZoom; UI->TimelineZoom += ZoomAmount; UI->ScrollXOffset -= ZoomAmount*ZoomRatio; } else if (io.KeyShift && io.MouseWheel) { UI->ScrollXOffset += io.MouseWheel*16; } else { UI->ScrollXOffset += io.MouseWheelH*16; UI->ScrollYOffset += io.MouseWheel*16; } } ImGui::End(); } static void ImGui_ProcessInputs(project_data *File, project_state *State, comp_buffer *CompBuffer, memory *Memory, ui *UI, ImGuiIO io) { if (io.KeysData[ImGuiKey_Q].Down) State->IsRunning = false; if (ImGui::IsKeyPressed(ImGuiKey_D)) { IncrementFrame(File, -1); State->UpdateFrame = true; State->UpdateKeyframes = true; } if (ImGui::IsKeyPressed(ImGuiKey_F)) { IncrementFrame(File, 1); State->UpdateFrame = true; State->UpdateKeyframes = true; } if (ImGui::IsKeyPressed(ImGuiKey_Z)) { if (io.KeyCtrl && io.KeyShift) { Action_Redo(Memory); } else if (io.KeyCtrl) { Action_Undo(Memory); } State->UpdateFrame = true; State->UpdateKeyframes = true; } if (ImGui::IsKeyPressed(ImGuiKey_Space)) { if (io.KeyShift) { State->RerouteEffects = true; } else { State->IsPlaying ^= 1; } } if (State->IsPlaying && !IsRendering) { IncrementFrame(File, 1); State->UpdateFrame = true; State->UpdateKeyframes = true; } if (ImGui::IsKeyPressed(ImGuiKey_R) && State->NumberOfSelectedLayers) TransformsInteract(File, State, Memory, UI, sliding_rotation); if (ImGui::IsKeyPressed(ImGuiKey_S) && State->NumberOfSelectedLayers) TransformsInteract(File, State, Memory, UI, sliding_scale); if (ImGui::IsKeyPressed(ImGuiKey_G) && State->NumberOfSelectedLayers) TransformsInteract(File, State, Memory, UI, sliding_position); if (ImGui::IsKeyPressed(ImGuiKey_A) && State->NumberOfSelectedLayers) TransformsInteract(File, State, Memory, UI, sliding_anchorpoint); if (ImGui::IsKeyPressed(ImGuiKey_1)) LoadTestFootage(File, State, Memory); if (ImGui::IsKeyPressed(ImGuiKey_Delete)) { switch (State->RecentSelectionType) { case selection_none: { } break; case selection_layer: { } break; case selection_effect: { } break; case selection_keyframe: { DeleteSelectedKeyframes(File, Memory); State->UpdateKeyframes = true; State->UpdateFrame = true; } case selection_maskpoint: { } case selection_source: { } break; } } #if DEBUG if (ImGui::IsKeyPressed(ImGuiKey_W)) { Debug.ToggleWindow ^= 1; } #endif bool32 Ended = ImGui::IsMouseDown(ImGuiMouseButton_Left); if (State->IsInteracting) { ImVec2 MouseIncrement = io.MouseDelta * (ImVec2(CompBuffer->Width, CompBuffer->Height) / UI->CompZoom); switch (State->TransformsHotkeyInteract) { case sliding_position: { InteractProperty(0, File, State, Ended, MouseIncrement.x, Memory); InteractProperty(1, File, State, Ended, MouseIncrement.y, Memory); } break; case sliding_anchorpoint: { InteractProperty(2, File, State, Ended, MouseIncrement.x, Memory); InteractProperty(3, File, State, Ended, MouseIncrement.y, Memory); } break; case sliding_rotation: { InteractProperty(4, File, State, Ended, MouseIncrement.x / 10.0, Memory); } break; case sliding_scale: { InteractProperty(5, File, State, Ended, MouseIncrement.x / 200.0, Memory); } break; } } if (!ImGui::IsMouseDown(ImGuiMouseButton_Left)) { UI->DraggingLayerThreshold = 0; UI->DraggingTimelineThreshold = 0; UI->DraggingKeyframeThreshold = 0; } } static char ImGuiPrefs[] = "[Window][DockSpaceViewport_11111111]" "\nPos=0,0" "\nSize=3200,1800" "\nCollapsed=0" "\n" "\n[Window][Debug##Default]" "\nPos=60,60" "\nSize=400,400" "\nCollapsed=0" "\n" "\n[Window][Viewport]" "\nPos=528,0" "\nSize=2168,1171" "\nCollapsed=0" "\nDockId=0x00000005,0" "\n" "\n[Window][###Properties]" "\nPos=0,0" "\nSize=526,1171" "\nCollapsed=0" "\nDockId=0x00000003,0" "\n" "\n[Window][Timeline]" "\nPos=0,1173" "\nSize=3200,627" "\nCollapsed=0" "\nDockId=0x00000002,0" "\n" "\n[Window][Dear ImGui Demo]" "\nPos=1881,692" "\nSize=550,680" "\nCollapsed=0" "\n" "\n[Window][Files]" "\nPos=2698,0" "\nSize=502,913" "\nCollapsed=0" "\nDockId=0x00000007,0" "\n" "\n[Window][Effects list]" "\nPos=2698,915" "\nSize=502,256" "\nCollapsed=0" "\nDockId=0x00000008,0" "\n" "\n[Docking][Data]" "\nDockSpace ID=0x8B93E3BD Window=0xA787BDB4 Pos=0,0 Size=3200,1800 Split=Y Selected=0x13926F0B" "\n DockNode ID=0x00000001 Parent=0x8B93E3BD SizeRef=3200,1171 Split=X Selected=0x13926F0B" "\n DockNode ID=0x00000003 Parent=0x00000001 SizeRef=526,1171 Selected=0xDBB8CEFA" "\n DockNode ID=0x00000004 Parent=0x00000001 SizeRef=2672,1171 Split=X Selected=0x13926F0B" "\n DockNode ID=0x00000005 Parent=0x00000004 SizeRef=2115,1171 CentralNode=1 Selected=0x13926F0B" "\n DockNode ID=0x00000006 Parent=0x00000004 SizeRef=502,1171 Split=Y Selected=0x86FA2F90" "\n DockNode ID=0x00000007 Parent=0x00000006 SizeRef=502,913 Selected=0x86FA2F90" "\n DockNode ID=0x00000008 Parent=0x00000006 SizeRef=502,256 Selected=0x812F222D" "\n DockNode ID=0x00000002 Parent=0x8B93E3BD SizeRef=3200,627 HiddenTabBar=1 Selected=0x0F18B61B";