From 631c9c02fa676949ed78f71f0680f78046470a35 Mon Sep 17 00:00:00 2001 From: shweet Date: Wed, 19 Nov 2025 20:04:42 -0500 Subject: [PATCH] some issues with item selection, fixed --- src/imgui/window/timeline.cpp | 209 +++++++++++++++++++++++++++------- 1 file changed, 170 insertions(+), 39 deletions(-) diff --git a/src/imgui/window/timeline.cpp b/src/imgui/window/timeline.cpp index 3bd2443..c09ecd4 100644 --- a/src/imgui/window/timeline.cpp +++ b/src/imgui/window/timeline.cpp @@ -109,9 +109,7 @@ namespace anm2ed::imgui }; 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; - }; + { return (value & ITEM_SELECTION_NULL_FLAG) ? anm2::NULL_ : anm2::LAYER; }; std::vector itemSelectionIndexMap{}; std::unordered_map layerSelectionIndex{}; std::unordered_map nullSelectionIndex{}; @@ -174,18 +172,31 @@ namespace anm2ed::imgui items.hovered = -1; - auto item_selection_type_get = [&]() -> anm2::Type + 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)) return anm2::LAYER; - if (valueType == anm2::NULL_ && animation->nullAnimations.contains(valueID)) return anm2::NULL_; + 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; }; @@ -193,8 +204,6 @@ namespace anm2ed::imgui { itemSelection.clear(); items.reference = -1; - document.layer.selection.clear(); - document.null.selection.clear(); }; auto item_selection_sync = [&]() @@ -205,25 +214,26 @@ namespace anm2ed::imgui return; } - auto type = item_selection_type_get(); - items.reference = (int)type; + 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); - auto assign_selection = [&](MultiSelectStorage& target, anm2::Type assignType) + if (type == anm2::NONE) { - target.clear(); - for (auto encoded : itemSelection) - { - if (item_selection_value_type(encoded) != assignType) continue; - target.insert(item_selection_decode(encoded)); - } - }; - - if (type == anm2::LAYER) - assign_selection(document.layer.selection, anm2::LAYER); - else if (type == anm2::NULL_) - assign_selection(document.null.selection, anm2::NULL_); - else 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 = [&]() @@ -256,8 +266,8 @@ namespace anm2ed::imgui } auto valueID = item_selection_decode(*it); - bool exists = - type == anm2::LAYER ? animation->layerAnimations.contains(valueID) : animation->nullAnimations.contains(valueID); + bool exists = type == anm2::LAYER ? animation->layerAnimations.contains(valueID) + : animation->nullAnimations.contains(valueID); if (!exists) it = itemSelection.erase(it); else @@ -447,6 +457,75 @@ namespace anm2ed::imgui return -1; }; + auto item_type_has_visible = [&](anm2::Type type) + { + if (!animation) return false; + auto& showUnused = settings.timelineIsShowUnused; + auto& showLayersOnly = settings.timelineIsOnlyShowLayers; + 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_) + { + if (showLayersOnly) return false; + 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; + auto& showLayersOnly = settings.timelineIsOnlyShowLayers; + + 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_ && !showLayersOnly) + { + 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_child = [&](anm2::Type type, int id, int& index) { ImGui::PushID(index); @@ -483,6 +562,12 @@ 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); @@ -504,14 +589,14 @@ namespace anm2ed::imgui 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)) { if (isSelectableItem) { auto previousType = item_selection_type_get(); - bool typeMismatch = - !itemSelection.empty() && previousType != anm2::NONE && previousType != type; + bool typeMismatch = !itemSelection.empty() && previousType != anm2::NONE && previousType != type; if (typeMismatch) { itemSelection.clear(); @@ -519,6 +604,8 @@ namespace anm2ed::imgui } item_selection_sync(); } + else if (isRootLike) + item_selection_clear(); if (type == anm2::LAYER) document.spritesheet.reference = anm2.content.layers[id].spritesheetID; @@ -576,7 +663,7 @@ namespace anm2ed::imgui ImGui::Image(resources.icons[icon].id, icon_size_get()); overlay_icon(resources.icons[icon].id, iconTintCurrent); ImGui::SameLine(); - if (isReferenced) ImGui::PushFont(resources.fonts[font::BOLD].get(), font::SIZE); + if (isReferenced) ImGui::PushFont(resources.fonts[font::ITALICS].get(), font::SIZE); ImGui::PushStyleColor(ImGuiCol_Text, itemTextColor); ImGui::TextUnformatted(label.c_str()); ImGui::PopStyleColor(); @@ -674,17 +761,49 @@ namespace anm2ed::imgui { auto itemsChildSize = ImVec2(ImGui::GetTextLineHeightWithSpacing() * 15, ImGui::GetContentRegionAvail().y); - if (ImGui::BeginChild("##Items Child", itemsChildSize, ImGuiChildFlags_Borders)) + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2()); + bool isItemsChildOpen = ImGui::BeginChild("##Items Child", itemsChildSize, ImGuiChildFlags_Borders); + ImGui::PopStyleVar(); + if (isItemsChildOpen) { auto itemsListChildSize = ImVec2(ImGui::GetContentRegionAvail().x, ImGui::GetContentRegionAvail().y - ImGui::GetTextLineHeightWithSpacing() - ImGui::GetStyle().ItemSpacing.y * 2); + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2()); + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2()); if (ImGui::BeginChild("##Items List Child", itemsListChildSize, ImGuiChildFlags_Borders, ImGuiWindowFlags_NoScrollWithMouse | ImGuiWindowFlags_NoScrollbar)) { 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; @@ -707,7 +826,7 @@ namespace anm2ed::imgui if (animation) { item_selection_prune(); - itemSelection.start(itemSelectionIndexMap.size(), ImGuiMultiSelectFlags_ClearOnEscape); + itemSelection.start(itemSelectionIndexMap.size()); item_child_row(anm2::ROOT); @@ -740,6 +859,7 @@ namespace anm2ed::imgui } ImGui::PopStyleVar(2); } + ImGui::PopStyleVar(2); ImGui::EndChild(); ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, style.WindowPadding); @@ -829,6 +949,18 @@ namespace anm2ed::imgui if (ImGui::BeginChild("##Frames Child", childSize, ImGuiChildFlags_Borders)) { + if (ImGui::IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows) && + ImGui::Shortcut(ImGuiKey_Escape, ImGuiInputFlags_RouteFocused)) + { + if (!frames.selection.empty()) + { + reference.frameIndex = -1; + frames_selection_set_reference(); + } + else if (reference.itemType != anm2::NONE || reference.itemID != -1) + reference_clear(); + } + auto drawList = ImGui::GetWindowDrawList(); auto clipMax = drawList->GetClipRectMax(); auto length = animation ? animation->frameNum : anm2.animations.length(); @@ -977,15 +1109,16 @@ namespace anm2ed::imgui ImGui::SetNextItemAllowOverlap(); ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, FRAME_ROUNDING); ImGui::SetNextItemSelectionUserData((int)i); + bool isDifferentItem = reference.itemType != type || reference.itemID != id; if (ImGui::Selectable("##Frame Button", isSelected, ImGuiSelectableFlags_None, buttonSize)) { - if (type == anm2::LAYER) + if (type == anm2::LAYER) document.spritesheet.reference = anm2.content.layers[id].spritesheetID; + if (type == anm2::LAYER || type == anm2::NULL_) { - document.spritesheet.reference = anm2.content.layers[id].spritesheetID; - document.layer.selection = {id}; + itemSelection.clear(); + itemSelection.insert(item_selection_encode(type, id)); + item_selection_sync(); } - else if (type == anm2::NULL_) - document.null.selection = {id}; if (type != anm2::TRIGGER) { @@ -998,6 +1131,7 @@ namespace anm2ed::imgui reference = frameReference; isReferenced = true; + if (isDifferentItem) frames_selection_set_reference(); } ImGui::PopStyleVar(); @@ -1215,10 +1349,7 @@ namespace anm2ed::imgui if (type == anm2::LAYER) { document.spritesheet.reference = anm2.content.layers[id].spritesheetID; - document.layer.selection = {id}; } - else if (type == anm2::NULL_) - document.null.selection = {id}; } } }