diff --git a/.gitmodules b/.gitmodules index 8b36230..bc6f51b 100644 --- a/.gitmodules +++ b/.gitmodules @@ -8,3 +8,6 @@ [submodule "include/tinyxml2"] path = include/tinyxml2 url = https://github.com/leethomason/tinyxml2 +[submodule "include/inih"] + path = include/inih + url = https://github.com/benhoyt/inih diff --git a/CMakeLists.txt b/CMakeLists.txt index 5851597..c08a949 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 3.10) -project(anm2ed CXX) +project(anm2ed C CXX) # Gather project sources file(GLOB SOURCES @@ -10,12 +10,13 @@ file(GLOB SOURCES "include/imgui/backends/imgui_impl_sdl3.cpp" "include/imgui/backends/imgui_impl_opengl3.cpp" "include/tinyxml2/tinyxml2.cpp" + "include/inih/ini.c" "${PROJECT_SOURCE_DIR}/src/*.cpp" ) add_executable(${PROJECT_NAME} ${SOURCES}) -target_include_directories(${PROJECT_NAME} PRIVATE include include/imgui include/tinyxml2 src) +target_include_directories(${PROJECT_NAME} PRIVATE include include/imgui include/tinyxml2 include/inih src) set(CMAKE_CXX_FLAGS "-g -O2 -std=c++23 -Wall -Wextra -pedantic -Wno-unused-variable -Wno-unused-parameter -Wno-ignored-qualifiers -Wno-parentheses -Wno-unused-function -Wno-class-memaccess -Wno-delete-incomplete") diff --git a/settings.ini b/settings.ini new file mode 100644 index 0000000..866eec4 --- /dev/null +++ b/settings.ini @@ -0,0 +1,69 @@ +[Settings] +panX=-521.000000 +panY=258.000000 + +# Dear ImGui +[Window][Window] +Pos=0,32 +Size=1918,1030 +Collapsed=0 + +[Window][Debug##Default] +Pos=60,60 +Size=400,400 +Collapsed=0 + +[Window][Animations] +Pos=1197,40 +Size=485,342 +Collapsed=0 +DockId=0x00000005,0 + +[Window][Events] +Pos=1197,384 +Size=485,345 +Collapsed=0 +DockId=0x00000006,0 + +[Window][Spritesheets] +Pos=1684,40 +Size=226,689 +Collapsed=0 +DockId=0x00000002,0 + +[Window][Spritesheet Editor] +Pos=60,60 +Size=32,35 +Collapsed=0 + +[Window][Timeline] +Pos=8,731 +Size=1902,323 +Collapsed=0 +DockId=0x0000000A,0 + +[Window][Frame Properties] +Pos=890,40 +Size=305,689 +Collapsed=0 +DockId=0x00000008,0 + +[Window][Animation Preview] +Pos=8,40 +Size=880,689 +Collapsed=0 +DockId=0x00000007,0 + +[Docking][Data] +DockSpace ID=0xFB691B2E Window=0xFA2EAA04 Pos=8,40 Size=1902,1014 Split=Y Selected=0x024430EF + DockNode ID=0x00000009 Parent=0xFB691B2E SizeRef=1902,689 Split=X + DockNode ID=0x00000001 Parent=0x00000009 SizeRef=1674,1014 Split=X Selected=0x024430EF + DockNode ID=0x00000003 Parent=0x00000001 SizeRef=1187,1014 Split=X Selected=0x024430EF + DockNode ID=0x00000007 Parent=0x00000003 SizeRef=880,1014 CentralNode=1 Selected=0x024430EF + DockNode ID=0x00000008 Parent=0x00000003 SizeRef=305,1014 Selected=0x754E368F + DockNode ID=0x00000004 Parent=0x00000001 SizeRef=485,1014 Split=Y Selected=0x8A65D963 + DockNode ID=0x00000005 Parent=0x00000004 SizeRef=485,504 Selected=0xC1986EE2 + DockNode ID=0x00000006 Parent=0x00000004 SizeRef=485,508 Selected=0x8A65D963 + DockNode ID=0x00000002 Parent=0x00000009 SizeRef=226,1014 Selected=0x4EFD0020 + DockNode ID=0x0000000A Parent=0xFB691B2E SizeRef=1902,323 Selected=0x4F89F0DC + diff --git a/src/COMMON.h b/src/COMMON.h index 4ebd0f5..b25688e 100644 --- a/src/COMMON.h +++ b/src/COMMON.h @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -18,6 +19,7 @@ #include #include +#include #include #include #include @@ -46,6 +48,13 @@ using namespace glm; // fuck you #define MAX(x, max) (x > max ? max : x) #define CLAMP(x, min, max) (MIN(MAX(x, max), min)) +#define COLOR_FLOAT_TO_INT(x) (static_cast((x) * 255.0f)) +#define COLOR_INT_TO_FLOAT(x) ((x) / 255.0f) + +#define TICK_DELAY 33.3f +#define SECOND 1000.0f +#define TICK_RATE (SECOND / TICK_DELAY) + static inline const char* enum_to_string(const char* arr[], s32 count, s32 index) { return (index >= 0 && index < count) ? arr[index] : "undefined"; } static inline s32 string_to_enum(const char* str, const char* const* arr, s32 n) { for (s32 i=0; i Play" #define STRING_IMGUI_TIMELINE_PAUSE "|| Pause" #define STRING_IMGUI_TIMELINE_FRAME_ADD "Add Frame" +#define STRING_IMGUI_TIMELINE_FRAMES "Frames" +#define STRING_IMGUI_TIMELINE_FRAME_LABEL "##Frame" +#define STRING_IMGUI_TIMELINE_TRIGGER_LABEL "##Trigger" #define STRING_IMGUI_TIMELINE_FRAME_REMOVE "Remove Frame" #define STRING_IMGUI_TIMELINE_FIT_ANIMATION_LENGTH "= Fit Animation Length" #define STRING_IMGUI_TIMELINE_ANIMATION LENGTH "Animation Length:" @@ -141,11 +148,20 @@ #define STRING_IMGUI_TIMELINE_ANIMATION_LENGTH "Animation Length" #define STRING_IMGUI_TIMELINE_ELEMENT_TIMELINE "Element Timeline" #define STRING_IMGUI_TIMELINE_ELEMENTS "Elements" +#define STRING_IMGUI_TIMELINE_ELEMENT_LIST "Element List" #define STRING_IMGUI_TIMELINE_ELEMENT_ADD "Add Element" #define STRING_IMGUI_TIMELINE_ELEMENT_ADD_MENU "Element Add Menu" #define STRING_IMGUI_TIMELINE_ELEMENT_ADD_MENU_LAYER "Add Layer..." #define STRING_IMGUI_TIMELINE_ELEMENT_ADD_MENU_NULL "Add Null..." #define STRING_IMGUI_TIMELINE_ELEMENT_REMOVE "Remove Element" +#define STRING_IMGUI_TIMELINE_LOOP "Loop" +#define STRING_IMGUI_TIMELINE_FPS "FPS" +#define STRING_IMGUI_TIMELINE_CREATED_BY "Author" +#define STRING_IMGUI_TIMELINE_CREATED_ON "Created on: %s" +#define STRING_IMGUI_TIMELINE_VERSION "Version: %i" +#define STRING_IMGUI_TIMELINE_VIEWER "Viewer" +#define STRING_IMGUI_TIMELINE_CHILD "Timeline" +#define STRING_IMGUI_TIMELINE_MAIN "Main" #define STRING_IMGUI_TOOLTIP_ANIMATIONS_ADD "Add a new animation." #define STRING_IMGUI_TOOLTIP_ANIMATIONS_DUPLICATE "Duplicates the selected animation." @@ -157,16 +173,21 @@ #define STRING_IMGUI_TOOLTIP_ANIMATION_PREVIEW_BACKGROUND_COLOR "Changes the background color of the animation preview." #define STRING_IMGUI_TOOLTIP_ANIMATION_PREVIEW_CENTER_VIEW "Centers the preview's pan." #define STRING_IMGUI_TOOLTIP_ANIMATION_PREVIEW_GRID "Toggles grid visibility on the animation preview." -#define STRING_IMGUI_TOOLTIP_ANIMATION_PREVIEW_GRID_SIZE "Changes the grid's size, in pixels." #define STRING_IMGUI_TOOLTIP_ANIMATION_PREVIEW_GRID_COLOR "Changes the animation preview grid color." +#define STRING_IMGUI_TOOLTIP_ANIMATION_PREVIEW_GRID_OFFSET "Changes the grid's offset, in pixels." +#define STRING_IMGUI_TOOLTIP_ANIMATION_PREVIEW_GRID_SIZE "Changes the grid's size, in pixels." #define STRING_IMGUI_TOOLTIP_ANIMATION_PREVIEW_ZOOM "Changes the animation preview zoom level." +#define STRING_IMGUI_TOOLTIP_ANIMATION_PREVIEW_ROOT_TRANSFORM "Toggle the properties of the Root element of an animation being able to change the properties (such as position or scale) of all of the animation's elements." +#define STRING_IMGUI_TOOLTIP_ANIMATION_PREVIEW_SHOW_PIVOT "Toggle the pivots of layer animation elements being visible." #define STRING_IMGUI_TOOLTIP_EVENTS_ADD "Add a new event." #define STRING_IMGUI_TOOLTIP_EVENTS_REMOVE "Removes the selected event." #define STRING_IMGUI_TOOLTIP_EVENTS_SELECT "Set the event for the trigger, or rename it." +#define STRING_IMGUI_TOOLTIP_FRAME_PROPERTIES_AT_FRAME "Sets the frame in which the trigger will activate." #define STRING_IMGUI_TOOLTIP_FRAME_PROPERTIES_COLOR_OFFSET "Change the color offset of the frame." #define STRING_IMGUI_TOOLTIP_FRAME_PROPERTIES_CROP_POSITION "Change the crop position of the frame." #define STRING_IMGUI_TOOLTIP_FRAME_PROPERTIES_CROP_SIZE "Change the crop size of the frame." #define STRING_IMGUI_TOOLTIP_FRAME_PROPERTIES_DURATION "Change the duration of the frame." +#define STRING_IMGUI_TOOLTIP_FRAME_PROPERTIES_EVENT "Sets the event the trigger will use." #define STRING_IMGUI_TOOLTIP_FRAME_PROPERTIES_INTERPOLATED "Toggles the interpolation of the frame.\nBetween keyframes, will transform the values in the in-betweens to be smooth." #define STRING_IMGUI_TOOLTIP_FRAME_PROPERTIES_PIVOT "Change the pivot of the frame." #define STRING_IMGUI_TOOLTIP_FRAME_PROPERTIES_POSITION "Change the position of the frame." @@ -174,30 +195,33 @@ #define STRING_IMGUI_TOOLTIP_FRAME_PROPERTIES_SCALE "Change the scale of the frame." #define STRING_IMGUI_TOOLTIP_FRAME_PROPERTIES_TINT "Change the tint of the frame." #define STRING_IMGUI_TOOLTIP_FRAME_PROPERTIES_VISIBLE "Toggles the visibility of the frame." -#define STRING_IMGUI_TOOLTIP_PROPERTIES_CREATED_BY "Change the author of the animation." -#define STRING_IMGUI_TOOLTIP_PROPERTIES_CREATED_ON_NOW "Set the date of creation to the current system time." -#define STRING_IMGUI_TOOLTIP_PROPERTIES_FPS "Change the FPS of the animation." #define STRING_IMGUI_TOOLTIP_SPRITESHEETS_ADD "Opens the file dialog to load in a new spritesheet." #define STRING_IMGUI_TOOLTIP_SPRITESHEETS_RELOAD "Reloads the selected spritesheet." #define STRING_IMGUI_TOOLTIP_SPRITESHEETS_REMOVE "Removes the selected spritesheet." #define STRING_IMGUI_TOOLTIP_SPRITESHEETS_REPLACE "Replaces the selected spritesheet; opens up the file dialog." #define STRING_IMGUI_TOOLTIP_SPRITESHEETS_SELECT "Select the spritesheet element." #define STRING_IMGUI_TOOLTIP_TIMELINE_ANIMATION_LENGTH "Changes the current animation's length." +#define STRING_IMGUI_TOOLTIP_TIMELINE_CREATED_BY "Change the author of the animation." +#define STRING_IMGUI_TOOLTIP_TIMELINE_CREATED_ON_NOW "Set the date of creation to the current system time." #define STRING_IMGUI_TOOLTIP_TIMELINE_ELEMENT_ADD "Add a layer or null timeline element." -#define STRING_IMGUI_TOOLTIP_TIMELINE_ELEMENT_LAYER "This is a Layer element.\nThese are the main graphical animation elements.\nClick to rename." +#define STRING_IMGUI_TOOLTIP_TIMELINE_ELEMENT_LAYER "This is a Layer element.\nThese are the main graphical animation elements.\nClick to select or rename." #define STRING_IMGUI_TOOLTIP_TIMELINE_ELEMENT_NAME "Click to rename the element." -#define STRING_IMGUI_TOOLTIP_TIMELINE_ELEMENT_NULL "This is a Null element.\nThese are invisible elements where a game engine may have access to them for additional effects.\nClick to rename." +#define STRING_IMGUI_TOOLTIP_TIMELINE_ELEMENT_NULL "This is a Null element.\nThese are invisible elements where a game engine may have access to them for additional effects.\nClick to select or rename." #define STRING_IMGUI_TOOLTIP_TIMELINE_ELEMENT_RECT "Toggle visibility for a rectangle around the null." #define STRING_IMGUI_TOOLTIP_TIMELINE_ELEMENT_REMOVE "Remove a timeline element." -#define STRING_IMGUI_TOOLTIP_TIMELINE_ELEMENT_ROOT "This is the Root element." +#define STRING_IMGUI_TOOLTIP_TIMELINE_ELEMENT_ROOT "This is the Root element.\nClick to select." #define STRING_IMGUI_TOOLTIP_TIMELINE_ELEMENT_SHIFT_DOWN "Shift this timeline element below.\nElements with lower indices will display further in front." #define STRING_IMGUI_TOOLTIP_TIMELINE_ELEMENT_SHIFT_UP "Shift this timeline element above.\nElements with higher indices will display further behind." #define STRING_IMGUI_TOOLTIP_TIMELINE_ELEMENT_SPRITESHEET "Click to change the spritesheet the layer is using." -#define STRING_IMGUI_TOOLTIP_TIMELINE_ELEMENT_TRIGGERS "This is the animation's Triggers.\nTriggers are special activations; each is bound to an Event." +#define STRING_IMGUI_TOOLTIP_TIMELINE_ELEMENT_TRIGGERS "This is the animation's Triggers.\nTriggers are special activations; each is bound to an Event.\nClick to select." #define STRING_IMGUI_TOOLTIP_TIMELINE_ELEMENT_VISIBLE "Toggle visibility for this element." +#define STRING_IMGUI_TOOLTIP_TIMELINE_FPS "Change the FPS of the animation." #define STRING_IMGUI_TOOLTIP_TIMELINE_FRAME_ADD "Add a frame to the current selected element." #define STRING_IMGUI_TOOLTIP_TIMELINE_FRAME_REMOVE "Removes the selected frame from the current selected element." +#define STRING_IMGUI_TOOLTIP_TIMELINE_LOOP "Toggles the animation looping." #define STRING_IMGUI_TOOLTIP_TIMELINE_PAUSE "Pauses the animation." #define STRING_IMGUI_TOOLTIP_TIMELINE_PLAY "Plays the animation." -#define STRING_OPENGL_VERSION "#version 330" \ No newline at end of file +#define STRING_OPENGL_VERSION "#version 330" + +#define PATH_SETTINGS "settings.ini" diff --git a/src/anm2.cpp b/src/anm2.cpp index 9008b74..20241ba 100644 --- a/src/anm2.cpp +++ b/src/anm2.cpp @@ -171,13 +171,13 @@ anm2_serialize(Anm2* self, const char* path) frameElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_Y_SCALE], frame.scale.y); /* XScale */ frameElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_DELAY], frame.delay); /* Delay */ frameElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_VISIBLE], frame.isVisible); /* Visible */ - frameElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_RED_TINT], frame.tintRGBA.r); /* RedTint */ - frameElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_GREEN_TINT], frame.tintRGBA.g); /* GreenTint */ - frameElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_BLUE_TINT], frame.tintRGBA.b); /* BlueTint */ - frameElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_ALPHA_TINT], frame.tintRGBA.a); /* AlphaTint */ - frameElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_RED_OFFSET], frame.offsetRGB.r); /* RedOffset */ - frameElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_GREEN_OFFSET], frame.offsetRGB.g); /* GreenOffset */ - frameElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_BLUE_OFFSET], frame.offsetRGB.b); /* BlueOffset */ + frameElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_RED_TINT], COLOR_FLOAT_TO_INT(frame.tintRGBA.r)); /* RedTint */ + frameElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_GREEN_TINT], COLOR_FLOAT_TO_INT(frame.tintRGBA.g)); /* GreenTint */ + frameElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_BLUE_TINT], COLOR_FLOAT_TO_INT(frame.tintRGBA.b)); /* BlueTint */ + frameElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_ALPHA_TINT], COLOR_FLOAT_TO_INT(frame.tintRGBA.a)); /* AlphaTint */ + frameElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_RED_OFFSET], COLOR_FLOAT_TO_INT(frame.offsetRGB.r)); /* RedOffset */ + frameElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_GREEN_OFFSET], COLOR_FLOAT_TO_INT(frame.offsetRGB.g)); /* GreenOffset */ + frameElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_BLUE_OFFSET], COLOR_FLOAT_TO_INT(frame.offsetRGB.b)); /* BlueOffset */ frameElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_ROTATION], frame.rotation); /* Rotation */ frameElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_INTERPOLATED], frame.isInterpolated); /* Interpolated */ @@ -217,13 +217,13 @@ anm2_serialize(Anm2* self, const char* path) frameElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_Y_SCALE], frame.scale.y); /* XScale */ frameElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_DELAY], frame.delay); /* Delay */ frameElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_VISIBLE], frame.isVisible); /* Visible */ - frameElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_RED_TINT], frame.tintRGBA.r); /* RedTint */ - frameElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_GREEN_TINT], frame.tintRGBA.g); /* GreenTint */ - frameElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_BLUE_TINT], frame.tintRGBA.b); /* BlueTint */ - frameElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_ALPHA_TINT], frame.tintRGBA.a); /* AlphaTint */ - frameElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_RED_OFFSET], frame.offsetRGB.r); /* RedOffset */ - frameElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_GREEN_OFFSET], frame.offsetRGB.g); /* GreenOffset */ - frameElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_BLUE_OFFSET], frame.offsetRGB.b); /* BlueOffset */ + frameElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_RED_TINT], COLOR_FLOAT_TO_INT(frame.tintRGBA.r)); /* RedTint */ + frameElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_GREEN_TINT], COLOR_FLOAT_TO_INT(frame.tintRGBA.g)); /* GreenTint */ + frameElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_BLUE_TINT], COLOR_FLOAT_TO_INT(frame.tintRGBA.b)); /* BlueTint */ + frameElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_ALPHA_TINT], COLOR_FLOAT_TO_INT(frame.tintRGBA.a)); /* AlphaTint */ + frameElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_RED_OFFSET], COLOR_FLOAT_TO_INT(frame.offsetRGB.r)); /* RedOffset */ + frameElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_GREEN_OFFSET], COLOR_FLOAT_TO_INT(frame.offsetRGB.g)); /* GreenOffset */ + frameElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_BLUE_OFFSET], COLOR_FLOAT_TO_INT(frame.offsetRGB.b)); /* BlueOffset */ frameElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_ROTATION], frame.rotation); /* Rotation */ frameElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_INTERPOLATED], frame.isInterpolated); /* Interpolated */ @@ -262,13 +262,13 @@ anm2_serialize(Anm2* self, const char* path) frameElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_Y_SCALE], frame.scale.y); /* XScale */ frameElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_DELAY], frame.delay); /* Delay */ frameElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_VISIBLE], frame.isVisible); /* Visible */ - frameElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_RED_TINT], frame.tintRGBA.r); /* RedTint */ - frameElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_GREEN_TINT], frame.tintRGBA.g); /* GreenTint */ - frameElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_BLUE_TINT], frame.tintRGBA.b); /* BlueTint */ - frameElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_ALPHA_TINT], frame.tintRGBA.a); /* AlphaTint */ - frameElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_RED_OFFSET], frame.offsetRGB.r); /* RedOffset */ - frameElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_GREEN_OFFSET], frame.offsetRGB.g); /* GreenOffset */ - frameElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_BLUE_OFFSET], frame.offsetRGB.b); /* BlueOffset */ + frameElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_RED_TINT], COLOR_FLOAT_TO_INT(frame.tintRGBA.r)); /* RedTint */ + frameElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_GREEN_TINT], COLOR_FLOAT_TO_INT(frame.tintRGBA.g)); /* GreenTint */ + frameElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_BLUE_TINT], COLOR_FLOAT_TO_INT(frame.tintRGBA.b)); /* BlueTint */ + frameElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_ALPHA_TINT], COLOR_FLOAT_TO_INT(frame.tintRGBA.a)); /* AlphaTint */ + frameElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_RED_OFFSET], COLOR_FLOAT_TO_INT(frame.offsetRGB.r)); /* RedOffset */ + frameElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_GREEN_OFFSET], COLOR_FLOAT_TO_INT(frame.offsetRGB.g)); /* GreenOffset */ + frameElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_BLUE_OFFSET], COLOR_FLOAT_TO_INT(frame.offsetRGB.b)); /* BlueOffset */ frameElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_ROTATION], frame.rotation); /* Rotation */ frameElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_INTERPOLATED], frame.isInterpolated); /* Interpolated */ @@ -341,6 +341,7 @@ anm2_deserialize(Anm2* self, Resources* resources, const char* path) Anm2Layer tempLayer; Anm2Spritesheet tempSpritesheet; Anm2Event tempEvent; + char lastSpritesheetPath[PATH_MAX]; *self = Anm2{}; @@ -354,7 +355,8 @@ anm2_deserialize(Anm2* self, Resources* resources, const char* path) resources_loaded_textures_free(resources); strncpy(self->path, path, PATH_MAX - 1); - + working_directory_from_path_set(path); + root = document.FirstChildElement(ANM2_ELEMENT_STRINGS[ANM2_ELEMENT_ANIMATED_ACTOR]); element = root; @@ -472,8 +474,8 @@ anm2_deserialize(Anm2* self, Resources* resources, const char* path) } break; case ANM2_ATTRIBUTE_PATH: - strncpy(lastSpritesheet->path, attribute->Value(), PATH_MAX - 1); - anm2_spritesheet_texture_load(self, resources, attribute->Value(), id); + /* Make path lowercase */ + strncpy(lastSpritesheetPath, attribute->Value(), PATH_MAX - 1); break; case ANM2_ATTRIBUTE_NAME: switch (anm2Element) @@ -566,25 +568,25 @@ anm2_deserialize(Anm2* self, Resources* resources, const char* path) } break; case ANM2_ATTRIBUTE_RED_TINT: - lastFrame->tintRGBA.r = atof(attribute->Value()); + lastFrame->tintRGBA.r = COLOR_INT_TO_FLOAT(atoi(attribute->Value())); break; case ANM2_ATTRIBUTE_GREEN_TINT: - lastFrame->tintRGBA.g = atof(attribute->Value()); + lastFrame->tintRGBA.g = COLOR_INT_TO_FLOAT(atoi(attribute->Value())); break; case ANM2_ATTRIBUTE_BLUE_TINT: - lastFrame->tintRGBA.b = atof(attribute->Value()); + lastFrame->tintRGBA.b = COLOR_INT_TO_FLOAT(atoi(attribute->Value())); break; case ANM2_ATTRIBUTE_ALPHA_TINT: - lastFrame->tintRGBA.a = atof(attribute->Value()); + lastFrame->tintRGBA.a = COLOR_INT_TO_FLOAT(atoi(attribute->Value())); break; case ANM2_ATTRIBUTE_RED_OFFSET: - lastFrame->offsetRGB.r = atof(attribute->Value()); + lastFrame->offsetRGB.r = COLOR_INT_TO_FLOAT(atoi(attribute->Value())); break; case ANM2_ATTRIBUTE_GREEN_OFFSET: - lastFrame->offsetRGB.g = atof(attribute->Value()); + lastFrame->offsetRGB.g = COLOR_INT_TO_FLOAT(atoi(attribute->Value())); break; case ANM2_ATTRIBUTE_BLUE_OFFSET: - lastFrame->offsetRGB.b = atof(attribute->Value()); + lastFrame->offsetRGB.b = COLOR_INT_TO_FLOAT(atoi(attribute->Value())); break; case ANM2_ATTRIBUTE_ROTATION: lastFrame->rotation = atof(attribute->Value()); @@ -615,6 +617,13 @@ anm2_deserialize(Anm2* self, Resources* resources, const char* path) attribute = attribute->Next(); } + /* Load spritesheet textures */ + if (anm2Element == ANM2_ELEMENT_SPRITESHEET) + { + strncpy(lastSpritesheet->path, lastSpritesheetPath, PATH_MAX); + anm2_spritesheet_texture_load(self, resources, lastSpritesheetPath , id); + } + /* Iterate through children */ child = element->FirstChildElement(); @@ -642,8 +651,6 @@ anm2_deserialize(Anm2* self, Resources* resources, const char* path) } } - working_directory_from_path_set(path); - printf(STRING_INFO_ANM2_READ, path); return true; @@ -702,12 +709,19 @@ anm2_animation_add(Anm2* self) /* match layers */ for (auto & [layerID, layer] : self->layers) + { animation.layerAnimations[layerID] = Anm2LayerAnimation{}; + } /* match nulls */ for (auto & [nullID, null] : self->nulls) - animation.nullAnimations[nullID] = Anm2NullAnimation{}; + { + animation.nullAnimations[nullID] = Anm2NullAnimation{}; + } + /* add a root frame */ + animation.rootAnimation.frames.push_back(Anm2Frame{}); + self->animations[id] = animation; return id; @@ -732,10 +746,99 @@ anm2_spritesheet_texture_load(Anm2* self, Resources* resources, const char* path { Texture texture; - /* free texture if it exists */ - if (resources->loadedTextures.find(id) != resources->loadedTextures.end()) + /* free texture if it exists, and if it's not texture error */ + if + ( + resources->loadedTextures.find(id) != resources->loadedTextures.end() && + resources->loadedTextures[id].handle != resources->textures[TEXTURE_ERROR].handle + ) texture_free(&resources->loadedTextures[id]); - texture_from_path_init(&texture, path); - resources->loadedTextures[id] = texture; + if (texture_from_path_init(&texture, path)) + resources->loadedTextures[id] = texture; + else + { + resources->loadedTextures[id] = resources->textures[TEXTURE_ERROR]; + resources->loadedTextures[id].isInvalid = true; + } +} + +/* Creates/fetches a frame from a given time. */ +/* Returns true/false if frame will be valid or not. */ +bool +anm2_frame_from_time(Anm2* self, Anm2Animation* animation, Anm2Frame* frame, Anm2AnimationType type, s32 id, f32 time) +{ + /* Out of range */ + if (time < 0 || time > animation->frameNum) + return false; + + Anm2RootAnimation* rootAnimation; + Anm2LayerAnimation* layerAnimation; + Anm2NullAnimation* nullAnimation; + Anm2Frame* baseFrame = NULL; + Anm2Frame* nextFrame = NULL; + std::vector* frames = NULL; + f32 delayCurrent = 0; + f32 delayNext = 0; + bool isBaseFrame = false; + + switch (type) + { + case ANM2_ROOT_ANIMATION: + frames = &animation->rootAnimation.frames; + break; + case ANM2_LAYER_ANIMATION: + if (id < 0 || id >= (s32)animation->layerAnimations.size()) + return false; + frames = &animation->layerAnimations[id].frames; + break; + case ANM2_NULL_ANIMATION: + if (id < 0 || id >= (s32)animation->nullAnimations.size()) + return false; + frames = &animation->nullAnimations[id].frames; + break; + default: + return false; + } + + for (s32 i = 0; i < (s32)frames->size(); i++) + { + Anm2Frame* frame = &(*frames)[i]; + delayNext += frame->delay; + + if (time >= delayCurrent && time < delayNext) + { + baseFrame = frame; + + if (i + 1 < (s32)frames->size()) + nextFrame = &(*frames)[i + 1]; + else + nextFrame = NULL; + + isBaseFrame = true; + break; + } + + delayCurrent += frame->delay; + } + + /* No valid frame found */ + if (!isBaseFrame) + return false; + + *frame = *baseFrame; + + /* interpolate only if there's a frame following */ + if (frame->isInterpolated && nextFrame) + { + f32 interpolationTime = (time - delayCurrent) / (delayNext - delayCurrent); + + frame->rotation = glm::mix(baseFrame->rotation, nextFrame->rotation, interpolationTime);; + frame->position = glm::mix(baseFrame->position, nextFrame->position, interpolationTime);; + frame->scale = glm::mix(baseFrame->scale, nextFrame->scale, interpolationTime);; + frame->offsetRGB = glm::mix(baseFrame->offsetRGB, nextFrame->offsetRGB, interpolationTime);; + frame->tintRGBA = glm::mix(baseFrame->tintRGBA, nextFrame->tintRGBA, interpolationTime);; + } + + return true; } \ No newline at end of file diff --git a/src/anm2.h b/src/anm2.h index 5ee2984..f742b94 100644 --- a/src/anm2.h +++ b/src/anm2.h @@ -11,8 +11,9 @@ #define ANM2_PATH_FORMATTED_MAX PATH_MAX + 0xFF #define ANM2_BUFFER_MAX 0xFFFFF #define ANM2_FPS_MIN 0 -#define ANM2_FPS_DEFAULT 30 -#define ANM2_FPS_MAX 60 +#define ANM2_FPS_MAX 120 +#define ANM2_FRAME_NUM_MIN 1 +#define ANM2_FRAME_NUM_MAX 1000000 /* Elements */ #define ANM2_ELEMENT_LIST \ @@ -114,7 +115,7 @@ enum Anm2AnimationType ANM2_ROOT_ANIMATION, ANM2_LAYER_ANIMATION, ANM2_NULL_ANIMATION, - ANM2_TRIGGERS + ANM2_TRIGGER }; struct Anm2Spritesheet @@ -154,10 +155,10 @@ struct Anm2Frame vec2 crop = {0.0f, 0.0f}; vec2 pivot = {0.0f, 0.0f}; vec2 position = {0.0f, 0.0f}; - vec2 size = {1.0f, 1.0f}; - vec2 scale = {1.0f, 1.0f}; - ivec3 offsetRGB = {0, 0, 0}; - ivec4 tintRGBA = {255, 255, 255, 255}; + vec2 size = {0.0f, 0.0f}; + vec2 scale = {100.0f, 100.0f}; + vec3 offsetRGB = {0.0f, 0.0f, 0.0f}; + vec4 tintRGBA = {1.0f, 1.0f, 1.0f, 1.0f}; }; struct Anm2LayerAnimation @@ -186,7 +187,7 @@ struct Anm2Triggers struct Anm2Animation { - s32 frameNum = 0; + s32 frameNum = ANM2_FRAME_NUM_MIN; char name[ANM2_STRING_MAX] = STRING_ANM2_NEW_ANIMATION; bool isLoop = true; Anm2RootAnimation rootAnimation; @@ -198,7 +199,7 @@ struct Anm2Animation struct Anm2 { char path[PATH_MAX] = STRING_EMPTY; - s32 fps = ANM2_FPS_DEFAULT; + s32 fps = 30; s32 version = 0; char createdBy[ANM2_STRING_MAX] = STRING_ANM2_CREATED_BY_DEFAULT; char createdOn[ANM2_STRING_MAX] = STRING_EMPTY; @@ -220,4 +221,5 @@ void anm2_new(Anm2* self); void anm2_created_on_set(Anm2* self); s32 anm2_animation_add(Anm2* self); void anm2_animation_remove(Anm2* self, s32 id); -void anm2_spritesheet_texture_load(Anm2* self, Resources* resources, const char* path, s32 id); \ No newline at end of file +void anm2_spritesheet_texture_load(Anm2* self, Resources* resources, const char* path, s32 id); +bool anm2_frame_from_time(Anm2* self, Anm2Animation* animation, Anm2Frame* frame, Anm2AnimationType type, s32 id, f32 time); diff --git a/src/dialog.cpp b/src/dialog.cpp index eb3fa6a..d062abc 100644 --- a/src/dialog.cpp +++ b/src/dialog.cpp @@ -16,14 +16,14 @@ _dialog_callback(void* userdata, const char* const* filelist, s32 filter) } else self->isSelected = false; - } void -dialog_init(Dialog* self, Anm2* anm2, Resources* resources) +dialog_init(Dialog* self, Anm2* anm2, Resources* resources, SDL_Window* window) { self->anm2 = anm2; self->resources = resources; + self->window = window; } /* Opens file dialog for user to pick anm2 files */ @@ -76,11 +76,13 @@ dialog_tick(Dialog* self) switch (self->type) { case DIALOG_ANM2_OPEN: - anm2_deserialize(self->anm2, self->resources, relativePath); resources_loaded_textures_free(self->resources); + anm2_deserialize(self->anm2, self->resources, self->path); + window_title_from_anm2_set(self->window, self->anm2); break; case DIALOG_ANM2_SAVE: anm2_serialize(self->anm2, relativePath); + window_title_from_anm2_set(self->window, self->anm2); break; case DIALOG_PNG_OPEN: id = map_next_id_get(self->resources->loadedTextures); diff --git a/src/dialog.h b/src/dialog.h index 774ab48..ab7bbac 100644 --- a/src/dialog.h +++ b/src/dialog.h @@ -2,6 +2,7 @@ #include "anm2.h" #include "resources.h" +#include "window.h" static const SDL_DialogFileFilter DIALOG_FILE_FILTER_ANM2[] = { @@ -26,13 +27,14 @@ struct Dialog { Anm2* anm2 = NULL; Resources* resources = NULL; + SDL_Window* window = NULL; s32 replaceID = -1; enum DialogType type = DIALOG_NONE; char path[PATH_MAX] = ""; bool isSelected = false; }; -void dialog_init(Dialog* self, Anm2* anm2, Resources* resources); +void dialog_init(Dialog* self, Anm2* anm2, Resources* resources, SDL_Window* window); void dialog_anm2_open(Dialog* self); void dialog_png_open(Dialog* self); void dialog_png_replace(Dialog* self); diff --git a/src/imgui.cpp b/src/imgui.cpp index 660eaac..391f074 100644 --- a/src/imgui.cpp +++ b/src/imgui.cpp @@ -1,19 +1,16 @@ #include "imgui.h" static void _imgui_tooltip(const char* tooltip); - -static void -_imgui_timeline_element -( - Imgui* self, - Anm2Animation* animation, - void* element, - s32* id, - s32* index, - Anm2AnimationType type, - Anm2AnimationType* selectType, - s32* selectID -); +static void _imgui_timeline_element_frames(Imgui* self, void* element, s32* id, s32* index, Anm2AnimationType type); +static void _imgui_timeline_element(Imgui* self, void* element, s32* id, s32* index, Anm2AnimationType type); +static void _imgui_timeline(Imgui* self); +static void _imgui_animations(Imgui* self); +static void _imgui_events(Imgui* self); +static void _imgui_spritesheets(Imgui* self); +static void _imgui_frame_properties(Imgui* self); +static void _imgui_spritesheet_editor(Imgui* self); +static void _imgui_animation_preview(Imgui* self); +static void _imgui_taskbar(Imgui* self); /* Makes a tooltip! */ static void _imgui_tooltip(const char* tooltip) @@ -22,35 +19,192 @@ static void _imgui_tooltip(const char* tooltip) ImGui::SetTooltip("%s", tooltip); } +/* Displays the element's frames */ +static void +_imgui_timeline_element_frames(Imgui* self, void* element, s32* id, s32* index, Anm2AnimationType type) +{ + Anm2Animation* animation = &self->anm2->animations[self->animationID]; + ImVec2 framePos; + ImVec2 frameFinishPos; + Anm2LayerAnimation* layerAnimation = NULL; + Anm2NullAnimation* nullAnimation = NULL; + Anm2RootAnimation* rootAnimation = NULL; + Anm2Triggers* triggers = NULL; + ImVec2 cursorPos = ImGui::GetCursorPos(); + + void* frames = NULL; + + switch (type) + { + case ANM2_ROOT_ANIMATION: + rootAnimation = (Anm2RootAnimation*)element; + frames = &rootAnimation->frames; + break; + case ANM2_LAYER_ANIMATION: + layerAnimation = (Anm2LayerAnimation*)element; + frames = &layerAnimation->frames; + break; + case ANM2_NULL_ANIMATION: + nullAnimation = (Anm2NullAnimation*)element; + frames = &nullAnimation->frames; + break; + case ANM2_TRIGGER: + triggers = (Anm2Triggers*)element; + frames = &triggers->items; + break; + default: + break; + } + + ImGui::PushID(*index); + + if (animation->frameNum > 0) + { + ImVec2 frameListSize = {IMGUI_TIMELINE_FRAME_SIZE.x * animation->frameNum, IMGUI_TIMELINE_ELEMENTS_TIMELINE_SIZE.y}; + + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0)); + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0)); + + ImGui::BeginChild(STRING_IMGUI_TIMELINE_FRAMES, frameListSize, true); + + framePos = ImGui::GetCursorPos(); + + for (s32 i = 0; i < animation->frameNum; i++) + { + ImGui::PushID(i); + + ImVec2 frameTexturePos = ImGui::GetCursorScreenPos(); + + if (i % IMGUI_TIMELINE_FRAME_INDICES_MULTIPLE == 0) + { + ImVec2 bgMin = frameTexturePos; + ImVec2 bgMax = ImVec2(frameTexturePos.x + IMGUI_TIMELINE_FRAME_SIZE.x, frameTexturePos.y + IMGUI_TIMELINE_FRAME_SIZE.y); + ImU32 bgColor = ImGui::GetColorU32(IMGUI_FRAME_OVERLAY_COLOR); + ImGui::GetWindowDrawList()->AddRectFilled(bgMin, bgMax, bgColor); + } + + ImGui::Image(self->resources->textures[TEXTURE_FRAME_ALTERNATE].handle, IMGUI_TIMELINE_FRAME_SIZE); + + ImGui::SameLine(); + ImGui::PopID(); + } + + frameFinishPos = ImGui::GetCursorPos(); + + if (type == ANM2_TRIGGER) + { + std::vector* elementTriggers = (std::vector*)frames; + + for (auto [i, trigger] : std::views::enumerate(*elementTriggers)) + { + ImVec2 triggerPos = framePos; + triggerPos.x = framePos.x + (IMGUI_TIMELINE_FRAME_SIZE.x * trigger.atFrame); + + ImGui::SetCursorPos(triggerPos); + + ImGui::PushStyleColor(ImGuiCol_Border, IMGUI_FRAME_BORDER_COLOR); + ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, IMGUI_FRAME_BORDER); + + ImVec4 buttonColor = self->frameIndex == i && self->frameVector == elementTriggers ? + ImGui::GetStyle().Colors[ImGuiCol_ButtonHovered] : ImGui::GetStyle().Colors[ImGuiCol_Button]; + ImGui::PushStyleColor(ImGuiCol_Button, buttonColor); + + ImGui::PushID(i); + if (ImGui::Button(STRING_IMGUI_TIMELINE_TRIGGER_LABEL, IMGUI_TIMELINE_FRAME_SIZE)) + { + self->frameIndex = i; + self->frameVector = elementTriggers; + self->animationType = type; + } + + ImGui::PopStyleVar(); + ImGui::PopStyleColor(); + ImGui::PopStyleColor(); + + ImGui::SetCursorPos(ImVec2(triggerPos.x + 1.0f, (triggerPos.y + (IMGUI_TIMELINE_FRAME_SIZE.y / 2)) - IMGUI_ICON_SMALL_SIZE.y / 2)); + + ImGui::Image(self->resources->textures[TEXTURE_TRIGGER_FRAME_ICON].handle, IMGUI_ICON_SMALL_SIZE); + + ImGui::PopID(); + } + } + else + { + std::vector* elementFrames = (std::vector*)frames; + + for (auto [i, frame] : std::views::enumerate(*elementFrames)) + { + Texture* texture = frame.isInterpolated ? + &self->resources->textures[TEXTURE_INTERPOLATED_FRAME_ICON] : + &self->resources->textures[TEXTURE_UNINTERPOLATED_FRAME_ICON]; + + f32 frameWidth = IMGUI_TIMELINE_FRAME_SIZE.x * frame.delay; + ImVec2 frameSize = ImVec2(frameWidth, IMGUI_TIMELINE_FRAME_SIZE.y); + + ImGui::SetCursorPos(framePos); + + ImGui::PushStyleColor(ImGuiCol_Border, IMGUI_FRAME_BORDER_COLOR); + ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, IMGUI_FRAME_BORDER); + + ImVec4 buttonColor = self->frameIndex == i && self->frameVector == elementFrames ? + ImGui::GetStyle().Colors[ImGuiCol_ButtonHovered] : ImGui::GetStyle().Colors[ImGuiCol_Button]; + ImGui::PushStyleColor(ImGuiCol_Button, buttonColor); + + ImGui::PushID(i); + + if (ImGui::Button(STRING_IMGUI_TIMELINE_FRAME_LABEL, frameSize)) + { + self->frameIndex = i; + self->frameVector = elementFrames; + self->animationType = type; + } + + ImGui::PopStyleVar(); + ImGui::PopStyleColor(); + ImGui::PopStyleColor(); + + ImGui::SetCursorPos(ImVec2(framePos.x + 1.0f, (framePos.y + (frameSize.y / 2)) - IMGUI_ICON_SMALL_SIZE.y / 2)); + + ImGui::Image(texture->handle, IMGUI_ICON_SMALL_SIZE); + + ImGui::PopID(); + + framePos.x += frameWidth; + } + } + + ImGui::EndChild(); + ImGui::PopStyleVar(); + ImGui::PopStyleVar(); + + ImGui::SetCursorPosX(cursorPos.x); + ImGui::SetCursorPosY(cursorPos.y + IMGUI_TIMELINE_FRAME_SIZE.y); + } + else + ImGui::Dummy(IMGUI_DUMMY_SIZE); + + *index = *index + 1; + + ImGui::PopID(); +} + /* Displays each element of the timeline of a selected animation */ static void -_imgui_timeline_element -( - Imgui* self, - Anm2Animation* animation, - void* element, - s32* id, - s32* index, - Anm2AnimationType type, - Anm2AnimationType* selectType, - s32* selectID -) +_imgui_timeline_element(Imgui* self, void* element, s32* id, s32* index, Anm2AnimationType type) { - static s32 selectedElementIndex = -1; static s32 selectedSpritesheetIndex = -1; - void* frames = NULL; + Anm2Animation* animation = &self->anm2->animations[self->animationID]; Anm2Layer* layer = NULL; Anm2LayerAnimation* layerAnimation = NULL; Anm2Null* null = NULL; Anm2NullAnimation* nullAnimation = NULL; Anm2RootAnimation* rootAnimation = NULL; Anm2Triggers* triggers = NULL; - ImVec2 frameListSize = {IMGUI_TIMELINE_FRAME_SIZE.x * animation->frameNum, IMGUI_TIMELINE_ELEMENTS_TIMELINE_SIZE.y}; + void* frames = NULL; + ImVec2 framePos; + ImVec2 frameFinishPos; ImTextureID iconTexture = -1; - ImGuiWindowFlags windowFlags = 0 | - ImGuiWindowFlags_NoScrollbar | - ImGuiWindowFlags_NoScrollWithMouse; - + bool isSelected = *index == self->timelineElementIndex; bool isArrows = false; bool* isShowRect = NULL; bool* isVisible = NULL; @@ -58,9 +212,9 @@ _imgui_timeline_element char nameVisible[ANM2_STRING_FORMATTED_MAX] = STRING_EMPTY; char* namePointer = NULL; s32* spritesheetID = NULL; - bool isSelected = selectedElementIndex == *index; - bool isChangeable = type != ANM2_ROOT_ANIMATION && type != ANM2_TRIGGERS; - + bool isChangeable = type != ANM2_ROOT_ANIMATION && type != ANM2_TRIGGER; + f32 cursorPosY = ImGui::GetCursorPosY(); + switch (type) { case ANM2_ROOT_ANIMATION: @@ -77,9 +231,9 @@ _imgui_timeline_element isVisible = &layerAnimation->isVisible; spritesheetID = &layer->spritesheetID; namePointer = layer->name; - frames = &layerAnimation->frames; snprintf(nameBuffer, ANM2_STRING_MAX, "%s", namePointer); snprintf(nameVisible, ANM2_STRING_FORMATTED_MAX, STRING_IMGUI_TIMELINE_ELEMENT_FORMAT, *id, namePointer); + frames = &layerAnimation->frames; break; case ANM2_NULL_ANIMATION: nullAnimation = (Anm2NullAnimation*)element; @@ -88,32 +242,35 @@ _imgui_timeline_element isVisible = &nullAnimation->isVisible; isShowRect = &null->isShowRect; namePointer = null->name; - frames = &nullAnimation->frames; snprintf(nameBuffer, ANM2_STRING_MAX, "%s", namePointer); snprintf(nameVisible, ANM2_STRING_FORMATTED_MAX, STRING_IMGUI_TIMELINE_ELEMENT_FORMAT, *id, namePointer); + frames = &nullAnimation->frames; break; - case ANM2_TRIGGERS: + case ANM2_TRIGGER: triggers = (Anm2Triggers*)element; - frames = &triggers->items; iconTexture = self->resources->textures[TEXTURE_TRIGGER].handle; strncpy(nameVisible, STRING_IMGUI_TIMELINE_TRIGGERS, ANM2_STRING_FORMATTED_MAX); isVisible = &triggers->isVisible; + frames = &triggers->items; break; default: break; } - ImGui::BeginChild(nameVisible, IMGUI_TIMELINE_ELEMENT_SIZE, true, windowFlags); - ImGui::PushID(*index); - + + ImGui::BeginChild(nameVisible, IMGUI_TIMELINE_ELEMENT_SIZE, true, ImGuiWindowFlags_NoScrollWithMouse | ImGuiWindowFlags_NoScrollbar); + /* Shift arrows */ if (isChangeable) { bool isSwap = false; bool isReversed = (type == ANM2_LAYER_ANIMATION); - auto arrows_draw = [&](auto it, auto begin, auto end, auto& map, bool* didSwap, bool isReversed) + auto arrows_draw = [&](auto it, auto begin, auto end, + auto& map, + auto& allAnimationMaps, // vector of maps across all animations + bool* didSwap, bool isReversed) { bool canMoveUp = isReversed ? (std::next(it) != end) : (it != begin); bool canMoveDown = isReversed ? (it != begin) : (std::next(it) != end); @@ -129,30 +286,46 @@ _imgui_timeline_element if (target != map.end() && ImGui::ImageButton(STRING_IMGUI_TIMELINE_ELEMENT_SHIFT_ABOVE, self->resources->textures[TEXTURE_ARROW_UP].handle, IMGUI_ICON_SIZE)) { map_swap(map, it->first, target->first); + + for (auto& animation : self->anm2->animations) + { + if (type == ANM2_LAYER_ANIMATION) + map_swap(animation.second.layerAnimations, it->first, target->first); + else if (type == ANM2_NULL_ANIMATION) + map_swap(animation.second.nullAnimations, it->first, target->first); + } + *didSwap = true; } - _imgui_tooltip(STRING_IMGUI_TOOLTIP_TIMELINE_ELEMENT_SHIFT_UP); - } if (canMoveDown) { - if (!canMoveUp) + { ImGui::Dummy(IMGUI_ICON_BUTTON_SIZE); - - ImGui::SameLine(); + ImGui::SameLine(0.0f, ImGui::GetStyle().ItemSpacing.x); + } + else + ImGui::SameLine(); auto target = isReversed ? std::prev(it) : std::next(it); if (target != map.end() && ImGui::ImageButton(STRING_IMGUI_TIMELINE_ELEMENT_SHIFT_BELOW, self->resources->textures[TEXTURE_ARROW_DOWN].handle, IMGUI_ICON_SIZE)) { map_swap(map, it->first, target->first); + + for (auto& animation : self->anm2->animations) + { + if (type == ANM2_LAYER_ANIMATION) + map_swap(animation.second.layerAnimations, it->first, target->first); + else if (type == ANM2_NULL_ANIMATION) + map_swap(animation.second.nullAnimations, it->first, target->first); + } + *didSwap = true; } - _imgui_tooltip(STRING_IMGUI_TOOLTIP_TIMELINE_ELEMENT_SHIFT_DOWN); - } if (isArrows) @@ -161,14 +334,17 @@ _imgui_timeline_element ImGui::SameLine(); } }; - + if (type == ANM2_LAYER_ANIMATION) { auto it = std::find_if(self->anm2->layers.begin(), self->anm2->layers.end(), [&](const auto& pair) { return &pair.second == layer; }); if (it != self->anm2->layers.end()) - arrows_draw(it, self->anm2->layers.begin(), self->anm2->layers.end(), self->anm2->layers, &isSwap, isReversed); + { + arrows_draw(it, self->anm2->layers.begin(), self->anm2->layers.end(), + self->anm2->layers, self->anm2->animations, &isSwap, isReversed); + } } if (type == ANM2_NULL_ANIMATION) @@ -177,38 +353,32 @@ _imgui_timeline_element [&](const auto& pair) { return &pair.second == null; }); if (it != self->anm2->nulls.end()) - arrows_draw(it, self->anm2->nulls.begin(), self->anm2->nulls.end(), self->anm2->nulls, &isSwap, isReversed); + { + arrows_draw(it, self->anm2->nulls.begin(), self->anm2->nulls.end(), + self->anm2->nulls, self->anm2->animations, &isSwap, isReversed); + } } } - ImGui::BeginGroup(); - ImGui::Image(iconTexture, IMGUI_ICON_SIZE); + ImGui::SameLine(); ImGui::BeginChild(STRING_IMGUI_TIMELINE_ELEMENT_NAME_LABEL, IMGUI_TIMELINE_ELEMENT_NAME_SIZE); - + if (isSelected && isChangeable) { if (ImGui::InputText(STRING_IMGUI_TIMELINE_ANIMATION_LABEL, nameBuffer, ANM2_STRING_MAX, ImGuiInputTextFlags_EnterReturnsTrue)) - { strncpy(namePointer, nameBuffer, ANM2_STRING_MAX); - selectedElementIndex = -1; - } } else { if (ImGui::Selectable(nameVisible, isSelected)) { - selectedElementIndex = *index; - - if (selectType && selectID) - { - *selectType = type; - *selectID = *id; - } + self->frameVector = frames; + self->animationType = type; + self->timelineElementIndex = *index; } - } switch (type) @@ -222,7 +392,7 @@ _imgui_timeline_element case ANM2_NULL_ANIMATION: _imgui_tooltip(STRING_IMGUI_TOOLTIP_TIMELINE_ELEMENT_NULL); break; - case ANM2_TRIGGERS: + case ANM2_TRIGGER: _imgui_tooltip(STRING_IMGUI_TOOLTIP_TIMELINE_ELEMENT_TRIGGERS); break; default: @@ -246,7 +416,6 @@ _imgui_timeline_element if (ImGui::ImageButton(STRING_IMGUI_TIMELINE_VISIBLE, visibilityIcon, IMGUI_ICON_SIZE)) *isVisible = !*isVisible; - _imgui_tooltip(STRING_IMGUI_TOOLTIP_TIMELINE_ELEMENT_VISIBLE); ImGui::SetCursorPos(cursorPos); @@ -262,11 +431,11 @@ _imgui_timeline_element else snprintf(spritesheetIDName, ANM2_STRING_FORMATTED_MAX, STRING_IMGUI_TIMELINE_SPRITESHEET_FORMAT, *spritesheetID); + ImGui::BeginChild(STRING_IMGUI_TIMELINE_ELEMENT_SPRITESHEET_ID_LABEL, IMGUI_TIMELINE_ELEMENT_SPRITESHEET_ID_SIZE); + ImGui::Image(self->resources->textures[TEXTURE_SPRITESHEET].handle, IMGUI_ICON_SIZE); ImGui::SameLine(); - ImGui::BeginChild(STRING_IMGUI_TIMELINE_ELEMENT_SPRITESHEET_ID_LABEL, IMGUI_TIMELINE_SPRITESHEET_ID_SIZE); - if (selectedSpritesheetIndex == *index) { if (ImGui::InputInt(STRING_IMGUI_TIMELINE_ELEMENT_SPRITESHEET_ID_LABEL, spritesheetID, 0, 0, ImGuiInputTextFlags_None)) @@ -277,7 +446,6 @@ _imgui_timeline_element if (ImGui::Selectable(spritesheetIDName)) selectedSpritesheetIndex = *index; } - _imgui_tooltip(STRING_IMGUI_TOOLTIP_TIMELINE_ELEMENT_SPRITESHEET); ImGui::EndChild(); @@ -287,8 +455,8 @@ _imgui_timeline_element if (isShowRect) { ImTextureID rectIcon = *isShowRect - ? self->resources->textures[TEXTURE_RECT_HIDE].handle - : self->resources->textures[TEXTURE_RECT_SHOW].handle; + ? self->resources->textures[TEXTURE_RECT_SHOW].handle + : self->resources->textures[TEXTURE_RECT_HIDE].handle; ImGui::SetCursorPosX(ImGui::GetCursorPosX() + ImGui::GetContentRegionAvail().x - ((IMGUI_ICON_BUTTON_SIZE.x - ImGui::GetStyle().FramePadding.x * 2) * 4)); @@ -300,105 +468,439 @@ _imgui_timeline_element if (ImGui::IsMouseClicked(0) && !ImGui::IsAnyItemHovered() && ImGui::IsWindowHovered(ImGuiHoveredFlags_AllowWhenBlockedByActiveItem)) { - selectedElementIndex = -1; + self->timelineElementIndex = -1; selectedSpritesheetIndex = -1; } - ImGui::EndGroup(); - + ImGui::EndChild(); + + *index = *index + 1; + ImGui::PopID(); + + ImGui::SetCursorPosY(cursorPosY + IMGUI_TIMELINE_ELEMENT_SIZE.y); +} + +/* Timeline */ +static void +_imgui_timeline(Imgui* self) +{ + /* -- Timeline -- */ + ImGui::Begin(STRING_IMGUI_TIMELINE); + + if (self->animationID != -1) + { + ImVec2 cursorPos; + ImVec2 mousePos; + ImVec2 mousePosRelative; + s32 index = 0; + Anm2Animation* animation = &self->anm2->animations[self->animationID]; + ImVec2 frameSize = IMGUI_TIMELINE_FRAME_SIZE; + ImVec2 pickerPos; + ImVec2 lineStart; + ImVec2 lineEnd; + ImDrawList* drawList; + static f32 elementScrollX = 0; + static f32 elementScrollY = 0; + ImVec2 frameIndicesSize = {frameSize.x * animation->frameNum, IMGUI_TIMELINE_FRAME_INDICES_SIZE.y}; + s32 idDefault = 0; + const char* buttonText = self->preview->isPlaying ? STRING_IMGUI_TIMELINE_PAUSE : STRING_IMGUI_TIMELINE_PLAY; + const char* buttonTooltipText = self->preview->isPlaying ? STRING_IMGUI_TOOLTIP_TIMELINE_PAUSE : STRING_IMGUI_TOOLTIP_TIMELINE_PLAY; + + ImVec2 timelineSize = {0, ImGui::GetContentRegionAvail().y - IMGUI_TIMELINE_OFFSET_Y}; + + /* Generally, things need to be dranw out of apparent order for correct scrolling to work. */ + + /* Main timeline child */ + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0)); + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0)); + ImGui::BeginChild(STRING_IMGUI_TIMELINE_CHILD, timelineSize, true); + + cursorPos = ImGui::GetCursorPos(); + + drawList = ImGui::GetWindowDrawList(); + + /* Element frames */ + ImGui::SetCursorPos(ImVec2(cursorPos.x + IMGUI_TIMELINE_ELEMENT_SIZE.x, cursorPos.y + IMGUI_TIMELINE_VIEWER_SIZE.y)); + ImGui::BeginChild(STRING_IMGUI_TIMELINE_ELEMENT_FRAMES, IMGUI_TIMELINE_ELEMENT_FRAMES_SIZE, true); + ImGui::PopStyleVar(); + ImGui::PopStyleVar(); + elementScrollX = ImGui::GetScrollX(); + elementScrollY = ImGui::GetScrollY(); + + _imgui_timeline_element_frames(self, &animation->rootAnimation, &idDefault, &index, ANM2_ROOT_ANIMATION); + + for (auto it = animation->layerAnimations.rbegin(); it != animation->layerAnimations.rend(); it++) + { + s32 id = it->first; + Anm2LayerAnimation& layer = it->second; + + _imgui_timeline_element_frames(self, &layer, &id, &index, ANM2_LAYER_ANIMATION); + } + + for (auto & [id, null] : animation->nullAnimations) + _imgui_timeline_element_frames(self, &null, (s32*)&id, &index, ANM2_NULL_ANIMATION); + + _imgui_timeline_element_frames(self, &animation->triggers, &idDefault, &index, ANM2_TRIGGER); + + ImGui::EndChild(); + + ImGui::SetCursorPos(cursorPos); + + /* Element bar */ + ImGui::BeginChild(STRING_IMGUI_TIMELINE_ELEMENTS, IMGUI_TIMELINE_ELEMENT_SIZE, true); + ImGui::Text(STRING_IMGUI_TIMELINE_ELEMENTS); ImGui::EndChild(); + /* Viewer */ if (animation->frameNum > 0) { + bool isMouseInElementsRegion = false; + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0)); + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0)); + ImGui::SameLine(); - - ImGui::PushID(*index); - - ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0)); - ImGui::BeginChild(STRING_IMGUI_TIMELINE_ELEMENT_TIMELINE, frameListSize, true); - - for (s32 i = 0; i < animation->frameNum; i++) - { - ImGui::PushID(i); - if (ImGui::Button(" ", IMGUI_TIMELINE_FRAME_SIZE)) - { + ImGui::BeginChild(STRING_IMGUI_TIMELINE_FRAME_INDICES, frameIndicesSize, true); + ImGui::SetScrollX(elementScrollX); + + ImVec2 elementsRectMin = ImGui::GetWindowPos(); + ImVec2 elementsRectMax = ImVec2(elementsRectMin.x + frameIndicesSize.x, elementsRectMin.y + frameIndicesSize.y); + + cursorPos = ImGui::GetCursorScreenPos(); + mousePos = ImGui::GetMousePos(); + mousePosRelative = ImVec2(ImGui::GetMousePos().x - cursorPos.x, ImGui::GetMousePos().y - cursorPos.y); + + isMouseInElementsRegion = + mousePos.x >= elementsRectMin.x && mousePos.x < elementsRectMax.x && + mousePos.y >= elementsRectMin.y && mousePos.y < elementsRectMax.y; + + if (isMouseInElementsRegion && ImGui::IsMouseDown(0) && !self->preview->isPlaying) + { + if (mousePosRelative.x >= 0 && mousePosRelative.x < frameIndicesSize.x) + { + s32 index = (s32)(mousePosRelative.x / frameSize.x); + if (index >= 0 && index < animation->frameNum) + self->preview->time = (f32)index; } - ImGui::SameLine(0.0f, 0.0f); - ImGui::PopID(); + else if (mousePosRelative.x < 0) + self->preview->time = 0; + else + self->preview->time = (f32)(animation->frameNum - 1); } - ImGui::EndChild(); + for (s32 i = 0; i < animation->frameNum; i++) + { + ImVec2 imagePos = ImGui::GetCursorScreenPos(); + + if (i % IMGUI_TIMELINE_FRAME_INDICES_MULTIPLE == 0) + { + char frameIndexString[IMGUI_TIMELINE_FRAME_INDICES_STRING_MAX]; + snprintf(frameIndexString, IMGUI_TIMELINE_FRAME_INDICES_STRING_MAX, "%i", i); + + ImVec2 bgMin = imagePos; + ImVec2 bgMax = ImVec2(imagePos.x + IMGUI_TIMELINE_FRAME_SIZE.x, + imagePos.y + IMGUI_TIMELINE_FRAME_SIZE.y); + + ImU32 bgColor = ImGui::GetColorU32(IMGUI_FRAME_INDICES_OVERLAY_COLOR); + drawList->AddRectFilled(bgMin, bgMax, bgColor); + + ImVec2 textSize = ImGui::CalcTextSize(frameIndexString); + + ImVec2 textPos; + textPos.x = imagePos.x + (IMGUI_TIMELINE_FRAME_SIZE.x - textSize.x) / 2.0f; + textPos.y = imagePos.y + (IMGUI_TIMELINE_FRAME_SIZE.y - textSize.y) / 2.0f; + + drawList->AddText(textPos, ImGui::GetColorU32(ImGuiCol_Text), frameIndexString); + } + else + { + ImVec2 bgMin = imagePos; + ImVec2 bgMax = ImVec2(imagePos.x + IMGUI_TIMELINE_FRAME_SIZE.x, + imagePos.y + IMGUI_TIMELINE_FRAME_SIZE.y); + + ImU32 bgColor = ImGui::GetColorU32(IMGUI_FRAME_INDICES_COLOR); + drawList->AddRectFilled(bgMin, bgMax, bgColor); + } + + ImGui::Image(self->resources->textures[TEXTURE_FRAME].handle, IMGUI_TIMELINE_FRAME_SIZE); + + ImGui::SameLine(); + } + + ImGui::PopStyleVar(); ImGui::PopStyleVar(); - ImGui::PopID(); + pickerPos = ImVec2(cursorPos.x + self->preview->time * frameSize.x, cursorPos.y); + lineStart = ImVec2(pickerPos.x + frameSize.x / 2.0f, pickerPos.y + frameSize.y); + lineEnd = ImVec2(lineStart.x, lineStart.y + timelineSize.y - IMGUI_TIMELINE_FRAME_SIZE.y); + + ImGui::GetWindowDrawList()->AddImage( + self->resources->textures[TEXTURE_PICKER].handle, + pickerPos, + ImVec2(pickerPos.x + frameSize.x, pickerPos.y + frameSize.y) + ); + + ImGui::GetForegroundDrawList()->AddRectFilled( + ImVec2(lineStart.x - IMGUI_PICKER_LINE_SIZE, lineStart.y), + ImVec2(lineStart.x + IMGUI_PICKER_LINE_SIZE, lineEnd.y), + IMGUI_PICKER_LINE_COLOR + ); + + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0)); + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0)); + ImGui::EndChild(); + ImGui::PopStyleVar(); + ImGui::PopStyleVar(); + } + else + { + ImGui::SameLine(); + ImGui::Dummy(frameIndicesSize); } - *index = *index + 1; + /* Element list */ + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0)); + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0)); + ImGui::BeginChild(STRING_IMGUI_TIMELINE_ELEMENT_LIST, IMGUI_TIMELINE_ELEMENT_LIST_SIZE, true, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse); + ImGui::PopStyleVar(); + ImGui::PopStyleVar(); + ImGui::SetScrollY(elementScrollY); + + _imgui_timeline_element(self, &animation->rootAnimation, &idDefault, &index, ANM2_ROOT_ANIMATION); + + for (auto it = animation->layerAnimations.rbegin(); it != animation->layerAnimations.rend(); it++) + { + s32 id = it->first; + Anm2LayerAnimation& layer = it->second; + + _imgui_timeline_element(self, &layer, &id, &index, ANM2_LAYER_ANIMATION); + } + + for (auto & [id, null] : animation->nullAnimations) + _imgui_timeline_element(self, &null, (s32*)&id, &index, ANM2_NULL_ANIMATION); + + _imgui_timeline_element(self, &animation->triggers, &idDefault, &index, ANM2_TRIGGER); + + ImGui::EndChild(); + + ImGui::EndChild(); + + /* Buttons */ + if (ImGui::Button(buttonText)) + self->preview->isPlaying = !self->preview->isPlaying; + _imgui_tooltip(buttonTooltipText); + + ImGui::SameLine(); + + if (ImGui::Button(STRING_IMGUI_TIMELINE_ELEMENT_ADD)) + ImGui::OpenPopup(STRING_IMGUI_TIMELINE_ELEMENT_ADD_MENU); + _imgui_tooltip(STRING_IMGUI_TOOLTIP_TIMELINE_ELEMENT_ADD); + + if (ImGui::BeginPopup(STRING_IMGUI_TIMELINE_ELEMENT_ADD_MENU)) + { + if (ImGui::Selectable(STRING_IMGUI_TIMELINE_ELEMENT_ADD_MENU_LAYER)) + anm2_layer_add(self->anm2); + + if (ImGui::Selectable(STRING_IMGUI_TIMELINE_ELEMENT_ADD_MENU_NULL)) + anm2_null_add(self->anm2); + + ImGui::EndPopup(); + } + + ImGui::SameLine(); + + if + ( + ImGui::Button(STRING_IMGUI_TIMELINE_ELEMENT_REMOVE) && + self->animationID != -1 + ) + { + switch (self->animationType) + { + case ANM2_LAYER_ANIMATION: + anm2_layer_remove(self->anm2, self->animationID); + break; + case ANM2_NULL_ANIMATION: + anm2_null_remove(self->anm2, self->animationID); + break; + default: + break; + } + + self->animationID = -1; + self->timelineElementIndex = -1; + self->animationType = ANM2_NONE; + } + _imgui_tooltip(STRING_IMGUI_TOOLTIP_TIMELINE_ELEMENT_REMOVE); + + ImGui::SameLine(); + + if (ImGui::Button(STRING_IMGUI_TIMELINE_FRAME_ADD)) + { + if (self->frameVector) + { + Anm2Animation* animation = &self->anm2->animations[self->animationID]; + std::vector* addFrameVector; + std::vector* addTriggerVector; + Anm2Frame frame = Anm2Frame{}; + Anm2Trigger trigger = Anm2Trigger{}; + s32 delay = 0; + s32 frameDelayCount = 0; + s32 index = -1; + + switch (self->animationType) + { + case ANM2_ROOT_ANIMATION: + case ANM2_LAYER_ANIMATION: + case ANM2_NULL_ANIMATION: + addFrameVector = (std::vector*)self->frameVector; + for (auto& frameCheck : *addFrameVector) + frameDelayCount += frameCheck.delay; + + if (frameDelayCount + frame.delay > animation->frameNum) + break; + + if (self->frameIndex > -1) + { + frame = (*addFrameVector)[self->frameIndex]; + + if (frameDelayCount + frame.delay > animation->frameNum) + frame.delay = animation->frameNum - frameDelayCount; + + if (frame.delay <= 0) + break; + + index = self->frameIndex + 1; + + addFrameVector->insert(addFrameVector->begin() + index, frame); + } + else + { + index = (s32)addFrameVector->size(); + addFrameVector->push_back(frame); + } + + self->frameIndex = index; + break; + case ANM2_TRIGGER: + addTriggerVector = (std::vector*)self->frameVector; + + if (self->preview->time > -1) + { + index = (s32)self->preview->time; + bool isTrigger = false; + + for (const auto & existingTrigger : *addTriggerVector) + if (existingTrigger.atFrame == index) + { + isTrigger = true; + break; + } + + if (isTrigger) + break; + + trigger.atFrame = index; + addTriggerVector->push_back(trigger); + + self->frameIndex = index; + } + else + break; + + break; + default: + break; + } + } + } + _imgui_tooltip(STRING_IMGUI_TOOLTIP_TIMELINE_FRAME_ADD); + + ImGui::SameLine(); + + if (ImGui::Button(STRING_IMGUI_TIMELINE_FRAME_REMOVE)) + { + if (self->frameVector && self->frameIndex > -1) + { + std::vector* removeFrameVector; + std::vector* removeTriggerVector; + + switch (self->animationType) + { + case ANM2_ROOT_ANIMATION: + case ANM2_LAYER_ANIMATION: + case ANM2_NULL_ANIMATION: + removeFrameVector = (std::vector*)self->frameVector; + removeFrameVector->erase(removeFrameVector->begin() + self->frameIndex); + break; + case ANM2_TRIGGER: + removeTriggerVector = (std::vector*)self->frameVector; + removeTriggerVector->erase(removeTriggerVector->begin() + self->frameIndex); + break; + break; + default: + break; + } + + self->frameIndex = -1; + self->frameVector = NULL; + self->animationType = ANM2_NONE; + } + } + _imgui_tooltip(STRING_IMGUI_TOOLTIP_TIMELINE_FRAME_REMOVE); + + ImGui::SameLine(); + + ImGui::SetNextItemWidth(IMGUI_TIMELINE_ANIMATION_LENGTH_WIDTH); + ImGui::InputInt(STRING_IMGUI_TIMELINE_ANIMATION_LENGTH, &animation->frameNum); + _imgui_tooltip(STRING_IMGUI_TOOLTIP_TIMELINE_ANIMATION_LENGTH); + + animation->frameNum = CLAMP(animation->frameNum, ANM2_FRAME_NUM_MIN, ANM2_FRAME_NUM_MAX); + + ImGui::SameLine(); + + /* FPS */ + ImGui::SetNextItemWidth(IMGUI_TIMELINE_FPS_WIDTH); + ImGui::SameLine(); + ImGui::InputInt(STRING_IMGUI_TIMELINE_FPS, &self->anm2->fps); + _imgui_tooltip(STRING_IMGUI_TOOLTIP_TIMELINE_FPS); + + self->anm2->fps = CLAMP(self->anm2->fps, ANM2_FPS_MIN, ANM2_FPS_MAX); + + ImGui::SameLine(); + + /* Loop */ + ImGui::Checkbox(STRING_IMGUI_TIMELINE_LOOP, &animation->isLoop); + _imgui_tooltip(STRING_IMGUI_TOOLTIP_TIMELINE_LOOP); + + ImGui::SameLine(); + + /* CreatedBy */ + ImGui::SetNextItemWidth(IMGUI_TIMELINE_CREATED_BY_WIDTH); + ImGui::SameLine(); + ImGui::InputText(STRING_IMGUI_TIMELINE_CREATED_BY, self->anm2->createdBy, ANM2_STRING_MAX); + _imgui_tooltip(STRING_IMGUI_TOOLTIP_TIMELINE_CREATED_BY); + + ImGui::SameLine(); + + /* CreatedOn */ + ImGui::Text(STRING_IMGUI_TIMELINE_CREATED_ON, self->anm2->createdOn); + + ImGui::SameLine(); + + /* Version */ + ImGui::Text(STRING_IMGUI_TIMELINE_VERSION, self->anm2->version); + } + + ImGui::End(); } -void -imgui_init -( - Imgui* self, - Dialog* dialog, - Resources* resources, - Input* input, - Anm2* anm2, - Preview* preview, - SDL_Window* window, - SDL_GLContext* glContext -) +/* Taskbar */ +static void +_imgui_taskbar(Imgui* self) { - IMGUI_CHECKVERSION(); - - self->dialog = dialog; - self->resources = resources; - self->input = input; - self->anm2 = anm2; - self->preview = preview; - self->window = window; - self->glContext = glContext; - - ImGui::CreateContext(); - ImGui::StyleColorsDark(); - - ImGui_ImplSDL3_InitForOpenGL(self->window, *self->glContext); - ImGui_ImplOpenGL3_Init(STRING_OPENGL_VERSION); - - ImGuiIO& io = ImGui::GetIO(); - io.ConfigFlags |= ImGuiConfigFlags_DockingEnable; - - printf(STRING_INFO_IMGUI_INIT); -} - -void -imgui_tick(Imgui* self) -{ - static s32 selectedSpritesheetID = -1; - static s32 selectedEventID = -1; - static s32 selectedAnimationID = -1; - static s32 selectedTimelineElementID = -1; - static Anm2AnimationType selectedTimelineElementType = ANM2_NONE; - static bool isInterpolated = true; - static bool isVisible = true; - static bool isHoverPreview = false; - static ImRect previewWindowRect; - static ImVec2 settingsCursorPos; - static bool isFirstTick = true; - - static f32 rotation = 0; - static s32 duration = 0; - static vec2 cropPosition = {0, 0}; - static vec2 cropSize = {0, 0}; - static vec2 pivot = {0, 0}; - static vec2 position = {0, 0}; - static vec2 xyScale = {0, 0}; - static vec3 offset = {0.0f, 0.0f, 0.0f}; - static vec4 tint = {0.0f, 0.0f, 0.0f, 1.0f}; - ImGuiWindowFlags taskbarWindowFlags = 0 | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | @@ -407,34 +909,8 @@ imgui_tick(Imgui* self) ImGuiWindowFlags_NoScrollWithMouse | ImGuiWindowFlags_NoSavedSettings; - ImGuiWindowFlags dockspaceWindowFlags = 0 | - ImGuiWindowFlags_NoTitleBar | - ImGuiWindowFlags_NoCollapse | - ImGuiWindowFlags_NoResize | - ImGuiWindowFlags_NoMove | - ImGuiWindowFlags_NoBringToFrontOnFocus | - ImGuiWindowFlags_NoNavFocus; - - ImGuiWindowFlags childWindowFlags = 0 | - ImGuiWindowFlags_NoTitleBar | - ImGuiWindowFlags_NoCollapse | - ImGuiWindowFlags_NoResize | - ImGuiWindowFlags_NoMove | - ImGuiWindowFlags_NoBringToFrontOnFocus | - ImGuiWindowFlags_NoNavFocus | - ImGuiWindowFlags_NoScrollbar | - ImGuiWindowFlags_NoScrollWithMouse; - - ImGuiDockNodeFlags dockNodeFlags = 0 | - ImGuiDockNodeFlags_PassthruCentralNode; - - ImGui_ImplSDL3_NewFrame(); - ImGui_ImplOpenGL3_NewFrame(); - ImGui::NewFrame(); - ImGuiViewport* viewport = ImGui::GetMainViewport(); - /* Taskbar */ ImGui::SetNextWindowPos(viewport->Pos); ImGui::SetNextWindowSize(ImVec2(viewport->Size.x, IMGUI_TASKBAR_HEIGHT)); @@ -450,21 +926,18 @@ imgui_tick(Imgui* self) { if (ImGui::Selectable(STRING_IMGUI_FILE_NEW)) { - selectedAnimationID = -1; - selectedSpritesheetID = -1; - selectedEventID = -1; + self->animationID = -1; + self->spritesheetID = -1; + self->eventID = -1; anm2_new(self->anm2); - window_title_from_anm2_set(self->window, self->anm2); - } if (ImGui::Selectable(STRING_IMGUI_FILE_OPEN)) { - selectedAnimationID = -1; - selectedEventID = -1; - selectedSpritesheetID = -1; + self->animationID = -1; + self->eventID = -1; + self->spritesheetID = -1; dialog_anm2_open(self->dialog); - window_title_from_anm2_set(self->window, self->anm2); } if (ImGui::Selectable(STRING_IMGUI_FILE_SAVE)) @@ -478,52 +951,18 @@ imgui_tick(Imgui* self) if (ImGui::Selectable(STRING_IMGUI_FILE_SAVE_AS)) { dialog_anm2_save(self->dialog); - window_title_from_anm2_set(self->window, self->anm2); } ImGui::EndPopup(); } ImGui::End(); - - /* Dockspace */ - ImGui::SetNextWindowPos(ImVec2(viewport->Pos.x, viewport->Pos.y + IMGUI_TASKBAR_HEIGHT)); - ImGui::SetNextWindowSize(ImVec2(viewport->Size.x, viewport->Size.y - IMGUI_TASKBAR_HEIGHT)); - ImGui::SetNextWindowViewport(viewport->ID); +} - ImGui::Begin(STRING_IMGUI_WINDOW, NULL, dockspaceWindowFlags); - - ImGuiID dockspace_id = ImGui::GetID(STRING_IMGUI_DOCKSPACE); - ImGui::DockSpace(dockspace_id, ImVec2(0.0f, 0.0f), dockNodeFlags); - - ImGui::End(); - - /* -- Properties -- */ - ImGui::Begin(STRING_IMGUI_PROPERTIES); - - /* FPS */ - ImGui::AlignTextToFramePadding(); - ImGui::Text(STRING_IMGUI_PROPERTIES_FPS); - ImGui::SameLine(); - ImGui::SliderInt(STRING_IMGUI_PROPERTIES_FPS_LABEL, &self->anm2->fps, ANM2_FPS_MIN, ANM2_FPS_MAX); - _imgui_tooltip(STRING_IMGUI_TOOLTIP_PROPERTIES_FPS); - - /* CreatedBy */ - ImGui::AlignTextToFramePadding(); - ImGui::Text(STRING_IMGUI_PROPERTIES_CREATED_BY); - ImGui::SameLine(); - ImGui::InputText(STRING_IMGUI_PROPERTIES_CREATED_BY_LABEL, self->anm2->createdBy, ANM2_STRING_MAX); - _imgui_tooltip(STRING_IMGUI_TOOLTIP_PROPERTIES_CREATED_BY); - - /* CreatedOn */ - ImGui::Text(STRING_IMGUI_PROPERTIES_CREATED_ON, self->anm2->createdOn); - - /* Version */ - ImGui::Text(STRING_IMGUI_PROPERTIES_VERSION, self->anm2->version); - - ImGui::End(); - - /* -- Animations -- */ +/* Animations */ +static void +_imgui_animations(Imgui* self) +{ ImGui::Begin(STRING_IMGUI_ANIMATIONS); /* Iterate through all animations, can be selected and names can be edited */ @@ -531,15 +970,11 @@ imgui_tick(Imgui* self) { char name[ANM2_STRING_FORMATTED_MAX]; char oldName[ANM2_STRING_MAX]; - bool isSelected = selectedAnimationID == id; - static s32 defaultAnimationID = -1; + bool isSelected = self->animationID == id; - /* Distinguish default animation; if animation has the same name, only mark the first one as default */ + /* Distinguish default animation */ if (strcmp(animation.name, self->anm2->defaultAnimation) == 0) - { snprintf(name, ANM2_STRING_FORMATTED_MAX, STRING_IMGUI_ANIMATIONS_DEFAULT_ANIMATION_FORMAT, animation.name); - defaultAnimationID = id; - } else strncpy(name, animation.name, ANM2_STRING_FORMATTED_MAX - 1); @@ -552,16 +987,22 @@ imgui_tick(Imgui* self) { if (ImGui::InputText(STRING_IMGUI_ANIMATIONS_ANIMATION_LABEL, animation.name, ANM2_STRING_MAX, ImGuiInputTextFlags_EnterReturnsTrue)) { - if (id == defaultAnimationID) strncpy(self->anm2->defaultAnimation, animation.name, ANM2_STRING_MAX); - selectedAnimationID = -1; + self->animationID = -1; } } else { if (ImGui::Selectable(name, isSelected)) - selectedAnimationID = id; + { + self->animationID = id; + self->frameIndex = -1; + self->frameVector = NULL; + self->animationType = ANM2_NONE; + self->preview->isPlaying = false; + self->preview->time = 0.0f; + } } _imgui_tooltip(STRING_IMGUI_TOOLTIP_ANIMATIONS_SELECT); @@ -571,12 +1012,10 @@ imgui_tick(Imgui* self) if (ImGui::Button(STRING_IMGUI_ANIMATIONS_ADD)) { - s32 id; bool isDefault = self->anm2->animations.size() == 0; /* first animation is default automatically */ - - id = anm2_animation_add(self->anm2); + s32 id = anm2_animation_add(self->anm2); - selectedAnimationID = id; + self->animationID = id; if (isDefault) strncpy(self->anm2->defaultAnimation, self->anm2->animations[id].name, ANM2_STRING_MAX); @@ -584,43 +1023,52 @@ imgui_tick(Imgui* self) _imgui_tooltip(STRING_IMGUI_TOOLTIP_ANIMATIONS_ADD); ImGui::SameLine(); - - if (selectedAnimationID != -1) - { - /* Remove */ - if (ImGui::Button(STRING_IMGUI_ANIMATIONS_REMOVE)) - { - anm2_animation_remove(self->anm2, selectedAnimationID); - selectedAnimationID = -1; - } - _imgui_tooltip(STRING_IMGUI_TOOLTIP_ANIMATIONS_REMOVE); - - ImGui::SameLine(); - /* Duplicate */ - if (ImGui::Button(STRING_IMGUI_ANIMATIONS_DUPLICATE)) + /* Remove */ + if (ImGui::Button(STRING_IMGUI_ANIMATIONS_REMOVE)) + { + if (self->animationID != -1) + { + anm2_animation_remove(self->anm2, self->animationID); + self->animationID = -1; + } + } + _imgui_tooltip(STRING_IMGUI_TOOLTIP_ANIMATIONS_REMOVE); + + ImGui::SameLine(); + + /* Duplicate */ + if (ImGui::Button(STRING_IMGUI_ANIMATIONS_DUPLICATE)) + { + if (self->animationID > -1) { s32 id = map_next_id_get(self->anm2->animations); - self->anm2->animations.insert({id, self->anm2->animations[selectedAnimationID]}); - selectedAnimationID = id; + self->anm2->animations.insert({id, self->anm2->animations[self->animationID]}); + self->animationID = id; } - _imgui_tooltip(STRING_IMGUI_TOOLTIP_ANIMATIONS_DUPLICATE); - - if (ImGui::Button(STRING_IMGUI_ANIMATIONS_SET_AS_DEFAULT)) - { - strncpy(self->anm2->defaultAnimation, self->anm2->animations[selectedAnimationID].name, ANM2_STRING_MAX); - selectedAnimationID = -1; - } - - _imgui_tooltip(STRING_IMGUI_TOOLTIP_ANIMATIONS_SET_AS_DEFAULT); } + _imgui_tooltip(STRING_IMGUI_TOOLTIP_ANIMATIONS_DUPLICATE); + + ImGui::SameLine(); + + /* Set as default */ + if (ImGui::Button(STRING_IMGUI_ANIMATIONS_SET_AS_DEFAULT)) + { + if (self->animationID > -1) + strncpy(self->anm2->defaultAnimation, self->anm2->animations[self->animationID].name, ANM2_STRING_MAX); + } + _imgui_tooltip(STRING_IMGUI_TOOLTIP_ANIMATIONS_SET_AS_DEFAULT); if (ImGui::IsMouseClicked(0) && !ImGui::IsAnyItemHovered() && ImGui::IsWindowHovered(ImGuiHoveredFlags_AllowWhenBlockedByActiveItem)) - selectedAnimationID = -1; + self->animationID = -1; ImGui::End(); +} - /* -- Events -- */ +/* Events */ +static void +_imgui_events(Imgui* self) +{ ImGui::Begin(STRING_IMGUI_EVENTS); /* Iterate through all events, can be selected and names can be edited */ @@ -636,17 +1084,17 @@ imgui_tick(Imgui* self) ImGui::Image(self->resources->textures[TEXTURE_EVENT].handle, IMGUI_ICON_SIZE); ImGui::SameLine(); - isSelected = selectedEventID == id; + isSelected = self->eventID == id; if (isSelected) { if (ImGui::InputText(STRING_IMGUI_EVENTS_EVENT_LABEL, event.name, ANM2_STRING_MAX, ImGuiInputTextFlags_EnterReturnsTrue)) - selectedEventID = -1; + self->eventID = -1; } else { if (ImGui::Selectable(eventString, isSelected)) - selectedEventID = id; + self->eventID = id; } _imgui_tooltip(STRING_IMGUI_TOOLTIP_EVENTS_SELECT); @@ -658,30 +1106,34 @@ imgui_tick(Imgui* self) { s32 id = map_next_id_get(self->anm2->events); self->anm2->events[id] = Anm2Event{}; - selectedEventID = id; + self->eventID = id; } _imgui_tooltip(STRING_IMGUI_TOOLTIP_EVENTS_ADD); ImGui::SameLine(); - if (selectedEventID != -1) + if (ImGui::Button(STRING_IMGUI_EVENTS_REMOVE)) { - if (ImGui::Button(STRING_IMGUI_EVENTS_REMOVE)) + if (self->eventID != -1) { - self->anm2->events.erase(selectedEventID); - selectedEventID = -1; + self->anm2->events.erase(self->eventID); + self->eventID = -1; } - _imgui_tooltip(STRING_IMGUI_TOOLTIP_EVENTS_REMOVE); } + _imgui_tooltip(STRING_IMGUI_TOOLTIP_EVENTS_REMOVE); if (ImGui::IsMouseClicked(0) && !ImGui::IsAnyItemHovered() && ImGui::IsWindowHovered(ImGuiHoveredFlags_AllowWhenBlockedByActiveItem)) - selectedEventID = -1; + self->eventID = -1; ImGui::End(); +} - /* -- Spritesheets -- */ +/* Spritesheets */ +static void +_imgui_spritesheets(Imgui* self) +{ ImGui::Begin(STRING_IMGUI_SPRITESHEETS); - + for (auto [id, spritesheet] : self->anm2->spritesheets) { ImVec2 spritesheetPreviewSize = IMGUI_SPRITESHEET_PREVIEW_SIZE; @@ -704,9 +1156,9 @@ imgui_tick(Imgui* self) ImGui::Image(self->resources->textures[TEXTURE_SPRITESHEET].handle, IMGUI_ICON_SIZE); ImGui::SameLine(); - isSelected = selectedSpritesheetID == id; + isSelected = self->spritesheetID == id; if (ImGui::Selectable(spritesheetString, isSelected)) - selectedSpritesheetID = id; + self->spritesheetID = id; _imgui_tooltip(STRING_IMGUI_TOOLTIP_SPRITESHEETS_SELECT); @@ -723,91 +1175,133 @@ imgui_tick(Imgui* self) ImGui::SameLine(); - if (selectedSpritesheetID != -1) + /* Remove */ + if (ImGui::Button(STRING_IMGUI_SPRITESHEETS_REMOVE)) { - /* Remove */ - if (ImGui::Button(STRING_IMGUI_SPRITESHEETS_REMOVE)) + if (self->spritesheetID > -1) { - self->resources->loadedTextures.erase(selectedSpritesheetID); - self->anm2->spritesheets.erase(selectedSpritesheetID); - selectedSpritesheetID = -1; + self->resources->loadedTextures.erase(self->spritesheetID); + self->anm2->spritesheets.erase(self->spritesheetID); + self->spritesheetID = -1; } - _imgui_tooltip(STRING_IMGUI_TOOLTIP_SPRITESHEETS_REMOVE); + } + _imgui_tooltip(STRING_IMGUI_TOOLTIP_SPRITESHEETS_REMOVE); - ImGui::SameLine(); - - /* Reload */ - if (ImGui::Button(STRING_IMGUI_SPRITESHEETS_RELOAD)) - anm2_spritesheet_texture_load(self->anm2, self->resources, self->anm2->spritesheets[selectedSpritesheetID].path, selectedSpritesheetID); - _imgui_tooltip(STRING_IMGUI_TOOLTIP_SPRITESHEETS_RELOAD); - ImGui::SameLine(); - - /* Replace */ - if (ImGui::Button(STRING_IMGUI_SPRITESHEETS_REPLACE)) + ImGui::SameLine(); + + /* Reload */ + if (ImGui::Button(STRING_IMGUI_SPRITESHEETS_RELOAD)) + { + if (self->spritesheetID > -1) + anm2_spritesheet_texture_load(self->anm2, self->resources, self->anm2->spritesheets[self->spritesheetID].path, self->spritesheetID); + } + _imgui_tooltip(STRING_IMGUI_TOOLTIP_SPRITESHEETS_RELOAD); + ImGui::SameLine(); + + /* Replace */ + if (ImGui::Button(STRING_IMGUI_SPRITESHEETS_REPLACE)) + { + if (self->spritesheetID > -1) { - self->dialog->replaceID = selectedSpritesheetID; + self->dialog->replaceID = self->spritesheetID; dialog_png_replace(self->dialog); } - _imgui_tooltip(STRING_IMGUI_TOOLTIP_SPRITESHEETS_REPLACE); } + _imgui_tooltip(STRING_IMGUI_TOOLTIP_SPRITESHEETS_REPLACE); if (ImGui::IsMouseClicked(0) && !ImGui::IsAnyItemHovered() && ImGui::IsWindowHovered(ImGuiHoveredFlags_AllowWhenBlockedByActiveItem)) - selectedSpritesheetID = -1; + self->spritesheetID = -1; ImGui::End(); +} - /* -- Frame Properties -- */ +/* Animation Preview */ +static void +_imgui_animation_preview(Imgui* self) +{ + static bool isHoverPreview = false; + static bool isPreviewCenter = false; + + ImGui::Begin(STRING_IMGUI_ANIMATION_PREVIEW, NULL, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse); + + /* Grid settings */ + ImGui::BeginChild(STRING_IMGUI_ANIMATION_PREVIEW_GRID_SETTINGS, IMGUI_ANIMATION_PREVIEW_SETTINGS_CHILD_SIZE, true); - ImGui::Begin(STRING_IMGUI_FRAME_PROPERTIES); + /* Grid toggle */ + ImGui::Checkbox(STRING_IMGUI_ANIMATION_PREVIEW_GRID, &self->settings->isGrid); + _imgui_tooltip(STRING_IMGUI_TOOLTIP_ANIMATION_PREVIEW_GRID); - ImGui::DragFloat2(STRING_IMGUI_FRAME_PROPERTIES_POSITION, value_ptr(position), 1, 0, 0, "%.0f"); - _imgui_tooltip(STRING_IMGUI_TOOLTIP_FRAME_PROPERTIES_POSITION); + ImGui::SameLine(); + + /* Grid Color */ + ImGui::ColorEdit4(STRING_IMGUI_ANIMATION_PREVIEW_GRID_COLOR, (f32*)&self->settings->gridColorR, ImGuiColorEditFlags_NoInputs); + _imgui_tooltip(STRING_IMGUI_TOOLTIP_ANIMATION_PREVIEW_GRID_COLOR); + + /* Grid Size */ + ImGui::InputInt2(STRING_IMGUI_ANIMATION_PREVIEW_GRID_SIZE, (s32*)&self->settings->gridSizeX); + self->settings->gridSizeX = CLAMP(self->settings->gridSizeX, PREVIEW_GRID_MIN, PREVIEW_GRID_MAX); + self->settings->gridSizeY = CLAMP(self->settings->gridSizeY, PREVIEW_GRID_MIN, PREVIEW_GRID_MAX); + _imgui_tooltip(STRING_IMGUI_TOOLTIP_ANIMATION_PREVIEW_GRID_SIZE); + + /* Grid Offset */ + ImGui::InputInt2(STRING_IMGUI_ANIMATION_PREVIEW_GRID_OFFSET, (s32*)&self->settings->gridOffsetX); + _imgui_tooltip(STRING_IMGUI_TOOLTIP_ANIMATION_PREVIEW_GRID_OFFSET); - ImGui::DragFloat2(STRING_IMGUI_FRAME_PROPERTIES_CROP_POSITION, value_ptr(cropPosition), 1, 0, 0, "%.0f"); - _imgui_tooltip(STRING_IMGUI_TOOLTIP_FRAME_PROPERTIES_CROP_POSITION); - - ImGui::DragFloat2(STRING_IMGUI_FRAME_PROPERTIES_CROP_SIZE, value_ptr(cropSize), 1, 0, 0, "%.0f"); - _imgui_tooltip(STRING_IMGUI_TOOLTIP_FRAME_PROPERTIES_CROP_SIZE); - - ImGui::DragFloat2(STRING_IMGUI_FRAME_PROPERTIES_PIVOT, value_ptr(pivot), 1, 0, 0, "%.0f"); - _imgui_tooltip(STRING_IMGUI_TOOLTIP_FRAME_PROPERTIES_PIVOT); - - ImGui::DragFloat2(STRING_IMGUI_FRAME_PROPERTIES_SCALE, value_ptr(xyScale), 1.0, 0, 0, "%.1f"); - _imgui_tooltip(STRING_IMGUI_TOOLTIP_FRAME_PROPERTIES_SCALE); - - ImGui::DragFloat(STRING_IMGUI_FRAME_PROPERTIES_ROTATION, &rotation, 1, 0, 0, "%.1f"); - _imgui_tooltip(STRING_IMGUI_TOOLTIP_FRAME_PROPERTIES_ROTATION); - - ImGui::DragInt(STRING_IMGUI_FRAME_PROPERTIES_DURATION, &duration, 1, 0, 255); - _imgui_tooltip(STRING_IMGUI_TOOLTIP_FRAME_PROPERTIES_DURATION); - - ImGui::ColorEdit4(STRING_IMGUI_FRAME_PROPERTIES_TINT, value_ptr(tint)); - _imgui_tooltip(STRING_IMGUI_TOOLTIP_FRAME_PROPERTIES_TINT); - - ImGui::ColorEdit3(STRING_IMGUI_FRAME_PROPERTIES_COLOR_OFFSET, value_ptr(offset)); - _imgui_tooltip(STRING_IMGUI_TOOLTIP_FRAME_PROPERTIES_COLOR_OFFSET); - - ImGui::Checkbox(STRING_IMGUI_FRAME_PROPERTIES_VISIBLE, &isVisible); - _imgui_tooltip(STRING_IMGUI_TOOLTIP_FRAME_PROPERTIES_VISIBLE); + ImGui::EndChild(); ImGui::SameLine(); - ImGui::Checkbox(STRING_IMGUI_FRAME_PROPERTIES_INTERPOLATED, &isInterpolated); - _imgui_tooltip(STRING_IMGUI_TOOLTIP_FRAME_PROPERTIES_INTERPOLATED); + /* Helper settings */ + ImGui::BeginChild(STRING_IMGUI_ANIMATION_PREVIEW_HELPER_SETTINGS, IMGUI_ANIMATION_PREVIEW_SETTINGS_CHILD_SIZE, true); - ImGui::End(); + /* Axis toggle */ + ImGui::Checkbox(STRING_IMGUI_ANIMATION_PREVIEW_AXIS, &self->settings->isAxis); + _imgui_tooltip(STRING_IMGUI_TOOLTIP_ANIMATION_PREVIEW_AXIS); - /* -- Animation Preview -- */ - ImGui::Begin(STRING_IMGUI_ANIMATION_PREVIEW); + ImGui::SameLine(); + + /* Axis colors */ + ImGui::ColorEdit4(STRING_IMGUI_ANIMATION_PREVIEW_AXIS_COLOR, (f32*)&self->settings->axisColorR, ImGuiColorEditFlags_NoInputs); + _imgui_tooltip(STRING_IMGUI_TOOLTIP_ANIMATION_PREVIEW_AXIS_COLOR); - /* elements drawn out of order in order to get the size of the preview before how it visually appears */ - settingsCursorPos = ImGui::GetCursorPos(); - ImGui::SetCursorPos(IMGUI_ANIMATION_PREVIEW_POSITION); + /* Root transform */ + ImGui::Checkbox(STRING_IMGUI_ANIMATION_PREVIEW_ROOT_TRANSFORM, &self->settings->isRootTransform); + _imgui_tooltip(STRING_IMGUI_TOOLTIP_ANIMATION_PREVIEW_ROOT_TRANSFORM); + + /* Show pivot */ + ImGui::Checkbox(STRING_IMGUI_ANIMATION_PREVIEW_SHOW_PIVOT, &self->settings->isShowPivot); + _imgui_tooltip(STRING_IMGUI_TOOLTIP_ANIMATION_PREVIEW_SHOW_PIVOT); + + ImGui::EndChild(); - ImGui::BeginChild(STRING_IMGUI_ANIMATION_PREVIEW_LABEL, ImVec2(0, 0), true, childWindowFlags); + ImGui::SameLine(); - previewWindowRect = ImGui::GetCurrentWindow()->ClipRect; + /* View settings */ + ImGui::BeginChild(STRING_IMGUI_ANIMATION_PREVIEW_VIEW_SETTINGS, IMGUI_ANIMATION_PREVIEW_SETTINGS_CHILD_SIZE, true); + + /* Zoom */ + ImGui::DragFloat(STRING_IMGUI_ANIMATION_PREVIEW_ZOOM, &self->settings->zoom, 1, PREVIEW_ZOOM_MIN, PREVIEW_ZOOM_MAX, "%.0f"); + _imgui_tooltip(STRING_IMGUI_TOOLTIP_ANIMATION_PREVIEW_ZOOM); + /* Center view */ + if (ImGui::Button(STRING_IMGUI_ANIMATION_PREVIEW_CENTER_VIEW)) + isPreviewCenter = true; + _imgui_tooltip(STRING_IMGUI_TOOLTIP_ANIMATION_PREVIEW_CENTER_VIEW); + + ImGui::EndChild(); + + ImGui::SameLine(); + + /* Background settings */ + ImGui::BeginChild(STRING_IMGUI_ANIMATION_PREVIEW_BACKGROUND_SETTINGS, IMGUI_ANIMATION_PREVIEW_SETTINGS_CHILD_SIZE, true); + + /* Background color */ + ImGui::ColorEdit4(STRING_IMGUI_ANIMATION_PREVIEW_BACKGROUND_COLOR, (f32*)&self->settings->backgroundColorR, ImGuiColorEditFlags_NoInputs); + _imgui_tooltip(STRING_IMGUI_TOOLTIP_ANIMATION_PREVIEW_BACKGROUND_COLOR); + + ImGui::EndChild(); + + /* Elements drawn out of order in order to get the size of the preview before how it visually appears */ ImGui::Image(self->preview->texture, ImVec2(PREVIEW_SIZE.x, PREVIEW_SIZE.y)); /* Panning */ @@ -818,19 +1312,19 @@ imgui_tick(Imgui* self) if (mouse_held(&self->input->mouse, MOUSE_LEFT)) { - self->preview->pan.x += self->input->mouse.delta.x; - self->preview->pan.y -= self->input->mouse.delta.y; + self->settings->panX += self->input->mouse.delta.x; + self->settings->panY -= self->input->mouse.delta.y; } - self->preview->zoom = self->preview->zoom == PREVIEW_ZOOM_MIN ? 0 : self->preview->zoom; + self->settings->zoom = self->settings->zoom == PREVIEW_ZOOM_MIN ? 0 : self->settings->zoom; if (self->input->mouse.wheelDeltaY > 0) - self->preview->zoom += PREVIEW_ZOOM_STEP; + self->settings->zoom += PREVIEW_ZOOM_STEP; if (self->input->mouse.wheelDeltaY < 0) - self->preview->zoom -= PREVIEW_ZOOM_STEP; + self->settings->zoom -= PREVIEW_ZOOM_STEP; - self->preview->zoom = CLAMP(self->preview->zoom, PREVIEW_ZOOM_MIN, PREVIEW_ZOOM_MAX); + self->settings->zoom = CLAMP(self->settings->zoom, PREVIEW_ZOOM_MIN, PREVIEW_ZOOM_MAX); } else { @@ -841,239 +1335,281 @@ imgui_tick(Imgui* self) } } - ImGui::EndChild(); - - ImGui::SetCursorPos(settingsCursorPos); - - /* Settings */ - ImGui::BeginChild(STRING_IMGUI_ANIMATION_PREVIEW_SETTINGS, IMGUI_ANIMATION_PREVIEW_SETTINGS_SIZE, true, childWindowFlags); - - /* Grid settings */ - ImGui::BeginChild(STRING_IMGUI_ANIMATION_PREVIEW_GRID_SETTINGS, IMGUI_ANIMATION_PREVIEW_SETTINGS_CHILD_SIZE, true); - - /* Grid toggle */ - ImGui::Checkbox(STRING_IMGUI_ANIMATION_PREVIEW_GRID, &self->preview->isGrid); - _imgui_tooltip(STRING_IMGUI_TOOLTIP_ANIMATION_PREVIEW_GRID); - - ImGui::SameLine(); - - /* Grid Color */ - ImGui::ColorEdit4(STRING_IMGUI_ANIMATION_PREVIEW_GRID_COLOR, value_ptr(self->preview->gridColor), ImGuiColorEditFlags_NoInputs); - _imgui_tooltip(STRING_IMGUI_TOOLTIP_ANIMATION_PREVIEW_GRID_COLOR); - - /* Grid Size */ - ImGui::InputInt2(STRING_IMGUI_ANIMATION_PREVIEW_GRID_SIZE, value_ptr(self->preview->gridSize)); - self->preview->gridSize.x = CLAMP(self->preview->gridSize.x, PREVIEW_GRID_MIN, PREVIEW_GRID_MAX); - self->preview->gridSize.y = CLAMP(self->preview->gridSize.y, PREVIEW_GRID_MIN, PREVIEW_GRID_MAX); - _imgui_tooltip(STRING_IMGUI_TOOLTIP_ANIMATION_PREVIEW_GRID_SIZE); - - ImGui::EndChild(); - - ImGui::SameLine(); - - /* Helper settings */ - ImGui::BeginChild(STRING_IMGUI_ANIMATION_PREVIEW_HELPER_SETTINGS, IMGUI_ANIMATION_PREVIEW_SETTINGS_CHILD_SIZE, true); - - /* Axis toggle */ - ImGui::Checkbox(STRING_IMGUI_ANIMATION_PREVIEW_AXIS, &self->preview->isAxis); - _imgui_tooltip(STRING_IMGUI_TOOLTIP_ANIMATION_PREVIEW_AXIS); - - ImGui::SameLine(); - - /* Axis colors*/ - ImGui::ColorEdit4(STRING_IMGUI_ANIMATION_PREVIEW_AXIS_COLOR, value_ptr(self->preview->axisColor), ImGuiColorEditFlags_NoInputs); - _imgui_tooltip(STRING_IMGUI_TOOLTIP_ANIMATION_PREVIEW_AXIS_COLOR); - - ImGui::EndChild(); - - ImGui::SameLine(); - - /* View settings */ - ImGui::BeginChild(STRING_IMGUI_ANIMATION_PREVIEW_VIEW_SETTINGS, IMGUI_ANIMATION_PREVIEW_SETTINGS_CHILD_SIZE, true); - - /* Zoom */ - ImGui::DragFloat(STRING_IMGUI_ANIMATION_PREVIEW_ZOOM, &self->preview->zoom, 1, PREVIEW_ZOOM_MIN, PREVIEW_ZOOM_MAX, "%.0f"); - _imgui_tooltip(STRING_IMGUI_TOOLTIP_ANIMATION_PREVIEW_ZOOM); - - /* Center view */ - if (ImGui::Button(STRING_IMGUI_ANIMATION_PREVIEW_CENTER_VIEW) || isFirstTick) + if (isPreviewCenter) { - ImVec2 previewWindowSize = previewWindowRect.GetSize(); + ImVec2 previewWindowRectSize = ImGui::GetCurrentWindow()->ClipRect.GetSize(); - previewWindowSize.x = MAX(previewWindowSize.x, PREVIEW_SIZE.x); - previewWindowSize.y = MAX(previewWindowSize.y, PREVIEW_SIZE.y); + /* Based on the preview's crop in its window, adjust the pan */ + self->settings->panX = PREVIEW_CENTER.x + ((previewWindowRectSize.x - PREVIEW_SIZE.x) / 2.0f); + self->settings->panY = PREVIEW_CENTER.y - ((previewWindowRectSize.y - PREVIEW_SIZE.y) / 2.0f); - /* Based on the preview's crop in its window, adjust the preview */ - self->preview->pan.x = PREVIEW_CENTER.x + ((previewWindowSize.x - PREVIEW_SIZE.x) / 2); - self->preview->pan.y = PREVIEW_CENTER.y - ((previewWindowSize.y - PREVIEW_SIZE.y) / 2); + self->settings->panY += (IMGUI_ANIMATION_PREVIEW_SETTINGS_SIZE.y / 2.0f); + isPreviewCenter = false; } - _imgui_tooltip(STRING_IMGUI_TOOLTIP_ANIMATION_PREVIEW_CENTER_VIEW); - - ImGui::EndChild(); - - ImGui::SameLine(); - - /* Background settings */ - ImGui::BeginChild(STRING_IMGUI_ANIMATION_PREVIEW_BACKGROUND_SETTINGS, IMGUI_ANIMATION_PREVIEW_SETTINGS_CHILD_SIZE, true); - - /* Background color */ - ImGui::ColorEdit4(STRING_IMGUI_ANIMATION_PREVIEW_BACKGROUND_COLOR, value_ptr(self->preview->backgroundColor), ImGuiColorEditFlags_NoInputs); - _imgui_tooltip(STRING_IMGUI_TOOLTIP_ANIMATION_PREVIEW_BACKGROUND_COLOR); - - ImGui::EndChild(); - - ImGui::EndChild(); ImGui::End(); - - /* -- Spritesheet Editor -- */ +} +/* Spritesheet Editor */ +static void +_imgui_spritesheet_editor(Imgui* self) +{ ImGui::Begin(STRING_IMGUI_SPRITESHEET_EDITOR); ImGui::End(); - - /* -- Timeline -- */ - ImGui::Begin(STRING_IMGUI_TIMELINE); +} - if (selectedAnimationID != -1) +/* Frame Properties */ +static void +_imgui_frame_properties(Imgui* self) +{ + ImGui::Begin(STRING_IMGUI_FRAME_PROPERTIES); + + if (self->frameIndex > -1) { - s32 index = 0; - ImVec2 timelineSize = IMGUI_TIMELINE_SIZE; - Anm2Animation* animation = &self->anm2->animations[selectedAnimationID]; - ImVec2 frameIndicesSize = - {IMGUI_TIMELINE_FRAME_SIZE.x * animation->frameNum, IMGUI_TIMELINE_FRAME_INDICES_SIZE.y}; + Anm2Animation* animation = &self->anm2->animations[self->animationID]; + + std::vector* frameVector; + std::vector* triggerVector; + Anm2Frame* frame = NULL; + Anm2Trigger* trigger = NULL; + std::vector eventNames; + std::vector eventIDs; + static s32 selectedEventIndex = -1; + + switch (self->animationType) + { + case ANM2_ROOT_ANIMATION: + ImGui::Text(STRING_IMGUI_FRAME_PROPERTIES_ROOT); + break; + case ANM2_LAYER_ANIMATION: + ImGui::Text(STRING_IMGUI_FRAME_PROPERTIES_LAYER); + break; + case ANM2_NULL_ANIMATION: + ImGui::Text(STRING_IMGUI_FRAME_PROPERTIES_NULL); + break; + case ANM2_TRIGGER: + ImGui::Text(STRING_IMGUI_FRAME_PROPERTIES_TRIGGER); + break; + default: + break; + } + + switch (self->animationType) + { + case ANM2_ROOT_ANIMATION: + case ANM2_NULL_ANIMATION: + frameVector = (std::vector*)self->frameVector; + frame = (Anm2Frame*)&(*frameVector)[self->frameIndex]; + + /* Position */ + ImGui::DragFloat2(STRING_IMGUI_FRAME_PROPERTIES_POSITION, value_ptr(frame->position), 1, 0, 0, "%.0f"); + _imgui_tooltip(STRING_IMGUI_TOOLTIP_FRAME_PROPERTIES_POSITION); + + /* Scale */ + ImGui::DragFloat2(STRING_IMGUI_FRAME_PROPERTIES_SCALE, value_ptr(frame->scale), 1.0, 0, 0, "%.1f"); + _imgui_tooltip(STRING_IMGUI_TOOLTIP_FRAME_PROPERTIES_SCALE); + + /* Rotation */ + ImGui::DragFloat(STRING_IMGUI_FRAME_PROPERTIES_ROTATION, &frame->rotation, 1, 0, 0, "%.1f"); + _imgui_tooltip(STRING_IMGUI_TOOLTIP_FRAME_PROPERTIES_ROTATION); + + /* Duration */ + ImGui::InputInt(STRING_IMGUI_FRAME_PROPERTIES_DURATION, &frame->delay); + _imgui_tooltip(STRING_IMGUI_TOOLTIP_FRAME_PROPERTIES_DURATION); + frame->delay = CLAMP(frame->delay, 0, animation->frameNum + 1); + + /* Tint */ + ImGui::ColorEdit4(STRING_IMGUI_FRAME_PROPERTIES_TINT, value_ptr(frame->tintRGBA)); + _imgui_tooltip(STRING_IMGUI_TOOLTIP_FRAME_PROPERTIES_TINT); + + /* Color Offset */ + ImGui::ColorEdit3(STRING_IMGUI_FRAME_PROPERTIES_COLOR_OFFSET, value_ptr(frame->offsetRGB)); + _imgui_tooltip(STRING_IMGUI_TOOLTIP_FRAME_PROPERTIES_COLOR_OFFSET); + + /* Visible */ + ImGui::Checkbox(STRING_IMGUI_FRAME_PROPERTIES_VISIBLE, &frame->isVisible); + _imgui_tooltip(STRING_IMGUI_TOOLTIP_FRAME_PROPERTIES_VISIBLE); + + ImGui::SameLine(); + + ImGui::Checkbox(STRING_IMGUI_FRAME_PROPERTIES_INTERPOLATED, &frame->isInterpolated); + _imgui_tooltip(STRING_IMGUI_TOOLTIP_FRAME_PROPERTIES_INTERPOLATED); + break; + case ANM2_LAYER_ANIMATION: + frameVector = (std::vector*)self->frameVector; + frame = (Anm2Frame*)&(*frameVector)[self->frameIndex]; + + /* Position */ + ImGui::DragFloat2(STRING_IMGUI_FRAME_PROPERTIES_POSITION, value_ptr(frame->position), 1, 0, 0, "%.0f"); + _imgui_tooltip(STRING_IMGUI_TOOLTIP_FRAME_PROPERTIES_POSITION); + + /* Crop Position */ + ImGui::DragFloat2(STRING_IMGUI_FRAME_PROPERTIES_CROP_POSITION, value_ptr(frame->crop), 1, 0, 0, "%.0f"); + _imgui_tooltip(STRING_IMGUI_TOOLTIP_FRAME_PROPERTIES_CROP_POSITION); + + /* Crop */ + ImGui::DragFloat2(STRING_IMGUI_FRAME_PROPERTIES_CROP_SIZE, value_ptr(frame->size), 1, 0, 0, "%.0f"); + _imgui_tooltip(STRING_IMGUI_TOOLTIP_FRAME_PROPERTIES_CROP_SIZE); + + /* Pivot */ + ImGui::DragFloat2(STRING_IMGUI_FRAME_PROPERTIES_PIVOT, value_ptr(frame->pivot), 1, 0, 0, "%.0f"); + _imgui_tooltip(STRING_IMGUI_TOOLTIP_FRAME_PROPERTIES_PIVOT); + + /* Scale */ + ImGui::DragFloat2(STRING_IMGUI_FRAME_PROPERTIES_SCALE, value_ptr(frame->scale), 1.0, 0, 0, "%.1f"); + _imgui_tooltip(STRING_IMGUI_TOOLTIP_FRAME_PROPERTIES_SCALE); + + /* Rotation */ + ImGui::DragFloat(STRING_IMGUI_FRAME_PROPERTIES_ROTATION, &frame->rotation, 1, 0, 0, "%.1f"); + _imgui_tooltip(STRING_IMGUI_TOOLTIP_FRAME_PROPERTIES_ROTATION); + + /* Duration */ + ImGui::InputInt(STRING_IMGUI_FRAME_PROPERTIES_DURATION, &frame->delay); + _imgui_tooltip(STRING_IMGUI_TOOLTIP_FRAME_PROPERTIES_DURATION); + /* clamp delay */ + frame->delay = CLAMP(frame->delay, 0, animation->frameNum + 1); + + /* Tint */ + ImGui::ColorEdit4(STRING_IMGUI_FRAME_PROPERTIES_TINT, value_ptr(frame->tintRGBA)); + _imgui_tooltip(STRING_IMGUI_TOOLTIP_FRAME_PROPERTIES_TINT); + + /* Color Offset */ + ImGui::ColorEdit3(STRING_IMGUI_FRAME_PROPERTIES_COLOR_OFFSET, value_ptr(frame->offsetRGB)); + _imgui_tooltip(STRING_IMGUI_TOOLTIP_FRAME_PROPERTIES_COLOR_OFFSET); + + /* Visible */ + ImGui::Checkbox(STRING_IMGUI_FRAME_PROPERTIES_VISIBLE, &frame->isVisible); + _imgui_tooltip(STRING_IMGUI_TOOLTIP_FRAME_PROPERTIES_VISIBLE); + + ImGui::SameLine(); + + ImGui::Checkbox(STRING_IMGUI_FRAME_PROPERTIES_INTERPOLATED, &frame->isInterpolated); + _imgui_tooltip(STRING_IMGUI_TOOLTIP_FRAME_PROPERTIES_INTERPOLATED); + break; + case ANM2_TRIGGER: + triggerVector = (std::vector*)self->frameVector; + trigger = (Anm2Trigger*)&(*triggerVector)[self->frameIndex]; + + /* Events drop down; pick one! */ + for (auto & [id, event] : self->anm2->events) + { + eventIDs.push_back(id); + eventNames.push_back(event.name); + if (id == trigger->eventID) + selectedEventIndex = eventIDs.size() - 1; + } + + if (ImGui::Combo(STRING_IMGUI_FRAME_PROPERTIES_EVENT, &selectedEventIndex, eventNames.data(), eventNames.size())) + { + trigger->eventID = eventIDs[selectedEventIndex]; + selectedEventIndex = -1; + } + _imgui_tooltip(STRING_IMGUI_TOOLTIP_FRAME_PROPERTIES_EVENT); + + /* At Frame */ + ImGui::InputInt(STRING_IMGUI_FRAME_PROPERTIES_AT_FRAME, &trigger->atFrame); + _imgui_tooltip(STRING_IMGUI_TOOLTIP_FRAME_PROPERTIES_AT_FRAME); + /* clamp at frame */ + trigger->atFrame = CLAMP(trigger->atFrame, 0, animation->frameNum- 1); + + break; + default: + break; + } - timelineSize.y = ImGui::GetContentRegionAvail().y - IMGUI_TIMELINE_OFFSET_Y; - - ImGui::BeginChild(STRING_IMGUI_TIMELINE_ANIMATIONS, timelineSize, true); - - /* Element top bar */ - ImGui::BeginChild(STRING_IMGUI_TIMELINE_ELEMENTS, IMGUI_TIMELINE_ELEMENTS_BAR_SIZE, true); - ImGui::Text(STRING_IMGUI_TIMELINE_ELEMENTS); - ImGui::EndChild(); - - - /* Frame indicies */ - if (animation->frameNum > 0) - { - ImGui::SameLine(); - ImGui::BeginChild(STRING_IMGUI_TIMELINE_FRAME_INDICES, frameIndicesSize, true); - ImGui::EndChild(); - } - - /* Root */ - _imgui_timeline_element(self, animation, &animation->rootAnimation, NULL, &index, ANM2_ROOT_ANIMATION, NULL, NULL); - - /* reverse order */ - for (auto it = animation->layerAnimations.rbegin(); it != animation->layerAnimations.rend(); it++) - { - s32 id = it->first; - Anm2LayerAnimation& layer = it->second; - - _imgui_timeline_element(self, animation, &layer, &id, &index, ANM2_LAYER_ANIMATION, &selectedTimelineElementType, &selectedTimelineElementID); - } - - for (auto & [id, null] : animation->nullAnimations) - _imgui_timeline_element(self, animation, &null, (s32*)&id, &index, ANM2_NULL_ANIMATION, &selectedTimelineElementType, &selectedTimelineElementID); - - /* Triggers */ - _imgui_timeline_element(self, animation, &animation->triggers, NULL, &index, ANM2_TRIGGERS, NULL, NULL); - - ImGui::EndChild(); - - /* Element configuration */ - if (ImGui::Button(STRING_IMGUI_TIMELINE_ELEMENT_ADD)) - ImGui::OpenPopup(STRING_IMGUI_TIMELINE_ELEMENT_ADD_MENU); - _imgui_tooltip(STRING_IMGUI_TOOLTIP_TIMELINE_ELEMENT_ADD); - - - if (ImGui::BeginPopup(STRING_IMGUI_TIMELINE_ELEMENT_ADD_MENU)) - { - if (ImGui::Selectable(STRING_IMGUI_TIMELINE_ELEMENT_ADD_MENU_LAYER)) - anm2_layer_add(self->anm2); - - if (ImGui::Selectable(STRING_IMGUI_TIMELINE_ELEMENT_ADD_MENU_NULL)) - anm2_null_add(self->anm2); - - ImGui::EndPopup(); - } - - ImGui::SameLine(); - - if - ( - ImGui::Button(STRING_IMGUI_TIMELINE_ELEMENT_REMOVE) && - selectedTimelineElementID != -1 - ) - { - switch (selectedTimelineElementType) - { - case ANM2_LAYER_ANIMATION: - anm2_layer_remove(self->anm2, selectedTimelineElementID); - break; - case ANM2_NULL_ANIMATION: - anm2_null_remove(self->anm2, selectedTimelineElementID); - break; - default: - break; - } - - selectedTimelineElementID = -1; - selectedTimelineElementType = ANM2_NONE; - } - _imgui_tooltip(STRING_IMGUI_TOOLTIP_TIMELINE_ELEMENT_REMOVE); - - ImGui::SameLine(); - - static bool isPlaying = false; - - if (isPlaying) - { - if (ImGui::Button(STRING_IMGUI_TIMELINE_PAUSE)) - { - isPlaying = !isPlaying; - } - _imgui_tooltip(STRING_IMGUI_TOOLTIP_TIMELINE_PLAY); - - } - else - { - if (ImGui::Button(STRING_IMGUI_TIMELINE_PLAY)) - { - isPlaying = !isPlaying; - } - _imgui_tooltip(STRING_IMGUI_TOOLTIP_TIMELINE_PAUSE); - } - - ImGui::SameLine(); - - if (ImGui::Button(STRING_IMGUI_TIMELINE_FRAME_ADD)) - { - - } - _imgui_tooltip(STRING_IMGUI_TOOLTIP_TIMELINE_FRAME_ADD); - - ImGui::SameLine(); - - if (ImGui::Button(STRING_IMGUI_TIMELINE_FRAME_REMOVE)) - { - - } - _imgui_tooltip(STRING_IMGUI_TOOLTIP_TIMELINE_FRAME_REMOVE); - - ImGui::SameLine(); - - ImGui::PushItemWidth(IMGUI_TIMELINE_ANIMATION_LENGTH_WIDTH); - ImGui::InputInt(STRING_IMGUI_TIMELINE_ANIMATION_LENGTH, &animation->frameNum); - ImGui::PopItemWidth(); - _imgui_tooltip(STRING_IMGUI_TOOLTIP_TIMELINE_ANIMATION_LENGTH); } ImGui::End(); +} - if (isFirstTick) - isFirstTick = false; + +void +imgui_init +( + Imgui* self, + Dialog* dialog, + Resources* resources, + Input* input, + Anm2* anm2, + Preview* preview, + Settings* settings, + SDL_Window* window, + SDL_GLContext* glContext +) +{ + IMGUI_CHECKVERSION(); + + self->dialog = dialog; + self->resources = resources; + self->input = input; + self->anm2 = anm2; + self->preview = preview; + self->settings = settings; + self->window = window; + self->glContext = glContext; + + ImGui::CreateContext(); + ImGui::StyleColorsDark(); + + ImGui_ImplSDL3_InitForOpenGL(self->window, *self->glContext); + ImGui_ImplOpenGL3_Init(STRING_OPENGL_VERSION); + + ImGuiIO& io = ImGui::GetIO(); + io.IniFilename = NULL; + io.ConfigFlags |= ImGuiConfigFlags_DockingEnable; + + /* load ini manually */ + ImGui::LoadIniSettingsFromDisk(PATH_SETTINGS); + + printf(STRING_INFO_IMGUI_INIT); +} + +/* Main dockspace */ +static void +_imgui_dock(Imgui* self) +{ + ImGuiViewport* viewport = ImGui::GetMainViewport(); + + ImGuiWindowFlags dockspaceWindowFlags = 0 | + ImGuiWindowFlags_NoTitleBar | + ImGuiWindowFlags_NoCollapse | + ImGuiWindowFlags_NoResize | + ImGuiWindowFlags_NoMove | + ImGuiWindowFlags_NoBringToFrontOnFocus | + ImGuiWindowFlags_NoNavFocus; + + ImGui::SetNextWindowPos(ImVec2(viewport->Pos.x, viewport->Pos.y + IMGUI_TASKBAR_HEIGHT)); + ImGui::SetNextWindowSize(ImVec2(viewport->Size.x, viewport->Size.y - IMGUI_TASKBAR_HEIGHT)); + ImGui::SetNextWindowViewport(viewport->ID); + + ImGui::Begin(STRING_IMGUI_WINDOW, NULL, dockspaceWindowFlags); + + ImGui::DockSpace(ImGui::GetID(STRING_IMGUI_DOCKSPACE), ImVec2(0.0f, 0.0f), ImGuiDockNodeFlags_PassthruCentralNode); + + _imgui_animations(self); + _imgui_events(self); + _imgui_spritesheets(self); + _imgui_animation_preview(self); + _imgui_spritesheet_editor(self); + _imgui_timeline(self); + _imgui_frame_properties(self); + + ImGui::End(); +} + +void +imgui_tick(Imgui* self) +{ + ImGui_ImplSDL3_NewFrame(); + ImGui_ImplOpenGL3_NewFrame(); + ImGui::NewFrame(); + + _imgui_taskbar(self); + _imgui_dock(self); + + self->preview->animationID = self->animationID; } void @@ -1088,6 +1624,9 @@ imgui_free(Imgui* self) { ImGui_ImplSDL3_Shutdown(); ImGui_ImplOpenGL3_Shutdown(); + + /* Save Ini manually */ + ImGui::SaveIniSettingsToDisk(PATH_SETTINGS); ImGui::DestroyContext(); printf(STRING_INFO_IMGUI_FREE); diff --git a/src/imgui.h b/src/imgui.h index 655a213..1869753 100644 --- a/src/imgui.h +++ b/src/imgui.h @@ -5,6 +5,7 @@ #include "preview.h" #include "window.h" #include "input.h" +#include "settings.h" #define IMGUI_IMPL_OPENGL_LOADER_CUSTOM #define IMGUI_ENABLE_DOCKING @@ -15,34 +16,50 @@ #define IMGUI_DRAG_SPEED 1.0 #define IMGUI_TASKBAR_HEIGHT 32 -#define IMGUI_TIMELINE_OFFSET_Y 32 +#define IMGUI_TIMELINE_OFFSET_Y 24 #define IMGUI_TIMELINE_ANIMATION_LENGTH_WIDTH 200 +#define IMGUI_TIMELINE_FPS_WIDTH 100 +#define IMGUI_TIMELINE_CREATED_BY_WIDTH 150 +#define IMGUI_TIMELINE_FRAME_INDICES_MULTIPLE 5 +#define IMGUI_TIMELINE_FRAME_INDICES_STRING_MAX 16 +#define IMGUI_PICKER_LINE_SIZE 1.0f +#define IMGUI_FRAME_BORDER 2.0f +#define IMGUI_PICKER_LINE_COLOR IM_COL32(255, 255, 255, 255) static const vec2 IMGUI_TASKBAR_MARGINS = {8, 4}; -static const ImVec2 IMGUI_ANIMATION_PREVIEW_SETTINGS_SIZE = {1280, 80}; -static const ImVec2 IMGUI_ANIMATION_PREVIEW_SETTINGS_CHILD_SIZE = {150, 64}; -static const ImVec2 IMGUI_ANIMATION_PREVIEW_POSITION = {8, 110}; +static const ImVec2 IMGUI_ANIMATION_PREVIEW_SETTINGS_SIZE = {1280, 105}; +static const ImVec2 IMGUI_ANIMATION_PREVIEW_SETTINGS_CHILD_SIZE = {175, 85}; +static const ImVec2 IMGUI_ANIMATION_PREVIEW_POSITION = {8, 135}; + +static const ImVec2 IMGUI_TIMELINE_ELEMENT_NAME_SIZE = {95, 20}; +static const ImVec2 IMGUI_TIMELINE_ELEMENT_SPRITESHEET_ID_SIZE = {45, 20}; -static const ImVec2 IMGUI_TIMELINE_FRAME_SIZE = {20, 40}; -static const ImVec2 IMGUI_TIMELINE_SIZE = {0, 0}; -static const ImVec2 IMGUI_TIMELINE_ELEMENTS_BAR_SIZE = {300, 32}; -static const ImVec2 IMGUI_TIMELINE_ELEMENTS_TIMELINE_SIZE = {0, 40}; -static const ImVec2 IMGUI_TIMELINE_FRAME_INDICES_SIZE = {0, 32}; static const ImVec2 IMGUI_TIMELINE_ELEMENT_LIST_SIZE = {300, 0}; +static const ImVec2 IMGUI_TIMELINE_FRAMES_SIZE = {0, 0}; +static const ImVec2 IMGUI_TIMELINE_ELEMENT_FRAMES_SIZE = {0, 0}; +static const ImVec2 IMGUI_TIMELINE_FRAME_SIZE = {16, 40}; +static const ImVec2 IMGUI_TIMELINE_VIEWER_SIZE = {0, 40}; +static const ImVec2 IMGUI_TIMELINE_ELEMENTS_TIMELINE_SIZE = {0, 40}; +static const ImVec2 IMGUI_TIMELINE_FRAME_INDICES_SIZE = {0, 40}; static const ImVec2 IMGUI_TIMELINE_ELEMENT_SIZE = {300, 40}; -static const ImVec2 IMGUI_TIMELINE_SPRITESHEET_ID_SIZE = {40, 20}; static const ImVec2 IMGUI_TIMELINE_SHIFT_ARROWS_SIZE = {64, 40}; -static const ImVec2 IMGUI_TIMELINE_ELEMENT_NAME_SIZE = {85, 20}; static const ImVec2 IMGUI_SPRITESHEET_SIZE = {0, 150}; static const ImVec2 IMGUI_SPRITESHEET_PREVIEW_SIZE = {100, 100}; static const ImVec2 IMGUI_ICON_SIZE = {16, 16}; +static const ImVec2 IMGUI_ICON_SMALL_SIZE = {8, 8}; static const ImVec2 IMGUI_ICON_DUMMY_SIZE = {20, 16}; static const ImVec2 IMGUI_ICON_BUTTON_SIZE = {24, 24}; static const ImVec2 IMGUI_IMAGE_TARGET_SIZE = {125, 125}; static const ImVec2 IMGUI_DUMMY_SIZE = {1, 1}; +static const ImVec4 IMGUI_FRAME_BORDER_COLOR = {1.0f, 1.0f, 1.0f, 0.5f}; +static const ImVec4 IMGUI_FRAME_OVERLAY_COLOR = {0.0f, 0.0f, 0.0f, 0.25f}; +static const ImVec4 IMGUI_FRAME_INDICES_OVERLAY_COLOR = {0.113, 0.184, 0.286, 1.0f}; +static const ImVec4 IMGUI_FRAME_INDICES_COLOR = {0.113, 0.184, 0.286, 0.5f}; + + #define IMGUI_TIMELINE_SHIFT_ARROWS_WIDTH (IMGUI_TIMELINE_SHIFT_ARROWS_SIZE.x * 1.35) struct Imgui @@ -54,6 +71,15 @@ struct Imgui Preview* preview = NULL; SDL_Window* window = NULL; SDL_GLContext* glContext = NULL; + Settings* settings = NULL; + s32 animationID = -1; + s32 timelineElementID = -1; + s32 eventID = -1; + s32 spritesheetID = -1; + s32 timelineElementIndex = -1; + Anm2AnimationType animationType = ANM2_NONE; + void* frameVector = NULL; + s32 frameIndex = -1; }; void imgui_init @@ -64,6 +90,7 @@ void imgui_init Input* input, Anm2* anm2, Preview* preview, + Settings* settings, SDL_Window* window, SDL_GLContext* glContext ); diff --git a/src/input.h b/src/input.h index 3daeb31..b39aa44 100644 --- a/src/input.h +++ b/src/input.h @@ -9,11 +9,24 @@ enum MouseType MOUSE_RIGHT }; +#define KEY_COUNT (KEY_DELETE + 1) +enum KeyType +{ + KEY_DELETE +}; + +#define INPUT_COUNT (INPUT_MOUSE_CLICK + 1) enum InputType { INPUT_MOUSE_CLICK }; +struct Keyboard +{ + bool current[MOUSE_COUNT]; + bool previous[MOUSE_COUNT]; +}; + struct Mouse { bool current[MOUSE_COUNT]; @@ -26,6 +39,7 @@ struct Mouse struct Input { + Keyboard keyboard; Mouse mouse; }; diff --git a/src/preview.cpp b/src/preview.cpp index bd08186..bdb0278 100644 --- a/src/preview.cpp +++ b/src/preview.cpp @@ -11,7 +11,7 @@ _preview_axis_set(Preview* self) glBufferData(GL_ARRAY_BUFFER, sizeof(PREVIEW_AXIS_VERTICES), PREVIEW_AXIS_VERTICES, GL_STATIC_DRAW); glEnableVertexAttribArray(0); - glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float), (void*)0); + glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(f32), (void*)0); glBindVertexArray(0); } @@ -21,13 +21,13 @@ _preview_grid_set(Preview* self) { std::vector vertices; - s32 verticalLineCount = PREVIEW_SIZE.x / self->gridSize.x; - s32 horizontalLineCount = PREVIEW_SIZE.y / self->gridSize.y; + s32 verticalLineCount = PREVIEW_SIZE.x / MIN(self->settings->gridSizeX, PREVIEW_GRID_MIN); + s32 horizontalLineCount = PREVIEW_SIZE.y / MIN(self->settings->gridSizeY, PREVIEW_GRID_MIN); /* Vertical */ for (s32 i = 0; i <= verticalLineCount; i++) { - s32 x = i * self->gridSize.x; + s32 x = i * self->settings->gridSizeX - self->settings->gridOffsetX; f32 normX = (2.0f * x) / PREVIEW_SIZE.x - 1.0f; vertices.push_back(normX); @@ -39,7 +39,7 @@ _preview_grid_set(Preview* self) /* Horizontal */ for (s32 i = 0; i <= horizontalLineCount; i++) { - s32 y = i * self->gridSize.y; + s32 y = i * self->settings->gridSizeY - self->settings->gridOffsetY; f32 normY = (2.0f * y) / PREVIEW_SIZE.y - 1.0f; vertices.push_back(-1.0f); @@ -52,17 +52,19 @@ _preview_grid_set(Preview* self) glBindVertexArray(self->gridVAO); glBindBuffer(GL_ARRAY_BUFFER, self->gridVBO); - glBufferData(GL_ARRAY_BUFFER, vertices.size() * sizeof(float), vertices.data(), GL_DYNAMIC_DRAW); + glBufferData(GL_ARRAY_BUFFER, vertices.size() * sizeof(f32), vertices.data(), GL_DYNAMIC_DRAW); glEnableVertexAttribArray(0); glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(f32), (void*)0); } void -preview_init(Preview* self, Resources* resources, Input* input) +preview_init(Preview* self, Anm2* anm2, Resources* resources, Input* input, Settings* settings) { + self->anm2 = anm2; self->resources = resources; self->input = input; + self->settings = settings; glGenFramebuffers(1, &self->fbo); @@ -89,6 +91,28 @@ preview_init(Preview* self, Resources* resources, Input* input) glGenVertexArrays(1, &self->gridVAO); glGenBuffers(1, &self->gridVBO); + glGenVertexArrays(1, &self->textureVAO); + glGenBuffers(1, &self->textureVBO); + glGenBuffers(1, &self->textureEBO); + + glBindVertexArray(self->textureVAO); + + glBindBuffer(GL_ARRAY_BUFFER, self->textureVBO); + glBufferData(GL_ARRAY_BUFFER, sizeof(f32) * 4 * 4, NULL, GL_DYNAMIC_DRAW); + + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, self->textureEBO); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(PREVIEW_TEXTURE_INDICES), PREVIEW_TEXTURE_INDICES, GL_STATIC_DRAW); + + /* Position */ + glEnableVertexAttribArray(0); + glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(f32), (void*)0); + + /* UV */ + glEnableVertexAttribArray(1); + glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(f32), (void*)(2 * sizeof(f32))); + + glBindVertexArray(0); + _preview_axis_set(self); _preview_grid_set(self); } @@ -96,52 +120,74 @@ preview_init(Preview* self, Resources* resources, Input* input) void preview_tick(Preview* self) { - self->zoom = CLAMP(self->zoom, PREVIEW_ZOOM_MIN, PREVIEW_ZOOM_MAX); - self->oldGridSize = self->gridSize; + self->settings->zoom = CLAMP(self->settings->zoom, PREVIEW_ZOOM_MIN, PREVIEW_ZOOM_MAX); + self->oldGridSize = glm::vec2(self->settings->gridSizeX, self->settings->gridSizeY); + self->oldGridOffset = glm::vec2(self->settings->gridOffsetX, self->settings->gridOffsetY); + + if (self->animationID > -1) + { + Anm2Animation* animation = &self->anm2->animations[self->animationID]; + + if (self->isPlaying) + { + self->time += (f32)self->anm2->fps / TICK_RATE; + + if (self->time >= (f32)animation->frameNum - 1) + self->time = 0.0f; + } + else + self->time = CLAMP(self->time, 0.0f, (f32)animation->frameNum); + } } void preview_draw(Preview* self) { - GLuint shader = self->resources->shaders[SHADER]; - float zoomFactor = self->zoom / 100.0f; + GLuint shaderLine = self->resources->shaders[SHADER_LINE]; + GLuint shaderTexture = self->resources->shaders[SHADER_TEXTURE]; + + f32 zoomFactor = self->settings->zoom / 100.0f; /* Convert pan to pixels */ glm::vec2 ndcPan = glm::vec2( - self->pan.x / (PREVIEW_SIZE.x / 2.0f), - -self->pan.y / (PREVIEW_SIZE.y / 2.0f) + self->settings->panX / (PREVIEW_SIZE.x / 2.0f), + -self->settings->panY / (PREVIEW_SIZE.y / 2.0f) ); /* Transformation matrix */ - glm::mat4 transform = glm::translate(glm::mat4(1.0f), glm::vec3(ndcPan, 0.0f)); - transform = glm::scale(transform, glm::vec3(zoomFactor, zoomFactor, 1.0f)); - + glm::mat4 previewTransform = glm::translate(glm::mat4(1.0f), glm::vec3(ndcPan, 0.0f)); + previewTransform = glm::scale(previewTransform, glm::vec3(zoomFactor, zoomFactor, 1.0f)); + glBindFramebuffer(GL_FRAMEBUFFER, self->fbo); glViewport(0, 0, PREVIEW_SIZE.x, PREVIEW_SIZE.y); glClearColor ( - self->backgroundColor.r, - self->backgroundColor.g, - self->backgroundColor.b, - self->backgroundColor.a + self->settings->backgroundColorR, + self->settings->backgroundColorG, + self->settings->backgroundColorB, + self->settings->backgroundColorA ); glClear(GL_COLOR_BUFFER_BIT); - glUseProgram(shader); - glUniformMatrix4fv(glGetUniformLocation(shader, SHADER_UNIFORM_TRANSFORM), 1, GL_FALSE, (f32*)value_ptr(transform)); + glUseProgram(shaderLine); + glUniformMatrix4fv(glGetUniformLocation(shaderLine, SHADER_UNIFORM_TRANSFORM), 1, GL_FALSE, (f32*)value_ptr(previewTransform)); - if (self->isGrid) + if (self->settings->isGrid) { - if (self->gridSize != self->oldGridSize) + if + ( + (ivec2(self->settings->gridSizeX, self->settings->gridSizeY) != self->oldGridSize) || + (ivec2(self->settings->gridOffsetX, self->settings->gridOffsetY) != self->oldGridOffset) + ) _preview_grid_set(self); glBindVertexArray(self->gridVAO); glUniform4f ( - glGetUniformLocation(shader, SHADER_UNIFORM_COLOR), - self->gridColor.r, self->gridColor.g, self->gridColor.b, self->gridColor.a + glGetUniformLocation(shaderLine, SHADER_UNIFORM_COLOR), + self->settings->gridColorR, self->settings->gridColorG, self->settings->gridColorB, self->settings->gridColorA ); glDrawArrays(GL_LINES, 0, self->gridVertexCount); @@ -149,31 +195,264 @@ preview_draw(Preview* self) glBindVertexArray(0); } - if (self->isAxis) + if (self->settings->isAxis) { glBindVertexArray(self->axisVAO); /* Axes */ glUniform4f ( - glGetUniformLocation(shader, SHADER_UNIFORM_COLOR), - self->axisColor.r, self->axisColor.g, self->axisColor.b, self->axisColor.a + glGetUniformLocation(shaderLine, SHADER_UNIFORM_COLOR), + self->settings->axisColorR, self->settings->axisColorG, self->settings->axisColorB, self->settings->axisColorA ); glDrawArrays(GL_LINES, 0, 2); - glUniform4f ( - glGetUniformLocation(shader, SHADER_UNIFORM_COLOR), - self->axisColor.r, self->axisColor.g, self->axisColor.b, self->axisColor.a + glGetUniformLocation(shaderLine, SHADER_UNIFORM_COLOR), + self->settings->axisColorR, self->settings->axisColorG, self->settings->axisColorB, self->settings->axisColorA ); glDrawArrays(GL_LINES, 2, 2); + glBindVertexArray(0); } - glUseProgram(0); - - glBindFramebuffer(GL_FRAMEBUFFER, 0); + glUseProgram(0); + + glUseProgram(shaderTexture); + + if (self->animationID > -1) + { + Anm2Frame rootFrame = Anm2Frame{}; + Anm2Animation* animation = &self->anm2->animations[self->animationID]; + bool isRootFrame = anm2_frame_from_time(self->anm2, animation, &rootFrame, ANM2_ROOT_ANIMATION, 0, self->time); + + /* Layers (Reversed) */ + for (auto & [id, layerAnimation] : animation->layerAnimations) + { + Anm2Layer* layer = &self->anm2->layers[id]; + Anm2Frame frame; + + Texture* texture = &self->resources->loadedTextures[layer->spritesheetID]; + + if (texture->isInvalid) + continue; + + if (!layerAnimation.isVisible || layerAnimation.frames.size() <= 0) + continue; + + if (!anm2_frame_from_time(self->anm2, animation, &frame, ANM2_LAYER_ANIMATION, id, self->time)) + continue; + + if (!frame.isVisible) + continue; + + glm::mat4 layerTransform = previewTransform; + glm::vec2 previewSize = glm::vec2(PREVIEW_SIZE); + + glm::vec2 scale = self->settings->isRootTransform ? + (frame.scale / 100.0f) * (rootFrame.scale / 100.0f) : + (frame.scale / 100.0f); + + glm::vec2 position = self->settings->isRootTransform ? + (frame.position + rootFrame.position) : + frame.position; + + glm::vec2 scaledSize = frame.size * scale; + glm::vec2 scaledPivot = frame.pivot * scale; + + glm::vec2 ndcPos = position / (previewSize / 2.0f); + glm::vec2 ndcPivotOffset = scaledPivot / (previewSize * 0.5f); + glm::vec2 ndcScale = scaledSize / (previewSize * 0.5f); + + layerTransform = glm::translate(layerTransform, glm::vec3(ndcPos - ndcPivotOffset, 0.0f)); + layerTransform = glm::translate(layerTransform, glm::vec3(ndcPivotOffset, 0.0f)); + layerTransform = glm::rotate(layerTransform, glm::radians(frame.rotation), glm::vec3(0, 0, 1)); + layerTransform = glm::translate(layerTransform, glm::vec3(-ndcPivotOffset, 0.0f)); + layerTransform = glm::scale(layerTransform, glm::vec3(ndcScale, 1.0f)); + + glm::vec2 uvMin = frame.crop / glm::vec2(texture->size); + glm::vec2 uvMax = (frame.crop + frame.size) / glm::vec2(texture->size); + + f32 vertices[] = { + 0, 0, uvMin.x, uvMin.y, + 1, 0, uvMax.x, uvMin.y, + 1, 1, uvMax.x, uvMax.y, + 0, 1, uvMin.x, uvMax.y + }; + + glBindVertexArray(self->textureVAO); + + glBindBuffer(GL_ARRAY_BUFFER, self->textureVBO); + glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_DYNAMIC_DRAW); + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, texture->handle); + glUniform1i(glGetUniformLocation(shaderTexture, SHADER_UNIFORM_TEXTURE), 0); + glUniform4fv(glGetUniformLocation(shaderTexture, SHADER_UNIFORM_TINT), 1, (f32*)value_ptr(frame.tintRGBA)); + glUniform3fv(glGetUniformLocation(shaderTexture, SHADER_UNIFORM_COLOR_OFFSET), 1, (f32*)value_ptr(frame.offsetRGB)); + glUniformMatrix4fv(glGetUniformLocation(shaderTexture, SHADER_UNIFORM_TRANSFORM), 1, GL_FALSE, value_ptr(layerTransform)); + + glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0); + + glBindTexture(GL_TEXTURE_2D, 0); + } + + /* Root */ + if + (isRootFrame && animation->rootAnimation.isVisible && rootFrame.isVisible) + { + glm::mat4 rootTransform = previewTransform; + glm::vec2 previewSize = {(f32)PREVIEW_SIZE.x, (f32)PREVIEW_SIZE.y}; + glm::vec2 ndcPos = (rootFrame.position - (PREVIEW_TARGET_SIZE / 2.0f)) / (previewSize / 2.0f); + glm::vec2 ndcScale = PREVIEW_TARGET_SIZE / (previewSize * 0.5f); + + rootTransform = glm::translate(rootTransform, glm::vec3(ndcPos, 0.0f)); + rootTransform = glm::scale(rootTransform, glm::vec3(ndcScale, 1.0f)); + + f32 vertices[] = + { + 0, 0, 0.0f, 0.0f, + 1, 0, 1.0f, 0.0f, + 1, 1, 1.0f, 1.0f, + 0, 1, 0.0f, 1.0f + }; + + glBindVertexArray(self->textureVAO); + + glBindBuffer(GL_ARRAY_BUFFER, self->textureVBO); + glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_DYNAMIC_DRAW); + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, self->resources->textures[TEXTURE_TARGET].handle); + glUniform1i(glGetUniformLocation(shaderTexture, SHADER_UNIFORM_TEXTURE), 0); + glUniform4fv(glGetUniformLocation(shaderTexture, SHADER_UNIFORM_TINT), 1, value_ptr(PREVIEW_ROOT_TINT)); + glUniform3fv(glGetUniformLocation(shaderTexture, SHADER_UNIFORM_COLOR_OFFSET), 1, value_ptr(PREVIEW_ROOT_COLOR_OFFSET)); + glUniformMatrix4fv(glGetUniformLocation(shaderTexture, SHADER_UNIFORM_TRANSFORM), 1, GL_FALSE, value_ptr(rootTransform)); + + glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0); + + glBindTexture(GL_TEXTURE_2D, 0); + } + + /* Pivots */ + if (self->settings->isShowPivot) + { + for (auto it = animation->layerAnimations.rbegin(); it != animation->layerAnimations.rend(); it++) + { + s32 id = it->first; + Anm2LayerAnimation layerAnimation = it->second; + + Anm2Layer* layer = &self->anm2->layers[id]; + Anm2Frame frame; + + Texture* texture = &self->resources->loadedTextures[layer->spritesheetID]; + + if (!layerAnimation.isVisible || layerAnimation.frames.size() <= 0) + continue; + + if (!anm2_frame_from_time(self->anm2, animation, &frame, ANM2_LAYER_ANIMATION, id, self->time)) + continue; + + if (!frame.isVisible) + continue; + + + glm::vec2 scale = self->settings->isRootTransform ? + (frame.scale / 100.0f) * (rootFrame.scale / 100.0f) : + (frame.scale / 100.0f); + + glm::vec2 position = self->settings->isRootTransform ? + (frame.position + rootFrame.position) : + frame.position; + + glm::mat4 pivotTransform = previewTransform; + glm::vec2 previewSize = {(f32)PREVIEW_SIZE.x, (f32)PREVIEW_SIZE.y}; + glm::vec2 scaledSize = PREVIEW_PIVOT_SIZE * scale; + glm::vec2 ndcPos = (position - (PREVIEW_PIVOT_SIZE / 2.0f)) / (previewSize / 2.0f); + glm::vec2 ndcScale = scaledSize / (previewSize * 0.5f); + + pivotTransform = glm::translate(pivotTransform, glm::vec3(ndcPos, 0.0f)); + pivotTransform = glm::scale(pivotTransform, glm::vec3(ndcScale, 1.0f)); + + f32 vertices[] = + { + 0, 0, 0.0f, 0.0f, + 1, 0, 1.0f, 0.0f, + 1, 1, 1.0f, 1.0f, + 0, 1, 0.0f, 1.0f + }; + + glBindVertexArray(self->textureVAO); + + glBindBuffer(GL_ARRAY_BUFFER, self->textureVBO); + glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_DYNAMIC_DRAW); + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, self->resources->textures[TEXTURE_PIVOT].handle); + glUniform1i(glGetUniformLocation(shaderTexture, SHADER_UNIFORM_TEXTURE), 0); + glUniform4fv(glGetUniformLocation(shaderTexture, SHADER_UNIFORM_TINT), 1, value_ptr(PREVIEW_PIVOT_TINT)); + glUniform3fv(glGetUniformLocation(shaderTexture, SHADER_UNIFORM_COLOR_OFFSET), 1, value_ptr(PREVIEW_PIVOT_COLOR_OFFSET)); + glUniformMatrix4fv(glGetUniformLocation(shaderTexture, SHADER_UNIFORM_TRANSFORM), 1, GL_FALSE, value_ptr(pivotTransform)); + + glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0); + + glBindTexture(GL_TEXTURE_2D, 0); + } + } + + /* Nulls */ + for (auto & [id, nullAnimation] : animation->nullAnimations) + { + Anm2Frame frame; + + if (!nullAnimation.isVisible || nullAnimation.frames.size() <= 0) + continue; + + if (!anm2_frame_from_time(self->anm2, animation, &frame, ANM2_NULL_ANIMATION, id, self->time)) + continue; + + if (!frame.isVisible) + continue; + + glm::mat4 nullTransform = previewTransform; + glm::vec2 previewSize = {(f32)PREVIEW_SIZE.x, (f32)PREVIEW_SIZE.y}; + + glm::vec2 pos = self->settings->isRootTransform ? + frame.position + (rootFrame.position) - (PREVIEW_TARGET_SIZE / 2.0f): + frame.position - (PREVIEW_TARGET_SIZE / 2.0f); + + glm::vec2 ndcPos = pos / (previewSize / 2.0f); + glm::vec2 ndcScale = PREVIEW_TARGET_SIZE / (previewSize * 0.5f); + + nullTransform = glm::translate(nullTransform, glm::vec3(ndcPos, 0.0f)); + nullTransform = glm::scale(nullTransform, glm::vec3(ndcScale, 1.0f)); + + f32 vertices[] = + { + 0, 0, 0.0f, 0.0f, + 1, 0, 1.0f, 0.0f, + 1, 1, 1.0f, 1.0f, + 0, 1, 0.0f, 1.0f + }; + + glBindVertexArray(self->textureVAO); + + glBindBuffer(GL_ARRAY_BUFFER, self->textureVBO); + glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_DYNAMIC_DRAW); + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, self->resources->textures[TEXTURE_TARGET].handle); + glUniform1i(glGetUniformLocation(shaderTexture, SHADER_UNIFORM_TEXTURE), 0); + glUniform4fv(glGetUniformLocation(shaderTexture, SHADER_UNIFORM_TINT), 1, value_ptr(PREVIEW_NULL_TINT)); + glUniform3fv(glGetUniformLocation(shaderTexture, SHADER_UNIFORM_COLOR_OFFSET), 1, value_ptr(PREVIEW_NULL_COLOR_OFFSET)); + glUniformMatrix4fv(glGetUniformLocation(shaderTexture, SHADER_UNIFORM_TRANSFORM), 1, GL_FALSE, value_ptr(nullTransform)); + + glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0); + + glBindTexture(GL_TEXTURE_2D, 0); + } + } + + glUseProgram(0); + + glBindFramebuffer(GL_FRAMEBUFFER, 0); } void diff --git a/src/preview.h b/src/preview.h index b1436d1..6f0c3fb 100644 --- a/src/preview.h +++ b/src/preview.h @@ -1,16 +1,21 @@ #pragma once +#include "anm2.h" #include "resources.h" #include "input.h" +#include "settings.h" -static const ivec2 PREVIEW_SIZE = {960, 720}; +static const ivec2 PREVIEW_SIZE = {2000, 2000}; static const ivec2 PREVIEW_CENTER = {0, 0}; -#define PREVIEW_ZOOM_MIN 1 -#define PREVIEW_ZOOM_MAX 400 +#define PREVIEW_ZOOM_MIN 1.0f +#define PREVIEW_ZOOM_MAX 800.0f #define PREVIEW_GRID_MIN 1 #define PREVIEW_GRID_MAX 50 -#define PREVIEW_ZOOM_STEP 10 +#define PREVIEW_GRID_OFFSET_MIN 0 +#define PREVIEW_GRID_OFFSET_MAX 100 +#define PREVIEW_ZOOM_STEP 50 +#define PREVIEW static const f32 PREVIEW_AXIS_VERTICES[] = { @@ -20,31 +25,44 @@ static const f32 PREVIEW_AXIS_VERTICES[] = 0.0f, 1.0f }; +static const GLuint PREVIEW_TEXTURE_INDICES[] = {0, 1, 2, 2, 3, 0}; + +static const vec2 PREVIEW_PIVOT_SIZE = {6, 6}; +static const vec2 PREVIEW_TARGET_SIZE = {16, 16}; +static const vec4 PREVIEW_ROOT_TINT = {0.0f, 1.0f, 0.0f, 1.0f}; +static const vec3 PREVIEW_ROOT_COLOR_OFFSET = {0.0f, 0.0f, 0.0f}; +static const vec4 PREVIEW_NULL_TINT = {0.0f, 0.0f, 1.0f, 1.0f}; +static const vec3 PREVIEW_NULL_COLOR_OFFSET = {0.0f, 0.0f, 0.0f}; +static const vec4 PREVIEW_PIVOT_TINT = {1.0f, 1.0f, 1.0f, 1.0f}; +static const vec3 PREVIEW_PIVOT_COLOR_OFFSET = {0.0f, 0.0f, 0.0f}; + struct Preview { - GLuint texture; - GLuint fbo; - GLuint rbo; - GLuint gridVAO; - GLuint gridVBO; + Anm2* anm2 = NULL; + Input* input = NULL; + Resources* resources = NULL; + Settings* settings = NULL; GLuint axisVAO; GLuint axisVBO; - Input* input; - Resources* resources; - bool isGrid = false; - bool isAxis = true; - ivec2 viewport = PREVIEW_SIZE; - f32 zoom = 100; - vec2 pan = PREVIEW_CENTER; - ivec2 gridSize = {10, 10}; + GLuint fbo; + GLuint gridVAO; + GLuint gridVBO; + GLuint rbo; + GLuint texture; + GLuint textureEBO; + GLuint textureVAO; + GLuint textureVBO; + bool isPlaying = false; + f32 time = 0; + ivec2 oldGridOffset = {-1, -1}; ivec2 oldGridSize = {-1, -1}; + ivec2 viewport = PREVIEW_SIZE; + s32 animationID = -1; s32 gridVertexCount = -1; - vec4 backgroundColor = {0.113, 0.184, 0.286, 1.0f}; - vec4 gridColor = {1.0, 1.0, 1.0, 0.125f}; - vec4 axisColor = {1.0, 1.0, 1.0, 0.5f}; + }; -void preview_init(Preview* self, Resources* resources, Input* input); +void preview_init(Preview* self, Anm2* anm2, Resources* resources, Input* input, Settings* settings); void preview_draw(Preview* self); void preview_tick(Preview* self); void preview_free(Preview* self); diff --git a/src/resources.cpp b/src/resources.cpp index 0ee28f5..930d9cd 100644 --- a/src/resources.cpp +++ b/src/resources.cpp @@ -25,6 +25,7 @@ resources_free(Resources* self) resources_loaded_textures_free(self); } +/* Frees loaded textures */ void resources_loaded_textures_free(Resources* self) { diff --git a/src/settings.cpp b/src/settings.cpp new file mode 100644 index 0000000..2ee60f8 --- /dev/null +++ b/src/settings.cpp @@ -0,0 +1,161 @@ +#include "settings.h" + +static void _settings_setting_load(Settings* self, char* line); +static void _settings_setting_write(Settings* self, SDL_IOStream* io, SettingsItem type); + +/* Load a particular settings from a line */ +static void +_settings_setting_load(Settings* self, char* line) +{ + for (int i = 0; i < SETTINGS_COUNT; i++) + { + if (strncmp(line, SETTINGS_ENTRIES[i].value, strlen(SETTINGS_ENTRIES[i].value)) == 0) + { + char* value = line + strlen(SETTINGS_ENTRIES[i].value); + void* target = (u8*)self + SETTINGS_ENTRIES[i].offset; + + + switch (SETTINGS_ENTRIES[i].type) + { + case SETTINGS_TYPE_INT: + case SETTINGS_TYPE_BOOL: + *(s32*)target = atoi(value); + break; + case SETTINGS_TYPE_FLOAT: + *(f32*)target = atof(value); + break; + case SETTINGS_TYPE_STRING: + strncpy((char*)target, value, SETTINGS_BUFFER_ITEM - 1); + ((char*)target)[SETTINGS_BUFFER_ITEM - 1] = '\0'; + break; + default: + break; + } + return; + } + } +} + +/* Writes a particular setting to the current IO */ +static void +_settings_setting_write(Settings* self, SDL_IOStream* io, SettingsItem type) +{ + char valueBuffer[SETTINGS_BUFFER_ITEM]; + SettingsEntry entry = SETTINGS_ENTRIES[type]; + + u8* selfPointer = (u8*)self; + + memset(valueBuffer, '\0', sizeof(valueBuffer)); + + switch (entry.type) + { + case SETTINGS_TYPE_INT: + snprintf(valueBuffer, SETTINGS_BUFFER_ITEM, entry.format, *(s32*)(selfPointer + entry.offset)); + break; + case SETTINGS_TYPE_BOOL: + snprintf(valueBuffer, SETTINGS_BUFFER_ITEM, entry.format, *(bool*)(selfPointer + entry.offset)); + break; + case SETTINGS_TYPE_FLOAT: + snprintf(valueBuffer, SETTINGS_BUFFER_ITEM, entry.format, *(f32*)(selfPointer + entry.offset)); + break; + case SETTINGS_TYPE_STRING: + snprintf(valueBuffer, SETTINGS_BUFFER_ITEM, entry.value, entry.format, *(char*)(selfPointer + entry.offset)); + break; + default: + break; + } + + SDL_WriteIO(io, valueBuffer, strlen(valueBuffer)); + SDL_WriteIO(io, "\n", strlen("\n")); +} + +/* Saves the program's settings to the PATH_SETTINGS file */ +void +settings_save(Settings* self) +{ + char buffer[SETTINGS_BUFFER]; + + /* Get the original settings.ini buffer (as previously saved by imgui before this is called) */ + memset(buffer, '\0', SETTINGS_BUFFER); + + SDL_IOStream* io = SDL_IOFromFile(PATH_SETTINGS, "r"); + + if (!io) + { + printf(STRING_ERROR_SETTINGS_INIT, PATH_SETTINGS); + return; + } + + SDL_ReadIO(io, buffer, SETTINGS_BUFFER); + SDL_CloseIO(io); + + io = SDL_IOFromFile(PATH_SETTINGS, "w"); + + if (!io) + { + printf(STRING_ERROR_SETTINGS_INIT, PATH_SETTINGS); + return; + } + + /* [Settings] */ + SDL_WriteIO(io, SETTINGS_SECTION, strlen(SETTINGS_SECTION)); + SDL_WriteIO(io, "\n", strlen("\n")); + + /* Write down all elements */ + for (s32 i = 0; i < SETTINGS_COUNT; i++) + _settings_setting_write(self, io, (SettingsItem)i); + + SDL_WriteIO(io, "\n", strlen("\n")); + + /* specify that the other settings are imgui */ + SDL_WriteIO(io, SETTINGS_SECTION_IMGUI, strlen(SETTINGS_SECTION_IMGUI)); + SDL_WriteIO(io, "\n", strlen("\n")); + + /* Then write original contents */ + SDL_WriteIO(io, buffer, strlen(buffer)); + SDL_CloseIO(io); +} + +/* Loads the settings from the PATH_SETTINGS file */ +void +settings_load(Settings* self) +{ + char buffer[SETTINGS_BUFFER]; + char* line = NULL; + + memset(buffer, '\0', SETTINGS_BUFFER); + + SDL_IOStream* io = SDL_IOFromFile(PATH_SETTINGS, "r"); + + if (!io) + { + printf(STRING_ERROR_SETTINGS_INIT, PATH_SETTINGS); + return; + } + + size_t bytesRead = SDL_ReadIO(io, buffer, SETTINGS_BUFFER); + SDL_CloseIO(io); + + buffer[bytesRead] = '\0'; + + line = strtok(buffer, "\n"); + + /* The settings will be the first section in the file */ + /* Go through its elements, load them to settings, and go on with your day */ + while (line != NULL) + { + if (strcmp(line, SETTINGS_SECTION) == 0) + { + line = strtok(NULL, "\n"); + continue; + } + + _settings_setting_load(self, line); + + /* get out here */ + if (strcmp(line, SETTINGS_SECTION_IMGUI) == 0) + break; + + line = strtok(NULL, "\n"); + } +} \ No newline at end of file diff --git a/src/settings.h b/src/settings.h new file mode 100644 index 0000000..45077e8 --- /dev/null +++ b/src/settings.h @@ -0,0 +1,115 @@ +#pragma once + +#include "COMMON.h" + +#define SETTINGS_BUFFER 0xFFFF +#define SETTINGS_BUFFER_ITEM 0xFF +#define SETTINGS_SECTION "[Settings]" +#define SETTINGS_SECTION_IMGUI "# Dear ImGui" + +enum SettingsValueType +{ + SETTINGS_TYPE_INT, + SETTINGS_TYPE_FLOAT, + SETTINGS_TYPE_BOOL, + SETTINGS_TYPE_STRING +}; + +struct SettingsEntry +{ + const char* value; + const char* format; + SettingsValueType type; + s32 offset; +}; + +#define SETTINGS_COUNT (SETTINGS_BACKGROUND_COLOR_A + 1) +enum SettingsItem +{ + SETTINGS_WINDOW_W, + SETTINGS_WINDOW_H, + SETTINGS_IS_AXIS, + SETTINGS_IS_GRID, + SETTINGS_IS_ROOT_TRANSFORM, + SETTINGS_IS_SHOW_PIVOT, + SETTINGS_PAN_X, + SETTINGS_PAN_Y, + SETTINGS_ZOOM, + SETTINGS_GRID_SIZE_X, + SETTINGS_GRID_SIZE_Y, + SETTINGS_GRID_OFFSET_X, + SETTINGS_GRID_OFFSET_Y, + SETTINGS_GRID_COLOR_R, + SETTINGS_GRID_COLOR_G, + SETTINGS_GRID_COLOR_B, + SETTINGS_GRID_COLOR_A, + SETTINGS_AXIS_COLOR_R, + SETTINGS_AXIS_COLOR_G, + SETTINGS_AXIS_COLOR_B, + SETTINGS_AXIS_COLOR_A, + SETTINGS_BACKGROUND_COLOR_R, + SETTINGS_BACKGROUND_COLOR_G, + SETTINGS_BACKGROUND_COLOR_B, + SETTINGS_BACKGROUND_COLOR_A +}; + +struct Settings +{ + s32 windowW = 1920; + s32 windowH = 1080; + bool isAxis = true; + bool isGrid = true; + bool isRootTransform = false; + bool isShowPivot = false; + f32 panX = 0.0f; + f32 panY = 0.0f; + f32 zoom = 200.0f; + s32 gridSizeX = 10; + s32 gridSizeY = 10; + s32 gridOffsetX = 10; + s32 gridOffsetY = 10; + f32 gridColorR = 1.0f; + f32 gridColorG = 1.0f; + f32 gridColorB = 1.0f; + f32 gridColorA = 0.125f; + f32 axisColorR = 1.0f; + f32 axisColorG = 1.0f; + f32 axisColorB = 1.0f; + f32 axisColorA = 0.5f; + f32 backgroundColorR = 0.113f; + f32 backgroundColorG = 0.184f; + f32 backgroundColorB = 0.286f; + f32 backgroundColorA = 1.0f; +}; + +static const SettingsEntry SETTINGS_ENTRIES[SETTINGS_COUNT] = +{ + {"windowW=", "windowW=%i", SETTINGS_TYPE_INT, offsetof(Settings, windowW)}, + {"windowH=", "windowH=%i", SETTINGS_TYPE_INT, offsetof(Settings, windowH)}, + {"isAxis=", "isAxis=%i", SETTINGS_TYPE_BOOL, offsetof(Settings, isAxis)}, + {"isGrid=", "isGrid=%i", SETTINGS_TYPE_BOOL, offsetof(Settings, isGrid)}, + {"isRootTransform=", "isRootTransform=%i", SETTINGS_TYPE_BOOL, offsetof(Settings, isRootTransform)}, + {"isShowPivot=", "isShowPivot=%i", SETTINGS_TYPE_BOOL, offsetof(Settings, isShowPivot)}, + {"panX=", "panX=%f", SETTINGS_TYPE_FLOAT, offsetof(Settings, panX)}, + {"panY=", "panY=%f", SETTINGS_TYPE_FLOAT, offsetof(Settings, panY)}, + {"zoom=", "zoom=%f", SETTINGS_TYPE_FLOAT, offsetof(Settings, zoom)}, + {"gridSizeX=", "gridSizeX=%i", SETTINGS_TYPE_INT, offsetof(Settings, gridSizeX)}, + {"gridSizeY=", "gridSizeY=%i", SETTINGS_TYPE_INT, offsetof(Settings, gridSizeY)}, + {"gridOffsetX=", "gridOffsetX=%i", SETTINGS_TYPE_INT, offsetof(Settings, gridOffsetX)}, + {"gridOffsetY=", "gridOffsetY=%i", SETTINGS_TYPE_INT, offsetof(Settings, gridOffsetY)}, + {"gridColorR=", "gridColorR=%f", SETTINGS_TYPE_FLOAT, offsetof(Settings, gridColorR)}, + {"gridColorG=", "gridColorG=%f", SETTINGS_TYPE_FLOAT, offsetof(Settings, gridColorG)}, + {"gridColorB=", "gridColorB=%f", SETTINGS_TYPE_FLOAT, offsetof(Settings, gridColorB)}, + {"gridColorA=", "gridColorA=%f", SETTINGS_TYPE_FLOAT, offsetof(Settings, gridColorA)}, + {"axisColorR=", "axisColorR=%f", SETTINGS_TYPE_FLOAT, offsetof(Settings, axisColorR)}, + {"axisColorG=", "axisColorG=%f", SETTINGS_TYPE_FLOAT, offsetof(Settings, axisColorG)}, + {"axisColorB=", "axisColorB=%f", SETTINGS_TYPE_FLOAT, offsetof(Settings, axisColorB)}, + {"axisColorA=", "axisColorA=%f", SETTINGS_TYPE_FLOAT, offsetof(Settings, axisColorA)}, + {"backgroundColorR=", "backgroundColorR=%f", SETTINGS_TYPE_FLOAT, offsetof(Settings, backgroundColorR)}, + {"backgroundColorG=", "backgroundColorG=%f", SETTINGS_TYPE_FLOAT, offsetof(Settings, backgroundColorG)}, + {"backgroundColorB=", "backgroundColorB=%f", SETTINGS_TYPE_FLOAT, offsetof(Settings, backgroundColorB)}, + {"backgroundColorA=", "backgroundColorA=%f", SETTINGS_TYPE_FLOAT, offsetof(Settings, backgroundColorA)} +}; + +void settings_save(Settings* self); +void settings_load(Settings* self); \ No newline at end of file diff --git a/src/state.cpp b/src/state.cpp index 4b52d74..64cd593 100644 --- a/src/state.cpp +++ b/src/state.cpp @@ -29,6 +29,8 @@ _tick(State* state) } } + SDL_GetWindowSize(state->window, &state->settings.windowW, &state->settings.windowH); + input_tick(&state->input); preview_tick(&state->preview); dialog_tick(&state->dialog); @@ -47,6 +49,15 @@ _draw(State* state) void init(State* state) { + /* set start working directory */ + std::filesystem::path startPath = std::filesystem::current_path(); + + memset(state->startPath, '\0', PATH_MAX - 1); + + strncpy(state->startPath, startPath.c_str(), PATH_MAX - 1); + + settings_load(&state->settings); + printf(STRING_INFO_INIT); if (!SDL_Init(SDL_INIT_VIDEO)) @@ -60,15 +71,21 @@ init(State* state) SDL_CreateWindowAndRenderer ( STRING_WINDOW_TITLE, - WINDOW_WIDTH, - WINDOW_HEIGHT, + state->settings.windowW, + state->settings.windowH, WINDOW_FLAGS, &state->window, &state->renderer ); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3); + state->glContext = SDL_GL_CreateContext(state->window); + printf(STRING_INFO_OPENGL, glGetString(GL_VERSION)); + if (!state->glContext) { printf(STRING_ERROR_GL_CONTEXT_INIT, SDL_GetError()); @@ -76,24 +93,18 @@ init(State* state) } glewInit(); - + glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glDisable(GL_DEPTH_TEST); printf(STRING_INFO_GLEW_INIT); - + resources_init(&state->resources); - dialog_init(&state->dialog, &state->anm2, &state->resources); + dialog_init(&state->dialog, &state->anm2, &state->resources, state->window); - preview_init(&state->preview, &state->resources, &state->input); + preview_init(&state->preview, &state->anm2, &state->resources, &state->input, &state->settings); - if (state->isArgument) - anm2_deserialize(&state->anm2, &state->resources, state->argument); - else - anm2_new(&state->anm2); - - window_title_from_anm2_set(state->window, &state->anm2); - imgui_init ( &state->imgui, @@ -102,9 +113,17 @@ init(State* state) &state->input, &state->anm2, &state->preview, + &state->settings, state->window, &state->glContext ); + + if (state->isArgument) + anm2_deserialize(&state->anm2, &state->resources, state->argument); + else + anm2_new(&state->anm2); + + window_title_from_anm2_set(state->window, &state->anm2); } void @@ -129,7 +148,11 @@ loop(State* state) void quit(State* state) { + /* return to base path */ + std::filesystem::current_path(state->startPath); + imgui_free(&state->imgui); + settings_save(&state->settings); preview_free(&state->preview); resources_free(&state->resources); diff --git a/src/state.h b/src/state.h index a5b5a30..4c380e0 100644 --- a/src/state.h +++ b/src/state.h @@ -1,11 +1,9 @@ #pragma once -#include "shader.h" #include "imgui.h" -#define TICK_DELAY 16 -#define WINDOW_WIDTH 1600 -#define WINDOW_HEIGHT 900 +#define WINDOW_WIDTH 1920 +#define WINDOW_HEIGHT 1080 #define WINDOW_FLAGS SDL_WINDOW_RESIZABLE struct State @@ -19,7 +17,9 @@ struct State Preview preview; Anm2 anm2; Resources resources; + Settings settings; char argument[PATH_MAX] = STRING_EMPTY; + char startPath[PATH_MAX] = STRING_EMPTY; bool isArgument = false; u64 tick = 0; u64 lastTick = 0; diff --git a/src/texture.h b/src/texture.h index a25bc58..1341322 100644 --- a/src/texture.h +++ b/src/texture.h @@ -7,6 +7,7 @@ struct Texture GLuint handle = 0; ivec2 size = {0, 0}; s32 channels = -1; + bool isInvalid = false; }; void texture_gl_set(Texture* self, void* data);