From 7804c6a96d26c2e757d09f1864eb73fb81eb280f Mon Sep 17 00:00:00 2001 From: Fox Caminiti Date: Thu, 20 Oct 2022 20:45:37 -0400 Subject: precomp recursion! --- createcalls.cpp | 86 +- defines.h | 6 + ffmpeg_backend.cpp | 2 +- functions.h | 5 + imgui_helper_widgets.cpp | 68 + imgui_ops.h | 10 + main.cpp | 447 ++++--- main.h | 205 ++- memory.cpp | 93 +- memory.h | 1 + my_imgui_widgets.cpp | 3225 +++++++++++++++++++++++++--------------------- prenderer.cpp | 435 +++++++ threading.cpp | 97 +- 13 files changed, 2974 insertions(+), 1706 deletions(-) create mode 100644 imgui_helper_widgets.cpp diff --git a/createcalls.cpp b/createcalls.cpp index 2c2df61..6a830b5 100644 --- a/createcalls.cpp +++ b/createcalls.cpp @@ -12,17 +12,17 @@ Source_Generate(project_data *File, project_state *State, memory *Memory, void * bool32 IsVideo = 0; if (AV_IsFileSupported((char *)TempString, &IsVideo)) { - uint16 Index = Memory_Block_AllocateNew(Memory, F_Strings); + uint16 Index = Memory_Block_AllocateNew(Memory, F_Sources); block_source *Source = (block_source *)Memory_Block_AddressAtIndex(Memory, F_Sources, Index); History_Entry_Commit(Memory, "Add source"); History_Action_Block_Swap(Memory, F_Sources, Source); Source->Occupied = 1; - Source->Block_String_Index = String_AddToFile(Memory, (char *)TempString); + Source->Path_String_Index = String_AddToFile(Memory, (char *)TempString); if (IsVideo) - Source->SourceType = source_type_video; + Source->Type = source_type_video; else - Source->SourceType = source_type_image; + Source->Type = source_type_image; History_Action_Swap(Memory, F_File, sizeof(File->Source_Count), &File->Source_Count); File->Source_Count++; @@ -45,6 +45,81 @@ Property_InitFloat(char *Name, real32 Val, real32 ScrubVal, real32 MinVal = PROP return Property; } +static void +Layer_Interact_Evaluate(memory *Memory, project_state *State, int32 *Frame_Start, int32 *Frame_End, real32 *Vertical_Offset) +{ + if (State->Interact_Active == interact_type_layer_move) { + *Frame_Start += (int32)(State->Interact_Offset[0] + 0); + *Frame_End += (int32)(State->Interact_Offset[0] + 0); + *Vertical_Offset += (int32)State->Interact_Offset[1]; + } else if (State->Interact_Active == interact_type_layer_timeadjust) { + int Side[2] = {0}; + Assert(State->Interact_Offset[1] == 0 || State->Interact_Offset[1] == 1); + Side[(int)State->Interact_Offset[1]] = 1; + *Frame_Start += (int32)(State->Interact_Offset[0] * Side[0]); + *Frame_End += (int32)(State->Interact_Offset[0] * Side[1]); + } +} + +// TODO(fox): Precomps! +void Layer_DeselectAll(memory *Memory, uint32 LayerCount) { + for (uint32 i = 0; i < LayerCount; i++) { + block_layer *Layer = (block_layer *)Memory_Block_AddressAtIndex(Memory, F_Layers, i); + if (Layer->IsSelected) + Layer->IsSelected = false; + } +} + +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; +} + +void Layer_SortAll(memory *Memory, sorted_layer *LayerArrayStart, sorted_comp_info *CompStart, uint32 LayerCount, uint32 CompCount) +{ + for (uint32 i = 0; i < LayerCount; i++) { + block_layer *Layer = (block_layer *)Memory_Block_AddressAtIndex(Memory, F_Layers, i); + Assert(Layer->Block_Composition_Index < CompCount); + CompStart[Layer->Block_Composition_Index].LayerCount++; + + } + for (uint32 i = 0; i < LayerCount; i++) { + // SortedLayerArray->Block_Layer_Index = 0; + block_layer *Layer = (block_layer *)Memory_Block_AddressAtIndex(Memory, F_Layers, i); + sorted_comp_info *SortedCompInfo = &CompStart[Layer->Block_Composition_Index]; + sorted_layer *SortedLayerInfo = Layer_GetSortedArray(LayerArrayStart, CompStart, Layer->Block_Composition_Index); + uint32 SortedIndex_Playhead = 0; + while (SortedIndex_Playhead < SortedCompInfo->CurrentSortIndex) { + sorted_layer LayerEntry = SortedLayerInfo[SortedIndex_Playhead]; + block_layer *TestLayer = (block_layer *)Memory_Block_AddressAtIndex(Memory, F_Layers, LayerEntry.Block_Layer_Index); + if (Layer->Vertical_Offset < TestLayer->Vertical_Offset) { + break; + } else { + SortedIndex_Playhead++; + } + } + if (SortedIndex_Playhead != SortedCompInfo->CurrentSortIndex) { + uint8 *Address_Start = (uint8 *)(SortedLayerInfo + SortedIndex_Playhead); + uint8 *Address_End = (uint8 *)(SortedLayerInfo + i); + Arbitrary_ShiftData(Address_Start, Address_End, sizeof(sorted_layer), 1); + } + sorted_layer *LayerEntry = SortedLayerInfo + SortedIndex_Playhead; + LayerEntry->Block_Layer_Index = i; + SortedCompInfo->CurrentSortIndex++; + } + Assert(CompStart[0].LayerCount == 2); + Assert(CompStart[1].LayerCount == 2); + // Assert(LayerArrayStart[0].Block_Layer_Index == 0); + // Assert(LayerArrayStart[1].Block_Layer_Index == 1); + // Assert(LayerArrayStart[2].Block_Layer_Index == 2); + // Assert(LayerArrayStart[3].Block_Layer_Index == 3); +} block_layer * Layer_Init(project_data *File, memory *Memory) { @@ -57,6 +132,7 @@ block_layer * Layer_Init(project_data *File, memory *Memory) Layer->Block_String_Index = Memory_Block_AllocateNew(Memory, F_Strings); block_string *String = (block_string *)Memory_Block_AddressAtIndex(Memory, F_Strings, Layer->Block_String_Index); 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); @@ -67,6 +143,8 @@ block_layer * Layer_Init(project_data *File, memory *Memory) 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); + Layer->IsVisible = 1; + History_Action_Swap(Memory, F_File, sizeof(File->Layer_Count), &File->Layer_Count); File->Layer_Count++; diff --git a/defines.h b/defines.h index c124f53..2ab2ba6 100644 --- a/defines.h +++ b/defines.h @@ -41,3 +41,9 @@ typedef uint64 ptrsize; // is there a compiler variable for 32 vs 64 bit like #define AmountOf(Array) sizeof((Array)) / sizeof((Array)[1]) +#if ARM +#define GetTime() Assert(0) +#else +#define GetTime() __rdtsc() +#endif + diff --git a/ffmpeg_backend.cpp b/ffmpeg_backend.cpp index b986d7a..a54fb51 100644 --- a/ffmpeg_backend.cpp +++ b/ffmpeg_backend.cpp @@ -125,7 +125,7 @@ void AV_Init(block_source *Source, av_info *AV, memory *Memory) // The two calls below theoretically shouldn't fail since we already tested them in IsFileSupported. AV->FileFormatContext = avformat_alloc_context(); - char *Path = (char *)Memory_Block_AddressAtIndex(Memory, F_Strings, Source->Block_String_Index); + char *Path = (char *)Memory_Block_AddressAtIndex(Memory, F_Strings, Source->Path_String_Index); err = avformat_open_input(&AV->FileFormatContext, Path, NULL, NULL);; if (err < 0) { fprintf(stderr, "Libav error: (%s)\n", av_err2str(err)); diff --git a/functions.h b/functions.h index 88278fe..4fe5a0e 100644 --- a/functions.h +++ b/functions.h @@ -1,5 +1,10 @@ static bool32 AV_IsFileSupported(char *filename, bool32 *IsVideo); +static void Arbitrary_WriteInto(uint8 *Address_Read, uint8 *Address_Write, 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); + # if 0 // Buffer management diff --git a/imgui_helper_widgets.cpp b/imgui_helper_widgets.cpp new file mode 100644 index 0000000..105dad2 --- /dev/null +++ b/imgui_helper_widgets.cpp @@ -0,0 +1,68 @@ +// Widgets not directly related to drawing UI. + +// Returns a normalized UV position of the composition +static v2 +ImGui_ScreenPointToCompUV(ImVec2 ViewportMin, ImVec2 CompPos, ImVec2 CompZoom, ImVec2 MousePos) +{ + ImVec2 LocalMousePos = MousePos - ViewportMin; + ImVec2 LocalCompPos = CompPos - ViewportMin; + ImVec2 MouseScreenUV = LocalMousePos - LocalCompPos; + ImVec2 Result = MouseScreenUV / CompZoom; + return V2(Result); +} + +// NOTE(fox): We have to do a bit of hackery here to tell how many times the +// mouse has been warped during a drag, since it doesn't seem like we can rely +// on SDL_WarpMouseGlobal to update on the first frame of a WantSetPos request. + +static void +ImGui_WarpMouse(ui *UI, ImVec2 MousePos, ImVec2 Min, ImVec2 Max, int Direction = 3) +{ + if (Direction & 1) { + if (MousePos.x < Min.x) { + UI->Warp_WantSetPos = true; + UI->Warp_PositionToSet = ImVec2(Max.x - 5, MousePos.y); + UI->Warp_PositionInitial = MousePos.x; + UI->Warp_Direction = 0; + } + if (MousePos.x > Max.x) { + UI->Warp_WantSetPos = true; + UI->Warp_PositionToSet = ImVec2(Min.x + 5, MousePos.y); + UI->Warp_PositionInitial = MousePos.x; + UI->Warp_Direction = 1; + } + } + if (Direction & 2) { + if (MousePos.y < Min.y) { + UI->Warp_WantSetPos = true; + UI->Warp_PositionToSet = ImVec2(MousePos.x, Max.y - 5); + UI->Warp_PositionInitial = MousePos.y; + UI->Warp_Direction = 2; + } + if (MousePos.y > Max.y) { + UI->Warp_WantSetPos = true; + UI->Warp_PositionToSet = ImVec2(MousePos.x, Min.y + 5); + UI->Warp_PositionInitial = MousePos.y; + UI->Warp_Direction = 3; + } + } +} + +// We record the initial position and the direction of the wrap, and only +// increment the wrap amount when MousePos actually is measured to be what we expect. + +static void +ImGui_WarpMouseFinish(ui *UI, ImVec2 MousePos) +{ + if (UI->Warp_Direction == 0) { + if (MousePos.x < UI->Warp_PositionInitial) UI->Warp_X--; + } else if (UI->Warp_Direction == 1) { + if (MousePos.x > UI->Warp_PositionInitial) UI->Warp_X++; + } else if (UI->Warp_Direction == 2) { + if (MousePos.y < UI->Warp_PositionInitial) UI->Warp_Y--; + } else if (UI->Warp_Direction == 3) { + if (MousePos.y > UI->Warp_PositionInitial) UI->Warp_Y++; + } else { + Assert(0); + } +} diff --git a/imgui_ops.h b/imgui_ops.h index 629815f..2249de3 100644 --- a/imgui_ops.h +++ b/imgui_ops.h @@ -58,6 +58,16 @@ ImVec2 operator/(ImVec2 A, ImVec2 B) return Result; } +ImVec2 operator/(ImVec2 A, real32 B) +{ + ImVec2 Result; + + Result.x = A.x / B; + Result.y = A.y / B; + + return Result; +} + inline bool32 IsRectTouching(ImVec2 Min1, ImVec2 Max1, ImVec2 Min2, ImVec2 Max2) { diff --git a/main.cpp b/main.cpp index d0349fa..d6f9e71 100644 --- a/main.cpp +++ b/main.cpp @@ -44,20 +44,21 @@ #include "functions.h" // #include "sharebuffer.h" -SDL_atomic_t Render_Interrupt; -// SDL_atomic_t CurrentEntry; -// SDL_atomic_t QueuedEntries; -// SDL_atomic_t CompletedEntries; -static bool32 IsRendering = false; +SDL_Thread *Thread[8]; +SDL_sem *Semaphore; + +SDL_atomic_t CurrentEntry; +SDL_atomic_t QueuedEntries; +SDL_atomic_t CompletedEntries; + +render_entry Entries[256]; + +static uint64 BitmapBlockSize; static instruction_mode InstructionMode = instruction_mode_scalar; static uint32 RandomGlobalIncrement = 0; // render_entry Entries[256]; -// SDL_Thread *thread[8]; -SDL_Thread *MainRenderThread; -SDL_sem *Semaphore; - #include "memory.cpp" #include "undo.cpp" #include "strings.cpp" @@ -67,47 +68,217 @@ SDL_sem *Semaphore; #include "createcalls.cpp" #include "ffmpeg_backend.cpp" #include "my_imgui_widgets.cpp" +#include "prenderer.cpp" #include "gl_calls.cpp" #if 0 #include "effects.cpp" #include "keyframes.cpp" #include "layer.cpp" #include "bezier.cpp" -#include "prenderer.cpp" #include "bitmap_calls.cpp" #endif +static void +Main_RenderUI(ImGuiIO io, ImVec4 clear_color, SDL_Window *window) +{ + printf("Call ImGui::Render\n"); + ImGui::Render(); + glViewport(0, 0, (int)io.DisplaySize.x, (int)io.DisplaySize.y); + glClearColor(clear_color.x * clear_color.w, clear_color.y * clear_color.w, clear_color.z * clear_color.w, clear_color.w); + glClear(GL_COLOR_BUFFER_BIT); + printf("Call GL renderer\n"); + ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); + printf("Call window swap\n"); + SDL_GL_SwapWindow(window); +} + +static void +Main_InputTest(project_data *File, project_state *State, memory *Memory, ui *UI, SDL_Window *window, GLuint textureID) +{ + ImGuiIO& io = ImGui::GetIO(); + SDL_Event event = {}; + while (SDL_PollEvent(&event)) + { + ImGui_ImplSDL2_ProcessEvent(&event); + if (event.type == SDL_DROPFILE) { + char *DropFile = event.drop.file; + Source_Generate(File, State, Memory, DropFile); + SDL_free(DropFile); + } + if (event.type == SDL_QUIT) + State->IsRunning = false; + if (event.type == SDL_WINDOWEVENT && event.window.event == SDL_WINDOWEVENT_CLOSE && event.window.windowID == SDL_GetWindowID(window)) + State->IsRunning = false; + } + + if (UI->Warp_WantSetPos) { + ImGui::GetIO().WantSetMousePos = true; + io.MousePos = UI->Warp_PositionToSet; + } + + ImGui_ImplOpenGL3_NewFrame(); + ImGui_ImplSDL2_NewFrame(); + + ImGui::NewFrame(); + + if (UI->Warp_WantSetPos) { + ImGui_WarpMouseFinish(UI, io.MousePos); + io.MouseDelta = {}; + UI->Warp_WantSetPos = false; + } + + ImGui::DockSpaceOverViewport(); + + if (!io.WantCaptureKeyboard) + ImGui_ProcessInputs(State, io); + #if 0 + + ImGui_Viewport(File, &State, &UI, &Memory, CompBuffer, io, textureID); + + ImGui_File(&File, &State, &Memory, &UI, io); + + ImGui_EffectsPanel(&File, &State, &Memory, &UI, io); + + ImGui_PropertiesPanel(&File, &State, &UI, &Memory, io); + + // ImGui_Graph(&File, &State, &Memory, &UI, io); + +#if DEBUG + ImGui_DebugUndoTree(&File, &Memory); + if (Debug.ToggleWindow) { + ImGui::ShowDemoWindow(); + ImGui_DebugMemoryViewer(&File, &Memory); + } +#endif + +#endif + + ImGui_Viewport(File, State, UI, Memory, io, textureID); + ImGui_Timeline(File, State, Memory, UI, io); + ImGui_File(File, State, Memory, io); + + ImGui_DebugMemoryViewer(State); + // ImGui::ShowDemoWindow(); + +#if DEBUG + Debug.Temp = {}; +#endif + + ImGui::EndFrame(); +} + static void -MainFunction(main_sdl *Main, memory *Memory, - project_state *State, project_data *File, - comp_buffer *CompBuffer) +Render_Comp(project_data *File, project_state *State, memory *Memory, ImGuiIO io, sorted_comp_info *SortedCompArray, sorted_layer *SortedLayerArray, uint32 CompIndex) { - Bitmap_Clear(CompBuffer->PackedBuffer, CompBuffer->Width, CompBuffer->Height, CompBuffer->BytesPerPixel); - Bitmap_Clear(CompBuffer->UnpackedBuffer, CompBuffer->Width, CompBuffer->Height, CompBuffer->BytesPerPixel); - for (int i = 0; i < File->NumberOfLayers; i++) { - project_layer *Layer = File->Layer[i]; - if (Layer->StartFrame <= File->CurrentFrame && - Layer->EndFrame >= File->CurrentFrame) + block_composition *Comp = (block_composition *)Memory_Block_AddressAtIndex(Memory, F_Precomps, CompIndex); + cache_entry *Entry_Main = Memory_Cache_Search(State, Memory, State->Render.Entry, cache_entry_type_comp, CompIndex, State->Frame_Current); + void *CompBuffer = Memory_Block_Bitmap_AddressAtIndex(Memory, Entry_Main->Block_StartIndex); + sorted_comp_info *SortedCompInfo = &SortedCompArray[CompIndex]; + sorted_layer *SortedLayerInfo = Layer_GetSortedArray(SortedLayerArray, SortedCompArray, CompIndex); + for (int i = 0; i < SortedCompInfo->LayerCount; 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); + if (Layer->Frame_Start <= State->Frame_Current && + Layer->Frame_End >= State->Frame_Current && Layer->IsVisible) { - if (State->UpdateKeyframes) { - for (int p = 0; p < Layer->NumberOfEffects; p++) { - for (int o = 0; o < Layer->Effect[p]->NumberOfProperties; o++) { - CalculateKeyframesLinearly(File->CurrentFrame, &Layer->Effect[p]->Property[o]); - } + layer_bitmap_state *BitmapState = &State->Render.Bitmap[Index_Physical]; + void *BitmapAddress = NULL; + if (!Layer->IsPrecomp) { + block_source *Source = (block_source *)Memory_Block_AddressAtIndex(Memory, F_Sources, Layer->Block_Source_Index); + cache_entry *Entry = Memory_Cache_Search(State, Memory, State->Render.Entry, cache_entry_type_source, Layer->Block_Source_Index, 0); + + Assert(Source->Type == source_type_image); + + if (!Entry->IsCached) { + uint64 Src_TimeStart = GetTime(); + block_string *Name = (block_string *)Memory_Block_AddressAtIndex(Memory, F_Strings, Source->Path_String_Index); + int w = 0, h = 0; + void *temp = stbi_load(Name->Char, &w, &h, NULL, 4); + Source->Width = w; + Source->Height = h; + Source->BytesPerPixel = 4; + uint64 Size = Source->Width * Source->Height * Source->BytesPerPixel; + void *Source_Address = Memory_Block_Bitmap_AddressAtIndex(Memory, Entry->Block_StartIndex); + Arbitrary_WriteInto((uint8 *)temp, (uint8 *)Source_Address, Size); + stbi_image_free(temp); + BitmapState->ToUpdate = false; + BitmapState->CurrentFrame = 0; + Entry->CycleTime = GetTime() - Src_TimeStart; + Layer->x.CurrentValue = (Layer->Block_Source_Index == 0) ? 200 : Comp->Width/2; + Layer->y.CurrentValue = Comp->Height/2; } - for (int r = 0; r < AmountOf(Layer->Property); r++) { - CalculateKeyframesLinearly(File->CurrentFrame, &Layer->Property[r]); + BitmapAddress = Memory_Block_Bitmap_AddressAtIndex(Memory, Entry->Block_StartIndex); + } else { + block_composition *Precomp = (block_composition *)Memory_Block_AddressAtIndex(Memory, F_Precomps, Layer->Block_Source_Index); + cache_entry *Entry = Memory_Cache_Search(State, Memory, State->Render.Entry, cache_entry_type_comp, Layer->Block_Source_Index, State->Frame_Current); + if (!Entry->IsCached) { + uint64 Src_TimeStart = GetTime(); + Render_Comp(File, State, Memory, io, SortedCompArray, SortedLayerArray, Layer->Block_Source_Index); + Layer->x.CurrentValue = (Layer->Block_Source_Index == 0) ? 200 : Comp->Width/2; + Layer->y.CurrentValue = Comp->Height/2; + Entry->CycleTime = GetTime() - Src_TimeStart; } + BitmapAddress = Memory_Block_Bitmap_AddressAtIndex(Memory, Entry->Block_StartIndex); + } + Assert(BitmapAddress); + + // for (int a = 0; a < Layer->Block_Mask_Count; a++) { + // } + // for (int a = 0; a < Layer->Block_Effect_Count; a++) { + // } + + transform_info T = Transform_Calculate(State, Memory, File, Layer, Comp); + T.SourceBuffer = BitmapAddress; + rectangle RenderRegion = {0, 0, Comp->Width, Comp->Height}; + + bool32 IsRendering = true; + Renderer_Start((void *)&T, CompBuffer, RenderRegion); + while (IsRendering) { + SDL_Delay(2); + Renderer_Check(&IsRendering); + // TODO(fox): Make interruptable if the render time gets too long. } - Layer_UpdateBitmap(File, Layer, Memory, File->CurrentFrame); } } - State->UpdateKeyframes = false; - QueueCurrentFrame(File, CompBuffer, State); } -#endif + +static void +Main_Renderer(project_data *File, project_state *State, memory *Memory, SDL_Window *window, GLuint textureID, ImGuiIO io) +{ + State->UpdateFrame = false; + + uint64 Comp_TimeStart = GetTime(); + + block_composition *MainComp = (block_composition *)Memory_Block_AddressAtIndex(Memory, F_Precomps, File->PrincipalCompIndex); + cache_entry *Entry_Main = Memory_Cache_Search(State, Memory, State->Render.Entry, cache_entry_type_comp, File->PrincipalCompIndex, State->Frame_Current); + void *MainCompBuffer = Memory_Block_Bitmap_AddressAtIndex(Memory, Entry_Main->Block_StartIndex); + + // NOTE(fox): All layers are given a slot here + uint64 SortSize = (sizeof(sorted_comp_info) * File->Comp_Count) + (sizeof(sorted_layer) * File->Layer_Count); + void *SortedArray = Memory_PushScratch(Memory, SortSize); + Arbitrary_Zero((uint8 *)SortedArray, SortSize); + sorted_comp_info *SortedCompArray = (sorted_comp_info *)SortedArray; + sorted_layer *SortedLayerArray = (sorted_layer *)((uint8 *)SortedArray + (sizeof(sorted_comp_info) * File->Comp_Count)); + Layer_SortAll(Memory, SortedLayerArray, SortedCompArray, File->Layer_Count, File->Comp_Count); + + Render_Comp(File, State, Memory, io, SortedCompArray, SortedLayerArray, File->PrincipalCompIndex); + + Memory_PopScratch(Memory, SortSize); + + glBindTexture(GL_TEXTURE_2D, textureID); + + int ByteFlag2 = (MainComp->BytesPerPixel == 4) ? GL_UNSIGNED_BYTE : GL_UNSIGNED_SHORT; + if (!Entry_Main->CycleTime) + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, MainComp->Width, MainComp->Height, 0, GL_RGBA, ByteFlag2, MainCompBuffer); + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, MainComp->Width, MainComp->Height, GL_RGBA, ByteFlag2, MainCompBuffer); + + Entry_Main->CycleTime = GetTime() - Comp_TimeStart; + + // TODO(fox): garbage collect AV state! +} int main(int argc, char *argv[]) { @@ -131,7 +302,7 @@ int main(int argc, char *argv[]) { Memory_InitTable(&GlobalMemory, &Memory, 1 * 1024 * 1024, P_AVInfo, "Image/video headers"); Memory_InitTable(&GlobalMemory, &Memory, 10 * 1024 * 1024, P_UndoBuffer, "Undo buffer"); - Memory_InitTable(&GlobalMemory, &Memory, 10 * 1024 * 1024, P_MiscCache, "Misc persistent"); + Memory_InitTable(&GlobalMemory, &Memory, 40 * 1024 * 1024, P_MiscCache, "Misc persistent"); Memory_InitTable(&GlobalMemory, &Memory, sizeof(project_data), F_File, "File", sizeof(project_data)); Memory_InitTable(&GlobalMemory, &Memory, 10 * 1024 * 1024, F_Precomps, "Precomps", sizeof(block_composition)); @@ -142,7 +313,8 @@ int main(int argc, char *argv[]) { Memory_InitTable(&GlobalMemory, &Memory, 10 * 1024 * 1024, F_Strings, "Strings", sizeof(block_string)); Memory_InitTable(&GlobalMemory, &Memory, (uint64)64 * 1024 * 1024, B_ScratchSpace, "Scratch"); - // Memory_InitTable(&GlobalMemory, &Memory, (uint64)200 * 1024 * 1024, B_CachedBitmaps, "Cached bitmap buffer"); + // Memory_InitTable(&GlobalMemory, &Memory, (uint64)1 * 1024 * 1024, B_CachedBitmapInfo, "Cached bitmap info"); + Memory_InitTable(&GlobalMemory, &Memory, (uint64)50 * 1024 * 1024, B_CachedBitmaps, "Cached bitmap buffer"); #if ARM InstructionMode = instruction_mode_neon; @@ -160,45 +332,98 @@ int main(int argc, char *argv[]) { project_data *File = (project_data *)Memory_Block_AllocateAddress(&Memory, F_File); *File = {}; File->Occupied = 1; + + ui UI = {}; + block_composition *MainComp = (block_composition *)Memory_Block_AllocateAddress(&Memory, F_Precomps); + // MainComp->Width = 3840; + // MainComp->Height = 2160; MainComp->Width = 1280; MainComp->Height = 720; MainComp->FPS = 24; MainComp->BytesPerPixel = 4; - MainComp->Frame_Count = 48; MainComp->Frame_End = 48; + MainComp->Occupied = 1; + MainComp->Name_String_Index = String_AddToFile(&Memory, "Main comp"); + + block_composition *Comp2 = (block_composition *)Memory_Block_AllocateAddress(&Memory, F_Precomps); + Comp2->Width = 500; + Comp2->Height = 500; + Comp2->FPS = 24; + Comp2->BytesPerPixel = 4; + Comp2->Frame_Count = 48; + Comp2->Frame_End = 48; + Comp2->Occupied = 1; + Comp2->Name_String_Index = String_AddToFile(&Memory, "Another comp"); + + File->Comp_Count = 2; + + // 1 MB for 4, 2 MB for 8 + BitmapBlockSize = (MainComp->BytesPerPixel / 4) * 1024 * 1024; { uint16 SourceIndex = Source_Generate(File, State, &Memory, (void *)"../asset/a.jpg"); block_source *Source = (block_source *)Memory_Block_AddressAtIndex(&Memory, F_Sources, 0); Layer_CreateFromSource(File, State, &Memory, SourceIndex, MainComp->Frame_End); - block_layer *Layer = (block_layer *)Memory_Block_AddressAtIndex(&Memory, F_Layers, 0); + } + + block_layer *Layer = (block_layer *)Memory_Block_AddressAtIndex(&Memory, F_Layers, 0); - History_Undo(&Memory); - History_Redo(&Memory); + { + uint16 SourceIndex = Source_Generate(File, State, &Memory, (void *)"../asset/b.jpg"); + block_source *Source = (block_source *)Memory_Block_AddressAtIndex(&Memory, F_Sources, 1); + + Layer_CreateFromSource(File, State, &Memory, 1, MainComp->Frame_End); + Layer_CreateFromSource(File, State, &Memory, SourceIndex, MainComp->Frame_End); } - State->Render.MainCompBuffer = (void *)((uint8 *)Memory.Slot[P_MiscCache].Address + sizeof(project_state)); + { + uint16 SourceIndex = Source_Generate(File, State, &Memory, (void *)"../asset/c.jpg"); + block_source *Source = (block_source *)Memory_Block_AddressAtIndex(&Memory, F_Sources, 2); + + Layer_CreateFromSource(File, State, &Memory, SourceIndex, MainComp->Frame_End); + } + + block_layer *Layer1 = (block_layer *)Memory_Block_AddressAtIndex(&Memory, F_Layers, 0); + Layer1->Vertical_Offset = 0; + + block_layer *Layer2 = (block_layer *)Memory_Block_AddressAtIndex(&Memory, F_Layers, 1); + Layer2->IsPrecomp = true; + Layer2->Vertical_Offset = 1; + Layer2->Col[0] = 1; + + block_layer *Layer3 = (block_layer *)Memory_Block_AddressAtIndex(&Memory, F_Layers, 2); + Layer3->Vertical_Offset = 0; + Layer3->Col[1] = 1; + Layer3->Block_Composition_Index = 1; + + block_layer *Layer4 = (block_layer *)Memory_Block_AddressAtIndex(&Memory, F_Layers, 3); + Layer4->Vertical_Offset = 1; + Layer4->Col[2] = 1; + Layer4->Block_Composition_Index = 1; + + + // History_Undo(&Memory); + // History_Redo(&Memory); SDL_Init(SDL_INIT_VIDEO); -#if 0 -#if THREADED - thread_info ThreadInfo[7]; + Semaphore = SDL_CreateSemaphore(0); + +#if THREADED + int Index[7]; for (int i = 0; i < 7; i++) { - char str[256]; - ThreadInfo[i].Index = i; - ThreadInfo[i].RenderInfo = &RenderInfo; - thread[i] = SDL_CreateThread(TestThread, str, &ThreadInfo[i]); + Index[i] = i; + Thread[i] = SDL_CreateThread(TestThread, "thread", (void *)&Index[i]); } -#endif #endif // Decide GL+GLSL versions + SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_DEBUG_FLAG); #if defined(IMGUI_IMPL_OPENGL_ES2) // GL ES 2.0 + GLSL 100 const char* glsl_version = "#version 100"; @@ -281,141 +506,37 @@ int main(int argc, char *argv[]) { ImGui_ImplSDL2_InitForOpenGL(window, gl_context); ImGui_ImplOpenGL3_Init(glsl_version); - int64 i = 0; - while (i < MainComp->Width*MainComp->Height) { - *((uint32 *)State->Render.MainCompBuffer + i++) = 0xFF2F0000; - } - GLuint textureID; glGenTextures(1, &textureID); glBindTexture(GL_TEXTURE_2D, textureID); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, MainComp->Width, MainComp->Height, 0, GL_RGBA, GL_UNSIGNED_BYTE, State->Render.MainCompBuffer); ImVec4 clear_color = ImVec4(0.45f, 0.55f, 0.60f, 1.00f); - // Semaphore = SDL_CreateSemaphore(0); - // MainRenderThread = SDL_CreateThread(MainRenderer, "Main render thread", &State->Render); - while (State->IsRunning) { - SDL_Event event; - while (SDL_PollEvent(&event)) - { - ImGui_ImplSDL2_ProcessEvent(&event); - if (event.type == SDL_DROPFILE) { - char *DropFile = event.drop.file; - Source_Generate(File, State, &Memory, DropFile); - SDL_free(DropFile); - } - if (event.type == SDL_QUIT) - State->IsRunning = false; - if (event.type == SDL_WINDOWEVENT && event.window.event == SDL_WINDOWEVENT_CLOSE && event.window.windowID == SDL_GetWindowID(window)) - State->IsRunning = false; - } - - // if (UI.WantSetPos) { - // ImGui::GetIO().WantSetMousePos = true; - // io.MousePos = UI.SetPos; - // } - - ImGui_ImplOpenGL3_NewFrame(); - - ImGui_ImplSDL2_NewFrame(); - - ImGui::NewFrame(); - - // if (UI.WantSetPos) { - // ImGui_WrapMouseFinish(&UI, io.MousePos); - // io.MouseDelta = {}; - // UI.WantSetPos = false; - // } + printf("Call UI\n"); + Main_InputTest(File, State, &Memory, &UI, window, textureID); - ImGui::DockSpaceOverViewport(); - -#if 0 - if (!io.WantCaptureKeyboard) - ImGui_ProcessInputs(&File, &State, &CompBuffer, &Memory, &UI, io); - - ImGui_Viewport(File, &State, &UI, &Memory, CompBuffer, io, textureID); - - ImGui_File(&File, &State, &Memory, &UI, io); - - ImGui_EffectsPanel(&File, &State, &Memory, &UI, io); - - ImGui_PropertiesPanel(&File, &State, &UI, &Memory, io); - - ImGui_Timeline(&File, &State, &Memory, &UI, io); - // ImGui_Graph(&File, &State, &Memory, &UI, io); -#endif - - ImGui_Viewport(MainComp, textureID); - ImGui::ShowDemoWindow(); - -#if 0 -#if DEBUG - ImGui_DebugUndoTree(&File, &Memory); - if (Debug.ToggleWindow) { - ImGui::ShowDemoWindow(); - ImGui_DebugMemoryViewer(&File, &Memory); - } -#endif - - if (UI.TemporaryUpdateOverride) { - UI.TemporaryUpdateOverride = 0; - State.UpdateFrame = 1; + if (State->UpdateFrame) { + printf("Call renderer\n"); + Main_Renderer(File, State, &Memory, window, textureID, io); } - if (UI.Initializing) - UI.Initializing--; + Assert(Debug.ScratchState == 0); - // if (File.CurrentFrame != shmp->shared_framenumber) { - // File.CurrentFrame = shmp->shared_framenumber; - // } + printf("Call render UI\n"); + Main_RenderUI(io, clear_color, window); - // Right now IsRendering does nothing. I have it here if we want to - // completely detatch the rendering updater onto its own thread so the - // UI never lags. -#endif - -#if 0 - if (State.UpdateFrame && !IsRendering) { - MainFunction(0, &Memory, &State, &File, &CompBuffer); - State.UpdateFrame = 0; - } - -#if THREADED - uint32 C = SDL_AtomicGet(&CompletedEntries); - if (IsRendering) { - while (C != 16) { - C = SDL_AtomicGet(&CompletedEntries); - CheckQueue(RenderInfo, 8); - } - C = SDL_AtomicGet(&CompletedEntries); - if (C == 16) { - FinishRenderAndUpload(&State, &CompBuffer, textureID); - } - } -#else - if (IsRendering) { - FinishRenderAndUpload(&State, &CompBuffer, textureID); - } -#endif -#endif - - ImGui::Render(); - glViewport(0, 0, (int)io.DisplaySize.x, (int)io.DisplaySize.y); - glClearColor(clear_color.x * clear_color.w, clear_color.y * clear_color.w, clear_color.z * clear_color.w, clear_color.w); - glClear(GL_COLOR_BUFFER_BIT); - ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); - SDL_GL_SwapWindow(window); + // TODO(fox): Fix things that rely on this. + if (State->Initializing) + State->Initializing--; } - // for (int i = 0; i < 7; i++) { - // SDL_DetachThread(thread[i]); - // } - // shm_unlink("/testl"); + for (int i = 0; i < 7; i++) { + SDL_DetachThread(Thread[i]); + } ImGui_ImplOpenGL3_Shutdown(); ImGui_ImplSDL2_Shutdown(); ImGui::DestroyContext(); diff --git a/main.h b/main.h index 8e29917..9ccb19e 100644 --- a/main.h +++ b/main.h @@ -44,6 +44,30 @@ struct block_string { char Char[1024 - sizeof(uint8)]; }; +struct bitmap_cache_status +{ + uint32 Block_End; +}; + +enum cache_entry_type +{ + cache_entry_type_assert, + cache_entry_type_comp, + cache_entry_type_source, + cache_entry_type_layer, +}; + +struct cache_entry +{ + uint8 IsOccupied; + uint8 IsCached; + uint64 CycleTime; + uint32 Block_StartIndex; + enum cache_entry_type Type; + uint32 TypeInfo; + uint32 TypeInfo_Sub; +}; + enum interpolation_type { interpolation_type_linear, @@ -63,9 +87,76 @@ struct block_bezier { bezier_point Point[MAX_KEYFRAMES_PER_BLOCK]; }; +struct layer_bitmap_state { + // Image and video + bool32 ToUpdate = 1; + + // GL state + // gl_effect_layer Test; + // gl_effect_layer TestM; + + // Video state + int32 CurrentFrame = -1; // The last frame number rendered to the bitmap. -1 gurantees a load upon layer creation. + void *AVInfo; // Internal data containing current frame info +}; + struct render_state { - void *MainCompBuffer; + struct layer_bitmap_state Bitmap[MAX_LAYERS]; + cache_entry Entry[2048]; +}; + +enum focused_window +{ + focus_viewport, + focus_properties, + focus_timeline +}; + +struct sorted_comp_info +{ + uint32 LayerCount; + uint32 CurrentSortIndex; +}; + +struct sorted_layer +{ + uint16 Block_Layer_Index; +}; + +struct ui +{ + ImVec2 CompZoom; // In screen pixels, not percentage. + ImVec2 CompPos; + + // Under 1 is zoomed in! + ImVec2 TimelinePercentZoomed; + ImVec2 TimelinePercentOffset; + + bool32 BoxSelect; + ImVec2 DragDelta_Prev; // TODO(fox): Make native ImGui? + + focused_window FocusedWindow; // Convenience for adding window-specific hotkeys. + + v2 TempZoomRatio = V2(1, 1); + + int32 Warp_X = 0; + int32 Warp_Y = 0; + bool32 Warp_WantSetPos = false; + ImVec2 Warp_PositionToSet; + real32 Warp_PositionInitial; + int32 Warp_Direction; +}; + +struct pen_state { + bool32 IsActive; +}; + +enum interact_type +{ + interact_type_none, + interact_type_layer_move, + interact_type_layer_timeadjust }; struct project_state @@ -76,8 +167,9 @@ struct project_state render_state Render; + int32 Frame_Current; // tool Tool = tool_default; - // pen_state Pen = {}; + pen_state Pen = {}; bool32 IsRunning = 1; bool32 IsPlaying; @@ -85,9 +177,11 @@ struct project_state int16 MostRecentlySelectedLayer = -1; // selection_type RecentSelectionType = selection_none; - bool32 IsInteracting; - real32 InteractCache[6]; - // interact_type InteractType; + interact_type Interact_Active; + real32 Interact_Offset[4]; + void *Interact_Address; + + int32 Initializing = 3; int32 MsgTime; // currently in "frames" char *Msg; @@ -101,19 +195,22 @@ struct project_data uint8 Occupied; uint16 Layer_Count; uint16 Source_Count; + uint16 Comp_Count; uint16 PrincipalCompIndex; }; struct block_composition { uint8 Occupied; + + uint16 Name_String_Index; + uint16 Width; uint16 Height; uint16 FPS; uint16 BytesPerPixel; uint32 Frame_Count; - int32 Frame_Current; int32 Frame_Start; int32 Frame_End; }; @@ -128,7 +225,8 @@ struct block_source { uint8 Occupied; - uint16 Block_String_Index; + uint16 Path_String_Index; + uint16 Name_String_Index; // Image and video uint16 Width; uint16 Height; @@ -138,7 +236,7 @@ struct block_source real32 FPS; real32 AvgPTSPerFrame; // set by Libav - source_type SourceType; + source_type Type; }; struct property_header @@ -163,24 +261,12 @@ struct property_channel { bool32 IsToggled; }; -struct layer_bitmap_state { - // Image and video - bool32 ToUpdate = 1; - - // GL state - // gl_effect_layer Test; - // gl_effect_layer TestM; - - // Video state - int32 CurrentFrame = -1; // The last frame number rendered to the bitmap. -1 gurantees a load upon layer creation. - void *AVInfo; // Internal data containing current frame info -}; - struct block_layer { uint8 Occupied; + bool32 IsPrecomp; + uint16 Block_Source_Index; // also used for precomp uint16 Block_String_Index; - uint16 Block_Source_Index; uint16 Block_Composition_Index; uint16 Block_Mask_Index[MAX_EFFECTS]; @@ -208,14 +294,60 @@ struct block_layer { }; bool32 IsSelected; + bool32 IsVisible; + bool32 IsAdjustment; int32 Frame_Offset; int32 Frame_Start; int32 Frame_End; - uint32 LayerColor; + real32 Vertical_Offset; + real32 Vertical_Height = 1; + + real32 Col[3]; }; +struct render_byte_info { + uint32 MaskPixel; + uint32 ByteOffset; + uint32 Bits; + real32 Normalized; +}; + +struct transform_info { + real32 XAxisPX; + real32 XAxisPY; + real32 YAxisPX; + real32 YAxisPY; + + real32 LayerWidth; + real32 LayerHeight; + real32 LayerBytesPerPixel; + real32 LayerPitch; + render_byte_info LayerBits; + + real32 BufferWidth; + real32 BufferHeight; + real32 BufferBytesPerPixel; + real32 BufferPitch; + render_byte_info BufferBits; + + real32 LayerOpacity; + real32 OriginX; + real32 OriginY; + blend_mode BlendMode; + + bool32 IsAdjustment; + + rectangle ClipRect; + void *SourceBuffer; +}; + +struct render_entry { + void *RenderData; + void *OutputBuffer; + rectangle RenderRegion; +}; #if 0 @@ -247,25 +379,6 @@ struct comp_buffer { void *UnpackedBuffer; }; -struct transform_info { - real32 XAxisPX; - real32 XAxisPY; - real32 YAxisPX; - real32 YAxisPY; - real32 LayerWidth; - real32 LayerHeight; - uint32 FullLayerWidth; - uint32 FullLayerHeight; - real32 LayerOpacity; - blend_mode BlendMode; - real32 OriginX; - real32 OriginY; - uint32 BufferPitch; - uint32 LayerPitch; - rectangle ClipRect; - void *SourceBuffer; -}; - struct mask_point { v2 Pos; bool32 HandleBezier; @@ -318,14 +431,6 @@ struct pen_state { bool32 IsActive; }; -enum interact_type -{ - interact_type_keyframe_move, - interact_type_keyframe_rotate, - interact_type_keyframe_scale -} - - struct brush_tool { real32 Size; diff --git a/memory.cpp b/memory.cpp index f502eb2..1dd7ae6 100644 --- a/memory.cpp +++ b/memory.cpp @@ -8,15 +8,6 @@ Memory_InitTable(global_memory *GlobalMemory, memory *Memory, uint64 Size, memor GlobalMemory->CurrentPosition += Size; } -void Memory_Zero(uint8 *Address_Write, uint64 Size) -{ - uint64 i = 0; - while (i < Size) { - *(Address_Write + i) = 0; - i++; - } -} - static uint32 Memory_Block_AllocateNew(memory *Memory, memory_table_list TableName) { @@ -29,7 +20,7 @@ Memory_Block_AllocateNew(memory *Memory, memory_table_list TableName) Address_Playhead += Table->Block_ElementSize; Index++; } - Memory_Zero(Address_Playhead, Table->Block_ElementSize); + Arbitrary_Zero(Address_Playhead, Table->Block_ElementSize); return Index; } @@ -50,6 +41,88 @@ Memory_Block_AllocateAddress(memory *Memory, memory_table_list TableName) return Memory_Block_AddressAtIndex(Memory, TableName, FileIndex); } +static uint32 +Memory_Block_Bitmap_AllocateNew(project_state *State, memory *Memory, cache_entry Entry, uint64 NewSize) +{ + uint32 LastVal = 0; + uint32 LastBlock = 0; + uint32 c = 0; + cache_entry *EntryArray = State->Render.Entry; + while (EntryArray[c].IsOccupied != 0) { + if (EntryArray[c].Block_StartIndex > LastBlock) { + LastBlock = EntryArray[c].Block_StartIndex; + LastVal = c; + } + c++; + } + cache_entry LastEntry = EntryArray[LastVal]; + uint32 LastEntry_BlockCount = 0; + switch (EntryArray[LastVal].Type) { + case cache_entry_type_comp: + { + block_composition Comp = *(block_composition *)Memory_Block_AddressAtIndex(Memory, F_Precomps, LastEntry.TypeInfo); + uint64 Size = Comp.Width * Comp.Height * Comp.BytesPerPixel; + LastEntry_BlockCount = (Size / BitmapBlockSize) + 1; + } break; + case cache_entry_type_source: + { + block_source Source = *(block_source *)Memory_Block_AddressAtIndex(Memory, F_Sources, LastEntry.TypeInfo); + uint64 Size = Source.Width * Source.Height * Source.BytesPerPixel; + LastEntry_BlockCount = (Size / BitmapBlockSize) + 1; + } break; + case cache_entry_type_layer: + { + Assert(0); + } break; + case cache_entry_type_assert: + { + Assert(0); + } break; + default: + { + Assert(0); + } break; + } + + return LastBlock + LastEntry_BlockCount; + + /* + uint32 Blocks_Needed = (NewSize / BitmapBlockSize) + 1; + uint32 Blocks_Max = Memory->Slot[B_CachedBitmaps].Size / BitmapBlockSize; + uint32 Block_Index_Available = 0; + */ + +} + +static cache_entry * +Memory_Cache_Search(project_state *State, memory *Memory, cache_entry *EntryArray, cache_entry_type Type, uint32 TypeInfo, uint32 TypeInfo_Sub) +{ + int c = 0; + while (EntryArray[c].IsOccupied != 0) { + if (EntryArray[c].Type == Type && + EntryArray[c].TypeInfo == TypeInfo && + EntryArray[c].TypeInfo_Sub == TypeInfo_Sub) { + return &EntryArray[c]; + } + c++; + } + if (c != 0) + EntryArray[c].Block_StartIndex = Memory_Block_Bitmap_AllocateNew(State, Memory, EntryArray[c], 0); + EntryArray[c].IsOccupied = true; + EntryArray[c].Type = Type; + EntryArray[c].TypeInfo = TypeInfo; + EntryArray[c].TypeInfo_Sub = TypeInfo_Sub; + return &EntryArray[c]; +} + +static void * +Memory_Block_Bitmap_AddressAtIndex(memory *Memory, uint32 Index) +{ + memory_table *Table = &Memory->Slot[B_CachedBitmaps]; + uint8 *Address = (uint8 *)Table->Address + Index*BitmapBlockSize; + return (void *)Address; +} + static void * Memory_PushScratch(memory *Memory, uint64 Size) { memory_table *Table = &Memory->Slot[B_ScratchSpace]; diff --git a/memory.h b/memory.h index 51b207a..6e704bf 100644 --- a/memory.h +++ b/memory.h @@ -15,6 +15,7 @@ enum memory_table_list { F_Strings, B_ScratchSpace, + // B_CachedBitmapInfo, B_CachedBitmaps, }; diff --git a/my_imgui_widgets.cpp b/my_imgui_widgets.cpp index e8159fe..179dff2 100644 --- a/my_imgui_widgets.cpp +++ b/my_imgui_widgets.cpp @@ -2,1740 +2,2081 @@ #include "my_imgui_internal_widgets.h" #include "imgui_ops.h" +#include "imgui_helper_widgets.cpp" + +static void +ImGui_DebugMemoryViewer(project_state *State) +{ + ImGui::Begin("Memory viewer"); + cache_entry *EntryArray = State->Render.Entry; + char *Type[4] = { "unassigned", "comp", "source", "layer" }; + int c = 0; + while (EntryArray[c].CycleTime != 0) { + ImGui::Text("Type - %s, Start - %i, Info - %i", Type[EntryArray[c].Type], EntryArray[c].Block_StartIndex, EntryArray[c].TypeInfo); + c++; + } + ImGui::End(); +} + +static void +ImGui_File(project_data *File, project_state *State, memory *Memory, ImGuiIO io) +{ + ImGui::Begin("Files"); + ImGui::Text("Application average %.3f ms/frame (%.1f FPS)", 1000.0f / io.Framerate, io.Framerate); + + for (int c = 0; c < File->Comp_Count; c++) { + block_composition *Comp = (block_composition *)Memory_Block_AddressAtIndex(Memory, F_Precomps, c); + block_string *String = (block_string *)Memory_Block_AddressAtIndex(Memory, F_Strings, Comp->Name_String_Index); + ImGui::Text(String->Char); + } + for (int c = 0; c < File->Source_Count; c++) { + block_source *Source = (block_source *)Memory_Block_AddressAtIndex(Memory, F_Sources, c); + block_string *String = (block_string *)Memory_Block_AddressAtIndex(Memory, F_Strings, Source->Path_String_Index); + ImGui::Text(String->Char); + } + +#if DEBUG + for (int i = 0; i < Debug.Temp.WatchedProperties; i++) { + if (Debug.Temp.DebugPropertyType[i] == d_float) { + ImGui::Text("%s: %f", Debug.Temp.String[i], Debug.Temp.Val[i].f); + } else if (Debug.Temp.DebugPropertyType[i] == d_int) { + ImGui::Text("%s: %i", Debug.Temp.String[i], Debug.Temp.Val[i].i); + } else if (Debug.Temp.DebugPropertyType[i] == d_uint) { + ImGui::Text("%s: %u", Debug.Temp.String[i], Debug.Temp.Val[i].u); + } + } +#endif + ImGui::End(); +} + static void -ImGui_Viewport(block_composition *MainComp, GLuint textureID) +ImGui_Viewport(project_data *File, project_state *State, ui *UI, memory *Memory, ImGuiIO io, GLuint textureID) { bool open = true; ImGui::Begin("Viewport", &open, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse); + if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) + UI->FocusedWindow = focus_viewport; + + block_composition *MainComp = (block_composition *)Memory_Block_AddressAtIndex(Memory, F_Precomps, File->PrincipalCompIndex); + block_layer *Layer = (block_layer *)Memory_Block_AddressAtIndex(Memory, F_Layers, 0); + + ImVec2 ViewportMin = ImGui::GetCursorScreenPos(); ImVec2 ViewportScale = ImGui::GetContentRegionAvail(); ImVec2 ViewportMax = ImVec2(ViewportMin.x + ViewportScale.x, ViewportMin.y + ViewportScale.y); + if (State->Initializing) { + UI->CompZoom = ImVec2(MainComp->Width, MainComp->Height); + UI->CompPos = ImVec2(ViewportMin.x + ((ViewportMax.x - ViewportMin.x)/2 - UI->CompZoom.x/2), + ViewportMin.y + ((ViewportMax.y - ViewportMin.y)/2 - UI->CompZoom.y/2)); + } + + ImVec2 CompPosMin = ImVec2(UI->CompPos.x, UI->CompPos.y); + ImVec2 CompPosMax = ImVec2(UI->CompPos.x + UI->CompZoom.x, UI->CompPos.y + UI->CompZoom.y); + ImDrawList* draw_list = ImGui::GetWindowDrawList(); draw_list->AddRectFilled(ViewportMin, ViewportMax, IM_COL32(50, 50, 50, 255)); draw_list->AddRect(ViewportMin, ViewportMax, IM_COL32(255, 255, 255, 255)); - - ImVec2 CompZoom(MainComp->Width, MainComp->Height); - ImVec2 CompPos = ViewportMin + (ViewportMax - ViewportMin)*0.5 - CompZoom*0.5; + draw_list->AddRect(CompPosMin, CompPosMax, IM_COL32(255, 255, 255, 55)); // Actual composition texture draw_list->PushClipRect(ViewportMin, ViewportMax, true); - draw_list->AddImage((void *)(intptr_t)textureID, ImVec2(CompPos.x, CompPos.y), - ImVec2(CompPos.x + CompZoom.x, CompPos.y + CompZoom.y)); + draw_list->AddImage((void *)(intptr_t)textureID, CompPosMin, CompPosMax); draw_list->PopClipRect(); - ImGui::End(); -} + // Interactions for dragging and zooming + ImGui::SetCursorScreenPos(ViewportMin); -#if 0 -// 0 for timeline keyframe, 1 for graph keyframe, 2 for left graph handle, 3 for right graph handle -static void -ImGui_KeyframeDragging(project_data *File, project_state *State, ui *UI, property_channel *Property, int32 b, ImGuiIO io, int16 Type) -{ - keyframe *Keyframe = KeyframeLookup(Property, b); - if (ImGui::IsItemActive()) { + ImGui::InvisibleButton("canvas", ViewportScale, ImGuiButtonFlags_MouseButtonLeft | ImGuiButtonFlags_MouseButtonRight); + bool32 IsHovered = ImGui::IsItemHovered(); + bool32 IsActive = ImGui::IsItemActive(); + bool32 IsActivated = ImGui::IsItemActivated(); + bool32 IsDeactivated = ImGui::IsItemDeactivated(); - if (!Keyframe->IsSelected && ImGui::IsItemActivated()) - { - if (!io.KeyShift) { - temp_keyframe_list Bad = GetSelectedKeyframes(File); - for (int i = 0; i < Bad.Amount; i++) - Bad.SelectedKeyframe[i]->IsSelected = false; - } - Keyframe->IsSelected = true; - State->RecentSelectionType = selection_keyframe; - } - if (ImGui::IsMouseDragging(ImGuiMouseButton_Left, -1)) - { - ImGui::SetMouseCursor(ImGuiMouseCursor_Hand); - if (Type == 0 || Type == 1) - { - UI->DraggingKeyframeThreshold += io.MouseDelta.x; - if (abs(UI->DraggingKeyframeThreshold) >= UI->TimelineZoom) { - int16 Increment = UI->DraggingKeyframeThreshold/UI->TimelineZoom; - // temp_keyframe_list Bad = GetSelectedKeyframes(File); - // for (int b = 0; b < Bad.Amount; b++) { - // keyframe *SelectedKeyframe = Bad.SelectedKeyframe[b]; - if (!(Keyframe->FrameNumber == 0 && Increment == -1)) { - Keyframe->FrameNumber += Increment; - CheckKeyframeSort(Property, Increment, b); - // SortAndCacheKeyframeAtFrame(SelectedKeyframe->FrameNumber, &File.LayerPTR[i]->Property[a], &Cache); - ClampSurroundingKeyframeHandles(Property, b); - } - // } - UI->DraggingKeyframeThreshold += -1*Increment*UI->TimelineZoom; - State->UpdateFrame = true; - State->UpdateKeyframes = true; - // Cache.Frame[File.CurrentFrame].Cached = false; + if (IsHovered && IsActivated && ImGui::IsMouseDown(ImGuiMouseButton_Left)) + { + // Point to zoom in on if Z is held + UI->TempZoomRatio = ImGui_ScreenPointToCompUV(ViewportMin, UI->CompPos, UI->CompZoom, io.MousePos); + + // Layer selection + /* + if (!ImGui::IsKeyDown(ImGuiKey_Z) || !State->Pen.IsActive) { + for (int i = File.NumberOfLayers - 1; i >= 0; i--) { + project_layer *Layer = File.Layer[i]; + if (!io.KeyShift) DeselectAllLayers(&File, State); + v2 LayerUV = CompUVToLayerUV(Layer, &CompBuffer, UI->TempZoomRatio); + if (TestUV(LayerUV) && !Layer->IsSelected) + { + SelectLayer(Layer, State, i); + break; } } - /* - if (Type != 0) - { - if (Type == 1) - { - real32 IncrementsPerPixel = (Property->LocalMaxVal.f - Property->LocalMinVal.f)/Property->GraphLength; - Keyframe->Value.f -= io.MouseDelta.y*IncrementsPerPixel; - CalculatePropertyMinMax(Property); - } - if (Type == 2) - { - Keyframe->TangentLeft.x += io.MouseDelta.x/UI->TimelineZoom; - Keyframe->TangentLeft.y -= io.MouseDelta.y; - ClampKeyframeHandles(Property, b, 0); - } - if (Type == 3) - { - Keyframe->TangentRight.x += io.MouseDelta.x/UI->TimelineZoom; - Keyframe->TangentRight.y -= io.MouseDelta.y; - ClampKeyframeHandles(Property, b, 1); - } - State->UpdateFrame = true; - State->UpdateKeyframes = true; - } - */ } + */ } -} -// NOTE(fox): We have to do a bit of hackery here to tell how many times the -// mouse has been wrapped during a drag, since it doesn't seem like we can rely -// on SDL_WarpMouseGlobal to update on the first frame of a WantSetPos request. + if (IsActive && ImGui::IsMouseDragging(ImGuiMouseButton_Right, -1.0f)) + { + UI->CompPos.x += io.MouseDelta.x; + UI->CompPos.y += io.MouseDelta.y; + } -static void -ImGui_WrapMouse(ui *UI, ImVec2 MousePos, ImVec2 Min, ImVec2 Max, int Direction = 3) -{ - if (Direction & 1) { - if (MousePos.x < Min.x) { - UI->WantSetPos = true; - UI->SetPos = ImVec2(Max.x - 5, MousePos.y); - UI->InitPos = MousePos.x; - UI->WrapDirection = 0; - } - if (MousePos.x > Max.x) { - UI->WantSetPos = true; - UI->SetPos = ImVec2(Min.x + 5, MousePos.y); - UI->InitPos = MousePos.x; - UI->WrapDirection = 1; - } - } - if (Direction & 2) { - if (MousePos.y < Min.y) { - UI->WantSetPos = true; - UI->SetPos = ImVec2(MousePos.x, Max.y - 5); - UI->InitPos = MousePos.y; - UI->WrapDirection = 2; - } - if (MousePos.y > Max.y) { - UI->WantSetPos = true; - UI->SetPos = ImVec2(MousePos.x, Min.y + 5); - UI->InitPos = MousePos.y; - UI->WrapDirection = 3; - } + if (IsActive && ImGui::IsMouseDragging(ImGuiMouseButton_Left, -1.0f) && ImGui::IsKeyDown(ImGuiKey_Z)) + { + real32 Distance = io.MouseDelta.x + io.MouseDelta.y; + UI->CompZoom.x += (Distance)*(real32)MainComp->Width/MainComp->Height; + UI->CompZoom.y += (Distance); + UI->CompPos.x -= ((Distance)*(real32)MainComp->Width/MainComp->Height)*UI->TempZoomRatio.x; + UI->CompPos.y -= Distance*UI->TempZoomRatio.y; } -} -static void -ImGui_WrapMouseFinish(ui *UI, ImVec2 MousePos) -{ - if (UI->WrapDirection == 0) { - if (MousePos.x < UI->InitPos) UI->Wrap_X--; - } else if (UI->WrapDirection == 1) { - if (MousePos.x > UI->InitPos) UI->Wrap_X++; - } else if (UI->WrapDirection == 2) { - if (MousePos.y < UI->InitPos) UI->Wrap_Y--; - } else if (UI->WrapDirection == 3) { - if (MousePos.y > UI->InitPos) UI->Wrap_Y++; - } else { - Assert(0); + ImGui::SetCursorScreenPos(ImVec2(ViewportMin.x, ViewportMin.y + ViewportScale.y - ImGui::GetFontSize()*1.5)); + + ImGui::Text("%.1f", 100.0f * (UI->CompZoom.x / MainComp->Width)); + if (State->MsgTime > 0) { + ImGui::SameLine(); + ImGui::SetCursorPosX((ViewportScale.x / 5)*4); + ImGui::Text(State->Msg); + State->MsgTime--; + } + + ImGui::End(); + + + /* + for (int i = 0; i < AmountOf(Layer->Property); i++) { + ImGui::PushID(i); + property_channel *Property = &Layer->Property[i]; + ImGui::DragScalar(Property->Name, ImGuiDataType_Float, &Property->CurrentValue, + Property->ScrubVal, &Property->MinVal, &Property->MaxVal, "%f"); + if (ImGui::IsItemActive()) + State->UpdateFrame = true; + ImGui::PopID(); } + */ } static void -ImGui_InteractSliderProperty(project_state *State, memory *Memory, property_channel *Property) +ImGui_TimelineHorizontalIncrementDraw(ui *UI, ImDrawList *draw_list, ImVec2 TimelineSizeWithBorder, ImVec2 TimelineAbsolutePos, block_composition MainComp) { - ImGui::DragScalar(Property->Name, ImGuiDataType_Float, &Property->CurrentValue.f, - Property->ScrubVal.f, &Property->MinVal.f, &Property->MaxVal.f, "%f"); - if (ImGui::IsItemActivated()) { - State->InteractCache[0] = Property->CurrentValue.f; - } - if (ImGui::IsItemActive()) { - State->UpdateFrame = true; - } - if (ImGui::IsItemDeactivatedAfterEdit()) { - if (ImGui::IsKeyPressed(ImGuiKey_Escape)) { - Property->CurrentValue.f = State->InteractCache[0]; + uint32 LineColor = IM_COL32(200, 200, 200, 40); + + real32 TimelineZoomSize = TimelineSizeWithBorder.x / UI->TimelinePercentZoomed.x ; + real32 TimelineMoveSize = TimelineSizeWithBorder.x * UI->TimelinePercentOffset.x / UI->TimelinePercentZoomed.x; + + Assert(TimelineZoomSize > 0.0f); + + real32 x = 0; + bool32 RightmostEdge = false; + real32 Increment = (real32)1 / MainComp.Frame_Count; + if (UI->TimelinePercentZoomed.x > 0.90) + Increment = (real32)MainComp.FPS / MainComp.Frame_Count; + else if (UI->TimelinePercentZoomed.x > 0.40) + Increment *= 2; + + while (!RightmostEdge) { + ImVec2 Min = ImVec2(TimelineAbsolutePos.x + TimelineMoveSize + x*TimelineZoomSize, TimelineAbsolutePos.y); + ImVec2 Max = ImVec2(Min.x + 2, TimelineAbsolutePos.y + TimelineSizeWithBorder.y); + if (Min.x < TimelineAbsolutePos.x + TimelineSizeWithBorder.x) { + draw_list->AddLine(Min, Max, LineColor); + char buf2[6]; + uint32 FrameNumber = (uint32)(x * MainComp.Frame_Count) % MainComp.FPS; + if (FrameNumber != 0) + sprintf(buf2, ":%.2i", FrameNumber); + else + sprintf(buf2, "%.2i:00", (uint32)(x * MainComp.Frame_Count) / MainComp.FPS); + draw_list->AddText(ImVec2(Min.x, TimelineAbsolutePos.y), IM_COL32(200, 200, 200, 130), buf2); + x += Increment; + if (x > 1.0f) + RightmostEdge = true; } else { - History_Entry_Commit(Memory, action_entry_default, "Tranforms interact"); - History_Action_Change(Memory, &Property->CurrentValue.f, &State->InteractCache[0], - &Property->CurrentValue.f, action_type_change_r32); - History_Entry_End(Memory); + RightmostEdge = true; } - State->UpdateFrame = true; } } -// Returns a normalized UV position of the composition -static v2 -ImGui_ScreenPointToCompUV(ImVec2 ViewportMin, ImVec2 CompPos, ImVec2 CompZoom, ImVec2 MousePos) -{ - ImVec2 LocalMousePos = MousePos - ViewportMin; - ImVec2 LocalCompPos = CompPos - ViewportMin; - ImVec2 MouseScreenUV = LocalMousePos - LocalCompPos; - ImVec2 Result = MouseScreenUV / CompZoom; - return V2(Result); -} - static void -ImGui_DebugUndoTree(project_data *File, memory *Memory) +ImGui_Timeline_DrawLayer(project_data *File, project_state *State, memory *Memory, ui *UI, ImGuiIO io, ImDrawList *draw_list, block_layer *Layer, block_composition *MainComp, uint16 i, + ImVec2 Increment, ImVec2 TimelineAbsolutePos, ImVec2 TimelineMoveSize, ImVec2 TimelineZoomSize, + ImVec2 TimelineSize, ImVec2 TimelineSizeWithBorder, real32 LayerIncrement, + sorted_comp_info *SortedCompArray, sorted_layer *SortedLayerArray) { - ImGui::SetNextWindowSize(ImVec2(200, 800)); - ImGui::Begin("undotree"); - for (int i = 0; i < Memory->Action.NumberOfEntries; i++) { - action_entry Entry = Memory->Action.Entry[i]; - bool32 CurrentPos = (i < Memory->Action.Index); - ImGui::MenuItem(Entry.Name, NULL, CurrentPos); + int32 Frame_Start = Layer->Frame_Start; + int32 Frame_End = Layer->Frame_End; + real32 Vertical_Offset = Layer->Vertical_Offset; + if (Layer->IsSelected) + Layer_Interact_Evaluate(Memory, State, &Frame_Start, &Frame_End, &Vertical_Offset); + ImVec2 Layer_LocalPos = ImVec2(Frame_Start, Vertical_Offset); + ImVec2 Layer_LocalSize = ImVec2(Frame_End - Frame_Start, Layer->Vertical_Height); + + ImVec2 Layer_LocalPos_Ratio = (Layer_LocalPos * Increment); + ImVec2 Layer_LocalSize_Ratio = Layer_LocalSize * Increment; + ImVec2 Layer_ScreenPos_Min = TimelineAbsolutePos + TimelineMoveSize + (Layer_LocalPos_Ratio * TimelineZoomSize); + ImVec2 Layer_ScreenPos_Max = TimelineAbsolutePos + TimelineMoveSize + ((Layer_LocalPos_Ratio + Layer_LocalSize_Ratio) * TimelineZoomSize); + ImVec2 Layer_ScreenSize = Layer_ScreenPos_Max - Layer_ScreenPos_Min; + + if (UI->BoxSelect) { + bool32 Test = 0; + if (io.MouseClickedPos[0].y < io.MousePos.y) + Test = (Layer_ScreenPos_Min.y >= io.MouseClickedPos[0].y && Layer_ScreenPos_Min.y <= io.MousePos.y); + else + Test = (Layer_ScreenPos_Max.y <= io.MouseClickedPos[0].y && Layer_ScreenPos_Max.y >= io.MousePos.y); + + if (Test) { + if (!Layer->IsSelected) { + Layer->IsSelected = true; + } + } else if (!io.KeyShift) { + Layer->IsSelected = false; + } } - ImGui::End(); -} -static void -ImGui_DebugMemoryViewer(project_data *File, memory *Memory) -{ - ImGui::SetNextWindowSize(ImVec2(800, 200)); - ImGui::Begin("memoryviewer"); - ImVec2 ViewportMin = ImGui::GetCursorScreenPos(); - ImVec2 ViewportScale = ImGui::GetContentRegionAvail(); - ImVec2 ViewportMax = ImVec2(ViewportMin.x + ViewportScale.x, ViewportMin.y + ViewportScale.y); + draw_list->AddRectFilled(Layer_ScreenPos_Min, Layer_ScreenPos_Max, + ImColor(Layer->Col[0], Layer->Col[1], Layer->Col[2], 1.0f)); + draw_list->AddRect(Layer_ScreenPos_Min, Layer_ScreenPos_Max, ImColor(1.0f, 1.0f, 1.0f, 0.5f), 2); - memory_table *Table = &Memory->Slot[B_LoadedBitmaps]; - real32 TotalMB = Table->Size/1024/1024; - real32 LineAmount = 50; - real32 IncrementMB = TotalMB / LineAmount; - real32 ScreenIncrement = ViewportScale.x / LineAmount; + if (Layer->IsSelected) { + draw_list->AddRectFilled(Layer_ScreenPos_Min, Layer_ScreenPos_Max, ImColor(0.25f, 0.25f, 0.25f, 0.5f), 2); + draw_list->AddRect(Layer_ScreenPos_Min, Layer_ScreenPos_Max, ImColor(1.0f, 1.0f, 1.0f, 0.5f), 2); + } - real32 CubeHeight = ImGui::GetFontSize(); - real32 TotalHeight = CubeHeight*4; + ImVec2 ResizeSize = ImVec2(Increment.x * 0.45 * TimelineZoomSize.x, Layer_ScreenSize.y); + ImVec2 ResizePos[2] = { Layer_ScreenPos_Min, ImVec2(Layer_ScreenPos_Max.x - ResizeSize.x, Layer_ScreenPos_Min.y) }; + for (int b = 0; b < 2; b++) { + ImGui::PushID(b); + ImGui::SetCursorScreenPos(ResizePos[b]); + ImGui::Button("##layer_resize", ResizeSize); - ImDrawList* draw_list = ImGui::GetWindowDrawList(); - draw_list->AddRectFilled(ViewportMin, ViewportMax, IM_COL32(50, 50, 50, 255)); + if (ImGui::IsItemHovered()) { + ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeEW); + } + if (ImGui::IsItemActivated()) { + if (!Layer->IsSelected) { + State->MostRecentlySelectedLayer = i; + Layer->IsSelected = true; + } + } + if (ImGui::IsItemActive()) { + if (ImGui::IsMouseDragging(ImGuiMouseButton_Left, -1)) { + State->Interact_Active = interact_type_layer_timeadjust; + ImVec2 DragDelta = ImGui::GetMouseDragDelta(); + DragDelta = DragDelta + (ImVec2(UI->Warp_X, UI->Warp_Y) * TimelineSize); + + State->Interact_Offset[0] = (DragDelta.x / TimelineSizeWithBorder.x * UI->TimelinePercentZoomed.x) * MainComp->Frame_Count; + State->Interact_Offset[1] = b; + DebugWatchVar("Offset1", &State->Interact_Offset[0], d_float); + } + } + if (ImGui::IsItemDeactivated()) { + if (State->Interact_Active == interact_type_layer_timeadjust) { + for (int a = 0; a < File->Layer_Count; a++) { + block_layer *Layer = (block_layer *)Memory_Block_AddressAtIndex(Memory, F_Layers, a); + if (Layer->IsSelected) { + Layer_Interact_Evaluate(Memory, State, &Layer->Frame_Start, &Layer->Frame_End, &Layer->Vertical_Offset); + } + } + State->Interact_Active = interact_type_none; + State->Interact_Offset[0] = 0; + State->Interact_Offset[1] = 0; + } + } - for (float x = 0; x < LineAmount; x++) { - uint32 LineColor = IM_COL32(200, 200, 200, 40); - ImVec2 Min = ImVec2(ViewportMin.x + ScreenIncrement * x, ViewportMin.y); - ImVec2 Max = ImVec2(Min.x + 2, ViewportMax.y); - draw_list->AddLine(Min, Max, LineColor); + ImGui::PopID(); } - // CurrentPosition line - { - uint32 LineColor = IM_COL32(000, 100, 200, 200); - real32 CurPosMB = (real32)Table->CurrentPosition/1024/1024; - ImVec2 Min = ImVec2(ViewportMin.x + (CurPosMB/IncrementMB * ScreenIncrement), ViewportMin.y); - ImVec2 Max = ImVec2(Min.x + 2, ViewportMax.y); - draw_list->AddLine(Min, Max, LineColor); + + ImGui::SetCursorScreenPos(Layer_ScreenPos_Min); + ImGui::InvisibleButton("##layer_mid", Layer_ScreenSize, ImGuiMouseButton_Left); + + if (ImGui::IsItemActivated()) { + if (!Layer->IsSelected) { + if (!io.KeyShift) Layer_DeselectAll(Memory, File->Layer_Count); + State->MostRecentlySelectedLayer = i; + Layer->IsSelected = true; + } } - for (uint32 i = 0; i < Table->NumberOfPointers; i++) { - if (Memory->Bitmap[i].SourceOwner) { - void *DataStart = Memory->Bitmap[i].Data; - void *NextDataStart; - int pp = 1; - for (;;) { - if (Memory->Bitmap[i+pp].SourceOwner) { - NextDataStart = Memory->Bitmap[i+pp].Data; - break; + if (ImGui::IsItemActive()) { + if (ImGui::IsMouseDragging(ImGuiMouseButton_Left, -1)) { + State->Interact_Active = interact_type_layer_move; + ImVec2 DragDelta = ImGui::GetMouseDragDelta(); + DragDelta = DragDelta + (ImVec2(UI->Warp_X, UI->Warp_Y) * TimelineSize); + + State->Interact_Offset[0] = (DragDelta.x / TimelineSizeWithBorder.x * UI->TimelinePercentZoomed.x) * MainComp->Frame_Count; + State->Interact_Offset[1] = (DragDelta.y / TimelineSizeWithBorder.y * UI->TimelinePercentZoomed.y) * LayerIncrement; + + /* + if (UI->DragDelta_Prev.x != 0) { + ImVec2 Offset_Old = (UI->DragDelta_Prev / TimelineSizeWithBorder * UI->TimelinePercentZoomed) * ImVec2(MainComp->Frame_Count, LayerIncrement); + if ((int32)State->Interact_Offset[1] != (int32)Offset_Old.y) + State->UpdateFrame = true; } - pp++; - } - uint64 BytesBetween = (uint8 *)NextDataStart - (uint8 *)DataStart; - source *Source = Memory->Bitmap[i].SourceOwner; - real32 Pos = Memory_NormalizedPosition(&File->Source[0], File->NumberOfSources, sizeof(source), Source); - ImVec4 col = ImColor::HSV(Pos, 0.3, 0.6, 1.0f); - uint64 BitmapSize = Bitmap_CalcTotalBytes(Source->Info.Width, Source->Info.Height, Source->Info.BytesPerPixel); - if (BitmapSize > BytesBetween && i != Table->NumberOfPointers - 1) { - col = ImColor::HSV(Pos, 1.0, 1.0, 1.0f); + UI->DragDelta_Prev = DragDelta; + */ } + } - uint64 DataStart2 = (uint8 *)DataStart - (uint8 *)Table->Address; - real32 StartReal = ((real32)DataStart2/1024/1024); - ImVec2 Min = ImVec2(ViewportMin.x + (StartReal/IncrementMB * ScreenIncrement), ViewportMin.y + (ViewportScale.y / 2) - (TotalHeight / 2) + TotalHeight*Pos); - real32 SizeReal = ((real32)BitmapSize/1024/1024); - ImVec2 Size = ImVec2(SizeReal/IncrementMB * ScreenIncrement, CubeHeight); - ImGui::SetCursorScreenPos(Min); - ImGui::PushID(i); - ImGui::PushStyleColor(ImGuiCol_Button, col); - char buf2[256]; - sprintf(buf2, "%i##mempos", Memory->Bitmap[i].Frame); - ImGui::Button(buf2, Size); - ImGui::PopStyleColor(); - if (ImGui::IsItemHovered()) { - char buf[1024]; - sprintf(buf, "Source owner: %s\nSize: %.02f MB\n Frame number: %i\n Index: %i", - Memory->Bitmap[i].SourceOwner->Path, SizeReal, - Memory->Bitmap[i].Frame, i); - ImGui::BeginTooltip(); - ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f); - ImGui::TextUnformatted(buf); - ImGui::PopTextWrapPos(); - ImGui::EndTooltip(); - } - ImGui::PopID(); - ImVec2 Max = ImVec2(ViewportMax.x, Min.y + CubeHeight); - // draw_list->AddRectFilled(Min, Max, IM_COL32(200, 200, 200, 255)); + if (ImGui::IsItemDeactivated()) { + if (State->Interact_Active == interact_type_layer_move) { + for (int a = 0; a < File->Layer_Count; a++) { + block_layer *Layer = (block_layer *)Memory_Block_AddressAtIndex(Memory, F_Layers, a); + if (Layer->IsSelected) { + Layer_Interact_Evaluate(Memory, State, &Layer->Frame_Start, &Layer->Frame_End, &Layer->Vertical_Offset); + } + } + State->Interact_Active = interact_type_none; + State->Interact_Offset[0] = 0; + State->Interact_Offset[1] = 0; } } - ImGui::SetCursorScreenPos(ImVec2(ViewportMin.x, ViewportMax.y - CubeHeight*2)); - char buf[1024]; - sprintf(buf, "Current index: %i\nAmount of pointers: %i", - Table->PointerIndex, Table->NumberOfPointers); - ImGui::Text(buf); - ImGui::End(); -} + if (Layer->IsPrecomp) { + ImVec2 MinClipPos = ImVec2(Layer_ScreenPos_Min.x, Layer_ScreenPos_Max.y); + ImVec2 MaxClipPos = ImVec2(Layer_ScreenPos_Max.x, MinClipPos.y + TimelineZoomSize.y); + draw_list->AddRectFilled(MinClipPos, MaxClipPos, ImColor(0.2f, 0.2f, 0.2f, 1.0f)); -static bool32 FU; + sorted_comp_info SortedCompInfo = SortedCompArray[Layer->Block_Source_Index]; + sorted_layer *SortedLayerInfo = Layer_GetSortedArray(SortedLayerArray, SortedCompArray, Layer->Block_Source_Index); + + for (int a = 0; a < SortedCompInfo.LayerCount; a++) + { + ImGui::PushID(a); + sorted_layer SortEntry = SortedLayerInfo[a]; + uint32 Index_Physical = SortEntry.Block_Layer_Index; + block_layer *Layer = (block_layer *)Memory_Block_AddressAtIndex(Memory, F_Layers, Index_Physical); + + ImVec2 Layer_LocalPos_Screen = Layer_LocalPos_Ratio * TimelineZoomSize; + ImVec2 NestedTimelineAbsolutePos = TimelineAbsolutePos + Layer_LocalPos_Screen + ImVec2(0, Layer_ScreenSize.y); + ImGui_Timeline_DrawLayer(File, State, Memory, UI, io, draw_list, Layer, MainComp, a, + Increment, NestedTimelineAbsolutePos, TimelineMoveSize, TimelineZoomSize, + TimelineSize, TimelineSizeWithBorder, LayerIncrement, + SortedCompArray, SortedLayerArray); + ImGui::PopID(); + } + } +} static void -ImGui_TimelineIncrementDraw(project_data *File, ui *UI, ImDrawList *draw_list, - ImVec2 TimelineSizeWithBorder, ImVec2 TimelineAbsolutePos, bool32 IsText) +ImGui_Timeline(project_data *File, project_state *State, memory *Memory, ui *UI, ImGuiIO io) { - uint32 LineColor = IM_COL32(200, 200, 200, 40); + ImVec2 FramePadding = ImGui::GetStyle().FramePadding; + ImVec2 ItemSpacing = ImGui::GetStyle().ItemSpacing; + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0)); + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0)); // makes setting up the layout easier + ImGui::Begin("Timeline", NULL); - real32 TimelineZoomSize = TimelineSizeWithBorder.x / UI->TimelinePercentZoomed; - real32 TimelineMoveSize = TimelineSizeWithBorder.x * UI->TimelinePercentOffset / UI->TimelinePercentZoomed; + if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) + UI->FocusedWindow = focus_timeline; - Assert(TimelineZoomSize > 0.0f); + real32 FontHeight = ImGui::GetFontSize(); - real32 x = 0; - bool32 RightmostEdge = false; - real32 Increment = (real32)1 / File->NumberOfFrames; - if (UI->TimelinePercentZoomed > 0.90) - Increment = (real32)File->FPS / File->NumberOfFrames; - else if (UI->TimelinePercentZoomed > 0.40) - Increment *= 2; + ImVec2 WindowSize = ImGui::GetWindowSize(); - while (!RightmostEdge) { - ImVec2 Min = ImVec2(TimelineAbsolutePos.x + TimelineMoveSize + x*TimelineZoomSize, TimelineAbsolutePos.y); - ImVec2 Max = ImVec2(Min.x + 2, TimelineAbsolutePos.y + TimelineSizeWithBorder.y); - if (Min.x < TimelineAbsolutePos.x + TimelineSizeWithBorder.x) { - draw_list->AddLine(Min, Max, LineColor); - char buf2[6]; - uint32 FrameNumber = (uint32)(x*File->NumberOfFrames) % File->FPS; - if (FrameNumber != 0) - sprintf(buf2, ":%.2i", FrameNumber); - else - sprintf(buf2, "%.2i:00", (uint32)(x*File->NumberOfFrames) / File->FPS); - draw_list->AddText(ImVec2(Min.x, TimelineAbsolutePos.y), IM_COL32(200, 200, 200, 130), buf2); - x += Increment; - if (x > 1.0f) - RightmostEdge = true; - } else { - RightmostEdge = true; - } + if (WindowSize.x < 50 || WindowSize.y < 50) { + ImGui::PopStyleVar(2); + ImGui::End(); + return; } -} -static void -ImGui_TimelineIncrementDraw2(project_data *File, ImDrawList *draw_list, real32 MaxVal_Y, real32 MinVal_Y, - real32 Y_TimelinePercentZoomed, real32 Y_TimelinePercentOffset, - ImVec2 TimelineSizeWithBorder, ImVec2 TimelineAbsolutePos, bool32 IsText) -{ - uint32 LineColor = IM_COL32(200, 200, 200, 40); + block_composition *MainComp = (block_composition *)Memory_Block_AddressAtIndex(Memory, F_Precomps, File->PrincipalCompIndex); - real32 TimelineZoomSize = TimelineSizeWithBorder.y / Y_TimelinePercentZoomed; - real32 TimelineMoveSize = TimelineSizeWithBorder.y * Y_TimelinePercentOffset / Y_TimelinePercentZoomed; + ImVec2 WindowMinAbs = ImGui::GetWindowPos(); + ImVec2 WindowMaxAbs = WindowMinAbs + WindowSize; - Assert(TimelineZoomSize > 0.0f); + ImVec2 ButtonSize = ImVec2(FontHeight*2, FontHeight*2); - real32 Fraction = (MinVal_Y > 0) ? MinVal_Y - (int32)MinVal_Y : (int32)MinVal_Y - MinVal_Y; - real32 x = (int32)MinVal_Y; - bool32 RightmostEdge = false; + ImVec2 TimelineBorderPadding = ImVec2(FontHeight, FontHeight); - while (!RightmostEdge) { - real32 Ratio_Y = (x - MinVal_Y) / (MaxVal_Y - MinVal_Y); - ImVec2 Min = ImVec2(TimelineAbsolutePos.x, TimelineAbsolutePos.y + TimelineMoveSize + (1.0f - Ratio_Y)*TimelineZoomSize); - ImVec2 Max = ImVec2(TimelineAbsolutePos.x + TimelineSizeWithBorder.x, Min.y + 2); - if (Min.y > TimelineAbsolutePos.y) { - draw_list->AddLine(Min, Max, LineColor); - char buf2[6]; - sprintf(buf2, "%.2f", x); - draw_list->AddText(ImVec2(TimelineAbsolutePos.x, Min.y), IM_COL32(200, 200, 200, 130), buf2); - x += 1.0f; - } else { - RightmostEdge = true; + ImVec2 TimelineSize = ImVec2(WindowSize.x, WindowSize.y); + ImVec2 TimelineSizeWithBorder = TimelineSize - TimelineBorderPadding*2; + ImVec2 TimelineAbsolutePos = WindowMinAbs + TimelineBorderPadding; + + ImVec2 KeyframeSize = ImVec2(FontHeight, FontHeight); + + int32 FrameCount = MainComp->Frame_Count; + + ImDrawList* draw_list = ImGui::GetWindowDrawList(); + draw_list->AddRectFilled(WindowMinAbs, WindowMaxAbs, + IM_COL32(255, 255, 255, 50)); + + + ImGui::BeginChild("Timeline", TimelineSize, true, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoScrollbar); + + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0.0f, ItemSpacing.y)); + + ImGui::PushClipRect(TimelineAbsolutePos, TimelineAbsolutePos + TimelineSizeWithBorder, true); + draw_list->PushClipRect(TimelineAbsolutePos, TimelineAbsolutePos + TimelineSizeWithBorder, true); + + real32 LayerIncrement = 40; + + ImVec2 Val_Min(0, 0); + ImVec2 Val_Max(40, LayerIncrement); + + if (UI->TimelinePercentZoomed.x == 0) { + UI->TimelinePercentZoomed = ImVec2(1, 1); + } + + ImGui_TimelineHorizontalIncrementDraw(UI, draw_list, TimelineSizeWithBorder, TimelineAbsolutePos, *MainComp); + + ImVec2 TimelineZoomSize = TimelineSizeWithBorder / UI->TimelinePercentZoomed; + ImVec2 TimelineMoveSize = TimelineSizeWithBorder * UI->TimelinePercentOffset / UI->TimelinePercentZoomed; + + ImVec2 Increment = ImVec2((real32)1 / MainComp->Frame_Count, (real32)1 / LayerIncrement); + + uint64 SortSize = (sizeof(sorted_comp_info) * File->Comp_Count) + (sizeof(sorted_layer) * File->Layer_Count); + void *SortedArray = Memory_PushScratch(Memory, SortSize); + Arbitrary_Zero((uint8 *)SortedArray, SortSize); + sorted_comp_info *SortedCompArray = (sorted_comp_info *)SortedArray; + sorted_layer *SortedLayerArray = (sorted_layer *)((uint8 *)SortedArray + (sizeof(sorted_comp_info) * File->Comp_Count)); + Layer_SortAll(Memory, SortedLayerArray, SortedCompArray, File->Layer_Count, File->Comp_Count); + + sorted_comp_info SortedCompInfo = SortedCompArray[File->PrincipalCompIndex]; + sorted_layer *SortedLayerInfo = Layer_GetSortedArray(SortedLayerArray, SortedCompArray, File->PrincipalCompIndex); + + for (int i = 0; i < SortedCompInfo.LayerCount; i++) + { + ImGui::PushID(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); + + ImGui_Timeline_DrawLayer(File, State, Memory, UI, io, draw_list, Layer, MainComp, i, + Increment, TimelineAbsolutePos, TimelineMoveSize, TimelineZoomSize, + TimelineSize, TimelineSizeWithBorder, LayerIncrement, + SortedCompArray, SortedLayerArray); + + ImGui::PopID(); + } + + Memory_PopScratch(Memory, SortSize); + + ImVec2 MouseDelta = io.MouseDelta / TimelineSize; + + real32 BarHandleSize = FontHeight; + real32 BarThickness = 50; + real32 BarMinZoom = 0.01; + + real32 BarH_Pos = -TimelineSizeWithBorder.x * UI->TimelinePercentOffset.x; + real32 BarH_Size = TimelineSizeWithBorder.x / (1 / UI->TimelinePercentZoomed.x); + + // I use "UI" to denote the size/position after clipping the bar so that it + // doesn't go out of bounds and the handles are always selectable at the edges. + + real32 BarH_Offset = Max(BarH_Pos, 0); + + real32 BarH_SizeUI = (BarH_Size + BarH_Pos > TimelineSizeWithBorder.x) ? + TimelineSizeWithBorder.x - BarH_Pos : + BarH_Size + (BarH_Pos - BarH_Offset); + + if (BarH_Offset == 0 && BarH_SizeUI > TimelineSizeWithBorder.x) + BarH_SizeUI = TimelineSizeWithBorder.x; + + BarH_SizeUI = BarH_SizeUI - BarHandleSize*2; + + BarH_SizeUI = Max(BarH_SizeUI, FontHeight*4); + + BarH_Offset = Min(BarH_Offset, TimelineSize.x - BarH_SizeUI - BarHandleSize*4); + ImVec2 BarH_PosUI = TimelineAbsolutePos + ImVec2(BarH_Offset, TimelineSize.y - BarThickness); + bool32 BarHeld = false; + + ImGui::SetCursorScreenPos(BarH_PosUI); + ImGui::Button("##scrollbarleft", ImVec2(BarHandleSize, BarThickness)); + + if ((ImGui::IsItemActive() && ImGui::IsMouseDragging(ImGuiMouseButton_Left, -1))) + { + if ((UI->TimelinePercentZoomed.x - MouseDelta.x) > BarMinZoom) { + UI->TimelinePercentZoomed.x -= MouseDelta.x; + UI->TimelinePercentOffset.x -= MouseDelta.x; } + BarHeld = true; } - x = (int32)MinVal_Y; - x -= 1.0f; - bool32 LeftmostEdge = false; + ImGui::SetCursorScreenPos(BarH_PosUI + ImVec2(BarHandleSize, 0)); + ImGui::Button("##scrollbarhori", ImVec2(BarH_SizeUI, BarThickness)); - while (!LeftmostEdge) { - real32 Ratio_Y = (x - MinVal_Y) / (MaxVal_Y - MinVal_Y); - ImVec2 Min = ImVec2(TimelineAbsolutePos.x, TimelineAbsolutePos.y + TimelineMoveSize + (1.0f - Ratio_Y)*TimelineZoomSize); - ImVec2 Max = ImVec2(TimelineAbsolutePos.x + TimelineSizeWithBorder.x, Min.y + 2); - if (Min.y < TimelineAbsolutePos.y + TimelineSizeWithBorder.y) { - draw_list->AddLine(Min, Max, LineColor); - char buf2[6]; - sprintf(buf2, "%.2f", x); - draw_list->AddText(ImVec2(TimelineAbsolutePos.x, Min.y), IM_COL32(200, 200, 200, 130), buf2); - x -= 1.0f; - } else { - LeftmostEdge = true; + if (ImGui::IsItemActive() && ImGui::IsMouseDragging(ImGuiMouseButton_Left, -1)) + { + UI->TimelinePercentOffset.x -= MouseDelta.x; + BarHeld = true; + } + + ImGui::SetCursorScreenPos(BarH_PosUI + ImVec2(BarHandleSize, 0) + ImVec2(BarH_SizeUI, 0)); + ImGui::Button("##scrollbarright", ImVec2(BarHandleSize, BarThickness)); + + if ((ImGui::IsItemActive() && ImGui::IsMouseDragging(ImGuiMouseButton_Left, -1))) + { + if ((UI->TimelinePercentZoomed.x + MouseDelta.x) > BarMinZoom) { + UI->TimelinePercentZoomed.x += MouseDelta.x; } + BarHeld = true; } -} -static void -ImGui_PropertiesPanel(project_data *File, project_state *State, ui *UI, memory *Memory, ImGuiIO io) -{ - if (State->MostRecentlySelectedLayer > -1) { - project_layer *Layer = File->Layer[State->MostRecentlySelectedLayer]; - char buf[256]; - sprintf(buf, "Properties: %s###Properties", Layer->Name); - ImGui::Begin(buf); - if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) - UI->FocusedWindow = focus_properties; - ImGui::Text("Transform"); - for (int h = 0; h < AmountOf(Layer->Property); h++) { - property_channel *Property = &Layer->Property[h]; - ImGui::PushID(Property); - if (ImGui::Button("K")) - Keyframe_Insert(Property, Memory, File->CurrentFrame, Property->CurrentValue.f); - ImGui::SameLine(); - ImGui_InteractSliderProperty(State, Memory, Property); - ImGui::PopID(); - } - for (int h = 0; h < Layer->NumberOfEffects; h++) { - effect *Effect = Layer->Effect[h]; - ImGui::PushID(Effect->ImGuiID); - if (FU && h == 0) { - int a = 0; - } - // this is stupid - if (Effect->IsActive) - ImGui::PushStyleColor(ImGuiCol_Button, ImGui::GetColorU32(ImGuiCol_ButtonHovered)); - else - ImGui::PushStyleColor(ImGuiCol_Button, ImGui::GetColorU32(ImGuiCol_Button)); - if (ImGui::Button("V")) { - History_Entry_Commit(Memory, action_entry_default, "Toggle effect"); - History_Action_Change_SwapBool(Memory, &Effect->IsActive); - History_Entry_End(Memory); - State->UpdateFrame = true; - } - ImGui::SameLine(); - ImGui::PopStyleColor(); - ImGui::Button("R"); ImGui::SameLine(); - ImGui::Selectable(Effect->Name, Effect->IsSelected); - // NOTE(fox): The logic for effect dragging has to be after we've - // done the UI for the rest of the effect! - real32 Effect_Top = ImGui::GetCursorScreenPos().y; - bool32 IsHovered = ImGui::IsItemHovered(); - bool32 IsActive = ImGui::IsItemActive(); - bool32 IsActivated = ImGui::IsItemActivated(); - bool32 IsDeactivated = ImGui::IsItemDeactivated(); + if (BarHeld) { + ImGui_WarpMouse(UI, io.MousePos, TimelineAbsolutePos, TimelineAbsolutePos + TimelineSizeWithBorder, 1); + } - if (Effect->DisplayType == standard) { - for (int i = 0; i < Effect->NumberOfProperties; i++) { - property_channel *Property = &Effect->Property[i]; - ImGui::PushID(Property); - if (Property->VarType == type_real) { - ImGui_InteractSliderProperty(State, Memory, Property); - } - if (Property->VarType == type_color) - if (ImGui::ColorEdit4("color 1", &Property->CurrentValue.f, ImGuiColorEditFlags_Float)) - State->UpdateFrame = true; - if (Property->VarType == type_blendmode) - { - uint32 *item_current_idx = (uint32 *)&Property->CurrentValue.blendmode; // Here we store our selection data as an index. - if (ImGui::BeginListBox("Blend mode")) - { - for (int n = 0; n < IM_ARRAYSIZE(BlendmodeNames); n++) - { - const bool is_selected = (*item_current_idx == n); - if (ImGui::Selectable(BlendmodeNames[n], is_selected)) { - *item_current_idx = n; - State->UpdateFrame = true; - } + Assert(UI->TimelinePercentZoomed.x > BarMinZoom); - // Set the initial focus when opening the combo (scrolling + keyboard navigation focus) - if (is_selected) - ImGui::SetItemDefaultFocus(); - } - ImGui::EndListBox(); - } - } - ImGui::PopID(); - } - } else if (Effect->DisplayType == levels) { - source *Source = Layer->Source; - layer_bitmap_info *BitmapInfo = &Layer->BitmapInfo; + real32 BarV_MaxSize = TimelineSizeWithBorder.y - BarThickness/2; + real32 BarV_Pos = -BarV_MaxSize * UI->TimelinePercentOffset.y; + real32 BarV_Size = BarV_MaxSize / (1 / UI->TimelinePercentZoomed.y); + BarV_Size = Max(BarV_Size, FontHeight*4); - if (!BitmapInfo->HistogramVals) { - uint64 Size = Bitmap_CalcUnpackedBytes(Source->Info.Width, Source->Info.Height, Source->Info.BytesPerPixel); - BitmapInfo->HistogramVals = AllocateMemory(Memory, (sizeof(uint32) * 5 * 256), P_MiscCache); - Bitmap_CalcHistogram(BitmapInfo->HistogramVals, BitmapInfo->BitmapBuffer, Source->Info.BytesPerPixel, Size); - } + real32 BarV_Offset = Max(BarV_Pos, 0); - char *LevelsButtons[5] = { "All", "Red", "Green", "Blue", "Alpha" }; - for (int i = 0; i < 5; i++) { - if (ImGui::Button(LevelsButtons[i])) { - BitmapInfo->LevelsSelector = i; - } - if (i != 4) { ImGui::SameLine(); } - } + real32 BarV_SizeUI = (BarV_Size + BarV_Pos > BarV_MaxSize) ? + BarV_MaxSize - BarV_Pos : + BarV_Size + (BarV_Pos - BarV_Offset); - real32 *Min, *Mid, *Max; - if (BitmapInfo->LevelsSelector == 0) { - Min = &Effect->Property[0].CurrentValue.f; - Mid = &Effect->Property[1].CurrentValue.f; - Max = &Effect->Property[2].CurrentValue.f; - } else { - Min = &Effect->Property[3].CurrentValue.col.E[BitmapInfo->LevelsSelector - 1]; - Mid = &Effect->Property[4].CurrentValue.col.E[BitmapInfo->LevelsSelector - 1]; - Max = &Effect->Property[5].CurrentValue.col.E[BitmapInfo->LevelsSelector - 1]; - } + if (BarV_Offset == 0 && BarV_SizeUI > BarV_MaxSize) + BarV_SizeUI = BarV_MaxSize; - if (BitmapInfo->LevelsSelector == 0) { - ImGui::PushStyleColor(ImGuiCol_PlotHistogram, ImVec4(0.6f, 0.6f, 0.6f, 1.0f)); - ImGui::PushStyleColor(ImGuiCol_PlotHistogramHovered, ImVec4(0.6f, 0.6f, 0.6f, 1.0f)); - } else if (BitmapInfo->LevelsSelector == 1) { - ImGui::PushStyleColor(ImGuiCol_PlotHistogram, ImVec4(0.9f, 0.6f, 0.6f, 1.0f)); - ImGui::PushStyleColor(ImGuiCol_PlotHistogramHovered, ImVec4(0.9f, 0.6f, 0.6f, 1.0f)); - } else if (BitmapInfo->LevelsSelector == 2) { - ImGui::PushStyleColor(ImGuiCol_PlotHistogram, ImVec4(0.6f, 0.9f, 0.6f, 1.0f)); - ImGui::PushStyleColor(ImGuiCol_PlotHistogramHovered, ImVec4(0.6f, 0.9f, 0.6f, 1.0f)); - } else if (BitmapInfo->LevelsSelector == 3) { - ImGui::PushStyleColor(ImGuiCol_PlotHistogram, ImVec4(0.6f, 0.6f, 0.9f, 1.0f)); - ImGui::PushStyleColor(ImGuiCol_PlotHistogramHovered, ImVec4(0.6f, 0.6f, 0.9f, 1.0f)); - } else if (BitmapInfo->LevelsSelector == 4) { - ImGui::PushStyleColor(ImGuiCol_PlotHistogram, ImVec4(0.9f, 0.9f, 0.9f, 1.0f)); - ImGui::PushStyleColor(ImGuiCol_PlotHistogramHovered, ImVec4(0.9f, 0.9f, 0.9f, 1.0f)); - } + BarV_SizeUI = BarV_SizeUI - BarHandleSize*2; - float *values = (float *)BitmapInfo->HistogramVals + 256*BitmapInfo->LevelsSelector; - int values_count = 256; - int values_offset = 0; - float scale_min = FLT_MIN; - float scale_max = FLT_MAX; - ImVec2 graph_size = ImVec2(0, 250); - int stride = sizeof(float); + BarV_SizeUI = Max(BarV_SizeUI, FontHeight*4); - // The default histogram is good enough for what we need. - ImGui::PlotHistogram("##histo", values, values_count, values_offset, NULL, scale_min, scale_max, graph_size, stride); - // TODO(fox): Figure out the proper way to represent these IDs. - if (ImGui::SliderLevels("##one", "##two", "three", Mid, Min, Max)) - State->UpdateFrame = true; + BarV_Offset = Min(BarV_Offset, BarV_MaxSize - BarV_SizeUI - BarHandleSize*4); + ImVec2 BarV_PosUI = TimelineAbsolutePos + ImVec2(TimelineSize.x - BarThickness, BarV_Offset); + BarHeld = false; - ImGui::PopStyleColor(); - ImGui::PopStyleColor(); + ImGui::SetCursorScreenPos(BarV_PosUI); + ImGui::Button("##h-scrollbarleft", ImVec2(BarThickness, BarHandleSize)); - if (State->UpdateFrame) { - uint64 Size = Bitmap_CalcUnpackedBytes(Source->Info.Width, Source->Info.Height, Source->Info.BytesPerPixel); - Bitmap_CalcHistogram(BitmapInfo->HistogramVals, BitmapInfo->BitmapBuffer, Source->Info.BytesPerPixel, Size); - } + if ((ImGui::IsItemActive() && ImGui::IsMouseDragging(ImGuiMouseButton_Left, -1))) + { + UI->TimelinePercentZoomed.y -= MouseDelta.y; + UI->TimelinePercentOffset.y -= MouseDelta.y; + BarHeld = true; + } - ImGui::Button("K"); - } - ImGui::PopID(); + ImGui::SetCursorScreenPos(BarV_PosUI + ImVec2(0, BarHandleSize)); + ImGui::Button("##h-scrollbar", ImVec2(BarThickness, BarV_SizeUI)); - if (IsActive) - { - if (!Effect->IsSelected && IsActivated) { - Effect->IsSelected ^= 1; - } - if (ImGui::IsMouseDragging(ImGuiMouseButton_Left, -1)) - { - real32 Effect_Bottom = ImGui::GetCursorScreenPos().y; - real32 Effect_Length = Effect_Bottom - Effect_Top; - ImGui::SetMouseCursor(ImGuiMouseCursor_Hand); - UI->DraggingEffectThreshold += io.MouseDelta.y; - if (abs(UI->DraggingEffectThreshold) >= Effect_Length) { - int16 Increment = UI->DraggingEffectThreshold/Effect_Length; - effect *Effect = Layer->Effect[1]; - Layer->Effect[1] = Layer->Effect[0]; - Layer->Effect[0] = Effect; - // History_Entry_Commit(Memory, action_entry_default, "Change effect order"); - // History_Entry_End(Memory); - FU = true; - UI->DraggingEffectThreshold += -1*Increment*Effect_Length; - State->UpdateFrame = true; - State->UpdateKeyframes = true; - } - } - } - if (FU) { - FU = false; - break; - } - } - } else { - char buf[256]; - sprintf(buf, "Properties: empty###Properties"); - ImGui::Begin(buf); - if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) - UI->FocusedWindow = focus_properties; + if (ImGui::IsItemHovered() && io.MouseWheel) + { + UI->TimelinePercentOffset.y -= io.MouseWheel/10; } - ImGui::End(); -} -static void -ImGui_Viewport(project_data File, project_state *State, ui *UI, memory *Memory, comp_buffer CompBuffer, - ImGuiIO io, GLuint textureID) -{ - bool open = true; - ImGui::Begin("Viewport", &open, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse); + if (ImGui::IsItemActive() && ImGui::IsMouseDragging(ImGuiMouseButton_Left, -1)) + { + UI->TimelinePercentOffset.y -= MouseDelta.y; + BarHeld = true; + } - if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) - UI->FocusedWindow = focus_viewport; + ImGui::SetCursorScreenPos(BarV_PosUI + ImVec2(0, BarHandleSize) + ImVec2(0, BarV_SizeUI)); + ImGui::Button("##h-scrollbarright", ImVec2(BarThickness, BarHandleSize)); - // Primarily taken from the Custom Rendering section of the demo + if ((ImGui::IsItemActive() && ImGui::IsMouseDragging(ImGuiMouseButton_Left, -1))) + { + UI->TimelinePercentZoomed.y += MouseDelta.y; + BarHeld = true; + } - ImVec2 ViewportMin = ImGui::GetCursorScreenPos(); - ImVec2 ViewportScale = ImGui::GetContentRegionAvail(); - ViewportScale.y -= ImGui::GetFontSize()*0.5; - if (ViewportScale.x < 50.0f) ViewportScale.x = 50.0f; - if (ViewportScale.y < 50.0f) ViewportScale.y = 50.0f; - ImVec2 ViewportMax = ImVec2(ViewportMin.x + ViewportScale.x, ViewportMin.y + ViewportScale.y); + UI->TimelinePercentZoomed.y = Max(UI->TimelinePercentZoomed.y, 0.01); - if (UI->Initializing) { - UI->CompZoom = ImVec2(CompBuffer.Width, CompBuffer.Height); - UI->CompPos = ImVec2(ViewportMin.x + ((ViewportMax.x - ViewportMin.x)/2 - UI->CompZoom.x/2), - ViewportMin.y + ((ViewportMax.y - ViewportMin.y)/2 - UI->CompZoom.y/2)); + if (BarHeld) { + ImGui_WarpMouse(UI, io.MousePos, TimelineAbsolutePos, TimelineAbsolutePos + TimelineSizeWithBorder, 2); } - ImDrawList* draw_list = ImGui::GetWindowDrawList(); - draw_list->AddRectFilled(ViewportMin, ViewportMax, IM_COL32(50, 50, 50, 255)); - draw_list->AddRect(ViewportMin, ViewportMax, IM_COL32(255, 255, 255, 255)); - - // Actual composition texture - draw_list->PushClipRect(ViewportMin, ViewportMax, true); - draw_list->AddImage((void *)(intptr_t)textureID, ImVec2(UI->CompPos.x, UI->CompPos.y), - ImVec2(UI->CompPos.x + UI->CompZoom.x, UI->CompPos.y + UI->CompZoom.y)); - draw_list->PopClipRect(); + ImGui::SetCursorScreenPos(TimelineAbsolutePos); + ImGui::InvisibleButton("TimelineMoving", TimelineSizeWithBorder, ImGuiButtonFlags_MouseButtonLeft | ImGuiButtonFlags_MouseButtonRight); + // ImGui::Button("TimelineMoving", TimelineSizeWithBorder); + bool32 IsHovered = ImGui::IsItemHovered(); + bool32 IsItemActive = ImGui::IsItemActive(); + bool32 IsItemActivated = ImGui::IsItemActivated(); + bool32 IsItemDeactivated = ImGui::IsItemDeactivated(); + bool32 LeftClick = ImGui::IsMouseDown(ImGuiMouseButton_Left); + bool32 RightClick = ImGui::IsMouseDown(ImGuiMouseButton_Right); - // UI+interaction for layer - if (State->MostRecentlySelectedLayer > -1) { - project_layer *Layer = File.Layer[State->MostRecentlySelectedLayer]; - source *Source = Layer->Source; + if (IsHovered && io.MouseWheel) { + real32 Increment = 0.1; + bool32 Direction = (io.MouseWheel > 0) ? 1 : -1; + ImVec2 Offset = (io.MousePos - (TimelineAbsolutePos + TimelineMoveSize)) / TimelineZoomSize; + if (io.KeyShift) { + UI->TimelinePercentOffset.y += Increment*Direction; + } else if (io.KeyCtrl) { + UI->TimelinePercentOffset.x += Increment*Direction*0.3; + } else { + if (Direction == 1) { + UI->TimelinePercentZoomed = UI->TimelinePercentZoomed - (UI->TimelinePercentZoomed * Increment); + UI->TimelinePercentOffset = UI->TimelinePercentOffset - ((UI->TimelinePercentOffset * Increment) + Offset*Increment); + } else { + UI->TimelinePercentOffset = ((UI->TimelinePercentOffset + Offset*Increment) / (1.0f - Increment)); + UI->TimelinePercentZoomed = (UI->TimelinePercentZoomed / (1.0f - Increment)); + } + } + } - // Anchor point UI - ImVec2 AUV = ImVec2(Layer->x.CurrentValue.f / CompBuffer.Width, Layer->y.CurrentValue.f / CompBuffer.Height); - ImVec2 ScreenAP = ImVec2(UI->CompPos.x + AUV.x * UI->CompZoom.x, UI->CompPos.y + AUV.y * UI->CompZoom.y); - draw_list->AddNgon(ScreenAP, 20, ImGui::GetColorU32(ImGuiCol_ScrollbarGrab), 8, 10.0f); + if (IsItemActivated) { + if (!io.KeyShift) Layer_DeselectAll(Memory, File->Layer_Count); + UI->BoxSelect = true; + } + if (IsItemActive) { + Assert(UI->BoxSelect); + draw_list->AddRectFilled(io.MouseClickedPos[0], io.MousePos, + IM_COL32(0, 0, 200, 50)); + } - // Mask UI - if (Layer->NumberOfMasks) { - for (int i = 0; i < Layer->NumberOfMasks; i++) { - mask *Mask = &Layer->Mask[i]; - ImGui::PushID(i); + if (IsItemDeactivated) { + UI->BoxSelect = false; + } - real32 PointSize = 40; + draw_list->PopClipRect(); + ImGui::PopClipRect(); - for (int p = 0; p < Mask->NumberOfPoints; p++) { + ImGui::PopStyleVar(); - mask_point *Point0 = &Mask->Point[p]; - mask_point *Point1 = &Mask->Point[p+1]; - if (p+1 == Mask->NumberOfPoints) - Point1 = &Mask->Point[0]; - // NOTE(fox): I want to keep operations in local space under the v2 data - // type and operations in screen space under ImVec2. + ImGui::EndChild(); - v2 Point0_Pos = Point0->Pos; - v2 Point0_Pos_Left = Point0_Pos + Point0->TangentLeft; - v2 Point0_Pos_Right = Point0_Pos + Point0->TangentRight; + ImGui::PopStyleVar(2); - v2 Point1_Pos = Point1->Pos; - v2 Point1_Pos_Left = Point1_Pos + Point1->TangentLeft; - v2 Point1_Pos_Right = Point1_Pos + Point1->TangentRight; + ImGui::End(); +} - ImVec2 Point0_ScreenPos = Layer_LocalToScreenSpace(Layer, UI, CompBuffer, Point0_Pos); - ImVec2 Point0_ScreenPos_Left = Layer_LocalToScreenSpace(Layer, UI, CompBuffer, Point0_Pos_Left); - ImVec2 Point0_ScreenPos_Right = Layer_LocalToScreenSpace(Layer, UI, CompBuffer, Point0_Pos_Right); +static void +ImGui_ProcessInputs(project_state *State, ImGuiIO io) +{ + if (io.KeysData[ImGuiKey_Q].Down) { + State->IsRunning = false; + } + if (io.KeysData[ImGuiKey_A].Down) { + State->UpdateFrame = true; + } +} - ImVec2 Point1_ScreenPos = Layer_LocalToScreenSpace(Layer, UI, CompBuffer, Point1_Pos); - ImVec2 Point1_ScreenPos_Left = Layer_LocalToScreenSpace(Layer, UI, CompBuffer, Point1_Pos_Left); - // ImVec2 Point1_ScreenPos_Right = Layer_LocalToScreenSpace(Layer, UI, CompBuffer, Point1_Pos_Right); +#if 0 + real32 MaxVal_Y = -10000; + real32 MinVal_Y = 10000; + for (int b = 0; b < Property->NumberOfTotalKeyframes; b++) { + keyframe *Keyframe = KeyframeLookup(Property, b); + MaxVal_Y = (Keyframe->Value.f > MaxVal_Y) ? Keyframe->Value.f : MaxVal_Y; + MinVal_Y = (Keyframe->Value.f < MinVal_Y) ? Keyframe->Value.f : MinVal_Y; + } - ImGui::PushID(p); + keyframe *FirstKeyframe = KeyframeLookup(Property, 0); + keyframe *LastKeyframe = KeyframeLookup(Property, Property->NumberOfTotalKeyframes - 1); + real32 MinVal_X = (Layer->BitmapInfo.FrameOffset + FirstKeyframe->FrameNumber); + real32 MaxVal_X = (Layer->BitmapInfo.FrameOffset + LastKeyframe->FrameNumber); - ImU32 col = ImGui::GetColorU32(ImGuiCol_ScrollbarGrab); + UI->Y_MaxVal = MaxVal_Y; + UI->Y_MinVal = MinVal_Y; - // The handle itself + if (!UI->IsDragging) { + UI->Display_Y_MinVal = UI->Y_MinVal; + UI->Display_Y_MaxVal = UI->Y_MaxVal; + } - col = ImGui::GetColorU32(ImGuiCol_ButtonHovered); - if (Point0->HandleBezier) { - draw_list->AddNgon(Point0_ScreenPos_Left, 10, col, 8, 5.0f); - draw_list->AddNgon(Point0_ScreenPos_Right, 10, col, 8, 5.0f); - draw_list->AddLine(Point0_ScreenPos, Point0_ScreenPos_Left, col, 2.0f); - draw_list->AddLine(Point0_ScreenPos, Point0_ScreenPos_Right, col, 2.0f); - } + real32 Y_TimelinePercentZoomed = UI->Y_TimelinePercentZoomed; + real32 Y_TimelinePercentOffset = UI->Y_TimelinePercentOffset; + MaxVal_Y = UI->Display_Y_MaxVal; + MinVal_Y = UI->Display_Y_MinVal; - draw_list->AddNgon(Point0_ScreenPos, 10, col, 8, 5.0f); + DebugWatchVar("offset: ", &Y_TimelinePercentOffset, d_float); + DebugWatchVar("zoom: ", &Y_TimelinePercentZoomed, d_float); - int max = 1; + real32 Ratio_Graph_X = (MaxVal_X - MinVal_X) / File->NumberOfFrames; + real32 TimelineZoomSize = TimelineSizeWithBorder.x / UI->TimelinePercentZoomed; + real32 TimelineMoveSize = TimelineSizeWithBorder.x * UI->TimelinePercentOffset / UI->TimelinePercentZoomed; + real32 Y_TimelineZoomSize = TimelineSizeWithBorder.y / Y_TimelinePercentZoomed; + real32 Y_TimelineMoveSize = TimelineSizeWithBorder.y * Y_TimelinePercentOffset / Y_TimelinePercentZoomed; - if (Point0->HandleBezier && Point0->IsSelected) { - max = 3; - } + for (int b = 0; b < Property->NumberOfTotalKeyframes; b++) { + ImGui::PushID(b); - for (int b = 0; b < max; b++) - { - ImGui::PushID(b); - if (b == 0) { - ImGui::SetCursorScreenPos(Point0_ScreenPos - ImVec2(PointSize/2, PointSize/2)); - } else if (b == 1) { - ImGui::SetCursorScreenPos(Point0_ScreenPos_Left - ImVec2(PointSize/2, PointSize/2)); - } else { - ImGui::SetCursorScreenPos(Point0_ScreenPos_Right - ImVec2(PointSize/2, PointSize/2)); - } + keyframe *Keyframe = KeyframeLookup(Property, b); + // Only used for drawing the bezier. + keyframe *NextKeyframe = (b != Property->NumberOfTotalKeyframes - 1) ? KeyframeLookup(Property, b + 1) : NULL; - ImGui::InvisibleButton("##point", ImVec2(PointSize, PointSize), ImGuiButtonFlags_MouseButtonLeft); + real32 Increment_X = (real32)1 / File->NumberOfFrames; + real32 UI_FrameDistance = Increment_X*TimelineZoomSize; - if (ImGui::IsItemHovered()) { - ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeEW); - } - if (ImGui::IsItemHovered() && ImGui::IsKeyPressed(ImGuiKey_Backspace)) { - Mask_DeletePoint(Memory, Mask, p); - State->UpdateFrame = true; - } - if (ImGui::IsItemActivated() && b == 0) { - if (p == 0 && State->Pen.IsActive) { - History_Entry_Commit(Memory, action_entry_default, "Close mask path"); - History_Action_Change_SwapBool(Memory, &State->Pen.IsActive); - History_Action_Change_SwapBool(Memory, &Mask->IsClosed); - // State->Pen.IsActive = false; - // Mask->IsClosed = true; - History_Entry_End(Memory); - } else if (io.KeyAlt) { - History_Entry_Commit(Memory, action_entry_default, "Switch handles on point"); - History_Action_Change_SwapBool(Memory, &Point0->HandleBezier); - History_Entry_End(Memory); - } - Point0->IsSelected = true; - } - if (ImGui::IsItemActive()) { - ImVec2 MouseIncrement = io.MouseDelta * (ImVec2(CompBuffer.Width, CompBuffer.Height) / UI->CompZoom); - if (b == 0) { - Layer_CalcRotatedOffset(Layer, V2(MouseIncrement), V2(1, 1), - &Point0->Pos.x, &Point0->Pos.y); - } else if (b == 1) { - Layer_CalcRotatedOffset(Layer, V2(MouseIncrement), V2(1, 1), - &Point0->TangentLeft.x, &Point0->TangentLeft.y); - } else { - Layer_CalcRotatedOffset(Layer, V2(MouseIncrement), V2(1, 1), - &Point0->TangentRight.x, &Point0->TangentRight.y); - } - State->UpdateFrame = true; - } - ImGui::PopID(); - } + int32 Keyframe_X = (Layer->BitmapInfo.FrameOffset + Keyframe->FrameNumber); + real32 Keyframe_Y = Keyframe->Value.f; - // The bezier path + real32 Ratio_X_Mid = (real32)Keyframe_X / File->NumberOfFrames; + real32 Ratio_Y_Mid = (Keyframe_Y - MinVal_Y) / (MaxVal_Y - MinVal_Y); + ImVec2 KeyframePos_Mid = ImVec2(TimelineAbsolutePos.x + TimelineMoveSize + Ratio_X_Mid*TimelineZoomSize, + TimelineAbsolutePos.y + Y_TimelineMoveSize + (1.0f - Ratio_Y_Mid)*Y_TimelineZoomSize); - if (Mask->NumberOfPoints == 1 || (p+1 == Mask->NumberOfPoints && !Mask->IsClosed)) { - ImGui::PopID(); - continue; - } + ImGui::SetCursorScreenPos(KeyframePos_Mid); + ImGui::Button("##keyframe", ImVec2(FontHeight, FontHeight)); - ImU32 col2 = ImGui::GetColorU32(ImGuiCol_Button); + if (ImGui::IsItemHovered() && ImGui::IsKeyPressed(ImGuiKey_R)) { + UI->TempVal = Keyframe->Value.f; + UI->TempVal_X = Keyframe->FrameNumber; + } - if (Point0->HandleBezier && Point1->HandleBezier) { - draw_list->AddBezierCubic(Point0_ScreenPos, Point0_ScreenPos_Right, - Point1_ScreenPos_Left, Point1_ScreenPos, col2, 6.0f, 0); - } else if (Point0->HandleBezier) { - draw_list->AddBezierCubic(Point0_ScreenPos, Point0_ScreenPos_Right, - Point1_ScreenPos, Point1_ScreenPos, col2, 6.0f, 0); - } else if (Point1->HandleBezier) { - draw_list->AddBezierCubic(Point0_ScreenPos, Point0_ScreenPos, - Point1_ScreenPos_Left, Point1_ScreenPos, col2, 6.0f, 0); - } else { - draw_list->AddLine(Point0_ScreenPos, Point1_ScreenPos, col2, 6.0f); - } + if (ImGui::IsItemActivated()) { + UI->IsDragging = true; + UI->TempVal = Keyframe->Value.f; + UI->TempVal_X = Keyframe->FrameNumber; + } - if (Point0->HandleBezier && Point1->HandleBezier) { - if (ImGui::BezierInteractive(Point0_ScreenPos, Point0_ScreenPos_Right, - Point1_ScreenPos_Left, Point1_ScreenPos) && - State->Tool == tool_pen) - { + if ((ImGui::IsItemActive() && ImGui::IsMouseDragging(ImGuiMouseButton_Left, -1))) + { + ImVec2 DragDelta = ImGui::GetMouseDragDelta(); + DragDelta = DragDelta + (ImVec2(UI->Wrap_X, UI->Wrap_Y) * TimelineSize); + DebugWatchVar("DragX", &DragDelta.x, d_float); + DebugWatchVar("DragY", &DragDelta.y, d_float); + DebugWatchVar("Wrap_X", &UI->Wrap_X, d_int); + DebugWatchVar("Wrap_Y", &UI->Wrap_Y, d_int); + real32 MouseDeltaRatio = -DragDelta.y / TimelineSizeWithBorder.y * Y_TimelinePercentZoomed; + ImVec2 Increment = ImVec2(DragDelta.x / UI_FrameDistance, ((MaxVal_Y - MinVal_Y) * MouseDeltaRatio)); - // Using a button like this may be kinda janky, but it gives us access - // to all of ButtonBehavior and the ID system without having to write on top of ImGui's. - ImGui::SetCursorScreenPos(io.MousePos - ImVec2(5,5)); - ImGui::Button("maskbezier", ImVec2(10, 10)); + // The plus 0.5 * X_Direction is for making the frame jump happen + // when the cursor is between two frames rather than when passing one. + real32 X_Direction = (Increment.x > 0) ? fabsf(Increment.x) / Increment.x : 0; - if (ImGui::IsItemHovered()) { -#if DEBUG - // Code that basically mimics Mask_AddPointToCurve but visualized in screen space, for checking bugs. - v2 LayerPoint = Layer_ScreenSpaceToLocal(Layer, UI, CompBuffer, ViewportMin, io.MousePos); - real32 ratio = Bezier_CubicRatioOfPoint(Point0_Pos, Point0_Pos_Right, Point1_Pos_Left, Point1_Pos, LayerPoint); - draw_list->AddNgon(io.MousePos, 2, col, 8, 5.0f); - ImVec2 RatioLeft = ImGui::RatioToPoint(Point0_ScreenPos, Point0_ScreenPos_Right, ratio); - ImVec2 RatioRight = ImGui::RatioToPoint(Point1_ScreenPos_Left, Point1_ScreenPos, ratio); - ImVec2 RatioTop = ImGui::RatioToPoint(Point0_ScreenPos_Right, Point1_ScreenPos_Left, ratio); - ImVec2 TangentLeft = ImGui::RatioToPoint(RatioLeft, RatioTop, ratio); - ImVec2 TangentRight = ImGui::RatioToPoint(RatioTop, RatioRight, ratio); - draw_list->AddLine(RatioLeft, RatioTop, col, 2.0f); - draw_list->AddLine(RatioRight, RatioTop, col, 2.0f); - draw_list->AddLine(TangentLeft, TangentRight, col, 2.0f); -#endif - } - if (ImGui::IsItemActivated() && io.KeyCtrl) { - v2 LayerPoint = Layer_ScreenSpaceToLocal(Layer, UI, CompBuffer, ViewportMin, io.MousePos); - real32 ratio = Bezier_CubicRatioOfPoint(Point0_Pos, Point0_Pos_Right, Point1_Pos_Left, Point1_Pos, LayerPoint); - Mask_AddPointToCurve(Memory, Mask, p, ratio); - } - } - } else { - if (ImGui::LineInteractive(Point0_ScreenPos, Point1_ScreenPos) && - State->Tool == tool_pen) - { - ImGui::SetCursorScreenPos(io.MousePos - ImVec2(5,5)); - ImGui::Button("maskline", ImVec2(10, 10)); + Keyframe->FrameNumber = UI->TempVal_X + (int32)(Increment.x + 0.5*X_Direction); + Keyframe->Value.f = UI->TempVal + Increment.y; - if (ImGui::IsItemHovered()) { - draw_list->AddNgon(io.MousePos, 2, col, 8, 5.0f); - } - if (ImGui::IsItemActivated() && io.KeyCtrl) { - v2 LayerPoint = Layer_ScreenSpaceToLocal(Layer, UI, CompBuffer, ViewportMin, io.MousePos); - Mask_AddPointToLine(Mask, p, LayerPoint); - } - } - } - ImGui::PopID(); + if (io.KeyShift) { + bool32 RestrainAxis = (fabsf(DragDelta.x) > fabsf(DragDelta.y)); + if (RestrainAxis) { + Keyframe->Value.f = UI->TempVal; + } else { + Keyframe->FrameNumber = UI->TempVal_X; } - ImGui::PopID(); } + ImGui_WrapMouse(UI, io.MousePos, TimelineAbsolutePos, TimelineAbsolutePos + TimelineSizeWithBorder); } - } - // Interactions for dragging and zooming - ImGui::SetCursorScreenPos(ViewportMin); - - real32 ButtonSize = 20; + // TODO(fox): This is kind of a mess. I built the graph around the + // ratios of the keyframes/timeline to make the bars straightforward, + // meaning the position/offset have to be transformed into a new space + // when a new min/max value is reached. - for (int t = 0; t < tool_count; t++) { - ImGui::PushID(t); - bool32 Selected = (State->Tool == t); - if (ImGui::Selectable(ToolName[t], Selected, 0, ImVec2(ButtonSize*2, ButtonSize))) { - State->Tool = (tool)t; + if (ImGui::IsItemDeactivated()) { + if ((UI->TempVal >= MaxVal_Y) || Keyframe->Value.f == UI->Y_MaxVal) { + real32 Min = ((Ratio_Y_Mid <= 0.0f) && (UI->TempVal >= MaxVal_Y)) ? MinVal_Y : UI->Y_MinVal; + real32 RealRatio_Y = (UI->Y_MaxVal - UI->Y_MinVal) / (MaxVal_Y - MinVal_Y); + UI->Y_TimelinePercentZoomed = UI->Y_TimelinePercentZoomed / RealRatio_Y; + UI->Y_TimelinePercentOffset = (1.0f/RealRatio_Y + UI->Y_TimelinePercentOffset/RealRatio_Y - 1.0f); + } else if (UI->TempVal <= MinVal_Y || Keyframe->Value.f == UI->Y_MinVal) { + real32 RealRatio_Y = (UI->Y_MinVal - MinVal_Y) / (MaxVal_Y - MinVal_Y); + UI->Y_TimelinePercentOffset = UI->Y_TimelinePercentOffset / (1 - RealRatio_Y); + UI->Y_TimelinePercentZoomed = UI->Y_TimelinePercentZoomed / (1 - RealRatio_Y); + } + UI->IsDragging = false; + UI->Wrap_X = 0; + UI->Wrap_Y = 0; } - ImGui::PopID(); - } - ImGui::InvisibleButton("canvas", ViewportScale, ImGuiButtonFlags_MouseButtonLeft | ImGuiButtonFlags_MouseButtonRight); - bool32 IsHovered = ImGui::IsItemHovered(); - bool32 IsActive = ImGui::IsItemActive(); - bool32 IsActivated = ImGui::IsItemActivated(); - bool32 IsDeactivated = ImGui::IsItemDeactivated(); + ImU32 col = ImGui::GetColorU32(ImGuiCol_ScrollbarGrab); - if (State->MostRecentlySelectedLayer > -1) - { - project_layer *Layer = File.Layer[State->MostRecentlySelectedLayer]; - if (Layer->NumberOfMasks == 0) { - if (IsActivated && ImGui::IsMouseDown(ImGuiMouseButton_Left)) { - if (State->Tool == tool_pen && !State->Pen.IsActive) { - State->Pen.IsActive = true; - Layer->NumberOfMasks++; + ImVec2 Handle_Pos[2] = {}; + + if (Keyframe->Type == bezier) { + ImVec2 Handle_Ratio[2] = {}; + + Handle_Ratio[0] = ImVec2((real32)(Keyframe_X + Keyframe->TangentLeft.x) / File->NumberOfFrames, + (Keyframe_Y + Keyframe->TangentLeft.y - MinVal_Y) / (MaxVal_Y - MinVal_Y)); + Handle_Ratio[1] = ImVec2((real32)(Keyframe_X + Keyframe->TangentRight.x) / File->NumberOfFrames, + (Keyframe_Y + Keyframe->TangentRight.y - MinVal_Y) / (MaxVal_Y - MinVal_Y)); + + Handle_Pos[0] = ImVec2(TimelineAbsolutePos.x + TimelineMoveSize + Handle_Ratio[0].x*TimelineZoomSize, + TimelineAbsolutePos.y + Y_TimelineMoveSize + (1.0f - Handle_Ratio[0].y)*Y_TimelineZoomSize); + Handle_Pos[1] = ImVec2(TimelineAbsolutePos.x + TimelineMoveSize + Handle_Ratio[1].x*TimelineZoomSize, + TimelineAbsolutePos.y + Y_TimelineMoveSize + (1.0f - Handle_Ratio[1].y)*Y_TimelineZoomSize); + + draw_list->AddLine(KeyframePos_Mid, Handle_Pos[0], col, 1.0f); + draw_list->AddLine(KeyframePos_Mid, Handle_Pos[1], col, 1.0f); + + for (int i = 0; i < 2; i++) { + ImGui::SetCursorScreenPos(Handle_Pos[i]); + ImGui::Button((i == 0) ? "##keyframe_left" : "##keyframe_right", ImVec2(FontHeight, FontHeight)); + v2 *Tangent = (i == 0) ? &Keyframe->TangentLeft : &Keyframe->TangentRight; + + if (ImGui::IsItemActivated()) { + UI->IsDragging = true; + UI->TempVal_X = Tangent->x; + UI->TempVal = Tangent->y; } - } - } - if (State->Pen.IsActive && !ImGui::IsKeyDown(ImGuiKey_Z)) { - if (IsActivated) { - v2 LayerPos = Layer_ScreenSpaceToLocal(Layer, UI, CompBuffer, ViewportMin, io.MousePos); - Mask_PushPoint(&Layer->Mask[Layer->NumberOfMasks-1], LayerPos); - } - if (IsActive) { - mask *Mask = &Layer->Mask[Layer->NumberOfMasks-1]; - mask_point *CurrentPoint = &Mask->Point[Mask->NumberOfPoints-1]; - v2 CompUV = ImGui_ScreenPointToCompUV(ViewportMin, UI->CompPos, UI->CompZoom, io.MousePos); - v2 LayerUV = CompUVToLayerUV(Layer, &CompBuffer, CompUV); - v2 LayerPos = LayerUV * V2(Layer->Source->Info.Width, Layer->Source->Info.Height); - v2 OffsetPos = CurrentPoint->Pos - LayerPos; - CurrentPoint->HandleBezier = true; - CurrentPoint->TangentRight = -OffsetPos; - CurrentPoint->TangentLeft = OffsetPos; - } - // Escape can be pressed to exit point-adding mode or to delete the - // mask if it was just created. - if (ImGui::IsKeyPressed(ImGuiKey_Escape)) { - mask *Mask = &Layer->Mask[Layer->NumberOfMasks-1]; - if (IsActive && Mask->NumberOfPoints == 1) { - Layer->NumberOfMasks--; - Mask->NumberOfPoints = 0; - State->Pen.IsActive = false; - } else { - History_Entry_Commit(Memory, action_entry_default, "Path adding exited"); - History_Action_Change_SwapBool(Memory, &State->Pen.IsActive); - History_Entry_End(Memory); + + if ((ImGui::IsItemActive() && ImGui::IsMouseDragging(ImGuiMouseButton_Left, -1))) + { + ImVec2 DragDelta = ImGui::GetMouseDragDelta(); + DragDelta = DragDelta + (ImVec2(UI->Wrap_X, UI->Wrap_Y) * TimelineSize); + ImVec2 MouseDeltaRatio = (ImVec2(1, -1) * DragDelta) / TimelineSizeWithBorder * ImVec2(UI->TimelinePercentZoomed / Ratio_Graph_X, Y_TimelinePercentZoomed); + real32 NewPos_X = ((MaxVal_X - MinVal_X) * MouseDeltaRatio.x); + real32 NewPos_Y = (io.KeyShift) ? 0 : ((MaxVal_Y - MinVal_Y) * MouseDeltaRatio.y); + *Tangent = V2(UI->TempVal_X, UI->TempVal) + V2(NewPos_X, NewPos_Y); + ImGui_WrapMouse(UI, io.MousePos, TimelineAbsolutePos, TimelineAbsolutePos + TimelineSizeWithBorder); } - IsDeactivated = false; // just in case escape and mouse release happen simultaneously - } - mask *Mask = &Layer->Mask[Layer->NumberOfMasks-1]; - if (IsDeactivated) { - mask *Mask = &Layer->Mask[Layer->NumberOfMasks-1]; - if (Mask->NumberOfPoints == 1) { - // NOTE(fox): We have to use this slightly janky way of writing to - // the history tree since we can only create entries/actions once we - // know the user is committed to them. Might write an escapable - // entry mode if we do this often. - uint16 PreviousNumberOfMasks = Layer->NumberOfMasks - 1; - uint16 PreviousNumberOfPoints = Mask->NumberOfPoints - 1; - bool32 NotActive = false; - History_Entry_Commit(Memory, action_entry_default, "Create mask"); - History_Action_Change(Memory, &State->Pen.IsActive, &NotActive, - &State->Pen.IsActive, action_type_change_u32); - History_Action_Change(Memory, &Layer->NumberOfMasks, &PreviousNumberOfMasks, - &Layer->NumberOfMasks, action_type_change_u16); - History_Action_Change(Memory, &Mask->NumberOfPoints, &PreviousNumberOfPoints, - &Mask->NumberOfPoints, action_type_change_u16); - History_Entry_End(Memory); - } else { - uint16 PreviousNumberOfPoints = Mask->NumberOfPoints - 1; - mask_point *CurrentPoint = &Mask->Point[Mask->NumberOfPoints-1]; - History_Entry_Commit(Memory, action_entry_default, "Add point"); - History_Action_Change(Memory, &Mask->NumberOfPoints, &PreviousNumberOfPoints, - &Mask->NumberOfPoints, action_type_change_u16); - History_Action_StoreData(Memory, &CurrentPoint, sizeof(mask_point)); - History_Entry_End(Memory); + + if (ImGui::IsItemDeactivated()) { + UI->IsDragging = false; + UI->Wrap_X = 0; + UI->Wrap_Y = 0; } } - if (State->Tool != tool_pen) { - State->Pen.IsActive = false; - } } + + if (NextKeyframe) { + real32 Ratio_X_2 = (real32)(Layer->BitmapInfo.FrameOffset + NextKeyframe->FrameNumber) / File->NumberOfFrames; + real32 Ratio_Y_2 = (NextKeyframe->Value.f - MinVal_Y) / (MaxVal_Y - MinVal_Y); + + ImVec2 NextKeyframePos = ImVec2(TimelineAbsolutePos.x + TimelineMoveSize + Ratio_X_2*TimelineZoomSize, + TimelineAbsolutePos.y + Y_TimelineMoveSize + (1.0f - Ratio_Y_2)*Y_TimelineZoomSize); + draw_list->AddLine(KeyframePos_Mid, NextKeyframePos, col, 1.0f); + } + + ImGui::PopID(); } - ImGui::OpenPopupOnItemClick("context", ImGuiPopupFlags_MouseButtonMiddle); - if (ImGui::BeginPopup("context")) { - if (ImGui::MenuItem("Scalar", NULL, false, InstructionMode != instruction_mode_scalar)) { InstructionMode = instruction_mode_scalar; State->UpdateFrame = true; } -#if ARM - if (ImGui::MenuItem("NEON", NULL, false, InstructionMode != instruction_mode_neon)) { InstructionMode = instruction_mode_neon; State->UpdateFrame = true; } -#else - if (ImGui::MenuItem("SSE", NULL, false, InstructionMode != instruction_mode_sse)) { InstructionMode = instruction_mode_sse; State->UpdateFrame = true; } - if (ImGui::MenuItem("AVX2", NULL, false, InstructionMode != instruction_mode_avx)) { InstructionMode = instruction_mode_avx; State->UpdateFrame = true; } + ImGui_TimelineIncrementDraw(File, UI, draw_list, TimelineSizeWithBorder, TimelineAbsolutePos, 0); + ImGui_TimelineIncrementDraw2(File, draw_list, MaxVal_Y, MinVal_Y, Y_TimelinePercentZoomed, Y_TimelinePercentOffset, TimelineSizeWithBorder, TimelineAbsolutePos, 0); + +#if DEBUG + draw_list->AddCircle(TimelineAbsolutePos + ImVec2(TimelineSizeWithBorder.x * 0.25, TimelineSizeWithBorder.y - 50), + 2, IM_COL32(200, 000, 200, 200), 16, 1); + draw_list->AddCircle(TimelineAbsolutePos + ImVec2(TimelineSizeWithBorder.x * 0.5, TimelineSizeWithBorder.y - 50), + 2, IM_COL32(200, 000, 200, 200), 16, 1); + draw_list->AddCircle(TimelineAbsolutePos + ImVec2(TimelineSizeWithBorder.x * 0.75, TimelineSizeWithBorder.y - 50), + 2, IM_COL32(200, 000, 200, 200), 16, 1); #endif - ImGui::EndPopup(); - } - if (IsHovered && IsActivated && ImGui::IsMouseDown(ImGuiMouseButton_Left)) - { - // Point to zoom in on if Z is held - UI->TempZoomRatio = ImGui_ScreenPointToCompUV(ViewportMin, UI->CompPos, UI->CompZoom, io.MousePos); +#endif - // Layer selection - if (!ImGui::IsKeyDown(ImGuiKey_Z) || !State->Pen.IsActive) { - for (int i = File.NumberOfLayers - 1; i >= 0; i--) { - project_layer *Layer = File.Layer[i]; - if (!io.KeyShift) DeselectAllLayers(&File, State); - v2 LayerUV = CompUVToLayerUV(Layer, &CompBuffer, UI->TempZoomRatio); - if (TestUV(LayerUV) && !Layer->IsSelected) - { - SelectLayer(Layer, State, i); - break; - } - } +#if 0 + + ImVec2 MouseDelta = io.MouseDelta / TimelineSize; + + real32 BarHandleSize = FontHeight; + real32 BarThickness = 50; + real32 BarMinZoom = 0.01; + + real32 BarH_Pos = -TimelineSizeWithBorder.x * UI->TimelinePercentOffset; + real32 BarH_Size = TimelineSizeWithBorder.x / (1 / UI->TimelinePercentZoomed); + + // I use "UI" to denote the size/position after clipping the bar so that it + // doesn't go out of bounds and the handles are always selectable at the edges. + + real32 BarH_Offset = Max(BarH_Pos, 0); + + real32 BarH_SizeUI = (BarH_Size + BarH_Pos > TimelineSizeWithBorder.x) ? + TimelineSizeWithBorder.x - BarH_Pos : + BarH_Size + (BarH_Pos - BarH_Offset); + + if (BarH_Offset == 0 && BarH_SizeUI > TimelineSizeWithBorder.x) + BarH_SizeUI = TimelineSizeWithBorder.x; + + BarH_SizeUI = BarH_SizeUI - BarHandleSize*2; + + BarH_SizeUI = Max(BarH_SizeUI, FontHeight*4); + + BarH_Offset = Min(BarH_Offset, TimelineSize.x - BarH_SizeUI - BarHandleSize*4); + ImVec2 BarH_PosUI = TimelineAbsolutePos + ImVec2(BarH_Offset, TimelineSize.y - BarThickness); + bool32 BarHeld = false; + + ImGui::SetCursorScreenPos(BarH_PosUI); + ImGui::Button("##scrollbarleft", ImVec2(BarHandleSize, BarThickness)); + + if ((ImGui::IsItemActive() && ImGui::IsMouseDragging(ImGuiMouseButton_Left, -1))) + { + if ((UI->TimelinePercentZoomed - MouseDelta.x) > BarMinZoom) { + UI->TimelinePercentZoomed -= MouseDelta.x; + UI->TimelinePercentOffset -= MouseDelta.x; } + BarHeld = true; } - if (IsActive && ImGui::IsMouseDragging(ImGuiMouseButton_Right, -1.0f)) + ImGui::SetCursorScreenPos(BarH_PosUI + ImVec2(BarHandleSize, 0)); + ImGui::Button("##scrollbarhori", ImVec2(BarH_SizeUI, BarThickness)); + + if (ImGui::IsItemActive() && ImGui::IsMouseDragging(ImGuiMouseButton_Left, -1)) { - UI->CompPos.x += io.MouseDelta.x; - UI->CompPos.y += io.MouseDelta.y; + UI->TimelinePercentOffset -= MouseDelta.x; + BarHeld = true; } - if (IsActive && ImGui::IsMouseDragging(ImGuiMouseButton_Left, -1.0f) && ImGui::IsKeyDown(ImGuiKey_Z)) + ImGui::SetCursorScreenPos(BarH_PosUI + ImVec2(BarHandleSize, 0) + ImVec2(BarH_SizeUI, 0)); + ImGui::Button("##scrollbarright", ImVec2(BarHandleSize, BarThickness)); + + if ((ImGui::IsItemActive() && ImGui::IsMouseDragging(ImGuiMouseButton_Left, -1))) { - real32 Distance = io.MouseDelta.x + io.MouseDelta.y; - UI->CompZoom.x += (Distance)*(real32)CompBuffer.Width/CompBuffer.Height; - UI->CompZoom.y += (Distance); - UI->CompPos.x -= ((Distance)*(real32)CompBuffer.Width/CompBuffer.Height)*UI->TempZoomRatio.x; - UI->CompPos.y -= Distance*UI->TempZoomRatio.y; + if ((UI->TimelinePercentZoomed + MouseDelta.x) > BarMinZoom) { + UI->TimelinePercentZoomed += MouseDelta.x; + } + BarHeld = true; } - ImGui::SetCursorScreenPos(ImVec2(ViewportMin.x, ViewportMin.y + ViewportScale.y - ImGui::GetFontSize()*1.5)); + if (BarHeld) { + ImGui_WrapMouse(UI, io.MousePos, TimelineAbsolutePos, TimelineAbsolutePos + TimelineSizeWithBorder, 1); + } - ImGui::Text("%.1f", 100.0f * (UI->CompZoom.x / CompBuffer.Width)); - if (State->MsgTime > 0) { - ImGui::SameLine(); - ImGui::SetCursorPosX((ViewportScale.x / 5)*4); - ImGui::Text(State->Msg); - State->MsgTime--; - } - - - ImGui::End(); -} - -// 1 for left, 2 for right, 3 for both -static bool32 -ImGui_SlidingLayer(project_layer *Layer, real32 *DraggingThreshold, real32 Delta, int16 TimelineZoom, int16 Side) -{ - bool32 Result = 0; - if (ImGui::IsItemActivated()) - { - // if (Side & 1) - // Layer->StartFrame += Increment; - // if (Side & 2) - // Layer->EndFrame += Increment; - // if (Side == 3) { - } - if (ImGui::IsItemActive() && ImGui::IsMouseDragging(ImGuiMouseButton_Left, -1)) - { - ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeEW); - *DraggingThreshold += Delta; - if (abs(*DraggingThreshold) >= TimelineZoom) { - int16 Increment = *DraggingThreshold/TimelineZoom; - - if (Side & 1) - Layer->StartFrame += Increment; - if (Side & 2) - Layer->EndFrame += Increment; - if (Side == 3) { - // TODO(fox): Make frame offset in keyframes local! - IncrementKeyframesInLayer(Layer, Increment); - if (Layer->Source->SourceType == source_type_video) { - Layer->BitmapInfo.FrameOffset += Increment; - } - } - *DraggingThreshold += -1*Increment*TimelineZoom; - } - Result = 1; - } - return Result; -} + Assert(UI->TimelinePercentZoomed > BarMinZoom); -static void -ImGui_File(project_data *File, project_state *State, memory *Memory, ui *UI, ImGuiIO io) -{ - ImGui::Begin("Files"); - ImGui::Text("Application average %.3f ms/frame (%.1f FPS)", 1000.0f / io.Framerate, io.Framerate); -#if DEBUG - if (State->DemoButton) { - if (ImGui::Button("Generate demo scene")) { - CreateDemoScene(File, State, Memory); - State->UpdateKeyframes = true; - State->UpdateFrame = true; - State->DemoButton = false; - } - } - if (State->GridButton) { - ImGui::SameLine(); - if (ImGui::Button("Generate square grid")) { - // CreateGrid(File, Memory); - State->UpdateKeyframes = true; - State->UpdateFrame = true; - State->GridButton = false; - } - } -#endif - ImGui::Text("Sources:"); - for (int i = 0; i < File->NumberOfSources; i++) { - bool32 Test = false; - if (File->SourceSelected == i) - Test = true; - ImGui::Selectable(File->Source[i].Path, Test); - if (ImGui::IsItemClicked()) - File->SourceSelected = i; - if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) - File->SourceSelected = i; - ImGui::OpenPopupOnItemClick("sourcecontext", ImGuiPopupFlags_MouseButtonRight); - } - if (ImGui::BeginPopup("sourcecontext")) { - if (ImGui::MenuItem("Create layer from source")) { - Layer_CreateFromSource(File, State, Memory, &File->Source[File->SourceSelected]); - } - ImGui::EndPopup(); - } - static char Input[1024]; - ImGui::InputTextWithHint("##sourceinput", "Input file path of source...", Input, STRING_SIZE); - if (ImGui::IsItemDeactivated() && ImGui::IsKeyPressed(ImGuiKey_Enter)) { - Source_Generate(File, State, Memory, Input); - } -#if DEBUG - for (int i = 0; i < Debug.Temp.WatchedProperties; i++) { - if (Debug.Temp.DebugPropertyType[i] == d_float) { - ImGui::Text("%s: %f", Debug.Temp.String[i], Debug.Temp.Val[i].f); - } else if (Debug.Temp.DebugPropertyType[i] == d_int) { - ImGui::Text("%s: %i", Debug.Temp.String[i], Debug.Temp.Val[i].i); - } else if (Debug.Temp.DebugPropertyType[i] == d_uint) { - ImGui::Text("%s: %u", Debug.Temp.String[i], Debug.Temp.Val[i].u); - } - } - Debug.Temp = {}; -#endif - ImGui::End(); -} + real32 BarV_MaxSize = TimelineSizeWithBorder.y - BarThickness/2; + real32 BarV_Pos = -BarV_MaxSize * Y_TimelinePercentOffset; + real32 BarV_Size = BarV_MaxSize / (1 / Y_TimelinePercentZoomed); + BarV_Size = Max(BarV_Size, FontHeight*4); -static void -ImGui_EffectsPanel(project_data *File, project_state *State, memory *Memory, ui *UI, ImGuiIO io) -{ - ImGui::Begin("Effects list", NULL); - if (State->RerouteEffects) { - ImGui::SetKeyboardFocusHere(); - State->RerouteEffects = 0; - } - int value_changed = ImGui::InputText("Effect name...", State->filter.InputBuf, IM_ARRAYSIZE(State->filter.InputBuf), - ImGuiInputTextFlags_CallbackCompletion, EffectConsoleCallback); + real32 BarV_Offset = Max(BarV_Pos, 0); - if (Hacko) { - if (!io.KeyShift) - EffectSel++; - else - EffectSel--; - Hacko = 0; - } - if (value_changed) { - State->filter.Build(); - EffectSel = -1; - } - // Enter conveniently deactivates the InputText field - if (ImGui::IsItemDeactivated() && ImGui::IsKeyPressed(ImGuiKey_Enter)) { - int32 p = 0; - for (int32 i = 0; i < AmountOf(EffectList); i++) { - if (State->filter.PassFilter(EffectList[i].Name)) { - if (EffectSel == p && State->MostRecentlySelectedLayer != -1) { - AddEffect(File->Layer[State->MostRecentlySelectedLayer], Memory, i); - State->UpdateFrame = true; - } - p++; - } - } - EffectSel = -1; - } - int32 p = 0; - for (int32 i = 0; i < AmountOf(EffectList); i++) { - if (State->filter.PassFilter(EffectList[i].Name)) { - bool t = false; - if (EffectSel == p) { - t = true; - } - ImGui::Selectable(EffectList[i].Name, &t); - if (ImGui::IsItemClicked()) { - if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left) && State->MostRecentlySelectedLayer != -1) { - AddEffect(File->Layer[State->MostRecentlySelectedLayer], Memory, i); - State->UpdateFrame = true; - } - } - p++; - } - } - ImGui::End(); -} + real32 BarV_SizeUI = (BarV_Size + BarV_Pos > BarV_MaxSize) ? + BarV_MaxSize - BarV_Pos : + BarV_Size + (BarV_Pos - BarV_Offset); + if (BarV_Offset == 0 && BarV_SizeUI > BarV_MaxSize) + BarV_SizeUI = BarV_MaxSize; -static void -ImGui_Timeline(project_data *File, project_state *State, memory *Memory, ui *UI, ImGuiIO io) -{ - ImVec2 FramePadding = ImGui::GetStyle().FramePadding; - ImVec2 ItemSpacing = ImGui::GetStyle().ItemSpacing; - ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0)); - ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0)); // makes setting up the layout easier - ImGui::Begin("Timeline", NULL); + BarV_SizeUI = BarV_SizeUI - BarHandleSize*2; - if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) - UI->FocusedWindow = focus_timeline; + BarV_SizeUI = Max(BarV_SizeUI, FontHeight*4); - real32 FontHeight = ImGui::GetFontSize(); + BarV_Offset = Min(BarV_Offset, BarV_MaxSize - BarV_SizeUI - BarHandleSize*4); + ImVec2 BarV_PosUI = TimelineAbsolutePos + ImVec2(TimelineSize.x - BarThickness, BarV_Offset); + BarHeld = false; - ImVec2 WindowSize = ImGui::GetWindowSize(); + ImGui::SetCursorScreenPos(BarV_PosUI); + ImGui::Button("##h-scrollbarleft", ImVec2(BarThickness, BarHandleSize)); - if (WindowSize.x < 50 || WindowSize.y < 50) { - ImGui::PopStyleVar(2); - ImGui::End(); - return; + if ((ImGui::IsItemActive() && ImGui::IsMouseDragging(ImGuiMouseButton_Left, -1))) + { + UI->Y_TimelinePercentZoomed -= MouseDelta.y; + UI->Y_TimelinePercentOffset -= MouseDelta.y; + BarHeld = true; } - ImVec2 WindowMinAbs = ImGui::GetWindowPos(); - ImVec2 WindowMaxAbs = WindowMinAbs + WindowSize; - - ImVec2 ButtonSize = ImVec2(FontHeight*2, FontHeight*2); - - real32 TopbarHeight = FontHeight*2; - ImVec2 TopbarMax = ImVec2(WindowMaxAbs.x, WindowMinAbs.y + TopbarHeight); - - ImVec2 TimelineBorderPadding = ImVec2(FontHeight, FontHeight); - - ImVec2 TopbarSize = ImVec2(WindowSize.x, TopbarHeight); - ImVec2 TopbarButtonSize = ImVec2(TopbarHeight, TopbarHeight); - - // NOTE(fox): StartingPos values include X and Y scroll, primarily used for - // the keyframes/layers. Absolute doesn't include scroll, primarily used - // for the clip rects. - - ImVec2 SidebarSize = ImVec2(UI->TimelineSplit, WindowSize.y - TopbarHeight); - ImVec2 SidebarSizeWithBorder = SidebarSize - TimelineBorderPadding*2; - ImVec2 SidebarAbsolutePos = WindowMinAbs + ImVec2(0, TopbarSize.y) + TimelineBorderPadding; - ImVec2 SidebarStartingPos = SidebarAbsolutePos + ImVec2(0, UI->ScrollYOffset); - - ImVec2 TimelineSize = ImVec2(WindowSize.x - SidebarSize.x, SidebarSize.y); - ImVec2 TimelineSizeWithBorder = TimelineSize - TimelineBorderPadding*2; - ImVec2 TimelineAbsolutePos = WindowMinAbs + ImVec2(SidebarSize.x, TopbarSize.y) + TimelineBorderPadding; - ImVec2 TimelineStartingPos = SidebarStartingPos + ImVec2(SidebarSize.x + UI->ScrollXOffset, 0); - - // Timeline and sidebar size including the padding between them - ImVec2 TimelineFullSize = TimelineSizeWithBorder + SidebarSizeWithBorder + ImVec2(TimelineBorderPadding.x*2, 0); + ImGui::SetCursorScreenPos(BarV_PosUI + ImVec2(0, BarHandleSize)); + ImGui::Button("##h-scrollbar", ImVec2(BarThickness, BarV_SizeUI)); - ImVec2 KeyframeSize = ImVec2(FontHeight, FontHeight); + if (ImGui::IsItemHovered() && io.MouseWheel) + { + UI->Y_TimelinePercentOffset -= io.MouseWheel/10; + } - ImVec2 PlayheadPos = ImVec2(TimelineStartingPos.x + UI->TimelineZoom * File->CurrentFrame, WindowMinAbs.y + TopbarSize.y/2); + if (ImGui::IsItemActive() && ImGui::IsMouseDragging(ImGuiMouseButton_Left, -1)) + { + UI->Y_TimelinePercentOffset -= MouseDelta.y; + BarHeld = true; + } - real32 MaxZoom = TimelineSizeWithBorder.x / (File->NumberOfFrames + 1); + ImGui::SetCursorScreenPos(BarV_PosUI + ImVec2(0, BarHandleSize) + ImVec2(0, BarV_SizeUI)); + ImGui::Button("##h-scrollbarright", ImVec2(BarThickness, BarHandleSize)); - if (UI->Initializing) { - UI->TimelineZoom = MaxZoom; + if ((ImGui::IsItemActive() && ImGui::IsMouseDragging(ImGuiMouseButton_Left, -1))) + { + UI->Y_TimelinePercentZoomed += MouseDelta.y; + BarHeld = true; } - ImDrawList* draw_list = ImGui::GetWindowDrawList(); - draw_list->AddRectFilled(WindowMinAbs, WindowMaxAbs, - IM_COL32(255, 255, 255, 50)); - draw_list->AddRectFilled(WindowMinAbs, TopbarMax, - IM_COL32(255, 255, 255, 50)); - + UI->Y_TimelinePercentZoomed = Max(UI->Y_TimelinePercentZoomed, 0.01); - // + if (BarHeld) { + ImGui_WrapMouse(UI, io.MousePos, TimelineAbsolutePos, TimelineAbsolutePos + TimelineSizeWithBorder, 2); + } + draw_list->PopClipRect(); + ImGui::PopClipRect(); - ImGui::BeginChild("Topbar", TopbarSize, true, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoScrollbar); - char buf2[6]; - sprintf(buf2, "%.2i:%.2i", File->CurrentFrame / File->FPS, File->CurrentFrame % File->FPS); - ImGui::Text(buf2); - /* - if (UI->IsDragging) { - ImGui::SameLine(); - sprintf(buf2, "X: %.3f, Y: %.3f", - ImGui::Text(buf2); - } - */ - /* - ImGui::Button("V", TopbarButtonSize); ImGui::SameLine(); - ImGui::Button("V", TopbarButtonSize); ImGui::SameLine(); - ImGui::Button("V", TopbarButtonSize); ImGui::SameLine(); - */ + ImGui::PopStyleVar(); - ImGui::SetCursorScreenPos(PlayheadPos); - ImGui::Button("P", ButtonSize); - /* - if (ImGui::IsItemActive()) { - if (ImGui::IsMouseDragging(ImGuiMouseButton_Left, -1)) - { - UI->DraggingKeyframeThreshold += io.MouseDelta.x; - if (abs(UI->DraggingKeyframeThreshold) >= UI->TimelineZoom) { - int16 Increment = UI->DraggingKeyframeThreshold/UI->TimelineZoom; - if (File->CurrentFrame + Increment <= 0) - File->CurrentFrame = 0; - else if (File->CurrentFrame + Increment >= File->EndFrame) { - File->CurrentFrame = File->EndFrame; - } else { - File->CurrentFrame += Increment; + if (io.MouseWheel) { + // NOTE(fox): Change this if any other action is added when hovering over the bar area. + bool32 BarHovering_H = TestRectangle(TimelineAbsolutePos + ImVec2(0, TimelineSize.y - BarThickness), + TimelineAbsolutePos + ImVec2(TimelineSize.x, TimelineSize.y), + io.MousePos); + bool32 BarHovering_V = TestRectangle(TimelineAbsolutePos + ImVec2(TimelineSize.x - BarThickness, 0), + TimelineAbsolutePos + ImVec2(TimelineSize.x, TimelineSize.y), + io.MousePos); + if (BarHovering_H && io.MouseWheel) { + UI->TimelinePercentOffset -= io.MouseWheel/15; + } else if (BarHovering_V && io.MouseWheel) { + UI->Y_TimelinePercentOffset -= io.MouseWheel/15; + } else { + real32 Increment = 0.1; + bool32 Direction = (io.MouseWheel > 0) ? 1 : -1; + real32 Offset = (io.MousePos.y - (TimelineAbsolutePos.y + Y_TimelineMoveSize)) / Y_TimelineZoomSize; + real32 X_Offset = (io.MousePos.x - (TimelineAbsolutePos.x + TimelineMoveSize)) / TimelineZoomSize; + DebugWatchVar("X Offset", &X_Offset, d_float); + if (io.KeyShift) { + UI->Y_TimelinePercentOffset += Increment*Direction; + } else if (io.KeyCtrl) { + UI->TimelinePercentOffset += Increment*Direction*0.3; + } else { + if (Direction == 1) { + UI->Y_TimelinePercentZoomed -= (UI->Y_TimelinePercentZoomed * Increment); + UI->Y_TimelinePercentOffset -= (UI->Y_TimelinePercentOffset * Increment) + Offset*Increment; + UI->TimelinePercentZoomed -= (UI->TimelinePercentZoomed * Increment); + UI->TimelinePercentOffset -= (UI->TimelinePercentOffset * Increment) + X_Offset*Increment; + } else { + UI->Y_TimelinePercentOffset = ((UI->Y_TimelinePercentOffset + Offset*Increment) / (1.0f - Increment)); + UI->Y_TimelinePercentZoomed = (UI->Y_TimelinePercentZoomed / (1.0f - Increment)); + UI->TimelinePercentOffset = ((UI->TimelinePercentOffset + X_Offset*Increment) / (1.0f - Increment)); + UI->TimelinePercentZoomed = (UI->TimelinePercentZoomed / (1.0f - Increment)); } + } + } + } + + // General timeline interaction + + ImGui::SetCursorScreenPos(TimelineAbsolutePos); + ImGui::InvisibleButton("TimelineMoving", TimelineSizeWithBorder, ImGuiButtonFlags_MouseButtonLeft | ImGuiButtonFlags_MouseButtonRight); + bool32 IsHovered = ImGui::IsItemHovered(); + bool32 IsActive = ImGui::IsItemActive(); + bool32 IsItemActivated = ImGui::IsItemActivated(); + bool32 IsItemDeactivated = ImGui::IsItemDeactivated(); + bool32 LeftClick = ImGui::IsMouseDown(ImGuiMouseButton_Left); + bool32 RightClick = ImGui::IsMouseDown(ImGuiMouseButton_Right); + + if (IsActive) { + if (LeftClick) { + if (io.KeyCtrl && IsActive) { + real32 LocalMousePos = ImGui::GetMousePos().x - TimelineStartingPos.x; + real32 ZoomRatio = LocalMousePos / UI->TimelineZoom; + File->CurrentFrame = (int32)(ZoomRatio + 0.5); State->UpdateFrame = true; State->UpdateKeyframes = true; - UI->DraggingKeyframeThreshold += -1*Increment*UI->TimelineZoom; + } else { + if (IsItemActivated) + { + if (!io.KeyShift) { + // DeselectAllKeyframes(&State); + // DeselectAllLayers(File, State); + } + UI->BoxStart = ImGui::GetMousePos(); + UI->BoxSelectActive = true; + } + if (ImGui::IsMouseDragging(ImGuiMouseButton_Left, -1) ) + { + UI->BoxEnd = ImGui::GetMousePos(); + draw_list->AddRectFilled(UI->BoxStart, UI->BoxEnd, + IM_COL32(0, 0, 200, 50)); + } + } + // Timeline zooming interaction + } else if (RightClick && IsActive) { + if (IsItemActivated) + { + real32 LocalMousePos = io.MousePos.x - WindowMinAbs.x - UI->TimelineSplit; + UI->TempZoomRatioTimeline = LocalMousePos / TimelineSize.x; + } + if (ImGui::IsMouseDragging(ImGuiMouseButton_Right, -1) ) + { + UI->TimelineZoom += io.MouseDelta.x; } } } - */ + if (IsItemDeactivated) { + UI->BoxStart = {0, 0}; + UI->BoxEnd = {0, 0}; + UI->BoxSelectActive = false; + if (!io.KeyShift) DeselectAllLayers(File, State); + } ImGui::EndChild(); - /// - - ImGui::BeginChild("Sidebar", SidebarSize, true, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoScrollbar); + ImGui::PopStyleVar(2); - ImGui::SetCursorScreenPos(SidebarStartingPos); +#endif - ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ItemSpacing); - ImGui::PushClipRect(SidebarAbsolutePos, SidebarAbsolutePos + SidebarSizeWithBorder, true); - ImGui::PopClipRect(); +#if 0 +// 0 for timeline keyframe, 1 for graph keyframe, 2 for left graph handle, 3 for right graph handle +static void +ImGui_KeyframeDragging(project_data *File, project_state *State, ui *UI, property_channel *Property, int32 b, ImGuiIO io, int16 Type) +{ + keyframe *Keyframe = KeyframeLookup(Property, b); + if (ImGui::IsItemActive()) { - /// Split size adjuster + if (!Keyframe->IsSelected && ImGui::IsItemActivated()) + { + if (!io.KeyShift) { + temp_keyframe_list Bad = GetSelectedKeyframes(File); + for (int i = 0; i < Bad.Amount; i++) + Bad.SelectedKeyframe[i]->IsSelected = false; + } + Keyframe->IsSelected = true; + State->RecentSelectionType = selection_keyframe; + } + if (ImGui::IsMouseDragging(ImGuiMouseButton_Left, -1)) + { + ImGui::SetMouseCursor(ImGuiMouseCursor_Hand); + if (Type == 0 || Type == 1) + { + UI->DraggingKeyframeThreshold += io.MouseDelta.x; + if (abs(UI->DraggingKeyframeThreshold) >= UI->TimelineZoom) { + int16 Increment = UI->DraggingKeyframeThreshold/UI->TimelineZoom; + // temp_keyframe_list Bad = GetSelectedKeyframes(File); + // for (int b = 0; b < Bad.Amount; b++) { + // keyframe *SelectedKeyframe = Bad.SelectedKeyframe[b]; + if (!(Keyframe->FrameNumber == 0 && Increment == -1)) { + Keyframe->FrameNumber += Increment; + CheckKeyframeSort(Property, Increment, b); + // SortAndCacheKeyframeAtFrame(SelectedKeyframe->FrameNumber, &File.LayerPTR[i]->Property[a], &Cache); + ClampSurroundingKeyframeHandles(Property, b); + } + // } + UI->DraggingKeyframeThreshold += -1*Increment*UI->TimelineZoom; + State->UpdateFrame = true; + State->UpdateKeyframes = true; + // Cache.Frame[File.CurrentFrame].Cached = false; + } + } + /* + if (Type != 0) + { + if (Type == 1) + { + real32 IncrementsPerPixel = (Property->LocalMaxVal.f - Property->LocalMinVal.f)/Property->GraphLength; + Keyframe->Value.f -= io.MouseDelta.y*IncrementsPerPixel; + CalculatePropertyMinMax(Property); + } + if (Type == 2) + { + Keyframe->TangentLeft.x += io.MouseDelta.x/UI->TimelineZoom; + Keyframe->TangentLeft.y -= io.MouseDelta.y; + ClampKeyframeHandles(Property, b, 0); + } + if (Type == 3) + { + Keyframe->TangentRight.x += io.MouseDelta.x/UI->TimelineZoom; + Keyframe->TangentRight.y -= io.MouseDelta.y; + ClampKeyframeHandles(Property, b, 1); + } + State->UpdateFrame = true; + State->UpdateKeyframes = true; + } + */ + } + } +} - ImGui::SetCursorScreenPos(ImVec2(WindowMinAbs.x + UI->TimelineSplit - TimelineBorderPadding.x, TimelineAbsolutePos.y)); - ImGui::InvisibleButton("##SplitMove", ImVec2(TimelineBorderPadding.x, SidebarSizeWithBorder.y), ImGuiButtonFlags_MouseButtonLeft); - if (ImGui::IsItemHovered()) { - ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeEW); +static void +ImGui_InteractSliderProperty(project_state *State, memory *Memory, property_channel *Property) +{ + ImGui::DragScalar(Property->Name, ImGuiDataType_Float, &Property->CurrentValue.f, + Property->ScrubVal.f, &Property->MinVal.f, &Property->MaxVal.f, "%f"); + if (ImGui::IsItemActivated()) { + State->InteractCache[0] = Property->CurrentValue.f; } - if (ImGui::IsItemActive() && ImGui::IsMouseDragging(ImGuiMouseButton_Left, -1)) - { - UI->TimelineSplit += io.MouseDelta.x; + if (ImGui::IsItemActive()) { + State->UpdateFrame = true; } + if (ImGui::IsItemDeactivatedAfterEdit()) { + if (ImGui::IsKeyPressed(ImGuiKey_Escape)) { + Property->CurrentValue.f = State->InteractCache[0]; + } else { + History_Entry_Commit(Memory, action_entry_default, "Tranforms interact"); + History_Action_Change(Memory, &Property->CurrentValue.f, &State->InteractCache[0], + &Property->CurrentValue.f, action_type_change_r32); + History_Entry_End(Memory); + } + State->UpdateFrame = true; + } +} +static void +ImGui_DebugUndoTree(project_data *File, memory *Memory) +{ + ImGui::SetNextWindowSize(ImVec2(200, 800)); + ImGui::Begin("undotree"); + for (int i = 0; i < Memory->Action.NumberOfEntries; i++) { + action_entry Entry = Memory->Action.Entry[i]; + bool32 CurrentPos = (i < Memory->Action.Index); + ImGui::MenuItem(Entry.Name, NULL, CurrentPos); + } + ImGui::End(); +} - ImGui::PopStyleVar(); - - ImGui::EndChild(); - ImGui::SameLine(); - - /// - - ImGui::BeginChild("Timeline", TimelineSize, true, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoScrollbar); - - ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0.0f, ItemSpacing.y)); +static void +ImGui_DebugMemoryViewer(project_data *File, memory *Memory) +{ + ImGui::SetNextWindowSize(ImVec2(800, 200)); + ImGui::Begin("memoryviewer"); + ImVec2 ViewportMin = ImGui::GetCursorScreenPos(); + ImVec2 ViewportScale = ImGui::GetContentRegionAvail(); + ImVec2 ViewportMax = ImVec2(ViewportMin.x + ViewportScale.x, ViewportMin.y + ViewportScale.y); - ImGui::SetCursorScreenPos(TimelineStartingPos); + memory_table *Table = &Memory->Slot[B_LoadedBitmaps]; - ImGui::PushClipRect(TimelineAbsolutePos, TimelineAbsolutePos + TimelineSizeWithBorder, true); - draw_list->PushClipRect(TimelineAbsolutePos, TimelineAbsolutePos + TimelineSizeWithBorder, true); + real32 TotalMB = Table->Size/1024/1024; + real32 LineAmount = 50; + real32 IncrementMB = TotalMB / LineAmount; + real32 ScreenIncrement = ViewportScale.x / LineAmount; - project_layer *Layer = File->Layer[0]; - property_channel *Property = &Layer->x; + real32 CubeHeight = ImGui::GetFontSize(); + real32 TotalHeight = CubeHeight*4; + ImDrawList* draw_list = ImGui::GetWindowDrawList(); + draw_list->AddRectFilled(ViewportMin, ViewportMax, IM_COL32(50, 50, 50, 255)); - real32 MaxVal_Y = -10000; - real32 MinVal_Y = 10000; - for (int b = 0; b < Property->NumberOfTotalKeyframes; b++) { - keyframe *Keyframe = KeyframeLookup(Property, b); - MaxVal_Y = (Keyframe->Value.f > MaxVal_Y) ? Keyframe->Value.f : MaxVal_Y; - MinVal_Y = (Keyframe->Value.f < MinVal_Y) ? Keyframe->Value.f : MinVal_Y; + for (float x = 0; x < LineAmount; x++) { + uint32 LineColor = IM_COL32(200, 200, 200, 40); + ImVec2 Min = ImVec2(ViewportMin.x + ScreenIncrement * x, ViewportMin.y); + ImVec2 Max = ImVec2(Min.x + 2, ViewportMax.y); + draw_list->AddLine(Min, Max, LineColor); } - - keyframe *FirstKeyframe = KeyframeLookup(Property, 0); - keyframe *LastKeyframe = KeyframeLookup(Property, Property->NumberOfTotalKeyframes - 1); - real32 MinVal_X = (Layer->BitmapInfo.FrameOffset + FirstKeyframe->FrameNumber); - real32 MaxVal_X = (Layer->BitmapInfo.FrameOffset + LastKeyframe->FrameNumber); - - UI->Y_MaxVal = MaxVal_Y; - UI->Y_MinVal = MinVal_Y; - - if (!UI->IsDragging) { - UI->Display_Y_MinVal = UI->Y_MinVal; - UI->Display_Y_MaxVal = UI->Y_MaxVal; + // CurrentPosition line + { + uint32 LineColor = IM_COL32(000, 100, 200, 200); + real32 CurPosMB = (real32)Table->CurrentPosition/1024/1024; + ImVec2 Min = ImVec2(ViewportMin.x + (CurPosMB/IncrementMB * ScreenIncrement), ViewportMin.y); + ImVec2 Max = ImVec2(Min.x + 2, ViewportMax.y); + draw_list->AddLine(Min, Max, LineColor); } - real32 Y_TimelinePercentZoomed = UI->Y_TimelinePercentZoomed; - real32 Y_TimelinePercentOffset = UI->Y_TimelinePercentOffset; - MaxVal_Y = UI->Display_Y_MaxVal; - MinVal_Y = UI->Display_Y_MinVal; + for (uint32 i = 0; i < Table->NumberOfPointers; i++) { + if (Memory->Bitmap[i].SourceOwner) { + void *DataStart = Memory->Bitmap[i].Data; + void *NextDataStart; + int pp = 1; + for (;;) { + if (Memory->Bitmap[i+pp].SourceOwner) { + NextDataStart = Memory->Bitmap[i+pp].Data; + break; + } + pp++; + } + uint64 BytesBetween = (uint8 *)NextDataStart - (uint8 *)DataStart; + source *Source = Memory->Bitmap[i].SourceOwner; + real32 Pos = Memory_NormalizedPosition(&File->Source[0], File->NumberOfSources, sizeof(source), Source); - DebugWatchVar("offset: ", &Y_TimelinePercentOffset, d_float); - DebugWatchVar("zoom: ", &Y_TimelinePercentZoomed, d_float); + ImVec4 col = ImColor::HSV(Pos, 0.3, 0.6, 1.0f); + uint64 BitmapSize = Bitmap_CalcTotalBytes(Source->Info.Width, Source->Info.Height, Source->Info.BytesPerPixel); + if (BitmapSize > BytesBetween && i != Table->NumberOfPointers - 1) { + col = ImColor::HSV(Pos, 1.0, 1.0, 1.0f); + } - real32 Ratio_Graph_X = (MaxVal_X - MinVal_X) / File->NumberOfFrames; - real32 TimelineZoomSize = TimelineSizeWithBorder.x / UI->TimelinePercentZoomed; - real32 TimelineMoveSize = TimelineSizeWithBorder.x * UI->TimelinePercentOffset / UI->TimelinePercentZoomed; - real32 Y_TimelineZoomSize = TimelineSizeWithBorder.y / Y_TimelinePercentZoomed; - real32 Y_TimelineMoveSize = TimelineSizeWithBorder.y * Y_TimelinePercentOffset / Y_TimelinePercentZoomed; + uint64 DataStart2 = (uint8 *)DataStart - (uint8 *)Table->Address; + real32 StartReal = ((real32)DataStart2/1024/1024); + ImVec2 Min = ImVec2(ViewportMin.x + (StartReal/IncrementMB * ScreenIncrement), ViewportMin.y + (ViewportScale.y / 2) - (TotalHeight / 2) + TotalHeight*Pos); + real32 SizeReal = ((real32)BitmapSize/1024/1024); + ImVec2 Size = ImVec2(SizeReal/IncrementMB * ScreenIncrement, CubeHeight); + ImGui::SetCursorScreenPos(Min); + ImGui::PushID(i); + ImGui::PushStyleColor(ImGuiCol_Button, col); + char buf2[256]; + sprintf(buf2, "%i##mempos", Memory->Bitmap[i].Frame); + ImGui::Button(buf2, Size); + ImGui::PopStyleColor(); + if (ImGui::IsItemHovered()) { + char buf[1024]; + sprintf(buf, "Source owner: %s\nSize: %.02f MB\n Frame number: %i\n Index: %i", + Memory->Bitmap[i].SourceOwner->Path, SizeReal, + Memory->Bitmap[i].Frame, i); + ImGui::BeginTooltip(); + ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f); + ImGui::TextUnformatted(buf); + ImGui::PopTextWrapPos(); + ImGui::EndTooltip(); + } + ImGui::PopID(); + ImVec2 Max = ImVec2(ViewportMax.x, Min.y + CubeHeight); + // draw_list->AddRectFilled(Min, Max, IM_COL32(200, 200, 200, 255)); + } + } + ImGui::SetCursorScreenPos(ImVec2(ViewportMin.x, ViewportMax.y - CubeHeight*2)); + char buf[1024]; + sprintf(buf, "Current index: %i\nAmount of pointers: %i", + Table->PointerIndex, Table->NumberOfPointers); + ImGui::Text(buf); - for (int b = 0; b < Property->NumberOfTotalKeyframes; b++) { - ImGui::PushID(b); + ImGui::End(); +} - keyframe *Keyframe = KeyframeLookup(Property, b); - // Only used for drawing the bezier. - keyframe *NextKeyframe = (b != Property->NumberOfTotalKeyframes - 1) ? KeyframeLookup(Property, b + 1) : NULL; +static bool32 FU; - real32 Increment_X = (real32)1 / File->NumberOfFrames; - real32 UI_FrameDistance = Increment_X*TimelineZoomSize; +static void +ImGui_TimelineIncrementDraw2(project_data *File, ImDrawList *draw_list, real32 MaxVal_Y, real32 MinVal_Y, + real32 Y_TimelinePercentZoomed, real32 Y_TimelinePercentOffset, + ImVec2 TimelineSizeWithBorder, ImVec2 TimelineAbsolutePos, bool32 IsText) +{ + uint32 LineColor = IM_COL32(200, 200, 200, 40); - int32 Keyframe_X = (Layer->BitmapInfo.FrameOffset + Keyframe->FrameNumber); - real32 Keyframe_Y = Keyframe->Value.f; + real32 TimelineZoomSize = TimelineSizeWithBorder.y / Y_TimelinePercentZoomed; + real32 TimelineMoveSize = TimelineSizeWithBorder.y * Y_TimelinePercentOffset / Y_TimelinePercentZoomed; - real32 Ratio_X_Mid = (real32)Keyframe_X / File->NumberOfFrames; - real32 Ratio_Y_Mid = (Keyframe_Y - MinVal_Y) / (MaxVal_Y - MinVal_Y); + Assert(TimelineZoomSize > 0.0f); - ImVec2 KeyframePos_Mid = ImVec2(TimelineAbsolutePos.x + TimelineMoveSize + Ratio_X_Mid*TimelineZoomSize, - TimelineAbsolutePos.y + Y_TimelineMoveSize + (1.0f - Ratio_Y_Mid)*Y_TimelineZoomSize); + real32 Fraction = (MinVal_Y > 0) ? MinVal_Y - (int32)MinVal_Y : (int32)MinVal_Y - MinVal_Y; + real32 x = (int32)MinVal_Y; + bool32 RightmostEdge = false; + + while (!RightmostEdge) { + real32 Ratio_Y = (x - MinVal_Y) / (MaxVal_Y - MinVal_Y); + ImVec2 Min = ImVec2(TimelineAbsolutePos.x, TimelineAbsolutePos.y + TimelineMoveSize + (1.0f - Ratio_Y)*TimelineZoomSize); + ImVec2 Max = ImVec2(TimelineAbsolutePos.x + TimelineSizeWithBorder.x, Min.y + 2); + if (Min.y > TimelineAbsolutePos.y) { + draw_list->AddLine(Min, Max, LineColor); + char buf2[6]; + sprintf(buf2, "%.2f", x); + draw_list->AddText(ImVec2(TimelineAbsolutePos.x, Min.y), IM_COL32(200, 200, 200, 130), buf2); + x += 1.0f; + } else { + RightmostEdge = true; + } + } + + x = (int32)MinVal_Y; + x -= 1.0f; + bool32 LeftmostEdge = false; + + while (!LeftmostEdge) { + real32 Ratio_Y = (x - MinVal_Y) / (MaxVal_Y - MinVal_Y); + ImVec2 Min = ImVec2(TimelineAbsolutePos.x, TimelineAbsolutePos.y + TimelineMoveSize + (1.0f - Ratio_Y)*TimelineZoomSize); + ImVec2 Max = ImVec2(TimelineAbsolutePos.x + TimelineSizeWithBorder.x, Min.y + 2); + if (Min.y < TimelineAbsolutePos.y + TimelineSizeWithBorder.y) { + draw_list->AddLine(Min, Max, LineColor); + char buf2[6]; + sprintf(buf2, "%.2f", x); + draw_list->AddText(ImVec2(TimelineAbsolutePos.x, Min.y), IM_COL32(200, 200, 200, 130), buf2); + x -= 1.0f; + } else { + LeftmostEdge = true; + } + } +} + +static void +ImGui_PropertiesPanel(project_data *File, project_state *State, ui *UI, memory *Memory, ImGuiIO io) +{ + if (State->MostRecentlySelectedLayer > -1) { + project_layer *Layer = File->Layer[State->MostRecentlySelectedLayer]; + char buf[256]; + sprintf(buf, "Properties: %s###Properties", Layer->Name); + ImGui::Begin(buf); + if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) + UI->FocusedWindow = focus_properties; + ImGui::Text("Transform"); + for (int h = 0; h < AmountOf(Layer->Property); h++) { + property_channel *Property = &Layer->Property[h]; + ImGui::PushID(Property); + if (ImGui::Button("K")) + Keyframe_Insert(Property, Memory, File->CurrentFrame, Property->CurrentValue.f); + ImGui::SameLine(); + ImGui_InteractSliderProperty(State, Memory, Property); + ImGui::PopID(); + } + for (int h = 0; h < Layer->NumberOfEffects; h++) { + effect *Effect = Layer->Effect[h]; + ImGui::PushID(Effect->ImGuiID); + if (FU && h == 0) { + int a = 0; + } + // this is stupid + if (Effect->IsActive) + ImGui::PushStyleColor(ImGuiCol_Button, ImGui::GetColorU32(ImGuiCol_ButtonHovered)); + else + ImGui::PushStyleColor(ImGuiCol_Button, ImGui::GetColorU32(ImGuiCol_Button)); + if (ImGui::Button("V")) { + History_Entry_Commit(Memory, action_entry_default, "Toggle effect"); + History_Action_Change_SwapBool(Memory, &Effect->IsActive); + History_Entry_End(Memory); + State->UpdateFrame = true; + } + ImGui::SameLine(); + ImGui::PopStyleColor(); + ImGui::Button("R"); ImGui::SameLine(); + ImGui::Selectable(Effect->Name, Effect->IsSelected); + // NOTE(fox): The logic for effect dragging has to be after we've + // done the UI for the rest of the effect! + real32 Effect_Top = ImGui::GetCursorScreenPos().y; + bool32 IsHovered = ImGui::IsItemHovered(); + bool32 IsActive = ImGui::IsItemActive(); + bool32 IsActivated = ImGui::IsItemActivated(); + bool32 IsDeactivated = ImGui::IsItemDeactivated(); + + if (Effect->DisplayType == standard) { + for (int i = 0; i < Effect->NumberOfProperties; i++) { + property_channel *Property = &Effect->Property[i]; + ImGui::PushID(Property); + if (Property->VarType == type_real) { + ImGui_InteractSliderProperty(State, Memory, Property); + } + if (Property->VarType == type_color) + if (ImGui::ColorEdit4("color 1", &Property->CurrentValue.f, ImGuiColorEditFlags_Float)) + State->UpdateFrame = true; + if (Property->VarType == type_blendmode) + { + uint32 *item_current_idx = (uint32 *)&Property->CurrentValue.blendmode; // Here we store our selection data as an index. + if (ImGui::BeginListBox("Blend mode")) + { + for (int n = 0; n < IM_ARRAYSIZE(BlendmodeNames); n++) + { + const bool is_selected = (*item_current_idx == n); + if (ImGui::Selectable(BlendmodeNames[n], is_selected)) { + *item_current_idx = n; + State->UpdateFrame = true; + } + + // Set the initial focus when opening the combo (scrolling + keyboard navigation focus) + if (is_selected) + ImGui::SetItemDefaultFocus(); + } + ImGui::EndListBox(); + } + } + ImGui::PopID(); + } + } else if (Effect->DisplayType == levels) { + source *Source = Layer->Source; + layer_bitmap_info *BitmapInfo = &Layer->BitmapInfo; + + if (!BitmapInfo->HistogramVals) { + uint64 Size = Bitmap_CalcUnpackedBytes(Source->Info.Width, Source->Info.Height, Source->Info.BytesPerPixel); + BitmapInfo->HistogramVals = AllocateMemory(Memory, (sizeof(uint32) * 5 * 256), P_MiscCache); + Bitmap_CalcHistogram(BitmapInfo->HistogramVals, BitmapInfo->BitmapBuffer, Source->Info.BytesPerPixel, Size); + } + + char *LevelsButtons[5] = { "All", "Red", "Green", "Blue", "Alpha" }; + for (int i = 0; i < 5; i++) { + if (ImGui::Button(LevelsButtons[i])) { + BitmapInfo->LevelsSelector = i; + } + if (i != 4) { ImGui::SameLine(); } + } + + real32 *Min, *Mid, *Max; + if (BitmapInfo->LevelsSelector == 0) { + Min = &Effect->Property[0].CurrentValue.f; + Mid = &Effect->Property[1].CurrentValue.f; + Max = &Effect->Property[2].CurrentValue.f; + } else { + Min = &Effect->Property[3].CurrentValue.col.E[BitmapInfo->LevelsSelector - 1]; + Mid = &Effect->Property[4].CurrentValue.col.E[BitmapInfo->LevelsSelector - 1]; + Max = &Effect->Property[5].CurrentValue.col.E[BitmapInfo->LevelsSelector - 1]; + } + + if (BitmapInfo->LevelsSelector == 0) { + ImGui::PushStyleColor(ImGuiCol_PlotHistogram, ImVec4(0.6f, 0.6f, 0.6f, 1.0f)); + ImGui::PushStyleColor(ImGuiCol_PlotHistogramHovered, ImVec4(0.6f, 0.6f, 0.6f, 1.0f)); + } else if (BitmapInfo->LevelsSelector == 1) { + ImGui::PushStyleColor(ImGuiCol_PlotHistogram, ImVec4(0.9f, 0.6f, 0.6f, 1.0f)); + ImGui::PushStyleColor(ImGuiCol_PlotHistogramHovered, ImVec4(0.9f, 0.6f, 0.6f, 1.0f)); + } else if (BitmapInfo->LevelsSelector == 2) { + ImGui::PushStyleColor(ImGuiCol_PlotHistogram, ImVec4(0.6f, 0.9f, 0.6f, 1.0f)); + ImGui::PushStyleColor(ImGuiCol_PlotHistogramHovered, ImVec4(0.6f, 0.9f, 0.6f, 1.0f)); + } else if (BitmapInfo->LevelsSelector == 3) { + ImGui::PushStyleColor(ImGuiCol_PlotHistogram, ImVec4(0.6f, 0.6f, 0.9f, 1.0f)); + ImGui::PushStyleColor(ImGuiCol_PlotHistogramHovered, ImVec4(0.6f, 0.6f, 0.9f, 1.0f)); + } else if (BitmapInfo->LevelsSelector == 4) { + ImGui::PushStyleColor(ImGuiCol_PlotHistogram, ImVec4(0.9f, 0.9f, 0.9f, 1.0f)); + ImGui::PushStyleColor(ImGuiCol_PlotHistogramHovered, ImVec4(0.9f, 0.9f, 0.9f, 1.0f)); + } + + float *values = (float *)BitmapInfo->HistogramVals + 256*BitmapInfo->LevelsSelector; + int values_count = 256; + int values_offset = 0; + float scale_min = FLT_MIN; + float scale_max = FLT_MAX; + ImVec2 graph_size = ImVec2(0, 250); + int stride = sizeof(float); + + // The default histogram is good enough for what we need. + ImGui::PlotHistogram("##histo", values, values_count, values_offset, NULL, scale_min, scale_max, graph_size, stride); + // TODO(fox): Figure out the proper way to represent these IDs. + if (ImGui::SliderLevels("##one", "##two", "three", Mid, Min, Max)) + State->UpdateFrame = true; + + ImGui::PopStyleColor(); + ImGui::PopStyleColor(); + + if (State->UpdateFrame) { + uint64 Size = Bitmap_CalcUnpackedBytes(Source->Info.Width, Source->Info.Height, Source->Info.BytesPerPixel); + Bitmap_CalcHistogram(BitmapInfo->HistogramVals, BitmapInfo->BitmapBuffer, Source->Info.BytesPerPixel, Size); + } + + ImGui::Button("K"); + } + ImGui::PopID(); + + if (IsActive) + { + if (!Effect->IsSelected && IsActivated) { + Effect->IsSelected ^= 1; + } + if (ImGui::IsMouseDragging(ImGuiMouseButton_Left, -1)) + { + real32 Effect_Bottom = ImGui::GetCursorScreenPos().y; + real32 Effect_Length = Effect_Bottom - Effect_Top; + ImGui::SetMouseCursor(ImGuiMouseCursor_Hand); + UI->DraggingEffectThreshold += io.MouseDelta.y; + if (abs(UI->DraggingEffectThreshold) >= Effect_Length) { + int16 Increment = UI->DraggingEffectThreshold/Effect_Length; + effect *Effect = Layer->Effect[1]; + Layer->Effect[1] = Layer->Effect[0]; + Layer->Effect[0] = Effect; + // History_Entry_Commit(Memory, action_entry_default, "Change effect order"); + // History_Entry_End(Memory); + FU = true; + UI->DraggingEffectThreshold += -1*Increment*Effect_Length; + State->UpdateFrame = true; + State->UpdateKeyframes = true; + } + } + } + if (FU) { + FU = false; + break; + } + } + } else { + char buf[256]; + sprintf(buf, "Properties: empty###Properties"); + ImGui::Begin(buf); + if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) + UI->FocusedWindow = focus_properties; + } + ImGui::End(); +} - ImGui::SetCursorScreenPos(KeyframePos_Mid); - ImGui::Button("##keyframe", ImVec2(FontHeight, FontHeight)); +static void +ImGui_Viewport(project_data File, project_state *State, ui *UI, memory *Memory, comp_buffer CompBuffer, + ImGuiIO io, GLuint textureID) +{ + bool open = true; + ImGui::Begin("Viewport", &open, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse); - if (ImGui::IsItemHovered() && ImGui::IsKeyPressed(ImGuiKey_R)) { - UI->TempVal = Keyframe->Value.f; - UI->TempVal_X = Keyframe->FrameNumber; - } + if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) + UI->FocusedWindow = focus_viewport; - if (ImGui::IsItemActivated()) { - UI->IsDragging = true; - UI->TempVal = Keyframe->Value.f; - UI->TempVal_X = Keyframe->FrameNumber; - } + // Primarily taken from the Custom Rendering section of the demo - if ((ImGui::IsItemActive() && ImGui::IsMouseDragging(ImGuiMouseButton_Left, -1))) - { - ImVec2 DragDelta = ImGui::GetMouseDragDelta(); - DragDelta = DragDelta + (ImVec2(UI->Wrap_X, UI->Wrap_Y) * TimelineSize); - DebugWatchVar("DragX", &DragDelta.x, d_float); - DebugWatchVar("DragY", &DragDelta.y, d_float); - DebugWatchVar("Wrap_X", &UI->Wrap_X, d_int); - DebugWatchVar("Wrap_Y", &UI->Wrap_Y, d_int); - real32 MouseDeltaRatio = -DragDelta.y / TimelineSizeWithBorder.y * Y_TimelinePercentZoomed; - ImVec2 Increment = ImVec2(DragDelta.x / UI_FrameDistance, ((MaxVal_Y - MinVal_Y) * MouseDeltaRatio)); + ImVec2 ViewportMin = ImGui::GetCursorScreenPos(); + ImVec2 ViewportScale = ImGui::GetContentRegionAvail(); + ViewportScale.y -= ImGui::GetFontSize()*0.5; + if (ViewportScale.x < 50.0f) ViewportScale.x = 50.0f; + if (ViewportScale.y < 50.0f) ViewportScale.y = 50.0f; + ImVec2 ViewportMax = ImVec2(ViewportMin.x + ViewportScale.x, ViewportMin.y + ViewportScale.y); - // The plus 0.5 * X_Direction is for making the frame jump happen - // when the cursor is between two frames rather than when passing one. - real32 X_Direction = (Increment.x > 0) ? fabsf(Increment.x) / Increment.x : 0; + if (UI->Initializing) { + UI->CompZoom = ImVec2(CompBuffer.Width, CompBuffer.Height); + UI->CompPos = ImVec2(ViewportMin.x + ((ViewportMax.x - ViewportMin.x)/2 - UI->CompZoom.x/2), + ViewportMin.y + ((ViewportMax.y - ViewportMin.y)/2 - UI->CompZoom.y/2)); + } - Keyframe->FrameNumber = UI->TempVal_X + (int32)(Increment.x + 0.5*X_Direction); - Keyframe->Value.f = UI->TempVal + Increment.y; + 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)); - if (io.KeyShift) { - bool32 RestrainAxis = (fabsf(DragDelta.x) > fabsf(DragDelta.y)); - if (RestrainAxis) { - Keyframe->Value.f = UI->TempVal; - } else { - Keyframe->FrameNumber = UI->TempVal_X; - } - } - ImGui_WrapMouse(UI, io.MousePos, TimelineAbsolutePos, TimelineAbsolutePos + TimelineSizeWithBorder); - } + // Actual composition texture + draw_list->PushClipRect(ViewportMin, ViewportMax, true); + draw_list->AddImage((void *)(intptr_t)textureID, ImVec2(UI->CompPos.x, UI->CompPos.y), + ImVec2(UI->CompPos.x + UI->CompZoom.x, UI->CompPos.y + UI->CompZoom.y)); + draw_list->PopClipRect(); - // TODO(fox): This is kind of a mess. I built the graph around the - // ratios of the keyframes/timeline to make the bars straightforward, - // meaning the position/offset have to be transformed into a new space - // when a new min/max value is reached. + // UI+interaction for layer + if (State->MostRecentlySelectedLayer > -1) { + project_layer *Layer = File.Layer[State->MostRecentlySelectedLayer]; + source *Source = Layer->Source; - if (ImGui::IsItemDeactivated()) { - if ((UI->TempVal >= MaxVal_Y) || Keyframe->Value.f == UI->Y_MaxVal) { - real32 Min = ((Ratio_Y_Mid <= 0.0f) && (UI->TempVal >= MaxVal_Y)) ? MinVal_Y : UI->Y_MinVal; - real32 RealRatio_Y = (UI->Y_MaxVal - UI->Y_MinVal) / (MaxVal_Y - MinVal_Y); - UI->Y_TimelinePercentZoomed = UI->Y_TimelinePercentZoomed / RealRatio_Y; - UI->Y_TimelinePercentOffset = (1.0f/RealRatio_Y + UI->Y_TimelinePercentOffset/RealRatio_Y - 1.0f); - } else if (UI->TempVal <= MinVal_Y || Keyframe->Value.f == UI->Y_MinVal) { - real32 RealRatio_Y = (UI->Y_MinVal - MinVal_Y) / (MaxVal_Y - MinVal_Y); - UI->Y_TimelinePercentOffset = UI->Y_TimelinePercentOffset / (1 - RealRatio_Y); - UI->Y_TimelinePercentZoomed = UI->Y_TimelinePercentZoomed / (1 - RealRatio_Y); - } - UI->IsDragging = false; - UI->Wrap_X = 0; - UI->Wrap_Y = 0; - } + // Anchor point UI + ImVec2 AUV = ImVec2(Layer->x.CurrentValue.f / CompBuffer.Width, Layer->y.CurrentValue.f / CompBuffer.Height); + ImVec2 ScreenAP = ImVec2(UI->CompPos.x + AUV.x * UI->CompZoom.x, UI->CompPos.y + AUV.y * UI->CompZoom.y); + draw_list->AddNgon(ScreenAP, 20, ImGui::GetColorU32(ImGuiCol_ScrollbarGrab), 8, 10.0f); - ImU32 col = ImGui::GetColorU32(ImGuiCol_ScrollbarGrab); + // Mask UI + if (Layer->NumberOfMasks) { + for (int i = 0; i < Layer->NumberOfMasks; i++) { + mask *Mask = &Layer->Mask[i]; + ImGui::PushID(i); - ImVec2 Handle_Pos[2] = {}; + real32 PointSize = 40; - if (Keyframe->Type == bezier) { - ImVec2 Handle_Ratio[2] = {}; + for (int p = 0; p < Mask->NumberOfPoints; p++) { - Handle_Ratio[0] = ImVec2((real32)(Keyframe_X + Keyframe->TangentLeft.x) / File->NumberOfFrames, - (Keyframe_Y + Keyframe->TangentLeft.y - MinVal_Y) / (MaxVal_Y - MinVal_Y)); - Handle_Ratio[1] = ImVec2((real32)(Keyframe_X + Keyframe->TangentRight.x) / File->NumberOfFrames, - (Keyframe_Y + Keyframe->TangentRight.y - MinVal_Y) / (MaxVal_Y - MinVal_Y)); + mask_point *Point0 = &Mask->Point[p]; + mask_point *Point1 = &Mask->Point[p+1]; + if (p+1 == Mask->NumberOfPoints) + Point1 = &Mask->Point[0]; - Handle_Pos[0] = ImVec2(TimelineAbsolutePos.x + TimelineMoveSize + Handle_Ratio[0].x*TimelineZoomSize, - TimelineAbsolutePos.y + Y_TimelineMoveSize + (1.0f - Handle_Ratio[0].y)*Y_TimelineZoomSize); - Handle_Pos[1] = ImVec2(TimelineAbsolutePos.x + TimelineMoveSize + Handle_Ratio[1].x*TimelineZoomSize, - TimelineAbsolutePos.y + Y_TimelineMoveSize + (1.0f - Handle_Ratio[1].y)*Y_TimelineZoomSize); + // NOTE(fox): I want to keep operations in local space under the v2 data + // type and operations in screen space under ImVec2. - draw_list->AddLine(KeyframePos_Mid, Handle_Pos[0], col, 1.0f); - draw_list->AddLine(KeyframePos_Mid, Handle_Pos[1], col, 1.0f); + v2 Point0_Pos = Point0->Pos; + v2 Point0_Pos_Left = Point0_Pos + Point0->TangentLeft; + v2 Point0_Pos_Right = Point0_Pos + Point0->TangentRight; - for (int i = 0; i < 2; i++) { - ImGui::SetCursorScreenPos(Handle_Pos[i]); - ImGui::Button((i == 0) ? "##keyframe_left" : "##keyframe_right", ImVec2(FontHeight, FontHeight)); - v2 *Tangent = (i == 0) ? &Keyframe->TangentLeft : &Keyframe->TangentRight; + v2 Point1_Pos = Point1->Pos; + v2 Point1_Pos_Left = Point1_Pos + Point1->TangentLeft; + v2 Point1_Pos_Right = Point1_Pos + Point1->TangentRight; - if (ImGui::IsItemActivated()) { - UI->IsDragging = true; - UI->TempVal_X = Tangent->x; - UI->TempVal = Tangent->y; - } + ImVec2 Point0_ScreenPos = Layer_LocalToScreenSpace(Layer, UI, CompBuffer, Point0_Pos); + ImVec2 Point0_ScreenPos_Left = Layer_LocalToScreenSpace(Layer, UI, CompBuffer, Point0_Pos_Left); + ImVec2 Point0_ScreenPos_Right = Layer_LocalToScreenSpace(Layer, UI, CompBuffer, Point0_Pos_Right); - if ((ImGui::IsItemActive() && ImGui::IsMouseDragging(ImGuiMouseButton_Left, -1))) - { - ImVec2 DragDelta = ImGui::GetMouseDragDelta(); - DragDelta = DragDelta + (ImVec2(UI->Wrap_X, UI->Wrap_Y) * TimelineSize); - ImVec2 MouseDeltaRatio = (ImVec2(1, -1) * DragDelta) / TimelineSizeWithBorder * ImVec2(UI->TimelinePercentZoomed / Ratio_Graph_X, Y_TimelinePercentZoomed); - real32 NewPos_X = ((MaxVal_X - MinVal_X) * MouseDeltaRatio.x); - real32 NewPos_Y = (io.KeyShift) ? 0 : ((MaxVal_Y - MinVal_Y) * MouseDeltaRatio.y); - *Tangent = V2(UI->TempVal_X, UI->TempVal) + V2(NewPos_X, NewPos_Y); - ImGui_WrapMouse(UI, io.MousePos, TimelineAbsolutePos, TimelineAbsolutePos + TimelineSizeWithBorder); - } + ImVec2 Point1_ScreenPos = Layer_LocalToScreenSpace(Layer, UI, CompBuffer, Point1_Pos); + ImVec2 Point1_ScreenPos_Left = Layer_LocalToScreenSpace(Layer, UI, CompBuffer, Point1_Pos_Left); + // ImVec2 Point1_ScreenPos_Right = Layer_LocalToScreenSpace(Layer, UI, CompBuffer, Point1_Pos_Right); - if (ImGui::IsItemDeactivated()) { - UI->IsDragging = false; - UI->Wrap_X = 0; - UI->Wrap_Y = 0; - } - } - } + ImGui::PushID(p); - if (NextKeyframe) { - real32 Ratio_X_2 = (real32)(Layer->BitmapInfo.FrameOffset + NextKeyframe->FrameNumber) / File->NumberOfFrames; - real32 Ratio_Y_2 = (NextKeyframe->Value.f - MinVal_Y) / (MaxVal_Y - MinVal_Y); + ImU32 col = ImGui::GetColorU32(ImGuiCol_ScrollbarGrab); - ImVec2 NextKeyframePos = ImVec2(TimelineAbsolutePos.x + TimelineMoveSize + Ratio_X_2*TimelineZoomSize, - TimelineAbsolutePos.y + Y_TimelineMoveSize + (1.0f - Ratio_Y_2)*Y_TimelineZoomSize); - draw_list->AddLine(KeyframePos_Mid, NextKeyframePos, col, 1.0f); - } + // The handle itself - ImGui::PopID(); - } + col = ImGui::GetColorU32(ImGuiCol_ButtonHovered); + if (Point0->HandleBezier) { + draw_list->AddNgon(Point0_ScreenPos_Left, 10, col, 8, 5.0f); + draw_list->AddNgon(Point0_ScreenPos_Right, 10, col, 8, 5.0f); + draw_list->AddLine(Point0_ScreenPos, Point0_ScreenPos_Left, col, 2.0f); + draw_list->AddLine(Point0_ScreenPos, Point0_ScreenPos_Right, col, 2.0f); + } - ImGui_TimelineIncrementDraw(File, UI, draw_list, TimelineSizeWithBorder, TimelineAbsolutePos, 0); - ImGui_TimelineIncrementDraw2(File, draw_list, MaxVal_Y, MinVal_Y, Y_TimelinePercentZoomed, Y_TimelinePercentOffset, TimelineSizeWithBorder, TimelineAbsolutePos, 0); + draw_list->AddNgon(Point0_ScreenPos, 10, col, 8, 5.0f); -#if DEBUG - draw_list->AddCircle(TimelineAbsolutePos + ImVec2(TimelineSizeWithBorder.x * 0.25, TimelineSizeWithBorder.y - 50), - 2, IM_COL32(200, 000, 200, 200), 16, 1); - draw_list->AddCircle(TimelineAbsolutePos + ImVec2(TimelineSizeWithBorder.x * 0.5, TimelineSizeWithBorder.y - 50), - 2, IM_COL32(200, 000, 200, 200), 16, 1); - draw_list->AddCircle(TimelineAbsolutePos + ImVec2(TimelineSizeWithBorder.x * 0.75, TimelineSizeWithBorder.y - 50), - 2, IM_COL32(200, 000, 200, 200), 16, 1); -#endif + int max = 1; - ImVec2 MouseDelta = io.MouseDelta / TimelineSize; + if (Point0->HandleBezier && Point0->IsSelected) { + max = 3; + } - real32 BarHandleSize = FontHeight; - real32 BarThickness = 50; - real32 BarMinZoom = 0.01; + for (int b = 0; b < max; b++) + { + ImGui::PushID(b); + if (b == 0) { + ImGui::SetCursorScreenPos(Point0_ScreenPos - ImVec2(PointSize/2, PointSize/2)); + } else if (b == 1) { + ImGui::SetCursorScreenPos(Point0_ScreenPos_Left - ImVec2(PointSize/2, PointSize/2)); + } else { + ImGui::SetCursorScreenPos(Point0_ScreenPos_Right - ImVec2(PointSize/2, PointSize/2)); + } - real32 BarH_Pos = -TimelineSizeWithBorder.x * UI->TimelinePercentOffset; - real32 BarH_Size = TimelineSizeWithBorder.x / (1 / UI->TimelinePercentZoomed); + ImGui::InvisibleButton("##point", ImVec2(PointSize, PointSize), ImGuiButtonFlags_MouseButtonLeft); - // I use "UI" to denote the size/position after clipping the bar so that it - // doesn't go out of bounds and the handles are always selectable at the edges. + if (ImGui::IsItemHovered()) { + ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeEW); + } + if (ImGui::IsItemHovered() && ImGui::IsKeyPressed(ImGuiKey_Backspace)) { + Mask_DeletePoint(Memory, Mask, p); + State->UpdateFrame = true; + } + if (ImGui::IsItemActivated() && b == 0) { + if (p == 0 && State->Pen.IsActive) { + History_Entry_Commit(Memory, action_entry_default, "Close mask path"); + History_Action_Change_SwapBool(Memory, &State->Pen.IsActive); + History_Action_Change_SwapBool(Memory, &Mask->IsClosed); + // State->Pen.IsActive = false; + // Mask->IsClosed = true; + History_Entry_End(Memory); + } else if (io.KeyAlt) { + History_Entry_Commit(Memory, action_entry_default, "Switch handles on point"); + History_Action_Change_SwapBool(Memory, &Point0->HandleBezier); + History_Entry_End(Memory); + } + Point0->IsSelected = true; + } + if (ImGui::IsItemActive()) { + ImVec2 MouseIncrement = io.MouseDelta * (ImVec2(CompBuffer.Width, CompBuffer.Height) / UI->CompZoom); + if (b == 0) { + Layer_CalcRotatedOffset(Layer, V2(MouseIncrement), V2(1, 1), + &Point0->Pos.x, &Point0->Pos.y); + } else if (b == 1) { + Layer_CalcRotatedOffset(Layer, V2(MouseIncrement), V2(1, 1), + &Point0->TangentLeft.x, &Point0->TangentLeft.y); + } else { + Layer_CalcRotatedOffset(Layer, V2(MouseIncrement), V2(1, 1), + &Point0->TangentRight.x, &Point0->TangentRight.y); + } + State->UpdateFrame = true; + } + ImGui::PopID(); + } - real32 BarH_Offset = Max(BarH_Pos, 0); + // The bezier path - real32 BarH_SizeUI = (BarH_Size + BarH_Pos > TimelineSizeWithBorder.x) ? - TimelineSizeWithBorder.x - BarH_Pos : - BarH_Size + (BarH_Pos - BarH_Offset); - if (BarH_Offset == 0 && BarH_SizeUI > TimelineSizeWithBorder.x) - BarH_SizeUI = TimelineSizeWithBorder.x; + if (Mask->NumberOfPoints == 1 || (p+1 == Mask->NumberOfPoints && !Mask->IsClosed)) { + ImGui::PopID(); + continue; + } + + ImU32 col2 = ImGui::GetColorU32(ImGuiCol_Button); - BarH_SizeUI = BarH_SizeUI - BarHandleSize*2; + if (Point0->HandleBezier && Point1->HandleBezier) { + draw_list->AddBezierCubic(Point0_ScreenPos, Point0_ScreenPos_Right, + Point1_ScreenPos_Left, Point1_ScreenPos, col2, 6.0f, 0); + } else if (Point0->HandleBezier) { + draw_list->AddBezierCubic(Point0_ScreenPos, Point0_ScreenPos_Right, + Point1_ScreenPos, Point1_ScreenPos, col2, 6.0f, 0); + } else if (Point1->HandleBezier) { + draw_list->AddBezierCubic(Point0_ScreenPos, Point0_ScreenPos, + Point1_ScreenPos_Left, Point1_ScreenPos, col2, 6.0f, 0); + } else { + draw_list->AddLine(Point0_ScreenPos, Point1_ScreenPos, col2, 6.0f); + } - BarH_SizeUI = Max(BarH_SizeUI, FontHeight*4); + if (Point0->HandleBezier && Point1->HandleBezier) { + if (ImGui::BezierInteractive(Point0_ScreenPos, Point0_ScreenPos_Right, + Point1_ScreenPos_Left, Point1_ScreenPos) && + State->Tool == tool_pen) + { - BarH_Offset = Min(BarH_Offset, TimelineSize.x - BarH_SizeUI - BarHandleSize*4); - ImVec2 BarH_PosUI = TimelineAbsolutePos + ImVec2(BarH_Offset, TimelineSize.y - BarThickness); - bool32 BarHeld = false; + // Using a button like this may be kinda janky, but it gives us access + // to all of ButtonBehavior and the ID system without having to write on top of ImGui's. + ImGui::SetCursorScreenPos(io.MousePos - ImVec2(5,5)); + ImGui::Button("maskbezier", ImVec2(10, 10)); - ImGui::SetCursorScreenPos(BarH_PosUI); - ImGui::Button("##scrollbarleft", ImVec2(BarHandleSize, BarThickness)); + if (ImGui::IsItemHovered()) { +#if DEBUG + // Code that basically mimics Mask_AddPointToCurve but visualized in screen space, for checking bugs. + v2 LayerPoint = Layer_ScreenSpaceToLocal(Layer, UI, CompBuffer, ViewportMin, io.MousePos); + real32 ratio = Bezier_CubicRatioOfPoint(Point0_Pos, Point0_Pos_Right, Point1_Pos_Left, Point1_Pos, LayerPoint); + draw_list->AddNgon(io.MousePos, 2, col, 8, 5.0f); + ImVec2 RatioLeft = ImGui::RatioToPoint(Point0_ScreenPos, Point0_ScreenPos_Right, ratio); + ImVec2 RatioRight = ImGui::RatioToPoint(Point1_ScreenPos_Left, Point1_ScreenPos, ratio); + ImVec2 RatioTop = ImGui::RatioToPoint(Point0_ScreenPos_Right, Point1_ScreenPos_Left, ratio); + ImVec2 TangentLeft = ImGui::RatioToPoint(RatioLeft, RatioTop, ratio); + ImVec2 TangentRight = ImGui::RatioToPoint(RatioTop, RatioRight, ratio); + draw_list->AddLine(RatioLeft, RatioTop, col, 2.0f); + draw_list->AddLine(RatioRight, RatioTop, col, 2.0f); + draw_list->AddLine(TangentLeft, TangentRight, col, 2.0f); +#endif + } + if (ImGui::IsItemActivated() && io.KeyCtrl) { + v2 LayerPoint = Layer_ScreenSpaceToLocal(Layer, UI, CompBuffer, ViewportMin, io.MousePos); + real32 ratio = Bezier_CubicRatioOfPoint(Point0_Pos, Point0_Pos_Right, Point1_Pos_Left, Point1_Pos, LayerPoint); + Mask_AddPointToCurve(Memory, Mask, p, ratio); + } + } + } else { + if (ImGui::LineInteractive(Point0_ScreenPos, Point1_ScreenPos) && + State->Tool == tool_pen) + { + ImGui::SetCursorScreenPos(io.MousePos - ImVec2(5,5)); + ImGui::Button("maskline", ImVec2(10, 10)); - if ((ImGui::IsItemActive() && ImGui::IsMouseDragging(ImGuiMouseButton_Left, -1))) - { - if ((UI->TimelinePercentZoomed - MouseDelta.x) > BarMinZoom) { - UI->TimelinePercentZoomed -= MouseDelta.x; - UI->TimelinePercentOffset -= MouseDelta.x; + if (ImGui::IsItemHovered()) { + draw_list->AddNgon(io.MousePos, 2, col, 8, 5.0f); + } + if (ImGui::IsItemActivated() && io.KeyCtrl) { + v2 LayerPoint = Layer_ScreenSpaceToLocal(Layer, UI, CompBuffer, ViewportMin, io.MousePos); + Mask_AddPointToLine(Mask, p, LayerPoint); + } + } + } + ImGui::PopID(); + } + ImGui::PopID(); + } } - BarHeld = true; } - ImGui::SetCursorScreenPos(BarH_PosUI + ImVec2(BarHandleSize, 0)); - ImGui::Button("##scrollbarhori", ImVec2(BarH_SizeUI, BarThickness)); + // Interactions for dragging and zooming + ImGui::SetCursorScreenPos(ViewportMin); - if (ImGui::IsItemActive() && ImGui::IsMouseDragging(ImGuiMouseButton_Left, -1)) - { - UI->TimelinePercentOffset -= MouseDelta.x; - BarHeld = true; + real32 ButtonSize = 20; + + for (int t = 0; t < tool_count; t++) { + ImGui::PushID(t); + bool32 Selected = (State->Tool == t); + if (ImGui::Selectable(ToolName[t], Selected, 0, ImVec2(ButtonSize*2, ButtonSize))) { + State->Tool = (tool)t; + } + ImGui::PopID(); } - ImGui::SetCursorScreenPos(BarH_PosUI + ImVec2(BarHandleSize, 0) + ImVec2(BarH_SizeUI, 0)); - ImGui::Button("##scrollbarright", ImVec2(BarHandleSize, BarThickness)); + ImGui::InvisibleButton("canvas", ViewportScale, ImGuiButtonFlags_MouseButtonLeft | ImGuiButtonFlags_MouseButtonRight); + bool32 IsHovered = ImGui::IsItemHovered(); + bool32 IsActive = ImGui::IsItemActive(); + bool32 IsActivated = ImGui::IsItemActivated(); + bool32 IsDeactivated = ImGui::IsItemDeactivated(); - if ((ImGui::IsItemActive() && ImGui::IsMouseDragging(ImGuiMouseButton_Left, -1))) + if (State->MostRecentlySelectedLayer > -1) { - if ((UI->TimelinePercentZoomed + MouseDelta.x) > BarMinZoom) { - UI->TimelinePercentZoomed += MouseDelta.x; + project_layer *Layer = File.Layer[State->MostRecentlySelectedLayer]; + if (Layer->NumberOfMasks == 0) { + if (IsActivated && ImGui::IsMouseDown(ImGuiMouseButton_Left)) { + if (State->Tool == tool_pen && !State->Pen.IsActive) { + State->Pen.IsActive = true; + Layer->NumberOfMasks++; + } + } + } + if (State->Pen.IsActive && !ImGui::IsKeyDown(ImGuiKey_Z)) { + if (IsActivated) { + v2 LayerPos = Layer_ScreenSpaceToLocal(Layer, UI, CompBuffer, ViewportMin, io.MousePos); + Mask_PushPoint(&Layer->Mask[Layer->NumberOfMasks-1], LayerPos); + } + if (IsActive) { + mask *Mask = &Layer->Mask[Layer->NumberOfMasks-1]; + mask_point *CurrentPoint = &Mask->Point[Mask->NumberOfPoints-1]; + v2 CompUV = ImGui_ScreenPointToCompUV(ViewportMin, UI->CompPos, UI->CompZoom, io.MousePos); + v2 LayerUV = CompUVToLayerUV(Layer, &CompBuffer, CompUV); + v2 LayerPos = LayerUV * V2(Layer->Source->Info.Width, Layer->Source->Info.Height); + v2 OffsetPos = CurrentPoint->Pos - LayerPos; + CurrentPoint->HandleBezier = true; + CurrentPoint->TangentRight = -OffsetPos; + CurrentPoint->TangentLeft = OffsetPos; + } + // Escape can be pressed to exit point-adding mode or to delete the + // mask if it was just created. + if (ImGui::IsKeyPressed(ImGuiKey_Escape)) { + mask *Mask = &Layer->Mask[Layer->NumberOfMasks-1]; + if (IsActive && Mask->NumberOfPoints == 1) { + Layer->NumberOfMasks--; + Mask->NumberOfPoints = 0; + State->Pen.IsActive = false; + } else { + History_Entry_Commit(Memory, action_entry_default, "Path adding exited"); + History_Action_Change_SwapBool(Memory, &State->Pen.IsActive); + History_Entry_End(Memory); + } + IsDeactivated = false; // just in case escape and mouse release happen simultaneously + } + mask *Mask = &Layer->Mask[Layer->NumberOfMasks-1]; + if (IsDeactivated) { + mask *Mask = &Layer->Mask[Layer->NumberOfMasks-1]; + if (Mask->NumberOfPoints == 1) { + // NOTE(fox): We have to use this slightly janky way of writing to + // the history tree since we can only create entries/actions once we + // know the user is committed to them. Might write an escapable + // entry mode if we do this often. + uint16 PreviousNumberOfMasks = Layer->NumberOfMasks - 1; + uint16 PreviousNumberOfPoints = Mask->NumberOfPoints - 1; + bool32 NotActive = false; + History_Entry_Commit(Memory, action_entry_default, "Create mask"); + History_Action_Change(Memory, &State->Pen.IsActive, &NotActive, + &State->Pen.IsActive, action_type_change_u32); + History_Action_Change(Memory, &Layer->NumberOfMasks, &PreviousNumberOfMasks, + &Layer->NumberOfMasks, action_type_change_u16); + History_Action_Change(Memory, &Mask->NumberOfPoints, &PreviousNumberOfPoints, + &Mask->NumberOfPoints, action_type_change_u16); + History_Entry_End(Memory); + } else { + uint16 PreviousNumberOfPoints = Mask->NumberOfPoints - 1; + mask_point *CurrentPoint = &Mask->Point[Mask->NumberOfPoints-1]; + History_Entry_Commit(Memory, action_entry_default, "Add point"); + History_Action_Change(Memory, &Mask->NumberOfPoints, &PreviousNumberOfPoints, + &Mask->NumberOfPoints, action_type_change_u16); + History_Action_StoreData(Memory, &CurrentPoint, sizeof(mask_point)); + History_Entry_End(Memory); + } + } + if (State->Tool != tool_pen) { + State->Pen.IsActive = false; + } } - BarHeld = true; } - if (BarHeld) { - ImGui_WrapMouse(UI, io.MousePos, TimelineAbsolutePos, TimelineAbsolutePos + TimelineSizeWithBorder, 1); + ImGui::OpenPopupOnItemClick("context", ImGuiPopupFlags_MouseButtonMiddle); + if (ImGui::BeginPopup("context")) { + if (ImGui::MenuItem("Scalar", NULL, false, InstructionMode != instruction_mode_scalar)) { InstructionMode = instruction_mode_scalar; State->UpdateFrame = true; } +#if ARM + if (ImGui::MenuItem("NEON", NULL, false, InstructionMode != instruction_mode_neon)) { InstructionMode = instruction_mode_neon; State->UpdateFrame = true; } +#else + if (ImGui::MenuItem("SSE", NULL, false, InstructionMode != instruction_mode_sse)) { InstructionMode = instruction_mode_sse; State->UpdateFrame = true; } + if (ImGui::MenuItem("AVX2", NULL, false, InstructionMode != instruction_mode_avx)) { InstructionMode = instruction_mode_avx; State->UpdateFrame = true; } +#endif + ImGui::EndPopup(); } - Assert(UI->TimelinePercentZoomed > BarMinZoom); - - real32 BarV_MaxSize = TimelineSizeWithBorder.y - BarThickness/2; - real32 BarV_Pos = -BarV_MaxSize * Y_TimelinePercentOffset; - real32 BarV_Size = BarV_MaxSize / (1 / Y_TimelinePercentZoomed); - BarV_Size = Max(BarV_Size, FontHeight*4); - - real32 BarV_Offset = Max(BarV_Pos, 0); - - real32 BarV_SizeUI = (BarV_Size + BarV_Pos > BarV_MaxSize) ? - BarV_MaxSize - BarV_Pos : - BarV_Size + (BarV_Pos - BarV_Offset); - - if (BarV_Offset == 0 && BarV_SizeUI > BarV_MaxSize) - BarV_SizeUI = BarV_MaxSize; - - BarV_SizeUI = BarV_SizeUI - BarHandleSize*2; - - BarV_SizeUI = Max(BarV_SizeUI, FontHeight*4); - - BarV_Offset = Min(BarV_Offset, BarV_MaxSize - BarV_SizeUI - BarHandleSize*4); - ImVec2 BarV_PosUI = TimelineAbsolutePos + ImVec2(TimelineSize.x - BarThickness, BarV_Offset); - BarHeld = false; - - ImGui::SetCursorScreenPos(BarV_PosUI); - ImGui::Button("##h-scrollbarleft", ImVec2(BarThickness, BarHandleSize)); - - if ((ImGui::IsItemActive() && ImGui::IsMouseDragging(ImGuiMouseButton_Left, -1))) + if (IsHovered && IsActivated && ImGui::IsMouseDown(ImGuiMouseButton_Left)) { - UI->Y_TimelinePercentZoomed -= MouseDelta.y; - UI->Y_TimelinePercentOffset -= MouseDelta.y; - BarHeld = true; - } - - ImGui::SetCursorScreenPos(BarV_PosUI + ImVec2(0, BarHandleSize)); - ImGui::Button("##h-scrollbar", ImVec2(BarThickness, BarV_SizeUI)); + // Point to zoom in on if Z is held + UI->TempZoomRatio = ImGui_ScreenPointToCompUV(ViewportMin, UI->CompPos, UI->CompZoom, io.MousePos); - if (ImGui::IsItemHovered() && io.MouseWheel) - { - UI->Y_TimelinePercentOffset -= io.MouseWheel/10; + // Layer selection + if (!ImGui::IsKeyDown(ImGuiKey_Z) || !State->Pen.IsActive) { + for (int i = File.NumberOfLayers - 1; i >= 0; i--) { + project_layer *Layer = File.Layer[i]; + if (!io.KeyShift) DeselectAllLayers(&File, State); + v2 LayerUV = CompUVToLayerUV(Layer, &CompBuffer, UI->TempZoomRatio); + if (TestUV(LayerUV) && !Layer->IsSelected) + { + SelectLayer(Layer, State, i); + break; + } + } + } } - if (ImGui::IsItemActive() && ImGui::IsMouseDragging(ImGuiMouseButton_Left, -1)) + if (IsActive && ImGui::IsMouseDragging(ImGuiMouseButton_Right, -1.0f)) { - UI->Y_TimelinePercentOffset -= MouseDelta.y; - BarHeld = true; - } - - ImGui::SetCursorScreenPos(BarV_PosUI + ImVec2(0, BarHandleSize) + ImVec2(0, BarV_SizeUI)); - ImGui::Button("##h-scrollbarright", ImVec2(BarThickness, BarHandleSize)); + UI->CompPos.x += io.MouseDelta.x; + UI->CompPos.y += io.MouseDelta.y; + } - if ((ImGui::IsItemActive() && ImGui::IsMouseDragging(ImGuiMouseButton_Left, -1))) + if (IsActive && ImGui::IsMouseDragging(ImGuiMouseButton_Left, -1.0f) && ImGui::IsKeyDown(ImGuiKey_Z)) { - UI->Y_TimelinePercentZoomed += MouseDelta.y; - BarHeld = true; + real32 Distance = io.MouseDelta.x + io.MouseDelta.y; + UI->CompZoom.x += (Distance)*(real32)CompBuffer.Width/CompBuffer.Height; + UI->CompZoom.y += (Distance); + UI->CompPos.x -= ((Distance)*(real32)CompBuffer.Width/CompBuffer.Height)*UI->TempZoomRatio.x; + UI->CompPos.y -= Distance*UI->TempZoomRatio.y; } - UI->Y_TimelinePercentZoomed = Max(UI->Y_TimelinePercentZoomed, 0.01); + ImGui::SetCursorScreenPos(ImVec2(ViewportMin.x, ViewportMin.y + ViewportScale.y - ImGui::GetFontSize()*1.5)); - if (BarHeld) { - ImGui_WrapMouse(UI, io.MousePos, TimelineAbsolutePos, TimelineAbsolutePos + TimelineSizeWithBorder, 2); + ImGui::Text("%.1f", 100.0f * (UI->CompZoom.x / CompBuffer.Width)); + if (State->MsgTime > 0) { + ImGui::SameLine(); + ImGui::SetCursorPosX((ViewportScale.x / 5)*4); + ImGui::Text(State->Msg); + State->MsgTime--; } - draw_list->PopClipRect(); - ImGui::PopClipRect(); - ImGui::PopStyleVar(); + ImGui::End(); +} - if (io.MouseWheel) { - // NOTE(fox): Change this if any other action is added when hovering over the bar area. - bool32 BarHovering_H = TestRectangle(TimelineAbsolutePos + ImVec2(0, TimelineSize.y - BarThickness), - TimelineAbsolutePos + ImVec2(TimelineSize.x, TimelineSize.y), - io.MousePos); - bool32 BarHovering_V = TestRectangle(TimelineAbsolutePos + ImVec2(TimelineSize.x - BarThickness, 0), - TimelineAbsolutePos + ImVec2(TimelineSize.x, TimelineSize.y), - io.MousePos); - if (BarHovering_H && io.MouseWheel) { - UI->TimelinePercentOffset -= io.MouseWheel/15; - } else if (BarHovering_V && io.MouseWheel) { - UI->Y_TimelinePercentOffset -= io.MouseWheel/15; - } else { - real32 Increment = 0.1; - bool32 Direction = (io.MouseWheel > 0) ? 1 : -1; - real32 Offset = (io.MousePos.y - (TimelineAbsolutePos.y + Y_TimelineMoveSize)) / Y_TimelineZoomSize; - real32 X_Offset = (io.MousePos.x - (TimelineAbsolutePos.x + TimelineMoveSize)) / TimelineZoomSize; - DebugWatchVar("X Offset", &X_Offset, d_float); - if (io.KeyShift) { - UI->Y_TimelinePercentOffset += Increment*Direction; - } else if (io.KeyCtrl) { - UI->TimelinePercentOffset += Increment*Direction*0.3; - } else { - if (Direction == 1) { - UI->Y_TimelinePercentZoomed -= (UI->Y_TimelinePercentZoomed * Increment); - UI->Y_TimelinePercentOffset -= (UI->Y_TimelinePercentOffset * Increment) + Offset*Increment; - UI->TimelinePercentZoomed -= (UI->TimelinePercentZoomed * Increment); - UI->TimelinePercentOffset -= (UI->TimelinePercentOffset * Increment) + X_Offset*Increment; - } else { - UI->Y_TimelinePercentOffset = ((UI->Y_TimelinePercentOffset + Offset*Increment) / (1.0f - Increment)); - UI->Y_TimelinePercentZoomed = (UI->Y_TimelinePercentZoomed / (1.0f - Increment)); - UI->TimelinePercentOffset = ((UI->TimelinePercentOffset + X_Offset*Increment) / (1.0f - Increment)); - UI->TimelinePercentZoomed = (UI->TimelinePercentZoomed / (1.0f - Increment)); +// 1 for left, 2 for right, 3 for both +static bool32 +ImGui_SlidingLayer(project_layer *Layer, real32 *DraggingThreshold, real32 Delta, int16 TimelineZoom, int16 Side) +{ + bool32 Result = 0; + if (ImGui::IsItemActivated()) + { + // if (Side & 1) + // Layer->StartFrame += Increment; + // if (Side & 2) + // Layer->EndFrame += Increment; + // if (Side == 3) { + } + if (ImGui::IsItemActive() && ImGui::IsMouseDragging(ImGuiMouseButton_Left, -1)) + { + ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeEW); + *DraggingThreshold += Delta; + if (abs(*DraggingThreshold) >= TimelineZoom) { + int16 Increment = *DraggingThreshold/TimelineZoom; + + if (Side & 1) + Layer->StartFrame += Increment; + if (Side & 2) + Layer->EndFrame += Increment; + if (Side == 3) { + // TODO(fox): Make frame offset in keyframes local! + IncrementKeyframesInLayer(Layer, Increment); + if (Layer->Source->SourceType == source_type_video) { + Layer->BitmapInfo.FrameOffset += Increment; } } + *DraggingThreshold += -1*Increment*TimelineZoom; } + Result = 1; } + return Result; +} - // General timeline interaction +static void +ImGui_File(project_data *File, project_state *State, memory *Memory, ui *UI, ImGuiIO io) +{ + ImGui::Begin("Files"); + ImGui::Text("Application average %.3f ms/frame (%.1f FPS)", 1000.0f / io.Framerate, io.Framerate); +#if DEBUG + if (State->DemoButton) { + if (ImGui::Button("Generate demo scene")) { + CreateDemoScene(File, State, Memory); + State->UpdateKeyframes = true; + State->UpdateFrame = true; + State->DemoButton = false; + } + } + if (State->GridButton) { + ImGui::SameLine(); + if (ImGui::Button("Generate square grid")) { + // CreateGrid(File, Memory); + State->UpdateKeyframes = true; + State->UpdateFrame = true; + State->GridButton = false; + } + } +#endif + ImGui::Text("Sources:"); + for (int i = 0; i < File->NumberOfSources; i++) { + bool32 Test = false; + if (File->SourceSelected == i) + Test = true; + ImGui::Selectable(File->Source[i].Path, Test); + if (ImGui::IsItemClicked()) + File->SourceSelected = i; + if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) + File->SourceSelected = i; + ImGui::OpenPopupOnItemClick("sourcecontext", ImGuiPopupFlags_MouseButtonRight); + } + if (ImGui::BeginPopup("sourcecontext")) { + if (ImGui::MenuItem("Create layer from source")) { + Layer_CreateFromSource(File, State, Memory, &File->Source[File->SourceSelected]); + } + ImGui::EndPopup(); + } + static char Input[1024]; + ImGui::InputTextWithHint("##sourceinput", "Input file path of source...", Input, STRING_SIZE); + if (ImGui::IsItemDeactivated() && ImGui::IsKeyPressed(ImGuiKey_Enter)) { + Source_Generate(File, State, Memory, Input); + } +#if DEBUG + for (int i = 0; i < Debug.Temp.WatchedProperties; i++) { + if (Debug.Temp.DebugPropertyType[i] == d_float) { + ImGui::Text("%s: %f", Debug.Temp.String[i], Debug.Temp.Val[i].f); + } else if (Debug.Temp.DebugPropertyType[i] == d_int) { + ImGui::Text("%s: %i", Debug.Temp.String[i], Debug.Temp.Val[i].i); + } else if (Debug.Temp.DebugPropertyType[i] == d_uint) { + ImGui::Text("%s: %u", Debug.Temp.String[i], Debug.Temp.Val[i].u); + } + } + Debug.Temp = {}; +#endif + ImGui::End(); +} - ImGui::SetCursorScreenPos(TimelineAbsolutePos); - ImGui::InvisibleButton("TimelineMoving", TimelineSizeWithBorder, ImGuiButtonFlags_MouseButtonLeft | ImGuiButtonFlags_MouseButtonRight); - bool32 IsHovered = ImGui::IsItemHovered(); - bool32 IsActive = ImGui::IsItemActive(); - bool32 IsItemActivated = ImGui::IsItemActivated(); - bool32 IsItemDeactivated = ImGui::IsItemDeactivated(); - bool32 LeftClick = ImGui::IsMouseDown(ImGuiMouseButton_Left); - bool32 RightClick = ImGui::IsMouseDown(ImGuiMouseButton_Right); +static void +ImGui_EffectsPanel(project_data *File, project_state *State, memory *Memory, ui *UI, ImGuiIO io) +{ + ImGui::Begin("Effects list", NULL); + if (State->RerouteEffects) { + ImGui::SetKeyboardFocusHere(); + State->RerouteEffects = 0; + } + int value_changed = ImGui::InputText("Effect name...", State->filter.InputBuf, IM_ARRAYSIZE(State->filter.InputBuf), + ImGuiInputTextFlags_CallbackCompletion, EffectConsoleCallback); - if (IsActive) { - if (LeftClick) { - if (io.KeyCtrl && IsActive) { - real32 LocalMousePos = ImGui::GetMousePos().x - TimelineStartingPos.x; - real32 ZoomRatio = LocalMousePos / UI->TimelineZoom; - File->CurrentFrame = (int32)(ZoomRatio + 0.5); - State->UpdateFrame = true; - State->UpdateKeyframes = true; - } else { - if (IsItemActivated) - { - if (!io.KeyShift) { - // DeselectAllKeyframes(&State); - // DeselectAllLayers(File, State); - } - UI->BoxStart = ImGui::GetMousePos(); - UI->BoxSelectActive = true; - } - if (ImGui::IsMouseDragging(ImGuiMouseButton_Left, -1) ) - { - UI->BoxEnd = ImGui::GetMousePos(); - draw_list->AddRectFilled(UI->BoxStart, UI->BoxEnd, - IM_COL32(0, 0, 200, 50)); + if (Hacko) { + if (!io.KeyShift) + EffectSel++; + else + EffectSel--; + Hacko = 0; + } + if (value_changed) { + State->filter.Build(); + EffectSel = -1; + } + // Enter conveniently deactivates the InputText field + if (ImGui::IsItemDeactivated() && ImGui::IsKeyPressed(ImGuiKey_Enter)) { + int32 p = 0; + for (int32 i = 0; i < AmountOf(EffectList); i++) { + if (State->filter.PassFilter(EffectList[i].Name)) { + if (EffectSel == p && State->MostRecentlySelectedLayer != -1) { + AddEffect(File->Layer[State->MostRecentlySelectedLayer], Memory, i); + State->UpdateFrame = true; } - } - // Timeline zooming interaction - } else if (RightClick && IsActive) { - if (IsItemActivated) - { - real32 LocalMousePos = io.MousePos.x - WindowMinAbs.x - UI->TimelineSplit; - UI->TempZoomRatioTimeline = LocalMousePos / TimelineSize.x; - } - if (ImGui::IsMouseDragging(ImGuiMouseButton_Right, -1) ) - { - UI->TimelineZoom += io.MouseDelta.x; + p++; } } + EffectSel = -1; } - if (IsItemDeactivated) { - UI->BoxStart = {0, 0}; - UI->BoxEnd = {0, 0}; - UI->BoxSelectActive = false; - if (!io.KeyShift) DeselectAllLayers(File, State); - } - - ImGui::EndChild(); - - ImGui::PopStyleVar(2); - - if (IsRectTouching(WindowMinAbs, WindowMaxAbs, io.MousePos, io.MousePos + 1)) { - real32 Multiplier = 16; - if (io.KeyCtrl && io.MouseWheel) { - real32 ZoomAmount = io.MouseWheel*Multiplier; - real32 LocalMousePos = ImGui::GetMousePos().x - TimelineStartingPos.x; - real32 ZoomRatio = LocalMousePos / UI->TimelineZoom; - if (UI->TimelineZoom + ZoomAmount > 0) { - UI->TimelineZoom += ZoomAmount; - UI->ScrollXOffset -= ZoomAmount*ZoomRatio; + int32 p = 0; + for (int32 i = 0; i < AmountOf(EffectList); i++) { + if (State->filter.PassFilter(EffectList[i].Name)) { + bool t = false; + if (EffectSel == p) { + t = true; } - } else if (io.KeyShift && io.MouseWheel) { - UI->ScrollXOffset += io.MouseWheel*Multiplier; - } else { - UI->ScrollXOffset += io.MouseWheelH*Multiplier; - UI->ScrollYOffset += io.MouseWheel*Multiplier; + ImGui::Selectable(EffectList[i].Name, &t); + if (ImGui::IsItemClicked()) { + if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left) && State->MostRecentlySelectedLayer != -1) { + AddEffect(File->Layer[State->MostRecentlySelectedLayer], Memory, i); + State->UpdateFrame = true; + } + } + p++; } } - - - ImGui::End(); } + static void ImGui_ProcessInputs(project_data *File, project_state *State, comp_buffer *CompBuffer, memory *Memory, ui *UI, ImGuiIO io) { diff --git a/prenderer.cpp b/prenderer.cpp index 9752663..b61290a 100644 --- a/prenderer.cpp +++ b/prenderer.cpp @@ -1,3 +1,437 @@ + +static void +Fallback_RenderLayer(transform_info T, void *OutputBuffer, rectangle RenderRegion); + +static void +RenderLayers(render_entry Entry) { + Fallback_RenderLayer(*(transform_info *)Entry.RenderData, Entry.OutputBuffer, Entry.RenderRegion); +#if 0 +#if ARM + Fallback_RenderLayer(RenderData->TransformInfo[i], RenderInfo->CompBuffer, RenderRegion); +#else + if (InstructionMode == instruction_mode_avx) + AVX2_RenderLayer(Entry.T, Entry.OutputBuffer, Entry.RenderRegion); + else + Fallback_RenderLayer(Entry.T, Entry.OutputBuffer, Entry.RenderRegion); +#endif +#endif +} + +static void +Renderer_Start(void *Data, void *OutputBuffer, rectangle RenderRegion) +{ + // CPU + Threading_BitmapOp(Data, OutputBuffer, RenderRegion); +} + +static void +Renderer_Check(bool32 *Test) +{ + // CPU + *Test = Threading_IsActive(); +} + +// Helper for working with different bit depths. +static render_byte_info +Bitmap_ByteInfo(uint32 BytesPerPixel) { + render_byte_info Byte = {}; + if (BytesPerPixel == 4) { + Byte.MaskPixel = 0xFF; + Byte.ByteOffset = 1; + Byte.Normalized = 1 / 255.0f; + Byte.Bits = 255; + } else if (BytesPerPixel == 8) { + Byte.MaskPixel = 0xFFFF; + Byte.ByteOffset = 2; + Byte.Normalized = 1 / 65535.0f; + Byte.Bits = 65535; + } else { + Byte.MaskPixel = 0xFFFFFFFF; + Byte.ByteOffset = 4; + Byte.Normalized = 1 / 4294967295.0f; + Byte.Bits = 4294967295; + Assert(0); + } + return Byte; +} + +static transform_info +Transform_Calculate(project_state *State, memory *Memory, project_data *File, block_layer *Layer, block_composition *Comp) +{ + transform_info TransformInfo; + int Width = 0, Height = 0, BytesPerPixel = 0; + if (!Layer->IsPrecomp) { + block_source *Source = (block_source *)Memory_Block_AddressAtIndex(Memory, F_Sources, Layer->Block_Source_Index); + Width = Source->Width; + Height = Source->Height; + BytesPerPixel = Source->BytesPerPixel; + } else { + block_composition *Precomp = (block_composition *)Memory_Block_AddressAtIndex(Memory, F_Precomps, Layer->Block_Source_Index); + Width = Precomp->Width; + Height = Precomp->Height; + BytesPerPixel = Precomp->BytesPerPixel; + } + + real32 Rotation = Layer->rotation.CurrentValue; + real32 X = Layer->x.CurrentValue; + real32 Y = Layer->y.CurrentValue; + real32 s = Layer->scale.CurrentValue; + blend_mode BlendMode = Layer->BlendMode; + + /* + state_file_ui *UI = &State->Context[State->CurrentFileIndex].UI; + if (UI->IsInteracting == true && UI->InteractMode == interact_transforms && Layer->IsSelected && !Layer->IsAdjustment) + Transform_ApplyInteractive(UI, &X, &Y, &Rotation, &s); + + if (UI->IsInteractingBlendmode == true && Layer->IsSelected) + BlendMode = UI->InteractBlendmode; + */ + + real32 Rad = (Rotation * (PI / 180)); + // v2 Scale = {Source->Raster.Width * s, Source->Raster.Height * s}; + + v2 XAxis = (Width * s)*V2(cos(Rad), sin(Rad)); + v2 YAxis = (Height * -s)*V2(sin(Rad), -cos(Rad)); + + real32 AnchorX = Layer->ax.CurrentValue; + real32 AnchorY = Layer->ay.CurrentValue; + + v2 Pos = {X, Y}; + v2 Origin = Pos - (XAxis * AnchorX) - (YAxis * AnchorY); + + real32 XLengthSq = 1.0f / LengthSq(XAxis); + real32 YLengthSq = 1.0f / LengthSq(YAxis); + + int32 MaxX = 0; + int32 MaxY = 0; + int32 MinX = Comp->Width; + int32 MinY = Comp->Height; + + v2 Points[4] = {Origin, Origin + XAxis, Origin + YAxis, Origin + XAxis + YAxis}; + for (int i = 0; i < 4; i++) { + if (Points[i].x < MinX) { MinX = Points[i].x; } + if (Points[i].y < MinY) { MinY = Points[i].y; } + if (Points[i].x > MaxX) { MaxX = Points[i].x; } + if (Points[i].y > MaxY) { MaxY = Points[i].y; } + } + + TransformInfo.XAxisPX = XLengthSq*XAxis.x; + TransformInfo.XAxisPY = XLengthSq*XAxis.y; + TransformInfo.YAxisPX = YLengthSq*YAxis.x; + TransformInfo.YAxisPY = YLengthSq*YAxis.y; + + TransformInfo.BufferWidth = Comp->Width; + TransformInfo.BufferHeight = Comp->Height; + TransformInfo.BufferBytesPerPixel = Comp->BytesPerPixel; + TransformInfo.BufferBits = Bitmap_ByteInfo(Comp->BytesPerPixel); + + TransformInfo.LayerWidth = Width; + TransformInfo.LayerHeight = Height; + TransformInfo.LayerBytesPerPixel = BytesPerPixel; + TransformInfo.LayerBits = Bitmap_ByteInfo(BytesPerPixel); + + TransformInfo.LayerOpacity = Layer->opacity.CurrentValue; + TransformInfo.BlendMode = BlendMode; + TransformInfo.OriginX = Origin.x; + TransformInfo.OriginY = Origin.y; + TransformInfo.BufferPitch = Comp->Width*Comp->BytesPerPixel; + TransformInfo.LayerPitch = Width*BytesPerPixel; + TransformInfo.ClipRect = {MinX - (MinX & 3), MinY, MaxX + 1, MaxY + 1}; + + TransformInfo.IsAdjustment = Layer->IsAdjustment; + + return TransformInfo; +} + +static void +Fallback_RenderLayer(transform_info T, void *OutputBuffer, rectangle RenderRegion) +{ + rectangle LayerBounds = ClipRectangle( T.ClipRect, RenderRegion); + + Assert(LayerBounds.Max.x <= T.BufferWidth); + Assert(LayerBounds.Max.y <= T.BufferHeight); + + for (int16 Y = LayerBounds.Min.y; Y < LayerBounds.Max.y; Y++) + { + real32 StartVectorY = (real32)Y - T.OriginY; + + for (int16 X = LayerBounds.Min.x; X < LayerBounds.Max.x; X++) + { + + real32 StartVectorX = X - T.OriginX; + real32 U = (StartVectorX * T.XAxisPX) + (StartVectorY * T.XAxisPY); + real32 V = (StartVectorX * T.YAxisPX) + (StartVectorY * T.YAxisPY); + + if (U < 1.0f && U >= 0.0f && V < 1.0f && V >= 0.0f) { + + real32 TexXFull = U * T.LayerWidth; + uint32 TexXInt = (uint32)TexXFull; + real32 TexX = TexXFull - TexXInt; + + real32 TexYFull = V * T.LayerHeight; + uint32 TexYInt = (uint32)TexYFull; + real32 TexY = TexYFull - TexYInt; + + real32 TexXInv = 1 - TexX; + real32 TexYInv = 1 - TexY; + real32 TexBothXInv = TexXInv * TexY; + real32 TexBothYInv = TexX * TexYInv; + real32 TexBoth = TexY * TexX; + real32 TexBothInv = TexXInv * TexYInv; + + uint32 XLookup, YLookup, PixelToSeek; + + uint16 LX = TexXInt; + uint16 LY = TexYInt; + uint16 LXPlus = Ceil(TexXInt+1, (uint32)T.LayerWidth - 1); + uint16 LYPlus = Ceil(TexYInt+1, (uint32)T.LayerHeight - 1); + + uint8 *TexPTR0 = ((uint8 *)T.SourceBuffer + ((uint16)T.LayerPitch * LY) + (LX * (uint16)T.LayerBytesPerPixel)); + uint8 *TexPTR1 = ((uint8 *)T.SourceBuffer + ((uint16)T.LayerPitch * LY) + (LXPlus * (uint16)T.LayerBytesPerPixel)); + uint8 *TexPTR2 = ((uint8 *)T.SourceBuffer + ((uint16)T.LayerPitch * LYPlus) + (LX * (uint16)T.LayerBytesPerPixel)); + uint8 *TexPTR3 = ((uint8 *)T.SourceBuffer + ((uint16)T.LayerPitch * LYPlus) + (LXPlus * (uint16)T.LayerBytesPerPixel)); + + uint32 PixelA = *(uint32 *)TexPTR0; + uint32 PixelB = *(uint32 *)TexPTR1; + uint32 PixelC = *(uint32 *)TexPTR2; + uint32 PixelD = *(uint32 *)TexPTR3; + + +#if 0 + real32 TexRA = (real32)(PixelA & 0xFF) * Normalized255; + real32 TexRB = (real32)(PixelB & 0xFF) * Normalized255; + real32 TexRC = (real32)(PixelC & 0xFF) * Normalized255; + real32 TexRD = (real32)(PixelD & 0xFF) * Normalized255; + + real32 TexGA = (real32)((PixelA >> 8) & 0xFF) * Normalized255; + real32 TexGB = (real32)((PixelB >> 8) & 0xFF) * Normalized255; + real32 TexGC = (real32)((PixelC >> 8) & 0xFF) * Normalized255; + real32 TexGD = (real32)((PixelD >> 8) & 0xFF) * Normalized255; + + real32 TexBA = (real32)((PixelA >> 16) & 0xFF) * Normalized255; + real32 TexBB = (real32)((PixelB >> 16) & 0xFF) * Normalized255; + real32 TexBC = (real32)((PixelC >> 16) & 0xFF) * Normalized255; + real32 TexBD = (real32)((PixelD >> 16) & 0xFF) * Normalized255; + + real32 TexAA = (real32)((PixelA >> 24) & 0xFF) * Normalized255; + real32 TexAB = (real32)((PixelB >> 24) & 0xFF) * Normalized255; + real32 TexAC = (real32)((PixelC >> 24) & 0xFF) * Normalized255; + real32 TexAD = (real32)((PixelD >> 24) & 0xFF) * Normalized255; +#else + real32 TexRA = (real32)(*(uint32 *)(TexPTR0 + T.LayerBits.ByteOffset * 0) & T.LayerBits.MaskPixel) * T.LayerBits.Normalized; + real32 TexGA = (real32)(*(uint32 *)(TexPTR0 + T.LayerBits.ByteOffset * 1) & T.LayerBits.MaskPixel) * T.LayerBits.Normalized; + real32 TexBA = (real32)(*(uint32 *)(TexPTR0 + T.LayerBits.ByteOffset * 2) & T.LayerBits.MaskPixel) * T.LayerBits.Normalized; + real32 TexAA = (real32)(*(uint32 *)(TexPTR0 + T.LayerBits.ByteOffset * 3) & T.LayerBits.MaskPixel) * T.LayerBits.Normalized; + + real32 TexRB = (real32)(*(uint32 *)(TexPTR1 + T.LayerBits.ByteOffset * 0) & T.LayerBits.MaskPixel) * T.LayerBits.Normalized; + real32 TexGB = (real32)(*(uint32 *)(TexPTR1 + T.LayerBits.ByteOffset * 1) & T.LayerBits.MaskPixel) * T.LayerBits.Normalized; + real32 TexBB = (real32)(*(uint32 *)(TexPTR1 + T.LayerBits.ByteOffset * 2) & T.LayerBits.MaskPixel) * T.LayerBits.Normalized; + real32 TexAB = (real32)(*(uint32 *)(TexPTR1 + T.LayerBits.ByteOffset * 3) & T.LayerBits.MaskPixel) * T.LayerBits.Normalized; + + real32 TexRC = (real32)(*(uint32 *)(TexPTR2 + T.LayerBits.ByteOffset * 0) & T.LayerBits.MaskPixel) * T.LayerBits.Normalized; + real32 TexGC = (real32)(*(uint32 *)(TexPTR2 + T.LayerBits.ByteOffset * 1) & T.LayerBits.MaskPixel) * T.LayerBits.Normalized; + real32 TexBC = (real32)(*(uint32 *)(TexPTR2 + T.LayerBits.ByteOffset * 2) & T.LayerBits.MaskPixel) * T.LayerBits.Normalized; + real32 TexAC = (real32)(*(uint32 *)(TexPTR2 + T.LayerBits.ByteOffset * 3) & T.LayerBits.MaskPixel) * T.LayerBits.Normalized; + + real32 TexRD = (real32)(*(uint32 *)(TexPTR3 + T.LayerBits.ByteOffset * 0) & T.LayerBits.MaskPixel) * T.LayerBits.Normalized; + real32 TexGD = (real32)(*(uint32 *)(TexPTR3 + T.LayerBits.ByteOffset * 1) & T.LayerBits.MaskPixel) * T.LayerBits.Normalized; + real32 TexBD = (real32)(*(uint32 *)(TexPTR3 + T.LayerBits.ByteOffset * 2) & T.LayerBits.MaskPixel) * T.LayerBits.Normalized; + real32 TexAD = (real32)(*(uint32 *)(TexPTR3 + T.LayerBits.ByteOffset * 3) & T.LayerBits.MaskPixel) * T.LayerBits.Normalized; +#if 0 + for (int i = 0; i < 50; i++) { + real32 TexRA = (real32)(*(uint32 *)(TexPTR0 + T.LayerBits.ByteOffset * 0) & T.LayerBits.MaskPixel) * T.LayerBits.Normalized; + real32 TexGA = (real32)(*(uint32 *)(TexPTR0 + T.LayerBits.ByteOffset * 1) & T.LayerBits.MaskPixel) * T.LayerBits.Normalized; + real32 TexBA = (real32)(*(uint32 *)(TexPTR0 + T.LayerBits.ByteOffset * 2) & T.LayerBits.MaskPixel) * T.LayerBits.Normalized; + real32 TexAA = (real32)(*(uint32 *)(TexPTR0 + T.LayerBits.ByteOffset * 3) & T.LayerBits.MaskPixel) * T.LayerBits.Normalized; + + real32 TexRB = (real32)(*(uint32 *)(TexPTR1 + T.LayerBits.ByteOffset * 0) & T.LayerBits.MaskPixel) * T.LayerBits.Normalized; + real32 TexGB = (real32)(*(uint32 *)(TexPTR1 + T.LayerBits.ByteOffset * 1) & T.LayerBits.MaskPixel) * T.LayerBits.Normalized; + real32 TexBB = (real32)(*(uint32 *)(TexPTR1 + T.LayerBits.ByteOffset * 2) & T.LayerBits.MaskPixel) * T.LayerBits.Normalized; + real32 TexAB = (real32)(*(uint32 *)(TexPTR1 + T.LayerBits.ByteOffset * 3) & T.LayerBits.MaskPixel) * T.LayerBits.Normalized; + + real32 TexRC = (real32)(*(uint32 *)(TexPTR2 + T.LayerBits.ByteOffset * 0) & T.LayerBits.MaskPixel) * T.LayerBits.Normalized; + real32 TexGC = (real32)(*(uint32 *)(TexPTR2 + T.LayerBits.ByteOffset * 1) & T.LayerBits.MaskPixel) * T.LayerBits.Normalized; + real32 TexBC = (real32)(*(uint32 *)(TexPTR2 + T.LayerBits.ByteOffset * 2) & T.LayerBits.MaskPixel) * T.LayerBits.Normalized; + real32 TexAC = (real32)(*(uint32 *)(TexPTR2 + T.LayerBits.ByteOffset * 3) & T.LayerBits.MaskPixel) * T.LayerBits.Normalized; + + real32 TexRD = (real32)(*(uint32 *)(TexPTR3 + T.LayerBits.ByteOffset * 0) & T.LayerBits.MaskPixel) * T.LayerBits.Normalized; + real32 TexGD = (real32)(*(uint32 *)(TexPTR3 + T.LayerBits.ByteOffset * 1) & T.LayerBits.MaskPixel) * T.LayerBits.Normalized; + real32 TexBD = (real32)(*(uint32 *)(TexPTR3 + T.LayerBits.ByteOffset * 2) & T.LayerBits.MaskPixel) * T.LayerBits.Normalized; + real32 TexAD = (real32)(*(uint32 *)(TexPTR3 + T.LayerBits.ByteOffset * 3) & T.LayerBits.MaskPixel) * T.LayerBits.Normalized; + } +#endif +#endif + + real32 R_Col = (TexBothInv * TexRA) + (TexBothYInv * TexRB) + + (TexBothXInv * TexRC) + (TexBoth * TexRD); + real32 G_Col = (TexBothInv * TexGA) + (TexBothYInv * TexGB) + + (TexBothXInv * TexGC) + (TexBoth * TexGD); + real32 B_Col = (TexBothInv * TexBA) + (TexBothYInv * TexBB) + + (TexBothXInv * TexBC) + (TexBoth * TexBD); + real32 A_Col = (TexBothInv * TexAA) + (TexBothYInv * TexAB) + + (TexBothXInv * TexAC) + (TexBoth * TexAD); + + real32 LayerAlpha = A_Col * T.LayerOpacity; + + real32 R_Blend = R_Col; + real32 G_Blend = G_Col; + real32 B_Blend = B_Col; + real32 A_Blend = A_Col; + + uint8 *DestPixel =((uint8 *)OutputBuffer + ((uint16)Y * (uint16)T.BufferPitch) + ((uint16)X * (uint16)T.BufferBytesPerPixel)); + + uint32 *R_DestAddress = (uint32 *)(DestPixel + T.BufferBits.ByteOffset * 0); + uint32 *G_DestAddress = (uint32 *)(DestPixel + T.BufferBits.ByteOffset * 1); + uint32 *B_DestAddress = (uint32 *)(DestPixel + T.BufferBits.ByteOffset * 2); + uint32 *A_DestAddress = (uint32 *)(DestPixel + T.BufferBits.ByteOffset * 3); + + if (LayerAlpha != 1.0f || T.BlendMode != blend_normal) { + + real32 R_Dest = (real32)(*R_DestAddress & T.BufferBits.MaskPixel) * T.BufferBits.Normalized; + real32 G_Dest = (real32)(*G_DestAddress & T.BufferBits.MaskPixel) * T.BufferBits.Normalized; + real32 B_Dest = (real32)(*B_DestAddress & T.BufferBits.MaskPixel) * T.BufferBits.Normalized; + real32 A_Dest = (real32)(*A_DestAddress & T.BufferBits.MaskPixel) * T.BufferBits.Normalized; + + switch (T.BlendMode) + { + case blend_normal: + { + } break; + case blend_multiply: + { + R_Blend = R_Dest * R_Col; + G_Blend = G_Dest * G_Col; + B_Blend = B_Dest * B_Col; + } break; + case blend_colorburn: + { + // NOTE(fox): Padding to prevent actual crashing from zero division + R_Blend = 1.0f - ((1.0f - R_Dest) / (R_Col + 0.001f)); + G_Blend = 1.0f - ((1.0f - G_Dest) / (G_Col + 0.001f)); + B_Blend = 1.0f - ((1.0f - B_Dest) / (B_Col + 0.001f)); + } break; + case blend_linearburn: + { + R_Blend = (R_Dest + R_Col) - 1.0f; + G_Blend = (G_Dest + G_Col) - 1.0f; + B_Blend = (B_Dest + B_Col) - 1.0f; + } break; + case blend_add: + { + R_Blend = R_Dest + R_Col; + G_Blend = G_Dest + G_Col; + B_Blend = B_Dest + B_Col; + } break; + case blend_screen: + { + R_Blend = 1.0f - ((1.0f - R_Dest) * (1.0f - R_Col)); + G_Blend = 1.0f - ((1.0f - G_Dest) * (1.0f - G_Col)); + B_Blend = 1.0f - ((1.0f - B_Dest) * (1.0f - B_Col)); + } break; + case blend_overlay: + { + if (R_Dest < 0.5) { + R_Blend = 2.0f * R_Dest * R_Col; + } else { + R_Blend = 1.0f - (2.0f * (1.0f - R_Dest) * (1.0f - R_Col)); + } + if (G_Dest < 0.5) { + G_Blend = 2.0f * G_Dest * G_Col; + } else { + G_Blend = 1.0f - (2.0f * (1.0f - G_Dest) * (1.0f - G_Col)); + } + if (B_Dest < 0.5) { + B_Blend = 2.0f * B_Dest * B_Col; + } else { + B_Blend = 1.0f - (2.0f * (1.0f - B_Dest) * (1.0f - B_Col)); + } + } break; + case blend_softlight: + { + // using Pegtop's equation + R_Blend = ((1.0f - R_Col * 2) * R_Dest * R_Dest) + (R_Col * 2 * R_Dest); + G_Blend = ((1.0f - G_Col * 2) * G_Dest * G_Dest) + (G_Col * 2 * G_Dest); + B_Blend = ((1.0f - B_Col * 2) * B_Dest * B_Dest) + (B_Col * 2 * B_Dest); + } break; + case blend_hardlight: + { + if (R_Dest > 0.5) { + R_Blend = 2.0f * R_Dest * R_Col; + } else { + R_Blend = 1.0f - (2.0f * (1.0f - R_Dest) * (1.0f - R_Col)); + } + if (G_Dest > 0.5) { + G_Blend = 2.0f * G_Dest * G_Col; + } else { + G_Blend = 1.0f - (2.0f * (1.0f - G_Dest) * (1.0f - G_Col)); + } + if (B_Dest > 0.5) { + B_Blend = 2.0f * B_Dest * B_Col; + } else { + B_Blend = 1.0f - (2.0f * (1.0f - B_Dest) * (1.0f - B_Col)); + } + } break; + case blend_subtract: + { + R_Blend = R_Dest - R_Col; + G_Blend = G_Dest - G_Col; + B_Blend = B_Dest - B_Col; + } break; + case blend_divide: + { + R_Blend = R_Dest / (R_Col + 0.001f); + G_Blend = G_Dest / (G_Col + 0.001f); + B_Blend = B_Dest / (B_Col + 0.001f); + } break; + case blend_difference: + { + if (R_Col - R_Dest > 0) { + R_Blend = R_Col - R_Dest; + } else { + R_Blend = R_Dest - R_Col; + } + if (G_Col - G_Dest > 0) { + G_Blend = G_Col - G_Dest; + } else { + G_Blend = G_Dest - G_Col; + } + if (B_Col - B_Dest > 0) { + B_Blend = B_Col - B_Dest; + } else { + B_Blend = B_Dest - B_Col; + } + } break; + } + + R_Blend = (R_Dest * (1.0f - LayerAlpha)) + (R_Blend * LayerAlpha); + G_Blend = (G_Dest * (1.0f - LayerAlpha)) + (G_Blend * LayerAlpha); + B_Blend = (B_Dest * (1.0f - LayerAlpha)) + (B_Blend * LayerAlpha); + + if (T.BlendMode == blend_normal) + A_Blend = A_Dest + LayerAlpha; + else + A_Blend = A_Dest; + } + + uint32 R_Out = (uint32)(Normalize(R_Blend) * T.BufferBits.Bits); + uint32 G_Out = (uint32)(Normalize(G_Blend) * T.BufferBits.Bits); + uint32 B_Out = (uint32)(Normalize(B_Blend) * T.BufferBits.Bits); + uint32 A_Out = (uint32)(Normalize(A_Blend) * T.BufferBits.Bits); + + *R_DestAddress = (*R_DestAddress & ~T.BufferBits.MaskPixel) | R_Out; + *G_DestAddress = (*G_DestAddress & ~T.BufferBits.MaskPixel) | G_Out; + *B_DestAddress = (*B_DestAddress & ~T.BufferBits.MaskPixel) | B_Out; + *A_DestAddress = (*A_DestAddress & ~T.BufferBits.MaskPixel) | A_Out; + // *R_DestAddress = 255; + // *G_DestAddress = 255; + // *B_DestAddress = 255; + // *A_DestAddress = 255; + } + } + } +} + +#if 0 static void Layer_CalcRotatedOffset(project_layer *Layer, v2 Increment, v2 Divisor, real32 *ValueX, real32 *ValueY) { @@ -1380,3 +1814,4 @@ Fallback_RenderLayer(transform_info T, comp_buffer *Buffer, rectangle RenderRegi } } } +#endif diff --git a/threading.cpp b/threading.cpp index ff891a6..84e5463 100644 --- a/threading.cpp +++ b/threading.cpp @@ -1,58 +1,83 @@ - -static int -MainRenderer(void *ptr) -{ - for(;;) - { - SDL_SemWait(Semaphore); - } -} - -#if 0 static void -PushRect(rectangle RenderRegion) -{ - uint32 Q = SDL_AtomicGet(&QueuedEntries); - render_entry *Entry = Entries + Q; - Entry->RenderRegion = RenderRegion; - SDL_AtomicAdd(&QueuedEntries, 1); - SDL_SemPost(Semaphore); -} - -static void -RenderLayers(render_queue *RenderInfo, rectangle RenderRegion); +RenderLayers(render_entry Entry); static bool32 -CheckQueue(render_queue RenderInfo, uint16 Index) +CheckQueue(uint16 Index) { bool32 Result = 0; uint32 Q = SDL_AtomicGet(&QueuedEntries); uint32 C = SDL_AtomicGet(&CurrentEntry); if (Q > C) - { - if (SDL_AtomicCAS(&CurrentEntry, C, C + 1)) { - render_entry *Entry = Entries + C; - Assert(Entry->RenderRegion.Max.x != 0); - RenderLayers(&RenderInfo, Entry->RenderRegion); - // printf("(FINISHED) Thread %i, region X%i Y%i\n", Index, Entry->RenderRegion.Min.x/240, Entry->RenderRegion.Min.y/135); - SDL_AtomicAdd(&CompletedEntries, 1); - Result = 1; - } + { + if (SDL_AtomicCAS(&CurrentEntry, C, C + 1)) { + // printf("C: %i START: F%i Thread %i, region X%i Y%i\n", C, Entry->FrameNumber, Index, Entry->RenderRegion.Min.x, Entry->RenderRegion.Min.y); + RenderLayers(Entries[C]); + // printf("END: F%i Thread %i, region X%i Y%i\n", Entry->FrameNumber, Index, Entry->RenderRegion.Min.x, Entry->RenderRegion.Min.y); + SDL_AtomicAdd(&CompletedEntries, 1); + Result = 1; } + } return Result; } static int TestThread(void *ptr) { - thread_info *ThreadInfo = (thread_info *)ptr; - uint16 Index = ThreadInfo->Index; + uint16 Index = *(uint16 *)ptr; for(;;) { - if (!CheckQueue(*ThreadInfo->RenderInfo, Index)) + if (!CheckQueue(Index)) { SDL_SemWait(Semaphore); } } } -#endif + +static bool32 +Threading_IsActive() +{ + uint32 C = SDL_AtomicGet(&CompletedEntries); + Assert(C < 17); + return (C == 16) ? false : true; +} + +static void +Threading_BitmapOp(void *Data, void *OutputBuffer, rectangle InitialRenderRegion) +{ + uint16 TileWidth = (InitialRenderRegion.Max.x - InitialRenderRegion.Min.x) / 4; + uint16 TileHeight = (InitialRenderRegion.Max.y - InitialRenderRegion.Min.y) / 4; + + SDL_AtomicSet(&QueuedEntries, 0); + SDL_AtomicSet(&CurrentEntry, 0); + SDL_AtomicSet(&CompletedEntries, 0); + + for (int y = 0; y < 4; y++) { + for (int x = 0; x < 4; x++) { + // if (x == y) { + rectangle RenderRegion = { TileWidth*x, TileHeight*y, TileWidth + TileWidth*x, TileHeight + TileHeight*y }; + + RenderRegion.Min.x -= RenderRegion.Min.x % 8; + RenderRegion.Min.y -= RenderRegion.Min.y % 8; + RenderRegion.Max.x -= RenderRegion.Max.x % 8; + RenderRegion.Max.y -= RenderRegion.Max.y % 8; + + if (RenderRegion.Max.x > InitialRenderRegion.Max.x) + RenderRegion.Max.x = InitialRenderRegion.Max.x; + if (RenderRegion.Max.y > InitialRenderRegion.Max.y) + RenderRegion.Max.y = InitialRenderRegion.Max.y; + + if (x == 3) + RenderRegion.Max.x = InitialRenderRegion.Max.x; + if (y == 3) + RenderRegion.Max.y = InitialRenderRegion.Max.y; + + render_entry Entry = { Data, OutputBuffer, RenderRegion }; + + uint32 Q = SDL_AtomicGet(&QueuedEntries); + *(Entries + Q) = Entry; + SDL_AtomicAdd(&QueuedEntries, 1); + SDL_SemPost(Semaphore); + } + // } + } +} -- cgit v1.2.3