From ee654e9217487f6fca12356ec8af82319c309592 Mon Sep 17 00:00:00 2001
From: Fox Caminiti <fox@foxcam.net>
Date: Tue, 7 Feb 2023 00:21:06 -0500
Subject: good changes

---
 .gitignore                  |   4 +-
 src/createcalls.cpp         |  90 ++++++++++---------
 src/imgui_ui.cpp            |   6 +-
 src/imgui_ui_properties.cpp |   3 +
 src/imgui_ui_timeline.cpp   |  30 +++++--
 src/imgui_ui_viewport.cpp   |  24 +++++-
 src/include/all.h           |  16 ++--
 src/include/functions.h     | 128 ---------------------------
 src/include/layer.h         |   3 -
 src/include/main.h          |   4 +
 src/layer.cpp               |  52 +++++++----
 src/main.cpp                | 204 ++++++++++++++++++++++++++++++++++++++++----
 src/prenderer.cpp           |  82 +++++-------------
 src/sorted.cpp              | 127 +++++++++++++++++++--------
 src/strings.cpp             |   5 +-
 src/undo.cpp                |   1 +
 16 files changed, 447 insertions(+), 332 deletions(-)
 delete mode 100644 src/include/functions.h

diff --git a/.gitignore b/.gitignore
index 7513e10..4047fb0 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,8 +4,6 @@ compile_commands.json
 build_ops
 imgui.ini
 test
-test2
-test3
-test4
+test*
 tags
 .cache
diff --git a/src/createcalls.cpp b/src/createcalls.cpp
index e41a9d2..4761a94 100644
--- a/src/createcalls.cpp
+++ b/src/createcalls.cpp
@@ -683,15 +683,38 @@ Property_IsGraphSelected(memory *Memory, property_channel *Property, uint16 *Arr
     return 0;
 }
 
