#if SPECIAL #include "main.h" #endif static void ImGui_Properties_RGBASwitch(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_Properties_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; State->Interact_Transform = {}; // 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_Properties_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() + ImVec2(Padding/6,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(); uint32 *SelectedChannel = (uint32 *)&Effect->ExtraData[5]; real32 *Num = &Effect->ExtraData[*SelectedChannel]; 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) { History_Entry_Commit(Memory, "Curves delete point"); property_channel *Property_X = Effect_Property(Memory, Effect, SortedPointStart[i]); History_Action_Swap(Memory, F_Properties, sizeof(Property_X->Identifier), &Property_X->Identifier); Property_X->Identifier = -1; property_channel *Property_Y = Effect_Property(Memory, Effect, SortedPointStart[i]+1); History_Action_Swap(Memory, F_Properties, sizeof(Property_Y->Identifier), &Property_Y->Identifier); Property_Y->Identifier = -1; History_Action_Swap(Memory, F_Effects, sizeof(Effect->ExtraData[*SelectedChannel]), &Effect->ExtraData[*SelectedChannel]); Effect->ExtraData[*SelectedChannel]--; History_Entry_End(Memory); } } if (ImGui::IsItemActive()) { if (io.MouseDelta.x || io.MouseDelta.y) { 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)); if (State->Interact_Active == interact_type_none) { State->Interact_Active = interact_type_slider_scrub; State->Interact_Offset[0] = Property_X->CurrentValue; State->Interact_Offset[1] = Property_Y->CurrentValue; } 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 (ImGui::IsItemDeactivated()) { if (State->Interact_Active == interact_type_slider_scrub) { History_Entry_Commit(Memory, "Curves change point pos"); property_channel *Property[2] = { Effect_Property(Memory, Effect, SortedPointStart[i]), Effect_Property(Memory, Effect, SortedPointStart[i]+1) }; for (int a = 0; a < 2; a++) { real32 Temp = Property[a]->CurrentValue; Property[a]->CurrentValue = State->Interact_Offset[a]; History_Action_Swap(Memory, F_Properties, sizeof(Property[a]->CurrentValue), &Property[a]->CurrentValue); Property[a]->CurrentValue = Temp; } History_Entry_End(Memory); State->Interact_Active = interact_type_none; } } 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; History_Entry_Commit(Memory, "Curves add point"); for (;;) { if (x > MAX_PROPERTIES_PER_EFFECT) break; property_channel *Property_X = Effect_Property(Memory, Effect, x); if (Property_X->Identifier == -1) { History_Action_Swap(Memory, F_Properties, sizeof(Property_X->CurrentValue), &Property_X->CurrentValue); Property_X->CurrentValue = Pos.x; History_Action_Swap(Memory, F_Properties, sizeof(Property_X->Identifier), &Property_X->Identifier); Property_X->Identifier = *SelectedChannel; property_channel *Property_Y = Effect_Property(Memory, Effect, x+1); Assert(Property_Y->Identifier == -1); History_Action_Swap(Memory, F_Properties, sizeof(Property_Y->CurrentValue), &Property_Y->CurrentValue); Property_Y->CurrentValue = Pos.y; History_Action_Swap(Memory, F_Properties, sizeof(Property_Y->Identifier), &Property_Y->Identifier); Property_Y->Identifier = *SelectedChannel; x = MAX_PROPERTIES_PER_EFFECT; } x++; } History_Action_Swap(Memory, F_Effects, sizeof(Effect->ExtraData[*SelectedChannel]), &Effect->ExtraData[*SelectedChannel]); Effect->ExtraData[*SelectedChannel]++; AddPoint = false; History_Entry_End(Memory); } // 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_Properties_RGBASwitch(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_array *SortedCompArray, sorted_layer_array *SortedLayerArray, sorted_property_array *SortedPropertyStart, uint16 *SortedKeyframeArray) { bool32 Display = 1; block_layer *Layer = NULL; sorted_layer_array *SortedLayer = NULL; if (State->MostRecentlySelectedLayer > -1) { Layer = (block_layer *)Memory_Block_AddressAtIndex(Memory, F_Layers, State->MostRecentlySelectedLayer, 0); sorted_comp_array SortedCompStart = SortedCompArray[Layer->Block_Composition_Index]; sorted_layer_array *SortedLayerStart = Sorted_GetLayerStart(SortedLayerArray, SortedCompArray, Layer->Block_Composition_Index); SortedLayer = &SortedLayerStart[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_array *InfoLocation = SortedPropertyStart + SortedLayer->SortedPropertyStart; uint16 *ArrayLocation = SortedKeyframeArray + SortedLayer->SortedKeyframeStart; int h = 0, c = 0, p = 0; property_channel *Property = NULL; block_effect *Effect = NULL; int MainProperties = AmountOf(Layer->Property); // don't display time offset for shape layers if (Layer->IsShapeLayer) { MainProperties -= 1; } while (Layer_LoopChannels(State, Memory, &InfoLocation, &ArrayLocation, Layer, &Property, &Effect, &h, &c, &p, MainProperties)) { ImGui::PushID(Property); if ((h - 1) < MainProperties && c == 0) { if (ImGui::Button("K")) { Property_AddKeyframe_AllSelected(File, State, Memory, F_Layers, SortedLayerArray, SortedCompArray, SortedKeyframeArray, h - 1, State->Frame_Current); } 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_Properties_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) { uint16 EffectIdx = Layer->Block_Effect_Index[h - AmountOf(Layer->Property)]; ImGui::PushID(h); #if DEBUG ImGui::Text("%s, %i", EffectHeader->Name, EffectIdx); #else ImGui::Text(EffectHeader->Name); #endif char T_VisibleIcon[4] = "o -"; char F_VisibleIcon[4] = "- -"; char *Icon = (Effect->IsToggled) ? T_VisibleIcon : F_VisibleIcon; if (ImGui::Button(Icon)) { History_Entry_Commit(Memory, "Toggle visibility"); History_Action_Swap(Memory, F_Effects, sizeof(Effect->IsToggled), &Effect->IsToggled); Effect->IsToggled ^= 1; History_Entry_End(Memory); State->UpdateFrame = true; } ImGui::SameLine(); if (ImGui::Button("/\\")) { if (EffectIdx != 0) { } } ImGui::SameLine(); if (ImGui::Button("\\/")) { } 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, ArrayLocation, Property, State->Frame_Current - Layer->Frame_Offset); } ImGui::SameLine(); ImGui::DragScalar(Name, ImGuiDataType_Float, &Property->CurrentValue, Property->ScrubVal, &Property->MinVal, &Property->MaxVal, "%f"); ImGui_Properties_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, ArrayLocation, Property, State->Frame_Current - Layer->Frame_Offset); } ImGui::SameLine(); ImGui::DragScalar(Name, ImGuiDataType_Float, &Property->CurrentValue, Property->ScrubVal, &Property->MinVal, &Property->MaxVal, "%f"); ImGui_Properties_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_Properties_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_Properties_RGBASwitch(State, Memory, io, (uint32 *)&Effect->ExtraData[0]); c = EffectHeader->Property_Count; } else { Assert(0); } } ImGui::PopID(); } 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->Interact_Transform = {}; State->UpdateFrame = true; } ImGui::DragScalar("Height", ImGuiDataType_U16, &Comp->Height); if (ImGui::IsItemActive()) { State->Interact_Transform = {}; State->UpdateFrame = true; } } if (Layer->IsShapeLayer) { shape_layer *Shape = &Layer->Shape; shape_options *Opt = &Layer->ShapeOpt; // TODO(fox): Combine with RGBA function? ImGui::DragScalar("Shape width", ImGuiDataType_Float, &Shape->Width); if (ImGui::IsItemActive()) { Memory->PurgeCache = true; State->UpdateFrame = true; } ImGui::DragScalar("Shape height ", ImGuiDataType_Float, &Shape->Height); if (ImGui::IsItemActive()) { Memory->PurgeCache = true; State->UpdateFrame = true; } char *Names[3] = { "Fill and stroke", "Fill only", "Stroke only" }; if (ImGui::BeginListBox("Shape render type")) { for (int i = 0; i < 3; i++) { if (ImGui::Selectable(Names[i], (Opt->Visibility == i))) { Opt->Visibility = i; Memory->PurgeCache = true; State->UpdateFrame = true; } } ImGui::EndListBox(); } char *Names2[3] = { "Butt", "Round", "Square" }; nvg_line_cap CapVals[3] = { NVG_BUTT, NVG_ROUND, NVG_SQUARE }; if (ImGui::BeginListBox("Path cap type")) { for (int i = 0; i < 2; i++) { if (ImGui::Selectable(Names2[i], (Opt->LineCapType == CapVals[i]))) { Opt->LineCapType = CapVals[i]; Memory->PurgeCache = true; State->UpdateFrame = true; } } ImGui::EndListBox(); } char *Names3[3] = { "Miter", "Round", "Bevel" }; nvg_line_cap JoinVals[3] = { NVG_MITER, NVG_ROUND, NVG_BEVEL }; if (ImGui::BeginListBox("Path join type")) { for (int i = 0; i < 2; i++) { if (ImGui::Selectable(Names3[i], (Opt->LineJoinType == JoinVals[i]))) { Opt->LineJoinType = JoinVals[i]; Memory->PurgeCache = true; State->UpdateFrame = true; } } ImGui::EndListBox(); } ImGui::ColorEdit4("Fill color", (real32 *)&Opt->FillCol, ImGuiColorEditFlags_Float); if (ImGui::IsItemActive()) { Memory->PurgeCache = true; State->UpdateFrame = true; } ImGui::ColorEdit4("Stroke color", (real32 *)&Opt->StrokeCol, ImGuiColorEditFlags_Float); if (ImGui::IsItemActive()) { Memory->PurgeCache = true; State->UpdateFrame = true; } real32 StrokeWidthMin = 0; real32 StrokeWidthMax = 1024; ImGui::DragScalar("Stroke width", ImGuiDataType_Float, &Opt->StrokeWidth, 1, &StrokeWidthMin, &StrokeWidthMax, "%.3f"); if (ImGui::IsItemActive()) { Memory->PurgeCache = true; State->UpdateFrame = true; } real32 RoundnessMin = 0; real32 RoundnessMax = 1024; ImGui::DragScalar("Roundness", ImGuiDataType_Float, &Opt->Roundness, 1, &RoundnessMin, &RoundnessMax, "%.3f"); if (ImGui::IsItemActive()) { Memory->PurgeCache = true; State->UpdateFrame = true; } // State->UpdateFrame = true; } ImGui::End(); } else { ImGui::Begin("Properties: empty###Properties"); if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) State->FocusedWindow = focus_properties; ImGui::End(); } }