From ed27ab2e6bbe40120702dcc57e9b21434bfb4944 Mon Sep 17 00:00:00 2001
From: Fox Caminiti <fox@foxcam.net>
Date: Sat, 3 Dec 2022 21:57:53 -0500
Subject: v2.0, effects functional

---
 bezier.cpp               |   1 -
 createcalls.cpp          | 328 +++++++++++++++++++++++++++++++-------
 defines.h                |   2 +
 effects.cpp              |  46 +++++-
 effects_constructors.cpp |  31 ++--
 effects_gl.cpp           |  31 +++-
 effects_software.cpp     |  28 ++--
 functions.h              |   5 +
 imgui_ops.h              |  10 ++
 keybinds.h               |   6 +-
 main.cpp                 |  29 ++--
 main.h                   |   7 +-
 my_imgui_widgets.cpp     | 403 ++++++++++++++++++++++++++++++++++++++++++-----
 package.sh               |   6 +-
 14 files changed, 775 insertions(+), 158 deletions(-)

diff --git a/bezier.cpp b/bezier.cpp
index f476986..1a177c4 100644
--- a/bezier.cpp
+++ b/bezier.cpp
@@ -1,4 +1,3 @@
-static real32 Tau = 0.9; // tension
 
 static real32
 Bezier_SolveYForX(v2 Point_P0, v2 Point_P1, v2 Point_P2, v2 Point_P3, real32 TargetX) {
diff --git a/createcalls.cpp b/createcalls.cpp
index 145a58a..912917f 100644
--- a/createcalls.cpp
+++ b/createcalls.cpp
@@ -206,14 +206,14 @@ Bezier_EvaluateValue(project_state *State, bezier_point *PointAddress, v2 *Pos,
 
 
 static void
-Bezier_Add(memory *Memory, property_channel *Property, bezier_point PointData, uint16 *ArrayLocation)
+Bezier_Add(memory *Memory, memory_table_list TableName, property_channel *Property, bezier_point PointData, uint16 *ArrayLocation)
 {
     if (!Property->Block_Bezier_Count) {
         Property->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], 0);
         Bezier->Occupied = true;
         // NOTE(fox): Effects will change this!
-        History_Action_Swap(Memory, F_Layers, sizeof(Property->Block_Bezier_Count), &Property->Block_Bezier_Count);
+        History_Action_Swap(Memory, TableName, sizeof(Property->Block_Bezier_Count), &Property->Block_Bezier_Count);
         Property->Block_Bezier_Count++;
     }
     // First check to see if the point to add overlaps an existing keyframe:
@@ -234,7 +234,7 @@ Bezier_Add(memory *Memory, property_channel *Property, bezier_point PointData, u
         if (!Point->Occupied) {
             History_Action_Swap(Memory, F_Bezier, sizeof(*Point), Point);
             *Point = PointData;
-            History_Action_Swap(Memory, F_Layers, sizeof(Property->Keyframe_Count), &Property->Keyframe_Count);
+            History_Action_Swap(Memory, TableName, sizeof(Property->Keyframe_Count), &Property->Keyframe_Count);
             Property->Keyframe_Count++;
             return;
         }
@@ -243,11 +243,11 @@ Bezier_Add(memory *Memory, property_channel *Property, bezier_point PointData, u
 }
 
 static void
-Property_AddKeyframe(memory *Memory, property_channel *Property, int Frame, uint16 *ArrayLocation)
+Property_AddKeyframe(memory *Memory, memory_table_list TableName, property_channel *Property, int Frame, uint16 *ArrayLocation)
 {
     History_Entry_Commit(Memory, "Add keyframe");
     bezier_point Point = { 1, {(real32)Frame, Property->CurrentValue, -1, 0, 1, 0}, interpolation_type_linear, 0, {0, 0, 0}, 0 };
-    Bezier_Add(Memory, Property, Point, ArrayLocation);
+    Bezier_Add(Memory, TableName, Property, Point, ArrayLocation);
     History_Entry_End(Memory);
 }
 
@@ -257,9 +257,8 @@ Property_AddKeyframe(memory *Memory, property_channel *Property, int Frame, uint
 // }
 
 static property_channel
-Property_InitFloat(char *Name, real32 Val, real32 ScrubVal, real32 MinVal = PROPERTY_REAL_MIN, real32 MaxVal = PROPERTY_REAL_MAX, bool32 AlwaysInteger = 0) {
+Property_InitFloat(real32 Val, real32 ScrubVal, real32 MinVal = PROPERTY_REAL_MIN, real32 MaxVal = PROPERTY_REAL_MAX, bool32 AlwaysInteger = 0) {
     property_channel Property = {};
-    Property.Name = Name;
     Property.CurrentValue = Val;
     Property.MinVal = MinVal;
     Property.MaxVal = MaxVal;
@@ -331,7 +330,7 @@ Effect_Init(project_state *State, memory *Memory, uint32 EffectEntryIndex, int E
         property_channel *Property = (property_channel *)Memory_Block_AddressAtIndex(Memory, F_Properties, Effect->Block_Property_Index[e], 0);
         Property->Occupied = true;
         header_property PropertyHeader = State->Property[EffectHeader->PropertyStartIndex + e];
-        Property->Name = PropertyHeader.Name;
+        Property->Identifier = -1;
         Property->CurrentValue = PropertyHeader.DefaultValue;
         Property->MinVal = PropertyHeader.MinVal;
         Property->MaxVal = PropertyHeader.MaxVal;
@@ -373,13 +372,32 @@ Layer_UpdateMasksEffects(project_state *State, block_layer *Layer, memory *Memor
         header_effect *EffectEntry = Effect_EntryFromID(State, Effect.ID);
 
         if (Effect.IsToggled) {
-            real32 *Data = (real32 *)Memory_PushScratch(Memory, sizeof(real32) * 50);
-            for (int c = 0; c < EffectEntry->Property_Count; c++) {
-                property_channel *Property = (property_channel *)Memory_Block_AddressAtIndex(Memory, F_Properties, Effect.Block_Property_Index[c]);
-                Data[c] = Property->CurrentValue;
+            uint64 Size = (sizeof(real32) * MAX_PROPERTIES_PER_EFFECT) + (sizeof(real32) * 10);
+            real32 *Data;
+            if (EffectEntry->DisplayType == effect_display_type_curves) {
+                Data = (real32 *)Memory_PushScratch(Memory, Size);
+                uint16 SortedPointIndex[MAX_PROPERTIES_PER_EFFECT];
+                v2 *SortedPointValues = (v2 *)(Data + 5);
+                for (int c = 0; c < 5; c++) {
+                    *(Data + c) = Effect.ExtraData[c];
+                    uint32 Shift = MAX_PROPERTIES_PER_EFFECT / 5 * c;
+                    uint16 *SortedPointIndexPlayhead = SortedPointIndex + Shift;
+                    v2 *SortedPointValuesPlayhead = SortedPointValues + Shift;
+                    Effect_Curves_Sort(Memory, &Effect, SortedPointIndexPlayhead, c);
+                    for (int a = 0; a < Effect.ExtraData[c]; a++) {
+                        *SortedPointValuesPlayhead = Effect_V2(Memory, &Effect, SortedPointIndexPlayhead[a]);
+                        SortedPointValuesPlayhead++;
+                    }
+                }
+            } else {
+                Data = (real32 *)Memory_PushScratch(Memory, Size);
+                for (int c = 0; c < EffectEntry->Property_Count; c++) {
+                    property_channel *Property = (property_channel *)Memory_Block_AddressAtIndex(Memory, F_Properties, Effect.Block_Property_Index[c]);
+                    Data[c] = Property->CurrentValue;
+                }
             }
             EffectEntry->func(Data, Width, Height, BytesPerPixel, EffectBitmapAddress, EffectEntry->GLShaderIndex);
-            Memory_PopScratch(Memory, sizeof(real32) * 50);
+            Memory_PopScratch(Memory, Size);
         }
     }
     /*
@@ -457,9 +475,9 @@ Layer_LoopChannels(project_state *State, memory *Memory, sorted_property_info **
                    property_channel **Property, block_effect **EffectOut, int *h, int *c, int *p)
 {
     uint32 Amount = AmountOf(Layer->Property) + Layer->Block_Effect_Count;
-    Assert(Layer->Block_Effect_Count < 2);
+    // Assert(Layer->Block_Effect_Count < 2);
     while (*h < Amount) {
-        if (*h < AmountOf(Layer->Property)) {
+        if (*h < AmountOf(Layer->Property) && *c == 0) {
             *Property = &Layer->Property[*h];
             if (*h != 0) {
                 *SortedProperty += 1;
@@ -477,12 +495,13 @@ Layer_LoopChannels(project_state *State, memory *Memory, sorted_property_info **
             while (*c < EffectHeader->Property_Count) {
                 // header_property ChannelHeader = State->Property[EffectHeader->PropertyStartIndex + c];
                 *Property = (property_channel *)Memory_Block_AddressAtIndex(Memory, F_Properties, Effect->Block_Property_Index[*c]);
+                *SortedProperty += 1;
                 *SortedKeyframe += *p;
                 *p = (**Property).Keyframe_Count;
-                *h += 1;
                 *c += 1;
                 return 1;
             }
+            *h += 1;
             *c = 0;
         }
     }
@@ -490,6 +509,53 @@ Layer_LoopChannels(project_state *State, memory *Memory, sorted_property_info **
     return 0;
 }
 
+static void
+Layer_ToggleAllChannels(project_state *State, memory *Memory, block_layer *Layer,
+                        sorted_comp_info *SortedCompInfo, sorted_layer *SortedLayerInfo,
+                        sorted_property_info *SortedPropertyInfo, uint16 *SortedPropertyArray)
+{
+    bool32 ToggleMode = 1;
+    {
+        sorted_property_info *InfoLocation = SortedPropertyInfo + SortedLayerInfo->SortedPropertyStart;
+        uint16 *ArrayLocation = SortedPropertyArray + SortedLayerInfo->SortedKeyframeStart;
+        int h = 0, c = 0, p = 0;
+        property_channel *Property = NULL;
+        block_effect *Effect = NULL;
+        while (Layer_LoopChannels(State, Memory, &InfoLocation, &ArrayLocation, Layer, &Property, &Effect, &h, &c, &p))
+        {
+            if (Property->IsToggled) {
+                ToggleMode = 0;
+                break;
+            }
+        }
+    }
+    sorted_property_info *InfoLocation = SortedPropertyInfo + SortedLayerInfo->SortedPropertyStart;
+    uint16 *ArrayLocation = SortedPropertyArray + SortedLayerInfo->SortedKeyframeStart;
+    int h = 0, c = 0, p = 0;
+    property_channel *Property = NULL;
+    block_effect *Effect = NULL;
+    while (Layer_LoopChannels(State, Memory, &InfoLocation, &ArrayLocation, Layer, &Property, &Effect, &h, &c, &p))
+    {
+        if (Property->Keyframe_Count) {
+            Property->IsToggled = ToggleMode;
+        }
+    }
+}
+
+static void
+Project_ToggleAllChannels(project_data *File, project_state *State, memory *Memory,
+                          sorted_comp_info *SortedCompInfo, sorted_layer *SortedLayerInfo,
+                          sorted_property_info *SortedPropertyInfo, uint16 *SortedPropertyArray)
+{
+    int h = 0, c = 0, i = 0;
+    while (Block_Loop(Memory, F_Layers, File->Layer_Count, &h, &c, &i)) {
+        block_layer *Layer = (block_layer *)Memory_Block_AddressAtIndex(Memory, F_Layers, i);
+        if (Layer->IsSelected) {
+            Layer_ToggleAllChannels(State, Memory, Layer, SortedCompInfo, SortedLayerInfo, SortedPropertyInfo, SortedPropertyArray);
+        }
+    }
+}
+
 inline sorted_property_info *
 Property_GetSortedInfo(sorted_property_info *SortedPropertyInfo, int i, int h)
 {
@@ -503,17 +569,36 @@ Property_GetSortedArray(uint16 *SortedPropertyArray, int i, int h)
     return SortedPropertyArray + (i * 8 * MAX_KEYFRAMES_PER_BLOCK) + (h * MAX_KEYFRAMES_PER_BLOCK);
 }
 
+static sorted_layer *
+Layer_GetSortedArray(sorted_layer *LayerArrayStart, sorted_comp_info *SortedCompStart, uint32 TargetComp)
+{
+    uint32 LayerOffset = 0; int s = 0;
+    while (s < TargetComp) {
+        LayerOffset += SortedCompStart[s].LayerCount;
+        s++;
+    }
+    return LayerArrayStart + LayerOffset;
+}
+
 static void
-Bezier_Commit(project_data *File, project_state *State, memory *Memory, uint16 *SortedPropertyArray) {
+Bezier_Commit(project_data *File, project_state *State, memory *Memory,
+              sorted_comp_info *SortedCompArray, sorted_layer *SortedLayerArray,
+              sorted_property_info *SortedPropertyInfo, uint16 *SortedPropertyArray)
+{
     History_Entry_Commit(Memory, "Move keyframe");
     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);
+        sorted_layer *SortedLayerInfo = Layer_GetSortedArray(SortedLayerArray, SortedCompArray, Layer->Block_Composition_Index);
         if ((State->TimelineMode == timeline_mode_graph) && !Layer->IsSelected)
             continue;
-        for (int h = 0; h < AmountOf(Layer->Property); h++) {
-            uint16 *ArrayLocation = Property_GetSortedArray(SortedPropertyArray, i, h);
-            property_channel *Property = &Layer->Property[h];
+        sorted_property_info *InfoLocation = SortedPropertyInfo + SortedLayerInfo->SortedPropertyStart;
+        uint16 *ArrayLocation = SortedPropertyArray + SortedLayerInfo->SortedKeyframeStart;
+        int h = 0, c = 0, p = 0;
+        property_channel *Property = NULL;
+        block_effect *Effect = NULL;
+        while (Layer_LoopChannels(State, Memory, &InfoLocation, &ArrayLocation, Layer, &Property, &Effect, &h, &c, &p))
+        {
             if ((State->TimelineMode != timeline_mode_graph) && !Property->IsToggled)
                 continue;
             for (int p = 0; p < Property->Keyframe_Count; p++) {
@@ -582,6 +667,8 @@ void Clipboard_Paste(project_data *File, project_state *State, memory *Memory, s
             break;
         // NOTE(fox): This loop assumes all layers and the clipboard have
         // channels laid out in the same way!
+        Assert(0);
+        /*
         for (int h = 0; h < AmountOf(Layer->Property); h++) {
             property_channel *Property = &Layer->Property[h];
             if (Property->Name == Channel->Name) {
@@ -589,7 +676,7 @@ void Clipboard_Paste(project_data *File, project_state *State, memory *Memory, s
                     bezier_point PointData = *(bezier_point *)((uint8 *)State->ClipboardBuffer + ClipboardPos);
                     PointData.Pos[0].x += State->Frame_Current;
                     uint16 *ArrayLocation = Property_GetSortedArray(SortedPropertyArray, i, h);
-                    Bezier_Add(Memory, Property, PointData, ArrayLocation);
+                    Bezier_Add(Memory, F_Layers, Property, PointData, ArrayLocation);
                     ClipboardPos += sizeof(bezier_point);
                 }
                 b++;
@@ -603,6 +690,7 @@ void Clipboard_Paste(project_data *File, project_state *State, memory *Memory, s
             else
                 b = 0;
         }
+        */
     }
 }
 
@@ -648,7 +736,8 @@ void Clipboard_Store(project_data *File, project_state *State, memory *Memory, s
                             LocalOffset = i;
                         Contents->ChannelCount++;
                         Channel->LayerOffset = LocalOffset - i;
-                        Channel->Name = Property->Name;
+                        Assert(0);
+                        // Channel->Name = Property->Name;
                     }
                 }
             }
@@ -658,17 +747,6 @@ void Clipboard_Store(project_data *File, project_state *State, memory *Memory, s
     }
 }
 
-static sorted_layer *
-Layer_GetSortedArray(sorted_layer *LayerArrayStart, sorted_comp_info *SortedCompStart, uint32 TargetComp)
-{
-    uint32 LayerOffset = 0; int s = 0;
-    while (s < TargetComp) {
-        LayerOffset += SortedCompStart[s].LayerCount;
-        s++;
-    }
-    return LayerArrayStart + LayerOffset;
-}
-
 static void
 Layer_Select_Traverse(uint16 PrincipalCompIndex, memory *Memory, project_state *State, int32 IndexToFind, sorted_comp_info *SortedCompArray, sorted_layer *SortedLayerArray,
                       int16 RecursionIdx[MAX_PRECOMP_RECURSIONS], int32 *Recursions)
@@ -734,8 +812,22 @@ int32 Layer_TestSelection(memory *Memory, project_state *State, ui *UI, sorted_c
     block_composition *Comp = (block_composition *)Memory_Block_AddressAtIndex(Memory, F_Precomps, PrincipalIndex);
     sorted_comp_info SortedCompInfo = SortedCompArray[PrincipalIndex];
     sorted_layer *SortedLayerInfo = Layer_GetSortedArray(SortedLayerArray, SortedCompArray, PrincipalIndex);
+    int SelectionCount = 0;
+    int SelectedLayerIndex = 0;
+    for (int i = SortedCompInfo.LayerCount - 1; i >= 0; i--) {
+        sorted_layer SortEntry = SortedLayerInfo[i];
+        uint32 Index_Physical = SortEntry.Block_Layer_Index;
+        block_layer *Layer = (block_layer *)Memory_Block_AddressAtIndex(Memory, F_Layers, Index_Physical);
+        block_source *Source = (block_source *)Memory_Block_AddressAtIndex(Memory, F_Sources, Layer->Block_Source_Index);
+        layer_transforms T = Layer_GetTransforms(Layer);
+        v2 UV = T_CompUVToLayerUV(T, Comp->Width, Comp->Height, Source->Width, Source->Height, State->TempZoomRatio);
+        if (UV.x <= 1.0f && UV.x >= 0.0f && UV.y <= 1.0f && UV.y >= 0.0f && Layer->IsSelected)
+        {
+            SelectionCount++;
+            SelectedLayerIndex = i;
+        }
+    }
     int32 LayerIndex = -1;
-    // for (int i = 0; i < SortedCompInfo.LayerCount; i++) {
     for (int i = SortedCompInfo.LayerCount - 1; i >= 0; i--) {
         sorted_layer SortEntry = SortedLayerInfo[i];
         uint32 Index_Physical = SortEntry.Block_Layer_Index;
@@ -745,8 +837,15 @@ int32 Layer_TestSelection(memory *Memory, project_state *State, ui *UI, sorted_c
         v2 UV = T_CompUVToLayerUV(T, Comp->Width, Comp->Height, Source->Width, Source->Height, State->TempZoomRatio);
         if (UV.x <= 1.0f && UV.x >= 0.0f && UV.y <= 1.0f && UV.y >= 0.0f && !Layer->IsSelected)
         {
-            LayerIndex = Index_Physical;
-            break;
+            if (SelectionCount == 1) {
+                if (i < SelectedLayerIndex) {
+                    LayerIndex = Index_Physical;
+                    break;
+                }
+            } else {
+                LayerIndex = Index_Physical;
+                break;
+            }
         }
         // if (Layer->IsPrecomp) {
         //     Layer_RecursiveDeselect(Memory, SortedCompArray, SortedLayerArray, TargetIndex, Layer->Block_Source_Index);
@@ -808,6 +907,55 @@ void Property_MinMax_Y(memory *Memory, project_state *State, property_channel *P
     }
 }
 
+inline property_channel *
+Effect_Property(memory *Memory, block_effect *Effect, int Offset)
+{
+    return (property_channel *)Memory_Block_AddressAtIndex(Memory, F_Properties, Effect->Block_Property_Index[Offset]);
+};
+
+inline v2 Effect_V2(memory *Memory, block_effect *Effect, int Offset)
+{
+    property_channel *Property_X = (property_channel *)Memory_Block_AddressAtIndex(Memory, F_Properties, Effect->Block_Property_Index[Offset]);
+    property_channel *Property_Y = (property_channel *)Memory_Block_AddressAtIndex(Memory, F_Properties, Effect->Block_Property_Index[Offset + 1]);
+    return V2(Property_X->CurrentValue, Property_Y->CurrentValue);
+}
+
+// TODO(fox): Merge with other sorting code.
+void Effect_Curves_Sort(memory *Memory, block_effect *Effect, uint16 *SortedPointStart, uint16 Which)
+{
+    int i = 0;
+    int SortedPoints = 0;
+    for (;;) {
+        property_channel *CurrentProperty = (property_channel *)Memory_Block_AddressAtIndex(Memory, F_Properties, Effect->Block_Property_Index[i]);
+        v2 Point = Effect_V2(Memory, Effect, i);
+        uint32 SortedIndex_Playhead = 0;
+        if (CurrentProperty->Identifier == Which) {
+            while (SortedIndex_Playhead < SortedPoints) {
+                uint16 TestPointEntry = SortedPointStart[SortedIndex_Playhead];
+                Assert(((property_channel *)Memory_Block_AddressAtIndex(Memory, F_Properties, Effect->Block_Property_Index[TestPointEntry]))->Identifier == Which);
+                v2 TestPoint = Effect_V2(Memory, Effect, TestPointEntry);
+                if (Point.x < TestPoint.x) {
+                    break;
+                } else {
+                    SortedIndex_Playhead += 1;
+                }
+            }
+            if (SortedIndex_Playhead != SortedPoints) {
+                uint8 *Address_Start = (uint8 *)(SortedPointStart + SortedIndex_Playhead);
+                uint8 *Address_End = (uint8 *)(SortedPointStart + SortedPoints) - 1;
+                Arbitrary_ShiftData(Address_Start, Address_End, sizeof(uint16), 1);
+            }
+
+            uint16 *PointEntry = SortedPointStart + SortedIndex_Playhead;
+            *PointEntry = i;
+            SortedPoints++;
+        }
+        i += 2;
+        if (i > (MAX_PROPERTIES_PER_EFFECT - 1))
+            break;
+    }
+}
+
 // The sorting algorithm is straightforward: read every point, evaluate it if
 // it's currently being interacted with by the user, record index in a sorted
 // list, and repeat, shiftig the list as necessary.
@@ -816,15 +964,16 @@ void Property_SortAll(memory *Memory, project_state *State, property_channel *Pr
 {
     int h = 0, c = 0, i = 0;
     uint32 CurrentSortIndex = 0;
-    real32 MinY = FLT_MAX;
-    real32 MaxY = FLT_MIN;
+    real32 MinY = 1000000;
+    real32 MaxY = -1000000;
     int32 Offset = (State->Interact_Active == interact_type_keyframe_move) ? (int32)State->Interact_Offset[0] : 0;
     while (Block_Loop(Memory, Property, Property->Keyframe_Count, &h, &c, &i)) {
         v2 PointPos[3];
         bezier_point *PointAddress = Bezier_LookupAddress(Memory, Property, i);
 
-        if (PointAddress->IsSelected)
+        if (PointAddress->IsSelected) {
             PropertyInfo->IsGraphSelected = true;
+        }
 
         Bezier_EvaluateValue(State, PointAddress, PointPos);
 
@@ -861,16 +1010,23 @@ void Property_SortAll(memory *Memory, project_state *State, property_channel *Pr
     }
 }
 
-void Property_DeselectAll(project_data *File, memory *Memory, uint16 *SortedPropertyArray)
+void Property_DeselectAll(project_data *File, project_state *State, memory *Memory,
+                          sorted_comp_info *SortedCompArray, sorted_layer *SortedLayerArray,
+                          sorted_property_info *SortedPropertyInfo, uint16 *SortedPropertyArray)
 {
     int h = 0, c = 0, i = 0;
     while (Block_Loop(Memory, F_Layers, File->Layer_Count, &h, &c, &i)) {
         block_layer *Layer = (block_layer *)Memory_Block_AddressAtIndex(Memory, F_Layers, i);
         if (!Layer->IsSelected)
             continue;
-        for (int h = 0; h < AmountOf(Layer->Property); h++) {
-            property_channel *Property = &Layer->Property[h];
-            uint16 *ArrayLocation = SortedPropertyArray + (i * 7 * MAX_KEYFRAMES_PER_BLOCK) + (h * MAX_KEYFRAMES_PER_BLOCK);
+        sorted_layer *SortedLayerInfo = Layer_GetSortedArray(SortedLayerArray, SortedCompArray, Layer->Block_Composition_Index);
+        sorted_property_info *InfoLocation = SortedPropertyInfo + SortedLayerInfo->SortedPropertyStart;
+        uint16 *ArrayLocation = SortedPropertyArray + SortedLayerInfo->SortedKeyframeStart;
+        int h = 0, c = 0, p = 0;
+        property_channel *Property = NULL;
+        block_effect *Effect = NULL;
+        while (Layer_LoopChannels(State, Memory, &InfoLocation, &ArrayLocation, Layer, &Property, &Effect, &h, &c, &p))
+        {
             for (int p = 0; p < Property->Keyframe_Count; p++) {
                 int k = ArrayLocation[p];
                 bezier_point *PointAddress = Bezier_LookupAddress(Memory, Property, k);
@@ -899,11 +1055,19 @@ void Layer_Sort_CheckPrev(memory *Memory, int i, int Direction, sorted_layer *So
     }
 }
 
-void Layer_Evaluate_Display(block_layer *Layer, sorted_layer *LayerArrayStart, sorted_comp_info *CompStart, sorted_layer *SortedLayerInfo, int i, real32 *Offset)
+void Layer_Evaluate_Display(project_state *State, memory *Memory, block_layer *Layer,
+                            sorted_property_info *SortedPropertyInfo, uint16 *SortedPropertyArray,
+                            sorted_layer *LayerArrayStart, sorted_comp_info *CompStart, sorted_layer *SortedLayerInfo,
+                            int i, real32 *Offset)
 {
     int ExtraPad = 1;
-    for (int h = 0; h < AmountOf(Layer->Property); h++) {
-        property_channel *Property = &Layer->Property[h];
+    sorted_property_info *InfoLocation = SortedPropertyInfo + SortedLayerInfo->SortedPropertyStart;
+    uint16 *ArrayLocation = SortedPropertyArray + SortedLayerInfo->SortedKeyframeStart;
+    int h = 0, c = 0, p = 0;
+    property_channel *Property = NULL;
+    block_effect *Effect = NULL;
+    while (Layer_LoopChannels(State, Memory, &InfoLocation, &ArrayLocation, Layer, &Property, &Effect, &h, &c, &p))
+    {
         if (Property->IsToggled) {
             *Offset += 1 + ExtraPad;
             ExtraPad = 0;
@@ -954,8 +1118,8 @@ void TempSource_SortAll(project_data *File, project_state *State, memory *Memory
 // second is for sorting the layers by offset, and the third is for applying
 // interactivity if the user is moving any layers.
 
-void Layer_SortAll(project_data *File, project_state *State, memory *Memory, sorted_layer *LayerArrayStart,
-                   sorted_comp_info *CompStart, sorted_property_info *SortedPropertyInfo, uint16 *SortedPropertyArray,
+void Layer_SortAll(project_data *File, project_state *State, memory *Memory,
+                   sorted_layer *LayerArrayStart,  sorted_comp_info *CompStart,
                    uint32 LayerCount, uint32 CompCount)
 {
     int h = 0, c = 0, i = 0;
@@ -1029,6 +1193,45 @@ void Layer_SortAll(project_data *File, project_state *State, memory *Memory, sor
     }
 }
 
+// NOTE(fox): We could be slightly more efficient and just allocate redundant data
+// instead of having another loop.
+void LayerProperty_Allocate(project_data *File, project_state *State, memory *Memory, sorted_layer *LayerArrayStart,
+                            sorted_comp_info *CompStart, uint32 LayerCount, uint32 CompCount,
+                            uint32 *TotalPropertyCount, uint32 *TotalKeyframeCount)
+{
+    uint32 SortedPropertyPlayhead = 0;
+    uint32 SortedKeyframePlayhead = 0;
+    for (int c = 0; c < CompCount; c++) {
+        sorted_comp_info SortedCompInfo = CompStart[c];
+        sorted_layer *SortedLayerInfo = Layer_GetSortedArray(LayerArrayStart, CompStart, c);
+        for (int i = 0; i < SortedCompInfo.LayerCount; i++) {
+            sorted_layer *SortedLayer = &SortedLayerInfo[i];
+            block_layer *Layer = (block_layer *)Memory_Block_AddressAtIndex(Memory, F_Layers, SortedLayer->Block_Layer_Index);
+            SortedLayer->SortedPropertyStart = SortedPropertyPlayhead;
+            SortedLayer->SortedKeyframeStart = SortedKeyframePlayhead;
+            for (int h = 0; h < AmountOf(Layer->Property); h++) {
+                property_channel *Property = &Layer->Property[h];
+                if (Property->Keyframe_Count) {
+                    SortedKeyframePlayhead += Property->Keyframe_Count;
+                }
+                SortedPropertyPlayhead++;
+            }
+            for (int i = 0; i < Layer->Block_Effect_Count; i++) {
+                block_effect Effect = *(block_effect *)Memory_Block_AddressAtIndex(Memory, F_Effects, Layer->Block_Effect_Index[i]);
+                header_effect *EffectHeader = Effect_EntryFromID(State, Effect.ID);
+                for (int h = 0; h < EffectHeader->Property_Count; h++) {
+                    header_property ChannelHeader = State->Property[EffectHeader->PropertyStartIndex + h];
+                    property_channel *Property = (property_channel *)Memory_Block_AddressAtIndex(Memory, F_Properties, Effect.Block_Property_Index[h]);
+                    if (Property->Keyframe_Count) {
+                        SortedKeyframePlayhead += Property->Keyframe_Count;
+                    }
+                    SortedPropertyPlayhead++;
+                }
+            }
+        }
+    }
+}
+
 void LayerProperty_SortAll(project_data *File, project_state *State, memory *Memory, sorted_layer *LayerArrayStart,
                    sorted_comp_info *CompStart, sorted_property_info *SortedPropertyInfo, uint16 *SortedPropertyArray,
                    uint32 LayerCount, uint32 CompCount)
@@ -1089,8 +1292,11 @@ sorted_file File_Sort_Push(project_data *File, project_state *State, memory *Mem
     Arbitrary_Zero((uint8 *)Source_SortedArray, Sorted.Source_SortSize);
     Sorted.SourceArray = (uint16 *)Source_SortedArray;
 
-    uint64 PropertyArraySize = sizeof(uint16) * 8 * MAX_KEYFRAMES_PER_BLOCK * File->Layer_Count;
-    uint64 PropertyInfoSize = sizeof(sorted_property_info) * 8 * File->Layer_Count;
+    uint32 TotalPropertyCount = 0;
+    uint32 TotalKeyframeCount = 0;
+    LayerProperty_Allocate(File, State, Memory, Sorted.LayerArray, Sorted.CompArray, File->Layer_Count, File->Comp_Count, &TotalPropertyCount, &TotalKeyframeCount);
+    uint64 PropertyInfoSize = TotalPropertyCount * sizeof(sorted_property_info);
+    uint64 PropertyArraySize = TotalKeyframeCount * sizeof(uint16);
     Sorted.Property_SortSize = PropertyArraySize + PropertyInfoSize;
     void *Property_SortedArray = Memory_PushScratch(Memory, Sorted.Property_SortSize);
     Arbitrary_Zero((uint8 *)Property_SortedArray, Sorted.Property_SortSize);
@@ -1098,7 +1304,7 @@ sorted_file File_Sort_Push(project_data *File, project_state *State, memory *Mem
     Sorted.PropertyArray = (uint16 *)((uint8 *)Property_SortedArray + PropertyInfoSize);
 
     TempSource_SortAll(File, State, Memory, Sorted.SourceArray, &Sorted.TempSourceCount);
-    Layer_SortAll(File, State, Memory, Sorted.LayerArray, Sorted.CompArray, Sorted.PropertyInfo, Sorted.PropertyArray, File->Layer_Count, File->Comp_Count);
+    Layer_SortAll(File, State, Memory, Sorted.LayerArray, Sorted.CompArray, File->Layer_Count, File->Comp_Count);
     LayerProperty_SortAll(File, State, Memory, Sorted.LayerArray, Sorted.CompArray, Sorted.PropertyInfo, Sorted.PropertyArray, File->Layer_Count, File->Comp_Count);
     return Sorted;
 }
@@ -1138,14 +1344,14 @@ block_layer * Layer_Init(project_data *File, memory *Memory)
     sprintf(String->Char, "Layer %i", File->Layer_Count + 1);  // CSbros...
     String->Occupied = 1;
 
-    Layer->x =        Property_InitFloat("X Position",   0.0f, 1.0f);
-    Layer->y =        Property_InitFloat("Y Position",   0.0f, 1.0f);
-    Layer->ax =       Property_InitFloat("Anchor X",     0.5f, 0.005f);
-    Layer->ay =       Property_InitFloat("Anchor Y",     0.5f, 0.005f);
-    Layer->scale =    Property_InitFloat("Scale",        1.0f, 0.005f);
-    Layer->rotation = Property_InitFloat("Rotation",     0.0f, 1.0f);
-    Layer->opacity =  Property_InitFloat("Opacity",      1.0f, 0.005f, 0.0f, 1.0f);
-    Layer->time =     Property_InitFloat("Frame Number", 0.0f, 1.0f, 0, 100000, 1);
+    Layer->x =        Property_InitFloat(0.0f, 1.0f);
+    Layer->y =        Property_InitFloat(0.0f, 1.0f);
+    Layer->ax =       Property_InitFloat(0.5f, 0.005f);
+    Layer->ay =       Property_InitFloat(0.5f, 0.005f);
+    Layer->scale =    Property_InitFloat(1.0f, 0.005f);
+    Layer->rotation = Property_InitFloat(0.0f, 1.0f);
+    Layer->opacity =  Property_InitFloat(1.0f, 0.005f, 0.0f, 1.0f);
+    Layer->time =     Property_InitFloat(0.0f, 1.0f, 0, 100000, 1);
 
     Layer->IsVisible = 1;
 
@@ -1265,8 +1471,8 @@ void Precomp_UICreateButton(project_data *File, project_state *State, memory *Me
     block_layer *PrecompLayer = Layer_Init(File, Memory);
     bezier_point Point0 = { 1, {0, 0, 1, 0, 1, 0}, interpolation_type_linear, 0, {0, 0, 0}, 0 };
     bezier_point Point1 = { 1, {(real32)NewComp->Frame_End, (real32)NewComp->Frame_End, 1, 0, 1, 0}, interpolation_type_linear, 0, {0, 0, 0}, 0 };
-    Bezier_Add(Memory, &PrecompLayer->time, Point0, NULL);
-    Bezier_Add(Memory, &PrecompLayer->time, Point1, NULL);
+    Bezier_Add(Memory, F_Layers, &PrecompLayer->time, Point0, NULL);
+    Bezier_Add(Memory, F_Layers, &PrecompLayer->time, Point1, NULL);
     PrecompLayer->IsPrecomp = true;
     Layer_Select(Memory, State, Memory_Block_LazyIndexAtAddress(Memory, F_Layers, PrecompLayer));
     PrecompLayer->Block_Source_Index = File->Comp_Count - 1;
diff --git a/defines.h b/defines.h
index 12db869..8216f1c 100644
--- a/defines.h
+++ b/defines.h
@@ -48,3 +48,5 @@ typedef uint64 ptrsize;   // is there a compiler variable for 32 vs 64 bit like
 #define GetCPUTime() __rdtsc()
 #endif
 
+static real32 Tau = 0.9; // tension
+
diff --git a/effects.cpp b/effects.cpp
index c5a07af..1aacddb 100644
--- a/effects.cpp
+++ b/effects.cpp
@@ -5,9 +5,7 @@ static void
 Effect_DrawColor(real32 *Data, int Width, int Height, int BytesPerPixel, void *EffectBitmapAddress, uint16 ShaderProgram)
 {
     v4 Color = { Data[0], Data[1], Data[2], Data[3] };
-    blend_mode BlendMode = (blend_mode)Data[4];
-    Effect_GL_DrawColor(Width, Height, BytesPerPixel, EffectBitmapAddress, ShaderProgram, Color, BlendMode);
-    // Effect_Software_DrawColor(Width, Height, BytesPerPixel, EffectBitmapAddress, Color, BlendMode);
+    Effect_GL_DrawColor(Width, Height, BytesPerPixel, EffectBitmapAddress, ShaderProgram, Color);
 }
 
 static void
@@ -16,3 +14,45 @@ Effect_GaussianBlur(real32 *Data, int Width, int Height, int BytesPerPixel, void
     real32 Radius = Data[0];
     Effect_GL_GaussianBlur(Width, Height, BytesPerPixel, EffectBitmapAddress, ShaderProgram, Radius);
 }
+
+static void
+Effect_Curves_Init(block_effect *Effect, property_channel *Property)
+{
+    for (int i = 0; i < 5; i++) {
+        Property->Identifier = i;
+        Property++;
+        Property->Identifier = i;
+        Property++;
+
+        Property->CurrentValue = 1.0f;
+        Property->Identifier = i;
+        Property++;
+        Property->CurrentValue = 1.0f;
+        Property->Identifier = i;
+        Property++;
+
+        Effect->ExtraData[i] = 2;
+    }
+}
+
+static void
+Effect_Levels(real32 *Data, int Width, int Height, int BytesPerPixel, void *EffectBitmapAddress, uint16 ShaderProgram)
+{
+    real32 Min = Data[0];
+    real32 Mid = Data[1];
+    real32 Max = Data[2];
+
+    v4 ColMin = *(v4 *)&Data[3];
+    v4 ColMid = *(v4 *)&Data[3+4];
+    v4 ColMax = *(v4 *)&Data[3+8];
+    Effect_GL_Levels(Width, Height, BytesPerPixel, EffectBitmapAddress, ShaderProgram, Min, Mid, Max, ColMin, ColMid, ColMax);
+}
+
+static void
+Effect_Curves(real32 *Data, int Width, int Height, int BytesPerPixel, void *EffectBitmapAddress, uint16 ShaderProgram)
+{
+    real32 PointCount = *Data;
+    v4 PointCount_Col = *(v4 *)(Data + 1);
+    v2 *PointData  = (v2 *)(Data + 5);
+    Effect_Software_Curves(Width, Height, BytesPerPixel, EffectBitmapAddress, PointData, PointCount, PointCount_Col);
+}
diff --git a/effects_constructors.cpp b/effects_constructors.cpp
index 7eb2143..406cb08 100644
--- a/effects_constructors.cpp
+++ b/effects_constructors.cpp
@@ -67,16 +67,6 @@ static void
 Effect_InitEntries(project_state *State)
 {
     /*
-    Effect_AddEntry(State, "Levels", "LVLS", &Effect_Levels, effect_display_type_levels);
-    // Levels
-    // min/max is handled by the UI
-    Effect_AddProperty_Real(State, "All start point", 0.0f);
-    Effect_AddProperty_Real(State, "All mid point", 1.0f);
-    Effect_AddProperty_Real(State, "All end point", 1.0f);
-    Effect_AddProperty_Col(State, "Channel start point", V4(0.0f));
-    Effect_AddProperty_Col(State, "Channel mid point",   V4(1.0f));
-    Effect_AddProperty_Col(State, "Channel end point",   V4(1.0f));
-    Effect_EndEntry(State);
     */
     // Curves
     /*
@@ -90,20 +80,35 @@ Effect_InitEntries(project_state *State)
     }
     Effect_EndEntry(State);
     */
+
     // Solid color
-    Effect_AddEntry(State, "Solid color", "REALSCOL", Effect_DrawColor, GLShader_SolidColor);
+    Effect_AddEntry(State, "Solid color", "REALSCOL", &Effect_DrawColor, GLShader_SolidColor);
     Effect_AddProperty_Col(State, "Color", V4(0.3f, 0.2f, 0.6f, 1.0f));
-    Effect_AddProperty_Blendmode(State, "Blend mode", blend_softlight);
     Effect_EndEntry(State);
+
     // Gaussian blur
     Effect_AddEntry(State, "Gaussian blur", "REALGBLR", &Effect_GaussianBlur, GLShader_GaussianBlur);
     Effect_AddProperty_Real(State, "Radius", 1.0f, 0.0f, 200.0f);
     Effect_EndEntry(State);
-    /*
+
+    // Curves
     Effect_AddEntry(State, "Curves", "REALCRVS", &Effect_Curves, NULL, effect_display_type_curves);
     for (int i = 0; i < MAX_PROPERTIES_PER_EFFECT; i++) {
         Effect_AddProperty_Real(State, "point", 0.0f);
     }
+    Effect_EndEntry(State);
+
+    // Levels
+    Effect_AddEntry(State, "Levels", "REALLVLS", &Effect_Levels, GLShader_Levels, effect_display_type_levels);
+    // min/max is handled by the UI
+    Effect_AddProperty_Real(State, "All start point", 0.0f);
+    Effect_AddProperty_Real(State, "All mid point", 1.0f);
+    Effect_AddProperty_Real(State, "All end point", 1.0f);
+    Effect_AddProperty_Col(State, "Channel start point", V4(0.0f));
+    Effect_AddProperty_Col(State, "Channel mid point",   V4(1.0f));
+    Effect_AddProperty_Col(State, "Channel end point",   V4(1.0f));
+    Effect_EndEntry(State);
+    /*
     // Test gradient
     Effect_AddEntry(State, "Test gradient", "REALTGRD", &Effect_TestGradient, NULL);
     Effect_AddProperty_Col(State, "Color", V4(0.3f, 0.2f, 0.6f, 1.0f));
diff --git a/effects_gl.cpp b/effects_gl.cpp
index 2b8eac1..c3ff444 100644
--- a/effects_gl.cpp
+++ b/effects_gl.cpp
@@ -21,7 +21,7 @@ void Effect_GL_Start(gl_effect_layer *Test, int Width, int Height, int BytesPerP
 }
 
 void Effect_GL_DrawColor(int Width, int Height, int BytesPerPixel, void *EffectBitmapAddress,
-                         uint16 ShaderProgram, v4 Color, blend_mode BlendMode)
+                         uint16 ShaderProgram, v4 Color)
 {
     gl_effect_layer Test = {};
 
@@ -72,3 +72,32 @@ void Effect_GL_GaussianBlur(int Width, int Height, int BytesPerPixel, void *Effe
     glBindFramebuffer(GL_FRAMEBUFFER, 0);
     GL_DeleteHWBuffer(&Test);
 }
+
+void Effect_GL_Levels(int Width, int Height, int BytesPerPixel, void *EffectBitmapAddress,
+                         uint16 ShaderProgram, real32 Min, real32 Mid, real32 Max, v4 ColMin, v4 ColMid, v4 ColMax)
+{
+    gl_effect_layer Test = {};
+
+    int ByteFlag = (BytesPerPixel == 4) ? GL_RGBA : GL_RGBA16;
+    int ByteFlag2 = (BytesPerPixel == 4) ? GL_UNSIGNED_BYTE : GL_UNSIGNED_SHORT;
+    Effect_GL_Start(&Test, Width, Height, BytesPerPixel, EffectBitmapAddress, ShaderProgram);
+
+    int Uniform = glGetUniformLocation(ShaderProgram, "Start");
+    glUniform1f(Uniform, Min);
+    Uniform = glGetUniformLocation(ShaderProgram, "Mid");
+    glUniform1f(Uniform, Mid);
+    Uniform = glGetUniformLocation(ShaderProgram, "End");
+    glUniform1f(Uniform, Max);
+    Uniform = glGetUniformLocation(ShaderProgram, "StartCol");
+    glUniform4f(Uniform, ColMin.r, ColMin.g, ColMin.b,  ColMin.a);
+    Uniform = glGetUniformLocation(ShaderProgram, "MidCol");
+    glUniform4f(Uniform, ColMid.r, ColMid.g, ColMid.b,  ColMid.a);
+    Uniform = glGetUniformLocation(ShaderProgram, "EndCol");
+    glUniform4f(Uniform, ColMax.r, ColMax.g, ColMax.b,  ColMax.a);
+
+    glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
+    glReadPixels(0, 0, Width, Height, GL_RGBA, ByteFlag2, EffectBitmapAddress);
+
+    glBindFramebuffer(GL_FRAMEBUFFER, 0);
+    GL_DeleteHWBuffer(&Test);
+}
diff --git a/effects_software.cpp b/effects_software.cpp
index 71023e0..031f0a6 100644
--- a/effects_software.cpp
+++ b/effects_software.cpp
@@ -107,23 +107,15 @@ CurvesSolver(real32 *LUT, v2 Point_P1, v2 Point_P2, v2 m1, v2 m2, int i)
    }
 }
 
-/*
 static void
-Effect_Curves(file_source *Source, void *BitmapAddress, uint16 ShaderProgram, uint8 *FileEffectAddress)
+Effect_Software_Curves(int Width, int Height, int BytesPerPixel, void *EffectBitmapAddress, v2 *PointData, real32 PointCount, v4 PointCount_Col)
 {
-    uint32 ChannelIndex = *(uint32 *)(real32 *)FileEffectAddress;
-    FileEffectAddress += sizeof(real32);
-    real32 NumberOfPoints_Main = *(real32 *)FileEffectAddress;
-    FileEffectAddress += sizeof(real32);
-    v4 NumberOfPoints_Col = *(v4 *)FileEffectAddress;
-    FileEffectAddress += sizeof(v4);
-
     real32 LUT[5][256] = {};
 
     for (int a = 0; a < 5; a++) {
 
-        int Num = (a == 0) ? NumberOfPoints_Main : NumberOfPoints_Col.E[a-1];
-        v2 *CurvePoint = (v2 *)(FileEffectAddress + (a * (sizeof(v2) * 10)));
+        int Num = (a == 0) ? (int)PointCount : (int)PointCount_Col.E[a-1];
+        v2 *CurvePoint = PointData + (MAX_PROPERTIES_PER_EFFECT / 5 * a);
 
         for (int i = 0; i < Num; i++) {
             v2 Point_P1 = CurvePoint[i];
@@ -164,13 +156,14 @@ Effect_Curves(file_source *Source, void *BitmapAddress, uint16 ShaderProgram, ui
     }
 
 
-    uint64 Size = Source->Width*Source->Height;
+    uint64 Size = Width*Height;
     int i = 0;
     Assert(BytesPerPixel == 4);
     while (i < Size) {
-        uint32 *Pixel = (uint32 *)BitmapAddress + i;
+        uint32 *Pixel = (uint32 *)EffectBitmapAddress + i;
         v4 t = Uint32ToCol8(*Pixel);
 
+#if 1
         real32 R_Lookup = LUT[1][(uint32)(t.r)];
         real32 G_Lookup = LUT[2][(uint32)(t.g)];
         real32 B_Lookup = LUT[3][(uint32)(t.b)];
@@ -179,8 +172,14 @@ Effect_Curves(file_source *Source, void *BitmapAddress, uint16 ShaderProgram, ui
         real32 R_Lookup_All = LUT[0][(uint32)(R_Lookup*255)];
         real32 G_Lookup_All = LUT[0][(uint32)(G_Lookup*255)];
         real32 B_Lookup_All = LUT[0][(uint32)(B_Lookup*255)];
+#else
+        real32 R_Lookup_All = LUT[0][(uint32)(t.r)];
+        real32 G_Lookup_All = LUT[0][(uint32)(t.g)];
+        real32 B_Lookup_All = LUT[0][(uint32)(t.b)];
+#endif
+
 
-        uint32 Result = (((uint32)((A_Lookup * 255.0f)     + 0.5) << 24) |
+        uint32 Result = (((uint32)((t.a)     + 0.5) << 24) |
                          ((uint32)((B_Lookup_All * 255.0f) + 0.5)  << 16) |
                          ((uint32)((G_Lookup_All * 255.0f) + 0.5)  << 8) |
                          ((uint32)((R_Lookup_All * 255.0f) + 0.5)  << 0));
@@ -189,4 +188,3 @@ Effect_Curves(file_source *Source, void *BitmapAddress, uint16 ShaderProgram, ui
         i++;
     }
 }
-*/
diff --git a/functions.h b/functions.h
index 2825f85..ffba30c 100644
--- a/functions.h
+++ b/functions.h
@@ -5,9 +5,14 @@ static void Arbitrary_ShiftData(uint8 *Address_Start, uint8 *Address_End, uint64
 
 static void Render_Main(void *Data, void *OutputBuffer, render_type RenderType, rectangle RenderRegion);
 
+static void Effect_Curves_Init(block_effect *Effect, property_channel *Property);
+
 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_info *SortedCompArray, sorted_layer *SortedLayerArray);
 
 static v2 Transform_ScreenSpaceToLocal(layer_transforms T, uint32 FileWidth, uint32 FileHeight, uint32 SourceWidth, uint32 SourceHeight, ImVec2 CompPos, ImVec2 CompZoom, ImVec2 ViewportMin, ImVec2 Point);
diff --git a/imgui_ops.h b/imgui_ops.h
index 2bf6335..6089f94 100644
--- a/imgui_ops.h
+++ b/imgui_ops.h
@@ -20,6 +20,16 @@ ImVec2 operator+(ImVec2 A, int B)
     return Result;
 }
 
+ImVec2 operator-(ImVec2 A, int B)
+{
+    ImVec2 Result;
+
+    Result.x = A.x - B;
+    Result.y = A.y - B;
+
+    return Result;
+}
+
 ImVec2 operator-(ImVec2 A, ImVec2 B)
 {
     ImVec2 Result;
diff --git a/keybinds.h b/keybinds.h
index 4cddb29..3fe5f64 100644
--- a/keybinds.h
+++ b/keybinds.h
@@ -38,6 +38,7 @@ static shortcut_entry ShortcutArray[] {
     { ImGuiKey_P, Mod_Ctrl, key_mode_all, "Paste" },
     { ImGuiKey_Z, Mod_Ctrl, key_mode_all, "Undo" },
     { ImGuiKey_Z, Mod_Ctrl, key_mode_all, "Redo" },
+    { ImGuiKey_Space, Mod_Shift, key_mode_all, "Focus effects search" },
     { ImGuiKey_Slash, Mod_Shift, key_mode_all, "Open help" },
 
     { ImGuiKey_None, Mod_None, key_mode_viewport, "Hold right click to pan." },
@@ -53,8 +54,9 @@ static shortcut_entry ShortcutArray[] {
     { ImGuiKey_S, Mod_None, key_mode_timeline, "Toggle scale keyframes" },
     { ImGuiKey_T, Mod_None, key_mode_timeline, "Toggle time remapping keyframes" },
     { ImGuiKey_T, Mod_Shift, key_mode_timeline, "Toggle opacity keyframes" },
-    { ImGuiKey_B, Mod_None, key_mode_timeline, "Mark frame start" },
-    { ImGuiKey_N, Mod_None, key_mode_timeline, "Mark frame end" },
+    { ImGuiKey_U, Mod_None, key_mode_timeline, "Toggle all active channels" },
+    { ImGuiKey_N, Mod_None, key_mode_timeline, "Mark frame start" },
+    { ImGuiKey_N, Mod_Shift, key_mode_timeline, "Mark frame end" },
 
     { ImGuiKey_G, Mod_None, key_mode_graph, "Enter keyframe moving mode" },
     { ImGuiKey_X, Mod_None, key_mode_graph, "Constrain to X axis" },
diff --git a/main.cpp b/main.cpp
index bb780c4..a32598f 100644
--- a/main.cpp
+++ b/main.cpp
@@ -161,6 +161,14 @@ Main_InputTest(project_data *File, project_state *State, memory *Memory, ui *UI,
             {
                 Clipboard_Paste(File, State, Memory, Sorted.CompArray, Sorted.LayerArray, Sorted.PropertyArray);
             } break;
+            case hotkey_togglechannels:
+            {
+                Project_ToggleAllChannels(File, State, Memory, Sorted.CompArray, Sorted.LayerArray, Sorted.PropertyInfo, Sorted.PropertyArray);
+            } break;
+            default:
+            {
+                Assert(0);
+            } break;
         }
         State->HotkeyInput = hotkey_none;
     }
@@ -176,15 +184,7 @@ Main_InputTest(project_data *File, project_state *State, memory *Memory, ui *UI,
     }
 #endif
 
-    // if (State->FocusedWindow == focus_viewport && State->SetFocus) {
-    //     ImGui::SetNextWindowFocus();
-    //     State->SetFocus = false;
-    // }
     ImGui_Viewport(File, State, UI, Memory, io, textureID, Sorted.CompArray, Sorted.LayerArray, Sorted.PropertyArray);
-    // if (State->FocusedWindow == focus_timeline && State->SetFocus) {
-    //     ImGui::SetNextWindowFocus();
-    //     State->SetFocus = false;
-    // }
     ImGui_Timeline(File, State, Memory, UI, io, Sorted.CompArray, Sorted.LayerArray, Sorted.PropertyInfo, Sorted.PropertyArray);
     ImGui_File(File, State, Memory, io, Sorted.CompArray, Sorted.LayerArray);
     ImGui_PropertiesPanel(File, State, UI, Memory, io, Sorted.CompArray, Sorted.LayerArray, Sorted.PropertyInfo, Sorted.PropertyArray);
@@ -197,7 +197,7 @@ Main_InputTest(project_data *File, project_state *State, memory *Memory, ui *UI,
     }
 #endif
     ImGui_Menu(File, State, UI, Memory, io);
-    ImGui_Popups(File, State, UI, Memory, io);  // NOTE(fox): If popup disappears unexpectedly it means something else took its focus!
+    ImGui_Popups(File, State, UI, Memory, io);
 
     File_Sort_Pop(Memory, Sorted.Layer_SortSize, Sorted.Property_SortSize, Sorted.Source_SortSize);
 
@@ -505,15 +505,6 @@ int main(int argc, char *argv[]) {
 
     File->Comp_Count = 1;
 
-
-#if 0
-    {
-        uint16 SourceIndex = Source_Generate(File, State, &Memory, (void *)"../asset/a_small.jpg");
-        block_source *Source = (block_source *)Memory_Block_AddressAtIndex(&Memory, F_Sources, File->Source_Count - 1);
-        Source->IsSelected = true;
-    }
-#endif
-
     SDL_Init(SDL_INIT_VIDEO);
 
     Semaphore = SDL_CreateSemaphore(0);
@@ -633,10 +624,12 @@ int main(int argc, char *argv[]) {
 #endif
 
 #if DEBUG
+#if 1
     sprintf(State->DummyName, "test2");
     File_Open(File, State, &Memory, State->DummyName);
     State->UpdateFrame = true;
     State->MostRecentlySelectedLayer = 0;
+#endif
 #endif
 
     while (State->IsRunning)
diff --git a/main.h b/main.h
index c1b37f3..37701e4 100644
--- a/main.h
+++ b/main.h
@@ -256,7 +256,8 @@ enum hotkey_input
     hotkey_none,
     hotkey_transform,
     hotkey_copy,
-    hotkey_paste
+    hotkey_paste,
+    hotkey_togglechannels
 };
 
 enum property_display_type
@@ -300,6 +301,7 @@ struct block_effect
     bool32 IsToggled;
     uint16 Index;
     uint32 Block_Property_Index[MAX_PROPERTIES_PER_EFFECT];
+    real32 ExtraData[16];
 };
 
 struct header_effect
@@ -520,11 +522,12 @@ struct block_source
 
 struct property_channel {
     uint8 Occupied;
-    char *Name; // TODO(fox): Delete this.
     uint16 Block_Bezier_Index[MAX_KEYFRAME_BLOCKS];
     uint16 Block_Bezier_Count;
     uint16 Keyframe_Count;
 
+    int32 Identifier;
+
     real32 CurrentValue;
     real32 MaxVal;
     real32 MinVal;
diff --git a/my_imgui_widgets.cpp b/my_imgui_widgets.cpp
index dbadd11..36bc79e 100644
--- a/my_imgui_widgets.cpp
+++ b/my_imgui_widgets.cpp
@@ -35,6 +35,248 @@ ImGui_PropertyInteract_Slider(project_state *State, memory *Memory, property_cha
     }
 }
 
+static void
+ImGui_RGBAModeSwitch(project_state *State, memory *Memory, ImGuiIO io, uint32 *Channel)
+{
+    char *Names[5] = {"All", "R", "G", "B", "A" };
+    if (ImGui::BeginListBox("RGB")) {
+        for (int i = 0; i < 5; i++) {
+            if (ImGui::Selectable(Names[i], (*Channel == i))) {
+                *Channel = i;
+            }
+        }
+        ImGui::EndListBox();
+    }
+}
+
+static void
+ImGui_CurvesUI(project_state *State, memory *Memory, ImGuiIO io, block_effect *Effect, property_channel *PropertyStart, uint16 *SortedPointStart)
+{
+
+    real32 Padding = ImGui::GetFontSize()*6;
+    ImVec2 ViewportMin = ImGui::GetCursorScreenPos() + Padding/6;
+    ImVec2 ViewportScale = ImGui::GetContentRegionAvail();
+    ViewportScale.y = ViewportScale.x = ViewportScale.x - Padding;  // square seems nice
+    ImVec2 ViewportMax = ViewportMin + ViewportScale;
+
+    ImDrawList* draw_list = ImGui::GetWindowDrawList();
+    draw_list->AddRectFilled(ViewportMin, ViewportMax, IM_COL32(50, 50, 50, 255));
+    draw_list->AddRect(ViewportMin, ViewportMax, IM_COL32(255, 255, 255, 255));
+
+    real32 PointSize = 40;
+
+    ImU32 col = ImGui::GetColorU32(ImGuiCol_Text);
+    ImU32 col_light = ImGui::GetColorU32(ImGuiCol_TextDisabled);
+
+    ImVec2 Point_ScreenPos[4];
+
+    // ocd?
+    draw_list->PushClipRect(ViewportMin + 2, ViewportMax - 2, true);
+
+    for (real32 i = 0.25; i < 1.0; i += 0.25) {
+        ImVec2 Horizontal = ViewportMin + ViewportScale * ImVec2(0, i);
+        ImVec2 Vertical = ViewportMin + ViewportScale * ImVec2(i, 0);
+        draw_list->AddLine(Horizontal, Horizontal + ImVec2(ViewportScale.x, 0), col_light, 1.0f);
+        draw_list->AddLine(Vertical, Vertical + ImVec2(0, ViewportScale.y), col_light, 1.0f);
+    }
+
+    draw_list->PopClipRect();
+
+
+#if 0
+    real32 LUT[5][256] = {};
+
+    for (int a = 0; a < 5; a++) {
+
+        int Num = (a == 0) ? *NumberOfPoints_Main : NumberOfPoints_Col->E[a-1];
+        v2 *CurvePoint = (v2 *)(FileEffectAddress + (a * (sizeof(v2) * 10)));
+
+        for (int i = 0; i < Num; i++) {
+            v2 Point_P1 = CurvePoint[i];
+            v2 Point_P2 = CurvePoint[i + 1];
+            v2 Point_P0 = (i != 0) ? CurvePoint[i - 1] : V2(0, 0);
+            v2 Point_P3 = (i != (Num - 2)) ? CurvePoint[i + 2] : V2(1, 1);
+
+            v2 m1 = (Point_P2 - Point_P0) / (2 * Tau);
+            v2 m2 = (Point_P3 - Point_P1) / (2 * Tau);
+
+            CurvesSolver(LUT[a], Point_P1, Point_P2, m1, m2, i);
+        }
+
+        if (CurvePoint[0].x > 0.0f) {
+           real32 Count_Start = 0;
+           real32 Count_End = (CurvePoint[0].x * 255);
+           real32 Count = Count_Start;
+           while (Count < Count_End) {
+               LUT[a][(uint32)Count] = LUT[a][(uint32)Count_End];
+               Count++;
+           }
+        }
+
+        if (CurvePoint[Num-1].x < 1.0f) {
+           real32 Count_Start = (CurvePoint[Num-1].x * 255) - 0.5;
+           real32 Count_End = 255;
+           real32 Count = Count_Start;
+           while (Count < Count_End) {
+               LUT[a][(uint32)Count] = LUT[a][(uint32)Count_Start];
+               Count++;
+           }
+        }
+
+        for (int i = 0; i < Num; i++) {
+            if (CurvePoint[i].y == 1.0f)
+                LUT[a][255] = 1.0f;
+        }
+    }
+#endif
+
+    real32 *Num = &Effect->ExtraData[0];
+    uint32 *SelectedChannel = (uint32 *)&Effect->ExtraData[5];
+
+    v2 Pos = {};
+    bool32 AddPoint = 0;
+
+    for (uint32 i = 0; i < *(uint32 *)Num; i += 1) {
+
+        v2 Point_P1 = Effect_V2(Memory, Effect, SortedPointStart[i]);
+        v2 Point_P2 = Effect_V2(Memory, Effect, SortedPointStart[i + 1]);
+        v2 Point_P0 = (i != 0) ? Effect_V2(Memory, Effect, SortedPointStart[i - 1]) : V2(0, 0);
+        v2 Point_P3 = (i != (*Num - 2)) ? Effect_V2(Memory, Effect, SortedPointStart[i + 2]) : V2(1, 1);
+
+        ImVec2 Point_P0_ScreenPos = ViewportMin + (ImVec2(Point_P0.x, 1.0f - Point_P0.y) * ViewportScale);
+        ImVec2 Point_P1_ScreenPos = ViewportMin + (ImVec2(Point_P1.x, 1.0f - Point_P1.y) * ViewportScale);
+        ImVec2 Point_P2_ScreenPos = ViewportMin + (ImVec2(Point_P2.x, 1.0f - Point_P2.y) * ViewportScale);
+        ImVec2 Point_P3_ScreenPos = ViewportMin + (ImVec2(Point_P3.x, 1.0f - Point_P3.y) * ViewportScale);
+
+        ImGui::PushID(&PropertyStart[SortedPointStart[i]]);
+
+        draw_list->AddNgon(Point_P1_ScreenPos, 2, col, 8, 5.0f);
+
+        ImGui::SetCursorScreenPos(Point_P1_ScreenPos - ImVec2(PointSize/2, PointSize/2));
+        ImGui::InvisibleButton("##point", ImVec2(PointSize, PointSize), ImGuiButtonFlags_MouseButtonLeft);
+
+        if (ImGui::IsItemHovered()) {
+            ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
+        }
+
+        if (ImGui::IsItemActivated()) {
+            if (io.KeyCtrl && *Num != 2) {
+                property_channel *Property_X = Effect_Property(Memory, Effect, SortedPointStart[i]);
+                Property_X->Identifier = -1;
+                property_channel *Property_Y = Effect_Property(Memory, Effect, SortedPointStart[i]+1);
+                Property_Y->Identifier = -1;
+                Effect->ExtraData[*SelectedChannel]--;
+            }
+        }
+
+        if (ImGui::IsItemActive()) {
+            property_channel *Property_X = Effect_Property(Memory, Effect, SortedPointStart[i]);
+            property_channel *Property_Y = Effect_Property(Memory, Effect, SortedPointStart[i]+1);
+            v2 Point = V2(((io.MousePos - ViewportMin) / ViewportScale));
+            Point.y = 1.0f - Point.y;
+            Point.x = Normalize(Point.x);
+            Point.y = Normalize(Point.y);
+            Property_X->CurrentValue = Point.x;
+            Property_Y->CurrentValue = Point.y;
+            State->UpdateFrame = true;
+        }
+
+        if (i == (*Num - 1)) {
+            ImGui::PopID();
+            break;
+        }
+
+        // Conversion from Catmull-Rom curves to Bezier curves for display,
+        // referencing https://pomax.github.io/bezierinfo/#catmullconv
+
+        ImVec2 bez_m1 = (Point_P2_ScreenPos - Point_P0_ScreenPos) / (6 * Tau);
+        ImVec2 bez_m2 = (Point_P3_ScreenPos - Point_P1_ScreenPos) / (6 * Tau);
+
+        ImVec2 Point_Bez[4];
+        Point_Bez[0] = Point_P1_ScreenPos;
+        Point_Bez[1] = Point_P1_ScreenPos + bez_m1;
+        Point_Bez[2] = Point_P2_ScreenPos - bez_m2;
+        Point_Bez[3] = Point_P2_ScreenPos;
+
+        draw_list->PushClipRect(ViewportMin, ViewportMax, true);
+
+        draw_list->AddBezierCubic(Point_Bez[0], Point_Bez[1], Point_Bez[2], Point_Bez[3], col, 1.0f, 0);
+
+        if (ImGui::BezierInteractive(Point_Bez[0], Point_Bez[1], Point_Bez[2], Point_Bez[3]) &&
+            io.MousePos.x > (Point_P1_ScreenPos.x + PointSize/2) &&
+            io.MousePos.x < (Point_P2_ScreenPos.x - PointSize/2))
+        {
+            ImGui::SetCursorScreenPos(io.MousePos - ImVec2(5,5));
+            ImGui::Button("pointclick", ImVec2(10, 10));
+            if (ImGui::IsItemActivated()) {
+                Pos = V2(((io.MousePos - ViewportMin) / ViewportScale));
+                Pos.y = 1.0f - Pos.y;
+                Pos.x = Normalize(Pos.x);
+                Pos.y = Normalize(Pos.y);
+                AddPoint = true;
+            }
+        }
+
+        if (i == 0)
+            draw_list->AddLine(ImVec2(ViewportMin.x, Point_Bez[0].y), Point_Bez[0], col, 1.0f);
+        if (i == (*Num - 2))
+            draw_list->AddLine(ImVec2(ViewportMax.x, Point_Bez[3].y), Point_Bez[3], col, 1.0f);
+
+        draw_list->PopClipRect();
+
+#if 0
+        for (int x = 0; x < 256; x++) {
+            v2 Point = V2((real32)x/256, LUT[*ChannelIndex][x]);
+            ImVec2 Point_ScreenPos = ViewportMin + (ImVec2(Point.x, 1.0f - Point.y) * ViewportScale);
+            draw_list->AddNgon(Point_ScreenPos, 1, col, 8, 5.0f);
+        }
+#endif
+
+        draw_list->AddNgon(Point_P1_ScreenPos, 2, col, 8, 5.0f);
+
+
+        ImGui::PopID();
+    }
+
+
+    if (AddPoint) {
+        int x = 0;
+        for (;;) {
+            property_channel *Property_X = Effect_Property(Memory, Effect, x);
+            if (Property_X->Identifier == -1) {
+                Property_X->CurrentValue = Pos.x;
+                Property_X->Identifier = *SelectedChannel;
+                property_channel *Property_Y = Effect_Property(Memory, Effect, x+1);
+                Assert(Property_Y->Identifier == -1);
+                Property_Y->CurrentValue = Pos.y;
+                Property_Y->Identifier = *SelectedChannel;
+                x = MAX_PROPERTIES_PER_EFFECT;
+            }
+            if (x > MAX_PROPERTIES_PER_EFFECT)
+                break;
+            x++;
+        }
+        Effect->ExtraData[*SelectedChannel]++;
+        AddPoint = false;
+    }
+
+    // ImVec2 ButtonPos = ImGui::GetCursorScreenPos();
+    ImGui::SetCursorScreenPos(ViewportMin);
+
+    ImGui::InvisibleButton("canvas", ViewportScale, ImGuiButtonFlags_MouseButtonLeft | ImGuiButtonFlags_MouseButtonRight);
+    bool32 IsHovered = ImGui::IsItemHovered();
+    bool32 IsActive = ImGui::IsItemActive();
+    bool32 IsActivated = ImGui::IsItemActivated();
+    bool32 IsDeactivated = ImGui::IsItemDeactivated();
+
+    ImVec2 EndPos = ImGui::GetCursorScreenPos();
+
+    ImGui::SetCursorScreenPos(ViewportMin + ImVec2(ViewportScale.x + 20, 0));
+    ImGui_RGBAModeSwitch(State, Memory, io, SelectedChannel);
+
+    ImGui::SetCursorScreenPos(EndPos);
+}
+
 static void
 ImGui_PropertiesPanel(project_data *File, project_state *State, ui *UI, memory *Memory, ImGuiIO io,
                       sorted_comp_info *SortedCompArray, sorted_layer *SortedLayerArray,
@@ -73,33 +315,99 @@ ImGui_PropertiesPanel(project_data *File, project_state *State, ui *UI, memory *
         while (Layer_LoopChannels(State, Memory, &InfoLocation, &ArrayLocation, Layer, &Property, &Effect, &h, &c, &p))
         {
             ImGui::PushID(Property);
-            if ((h - 1) < AmountOf(Layer->Property)) {
+            if ((h - 1) < AmountOf(Layer->Property) && c == 0) {
                 if (ImGui::Button("K")) {
-                    uint16 *ArrayLocation = Property_GetSortedArray(SortedPropertyArray, State->MostRecentlySelectedLayer, h-1);
-                    Property_AddKeyframe(Memory, Property, State->Frame_Current, ArrayLocation);
+                    Property_AddKeyframe(Memory, F_Layers, Property, State->Frame_Current, ArrayLocation);
                 }
                 ImGui::SameLine();
+#if DEBUG
+                char size[64];
+                sprintf(size, "%s, %i", DefaultChannel[h-1], Property->Keyframe_Count);
+                char *Name = size;
+#else
                 char *Name = DefaultChannel[h-1];
+#endif
                 ImGui::DragScalar(Name, ImGuiDataType_Float, &Property->CurrentValue, Property->ScrubVal, &Property->MinVal, &Property->MaxVal, "%f");
                 ImGui_PropertyInteract_Slider(State, Memory, Property, io, WindowMinAbs, WindowMaxAbs, F_Layers);
             } else {
                 Assert(Effect);
                 header_effect *EffectHeader = Effect_EntryFromID(State, Effect->ID);
                 header_property ChannelHeader = State->Property[EffectHeader->PropertyStartIndex + c - 1];
-                Assert(EffectHeader->DisplayType == effect_display_type_standard);
+#if DEBUG
+                char size[64];
+                sprintf(size, "%s, %i", ChannelHeader.Name, Property->Keyframe_Count);
+                char *Name = size;
+#else
+                char *Name = ChannelHeader.Name;
+#endif
                 if ((c - 1) == 0) {
                     ImGui::PushID(Effect->Index);
+#if DEBUG
+                    ImGui::Text("%s, %i", EffectHeader->Name, Effect->Index);
+#else
                     ImGui::Text(EffectHeader->Name);
+#endif
                     ImGui::PopID();
                 }
-                if (ChannelHeader.DisplayType == property_display_type_standard) {
-                    if (ImGui::Button("K")) {
-                        // uint16 *ArrayLocation = Property_GetSortedArray(SortedPropertyArray, State->MostRecentlySelectedLayer, h);
-                        // Property_AddKeyframe(Memory, Property, State->Frame_Current, ArrayLocation);
+                if (EffectHeader->DisplayType == effect_display_type_standard) {
+                    if (ChannelHeader.DisplayType == property_display_type_standard) {
+                        if (ImGui::Button("K")) {
+                            Property_AddKeyframe(Memory, F_Properties, Property, State->Frame_Current, ArrayLocation);
+                        }
+                        ImGui::SameLine();
+                        ImGui::DragScalar(Name, ImGuiDataType_Float, &Property->CurrentValue, Property->ScrubVal, &Property->MinVal, &Property->MaxVal, "%f");
+                        ImGui_PropertyInteract_Slider(State, Memory, Property, io, WindowMinAbs, WindowMaxAbs, F_Properties);
+                    } else if (ChannelHeader.DisplayType == property_display_type_color) {
+                        if (ImGui::Button("K")) {
+                            Property_AddKeyframe(Memory, F_Properties, Property, State->Frame_Current, ArrayLocation);
+                        }
+                        ImGui::SameLine();
+                        ImGui::DragScalar(Name, ImGuiDataType_Float, &Property->CurrentValue, Property->ScrubVal, &Property->MinVal, &Property->MaxVal, "%f");
+                        ImGui_PropertyInteract_Slider(State, Memory, Property, io, WindowMinAbs, WindowMaxAbs, F_Properties);
+                        // if (c == 3) {
+                        //     ImGui::ColorEdit4("col", Col, ImGuiColorEditFlags_Float);
+                        // }
+                    } else {
+                        Assert(0);
+                    }
+                } else if (EffectHeader->DisplayType == effect_display_type_curves) {
+#if DEBUG
+                    ImGui::Text("Points (RGBA): %.02f, Points (indiv): %.02f, %.02f, %.02f, %.02f", Effect->ExtraData[0],
+                                Effect->ExtraData[1],  Effect->ExtraData[2], Effect->ExtraData[3], Effect->ExtraData[4]);
+#endif
+                    if (Property->Identifier == -1) {
+                        Effect_Curves_Init(Effect, Property);
+                    }
+                    uint16 SortedPointStart[MAX_PROPERTIES_PER_EFFECT/5];
+                    uint32 VisibleChannel = *(uint32 *)&Effect->ExtraData[5];
+                    Effect_Curves_Sort(Memory, Effect, SortedPointStart, VisibleChannel);
+                    ImGui_CurvesUI(State, Memory, io, Effect, Property, SortedPointStart);
+                    c = EffectHeader->Property_Count; // Causes this loop to only iterate once.
+                } else if (EffectHeader->DisplayType == effect_display_type_levels) {
+                    ImGui::Text("Levels!");
+                    uint32 VisibleChannel = *(uint32 *)&Effect->ExtraData[0];
+                    real32 *P_Left = 0, *P_Mid = 0, *P_Right = 0;
+                    if (VisibleChannel == 0) {
+                        property_channel *Property0 = (property_channel *)Memory_Block_AddressAtIndex(Memory, F_Properties, Effect->Block_Property_Index[0]);
+                        property_channel *Property1 = (property_channel *)Memory_Block_AddressAtIndex(Memory, F_Properties, Effect->Block_Property_Index[1]);
+                        property_channel *Property2 = (property_channel *)Memory_Block_AddressAtIndex(Memory, F_Properties, Effect->Block_Property_Index[2]);
+                        P_Left =  &Property0->CurrentValue;
+                        P_Mid =   &Property1->CurrentValue;
+                        P_Right = &Property2->CurrentValue;
+                    } else {
+                        property_channel *Property0 = (property_channel *)Memory_Block_AddressAtIndex(Memory, F_Properties, Effect->Block_Property_Index[3+(VisibleChannel-1)]);
+                        property_channel *Property1 = (property_channel *)Memory_Block_AddressAtIndex(Memory, F_Properties, Effect->Block_Property_Index[7+(VisibleChannel-1)]);
+                        property_channel *Property2 = (property_channel *)Memory_Block_AddressAtIndex(Memory, F_Properties, Effect->Block_Property_Index[11+(VisibleChannel-1)]);
+                        P_Left =  &Property0->CurrentValue;
+                        P_Mid =   &Property1->CurrentValue;
+                        P_Right = &Property2->CurrentValue;
+                    }
+                    ImGui::SliderLevels("1", "2,", "3", (void *)P_Mid, (void *)P_Left, (void *)P_Right);
+                    if (ImGui::IsItemActive()) {
+                        State->UpdateFrame = true;
                     }
-                    ImGui::SameLine();
-                    ImGui::DragScalar(ChannelHeader.Name, ImGuiDataType_Float, &Property->CurrentValue, Property->ScrubVal, &Property->MinVal, &Property->MaxVal, "%f");
-                    ImGui_PropertyInteract_Slider(State, Memory, Property, io, WindowMinAbs, WindowMaxAbs, F_Properties);
+                    ImGui_RGBAModeSwitch(State, Memory, io, (uint32 *)&Effect->ExtraData[0]);
+                    c = EffectHeader->Property_Count;
                 } else {
                     Assert(0);
                 }
@@ -959,7 +1267,7 @@ ImGui_TransformUI(project_data *File, project_state *State, memory *Memory, ui *
                             History_Entry_Commit(Memory, "Add keyframe");
                             bezier_point Point = { 1, {(real32)State->Frame_Current, Val[a], -1, 0, 1, 0}, interpolation_type_linear, 0, {0, 0, 0}, 0 };
                             uint16 *ArrayLocation = Property_GetSortedArray(SortedPropertyArray, State->MostRecentlySelectedLayer, h);
-                            Bezier_Add(Memory, Property[a], Point, ArrayLocation);
+                            Bezier_Add(Memory, F_Layers, Property[a], Point, ArrayLocation);
                             History_Entry_End(Memory);
                         }
                     }
@@ -1093,7 +1401,6 @@ ImGui_Viewport(project_data *File, project_state *State, ui *UI, memory *Memory,
     ImGui::Begin("Viewport", &open, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse);
 
     if (ImGui::IsWindowHovered(ImGuiFocusedFlags_ChildWindows)) {
-        State->SetFocus = true;
         State->FocusedWindow = focus_viewport;
     }
 
@@ -1287,7 +1594,7 @@ ImGui_Viewport(project_data *File, project_state *State, ui *UI, memory *Memory,
         }
     }
 
-    if (ImGui::IsKeyDown(ImGuiKey_Z)) {
+    if (ImGui::IsKeyDown(ImGuiKey_Z) && ImGui::IsWindowHovered()) {
         if (IsActive)
             ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeAll);
         else
@@ -1420,7 +1727,9 @@ ImGui_Timeline_HorizontalIncrementDraw(project_state *State, ui *UI, ImDrawList
 
 
 static void
-ImGui_GraphInfo(project_data *File, project_state *State, memory *Memory, ui *UI, ImGuiIO io, sorted_property_info *SortedPropertyInfo, uint16 *SortedPropertyArray)
+ImGui_GraphInfo(project_data *File, project_state *State, memory *Memory, ui *UI, ImGuiIO io,
+                sorted_comp_info *SortedCompArray, sorted_layer *SortedLayerArray,
+                sorted_property_info *SortedPropertyInfo, uint16 *SortedPropertyArray)
 {
     bool open = true;
     ImGui::Begin("Graph info");
@@ -1443,7 +1752,7 @@ ImGui_GraphInfo(project_data *File, project_state *State, memory *Memory, ui *UI
                 uint16 *ArrayLocation = Property_GetSortedArray(SortedPropertyArray, i, h);
                 ImGui::PushID(Property);
                 if (ImGui::Selectable(DefaultChannel[h], InfoLocation->IsGraphSelected)) {
-                    Property_DeselectAll(File, Memory, SortedPropertyArray);
+                    Property_DeselectAll(File, State, Memory, SortedCompArray, SortedLayerArray, SortedPropertyInfo, SortedPropertyArray);
                     for (int p = 0; p < Property->Keyframe_Count; p++) {
                         int k = ArrayLocation[p];
                         bezier_point *Point = Bezier_LookupAddress(Memory, Property, k);
@@ -1464,7 +1773,8 @@ ImGui_GraphInfo(project_data *File, project_state *State, memory *Memory, ui *UI
 static void
 ImGui_Timeline_DrawKeySheet(project_data *File, project_state *State, memory *Memory, ui *UI, ImGuiIO io, ImDrawList *draw_list, property_channel *Property, uint16 *ArrayLocation,
                             ImVec2 Increment, ImVec2 TimelineAbsolutePos, ImVec2 GraphPos, ImVec2 TimelineMoveSize, ImVec2 TimelineZoomSize,
-                            ImVec2 TimelineSize, ImVec2 TimelineSizeWithBorder, real32 LayerIncrement, uint16 *SortedPropertyArray)
+                            ImVec2 TimelineSize, ImVec2 TimelineSizeWithBorder, real32 LayerIncrement,
+                            sorted_comp_info *SortedCompArray, sorted_layer *SortedLayerArray, sorted_property_info *SortedPropertyInfo, uint16 *SortedPropertyArray)
 {
     ImGui::PushID(Property);
 
@@ -1531,7 +1841,7 @@ ImGui_Timeline_DrawKeySheet(project_data *File, project_state *State, memory *Me
         }
 
         if (IsItemDeactivated) {
-            Bezier_Commit(File, State, Memory, SortedPropertyArray);
+            Bezier_Commit(File, State, Memory, SortedCompArray, SortedLayerArray, SortedPropertyInfo, SortedPropertyArray);
         }
 
         draw_list->AddCircleFilled(Keyframe_ScreenPos, 4, PointCol);
@@ -1604,8 +1914,6 @@ ImGui_Timeline_DrawGraph(project_data *File, project_state *State, memory *Memor
                     Assert(InfoLocation->MinYIndex < Property->Keyframe_Count);
                     Assert(InfoLocation->MaxYIndex < Property->Keyframe_Count);
                     Assert(MaxY >= MinY);
-                    if (MaxY <= MinY)
-                        int p = 0;
                     real32 Y_Increment = (MaxY - MinY) ? (1 / (MaxY - MinY)) : 0.5;
 
                     real32 GraphScale = 0;
@@ -1752,7 +2060,7 @@ ImGui_Timeline_DrawPrecomp(project_data *File, project_state *State, memory *Mem
         int32 Frame_End = Layer->Frame_End;
         real32 Vertical_Offset = SortEntry.SortedOffset + DisplayOffset;
 
-        Layer_Evaluate_Display(Layer, SortedLayerArray, SortedCompArray, SortedLayerInfo, i, &DisplayOffset);
+        Layer_Evaluate_Display(State, Memory, Layer, SortedPropertyInfo, SortedPropertyArray, SortedLayerArray, SortedCompArray, SortedLayerInfo, i, &DisplayOffset);
 
         if (Layer->IsSelected)
             Layer_Interact_Evaluate(Memory, State, Index_Physical, SortedCompInfo, SortedLayerInfo, &Frame_Start, &Frame_End);
@@ -2004,15 +2312,25 @@ ImGui_Timeline_DrawPrecomp(project_data *File, project_state *State, memory *Mem
 
             // Keyframe view
             uint32 Channel = 0;
-            for (int h = 0; h < AmountOf(Layer->Property); h++) {
-                property_channel *Property = &Layer->Property[h];
+
+            sorted_property_info *InfoLocation = SortedPropertyInfo + SortedLayerInfo->SortedPropertyStart;
+            uint16 *ArrayLocation = SortedPropertyArray + SortedLayerInfo->SortedKeyframeStart;
+            int h = 0, c = 0, p = 0;
+            property_channel *Property = NULL;
+            block_effect *Effect = NULL;
+            while (Layer_LoopChannels(State, Memory, &InfoLocation, &ArrayLocation, Layer, &Property, &Effect, &h, &c, &p))
+            {
                 if (Property->IsToggled) {
-                    sorted_property_info *InfoLocation = Property_GetSortedInfo(SortedPropertyInfo, i, h);
-                    uint16 *ArrayLocation = Property_GetSortedArray(SortedPropertyArray, i, h);
-                    ImVec2 GraphPos(TimelineAbsolutePos.x, Layer_ScreenPos_Min.y + (Layer_ScreenSize.y * 2) + (Layer_ScreenSize.y * Channel));
+                    ImVec2 GraphMinPos = ImVec2(TimelineAbsolutePos.x, Layer_ScreenPos_Min.y + Layer_ScreenSize.y + (Layer_ScreenSize.y * Channel));
+                    ImVec2 GraphPos = GraphMinPos + ImVec2(0, Layer_ScreenSize.y);
+                    ImVec2 GraphMinBounds = GraphMinPos + ImVec2(0, Layer_ScreenSize.y * 0.5);
+                    ImVec2 GraphMaxBounds = GraphMinBounds + ImVec2(TimelineSizeWithBorder.x, Layer_ScreenSize.y);
+                    uint32 col = (Channel % 2) ? IM_COL32(50, 50, 50, 255) : IM_COL32(50, 50, 50, 128);
+                    draw_list->AddRectFilled(GraphMinBounds, GraphMaxBounds, col);
                     ImGui_Timeline_DrawKeySheet(File, State, Memory, UI, io, draw_list, Property, ArrayLocation,
                                                 Increment, TimelineAbsolutePos, GraphPos, TimelineMoveSize, TimelineZoomSize,
-                                                TimelineSize, TimelineSizeWithBorder, LayerIncrement, SortedPropertyArray);
+                                                TimelineSize, TimelineSizeWithBorder, LayerIncrement,
+                                                SortedCompArray, SortedLayerArray, SortedPropertyInfo, SortedPropertyArray);
                     Channel++;
                 }
             }
@@ -2075,7 +2393,6 @@ ImGui_Timeline(project_data *File, project_state *State, memory *Memory, ui *UI,
     ImGui::Begin("Timeline", NULL);
 
     if (ImGui::IsWindowHovered(ImGuiFocusedFlags_ChildWindows)) {
-        State->SetFocus = true;
         State->FocusedWindow = focus_timeline;
     }
 
@@ -2348,7 +2665,7 @@ ImGui_Timeline(project_data *File, project_state *State, memory *Memory, ui *UI,
             if (State->Interact_Active == interact_type_keyframe_move ||
                 State->Interact_Active == interact_type_keyframe_rotate ||
                 State->Interact_Active == interact_type_keyframe_scale) {
-                Bezier_Commit(File, State, Memory, SortedPropertyArray);
+                Bezier_Commit(File, State, Memory, SortedCompArray, SortedLayerArray, SortedPropertyInfo, SortedPropertyArray);
             }
             State->BoxSelect = true;
         }
@@ -2520,13 +2837,11 @@ ImGui_Popups(project_data *File, project_state *State, ui *UI, memory *Memory, I
         {
             ImGui::OpenPopup("Save as");
             ImGui::SetKeyboardFocusHere();
-            State->SetFocus = 0;
         } break;
         case popup_keybinds:
         {
             ImGui::OpenPopup("Keybinds");
             ImGui::SetKeyboardFocusHere();
-            State->SetFocus = 0;
         } break;
         default:
         {
@@ -2575,6 +2890,9 @@ ImGui_ProcessInputs(project_data *File, project_state *State, ui *UI, memory *Me
             State->Brush.EraseMode ^= 1;
         }
     }
+    if (ImGui::IsKeyPressed(ImGuiKey_U)) {
+        State->HotkeyInput = hotkey_togglechannels;
+    }
     if (ImGui::IsKeyPressed(ImGuiKey_X)) {
         if (State->TimelineMode == timeline_mode_graph && State->Interact_Active == interact_type_keyframe_move) {
             if (State->Interact_Modifier != 1)
@@ -2603,14 +2921,15 @@ ImGui_ProcessInputs(project_data *File, project_state *State, ui *UI, memory *Me
     }
     // NOTE(fox): File data not tracked on undo tree!
     if (ImGui::IsKeyPressed(ImGuiKey_N)) {
-        block_composition *MainComp = (block_composition *)Memory_Block_AddressAtIndex(Memory, F_Precomps, File->PrincipalCompIndex);
-        if (MainComp->Frame_Start < State->Frame_Current)
-            MainComp->Frame_End = State->Frame_Current;
-    }
-    if (ImGui::IsKeyPressed(ImGuiKey_B)) {
-        block_composition *MainComp = (block_composition *)Memory_Block_AddressAtIndex(Memory, F_Precomps, File->PrincipalCompIndex);
-        if (MainComp->Frame_End > State->Frame_Current)
-            MainComp->Frame_Start = State->Frame_Current;
+        if (io.KeyShift) {
+            block_composition *MainComp = (block_composition *)Memory_Block_AddressAtIndex(Memory, F_Precomps, File->PrincipalCompIndex);
+            if (MainComp->Frame_Start < State->Frame_Current)
+                MainComp->Frame_End = State->Frame_Current;
+        } else {
+            block_composition *MainComp = (block_composition *)Memory_Block_AddressAtIndex(Memory, F_Precomps, File->PrincipalCompIndex);
+            if (MainComp->Frame_End > State->Frame_Current)
+                MainComp->Frame_Start = State->Frame_Current;
+        }
     }
     if (ImGui::IsKeyPressed(ImGuiKey_Tab)) {
         State->TimelineMode = (State->TimelineMode == timeline_mode_default) ? timeline_mode_graph : timeline_mode_default;
@@ -2618,6 +2937,9 @@ ImGui_ProcessInputs(project_data *File, project_state *State, ui *UI, memory *Me
         UI->GraphMoveSize = ImVec2(0, 0);
     }
     if (!io.KeyCtrl) {
+        // NOTE(fox): Checking IsWindowHovered seems to be all we need to do to
+        // make per-window hotkeys work; setting it as the focused window causes
+        // problems with popups.
         if (State->FocusedWindow == focus_timeline) {
             if (State->TimelineMode == timeline_mode_default) {
                 if (ImGui::IsKeyPressed(ImGuiKey_G)) {
@@ -2857,8 +3179,7 @@ ImGui_EffectsPanel(project_data *File, project_state *State, memory *Memory, ui
             header_effect *EffectHeader = &State->Effect[i];
             if (State->filter.PassFilter(EffectHeader->Name)) {
                 if (EffectSel == p && State->MostRecentlySelectedLayer != -1) {
-                    Assert(0);
-                    // AddEffect(File->Layer[State->MostRecentlySelectedLayer], Memory, i);
+                    Effect_Add(File, State, Memory, i);
                     State->UpdateFrame = true;
                 }
                 p++;
diff --git a/package.sh b/package.sh
index 01868b1..f1861b4 100755
--- a/package.sh
+++ b/package.sh
@@ -2,6 +2,7 @@
 
 # simple helper to zip up the builds
 cd bin
+mkdir zip
 
 # windows
 mkdir real2d_windows
@@ -17,4 +18,7 @@ zip -r zip/real2d_m1_mac.zip real_arm.app
 zip -r zip/real2d_x86_linux.zip real2d_x86_linux
 zip -r zip/real2d_arm_linux.zip real2d_arm_linux
 
-rsync -avz bin/zip/* root@foxcam.net:/var/www/foxcam/bin/
+rsync -avz zip/* root@foxcam.net:/var/www/foxcam/bin/
+
+rm -r zip
+rm -r real2d_windows
-- 
cgit v1.2.3