Added animations to file selector

This commit is contained in:
Yousef Khadadeh 2026-04-24 11:07:15 +03:00
parent 4e17d6e40c
commit 2d039dc47b
10 changed files with 250 additions and 178 deletions

View file

@ -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"

View file

@ -7,7 +7,7 @@
#include "BezierEasing.hpp"
#include <cmath>
BezierEasing BezierEasing::linear{0.0, 1.0, 0.0, 1.0};
BezierEasing BezierEasing::linear{0.0, 0.0, 1.0, 1.0};
BezierEasing::BezierEasing() {}

View file

@ -11,59 +11,6 @@ void LayoutElement::layout(const Clay_ElementId& id, const std::function<void(La
layout(this, id);
}
void LayoutElement::update() {
for(auto& [id, animation] : animations) {
switch(animation.state) {
case InternalFloatAnimation::State::RUN: {
animation.currentTime += gui.io.deltaTime;
if(animation.currentTime >= 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);
}

View file

@ -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<void(LayoutElement* l, const InputManager::MouseWheelCallbackArgs& wheel)> mouseWheel;
std::function<void(LayoutElement* l, const InputManager::KeyCallbackArgs& key)> 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<void(LayoutElement*, const Clay_ElementId&)>& 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<const char*, InternalFloatAnimation> animations;
Callbacks c;
};

View file

@ -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;
}
}

View file

@ -0,0 +1,40 @@
#pragma once
#include <Helpers/BezierEasing.hpp>
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;
};
}

View file

@ -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
});

View file

@ -4,6 +4,7 @@
#include "GUIManagerID.hpp"
#include <clay.h>
#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<GUIManagerIDStack, GUIFloatAnimation> animations;
void layout();
void layout_begin();
void layout_end();

View file

@ -116,9 +116,15 @@ void FileSelectScreen::main_display() {
.layoutDirection = CLAY_LEFT_TO_RIGHT
},
}) {
if(mainMenuOpen)
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();
if(!(mainMenuOpen && main_menu_fills_screen())) {
CLAY_AUTO_ID({
.layout = {
.sizing = {.width = CLAY_SIZING_FIXED(gui.io.windowSize.x()), .height = CLAY_SIZING_GROW(0)},
@ -137,10 +143,16 @@ void FileSelectScreen::main_display() {
}) {
if(editMode) {
numberOfSelectedEntries = std::count_if(fileList.begin(), fileList.end(), [] (const FileInfo& f) { return f.selected; });
if(numberOfSelectedEntries == 0)
actionBarOpenAnim->animation_trigger_reverse();
else
actionBarOpenAnim->animation_trigger();
edit_title_bar();
}
else
else {
actionBarOpenAnim->animation_trigger_reverse();
title_bar();
}
switch(selectedMenu) {
case SelectedMenu::FILES:
file_view();
@ -153,16 +165,10 @@ void FileSelectScreen::main_display() {
}
menu_black_box();
}
if(editMode && numberOfSelectedEntries != 0)
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<void()>& onClick) {
auto& gui = main.g.gui;
@ -275,15 +281,17 @@ void FileSelectScreen::delete_selected_files_in_trash() {
void FileSelectScreen::edit_action_bar() {
auto& gui = main.g.gui;
if(actionBarOpenAnim->get_val()) {
gui.element<LayoutElement>("edit action bar", [&](LayoutElement*, const Clay_ElementId& lId) {
CLAY(lId, {
.layout = {
.sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_FIT(0)},
.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<Clay_Color>(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);
@ -309,8 +317,10 @@ void FileSelectScreen::edit_action_bar() {
});
}
}
}
});
}
}
void FileSelectScreen::edit_title_bar() {
auto& gui = main.g.gui;
@ -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,33 +456,37 @@ void FileSelectScreen::icon_text_transparent_option_selected_button(const char*
}
void FileSelectScreen::main_menu() {
if(!mainMenuOpenAnim->is_at_start()) {
auto& gui = main.g.gui;
gui.element<LayoutElement>("main menu popup", [&] (LayoutElement*, const Clay_ElementId& lId) {
CLAY(lId, {
.layout = {
.sizing = {.width = CLAY_SIZING_GROW(static_cast<float>(main_menu_fills_screen() ? 0 : 300)), .height = CLAY_SIZING_GROW(0)},
.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);
mainMenuOpen = 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);
mainMenuOpen = false;
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;
mainMenuOpen = false;
mainMenuOpenAnim->animation_trigger_reverse();
});
}
}
});
}
}
void FileSelectScreen::file_view() {
auto& gui = main.g.gui;
@ -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<LayoutElement>("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();
}
}

View file

@ -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<FileInfo> 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;
};