diff --git a/CMakeLists.txt b/CMakeLists.txt index 88379c6..e8567af 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -106,6 +106,7 @@ set(sources "src/main.cpp" "src/CanvasComponents/ImageCanvasComponent.cpp" "src/CanvasComponents/CanvasComponent.cpp" "src/GUIStuff/GUIManager.cpp" + "src/GUIStuff/GUIFloatAnimation.cpp" "src/GUIStuff/GUIManagerID.cpp" "src/GUIStuff/ClayImplement.cpp" "src/GUIStuff/Elements/GUIStuffHelpers.cpp" diff --git a/include/Helpers/BezierEasing.cpp b/include/Helpers/BezierEasing.cpp index f7c2e3b..0b534d9 100644 --- a/include/Helpers/BezierEasing.cpp +++ b/include/Helpers/BezierEasing.cpp @@ -7,7 +7,7 @@ #include "BezierEasing.hpp" #include -BezierEasing BezierEasing::linear{0.0, 1.0, 0.0, 1.0}; +BezierEasing BezierEasing::linear{0.0, 0.0, 1.0, 1.0}; BezierEasing::BezierEasing() {} diff --git a/src/GUIStuff/Elements/LayoutElement.cpp b/src/GUIStuff/Elements/LayoutElement.cpp index a546fa7..c636367 100644 --- a/src/GUIStuff/Elements/LayoutElement.cpp +++ b/src/GUIStuff/Elements/LayoutElement.cpp @@ -11,59 +11,6 @@ void LayoutElement::layout(const Clay_ElementId& id, const std::function= animation.anim.duration) { - animation.currentTime = animation.anim.duration; - animation.state = InternalFloatAnimation::State::DONE; - } - gui.set_to_layout(); - break; - } - case InternalFloatAnimation::State::RUN_REVERSE: { - animation.currentTime -= gui.io.deltaTime; - if(animation.currentTime <= 0.0f) { - animation.currentTime = 0.0f; - animation.state = InternalFloatAnimation::State::DONE; - } - gui.set_to_layout(); - break; - } - case InternalFloatAnimation::State::DONE: - break; - } - } -} - -float LayoutElement::get_float_animation(const char* animationID, const FloatAnimation& animation) { - InternalFloatAnimation internalAnim; - internalAnim.anim = animation; - auto [it, placed] = animations.emplace(animationID, internalAnim); - const InternalFloatAnimation& actualInternalAnim = it->second; - return std::lerp(actualInternalAnim.anim.startVal, actualInternalAnim.anim.endVal, actualInternalAnim.anim.easing(it->second.currentTime / actualInternalAnim.anim.duration)); -} - -void LayoutElement::animation_trigger(const char* animationID) { - auto it = animations.find(animationID); - if(it != animations.end()) { - InternalFloatAnimation& animation = it->second; - if(animation.currentTime < animation.anim.duration) - animation.state = InternalFloatAnimation::State::RUN; - } -} - -void LayoutElement::animation_trigger_reverse(const char* animationID) { - auto it = animations.find(animationID); - if(it != animations.end()) { - InternalFloatAnimation& animation = it->second; - if(animation.currentTime > 0.0f) - animation.state = InternalFloatAnimation::State::RUN_REVERSE; - } -} - void LayoutElement::input_mouse_button_callback(const InputManager::MouseButtonCallbackArgs& button) { if(c.mouseButton) c.mouseButton(this, button); } diff --git a/src/GUIStuff/Elements/LayoutElement.hpp b/src/GUIStuff/Elements/LayoutElement.hpp index a9fe97a..beba2e5 100644 --- a/src/GUIStuff/Elements/LayoutElement.hpp +++ b/src/GUIStuff/Elements/LayoutElement.hpp @@ -1,6 +1,5 @@ #pragma once #include "Element.hpp" -#include "Helpers/BezierEasing.hpp" namespace GUIStuff { @@ -13,32 +12,12 @@ class LayoutElement : public Element { std::function mouseWheel; std::function key; }; - struct FloatAnimation { - float startVal = 0.0f; - float endVal = 1.0f; - float duration = 1.0f; - BezierEasing easing = BezierEasing::linear; - }; void layout(const Clay_ElementId& id, const std::function& layout, const Callbacks& c = {}); - virtual void update() override; - float get_float_animation(const char* animationID, const FloatAnimation& animation); - void animation_trigger(const char* animationID); - void animation_trigger_reverse(const char* animationID); virtual void input_mouse_button_callback(const InputManager::MouseButtonCallbackArgs& button) override; virtual void input_mouse_motion_callback(const InputManager::MouseMotionCallbackArgs& motion) override; virtual void input_mouse_wheel_callback(const InputManager::MouseWheelCallbackArgs& wheel) override; virtual void input_key_callback(const InputManager::KeyCallbackArgs& key) override; private: - struct InternalFloatAnimation { - enum class State { - RUN, - RUN_REVERSE, - DONE - } state = State::DONE; - float currentTime = 0.0f; - FloatAnimation anim; - }; - std::unordered_map animations; Callbacks c; }; diff --git a/src/GUIStuff/GUIFloatAnimation.cpp b/src/GUIStuff/GUIFloatAnimation.cpp new file mode 100644 index 0000000..60f2ec2 --- /dev/null +++ b/src/GUIStuff/GUIFloatAnimation.cpp @@ -0,0 +1,68 @@ +#include "GUIFloatAnimation.hpp" +#include "GUIManager.hpp" + +namespace GUIStuff { + +GUIFloatAnimation::GUIFloatAnimation(const GUIFloatAnimationData& animData): + anim(animData), + animVal(animData.startVal) +{} + +bool GUIFloatAnimation::is_at_start() { + return state == State::DONE && currentTime == 0.0f; +} + +bool GUIFloatAnimation::is_at_end() { + return state == State::DONE && currentTime == anim.duration; +} + +bool GUIFloatAnimation::is_done() { + return state == State::DONE; +} + +void GUIFloatAnimation::calculate_anim_val() { + animVal = std::lerp(anim.startVal, anim.endVal, anim.easing(currentTime / anim.duration)); +} + +void GUIFloatAnimation::update(GUIManager& gui) { + switch(state) { + case State::RUN: { + currentTime += gui.io.deltaTime; + if(currentTime >= anim.duration) { + currentTime = anim.duration; + state = State::DONE; + } + calculate_anim_val(); + gui.set_to_layout(); + break; + } + case State::RUN_REVERSE: { + currentTime -= gui.io.deltaTime; + if(currentTime <= 0.0f) { + currentTime = 0.0f; + state = State::DONE; + } + calculate_anim_val(); + gui.set_to_layout(); + break; + } + case State::DONE: + break; + } +} + +float GUIFloatAnimation::get_val() const { + return animVal; +} + +void GUIFloatAnimation::animation_trigger() { + if(currentTime < anim.duration) + state = State::RUN; +} + +void GUIFloatAnimation::animation_trigger_reverse() { + if(currentTime > 0.0f) + state = State::RUN_REVERSE; +} + +} diff --git a/src/GUIStuff/GUIFloatAnimation.hpp b/src/GUIStuff/GUIFloatAnimation.hpp new file mode 100644 index 0000000..68a9cab --- /dev/null +++ b/src/GUIStuff/GUIFloatAnimation.hpp @@ -0,0 +1,40 @@ +#pragma once +#include + +namespace GUIStuff { + +class GUIManager; + +struct GUIFloatAnimationData { + float startVal = 0.0f; + float endVal = 1.0f; + float duration = 1.0f; + BezierEasing easing = BezierEasing::linear; +}; + +class GUIFloatAnimation { + public: + GUIFloatAnimation(const GUIFloatAnimationData& animData); + void update(GUIManager& gui); + void animation_trigger(); + void animation_trigger_reverse(); + float get_val() const; + + bool is_at_start(); + bool is_at_end(); + bool is_done(); + + bool isUsedThisFrame; + private: + void calculate_anim_val(); + enum class State { + RUN, + RUN_REVERSE, + DONE + } state = State::DONE; + float currentTime = 0.0f; + GUIFloatAnimationData anim; + float animVal; +}; + +} diff --git a/src/GUIStuff/GUIManager.cpp b/src/GUIStuff/GUIManager.cpp index ac4a49d..f02f97b 100644 --- a/src/GUIStuff/GUIManager.cpp +++ b/src/GUIStuff/GUIManager.cpp @@ -506,6 +506,19 @@ void GUIManager::update_invalidated_draw_area_from_layout() { void GUIManager::update() { for(ElementContainer* e : orderedElements) e->elem->update(); + for(auto& [id, animation] : animations) + animation.update(*this); +} + +GUIFloatAnimation* GUIManager::float_animation(const char* animationID, const GUIFloatAnimationData& animation) { + GUIFloatAnimation* toRet = nullptr; + new_id(animationID, [&] { + auto [it, placed] = animations.emplace(idStack, animation); + GUIFloatAnimation& actualInternalAnim = it->second; + actualInternalAnim.isUsedThisFrame = true; + toRet = &actualInternalAnim; + }); + return toRet; } void GUIManager::update_window(const Vector2f& windowPos, const Vector2f& windowSize, float guiScaleMultiplier) { @@ -539,6 +552,8 @@ void GUIManager::layout() { void GUIManager::layout_begin() { for(auto& [id, e] : elements) e.isUsedThisFrame = false; + for(auto& [id, a] : animations) + a.isUsedThisFrame = false; Clay_SetLayoutDimensions(Clay_Dimensions(io.windowSize.x(), io.windowSize.y())); Clay_SetCurrentContext(clayInstance); @@ -557,6 +572,9 @@ void GUIManager::layout_end() { std::erase_if(elements, [](auto& p) { return !p.second.isUsedThisFrame; }); + std::erase_if(animations, [](auto& p) { + return !p.second.isUsedThisFrame; + }); std::stable_sort(orderedElements.begin(), orderedElements.end(), [](ElementContainer* a, ElementContainer* b) { return b->elem->zIndex < a->elem->zIndex; // Order from highest to lowest zIndex }); diff --git a/src/GUIStuff/GUIManager.hpp b/src/GUIStuff/GUIManager.hpp index 88c5af5..04c862e 100644 --- a/src/GUIStuff/GUIManager.hpp +++ b/src/GUIStuff/GUIManager.hpp @@ -4,6 +4,7 @@ #include "GUIManagerID.hpp" #include #include "Elements/Element.hpp" +#include "GUIFloatAnimation.hpp" using namespace Eigen; @@ -22,6 +23,8 @@ class GUIManager { void set_to_layout(); void layout_if_necessary(); + GUIFloatAnimation* float_animation(const char* animationID, const GUIFloatAnimationData& animation); + void update(); void update_window(const Vector2f& windowPos, const Vector2f& windowSize, float guiScaleMultiplier); @@ -101,6 +104,8 @@ class GUIManager { bool cursor_obstructed() const; private: + std::unordered_map animations; + void layout(); void layout_begin(); void layout_end(); diff --git a/src/Screens/FileSelectScreen.cpp b/src/Screens/FileSelectScreen.cpp index c344128..2bee6dc 100644 --- a/src/Screens/FileSelectScreen.cpp +++ b/src/Screens/FileSelectScreen.cpp @@ -116,54 +116,60 @@ void FileSelectScreen::main_display() { .layoutDirection = CLAY_LEFT_TO_RIGHT }, }) { - if(mainMenuOpen) - main_menu(); - if(!(mainMenuOpen && main_menu_fills_screen())) { + mainMenuOpenAnim = gui.float_animation("main menu animation", { + .duration = 0.1f, + .easing = BezierEasing(0.36f, 0.0f, 0.64f, 1.0f) + }); + actionBarOpenAnim = gui.float_animation("action bar animation", { + .duration = 0.1f, + .easing = BezierEasing(0.36f, 0.0f, 0.64f, 1.0f) + }); + main_menu(); + CLAY_AUTO_ID({ + .layout = { + .sizing = {.width = CLAY_SIZING_FIXED(gui.io.windowSize.x()), .height = CLAY_SIZING_GROW(0)}, + .childAlignment = { .x = CLAY_ALIGN_X_CENTER, .y = CLAY_ALIGN_Y_TOP }, + .layoutDirection = CLAY_TOP_TO_BOTTOM, + }, + }) { CLAY_AUTO_ID({ .layout = { - .sizing = {.width = CLAY_SIZING_FIXED(gui.io.windowSize.x()), .height = CLAY_SIZING_GROW(0)}, + .sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_GROW(0)}, + .padding = CLAY_PADDING_ALL(gui.io.theme->padding1), + .childGap = gui.io.theme->childGap1, .childAlignment = { .x = CLAY_ALIGN_X_CENTER, .y = CLAY_ALIGN_Y_TOP }, .layoutDirection = CLAY_TOP_TO_BOTTOM, }, }) { - CLAY_AUTO_ID({ - .layout = { - .sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_GROW(0)}, - .padding = CLAY_PADDING_ALL(gui.io.theme->padding1), - .childGap = gui.io.theme->childGap1, - .childAlignment = { .x = CLAY_ALIGN_X_CENTER, .y = CLAY_ALIGN_Y_TOP }, - .layoutDirection = CLAY_TOP_TO_BOTTOM, - }, - }) { - if(editMode) { - numberOfSelectedEntries = std::count_if(fileList.begin(), fileList.end(), [] (const FileInfo& f) { return f.selected; }); - edit_title_bar(); - } + if(editMode) { + numberOfSelectedEntries = std::count_if(fileList.begin(), fileList.end(), [] (const FileInfo& f) { return f.selected; }); + if(numberOfSelectedEntries == 0) + actionBarOpenAnim->animation_trigger_reverse(); else - title_bar(); - switch(selectedMenu) { - case SelectedMenu::FILES: - file_view(); - break; - case SelectedMenu::TRASH: - file_view(); - break; - case SelectedMenu::SETTINGS: - break; - } - menu_black_box(); + actionBarOpenAnim->animation_trigger(); + edit_title_bar(); } - if(editMode && numberOfSelectedEntries != 0) - edit_action_bar(); + else { + actionBarOpenAnim->animation_trigger_reverse(); + title_bar(); + } + switch(selectedMenu) { + case SelectedMenu::FILES: + file_view(); + break; + case SelectedMenu::TRASH: + file_view(); + break; + case SelectedMenu::SETTINGS: + break; + } + menu_black_box(); } + edit_action_bar(); } } } -bool FileSelectScreen::main_menu_fills_screen() { - return main.g.gui.io.windowSize.x() < MAIN_MENU_SIZE + 50; -} - void FileSelectScreen::edit_action_bar_button(const char* id, const std::string& svgPath, const char* text, const std::function& onClick) { auto& gui = main.g.gui; CLAY_AUTO_ID({ @@ -275,41 +281,45 @@ void FileSelectScreen::delete_selected_files_in_trash() { void FileSelectScreen::edit_action_bar() { auto& gui = main.g.gui; - gui.element("edit action bar", [&](LayoutElement*, const Clay_ElementId& lId) { - CLAY(lId, { - .layout = { - .sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_FIT(0)}, - .childAlignment = { .x = CLAY_ALIGN_X_CENTER, .y = CLAY_ALIGN_Y_CENTER }, - .layoutDirection = CLAY_LEFT_TO_RIGHT, - }, - .backgroundColor = convert_vec4(gui.io.theme->backColor1), - }) { - if(selectedMenu == SelectedMenu::FILES) { - edit_action_bar_button("trash", "data/icons/trash.svg", "Trash", [&] { - move_selected_files(savePath, trashPath, TrashMoveType::MOVE_TO_TRASH); - update_file_list(fileList, savePath, false); - editMode = false; - }); - edit_action_bar_button("duplicate", "data/icons/RemixIcon/file-copy-line.svg", "Duplicate", [&] { - duplicate_selected_files(savePath); - update_file_list(fileList, savePath, false); - editMode = false; - }); + if(actionBarOpenAnim->get_val()) { + gui.element("edit action bar", [&](LayoutElement*, const Clay_ElementId& lId) { + CLAY(lId, { + .layout = { + .sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_FIXED(50 * actionBarOpenAnim->get_val())}, + .childAlignment = { .x = CLAY_ALIGN_X_CENTER, .y = CLAY_ALIGN_Y_CENTER }, + .layoutDirection = CLAY_LEFT_TO_RIGHT, + }, + .backgroundColor = convert_vec4(gui.io.theme->backColor1), + }) { + if(actionBarOpenAnim->is_at_end()) { + if(selectedMenu == SelectedMenu::FILES) { + edit_action_bar_button("trash", "data/icons/trash.svg", "Trash", [&] { + move_selected_files(savePath, trashPath, TrashMoveType::MOVE_TO_TRASH); + update_file_list(fileList, savePath, false); + editMode = false; + }); + edit_action_bar_button("duplicate", "data/icons/RemixIcon/file-copy-line.svg", "Duplicate", [&] { + duplicate_selected_files(savePath); + update_file_list(fileList, savePath, false); + editMode = false; + }); + } + else if(selectedMenu == SelectedMenu::TRASH) { + edit_action_bar_button("restore", "data/icons/RemixIcon/refresh-line.svg", "Restore", [&] { + move_selected_files(trashPath, savePath, TrashMoveType::MOVE_OUT_OF_TRASH); + update_file_list(fileList, trashPath, true); + editMode = false; + }); + edit_action_bar_button("delete", "data/icons/trash.svg", "Delete", [&] { + delete_selected_files_in_trash(); + update_file_list(fileList, trashPath, true); + editMode = false; + }); + } + } } - else if(selectedMenu == SelectedMenu::TRASH) { - edit_action_bar_button("restore", "data/icons/RemixIcon/refresh-line.svg", "Restore", [&] { - move_selected_files(trashPath, savePath, TrashMoveType::MOVE_OUT_OF_TRASH); - update_file_list(fileList, trashPath, true); - editMode = false; - }); - edit_action_bar_button("delete", "data/icons/trash.svg", "Delete", [&] { - delete_selected_files_in_trash(); - update_file_list(fileList, trashPath, true); - editMode = false; - }); - } - } - }); + }); + } } void FileSelectScreen::edit_title_bar() { @@ -345,7 +355,7 @@ void FileSelectScreen::title_bar() { svg_icon_button(gui, "main settings button", "data/icons/menu.svg", { .drawType = SelectableButton::DrawType::TRANSPARENT_ALL, .onClick = [&] { - mainMenuOpen = true; + mainMenuOpenAnim->animation_trigger(); } }); switch(selectedMenu) { @@ -446,32 +456,36 @@ void FileSelectScreen::icon_text_transparent_option_selected_button(const char* } void FileSelectScreen::main_menu() { - auto& gui = main.g.gui; - gui.element("main menu popup", [&] (LayoutElement*, const Clay_ElementId& lId) { - CLAY(lId, { - .layout = { - .sizing = {.width = CLAY_SIZING_GROW(static_cast(main_menu_fills_screen() ? 0 : 300)), .height = CLAY_SIZING_GROW(0)}, - .padding = CLAY_PADDING_ALL(gui.io.theme->padding1), - .childGap = 0, - .layoutDirection = CLAY_TOP_TO_BOTTOM, - }, - }) { - icon_text_transparent_option_selected_button("Files", "data/icons/folder.svg", "Files", selectedMenu == SelectedMenu::FILES, [&] { - selectedMenu = SelectedMenu::FILES; - update_file_list(fileList, savePath, false); - mainMenuOpen = false; - }); - icon_text_transparent_option_selected_button("Trash", "data/icons/trash.svg", "Trash", selectedMenu == SelectedMenu::TRASH, [&] { - selectedMenu = SelectedMenu::TRASH; - update_file_list(fileList, trashPath, true); - mainMenuOpen = false; - }); - icon_text_transparent_option_selected_button("Settings", "data/icons/RemixIcon/settings-3-line.svg", "Settings", selectedMenu == SelectedMenu::SETTINGS, [&] { - selectedMenu = SelectedMenu::SETTINGS; - mainMenuOpen = false; - }); - } - }); + if(!mainMenuOpenAnim->is_at_start()) { + auto& gui = main.g.gui; + gui.element("main menu popup", [&] (LayoutElement*, const Clay_ElementId& lId) { + CLAY(lId, { + .layout = { + .sizing = {.width = CLAY_SIZING_FIXED(300.0f * mainMenuOpenAnim->get_val()), .height = CLAY_SIZING_GROW(0)}, + .padding = CLAY_PADDING_ALL(gui.io.theme->padding1), + .childGap = 0, + .layoutDirection = CLAY_TOP_TO_BOTTOM, + }, + }) { + if(mainMenuOpenAnim->is_at_end()) { + icon_text_transparent_option_selected_button("Files", "data/icons/folder.svg", "Files", selectedMenu == SelectedMenu::FILES, [&] { + selectedMenu = SelectedMenu::FILES; + update_file_list(fileList, savePath, false); + mainMenuOpenAnim->animation_trigger_reverse(); + }); + icon_text_transparent_option_selected_button("Trash", "data/icons/trash.svg", "Trash", selectedMenu == SelectedMenu::TRASH, [&] { + selectedMenu = SelectedMenu::TRASH; + update_file_list(fileList, trashPath, true); + mainMenuOpenAnim->animation_trigger_reverse(); + }); + icon_text_transparent_option_selected_button("Settings", "data/icons/RemixIcon/settings-3-line.svg", "Settings", selectedMenu == SelectedMenu::SETTINGS, [&] { + selectedMenu = SelectedMenu::SETTINGS; + mainMenuOpenAnim->animation_trigger_reverse(); + }); + } + } + }); + } } void FileSelectScreen::file_view() { @@ -593,14 +607,14 @@ void FileSelectScreen::file_view() { void FileSelectScreen::menu_black_box() { auto& gui = main.g.gui; - if(mainMenuOpen) { + if(!mainMenuOpenAnim->is_at_start()) { gui.set_z_index(gui.get_z_index() + 1, [&] { gui.element("file list disable fill", [&] (LayoutElement* l, const Clay_ElementId& lId) { CLAY(lId, { .layout = { .sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_GROW(0)}, }, - .backgroundColor = {0.0f, 0.0f, 0.0f, 0.6f}, + .backgroundColor = {0.0f, 0.0f, 0.0f, 0.6f * mainMenuOpenAnim->get_val()}, .floating = { .zIndex = gui.get_z_index(), .attachPoints = { @@ -614,7 +628,7 @@ void FileSelectScreen::menu_black_box() { }, LayoutElement::Callbacks{ .mouseButton = [&] (LayoutElement* l, const InputManager::MouseButtonCallbackArgs& m) { if(m.down && m.button == InputManager::MouseButton::LEFT && l->mouseHovering) { - mainMenuOpen = false; + mainMenuOpenAnim->animation_trigger_reverse(); gui.set_to_layout(); } } diff --git a/src/Screens/FileSelectScreen.hpp b/src/Screens/FileSelectScreen.hpp index 7b3608e..b8d0cec 100644 --- a/src/Screens/FileSelectScreen.hpp +++ b/src/Screens/FileSelectScreen.hpp @@ -1,7 +1,7 @@ #pragma once #include "Screen.hpp" #include "nlohmann/json.hpp" - +#include "../GUIStuff/GUIManager.hpp" class FileSelectScreen : public Screen { public: @@ -37,9 +37,11 @@ class FileSelectScreen : public Screen { TrashInfo trashInfo; std::vector fileList; + GUIStuff::GUIFloatAnimation* mainMenuOpenAnim = nullptr; + GUIStuff::GUIFloatAnimation* actionBarOpenAnim = nullptr; + void main_display(); void main_menu(); - bool main_menu_fills_screen(); void file_view(); void file_view_edit(); void menu_black_box(); @@ -81,6 +83,4 @@ class FileSelectScreen : public Screen { void start_edit_mode(); size_t numberOfSelectedEntries; bool editMode = false; - - bool mainMenuOpen = false; };