From 183f3390fae40deb513576fbe0166a1a279fd3f0 Mon Sep 17 00:00:00 2001 From: shweet Date: Thu, 4 Dec 2025 02:38:18 -0500 Subject: [PATCH] Timeline context menu update; baking/splitting shortcuts --- src/imgui/window/animation_preview.cpp | 25 +++---- src/imgui/window/events.cpp | 4 +- src/imgui/window/timeline.cpp | 96 ++++++++++++++++++++------ src/resource/strings.h | 9 ++- src/settings.h | 4 +- 5 files changed, 101 insertions(+), 37 deletions(-) diff --git a/src/imgui/window/animation_preview.cpp b/src/imgui/window/animation_preview.cpp index d6db441..598256b 100644 --- a/src/imgui/window/animation_preview.cpp +++ b/src/imgui/window/animation_preview.cpp @@ -73,13 +73,12 @@ namespace anm2ed::imgui if (isSuccess) { toasts.push(std::vformat(localize.get(TOAST_EXPORT_RENDERED_FRAMES), std::make_format_args(path))); - logger.info(std::vformat(localize.get(TOAST_EXPORT_RENDERED_FRAMES, anm2ed::ENGLISH), - std::make_format_args(path))); + logger.info( + std::vformat(localize.get(TOAST_EXPORT_RENDERED_FRAMES, anm2ed::ENGLISH), std::make_format_args(path))); } else { - toasts.push(std::vformat(localize.get(TOAST_EXPORT_RENDERED_FRAMES_FAILED), - std::make_format_args(path))); + toasts.push(std::vformat(localize.get(TOAST_EXPORT_RENDERED_FRAMES_FAILED), std::make_format_args(path))); logger.error(std::vformat(localize.get(TOAST_EXPORT_RENDERED_FRAMES_FAILED, anm2ed::ENGLISH), std::make_format_args(path))); } @@ -132,13 +131,12 @@ namespace anm2ed::imgui if (spritesheetTexture.write_png(path)) { toasts.push(std::vformat(localize.get(TOAST_EXPORT_SPRITESHEET), std::make_format_args(path))); - logger.info(std::vformat(localize.get(TOAST_EXPORT_SPRITESHEET, anm2ed::ENGLISH), - std::make_format_args(path))); + logger.info( + std::vformat(localize.get(TOAST_EXPORT_SPRITESHEET, anm2ed::ENGLISH), std::make_format_args(path))); } else { - toasts.push(std::vformat(localize.get(TOAST_EXPORT_SPRITESHEET_FAILED), - std::make_format_args(path))); + toasts.push(std::vformat(localize.get(TOAST_EXPORT_SPRITESHEET_FAILED), std::make_format_args(path))); logger.error(std::vformat(localize.get(TOAST_EXPORT_SPRITESHEET_FAILED, anm2ed::ENGLISH), std::make_format_args(path))); } @@ -155,8 +153,8 @@ namespace anm2ed::imgui } else { - toasts.push(std::vformat(localize.get(TOAST_EXPORT_RENDERED_ANIMATION_FAILED), - std::make_format_args(path))); + toasts.push( + std::vformat(localize.get(TOAST_EXPORT_RENDERED_ANIMATION_FAILED), std::make_format_args(path))); logger.error(std::vformat(localize.get(TOAST_EXPORT_RENDERED_ANIMATION_FAILED, anm2ed::ENGLISH), std::make_format_args(path))); } @@ -640,8 +638,11 @@ namespace anm2ed::imgui unbind(); - sync_checker_pan(); - render_checker_background(ImGui::GetWindowDrawList(), min, max, -size - checkerPan, CHECKER_SIZE); + if (manager.isRecording && settings.renderIsRawAnimation) + { + sync_checker_pan(); + render_checker_background(ImGui::GetWindowDrawList(), min, max, -size - checkerPan, CHECKER_SIZE); + } ImGui::Image(texture, to_imvec2(size)); isPreviewHovered = ImGui::IsItemHovered(); diff --git a/src/imgui/window/events.cpp b/src/imgui/window/events.cpp index 6be0206..a2dd6f8 100644 --- a/src/imgui/window/events.cpp +++ b/src/imgui/window/events.cpp @@ -89,8 +89,8 @@ namespace anm2ed::imgui document.change(Document::EVENTS); else { - toasts.push(std::vformat(localize.get(TOAST_DESERIALIZE_EVENTS_FAILED), - std::make_format_args(errorString))); + toasts.push( + std::vformat(localize.get(TOAST_DESERIALIZE_EVENTS_FAILED), std::make_format_args(errorString))); logger.error(std::vformat(localize.get(TOAST_DESERIALIZE_EVENTS_FAILED, anm2ed::ENGLISH), std::make_format_args(errorString))); } diff --git a/src/imgui/window/timeline.cpp b/src/imgui/window/timeline.cpp index fc1611a..eb3aa74 100644 --- a/src/imgui/window/timeline.cpp +++ b/src/imgui/window/timeline.cpp @@ -324,6 +324,55 @@ namespace anm2ed::imgui } }; + auto frames_bake = [&]() + { + if (auto item = document.item_get()) + for (auto i : frames.selection | std::views::reverse) + item->frames_bake(i, settings.bakeInterval, settings.bakeIsRoundScale, settings.bakeIsRoundRotation); + + frames.clear(); + }; + + auto frame_split = [&]() + { + if (reference.itemType == anm2::TRIGGER) return; + + auto item = document.item_get(); + auto frame = document.frame_get(); + + if (!item || !frame) return; + + auto originalDuration = frame->duration; + if (originalDuration <= 1) return; + + auto frameStartTime = item->frame_time_from_index_get(reference.frameIndex); + int frameStart = (int)std::round(frameStartTime); + int playheadTime = (int)std::floor(playback.time); + int firstDuration = playheadTime - frameStart + 1; + + if (firstDuration <= 0 || firstDuration >= originalDuration) return; + + int secondDuration = originalDuration - firstDuration; + anm2::Frame splitFrame = *frame; + splitFrame.duration = secondDuration; + + auto nextFrame = + vector::in_bounds(item->frames, reference.frameIndex + 1) ? &item->frames[reference.frameIndex + 1] : nullptr; + if (frame->isInterpolated && nextFrame) + { + float interpolation = (float)firstDuration / (float)originalDuration; + splitFrame.rotation = glm::mix(frame->rotation, nextFrame->rotation, interpolation); + splitFrame.position = glm::mix(frame->position, nextFrame->position, interpolation); + splitFrame.scale = glm::mix(frame->scale, nextFrame->scale, interpolation); + splitFrame.colorOffset = glm::mix(frame->colorOffset, nextFrame->colorOffset, interpolation); + splitFrame.tint = glm::mix(frame->tint, nextFrame->tint, interpolation); + } + + frame->duration = firstDuration; + item->frames.insert(item->frames.begin() + reference.frameIndex + 1, splitFrame); + frames_selection_set_reference(); + }; + auto frames_selection_reset = [&]() { frames.clear(); @@ -399,8 +448,8 @@ namespace anm2ed::imgui else { toasts.push(std::format("{} {}", localize.get(TOAST_DESERIALIZE_FRAMES_FAILED), errorString)); - logger.error(std::format("{} {}", localize.get(TOAST_DESERIALIZE_FRAMES_FAILED, anm2ed::ENGLISH), - errorString)); + logger.error( + std::format("{} {}", localize.get(TOAST_DESERIALIZE_FRAMES_FAILED, anm2ed::ENGLISH), errorString)); } } else @@ -420,7 +469,20 @@ namespace anm2ed::imgui cut(); if (ImGui::MenuItem(localize.get(BASIC_COPY), settings.shortcutCopy.c_str(), false, !frames.selection.empty())) copy(); - if (ImGui::MenuItem(localize.get(BASIC_PASTE), nullptr, false, !clipboard.is_empty())) paste(); + if (ImGui::MenuItem(localize.get(BASIC_PASTE), settings.shortcutPaste.c_str(), false, !clipboard.is_empty())) + paste(); + + ImGui::Separator(); + + if (ImGui::MenuItem(localize.get(LABEL_BAKE), settings.shortcutBake.c_str(), false, !frames.selection.empty())) + DOCUMENT_EDIT(document, localize.get(EDIT_BAKE_FRAMES), Document::FRAMES, frames_bake()); + ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_BAKE_FRAMES_OPTIONS)); + + if (ImGui::MenuItem(localize.get(LABEL_SPLIT), settings.shortcutSplit.c_str(), false, + !frames.selection.empty())) + DOCUMENT_EDIT(document, localize.get(EDIT_SPLIT_FRAME), Document::FRAMES, frame_split()); + ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_SPLIT)); + ImGui::EndPopup(); } @@ -672,11 +734,10 @@ namespace anm2ed::imgui ImGui::TextUnformatted(layer.name.c_str()); ImGui::PopFont(); + ImGui::TextUnformatted(std::vformat(localize.get(FORMAT_ID), std::make_format_args(id)).c_str()); ImGui::TextUnformatted( - std::vformat(localize.get(FORMAT_ID), std::make_format_args(id)).c_str()); - ImGui::TextUnformatted(std::vformat(localize.get(FORMAT_SPRITESHEET_ID), - std::make_format_args(layer.spritesheetID)) - .c_str()); + std::vformat(localize.get(FORMAT_SPRITESHEET_ID), std::make_format_args(layer.spritesheetID)) + .c_str()); ImGui::TextUnformatted( std::vformat(localize.get(FORMAT_VISIBLE), std::make_format_args(visibleLabel)).c_str()); ImGui::TextUnformatted( @@ -691,8 +752,7 @@ namespace anm2ed::imgui ImGui::PopFont(); auto rectLabel = yesNoLabel(nullInfo.isShowRect); - ImGui::TextUnformatted( - std::vformat(localize.get(FORMAT_ID), std::make_format_args(id)).c_str()); + ImGui::TextUnformatted(std::vformat(localize.get(FORMAT_ID), std::make_format_args(id)).c_str()); ImGui::TextUnformatted( std::vformat(localize.get(FORMAT_RECT), std::make_format_args(rectLabel)).c_str()); ImGui::TextUnformatted( @@ -1763,7 +1823,7 @@ namespace anm2ed::imgui ImGui::SameLine(); if (ImGui::Button(localize.get(LABEL_BAKE), widgetSize)) bakePopup.open(); - ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_BAKE_FRAMES)); + set_item_tooltip_shortcut(localize.get(TOOLTIP_BAKE_FRAMES), settings.shortcutBake); } ImGui::EndDisabled(); } @@ -2030,17 +2090,7 @@ namespace anm2ed::imgui if (ImGui::Button(localize.get(LABEL_BAKE), widgetSize)) { - auto frames_bake = [&]() - { - if (auto item = document.item_get()) - for (auto i : frames.selection | std::views::reverse) - item->frames_bake(i, interval, isRoundScale, isRoundRotation); - - frames.clear(); - }; - DOCUMENT_EDIT(document, localize.get(EDIT_BAKE_FRAMES), Document::FRAMES, frames_bake()); - bakePopup.close(); } ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_BAKE_FRAMES_OPTIONS)); @@ -2117,6 +2167,12 @@ namespace anm2ed::imgui document.frameTime = item->frame_time_from_index_get(reference.frameIndex); } } + + if (shortcut(manager.chords[SHORTCUT_SPLIT], shortcut::GLOBAL)) + DOCUMENT_EDIT(document, localize.get(EDIT_BAKE_FRAMES), Document::FRAMES, frame_split()); + + if (shortcut(manager.chords[SHORTCUT_BAKE], shortcut::GLOBAL)) + DOCUMENT_EDIT(document, localize.get(EDIT_BAKE_FRAMES), Document::FRAMES, frames_bake()); } if (isTextPushed) ImGui::PopStyleColor(); diff --git a/src/resource/strings.h b/src/resource/strings.h index 75b215b..a0416d0 100644 --- a/src/resource/strings.h +++ b/src/resource/strings.h @@ -143,6 +143,7 @@ namespace anm2ed X(EDIT_REPLACE_SPRITESHEET, "Replace Spritesheet", "Заменить спрайт-лист", "替换图集", "스프라이트 시트 교체") \ X(EDIT_SET_LAYER_PROPERTIES, "Set Layer Properties", "Установить свойства слоя", "更改动画层属性", "레이어 속성 설정") \ X(EDIT_SET_NULL_PROPERTIES, "Set Null Properties", "Установить свойства нуля", "更改Null属性", "Null 속성 설정") \ + X(EDIT_SPLIT_FRAME, "Split Frame", "Разделить кадр", "拆分帧", "프레임 분할") \ X(EDIT_SHORTEN_FRAME, "Shorten Frame", "Укоротить кадр", "缩短帧时长", "프레임 단축") \ X(EDIT_TOGGLE_ITEM_VISIBILITY, "Toggle Item Visibility", "Переключить видимость предмета", "物品可见", "항목 표시/숨기기") \ X(EDIT_TOGGLE_NULL_RECT, "Toggle Null Rectangle", "Переключить прямоугольник нуля", "Null框可见", "Null 사각형 표시/숨기기") \ @@ -195,6 +196,7 @@ namespace anm2ed X(LABEL_AXES, "Axes", "Оси", "坐标轴", "가로/세로 축") \ X(LABEL_BACKGROUND_COLOR, "Background", "Фон", "背景", "배경색") \ X(LABEL_BAKE, "Bake", "Запечь", "提前渲染", "베이크") \ + X(LABEL_SPLIT, "Split", "Разделить", "拆分", "분할") \ X(LABEL_BORDER, "Border", "Границы", "边框", "경계선") \ X(LABEL_CENTER_VIEW, "Center View", "Центрировать вид", "视角中心", "가운데서 보기") \ X(LABEL_CLAMP, "Clamp", "Ограничить", "限制数值范围", "작업 영역 제한") \ @@ -365,6 +367,8 @@ namespace anm2ed X(SHORTCUT_STRING_SAVE, "Save", "Сохранить", "保存", "저장") \ X(SHORTCUT_STRING_SAVE_AS, "Save As", "Сохранить как", "保存为", "다른 이름으로 저장") \ X(SHORTCUT_STRING_SCALE, "Scale", "Изменить масштаб", "缩放", "비율") \ + X(SHORTCUT_STRING_SPLIT, "Split", "", "", "") \ + X(SHORTCUT_STRING_BAKE, "Bake", "Запечь", "提前渲染", "베이크") \ X(SHORTCUT_STRING_SHORTEN_FRAME, "Shorten Frame", "Укоротить кадр", "缩短帧时长", "프레임 단축") \ X(SHORTCUT_STRING_UNDO, "Undo", "Отменить", "撤销", "실행 취소") \ X(SHORTCUT_STRING_ZOOM_IN, "Zoom In", "Увеличить", "视图放大", "확대") \ @@ -433,7 +437,7 @@ namespace anm2ed X(TOOLTIP_AXES, "Toggle the axes' visibility.", "Переключить видимость осей.", "切换坐标轴是否可见.", "가로/세로 축을 표시하거나 숨깁니다.") \ X(TOOLTIP_AXES_COLOR, "Set the color of the axes.", "Установить цвет осей.", "设置坐标轴的颜色.", "가로/세로 축의 색상을 설정합니다.") \ X(TOOLTIP_BACKGROUND_COLOR, "Change the background color.", "Изменить цвет фона.", "更改背景颜色.", "배경색을 변경합니다.") \ - X(TOOLTIP_BAKE_FRAMES, "Turn interpolated frames into uninterpolated ones.", "Превратить интерполированные кадры в неинтерполированных.", "转换线性插值的帧为正常(未线性插值)的帧.", "연결된 프레임을 연결되지 않은 프레임으로 고정화합니다.") \ + X(TOOLTIP_BAKE_FRAMES, "Turn interpolated frames into uninterpolated ones.\nUse the shortcut to bake frames quickly.", "Превратить интерполированные кадры в неинтерполированные.\nИспользуйте горячую клавишу, чтобы быстро запечь кадры.", "将线性插值的帧转换为普通帧。\n使用快捷键可快速烘焙帧.", "연결된 프레임을 연결되지 않은 프레임으로 고정화합니다.\n단축키를 사용하면 프레임을 빠르게 베이크할 수 있습니다.") \ X(TOOLTIP_BAKE_FRAMES_OPTIONS, "Bake the selected frame(s) with the options selected.", "Запечь выбранные кадры с выбранными настройками.", "替换所选旧图集为新图集.", "선택된 프레임을 선택한 옵션으로 베이킹합니다.") \ X(TOOLTIP_BORDER, "Toggle the visibility of borders around layers.", "Переключить видимость границ около слоев.", "切换动画层边框是否可见.", "레이어 주변 경계선을 표시하거나 숨깁니다.") \ X(TOOLTIP_CANCEL_ADD_ITEM, "Cancel adding an item.", "Отменить добавление предмета.", "取消添加物品.", "항목 추가를 취소합니다.") \ @@ -491,7 +495,7 @@ namespace anm2ed X(TOOLTIP_ONIONSKIN_INDEX, "The onionskinned frames will be based on frame index.", "Кадры оньонскина будут основаны на индексе кадров.", "洋葱皮预览的帧会基于帧下标.", "프레임 비교를 프레임 인덱스를 기준으로 합니다.") \ X(TOOLTIP_ONIONSKIN_TIME, "The onionskinned frames will be based on frame time.", "Кадры оньонскина будут основаны на времени кадров.", "洋葱皮预览的帧会基于帧时间.", "프레임 비교를 프레임 시간을 기준으로 합니다.") \ X(TOOLTIP_ONLY_LAYERS_VISIBLE, "Only layers are visible. Press to show all items.", "Только слои видимы. Нажмите, чтобы показать все предметы.", "当前仅有动画层可见. 点击以显示所有物品.", "레이어만 표시합니다. 모두 보려면 누르세요.") \ - X(TOOLTIP_OPEN_MERGE_POPUP, "Open merge popup.", "Открыть всплывающее окно соединения.", "打开合并弹窗.", "병합 팝업을 엽습니다.") \ + X(TOOLTIP_OPEN_MERGE_POPUP, "Open merge popup.\nUse the shortcut to merge quickly.", "Открыть всплывающее окно соединения.\nИспользуйте горячую клавишу, чтобы быстро выполнить слияние.", "打开合并弹窗。\n使用快捷键可快速合并。", "병합 팝업을 엽니다.\n단축키로 빠르게 병합하세요.") \ X(TOOLTIP_OUTPUT_PATH, "Set the output path or directory for the animation.", "Установить путь или директорию вывода для анимации.", "更改动画的输出路径/目录.", "애니메이션의 출력 경로 또는 디렉터리를 설정합니다.") \ X(TOOLTIP_OVERLAY, "Set an animation to be drawn over the current animation.", "Установить анимацию, которая будет выведена над текущей анимацией.", "设置一个当前动画的覆盖动画.", "현재 애니메이션 위에 그려질 애니메이션을 설정합니다.") \ X(TOOLTIP_OVERLAY_ALPHA, "Set the alpha of the overlayed animation.", "Установить прозрачность наложенной анимации.", "更改覆盖动画的透明度.", "오버레이된 애니메이션의 불투명도를 설정합니다.") \ @@ -528,6 +532,7 @@ namespace anm2ed X(TOOLTIP_SETTINGS_SAVE, "Use the configured settings.", "Использовать настроенные настройки.", "应用更改的设置.", "구성된 설정을 사용합니다.") \ X(TOOLTIP_SET_DEFAULT_ANIMATION, "Set the selected animation as the default.", "Установить выбранную анимацию как анимацию по умолчанию.", "把当前所选动画设置为默认动画.", "선택한 애니메이션을 기본 애니메이션으로 설정합니다.") \ X(TOOLTIP_SET_TO_RECOMMENDED, "Use a recommended value for rows/columns.", "Использовать рекомендованное значение для рядов/колонн.", "应用列/行的推荐值.", "행/열에 권장값을 사용합니다.") \ + X(TOOLTIP_SPLIT, "Based on the playhead time, split the selected frame into two.", "С учётом позиции ползунка воспроизведения разделяет выбранный кадр на два.", "根据播放头位置,将所选帧拆分成两个。", "재생 헤드 시간에 따라 선택한 프레임을 두 개로 분할합니다.") \ X(TOOLTIP_SIZE, "Change the crop size the frame uses.", "Изменить размер обрезки, который использует этот кадр.", "更改此帧的裁剪大小.", "프레임에 대응되는 스프라이트 시트의 사용 영역의 크기를 변경합니다.") \ X(TOOLTIP_SOUND, "Toggle sounds playing with triggers.\nBind sounds to events in the Events window.", "Переключить воспроизведения звуков с помощью триггеров.\nПривязывайте звуки к событиям в окне событий.", "切换是否在触发器触发时播放声音.\n可以在事件窗口里链接声音与事件.", "트리거와 함께 사운드를 재생할지 정합니다.\n사운드는 이벤트 창에서 이벤트에 연결하세요.") \ X(TOOLTIP_SOUNDS_PLAY, "Click to play.", "Нажмите, чтобы возпроизвести.", "点击播放.", "클릭하여 재생합니다.") \ diff --git a/src/settings.h b/src/settings.h index 11c5b0d..b52f801 100644 --- a/src/settings.h +++ b/src/settings.h @@ -169,7 +169,7 @@ namespace anm2ed X(SHORTCUT_EXIT, shortcutExit, SHORTCUT_STRING_EXIT, STRING, "Alt+F4") \ /* Edit */ \ X(SHORTCUT_UNDO, shortcutUndo, SHORTCUT_STRING_UNDO, STRING, "Ctrl+Z") \ - X(SHORTCUT_REDO, shortcutRedo, SHORTCUT_STRING_REDO, STRING, "Ctrl+Y") \ + X(SHORTCUT_REDO, shortcutRedo, SHORTCUT_STRING_REDO, STRING, "Ctrl+Y") \ X(SHORTCUT_CUT, shortcutCut, SHORTCUT_STRING_CUT, STRING, "Ctrl+X") \ X(SHORTCUT_COPY, shortcutCopy, SHORTCUT_STRING_COPY, STRING, "Ctrl+C") \ X(SHORTCUT_PASTE, shortcutPaste, SHORTCUT_STRING_PASTE, STRING, "Ctrl+V") \ @@ -194,6 +194,8 @@ namespace anm2ed X(SHORTCUT_ZOOM_IN, shortcutZoomIn, SHORTCUT_STRING_ZOOM_IN, STRING, "Ctrl+Equal") \ X(SHORTCUT_ZOOM_OUT, shortcutZoomOut, SHORTCUT_STRING_ZOOM_OUT, STRING, "Ctrl+Minus") \ /* Timeline / Playback */ \ + X(SHORTCUT_BAKE, shortcutBake, SHORTCUT_STRING_BAKE, STRING, "Ctrl+B") \ + X(SHORTCUT_SPLIT, shortcutSplit, SHORTCUT_STRING_SPLIT, STRING, "Ctrl+P") \ X(SHORTCUT_PLAY_PAUSE, shortcutPlayPause, SHORTCUT_STRING_PLAY_PAUSE, STRING, "Space") \ X(SHORTCUT_MOVE_PLAYHEAD_BACK, shortcutMovePlayheadBack, SHORTCUT_STRING_PLAYHEAD_BACK, STRING, "Comma") \ X(SHORTCUT_MOVE_PLAYHEAD_FORWARD, shortcutMovePlayheadForward, SHORTCUT_STRING_PLAYHEAD_FORWARD, STRING, "Period") \