+// TODO(fox): Add different modes (all dupes on top, each dupe above its layer, two for bottom)
+void
+SortUnionTest(memory *Memory, sorted_layer_array *SortedLayerStart, block_layer *StartLayer, 
+                int i, int FauxIncrement, int LayerCount, int Mode)
+{
+    block_layer *PrevLayer = StartLayer;
+    for (int a = i+1; a < LayerCount; a++) {
+        sorted_layer_array *NextSortEntry = &SortedLayerStart[a];
+        uint32 NextIndex_Physical = NextSortEntry->Block_Layer_Index;
+        block_layer *NextLayer = (block_layer *)Memory_Block_AddressAtIndex(Memory, F_Layers, NextIndex_Physical);
+        if (NextLayer->Vertical_Offset == PrevLayer->Vertical_Offset) {
+            if (Mode == 0) {
+                History_Action_Swap(Memory, F_Layers, sizeof(NextLayer->Vertical_Offset), &NextLayer->Vertical_Offset);
+                NextLayer->Vertical_Offset -= 1;
+            } else {
+                NextSortEntry->SortedOffset -= 1;
+            }
+        } else {
+            break;
+        }
+        PrevLayer = NextLayer;
+    }
+}
+
 static void
 Project_Layer_Duplicate(project_data *File, project_state *State, memory *Memory,
-                        sorted_comp_array *SortedCompArray, sorted_layer_array *SortedLayerArray, v2 Offset, bool32 FakeOnly)
+                        sorted_comp_array *SortedCompArray, sorted_layer_array *SortedLayerArray, v2 Offset, bool32 FakeOnly, bool32 NewPrecomp)
 {
     for (int c = 0; c < File->Comp_Count; c++) {
         sorted_comp_array SortedCompStart = SortedCompArray[c];
         sorted_layer_array *SortedLayerStart = Sorted_GetLayerStart(SortedLayerArray, SortedCompArray, c);
         int LayerCount = SortedCompStart.LayerCount + SortedCompStart.FakeLayerCount;
-        int Increment = 0;
         for (int i = 0; i < LayerCount; i++) 
         {
             sorted_layer_array SortEntry = SortedLayerStart[i];
@@ -706,23 +729,16 @@ Project_Layer_Duplicate(project_data *File, project_state *State, memory *Memory
                 History_Action_Block_Swap(Memory, F_Layers, NewLayer);
                 *NewLayer = *Layer;
 
+                if (NewPrecomp && Layer->IsPrecomp) {
+                    NewLayer->Block_Source_Index = Layer_Precomp_CopyContents();
+                }
+
                 Assert(Layer->Block_Effect_Count == 0);
 
                 Layer_Select(Memory, State, Memory_Block_LazyIndexAtAddress(Memory, F_Layers, NewLayer));
                 NewLayer->Vertical_Offset--;
 
-                for (int a = i+1; a < LayerCount; a++) {
-                    sorted_layer_array PrevSortEntry = SortedLayerStart[a-1];
-                    uint32 PrevIndex_Physical = PrevSortEntry.Block_Layer_Index;
-                    block_layer *PrevLayer = (block_layer *)Memory_Block_AddressAtIndex(Memory, F_Layers, PrevIndex_Physical);
-                    sorted_layer_array NextSortEntry = SortedLayerStart[a];
-                    uint32 NextIndex_Physical = NextSortEntry.Block_Layer_Index;
-                    block_layer *NextLayer = (block_layer *)Memory_Block_AddressAtIndex(Memory, F_Layers, NextIndex_Physical);
-                    if (NextLayer->Vertical_Offset == PrevLayer->Vertical_Offset)
-                        NextLayer->Vertical_Offset -= 1;
-                    else
-                        break;
-                }
+                SortUnionTest(Memory, SortedLayerStart, NewLayer, i, 0, LayerCount, 0);
     
                 Assert(!NewLayer->x.Keyframe_Count);
                 Assert(!NewLayer->y.Keyframe_Count);
@@ -738,15 +754,18 @@ Project_Layer_Duplicate(project_data *File, project_state *State, memory *Memory
                 History_Action_Swap(Memory, F_File, sizeof(File->Layer_Count), &File->Layer_Count);
                 File->Layer_Count++;
     
-                Increment++;
             } else {
-                // History_Action_Swap(Memory, F_Layers, sizeof(Layer->Vertical_Offset), &Layer->Vertical_Offset);
                 Layer->IsSelected = false;
             }
         }
     }
 }
 
+// TODO(fox): We may need a way to recalculate width/height when points are
+// moved since selection depends on it. 
+// TODO(fox): We could also write better selection code for shapes that
+// actually tests using the bezier path (preferrable).
+
 static void
 Project_ShapeLayer_New(project_data *File, project_state *State, memory *Memory)
 {
@@ -767,6 +786,14 @@ Project_ShapeLayer_New(project_data *File, project_state *State, memory *Memory)
 
     shape_layer *Shape = &File->UI.Shape;
 
+    Layer->Block_Composition_Index = File->PrincipalCompIndex;
+    if (State->MostRecentlySelectedLayer == -1) {
+        Layer->Block_Composition_Index = File->PrincipalCompIndex;
+    } else {
+        // LayerIterate_DeepestPrecomp(State, Memory, CompIndex, ExtraT, Center,
+        //                     SortedCompArray, SortedLayerArray, SelectionCount, SelectedLayerIndex, SelectedPrecompIndex)
+    }
+    // Rect mode
     if (State->Interact_Modifier == 1) {
         v2 *CompPoint = (v2 *)&State->Interact_Offset[0];
         for (int i = 0; i < 4; i++) {
@@ -896,37 +923,6 @@ void Source_UICreateButton(project_data *File, project_state *State, memory *Mem
     Source_DeselectAll(File, Memory);
 }
 
-void Precomp_UIDuplicate(project_data *File, project_state *State, memory *Memory, uint16 CompIndex,
-                           sorted_comp_array SortedCompStart, sorted_layer_array *SortedLayerStart)
-{
-    block_layer *Layer = NULL;
-    for (int i = SortedCompStart.LayerCount - 1; i >= 0; i--)
-    {
-        sorted_layer_array SortEntry = SortedLayerStart[i];
-        uint32 Index_Physical = SortEntry.Block_Layer_Index;
-        Layer = (block_layer *)Memory_Block_AddressAtIndex(Memory, F_Layers, Index_Physical);
-        if (Layer->IsSelected) {
-            break;
-        }
-    }
-    if (Layer) {
-        block_layer *DupeLayer = Layer_Init(File, Memory);
-        *DupeLayer = *Layer;
-        DupeLayer->Vertical_Offset += 1;
-        for (int h = 0; h < AmountOf(Layer->Property); h++) {
-            property_channel *Property = &Layer->Property[h];
-            if (Property->Block_Bezier_Count) {
-                property_channel *DupeProperty = &DupeLayer->Property[h];
-                DupeProperty->Block_Bezier_Index[0] = Memory_Block_AllocateNew(Memory, F_Bezier);
-                block_bezier *Bezier = (block_bezier *)Memory_Block_AddressAtIndex(Memory, F_Bezier, Property->Block_Bezier_Index[0]);
-                block_bezier *DupeBezier = (block_bezier *)Memory_Block_AddressAtIndex(Memory, F_Bezier, DupeProperty->Block_Bezier_Index[0], 0);
-                Bezier->Occupied = true;
-                *DupeBezier = *Bezier;
-            }
-        }
-    }
-}
-
 void Precomp_UIDelete(project_data *File, project_state *State, memory *Memory, uint16 CompIndex,
                            sorted_comp_array SortedCompStart, sorted_layer_array *SortedLayerStart)
 {
diff --git a/src/imgui_ui.cpp b/src/imgui_ui.cpp
index c24571c..a07854e 100644
--- a/src/imgui_ui.cpp
+++ b/src/imgui_ui.cpp
@@ -444,11 +444,7 @@ ImGui_ProcessInputs(project_data *File, project_state *State, ui *UI, memory *Me
     }
     if (ImGui::IsKeyPressed(ImGuiKey_D)) {
         if (io.KeyShift && State->Interact_Active == interact_type_none) {
-            History_Entry_Commit(Memory, "Duplicate layers");
-            v2 Offset = V2(State->Interact_Dup_Previous[0], State->Interact_Dup_Previous[1]);
-            Project_Layer_Duplicate(File, State, Memory, Sorted.CompArray, Sorted.LayerArray, Offset, 0);
-            State->Interact_Transform = {};
-            History_Entry_End(Memory);
+            State->HotkeyInput = hotkey_duplicatelayer;
         } else {
             State->Tool = tool_pen;
         }
diff --git a/src/imgui_ui_properties.cpp b/src/imgui_ui_properties.cpp
index 43c4a79..345f612 100644
--- a/src/imgui_ui_properties.cpp
+++ b/src/imgui_ui_properties.cpp
@@ -21,6 +21,7 @@ ImGui_Properties_Slider(project_state *State, memory *Memory, property_channel *
 {
     if (ImGui::IsItemActive()) {
         State->UpdateFrame = true;
+        State->Interact_Transform = {};
         // ImGui_WarpMouse(State, io.MousePos, WindowMinAbs, WindowMaxAbs, 1);
     }
 
@@ -433,10 +434,12 @@ ImGui_PropertiesPanel(project_data *File, project_state *State, ui *UI, memory *
             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;
             }
         }
diff --git a/src/imgui_ui_timeline.cpp b/src/imgui_ui_timeline.cpp
index dcb3bc2..f8c5258 100644
--- a/src/imgui_ui_timeline.cpp
+++ b/src/imgui_ui_timeline.cpp
@@ -468,9 +468,15 @@ ImGui_Timeline_DrawPrecomp(project_data *File, project_state *State, memory *Mem
         block_string *String = (block_string *)Memory_Block_AddressAtIndex(Memory, F_Strings, Layer->Block_String_Index);
         char buf[128];
 #if DEBUG
-        sprintf(buf, "%s, (idx: %i so: %.1f di: %.1f", String->Char, Index_Physical, SortEntry.SortedOffset, SortEntry. DisplayOffset);
+        if (SortEntry.IsFake)
+            sprintf(buf, "%s (duplicate), (idx: %i so: %.1f di: %.1f", String->Char, Index_Physical, SortEntry.SortedOffset, SortEntry. DisplayOffset);
+        else
+            sprintf(buf, "%s, (idx: %i so: %.1f di: %.1f", String->Char, Index_Physical, SortEntry.SortedOffset, SortEntry. DisplayOffset);
 #else
-        sprintf(buf, "%s", String->Char);
+        if (SortEntry.IsFake)
+            sprintf(buf, "%s (duplicate)", String->Char);
+        else
+            sprintf(buf, "%s", String->Char);
 #endif
         if (UI->TimelinePercentZoomed.y <= 1.0f) {
             real32 TextX = (Layer_ScreenPos_Min.x > TimelineAbsolutePos.x) ? Layer_ScreenPos_Min.x : TimelineAbsolutePos.x;
@@ -522,7 +528,11 @@ ImGui_Timeline_DrawPrecomp(project_data *File, project_state *State, memory *Mem
                 ImGui::Button("##layer_resize", ResizeSize);
 
                 if (ImGui::IsItemHovered()) {
-                    ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeEW);
+                    if (Layer->IsLocked) {
+                        ImGui::SetMouseCursor(ImGuiMouseCursor_NotAllowed);
+                    } else {
+                        ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeEW);
+                    }
                 }
                 if (ImGui::IsItemActivated()) {
                     if (!Layer->IsSelected && !Layer->IsLocked) {
@@ -568,6 +578,12 @@ ImGui_Timeline_DrawPrecomp(project_data *File, project_state *State, memory *Mem
             ImGui::SetCursorScreenPos(Layer_ScreenPos_Min);
             ImGui::InvisibleButton("##layer_mid", Layer_ScreenSize, ImGuiMouseButton_Left);
 
+            if (ImGui::IsItemHovered()) {
+                if (Layer->IsLocked) {
+                    ImGui::SetMouseCursor(ImGuiMouseCursor_NotAllowed);
+                } 
+            }
+
             if (ImGui::IsItemActivated()) {
                 if (!Layer->IsSelected && !Layer->IsLocked) {
                     if (!io.KeyShift) Layer_DeselectAll(File, State, Memory);
@@ -602,7 +618,7 @@ ImGui_Timeline_DrawPrecomp(project_data *File, project_state *State, memory *Mem
                     int h = 0, z = 0, i = 0;
                     while (Block_Loop(Memory, F_Layers, File->Layer_Count, &h, &z, &i)) {
                         block_layer *Layer = (block_layer *)Memory_Block_AddressAtIndex(Memory, F_Layers, i);
-                        if (Layer->IsSelected) {
+                        if (Layer->IsSelected == 1 || Layer->IsLocked) {
                             if (!Commit) {
                                 History_Entry_Commit(Memory, "Toggle lock");
                                 Commit = true;
@@ -621,10 +637,10 @@ ImGui_Timeline_DrawPrecomp(project_data *File, project_state *State, memory *Mem
                     State->UpdateKeyframes = true;
                 }
                 if (ImGui::MenuItem("Duplicate layer")) {
-                    Precomp_UIDuplicate(File, State, Memory, CompIndex, SortedCompStart, SortedLayerStart);
+                    State->HotkeyInput = hotkey_duplicatelayer;
                 }
                 if (ImGui::MenuItem("Delete layer")) {
-                    Precomp_UICreateButton(File, State, Memory, CompIndex, SortedCompStart, SortedLayerStart);
+                    State->HotkeyInput = hotkey_deletelayer;
                     State->UpdateKeyframes = true;
                 }
                 if (ImGui::BeginMenu("Layer color"))
@@ -639,7 +655,7 @@ ImGui_Timeline_DrawPrecomp(project_data *File, project_state *State, memory *Mem
                             int h = 0, z = 0, i = 0;
                             while (Block_Loop(Memory, F_Layers, File->Layer_Count, &h, &z, &i)) {
                                 block_layer *Layer = (block_layer *)Memory_Block_AddressAtIndex(Memory, F_Layers, i);
-                                if (Layer->IsSelected) {
+                                if (Layer->IsSelected == 1) {
                                     Layer->ColIndex = c;
                                 }
                             }
diff --git a/src/imgui_ui_viewport.cpp b/src/imgui_ui_viewport.cpp
index 946edec..2489cd5 100644
--- a/src/imgui_ui_viewport.cpp
+++ b/src/imgui_ui_viewport.cpp
@@ -279,11 +279,11 @@ T_FindBestFit(int PointCount, v2 *PointData, v2 *Center, v2 *NewCenter, v2 *Best
     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, 
@@ -653,12 +653,20 @@ ImGui_Viewport_TransformUI2(project_data *File, project_state *State, memory *Me
                 }
 
                 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);
+                // 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);
@@ -1258,7 +1266,11 @@ ImGui_Viewport(project_data *File, project_state *State, ui *UI, memory *Memory,
             BoxMax.y = (MousePos.y > ClickedPos.y) ? MousePos.y : ClickedPos.y;
             if (io.MouseDelta.x || io.MouseDelta.y) {
                 if (State->Tool == tool_default && State->Interact_Active == interact_type_none) {
-                    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(MainComp->Width, MainComp->Height);
+                    int32 Selection = LayerIterate_TestSelection(State, Memory, File->PrincipalCompIndex, T, CompPoint,
+                            SortedCompArray, SortedLayerArray, io.KeyShift);
                     if (!State->InteractTransformMode && Selection != -1) {
                         if (!io.KeyShift)
                             Layer_DeselectAll(File, State, Memory);
@@ -1296,7 +1308,11 @@ ImGui_Viewport(project_data *File, project_state *State, ui *UI, memory *Memory,
                 State->Interact_OutOfDrag = 0;
             } else {
                 // Layer selection
-                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(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);
diff --git a/src/include/all.h b/src/include/all.h
index 7d7557a..82731ff 100644
--- a/src/include/all.h
+++ b/src/include/all.h
@@ -1,3 +1,9 @@
+
+static int32
+LayerIterate_TestSelection(project_state *State, memory *Memory, uint32 PrincipalIndex, layer_transforms ExtraT, v2 Center,
+                             sorted_comp_array *SortedCompArray, sorted_layer_array *SortedLayerArray, bool32 BelowOnly);
+
+
 // TODO(fox);: Incorporate sorting for non-continuous shapes.
 static uint32
 Bezier_Shape_Sort(memory *Memory, shape_layer *Shape, bezier_point *PointData,
@@ -353,13 +359,13 @@ Layer_GetDimensions(memory *Memory, block_layer *Layer, int *Width, int *Height)
 static v2
 Layer_TraverseForPoint(project_data *File, project_state *State, memory *Memory, v2 PrincipalCompUV, sorted_comp_array *SortedCompArray, sorted_layer_array *SortedLayerArray, uint16 LayerIndex);
 
-// TODO(fox);: Precomps?
-static int32
-Layer_TestSelection(memory *Memory, project_state *State, ui *UI, sorted_comp_array *SortedCompArray, sorted_layer_array *SortedLayerArray, uint16 PrincipalIndex, bool32 BelowOnly);
-
 static bool32
 Shape_TestBoxSelect(v2 Min, v2 Max, bezier_point *BezierPointData, uint32 BezierCount);
 
+static int32
+LayerIterate_TestSelection(project_state *State, memory *Memory, uint32 PrincipalIndex, layer_transforms ExtraT, v2 Center,
+                             sorted_comp_array *SortedCompArray, sorted_layer_array *SortedLayerArray, bool32 BelowOnly);
+
 static bool32
 Transform_TestBox(block_layer *Layer, uint32 Width, uint32 Height, v2 Min, v2 Max);
 
@@ -601,7 +607,7 @@ static void
 CopyStrings(void *Dest, void *Data);
 
 static uint16
-String_AddToFile(memory *Memory, char *Char);
+String_AddToFile(memory *Memory, char *Char, bool32 NoUndo = 1);
 
 static bool32
 String_Compare(char *String1, char *String2, uint32 Length);
diff --git a/src/include/functions.h b/src/include/functions.h
deleted file mode 100644
index 3826824..0000000
--- a/src/include/functions.h
+++ /dev/null
@@ -1,128 +0,0 @@
-
-// Memory
-
-static void Memory_Copy(uint8 *Address_Write, uint8 *Address_Read, uint64 Size);
-static void Arbitrary_Zero(uint8 *Address_Write, uint64 Size);
-static void Arbitrary_SwapData(memory *Memory, uint8 *Address_0, uint8 *Address_1, uint64 Size);
-static void Arbitrary_ShiftData(uint8 *Address_Start, uint8 *Address_End, uint64 ShiftAmount, int32 Direction);
-
-void AV_Init(block_source *Source, av_info *AV, memory *Memory);
-
-// Rudimentary guess-until-correct solver for bezier curves, used to evaluate
-// the keyframe graph. Similar to the Catmull-Rom solver in CurvesSolver().
-
-static real32         Bezier_SolveYForX(v2 Point_P0, v2 Point_P1, v2 Point_P2, v2 Point_P3, real32 TargetX);
-static bezier_point * Bezier_LookupAddress(memory *Memory, property_channel *Property, uint16 Index, bool32 AssertExists = 1);
-static bezier_point * Bezier_LookupAddress(memory *Memory, uint16 *Block_Bezier_Index, uint16 Index, bool32 AssertExists);
-static void           Bezier_Interact_Evaluate(project_state *State, bezier_point *PointAddress, v2 *Pos, real32 GraphZoomHeight = 1, real32 Y_Increment = 1);
-// NOTE(fox): GraphZoomHeight and Y_Increment don't have to be specified if the Y value isn't needed, i.e. in Property_SortAll().
-static void           Bezier_Add(memory *Memory, memory_table_list TableName, property_channel *Property, bezier_point PointData, uint16 *ArrayLocation);
-static void           Bezier_Add(memory *Memory, memory_table_list TableName, uint16 *Block_Bezier_Index, uint16 *Block_Bezier_Count, uint16 *PointCount, bezier_point PointData);
-
-static void
-Render_UI(project_data *File, project_state *State, memory *Memory, ui *UI, ImDrawList *draw_list,
-          sorted_comp_array *SortedCompArray, sorted_layer_array *SortedLayerArray,
-          sorted_property_array *SortedPropertyStart, uint16 *SortedKeyframeArray, uint32 CompIndex, int32 Frame_Current);
-
-static void *
-Memory_Block_AddressAtIndex(memory *Memory, memory_table_list TableName, uint32 Index, bool32 AssertExists = 1);
-
-static bool32
-Block_Loop(memory *Memory, memory_table_list TableName, uint32 TotalCount, int *HasIncremented, int *CurrentCount, int *Index);
-
-static void
-File_SaveAs(project_data *File, project_state *State, memory *Memory, char *Filename);
-
-static uint32
-Bezier_Shape_Sort(memory *Memory, shape_layer *Shape, bezier_point *PointData,
-                  project_state *State, layer_transforms T, int Width, int Height,
-                  int CompWidth, int CompHeight, bool32 Interact);
-
-void * Bezier_CubicCalcPoints(v2 p1, v2 p2, v2 p3, v2 p4, void *Data, uint32 Size);
-
-static layer_transforms Transform_Inverse(layer_transforms T);
-
-static v2 T_CompPosToLayerPos(layer_transforms T, uint32 FileWidth, uint32 FileHeight, uint32 SourceWidth, uint32 SourceHeight, real32 X, real32 Y);
-
-static layer_transforms 
-Transform_TestInteracts(project_state *State, block_layer *Layer, sorted_layer_array SortEntry, layer_transforms T);
-
-static layer_transforms
-Transform_Add(layer_transforms T, layer_transforms ExtraT, real32 Width, real32 Height);
-
-inline void
-ImGui_DrawCenteredRect(ImDrawList *draw_list, ImVec2 Point, real32 Width, uint32 col)
-
-static void ImGui_ProcessInputs(project_data *File, project_state *State, ui *UI, memory *Memory, ImGuiIO io, sorted_file Sorted);
-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);
-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);
-static void ImGui_Timeline(project_data *File, project_state *State, memory *Memory, ui *UI, ImGuiIO io, sorted_comp_array *SortedCompArray, sorted_layer_array *SortedLayerArray, sorted_property_array *SortedPropertyStart, uint16 *SortedKeyframeArray);
-
-static void ImGui_File(project_data *File, project_state *State, memory *Memory, ImGuiIO io, sorted_comp_array *SortedCompArray, sorted_layer_array *SortedLayerArray);
-static void ImGui_EffectsPanel(project_data *File, project_state *State, memory *Memory, ui *UI, ImGuiIO io);
-static void ImGui_ColorPanel(project_data *File, project_state *State, ui *UI, memory *Memory, ImGuiIO io);
-
-static void ImGui_Menu(project_data *File, project_state *State, ui *UI, memory *Memory, ImGuiIO io);
-static void ImGui_Popups(project_data *File, project_state *State, ui *UI, memory *Memory, ImGuiIO io);
-static void ImGui_KeybindUI(project_data *File, project_state *State, ui *UI, memory *Memory, ImGuiIO io);
-
-#if DEBUG
-static void ImGui_DebugMemoryViewer(memory *Memory, project_state *State);
-static void ImGui_DebugRenderQueue(project_state *State);
-static void ImGui_DebugUndoTree(memory *Memory, project_state *State);
-#endif
-#if SD
-static void ImGui_SD_Thumbnail(project_data *File, project_state *State, ui *UI, memory *Memory, ImGuiIO io, sorted_comp_array *SortedCompArray, sorted_layer_array *SortedLayerArray, uint16 *SourceArray, uint16 SourceCount);
-static void ImGui_SD_Prompt(project_data *File, project_state *State, ui *UI, memory *Memory, ImGuiIO io, sorted_comp_array *SortedCompArray, sorted_layer_array *SortedLayerArray);
-#endif
-
-// Widgets not involving drawing UI.
-
-static v2 ImGui_ScreenPointToCompUV(ImVec2 ViewportMin, ImVec2 CompPos, ImVec2 CompZoom, ImVec2 MousePos);
-static void ImGui_WarpMouse(project_state *State, ImVec2 MousePos, ImVec2 Min, ImVec2 Max, int Direction = 3);
-static void ImGui_WarpMouseFinish(project_state *State, ImVec2 MousePos);
-static ImVec2 ImGui_Brush_CalcMousePos(project_state *State, ImGuiIO &io, ImVec2 MouseDelta, int32 i, real32 DeltaDistance, real32 DeltaSlope);
-static bool32 ImGui_TestBoxSelection_Point(ImVec2 Pos, ImGuiIO &io, bool32 *Test);
-
-
-static void
-Render_Main(project_data *File, project_state *State, memory *Memory, sorted_file Sorted, ui *UI, SDL_Window *window, GLuint textureID,
-    void *Data, void *OutputBuffer, render_type RenderType, rectangle RenderRegion);
-
-static void Effect_Curves_Init(block_effect *Effect, property_channel *Property);
-
-void AV_IsFileSupported(char *filename, bool32 *IsVideo, bool32 *HasAudio);
-
-static v2 T_CompUVToLayerUV(layer_transforms T, uint32 FileWidth, uint32 FileHeight, uint32 SourceWidth, uint32 SourceHeight, v2 CompUV);
-static header_effect* Effect_EntryFromID(project_state *State, char *ID);
-
-void Effect_Curves_Sort(memory *Memory, block_effect *Effect, uint16 *SortedPointStart, uint16 Which);
-inline v2 Effect_V2(memory *Memory, block_effect *Effect, int Offset);
-
-static void Interact_Transform_Begin(project_data *File, memory *Memory, project_state *State, ImVec2 OGPos, sorted_comp_array *SortedCompArray, sorted_layer_array *SortedLayerArray);
-
-static v2 Transform_ScreenSpaceToLocal(layer_transforms T, uint32 FileWidth, uint32 FileHeight, uint32 SourceWidth, uint32 SourceHeight, ImVec2 CompPos, ImVec2 CompZoom, ImVec2 ViewportMin, ImVec2 Point);
-static ImVec2 Layer_LocalToScreenSpace(project_state *State, memory *Memory, block_layer *Layer, ui *UI, uint32 PrincipalCompIndex, v2 Point);
-
-static v2 TransformPoint(layer_transforms T, real32 Width, real32 Height, v2 Point);
-
-static void Layer_GetDimensions(memory *Memory, block_layer *Layer, int *Width, int *Height);
-
-static void * Memory_PushScratch(memory *Memory, uint64 Size);
-
-static void Memory_PopScratch(memory *Memory, uint64 Size);
-
-void Bitmap_SwapData(uint8 *Address_0, uint8 *Address_1, uint64 Size, uint16 BytesPerPixel);
-void GL_DeleteHWBuffer(gl_effect_layer *Test);
-
-void GL_UpdateTexture(gl_effect_layer *Test, void *Data, uint16 Width, uint16 Height, uint16 BytesPerPixel, bool32 Multisample);
-
-static void Transform_ApplyInteractive(interact_transform Interact, real32 *OutputX, real32 *OutputY, real32 *OutputRotation, real32 *OutputScale);
-
-static layer_transforms Layer_GetTransforms(block_layer *Layer);
-void GL_GenAndBindTexture(GLuint *GLTexture, int Width, int Height, int BytesPerPixel, void *BufferAddress);
-
-static real32 Bezier_SolveYForX(v2 Point_P0, v2 Point_P1, v2 Point_P2, v2 Point_P3, real32 X);
-
diff --git a/src/include/layer.h b/src/include/layer.h
index d059bb6..d354c89 100644
--- a/src/include/layer.h
+++ b/src/include/layer.h
@@ -42,9 +42,6 @@ Layer_Select_Traverse(uint16 PrincipalCompIndex, memory *Memory, project_state *
 static v2
 Layer_TraverseForPoint(project_data *File, project_state *State, memory *Memory, v2 PrincipalCompUV, sorted_comp_array *SortedCompArray, sorted_layer_array *SortedLayerArray, uint16 LayerIndex);
 
-static int32
-Layer_TestSelection(memory *Memory, project_state *State, ui *UI, sorted_comp_array *SortedCompArray, sorted_layer_array *SortedLayerArray, uint16 PrincipalIndex, bool32 BelowOnly);
-
 static void
 Layer_RecursiveDeselect(memory *Memory, sorted_comp_array *SortedCompArray, sorted_layer_array *SortedLayerArray, uint16 TargetIndex, uint16 PrincipalIndex);
 
diff --git a/src/include/main.h b/src/include/main.h
index 1f04d41..0ee8511 100644
--- a/src/include/main.h
+++ b/src/include/main.h
@@ -367,6 +367,7 @@ enum hotkey_input
     hotkey_newlayer_paint,
     hotkey_newlayer_shape,
     hotkey_deletelayer,
+    hotkey_duplicatelayer,
     hotkey_undo,
     hotkey_redo,
 };
@@ -533,6 +534,9 @@ struct project_state
     void *Interact_Address;
     int Interact_Count;
 
+    // whether duplicated layers get created above or below
+    int DuplicateDirection = -1;
+
     ImGuiID RightDock;
 
     // NOTE(fox): We need to keep track of when the user changes the CurrentValue of a
diff --git a/src/layer.cpp b/src/layer.cpp
index dfe91a4..4c94987 100644
--- a/src/layer.cpp
+++ b/src/layer.cpp
@@ -211,7 +211,12 @@ Layer_DeselectAll(project_data *File, project_state *State, memory *Memory) {
     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);
-        Layer->IsSelected = false;
+        // stupid way to preserve this
+        if (Layer->IsSelected == -1) {
+            Layer->IsSelected = 2;
+        } else {
+            Layer->IsSelected = false;
+        }
     }
     State->Interact_Transform = {};
     State->MostRecentlySelectedLayer = -1;
@@ -372,11 +377,11 @@ Layer_TraverseForPoint(project_data *File, project_state *State, memory *Memory,
 
 // TODO(fox): Precomps?
 static bool32
-Layer_TestForPoint(memory *Memory, project_state *State, ui *UI, sorted_comp_array *SortedCompArray, sorted_layer_array *SortedLayerArray, uint16 PrincipalIndex, v2 CompUV)
+Layer_TestForPoint(memory *Memory, project_state *State, ui *UI, sorted_comp_array *SortedCompArray, sorted_layer_array *SortedLayerArray, uint16 CompIndex, v2 CompUV)
 {
-    block_composition *Comp = (block_composition *)Memory_Block_AddressAtIndex(Memory, F_Precomps, PrincipalIndex);
-    sorted_comp_array SortedCompStart = SortedCompArray[PrincipalIndex];
-    sorted_layer_array *SortedLayerStart = Sorted_GetLayerStart(SortedLayerArray, SortedCompArray, PrincipalIndex);
+    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);
     for (int i = SortedCompStart.LayerCount - 1; i >= 0; i--) {
         sorted_layer_array SortEntry = SortedLayerStart[i];
         uint32 Index_Physical = SortEntry.Block_Layer_Index;
@@ -385,7 +390,7 @@ Layer_TestForPoint(memory *Memory, project_state *State, ui *UI, sorted_comp_arr
         Layer_GetDimensions(Memory, Layer, &Width, &Height);
         layer_transforms T = Layer_GetTransforms(Layer);
         v2 UV = T_CompUVToLayerUV(T, Comp->Width, Comp->Height, Width, Height, CompUV);
-        if (UV.x <= 1.0f && UV.x >= 0.0f && UV.y <= 1.0f && UV.y >= 0.0f && Layer->IsSelected)
+        if (UV.x <= 1.0f && UV.x >= 0.0f && UV.y <= 1.0f && UV.y >= 0.0f && (Layer->IsSelected || Layer->IsPrecomp) && !Layer->IsLocked)
         {
             return 1;
         }
@@ -393,15 +398,16 @@ Layer_TestForPoint(memory *Memory, project_state *State, ui *UI, sorted_comp_arr
     return 0;
 }
 
+// Same loop as Render_UI and ImGui_Viewport_SelectedLayerUI except we're
 // NOTE(fox): We loop twice to allow for convenient down-travelling when only
 // one layer is selected, toggleable by the BelowOnly bool (i.e. shift held).
-// TODO(fox): Precomps?
+#if 0
 static int32
-Layer_TestSelection(memory *Memory, project_state *State, ui *UI, sorted_comp_array *SortedCompArray, sorted_layer_array *SortedLayerArray, uint16 PrincipalIndex, bool32 BelowOnly)
+Layer_TestSelection(memory *Memory, project_state *State, ui *UI, sorted_comp_array *SortedCompArray, sorted_layer_array *SortedLayerArray, uint16 CompIndex, bool32 BelowOnly)
 {
-    block_composition *Comp = (block_composition *)Memory_Block_AddressAtIndex(Memory, F_Precomps, PrincipalIndex);
-    sorted_comp_array SortedCompStart = SortedCompArray[PrincipalIndex];
-    sorted_layer_array *SortedLayerStart = Sorted_GetLayerStart(SortedLayerArray, SortedCompArray, PrincipalIndex);
+    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 SelectionCount = 0;
     int SelectedLayerIndex = 0;
     for (int i = SortedCompStart.LayerCount - 1; i >= 0; i--) {
@@ -427,16 +433,27 @@ Layer_TestSelection(memory *Memory, project_state *State, ui *UI, sorted_comp_ar
         Layer_GetDimensions(Memory, Layer, &Width, &Height);
         layer_transforms T = Layer_GetTransforms(Layer);
         v2 UV = T_CompUVToLayerUV(T, Comp->Width, Comp->Height, Width, Height, State->LastClickedPoint);
-        if (UV.x <= 1.0f && UV.x >= 0.0f && UV.y <= 1.0f && UV.y >= 0.0f && !Layer->IsSelected && !Layer->IsLocked)
+        if (UV.x <= 1.0f && UV.x >= 0.0f && UV.y <= 1.0f && UV.y >= 0.0f && (!Layer->IsSelected || (Layer->IsPrecomp && Layer->IsSelected)) && !Layer->IsLocked)
         {
-            if (!BelowOnly && SelectionCount == 1) {
-                if (i < SelectedLayerIndex) {
+            if (Layer->IsPrecomp) {
+                layer_transforms T = Layer_GetTransforms(Layer);
+                if (ExtraT.scale != 0) {
+                    T = Transform_Add(T, ExtraT, Comp->Width, Comp->Height);
+                }
+                T = Transform_TestInteracts(State, Layer, SortEntry, T);
+                LayerIndex = Layer_TestSelection(Memory, State, UI, SortedCompArray, SortedLayerArray, Layer->Block_Source_Index, BelowOnly);
+                if (LayerIndex != -1)
+                    break;
+            } else {
+                if (!BelowOnly && SelectionCount == 1) {
+                    if (i < SelectedLayerIndex) {
+                        LayerIndex = Index_Physical;
+                        break;
+                    }
+                } else {
                     LayerIndex = Index_Physical;
                     break;
                 }
-            } else {
-                LayerIndex = Index_Physical;
-                break;
             }
         }
         // if (Layer->IsPrecomp) {
@@ -448,6 +465,7 @@ Layer_TestSelection(memory *Memory, project_state *State, ui *UI, sorted_comp_ar
     }
     return LayerIndex;
 }
+#endif
 
 // TODO(fox): learn this; stop stealing from stackoverflow
 bool32 Line_TestIntersect(real32 p0_x, real32 p0_y, real32 p1_x, real32 p1_y, 
diff --git a/src/main.cpp b/src/main.cpp
index c7e4808..f8dd546 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -40,6 +40,7 @@ uint32 BitmapFill = 0x00000001;
 #include "imgui_helper.cpp"
 #include "imgui_ui_properties.cpp"
 #include "imgui_ui_timeline.cpp"
+
 #include "imgui_ui_viewport.cpp"
 #if DEBUG
 #include "imgui_ui_debug.cpp"
@@ -345,6 +346,158 @@ GL_Test(const ImDrawList* parent_list, const ImDrawCmd* cmd)
     GL_DeleteHWBuffer(&MSBuffer);
 }
 
+// TODO(fox): We have five functions that essentially do this same precomp loop
+// that carries over transform data; wrap the loop into a function like Block_Loop.
+
+// Find the precomp the user most likely wants to insert a shape into, the
+// precomp that is or contains MostRecentlySelectedLayer.
+static void
+LayerIterate_DeepestPrecomp(project_state *State, memory *Memory, uint32 CompIndex, layer_transforms ExtraT, v2 Center,
+                            sorted_comp_array *SortedCompArray, sorted_layer_array *SortedLayerArray, int *SelectionCount, int *SelectedLayerIndex, int *SelectedPrecompIndex)
+{
+    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 = LayerCount - 1; i >= 0; 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);
+        Assert(Layer->Block_Composition_Index == CompIndex);
+        int Width = 0, Height = 0;
+        Layer_GetDimensions(Memory, Layer, &Width, &Height);
+        if (Layer->IsPrecomp) {
+            // only like 20% sure how this works...
+            layer_transforms NewExtraT = Layer_GetTransforms(Layer);
+            v2 NewCenter = T_CompPosToLayerPos(NewExtraT, Comp->Width, Comp->Height, Width, Height, Center.x, Center.y);
+            NewExtraT.rotation = ExtraT.rotation - NewExtraT.rotation;
+            NewExtraT.scale = ExtraT.scale / NewExtraT.scale;
+            Assert(0);
+            // LayerIterate_Te(State, Memory, Layer->Block_Source_Index, NewExtraT, NewCenter, SortedCompArray, SortedLayerArray);
+        }
+        v2 Position = State->Interact_Transform.Position;
+        real32 Rad = (ExtraT.rotation * (PI / 180));
+        v2 XAxis = Position.x *  ExtraT.scale  * V2(cos(Rad), sin(Rad));
+        v2 YAxis = Position.y * -ExtraT.scale  * V2(sin(Rad), -cos(Rad));
+        Position = XAxis + YAxis;
+        layer_transforms T = Layer_GetTransforms(Layer);
+        v2 UV = T_CompUVToLayerUV(T, Comp->Width, Comp->Height, Width, Height, Center / V2(Comp->Width, Comp->Height));
+        if (UV.x <= 1.0f && UV.x >= 0.0f && UV.y <= 1.0f && UV.y >= 0.0f && Layer->IsSelected)
+        {
+            *SelectionCount += 1;
+            *SelectedLayerIndex = i;
+            *SelectedPrecompIndex = Layer->Block_Composition_Index;
+        }
+    }
+}
+
+// We have to loop over the layers a second time when testing selection because
+// we want to change the behavior if the mouse is clicking on a layer that is
+// already selected.
+static void
+LayerIterate_SelectionStatus(project_state *State, memory *Memory, uint32 CompIndex, layer_transforms ExtraT, v2 Center,
+                             sorted_comp_array *SortedCompArray, sorted_layer_array *SortedLayerArray, int *SelectionCount, int *SelectedLayerIndex, int *SelectedPrecompIndex)
+{
+    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 = LayerCount - 1; i >= 0; 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);
+        Assert(Layer->Block_Composition_Index == CompIndex);
+        int Width = 0, Height = 0;
+        Layer_GetDimensions(Memory, Layer, &Width, &Height);
+        if (Layer->IsPrecomp) {
+            // only like 20% sure how this works...
+            layer_transforms NewExtraT = Layer_GetTransforms(Layer);
+            v2 NewCenter = T_CompPosToLayerPos(NewExtraT, Comp->Width, Comp->Height, Width, Height, Center.x, Center.y);
+            NewExtraT.rotation = ExtraT.rotation - NewExtraT.rotation;
+            NewExtraT.scale = ExtraT.scale / NewExtraT.scale;
+            LayerIterate_SelectionStatus(State, Memory, Layer->Block_Source_Index, NewExtraT, NewCenter, SortedCompArray, SortedLayerArray, SelectionCount, SelectedLayerIndex, SelectedPrecompIndex);
+        }
+        v2 Position = State->Interact_Transform.Position;
+        real32 Rad = (ExtraT.rotation * (PI / 180));
+        v2 XAxis = Position.x *  ExtraT.scale  * V2(cos(Rad), sin(Rad));
+        v2 YAxis = Position.y * -ExtraT.scale  * V2(sin(Rad), -cos(Rad));
+        Position = XAxis + YAxis;
+        layer_transforms T = Layer_GetTransforms(Layer);
+        v2 UV = T_CompUVToLayerUV(T, Comp->Width, Comp->Height, Width, Height, Center / V2(Comp->Width, Comp->Height));
+        if (UV.x <= 1.0f && UV.x >= 0.0f && UV.y <= 1.0f && UV.y >= 0.0f && Layer->IsSelected && !Layer->IsLocked)
+        {
+            *SelectionCount += 1;
+            *SelectedLayerIndex = i;
+            *SelectedPrecompIndex = Layer->Block_Composition_Index;
+        }
+    }
+}
+
+static int32
+LayerIterate_SelectionAct(project_state *State, memory *Memory, uint32 CompIndex, layer_transforms ExtraT, v2 Center,
+                             sorted_comp_array *SortedCompArray, sorted_layer_array *SortedLayerArray, int SelectionCount, int SelectedLayerIndex, int SelectedPrecompIndex, bool32 BelowOnly)
+{
+    int32 LayerIndex = -1;
+    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 = LayerCount - 1; i >= 0; 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);
+        Assert(Layer->Block_Composition_Index == CompIndex);
+        int Width = 0, Height = 0;
+        Layer_GetDimensions(Memory, Layer, &Width, &Height);
+        if (Layer->IsPrecomp && Layer->IsSelected) {
+            // only like 20% sure how this works...
+            layer_transforms NewExtraT = Layer_GetTransforms(Layer);
+            v2 NewCenter = T_CompPosToLayerPos(NewExtraT, Comp->Width, Comp->Height, Width, Height, Center.x, Center.y);
+            NewExtraT.rotation = ExtraT.rotation - NewExtraT.rotation;
+            NewExtraT.scale = ExtraT.scale / NewExtraT.scale;
+            LayerIndex = LayerIterate_SelectionAct(State, Memory, Layer->Block_Source_Index, NewExtraT, NewCenter, SortedCompArray, SortedLayerArray, SelectionCount, SelectedLayerIndex, SelectedPrecompIndex, BelowOnly);
+            if (LayerIndex != -1) {
+                Layer->IsSelected = -1;
+                return LayerIndex;
+            }
+        }
+        v2 Position = State->Interact_Transform.Position;
+        real32 Rad = (ExtraT.rotation * (PI / 180));
+        v2 XAxis = Position.x *  ExtraT.scale  * V2(cos(Rad), sin(Rad));
+        v2 YAxis = Position.y * -ExtraT.scale  * V2(sin(Rad), -cos(Rad));
+        Position = XAxis + YAxis;
+        layer_transforms T = Layer_GetTransforms(Layer);
+        v2 UV = T_CompUVToLayerUV(T, Comp->Width, Comp->Height, Width, Height, Center / V2(Comp->Width, Comp->Height));
+        if (UV.x <= 1.0f && UV.x >= 0.0f && UV.y <= 1.0f && UV.y >= 0.0f && !Layer->IsSelected && !Layer->IsLocked)
+        {
+            if (!BelowOnly && SelectionCount == 1) {
+                if (i < SelectedLayerIndex || SelectedPrecompIndex != CompIndex) {
+                    LayerIndex = Index_Physical;
+                    break;
+                }
+            } else {
+                LayerIndex = Index_Physical;
+                break;
+            }
+        }
+    }
+    return LayerIndex;
+}
+
+static int32
+LayerIterate_TestSelection(project_state *State, memory *Memory, uint32 PrincipalIndex, layer_transforms ExtraT, v2 Center,
+                             sorted_comp_array *SortedCompArray, sorted_layer_array *SortedLayerArray, bool32 BelowOnly)
+{
+    int SelectionCount = 0;
+    int SelectedLayerIndex = 0;
+    int SelectedPrecompIndex = 0;
+    LayerIterate_SelectionStatus(State, Memory, PrincipalIndex, ExtraT, Center, SortedCompArray, SortedLayerArray, &SelectionCount, &SelectedLayerIndex, &SelectedPrecompIndex);
+    return LayerIterate_SelectionAct(State, Memory, PrincipalIndex, ExtraT, Center, SortedCompArray, SortedLayerArray, SelectionCount, SelectedLayerIndex, SelectedPrecompIndex, BelowOnly);
+}
+
 // Same loop as Render_UI and ImGui_Viewport_SelectedLayerUI except we're
 // finding the inverse transform-- i.e. transforming the layers locally rather
 // than transforming them in comp space.
@@ -367,17 +520,8 @@ LayerIterate(project_state *State, memory *Memory, uint32 CompIndex, layer_trans
             // only like 20% sure how this works...
             layer_transforms NewExtraT = Layer_GetTransforms(Layer);
             v2 NewCenter = T_CompPosToLayerPos(NewExtraT, Comp->Width, Comp->Height, Width, Height, Center.x, Center.y);
-            if (State->Interact_Transform.Radians != 0.0f)
-                NewExtraT.rotation = -NewExtraT.rotation;
-            if (ExtraT.scale) {
-                NewExtraT = Transform_Add2(ExtraT, NewExtraT, Comp->Width, Comp->Height);
-                v2 NewPos = T_CompPosToLayerPos(NewExtraT, Comp->Width, Comp->Height, Width, Height, NewExtraT.x, NewExtraT.y);
-                NewExtraT.x = NewPos.x;
-                NewExtraT.y = NewPos.y;
-            } else {
-                NewExtraT.rotation = -NewExtraT.rotation;
-                NewExtraT.scale = 1.0f / NewExtraT.scale;
-            }
+            NewExtraT.rotation = ExtraT.rotation - NewExtraT.rotation;
+            NewExtraT.scale = ExtraT.scale / NewExtraT.scale;
             LayerIterate(State, Memory, Layer->Block_Source_Index, NewExtraT, NewCenter, SortedCompArray, SortedLayerArray);
         }
         if (Layer->IsSelected == 1) {
@@ -385,12 +529,17 @@ LayerIterate(project_state *State, memory *Memory, uint32 CompIndex, layer_trans
             if (Layer->IsPrecomp)
                 PostMsg(State, "DEBUG: Precomp transformed!");
 #endif
+            v2 Position = State->Interact_Transform.Position;
+            real32 Rad = (ExtraT.rotation * (PI / 180));
+            v2 XAxis = Position.x *  ExtraT.scale  * V2(cos(Rad), sin(Rad));
+            v2 YAxis = Position.y * -ExtraT.scale  * V2(sin(Rad), -cos(Rad));
+            Position = XAxis + YAxis;
             layer_transforms T = Layer_GetTransforms(Layer);
             History_Action_Swap(Memory, F_Layers, sizeof(Layer->x.CurrentValue), &Layer->x.CurrentValue);
             History_Action_Swap(Memory, F_Layers, sizeof(Layer->y.CurrentValue), &Layer->y.CurrentValue);
             History_Action_Swap(Memory, F_Layers, sizeof(Layer->scale.CurrentValue), &Layer->scale.CurrentValue);
             History_Action_Swap(Memory, F_Layers, sizeof(Layer->rotation.CurrentValue), &Layer->rotation.CurrentValue);
-            Transform_ApplyLocal(State->Interact_Transform, T, ExtraT, Center, Comp->Width, Comp->Height, Width, Height,
+            Transform_ApplyLocal(State->Interact_Transform, Position, Center,
                     &Layer->x.CurrentValue, &Layer->y.CurrentValue, &Layer->rotation.CurrentValue, &Layer->scale.CurrentValue);
         }
     }
@@ -862,7 +1011,7 @@ int main(int argc, char *argv[]) {
     MainComp->Frame_Count = 80;
     MainComp->Frame_End = 79;
     MainComp->Occupied = 1;
-    MainComp->Name_String_Index = String_AddToFile(&Memory, "Main comp");
+    MainComp->Name_String_Index = String_AddToFile(&Memory, "Main comp", 0);
 
     File->Comp_Count = 1;
 
@@ -1014,7 +1163,7 @@ int main(int argc, char *argv[]) {
         State->UpdateFrame = true;
         State->MostRecentlySelectedLayer = 0;
     }
-
+    // File->PrincipalCompIndex = 1;
 #else
     // uint16 SourceIndex = Source_Generate(File, State, &Memory, (void *)"../asset/yu.webm");
     // block_source *Source = (block_source *)Memory_Block_AddressAtIndex(&Memory, F_Sources, SourceIndex);
@@ -1082,6 +1231,14 @@ int main(int argc, char *argv[]) {
 
         // NOTE(fox): These commands affect sorting, and should not be executed
         // in any UI or the renderer.
+
+        // TODO(fox): Even though they affect sorting there are some of them
+        // that _need_ sort info in order to be able to be wrapped in one
+        // HistoryEntry, so we can either create a new sort info in place or
+        // execute the command right before the sort info used by the
+        // UI/renderer is popped, and I'm choosing the latter.
+        int State_ExecuteAtEnd = 0;
+
         if (State->HotkeyInput) {
             switch (State->HotkeyInput) {
                 case hotkey_none:
@@ -1104,13 +1261,17 @@ int main(int argc, char *argv[]) {
                 } break;
                 case hotkey_newlayer_shape:
                 {
-                    Project_ShapeLayer_New(File, State, &Memory);
+                    State_ExecuteAtEnd = 1;
                 } break;
                 case hotkey_newlayer_source:
                 {
                     Source_UICreateButton(File, State, &Memory);
                     State->UpdateKeyframes = true;
                 } break;
+                case hotkey_duplicatelayer:
+                {
+                    State_ExecuteAtEnd = 2;
+                } break;
                 case hotkey_deletelayer:
                 {
                     Project_Layer_Delete(File, State, &Memory);
@@ -1223,6 +1384,19 @@ int main(int argc, char *argv[]) {
         Arbitrary_Zero((uint8 *)State->Queue.Item, sizeof(State->Queue.Item));
 #endif
 
+        if (State_ExecuteAtEnd) {
+            if (State_ExecuteAtEnd == 1) {
+                Project_ShapeLayer_New(File, State, &Memory);
+            } else if (State_ExecuteAtEnd == 2) {
+                History_Entry_Commit(&Memory, "Duplicate layers");
+                v2 Offset = V2(State->Interact_Dup_Previous[0], State->Interact_Dup_Previous[1]);
+                Project_Layer_Duplicate(File, State, &Memory, Sorted.CompArray, Sorted.LayerArray, Offset, 0);
+                State->Interact_Transform = {};
+                History_Entry_End(&Memory);
+            }
+            State->UpdateFrame = true;
+        }
+
         File_Sort_Pop(&Memory, Sorted.Layer_SortSize, Sorted.Property_SortSize, Sorted.Source_SortSize);
 
         // NOTE(fox): We could theoretically allow scratch to persist between
diff --git a/src/prenderer.cpp b/src/prenderer.cpp
index 11f72f6..0af9911 100644
--- a/src/prenderer.cpp
+++ b/src/prenderer.cpp
@@ -63,20 +63,9 @@ Transform_ScreenSpaceToLocal(layer_transforms T, uint32 FileWidth, uint32 FileHe
 
 // Transform given data based on state's Interact data.
 static void
-Transform_ApplyInteractive(interact_transform Interact, real32 *OutputX, real32 *OutputY, real32 *OutputRotation, real32 *OutputScale)
+Transform_ApplyLocal(interact_transform Interact, v2 Position, v2 Center,
+                    real32 *OutputX, real32 *OutputY, real32 *OutputRotation, real32 *OutputScale)
 {
-    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;
-    }
-
     real32 Point0X = Center.x - *OutputX;
     real32 Point0Y = Center.y - *OutputY;
 
@@ -89,13 +78,31 @@ Transform_ApplyInteractive(interact_transform Interact, real32 *OutputX, real32
     real32 X0 = -XAxis.x - YAxis.x + Center.x;
     real32 Y0 = -XAxis.y - YAxis.y + Center.y;
 
-    *OutputX = X0 + Interact.Position.x;
-    *OutputY = Y0 + Interact.Position.y;
+    *OutputX = X0 + Position.x;
+    *OutputY = Y0 + Position.y;
     *OutputRotation += Rotation;
-    // *OutputScale += Interact.Scale - 1.0f;
     *OutputScale *= Interact.Scale;
 }
 
+// TODO(fox): Clean up function to do the RadianOffset rotation earlier in the loops of where the function is called
+static void
+Transform_ApplyInteractive(interact_transform Interact, real32 *OutputX, real32 *OutputY, real32 *OutputRotation, real32 *OutputScale)
+{
+    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;
+    }
+
+    Transform_ApplyLocal(Interact, Interact.Position, Center, OutputX, OutputY, OutputRotation, OutputScale);
+}
+
 static v2
 TransformVec(layer_transforms T, real32 Width, real32 Height, v2 Point)
 {
@@ -106,36 +113,6 @@ TransformVec(layer_transforms T, real32 Width, real32 Height, v2 Point)
     return V2(LocalPoint.x, LocalPoint.y);
 }
 
-static void
-Transform_ApplyLocal(interact_transform Interact, layer_transforms T, layer_transforms ExtraT, v2 Center,
-            real32 CompWidth, real32 CompHeight, real32 Width, real32 Height,
-            real32 *OutputX, real32 *OutputY, real32 *OutputRotation, real32 *OutputScale)
-{
-    v2 Position = Interact.Position;
-    if (ExtraT.scale) {
-        v2 CompPosition = TransformVec(ExtraT, CompWidth, CompHeight, Interact.Position);
-        Position = CompPosition;
-    }
-
-    real32 Point0X = Center.x - *OutputX;
-    real32 Point0Y = Center.y - *OutputY;
-
-    real32 Rad = Interact.Radians;
-    real32 Rotation = Rad / (PI / 180);
-
-    v2 XAxis = (Point0X *  Interact.Scale)*V2(cos(Rad),  sin(Rad));
-    v2 YAxis = (Point0Y * -Interact.Scale)*V2(sin(Rad), -cos(Rad));
-
-    real32 X0 = -XAxis.x - YAxis.x + Center.x;
-    real32 Y0 = -XAxis.y - YAxis.y + Center.y;
-
-    *OutputX = X0 + Position.x;
-    *OutputY = Y0 + Position.y;
-    *OutputRotation += Rotation;
-    // *OutputScale += Interact.Scale - 1.0f;
-    *OutputScale *= Interact.Scale;
-}
-
 static void
 Transform_IterateOuterBounds(block_layer *Layer, uint32 Width, uint32 Height, real32 *MinX, real32 *MinY, real32 *MaxX, real32 *MaxY)
 {
@@ -353,19 +330,6 @@ Transform_Add(layer_transforms T, layer_transforms ExtraT, real32 Width, real32
     return T;
 }
 
-static layer_transforms
-Transform_Add2(layer_transforms T, layer_transforms ExtraT, real32 Width, real32 Height)
-{
-    v2 NewPos = TransformPoint(ExtraT, Width, Height, V2(T.x, T.y));
-    T.x = NewPos.x;
-    T.y = NewPos.y;
-    T.ax = T.ax;
-    T.ay = T.ay;
-    T.rotation = T.rotation - ExtraT.rotation;
-    T.scale = T.scale / ExtraT.scale;
-    return T;
-}
-
 static ImVec2
 Layer_LocalToScreenSpace(project_state *State, memory *Memory, block_layer *Layer, ui *UI, uint32 PrincipalCompIndex, v2 Point)
 {
diff --git a/src/sorted.cpp b/src/sorted.cpp
index 1d2b4f2..5826e68 100644
--- a/src/sorted.cpp
+++ b/src/sorted.cpp
@@ -155,6 +155,93 @@ Layer_Sort_CheckPrev(memory *Memory, int i, int Direction, sorted_layer_array *S
 // second is for sorting the layers by offset, and the third is for applying
 // interactivity if the user is moving any layers.
 
+static void
+Layer_Sort_Shift(project_state *State, memory *Memory,
+                 sorted_layer_array *LayerArrayStart,  sorted_comp_array *CompArrayStart,
+                 uint32 LayerCount, uint32 CompCount)
+{
+    int32 Offset = (int32)State->Interact_Offset[1];
+    bool32 Direction = (Offset > 0) ? 1 : -1;
+    for (uint32 c = 0; c < CompCount; c++) {
+        sorted_comp_array *SortedCompStart = &CompArrayStart[c];
+        if (!SortedCompStart->LayerCount)
+            continue;
+        sorted_layer_array *SortedLayerStart = Sorted_GetLayerStart(LayerArrayStart, CompArrayStart, c);
+        int i = (Direction > 0) ? SortedCompStart->LayerCount - 1 : 0;
+        bool32 Case = 1;
+        while (Case) {
+            int32 EntriesPassed = 0;
+            sorted_layer_array *LayerEntry = &SortedLayerStart[i];
+            block_layer *Layer = (block_layer *)Memory_Block_AddressAtIndex(Memory, F_Layers, LayerEntry->Block_Layer_Index);
+            Assert(LayerEntry->SortedOffset == Layer->Vertical_Offset);
+            if (Layer->IsSelected) {
+                int32 SpacesToMove = Offset * Direction;
+                while (SpacesToMove) {
+                    Layer_Sort_CheckPrev(Memory, i, Direction, SortedLayerStart, *SortedCompStart, &EntriesPassed, LayerEntry, 0);
+                    LayerEntry->SortedOffset -= Direction;
+                    SpacesToMove--;
+                }
+            }
+            int b = 0;
+            while (b < EntriesPassed) {
+                sorted_layer_array *FrontEntry = &SortedLayerStart[i+(b*Direction)];
+                sorted_layer_array *BackEntry = &SortedLayerStart[i+((b+1)*Direction)];
+                sorted_layer_array Swap = *FrontEntry;
+                *FrontEntry = *BackEntry;
+                *BackEntry = Swap;
+                b++;
+            }
+            i -= Direction;
+            Case = (Direction > 0) ? (i >= 0) : (i < SortedCompStart->LayerCount);
+        }
+    }
+}
+
+#if 0
+static void
+Layer_Sort_Shift2(project_state *State, memory *Memory,
+                  sorted_layer_array *LayerArrayStart, sorted_comp_array *CompArrayStart,
+                  uint32 LayerCount, uint32 CompCount, int32 Offset)
+{
+    bool32 Direction = (Offset > 0) ? 1 : -1;
+    for (uint32 c = 0; c < CompCount; c++) {
+        sorted_comp_array *SortedCompStart = &CompArrayStart[c];
+        if (!SortedCompStart->LayerCount)
+            continue;
+        sorted_layer_array *SortedLayerStart = Sorted_GetLayerStart(LayerArrayStart, CompArrayStart, c);
+        int i = (Direction > 0) ? SortedCompStart->LayerCount - 1 : 0;
+        int FauxIncrement = 0;
+        bool32 Case = 1;
+        while (Case) {
+            int Idx = i + FauxIncrement;
+            int32 EntriesPassed = 0;
+            sorted_layer_array *LayerEntry = &SortedLayerStart[Idx];
+            block_layer *Layer = (block_layer *)Memory_Block_AddressAtIndex(Memory, F_Layers, LayerEntry->Block_Layer_Index);
+            Assert(LayerEntry->SortedOffset == Layer->Vertical_Offset);
+            if (Layer->IsSelected) {
+                int32 SpacesToMove = Offset * Direction;
+                Assert(SpacesToMove == 1);
+                while (SpacesToMove) {
+                    Layer_Sort_CheckPrev(Memory, i, Direction, SortedLayerStart, *SortedCompStart, &EntriesPassed, LayerEntry, 0);
+                    SpacesToMove--;
+                }
+                int b = 0;
+                while (b < EntriesPassed) {
+                    sorted_layer_array *FrontEntry = &SortedLayerStart[i+(b*Direction)];
+                    sorted_layer_array *BackEntry = &SortedLayerStart[i+((b+1)*Direction)];
+                    sorted_layer_array Swap = *FrontEntry;
+                    *FrontEntry = *BackEntry;
+                    *BackEntry = Swap;
+                    b++;
+                }
+            }
+            i -= Direction;
+            Case = (Direction > 0) ? (i >= 0) : (i < SortedCompStart->LayerCount);
+        }
+    }
+}
+#endif
+
 static void
 Layer_SortAll(project_state *State, memory *Memory,
               sorted_layer_array *LayerArrayStart,  sorted_comp_array *CompArrayStart,
@@ -166,7 +253,7 @@ Layer_SortAll(project_state *State, memory *Memory,
         Assert(Layer->Block_Composition_Index < CompCount);
         sorted_comp_array *SortedCompStart = &CompArrayStart[Layer->Block_Composition_Index];
         SortedCompStart->LayerCount++;
-        if (State->Interact_Active == interact_type_viewport_duplicate && Layer->IsSelected) {
+        if (State->Interact_Active == interact_type_viewport_duplicate && Layer->IsSelected == 1) {
             SortedCompStart->FakeLayerCount++;
         }
     }
@@ -197,41 +284,7 @@ Layer_SortAll(project_state *State, memory *Memory,
         SortedCompStart->CurrentSortIndex++;
     }
     if (State->Interact_Active == interact_type_layer_move) {
-        int32 Offset = (int32)State->Interact_Offset[1];
-        bool32 Direction = (Offset > 0) ? 1 : -1;
-        for (uint32 c = 0; c < CompCount; c++) {
-            sorted_comp_array *SortedCompStart = &CompArrayStart[c];
-            if (!SortedCompStart->LayerCount)
-                continue;
-            sorted_layer_array *SortedLayerStart = Sorted_GetLayerStart(LayerArrayStart, CompArrayStart, c);
-            int i = (Direction > 0) ? SortedCompStart->LayerCount - 1 : 0;
-            bool32 Case = 1;
-            while (Case) {
-                int32 EntriesPassed = 0;
-                sorted_layer_array *LayerEntry = &SortedLayerStart[i];
-                block_layer *Layer = (block_layer *)Memory_Block_AddressAtIndex(Memory, F_Layers, LayerEntry->Block_Layer_Index);
-                Assert(LayerEntry->SortedOffset == Layer->Vertical_Offset);
-                if (Layer->IsSelected) {
-                    int32 SpacesToMove = Offset * Direction;
-                    while (SpacesToMove) {
-                        Layer_Sort_CheckPrev(Memory, i, Direction, SortedLayerStart, *SortedCompStart, &EntriesPassed, LayerEntry, 0);
-                        LayerEntry->SortedOffset -= Direction;
-                        SpacesToMove--;
-                    }
-                }
-                int b = 0;
-                while (b < EntriesPassed) {
-                    sorted_layer_array *FrontEntry = &SortedLayerStart[i+(b*Direction)];
-                    sorted_layer_array *BackEntry = &SortedLayerStart[i+((b+1)*Direction)];
-                    sorted_layer_array Swap = *FrontEntry;
-                    *FrontEntry = *BackEntry;
-                    *BackEntry = Swap;
-                    b++;
-                }
-                i -= Direction;
-                Case = (Direction > 0) ? (i >= 0) : (i < SortedCompStart->LayerCount);
-            }
-        }
+        Layer_Sort_Shift(State, Memory, LayerArrayStart, CompArrayStart, LayerCount, CompCount);
     }
     else if (State->Interact_Active == interact_type_viewport_duplicate) {
         for (uint32 c = 0; c < CompCount; c++) {
@@ -245,7 +298,7 @@ Layer_SortAll(project_state *State, memory *Memory,
                 int Idx = i + FauxIncrement;
                 sorted_layer_array *LayerEntry = &SortedLayerStart[Idx];
                 block_layer *Layer = (block_layer *)Memory_Block_AddressAtIndex(Memory, F_Layers, LayerEntry->Block_Layer_Index);
-                if (Layer->IsSelected) {
+                if (Layer->IsSelected == 1) {
                     for (int a = i+1; a < SortedCompStart->LayerCount; a++) {
                         int PrevIdx = a + FauxIncrement - 1;
                         sorted_layer_array *PrevLayerEntry = &SortedLayerStart[PrevIdx];
diff --git a/src/strings.cpp b/src/strings.cpp
index e6c3ec5..73adf8a 100644
--- a/src/strings.cpp
+++ b/src/strings.cpp
@@ -28,11 +28,12 @@ CopyStrings(void *Dest, void *Data)
 }
 
 static uint16
-String_AddToFile(memory *Memory, char *Char)
+String_AddToFile(memory *Memory, char *Char, bool32 NoUndo)
 {
     uint16 FileIndex = Memory_Block_AllocateNew(Memory, F_Strings);
     block_string *String = (block_string *)Memory_Block_AddressAtIndex(Memory, F_Strings, FileIndex, 0);
-    History_Action_Block_Swap(Memory, F_Strings, String);
+    if (NoUndo)
+        History_Action_Block_Swap(Memory, F_Strings, String);
     String->Occupied = 1;
     uint16 i = 0;
     while (Char[i] != '\0') {
diff --git a/src/undo.cpp b/src/undo.cpp
index 38c22ef..c26ab1b 100644
--- a/src/undo.cpp
+++ b/src/undo.cpp
@@ -271,6 +271,7 @@ History_Purge(memory *Memory, history_entry_list *History, uint64 ActionCount_To
 
 static void History_Action_Add(memory *Memory, history_action ActionData, void *ReferenceData = NULL)
 {
+    Assert(Debug.UndoState == 1);   // This action wasn't preceded by a History_Undo_Start!
     history_entry_list *History = &Memory->History;
     history_entry *Entry = &History->Entry[History->EntryPlayhead - 1];
 
-- 
cgit v1.2.3