#include "imgui.h" #if SPECIAL #include "main.h" #endif static void ImGui_Viewport_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_Viewport_ShapeUI(project_state *State, memory *Memory, ui *UI, ImGuiIO &io, block_layer *Layer, int Width, int Height, shape_layer *Shape, block_composition *MainComp, ImDrawList *draw_list) { memory_table_list TableName = (Layer == NULL) ? F_File : F_Layers; if (Shape->Point_Count) { uint32 wcol = IM_COL32(00, 00, 80, 255); v2 CompUV = State->LastClickedPoint; ImVec2 ScreenPoint = ImVec2(UI->CompPos.x + CompUV.x * UI->CompZoom.x, UI->CompPos.y + CompUV.y * UI->CompZoom.y); bezier_point *Point_0 = Bezier_LookupAddress(Memory, Shape->Block_Bezier_Index, Shape->Point_Count - 1, 1); bezier_point *Point_1 = Bezier_LookupAddress(Memory, Shape->Block_Bezier_Index, 0, 1); Assert(Shape->Contiguous); int i = 0; while (i < Shape->Point_Count) { ImVec2 CompDimensions = ImVec2(MainComp->Width, MainComp->Height); ImVec2 ScreenPoint_0[3]; ImVec2 ScreenPoint_1[3]; if (ImGui::IsKeyPressed(ImGuiKey_Escape)) { Point_1->IsSelected = false; } for (int i = 0; i < 3; i++) { ImVec2 Point = (i == 0) ? IV2(Point_0->Pos[0]) : IV2(Point_0->Pos[0] + Point_0->Pos[i]); if (Layer != NULL) { layer_transforms T = Layer_GetTransforms(Layer); Point = Point * ImVec2(Shape->Width, Shape->Height); Point = IV2(TransformPoint(T, Width, Height, *(v2 *)&Point)); } if (State->Interact_Active == interact_type_keyframe_move && Point_0->IsSelected) { Point.x += State->Interact_Offset[0]; Point.y += State->Interact_Offset[1]; } ImVec2 Point_Ratio = Point / CompDimensions; ScreenPoint_0[i] = UI->CompPos + Point_Ratio * UI->CompZoom; } for (int i = 0; i < 3; i++) { ImVec2 Point = (i == 0) ? IV2(Point_1->Pos[0]) : IV2(Point_1->Pos[0] + Point_1->Pos[i]); if (Layer != NULL) { layer_transforms T = Layer_GetTransforms(Layer); Point = Point * ImVec2(Shape->Width, Shape->Height); Point = IV2(TransformPoint(T, Width, Height, *(v2 *)&Point)); } if (State->Interact_Active == interact_type_keyframe_move && Point_1->IsSelected) { Point.x += State->Interact_Offset[0]; Point.y += State->Interact_Offset[1]; } ImVec2 Point_Ratio = Point / CompDimensions; ScreenPoint_1[i] = UI->CompPos + Point_Ratio * UI->CompZoom; } if (Point_1->Type == interpolation_type_bezier) draw_list->AddLine(ScreenPoint_0[1], ScreenPoint_0[2], wcol, 2.0f); ImU32 PointCol = (Point_1->IsSelected) ? ImColor(0.8f, 0.5f, 0.0f, 1.0f) : ImColor(0.1f, 0.1f, 0.1f, 0.2f); ImGui::PushID(i); for (int a = 0; a < 3; a++) { ImGui::PushID(a); real32 FontSize = ImGui::GetFontSize(); ImGui::SetCursorScreenPos(ScreenPoint_1[a] - ImVec2(FontSize / 2, FontSize / 2)); ImGui::InvisibleButton("##bezhandle", ImVec2(FontSize, FontSize), 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) { if (!io.KeyShift) { for (int p = 0; p < Shape->Point_Count; p++) { bezier_point *Point = Bezier_LookupAddress(Memory, Shape->Block_Bezier_Index, p, 1); Point->IsSelected = 0; } } Point_1->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; } else { Assert(State->Interact_Active == interact_type_keyframe_move); ImVec2 DragDelta = io.MousePos - ImVec2(State->Interact_Offset[2], State->Interact_Offset[3]); if (io.MouseDelta.x || io.MouseDelta.y) { if (State->Interact_Active == interact_type_keyframe_move) { State->Interact_Offset[0] = DragDelta.x * (MainComp->Width / UI->CompZoom.x); State->Interact_Offset[1] = DragDelta.y * (MainComp->Height / UI->CompZoom.y); } State->UpdateFrame = true; } } } if (IsItemDeactivated) { if (Layer == NULL && a == 0 && i == 0 && Shape->IsClosed == false) { History_Entry_Commit(Memory, "Close shape"); History_Action_Swap(Memory, TableName, sizeof(Shape->IsClosed), &Shape->IsClosed); Shape->IsClosed = true; History_Entry_End(Memory); } else if (Layer != NULL) { History_Entry_Commit(Memory, "Move point"); layer_transforms T = Layer_GetTransforms(Layer); for (int p = 0; p < Shape->Point_Count; p++) { bezier_point *Point = Bezier_LookupAddress(Memory, Shape->Block_Bezier_Index, p, 1); if (Point->IsSelected) { v2 *Pos = &Point->Pos[Point->IsSelected-1]; History_Action_Swap(Memory, F_Bezier, sizeof(*Pos), Pos); *Pos = TransformPoint(T, Shape->Width, Shape->Height, *Pos); Pos->x += State->Interact_Offset[0] / Shape->Width; Pos->y += State->Interact_Offset[1] / Shape->Height; *Pos = T_CompPosToLayerPos(T, MainComp->Width, MainComp->Height, Shape->Width, Shape->Height, Pos->x, Pos->y); } } History_Entry_End(Memory); State->Interact_Active = interact_type_none; State->Interact_Offset[0] = 0; State->Interact_Offset[1] = 0; } } draw_list->AddNgon(ScreenPoint_1[a], 4, PointCol, 8, 5.0f); draw_list->AddNgon(ScreenPoint_1[a], 4, 0xFFFFFFFF, 8, 8.0f); ImGui::PopID(); } ImGui::PopID(); // draw_list->AddBezierCubic(ScreenPoint_0, ScreenPoint_R_0, ScreenPoint_L_1, ScreenPoint_1, IM_COL32(10, 10, 10, 255), 1.0f, 0); // draw_list->AddLine(ScreenPoint_0, ScreenPoint_1, wcol, 2.0f); i++; if (i < Shape->Point_Count) { Point_0 = Bezier_LookupAddress(Memory, Shape->Block_Bezier_Index, i-1, 1); Point_1 = Bezier_LookupAddress(Memory, Shape->Block_Bezier_Index, i, 1); } } } } static void ImGui_Viewport_BrushUI(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.KeyCtrl) { // ImGui::SetCursorScreenPos(State->Brush.UIPos); // char buf[256]; // sprintf(buf, "RGBA: %.1f, %.1f, %.1f, %.1f", State->Brush.Size, State->Brush.Hardness); // } 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 real32 T_AreaAtAngle(int PointCount, v2 *PointData, v2 Center, real32 Rotation, v2 *CurrentMin, v2 *CurrentMax) { v2 Min = V2(10000, 10000), Max = V2(-10000, -10000); real32 Rad = (Rotation * (PI / 180)); for (int i = 0; i < PointCount; i++) { v2 Pos = PointData[i]; v2 XAxis = (Pos.x - Center.x) * V2(cos(Rad), sin(Rad)); v2 YAxis = (Pos.y - Center.y) * V2(sin(Rad), -cos(Rad)); Pos = XAxis + YAxis; if (Pos.x < Min.x) { Min.x = Pos.x; } if (Pos.y < Min.y) { Min.y = Pos.y; } if (Pos.x > Max.x) { Max.x = Pos.x; } if (Pos.y > Max.y) { Max.y = Pos.y; } } v2 Dimensions = Max - Min; *CurrentMin = Min + Center; *CurrentMax = Max + Center; return Dimensions.x * Dimensions.y; } static real32 T_FindBestFit(int PointCount, v2 *PointData, v2 *Center, v2 *NewCenter, v2 *BestMin, v2 *BestMax, real32 *BestRadians) { v2 Min = V2(10000, 10000), Max = V2(-10000, -10000); for (int i = 0; i < PointCount; i++) { v2 Pos = PointData[i]; if (Pos.x < Min.x) { Min.x = Pos.x; } if (Pos.y < Min.y) { Min.y = Pos.y; } if (Pos.x > Max.x) { Max.x = Pos.x; } if (Pos.y > Max.y) { Max.y = Pos.y; } } v2 Dimensions = Max - Min; *Center = Min + Dimensions/2; real32 BestArea = Dimensions.x * Dimensions.y; *BestMin = Min; *BestMax = Max; *BestRadians = 0; // TODO(fox): Make more efficient and more precise. for (real32 i = 0; i < 90; i+= 1) { v2 NewMin = {}, NewMax = {}; real32 NewArea = T_AreaAtAngle(PointCount, PointData, *Center, i, &NewMin, &NewMax); if (NewArea < BestArea) { BestArea = NewArea; v2 NewDim = NewMax - NewMin; // *BestMin = Center - (NewDim/2); // *BestMax = Center + (NewDim/2); *NewCenter = Min + (NewDim / 2); *BestMin = NewMin; *BestMax = NewMax; *BestRadians = (real32)i * (PI / 180); } } int a = 0; return 0; } static void LayerIterate(project_state *State, memory *Memory, uint32 CompIndex, layer_transforms ExtraT, v2 Center, sorted_comp_array *SortedCompArray, sorted_layer_array *SortedLayerArray); static void ImGui_Viewport_TransformUI2(project_data *File, project_state *State, memory *Memory, ui *UI, ImGuiIO &io, ImDrawList *draw_list, int *PointCount, v2 *PointData, ImVec2 ViewportMin, uint32 CompWidth, uint32 CompHeight, sorted_comp_array *SortedCompArray, sorted_layer_array *SortedLayerArray, uint16 *SortedKeyframeArray) { interact_transform *Interact = &State->Interact_Transform; v2 InteractMin = {}; v2 InteractMax = {}; real32 Rad = 0; real32 Rad2 = 0; real32 Scale = 1; if (Interact->Min.x == 0.0f) { if (*PointCount > 0) T_FindBestFit(*PointCount, PointData, &Interact->OGCenter, &Interact->NewCenter, &Interact->Min, &Interact->Max, &Interact->RadianOffset); // Interact->RadianOffset += Interact->Scale = 1.0f; } if (Interact->Scale != 0.0f) { InteractMin = Interact->Min; InteractMax = Interact->Max; Rad = Interact->RadianOffset; Rad2 = Interact->Radians; Scale = Interact->Scale; } real32 Angle = Interact->RadianOffset / (PI / 180); // DebugWatchVar("Rad", &Interact->Radians, d_float); // DebugWatchVar("bestangle", &Angle, d_float); v2 BoxLength = InteractMax - InteractMin; v2 Center = Interact->OGCenter; v2 PosL[8] = {}; v2 PosS[8] = { InteractMin, V2(InteractMax.x, InteractMin.y), InteractMax, V2(InteractMin.x, InteractMax.y), InteractMin + V2(BoxLength.x * 0.5, 0), InteractMin + V2(0, BoxLength.y * 0.5), InteractMax - V2(BoxLength.x * 0.5, 0), InteractMax - V2(0, BoxLength.y * 0.5) }; v2 LocalCenter = InteractMax - (BoxLength/2); // NOTE(fox): There's two transformations we need to do: the best-fit // bounding box transformed from the center of the 0 degree bounding box, // and then the Interactive rotation based on the center of the best-fit // bounding box _at its new position_. v2 NewCenter = {}; { real32 Point0X = LocalCenter.x - Center.x; real32 Point0Y = LocalCenter.y - Center.y; v2 XAxis = (Point0X * 1.0f)*V2(cos(Rad), sin(Rad)); v2 YAxis = (Point0Y * 1.0f)*V2(sin(Rad), -cos(Rad)); v2 Pos = XAxis + YAxis; NewCenter.x = Center.x + Pos.x; NewCenter.y = Center.y + Pos.y; } for (int i = 0; i < 8; i++) { real32 Point0X = PosS[i].x - Center.x; real32 Point0Y = PosS[i].y - Center.y; v2 XAxis = (Point0X * 1.0f)*V2(cos(Rad), sin(Rad)); v2 YAxis = (Point0Y * 1.0f)*V2(sin(Rad), -cos(Rad)); v2 Pos = XAxis + YAxis; PosL[i].x = Center.x + Pos.x; PosL[i].y = Center.y + Pos.y; { Point0X = PosL[i].x - NewCenter.x; Point0Y = PosL[i].y - NewCenter.y; XAxis = (Point0X * Scale)*V2(cos(Rad2), sin(Rad2)); YAxis = (Point0Y * -Scale)*V2(sin(Rad2), -cos(Rad2)); v2 NewPos = XAxis + YAxis; PosL[i].x = NewCenter.x + NewPos.x; PosL[i].y = NewCenter.y + NewPos.y; PosL[i].x += Interact->Position.x; PosL[i].y += Interact->Position.y; } } ImVec2 CompScale = UI->CompZoom / ImVec2(CompWidth, CompHeight); ImVec2 P[4]; P[0] = IV2(PosL[0])*CompScale + UI->CompPos; P[1] = IV2(PosL[1])*CompScale + UI->CompPos; P[2] = IV2(PosL[2])*CompScale + UI->CompPos; P[3] = IV2(PosL[3])*CompScale + UI->CompPos; ImVec2 Mid_P[4]; Mid_P[0] = IV2(PosL[4])*CompScale + UI->CompPos; Mid_P[1] = IV2(PosL[5])*CompScale + UI->CompPos; Mid_P[2] = IV2(PosL[6])*CompScale + UI->CompPos; Mid_P[3] = IV2(PosL[7])*CompScale + UI->CompPos; ImU32 wcol = IM_COL32(0, 0, 255, 255); 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); draw_list->AddLine(P[0], P[2], wcol, 1.0f); draw_list->AddLine(P[1], P[3], wcol, 1.0f); layer_transforms BoxTransforms = { NewCenter.x, NewCenter.y, 0.5, 0.5, (real32)(Rad / (PI / 180)), 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 BoxScreenDimensions = IV2(BoxLength)*CompScale; real32 HandleSize = ImGui::GetFontSize() * 2; if (BoxScreenDimensions.x < (HandleSize*2) || BoxScreenDimensions.y < (HandleSize*2)) HandleSize *= 0.5; ImVec2 ScaleHandleSize(HandleSize, HandleSize); bool32 OtherActions = ImGui::IsKeyDown(ImGuiKey_Z); uint32 Col[4] = { IM_COL32(255, 0, 0, 255), IM_COL32(0, 255, 0, 255), IM_COL32(0, 0, 255, 255), IM_COL32(255, 255, 255, 255) }; // TODO(fox): Combine halfway and corner scale code? for (int i = 0; i < 4; i++) { ImGui::SetCursorScreenPos(Mid_P[i] - ScaleHandleSize/2); ImGui::PushID(i); ImGui::InvisibleButton("##ScaleMids", ScaleHandleSize); ImGui_DrawCenteredRect(draw_list, Mid_P[i], 12, IM_COL32(20, 20, 20, 255)); ImGui_DrawCenteredRect(draw_list, Mid_P[i], 10, IM_COL32(20, 20, 220, 255)); ImGui_DrawCenteredRect(draw_list, Mid_P[i], 6, Col[i]); if (ImGui::GetMouseCursor() != ImGuiMouseCursor_None && ImGui::IsItemHovered() && !OtherActions) { ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeNS); } if (ImGui::IsItemActivated() && !OtherActions) { State->Interact_Active = interact_type_viewport_transform_gizmo; State->InteractTransformMode = 1; } if (State->InteractTransformMode == 1 && ImGui::IsItemActive()) { // real32 NegRad = Rad - (90 * (PI / 180)); ImVec2 LengthVec = (io.MousePos - io.MouseClickedPos[0]) / CompScale; real32 NegRad = -Rad; v2 XAxis = (LengthVec.x * 1.0f)*V2(cos(NegRad), sin(NegRad)); v2 YAxis = (LengthVec.y * -1.0f)*V2(sin(NegRad), -cos(NegRad)); v2 UnrotatedLengthVec = XAxis + YAxis; v2 Dir = {}; if (i == 0) { Dir = V2(-1, 1); } else if (i == 1) { Dir = V2(1, 1); } else if (i == 2) { Dir = V2(1, -1); } else if (i == 3) { Dir = V2(-1, -1); } else { Assert(0); } if (i == 1 || i == 3) { real32 Length = UnrotatedLengthVec.x * Dir.x; real32 BoxAxis = BoxLength.x; Interact->Scale = 1.0f + -Length / BoxAxis; v2 MovePos = V2((Length / 2), 0); v2 XAxis = (MovePos.x * Dir.x)*V2(cos(Rad), sin(Rad)); v2 YAxis = (MovePos.y * Dir.y)*V2(sin(Rad), -cos(Rad)); v2 Pos = XAxis + YAxis; if (!io.KeyCtrl) { Interact->Position.x = Pos.x; Interact->Position.y = Pos.y; } else { Interact->Position.x = 0; Interact->Position.y = 0; } } else { real32 Length = UnrotatedLengthVec.y * Dir.x; real32 BoxAxis = BoxLength.y; Interact->Scale = 1.0f + -Length / BoxAxis; v2 MovePos = V2(0, (Length / 2)); v2 XAxis = (MovePos.x * Dir.x)*V2(cos(Rad), sin(Rad)); v2 YAxis = (MovePos.y * Dir.y)*V2(sin(Rad), -cos(Rad)); v2 Pos = XAxis + YAxis; if (!io.KeyCtrl) { Interact->Position.x = Pos.x; Interact->Position.y = Pos.y; } else { Interact->Position.x = 0; Interact->Position.y = 0; } } } 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::InvisibleButton("##ScaleRotateCorners", ScaleHandleSize); ImGui_DrawCenteredRect(draw_list, P[i], 12, IM_COL32(80, 80, 80, 255)); ImGui_DrawCenteredRect(draw_list, P[i], 10, IM_COL32(20, 20, 220, 255)); ImGui_DrawCenteredRect(draw_list, P[i], 6, IM_COL32(255, 255, 225, 50)); if (ImGui::GetMouseCursor() != ImGuiMouseCursor_None && ImGui::IsItemHovered() && !OtherActions) { if (InBounds) { ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeNESW); } else { ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeAll); } } if (ImGui::IsItemActivated() && !OtherActions) { if (InBounds) State->InteractTransformMode = 1; else State->InteractTransformMode = 2; State->Interact_Active = interact_type_viewport_transform_gizmo; } // Scale part if (State->InteractTransformMode == 1 && ImGui::IsItemActive()) { ImVec2 LengthVec = (io.MousePos - io.MouseClickedPos[0]) / CompScale; real32 NegRad = -Rad; v2 XAxis = (LengthVec.x * 1.0f)*V2(cos(NegRad), sin(NegRad)); v2 YAxis = (LengthVec.y * -1.0f)*V2(sin(NegRad), -cos(NegRad)); v2 UnrotatedLengthVec = XAxis + YAxis; v2 Dir = {}; if (i == 0) { Dir = V2(-1, 1); } else if (i == 1) { Dir = V2(1, 1); } else if (i == 2) { Dir = V2(1, -1); } else if (i == 3) { Dir = V2(-1, -1); } else { Assert(0); } // Determine whether to use the unrotated X or Y axis based on // where the mouse is. Makes the box corner always 'stick' to the mouse. v2 Vector0 = V2(BoxLength.x, BoxLength.y); real32 Dot = Inner(V2(-Vector0.y * Dir.x, Vector0.x * Dir.y), UnrotatedLengthVec); bool32 Test = (Dot < 0.0f) ? 0 : 1; if (!Test) { real32 Length = UnrotatedLengthVec.x * Dir.x; real32 BoxAxis = BoxLength.x; Interact->Scale = 1.0f + Length / BoxAxis; v2 MovePos = V2((Length / 2), -(Length / 2) * (BoxLength.y / BoxLength.x)); v2 XAxis = (MovePos.x * Dir.x)*V2(cos(Rad), sin(Rad)); v2 YAxis = (MovePos.y * Dir.y)*V2(sin(Rad), -cos(Rad)); v2 Pos = XAxis + YAxis; if (!io.KeyCtrl) { Interact->Position.x = Pos.x; Interact->Position.y = Pos.y; } else { Interact->Position.x = 0; Interact->Position.y = 0; } } else { real32 Mult = (i == 1 || i == 3) ? -1 : 1; real32 Length = UnrotatedLengthVec.y * Mult * Dir.x; real32 BoxAxis = BoxLength.y; Interact->Scale = 1.0f + -Length / BoxAxis; v2 MovePos = V2(-(Length / 2) * (BoxLength.x / BoxLength.y), (Length / 2)); v2 XAxis = (MovePos.x * Dir.x)*V2(cos(Rad), sin(Rad)); v2 YAxis = (MovePos.y * Dir.y)*V2(sin(Rad), -cos(Rad)); v2 Pos = XAxis + YAxis; if (!io.KeyCtrl) { Interact->Position.x = Pos.x; Interact->Position.y = Pos.y; } else { Interact->Position.x = 0; Interact->Position.y = 0; } } } // Rotation part if (State->InteractTransformMode == 2 && ImGui::IsItemActive()) { v2 Vector0 = V2(io.MouseClickedPos[0] - UI->CompPos)/V2(CompScale) - InteractMin - (BoxLength/2); v2 Vector1 = V2(io.MousePos - UI->CompPos)/V2(CompScale) - InteractMin - (BoxLength/2); Vector0 = Vector0 * V2(1, -1); Vector1 = Vector1 * V2(1, -1); real32 Length0 = Length(Vector0); real32 Length1 = Length(Vector1); real32 Direction = (Inner(V2(Vector0.y, -Vector0.x), Vector1) < 0.0f) ? -1 : 1; real32 MouseRadians = acosf(Inner(Vector0, Vector1) / (Length0 * Length1)); if (isnan(MouseRadians)) MouseRadians = 0.0f; Interact->Radians = Direction * MouseRadians; if (io.KeyShift) { real32 Angle = (Interact->Radians / (PI / 180)); Interact->Radians = (int)(Angle / 15.0f) * 15.0f * (PI / 180); } } ImGui::PopID(); } if (InBounds && !io.KeyAlt && !ImGui::IsKeyDown(ImGuiKey_Z) && !ImGui::IsKeyDown(ImGuiKey_ModShift)) { v2 MouseLocalPos = ImGui_ScreenPointToCompUV(ViewportMin, UI->CompPos, UI->CompZoom, io.MousePos) * V2(CompWidth, CompHeight); layer_transforms T = {}; T.scale = 1.0f; bool32 LayerHovered = Layer_TestForPoint(Memory, State, UI, SortedCompArray, SortedLayerArray, File->PrincipalCompIndex, MouseLocalPos, T); if (LayerHovered) { if (ImGui::GetMouseCursor() == ImGuiMouseCursor_None) { ImGui::SetMouseCursor(ImGuiMouseCursor_Hand); } ImGui::SetCursorScreenPos(io.MousePos - ScaleHandleSize/2); ImGui::InvisibleButton("##mover", ScaleHandleSize); if (!State->InteractTransformMode && ImGui::IsItemActivated() && !OtherActions) { State->Interact_Active = interact_type_viewport_transform_gizmo; 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)) { // A mouse release without any delta usually means the user wants // to initiate a layer select. ImVec2 LengthVec = (io.MousePos - io.MouseClickedPos[0]); if (fabsf(LengthVec.x) + fabsf(LengthVec.y) > 2) { Assert(State->Interact_Active == interact_type_viewport_transform_gizmo); // NOTE(fox): We have to go through these sorted instead of a // Block_Loop since Interact_Transform is only saving the // mouse's transform. History_Entry_Commit(Memory, "Transform layers"); interact_transform Interact = State->Interact_Transform; v2 BoxLength = Interact.Max - Interact.Min; v2 Center = Interact.Max - (BoxLength/2); if (Interact.RadianOffset != 0.0f) { v2 LocalCenter = Interact.NewCenter; real32 Rad = Interact.RadianOffset; real32 Point0X = Center.x - Interact.OGCenter.x; real32 Point0Y = Center.y - Interact.OGCenter.y; v2 XAxis = (Point0X * 1.0f)*V2(cos(Rad), sin(Rad)); v2 YAxis = (Point0Y * 1.0f)*V2(sin(Rad), -cos(Rad)); Center = Interact.OGCenter + XAxis + YAxis; } layer_transforms T = {}; T.scale = 1.0f; LayerIterate(State, Memory, File->PrincipalCompIndex, T, Center, SortedCompArray, SortedLayerArray); History_Entry_End(Memory); State->UpdateFrame = true; State->UncommitedKeyframe = 1; } else { // int32 Selection = Layer_TestSelection(Memory, State, UI, SortedCompArray, SortedLayerArray, File->PrincipalCompIndex, io.KeyShift); layer_transforms T = {}; T.scale = 1.0f; v2 CompPoint = ImGui_ScreenPointToCompUV(ViewportMin, UI->CompPos, UI->CompZoom, io.MousePos) * V2(CompWidth, CompHeight); int32 Selection = LayerIterate_TestSelection(State, Memory, File->PrincipalCompIndex, T, CompPoint, SortedCompArray, SortedLayerArray, io.KeyShift); Assert(State->Tool == tool_default); if (!io.KeyShift) { Layer_DeselectAll(File, State, Memory, 1); State->Interact_Transform = {}; } if (Selection > 0) Layer_Select(Memory, State, Selection); } State->Interact_Active = interact_type_none; State->Interact_Transform = {}; State->InteractTransformMode = 0; State->Interact_Modifier = 0; } } if (InBounds == true) { // ImGui::PopStyleColor(); } } static void ImGui_Viewport_SelectedLayerUI(project_state *State, memory *Memory, ui *UI, ImGuiIO &io, ImDrawList *draw_list, block_composition *MainComp, uint32 CompIndex, layer_transforms ExtraT, int *PointCount, v2 *PointData, sorted_comp_array *SortedCompArray, sorted_layer_array *SortedLayerArray) { block_composition *Comp = (block_composition *)Memory_Block_AddressAtIndex(Memory, F_Precomps, CompIndex); sorted_comp_array *SortedCompStart = &SortedCompArray[CompIndex]; sorted_layer_array *SortedLayerStart = Sorted_GetLayerStart(SortedLayerArray, SortedCompArray, CompIndex); int LayerCount = SortedCompStart->LayerCount + SortedCompStart->FakeLayerCount; for (int i = 0; i < LayerCount; i++) { sorted_layer_array SortEntry = SortedLayerStart[i]; uint32 Index_Physical = SortEntry.Block_Layer_Index; block_layer *Layer = (block_layer *)Memory_Block_AddressAtIndex(Memory, F_Layers, Index_Physical); if (Layer->IsPrecomp) { layer_transforms NewExtraT = Layer_GetTransforms(Layer); if (ExtraT.scale != 0) { NewExtraT = Transform_Add(NewExtraT, ExtraT, Comp->Width, Comp->Height); } ImGui_Viewport_SelectedLayerUI(State, Memory, UI, io, draw_list, MainComp, Layer->Block_Source_Index, NewExtraT, PointCount, PointData, SortedCompArray, SortedLayerArray); } if (Layer->IsSelected & 0x01) { uint32 Width = 0, Height = 0; void *Data; uint32 NumberOfVerts; if (Layer->IsPrecomp) { block_composition *Comp = (block_composition *)Memory_Block_AddressAtIndex(Memory, F_Precomps, Layer->Block_Source_Index); Width = Comp->Width; Height = Comp->Height; } else if (Layer->IsShapeLayer) { #if 0 block_bezier *Bezier = (block_bezier *)Memory_Block_AddressAtIndex(Memory, F_Bezier, Layer->Shape.Block_Bezier_Index[0]); Data = Memory_PushScratch(Memory, sizeof(nvg_point) * 128); int L_Width = 0, L_Height = 0; Assert(Layer->Shape.Contiguous); v2 Min = {}, Max = {}; layer_transforms T = Layer_GetTransforms(Layer); NumberOfVerts = NVG_FlattenPath(Memory, &Layer->Shape, (nvg_point *)Data, State, T, Layer->Shape.Width, Layer->Shape.Height, Width, Height, 1, &Min, &Max); #endif Width = Layer->Shape.Width; Height = Layer->Shape.Height; } else { block_source *Source = (block_source *)Memory_Block_AddressAtIndex(Memory, F_Sources, Layer->Block_Source_Index); Width = Source->Width; Height = Source->Height; } if (State->Interact_Active == interact_type_viewport_slide) { real32 Threshold = 10; v2 CompUV = V2(State->Interact_Offset[0], State->Interact_Offset[1]); v2 CompPos = CompUV * V2(Comp->Width, Comp->Height); v2 LayerPos = V2(Layer->x.CurrentValue, Layer->y.CurrentValue); v2 Difference = V2(fabs(CompPos.x - LayerPos.x), fabs(CompPos.y - LayerPos.y)); printf("Diff: %.1f, %.1f\n", Difference.x, Difference.y); if (Difference.x < Threshold && Difference.y < Threshold) { Assert(0); } } layer_transforms T = Layer_GetTransforms(Layer); if ((State->Interact_Active == interact_type_viewport_transform || State->Interact_Active == interact_type_viewport_transform_gizmo) && Layer->IsSelected & 0x01) { Transform_ApplyInteractive(State->Interact_Transform, &T.x, &T.y, &T.rotation, &T.scale); } if (State->Interact_Active == interact_type_viewport_duplicate && SortEntry.IsFake) { Assert(Layer->IsSelected & 0x01); T.x += State->Interact_Offset[0]; T.y += State->Interact_Offset[1]; } if (ExtraT.scale != 0) { T = Transform_Add(T, ExtraT, Comp->Width, Comp->Height); } if (Layer->IsShapeLayer && State->Tool == tool_default_pointmove && (State->Interact_Active == interact_type_none || State->Interact_Active == interact_type_keyframe_move) ) { ImGui_Viewport_ShapeUI(State, Memory, UI, io, Layer, Width, Height, &Layer->Shape, MainComp, draw_list); // point visualization #if 0 void *Data2 = Memory_PushScratch(Memory, sizeof(real32) * 3 * 256); uint32 GL_PointCount = NVG_ExpandStroke(Memory, NumberOfVerts, Layer->Shape.Opt.StrokeWidth, Layer->Shape.Opt.LineCapType, Layer->Shape.Opt.LineJoinType, Layer->Shape.IsClosed, (nvg_point *)Data, (real32 *)Data2); for (int i = 0; i < NumberOfVerts; i++) { nvg_point Point = *((nvg_point *)Data + i); v2 PointPos = V2(Point.x, Point.y); v2 Pos = TransformPoint(T, Width, Height, PointPos); v2 CompUV = Pos / V2(MainComp->Width, MainComp->Height); ImVec2 ScreenPoint = ImVec2(UI->CompPos.x + CompUV.x * UI->CompZoom.x, UI->CompPos.y + CompUV.y * UI->CompZoom.y); draw_list->AddNgon(ScreenPoint, 2, IM_COL32(00, 00, 80, 255), 8, 2.0f); } for (int i = 0; i < GL_PointCount; i++) { v2 PointPos = *((v2 *)Data2 + i*2); v2 Pos = TransformPoint(T, Width, Height, PointPos); v2 CompUV = Pos / V2(MainComp->Width, MainComp->Height); ImVec2 ScreenPoint = ImVec2(UI->CompPos.x + CompUV.x * UI->CompZoom.x, UI->CompPos.y + CompUV.y * UI->CompZoom.y); draw_list->AddNgon(ScreenPoint, 2, IM_COL32(80, 80, 10, 255), 8, 2.0f); } Memory_PopScratch(Memory, sizeof(real32) * 3 * 256); Memory_PopScratch(Memory, sizeof(nvg_point) * 128); #endif } v2 BoundingPoint[5] = { V2(Width*Layer->ax.CurrentValue, Height*Layer->ay.CurrentValue), V2(0, 0), V2(Width, 0), V2(0, Height), V2(Width, Height) }; v2 NewPos[5]; for (int i = 0; i < 5; i++) { NewPos[i] = TransformPoint(T, Width, Height, BoundingPoint[i]); } for (int i = 0; i < 4; i++) { v2 Pos = NewPos[i+1]; PointData[*PointCount] = Pos; *PointCount += 1; } 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); } ImU32 wcol = IM_COL32(200, 200, 255, 255); if (State->Interact_Active != interact_type_viewport_transform_gizmo) { if (State->Tool != tool_brush) { ImU32 wcol2 = IM_COL32(10, 10, 10, 128); draw_list->AddNgon(ScreenPoint[0], 10, wcol2, 8, 9.0f); draw_list->AddNgon(ScreenPoint[0], 10, wcol, 8, 5.0f); } draw_list->AddLine(ScreenPoint[1], ScreenPoint[2], wcol, 1.0f); draw_list->AddLine(ScreenPoint[2], ScreenPoint[4], wcol, 1.0f); draw_list->AddLine(ScreenPoint[1], ScreenPoint[3], wcol, 1.0f); draw_list->AddLine(ScreenPoint[3], ScreenPoint[4], wcol, 1.0f); } } } } static void ImGui_Viewport(project_data *File, project_state *State, ui *UI, memory *Memory, ImGuiIO io, GLuint textureID, sorted_comp_array *SortedCompArray, sorted_layer_array *SortedLayerArray, sorted_property_array *SortedPropertyStart, uint16 *SortedKeyframeArray) { bool open = true; ImGui::Begin("Viewport", &open, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse); // DebugWatchVar("Count: ", &State->PreviousSelectionCount, d_int); if (ImGui::IsWindowHovered(ImGuiFocusedFlags_ChildWindows)) { State->FocusedWindow = focus_viewport; // normal people won't use this... if (ImGui::IsKeyPressed(ImGuiKey_Escape)) { Layer_DeselectAll(File, State, Memory); } } 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 (ViewportScale.x < 50 || ViewportScale.y < 50) { ImGui::End(); return; } 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(); uint32 BGCOL = IM_COL32(50, 50, 50, 255); uint32 OUTLINE = IM_COL32(255, 255, 255, 255); if (UI->Mode == 1) { // BGCOL = IM_COL32(0, 0, 0, 255); OUTLINE = IM_COL32(255,255,255, 255); } draw_list->AddRectFilled(ViewportMin, ViewportMax, BGCOL); // Actual composition texture draw_list->PushClipRect(ViewportMin, ViewportMax, true); if (UI->Mode == 0) { draw_list->AddRect(CompPosMin, CompPosMax, OUTLINE); draw_list->AddImage((void *)(intptr_t)textureID, CompPosMin, CompPosMax); } else { block_composition *Comp = (block_composition *)Memory_Block_AddressAtIndex(Memory, F_Precomps, File->PrincipalCompIndex); uint8 *StartAddress = (uint8 *)Memory->Slot[B_PointData].Address; uint8 *Data = StartAddress; Arbitrary_Zero(Data, Memory->Slot[B_PointData].Size); gl_viewport_data *RenderData = (gl_viewport_data *)Data; Data += sizeof(gl_viewport_data); *RenderData = { ViewportMin, ViewportMax, ImGui::GetMainViewport()->Size, State->ViewportEnabled, Comp->Width, Comp->Height, Comp->BytesPerPixel, UI->CompPos, UI->CompZoom, UI->CompZoom.x / Comp->Width, {} }; layer_transforms ExtraT = {}; Render_UI(File, State, Memory, UI, draw_list, Data, RenderData, SortedCompArray, SortedLayerArray, ExtraT, SortedPropertyStart, SortedKeyframeArray, File->PrincipalCompIndex, State->Frame_Current); State->UpdateFrame = false; State->UpdateKeyframes = false; draw_list->AddCallback(GL_Test, (void *)StartAddress); draw_list->AddCallback(ImDrawCallback_ResetRenderState, NULL); draw_list->AddRect(CompPosMin, CompPosMax, OUTLINE); } draw_list->PopClipRect(); draw_list->AddRect(ViewportMin, ViewportMax, IM_COL32(255,255,255, 255)); 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(); // UI+interaction for layer if (State->MostRecentlySelectedLayer > -1) { v2 *PointData = (v2 *)Memory_PushScratch(Memory, sizeof(v2) * 512); int PointCount = 0; layer_transforms T = {}; ImGui_Viewport_SelectedLayerUI(State, Memory, UI, io, draw_list, MainComp, File->PrincipalCompIndex, T, &PointCount, PointData, SortedCompArray, SortedLayerArray); ImGui_Viewport_TransformUI2(File, State, Memory, UI, io, draw_list, &PointCount, PointData, ViewportMin, MainComp->Width, MainComp->Height, SortedCompArray, SortedLayerArray, SortedKeyframeArray); Memory_PopScratch(Memory, sizeof(v2) * 512); } shape_layer *Shape = &UI->Shape; if ((State->Tool == tool_pen) && State->MostRecentlySelectedLayer == -1) ImGui_Viewport_ShapeUI(State, Memory, UI, io, NULL, 0, 0, Shape, MainComp, draw_list); // 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 // TODO(fox): Tidy up once all work is done if (IsHovered && IsActivated && !ImGui::IsMouseDown(ImGuiMouseButton_Right)) { State->LastClickedPoint = ImGui_ScreenPointToCompUV(ViewportMin, UI->CompPos, UI->CompZoom, io.MousePos); if (!ImGui::IsKeyDown(ImGuiKey_Z)) { if (State->Tool == tool_brush && State->Interact_Active != interact_type_brush) { 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 & 0x01) && Source->Type == source_type_principal) { Assert(Source->BytesPerPixel == 4); 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) { State->HotkeyInput = hotkey_newlayer_paint; } } } } bool32 OtherActions = ImGui::IsKeyDown(ImGuiKey_Z) || ImGui::IsMouseDown(ImGuiMouseButton_Right); // If there's any sort of drag before the user lets go of the mouse, they // either want to use the bounding box or drag an object that isn't // currently selected, so we need to select it now instead of on mouse // release. This requires a state override (Interact_OutOfDrag) to force // the deselection code to not be called on mouse release. if (IsActive && !OtherActions && !io.KeyCtrl && !io.KeyAlt) { ImVec2 ClickedPos = io.MouseClickedPos[0]; ImVec2 MousePos = io.MousePos; ImVec2 BoxMin = {}, BoxMax = {}; BoxMin.x = (MousePos.x < ClickedPos.x) ? MousePos.x : ClickedPos.x; BoxMin.y = (MousePos.y < ClickedPos.y) ? MousePos.y : ClickedPos.y; BoxMax.x = (MousePos.x > ClickedPos.x) ? MousePos.x : ClickedPos.x; BoxMax.y = (MousePos.y > ClickedPos.y) ? MousePos.y : ClickedPos.y; if (BoxMax.x - BoxMin.x > 0.1f && BoxMax.y - BoxMin.y > 0.1f) { Assert(BoxMax.x > BoxMin.x && BoxMax.y > BoxMin.y) if (io.MouseDelta.x || io.MouseDelta.y) { if (State->Tool == tool_default && State->Interact_Active == interact_type_none) { layer_transforms T = {}; T.scale = 1.0f; v2 CompPoint = ImGui_ScreenPointToCompUV(ViewportMin, UI->CompPos, UI->CompZoom, io.MousePos) * V2(MainComp->Width, MainComp->Height); int32 Selection = LayerIterate_TestSelection(State, Memory, File->PrincipalCompIndex, T, CompPoint, SortedCompArray, SortedLayerArray, io.KeyShift); if (!io.KeyShift && !State->InteractTransformMode && Selection > 0) { Layer_DeselectAll(File, State, Memory); Layer_Select(Memory, State, Selection); State->Interact_Active = interact_type_viewport_transform_gizmo; State->Interact_OutOfDrag = true; State->InteractTransformMode = 3; } else { State->Interact_Active = interact_type_viewport_selection; if (io.KeyShift) { Interact_BoxSelect_Start(File, State, Memory); } } } if (State->Interact_Active == interact_type_viewport_selection) { Layer_DeselectAll(File, State, Memory, 1); v2 MinPos_Comp = ImGui_ScreenPointToCompUV(ViewportMin, UI->CompPos, UI->CompZoom, BoxMin) * V2(MainComp->Width, MainComp->Height); v2 MaxPos_Comp = ImGui_ScreenPointToCompUV(ViewportMax, UI->CompPos, UI->CompZoom, BoxMax) * V2(MainComp->Width, MainComp->Height); layer_transforms T = {}; T.scale = 1.0f; Layer_TestBoxSelect(Memory, State, UI, SortedCompArray, SortedLayerArray, File->PrincipalCompIndex, T, MinPos_Comp, MaxPos_Comp); } } if (State->Interact_Active == interact_type_viewport_selection) { uint32 fcol = IM_COL32(00, 00, 255, 50); uint32 wcol = IM_COL32(128, 128, 255, 255); draw_list->AddRectFilled(BoxMin, BoxMax, fcol, 2.0f); draw_list->AddRect(BoxMin, BoxMax, wcol, 2.0f); } } } bool32 Delta = (io.MouseDelta.x || io.MouseDelta.y); if (IsActivated || IsDeactivated) { if (((io.KeyAlt && IsActivated && State->MostRecentlySelectedLayer == -1) || (IsHovered && IsDeactivated && !Delta && !io.KeyCtrl && !io.KeyAlt)) && !ImGui::IsMouseDown(ImGuiMouseButton_Right) && !ImGui::IsKeyDown(ImGuiKey_Z)) { if (State->Interact_OutOfDrag) { State->Interact_OutOfDrag = 0; } else { // Layer selection layer_transforms T = {}; T.scale = 1.0f; v2 CompPoint = ImGui_ScreenPointToCompUV(ViewportMin, UI->CompPos, UI->CompZoom, io.MousePos) * V2(MainComp->Width, MainComp->Height); int32 Selection = LayerIterate_TestSelection(State, Memory, File->PrincipalCompIndex, T, CompPoint, SortedCompArray, SortedLayerArray, io.KeyShift); if (State->Tool == tool_default && State->Interact_Active == interact_type_none) { if (!io.KeyShift && State->Interact_Active == interact_type_none) { Layer_DeselectAll(File, State, Memory, 0, 1); State->Interact_Transform = {}; } if (Selection > 0) Layer_Select(Memory, State, Selection); } } } } if (IsDeactivated && State->Interact_Active == interact_type_viewport_selection) { Interact_BoxSelect_End(File, State, Memory); State->Interact_Active = interact_type_none; } if (State->Interact_Active == interact_type_viewport_transform) { if (io.MouseDelta.x || io.MouseDelta.y) { State->Interact_Offset[4] = io.MousePos.x; State->Interact_Offset[5] = io.MousePos.y; } } if (IsActive && ImGui::IsMouseDragging(ImGuiMouseButton_Right, -1.0f)) { UI->CompPos.x += io.MouseDelta.x; UI->CompPos.y += io.MouseDelta.y; } 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; v2 PrincipalCompUV = ImGui_ScreenPointToCompUV(ViewportMin, UI->CompPos, UI->CompZoom, io.MousePos); v2 LayerPos = Layer_TraverseForPoint(File, State, Memory, PrincipalCompUV, SortedCompArray, SortedLayerArray, State->Brush.LayerToPaint_Index); if (IsActivated) { RenderQueue_AddBrush(State, LayerPos); } else if (Delta != 0.0f) { v2 PrevPos = State->Brush.PrevPos; v2 Delta = PrevPos - LayerPos; real32 Dist = sqrt(LengthSq(Delta)); if (Dist > State->Brush.Spacing) { RenderQueue_AddBrush(State, LayerPos); } } State->UpdateFrame = true; } if (IsDeactivated) { RenderQueue_AddBlit(State); } } if (IsActive) int a = 0; if (State->Tool == tool_default && State->Interact_Active == interact_type_none && !io.MouseDelta.x && !io.MouseDelta.y) { ImGui::OpenPopupOnItemClick("defaultcontext", ImGuiPopupFlags_MouseButtonRight); if (ImGui::BeginPopup("defaultcontext")) { ImGui::Text("Insert keyframe..."); if (ImGui::Button("X/Y")) { Property_AddKeyframe_AllSelected(File, State, Memory, F_Layers, SortedLayerArray, SortedCompArray, SortedKeyframeArray, 0, State->Frame_Current); Property_AddKeyframe_AllSelected(File, State, Memory, F_Layers, SortedLayerArray, SortedCompArray, SortedKeyframeArray, 1, State->Frame_Current); ImGui::CloseCurrentPopup(); } ImGui::SameLine(); if (ImGui::Button("Rotation")) { Property_AddKeyframe_AllSelected(File, State, Memory, F_Layers, SortedLayerArray, SortedCompArray, SortedKeyframeArray, 4, State->Frame_Current); ImGui::CloseCurrentPopup(); } if (ImGui::Button("Scale")) { Property_AddKeyframe_AllSelected(File, State, Memory, F_Layers, SortedLayerArray, SortedCompArray, SortedKeyframeArray, 5, State->Frame_Current); ImGui::CloseCurrentPopup(); } ImGui::SameLine(); if (ImGui::Button("X/Y+R+S")) { Property_AddKeyframe_AllSelected(File, State, Memory, F_Layers, SortedLayerArray, SortedCompArray, SortedKeyframeArray, 0, State->Frame_Current); Property_AddKeyframe_AllSelected(File, State, Memory, F_Layers, SortedLayerArray, SortedCompArray, SortedKeyframeArray, 1, State->Frame_Current); Property_AddKeyframe_AllSelected(File, State, Memory, F_Layers, SortedLayerArray, SortedCompArray, SortedKeyframeArray, 4, State->Frame_Current); Property_AddKeyframe_AllSelected(File, State, Memory, F_Layers, SortedLayerArray, SortedCompArray, SortedKeyframeArray, 5, State->Frame_Current); ImGui::CloseCurrentPopup(); } ImGui::EndPopup(); } } if (State->Tool == tool_pen && State->Interact_Active == interact_type_none && State->MostRecentlySelectedLayer == -1) { v2 CompUV = State->LastClickedPoint; ImVec2 ScreenPoint = ImVec2(UI->CompPos.x + CompUV.x * UI->CompZoom.x, UI->CompPos.y + CompUV.y * UI->CompZoom.y); if (Shape->Point_Count > 1) { ImGui::OpenPopupOnItemClick("shapecreate", ImGuiPopupFlags_MouseButtonRight); } if (ImGui::BeginPopup("shapecreate")) { if (ImGui::Selectable("Create shape layer")) { State->HotkeyInput = hotkey_newlayer_shape; } ImGui::EndPopup(); } if (!Shape->IsClosed) { ImVec2 Vector = io.MousePos - ScreenPoint; uint32 wcol = IM_COL32(00, 00, 80, 255); if (IsActive && !OtherActions) { draw_list->AddLine(ScreenPoint - Vector, io.MousePos, wcol, 2.0f); draw_list->AddNgon(ScreenPoint, 2, wcol, 8, 2.0f); } if (IsDeactivated && !OtherActions && !ImGui::IsMouseReleased(ImGuiMouseButton_Right)) { interpolation_type Type = interpolation_type_bezier; if (fabs(Vector.x) < 5 && fabs(Vector.y) < 5) { Type = interpolation_type_linear; Vector = ImVec2(0, 0); } bezier_point PointData = { 1, { CompUV * V2(MainComp->Width, MainComp->Height), V2(Vector.x, Vector.y), V2(-Vector.x, -Vector.y) }, Type, 0 }; History_Entry_Commit(Memory, "Bezier point"); Bezier_Add(Memory, F_File, Shape->Block_Bezier_Index, &Shape->Block_Bezier_Count, &Shape->Point_Count, PointData); History_Entry_End(Memory); } } } if (State->Tool == tool_slide && State->Interact_Active == interact_type_none && !OtherActions) { real32 Delta = io.MouseDelta.x + io.MouseDelta.y; if (Delta && IsActive) { State->Interact_Active = interact_type_viewport_slide; Slide_Init(File, State, Memory); } } if (State->Interact_Active == interact_type_viewport_slide) { if (IsDeactivated) { Assert(0); } else { Assert(IsActive); v2 PrevMouseCompPos = ImGui_ScreenPointToCompUV(ViewportMin, UI->CompPos, UI->CompZoom, io.MousePosPrev); v2 MouseCompPos = ImGui_ScreenPointToCompUV(ViewportMin, UI->CompPos, UI->CompZoom, io.MousePos); State->Interact_Offset[0] = MouseCompPos.x; State->Interact_Offset[1] = MouseCompPos.y; State->Interact_Offset[3] = PrevMouseCompPos.x; State->Interact_Offset[4] = PrevMouseCompPos.y; } } if (State->Tool == tool_rectangle && State->Interact_Active == interact_type_none) { v2 CompUV = State->LastClickedPoint; ImVec2 ScreenPointStart = ImVec2(UI->CompPos.x + CompUV.x * UI->CompZoom.x, UI->CompPos.y + CompUV.y * UI->CompZoom.y); uint32 wcol = IM_COL32(00, 00, 255, 255); ImVec2 Vector = io.MousePos - ScreenPointStart; ImVec2 MousePos = io.MousePos; if (io.KeyShift) { MousePos = ScreenPointStart + ImVec2(Vector.x, Vector.x); } if (IsActive && !OtherActions) { if (State->ShapeMode == 0) { draw_list->AddRect(ScreenPointStart, MousePos, wcol, 2.0f); } else if (State->ShapeMode == 1) { ImVec2 Radius = (MousePos - ScreenPointStart) / 2; ImVec2 Center = ScreenPointStart + Radius; real32 cx = Center.x; real32 cy = Center.y; real32 rx = Radius.x; real32 ry = Radius.y; draw_list->AddBezierCubic(ImVec2(cx-rx, cy), ImVec2(cx-rx, cy+ry*KAPPA), ImVec2(cx-rx*KAPPA, cy+ry), ImVec2(cx, cy+ry), wcol, 1.0f); draw_list->AddBezierCubic(ImVec2(cx, cy+ry), ImVec2(cx+rx*KAPPA, cy+ry), ImVec2(cx+rx, cy+ry*KAPPA), ImVec2(cx+rx, cy), wcol, 1.0f); draw_list->AddBezierCubic(ImVec2(cx+rx, cy), ImVec2(cx+rx, cy-ry*KAPPA), ImVec2(cx+rx*KAPPA, cy-ry), ImVec2(cx, cy-ry), wcol, 1.0f); draw_list->AddBezierCubic(ImVec2(cx, cy-ry), ImVec2(cx-rx*KAPPA, cy-ry), ImVec2(cx-rx, cy-ry*KAPPA), ImVec2(cx-rx, cy), wcol, 1.0f); } } if (Shape->Point_Count == 0 && IsDeactivated && !OtherActions && !ImGui::IsMouseReleased(ImGuiMouseButton_Right)) { ImVec2 ScreenPoint[4] = { ScreenPointStart, ImVec2(ScreenPointStart.x, MousePos.y), MousePos, ImVec2(MousePos.x, ScreenPointStart.y) }; if (State->ShapeMode == 0) { v2 *CompPoint = (v2 *)&State->Interact_Offset[0]; for (int i = 0; i < 4; i++) { CompPoint[i] = ImGui_ScreenPointToCompUV(ViewportMin, UI->CompPos, UI->CompZoom, ScreenPoint[i]); CompPoint[i] = CompPoint[i] * V2(MainComp->Width, MainComp->Height); } } else if (State->ShapeMode == 1) { v2 CompMin = ImGui_ScreenPointToCompUV(ViewportMin, UI->CompPos, UI->CompZoom, ScreenPointStart) * V2(MainComp->Width, MainComp->Height); v2 CompMax = ImGui_ScreenPointToCompUV(ViewportMin, UI->CompPos, UI->CompZoom, io.MousePos) * V2(MainComp->Width, MainComp->Height); v2 Radius = (CompMax - CompMin) / 2; v2 Center = CompMin + Radius; *((v2 *)&State->Interact_Offset + 0) = Radius; *((v2 *)&State->Interact_Offset + 1) = Center; } State->HotkeyInput = hotkey_newlayer_shape; } } 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)) { real32 MouseVecLength = Length(io.MousePos - io.MouseClickedPos[0]); real32 MouseMaxLength = ViewportScale.x; Distance = io.MouseDelta.x + io.MouseDelta.y; real32 Multiplier = -log(1.0f - (MouseVecLength / MouseMaxLength)); if (isnan(Multiplier)) Multiplier = 0.0f; Distance = Distance + (Distance * Multiplier); } 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->LastClickedPoint.x; UI->CompPos.y -= Distance*State->LastClickedPoint.y; } else if (Distance && io.KeyAlt) { State->Interact_Active = interact_type_viewport_duplicate; } if (State->Interact_Active == interact_type_viewport_duplicate) { if (IsDeactivated) { // NOTE(fox): Normally calling functions that add/delete things // shouldn't be done here since they affect sorting and can lead to // access of invalid data, but since the duplicate state already // pre-modifies the sort data to be correct, it's safe to call it // here. // UI bugs can form if code prior to this and code after this // depend on the IsFake flag! History_Entry_Commit(Memory, "Duplicate layers"); State->Interact_Active = interact_type_none; State->Interact_Modifier = 0; v2 Offset = V2(State->Interact_Offset[0], State->Interact_Offset[1]); Project_Layer_Duplicate(File, State, Memory, SortedCompArray, SortedLayerArray, Offset, io.KeyCtrl); State->Interact_Dup_Previous[0] = State->Interact_Offset[0]; State->Interact_Dup_Previous[1] = State->Interact_Offset[1]; State->Interact_Offset[0] = 0; State->Interact_Offset[1] = 0; State->Interact_Transform = {}; History_Entry_End(Memory); } else { Assert(IsActive); v2 MouseCompPos = ImGui_ScreenPointToCompUV(ViewportMin, UI->CompPos, UI->CompZoom, io.MousePos); v2 DragDelta = MouseCompPos - State->LastClickedPoint; State->Interact_Offset[0] = DragDelta.x * MainComp->Width; State->Interact_Offset[1] = DragDelta.y * MainComp->Height; } } 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_Viewport_Toolbar(State, draw_list); ImGui_Viewport_BrushUI(State, Memory, ViewportMin, ViewportMax, UI->CompPos, io, MainComp->Width, MainComp->Height); ImGui::End(); }