diff --git a/src/anm2/anm2.cpp b/src/anm2/anm2.cpp index f6151f8..89f4035 100644 --- a/src/anm2/anm2.cpp +++ b/src/anm2/anm2.cpp @@ -24,7 +24,7 @@ namespace anm2ed::anm2 XMLDocument document; #ifdef _WIN32 - auto file = _wfopen(path.string().c_str(), L"rb"); + auto file = _wfopen(path.wstring().c_str(), L"rb"); #else auto file = fopen(path.c_str(), "rb"); #endif @@ -63,7 +63,7 @@ namespace anm2ed::anm2 document.InsertFirstChild(to_element(document)); #ifdef _WIN32 - auto file = _wfopen(path.string().c_str(), L"w"); + auto file = _wfopen(path.wstring().c_str(), L"w"); #else auto file = fopen(path.c_str(), "w"); #endif diff --git a/src/imgui/imgui_.cpp b/src/imgui/imgui_.cpp index 3bc0ee5..efc5c4d 100644 --- a/src/imgui/imgui_.cpp +++ b/src/imgui/imgui_.cpp @@ -93,6 +93,159 @@ namespace anm2ed::imgui return isActivated; } + edit::Type drag_int_persistent(const char* label, int* value, float speed, int min, int max, const char* format, + ImGuiSliderFlags flags) + { + static bool isEditing{}; + static int start{INT_MAX}; + auto persistent = value ? *value : 0; + + ImGui::DragInt(label, &persistent, speed, min, max, format, flags); + if (!value) return edit::NONE; + if (ImGui::IsItemActivated() && persistent != start) + { + isEditing = true; + start = *value; + return edit::START; + } + else if (ImGui::IsItemDeactivatedAfterEdit()) + { + isEditing = false; + *value = persistent; + start = INT_MAX; + return edit::END; + } + else if (isEditing) + { + *value = persistent; + return edit::DURING; + } + + return edit::NONE; + } + + edit::Type drag_float_persistent(const char* label, float* value, float speed, float min, float max, + const char* format, ImGuiSliderFlags flags) + { + static bool isEditing{}; + static float start{NAN}; + auto persistent = value ? *value : 0; + + ImGui::DragFloat(label, &persistent, speed, min, max, format, flags); + if (!value) return edit::NONE; + if (ImGui::IsItemActivated() && persistent != start) + { + isEditing = true; + start = *value; + return edit::START; + } + else if (ImGui::IsItemDeactivatedAfterEdit()) + { + isEditing = false; + *value = persistent; + start = NAN; + return edit::END; + } + else if (isEditing) + { + *value = persistent; + return edit::DURING; + } + + return edit::NONE; + } + + edit::Type drag_float2_persistent(const char* label, vec2* value, float speed, float min, float max, + const char* format, ImGuiSliderFlags flags) + { + static bool isEditing{}; + static vec2 start{NAN}; + auto persistent = value ? *value : vec2(); + + ImGui::DragFloat2(label, value_ptr(persistent), speed, min, max, format, flags); + if (!value) return edit::NONE; + if (ImGui::IsItemActivated() && persistent != start) + { + isEditing = true; + start = *value; + return edit::START; + } + else if (ImGui::IsItemDeactivatedAfterEdit()) + { + isEditing = false; + *value = persistent; + start = vec2{NAN}; + return edit::END; + } + else if (isEditing) + { + *value = persistent; + return edit::DURING; + } + + return edit::NONE; + } + + edit::Type color_edit3_persistent(const char* label, vec3* value, ImGuiColorEditFlags flags) + { + static bool isEditing{}; + static vec3 start{NAN}; + auto persistent = value ? *value : vec4(); + + ImGui::ColorEdit3(label, value_ptr(persistent), flags); + if (!value) return edit::NONE; + if (ImGui::IsItemActivated() && persistent != start) + { + isEditing = true; + start = *value; + return edit::START; + } + else if (ImGui::IsItemDeactivatedAfterEdit()) + { + isEditing = false; + *value = persistent; + start = vec4{NAN}; + return edit::END; + } + else if (isEditing) + { + *value = persistent; + return edit::DURING; + } + + return edit::NONE; + } + + edit::Type color_edit4_persistent(const char* label, vec4* value, ImGuiColorEditFlags flags) + { + static bool isEditing{}; + static vec4 start{NAN}; + auto persistent = value ? *value : vec4(); + + ImGui::ColorEdit4(label, value_ptr(persistent), flags); + if (!value) return edit::NONE; + if (ImGui::IsItemActivated() && persistent != start) + { + isEditing = true; + start = *value; + return edit::START; + } + else if (ImGui::IsItemDeactivatedAfterEdit()) + { + isEditing = false; + *value = persistent; + start = vec4{NAN}; + return edit::END; + } + else if (isEditing) + { + *value = persistent; + return edit::DURING; + } + + return edit::NONE; + } + bool input_int_range(const char* label, int& value, int min, int max, int step, int stepFast, ImGuiInputTextFlags flags) { diff --git a/src/imgui/imgui_.h b/src/imgui/imgui_.h index 7481d9f..25bda14 100644 --- a/src/imgui/imgui_.h +++ b/src/imgui/imgui_.h @@ -12,7 +12,8 @@ namespace anm2ed::imgui { - constexpr auto DRAG_SPEED = 1.0f; + constexpr auto DRAG_SPEED = 0.25f; + constexpr auto DRAG_SPEED_FAST = 1.00f; constexpr auto STEP = 1.0f; constexpr auto STEP_FAST = 5.0f; @@ -179,6 +180,14 @@ namespace anm2ed::imgui bool input_int2_range(const char*, glm::ivec2&, glm::ivec2, glm::ivec2, ImGuiInputTextFlags = 0); bool input_float_range(const char*, float&, float, float, float = STEP, float = STEP_FAST, const char* = "%.3f", ImGuiInputTextFlags = 0); + types::edit::Type drag_int_persistent(const char*, int*, float = DRAG_SPEED, int = {}, int = {}, const char* = "%d", + ImGuiSliderFlags = 0); + types::edit::Type drag_float_persistent(const char*, float*, float = DRAG_SPEED, float = {}, float = {}, + const char* = "%.3f", ImGuiSliderFlags = 0); + types::edit::Type drag_float2_persistent(const char*, glm::vec2*, float = DRAG_SPEED, float = {}, float = {}, + const char* = "%.3f", ImGuiSliderFlags = 0); + types::edit::Type color_edit3_persistent(const char*, glm::vec3*, ImGuiColorEditFlags = 0); + types::edit::Type color_edit4_persistent(const char*, glm::vec4*, ImGuiColorEditFlags = 0); bool combo_negative_one_indexed(const std::string&, int*, std::vector&); std::string& selectable_input_text_id(); bool selectable_input_text(const std::string& label, const std::string& id, std::string& text, bool isSelected, diff --git a/src/imgui/taskbar.cpp b/src/imgui/taskbar.cpp index 069d486..e883882 100644 --- a/src/imgui/taskbar.cpp +++ b/src/imgui/taskbar.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -225,6 +226,7 @@ namespace anm2ed::imgui auto document = manager.get(); auto animation = document ? document->animation_get() : nullptr; auto item = document ? document->item_get() : nullptr; + auto frames = document ? &document->frames : nullptr; if (ImGui::BeginMainMenuBar()) { @@ -296,7 +298,12 @@ namespace anm2ed::imgui item && document->reference.itemType == anm2::LAYER)) generatePopup.open(); + if (ImGui::MenuItem(localize.get(LABEL_CHANGE_ALL_FRAME_PROPERTIES), nullptr, false, + frames && !frames->selection.empty() && document->reference.itemType != anm2::TRIGGER)) + changePopup.open(); + ImGui::Separator(); + if (ImGui::MenuItem(localize.get(LABEL_TASKBAR_RENDER_ANIMATION), nullptr, false, animation)) renderPopup.open(); ImGui::EndMenu(); @@ -337,7 +344,6 @@ namespace anm2ed::imgui if (ImGui::BeginMenu(localize.get(LABEL_HELP_MENU))) { - if (ImGui::MenuItem(localize.get(LABEL_TASKBAR_ABOUT))) aboutPopup.open(); ImGui::EndMenu(); } @@ -445,6 +451,135 @@ namespace anm2ed::imgui 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& isDuration = settings.changeIsDuration; + 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& duration = settings.changeDuration; + auto& tint = settings.changeTint; + auto& colorOffset = settings.changeColorOffset; + auto& isVisible = settings.changeIsVisible; + auto& isInterpolated = settings.changeIsInterpolated; + +#define PROPERTIES_WIDGET(body) \ + ImGui::Checkbox(checkboxLabel, &isEnabled); \ + ImGui::SameLine(); \ + ImGui::BeginDisabled(!isEnabled); \ + body; \ + ImGui::EndDisabled(); + + auto bool_value = [&](const char* checkboxLabel, const char* valueLabel, bool& isEnabled, bool& value) + { PROPERTIES_WIDGET(ImGui::Checkbox(valueLabel, &value)); }; + + auto color3_value = [&](const char* checkboxLabel, const char* valueLabel, bool& isEnabled, vec3& value) + { PROPERTIES_WIDGET(ImGui::ColorEdit3(valueLabel, value_ptr(value))); }; + + auto color4_value = [&](const char* checkboxLabel, const char* valueLabel, bool& isEnabled, vec4& value) + { PROPERTIES_WIDGET(ImGui::ColorEdit4(valueLabel, value_ptr(value))); }; + + auto float2_value = [&](const char* checkboxLabel, const char* valueLabel, bool& isEnabled, vec2& value) + { PROPERTIES_WIDGET(ImGui::InputFloat2(valueLabel, value_ptr(value), math::vec2_format_get(value))); }; + + auto float_value = [&](const char* checkboxLabel, const char* valueLabel, bool& isEnabled, float& value) + { PROPERTIES_WIDGET(ImGui::InputFloat(valueLabel, &value, STEP, STEP_FAST, math::float_format_get(value))); }; + + auto duration_value = [&](const char* checkboxLabel, const char* valueLabel, bool& isEnabled, int& value) + { + PROPERTIES_WIDGET( + input_int_range(valueLabel, value, anm2::FRAME_DURATION_MIN, anm2::FRAME_DURATION_MAX, STEP, STEP_FAST)); + }; + +#undef PROPERTIES_WIDGET + + float2_value("##Is Crop", localize.get(BASIC_CROP), isCrop, crop); + float2_value("##Is Size", localize.get(BASIC_SIZE), isSize, size); + float2_value("##Is Position", localize.get(BASIC_POSITION), isPosition, position); + float2_value("##Is Pivot", localize.get(BASIC_PIVOT), isPivot, pivot); + float2_value("##Is Scale", localize.get(BASIC_SCALE), isScale, scale); + float_value("##Is Rotation", localize.get(BASIC_ROTATION), isRotation, rotation); + duration_value("##Is Duration", localize.get(BASIC_DURATION), isDuration, duration); + color4_value("##Is Tint", localize.get(BASIC_TINT), isTint, tint); + color3_value("##Is Color Offset", localize.get(BASIC_COLOR_OFFSET), isColorOffset, colorOffset); + bool_value("##Is Visible", localize.get(BASIC_VISIBLE), isVisibleSet, isVisible); + ImGui::SameLine(); + bool_value("##Is Interpolated", localize.get(BASIC_INTERPOLATED), isInterpolatedSet, isInterpolated); + + auto frame_change = [&](anm2::ChangeType type) + { + anm2::FrameChange frameChange; + if (isCrop) frameChange.crop = std::make_optional(crop); + if (isSize) frameChange.size = std::make_optional(size); + if (isPosition) frameChange.position = std::make_optional(position); + if (isPivot) frameChange.pivot = std::make_optional(pivot); + if (isScale) frameChange.scale = std::make_optional(scale); + if (isRotation) frameChange.rotation = std::make_optional(rotation); + if (isDuration) frameChange.duration = std::make_optional(duration); + if (isTint) frameChange.tint = std::make_optional(tint); + if (isColorOffset) frameChange.colorOffset = std::make_optional(colorOffset); + if (isVisibleSet) frameChange.isVisible = std::make_optional(isVisible); + if (isInterpolatedSet) frameChange.isInterpolated = std::make_optional(isInterpolated); + + DOCUMENT_EDIT_PTR( + document, localize.get(EDIT_CHANGE_FRAME_PROPERTIES), Document::FRAMES, + item->frames_change(frameChange, type, *frames->selection.begin(), (int)frames->selection.size())); + + changePopup.close(); + }; + + bool isAnyProperty = isCrop || isSize || isPosition || isPivot || isScale || isRotation || isDuration || isTint || + isColorOffset || isVisibleSet || isInterpolatedSet; + + ImGui::Separator(); + + auto rowWidgetSize = widget_size_with_row_get(5); + + ImGui::BeginDisabled(!isAnyProperty); + + if (ImGui::Button(localize.get(LABEL_ADJUST), rowWidgetSize)) frame_change(anm2::ADJUST); + ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_ADJUST)); + + ImGui::SameLine(); + + if (ImGui::Button(localize.get(BASIC_ADD), rowWidgetSize)) frame_change(anm2::ADD); + ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_ADD_VALUES)); + + ImGui::SameLine(); + + if (ImGui::Button(localize.get(LABEL_SUBTRACT), rowWidgetSize)) frame_change(anm2::SUBTRACT); + ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_SUBTRACT_VALUES)); + + ImGui::SameLine(); + + if (ImGui::Button(localize.get(LABEL_MULTIPLY), rowWidgetSize)) frame_change(anm2::MULTIPLY); + ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_MULTIPLY_VALUES)); + + ImGui::SameLine(); + + if (ImGui::Button(localize.get(LABEL_DIVIDE), rowWidgetSize)) frame_change(anm2::DIVIDE); + ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_DIVIDE_VALUES)); + + ImGui::EndDisabled(); + + ImGui::EndPopup(); + } + configurePopup.trigger(); if (ImGui::BeginPopupModal(configurePopup.label(), &configurePopup.isOpen, ImGuiWindowFlags_NoResize)) @@ -654,6 +789,7 @@ namespace anm2ed::imgui auto& columns = settings.renderColumns; auto& isRange = manager.isRecordingRange; auto& frames = document->frames.selection; + auto& reference = document->reference; int length = std::max(1, end - start + 1); auto range_to_frames_set = [&]() @@ -661,17 +797,10 @@ namespace anm2ed::imgui if (auto item = document->item_get()) { int duration{}; - for (std::size_t index = 0; index < item->frames.size(); ++index) + for (auto [i, frame] : std::views::enumerate(item->frames)) { - const auto& frame = item->frames[index]; - - if ((int)index == *frames.begin()) - start = duration; - else if ((int)index == *frames.rbegin()) - { - end = duration; - break; - } + if ((int)i == *frames.begin()) start = duration; + if ((int)i == *frames.rbegin()) end = duration + frame.duration - 1; duration += frame.duration; } @@ -800,7 +929,7 @@ namespace anm2ed::imgui ImGui::SameLine(); - ImGui::BeginDisabled(frames.empty()); + ImGui::BeginDisabled(frames.empty() || reference.itemID == anm2::TRIGGER); if (ImGui::Button(localize.get(LABEL_TO_SELECTED_FRAMES))) range_to_frames_set(); ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_TO_SELECTED_FRAMES)); ImGui::EndDisabled(); diff --git a/src/imgui/taskbar.h b/src/imgui/taskbar.h index f28351f..6de051f 100644 --- a/src/imgui/taskbar.h +++ b/src/imgui/taskbar.h @@ -15,10 +15,9 @@ namespace anm2ed::imgui Canvas generate; float generateTime{}; PopupHelper generatePopup{PopupHelper(LABEL_TASKBAR_GENERATE_ANIMATION_FROM_GRID)}; - PopupHelper overwritePopup{ - PopupHelper(LABEL_TASKBAR_OVERWRITE_FILE, imgui::POPUP_SMALL_NO_HEIGHT)}; - PopupHelper renderPopup{ - PopupHelper(LABEL_TASKBAR_RENDER_ANIMATION, imgui::POPUP_SMALL_NO_HEIGHT)}; + PopupHelper changePopup{PopupHelper(LABEL_CHANGE_ALL_FRAME_PROPERTIES, imgui::POPUP_NORMAL_NO_HEIGHT)}; + PopupHelper overwritePopup{PopupHelper(LABEL_TASKBAR_OVERWRITE_FILE, imgui::POPUP_SMALL_NO_HEIGHT)}; + PopupHelper renderPopup{PopupHelper(LABEL_TASKBAR_RENDER_ANIMATION, imgui::POPUP_SMALL_NO_HEIGHT)}; PopupHelper configurePopup{PopupHelper(LABEL_TASKBAR_CONFIGURE)}; PopupHelper aboutPopup{PopupHelper(LABEL_TASKBAR_ABOUT)}; Settings editSettings{}; diff --git a/src/imgui/window/animation_preview.cpp b/src/imgui/window/animation_preview.cpp index 6bfd45f..d80799b 100644 --- a/src/imgui/window/animation_preview.cpp +++ b/src/imgui/window/animation_preview.cpp @@ -430,7 +430,7 @@ namespace anm2ed::imgui bind(); viewport_set(); - clear(manager.isRecording && settings.renderIsRawAnimation ? vec4() : vec4(backgroundColor, 1.0f)); + clear(manager.isRecording && settings.renderIsRawAnimation ? vec4(0) : vec4(backgroundColor, 1.0f)); if (isAxes) axes_render(shaderAxes, zoom, pan, axesColor); if (isGrid) grid_render(shaderGrid, zoom, pan, gridSize, gridOffset, gridColor); @@ -731,9 +731,13 @@ namespace anm2ed::imgui if (tool == tool::MOVE && isMouseRightDown) useTool = tool::SCALE; if (tool == tool::SCALE && isMouseRightDown) useTool = tool::MOVE; - auto& areaType = tool::INFO[useTool].areaType; - auto cursor = areaType == tool::ANIMATION_PREVIEW || areaType == tool::ALL ? tool::INFO[useTool].cursor - : ImGuiMouseCursor_NotAllowed; + auto& toolInfo = tool::INFO[useTool]; + auto& areaType = toolInfo.areaType; + bool isAreaAllowed = areaType == tool::ALL || areaType == tool::ANIMATION_PREVIEW; + bool isFrameRequired = + !(useTool == tool::PAN || useTool == tool::DRAW || useTool == tool::ERASE || useTool == tool::COLOR_PICKER); + bool isFrameAvailable = !isFrameRequired || frame; + auto cursor = (isAreaAllowed && isFrameAvailable) ? toolInfo.cursor : ImGuiMouseCursor_NotAllowed; ImGui::SetMouseCursor(cursor); ImGui::SetKeyboardFocusHere(); if (useTool != tool::MOVE) isMoveDragging = false; @@ -820,6 +824,26 @@ namespace anm2ed::imgui break; } + if ((isMouseDown || isKeyDown) && useTool != tool::PAN) + { + if (!isAreaAllowed && areaType == tool::SPRITESHEET_EDITOR) + { + if (ImGui::BeginTooltip()) + { + ImGui::TextUnformatted(localize.get(TEXT_TOOL_SPRITESHEET_EDITOR)); + ImGui::EndTooltip(); + } + } + else if (isFrameRequired && !isFrameAvailable) + { + if (ImGui::BeginTooltip()) + { + ImGui::TextUnformatted(localize.get(TEXT_SELECT_FRAME)); + ImGui::EndTooltip(); + } + } + } + if (mouseWheel != 0 || isZoomIn || isZoomOut) { auto previousZoom = zoom; @@ -829,7 +853,7 @@ namespace anm2ed::imgui } } - if (ImGui::BeginPopupContextWindow("##Animation Preview Context Menu", ImGuiMouseButton_Right)) + if (tool == tool::PAN && ImGui::BeginPopupContextWindow("##Animation Preview Context Menu", ImGuiMouseButton_Right)) { if (ImGui::MenuItem(localize.get(SHORTCUT_STRING_UNDO), settings.shortcutUndo.c_str(), false, document.is_able_to_undo())) diff --git a/src/imgui/window/frame_properties.cpp b/src/imgui/window/frame_properties.cpp index ac6f313..83bbcc5 100644 --- a/src/imgui/window/frame_properties.cpp +++ b/src/imgui/window/frame_properties.cpp @@ -1,6 +1,7 @@ #include "frame_properties.h" #include +#include #include "math_.h" #include "strings.h" @@ -12,6 +13,7 @@ using namespace glm; namespace anm2ed::imgui { + void FrameProperties::update(Manager& manager, Settings& settings) { if (ImGui::Begin(localize.get(LABEL_FRAME_PROPERTIES_WINDOW), &settings.windowIsFrameProperties)) @@ -45,8 +47,9 @@ namespace anm2ed::imgui frame->soundID = useFrame.soundID); ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_TRIGGER_SOUND)); - if (ImGui::InputInt(localize.get(BASIC_AT_FRAME), frame ? &useFrame.atFrame : &dummy_value(), STEP, - STEP_FAST, !frame ? ImGuiInputTextFlags_DisplayEmptyRefVal : 0) && + if (input_int_range(localize.get(BASIC_AT_FRAME), frame ? useFrame.atFrame : dummy_value(), 0, + std::numeric_limits::max(), STEP, STEP_FAST, + !frame ? ImGuiInputTextFlags_DisplayEmptyRefVal : 0) && frame) DOCUMENT_EDIT(document, localize.get(EDIT_TRIGGER_AT_FRAME), Document::FRAMES, frame->atFrame = useFrame.atFrame); @@ -62,52 +65,65 @@ namespace anm2ed::imgui { ImGui::BeginDisabled(type == anm2::ROOT || type == anm2::NULL_); { - if (ImGui::InputFloat2(localize.get(BASIC_CROP), frame ? value_ptr(useFrame.crop) : &dummy_value(), - frame ? vec2_format_get(useFrame.crop) : "") && - frame) - DOCUMENT_EDIT(document, localize.get(EDIT_FRAME_CROP), Document::FRAMES, frame->crop = useFrame.crop); + auto cropEdit = + drag_float2_persistent(localize.get(BASIC_CROP), frame ? &frame->crop : &dummy_value(), + DRAG_SPEED, 0.0f, 0.0f, frame ? vec2_format_get(frame->crop) : ""); + if (cropEdit == edit::START) + document.snapshot(localize.get(EDIT_FRAME_CROP)); + else if (cropEdit == edit::END) + document.change(Document::FRAMES); ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_CROP)); - if (ImGui::InputFloat2(localize.get(BASIC_SIZE), frame ? value_ptr(useFrame.size) : &dummy_value(), - frame ? vec2_format_get(useFrame.size) : "") && - frame) - DOCUMENT_EDIT(document, localize.get(EDIT_FRAME_SIZE), Document::FRAMES, frame->size = useFrame.size); + auto sizeEdit = + drag_float2_persistent(localize.get(BASIC_SIZE), frame ? &frame->size : &dummy_value(), + DRAG_SPEED, 0.0f, 0.0f, frame ? vec2_format_get(frame->size) : ""); + if (sizeEdit == edit::START) + document.snapshot(localize.get(EDIT_FRAME_SIZE)); + else if (sizeEdit == edit::END) + document.change(Document::FRAMES); ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_SIZE)); } ImGui::EndDisabled(); - if (ImGui::InputFloat2(localize.get(BASIC_POSITION), - frame ? value_ptr(useFrame.position) : &dummy_value(), - frame ? vec2_format_get(useFrame.position) : "") && - frame) - DOCUMENT_EDIT(document, localize.get(EDIT_FRAME_POSITION), Document::FRAMES, - frame->position = useFrame.position); + auto positionEdit = + drag_float2_persistent(localize.get(BASIC_POSITION), frame ? &frame->position : &dummy_value(), + DRAG_SPEED, 0.0f, 0.0f, frame ? vec2_format_get(frame->position) : ""); + if (positionEdit == edit::START) + document.snapshot(localize.get(EDIT_FRAME_POSITION)); + else if (positionEdit == edit::END) + document.change(Document::FRAMES); ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_POSITION)); ImGui::BeginDisabled(type == anm2::ROOT || type == anm2::NULL_); { - if (ImGui::InputFloat2(localize.get(BASIC_PIVOT), - frame ? value_ptr(useFrame.pivot) : &dummy_value(), - frame ? vec2_format_get(useFrame.pivot) : "") && - frame) - DOCUMENT_EDIT(document, localize.get(EDIT_FRAME_PIVOT), Document::FRAMES, - frame->pivot = useFrame.pivot); + auto pivotEdit = + drag_float2_persistent(localize.get(BASIC_PIVOT), frame ? &frame->pivot : &dummy_value(), + DRAG_SPEED, 0.0f, 0.0f, frame ? vec2_format_get(frame->pivot) : ""); + if (pivotEdit == edit::START) + document.snapshot(localize.get(EDIT_FRAME_PIVOT)); + else if (pivotEdit == edit::END) + document.change(Document::FRAMES); ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_PIVOT)); } ImGui::EndDisabled(); - if (ImGui::InputFloat2(localize.get(BASIC_SCALE), frame ? value_ptr(useFrame.scale) : &dummy_value(), - frame ? vec2_format_get(useFrame.scale) : "") && - frame) - DOCUMENT_EDIT(document, localize.get(EDIT_FRAME_SCALE), Document::FRAMES, frame->scale = useFrame.scale); + auto scaleEdit = + drag_float2_persistent(localize.get(BASIC_SCALE), frame ? &frame->scale : &dummy_value(), + DRAG_SPEED, 0.0f, 0.0f, frame ? vec2_format_get(frame->scale) : ""); ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_SCALE)); + if (scaleEdit == edit::START) + document.snapshot(localize.get(EDIT_FRAME_SCALE)); + else if (scaleEdit == edit::END) + document.change(Document::FRAMES); - if (ImGui::InputFloat(localize.get(BASIC_ROTATION), frame ? &useFrame.rotation : &dummy_value(), - STEP, STEP_FAST, frame ? float_format_get(useFrame.rotation) : "") && - frame) - DOCUMENT_EDIT(document, localize.get(EDIT_FRAME_ROTATION), Document::FRAMES, - frame->rotation = useFrame.rotation); + auto rotationEdit = + drag_float_persistent(localize.get(BASIC_ROTATION), frame ? &frame->rotation : &dummy_value(), + DRAG_SPEED, 0.0f, 0.0f, frame ? float_format_get(frame->rotation) : ""); ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_ROTATION)); + if (rotationEdit == edit::START) + document.snapshot(localize.get(EDIT_FRAME_ROTATION)); + else if (rotationEdit == edit::END) + document.change(Document::FRAMES); if (input_int_range(localize.get(BASIC_DURATION), frame ? useFrame.duration : dummy_value(), frame ? anm2::FRAME_DURATION_MIN : 0, anm2::FRAME_DURATION_MAX, STEP, STEP_FAST, @@ -117,17 +133,21 @@ namespace anm2ed::imgui frame->duration = useFrame.duration); ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_DURATION)); - if (ImGui::ColorEdit4(localize.get(BASIC_TINT), frame ? value_ptr(useFrame.tint) : &dummy_value()) && - frame) - DOCUMENT_EDIT(document, localize.get(EDIT_FRAME_TINT), Document::FRAMES, frame->tint = useFrame.tint); + auto tintEdit = + color_edit4_persistent(localize.get(BASIC_TINT), frame ? &frame->tint : &dummy_value()); ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_TINT)); + if (tintEdit == edit::START) + document.snapshot(localize.get(EDIT_FRAME_TINT)); + else if (tintEdit == edit::END) + document.change(Document::FRAMES); - if (ImGui::ColorEdit3(localize.get(BASIC_COLOR_OFFSET), - frame ? value_ptr(useFrame.colorOffset) : &dummy_value()) && - frame) - DOCUMENT_EDIT(document, localize.get(EDIT_FRAME_COLOR_OFFSET), Document::FRAMES, - frame->colorOffset = useFrame.colorOffset); + auto colorOffsetEdit = color_edit3_persistent(localize.get(BASIC_COLOR_OFFSET), + frame ? &frame->colorOffset : &dummy_value()); ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_COLOR_OFFSET)); + if (colorOffsetEdit == edit::START) + document.snapshot(localize.get(EDIT_FRAME_COLOR_OFFSET)); + else if (colorOffsetEdit == edit::END) + document.change(Document::FRAMES); if (ImGui::Checkbox(localize.get(BASIC_VISIBLE), frame ? &useFrame.isVisible : &dummy_value()) && frame) @@ -246,24 +266,39 @@ namespace anm2ed::imgui document.item_get()->frames_change(frameChange, type, *frames.begin(), (int)frames.size())); }; - auto rowOneWidgetSize = widget_size_with_row_get(1); + ImGui::Separator(); - if (ImGui::Button(localize.get(LABEL_ADJUST), rowOneWidgetSize)) frame_change(anm2::ADJUST); + bool isAnyProperty = isCrop || isSize || isPosition || isPivot || isScale || isRotation || isDuration || + isTint || isColorOffset || isVisibleSet || isInterpolatedSet; + + auto rowWidgetSize = widget_size_with_row_get(5); + + ImGui::BeginDisabled(!isAnyProperty); + + if (ImGui::Button(localize.get(LABEL_ADJUST), rowWidgetSize)) frame_change(anm2::ADJUST); ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_ADJUST)); - auto rowTwoWidgetSize = widget_size_with_row_get(4); + ImGui::SameLine(); - if (ImGui::Button(localize.get(BASIC_ADD), rowTwoWidgetSize)) frame_change(anm2::ADD); + if (ImGui::Button(localize.get(BASIC_ADD), rowWidgetSize)) frame_change(anm2::ADD); ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_ADD_VALUES)); + ImGui::SameLine(); - if (ImGui::Button(localize.get(LABEL_SUBTRACT), rowTwoWidgetSize)) frame_change(anm2::SUBTRACT); + + if (ImGui::Button(localize.get(LABEL_SUBTRACT), rowWidgetSize)) frame_change(anm2::SUBTRACT); ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_SUBTRACT_VALUES)); + ImGui::SameLine(); - if (ImGui::Button(localize.get(LABEL_MULTIPLY), rowTwoWidgetSize)) frame_change(anm2::MULTIPLY); + + if (ImGui::Button(localize.get(LABEL_MULTIPLY), rowWidgetSize)) frame_change(anm2::MULTIPLY); ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_MULTIPLY_VALUES)); + ImGui::SameLine(); - if (ImGui::Button(localize.get(LABEL_DIVIDE), rowTwoWidgetSize)) frame_change(anm2::DIVIDE); + + if (ImGui::Button(localize.get(LABEL_DIVIDE), rowWidgetSize)) frame_change(anm2::DIVIDE); ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_DIVIDE_VALUES)); + + ImGui::EndDisabled(); } } ImGui::End(); diff --git a/src/imgui/window/frame_properties.h b/src/imgui/window/frame_properties.h index d8cfd86..eb2ef2c 100644 --- a/src/imgui/window/frame_properties.h +++ b/src/imgui/window/frame_properties.h @@ -1,13 +1,25 @@ #pragma once +#include + #include "manager.h" #include "settings.h" +namespace anm2 +{ + struct Frame; +} + namespace anm2ed::imgui { class FrameProperties { public: void update(Manager&, Settings&); + + private: + glm::vec2 cropEditingValue{}; + const anm2::Frame* cropEditingFrame{}; + bool isCropEditing{}; }; } diff --git a/src/imgui/window/sounds.cpp b/src/imgui/window/sounds.cpp index a9c8854..0ff7e90 100644 --- a/src/imgui/window/sounds.cpp +++ b/src/imgui/window/sounds.cpp @@ -210,7 +210,7 @@ namespace anm2ed::imgui bool isValid = sound.is_valid(); auto& soundIcon = isValid ? resources.icons[icon::SOUND] : resources.icons[icon::NONE]; auto tintColor = !isValid ? ImVec4(1.0f, 0.25f, 0.25f, 1.0f) : ImVec4(1.0f, 1.0f, 1.0f, 1.0f); - auto pathCStr = sound.path.string().c_str(); + auto pathString = sound.path.string(); ImGui::SetNextItemSelectionUserData(id); ImGui::SetNextItemStorageID(id); @@ -226,7 +226,7 @@ namespace anm2ed::imgui newSoundId = -1; } - auto textWidth = ImGui::CalcTextSize(pathCStr).x; + auto textWidth = ImGui::CalcTextSize(pathString.c_str()).x; auto tooltipPadding = style.WindowPadding.x * 4.0f; auto minWidth = textWidth + style.ItemSpacing.x + tooltipPadding; @@ -236,7 +236,7 @@ namespace anm2ed::imgui if (ImGui::BeginItemTooltip()) { ImGui::PushFont(resources.fonts[font::BOLD].get(), font::SIZE); - ImGui::TextUnformatted(pathCStr); + ImGui::TextUnformatted(pathString.c_str()); ImGui::PopFont(); ImGui::Text("%s: %d", localize.get(BASIC_ID), id); if (!isValid) @@ -261,7 +261,7 @@ namespace anm2ed::imgui soundChildSize.y - soundChildSize.y / 2 - ImGui::GetTextLineHeight() / 2)); ImGui::TextUnformatted( - std::vformat(localize.get(FORMAT_SOUND), std::make_format_args(id, pathCStr)).c_str()); + std::vformat(localize.get(FORMAT_SOUND), std::make_format_args(id, pathString)).c_str()); } ImGui::EndChild(); diff --git a/src/imgui/window/spritesheet_editor.cpp b/src/imgui/window/spritesheet_editor.cpp index 533c90f..0bc3ad9 100644 --- a/src/imgui/window/spritesheet_editor.cpp +++ b/src/imgui/window/spritesheet_editor.cpp @@ -190,7 +190,7 @@ namespace anm2ed::imgui bind(); viewport_set(); - clear(isTransparent ? vec4() : vec4(backgroundColor, 1.0f)); + clear(isTransparent ? vec4(0) : vec4(backgroundColor, 1.0f)); auto frame = document.frame_get(); @@ -312,8 +312,15 @@ namespace anm2ed::imgui if (tool == tool::DRAW && isMouseRightDown) useTool = tool::ERASE; if (tool == tool::ERASE && isMouseRightDown) useTool = tool::DRAW; - auto& areaType = tool::INFO[useTool].areaType; - auto cursor = areaType == tool::SPRITESHEET_EDITOR || areaType == tool::ALL ? tool::INFO[useTool].cursor + auto& toolInfo = tool::INFO[useTool]; + auto& areaType = toolInfo.areaType; + bool isAreaAllowed = areaType == tool::ALL || areaType == tool::SPRITESHEET_EDITOR; + bool isFrameRequired = + !(useTool == tool::PAN || useTool == tool::DRAW || useTool == tool::ERASE || useTool == tool::COLOR_PICKER); + bool isFrameAvailable = !isFrameRequired || frame; + bool isSpritesheetRequired = useTool == tool::DRAW || useTool == tool::ERASE || useTool == tool::COLOR_PICKER; + bool isSpritesheetAvailable = !isSpritesheetRequired || (spritesheet && spritesheet->texture.is_valid()); + auto cursor = (isAreaAllowed && isFrameAvailable && isSpritesheetAvailable) ? toolInfo.cursor : ImGuiMouseCursor_NotAllowed; ImGui::SetMouseCursor(cursor); ImGui::SetKeyboardFocusHere(); @@ -429,6 +436,34 @@ namespace anm2ed::imgui break; } + if ((isMouseDown || isKeyDown) && useTool != tool::PAN) + { + if (!isAreaAllowed && areaType == tool::ANIMATION_PREVIEW) + { + if (ImGui::BeginTooltip()) + { + ImGui::TextUnformatted(localize.get(TEXT_TOOL_ANIMATION_PREVIEW)); + ImGui::EndTooltip(); + } + } + else if (isSpritesheetRequired && !isSpritesheetAvailable) + { + if (ImGui::BeginTooltip()) + { + ImGui::TextUnformatted(localize.get(TEXT_SELECT_SPRITESHEET)); + ImGui::EndTooltip(); + } + } + else if (isFrameRequired && !isFrameAvailable) + { + if (ImGui::BeginTooltip()) + { + ImGui::TextUnformatted(localize.get(TEXT_SELECT_FRAME)); + ImGui::EndTooltip(); + } + } + } + if (mouseWheel != 0 || isZoomIn || isZoomOut) { auto focus = mouseWheel != 0 ? vec2(mousePos) : vec2(); @@ -442,7 +477,8 @@ namespace anm2ed::imgui } } - if (ImGui::BeginPopupContextWindow("##Spritesheet Editor Context Menu", ImGuiMouseButton_Right)) + if (tool == tool::PAN && + ImGui::BeginPopupContextWindow("##Spritesheet Editor Context Menu", ImGuiMouseButton_Right)) { if (ImGui::MenuItem(localize.get(SHORTCUT_STRING_UNDO), settings.shortcutUndo.c_str(), false, document.is_able_to_undo())) diff --git a/src/imgui/window/timeline.cpp b/src/imgui/window/timeline.cpp index 9370644..e32bb58 100644 --- a/src/imgui/window/timeline.cpp +++ b/src/imgui/window/timeline.cpp @@ -3,7 +3,6 @@ #include #include #include -#include #include @@ -81,7 +80,6 @@ namespace anm2ed::imgui constexpr auto FRAME_MULTIPLE = 5; constexpr auto FRAME_DRAG_PAYLOAD_ID = "Frame Drag Drop"; constexpr auto FRAME_TOOLTIP_HOVER_DELAY = 0.75f; // Extra delay for frame info tooltip. - constexpr int ITEM_SELECTION_NULL_FLAG = 1 << 30; void Timeline::update(Manager& manager, Settings& settings, Resources& resources, Clipboard& clipboard) { @@ -90,38 +88,7 @@ namespace anm2ed::imgui auto& playback = document.playback; auto& reference = document.reference; auto& frames = document.frames; - auto& items = document.items; - auto& itemSelection = items.selection; auto animation = document.animation_get(); - auto item_selection_encode = [&](anm2::Type type, int id) -> int - { - if (type == anm2::NULL_) return id | ITEM_SELECTION_NULL_FLAG; - return id; - }; - auto item_selection_decode = [&](int value) -> int { return value & ~ITEM_SELECTION_NULL_FLAG; }; - auto item_selection_value_type = [&](int value) -> anm2::Type - { return (value & ITEM_SELECTION_NULL_FLAG) ? anm2::NULL_ : anm2::LAYER; }; - std::vector itemSelectionIndexMap{}; - std::unordered_map layerSelectionIndex{}; - std::unordered_map nullSelectionIndex{}; - if (animation) - { - itemSelectionIndexMap.reserve(animation->layerOrder.size() + animation->nullAnimations.size()); - for (auto id : animation->layerOrder) - { - layerSelectionIndex[id] = (int)itemSelectionIndexMap.size(); - itemSelectionIndexMap.push_back(item_selection_encode(anm2::LAYER, id)); - } - for (auto& [id, nullAnimation] : animation->nullAnimations) - { - (void)nullAnimation; - nullSelectionIndex[id] = (int)itemSelectionIndexMap.size(); - itemSelectionIndexMap.push_back(item_selection_encode(anm2::NULL_, id)); - } - itemSelection.set_index_map(&itemSelectionIndexMap); - } - else - itemSelection.set_index_map(nullptr); style = ImGui::GetStyle(); auto isLightTheme = settings.theme == theme::LIGHT; @@ -161,113 +128,6 @@ namespace anm2ed::imgui return ITEM_COLOR_LIGHT_ACTIVE[type_index(type)]; }; - items.hovered = -1; - - auto item_selection_type_get = [&](anm2::Type preferredType = anm2::NONE) -> anm2::Type - { - if (itemSelection.empty() || !animation) return anm2::NONE; - - bool hasLayer = false; - bool hasNull = false; - - for (auto encoded : itemSelection) - { - auto valueType = item_selection_value_type(encoded); - auto valueID = item_selection_decode(encoded); - if (valueType == anm2::LAYER && animation->layerAnimations.contains(valueID)) hasLayer = true; - if (valueType == anm2::NULL_ && animation->nullAnimations.contains(valueID)) hasNull = true; - } - - auto type_available = [&](anm2::Type type) - { - if (type == anm2::LAYER) return hasLayer; - if (type == anm2::NULL_) return hasNull; - return false; - }; - - if (preferredType != anm2::NONE && type_available(preferredType)) return preferredType; - if (hasLayer) return anm2::LAYER; - if (hasNull) return anm2::NULL_; - return anm2::NONE; - }; - - auto item_selection_clear = [&]() { itemSelection.clear(); }; - - auto item_selection_sync = [&]() - { - if (itemSelection.empty()) - { - item_selection_clear(); - return; - } - - auto preferredType = items.reference == (int)anm2::LAYER || items.reference == (int)anm2::NULL_ - ? (anm2::Type)items.reference - : anm2::NONE; - auto type = item_selection_type_get(preferredType); - - if (type == anm2::NONE) - { - item_selection_clear(); - return; - } - - for (auto it = itemSelection.begin(); it != itemSelection.end();) - { - if (item_selection_value_type(*it) != type) - it = itemSelection.erase(it); - else - ++it; - } - - items.reference = (int)type; - }; - - auto item_selection_prune = [&]() - { - if (itemSelection.empty()) - { - return; - } - - if (!animation) - { - item_selection_clear(); - return; - } - - auto type = item_selection_type_get(); - if (type != anm2::LAYER && type != anm2::NULL_) - { - item_selection_clear(); - return; - } - - for (auto it = itemSelection.begin(); it != itemSelection.end();) - { - if (item_selection_value_type(*it) != type) - { - it = itemSelection.erase(it); - continue; - } - - auto valueID = item_selection_decode(*it); - bool exists = type == anm2::LAYER ? animation->layerAnimations.contains(valueID) - : animation->nullAnimations.contains(valueID); - if (!exists) - it = itemSelection.erase(it); - else - ++it; - } - - if (itemSelection.empty()) - item_selection_clear(); - else - item_selection_sync(); - }; - - item_selection_prune(); - auto iconTintDefault = isLightTheme ? ICON_TINT_DEFAULT_LIGHT : ICON_TINT_DEFAULT_DARK; auto itemIconTint = isLightTheme ? ICON_TINT_DEFAULT_LIGHT : iconTintDefault; auto frameBorderColor = isLightTheme ? FRAME_BORDER_COLOR_LIGHT : FRAME_BORDER_COLOR_DARK; @@ -456,8 +316,19 @@ namespace anm2ed::imgui { reference = {reference.animationIndex, type, id}; frames_selection_reset(); - if (type == anm2::LAYER || type == anm2::NULL_) items.reference = (int)type; - items.selection = {(int)id}; + }; + + auto item_remove = [&]() + { + auto behavior = [&]() + { + if (!animation) return; + if (reference.itemType == anm2::LAYER || reference.itemType == anm2::NULL_) + animation->item_remove(reference.itemType, reference.itemID); + reference_clear(); + }; + + DOCUMENT_EDIT(document, localize.get(EDIT_REMOVE_ITEMS), Document::ITEMS, behavior()); }; auto fit_animation_length = [&]() @@ -595,6 +466,20 @@ namespace anm2ed::imgui ImGui::PopStyleVar(2); }; + auto item_base_properties_open = [&](anm2::Type type, int id) + { + switch (type) + { + case anm2::LAYER: + manager.layer_properties_open(id); + break; + case anm2::NULL_: + manager.null_properties_open(id); + default: + break; + }; + }; + auto item_properties_reset = [&]() { addItemName.clear(); @@ -610,94 +495,14 @@ namespace anm2ed::imgui return std::set{}; }; - auto item_selection_index_get = [&](anm2::Type type, int id) - { - if (type == anm2::LAYER) - { - if (auto it = layerSelectionIndex.find(id); it != layerSelectionIndex.end()) return it->second; - } - else if (type == anm2::NULL_) - { - if (auto it = nullSelectionIndex.find(id); it != nullSelectionIndex.end()) return it->second; - } - - return -1; - }; - - auto item_type_has_visible = [&](anm2::Type type) - { - if (!animation) return false; - auto& showUnused = settings.timelineIsShowUnused; - if (type == anm2::LAYER) - { - for (auto id : animation->layerOrder) - { - if (!showUnused && animation->layerAnimations[id].frames.empty()) continue; - return true; - } - return false; - } - if (type == anm2::NULL_) - { - for (auto& [id, nullAnimation] : animation->nullAnimations) - { - if (!showUnused && nullAnimation.frames.empty()) continue; - return true; - } - return false; - } - return false; - }; - - auto item_selection_select_all = [&](anm2::Type type) - { - if (!animation || (type != anm2::LAYER && type != anm2::NULL_)) return; - - auto& showUnused = settings.timelineIsShowUnused; - - itemSelection.clear(); - bool hasInserted = false; - - auto try_insert = [&](int id) - { - itemSelection.insert(item_selection_encode(type, id)); - hasInserted = true; - }; - - if (type == anm2::LAYER) - { - for (auto id : animation->layerOrder) - { - if (!showUnused && animation->layerAnimations[id].frames.empty()) continue; - try_insert(id); - } - } - else if (type == anm2::NULL_) - { - for (auto& [id, nullAnimation] : animation->nullAnimations) - { - if (!showUnused && nullAnimation.frames.empty()) continue; - try_insert(id); - } - } - - if (!hasInserted) - { - item_selection_clear(); - return; - } - - item_selection_sync(); - }; - auto item_context_menu = [&]() { ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, style.WindowPadding); ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, style.ItemSpacing); - auto selectionType = item_selection_type_get(); - bool hasSelection = !itemSelection.empty() && (selectionType == anm2::LAYER || selectionType == anm2::NULL_); - auto currentItem = document.item_get(); + auto& type = reference.itemType; + auto& id = reference.itemID; + auto item = document.item_get(); if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenOverlappedByWindow) && ImGui::IsMouseClicked(ImGuiMouseButton_Right)) @@ -715,35 +520,17 @@ namespace anm2ed::imgui ImGui::Separator(); + if (ImGui::MenuItem(localize.get(BASIC_PROPERTIES), nullptr, false, + item && (type == anm2::LAYER || type == anm2::NULL_))) + item_base_properties_open(type, id); + if (ImGui::MenuItem(localize.get(BASIC_ADD), settings.shortcutAdd.c_str(), false, animation)) { item_properties_reset(); propertiesPopup.open(); } - if (ImGui::MenuItem(localize.get(BASIC_REMOVE), settings.shortcutRemove.c_str(), false, - hasSelection || currentItem)) - { - auto remove = [&]() - { - if (hasSelection) - { - std::vector ids{}; - ids.reserve(itemSelection.size()); - for (auto value : itemSelection) - ids.push_back(item_selection_decode(value)); - std::sort(ids.begin(), ids.end()); - for (auto id : ids) - animation->item_remove(selectionType, id); - item_selection_clear(); - } - else if (reference.itemType == anm2::LAYER || reference.itemType == anm2::NULL_) - animation->item_remove(reference.itemType, reference.itemID); - reference_clear(); - }; - - DOCUMENT_EDIT(document, localize.get(EDIT_REMOVE_ITEMS), Document::ITEMS, remove()); - } + if (ImGui::MenuItem(localize.get(BASIC_REMOVE), settings.shortcutRemove.c_str(), false, item)) item_remove(); ImGui::EndPopup(); } @@ -751,7 +538,7 @@ namespace anm2ed::imgui ImGui::PopStyleVar(2); }; - auto item_child = [&](anm2::Type type, int id, int& index) + auto item_child = [&](anm2::Type type, int id, int index) { ImGui::PushID(index); @@ -759,7 +546,7 @@ namespace anm2ed::imgui auto isVisible = item ? item->isVisible : false; auto& isOnlyShowLayers = settings.timelineIsOnlyShowLayers; if (isOnlyShowLayers && type != anm2::LAYER) isVisible = false; - auto isActive = reference.itemType == type && reference.itemID == id; + auto isReferenced = reference.itemType == type && reference.itemID == id; auto label = type == anm2::LAYER ? std::vformat(localize.get(FORMAT_LAYER), std::make_format_args(id, anm2.content.layers[id].name, @@ -771,14 +558,8 @@ namespace anm2ed::imgui auto iconTintCurrent = isLightTheme && type == anm2::NONE ? ImVec4(1.0f, 1.0f, 1.0f, 1.0f) : itemIconTint; auto baseColorVec = item_color_vec(type); auto activeColorVec = item_color_active_vec(type); - auto selectionType = item_selection_type_get(); - bool isSelectableItem = type == anm2::LAYER || type == anm2::NULL_; - auto selectionIndex = item_selection_index_get(type, id); - int selectionValue = item_selection_encode(type, id); - bool isMultiSelected = isSelectableItem && selectionType == type && itemSelection.contains(selectionValue); - bool isTypeNone = type == anm2::NONE; auto colorVec = baseColorVec; - if ((isActive || isMultiSelected) && !isTypeNone) + if (isReferenced && type != anm2::NONE) { if (isLightTheme) colorVec = ITEM_COLOR_LIGHT_SELECTED[type_index(type)]; @@ -789,12 +570,6 @@ namespace anm2ed::imgui color = !isVisible ? to_imvec4(colorVec * COLOR_HIDDEN_MULTIPLIER) : color; ImGui::PushStyleColor(ImGuiCol_ChildBg, color); - if (index == 1 && type != anm2::NONE) - { - auto cursor = ImGui::GetCursorPos(); - ImGui::SetCursorPos(ImVec2(cursor.x, cursor.y - style.ItemSpacing.y)); - } - ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, style.ItemSpacing); ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, style.WindowPadding); @@ -803,63 +578,26 @@ namespace anm2ed::imgui if (ImGui::BeginChild(label.c_str(), itemSize, ImGuiChildFlags_Borders, ImGuiWindowFlags_NoScrollWithMouse)) { - auto isReferenced = reference.itemType == type && reference.itemID == id; - auto cursorPos = ImGui::GetCursorPos(); - if (!isTypeNone) + if (type != anm2::NONE) { ImGui::SetCursorPos(to_imvec2(to_vec2(cursorPos) - to_vec2(style.ItemSpacing))); - ImGui::SetNextItemAllowOverlap(); ImGui::PushStyleColor(ImGuiCol_Header, ImVec4()); ImGui::PushStyleColor(ImGuiCol_HeaderActive, ImVec4()); ImGui::PushStyleColor(ImGuiCol_HeaderHovered, ImVec4()); - if (isSelectableItem && selectionIndex != -1) ImGui::SetNextItemSelectionUserData(selectionIndex); - bool isRootLike = type == anm2::ROOT || type == anm2::TRIGGER; - if (ImGui::Selectable("##Item Button", isSelectableItem && isMultiSelected, ImGuiSelectableFlags_SelectOnNav, - itemSize)) + ImGui::SetNextItemStorageID(id); + if (ImGui::Selectable("##Item Button", isReferenced, ImGuiSelectableFlags_SelectOnClick, itemSize)) { - if (isSelectableItem) - { - auto previousType = item_selection_type_get(); - bool typeMismatch = !itemSelection.empty() && previousType != anm2::NONE && previousType != type; - if (typeMismatch) - { - itemSelection.clear(); - itemSelection.insert(selectionValue); - } - item_selection_sync(); - } - else if (isRootLike) - item_selection_clear(); - if (type == anm2::LAYER) document.spritesheet.reference = anm2.content.layers[id].spritesheetID; - reference_set_item(type, id); } ImGui::PopStyleColor(3); - bool isItemHovered = ImGui::IsItemHovered(); - if (isItemHovered) items.hovered = id; - if (isItemHovered) + if (ImGui::IsItemHovered()) { - if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) - { - switch (type) - { - case anm2::LAYER: - manager.layer_properties_open(id); - break; - case anm2::NULL_: - manager.null_properties_open(id); - default: - break; - } - } - } + if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) item_base_properties_open(type, id); - if (isItemHovered) - { auto& imguiStyle = ImGui::GetStyle(); auto previousTooltipFlags = imguiStyle.HoverFlagsForTooltipMouse; auto previousTooltipDelay = imguiStyle.HoverDelayNormal; @@ -1074,7 +812,6 @@ namespace anm2ed::imgui ImGui::EndChild(); ImGui::PopStyleColor(); ImGui::PopStyleVar(2); - index++; ImGui::PopID(); }; @@ -1099,44 +836,15 @@ namespace anm2ed::imgui { ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, ImVec2()); ImGui::PushStyleVar(ImGuiStyleVar_ScrollbarSize, 0.0f); - if (animation && ImGui::IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows) && - ImGui::Shortcut(ImGuiMod_Ctrl | ImGuiKey_A, ImGuiInputFlags_RouteFocused)) - { - auto preferredType = reference.itemType == anm2::LAYER || reference.itemType == anm2::NULL_ - ? reference.itemType - : (items.reference == (int)anm2::LAYER || items.reference == (int)anm2::NULL_ - ? (anm2::Type)items.reference - : item_selection_type_get()); - auto targetType = preferredType; - if (targetType == anm2::NONE || !item_type_has_visible(targetType)) - { - if (item_type_has_visible(anm2::LAYER)) - targetType = anm2::LAYER; - else if (item_type_has_visible(anm2::NULL_)) - targetType = anm2::NULL_; - else - targetType = anm2::NONE; - } - - if (targetType != anm2::NONE) item_selection_select_all(targetType); - } - if (animation && ImGui::IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows) && - ImGui::Shortcut(ImGuiKey_Escape, ImGuiInputFlags_RouteFocused)) - { - item_selection_clear(); - reference_clear(); - } if (ImGui::BeginTable("##Item Table", 1, ImGuiTableFlags_Borders | ImGuiTableFlags_ScrollY)) { ImGui::GetCurrentWindow()->Flags |= ImGuiWindowFlags_NoScrollWithMouse; ImGui::SetScrollY(scroll.y); - int index{}; - ImGui::TableSetupScrollFreeze(0, 1); ImGui::TableSetupColumn("##Items"); - auto item_child_row = [&](anm2::Type type, int id = -1) + auto item_child_row = [&](anm2::Type type, int id = -1, int index = 0) { ImGui::TableNextRow(); ImGui::TableSetColumnIndex(0); @@ -1145,29 +853,24 @@ namespace anm2ed::imgui item_child_row(anm2::NONE); + int index{}; if (animation) { - item_selection_prune(); - itemSelection.start(itemSelectionIndexMap.size()); + item_child_row(anm2::ROOT, -1, index++); - item_child_row(anm2::ROOT); - - for (auto& id : animation->layerOrder) + for (auto& id : animation->layerOrder | std::views::reverse) { if (!settings.timelineIsShowUnused && animation->layerAnimations[id].frames.empty()) continue; - item_child_row(anm2::LAYER, id); + item_child_row(anm2::LAYER, id, index++); } for (auto& [id, nullAnimation] : animation->nullAnimations) { if (!settings.timelineIsShowUnused && nullAnimation.frames.empty()) continue; - item_child_row(anm2::NULL_, id); + item_child_row(anm2::NULL_, id, index++); } item_child_row(anm2::TRIGGER); - - itemSelection.finish(); - item_selection_sync(); } if (isHorizontalScroll && ImGui::GetCurrentWindow()->ScrollbarY) @@ -1202,36 +905,10 @@ namespace anm2ed::imgui set_item_tooltip_shortcut(localize.get(TOOLTIP_ADD_ITEM), settings.shortcutAdd); ImGui::SameLine(); - auto selectionType = item_selection_type_get(); - bool hasSelection = !itemSelection.empty() && (selectionType == anm2::LAYER || selectionType == anm2::NULL_); - bool hasReferenceItem = document.item_get() != nullptr; - ImGui::BeginDisabled(!hasSelection && !hasReferenceItem); - { - shortcut(manager.chords[SHORTCUT_REMOVE]); - if (ImGui::Button(localize.get(BASIC_REMOVE), widgetSize)) - { - auto remove = [&]() - { - if (hasSelection) - { - std::vector ids{}; - ids.reserve(itemSelection.size()); - for (auto value : itemSelection) - ids.push_back(item_selection_decode(value)); - std::sort(ids.begin(), ids.end()); - for (auto id : ids) - animation->item_remove(selectionType, id); - item_selection_clear(); - } - else if (reference.itemType == anm2::LAYER || reference.itemType == anm2::NULL_) - animation->item_remove(reference.itemType, reference.itemID); - reference_clear(); - }; - - DOCUMENT_EDIT(document, localize.get(EDIT_REMOVE_ITEMS), Document::ITEMS, remove()); - } - set_item_tooltip_shortcut(localize.get(TOOLTIP_REMOVE_ITEMS), settings.shortcutRemove); - } + ImGui::BeginDisabled(!document.item_get()); + shortcut(manager.chords[SHORTCUT_REMOVE]); + if (ImGui::Button(localize.get(BASIC_REMOVE), widgetSize)) item_remove(); + set_item_tooltip_shortcut(localize.get(TOOLTIP_REMOVE_ITEMS), settings.shortcutRemove); ImGui::EndDisabled(); } ImGui::EndDisabled(); @@ -1443,12 +1120,6 @@ namespace anm2ed::imgui if (ImGui::Selectable("##Frame Button", isSelected, ImGuiSelectableFlags_None, buttonSize)) { if (type == anm2::LAYER) document.spritesheet.reference = anm2.content.layers[id].spritesheetID; - if (type == anm2::LAYER || type == anm2::NULL_) - { - itemSelection.clear(); - itemSelection.insert(item_selection_encode(type, id)); - item_selection_sync(); - } if (type != anm2::TRIGGER) { @@ -1863,7 +1534,7 @@ namespace anm2ed::imgui frames_child_row(anm2::ROOT); - for (auto& id : animation->layerOrder) + for (auto& id : animation->layerOrder | std::views::reverse) { if (auto item = animation->item_get(anm2::LAYER, id); item) if (!settings.timelineIsShowUnused && item->frames.empty()) continue; @@ -2173,13 +1844,7 @@ namespace anm2ed::imgui document.change(Document::ITEMS); - reference = addReference; - itemSelection.clear(); - if (addReference.itemType == anm2::LAYER || addReference.itemType == anm2::NULL_) - { - itemSelection.insert(item_selection_encode(addReference.itemType, addReference.itemID)); - item_selection_sync(); - } + reference_set_item(addReference.itemType, addReference.itemID); item_properties_close(); } diff --git a/src/resource/strings.h b/src/resource/strings.h index 4f735c6..4bde5c1 100644 --- a/src/resource/strings.h +++ b/src/resource/strings.h @@ -199,7 +199,7 @@ namespace anm2ed X(LABEL_ANIMATION_PREVIEW_WINDOW, "Animation Preview###Animation Preview", "Vista Previa de Animacion###Animation Preview", "Предпросмотр анимации###Animation Preview", "动画预放###Animation Preview", "애니메이션 프리뷰###Animation Preview") \ X(LABEL_APPEND_FRAMES, "Append Frames", "Anteponer Frames", "Добавить кадры к концу", "在后面添加帧", "뒷프레임에 추가") \ X(LABEL_APPLICATION_NAME, "Anm2Ed", "Anm2Ed", "Anm2Ed", "Anm2Ed", "Anm2Ed") \ - X(LABEL_APPLICATION_VERSION, "Version 2.1", "Version 2.1", "Версия 2.1", "2.1版本", "버전 2.1") \ + X(LABEL_APPLICATION_VERSION, "Version 2.2", "Version 2.2", "Версия 2.2", "2.2版本", "버전 2.2") \ X(LABEL_AUTHOR, "Author", "Autor", "Автор", "制作者", "작성자") \ X(LABEL_AUTOSAVE, "Autosave", "Autoguardado", "Автосохранение", "自动保存", "자동저장") \ X(LABEL_AXES, "Axes", "Ejes", "Оси", "坐标轴", "가로/세로 축") \ @@ -207,6 +207,7 @@ namespace anm2ed X(LABEL_BAKE, "Bake", "Bake", "Запечь", "提前渲染", "베이크") \ X(LABEL_SPLIT, "Split", "Dividir", "Разделить", "拆分", "분할") \ X(LABEL_BORDER, "Border", "Borde", "Границы", "边框", "경계선") \ + X(LABEL_CHANGE_ALL_FRAME_PROPERTIES, "Change All Frame Properties", "Cambiar todas las propiedades de frame", "Изменить все свойства кадра", "更改所有帧属性", "모든 프레임 속성 변경") \ X(LABEL_CENTER_VIEW, "Center View", "Vista de Centro", "Центрировать вид", "视角中心", "가운데서 보기") \ X(LABEL_CLAMP, "Clamp", "Clamp", "Ограничить", "限制数值范围", "작업 영역 제한") \ X(LABEL_CLEAR_LIST, "Clear List", "Limpiar Lista", "Стереть список", "清除列表", "기록 삭제") \ @@ -380,6 +381,10 @@ namespace anm2ed X(SHORTCUT_STRING_ZOOM_IN, "Zoom In", "Zoom In", "Увеличить", "视图放大", "확대") \ X(SHORTCUT_STRING_ZOOM_OUT, "Zoom Out", "Zoom Out", "Уменьшить", "视图缩小", "축소") \ X(SNAPSHOT_RENAME_ANIMATION, "Rename Animation", "Renombrar Animacion", "Переименовать анимацию", "重命名动画", "애니메이션 이름 바꾸기") \ + X(TEXT_SELECT_FRAME, "Select a frame first!", "¡Selecciona primero un frame!", "Сначала выберите кадр!", "请先选择帧!", "먼저 프레임을 선택하세요!") \ + X(TEXT_SELECT_SPRITESHEET, "Select a spritesheet first!", "¡Selecciona primero un spritesheet!", "Сначала выберите спрайт-лист!", "请先选择图集!", "먼저 스프라이트 시트를 선택하세요!") \ + X(TEXT_TOOL_ANIMATION_PREVIEW, "This tool can only be used in Animation Preview!", "¡Esta herramienta solo se puede usar en Vista previa de animación!", "Этот инструмент можно использовать только в \"Предпросмотре анимации\"!", "该工具只能在“动画预放”中使用!", "이 도구는 애니메이션 프리뷰에서만 사용할 수 있습니다!") \ + X(TEXT_TOOL_SPRITESHEET_EDITOR, "This tool can only be used in Spritesheet Editor!", "¡Esta herramienta solo se puede usar en el Editor de spritesheets!", "Этот инструмент можно использовать только в \"Редакторе спрайт-листов\"!", "该工具只能在“图集编辑器”中使用!", "이 도구는 스프라이트 시트 편집기에서만 사용할 수 있습니다!") \ X(TEXT_NEW_ANIMATION, "New Animation", "Nueva Animacion", "Новая анимация", "新动画", "새 애니메이션") \ X(TEXT_NEW_EVENT, "New Event", "Nuevo Evento", "Новое событие", "新事件", "새 이벤트") \ X(TEXT_RECORDING_PROGRESS, "Once recording is complete, rendering may take some time.\nPlease be patient...", "Una vez que el grabado este completo, renderizar puede tomar algo de tiempo. \nPor favor se paciente...", "Когда запись завершена, рендеринг может занять некоторое время.\nПожалуйста потерпите...", "录制完成时,渲染可能会花一些时间.\n请耐心等待...", "녹화가 완료되면 렌더링에 시간이 걸릴 수 있습니다.\n잠시만 기다려 주세요...") \ diff --git a/src/resource/texture.cpp b/src/resource/texture.cpp index 13d7d79..6aae222 100644 --- a/src/resource/texture.cpp +++ b/src/resource/texture.cpp @@ -138,8 +138,11 @@ namespace anm2ed::resource auto index = ((size_t)(y) * (size_t)(size.x) + (size_t)(x)) * CHANNELS; if (index + CHANNELS > pixels.size()) return vec4(0.0f); - return vec4(uint8_to_float(pixels[index + 0]), uint8_to_float(pixels[index + 1]), uint8_to_float(pixels[index + 2]), - uint8_to_float(pixels[index + 3])); + vec4 color{uint8_to_float(pixels[index + 0]), uint8_to_float(pixels[index + 1]), uint8_to_float(pixels[index + 2]), + uint8_to_float(pixels[index + 3])}; + + if (color.a <= 0.0f) color = vec4(0.0f); + return color; } void Texture::pixel_set(ivec2 position, vec4 color) diff --git a/src/types.h b/src/types.h index 4c03a6c..d0adf10 100644 --- a/src/types.h +++ b/src/types.h @@ -70,6 +70,17 @@ namespace anm2ed::types::merge }; } +namespace anm2ed::types::edit +{ + enum Type + { + NONE, + START, + DURING, + END + }; +} + namespace anm2ed::types::color { using namespace glm;