Big refactor, shuffling a lot of files around
This commit is contained in:
50
src/imgui/dockspace.cpp
Normal file
50
src/imgui/dockspace.cpp
Normal file
@@ -0,0 +1,50 @@
|
||||
#include "dockspace.h"
|
||||
|
||||
namespace anm2ed::imgui
|
||||
{
|
||||
void Dockspace::tick(Manager& manager, Settings& settings)
|
||||
{
|
||||
if (auto document = manager.get(); document)
|
||||
if (settings.windowIsAnimationPreview) animationPreview.tick(manager, *document, settings);
|
||||
}
|
||||
|
||||
void Dockspace::update(Taskbar& taskbar, Documents& documents, Manager& manager, Settings& settings,
|
||||
Resources& resources, Dialog& dialog, Clipboard& clipboard)
|
||||
{
|
||||
|
||||
auto viewport = ImGui::GetMainViewport();
|
||||
auto windowHeight = viewport->Size.y - taskbar.height - documents.height;
|
||||
|
||||
ImGui::SetNextWindowViewport(viewport->ID);
|
||||
ImGui::SetNextWindowPos(ImVec2(viewport->Pos.x, viewport->Pos.y + taskbar.height + documents.height));
|
||||
ImGui::SetNextWindowSize(ImVec2(viewport->Size.x, windowHeight));
|
||||
|
||||
if (ImGui::Begin("##DockSpace", nullptr,
|
||||
ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize |
|
||||
ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoBringToFrontOnFocus |
|
||||
ImGuiWindowFlags_NoNavFocus))
|
||||
{
|
||||
if (auto document = manager.get(); document)
|
||||
{
|
||||
if (ImGui::DockSpace(ImGui::GetID("##DockSpace"), ImVec2(), ImGuiDockNodeFlags_PassthruCentralNode))
|
||||
{
|
||||
if (settings.windowIsAnimationPreview) animationPreview.update(manager, settings, resources);
|
||||
if (settings.windowIsAnimations) animations.update(manager, settings, resources, clipboard);
|
||||
if (settings.windowIsEvents) events.update(manager, settings, resources, clipboard);
|
||||
if (settings.windowIsFrameProperties) frameProperties.update(manager, settings);
|
||||
if (settings.windowIsLayers) layers.update(manager, settings, resources, clipboard);
|
||||
if (settings.windowIsNulls) nulls.update(manager, settings, resources, clipboard);
|
||||
if (settings.windowIsOnionskin) onionskin.update(settings);
|
||||
if (settings.windowIsSounds) sounds.update(manager, settings, resources, dialog, clipboard);
|
||||
if (settings.windowIsSpritesheetEditor) spritesheetEditor.update(manager, settings, resources);
|
||||
if (settings.windowIsSpritesheets) spritesheets.update(manager, settings, resources, dialog, clipboard);
|
||||
if (settings.windowIsTimeline) timeline.update(manager, settings, resources, clipboard);
|
||||
if (settings.windowIsTools) tools.update(manager, settings, resources);
|
||||
}
|
||||
}
|
||||
else
|
||||
welcome.update(manager, resources, dialog, taskbar, documents);
|
||||
}
|
||||
ImGui::End();
|
||||
}
|
||||
}
|
||||
41
src/imgui/dockspace.h
Normal file
41
src/imgui/dockspace.h
Normal file
@@ -0,0 +1,41 @@
|
||||
#pragma once
|
||||
|
||||
#include "documents.h"
|
||||
#include "taskbar.h"
|
||||
#include "window/animation_preview.h"
|
||||
#include "window/animations.h"
|
||||
#include "window/events.h"
|
||||
#include "window/frame_properties.h"
|
||||
#include "window/layers.h"
|
||||
#include "window/nulls.h"
|
||||
#include "window/onionskin.h"
|
||||
#include "window/sounds.h"
|
||||
#include "window/spritesheet_editor.h"
|
||||
#include "window/spritesheets.h"
|
||||
#include "window/timeline.h"
|
||||
#include "window/tools.h"
|
||||
#include "window/welcome.h"
|
||||
|
||||
namespace anm2ed::imgui
|
||||
{
|
||||
class Dockspace
|
||||
{
|
||||
AnimationPreview animationPreview;
|
||||
Animations animations;
|
||||
Events events;
|
||||
FrameProperties frameProperties;
|
||||
Layers layers;
|
||||
Nulls nulls;
|
||||
Onionskin onionskin;
|
||||
SpritesheetEditor spritesheetEditor;
|
||||
Spritesheets spritesheets;
|
||||
Sounds sounds;
|
||||
Timeline timeline;
|
||||
Tools tools;
|
||||
Welcome welcome;
|
||||
|
||||
public:
|
||||
void tick(Manager&, Settings&);
|
||||
void update(Taskbar&, Documents&, Manager&, Settings&, Resources&, Dialog&, Clipboard&);
|
||||
};
|
||||
}
|
||||
162
src/imgui/documents.cpp
Normal file
162
src/imgui/documents.cpp
Normal file
@@ -0,0 +1,162 @@
|
||||
#include "documents.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "time_.h"
|
||||
|
||||
using namespace anm2ed::resource;
|
||||
using namespace anm2ed::types;
|
||||
using namespace anm2ed::util;
|
||||
|
||||
namespace anm2ed::imgui
|
||||
{
|
||||
void Documents::update(Taskbar& taskbar, Manager& manager, Settings& settings, Resources& resources, bool& isQuitting)
|
||||
{
|
||||
auto viewport = ImGui::GetMainViewport();
|
||||
auto windowHeight = ImGui::GetFrameHeightWithSpacing();
|
||||
|
||||
ImGui::SetNextWindowViewport(viewport->ID);
|
||||
ImGui::SetNextWindowPos(ImVec2(viewport->Pos.x, viewport->Pos.y + taskbar.height));
|
||||
ImGui::SetNextWindowSize(ImVec2(viewport->Size.x, windowHeight));
|
||||
|
||||
for (auto& document : manager.documents)
|
||||
{
|
||||
auto isDirty = document.is_dirty() && document.is_autosave_dirty();
|
||||
document.lastAutosaveTime += ImGui::GetIO().DeltaTime;
|
||||
|
||||
if (isDirty && document.lastAutosaveTime > settings.fileAutosaveTime * time::SECOND_M) manager.autosave(document);
|
||||
}
|
||||
|
||||
if (ImGui::Begin("##Documents", nullptr,
|
||||
ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize |
|
||||
ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoBringToFrontOnFocus |
|
||||
ImGuiWindowFlags_NoNavFocus | ImGuiWindowFlags_NoScrollbar |
|
||||
ImGuiWindowFlags_NoScrollWithMouse))
|
||||
{
|
||||
height = ImGui::GetWindowSize().y;
|
||||
|
||||
if (ImGui::BeginTabBar("Documents Bar", ImGuiTabBarFlags_Reorderable))
|
||||
{
|
||||
auto documentsCount = (int)manager.documents.size();
|
||||
bool closeShortcut = imgui::shortcut(settings.shortcutClose, shortcut::GLOBAL) && !closePopup.is_open();
|
||||
int closeShortcutIndex =
|
||||
closeShortcut && manager.selected >= 0 && manager.selected < documentsCount ? manager.selected : -1;
|
||||
|
||||
std::vector<int> closeIndices{};
|
||||
closeIndices.reserve(documentsCount);
|
||||
|
||||
for (int i = 0; i < documentsCount; ++i)
|
||||
{
|
||||
auto& document = manager.documents[i];
|
||||
auto isDirty = document.is_dirty() || document.isForceDirty;
|
||||
|
||||
if (!closePopup.is_open())
|
||||
{
|
||||
if (isQuitting)
|
||||
document.isOpen = false;
|
||||
else if (i == closeShortcutIndex)
|
||||
document.isOpen = false;
|
||||
}
|
||||
|
||||
if (!closePopup.is_open() && !document.isOpen)
|
||||
{
|
||||
if (isDirty)
|
||||
{
|
||||
closePopup.open();
|
||||
closeDocumentIndex = i;
|
||||
document.isOpen = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
closeIndices.push_back(i);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
auto isRequested = i == manager.pendingSelected;
|
||||
|
||||
auto font = isDirty ? font::ITALICS : font::REGULAR;
|
||||
|
||||
auto string = isDirty ? std::format("[Not Saved] {}", document.filename_get().string())
|
||||
: document.filename_get().string();
|
||||
|
||||
auto label = std::format("{}###Document{}", string, i);
|
||||
|
||||
auto flags = isDirty ? ImGuiTabItemFlags_UnsavedDocument : 0;
|
||||
if (isRequested) flags |= ImGuiTabItemFlags_SetSelected;
|
||||
|
||||
ImGui::PushFont(resources.fonts[font].get(), font::SIZE);
|
||||
if (ImGui::BeginTabItem(label.c_str(), &document.isOpen, flags))
|
||||
{
|
||||
manager.set(i);
|
||||
if (isRequested) manager.pendingSelected = -1;
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
ImGui::PopFont();
|
||||
}
|
||||
|
||||
for (auto it = closeIndices.rbegin(); it != closeIndices.rend(); ++it)
|
||||
{
|
||||
if (closePopup.is_open() && closeDocumentIndex > *it) --closeDocumentIndex;
|
||||
manager.close(*it);
|
||||
}
|
||||
|
||||
ImGui::EndTabBar();
|
||||
}
|
||||
|
||||
closePopup.trigger();
|
||||
|
||||
if (ImGui::BeginPopupModal(closePopup.label, &closePopup.isOpen, ImGuiWindowFlags_NoResize))
|
||||
{
|
||||
if (closeDocumentIndex >= 0 && closeDocumentIndex < (int)manager.documents.size())
|
||||
{
|
||||
auto& closeDocument = manager.documents[closeDocumentIndex];
|
||||
|
||||
ImGui::TextUnformatted(std::format("The document \"{}\" has been modified.\nDo you want to save it?",
|
||||
closeDocument.filename_get().string())
|
||||
.c_str());
|
||||
|
||||
auto widgetSize = imgui::widget_size_with_row_get(3);
|
||||
|
||||
auto close = [&]()
|
||||
{
|
||||
closeDocumentIndex = -1;
|
||||
closePopup.close();
|
||||
};
|
||||
|
||||
if (ImGui::Button("Yes", widgetSize))
|
||||
{
|
||||
manager.save(closeDocumentIndex);
|
||||
manager.close(closeDocumentIndex);
|
||||
close();
|
||||
}
|
||||
|
||||
ImGui::SameLine();
|
||||
|
||||
if (ImGui::Button("No", widgetSize))
|
||||
{
|
||||
manager.close(closeDocumentIndex);
|
||||
close();
|
||||
}
|
||||
|
||||
ImGui::SameLine();
|
||||
|
||||
if (ImGui::Button("Cancel", widgetSize))
|
||||
{
|
||||
isQuitting = false;
|
||||
close();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
closeDocumentIndex = -1;
|
||||
closePopup.close();
|
||||
}
|
||||
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::End();
|
||||
}
|
||||
}
|
||||
21
src/imgui/documents.h
Normal file
21
src/imgui/documents.h
Normal file
@@ -0,0 +1,21 @@
|
||||
#pragma once
|
||||
|
||||
#include "imgui.h"
|
||||
#include "manager.h"
|
||||
#include "resources.h"
|
||||
#include "settings.h"
|
||||
#include "taskbar.h"
|
||||
|
||||
namespace anm2ed::imgui
|
||||
{
|
||||
class Documents
|
||||
{
|
||||
int closeDocumentIndex{-1};
|
||||
imgui::PopupHelper closePopup{imgui::PopupHelper("Close Document", imgui::POPUP_TO_CONTENT)};
|
||||
|
||||
public:
|
||||
float height{};
|
||||
|
||||
void update(Taskbar&, Manager&, Settings&, Resources&, bool&);
|
||||
};
|
||||
}
|
||||
322
src/imgui/imgui_.cpp
Normal file
322
src/imgui/imgui_.cpp
Normal file
@@ -0,0 +1,322 @@
|
||||
#include "imgui_.h"
|
||||
|
||||
#include <imgui/imgui_internal.h>
|
||||
|
||||
#include <set>
|
||||
#include <sstream>
|
||||
#include <unordered_map>
|
||||
|
||||
using namespace anm2ed::types;
|
||||
using namespace glm;
|
||||
|
||||
namespace anm2ed::imgui
|
||||
{
|
||||
int input_text_callback(ImGuiInputTextCallbackData* data)
|
||||
{
|
||||
if (data->EventFlag == ImGuiInputTextFlags_CallbackResize)
|
||||
{
|
||||
auto* string = (std::string*)(data->UserData);
|
||||
string->resize(data->BufTextLen);
|
||||
data->Buf = string->data();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool input_text_string(const char* label, std::string* string, ImGuiInputTextFlags flags)
|
||||
{
|
||||
flags |= ImGuiInputTextFlags_CallbackResize;
|
||||
return ImGui::InputText(label, string->data(), string->capacity() + 1, flags, input_text_callback, string);
|
||||
}
|
||||
|
||||
bool combo_strings(const std::string& label, int* index, std::vector<std::string>& strings)
|
||||
{
|
||||
std::vector<const char*> items{};
|
||||
for (auto& string : strings)
|
||||
items.push_back(string.c_str());
|
||||
return ImGui::Combo(label.c_str(), index, items.data(), (int)items.size());
|
||||
}
|
||||
|
||||
bool combo_strings(const std::string& label, int* index, std::vector<const char*>& strings)
|
||||
{
|
||||
return ImGui::Combo(label.c_str(), index, strings.data(), (int)strings.size());
|
||||
}
|
||||
|
||||
bool input_int_range(const char* label, int& value, int min, int max, int step, int stepFast,
|
||||
ImGuiInputTextFlags flags)
|
||||
{
|
||||
auto isActivated = ImGui::InputInt(label, &value, step, stepFast, flags);
|
||||
value = glm::clamp(value, min, max);
|
||||
return isActivated;
|
||||
}
|
||||
|
||||
bool selectable_input_text(const std::string& label, const std::string& id, std::string& text, bool isSelected,
|
||||
ImGuiSelectableFlags flags, bool* isRenamed)
|
||||
{
|
||||
static std::string editID{};
|
||||
static bool isJustEdit{};
|
||||
const bool isEditing = editID == id;
|
||||
bool isActivated{};
|
||||
|
||||
if (isEditing)
|
||||
{
|
||||
if (isJustEdit)
|
||||
{
|
||||
ImGui::SetKeyboardFocusHere();
|
||||
isJustEdit = false;
|
||||
}
|
||||
|
||||
ImGui::SetNextItemWidth(-FLT_MIN);
|
||||
if (input_text_string("##Edit", &text, ImGuiInputTextFlags_EnterReturnsTrue | ImGuiInputTextFlags_AutoSelectAll))
|
||||
{
|
||||
editID.clear();
|
||||
isActivated = true;
|
||||
if (isRenamed) *isRenamed = true;
|
||||
}
|
||||
if (ImGui::IsItemDeactivatedAfterEdit() || ImGui::IsKeyPressed(ImGuiKey_Escape)) editID.clear();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (ImGui::Selectable(label.c_str(), isSelected, flags)) isActivated = true;
|
||||
|
||||
if ((ImGui::IsWindowFocused() && ImGui::IsKeyPressed(ImGuiKey_F2) && isSelected) ||
|
||||
(ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)))
|
||||
{
|
||||
editID = id;
|
||||
isJustEdit = true;
|
||||
}
|
||||
}
|
||||
|
||||
return isActivated;
|
||||
}
|
||||
|
||||
void set_item_tooltip_shortcut(const char* tooltip, const std::string& shortcut)
|
||||
{
|
||||
ImGui::SetItemTooltip("%s\n(Shortcut: %s)", tooltip, shortcut.c_str());
|
||||
}
|
||||
|
||||
void external_storage_set(ImGuiSelectionExternalStorage* self, int id, bool isSelected)
|
||||
{
|
||||
auto* set = (std::set<int>*)self->UserData;
|
||||
if (isSelected)
|
||||
set->insert(id);
|
||||
else
|
||||
set->erase(id);
|
||||
};
|
||||
|
||||
std::string chord_to_string(ImGuiKeyChord chord)
|
||||
{
|
||||
std::string result;
|
||||
|
||||
if (chord & ImGuiMod_Ctrl) result += "Ctrl+";
|
||||
if (chord & ImGuiMod_Shift) result += "Shift+";
|
||||
if (chord & ImGuiMod_Alt) result += "Alt+";
|
||||
if (chord & ImGuiMod_Super) result += "Super+";
|
||||
|
||||
if (auto key = (ImGuiKey)(chord & ~ImGuiMod_Mask_); key != ImGuiKey_None)
|
||||
{
|
||||
if (const char* name = ImGui::GetKeyName(key); name && *name)
|
||||
result += name;
|
||||
else
|
||||
result += "Unknown";
|
||||
}
|
||||
|
||||
if (!result.empty() && result.back() == '+') result.pop_back();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
ImGuiKeyChord string_to_chord(const std::string& string)
|
||||
{
|
||||
ImGuiKeyChord chord = 0;
|
||||
ImGuiKey baseKey = ImGuiKey_None;
|
||||
|
||||
std::stringstream ss(string);
|
||||
std::string token;
|
||||
while (std::getline(ss, token, '+'))
|
||||
{
|
||||
token.erase(0, token.find_first_not_of(" \t\r\n"));
|
||||
token.erase(token.find_last_not_of(" \t\r\n") + 1);
|
||||
|
||||
if (token.empty()) continue;
|
||||
|
||||
if (auto it = MOD_MAP.find(token); it != MOD_MAP.end())
|
||||
chord |= it->second;
|
||||
else if (baseKey == ImGuiKey_None)
|
||||
if (auto it2 = KEY_MAP.find(token); it2 != KEY_MAP.end()) baseKey = it2->second;
|
||||
}
|
||||
|
||||
if (baseKey != ImGuiKey_None) chord |= baseKey;
|
||||
|
||||
return chord;
|
||||
}
|
||||
|
||||
float row_widget_width_get(int count, float width)
|
||||
{
|
||||
return (width - (ImGui::GetStyle().ItemSpacing.x * (float)(count - 1))) / (float)count;
|
||||
}
|
||||
|
||||
ImVec2 widget_size_with_row_get(int count, float width)
|
||||
{
|
||||
return ImVec2(row_widget_width_get(count, width), 0);
|
||||
}
|
||||
|
||||
float footer_height_get(int itemCount)
|
||||
{
|
||||
return ImGui::GetTextLineHeightWithSpacing() * itemCount + ImGui::GetStyle().WindowPadding.y +
|
||||
ImGui::GetStyle().ItemSpacing.y * (itemCount);
|
||||
}
|
||||
|
||||
ImVec2 footer_size_get(int itemCount)
|
||||
{
|
||||
return ImVec2(ImGui::GetContentRegionAvail().x, footer_height_get(itemCount));
|
||||
}
|
||||
|
||||
ImVec2 size_without_footer_get(int rowCount)
|
||||
{
|
||||
return ImVec2(ImGui::GetContentRegionAvail().x, ImGui::GetContentRegionAvail().y - footer_height_get(rowCount));
|
||||
}
|
||||
|
||||
ImVec2 child_size_get(int rowCount)
|
||||
{
|
||||
return ImVec2(ImGui::GetContentRegionAvail().x,
|
||||
(ImGui::GetFrameHeightWithSpacing() * rowCount) + (ImGui::GetStyle().WindowPadding.y * 2.0f));
|
||||
}
|
||||
|
||||
ImVec2 icon_size_get()
|
||||
{
|
||||
return ImVec2(ImGui::GetTextLineHeightWithSpacing(), ImGui::GetTextLineHeightWithSpacing());
|
||||
}
|
||||
|
||||
bool chord_held(ImGuiKeyChord chord)
|
||||
{
|
||||
auto& io = ImGui::GetIO();
|
||||
|
||||
for (constexpr ImGuiKey mods[] = {ImGuiMod_Ctrl, ImGuiMod_Shift, ImGuiMod_Alt, ImGuiMod_Super}; ImGuiKey mod : mods)
|
||||
{
|
||||
bool required = (chord & mod) != 0;
|
||||
if (bool held = io.KeyMods & mod; required && !held) return false;
|
||||
}
|
||||
|
||||
auto main_key = (ImGuiKey)(chord & ~ImGuiMod_Mask_);
|
||||
if (main_key == ImGuiKey_None) return false;
|
||||
|
||||
return ImGui::IsKeyDown(main_key);
|
||||
}
|
||||
|
||||
bool chord_repeating(ImGuiKeyChord chord, float delay, float rate)
|
||||
{
|
||||
struct State
|
||||
{
|
||||
float timeHeld = 0.f;
|
||||
float nextRepeat = 0.f;
|
||||
};
|
||||
static std::unordered_map<ImGuiKeyChord, State> stateMap;
|
||||
|
||||
auto& io = ImGui::GetIO();
|
||||
auto& state = stateMap[chord];
|
||||
|
||||
if (chord_held(chord))
|
||||
{
|
||||
state.timeHeld += io.DeltaTime;
|
||||
|
||||
if (state.timeHeld <= io.DeltaTime)
|
||||
{
|
||||
state.nextRepeat = delay;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (state.timeHeld >= state.nextRepeat)
|
||||
{
|
||||
state.nextRepeat += rate;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
state.timeHeld = 0.f;
|
||||
state.nextRepeat = 0.f;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool shortcut(std::string string, shortcut::Type type)
|
||||
{
|
||||
if (ImGui::GetTopMostPopupModal() != nullptr) return false;
|
||||
int flags = type == shortcut::GLOBAL || type == shortcut::GLOBAL_SET ? ImGuiInputFlags_RouteGlobal
|
||||
: ImGuiInputFlags_RouteFocused;
|
||||
if (type == shortcut::GLOBAL_SET || type == shortcut::FOCUSED_SET)
|
||||
{
|
||||
ImGui::SetNextItemShortcut(string_to_chord(string), flags);
|
||||
return false;
|
||||
}
|
||||
|
||||
return ImGui::Shortcut(string_to_chord(string), flags);
|
||||
}
|
||||
|
||||
MultiSelectStorage::MultiSelectStorage()
|
||||
{
|
||||
internal.AdapterSetItemSelected = external_storage_set;
|
||||
}
|
||||
|
||||
void MultiSelectStorage::start(size_t size)
|
||||
{
|
||||
internal.UserData = this;
|
||||
|
||||
auto io = ImGui::BeginMultiSelect(ImGuiMultiSelectFlags_ClearOnEscape, this->size(), size);
|
||||
internal.ApplyRequests(io);
|
||||
}
|
||||
|
||||
void MultiSelectStorage::finish()
|
||||
{
|
||||
auto io = ImGui::EndMultiSelect();
|
||||
internal.ApplyRequests(io);
|
||||
}
|
||||
|
||||
PopupHelper::PopupHelper(const char* label, PopupType type, PopupPosition position)
|
||||
{
|
||||
this->label = label;
|
||||
this->type = type;
|
||||
this->position = position;
|
||||
}
|
||||
|
||||
void PopupHelper::open()
|
||||
{
|
||||
isOpen = true;
|
||||
isTriggered = true;
|
||||
isJustOpened = true;
|
||||
}
|
||||
|
||||
bool PopupHelper::is_open()
|
||||
{
|
||||
return isOpen;
|
||||
}
|
||||
|
||||
void PopupHelper::trigger()
|
||||
{
|
||||
if (isTriggered) ImGui::OpenPopup(label);
|
||||
isTriggered = false;
|
||||
|
||||
auto viewport = ImGui::GetMainViewport();
|
||||
|
||||
if (position == POPUP_CENTER)
|
||||
ImGui::SetNextWindowPos(viewport->GetCenter(), ImGuiCond_None, to_imvec2(vec2(0.5f)));
|
||||
else
|
||||
ImGui::SetNextWindowPos(ImGui::GetItemRectMin(), ImGuiCond_None);
|
||||
|
||||
if (POPUP_IS_HEIGHT_SET[type])
|
||||
ImGui::SetNextWindowSize(to_imvec2(to_vec2(viewport->Size) * POPUP_MULTIPLIERS[type]));
|
||||
else
|
||||
ImGui::SetNextWindowSize(ImVec2(viewport->Size.x * POPUP_MULTIPLIERS[type], 0));
|
||||
}
|
||||
|
||||
void PopupHelper::end()
|
||||
{
|
||||
isJustOpened = false;
|
||||
}
|
||||
|
||||
void PopupHelper::close()
|
||||
{
|
||||
isOpen = false;
|
||||
}
|
||||
}
|
||||
216
src/imgui/imgui_.h
Normal file
216
src/imgui/imgui_.h
Normal file
@@ -0,0 +1,216 @@
|
||||
#pragma once
|
||||
|
||||
#include <imgui/imgui.h>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include "types.h"
|
||||
|
||||
namespace anm2ed::imgui
|
||||
{
|
||||
constexpr auto DRAG_SPEED = 1.0f;
|
||||
constexpr auto STEP = 1.0f;
|
||||
constexpr auto STEP_FAST = 5.0f;
|
||||
|
||||
#define POPUP_LIST \
|
||||
X(POPUP_SMALL, 0.25f, true) \
|
||||
X(POPUP_NORMAL, 0.5f, true) \
|
||||
X(POPUP_TO_CONTENT, 0.0f, true) \
|
||||
X(POPUP_SMALL_NO_HEIGHT, 0.25f, false) \
|
||||
X(POPUP_NORMAL_NO_HEIGHT, 0.5f, false)
|
||||
|
||||
enum PopupType
|
||||
{
|
||||
#define X(name, multiplier, isHeightSet) name,
|
||||
POPUP_LIST
|
||||
#undef X
|
||||
};
|
||||
|
||||
enum PopupPosition
|
||||
{
|
||||
POPUP_CENTER,
|
||||
POPUP_BY_ITEM
|
||||
};
|
||||
|
||||
constexpr float POPUP_MULTIPLIERS[] = {
|
||||
#define X(name, multiplier, isHeightSet) multiplier,
|
||||
POPUP_LIST
|
||||
#undef X
|
||||
};
|
||||
|
||||
constexpr bool POPUP_IS_HEIGHT_SET[] = {
|
||||
#define X(name, multiplier, isHeightSet) isHeightSet,
|
||||
POPUP_LIST
|
||||
#undef X
|
||||
};
|
||||
|
||||
const std::unordered_map<std::string, ImGuiKey> KEY_MAP = {{"A", ImGuiKey_A},
|
||||
{"B", ImGuiKey_B},
|
||||
{"C", ImGuiKey_C},
|
||||
{"D", ImGuiKey_D},
|
||||
{"E", ImGuiKey_E},
|
||||
{"F", ImGuiKey_F},
|
||||
{"G", ImGuiKey_G},
|
||||
{"H", ImGuiKey_H},
|
||||
{"I", ImGuiKey_I},
|
||||
{"J", ImGuiKey_J},
|
||||
{"K", ImGuiKey_K},
|
||||
{"L", ImGuiKey_L},
|
||||
{"M", ImGuiKey_M},
|
||||
{"N", ImGuiKey_N},
|
||||
{"O", ImGuiKey_O},
|
||||
{"P", ImGuiKey_P},
|
||||
{"Q", ImGuiKey_Q},
|
||||
{"R", ImGuiKey_R},
|
||||
{"S", ImGuiKey_S},
|
||||
{"T", ImGuiKey_T},
|
||||
{"U", ImGuiKey_U},
|
||||
{"V", ImGuiKey_V},
|
||||
{"W", ImGuiKey_W},
|
||||
{"X", ImGuiKey_X},
|
||||
{"Y", ImGuiKey_Y},
|
||||
{"Z", ImGuiKey_Z},
|
||||
|
||||
{"0", ImGuiKey_0},
|
||||
{"1", ImGuiKey_1},
|
||||
{"2", ImGuiKey_2},
|
||||
{"3", ImGuiKey_3},
|
||||
{"4", ImGuiKey_4},
|
||||
{"5", ImGuiKey_5},
|
||||
{"6", ImGuiKey_6},
|
||||
{"7", ImGuiKey_7},
|
||||
{"8", ImGuiKey_8},
|
||||
{"9", ImGuiKey_9},
|
||||
|
||||
{"Num0", ImGuiKey_Keypad0},
|
||||
{"Num1", ImGuiKey_Keypad1},
|
||||
{"Num2", ImGuiKey_Keypad2},
|
||||
{"Num3", ImGuiKey_Keypad3},
|
||||
{"Num4", ImGuiKey_Keypad4},
|
||||
{"Num5", ImGuiKey_Keypad5},
|
||||
{"Num6", ImGuiKey_Keypad6},
|
||||
{"Num7", ImGuiKey_Keypad7},
|
||||
{"Num8", ImGuiKey_Keypad8},
|
||||
{"Num9", ImGuiKey_Keypad9},
|
||||
{"NumAdd", ImGuiKey_KeypadAdd},
|
||||
{"NumSubtract", ImGuiKey_KeypadSubtract},
|
||||
{"NumMultiply", ImGuiKey_KeypadMultiply},
|
||||
{"NumDivide", ImGuiKey_KeypadDivide},
|
||||
{"NumEnter", ImGuiKey_KeypadEnter},
|
||||
{"NumDecimal", ImGuiKey_KeypadDecimal},
|
||||
|
||||
{"F1", ImGuiKey_F1},
|
||||
{"F2", ImGuiKey_F2},
|
||||
{"F3", ImGuiKey_F3},
|
||||
{"F4", ImGuiKey_F4},
|
||||
{"F5", ImGuiKey_F5},
|
||||
{"F6", ImGuiKey_F6},
|
||||
{"F7", ImGuiKey_F7},
|
||||
{"F8", ImGuiKey_F8},
|
||||
{"F9", ImGuiKey_F9},
|
||||
{"F10", ImGuiKey_F10},
|
||||
{"F11", ImGuiKey_F11},
|
||||
{"F12", ImGuiKey_F12},
|
||||
|
||||
{"Up", ImGuiKey_UpArrow},
|
||||
{"Down", ImGuiKey_DownArrow},
|
||||
{"Left", ImGuiKey_LeftArrow},
|
||||
{"Right", ImGuiKey_RightArrow},
|
||||
|
||||
{"Space", ImGuiKey_Space},
|
||||
{"Enter", ImGuiKey_Enter},
|
||||
{"Escape", ImGuiKey_Escape},
|
||||
{"Tab", ImGuiKey_Tab},
|
||||
{"Backspace", ImGuiKey_Backspace},
|
||||
{"Delete", ImGuiKey_Delete},
|
||||
{"Insert", ImGuiKey_Insert},
|
||||
{"Home", ImGuiKey_Home},
|
||||
{"End", ImGuiKey_End},
|
||||
{"PageUp", ImGuiKey_PageUp},
|
||||
{"PageDown", ImGuiKey_PageDown},
|
||||
|
||||
{"Minus", ImGuiKey_Minus},
|
||||
{"Equal", ImGuiKey_Equal},
|
||||
{"LeftBracket", ImGuiKey_LeftBracket},
|
||||
{"RightBracket", ImGuiKey_RightBracket},
|
||||
{"Semicolon", ImGuiKey_Semicolon},
|
||||
{"Apostrophe", ImGuiKey_Apostrophe},
|
||||
{"Comma", ImGuiKey_Comma},
|
||||
{"Period", ImGuiKey_Period},
|
||||
{"Slash", ImGuiKey_Slash},
|
||||
{"Backslash", ImGuiKey_Backslash},
|
||||
{"GraveAccent", ImGuiKey_GraveAccent},
|
||||
|
||||
{"MouseLeft", ImGuiKey_MouseLeft},
|
||||
{"MouseRight", ImGuiKey_MouseRight},
|
||||
{"MouseMiddle", ImGuiKey_MouseMiddle},
|
||||
{"MouseX1", ImGuiKey_MouseX1},
|
||||
{"MouseX2", ImGuiKey_MouseX2}};
|
||||
|
||||
const std::unordered_map<std::string, ImGuiKey> MOD_MAP = {
|
||||
{"Ctrl", ImGuiMod_Ctrl},
|
||||
{"Shift", ImGuiMod_Shift},
|
||||
{"Alt", ImGuiMod_Alt},
|
||||
{"Super", ImGuiMod_Super},
|
||||
};
|
||||
|
||||
std::string chord_to_string(ImGuiKeyChord);
|
||||
ImGuiKeyChord string_to_chord(const std::string&);
|
||||
float row_widget_width_get(int, float = ImGui::GetContentRegionAvail().x);
|
||||
ImVec2 widget_size_with_row_get(int, float = ImGui::GetContentRegionAvail().x);
|
||||
float footer_height_get(int = 1);
|
||||
ImVec2 footer_size_get(int = 1);
|
||||
ImVec2 size_without_footer_get(int = 1);
|
||||
ImVec2 child_size_get(int = 1);
|
||||
int input_text_callback(ImGuiInputTextCallbackData*);
|
||||
bool input_text_string(const char*, std::string*, ImGuiInputTextFlags = 0);
|
||||
bool input_int_range(const char*, int&, int, int, int = STEP, int = STEP_FAST, ImGuiInputTextFlags = 0);
|
||||
bool combo_strings(const std::string&, int*, std::vector<std::string>&);
|
||||
bool combo_strings(const std::string&, int*, std::vector<const char*>&);
|
||||
bool selectable_input_text(const std::string&, const std::string&, std::string&, bool = false,
|
||||
ImGuiSelectableFlags = 0, bool* = nullptr);
|
||||
void set_item_tooltip_shortcut(const char*, const std::string& = {});
|
||||
void external_storage_set(ImGuiSelectionExternalStorage*, int, bool);
|
||||
ImVec2 icon_size_get();
|
||||
bool chord_held(ImGuiKeyChord);
|
||||
bool chord_repeating(ImGuiKeyChord, float = 0.125f, float = 0.025f);
|
||||
bool shortcut(std::string, types::shortcut::Type = types::shortcut::FOCUSED_SET);
|
||||
|
||||
class MultiSelectStorage : public std::set<int>
|
||||
{
|
||||
public:
|
||||
ImGuiSelectionExternalStorage internal{};
|
||||
using std::set<int>::set;
|
||||
using std::set<int>::operator=;
|
||||
using std::set<int>::begin;
|
||||
using std::set<int>::rbegin;
|
||||
using std::set<int>::end;
|
||||
using std::set<int>::size;
|
||||
using std::set<int>::insert;
|
||||
using std::set<int>::erase;
|
||||
|
||||
MultiSelectStorage();
|
||||
void start(size_t);
|
||||
void finish();
|
||||
};
|
||||
|
||||
class PopupHelper
|
||||
{
|
||||
public:
|
||||
const char* label{};
|
||||
PopupType type{};
|
||||
PopupPosition position{};
|
||||
bool isOpen{};
|
||||
bool isTriggered{};
|
||||
bool isJustOpened{};
|
||||
|
||||
PopupHelper(const char*, PopupType = POPUP_NORMAL, PopupPosition = POPUP_CENTER);
|
||||
bool is_open();
|
||||
void open();
|
||||
void trigger();
|
||||
void end();
|
||||
void close();
|
||||
};
|
||||
}
|
||||
616
src/imgui/taskbar.cpp
Normal file
616
src/imgui/taskbar.cpp
Normal file
@@ -0,0 +1,616 @@
|
||||
#include "taskbar.h"
|
||||
|
||||
#include <imgui/imgui.h>
|
||||
#include <ranges>
|
||||
|
||||
#include "math_.h"
|
||||
#include "render.h"
|
||||
#include "shader.h"
|
||||
#include "types.h"
|
||||
|
||||
using namespace anm2ed::resource;
|
||||
using namespace anm2ed::types;
|
||||
using namespace anm2ed::canvas;
|
||||
using namespace anm2ed::util;
|
||||
using namespace glm;
|
||||
|
||||
namespace anm2ed::imgui
|
||||
{
|
||||
Taskbar::Taskbar() : generate(vec2())
|
||||
{
|
||||
}
|
||||
|
||||
void Taskbar::update(Manager& manager, Settings& settings, Resources& resources, Dialog& dialog, bool& isQuitting)
|
||||
{
|
||||
auto document = manager.get();
|
||||
|
||||
if (ImGui::BeginMainMenuBar())
|
||||
{
|
||||
height = ImGui::GetWindowSize().y;
|
||||
|
||||
if (ImGui::BeginMenu("File"))
|
||||
{
|
||||
if (ImGui::MenuItem("New", settings.shortcutNew.c_str())) dialog.file_open(dialog::ANM2_NEW);
|
||||
|
||||
if (ImGui::MenuItem("Open", settings.shortcutOpen.c_str())) dialog.file_open(dialog::ANM2_NEW);
|
||||
|
||||
if (manager.recentFiles.empty())
|
||||
{
|
||||
ImGui::BeginDisabled();
|
||||
ImGui::MenuItem("Open Recent");
|
||||
ImGui::EndDisabled();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (ImGui::BeginMenu("Open Recent"))
|
||||
{
|
||||
for (auto [i, file] : std::views::enumerate(manager.recentFiles))
|
||||
{
|
||||
auto label = std::format(FILE_LABEL_FORMAT, file.filename().string(), file.string());
|
||||
|
||||
ImGui::PushID(i);
|
||||
if (ImGui::MenuItem(label.c_str())) manager.open(file);
|
||||
ImGui::PopID();
|
||||
}
|
||||
|
||||
if (!manager.recentFiles.empty())
|
||||
if (ImGui::MenuItem("Clear List")) manager.recent_files_clear();
|
||||
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::BeginDisabled(!document);
|
||||
{
|
||||
if (ImGui::MenuItem("Save", settings.shortcutSave.c_str())) manager.save();
|
||||
if (ImGui::MenuItem("Save As", settings.shortcutSaveAs.c_str())) dialog.file_save(dialog::ANM2_SAVE);
|
||||
if (ImGui::MenuItem("Explore XML Location")) dialog.file_explorer_open(document->directory_get());
|
||||
}
|
||||
ImGui::EndDisabled();
|
||||
|
||||
ImGui::Separator();
|
||||
if (ImGui::MenuItem("Exit", settings.shortcutExit.c_str())) isQuitting = true;
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
if (dialog.is_selected(dialog::ANM2_NEW))
|
||||
{
|
||||
manager.new_(dialog.path);
|
||||
dialog.reset();
|
||||
}
|
||||
|
||||
if (dialog.is_selected(dialog::ANM2_OPEN))
|
||||
{
|
||||
manager.open(dialog.path);
|
||||
dialog.reset();
|
||||
}
|
||||
|
||||
if (dialog.is_selected(dialog::ANM2_SAVE))
|
||||
{
|
||||
manager.save(dialog.path);
|
||||
dialog.reset();
|
||||
}
|
||||
|
||||
if (ImGui::BeginMenu("Wizard"))
|
||||
{
|
||||
auto animation = document ? document->animation_get() : nullptr;
|
||||
auto item = document ? document->item_get() : nullptr;
|
||||
ImGui::BeginDisabled(!item || document->reference.itemType != anm2::LAYER);
|
||||
if (ImGui::MenuItem("Generate Animation From Grid")) generatePopup.open();
|
||||
if (ImGui::MenuItem("Change All Frame Properties")) changePopup.open();
|
||||
ImGui::EndDisabled();
|
||||
ImGui::Separator();
|
||||
ImGui::BeginDisabled(!animation);
|
||||
if (ImGui::MenuItem("Render Animation")) renderPopup.open();
|
||||
ImGui::EndDisabled();
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
|
||||
if (ImGui::BeginMenu("Playback"))
|
||||
{
|
||||
ImGui::MenuItem("Always Loop", nullptr, &settings.playbackIsLoop);
|
||||
ImGui::SetItemTooltip("%s", "Animations will always loop during playback, even if looping isn't set.");
|
||||
|
||||
ImGui::MenuItem("Clamp Playhead", nullptr, &settings.playbackIsClampPlayhead);
|
||||
ImGui::SetItemTooltip("%s", "The playhead will always clamp to the animation's length.");
|
||||
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
|
||||
if (ImGui::BeginMenu("Window"))
|
||||
{
|
||||
for (auto [i, member] : std::views::enumerate(WINDOW_MEMBERS))
|
||||
ImGui::MenuItem(WINDOW_STRINGS[i], nullptr, &(settings.*member));
|
||||
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
|
||||
if (ImGui::BeginMenu("Settings"))
|
||||
{
|
||||
if (ImGui::MenuItem("Configure"))
|
||||
{
|
||||
editSettings = settings;
|
||||
configurePopup.open();
|
||||
}
|
||||
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
|
||||
if (ImGui::BeginMenu("Help"))
|
||||
{
|
||||
if (ImGui::MenuItem("About")) aboutPopup.open();
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
|
||||
ImGui::EndMainMenuBar();
|
||||
}
|
||||
|
||||
generatePopup.trigger();
|
||||
|
||||
if (ImGui::BeginPopupModal(generatePopup.label, &generatePopup.isOpen, ImGuiWindowFlags_NoResize))
|
||||
{
|
||||
auto& startPosition = settings.generateStartPosition;
|
||||
auto& size = settings.generateSize;
|
||||
auto& pivot = settings.generatePivot;
|
||||
auto& rows = settings.generateRows;
|
||||
auto& columns = settings.generateColumns;
|
||||
auto& count = settings.generateCount;
|
||||
auto& delay = settings.generateDelay;
|
||||
auto& zoom = settings.generateZoom;
|
||||
auto& zoomStep = settings.viewZoomStep;
|
||||
|
||||
auto childSize = ImVec2(imgui::row_widget_width_get(2), imgui::size_without_footer_get().y);
|
||||
|
||||
if (ImGui::BeginChild("##Options Child", childSize, ImGuiChildFlags_Borders))
|
||||
{
|
||||
ImGui::InputInt2("Start Position", value_ptr(startPosition));
|
||||
ImGui::InputInt2("Frame Size", value_ptr(size));
|
||||
ImGui::InputInt2("Pivot", value_ptr(pivot));
|
||||
ImGui::InputInt("Rows", &rows, imgui::STEP, imgui::STEP_FAST);
|
||||
ImGui::InputInt("Columns", &columns, imgui::STEP, imgui::STEP_FAST);
|
||||
|
||||
ImGui::InputInt("Count", &count, imgui::STEP, imgui::STEP_FAST);
|
||||
count = glm::min(count, rows * columns);
|
||||
|
||||
ImGui::InputInt("Delay", &delay, imgui::STEP, imgui::STEP_FAST);
|
||||
}
|
||||
ImGui::EndChild();
|
||||
|
||||
ImGui::SameLine();
|
||||
|
||||
if (ImGui::BeginChild("##Preview Child", childSize, ImGuiChildFlags_Borders))
|
||||
{
|
||||
auto& backgroundColor = settings.previewBackgroundColor;
|
||||
auto& time = generateTime;
|
||||
auto& shaderTexture = resources.shaders[resource::shader::TEXTURE];
|
||||
|
||||
auto previewSize = ImVec2(ImGui::GetContentRegionAvail().x, imgui::size_without_footer_get(2).y);
|
||||
|
||||
generate.size_set(to_vec2(previewSize));
|
||||
generate.bind();
|
||||
generate.viewport_set();
|
||||
generate.clear(backgroundColor);
|
||||
|
||||
if (document && document->reference.itemType == anm2::LAYER)
|
||||
{
|
||||
auto& texture = document->anm2.content
|
||||
.spritesheets[document->anm2.content.layers[document->reference.itemID].spritesheetID]
|
||||
.texture;
|
||||
|
||||
auto index = std::clamp((int)(time * count - 1), 0, count - 1);
|
||||
auto row = index / columns;
|
||||
auto column = index % columns;
|
||||
auto crop = startPosition + ivec2(size.x * column, size.y * row);
|
||||
auto uvMin = (vec2(crop) + vec2(0.5f)) / vec2(texture.size);
|
||||
auto uvMax = (vec2(crop) + vec2(size) - vec2(0.5f)) / vec2(texture.size);
|
||||
|
||||
mat4 transform = generate.transform_get(zoom) * math::quad_model_get(size, {}, pivot);
|
||||
|
||||
generate.texture_render(shaderTexture, texture.id, transform, vec4(1.0f), {},
|
||||
math::uv_vertices_get(uvMin, uvMax).data());
|
||||
}
|
||||
generate.unbind();
|
||||
|
||||
ImGui::Image(generate.texture, previewSize);
|
||||
|
||||
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
|
||||
ImGui::SliderFloat("##Time", &time, 0.0f, 1.0f, "");
|
||||
|
||||
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
|
||||
ImGui::InputFloat("##Zoom", &zoom, zoomStep, zoomStep, "%.0f%%");
|
||||
zoom = glm::clamp(zoom, ZOOM_MIN, ZOOM_MAX);
|
||||
}
|
||||
|
||||
ImGui::EndChild();
|
||||
|
||||
auto widgetSize = imgui::widget_size_with_row_get(2);
|
||||
|
||||
if (ImGui::Button("Generate", widgetSize))
|
||||
{
|
||||
document->generate_animation_from_grid(startPosition, size, pivot, columns, count, delay);
|
||||
generatePopup.close();
|
||||
}
|
||||
|
||||
ImGui::SameLine();
|
||||
|
||||
if (ImGui::Button("Cancel", widgetSize)) generatePopup.close();
|
||||
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
|
||||
changePopup.trigger();
|
||||
|
||||
if (ImGui::BeginPopupModal(changePopup.label, &changePopup.isOpen, ImGuiWindowFlags_NoResize))
|
||||
{
|
||||
auto& isCrop = settings.changeIsCrop;
|
||||
auto& isSize = settings.changeIsSize;
|
||||
auto& isPosition = settings.changeIsPosition;
|
||||
auto& isPivot = settings.changeIsPivot;
|
||||
auto& isScale = settings.changeIsScale;
|
||||
auto& isRotation = settings.changeIsRotation;
|
||||
auto& isDelay = settings.changeIsDelay;
|
||||
auto& isTint = settings.changeIsTint;
|
||||
auto& isColorOffset = settings.changeIsColorOffset;
|
||||
auto& isVisibleSet = settings.changeIsVisibleSet;
|
||||
auto& isInterpolatedSet = settings.changeIsInterpolatedSet;
|
||||
auto& crop = settings.changeCrop;
|
||||
auto& size = settings.changeSize;
|
||||
auto& position = settings.changePosition;
|
||||
auto& pivot = settings.changePivot;
|
||||
auto& scale = settings.changeScale;
|
||||
auto& rotation = settings.changeRotation;
|
||||
auto& delay = settings.changeDelay;
|
||||
auto& tint = settings.changeTint;
|
||||
auto& colorOffset = settings.changeColorOffset;
|
||||
auto& isVisible = settings.changeIsVisible;
|
||||
auto& isInterpolated = settings.changeIsInterpolated;
|
||||
|
||||
auto& isFromSelectedFrame = settings.changeIsFromSelectedFrame;
|
||||
auto& numberFrames = settings.changeNumberFrames;
|
||||
|
||||
auto propertiesSize = imgui::child_size_get(10);
|
||||
|
||||
if (ImGui::BeginChild("##Properties", propertiesSize, ImGuiChildFlags_Borders))
|
||||
{
|
||||
auto start = [&](const char* checkboxLabel, bool& isEnabled)
|
||||
{
|
||||
ImGui::Checkbox(checkboxLabel, &isEnabled);
|
||||
ImGui::SameLine();
|
||||
ImGui::BeginDisabled(!isEnabled);
|
||||
};
|
||||
auto end = [&]() { ImGui::EndDisabled(); };
|
||||
|
||||
auto bool_value = [&](const char* checkboxLabel, const char* valueLabel, bool& isEnabled, bool& value)
|
||||
{
|
||||
start(checkboxLabel, isEnabled);
|
||||
ImGui::Checkbox(valueLabel, &value);
|
||||
end();
|
||||
};
|
||||
|
||||
auto color3_value = [&](const char* checkboxLabel, const char* valueLabel, bool& isEnabled, vec3& value)
|
||||
{
|
||||
start(checkboxLabel, isEnabled);
|
||||
ImGui::ColorEdit3(valueLabel, value_ptr(value));
|
||||
end();
|
||||
};
|
||||
|
||||
auto color4_value = [&](const char* checkboxLabel, const char* valueLabel, bool& isEnabled, vec4& value)
|
||||
{
|
||||
start(checkboxLabel, isEnabled);
|
||||
ImGui::ColorEdit4(valueLabel, value_ptr(value));
|
||||
end();
|
||||
};
|
||||
|
||||
auto float2_value = [&](const char* checkboxLabel, const char* valueLabel, bool& isEnabled, vec2& value)
|
||||
{
|
||||
start(checkboxLabel, isEnabled);
|
||||
ImGui::InputFloat2(valueLabel, value_ptr(value), math::vec2_format_get(value));
|
||||
end();
|
||||
};
|
||||
|
||||
auto float_value = [&](const char* checkboxLabel, const char* valueLabel, bool& isEnabled, float& value)
|
||||
{
|
||||
start(checkboxLabel, isEnabled);
|
||||
ImGui::InputFloat(valueLabel, &value, imgui::STEP, imgui::STEP_FAST, math::float_format_get(value));
|
||||
end();
|
||||
};
|
||||
|
||||
auto int_value = [&](const char* checkboxLabel, const char* valueLabel, bool& isEnabled, int& value)
|
||||
{
|
||||
start(checkboxLabel, isEnabled);
|
||||
ImGui::InputInt(valueLabel, &value, imgui::STEP, imgui::STEP_FAST);
|
||||
end();
|
||||
};
|
||||
|
||||
float2_value("##Is Crop", "Crop", isCrop, crop);
|
||||
float2_value("##Is Size", "Size", isSize, size);
|
||||
float2_value("##Is Position", "Position", isPosition, position);
|
||||
float2_value("##Is Pivot", "Pivot", isPivot, pivot);
|
||||
float2_value("##Is Scale", "Scale", isScale, scale);
|
||||
float_value("##Is Rotation", "Rotation", isRotation, rotation);
|
||||
int_value("##Is Delay", "Delay", isDelay, delay);
|
||||
color4_value("##Is Tint", "Tint", isTint, tint);
|
||||
color3_value("##Is Color Offset", "Color Offset", isColorOffset, colorOffset);
|
||||
bool_value("##Is Visible", "Visible", isVisibleSet, isVisible);
|
||||
ImGui::SameLine();
|
||||
bool_value("##Is Interpolated", "Interpolated", isInterpolatedSet, isInterpolated);
|
||||
}
|
||||
ImGui::EndChild();
|
||||
|
||||
auto settingsSize = imgui::child_size_get(2);
|
||||
|
||||
if (ImGui::BeginChild("##Settings", settingsSize, ImGuiChildFlags_Borders))
|
||||
{
|
||||
ImGui::Checkbox("From Selected Frame", &isFromSelectedFrame);
|
||||
ImGui::SetItemTooltip("The frames after the currently referenced frame will be changed with these values.\nIf"
|
||||
"off, will use all frames.");
|
||||
|
||||
ImGui::BeginDisabled(!isFromSelectedFrame);
|
||||
ImGui::InputInt("Number of Frames", &numberFrames, imgui::STEP, imgui::STEP_FAST);
|
||||
numberFrames = glm::clamp(numberFrames, anm2::FRAME_NUM_MIN,
|
||||
(int)document->item_get()->frames.size() - document->reference.frameIndex);
|
||||
ImGui::SetItemTooltip("Set the number of frames that will be changed.");
|
||||
ImGui::EndDisabled();
|
||||
}
|
||||
ImGui::EndChild();
|
||||
|
||||
auto widgetSize = imgui::widget_size_with_row_get(4);
|
||||
|
||||
auto frame_change = [&](anm2::ChangeType type)
|
||||
{
|
||||
anm2::FrameChange frameChange;
|
||||
frameChange.crop = isCrop ? std::make_optional(crop) : std::nullopt;
|
||||
frameChange.size = isSize ? std::make_optional(size) : std::nullopt;
|
||||
frameChange.position = isPosition ? std::make_optional(position) : std::nullopt;
|
||||
frameChange.pivot = isPivot ? std::make_optional(pivot) : std::nullopt;
|
||||
frameChange.scale = isScale ? std::make_optional(scale) : std::nullopt;
|
||||
frameChange.rotation = isRotation ? std::make_optional(rotation) : std::nullopt;
|
||||
frameChange.delay = isDelay ? std::make_optional(delay) : std::nullopt;
|
||||
frameChange.tint = isTint ? std::make_optional(tint) : std::nullopt;
|
||||
frameChange.colorOffset = isColorOffset ? std::make_optional(colorOffset) : std::nullopt;
|
||||
frameChange.isVisible = isVisibleSet ? std::make_optional(isVisible) : std::nullopt;
|
||||
frameChange.isInterpolated = isInterpolatedSet ? std::make_optional(isInterpolated) : std::nullopt;
|
||||
|
||||
document->frames_change(frameChange, type, isFromSelectedFrame, numberFrames);
|
||||
};
|
||||
|
||||
if (ImGui::Button("Add", widgetSize))
|
||||
{
|
||||
frame_change(anm2::ADD);
|
||||
changePopup.close();
|
||||
}
|
||||
|
||||
ImGui::SameLine();
|
||||
|
||||
if (ImGui::Button("Subtract", widgetSize))
|
||||
{
|
||||
frame_change(anm2::SUBTRACT);
|
||||
changePopup.close();
|
||||
}
|
||||
|
||||
ImGui::SameLine();
|
||||
|
||||
if (ImGui::Button("Adjust", widgetSize))
|
||||
{
|
||||
frame_change(anm2::ADJUST);
|
||||
changePopup.close();
|
||||
}
|
||||
|
||||
ImGui::SameLine();
|
||||
|
||||
if (ImGui::Button("Cancel", widgetSize)) changePopup.close();
|
||||
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
|
||||
configurePopup.trigger();
|
||||
|
||||
if (ImGui::BeginPopupModal(configurePopup.label, &configurePopup.isOpen, ImGuiWindowFlags_NoResize))
|
||||
{
|
||||
auto childSize = imgui::size_without_footer_get(2);
|
||||
|
||||
if (ImGui::BeginTabBar("##Configure Tabs"))
|
||||
{
|
||||
if (ImGui::BeginTabItem("General"))
|
||||
{
|
||||
if (ImGui::BeginChild("##Tab Child", childSize, true))
|
||||
{
|
||||
ImGui::SeparatorText("File");
|
||||
|
||||
ImGui::Checkbox("Autosaving", &editSettings.fileIsAutosave);
|
||||
ImGui::SetItemTooltip("Enables autosaving of documents.");
|
||||
|
||||
ImGui::BeginDisabled(!editSettings.fileIsAutosave);
|
||||
ImGui::InputInt("Autosave Time (minutes", &editSettings.fileAutosaveTime, imgui::STEP, imgui::STEP_FAST);
|
||||
editSettings.fileAutosaveTime = glm::clamp(editSettings.fileAutosaveTime, 0, 10);
|
||||
ImGui::SetItemTooltip("If changed, will autosave documents using this interval.");
|
||||
ImGui::EndDisabled();
|
||||
|
||||
ImGui::SeparatorText("View");
|
||||
|
||||
ImGui::InputFloat("Display Scale", &editSettings.displayScale, 0.25f, 0.25f, "%.2f");
|
||||
ImGui::SetItemTooltip("Change the scale of the display.");
|
||||
editSettings.displayScale = glm::clamp(editSettings.displayScale, 0.5f, 2.0f);
|
||||
|
||||
ImGui::InputFloat("Zoom Step", &editSettings.viewZoomStep, 10.0f, 10.0f, "%.2f");
|
||||
ImGui::SetItemTooltip("When zooming in/out with mouse or shortcut, this value will be used.");
|
||||
editSettings.viewZoomStep = glm::clamp(editSettings.viewZoomStep, 1.0f, 250.0f);
|
||||
|
||||
ImGui::Checkbox("Vsync", &editSettings.isVsync);
|
||||
ImGui::SetItemTooltip("Toggle vertical sync; synchronizes program update rate with monitor refresh rate.");
|
||||
}
|
||||
ImGui::EndChild();
|
||||
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
|
||||
if (ImGui::BeginTabItem("Shortcuts"))
|
||||
{
|
||||
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2());
|
||||
|
||||
if (ImGui::BeginChild("##Tab Child", childSize, true))
|
||||
{
|
||||
|
||||
if (ImGui::BeginTable("Shortcuts", 2, ImGuiTableFlags_Borders | ImGuiTableFlags_ScrollY))
|
||||
{
|
||||
ImGui::TableSetupScrollFreeze(0, 1);
|
||||
ImGui::TableSetupColumn("Shortcut");
|
||||
ImGui::TableSetupColumn("Value");
|
||||
ImGui::TableHeadersRow();
|
||||
|
||||
for (int i = 0; i < SHORTCUT_COUNT; ++i)
|
||||
{
|
||||
bool isSelected = selectedShortcut == i;
|
||||
|
||||
ShortcutMember member = SHORTCUT_MEMBERS[i];
|
||||
std::string* settingString = &(editSettings.*member);
|
||||
std::string chordString = isSelected ? "" : *settingString;
|
||||
|
||||
ImGui::PushID(i);
|
||||
ImGui::TableNextRow();
|
||||
ImGui::TableSetColumnIndex(0);
|
||||
ImGui::TextUnformatted(SHORTCUT_STRINGS[i]);
|
||||
ImGui::TableSetColumnIndex(1);
|
||||
|
||||
if (ImGui::Selectable(chordString.c_str(), isSelected)) selectedShortcut = i;
|
||||
|
||||
ImGui::PopID();
|
||||
|
||||
if (isSelected)
|
||||
{
|
||||
ImGuiKeyChord chord{ImGuiKey_None};
|
||||
|
||||
if (ImGui::IsKeyDown(ImGuiMod_Ctrl)) chord |= ImGuiMod_Ctrl;
|
||||
if (ImGui::IsKeyDown(ImGuiMod_Shift)) chord |= ImGuiMod_Shift;
|
||||
if (ImGui::IsKeyDown(ImGuiMod_Alt)) chord |= ImGuiMod_Alt;
|
||||
if (ImGui::IsKeyDown(ImGuiMod_Super)) chord |= ImGuiMod_Super;
|
||||
|
||||
for (auto& key : imgui::KEY_MAP | std::views::values)
|
||||
{
|
||||
if (ImGui::IsKeyPressed(key))
|
||||
{
|
||||
chord |= key;
|
||||
*settingString = imgui::chord_to_string(chord);
|
||||
selectedShortcut = -1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ImGui::EndTable();
|
||||
}
|
||||
ImGui::EndChild();
|
||||
|
||||
ImGui::PopStyleVar();
|
||||
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::EndTabBar();
|
||||
}
|
||||
|
||||
auto widgetSize = imgui::widget_size_with_row_get(3);
|
||||
|
||||
if (ImGui::Button("Save", widgetSize))
|
||||
{
|
||||
settings = editSettings;
|
||||
configurePopup.close();
|
||||
}
|
||||
ImGui::SetItemTooltip("Use the configured settings.");
|
||||
|
||||
ImGui::SameLine();
|
||||
|
||||
if (ImGui::Button("Use Default Settings", widgetSize)) editSettings = Settings();
|
||||
ImGui::SetItemTooltip("Reset the settings to their defaults.");
|
||||
|
||||
ImGui::SameLine();
|
||||
|
||||
if (ImGui::Button("Close", widgetSize)) configurePopup.close();
|
||||
ImGui::SetItemTooltip("Close without updating settings.");
|
||||
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
|
||||
renderPopup.trigger();
|
||||
|
||||
if (ImGui::BeginPopupModal(renderPopup.label, &renderPopup.isOpen, ImGuiWindowFlags_NoResize))
|
||||
{
|
||||
auto animation = document ? document->animation_get() : nullptr;
|
||||
if (!animation) renderPopup.close();
|
||||
|
||||
auto& playback = document->playback;
|
||||
auto& ffmpegPath = settings.renderFFmpegPath;
|
||||
auto& path = settings.renderPath;
|
||||
auto& format = settings.renderFormat;
|
||||
auto& type = settings.renderType;
|
||||
auto& start = manager.recordingStart;
|
||||
auto& end = manager.recordingEnd;
|
||||
auto& isRange = settings.renderIsRange;
|
||||
auto widgetSize = imgui::widget_size_with_row_get(2);
|
||||
auto dialogType = type == render::PNGS ? dialog::PNG_DIRECTORY_SET
|
||||
: type == render::GIF ? dialog::GIF_PATH_SET
|
||||
: type == render::WEBM ? dialog::WEBM_PATH_SET
|
||||
: dialog::NONE;
|
||||
|
||||
if (ImGui::ImageButton("##FFmpeg Path Set", resources.icons[icon::FOLDER].id, imgui::icon_size_get()))
|
||||
dialog.file_open(dialog::FFMPEG_PATH_SET);
|
||||
ImGui::SameLine();
|
||||
imgui::input_text_string("FFmpeg Path", &ffmpegPath);
|
||||
dialog.set_string_to_selected_path(ffmpegPath, dialog::FFMPEG_PATH_SET);
|
||||
|
||||
if (ImGui::ImageButton("##Path Set", resources.icons[icon::FOLDER].id, imgui::icon_size_get()))
|
||||
{
|
||||
if (dialogType == dialog::PNG_DIRECTORY_SET)
|
||||
dialog.folder_open(dialogType);
|
||||
else
|
||||
dialog.file_open(dialogType);
|
||||
}
|
||||
ImGui::SameLine();
|
||||
imgui::input_text_string(type == render::PNGS ? "Directory" : "Path", &path);
|
||||
dialog.set_string_to_selected_path(path, dialogType);
|
||||
|
||||
ImGui::Combo("Type", &type, render::STRINGS, render::COUNT);
|
||||
|
||||
ImGui::BeginDisabled(type != render::PNGS);
|
||||
imgui::input_text_string("Format", &format);
|
||||
ImGui::EndDisabled();
|
||||
|
||||
ImGui::BeginDisabled(!isRange);
|
||||
imgui::input_int_range("Start", start, 0, animation->frameNum - 1);
|
||||
ImGui::InputInt("End", &end, start, animation->frameNum);
|
||||
ImGui::EndDisabled();
|
||||
|
||||
ImGui::Checkbox("Custom Range", &isRange);
|
||||
|
||||
if (ImGui::Button("Render", widgetSize))
|
||||
{
|
||||
manager.isRecording = true;
|
||||
playback.time = start;
|
||||
playback.isPlaying = true;
|
||||
renderPopup.close();
|
||||
manager.progressPopup.open();
|
||||
}
|
||||
|
||||
ImGui::SameLine();
|
||||
|
||||
if (ImGui::Button("Cancel", widgetSize)) renderPopup.close();
|
||||
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
|
||||
aboutPopup.trigger();
|
||||
|
||||
if (ImGui::BeginPopupModal(aboutPopup.label, &aboutPopup.isOpen, ImGuiWindowFlags_NoResize))
|
||||
{
|
||||
if (ImGui::Button("Close")) aboutPopup.close();
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
|
||||
if (imgui::shortcut(settings.shortcutNew, shortcut::GLOBAL)) dialog.file_open(dialog::ANM2_NEW);
|
||||
if (imgui::shortcut(settings.shortcutOpen, shortcut::GLOBAL)) dialog.file_open(dialog::ANM2_OPEN);
|
||||
if (imgui::shortcut(settings.shortcutSave, shortcut::GLOBAL)) document->save();
|
||||
if (imgui::shortcut(settings.shortcutSaveAs, shortcut::GLOBAL)) dialog.file_save(dialog::ANM2_SAVE);
|
||||
if (imgui::shortcut(settings.shortcutExit, shortcut::GLOBAL)) isQuitting = true;
|
||||
}
|
||||
}
|
||||
31
src/imgui/taskbar.h
Normal file
31
src/imgui/taskbar.h
Normal file
@@ -0,0 +1,31 @@
|
||||
#pragma once
|
||||
|
||||
#include "canvas.h"
|
||||
#include "dialog.h"
|
||||
#include "imgui_.h"
|
||||
#include "manager.h"
|
||||
#include "resources.h"
|
||||
#include "settings.h"
|
||||
|
||||
namespace anm2ed::imgui
|
||||
{
|
||||
class Taskbar
|
||||
{
|
||||
Canvas generate;
|
||||
float generateTime{};
|
||||
PopupHelper generatePopup{PopupHelper("Generate Animation from Grid")};
|
||||
PopupHelper changePopup{PopupHelper("Change All Frame Properties", imgui::POPUP_SMALL_NO_HEIGHT)};
|
||||
PopupHelper renderPopup{PopupHelper("Render Animation", imgui::POPUP_SMALL_NO_HEIGHT)};
|
||||
PopupHelper configurePopup{PopupHelper("Configure")};
|
||||
PopupHelper aboutPopup{PopupHelper("About")};
|
||||
Settings editSettings{};
|
||||
int selectedShortcut{-1};
|
||||
bool isQuittingMode{};
|
||||
|
||||
public:
|
||||
float height{};
|
||||
|
||||
Taskbar();
|
||||
void update(Manager&, Settings&, Resources&, Dialog&, bool&);
|
||||
};
|
||||
};
|
||||
86
src/imgui/toast.cpp
Normal file
86
src/imgui/toast.cpp
Normal file
@@ -0,0 +1,86 @@
|
||||
#include "toast.h"
|
||||
|
||||
#include "log.h"
|
||||
#include <imgui/imgui.h>
|
||||
|
||||
#include "types.h"
|
||||
|
||||
using namespace anm2ed::types;
|
||||
|
||||
namespace anm2ed::imgui
|
||||
{
|
||||
constexpr auto LIFETIME = 3.0f;
|
||||
|
||||
Toast::Toast(const std::string& message)
|
||||
{
|
||||
this->message = message;
|
||||
lifetime = LIFETIME;
|
||||
}
|
||||
|
||||
void Toasts::update()
|
||||
{
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
|
||||
auto borderColor = ImGui::GetStyleColorVec4(ImGuiCol_Border);
|
||||
auto textColor = ImGui::GetStyleColorVec4(ImGuiCol_Text);
|
||||
|
||||
auto position = to_vec2(io.DisplaySize) - to_vec2(ImGui::GetStyle().ItemSpacing);
|
||||
|
||||
for (int i = (int)toasts.size() - 1; i >= 0; --i)
|
||||
{
|
||||
Toast& toast = toasts[i];
|
||||
|
||||
toast.lifetime -= ImGui::GetIO().DeltaTime;
|
||||
|
||||
if (toast.lifetime <= 0.0f)
|
||||
{
|
||||
toasts.erase(toasts.begin() + i);
|
||||
i--;
|
||||
continue;
|
||||
}
|
||||
|
||||
auto alpha = toast.lifetime / LIFETIME;
|
||||
borderColor.w = alpha;
|
||||
textColor.w = alpha;
|
||||
|
||||
ImGui::SetNextWindowPos(to_imvec2(position), ImGuiCond_None, {1.0f, 1.0f});
|
||||
ImGui::SetNextWindowSize(ImVec2(0, ImGui::GetTextLineHeightWithSpacing() + ImGui::GetStyle().WindowPadding.y));
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, textColor);
|
||||
ImGui::PushStyleColor(ImGuiCol_Border, borderColor);
|
||||
ImGui::SetNextWindowBgAlpha(alpha);
|
||||
|
||||
if (ImGui::Begin(std::format("##Toast #{}", i).c_str(), nullptr,
|
||||
ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove |
|
||||
ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse |
|
||||
ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_AlwaysAutoResize |
|
||||
ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoNav))
|
||||
{
|
||||
ImGui::TextUnformatted(toast.message.c_str());
|
||||
position.y -= ImGui::GetWindowSize().y + ImGui::GetStyle().ItemSpacing.y;
|
||||
}
|
||||
ImGui::End();
|
||||
ImGui::PopStyleColor(2);
|
||||
}
|
||||
}
|
||||
|
||||
void Toasts::info(const std::string& message)
|
||||
{
|
||||
toasts.emplace_back(Toast(message));
|
||||
logger.info(message);
|
||||
}
|
||||
|
||||
void Toasts::error(const std::string& message)
|
||||
{
|
||||
toasts.emplace_back(Toast(message));
|
||||
logger.error(message);
|
||||
}
|
||||
|
||||
void Toasts::warning(const std::string& message)
|
||||
{
|
||||
toasts.emplace_back(Toast(message));
|
||||
logger.warning(message);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
anm2ed::imgui::Toasts toasts;
|
||||
30
src/imgui/toast.h
Normal file
30
src/imgui/toast.h
Normal file
@@ -0,0 +1,30 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace anm2ed::imgui
|
||||
{
|
||||
class Toast
|
||||
{
|
||||
public:
|
||||
std::string message{};
|
||||
float lifetime{};
|
||||
|
||||
Toast(const std::string&);
|
||||
};
|
||||
|
||||
class Toasts
|
||||
{
|
||||
public:
|
||||
std::vector<Toast> toasts{};
|
||||
|
||||
void update();
|
||||
void info(const std::string&);
|
||||
void error(const std::string&);
|
||||
void warning(const std::string&);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
extern anm2ed::imgui::Toasts toasts;
|
||||
482
src/imgui/window/animation_preview.cpp
Normal file
482
src/imgui/window/animation_preview.cpp
Normal file
@@ -0,0 +1,482 @@
|
||||
#include "animation_preview.h"
|
||||
|
||||
#include <ranges>
|
||||
|
||||
#include <glm/gtc/type_ptr.hpp>
|
||||
|
||||
#include "log.h"
|
||||
#include "math_.h"
|
||||
#include "toast.h"
|
||||
#include "tool.h"
|
||||
#include "types.h"
|
||||
|
||||
using namespace anm2ed::canvas;
|
||||
using namespace anm2ed::types;
|
||||
using namespace anm2ed::util;
|
||||
using namespace anm2ed::resource;
|
||||
using namespace glm;
|
||||
|
||||
namespace anm2ed::imgui
|
||||
{
|
||||
constexpr auto NULL_COLOR = vec4(0.0f, 0.0f, 1.0f, 0.90f);
|
||||
constexpr auto TARGET_SIZE = vec2(32, 32);
|
||||
constexpr auto POINT_SIZE = vec2(4, 4);
|
||||
constexpr auto NULL_RECT_SIZE = vec2(100);
|
||||
constexpr auto TRIGGER_TEXT_COLOR = ImVec4(1.0f, 1.0f, 1.0f, 0.5f);
|
||||
|
||||
AnimationPreview::AnimationPreview() : Canvas(vec2())
|
||||
{
|
||||
}
|
||||
|
||||
void AnimationPreview::tick(Manager& manager, Document& document, Settings& settings)
|
||||
{
|
||||
auto& anm2 = document.anm2;
|
||||
auto& playback = document.playback;
|
||||
|
||||
if (playback.isPlaying)
|
||||
{
|
||||
auto& isSound = settings.timelineIsSound;
|
||||
auto& isOnlyShowLayers = settings.timelineIsOnlyShowLayers;
|
||||
|
||||
if (isSound && !anm2.content.sounds.empty())
|
||||
if (auto animation = document.animation_get(); animation)
|
||||
if (animation->triggers.isVisible && !isOnlyShowLayers)
|
||||
if (auto trigger = animation->triggers.frame_generate(playback.time, anm2::TRIGGER); trigger.isVisible)
|
||||
anm2.content.sounds[anm2.content.events[trigger.eventID].soundID].audio.play();
|
||||
|
||||
document.reference.frameTime = playback.time;
|
||||
}
|
||||
|
||||
if (manager.isRecording)
|
||||
{
|
||||
auto pixels = pixels_get();
|
||||
renderFrames.push_back(Texture(pixels.data(), size));
|
||||
|
||||
if (playback.isFinished)
|
||||
{
|
||||
auto& ffmpegPath = settings.renderFFmpegPath;
|
||||
auto& path = settings.renderPath;
|
||||
auto& type = settings.renderType;
|
||||
|
||||
if (type == render::PNGS)
|
||||
{
|
||||
auto& format = settings.renderFormat;
|
||||
bool isSuccess{true};
|
||||
for (auto [i, frame] : std::views::enumerate(renderFrames))
|
||||
{
|
||||
std::filesystem::path outputPath =
|
||||
std::filesystem::path(path) / std::vformat(format, std::make_format_args(i));
|
||||
|
||||
if (!frame.write_png(outputPath))
|
||||
{
|
||||
isSuccess = false;
|
||||
break;
|
||||
}
|
||||
logger.info(std::format("Saved frame to PNG: {}", outputPath.string()));
|
||||
}
|
||||
|
||||
if (isSuccess)
|
||||
toasts.info(std::format("Exported rendered frames to: {}", path));
|
||||
else
|
||||
toasts.warning(std::format("Could not export frames to: {}", path));
|
||||
}
|
||||
else
|
||||
{
|
||||
if (animation_render(ffmpegPath, path, renderFrames, (render::Type)type, size, anm2.info.fps))
|
||||
toasts.info(std::format("Exported rendered animation to: {}", path));
|
||||
else
|
||||
toasts.warning(std::format("Could not output rendered animation: {}", path));
|
||||
}
|
||||
|
||||
renderFrames.clear();
|
||||
playback.isPlaying = false;
|
||||
playback.isFinished = false;
|
||||
manager.isRecording = false;
|
||||
manager.progressPopup.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AnimationPreview::update(Manager& manager, Settings& settings, Resources& resources)
|
||||
{
|
||||
auto& document = *manager.get();
|
||||
auto& anm2 = document.anm2;
|
||||
auto& playback = document.playback;
|
||||
auto& reference = document.reference;
|
||||
auto animation = document.animation_get();
|
||||
auto& pan = document.previewPan;
|
||||
auto& zoom = document.previewZoom;
|
||||
auto& backgroundColor = settings.previewBackgroundColor;
|
||||
auto& axesColor = settings.previewAxesColor;
|
||||
auto& gridColor = settings.previewGridColor;
|
||||
auto& gridSize = settings.previewGridSize;
|
||||
auto& gridOffset = settings.previewGridOffset;
|
||||
auto& zoomStep = settings.viewZoomStep;
|
||||
auto& isGrid = settings.previewIsGrid;
|
||||
auto& overlayTransparency = settings.previewOverlayTransparency;
|
||||
auto& overlayIndex = document.overlayIndex;
|
||||
auto& isRootTransform = settings.previewIsRootTransform;
|
||||
auto& isPivots = settings.previewIsPivots;
|
||||
auto& isAxes = settings.previewIsAxes;
|
||||
auto& isAltIcons = settings.previewIsAltIcons;
|
||||
auto& isBorder = settings.previewIsBorder;
|
||||
auto& tool = settings.tool;
|
||||
auto& isOnlyShowLayers = settings.timelineIsOnlyShowLayers;
|
||||
auto& shaderLine = resources.shaders[shader::LINE];
|
||||
auto& shaderAxes = resources.shaders[shader::AXIS];
|
||||
auto& shaderGrid = resources.shaders[shader::GRID];
|
||||
auto& shaderTexture = resources.shaders[shader::TEXTURE];
|
||||
|
||||
settings.previewPan = pan;
|
||||
settings.previewZoom = zoom;
|
||||
|
||||
if (ImGui::Begin("Animation Preview", &settings.windowIsAnimationPreview))
|
||||
{
|
||||
auto childSize = ImVec2(imgui::row_widget_width_get(4),
|
||||
(ImGui::GetTextLineHeightWithSpacing() * 4) + (ImGui::GetStyle().WindowPadding.y * 2));
|
||||
|
||||
if (ImGui::BeginChild("##Grid Child", childSize, true, ImGuiWindowFlags_HorizontalScrollbar))
|
||||
{
|
||||
|
||||
ImGui::Checkbox("Grid", &isGrid);
|
||||
ImGui::SameLine();
|
||||
ImGui::ColorEdit4("Color", value_ptr(gridColor), ImGuiColorEditFlags_NoInputs);
|
||||
ImGui::InputInt2("Size", value_ptr(gridSize));
|
||||
ImGui::InputInt2("Offset", value_ptr(gridOffset));
|
||||
}
|
||||
ImGui::EndChild();
|
||||
|
||||
ImGui::SameLine();
|
||||
|
||||
if (ImGui::BeginChild("##View Child", childSize, true, ImGuiWindowFlags_HorizontalScrollbar))
|
||||
{
|
||||
ImGui::InputFloat("Zoom", &zoom, zoomStep, zoomStep, "%.0f%%");
|
||||
|
||||
auto widgetSize = imgui::widget_size_with_row_get(2);
|
||||
|
||||
imgui::shortcut(settings.shortcutCenterView);
|
||||
if (ImGui::Button("Center View", widgetSize)) pan = vec2();
|
||||
imgui::set_item_tooltip_shortcut("Centers the view.", settings.shortcutCenterView);
|
||||
|
||||
ImGui::SameLine();
|
||||
|
||||
imgui::shortcut(settings.shortcutFit);
|
||||
if (ImGui::Button("Fit", widgetSize))
|
||||
if (animation) set_to_rect(zoom, pan, animation->rect(isRootTransform));
|
||||
imgui::set_item_tooltip_shortcut("Set the view to match the extent of the animation.", settings.shortcutFit);
|
||||
|
||||
ImGui::TextUnformatted(std::format(POSITION_FORMAT, (int)mousePos.x, (int)mousePos.y).c_str());
|
||||
}
|
||||
ImGui::EndChild();
|
||||
|
||||
ImGui::SameLine();
|
||||
|
||||
if (ImGui::BeginChild("##Background Child", childSize, true, ImGuiWindowFlags_HorizontalScrollbar))
|
||||
{
|
||||
ImGui::ColorEdit4("Background", value_ptr(backgroundColor), ImGuiColorEditFlags_NoInputs);
|
||||
ImGui::SameLine();
|
||||
ImGui::Checkbox("Axes", &isAxes);
|
||||
ImGui::SameLine();
|
||||
ImGui::ColorEdit4("Color", value_ptr(axesColor), ImGuiColorEditFlags_NoInputs);
|
||||
|
||||
imgui::combo_strings("Overlay", &overlayIndex, document.animationNamesCStr);
|
||||
|
||||
ImGui::InputFloat("Alpha", &overlayTransparency, 0, 0, "%.0f");
|
||||
}
|
||||
ImGui::EndChild();
|
||||
|
||||
ImGui::SameLine();
|
||||
|
||||
if (ImGui::BeginChild("##Helpers Child", childSize, true, ImGuiWindowFlags_HorizontalScrollbar))
|
||||
{
|
||||
auto helpersChildSize = ImVec2(imgui::row_widget_width_get(2), ImGui::GetContentRegionAvail().y);
|
||||
|
||||
if (ImGui::BeginChild("##Helpers Child 1", helpersChildSize))
|
||||
{
|
||||
ImGui::Checkbox("Root Transform", &isRootTransform);
|
||||
ImGui::Checkbox("Pivots", &isPivots);
|
||||
}
|
||||
ImGui::EndChild();
|
||||
|
||||
ImGui::SameLine();
|
||||
|
||||
if (ImGui::BeginChild("##Helpers Child 2", helpersChildSize))
|
||||
{
|
||||
ImGui::Checkbox("Alt Icons", &isAltIcons);
|
||||
ImGui::Checkbox("Border", &isBorder);
|
||||
}
|
||||
ImGui::EndChild();
|
||||
}
|
||||
ImGui::EndChild();
|
||||
|
||||
auto cursorScreenPos = ImGui::GetCursorScreenPos();
|
||||
|
||||
size_set(to_vec2(ImGui::GetContentRegionAvail()));
|
||||
bind();
|
||||
viewport_set();
|
||||
clear(backgroundColor);
|
||||
if (isAxes) axes_render(shaderAxes, zoom, pan, axesColor);
|
||||
if (isGrid) grid_render(shaderGrid, zoom, pan, gridSize, gridOffset, gridColor);
|
||||
|
||||
auto render = [&](anm2::Animation* animation, float time, vec3 colorOffset = {}, float alphaOffset = {},
|
||||
bool isOnionskin = false)
|
||||
{
|
||||
auto transform = transform_get(zoom, pan);
|
||||
auto root = animation->rootAnimation.frame_generate(time, anm2::ROOT);
|
||||
|
||||
if (isRootTransform)
|
||||
transform *= math::quad_model_parent_get(root.position, {}, math::percent_to_unit(root.scale), root.rotation);
|
||||
|
||||
if (!isOnlyShowLayers && root.isVisible && animation->rootAnimation.isVisible)
|
||||
{
|
||||
auto rootTransform = transform * math::quad_model_get(TARGET_SIZE, root.position, TARGET_SIZE * 0.5f,
|
||||
math::percent_to_unit(root.scale), root.rotation);
|
||||
|
||||
vec4 color = isOnionskin ? vec4(colorOffset, alphaOffset) : color::GREEN;
|
||||
|
||||
texture_render(shaderTexture, resources.icons[icon::TARGET].id, rootTransform, color);
|
||||
}
|
||||
|
||||
for (auto& id : animation->layerOrder)
|
||||
{
|
||||
auto& layerAnimation = animation->layerAnimations.at(id);
|
||||
if (!layerAnimation.isVisible) continue;
|
||||
|
||||
auto& layer = anm2.content.layers.at(id);
|
||||
|
||||
if (auto frame = layerAnimation.frame_generate(time, anm2::LAYER); frame.isVisible)
|
||||
{
|
||||
auto spritesheet = anm2.spritesheet_get(layer.spritesheetID);
|
||||
if (!spritesheet) continue;
|
||||
|
||||
auto& texture = spritesheet->texture;
|
||||
if (!texture.is_valid()) continue;
|
||||
|
||||
auto layerModel = math::quad_model_get(frame.size, frame.position, frame.pivot,
|
||||
math::percent_to_unit(frame.scale), frame.rotation);
|
||||
auto layerTransform = transform * layerModel;
|
||||
|
||||
auto uvMin = frame.crop / vec2(texture.size);
|
||||
auto uvMax = (frame.crop + frame.size) / vec2(texture.size);
|
||||
auto vertices = math::uv_vertices_get(uvMin, uvMax);
|
||||
vec3 frameColorOffset = frame.colorOffset + colorOffset;
|
||||
vec4 frameTint = frame.tint;
|
||||
frameTint.a = std::max(0.0f, frameTint.a - alphaOffset);
|
||||
|
||||
texture_render(shaderTexture, texture.id, layerTransform, frameTint, frameColorOffset, vertices.data());
|
||||
|
||||
auto color = isOnionskin ? vec4(colorOffset, 1.0f - alphaOffset) : color::RED;
|
||||
|
||||
if (isBorder) rect_render(shaderLine, layerTransform, layerModel, color);
|
||||
|
||||
if (isPivots)
|
||||
{
|
||||
auto pivotModel = math::quad_model_get(PIVOT_SIZE, frame.position, PIVOT_SIZE * 0.5f,
|
||||
math::percent_to_unit(frame.scale), frame.rotation);
|
||||
auto pivotTransform = transform * pivotModel;
|
||||
|
||||
texture_render(shaderTexture, resources.icons[icon::PIVOT].id, pivotTransform, color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (auto& [id, nullAnimation] : animation->nullAnimations)
|
||||
{
|
||||
if (!nullAnimation.isVisible || isOnlyShowLayers) continue;
|
||||
|
||||
auto& isShowRect = anm2.content.nulls[id].isShowRect;
|
||||
|
||||
if (auto frame = nullAnimation.frame_generate(time, anm2::NULL_); frame.isVisible)
|
||||
{
|
||||
auto icon = isShowRect ? icon::POINT : icon::TARGET;
|
||||
auto& size = isShowRect ? POINT_SIZE : TARGET_SIZE;
|
||||
auto color = isOnionskin ? vec4(colorOffset, 1.0f - alphaOffset)
|
||||
: id == reference.itemID && reference.itemType == anm2::NULL_ ? color::RED
|
||||
: NULL_COLOR;
|
||||
|
||||
auto nullModel = math::quad_model_get(size, frame.position, size * 0.5f, math::percent_to_unit(frame.scale),
|
||||
frame.rotation);
|
||||
auto nullTransform = transform * nullModel;
|
||||
|
||||
texture_render(shaderTexture, resources.icons[icon].id, nullTransform, color);
|
||||
|
||||
if (isShowRect)
|
||||
{
|
||||
auto rectModel = math::quad_model_get(NULL_RECT_SIZE, frame.position, NULL_RECT_SIZE * 0.5f,
|
||||
math::percent_to_unit(frame.scale), frame.rotation);
|
||||
auto rectTransform = transform * rectModel;
|
||||
|
||||
rect_render(shaderLine, rectTransform, rectModel, color);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
auto onionskin_render = [&](float time, int count, int direction, vec3 color)
|
||||
{
|
||||
for (int i = 1; i <= count; i++)
|
||||
{
|
||||
float useTime = time + (float)(direction * i);
|
||||
if (useTime < 0.0f || useTime > animation->frameNum) continue;
|
||||
|
||||
float alphaOffset = (1.0f / (count + 1)) * i;
|
||||
render(animation, useTime, color, alphaOffset, true);
|
||||
}
|
||||
};
|
||||
|
||||
auto onionskins_render = [&](float time)
|
||||
{
|
||||
onionskin_render(time, settings.onionskinBeforeCount, -1, settings.onionskinBeforeColor);
|
||||
onionskin_render(time, settings.onionskinAfterCount, 1, settings.onionskinAfterColor);
|
||||
};
|
||||
|
||||
auto frameTime = reference.frameTime > -1 && !playback.isPlaying ? reference.frameTime : playback.time;
|
||||
|
||||
if (animation)
|
||||
{
|
||||
auto& drawOrder = settings.onionskinDrawOrder;
|
||||
auto& isEnabled = settings.onionskinIsEnabled;
|
||||
|
||||
if (drawOrder == draw_order::BELOW && isEnabled) onionskins_render(frameTime);
|
||||
|
||||
render(animation, frameTime);
|
||||
|
||||
if (overlayIndex > 0)
|
||||
render(document.anm2.animation_get({overlayIndex - 1}), frameTime, {},
|
||||
1.0f - math::uint8_to_float(overlayTransparency));
|
||||
|
||||
if (drawOrder == draw_order::ABOVE && isEnabled) onionskins_render(frameTime);
|
||||
}
|
||||
|
||||
unbind();
|
||||
|
||||
ImGui::Image(texture, to_imvec2(size));
|
||||
|
||||
isPreviewHovered = ImGui::IsItemHovered();
|
||||
|
||||
if (animation && animation->triggers.isVisible && !isOnlyShowLayers)
|
||||
{
|
||||
if (auto trigger = animation->triggers.frame_generate(frameTime, anm2::TRIGGER); trigger.isVisible)
|
||||
{
|
||||
auto clipMin = ImGui::GetItemRectMin();
|
||||
auto clipMax = ImGui::GetItemRectMax();
|
||||
auto drawList = ImGui::GetWindowDrawList();
|
||||
auto textPos = to_imvec2(to_vec2(cursorScreenPos) + to_vec2(ImGui::GetStyle().WindowPadding));
|
||||
|
||||
drawList->PushClipRect(clipMin, clipMax);
|
||||
ImGui::PushFont(resources.fonts[font::BOLD].get(), font::SIZE_LARGE);
|
||||
drawList->AddText(textPos, ImGui::GetColorU32(TRIGGER_TEXT_COLOR),
|
||||
anm2.content.events.at(trigger.eventID).name.c_str());
|
||||
ImGui::PopFont();
|
||||
drawList->PopClipRect();
|
||||
}
|
||||
}
|
||||
|
||||
if (isPreviewHovered)
|
||||
{
|
||||
ImGui::SetKeyboardFocusHere(-1);
|
||||
|
||||
mousePos = position_translate(zoom, pan, to_vec2(ImGui::GetMousePos()) - to_vec2(cursorScreenPos));
|
||||
|
||||
auto isMouseClick = ImGui::IsMouseClicked(ImGuiMouseButton_Left);
|
||||
auto isMouseReleased = ImGui::IsMouseReleased(ImGuiMouseButton_Left);
|
||||
auto isMouseDown = ImGui::IsMouseDown(ImGuiMouseButton_Left);
|
||||
auto isMouseMiddleDown = ImGui::IsMouseDown(ImGuiMouseButton_Middle);
|
||||
auto isLeftPressed = ImGui::IsKeyPressed(ImGuiKey_LeftArrow, false);
|
||||
auto isRightPressed = ImGui::IsKeyPressed(ImGuiKey_RightArrow, false);
|
||||
auto isUpPressed = ImGui::IsKeyPressed(ImGuiKey_UpArrow, false);
|
||||
auto isDownPressed = ImGui::IsKeyPressed(ImGuiKey_DownArrow, false);
|
||||
auto isLeftReleased = ImGui::IsKeyReleased(ImGuiKey_LeftArrow);
|
||||
auto isRightReleased = ImGui::IsKeyReleased(ImGuiKey_RightArrow);
|
||||
auto isUpReleased = ImGui::IsKeyReleased(ImGuiKey_UpArrow);
|
||||
auto isDownReleased = ImGui::IsKeyReleased(ImGuiKey_DownArrow);
|
||||
auto isLeft = imgui::chord_repeating(ImGuiKey_LeftArrow);
|
||||
auto isRight = imgui::chord_repeating(ImGuiKey_RightArrow);
|
||||
auto isUp = imgui::chord_repeating(ImGuiKey_UpArrow);
|
||||
auto isDown = imgui::chord_repeating(ImGuiKey_DownArrow);
|
||||
auto isMouseRightDown = ImGui::IsMouseDown(ImGuiMouseButton_Right);
|
||||
auto mouseDelta = to_ivec2(ImGui::GetIO().MouseDelta);
|
||||
auto mouseWheel = ImGui::GetIO().MouseWheel;
|
||||
auto isZoomIn = imgui::chord_repeating(imgui::string_to_chord(settings.shortcutZoomIn));
|
||||
auto isZoomOut = imgui::chord_repeating(imgui::string_to_chord(settings.shortcutZoomOut));
|
||||
auto isMod = ImGui::IsKeyDown(ImGuiMod_Shift);
|
||||
auto frame = document.frame_get();
|
||||
auto useTool = tool;
|
||||
auto step = isMod ? canvas::STEP_FAST : canvas::STEP;
|
||||
auto isKeyPressed = isLeftPressed || isRightPressed || isUpPressed || isDownPressed;
|
||||
auto isKeyReleased = isLeftReleased || isRightReleased || isUpReleased || isDownReleased;
|
||||
auto isBegin = isMouseClick || isKeyPressed;
|
||||
auto isEnd = isMouseReleased || isKeyReleased;
|
||||
|
||||
if (isMouseMiddleDown) useTool = tool::PAN;
|
||||
if (tool == tool::MOVE && isMouseRightDown) useTool = tool::SCALE;
|
||||
if (tool == tool::SCALE && isMouseRightDown) useTool = tool::MOVE;
|
||||
|
||||
ImGui::SetMouseCursor(tool::INFO[useTool].cursor);
|
||||
|
||||
switch (useTool)
|
||||
{
|
||||
case tool::PAN:
|
||||
if (isMouseDown || isMouseMiddleDown) pan += mouseDelta;
|
||||
break;
|
||||
case tool::MOVE:
|
||||
if (!frame) break;
|
||||
if (isBegin) document.snapshot("Frame Position");
|
||||
if (isMouseDown) frame->position = mousePos;
|
||||
if (isLeft) frame->position.x -= step;
|
||||
if (isRight) frame->position.x += step;
|
||||
if (isUp) frame->position.y -= step;
|
||||
if (isDown) frame->position.y += step;
|
||||
if (isEnd) document.change(Document::FRAMES);
|
||||
break;
|
||||
case tool::SCALE:
|
||||
if (!frame) break;
|
||||
if (isBegin) document.snapshot("Frame Scale");
|
||||
if (isMouseDown) frame->scale += mouseDelta;
|
||||
if (isLeft) frame->scale.x -= step;
|
||||
if (isRight) frame->scale.x += step;
|
||||
if (isUp) frame->scale.y -= step;
|
||||
if (isDown) frame->scale.y += step;
|
||||
if (isEnd) document.change(Document::FRAMES);
|
||||
break;
|
||||
case tool::ROTATE:
|
||||
if (!frame) break;
|
||||
if (isBegin) document.snapshot("Frame Rotation");
|
||||
if (isMouseDown) frame->rotation += mouseDelta.y;
|
||||
if (isLeft || isDown) frame->rotation -= step;
|
||||
if (isUp || isRight) frame->rotation += step;
|
||||
if (isEnd) document.change(Document::FRAMES);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (mouseWheel != 0 || isZoomIn || isZoomOut)
|
||||
zoom_set(zoom, pan, vec2(mousePos), (mouseWheel > 0 || isZoomIn) ? zoomStep : -zoomStep);
|
||||
}
|
||||
}
|
||||
ImGui::End();
|
||||
|
||||
manager.progressPopup.trigger();
|
||||
|
||||
if (ImGui::BeginPopupModal(manager.progressPopup.label, &manager.progressPopup.isOpen, ImGuiWindowFlags_NoResize))
|
||||
{
|
||||
if (!animation) return;
|
||||
|
||||
auto& start = manager.recordingStart;
|
||||
auto& end = manager.recordingEnd;
|
||||
auto progress = (playback.time - start) / (end - start);
|
||||
|
||||
ImGui::ProgressBar(progress);
|
||||
|
||||
if (ImGui::Button("Cancel", ImVec2(ImGui::GetContentRegionAvail().x, 0)))
|
||||
{
|
||||
playback.isPlaying = false;
|
||||
manager.isRecording = false;
|
||||
manager.progressPopup.close();
|
||||
}
|
||||
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
}
|
||||
}
|
||||
21
src/imgui/window/animation_preview.h
Normal file
21
src/imgui/window/animation_preview.h
Normal file
@@ -0,0 +1,21 @@
|
||||
#pragma once
|
||||
|
||||
#include "canvas.h"
|
||||
#include "manager.h"
|
||||
#include "resources.h"
|
||||
#include "settings.h"
|
||||
|
||||
namespace anm2ed::imgui
|
||||
{
|
||||
class AnimationPreview : public Canvas
|
||||
{
|
||||
bool isPreviewHovered{};
|
||||
glm::ivec2 mousePos{};
|
||||
std::vector<resource::Texture> renderFrames{};
|
||||
|
||||
public:
|
||||
AnimationPreview();
|
||||
void tick(Manager&, Document&, Settings&);
|
||||
void update(Manager&, Settings&, Resources&);
|
||||
};
|
||||
}
|
||||
272
src/imgui/window/animations.cpp
Normal file
272
src/imgui/window/animations.cpp
Normal file
@@ -0,0 +1,272 @@
|
||||
#include "animations.h"
|
||||
|
||||
#include <ranges>
|
||||
|
||||
using namespace anm2ed::resource;
|
||||
using namespace anm2ed::types;
|
||||
|
||||
namespace anm2ed::imgui
|
||||
{
|
||||
void Animations::update(Manager& manager, Settings& settings, Resources& resources, Clipboard& clipboard)
|
||||
{
|
||||
auto& document = *manager.get();
|
||||
auto& anm2 = document.anm2;
|
||||
auto& reference = document.reference;
|
||||
auto& hovered = document.hoveredAnimation;
|
||||
auto& multiSelect = document.animationMultiSelect;
|
||||
auto& mergeMultiSelect = document.animationMergeMultiSelect;
|
||||
auto& mergeTarget = document.mergeTarget;
|
||||
|
||||
hovered = -1;
|
||||
|
||||
if (ImGui::Begin("Animations", &settings.windowIsAnimations))
|
||||
{
|
||||
auto childSize = size_without_footer_get();
|
||||
|
||||
if (ImGui::BeginChild("##Animations Child", childSize, ImGuiChildFlags_Borders))
|
||||
{
|
||||
multiSelect.start(anm2.animations.items.size());
|
||||
|
||||
for (auto [i, animation] : std::views::enumerate(anm2.animations.items))
|
||||
{
|
||||
ImGui::PushID(i);
|
||||
|
||||
auto isDefault = anm2.animations.defaultAnimation == animation.name;
|
||||
auto isReferenced = reference.animationIndex == i;
|
||||
|
||||
auto font = isDefault && isReferenced ? font::BOLD_ITALICS
|
||||
: isDefault ? font::BOLD
|
||||
: isReferenced ? font::ITALICS
|
||||
: font::REGULAR;
|
||||
|
||||
ImGui::PushFont(resources.fonts[font].get(), font::SIZE);
|
||||
ImGui::SetNextItemSelectionUserData(i);
|
||||
if (selectable_input_text(animation.name, std::format("###Document #{} Animation #{}", manager.selected, i),
|
||||
animation.name, multiSelect.contains(i)))
|
||||
document.animation_set(i);
|
||||
if (ImGui::IsItemHovered()) hovered = i;
|
||||
ImGui::PopFont();
|
||||
|
||||
if (ImGui::BeginItemTooltip())
|
||||
{
|
||||
ImGui::PushFont(resources.fonts[font::BOLD].get(), font::SIZE);
|
||||
ImGui::TextUnformatted(animation.name.c_str());
|
||||
ImGui::PopFont();
|
||||
|
||||
if (isDefault)
|
||||
{
|
||||
|
||||
ImGui::PushFont(resources.fonts[font::ITALICS].get(), font::SIZE);
|
||||
ImGui::TextUnformatted("(Default Animation)");
|
||||
ImGui::PopFont();
|
||||
}
|
||||
|
||||
ImGui::Text("Length: %d", animation.frameNum);
|
||||
ImGui::Text("Loop: %s", animation.isLoop ? "true" : "false");
|
||||
|
||||
ImGui::EndTooltip();
|
||||
}
|
||||
|
||||
if (ImGui::BeginDragDropSource())
|
||||
{
|
||||
static std::vector<int> selection;
|
||||
selection.assign(multiSelect.begin(), multiSelect.end());
|
||||
ImGui::SetDragDropPayload("Animation Drag Drop", selection.data(), selection.size() * sizeof(int));
|
||||
for (auto& i : selection)
|
||||
ImGui::TextUnformatted(anm2.animations.items[i].name.c_str());
|
||||
ImGui::EndDragDropSource();
|
||||
}
|
||||
|
||||
if (ImGui::BeginDragDropTarget())
|
||||
{
|
||||
if (auto payload = ImGui::AcceptDragDropPayload("Animation Drag Drop"))
|
||||
{
|
||||
auto payloadIndices = (int*)(payload->Data);
|
||||
auto payloadCount = payload->DataSize / sizeof(int);
|
||||
std::vector<int> indices(payloadIndices, payloadIndices + payloadCount);
|
||||
std::sort(indices.begin(), indices.end());
|
||||
document.animations_move(indices, i);
|
||||
}
|
||||
ImGui::EndDragDropTarget();
|
||||
}
|
||||
|
||||
ImGui::PopID();
|
||||
}
|
||||
|
||||
multiSelect.finish();
|
||||
|
||||
auto copy = [&]()
|
||||
{
|
||||
if (!multiSelect.empty())
|
||||
{
|
||||
std::string clipboardText{};
|
||||
for (auto& i : multiSelect)
|
||||
clipboardText += anm2.animations.items[i].to_string();
|
||||
clipboard.set(clipboardText);
|
||||
}
|
||||
else if (hovered > -1)
|
||||
clipboard.set(anm2.animations.items[hovered].to_string());
|
||||
};
|
||||
|
||||
auto cut = [&]()
|
||||
{
|
||||
copy();
|
||||
document.animations_remove();
|
||||
};
|
||||
|
||||
auto paste = [&]()
|
||||
{
|
||||
auto clipboardText = clipboard.get();
|
||||
document.animations_deserialize(clipboardText);
|
||||
};
|
||||
|
||||
if (shortcut(settings.shortcutCut, shortcut::FOCUSED)) cut();
|
||||
if (shortcut(settings.shortcutCopy, shortcut::FOCUSED)) copy();
|
||||
if (shortcut(settings.shortcutPaste, shortcut::FOCUSED)) paste();
|
||||
|
||||
if (ImGui::BeginPopupContextWindow("##Context Menu", ImGuiPopupFlags_MouseButtonRight))
|
||||
{
|
||||
ImGui::BeginDisabled(multiSelect.empty() && hovered == -1);
|
||||
if (ImGui::MenuItem("Cut", settings.shortcutCut.c_str())) cut();
|
||||
if (ImGui::MenuItem("Copy", settings.shortcutCopy.c_str())) copy();
|
||||
ImGui::EndDisabled();
|
||||
|
||||
ImGui::BeginDisabled(clipboard.is_empty());
|
||||
if (ImGui::MenuItem("Paste", settings.shortcutPaste.c_str())) paste();
|
||||
ImGui::EndDisabled();
|
||||
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
}
|
||||
ImGui::EndChild();
|
||||
|
||||
auto widgetSize = widget_size_with_row_get(5);
|
||||
|
||||
shortcut(settings.shortcutAdd);
|
||||
if (ImGui::Button("Add", widgetSize)) document.animation_add();
|
||||
set_item_tooltip_shortcut("Add a new animation.", settings.shortcutAdd);
|
||||
|
||||
ImGui::SameLine();
|
||||
|
||||
ImGui::BeginDisabled(multiSelect.empty());
|
||||
{
|
||||
shortcut(settings.shortcutDuplicate);
|
||||
if (ImGui::Button("Duplicate", widgetSize)) document.animation_duplicate();
|
||||
set_item_tooltip_shortcut("Duplicate the selected animation(s).", settings.shortcutDuplicate);
|
||||
|
||||
ImGui::SameLine();
|
||||
|
||||
if (shortcut(settings.shortcutMerge, shortcut::FOCUSED))
|
||||
if (multiSelect.size() > 0) document.animations_merge_quick();
|
||||
|
||||
ImGui::BeginDisabled(multiSelect.size() != 1);
|
||||
{
|
||||
if (ImGui::Button("Merge", widgetSize))
|
||||
{
|
||||
mergePopup.open();
|
||||
mergeMultiSelect.clear();
|
||||
mergeTarget = *multiSelect.begin();
|
||||
}
|
||||
}
|
||||
ImGui::EndDisabled();
|
||||
|
||||
set_item_tooltip_shortcut("Open the merge popup.\nUsing the shortcut will merge the animations with\nthe last "
|
||||
"configured merge settings.",
|
||||
settings.shortcutMerge);
|
||||
|
||||
ImGui::SameLine();
|
||||
|
||||
shortcut(settings.shortcutRemove);
|
||||
if (ImGui::Button("Remove", widgetSize)) document.animations_remove();
|
||||
set_item_tooltip_shortcut("Remove the selected animation(s).", settings.shortcutDuplicate);
|
||||
|
||||
ImGui::SameLine();
|
||||
|
||||
shortcut(settings.shortcutDefault);
|
||||
ImGui::BeginDisabled(multiSelect.size() != 1);
|
||||
if (ImGui::Button("Default", widgetSize)) document.animation_default();
|
||||
ImGui::EndDisabled();
|
||||
set_item_tooltip_shortcut("Set the selected animation as the default.", settings.shortcutDefault);
|
||||
}
|
||||
ImGui::EndDisabled();
|
||||
|
||||
mergePopup.trigger();
|
||||
|
||||
if (ImGui::BeginPopupModal(mergePopup.label, &mergePopup.isOpen, ImGuiWindowFlags_NoResize))
|
||||
{
|
||||
auto merge_close = [&]()
|
||||
{
|
||||
mergeMultiSelect.clear();
|
||||
mergePopup.close();
|
||||
};
|
||||
|
||||
auto& type = settings.mergeType;
|
||||
auto& isDeleteAnimationsAfter = settings.mergeIsDeleteAnimationsAfter;
|
||||
|
||||
auto footerSize = footer_size_get();
|
||||
auto optionsSize = child_size_get(2);
|
||||
auto deleteAfterSize = child_size_get();
|
||||
auto animationsSize =
|
||||
ImVec2(0, ImGui::GetContentRegionAvail().y -
|
||||
(optionsSize.y + deleteAfterSize.y + footerSize.y + ImGui::GetStyle().ItemSpacing.y * 3));
|
||||
|
||||
if (ImGui::BeginChild("Animations", animationsSize, ImGuiChildFlags_Borders))
|
||||
{
|
||||
mergeMultiSelect.start(anm2.animations.items.size());
|
||||
|
||||
for (auto [i, animation] : std::views::enumerate(anm2.animations.items))
|
||||
{
|
||||
ImGui::PushID(i);
|
||||
|
||||
ImGui::SetNextItemSelectionUserData(i);
|
||||
ImGui::Selectable(animation.name.c_str(), mergeMultiSelect.contains(i));
|
||||
|
||||
ImGui::PopID();
|
||||
}
|
||||
|
||||
mergeMultiSelect.finish();
|
||||
}
|
||||
ImGui::EndChild();
|
||||
|
||||
if (ImGui::BeginChild("Merge Options", optionsSize, ImGuiChildFlags_Borders))
|
||||
{
|
||||
auto size = ImVec2(optionsSize.x * 0.5f, optionsSize.y - ImGui::GetStyle().WindowPadding.y * 2);
|
||||
|
||||
if (ImGui::BeginChild("Merge Options 1", size))
|
||||
{
|
||||
ImGui::RadioButton("Append Frames", &type, merge::APPEND);
|
||||
ImGui::RadioButton("Prepend Frames", &type, merge::PREPEND);
|
||||
}
|
||||
ImGui::EndChild();
|
||||
|
||||
ImGui::SameLine();
|
||||
|
||||
if (ImGui::BeginChild("Merge Options 2", size))
|
||||
{
|
||||
ImGui::RadioButton("Replace Frames", &type, merge::REPLACE);
|
||||
ImGui::RadioButton("Ignore Frames", &type, merge::IGNORE);
|
||||
}
|
||||
ImGui::EndChild();
|
||||
}
|
||||
ImGui::EndChild();
|
||||
|
||||
if (ImGui::BeginChild("Merge Delete After", deleteAfterSize, ImGuiChildFlags_Borders))
|
||||
ImGui::Checkbox("Delete Animations After", &isDeleteAnimationsAfter);
|
||||
ImGui::EndChild();
|
||||
|
||||
auto widgetSize = widget_size_with_row_get(2);
|
||||
|
||||
if (ImGui::Button("Merge", widgetSize))
|
||||
{
|
||||
document.animations_merge((merge::Type)type, isDeleteAnimationsAfter);
|
||||
merge_close();
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Close", widgetSize)) merge_close();
|
||||
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
}
|
||||
ImGui::End();
|
||||
}
|
||||
}
|
||||
17
src/imgui/window/animations.h
Normal file
17
src/imgui/window/animations.h
Normal file
@@ -0,0 +1,17 @@
|
||||
#pragma once
|
||||
|
||||
#include "clipboard.h"
|
||||
#include "manager.h"
|
||||
#include "resources.h"
|
||||
#include "settings.h"
|
||||
|
||||
namespace anm2ed::imgui
|
||||
{
|
||||
class Animations
|
||||
{
|
||||
PopupHelper mergePopup{PopupHelper("Merge Animations")};
|
||||
|
||||
public:
|
||||
void update(Manager&, Settings&, Resources&, Clipboard&);
|
||||
};
|
||||
}
|
||||
160
src/imgui/window/events.cpp
Normal file
160
src/imgui/window/events.cpp
Normal file
@@ -0,0 +1,160 @@
|
||||
#include "events.h"
|
||||
|
||||
#include <ranges>
|
||||
|
||||
using namespace anm2ed::resource;
|
||||
using namespace anm2ed::types;
|
||||
|
||||
namespace anm2ed::imgui
|
||||
{
|
||||
void Events::update(Manager& manager, Settings& settings, Resources& resources, Clipboard& clipboard)
|
||||
{
|
||||
auto& document = *manager.get();
|
||||
auto& anm2 = document.anm2;
|
||||
auto& unused = document.unusedEventIDs;
|
||||
auto& hovered = document.hoveredEvent;
|
||||
auto& reference = document.referenceEvent;
|
||||
auto& multiSelect = document.eventMultiSelect;
|
||||
|
||||
hovered = -1;
|
||||
|
||||
if (ImGui::Begin("Events", &settings.windowIsEvents))
|
||||
{
|
||||
auto childSize = size_without_footer_get();
|
||||
|
||||
if (ImGui::BeginChild("##Events Child", childSize, true))
|
||||
{
|
||||
multiSelect.start(anm2.content.events.size());
|
||||
|
||||
for (auto& [id, event] : anm2.content.events)
|
||||
{
|
||||
ImGui::PushID(id);
|
||||
ImGui::SetNextItemSelectionUserData(id);
|
||||
ImGui::Selectable(event.name.c_str(), multiSelect.contains(id));
|
||||
if (ImGui::IsItemHovered())
|
||||
{
|
||||
hovered = id;
|
||||
if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left))
|
||||
{
|
||||
reference = id;
|
||||
editEvent = document.anm2.content.events[reference];
|
||||
propertiesPopup.open();
|
||||
}
|
||||
}
|
||||
|
||||
if (ImGui::BeginItemTooltip())
|
||||
{
|
||||
ImGui::PushFont(resources.fonts[font::BOLD].get(), font::SIZE);
|
||||
ImGui::TextUnformatted(event.name.c_str());
|
||||
ImGui::PopFont();
|
||||
ImGui::EndTooltip();
|
||||
}
|
||||
ImGui::PopID();
|
||||
}
|
||||
|
||||
multiSelect.finish();
|
||||
|
||||
auto copy = [&]()
|
||||
{
|
||||
if (!multiSelect.empty())
|
||||
{
|
||||
std::string clipboardText{};
|
||||
for (auto& id : multiSelect)
|
||||
clipboardText += anm2.content.events[id].to_string(id);
|
||||
clipboard.set(clipboardText);
|
||||
}
|
||||
else if (hovered > -1)
|
||||
clipboard.set(anm2.content.events[hovered].to_string(hovered));
|
||||
};
|
||||
|
||||
auto paste = [&](merge::Type type)
|
||||
{
|
||||
auto clipboardText = clipboard.get();
|
||||
document.events_deserialize(clipboardText, type);
|
||||
};
|
||||
|
||||
if (shortcut(settings.shortcutCopy, shortcut::FOCUSED)) copy();
|
||||
if (shortcut(settings.shortcutPaste, shortcut::FOCUSED)) paste(merge::APPEND);
|
||||
|
||||
if (ImGui::BeginPopupContextWindow("##Context Menu", ImGuiPopupFlags_MouseButtonRight))
|
||||
{
|
||||
ImGui::BeginDisabled();
|
||||
ImGui::MenuItem("Cut", settings.shortcutCut.c_str());
|
||||
ImGui::EndDisabled();
|
||||
|
||||
ImGui::BeginDisabled(multiSelect.empty() && hovered == -1);
|
||||
if (ImGui::MenuItem("Copy", settings.shortcutCopy.c_str())) copy();
|
||||
ImGui::EndDisabled();
|
||||
|
||||
ImGui::BeginDisabled(clipboard.is_empty());
|
||||
{
|
||||
if (ImGui::BeginMenu("Paste"))
|
||||
{
|
||||
if (ImGui::MenuItem("Append", settings.shortcutPaste.c_str())) paste(merge::APPEND);
|
||||
if (ImGui::MenuItem("Replace")) paste(merge::REPLACE);
|
||||
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
}
|
||||
ImGui::EndDisabled();
|
||||
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
}
|
||||
ImGui::EndChild();
|
||||
|
||||
auto widgetSize = widget_size_with_row_get(2);
|
||||
|
||||
shortcut(settings.shortcutAdd);
|
||||
if (ImGui::Button("Add", widgetSize))
|
||||
{
|
||||
reference = -1;
|
||||
editEvent = anm2::Event();
|
||||
propertiesPopup.open();
|
||||
}
|
||||
set_item_tooltip_shortcut("Add an event.", settings.shortcutAdd);
|
||||
ImGui::SameLine();
|
||||
|
||||
shortcut(settings.shortcutRemove);
|
||||
ImGui::BeginDisabled(unused.empty());
|
||||
if (ImGui::Button("Remove Unused", widgetSize)) document.events_remove_unused();
|
||||
ImGui::EndDisabled();
|
||||
set_item_tooltip_shortcut("Remove unused events (i.e., ones not used by any trigger in any animation.)",
|
||||
settings.shortcutRemove);
|
||||
}
|
||||
ImGui::End();
|
||||
|
||||
propertiesPopup.trigger();
|
||||
|
||||
if (ImGui::BeginPopupModal(propertiesPopup.label, &propertiesPopup.isOpen, ImGuiWindowFlags_NoResize))
|
||||
{
|
||||
auto childSize = child_size_get(2);
|
||||
auto& event = editEvent;
|
||||
|
||||
if (ImGui::BeginChild("Child", childSize, ImGuiChildFlags_Borders))
|
||||
{
|
||||
if (propertiesPopup.isJustOpened) ImGui::SetKeyboardFocusHere();
|
||||
input_text_string("Name", &event.name);
|
||||
ImGui::SetItemTooltip("Set the event's name.");
|
||||
combo_strings("Sound", &event.soundID, document.soundNames);
|
||||
ImGui::SetItemTooltip("Set the event sound; it will play when a trigger associated with this event activates.");
|
||||
}
|
||||
ImGui::EndChild();
|
||||
|
||||
auto widgetSize = widget_size_with_row_get(2);
|
||||
|
||||
if (ImGui::Button(reference == -1 ? "Add" : "Confirm", widgetSize))
|
||||
{
|
||||
document.event_set(event);
|
||||
propertiesPopup.close();
|
||||
}
|
||||
|
||||
ImGui::SameLine();
|
||||
|
||||
if (ImGui::Button("Cancel", widgetSize)) propertiesPopup.close();
|
||||
|
||||
propertiesPopup.end();
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
}
|
||||
}
|
||||
18
src/imgui/window/events.h
Normal file
18
src/imgui/window/events.h
Normal file
@@ -0,0 +1,18 @@
|
||||
#pragma once
|
||||
|
||||
#include "clipboard.h"
|
||||
#include "manager.h"
|
||||
#include "resources.h"
|
||||
#include "settings.h"
|
||||
|
||||
namespace anm2ed::imgui
|
||||
{
|
||||
class Events
|
||||
{
|
||||
anm2::Event editEvent{};
|
||||
PopupHelper propertiesPopup{PopupHelper("Event Properties", POPUP_SMALL_NO_HEIGHT)};
|
||||
|
||||
public:
|
||||
void update(Manager&, Settings&, Resources&, Clipboard&);
|
||||
};
|
||||
}
|
||||
125
src/imgui/window/frame_properties.cpp
Normal file
125
src/imgui/window/frame_properties.cpp
Normal file
@@ -0,0 +1,125 @@
|
||||
#include "frame_properties.h"
|
||||
|
||||
#include <ranges>
|
||||
|
||||
#include <glm/gtc/type_ptr.hpp>
|
||||
|
||||
#include "math_.h"
|
||||
#include "types.h"
|
||||
|
||||
using namespace anm2ed::util::math;
|
||||
using namespace anm2ed::types;
|
||||
using namespace glm;
|
||||
|
||||
namespace anm2ed::imgui
|
||||
{
|
||||
void FrameProperties::update(Manager& manager, Settings& settings)
|
||||
{
|
||||
if (ImGui::Begin("Frame Properties", &settings.windowIsFrameProperties))
|
||||
{
|
||||
auto& document = *manager.get();
|
||||
auto& anm2 = document.anm2;
|
||||
auto& reference = document.reference;
|
||||
auto& type = reference.itemType;
|
||||
auto frame = document.frame_get();
|
||||
auto useFrame = frame ? *frame : anm2::Frame();
|
||||
|
||||
ImGui::BeginDisabled(!frame);
|
||||
{
|
||||
if (type == anm2::TRIGGER)
|
||||
{
|
||||
std::vector<std::string> eventNames{};
|
||||
for (auto& event : anm2.content.events | std::views::values)
|
||||
eventNames.emplace_back(event.name);
|
||||
|
||||
if (imgui::combo_strings("Event", frame ? &useFrame.eventID : &dummy_value<int>(), eventNames))
|
||||
DOCUMENT_EDIT(document, "Trigger Event", Document::FRAMES, frame->eventID = useFrame.eventID);
|
||||
ImGui::SetItemTooltip("Change the event this trigger uses.");
|
||||
if (ImGui::InputInt("At Frame", frame ? &useFrame.atFrame : &dummy_value<int>(), imgui::STEP,
|
||||
imgui::STEP_FAST, !frame ? ImGuiInputTextFlags_DisplayEmptyRefVal : 0))
|
||||
DOCUMENT_EDIT(document, "Trigger At Frame", Document::FRAMES, frame->atFrame = useFrame.atFrame);
|
||||
ImGui::SetItemTooltip("Change the frame the trigger will be activated at.");
|
||||
}
|
||||
else
|
||||
{
|
||||
ImGui::BeginDisabled(type == anm2::ROOT || type == anm2::NULL_);
|
||||
{
|
||||
if (ImGui::InputFloat2("Crop", frame ? value_ptr(useFrame.crop) : &dummy_value<float>(),
|
||||
frame ? vec2_format_get(useFrame.crop) : ""))
|
||||
DOCUMENT_EDIT(document, "Frame Crop", Document::FRAMES, frame->crop = useFrame.crop);
|
||||
ImGui::SetItemTooltip("Change the crop position the frame uses.");
|
||||
|
||||
if (ImGui::InputFloat2("Size", frame ? value_ptr(useFrame.size) : &dummy_value<float>(),
|
||||
frame ? vec2_format_get(useFrame.size) : ""))
|
||||
DOCUMENT_EDIT(document, "Frame Size", Document::FRAMES, frame->size = useFrame.size);
|
||||
ImGui::SetItemTooltip("Change the size of the crop the frame uses.");
|
||||
}
|
||||
ImGui::EndDisabled();
|
||||
|
||||
if (ImGui::InputFloat2("Position", frame ? value_ptr(useFrame.position) : &dummy_value<float>(),
|
||||
frame ? vec2_format_get(useFrame.position) : ""))
|
||||
DOCUMENT_EDIT(document, "Frame Position", Document::FRAMES, frame->position = useFrame.position);
|
||||
ImGui::SetItemTooltip("Change the position of the frame.");
|
||||
|
||||
ImGui::BeginDisabled(type == anm2::ROOT || type == anm2::NULL_);
|
||||
{
|
||||
if (ImGui::InputFloat2("Pivot", frame ? value_ptr(useFrame.pivot) : &dummy_value<float>(),
|
||||
frame ? vec2_format_get(useFrame.pivot) : ""))
|
||||
DOCUMENT_EDIT(document, "Frame Pivot", Document::FRAMES, frame->pivot = useFrame.pivot);
|
||||
ImGui::SetItemTooltip("Change the pivot of the frame; i.e., where it is centered.");
|
||||
}
|
||||
ImGui::EndDisabled();
|
||||
|
||||
if (ImGui::InputFloat2("Scale", frame ? value_ptr(useFrame.scale) : &dummy_value<float>(),
|
||||
frame ? vec2_format_get(useFrame.scale) : ""))
|
||||
DOCUMENT_EDIT(document, "Frame Scale", Document::FRAMES, frame->scale = useFrame.scale);
|
||||
ImGui::SetItemTooltip("Change the scale of the frame, in percent.");
|
||||
|
||||
if (ImGui::InputFloat("Rotation", frame ? &useFrame.rotation : &dummy_value<float>(), imgui::STEP,
|
||||
imgui::STEP_FAST, frame ? float_format_get(useFrame.rotation) : ""))
|
||||
DOCUMENT_EDIT(document, "Frame Rotation", Document::FRAMES, frame->rotation = useFrame.rotation);
|
||||
ImGui::SetItemTooltip("Change the rotation of the frame.");
|
||||
|
||||
if (ImGui::InputInt("Duration", frame ? &useFrame.delay : &dummy_value<int>(), imgui::STEP, imgui::STEP_FAST,
|
||||
!frame ? ImGuiInputTextFlags_DisplayEmptyRefVal : 0))
|
||||
DOCUMENT_EDIT(document, "Frame Duration", Document::FRAMES, frame->delay = useFrame.delay);
|
||||
ImGui::SetItemTooltip("Change how long the frame lasts.");
|
||||
|
||||
if (ImGui::ColorEdit4("Tint", frame ? value_ptr(useFrame.tint) : &dummy_value<float>()))
|
||||
DOCUMENT_EDIT(document, "Frame Tint", Document::FRAMES, frame->tint = useFrame.tint);
|
||||
ImGui::SetItemTooltip("Change the tint of the frame.");
|
||||
|
||||
if (ImGui::ColorEdit3("Color Offset", frame ? value_ptr(useFrame.colorOffset) : &dummy_value<float>()))
|
||||
DOCUMENT_EDIT(document, "Frame Color Offset", Document::FRAMES, frame->colorOffset = useFrame.colorOffset);
|
||||
ImGui::SetItemTooltip("Change the color added onto the frame.");
|
||||
|
||||
if (ImGui::Checkbox("Visible", frame ? &useFrame.isVisible : &dummy_value<bool>()))
|
||||
DOCUMENT_EDIT(document, "Frame Visibility", Document::FRAMES, frame->isVisible = useFrame.isVisible);
|
||||
ImGui::SetItemTooltip("Toggle the frame's visibility.");
|
||||
|
||||
ImGui::SameLine();
|
||||
|
||||
if (ImGui::Checkbox("Interpolated", frame ? &useFrame.isInterpolated : &dummy_value<bool>()))
|
||||
DOCUMENT_EDIT(document, "Frame Interpolation", Document::FRAMES,
|
||||
frame->isInterpolated = useFrame.isInterpolated);
|
||||
ImGui::SetItemTooltip(
|
||||
"Toggle the frame interpolating; i.e., blending its values into the next frame based on the time.");
|
||||
|
||||
auto widgetSize = imgui::widget_size_with_row_get(2);
|
||||
|
||||
if (ImGui::Button("Flip X", widgetSize))
|
||||
DOCUMENT_EDIT(document, "Frame Flip X", Document::FRAMES, frame->scale.x = -frame->scale.x);
|
||||
ImGui::SetItemTooltip("%s", "Flip the horizontal scale of the frame, to cheat mirroring the frame "
|
||||
"horizontally.\n(Note: the format does not support mirroring.)");
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Flip Y", widgetSize))
|
||||
DOCUMENT_EDIT(document, "Frame Flip Y", Document::FRAMES, frame->scale.y = -frame->scale.y);
|
||||
ImGui::SetItemTooltip("%s", "Flip the vertical scale of the frame, to cheat mirroring the frame "
|
||||
"vertically.\n(Note: the format does not support mirroring.)");
|
||||
}
|
||||
}
|
||||
ImGui::EndDisabled();
|
||||
}
|
||||
ImGui::End();
|
||||
}
|
||||
}
|
||||
13
src/imgui/window/frame_properties.h
Normal file
13
src/imgui/window/frame_properties.h
Normal file
@@ -0,0 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
#include "manager.h"
|
||||
#include "settings.h"
|
||||
|
||||
namespace anm2ed::imgui
|
||||
{
|
||||
class FrameProperties
|
||||
{
|
||||
public:
|
||||
void update(Manager&, Settings&);
|
||||
};
|
||||
}
|
||||
158
src/imgui/window/layers.cpp
Normal file
158
src/imgui/window/layers.cpp
Normal file
@@ -0,0 +1,158 @@
|
||||
#include "layers.h"
|
||||
|
||||
#include <ranges>
|
||||
|
||||
using namespace anm2ed::resource;
|
||||
using namespace anm2ed::types;
|
||||
|
||||
namespace anm2ed::imgui
|
||||
{
|
||||
void Layers::update(Manager& manager, Settings& settings, Resources& resources, Clipboard& clipboard)
|
||||
{
|
||||
auto& document = *manager.get();
|
||||
auto& anm2 = document.anm2;
|
||||
auto& reference = document.referenceLayer;
|
||||
auto& unused = document.unusedLayerIDs;
|
||||
auto& hovered = document.hoveredLayer;
|
||||
auto& multiSelect = document.layersMultiSelect;
|
||||
auto& propertiesPopup = manager.layerPropertiesPopup;
|
||||
|
||||
hovered = -1;
|
||||
|
||||
if (ImGui::Begin("Layers", &settings.windowIsLayers))
|
||||
{
|
||||
auto childSize = size_without_footer_get();
|
||||
|
||||
if (ImGui::BeginChild("##Layers Child", childSize, true))
|
||||
{
|
||||
multiSelect.start(anm2.content.layers.size());
|
||||
|
||||
for (auto& [id, layer] : anm2.content.layers)
|
||||
{
|
||||
auto isSelected = multiSelect.contains(id);
|
||||
|
||||
ImGui::PushID(id);
|
||||
|
||||
ImGui::SetNextItemSelectionUserData(id);
|
||||
ImGui::Selectable(std::format(anm2::LAYER_FORMAT, id, layer.name, layer.spritesheetID).c_str(), isSelected);
|
||||
if (ImGui::IsItemHovered())
|
||||
{
|
||||
hovered = id;
|
||||
if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) manager.layer_properties_open(id);
|
||||
}
|
||||
else
|
||||
hovered = -1;
|
||||
|
||||
if (ImGui::BeginItemTooltip())
|
||||
{
|
||||
ImGui::PushFont(resources.fonts[font::BOLD].get(), font::SIZE);
|
||||
ImGui::TextUnformatted(layer.name.c_str());
|
||||
ImGui::PopFont();
|
||||
ImGui::Text("ID: %d", id);
|
||||
ImGui::Text("Spritesheet ID: %d", layer.spritesheetID);
|
||||
ImGui::EndTooltip();
|
||||
}
|
||||
ImGui::PopID();
|
||||
}
|
||||
|
||||
multiSelect.finish();
|
||||
|
||||
auto copy = [&]()
|
||||
{
|
||||
if (!multiSelect.empty())
|
||||
{
|
||||
std::string clipboardText{};
|
||||
for (auto& id : multiSelect)
|
||||
clipboardText += anm2.content.layers[id].to_string(id);
|
||||
clipboard.set(clipboardText);
|
||||
}
|
||||
else if (hovered > -1)
|
||||
clipboard.set(anm2.content.layers[hovered].to_string(hovered));
|
||||
};
|
||||
|
||||
auto paste = [&](merge::Type type)
|
||||
{
|
||||
auto clipboardText = clipboard.get();
|
||||
document.layers_deserialize(clipboardText, type);
|
||||
};
|
||||
|
||||
if (shortcut(settings.shortcutCopy, shortcut::FOCUSED)) copy();
|
||||
if (shortcut(settings.shortcutPaste, shortcut::FOCUSED)) paste(merge::APPEND);
|
||||
|
||||
if (ImGui::BeginPopupContextWindow("##Context Menu", ImGuiPopupFlags_MouseButtonRight))
|
||||
{
|
||||
ImGui::BeginDisabled();
|
||||
ImGui::MenuItem("Cut", settings.shortcutCut.c_str());
|
||||
ImGui::EndDisabled();
|
||||
|
||||
ImGui::BeginDisabled(multiSelect.empty() && hovered == -1);
|
||||
if (ImGui::MenuItem("Copy", settings.shortcutCopy.c_str())) copy();
|
||||
ImGui::EndDisabled();
|
||||
|
||||
ImGui::BeginDisabled(clipboard.is_empty());
|
||||
{
|
||||
if (ImGui::BeginMenu("Paste"))
|
||||
{
|
||||
if (ImGui::MenuItem("Append", settings.shortcutPaste.c_str())) paste(merge::APPEND);
|
||||
if (ImGui::MenuItem("Replace")) paste(merge::REPLACE);
|
||||
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
}
|
||||
ImGui::EndDisabled();
|
||||
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
}
|
||||
ImGui::EndChild();
|
||||
|
||||
auto widgetSize = widget_size_with_row_get(2);
|
||||
|
||||
shortcut(settings.shortcutAdd);
|
||||
if (ImGui::Button("Add", widgetSize)) manager.layer_properties_open();
|
||||
set_item_tooltip_shortcut("Add a layer.", settings.shortcutAdd);
|
||||
ImGui::SameLine();
|
||||
|
||||
shortcut(settings.shortcutRemove);
|
||||
ImGui::BeginDisabled(unused.empty());
|
||||
if (ImGui::Button("Remove Unused", widgetSize)) document.layers_remove_unused();
|
||||
ImGui::EndDisabled();
|
||||
set_item_tooltip_shortcut("Remove unused layers (i.e., ones not used in any animation.)",
|
||||
settings.shortcutRemove);
|
||||
}
|
||||
ImGui::End();
|
||||
|
||||
manager.layer_properties_trigger();
|
||||
|
||||
if (ImGui::BeginPopupModal(propertiesPopup.label, &propertiesPopup.isOpen, ImGuiWindowFlags_NoResize))
|
||||
{
|
||||
auto childSize = child_size_get(2);
|
||||
auto& layer = manager.editLayer;
|
||||
|
||||
if (ImGui::BeginChild("Child", childSize, ImGuiChildFlags_Borders))
|
||||
{
|
||||
if (propertiesPopup.isJustOpened) ImGui::SetKeyboardFocusHere();
|
||||
input_text_string("Name", &layer.name);
|
||||
ImGui::SetItemTooltip("Set the item's name.");
|
||||
combo_strings("Spritesheet", &layer.spritesheetID, document.spritesheetNames);
|
||||
ImGui::SetItemTooltip("Set the layer item's spritesheet.");
|
||||
}
|
||||
ImGui::EndChild();
|
||||
|
||||
auto widgetSize = widget_size_with_row_get(2);
|
||||
|
||||
if (ImGui::Button(reference == -1 ? "Add" : "Confirm", widgetSize))
|
||||
{
|
||||
document.layer_set(layer);
|
||||
manager.layer_properties_close();
|
||||
}
|
||||
|
||||
ImGui::SameLine();
|
||||
|
||||
if (ImGui::Button("Cancel", widgetSize)) manager.layer_properties_close();
|
||||
|
||||
manager.layer_properties_end();
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
}
|
||||
}
|
||||
15
src/imgui/window/layers.h
Normal file
15
src/imgui/window/layers.h
Normal file
@@ -0,0 +1,15 @@
|
||||
#pragma once
|
||||
|
||||
#include "clipboard.h"
|
||||
#include "manager.h"
|
||||
#include "resources.h"
|
||||
#include "settings.h"
|
||||
|
||||
namespace anm2ed::imgui
|
||||
{
|
||||
class Layers
|
||||
{
|
||||
public:
|
||||
void update(Manager&, Settings&, Resources&, Clipboard&);
|
||||
};
|
||||
}
|
||||
159
src/imgui/window/nulls.cpp
Normal file
159
src/imgui/window/nulls.cpp
Normal file
@@ -0,0 +1,159 @@
|
||||
#include "nulls.h"
|
||||
|
||||
#include <ranges>
|
||||
|
||||
using namespace anm2ed::resource;
|
||||
using namespace anm2ed::types;
|
||||
|
||||
namespace anm2ed::imgui
|
||||
{
|
||||
void Nulls::update(Manager& manager, Settings& settings, Resources& resources, Clipboard& clipboard)
|
||||
{
|
||||
auto& document = *manager.get();
|
||||
auto& anm2 = document.anm2;
|
||||
auto& reference = document.referenceNull;
|
||||
auto& unused = document.unusedNullIDs;
|
||||
auto& hovered = document.hoveredNull;
|
||||
auto& multiSelect = document.nullMultiSelect;
|
||||
auto& propertiesPopup = manager.nullPropertiesPopup;
|
||||
|
||||
hovered = -1;
|
||||
|
||||
if (ImGui::Begin("Nulls", &settings.windowIsNulls))
|
||||
{
|
||||
auto childSize = size_without_footer_get();
|
||||
|
||||
if (ImGui::BeginChild("##Nulls Child", childSize, true))
|
||||
{
|
||||
multiSelect.start(anm2.content.nulls.size());
|
||||
|
||||
for (auto& [id, null] : anm2.content.nulls)
|
||||
{
|
||||
auto isSelected = multiSelect.contains(id);
|
||||
auto isReferenced = reference == id;
|
||||
|
||||
ImGui::PushID(id);
|
||||
ImGui::SetNextItemSelectionUserData(id);
|
||||
if (isReferenced) ImGui::PushFont(resources.fonts[font::ITALICS].get(), font::SIZE);
|
||||
ImGui::Selectable(std::format(anm2::NULL_FORMAT, id, null.name).c_str(), isSelected);
|
||||
if (ImGui::IsItemHovered())
|
||||
{
|
||||
hovered = id;
|
||||
if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) manager.null_properties_open(id);
|
||||
}
|
||||
|
||||
if (isReferenced) ImGui::PopFont();
|
||||
|
||||
if (ImGui::BeginItemTooltip())
|
||||
{
|
||||
ImGui::PushFont(resources.fonts[font::BOLD].get(), font::SIZE);
|
||||
ImGui::TextUnformatted(null.name.c_str());
|
||||
ImGui::PopFont();
|
||||
ImGui::Text("ID: %d", id);
|
||||
ImGui::EndTooltip();
|
||||
}
|
||||
ImGui::PopID();
|
||||
}
|
||||
|
||||
multiSelect.finish();
|
||||
|
||||
auto copy = [&]()
|
||||
{
|
||||
if (!multiSelect.empty())
|
||||
{
|
||||
std::string clipboardText{};
|
||||
for (auto& id : multiSelect)
|
||||
clipboardText += anm2.content.nulls[id].to_string(id);
|
||||
clipboard.set(clipboardText);
|
||||
}
|
||||
else if (hovered > -1)
|
||||
clipboard.set(anm2.content.nulls[hovered].to_string(hovered));
|
||||
};
|
||||
|
||||
auto paste = [&](merge::Type type)
|
||||
{
|
||||
auto clipboardText = clipboard.get();
|
||||
document.nulls_deserialize(clipboardText, type);
|
||||
};
|
||||
|
||||
if (shortcut(settings.shortcutCopy, shortcut::FOCUSED)) copy();
|
||||
if (shortcut(settings.shortcutPaste, shortcut::FOCUSED)) paste(merge::APPEND);
|
||||
|
||||
if (ImGui::BeginPopupContextWindow("##Context Menu", ImGuiPopupFlags_MouseButtonRight))
|
||||
{
|
||||
ImGui::BeginDisabled();
|
||||
ImGui::MenuItem("Cut", settings.shortcutCut.c_str());
|
||||
ImGui::EndDisabled();
|
||||
|
||||
ImGui::BeginDisabled(multiSelect.empty() && hovered == -1);
|
||||
if (ImGui::MenuItem("Copy", settings.shortcutCopy.c_str())) copy();
|
||||
ImGui::EndDisabled();
|
||||
|
||||
ImGui::BeginDisabled(clipboard.is_empty());
|
||||
{
|
||||
if (ImGui::BeginMenu("Paste"))
|
||||
{
|
||||
if (ImGui::MenuItem("Append", settings.shortcutPaste.c_str())) paste(merge::APPEND);
|
||||
if (ImGui::MenuItem("Replace")) paste(merge::REPLACE);
|
||||
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
}
|
||||
ImGui::EndDisabled();
|
||||
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
}
|
||||
ImGui::EndChild();
|
||||
|
||||
auto widgetSize = widget_size_with_row_get(2);
|
||||
|
||||
shortcut(settings.shortcutAdd);
|
||||
if (ImGui::Button("Add", widgetSize)) manager.null_properties_open();
|
||||
set_item_tooltip_shortcut("Add a null.", settings.shortcutAdd);
|
||||
ImGui::SameLine();
|
||||
|
||||
shortcut(settings.shortcutRemove);
|
||||
ImGui::BeginDisabled(unused.empty());
|
||||
if (ImGui::Button("Remove Unused", widgetSize)) document.nulls_remove_unused();
|
||||
ImGui::EndDisabled();
|
||||
set_item_tooltip_shortcut("Remove unused nulls (i.e., ones not used in any animation.)", settings.shortcutRemove);
|
||||
}
|
||||
ImGui::End();
|
||||
|
||||
manager.null_properties_trigger();
|
||||
|
||||
if (ImGui::BeginPopupModal(propertiesPopup.label, &propertiesPopup.isOpen, ImGuiWindowFlags_NoResize))
|
||||
{
|
||||
auto childSize = child_size_get(2);
|
||||
auto& null = manager.editNull;
|
||||
|
||||
if (ImGui::BeginChild("Child", childSize, ImGuiChildFlags_Borders))
|
||||
{
|
||||
if (propertiesPopup.isJustOpened) ImGui::SetKeyboardFocusHere();
|
||||
input_text_string("Name", &null.name);
|
||||
ImGui::SetItemTooltip("Set the null's name.");
|
||||
|
||||
ImGui::Checkbox("Rect", &null.isShowRect);
|
||||
ImGui::SetItemTooltip("The null will have a rectangle show around it.");
|
||||
}
|
||||
ImGui::EndChild();
|
||||
|
||||
auto widgetSize = widget_size_with_row_get(2);
|
||||
|
||||
if (ImGui::Button(reference == -1 ? "Add" : "Confirm", widgetSize))
|
||||
{
|
||||
document.null_set(null);
|
||||
manager.null_properties_close();
|
||||
}
|
||||
|
||||
ImGui::SameLine();
|
||||
|
||||
if (ImGui::Button("Cancel", widgetSize)) manager.null_properties_close();
|
||||
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
|
||||
manager.null_properties_end();
|
||||
}
|
||||
}
|
||||
15
src/imgui/window/nulls.h
Normal file
15
src/imgui/window/nulls.h
Normal file
@@ -0,0 +1,15 @@
|
||||
#pragma once
|
||||
|
||||
#include "clipboard.h"
|
||||
#include "manager.h"
|
||||
#include "resources.h"
|
||||
#include "settings.h"
|
||||
|
||||
namespace anm2ed::imgui
|
||||
{
|
||||
class Nulls
|
||||
{
|
||||
public:
|
||||
void update(Manager&, Settings&, Resources&, Clipboard&);
|
||||
};
|
||||
}
|
||||
55
src/imgui/window/onionskin.cpp
Normal file
55
src/imgui/window/onionskin.cpp
Normal file
@@ -0,0 +1,55 @@
|
||||
#include "onionskin.h"
|
||||
|
||||
#include <glm/gtc/type_ptr.hpp>
|
||||
|
||||
#include "imgui_.h"
|
||||
|
||||
using namespace anm2ed::types;
|
||||
using namespace glm;
|
||||
|
||||
namespace anm2ed::imgui
|
||||
{
|
||||
constexpr auto FRAMES_MAX = 100;
|
||||
|
||||
void Onionskin::update(Settings& settings)
|
||||
{
|
||||
auto& isEnabled = settings.onionskinIsEnabled;
|
||||
auto& beforeCount = settings.onionskinBeforeCount;
|
||||
auto& beforeColor = settings.onionskinBeforeColor;
|
||||
auto& afterCount = settings.onionskinAfterCount;
|
||||
auto& afterColor = settings.onionskinAfterColor;
|
||||
auto& drawOrder = settings.onionskinDrawOrder;
|
||||
|
||||
if (ImGui::Begin("Onionskin", &settings.windowIsOnionskin))
|
||||
{
|
||||
auto configure_widgets = [&](const char* separator, int& frames, vec3& color)
|
||||
{
|
||||
ImGui::PushID(separator);
|
||||
ImGui::SeparatorText(separator);
|
||||
input_int_range("Frames", frames, 0, FRAMES_MAX);
|
||||
ImGui::SetItemTooltip("Change the amount of frames this onionskin will use.");
|
||||
ImGui::ColorEdit3("Color", value_ptr(color));
|
||||
ImGui::SetItemTooltip("Change the color of the frames this onionskin will use.");
|
||||
ImGui::PopID();
|
||||
};
|
||||
|
||||
ImGui::Checkbox("Enabled", &isEnabled);
|
||||
set_item_tooltip_shortcut("Toggle onionskinning.", settings.shortcutOnionskin);
|
||||
|
||||
configure_widgets("Before", beforeCount, beforeColor);
|
||||
configure_widgets("After", afterCount, afterColor);
|
||||
|
||||
ImGui::Text("Draw Order");
|
||||
ImGui::SameLine();
|
||||
ImGui::RadioButton("Below", &drawOrder, draw_order::BELOW);
|
||||
ImGui::SetItemTooltip("The onionskin frames will draw below the original frames.");
|
||||
ImGui::SameLine();
|
||||
ImGui::RadioButton("Above", &drawOrder, draw_order::ABOVE);
|
||||
ImGui::SetItemTooltip("The onionskin frames will draw above the original frames.");
|
||||
}
|
||||
ImGui::End();
|
||||
|
||||
if (shortcut(settings.shortcutOnionskin, shortcut::GLOBAL)) isEnabled = !isEnabled;
|
||||
}
|
||||
|
||||
}
|
||||
12
src/imgui/window/onionskin.h
Normal file
12
src/imgui/window/onionskin.h
Normal file
@@ -0,0 +1,12 @@
|
||||
#pragma once
|
||||
|
||||
#include "settings.h"
|
||||
|
||||
namespace anm2ed::imgui
|
||||
{
|
||||
class Onionskin
|
||||
{
|
||||
public:
|
||||
void update(Settings&);
|
||||
};
|
||||
}
|
||||
135
src/imgui/window/sounds.cpp
Normal file
135
src/imgui/window/sounds.cpp
Normal file
@@ -0,0 +1,135 @@
|
||||
#include "sounds.h"
|
||||
|
||||
#include <ranges>
|
||||
|
||||
using namespace anm2ed::dialog;
|
||||
using namespace anm2ed::types;
|
||||
using namespace anm2ed::resource;
|
||||
|
||||
namespace anm2ed::imgui
|
||||
{
|
||||
void Sounds::update(Manager& manager, Settings& settings, Resources& resources, Dialog& dialog, Clipboard& clipboard)
|
||||
{
|
||||
auto& document = *manager.get();
|
||||
auto& anm2 = document.anm2;
|
||||
auto& reference = document.referenceNull;
|
||||
auto& unused = document.unusedNullIDs;
|
||||
auto& hovered = document.hoveredNull;
|
||||
auto& multiSelect = document.soundMultiSelect;
|
||||
|
||||
hovered = -1;
|
||||
|
||||
if (ImGui::Begin("Sounds", &settings.windowIsSounds))
|
||||
{
|
||||
auto childSize = imgui::size_without_footer_get();
|
||||
|
||||
if (ImGui::BeginChild("##Sounds Child", childSize, true))
|
||||
{
|
||||
multiSelect.start(anm2.content.sounds.size());
|
||||
|
||||
for (auto& [id, sound] : anm2.content.sounds)
|
||||
{
|
||||
auto isSelected = multiSelect.contains(id);
|
||||
auto isReferenced = reference == id;
|
||||
|
||||
ImGui::PushID(id);
|
||||
ImGui::SetNextItemSelectionUserData(id);
|
||||
if (isReferenced) ImGui::PushFont(resources.fonts[font::ITALICS].get(), font::SIZE);
|
||||
if (ImGui::Selectable(std::format(anm2::SOUND_FORMAT, id, sound.path.string()).c_str(), isSelected))
|
||||
sound.audio.play();
|
||||
if (ImGui::IsItemHovered())
|
||||
{
|
||||
hovered = id;
|
||||
if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left))
|
||||
;
|
||||
}
|
||||
|
||||
if (isReferenced) ImGui::PopFont();
|
||||
|
||||
if (ImGui::BeginItemTooltip())
|
||||
{
|
||||
ImGui::PushFont(resources.fonts[font::BOLD].get(), font::SIZE);
|
||||
ImGui::TextUnformatted(sound.path.c_str());
|
||||
ImGui::PopFont();
|
||||
ImGui::Text("ID: %d", id);
|
||||
ImGui::Text("Click to play.");
|
||||
ImGui::EndTooltip();
|
||||
}
|
||||
ImGui::PopID();
|
||||
}
|
||||
|
||||
multiSelect.finish();
|
||||
|
||||
auto copy = [&]()
|
||||
{
|
||||
if (!multiSelect.empty())
|
||||
{
|
||||
std::string clipboardText{};
|
||||
for (auto& id : multiSelect)
|
||||
clipboardText += anm2.content.sounds[id].to_string(id);
|
||||
clipboard.set(clipboardText);
|
||||
}
|
||||
else if (hovered > -1)
|
||||
clipboard.set(anm2.content.sounds[hovered].to_string(hovered));
|
||||
};
|
||||
|
||||
auto paste = [&](merge::Type type)
|
||||
{
|
||||
auto clipboardText = clipboard.get();
|
||||
document.sounds_deserialize(clipboardText, type);
|
||||
};
|
||||
|
||||
if (imgui::shortcut(settings.shortcutCopy, shortcut::FOCUSED)) copy();
|
||||
if (imgui::shortcut(settings.shortcutPaste, shortcut::FOCUSED)) paste(merge::APPEND);
|
||||
|
||||
if (ImGui::BeginPopupContextWindow("##Context Menu", ImGuiPopupFlags_MouseButtonRight))
|
||||
{
|
||||
ImGui::BeginDisabled();
|
||||
ImGui::MenuItem("Cut", settings.shortcutCut.c_str());
|
||||
ImGui::EndDisabled();
|
||||
|
||||
ImGui::BeginDisabled(multiSelect.empty() && hovered == -1);
|
||||
if (ImGui::MenuItem("Copy", settings.shortcutCopy.c_str())) copy();
|
||||
ImGui::EndDisabled();
|
||||
|
||||
ImGui::BeginDisabled(clipboard.is_empty());
|
||||
{
|
||||
if (ImGui::BeginMenu("Paste"))
|
||||
{
|
||||
if (ImGui::MenuItem("Append", settings.shortcutPaste.c_str())) paste(merge::APPEND);
|
||||
if (ImGui::MenuItem("Replace")) paste(merge::REPLACE);
|
||||
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
}
|
||||
ImGui::EndDisabled();
|
||||
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
}
|
||||
ImGui::EndChild();
|
||||
|
||||
auto widgetSize = imgui::widget_size_with_row_get(2);
|
||||
|
||||
imgui::shortcut(settings.shortcutAdd);
|
||||
if (ImGui::Button("Add", widgetSize)) dialog.file_open(dialog::SOUND_OPEN);
|
||||
imgui::set_item_tooltip_shortcut("Add a sound.", settings.shortcutAdd);
|
||||
ImGui::SameLine();
|
||||
|
||||
imgui::shortcut(settings.shortcutRemove);
|
||||
ImGui::BeginDisabled(unused.empty());
|
||||
if (ImGui::Button("Remove Unused", widgetSize))
|
||||
;
|
||||
ImGui::EndDisabled();
|
||||
imgui::set_item_tooltip_shortcut("Remove unused sounds (i.e., ones not used in any trigger.)",
|
||||
settings.shortcutRemove);
|
||||
}
|
||||
ImGui::End();
|
||||
|
||||
if (dialog.is_selected(dialog::SOUND_OPEN))
|
||||
{
|
||||
document.sound_add(dialog.path);
|
||||
dialog.reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
16
src/imgui/window/sounds.h
Normal file
16
src/imgui/window/sounds.h
Normal file
@@ -0,0 +1,16 @@
|
||||
#pragma once
|
||||
|
||||
#include "clipboard.h"
|
||||
#include "dialog.h"
|
||||
#include "manager.h"
|
||||
#include "resources.h"
|
||||
#include "settings.h"
|
||||
|
||||
namespace anm2ed::imgui
|
||||
{
|
||||
class Sounds
|
||||
{
|
||||
public:
|
||||
void update(Manager&, Settings&, Resources&, Dialog&, Clipboard&);
|
||||
};
|
||||
}
|
||||
230
src/imgui/window/spritesheet_editor.cpp
Normal file
230
src/imgui/window/spritesheet_editor.cpp
Normal file
@@ -0,0 +1,230 @@
|
||||
#include "spritesheet_editor.h"
|
||||
|
||||
#include "math_.h"
|
||||
#include "tool.h"
|
||||
#include "types.h"
|
||||
|
||||
using namespace anm2ed::canvas;
|
||||
using namespace anm2ed::types;
|
||||
using namespace anm2ed::resource;
|
||||
using namespace anm2ed::util;
|
||||
using namespace glm;
|
||||
|
||||
namespace anm2ed::imgui
|
||||
{
|
||||
constexpr auto PIVOT_COLOR = color::PINK;
|
||||
|
||||
SpritesheetEditor::SpritesheetEditor() : Canvas(vec2())
|
||||
{
|
||||
}
|
||||
|
||||
void SpritesheetEditor::update(Manager& manager, Settings& settings, Resources& resources)
|
||||
{
|
||||
auto& document = *manager.get();
|
||||
auto& anm2 = document.anm2;
|
||||
auto& reference = document.reference;
|
||||
auto& referenceSpritesheet = document.referenceSpritesheet;
|
||||
auto& pan = document.editorPan;
|
||||
auto& zoom = document.editorZoom;
|
||||
auto& backgroundColor = settings.editorBackgroundColor;
|
||||
auto& gridColor = settings.editorGridColor;
|
||||
auto& gridSize = settings.editorGridSize;
|
||||
auto& gridOffset = settings.editorGridOffset;
|
||||
auto& isGrid = settings.editorIsGrid;
|
||||
auto& zoomStep = settings.viewZoomStep;
|
||||
auto& isBorder = settings.editorIsBorder;
|
||||
auto spritesheet = document.spritesheet_get();
|
||||
auto& tool = settings.tool;
|
||||
auto& shaderGrid = resources.shaders[shader::GRID];
|
||||
auto& shaderTexture = resources.shaders[shader::TEXTURE];
|
||||
auto& dashedShader = resources.shaders[shader::DASHED];
|
||||
|
||||
if (ImGui::Begin("Spritesheet Editor", &settings.windowIsSpritesheetEditor))
|
||||
{
|
||||
auto childSize = ImVec2(imgui::row_widget_width_get(3),
|
||||
(ImGui::GetTextLineHeightWithSpacing() * 4) + (ImGui::GetStyle().WindowPadding.y * 2));
|
||||
|
||||
if (ImGui::BeginChild("##Grid Child", childSize, true, ImGuiWindowFlags_HorizontalScrollbar))
|
||||
{
|
||||
ImGui::Checkbox("Grid", &isGrid);
|
||||
ImGui::SameLine();
|
||||
ImGui::ColorEdit4("Color", value_ptr(gridColor), ImGuiColorEditFlags_NoInputs);
|
||||
ImGui::InputInt2("Size", value_ptr(gridSize));
|
||||
ImGui::InputInt2("Offset", value_ptr(gridOffset));
|
||||
}
|
||||
ImGui::EndChild();
|
||||
|
||||
ImGui::SameLine();
|
||||
|
||||
if (ImGui::BeginChild("##View Child", childSize, true, ImGuiWindowFlags_HorizontalScrollbar))
|
||||
{
|
||||
ImGui::InputFloat("Zoom", &zoom, zoomStep, zoomStep, "%.0f%%");
|
||||
|
||||
auto widgetSize = ImVec2(imgui::row_widget_width_get(2), 0);
|
||||
|
||||
imgui::shortcut(settings.shortcutCenterView);
|
||||
if (ImGui::Button("Center View", widgetSize)) pan = -size * 0.5f;
|
||||
imgui::set_item_tooltip_shortcut("Centers the view.", settings.shortcutCenterView);
|
||||
|
||||
ImGui::SameLine();
|
||||
|
||||
imgui::shortcut(settings.shortcutFit);
|
||||
if (ImGui::Button("Fit", widgetSize))
|
||||
if (spritesheet) set_to_rect(zoom, pan, {0, 0, spritesheet->texture.size.x, spritesheet->texture.size.y});
|
||||
imgui::set_item_tooltip_shortcut("Set the view to match the extent of the spritesheet.", settings.shortcutFit);
|
||||
|
||||
ImGui::TextUnformatted(std::format(POSITION_FORMAT, (int)mousePos.x, (int)mousePos.y).c_str());
|
||||
}
|
||||
ImGui::EndChild();
|
||||
|
||||
ImGui::SameLine();
|
||||
|
||||
if (ImGui::BeginChild("##Background Child", childSize, true, ImGuiWindowFlags_HorizontalScrollbar))
|
||||
{
|
||||
ImGui::ColorEdit4("Background", value_ptr(backgroundColor), ImGuiColorEditFlags_NoInputs);
|
||||
|
||||
ImGui::Checkbox("Border", &isBorder);
|
||||
}
|
||||
ImGui::EndChild();
|
||||
|
||||
auto cursorScreenPos = ImGui::GetCursorScreenPos();
|
||||
|
||||
size_set(to_vec2(ImGui::GetContentRegionAvail()));
|
||||
bind();
|
||||
viewport_set();
|
||||
clear(backgroundColor);
|
||||
|
||||
auto frame = document.frame_get();
|
||||
|
||||
if (spritesheet && spritesheet->texture.is_valid())
|
||||
{
|
||||
auto& texture = spritesheet->texture;
|
||||
auto transform = transform_get(zoom, pan);
|
||||
|
||||
auto spritesheetModel = math::quad_model_get(texture.size);
|
||||
auto spritesheetTransform = transform * spritesheetModel;
|
||||
texture_render(shaderTexture, texture.id, spritesheetTransform);
|
||||
if (isBorder) rect_render(dashedShader, spritesheetTransform, spritesheetModel);
|
||||
|
||||
if (frame && reference.itemID > -1 &&
|
||||
anm2.content.layers.at(reference.itemID).spritesheetID == referenceSpritesheet)
|
||||
{
|
||||
auto cropModel = math::quad_model_get(frame->size, frame->crop);
|
||||
auto cropTransform = transform * cropModel;
|
||||
rect_render(dashedShader, cropTransform, cropModel, color::RED);
|
||||
|
||||
auto pivotTransform =
|
||||
transform * math::quad_model_get(canvas::PIVOT_SIZE, frame->crop + frame->pivot, PIVOT_SIZE * 0.5f);
|
||||
texture_render(shaderTexture, resources.icons[icon::PIVOT].id, pivotTransform, PIVOT_COLOR);
|
||||
}
|
||||
}
|
||||
|
||||
if (isGrid) grid_render(shaderGrid, zoom, pan, gridSize, gridOffset, gridColor);
|
||||
|
||||
unbind();
|
||||
|
||||
ImGui::Image(texture, to_imvec2(size));
|
||||
|
||||
if (ImGui::IsItemHovered())
|
||||
{
|
||||
ImGui::SetKeyboardFocusHere(-1);
|
||||
|
||||
previousMousePos = mousePos;
|
||||
mousePos = position_translate(zoom, pan, to_vec2(ImGui::GetMousePos()) - to_vec2(cursorScreenPos));
|
||||
|
||||
auto isMouseClicked = ImGui::IsMouseClicked(ImGuiMouseButton_Left);
|
||||
auto isMouseDown = ImGui::IsMouseDown(ImGuiMouseButton_Left);
|
||||
auto isMouseMiddleDown = ImGui::IsMouseDown(ImGuiMouseButton_Middle);
|
||||
auto mouseDelta = to_ivec2(ImGui::GetIO().MouseDelta);
|
||||
auto mouseWheel = ImGui::GetIO().MouseWheel;
|
||||
auto& toolColor = settings.toolColor;
|
||||
auto isZoomIn = imgui::chord_repeating(imgui::string_to_chord(settings.shortcutZoomIn));
|
||||
auto isZoomOut = imgui::chord_repeating(imgui::string_to_chord(settings.shortcutZoomOut));
|
||||
auto isLeft = imgui::chord_repeating(ImGuiKey_LeftArrow);
|
||||
auto isRight = imgui::chord_repeating(ImGuiKey_RightArrow);
|
||||
auto isUp = imgui::chord_repeating(ImGuiKey_UpArrow);
|
||||
auto isDown = imgui::chord_repeating(ImGuiKey_DownArrow);
|
||||
auto isMod = ImGui::IsKeyDown(ImGuiMod_Shift);
|
||||
auto step = isMod ? canvas::STEP_FAST : canvas::STEP;
|
||||
auto useTool = tool;
|
||||
auto isMouseClick = ImGui::IsMouseClicked(ImGuiMouseButton_Left);
|
||||
auto isMouseReleased = ImGui::IsMouseReleased(ImGuiMouseButton_Left);
|
||||
auto isLeftPressed = ImGui::IsKeyPressed(ImGuiKey_LeftArrow, false);
|
||||
auto isRightPressed = ImGui::IsKeyPressed(ImGuiKey_RightArrow, false);
|
||||
auto isUpPressed = ImGui::IsKeyPressed(ImGuiKey_UpArrow, false);
|
||||
auto isDownPressed = ImGui::IsKeyPressed(ImGuiKey_DownArrow, false);
|
||||
auto isLeftReleased = ImGui::IsKeyReleased(ImGuiKey_LeftArrow);
|
||||
auto isRightReleased = ImGui::IsKeyReleased(ImGuiKey_RightArrow);
|
||||
auto isUpReleased = ImGui::IsKeyReleased(ImGuiKey_UpArrow);
|
||||
auto isDownReleased = ImGui::IsKeyReleased(ImGuiKey_DownArrow);
|
||||
auto frame = document.frame_get();
|
||||
auto isKeyPressed = isLeftPressed || isRightPressed || isUpPressed || isDownPressed;
|
||||
auto isKeyReleased = isLeftReleased || isRightReleased || isUpReleased || isDownReleased;
|
||||
auto isBegin = isMouseClick || isKeyPressed;
|
||||
auto isEnd = isMouseReleased || isKeyReleased;
|
||||
|
||||
if (isMouseMiddleDown) useTool = tool::PAN;
|
||||
|
||||
ImGui::SetMouseCursor(tool::INFO[useTool].cursor);
|
||||
|
||||
switch (useTool)
|
||||
{
|
||||
case tool::PAN:
|
||||
if (isMouseDown || isMouseMiddleDown) pan += mouseDelta;
|
||||
break;
|
||||
case tool::MOVE:
|
||||
if (!frame) break;
|
||||
if (isBegin) document.snapshot("Frame Pivot");
|
||||
if (isMouseDown) frame->pivot = ivec2(mousePos - frame->crop);
|
||||
if (isLeft) frame->pivot.x -= step;
|
||||
if (isRight) frame->pivot.x += step;
|
||||
if (isUp) frame->pivot.y -= step;
|
||||
if (isDown) frame->pivot.y += step;
|
||||
if (isEnd) document.change(Document::FRAMES);
|
||||
break;
|
||||
case tool::CROP:
|
||||
if (!frame) break;
|
||||
if (isBegin) document.snapshot(isMod ? "Frame Size" : "Frame Crop");
|
||||
if (isMouseClicked) frame->crop = ivec2(mousePos);
|
||||
if (isMouseDown) frame->size = ivec2(mousePos - frame->crop);
|
||||
if (isLeft) isMod ? frame->size.x -= step : frame->crop.x -= step;
|
||||
if (isRight) isMod ? frame->size.x += step : frame->crop.x += step;
|
||||
if (isUp) isMod ? frame->size.y -= step : frame->crop.y -= step;
|
||||
if (isDown) isMod ? frame->size.y += step : frame->crop.y += step;
|
||||
if (isEnd) document.change(Document::FRAMES);
|
||||
break;
|
||||
case tool::DRAW:
|
||||
case tool::ERASE:
|
||||
{
|
||||
if (!spritesheet) break;
|
||||
auto color = tool == tool::DRAW ? toolColor : vec4();
|
||||
if (isMouseClicked) document.snapshot(tool == tool::DRAW ? "Draw" : "Erase");
|
||||
if (isMouseDown) spritesheet->texture.pixel_line(ivec2(previousMousePos), ivec2(mousePos), color);
|
||||
if (isMouseReleased) document.change(Document::FRAMES);
|
||||
break;
|
||||
}
|
||||
case tool::COLOR_PICKER:
|
||||
{
|
||||
if (isMouseDown)
|
||||
{
|
||||
auto position = to_vec2(ImGui::GetMousePos());
|
||||
toolColor = pixel_read(position, {settings.windowSize.x, settings.windowSize.y});
|
||||
if (ImGui::BeginTooltip())
|
||||
{
|
||||
ImGui::ColorButton("##Color Picker Button", to_imvec4(toolColor));
|
||||
ImGui::EndTooltip();
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (mouseWheel != 0 || isZoomIn || isZoomOut)
|
||||
zoom_set(zoom, pan, mousePos, (mouseWheel > 0 || isZoomIn) ? zoomStep : -zoomStep);
|
||||
}
|
||||
}
|
||||
ImGui::End();
|
||||
}
|
||||
}
|
||||
19
src/imgui/window/spritesheet_editor.h
Normal file
19
src/imgui/window/spritesheet_editor.h
Normal file
@@ -0,0 +1,19 @@
|
||||
#pragma once
|
||||
|
||||
#include "canvas.h"
|
||||
#include "manager.h"
|
||||
#include "resources.h"
|
||||
#include "settings.h"
|
||||
|
||||
namespace anm2ed::imgui
|
||||
{
|
||||
class SpritesheetEditor : public Canvas
|
||||
{
|
||||
glm::vec2 mousePos{};
|
||||
glm::vec2 previousMousePos{};
|
||||
|
||||
public:
|
||||
SpritesheetEditor();
|
||||
void update(Manager&, Settings&, Resources&);
|
||||
};
|
||||
}
|
||||
297
src/imgui/window/spritesheets.cpp
Normal file
297
src/imgui/window/spritesheets.cpp
Normal file
@@ -0,0 +1,297 @@
|
||||
#include "spritesheets.h"
|
||||
|
||||
#include <ranges>
|
||||
|
||||
#include "toast.h"
|
||||
|
||||
using namespace anm2ed::types;
|
||||
using namespace anm2ed::resource;
|
||||
using namespace glm;
|
||||
|
||||
namespace anm2ed::imgui
|
||||
{
|
||||
void Spritesheets::update(Manager& manager, Settings& settings, Resources& resources, Dialog& dialog,
|
||||
Clipboard& clipboard)
|
||||
{
|
||||
auto& document = *manager.get();
|
||||
auto& anm2 = document.anm2;
|
||||
auto& multiSelect = document.spritesheetMultiSelect;
|
||||
auto& unused = document.unusedSpritesheetIDs;
|
||||
auto& hovered = document.hoveredSpritesheet;
|
||||
auto& reference = document.referenceSpritesheet;
|
||||
|
||||
hovered = -1;
|
||||
|
||||
if (ImGui::Begin("Spritesheets", &settings.windowIsSpritesheets))
|
||||
{
|
||||
auto style = ImGui::GetStyle();
|
||||
|
||||
auto context_menu = [&]()
|
||||
{
|
||||
auto copy = [&]()
|
||||
{
|
||||
if (!multiSelect.empty())
|
||||
{
|
||||
std::string clipboardText{};
|
||||
for (auto& id : multiSelect)
|
||||
clipboardText += anm2.content.spritesheets[id].to_string(id);
|
||||
clipboard.set(clipboardText);
|
||||
}
|
||||
else if (hovered > -1)
|
||||
clipboard.set(anm2.content.spritesheets[hovered].to_string(hovered));
|
||||
};
|
||||
|
||||
auto paste = [&](merge::Type type)
|
||||
{
|
||||
auto clipboardText = clipboard.get();
|
||||
document.spritesheets_deserialize(clipboardText, type);
|
||||
};
|
||||
|
||||
if (imgui::shortcut(settings.shortcutCopy, shortcut::FOCUSED)) copy();
|
||||
if (imgui::shortcut(settings.shortcutPaste, shortcut::FOCUSED)) paste(merge::APPEND);
|
||||
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, style.WindowPadding);
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, style.ItemSpacing);
|
||||
if (ImGui::BeginPopupContextWindow("##Context Menu", ImGuiPopupFlags_MouseButtonRight))
|
||||
{
|
||||
ImGui::BeginDisabled();
|
||||
ImGui::MenuItem("Cut", settings.shortcutCut.c_str());
|
||||
ImGui::EndDisabled();
|
||||
|
||||
ImGui::BeginDisabled(multiSelect.empty() && hovered == -1);
|
||||
if (ImGui::MenuItem("Copy", settings.shortcutCopy.c_str())) copy();
|
||||
ImGui::EndDisabled();
|
||||
|
||||
ImGui::BeginDisabled(clipboard.is_empty());
|
||||
{
|
||||
if (ImGui::BeginMenu("Paste"))
|
||||
{
|
||||
if (ImGui::MenuItem("Append", settings.shortcutPaste.c_str())) paste(merge::APPEND);
|
||||
if (ImGui::MenuItem("Replace")) paste(merge::REPLACE);
|
||||
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
}
|
||||
ImGui::EndDisabled();
|
||||
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
ImGui::PopStyleVar(2);
|
||||
};
|
||||
|
||||
auto childSize = imgui::size_without_footer_get(2);
|
||||
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2());
|
||||
|
||||
if (ImGui::BeginChild("##Spritesheets Child", childSize, ImGuiChildFlags_Borders))
|
||||
{
|
||||
auto spritesheetChildSize = ImVec2(ImGui::GetContentRegionAvail().x, ImGui::GetTextLineHeightWithSpacing() * 4);
|
||||
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2());
|
||||
|
||||
multiSelect.start(anm2.content.spritesheets.size());
|
||||
|
||||
for (auto& [id, spritesheet] : anm2.content.spritesheets)
|
||||
{
|
||||
ImGui::PushID(id);
|
||||
|
||||
if (ImGui::BeginChild("##Spritesheet Child", spritesheetChildSize, ImGuiChildFlags_Borders))
|
||||
{
|
||||
auto isSelected = multiSelect.contains(id);
|
||||
auto isReferenced = id == reference;
|
||||
auto cursorPos = ImGui::GetCursorPos();
|
||||
auto& texture = spritesheet.texture.is_valid() ? spritesheet.texture : resources.icons[icon::NONE];
|
||||
auto path = spritesheet.path.empty() ? anm2::NO_PATH : spritesheet.path.c_str();
|
||||
|
||||
ImGui::SetNextItemSelectionUserData(id);
|
||||
ImGui::SetNextItemStorageID(id);
|
||||
if (ImGui::Selectable("##Spritesheet Selectable", isSelected, 0, spritesheetChildSize)) reference = id;
|
||||
if (ImGui::IsItemHovered()) hovered = id;
|
||||
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, style.ItemSpacing);
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, style.WindowPadding);
|
||||
if (ImGui::BeginItemTooltip())
|
||||
{
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2());
|
||||
|
||||
auto viewport = ImGui::GetMainViewport();
|
||||
|
||||
auto textureSize = texture.size.x * texture.size.y > (viewport->Size.x * viewport->Size.y) * 0.5f
|
||||
? to_vec2(viewport->Size) * 0.5f
|
||||
: vec2(texture.size);
|
||||
auto aspectRatio = (float)texture.size.x / texture.size.y;
|
||||
|
||||
if (textureSize.x / textureSize.y > aspectRatio)
|
||||
textureSize.x = textureSize.y * aspectRatio;
|
||||
else
|
||||
textureSize.y = textureSize.x / aspectRatio;
|
||||
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2());
|
||||
if (ImGui::BeginChild("##Spritesheet Tooltip Image Child", to_imvec2(textureSize),
|
||||
ImGuiChildFlags_Borders))
|
||||
ImGui::Image(texture.id, ImGui::GetContentRegionAvail());
|
||||
ImGui::PopStyleVar();
|
||||
ImGui::EndChild();
|
||||
|
||||
ImGui::PopStyleVar();
|
||||
|
||||
ImGui::SameLine();
|
||||
|
||||
if (ImGui::BeginChild("##Spritesheet Info Tooltip Child"))
|
||||
{
|
||||
ImGui::PushFont(resources.fonts[font::BOLD].get(), font::SIZE);
|
||||
ImGui::TextUnformatted(path);
|
||||
ImGui::PopFont();
|
||||
ImGui::Text("ID: %d", id);
|
||||
ImGui::Text("Size: %d x %d", texture.size.x, texture.size.y);
|
||||
}
|
||||
ImGui::EndChild();
|
||||
|
||||
ImGui::EndTooltip();
|
||||
}
|
||||
ImGui::PopStyleVar(2);
|
||||
|
||||
auto imageSize = to_imvec2(vec2(spritesheetChildSize.y));
|
||||
auto aspectRatio = (float)texture.size.x / texture.size.y;
|
||||
|
||||
if (imageSize.x / imageSize.y > aspectRatio)
|
||||
imageSize.x = imageSize.y * aspectRatio;
|
||||
else
|
||||
imageSize.y = imageSize.x / aspectRatio;
|
||||
|
||||
ImGui::SetCursorPos(cursorPos);
|
||||
ImGui::Image(texture.id, imageSize);
|
||||
|
||||
ImGui::SetCursorPos(
|
||||
ImVec2(spritesheetChildSize.y + style.ItemSpacing.x,
|
||||
spritesheetChildSize.y - spritesheetChildSize.y / 2 - ImGui::GetTextLineHeight() / 2));
|
||||
|
||||
if (isReferenced) ImGui::PushFont(resources.fonts[font::ITALICS].get(), font::SIZE);
|
||||
ImGui::Text(anm2::SPRITESHEET_FORMAT_C, id, path);
|
||||
if (isReferenced) ImGui::PopFont();
|
||||
|
||||
context_menu();
|
||||
}
|
||||
ImGui::EndChild();
|
||||
|
||||
ImGui::PopID();
|
||||
}
|
||||
|
||||
multiSelect.finish();
|
||||
|
||||
ImGui::PopStyleVar(2);
|
||||
|
||||
context_menu();
|
||||
}
|
||||
ImGui::EndChild();
|
||||
|
||||
auto rowOneWidgetSize = imgui::widget_size_with_row_get(4);
|
||||
|
||||
imgui::shortcut(settings.shortcutAdd);
|
||||
if (ImGui::Button("Add", rowOneWidgetSize)) dialog.file_open(dialog::SPRITESHEET_OPEN);
|
||||
imgui::set_item_tooltip_shortcut("Add a new spritesheet.", settings.shortcutAdd);
|
||||
|
||||
if (dialog.is_selected(dialog::SPRITESHEET_OPEN))
|
||||
{
|
||||
document.spritesheet_add(dialog.path);
|
||||
dialog.reset();
|
||||
}
|
||||
|
||||
ImGui::SameLine();
|
||||
|
||||
ImGui::BeginDisabled(multiSelect.empty());
|
||||
{
|
||||
if (ImGui::Button("Reload", rowOneWidgetSize))
|
||||
{
|
||||
for (auto& id : multiSelect)
|
||||
{
|
||||
anm2::Spritesheet& spritesheet = anm2.content.spritesheets[id];
|
||||
spritesheet.reload(document.directory_get());
|
||||
toasts.info(std::format("Reloaded spritesheet #{}: {}", id, spritesheet.path.string()));
|
||||
}
|
||||
}
|
||||
ImGui::SetItemTooltip("Reloads the selected spritesheets.");
|
||||
}
|
||||
ImGui::EndDisabled();
|
||||
|
||||
ImGui::SameLine();
|
||||
|
||||
ImGui::BeginDisabled(multiSelect.size() != 1);
|
||||
{
|
||||
if (ImGui::Button("Replace", rowOneWidgetSize)) dialog.file_open(dialog::SPRITESHEET_REPLACE);
|
||||
ImGui::SetItemTooltip("Replace the selected spritesheet with a new one.");
|
||||
}
|
||||
ImGui::EndDisabled();
|
||||
|
||||
if (dialog.is_selected(dialog::SPRITESHEET_REPLACE))
|
||||
{
|
||||
auto& id = *multiSelect.begin();
|
||||
anm2::Spritesheet& spritesheet = anm2.content.spritesheets[id];
|
||||
spritesheet = anm2::Spritesheet(document.directory_get(), dialog.path);
|
||||
toasts.info(std::format("Replaced spritesheet #{}: {}", id, spritesheet.path.string()));
|
||||
dialog.reset();
|
||||
}
|
||||
|
||||
ImGui::SameLine();
|
||||
|
||||
ImGui::BeginDisabled(unused.empty());
|
||||
{
|
||||
imgui::shortcut(settings.shortcutRemove);
|
||||
if (ImGui::Button("Remove Unused", rowOneWidgetSize))
|
||||
{
|
||||
for (auto& id : unused)
|
||||
{
|
||||
anm2::Spritesheet& spritesheet = anm2.content.spritesheets[id];
|
||||
toasts.info(std::format("Removed spritesheet #{}: {}", id, spritesheet.path.string()));
|
||||
anm2.spritesheet_remove(id);
|
||||
}
|
||||
unused.clear();
|
||||
document.change(Document::SPRITESHEETS);
|
||||
}
|
||||
imgui::set_item_tooltip_shortcut("Remove all unused spritesheets (i.e., not used in any layer.).",
|
||||
settings.shortcutRemove);
|
||||
}
|
||||
ImGui::EndDisabled();
|
||||
|
||||
auto rowTwoWidgetSize = imgui::widget_size_with_row_get(3);
|
||||
|
||||
imgui::shortcut(settings.shortcutSelectAll);
|
||||
ImGui::BeginDisabled(multiSelect.size() == anm2.content.spritesheets.size());
|
||||
{
|
||||
if (ImGui::Button("Select All", rowTwoWidgetSize))
|
||||
for (auto& id : anm2.content.spritesheets | std::views::keys)
|
||||
multiSelect.insert(id);
|
||||
}
|
||||
ImGui::EndDisabled();
|
||||
imgui::set_item_tooltip_shortcut("Select all spritesheets.", settings.shortcutSelectAll);
|
||||
|
||||
ImGui::SameLine();
|
||||
|
||||
imgui::shortcut(settings.shortcutSelectNone);
|
||||
ImGui::BeginDisabled(multiSelect.empty());
|
||||
if (ImGui::Button("Select None", rowTwoWidgetSize)) multiSelect.clear();
|
||||
imgui::set_item_tooltip_shortcut("Unselect all spritesheets.", settings.shortcutSelectNone);
|
||||
ImGui::EndDisabled();
|
||||
|
||||
ImGui::SameLine();
|
||||
|
||||
ImGui::BeginDisabled(multiSelect.empty());
|
||||
{
|
||||
if (ImGui::Button("Save", rowTwoWidgetSize))
|
||||
{
|
||||
for (auto& id : multiSelect)
|
||||
{
|
||||
anm2::Spritesheet& spritesheet = anm2.content.spritesheets[id];
|
||||
if (spritesheet.save(document.directory_get()))
|
||||
toasts.info(std::format("Saved spritesheet #{}: {}", id, spritesheet.path.string()));
|
||||
else
|
||||
toasts.info(std::format("Unable to save spritesheet #{}: {}", id, spritesheet.path.string()));
|
||||
}
|
||||
}
|
||||
}
|
||||
ImGui::EndDisabled();
|
||||
ImGui::SetItemTooltip("Save the selected spritesheets.");
|
||||
}
|
||||
ImGui::End();
|
||||
}
|
||||
}
|
||||
16
src/imgui/window/spritesheets.h
Normal file
16
src/imgui/window/spritesheets.h
Normal file
@@ -0,0 +1,16 @@
|
||||
#pragma once
|
||||
|
||||
#include "clipboard.h"
|
||||
#include "dialog.h"
|
||||
#include "manager.h"
|
||||
#include "resources.h"
|
||||
#include "settings.h"
|
||||
|
||||
namespace anm2ed::imgui
|
||||
{
|
||||
class Spritesheets
|
||||
{
|
||||
public:
|
||||
void update(Manager&, Settings&, Resources&, Dialog&, Clipboard& clipboard);
|
||||
};
|
||||
}
|
||||
1032
src/imgui/window/timeline.cpp
Normal file
1032
src/imgui/window/timeline.cpp
Normal file
File diff suppressed because it is too large
Load Diff
39
src/imgui/window/timeline.h
Normal file
39
src/imgui/window/timeline.h
Normal file
@@ -0,0 +1,39 @@
|
||||
#pragma once
|
||||
|
||||
#include "clipboard.h"
|
||||
#include "document.h"
|
||||
#include "manager.h"
|
||||
#include "resources.h"
|
||||
#include "settings.h"
|
||||
|
||||
namespace anm2ed::imgui
|
||||
{
|
||||
class Timeline
|
||||
{
|
||||
bool isDragging{};
|
||||
bool isWindowHovered{};
|
||||
bool isHorizontalScroll{};
|
||||
PopupHelper propertiesPopup{PopupHelper("Item Properties", POPUP_NORMAL)};
|
||||
PopupHelper bakePopup{PopupHelper("Bake", POPUP_TO_CONTENT)};
|
||||
std::string addItemName{};
|
||||
int addItemSpritesheetID{};
|
||||
bool addItemIsRect{};
|
||||
int addItemID{-1};
|
||||
bool isUnusedItemsSet{};
|
||||
std::set<int> unusedItems{};
|
||||
glm::vec2 scroll{};
|
||||
ImDrawList* pickerLineDrawList{};
|
||||
ImGuiStyle style{};
|
||||
|
||||
void context_menu(Document&, Settings&, Clipboard&);
|
||||
void item_child(Manager&, Document&, anm2::Animation*, Settings&, Resources&, Clipboard&, anm2::Type, int, int&);
|
||||
void items_child(Manager&, Document&, anm2::Animation*, Settings&, Resources&, Clipboard&);
|
||||
void frame_child(Document&, anm2::Animation*, Settings&, Resources&, Clipboard&, anm2::Type, int, int&, float);
|
||||
void frames_child(Document&, anm2::Animation*, Settings&, Resources&, Clipboard&);
|
||||
|
||||
void popups(Document&, anm2::Animation*, Settings&);
|
||||
|
||||
public:
|
||||
void update(Manager&, Settings&, Resources&, Clipboard&);
|
||||
};
|
||||
}
|
||||
99
src/imgui/window/tools.cpp
Normal file
99
src/imgui/window/tools.cpp
Normal file
@@ -0,0 +1,99 @@
|
||||
|
||||
#include "tools.h"
|
||||
|
||||
#include <glm/gtc/type_ptr.hpp>
|
||||
|
||||
#include "tool.h"
|
||||
#include "types.h"
|
||||
|
||||
using namespace anm2ed::resource;
|
||||
using namespace anm2ed::types;
|
||||
using namespace glm;
|
||||
|
||||
namespace anm2ed::imgui
|
||||
{
|
||||
void Tools::update(Manager& manager, Settings& settings, Resources& resources)
|
||||
{
|
||||
auto& document = *manager.get();
|
||||
|
||||
if (ImGui::Begin("Tools", &settings.windowIsTools))
|
||||
{
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(2, 2));
|
||||
|
||||
auto availableWidth = ImGui::GetContentRegionAvail().x;
|
||||
auto size = vec2(ImGui::GetTextLineHeightWithSpacing() * 1.5f);
|
||||
auto usedWidth = ImGui::GetStyle().WindowPadding.x;
|
||||
|
||||
auto tool_use = [&](tool::Type type)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case tool::UNDO:
|
||||
if (document.is_able_to_undo()) document.undo();
|
||||
break;
|
||||
case tool::REDO:
|
||||
if (document.is_able_to_redo()) document.redo();
|
||||
break;
|
||||
case tool::COLOR:
|
||||
colorEditPopup.open();
|
||||
break;
|
||||
default:
|
||||
settings.tool = type;
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
for (int i = 0; i < tool::COUNT; i++)
|
||||
{
|
||||
auto& info = tool::INFO[i];
|
||||
auto isSelected = settings.tool == i;
|
||||
|
||||
if (isSelected) ImGui::PushStyleColor(ImGuiCol_Button, ImGui::GetStyleColorVec4(ImGuiCol_ButtonActive));
|
||||
|
||||
auto member = SHORTCUT_MEMBERS[info.shortcut];
|
||||
|
||||
if (shortcut(settings.*member, shortcut::GLOBAL_SET)) tool_use((tool::Type)i);
|
||||
|
||||
if (i == tool::COLOR)
|
||||
{
|
||||
size += to_vec2(ImGui::GetStyle().FramePadding) * 2.0f;
|
||||
if (ImGui::ColorButton(info.label, to_imvec4(settings.toolColor), ImGuiColorEditFlags_NoTooltip,
|
||||
to_imvec2(size)))
|
||||
tool_use((tool::Type)i);
|
||||
|
||||
colorEditPosition = ImGui::GetCursorScreenPos();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (i == tool::UNDO) ImGui::BeginDisabled(!document.is_able_to_undo());
|
||||
if (i == tool::REDO) ImGui::BeginDisabled(!document.is_able_to_redo());
|
||||
if (ImGui::ImageButton(info.label, resources.icons[info.icon].id, to_imvec2(size))) tool_use((tool::Type)i);
|
||||
if (i == tool::UNDO) ImGui::EndDisabled();
|
||||
if (i == tool::REDO) ImGui::EndDisabled();
|
||||
}
|
||||
|
||||
auto widthIncrement = ImGui::GetItemRectSize().x + ImGui::GetStyle().ItemSpacing.x;
|
||||
usedWidth += widthIncrement;
|
||||
|
||||
if (usedWidth + widthIncrement < availableWidth)
|
||||
ImGui::SameLine();
|
||||
else
|
||||
usedWidth = ImGui::GetStyle().WindowPadding.x;
|
||||
set_item_tooltip_shortcut(info.tooltip, settings.*SHORTCUT_MEMBERS[info.shortcut]);
|
||||
|
||||
if (isSelected) ImGui::PopStyleColor();
|
||||
}
|
||||
|
||||
ImGui::PopStyleVar();
|
||||
|
||||
colorEditPopup.trigger();
|
||||
|
||||
if (ImGui::BeginPopup(colorEditPopup.label))
|
||||
{
|
||||
ImGui::ColorPicker4(colorEditPopup.label, value_ptr(settings.toolColor));
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
}
|
||||
ImGui::End();
|
||||
}
|
||||
}
|
||||
19
src/imgui/window/tools.h
Normal file
19
src/imgui/window/tools.h
Normal file
@@ -0,0 +1,19 @@
|
||||
#pragma once
|
||||
|
||||
#include "manager.h"
|
||||
#include "resources.h"
|
||||
#include "settings.h"
|
||||
|
||||
namespace anm2ed::imgui
|
||||
{
|
||||
class Tools
|
||||
{
|
||||
bool isOpenColorEdit{};
|
||||
ImVec2 colorEditPosition{};
|
||||
|
||||
PopupHelper colorEditPopup{PopupHelper("##Color Edit", POPUP_TO_CONTENT, POPUP_BY_ITEM)};
|
||||
|
||||
public:
|
||||
void update(Manager&, Settings&, Resources&);
|
||||
};
|
||||
}
|
||||
102
src/imgui/window/welcome.cpp
Normal file
102
src/imgui/window/welcome.cpp
Normal file
@@ -0,0 +1,102 @@
|
||||
#include "welcome.h"
|
||||
|
||||
#include <ranges>
|
||||
|
||||
using namespace anm2ed::resource;
|
||||
|
||||
namespace anm2ed::imgui
|
||||
{
|
||||
void Welcome::update(Manager& manager, Resources& resources, Dialog& dialog, Taskbar& taskbar, Documents& documents)
|
||||
{
|
||||
auto viewport = ImGui::GetMainViewport();
|
||||
auto windowHeight = viewport->Size.y - taskbar.height - documents.height;
|
||||
|
||||
ImGui::SetNextWindowViewport(viewport->ID);
|
||||
ImGui::SetNextWindowPos(ImVec2(viewport->Pos.x, viewport->Pos.y + taskbar.height + documents.height));
|
||||
ImGui::SetNextWindowSize(ImVec2(viewport->Size.x, windowHeight));
|
||||
|
||||
if (ImGui::Begin("Welcome", nullptr,
|
||||
ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize |
|
||||
ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoNavFocus | ImGuiWindowFlags_NoScrollbar |
|
||||
ImGuiWindowFlags_NoScrollWithMouse))
|
||||
{
|
||||
|
||||
ImGui::PushFont(resources.fonts[font::BOLD].get(), font::SIZE_LARGE);
|
||||
ImGui::TextUnformatted("Anm2Ed");
|
||||
ImGui::PopFont();
|
||||
|
||||
ImGui::TextUnformatted("Select a recent file or open a new or existing document. You can also drag and drop "
|
||||
"files into the window to open them.");
|
||||
|
||||
auto widgetSize = widget_size_with_row_get(2);
|
||||
|
||||
if (ImGui::Button("New", widgetSize)) dialog.file_open(dialog::ANM2_NEW); // handled in taskbar.cpp
|
||||
|
||||
ImGui::SameLine();
|
||||
|
||||
if (ImGui::Button("Open", widgetSize)) dialog.file_open(dialog::ANM2_OPEN); // handled in taskbar.cpp
|
||||
|
||||
if (ImGui::BeginChild("##Recent Files Child", ImVec2(), ImGuiChildFlags_Borders))
|
||||
{
|
||||
for (auto [i, file] : std::views::enumerate(manager.recentFiles))
|
||||
{
|
||||
ImGui::PushID(i);
|
||||
|
||||
auto label = std::format(FILE_LABEL_FORMAT, file.filename().string(), file.string());
|
||||
|
||||
if (ImGui::Selectable(label.c_str()))
|
||||
{
|
||||
manager.open(file);
|
||||
ImGui::PopID();
|
||||
break;
|
||||
}
|
||||
|
||||
ImGui::PopID();
|
||||
}
|
||||
}
|
||||
ImGui::EndChild();
|
||||
}
|
||||
ImGui::End();
|
||||
|
||||
if (!manager.autosaveFiles.empty() && !restorePopup.is_open()) restorePopup.open();
|
||||
|
||||
restorePopup.trigger();
|
||||
|
||||
if (ImGui::BeginPopupModal(restorePopup.label, &restorePopup.isOpen, ImGuiWindowFlags_NoResize))
|
||||
{
|
||||
ImGui::TextUnformatted("Autosaved documents detected. Would you like to restore them?");
|
||||
|
||||
auto childSize = child_size_get(5);
|
||||
|
||||
if (ImGui::BeginChild("##Restore Files Child", childSize, ImGuiChildFlags_Borders,
|
||||
ImGuiWindowFlags_HorizontalScrollbar))
|
||||
{
|
||||
for (auto& file : manager.autosaveFiles)
|
||||
{
|
||||
auto label = std::format(FILE_LABEL_FORMAT, file.filename().string(), file.string());
|
||||
ImGui::TextUnformatted(label.c_str());
|
||||
}
|
||||
}
|
||||
ImGui::EndChild();
|
||||
|
||||
auto widgetSize = widget_size_with_row_get(2);
|
||||
|
||||
if (ImGui::Button("Yes", widgetSize))
|
||||
{
|
||||
manager.autosave_files_open();
|
||||
restorePopup.close();
|
||||
}
|
||||
|
||||
ImGui::SameLine();
|
||||
|
||||
if (ImGui::Button("No", widgetSize))
|
||||
{
|
||||
manager.autosave_files_clear();
|
||||
restorePopup.close();
|
||||
}
|
||||
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
16
src/imgui/window/welcome.h
Normal file
16
src/imgui/window/welcome.h
Normal file
@@ -0,0 +1,16 @@
|
||||
#pragma once
|
||||
|
||||
#include "documents.h"
|
||||
#include "manager.h"
|
||||
#include "taskbar.h"
|
||||
|
||||
namespace anm2ed::imgui
|
||||
{
|
||||
class Welcome
|
||||
{
|
||||
PopupHelper restorePopup{PopupHelper("Restore", imgui::POPUP_SMALL_NO_HEIGHT)};
|
||||
|
||||
public:
|
||||
void update(Manager&, Resources&, Dialog&, Taskbar&, Documents&);
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user