From e9e15282dfec07f41c020c6f23c65d8b12d5e585 Mon Sep 17 00:00:00 2001 From: Thomas Wilshaw Date: Tue, 4 Mar 2025 23:03:21 +0000 Subject: [PATCH 01/21] Add basic search and color select for markers Signed-off-by: Thomas Wilshaw --- app.h | 3 +++ inspector.cpp | 67 +++++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 68 insertions(+), 2 deletions(-) diff --git a/app.h b/app.h index 02271b4..88faa2f 100644 --- a/app.h +++ b/app.h @@ -115,6 +115,9 @@ struct AppState { char message[1024]; // single-line message displayed in main window bool message_is_error = false; + // Search + std::string curent_selected_marker_color; + // Toggles for Dear ImGui windows bool show_main_window = true; bool show_style_editor = false; diff --git a/inspector.cpp b/inspector.cpp index 6b31010..a8688a8 100644 --- a/inspector.cpp +++ b/inspector.cpp @@ -710,6 +710,53 @@ void DrawMarkersInspector() { // between the datatypes that OTIO uses vs the one that ImGui widget uses. char tmp_str[1000]; + // Colour box + bool color_clicked = false; + if (ImGui::Button("X##color")) + color_clicked = true; + if (color_clicked) + { + appState.curent_selected_marker_color = ""; + color_clicked = false; + } + ImGui::SameLine(); + + const char** color_choices = marker_color_names; + int num_color_choices = IM_ARRAYSIZE(marker_color_names); + std::string current_color_name; + + int current_index = -1; + for (int i = 0; i < num_color_choices; i++) { + if (appState.curent_selected_marker_color == color_choices[i]) { + current_index = i; + break; + } + } + if (ImGui::Combo("Color", ¤t_index, color_choices, num_color_choices)) { + if (current_index >= 0 && current_index < num_color_choices) { + appState.curent_selected_marker_color = color_choices[current_index]; + } + } + + ImGui::SameLine(); + ImGui::PushStyleColor(ImGuiCol_Text, UIColorFromName(appState.curent_selected_marker_color)); + ImGui::TextUnformatted("\xef\x80\xab"); + ImGui::PopStyleColor(); + + static ImGuiTextFilter marker_filter; + + bool filter_clicked = false; + if (ImGui::Button("X##search")) + filter_clicked = true; + if (filter_clicked) + { + marker_filter.Clear(); + filter_clicked = false; + } + ImGui::SameLine(); + + marker_filter.Draw("Search (inc,-exc)"); + typedef std::pair, otio::SerializableObject::Retainer> marker_parent_pair; std::vector pairs; @@ -717,7 +764,15 @@ void DrawMarkersInspector() { auto global_start = appState.timeline->global_start_time().value_or(otio::RationalTime()); for (const auto& marker : root->markers()) { - pairs.push_back(marker_parent_pair(marker, root)); + if (appState.curent_selected_marker_color != ""){ + if (marker->color() != appState.curent_selected_marker_color){ + continue; + } + } + if (marker_filter.PassFilter(marker->name().c_str()) || + marker_filter.PassFilter(root->name().c_str())) { + pairs.push_back(marker_parent_pair(marker, root)); + } } for (const auto& child : @@ -726,7 +781,15 @@ void DrawMarkersInspector() { if (const auto& item = dynamic_cast(&*child)) { for (const auto& marker : item->markers()) { - pairs.push_back(marker_parent_pair(marker, item)); + if (appState.curent_selected_marker_color != ""){ + if (marker->color() != appState.curent_selected_marker_color){ + continue; + } + } + if (marker_filter.PassFilter(marker->name().c_str()) || + marker_filter.PassFilter(item->name().c_str())) { + pairs.push_back(marker_parent_pair(marker, item)); + } } } } From 2f1c0fb17b70cbaa538d9076c00bc80d8f5d0e0c Mon Sep 17 00:00:00 2001 From: Thomas Wilshaw Date: Wed, 5 Mar 2025 16:06:50 +0000 Subject: [PATCH 02/21] Add 'Filter By' selction and cleanup Signed-off-by: Thomas Wilshaw --- inspector.cpp | 45 +++++++++++++++++++++++++++------------------ 1 file changed, 27 insertions(+), 18 deletions(-) diff --git a/inspector.cpp b/inspector.cpp index a8688a8..69984c3 100644 --- a/inspector.cpp +++ b/inspector.cpp @@ -711,19 +711,14 @@ void DrawMarkersInspector() { char tmp_str[1000]; // Colour box - bool color_clicked = false; - if (ImGui::Button("X##color")) - color_clicked = true; - if (color_clicked) - { + if (ImGui::Button("X##color")){ appState.curent_selected_marker_color = ""; - color_clicked = false; } + ImGui::SameLine(); const char** color_choices = marker_color_names; int num_color_choices = IM_ARRAYSIZE(marker_color_names); - std::string current_color_name; int current_index = -1; for (int i = 0; i < num_color_choices; i++) { @@ -743,20 +738,28 @@ void DrawMarkersInspector() { ImGui::TextUnformatted("\xef\x80\xab"); ImGui::PopStyleColor(); + // Filter box static ImGuiTextFilter marker_filter; - bool filter_clicked = false; - if (ImGui::Button("X##search")) - filter_clicked = true; - if (filter_clicked) - { + if (ImGui::Button("X##filter")) { marker_filter.Clear(); - filter_clicked = false; } + ImGui::SameLine(); + marker_filter.Draw("Filter (inc,-exc)"); - marker_filter.Draw("Search (inc,-exc)"); + // "Filter By" selection + ImGui::TextUnformatted("Filter By:"); + ImGui::SameLine(); + static bool name_check = true; + ImGui::Checkbox("Name##filter", &name_check); + ImGui::SameLine(); + + static bool item_check = false; + ImGui::Checkbox("Item##filter", &item_check); + + // Build marker list based on filtering typedef std::pair, otio::SerializableObject::Retainer> marker_parent_pair; std::vector pairs; @@ -769,8 +772,9 @@ void DrawMarkersInspector() { continue; } } - if (marker_filter.PassFilter(marker->name().c_str()) || - marker_filter.PassFilter(root->name().c_str())) { + if ((marker_filter.PassFilter(marker->name().c_str()) && name_check) || + (marker_filter.PassFilter(root->name().c_str()) && item_check) || + (!name_check && ! item_check)) { pairs.push_back(marker_parent_pair(marker, root)); } } @@ -786,14 +790,19 @@ void DrawMarkersInspector() { continue; } } - if (marker_filter.PassFilter(marker->name().c_str()) || - marker_filter.PassFilter(item->name().c_str())) { + if ((marker_filter.PassFilter(marker->name().c_str()) && name_check) || + (marker_filter.PassFilter(item->name().c_str()) && item_check) || + (!name_check && ! item_check)) { pairs.push_back(marker_parent_pair(marker, item)); } } } } + // Count of filtered items + ImGui::Text("Count: %d", pairs.size()); + + // Draw list auto selectable_flags = ImGuiSelectableFlags_SpanAllColumns | ImGuiSelectableFlags_AllowItemOverlap; if (ImGui::BeginTable("Markers", From 2021d55ce4738b5acad08306d527aab09cb5356a Mon Sep 17 00:00:00 2001 From: Thomas Wilshaw Date: Wed, 5 Mar 2025 16:20:18 +0000 Subject: [PATCH 03/21] Add Effect filtering Signed-off-by: Thomas Wilshaw --- inspector.cpp | 43 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 41 insertions(+), 2 deletions(-) diff --git a/inspector.cpp b/inspector.cpp index 69984c3..1319e1e 100644 --- a/inspector.cpp +++ b/inspector.cpp @@ -894,11 +894,42 @@ void DrawEffectsInspector() { typedef std::pair, otio::SerializableObject::Retainer> effect_parent_pair; std::vector pairs; + // Filter box + static ImGuiTextFilter effect_filter; + + if (ImGui::Button("X##filter")) { + effect_filter.Clear(); + } + + ImGui::SameLine(); + effect_filter.Draw("Filter (inc,-exc)"); + + // "Filter By" selection + ImGui::TextUnformatted("Filter By:"); + ImGui::SameLine(); + + static bool name_check = true; + ImGui::Checkbox("Name##filter", &name_check); + ImGui::SameLine(); + + static bool effect_check = true; + ImGui::Checkbox("Effect##filter", &effect_check); + ImGui::SameLine(); + + static bool item_check = false; + ImGui::Checkbox("Item##filter", &item_check); + + auto root = appState.timeline->tracks(); auto global_start = appState.timeline->global_start_time().value_or(otio::RationalTime()); for (const auto& effect : root->effects()) { - pairs.push_back(effect_parent_pair(effect, root)); + if ((!name_check && !effect_check && !item_check) || + (effect_filter.PassFilter(effect->name().c_str()) && name_check) || + (effect_filter.PassFilter(effect->effect_name().c_str()) && effect_check) || + (effect_filter.PassFilter(root->name().c_str()) && item_check)) { + pairs.push_back(effect_parent_pair(effect, root)); + } } for (const auto& child : @@ -907,11 +938,19 @@ void DrawEffectsInspector() { if (const auto& item = dynamic_cast(&*child)) { for (const auto& effect : item->effects()) { - pairs.push_back(effect_parent_pair(effect, item)); + if ((!name_check && !effect_check && !item_check) || + (effect_filter.PassFilter(effect->name().c_str()) && name_check) || + (effect_filter.PassFilter(effect->effect_name().c_str()) && effect_check) || + (effect_filter.PassFilter(item->name().c_str()) && item_check)){ + pairs.push_back(effect_parent_pair(effect, item)); + } } } } + // Count of filtered items + ImGui::Text("Count: %d", pairs.size()); + auto selectable_flags = ImGuiSelectableFlags_SpanAllColumns | ImGuiSelectableFlags_AllowItemOverlap; if (ImGui::BeginTable("Effects", From d053029d8ef556276a765d097f864a0138f387c0 Mon Sep 17 00:00:00 2001 From: Thomas Wilshaw Date: Wed, 5 Mar 2025 18:52:31 +0000 Subject: [PATCH 04/21] Add state information to the marker filter to improve performance Signed-off-by: Thomas Wilshaw --- app.cpp | 1 + app.h | 15 +++++++++- inspector.cpp | 81 ++++++++++++++++++++++++++++++--------------------- 3 files changed, 63 insertions(+), 34 deletions(-) diff --git a/app.cpp b/app.cpp index 9c8b726..e2108a7 100644 --- a/app.cpp +++ b/app.cpp @@ -436,6 +436,7 @@ void LoadFile(std::string path) { LoadTimeline(timeline); appState.file_path = path; + appState.marker_filter_state.reload = true; auto end = std::chrono::high_resolution_clock::now(); std::chrono::duration elapsed = (end - start); diff --git a/app.h b/app.h index 88faa2f..c8c71bf 100644 --- a/app.h +++ b/app.h @@ -7,6 +7,7 @@ #include "imgui.h" #include "imgui_internal.h" +#include #include namespace otio = opentimelineio::OPENTIMELINEIO_VERSION; @@ -76,6 +77,17 @@ struct AppTheme { ImU32 colors[AppThemeCol_COUNT]; }; +typedef std::pair, otio::SerializableObject::Retainer> marker_parent_pair; + +struct MarkerFilterState { + bool color_change; + std::string filter_text; + bool name_check; + bool item_check; + std::vector pairs; + bool reload = false; +}; + // Struct that holds the application's state struct AppState { // What file did we load? @@ -116,7 +128,8 @@ struct AppState { bool message_is_error = false; // Search - std::string curent_selected_marker_color; + MarkerFilterState marker_filter_state; + std::string filter_marker_color; // Toggles for Dear ImGui windows bool show_main_window = true; diff --git a/inspector.cpp b/inspector.cpp index 1319e1e..6debacc 100644 --- a/inspector.cpp +++ b/inspector.cpp @@ -712,7 +712,7 @@ void DrawMarkersInspector() { // Colour box if (ImGui::Button("X##color")){ - appState.curent_selected_marker_color = ""; + appState.filter_marker_color = ""; } ImGui::SameLine(); @@ -722,19 +722,20 @@ void DrawMarkersInspector() { int current_index = -1; for (int i = 0; i < num_color_choices; i++) { - if (appState.curent_selected_marker_color == color_choices[i]) { + if (appState.filter_marker_color == color_choices[i]) { current_index = i; break; } } if (ImGui::Combo("Color", ¤t_index, color_choices, num_color_choices)) { if (current_index >= 0 && current_index < num_color_choices) { - appState.curent_selected_marker_color = color_choices[current_index]; + appState.filter_marker_color = color_choices[current_index]; + appState.marker_filter_state.color_change = true; } } ImGui::SameLine(); - ImGui::PushStyleColor(ImGuiCol_Text, UIColorFromName(appState.curent_selected_marker_color)); + ImGui::PushStyleColor(ImGuiCol_Text, UIColorFromName(appState.filter_marker_color)); ImGui::TextUnformatted("\xef\x80\xab"); ImGui::PopStyleColor(); @@ -760,47 +761,61 @@ void DrawMarkersInspector() { ImGui::Checkbox("Item##filter", &item_check); // Build marker list based on filtering - typedef std::pair, otio::SerializableObject::Retainer> marker_parent_pair; - std::vector pairs; - auto root = appState.timeline->tracks(); auto global_start = appState.timeline->global_start_time().value_or(otio::RationalTime()); - for (const auto& marker : root->markers()) { - if (appState.curent_selected_marker_color != ""){ - if (marker->color() != appState.curent_selected_marker_color){ - continue; + if (appState.marker_filter_state.color_change || + appState.marker_filter_state.filter_text != marker_filter.InputBuf || + appState.marker_filter_state.name_check != name_check || + appState.marker_filter_state.item_check != item_check || + appState.marker_filter_state.reload){ + + std::vector pairs; + + for (const auto& marker : root->markers()) { + if (appState.filter_marker_color != ""){ + if (marker->color() != appState.filter_marker_color){ + continue; + } + } + if ((marker_filter.PassFilter(marker->name().c_str()) && name_check) || + (marker_filter.PassFilter(root->name().c_str()) && item_check) || + (!name_check && ! item_check)) { + pairs.push_back(marker_parent_pair(marker, root)); } } - if ((marker_filter.PassFilter(marker->name().c_str()) && name_check) || - (marker_filter.PassFilter(root->name().c_str()) && item_check) || - (!name_check && ! item_check)) { - pairs.push_back(marker_parent_pair(marker, root)); - } - } - for (const auto& child : - appState.timeline->tracks()->find_children()) - { - if (const auto& item = dynamic_cast(&*child)) + for (const auto& child : + appState.timeline->tracks()->find_children()) { - for (const auto& marker : item->markers()) { - if (appState.curent_selected_marker_color != ""){ - if (marker->color() != appState.curent_selected_marker_color){ - continue; + if (const auto& item = dynamic_cast(&*child)) + { + for (const auto& marker : item->markers()) { + if (appState.filter_marker_color != ""){ + if (marker->color() != appState.filter_marker_color){ + continue; + } + } + if ((marker_filter.PassFilter(marker->name().c_str()) && name_check) || + (marker_filter.PassFilter(item->name().c_str()) && item_check) || + (!name_check && ! item_check)) { + pairs.push_back(marker_parent_pair(marker, item)); } - } - if ((marker_filter.PassFilter(marker->name().c_str()) && name_check) || - (marker_filter.PassFilter(item->name().c_str()) && item_check) || - (!name_check && ! item_check)) { - pairs.push_back(marker_parent_pair(marker, item)); } } } + + // Update state + appState.marker_filter_state.color_change = false; + appState.marker_filter_state.filter_text = marker_filter.InputBuf; + appState.marker_filter_state.name_check = name_check; + appState.marker_filter_state.item_check = item_check; + appState.marker_filter_state.pairs = pairs; + appState.marker_filter_state.reload = false; } // Count of filtered items - ImGui::Text("Count: %d", pairs.size()); + ImGui::Text("Count: %d", appState.marker_filter_state.pairs.size()); // Draw list auto selectable_flags = ImGuiSelectableFlags_SpanAllColumns | ImGuiSelectableFlags_AllowItemOverlap; @@ -826,13 +841,13 @@ void DrawMarkersInspector() { ImGuiListClipper marker_clipper; - marker_clipper.Begin(pairs.size()); + marker_clipper.Begin(appState.marker_filter_state.pairs.size()); while(marker_clipper.Step()) { for (int row = marker_clipper.DisplayStart; row < marker_clipper.DisplayEnd; row++) { - auto pair = pairs.at(row); + auto pair = appState.marker_filter_state.pairs.at(row); auto marker = pair.first; auto parent = pair.second; From 0c3ce05ac48ad974c20e5af8b33d6e3267438df9 Mon Sep 17 00:00:00 2001 From: Thomas Wilshaw Date: Fri, 14 Mar 2025 12:42:27 +0000 Subject: [PATCH 05/21] Fix merge Signed-off-by: Thomas Wilshaw --- inspector.cpp | 34 ++++++++++++++++++++-------------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/inspector.cpp b/inspector.cpp index 6632bbb..5b2e17c 100644 --- a/inspector.cpp +++ b/inspector.cpp @@ -761,10 +761,13 @@ void DrawMarkersInspector() { static bool item_check = false; ImGui::Checkbox("Item##filter", &item_check); + auto root = new otio::Stack(); + auto global_start = otio::RationalTime(0.0); + // Build marker list based on filtering if (const auto& timeline = dynamic_cast(appState.root.value)) { - auto root = timeline->tracks(); - auto global_start = timeline->global_start_time().value_or(otio::RationalTime()); + root = timeline->tracks(); + global_start = timeline->global_start_time().value_or(otio::RationalTime()); if (appState.marker_filter_state.color_change || appState.marker_filter_state.filter_text != marker_filter.InputBuf || @@ -788,7 +791,7 @@ void DrawMarkersInspector() { } for (const auto& child : - appState.timeline->tracks()->find_children()) + root->find_children()) { if (const auto& item = dynamic_cast(&*child)) { @@ -806,15 +809,15 @@ void DrawMarkersInspector() { } } } - } - // Update state - appState.marker_filter_state.color_change = false; - appState.marker_filter_state.filter_text = marker_filter.InputBuf; - appState.marker_filter_state.name_check = name_check; - appState.marker_filter_state.item_check = item_check; - appState.marker_filter_state.pairs = pairs; - appState.marker_filter_state.reload = false; + // Update state + appState.marker_filter_state.color_change = false; + appState.marker_filter_state.filter_text = marker_filter.InputBuf; + appState.marker_filter_state.name_check = name_check; + appState.marker_filter_state.item_check = item_check; + appState.marker_filter_state.pairs = pairs; + appState.marker_filter_state.reload = false; + } } // Count of filtered items @@ -937,9 +940,12 @@ void DrawEffectsInspector() { static bool item_check = false; ImGui::Checkbox("Item##filter", &item_check); + auto root = new otio::Stack(); + auto global_start = otio::RationalTime(0.0); + if (const auto& timeline = dynamic_cast(appState.root.value)) { - auto root = timeline->tracks(); - auto global_start = timeline->global_start_time().value_or(otio::RationalTime()); + root = timeline->tracks(); + global_start = timeline->global_start_time().value_or(otio::RationalTime()); for (const auto& effect : root->effects()) { if ((!name_check && !effect_check && !item_check) || @@ -951,7 +957,7 @@ void DrawEffectsInspector() { } for (const auto& child : - appState.timeline->tracks()->find_children()) + root->find_children()) { if (const auto& item = dynamic_cast(&*child)) { From 2b5c3e3ea3444d0a3926bfc3a6df763dd921c188 Mon Sep 17 00:00:00 2001 From: Thomas Wilshaw Date: Fri, 14 Mar 2025 12:50:42 +0000 Subject: [PATCH 06/21] Redraw list when marker colour selection is cleared Signed-off-by: Thomas Wilshaw --- inspector.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/inspector.cpp b/inspector.cpp index 5b2e17c..8c57281 100644 --- a/inspector.cpp +++ b/inspector.cpp @@ -714,6 +714,7 @@ void DrawMarkersInspector() { // Colour box if (ImGui::Button("X##color")){ appState.filter_marker_color = ""; + appState.marker_filter_state.color_change = true; } ImGui::SameLine(); From cf6a058f98fe4a42fca2a1846a324b96ea27b6c2 Mon Sep 17 00:00:00 2001 From: Thomas Wilshaw Date: Fri, 14 Mar 2025 13:14:50 +0000 Subject: [PATCH 07/21] Improve comemnting Signed-off-by: Thomas Wilshaw --- app.cpp | 1 + app.h | 23 +++++++++++++---------- inspector.cpp | 28 +++++++++++++++------------- 3 files changed, 29 insertions(+), 23 deletions(-) diff --git a/app.cpp b/app.cpp index af6c1b3..65f0b16 100644 --- a/app.cpp +++ b/app.cpp @@ -498,6 +498,7 @@ void LoadFile(std::string path) { } appState.file_path = path; + // Force inspector to relaod marker list appState.marker_filter_state.reload = true; auto end = std::chrono::high_resolution_clock::now(); diff --git a/app.h b/app.h index 9d14e6c..72f0a09 100644 --- a/app.h +++ b/app.h @@ -78,15 +78,19 @@ struct AppTheme { ImU32 colors[AppThemeCol_COUNT]; }; -typedef std::pair, otio::SerializableObject::Retainer> marker_parent_pair; +typedef std::pair, + otio::SerializableObject::Retainer> marker_parent_pair; +// Store the state of the marker filter to save regenerating the list every frame +// even if the filter options haven't changed struct MarkerFilterState { - bool color_change; - std::string filter_text; - bool name_check; - bool item_check; - std::vector pairs; - bool reload = false; + bool color_change; // Has the color combo box changed? + std::string filter_text; // Text in filter box + bool name_check; // State of filter by Name checkbox + bool item_check; // State of filter by Item checkbox + std::vector pairs; // List of Markers the passed filtering + bool reload = false; // Trigger from loading a new file + std::string filter_marker_color; // Stores the selected color in the combo box }; // Struct that holds the application's state @@ -129,9 +133,8 @@ struct AppState { char message[1024]; // single-line message displayed in main window bool message_is_error = false; - // Search - MarkerFilterState marker_filter_state; - std::string filter_marker_color; + // Filter + MarkerFilterState marker_filter_state; // Persistant state of Marker filtering // Toggles for Dear ImGui windows bool show_main_window = true; diff --git a/inspector.cpp b/inspector.cpp index 8c57281..265e70c 100644 --- a/inspector.cpp +++ b/inspector.cpp @@ -707,16 +707,13 @@ void DrawInspector() { } void DrawMarkersInspector() { - // This temporary variable is used only for a moment to convert - // between the datatypes that OTIO uses vs the one that ImGui widget uses. - char tmp_str[1000]; - - // Colour box + // Clear color selction button if (ImGui::Button("X##color")){ - appState.filter_marker_color = ""; + appState.marker_filter_state.filter_marker_color = ""; appState.marker_filter_state.color_change = true; } + // Draw color selection combo box ImGui::SameLine(); const char** color_choices = marker_color_names; @@ -724,26 +721,28 @@ void DrawMarkersInspector() { int current_index = -1; for (int i = 0; i < num_color_choices; i++) { - if (appState.filter_marker_color == color_choices[i]) { + if (appState.marker_filter_state.filter_marker_color == color_choices[i]) { current_index = i; break; } } if (ImGui::Combo("Color", ¤t_index, color_choices, num_color_choices)) { if (current_index >= 0 && current_index < num_color_choices) { - appState.filter_marker_color = color_choices[current_index]; + appState.marker_filter_state.filter_marker_color = color_choices[current_index]; appState.marker_filter_state.color_change = true; } } + // Show selected marker color ImGui::SameLine(); - ImGui::PushStyleColor(ImGuiCol_Text, UIColorFromName(appState.filter_marker_color)); + ImGui::PushStyleColor(ImGuiCol_Text, UIColorFromName(appState.marker_filter_state.filter_marker_color)); ImGui::TextUnformatted("\xef\x80\xab"); ImGui::PopStyleColor(); // Filter box static ImGuiTextFilter marker_filter; + // Clear filter button if (ImGui::Button("X##filter")) { marker_filter.Clear(); } @@ -770,6 +769,7 @@ void DrawMarkersInspector() { root = timeline->tracks(); global_start = timeline->global_start_time().value_or(otio::RationalTime()); + // Only rebuild list if the filter state has changed if (appState.marker_filter_state.color_change || appState.marker_filter_state.filter_text != marker_filter.InputBuf || appState.marker_filter_state.name_check != name_check || @@ -779,8 +779,8 @@ void DrawMarkersInspector() { std::vector pairs; for (const auto& marker : root->markers()) { - if (appState.filter_marker_color != ""){ - if (marker->color() != appState.filter_marker_color){ + if (appState.marker_filter_state.filter_marker_color != ""){ + if (marker->color() != appState.marker_filter_state.filter_marker_color){ continue; } } @@ -797,8 +797,8 @@ void DrawMarkersInspector() { if (const auto& item = dynamic_cast(&*child)) { for (const auto& marker : item->markers()) { - if (appState.filter_marker_color != ""){ - if (marker->color() != appState.filter_marker_color){ + if (appState.marker_filter_state.filter_marker_color != ""){ + if (marker->color() != appState.marker_filter_state.filter_marker_color){ continue; } } @@ -919,6 +919,7 @@ void DrawEffectsInspector() { // Filter box static ImGuiTextFilter effect_filter; + // lear filter button if (ImGui::Button("X##filter")) { effect_filter.Clear(); } @@ -941,6 +942,7 @@ void DrawEffectsInspector() { static bool item_check = false; ImGui::Checkbox("Item##filter", &item_check); + // Build list of filtered effects auto root = new otio::Stack(); auto global_start = otio::RationalTime(0.0); From ae003605937e44e7766da16f462acf99fbb48698 Mon Sep 17 00:00:00 2001 From: Thomas Wilshaw Date: Thu, 25 Sep 2025 19:37:54 +0100 Subject: [PATCH 08/21] Move marker filter state to tab data structure Signed-off-by: Thomas Wilshaw --- app.cpp | 2 +- app.h | 8 ++++---- inspector.cpp | 55 +++++++++++++++++++++++++++++---------------------- 3 files changed, 36 insertions(+), 29 deletions(-) diff --git a/app.cpp b/app.cpp index 72e27b4..747a639 100644 --- a/app.cpp +++ b/app.cpp @@ -520,7 +520,7 @@ void LoadFile(std::string path) { } // Force inspector to relaod marker list - appState.marker_filter_state.reload = true; + appState.active_tab->marker_filter_state.reload = true; appState.active_tab->file_path = path; diff --git a/app.h b/app.h index 8d4afb5..d37554f 100644 --- a/app.h +++ b/app.h @@ -86,7 +86,7 @@ typedef std::pair, // even if the filter options haven't changed struct MarkerFilterState { bool color_change; // Has the color combo box changed? - std::string filter_text; // Text in filter box + std::string filter_text = ""; // Text in filter box bool name_check; // State of filter by Name checkbox bool item_check; // State of filter by Item checkbox std::vector pairs; // List of Markers the passed filtering @@ -110,6 +110,9 @@ struct TabData { bool first_frame = true; // The timeline drawing code has to be drawn across // two frames so we keep track of that here + + // Filter + MarkerFilterState marker_filter_state; // Persistant state of Marker filtering }; // Struct that holds the application's state @@ -144,9 +147,6 @@ struct AppState { char message[1024]; // single-line message displayed in main window bool message_is_error = false; - // Filter - MarkerFilterState marker_filter_state; // Persistant state of Marker filtering - // Store the currently selected MediaReference index for the inspector. int selected_reference_index = -1; diff --git a/inspector.cpp b/inspector.cpp index e1869ec..01e120d 100644 --- a/inspector.cpp +++ b/inspector.cpp @@ -888,10 +888,16 @@ void DrawInspector() { } void DrawMarkersInspector() { + + if (!GetActiveRoot()) { + return; + } + MarkerFilterState* active_tab_filter_state = &appState.active_tab->marker_filter_state; + // Clear color selction button if (ImGui::Button("X##color")){ - appState.marker_filter_state.filter_marker_color = ""; - appState.marker_filter_state.color_change = true; + active_tab_filter_state->filter_marker_color = ""; + active_tab_filter_state->color_change = true; } // Draw color selection combo box @@ -902,26 +908,27 @@ void DrawMarkersInspector() { int current_index = -1; for (int i = 0; i < num_color_choices; i++) { - if (appState.marker_filter_state.filter_marker_color == color_choices[i]) { + if (active_tab_filter_state->filter_marker_color == color_choices[i]) { current_index = i; break; } } if (ImGui::Combo("Color", ¤t_index, color_choices, num_color_choices)) { if (current_index >= 0 && current_index < num_color_choices) { - appState.marker_filter_state.filter_marker_color = color_choices[current_index]; - appState.marker_filter_state.color_change = true; + active_tab_filter_state->filter_marker_color = color_choices[current_index]; + active_tab_filter_state->color_change = true; } } // Show selected marker color ImGui::SameLine(); - ImGui::PushStyleColor(ImGuiCol_Text, UIColorFromName(appState.marker_filter_state.filter_marker_color)); + ImGui::PushStyleColor(ImGuiCol_Text, UIColorFromName(active_tab_filter_state->filter_marker_color)); ImGui::TextUnformatted("\xef\x80\xab"); ImGui::PopStyleColor(); // Filter box static ImGuiTextFilter marker_filter; + strncpy(marker_filter.InputBuf, active_tab_filter_state->filter_text.c_str(), 256); // Clear filter button if (ImGui::Button("X##filter")) { @@ -950,17 +957,17 @@ void DrawMarkersInspector() { global_start = timeline->global_start_time().value_or(otio::RationalTime()); // Only rebuild list if the filter state has changed - if (appState.marker_filter_state.color_change || - appState.marker_filter_state.filter_text != marker_filter.InputBuf || - appState.marker_filter_state.name_check != name_check || - appState.marker_filter_state.item_check != item_check || - appState.marker_filter_state.reload){ + if (active_tab_filter_state->color_change || + active_tab_filter_state->filter_text != marker_filter.InputBuf || + active_tab_filter_state->name_check != name_check || + active_tab_filter_state->item_check != item_check || + active_tab_filter_state->reload){ std::vector pairs; for (const auto& marker : root->markers()) { - if (appState.marker_filter_state.filter_marker_color != ""){ - if (marker->color() != appState.marker_filter_state.filter_marker_color){ + if (active_tab_filter_state->filter_marker_color != "") { + if (marker->color() != active_tab_filter_state->filter_marker_color) { continue; } } @@ -977,8 +984,8 @@ void DrawMarkersInspector() { if (const auto& item = dynamic_cast(&*child)) { for (const auto& marker : item->markers()) { - if (appState.marker_filter_state.filter_marker_color != ""){ - if (marker->color() != appState.marker_filter_state.filter_marker_color){ + if (active_tab_filter_state->filter_marker_color != "") { + if (marker->color() != active_tab_filter_state->filter_marker_color) { continue; } } @@ -992,17 +999,17 @@ void DrawMarkersInspector() { } // Update state - appState.marker_filter_state.color_change = false; - appState.marker_filter_state.filter_text = marker_filter.InputBuf; - appState.marker_filter_state.name_check = name_check; - appState.marker_filter_state.item_check = item_check; - appState.marker_filter_state.pairs = pairs; - appState.marker_filter_state.reload = false; + active_tab_filter_state->color_change = false; + active_tab_filter_state->filter_text = marker_filter.InputBuf; + active_tab_filter_state->name_check = name_check; + active_tab_filter_state->item_check = item_check; + active_tab_filter_state->pairs = pairs; + active_tab_filter_state->reload = false; } } // Count of filtered items - ImGui::Text("Count: %d", appState.marker_filter_state.pairs.size()); + ImGui::Text("Count: %d", active_tab_filter_state->pairs.size()); // Draw list auto selectable_flags = ImGuiSelectableFlags_SpanAllColumns | ImGuiSelectableFlags_AllowItemOverlap; @@ -1028,13 +1035,13 @@ void DrawMarkersInspector() { ImGuiListClipper marker_clipper; - marker_clipper.Begin(appState.marker_filter_state.pairs.size()); + marker_clipper.Begin(active_tab_filter_state->pairs.size()); while(marker_clipper.Step()) { for (int row = marker_clipper.DisplayStart; row < marker_clipper.DisplayEnd; row++) { - auto pair = appState.marker_filter_state.pairs.at(row); + auto pair = active_tab_filter_state->pairs.at(row); auto marker = pair.first; auto parent = pair.second; From 2cdd4ffa745a571275347dffa991f1b384ca4825 Mon Sep 17 00:00:00 2001 From: Thomas Wilshaw Date: Thu, 25 Sep 2025 20:30:56 +0100 Subject: [PATCH 09/21] Add usage box and tooltip to the marker filter UI Signed-off-by: Thomas Wilshaw --- inspector.cpp | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/inspector.cpp b/inspector.cpp index 01e120d..495ce2b 100644 --- a/inspector.cpp +++ b/inspector.cpp @@ -919,6 +919,9 @@ void DrawMarkersInspector() { active_tab_filter_state->color_change = true; } } + if (ImGui::IsItemHovered(ImGuiHoveredFlags_::ImGuiHoveredFlags_DelayNormal)) { + ImGui::SetTooltip("Select Marker Color\nDefault is all colours selected"); + } // Show selected marker color ImGui::SameLine(); @@ -936,7 +939,7 @@ void DrawMarkersInspector() { } ImGui::SameLine(); - marker_filter.Draw("Filter (inc,-exc)"); + marker_filter.Draw("Filter"); // "Filter By" selection ImGui::TextUnformatted("Filter By:"); @@ -949,6 +952,21 @@ void DrawMarkersInspector() { static bool item_check = false; ImGui::Checkbox("Item##filter", &item_check); + // ImGuiTextFilter is not a widget in its own right so tooltips don't work + // so we have to put the usage instructions in a collapsable header. + + if (ImGui::CollapsingHeader("Filter Usage", ImGuiTreeNodeFlags_None)) { + ImGui::Separator(); + ImGui::TextUnformatted("Filter Usage:"); + ImGui::TextUnformatted("Type to filter by Marker or Item name"); + ImGui::TextUnformatted("To exclude values, use the \"-\" symbol"); + ImGui::TextUnformatted("e.g. -special_marker"); + ImGui::TextUnformatted("To filter multiple values use a comma (,)"); + ImGui::TextUnformatted("e.g. marker1,marker2"); + ImGui::Separator(); + } + + auto root = new otio::Stack(); auto global_start = otio::RationalTime(0.0); From 6d191cacdf6e1ec91935ac5b7fdc1295f9f04661 Mon Sep 17 00:00:00 2001 From: Thomas Wilshaw Date: Fri, 26 Sep 2025 18:02:53 +0100 Subject: [PATCH 10/21] Add place holder text when no file is loaded Signed-off-by: Thomas Wilshaw --- inspector.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/inspector.cpp b/inspector.cpp index 495ce2b..9ef0316 100644 --- a/inspector.cpp +++ b/inspector.cpp @@ -890,6 +890,7 @@ void DrawInspector() { void DrawMarkersInspector() { if (!GetActiveRoot()) { + ImGui::Text("No file loaded."); return; } MarkerFilterState* active_tab_filter_state = &appState.active_tab->marker_filter_state; From 4a990f80566891f01a45fee7ecb62fec0e8129c7 Mon Sep 17 00:00:00 2001 From: Thomas Wilshaw Date: Fri, 26 Sep 2025 19:16:57 +0100 Subject: [PATCH 11/21] Move effect state to tab data and fix marker checkboxes Signed-off-by: Thomas Wilshaw --- app.cpp | 3 +- app.h | 31 ++++++++++++----- inspector.cpp | 92 ++++++++++++++++++++++++++++++++++----------------- 3 files changed, 86 insertions(+), 40 deletions(-) diff --git a/app.cpp b/app.cpp index 747a639..33e88a3 100644 --- a/app.cpp +++ b/app.cpp @@ -519,8 +519,9 @@ void LoadFile(std::string path) { return; } - // Force inspector to relaod marker list + // Force inspector to relaod marker and effect lists appState.active_tab->marker_filter_state.reload = true; + appState.active_tab->effect_filter_state.reload = true; appState.active_tab->file_path = path; diff --git a/app.h b/app.h index d37554f..a1be00f 100644 --- a/app.h +++ b/app.h @@ -83,15 +83,29 @@ typedef std::pair, otio::SerializableObject::Retainer> marker_parent_pair; // Store the state of the marker filter to save regenerating the list every frame -// even if the filter options haven't changed +// if the filter options haven't changed struct MarkerFilterState { - bool color_change; // Has the color combo box changed? - std::string filter_text = ""; // Text in filter box - bool name_check; // State of filter by Name checkbox - bool item_check; // State of filter by Item checkbox + bool color_change; // Has the color combo box changed? + std::string filter_text = ""; // Text in filter box + bool name_check = true; // State of filter by Name checkbox + bool item_check = false; // State of filter by Item checkbox std::vector pairs; // List of Markers the passed filtering - bool reload = false; // Trigger from loading a new file - std::string filter_marker_color; // Stores the selected color in the combo box + bool reload = false; // Trigger from loading a new file + std::string filter_marker_color; // Stores the selected color in the combo box +}; + +typedef std::pair, + otio::SerializableObject::Retainer> effect_parent_pair; + +// Store the state of the effect filter to save regenerating the list every frame +// if the filter options haven't changed +struct EffectFilterState { + std::string filter_text = ""; // Text in filter box + bool reload = false; // Trigger from loading a new file + bool name_check = true; // State of filter by Name checkbox + bool item_check = false; // State of filter by Item checkbox + bool effect_check = true; // State of filter by Effect checkbox + std::vector pairs; //List of Effects that passed filtering }; // Struct that holds data specific to individual tabs. @@ -111,8 +125,9 @@ struct TabData { bool first_frame = true; // The timeline drawing code has to be drawn across // two frames so we keep track of that here - // Filter + // Filters MarkerFilterState marker_filter_state; // Persistant state of Marker filtering + EffectFilterState effect_filter_state; // Persistant state of Effect filtering }; // Struct that holds the application's state diff --git a/inspector.cpp b/inspector.cpp index 9ef0316..bdea578 100644 --- a/inspector.cpp +++ b/inspector.cpp @@ -932,7 +932,7 @@ void DrawMarkersInspector() { // Filter box static ImGuiTextFilter marker_filter; - strncpy(marker_filter.InputBuf, active_tab_filter_state->filter_text.c_str(), 256); + strncpy(marker_filter.InputBuf, active_tab_filter_state->filter_text.c_str(), 256); // InputBuf is hardcoded as 256 chars // Clear filter button if (ImGui::Button("X##filter")) { @@ -946,11 +946,11 @@ void DrawMarkersInspector() { ImGui::TextUnformatted("Filter By:"); ImGui::SameLine(); - static bool name_check = true; + bool name_check = active_tab_filter_state->name_check; ImGui::Checkbox("Name##filter", &name_check); ImGui::SameLine(); - static bool item_check = false; + bool item_check = active_tab_filter_state->item_check; ImGui::Checkbox("Item##filter", &item_check); // ImGuiTextFilter is not a widget in its own right so tooltips don't work @@ -958,7 +958,6 @@ void DrawMarkersInspector() { if (ImGui::CollapsingHeader("Filter Usage", ImGuiTreeNodeFlags_None)) { ImGui::Separator(); - ImGui::TextUnformatted("Filter Usage:"); ImGui::TextUnformatted("Type to filter by Marker or Item name"); ImGui::TextUnformatted("To exclude values, use the \"-\" symbol"); ImGui::TextUnformatted("e.g. -special_marker"); @@ -1119,11 +1118,16 @@ void DrawMarkersInspector() { } void DrawEffectsInspector() { - typedef std::pair, otio::SerializableObject::Retainer> effect_parent_pair; - std::vector pairs; + if (!GetActiveRoot()) { + ImGui::Text("No file loaded."); + return; + } + + EffectFilterState* active_tab_filter_state = &appState.active_tab->effect_filter_state; // Filter box static ImGuiTextFilter effect_filter; + strncpy(effect_filter.InputBuf, active_tab_filter_state->filter_text.c_str(), 256); // InputBuf is hardcoded as 256 chars // lear filter button if (ImGui::Button("X##filter")) { @@ -1131,23 +1135,36 @@ void DrawEffectsInspector() { } ImGui::SameLine(); - effect_filter.Draw("Filter (inc,-exc)"); + effect_filter.Draw("Filter"); // "Filter By" selection ImGui::TextUnformatted("Filter By:"); ImGui::SameLine(); - static bool name_check = true; + bool name_check = active_tab_filter_state->name_check; ImGui::Checkbox("Name##filter", &name_check); ImGui::SameLine(); - static bool effect_check = true; + bool effect_check = active_tab_filter_state->effect_check; ImGui::Checkbox("Effect##filter", &effect_check); ImGui::SameLine(); - static bool item_check = false; + bool item_check = active_tab_filter_state->item_check; ImGui::Checkbox("Item##filter", &item_check); + // ImGuiTextFilter is not a widget in its own right so tooltips don't work + // so we have to put the usage instructions in a collapsable header. + + if (ImGui::CollapsingHeader("Filter Usage", ImGuiTreeNodeFlags_None)) { + ImGui::Separator(); + ImGui::TextUnformatted("Type to filter by Name, Effect type or Item name"); + ImGui::TextUnformatted("To exclude values, use the \"-\" symbol"); + ImGui::TextUnformatted("e.g. -special_effect"); + ImGui::TextUnformatted("To filter multiple values use a comma (,)"); + ImGui::TextUnformatted("e.g. effect1,effect2"); + ImGui::Separator(); + } + // Build list of filtered effects auto root = new otio::Stack(); auto global_start = otio::RationalTime(0.0); @@ -1156,34 +1173,47 @@ void DrawEffectsInspector() { root = timeline->tracks(); global_start = timeline->global_start_time().value_or(otio::RationalTime()); - for (const auto& effect : root->effects()) { - if ((!name_check && !effect_check && !item_check) || - (effect_filter.PassFilter(effect->name().c_str()) && name_check) || - (effect_filter.PassFilter(effect->effect_name().c_str()) && effect_check) || - (effect_filter.PassFilter(root->name().c_str()) && item_check)) { - pairs.push_back(effect_parent_pair(effect, root)); + if (active_tab_filter_state->filter_text != effect_filter.InputBuf || + active_tab_filter_state->effect_check != effect_check || + active_tab_filter_state->item_check != item_check || + active_tab_filter_state->name_check != name_check || + active_tab_filter_state->reload) { + + std::vector pairs; + + for (const auto& effect : root->effects()) { + if ((!name_check && !effect_check && !item_check) || + (effect_filter.PassFilter(effect->name().c_str()) && name_check) || + (effect_filter.PassFilter(effect->effect_name().c_str()) && effect_check) || + (effect_filter.PassFilter(root->name().c_str()) && item_check)) { + pairs.push_back(effect_parent_pair(effect, root)); + } } - } - for (const auto& child : - root->find_children()) - { - if (const auto& item = dynamic_cast(&*child)) - { - for (const auto& effect : item->effects()) { - if ((!name_check && !effect_check && !item_check) || - (effect_filter.PassFilter(effect->name().c_str()) && name_check) || - (effect_filter.PassFilter(effect->effect_name().c_str()) && effect_check) || - (effect_filter.PassFilter(item->name().c_str()) && item_check)){ - pairs.push_back(effect_parent_pair(effect, item)); + for (const auto& child : + root->find_children()) { + if (const auto& item = dynamic_cast(&*child)) { + for (const auto& effect : item->effects()) { + if ((!name_check && !effect_check && !item_check) || + (effect_filter.PassFilter(effect->name().c_str()) && name_check) || + (effect_filter.PassFilter(effect->effect_name().c_str()) && effect_check) || + (effect_filter.PassFilter(item->name().c_str()) && item_check)) { + pairs.push_back(effect_parent_pair(effect, item)); + } } } } + active_tab_filter_state->filter_text = effect_filter.InputBuf; + active_tab_filter_state->effect_check = effect_check; + active_tab_filter_state->item_check = item_check; + active_tab_filter_state->name_check = name_check; + active_tab_filter_state->pairs = pairs; + active_tab_filter_state->reload = false; } } // Count of filtered items - ImGui::Text("Count: %d", pairs.size()); + ImGui::Text("Count: %d", active_tab_filter_state->pairs.size()); auto selectable_flags = ImGuiSelectableFlags_SpanAllColumns | ImGuiSelectableFlags_AllowItemOverlap; @@ -1207,13 +1237,13 @@ void DrawEffectsInspector() { ImGuiListClipper effects_clipper; - effects_clipper.Begin(pairs.size()); + effects_clipper.Begin(active_tab_filter_state->pairs.size()); while (effects_clipper.Step()) { for (int row = effects_clipper.DisplayStart; row < effects_clipper.DisplayEnd; row++) { - auto pair = pairs.at(row); + auto pair = active_tab_filter_state->pairs.at(row); auto effect = pair.first; auto parent = pair.second; From edc97cf14d006cbfb70131c07a067327d34ee425 Mon Sep 17 00:00:00 2001 From: Thomas Wilshaw Date: Sat, 27 Sep 2025 15:39:26 +0100 Subject: [PATCH 12/21] Remove Usage box and move text to help icon Signed-off-by: Thomas Wilshaw --- inspector.cpp | 57 +++++++++++++++++++++++++++------------------------ 1 file changed, 30 insertions(+), 27 deletions(-) diff --git a/inspector.cpp b/inspector.cpp index bdea578..9872371 100644 --- a/inspector.cpp +++ b/inspector.cpp @@ -942,6 +942,21 @@ void DrawMarkersInspector() { ImGui::SameLine(); marker_filter.Draw("Filter"); + // A TextFilter is not a normal widget so we cannot append a tooltip directly too it. + // Instead we add a (?) symbol and add the tooltip to that. + ImGui::SameLine(); + ImGui::TextDisabled("(?)"); + if (ImGui::IsItemHovered(ImGuiHoveredFlags_::ImGuiHoveredFlags_DelayNormal)){ + if (ImGui::BeginTooltip()) { + ImGui::TextUnformatted("Type to filter by Marker or Item name"); + ImGui::TextUnformatted("To exclude values, use the \"-\" symbol"); + ImGui::TextUnformatted("e.g. -special_marker"); + ImGui::TextUnformatted("To filter multiple values use a comma (,)"); + ImGui::TextUnformatted("e.g. marker1,marker2"); + ImGui::EndTooltip(); + } + } + // "Filter By" selection ImGui::TextUnformatted("Filter By:"); ImGui::SameLine(); @@ -953,20 +968,6 @@ void DrawMarkersInspector() { bool item_check = active_tab_filter_state->item_check; ImGui::Checkbox("Item##filter", &item_check); - // ImGuiTextFilter is not a widget in its own right so tooltips don't work - // so we have to put the usage instructions in a collapsable header. - - if (ImGui::CollapsingHeader("Filter Usage", ImGuiTreeNodeFlags_None)) { - ImGui::Separator(); - ImGui::TextUnformatted("Type to filter by Marker or Item name"); - ImGui::TextUnformatted("To exclude values, use the \"-\" symbol"); - ImGui::TextUnformatted("e.g. -special_marker"); - ImGui::TextUnformatted("To filter multiple values use a comma (,)"); - ImGui::TextUnformatted("e.g. marker1,marker2"); - ImGui::Separator(); - } - - auto root = new otio::Stack(); auto global_start = otio::RationalTime(0.0); @@ -1137,6 +1138,21 @@ void DrawEffectsInspector() { ImGui::SameLine(); effect_filter.Draw("Filter"); + // A TextFilter is not a normal widget so we cannot append a tooltip directly too it. + // Instead we add a (?) symbol and add the tooltip to that. + ImGui::SameLine(); + ImGui::TextDisabled("(?)"); + if (ImGui::IsItemHovered(ImGuiHoveredFlags_::ImGuiHoveredFlags_DelayNormal)) { + if (ImGui::BeginTooltip()) { + ImGui::TextUnformatted("Type to filter by Name, Effect type or Item name"); + ImGui::TextUnformatted("To exclude values, use the \"-\" symbol"); + ImGui::TextUnformatted("e.g. -special_effect"); + ImGui::TextUnformatted("To filter multiple values use a comma (,)"); + ImGui::TextUnformatted("e.g. effect1,effect2"); + ImGui::EndTooltip(); + } + } + // "Filter By" selection ImGui::TextUnformatted("Filter By:"); ImGui::SameLine(); @@ -1152,19 +1168,6 @@ void DrawEffectsInspector() { bool item_check = active_tab_filter_state->item_check; ImGui::Checkbox("Item##filter", &item_check); - // ImGuiTextFilter is not a widget in its own right so tooltips don't work - // so we have to put the usage instructions in a collapsable header. - - if (ImGui::CollapsingHeader("Filter Usage", ImGuiTreeNodeFlags_None)) { - ImGui::Separator(); - ImGui::TextUnformatted("Type to filter by Name, Effect type or Item name"); - ImGui::TextUnformatted("To exclude values, use the \"-\" symbol"); - ImGui::TextUnformatted("e.g. -special_effect"); - ImGui::TextUnformatted("To filter multiple values use a comma (,)"); - ImGui::TextUnformatted("e.g. effect1,effect2"); - ImGui::Separator(); - } - // Build list of filtered effects auto root = new otio::Stack(); auto global_start = otio::RationalTime(0.0); From 02a6234fef2ea09eac79c7154b8ab695084a992f Mon Sep 17 00:00:00 2001 From: Thomas Wilshaw Date: Sat, 27 Sep 2025 15:44:20 +0100 Subject: [PATCH 13/21] Tidy up code Signed-off-by: Thomas Wilshaw --- app.cpp | 2 +- app.h | 2 +- inspector.cpp | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app.cpp b/app.cpp index 33e88a3..16817b2 100644 --- a/app.cpp +++ b/app.cpp @@ -519,7 +519,7 @@ void LoadFile(std::string path) { return; } - // Force inspector to relaod marker and effect lists + // Force inspector to reload marker and effect lists appState.active_tab->marker_filter_state.reload = true; appState.active_tab->effect_filter_state.reload = true; diff --git a/app.h b/app.h index a1be00f..704b47d 100644 --- a/app.h +++ b/app.h @@ -125,7 +125,7 @@ struct TabData { bool first_frame = true; // The timeline drawing code has to be drawn across // two frames so we keep track of that here - // Filters + // Filter state MarkerFilterState marker_filter_state; // Persistant state of Marker filtering EffectFilterState effect_filter_state; // Persistant state of Effect filtering }; diff --git a/inspector.cpp b/inspector.cpp index 9872371..fd2df3c 100644 --- a/inspector.cpp +++ b/inspector.cpp @@ -888,11 +888,11 @@ void DrawInspector() { } void DrawMarkersInspector() { - if (!GetActiveRoot()) { ImGui::Text("No file loaded."); return; } + MarkerFilterState* active_tab_filter_state = &appState.active_tab->marker_filter_state; // Clear color selction button @@ -1130,7 +1130,7 @@ void DrawEffectsInspector() { static ImGuiTextFilter effect_filter; strncpy(effect_filter.InputBuf, active_tab_filter_state->filter_text.c_str(), 256); // InputBuf is hardcoded as 256 chars - // lear filter button + // Clear filter button if (ImGui::Button("X##filter")) { effect_filter.Clear(); } From 1a7bd5a7f4e2314f5166b12921199f46cc2c0c10 Mon Sep 17 00:00:00 2001 From: Thomas Wilshaw Date: Tue, 30 Sep 2025 17:20:43 +0100 Subject: [PATCH 14/21] Fix marker and effect filter logic Signed-off-by: Thomas Wilshaw --- inspector.cpp | 90 +++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 76 insertions(+), 14 deletions(-) diff --git a/inspector.cpp b/inspector.cpp index fd2df3c..fe9955b 100644 --- a/inspector.cpp +++ b/inspector.cpp @@ -887,6 +887,36 @@ void DrawInspector() { } } +bool MarkerFilterTest(ImGuiTextFilter* filter, std::string marker_name, bool name_check, std::string marker_item, bool item_check) { + // If we are not filtering by anything return all values + if (!name_check && !item_check) { + return true; + } + + // When filtering values out (-), if a header is checked and it's corresponding + // filter fails, immediately skip. When filtering in, if a header is checked and + // its corresponding filter passes, immediately pass. + if (filter->InputBuf[0] == '-') { + if (name_check && !filter->PassFilter(marker_name.c_str())) { + return false; + } + if (item_check && !filter->PassFilter(marker_item.c_str())) { + return false; + } + + return true; + } else { + if (name_check && filter->PassFilter(marker_name.c_str())) { + return true; + } + if (item_check && filter->PassFilter(marker_item.c_str())) { + return true; + } + + return false; + } +} + void DrawMarkersInspector() { if (!GetActiveRoot()) { ImGui::Text("No file loaded."); @@ -990,9 +1020,7 @@ void DrawMarkersInspector() { continue; } } - if ((marker_filter.PassFilter(marker->name().c_str()) && name_check) || - (marker_filter.PassFilter(root->name().c_str()) && item_check) || - (!name_check && ! item_check)) { + if (MarkerFilterTest(&marker_filter, marker->name(), name_check, root->name(), item_check)) { pairs.push_back(marker_parent_pair(marker, root)); } } @@ -1008,9 +1036,7 @@ void DrawMarkersInspector() { continue; } } - if ((marker_filter.PassFilter(marker->name().c_str()) && name_check) || - (marker_filter.PassFilter(item->name().c_str()) && item_check) || - (!name_check && ! item_check)) { + if (MarkerFilterTest(&marker_filter, marker->name(), name_check, item->name(), item_check)) { pairs.push_back(marker_parent_pair(marker, item)); } } @@ -1118,6 +1144,42 @@ void DrawMarkersInspector() { ImGui::EndTable(); } +bool EffectsFilterTest(ImGuiTextFilter* filter, std::string effect_name, bool name_check, std::string effect_effect, bool effect_check, std::string effect_item, bool item_check) { + // If we are not filtering by anything return all values + if (!name_check && !effect_check && !item_check) { + return true; + } + + // When filtering values out (-), if a header is checked and it's corresponding + // filter fails, immediately skip. When filtering in, if a header is checked and + // its corresponding filter passes, immediately pass. + if (filter->InputBuf[0] == '-') { + if (name_check && !filter->PassFilter(effect_name.c_str())) { + return false; + } + if (effect_check && !filter->PassFilter(effect_effect.c_str())) { + return false; + } + if (item_check && !filter->PassFilter(effect_item.c_str())) { + return false; + } + + return true; + } else { + if (name_check && filter->PassFilter(effect_name.c_str())) { + return true; + } + if (effect_check && filter->PassFilter(effect_effect.c_str())) { + return true; + } + if (item_check && filter->PassFilter(effect_item.c_str())) { + return true; + } + + return false; + } +} + void DrawEffectsInspector() { if (!GetActiveRoot()) { ImGui::Text("No file loaded."); @@ -1185,10 +1247,10 @@ void DrawEffectsInspector() { std::vector pairs; for (const auto& effect : root->effects()) { - if ((!name_check && !effect_check && !item_check) || - (effect_filter.PassFilter(effect->name().c_str()) && name_check) || - (effect_filter.PassFilter(effect->effect_name().c_str()) && effect_check) || - (effect_filter.PassFilter(root->name().c_str()) && item_check)) { + if (EffectsFilterTest(&effect_filter, + effect->name(), name_check, + effect->effect_name(), effect_check, + root->name(), item_check)) { pairs.push_back(effect_parent_pair(effect, root)); } } @@ -1197,10 +1259,10 @@ void DrawEffectsInspector() { root->find_children()) { if (const auto& item = dynamic_cast(&*child)) { for (const auto& effect : item->effects()) { - if ((!name_check && !effect_check && !item_check) || - (effect_filter.PassFilter(effect->name().c_str()) && name_check) || - (effect_filter.PassFilter(effect->effect_name().c_str()) && effect_check) || - (effect_filter.PassFilter(item->name().c_str()) && item_check)) { + if (EffectsFilterTest(&effect_filter, + effect->name(), name_check, + effect->effect_name(), effect_check, + item->name(), item_check)) { pairs.push_back(effect_parent_pair(effect, item)); } } From e5a9c6fbf0efe9dc2d9ec8446768727d604c7dbc Mon Sep 17 00:00:00 2001 From: Thomas Wilshaw Date: Tue, 30 Sep 2025 17:42:34 +0100 Subject: [PATCH 15/21] Add a state change variable to TabData that can be used to check if we need to redraw things Signed-off-by: Thomas Wilshaw --- app.cpp | 4 ++++ app.h | 7 +++++++ editing.cpp | 2 ++ inspector.cpp | 11 ++++++++--- 4 files changed, 21 insertions(+), 3 deletions(-) diff --git a/app.cpp b/app.cpp index 16817b2..c5e5d54 100644 --- a/app.cpp +++ b/app.cpp @@ -946,6 +946,10 @@ void MainGui() { if (appState.show_implot_demo_window) { ImPlot::ShowDemoWindow(); } + + if (appState.active_tab && appState.active_tab->state_change) { + appState.active_tab->state_change = false; + } } void SaveTheme() { diff --git a/app.h b/app.h index 704b47d..1cb5178 100644 --- a/app.h +++ b/app.h @@ -128,6 +128,13 @@ struct TabData { // Filter state MarkerFilterState marker_filter_state; // Persistant state of Marker filtering EffectFilterState effect_filter_state; // Persistant state of Effect filtering + + // This should be set to true whenever something happens that changes to state + // of the tab . Then on the next draw loop we can check this and update things + // as required. See the Effects Inspector for an example. If set to true it is + // reset at the end of MainGui() in app.c. + // TODO: Could use to add a "file changed" indicator to the tab headers + bool state_change = false; }; // Struct that holds the application's state diff --git a/editing.cpp b/editing.cpp index 6987c87..78469bf 100644 --- a/editing.cpp +++ b/editing.cpp @@ -12,6 +12,8 @@ void DeleteSelectedObject() { return; } + appState.active_tab->state_change = true; + if (appState.selected_object == GetActiveRoot()) { CloseTab(appState.active_tab); return; diff --git a/inspector.cpp b/inspector.cpp index fe9955b..3f5b1c5 100644 --- a/inspector.cpp +++ b/inspector.cpp @@ -1005,12 +1005,14 @@ void DrawMarkersInspector() { root = timeline->tracks(); global_start = timeline->global_start_time().value_or(otio::RationalTime()); - // Only rebuild list if the filter state has changed + // Only rebuild list if the filter state or the overall tab state + // has changed if (active_tab_filter_state->color_change || active_tab_filter_state->filter_text != marker_filter.InputBuf || active_tab_filter_state->name_check != name_check || active_tab_filter_state->item_check != item_check || - active_tab_filter_state->reload){ + active_tab_filter_state->reload || + appState.active_tab->state_change){ std::vector pairs; @@ -1238,11 +1240,14 @@ void DrawEffectsInspector() { root = timeline->tracks(); global_start = timeline->global_start_time().value_or(otio::RationalTime()); + // Only rebuild list if the filter state or the overall tab state + // has changed if (active_tab_filter_state->filter_text != effect_filter.InputBuf || active_tab_filter_state->effect_check != effect_check || active_tab_filter_state->item_check != item_check || active_tab_filter_state->name_check != name_check || - active_tab_filter_state->reload) { + active_tab_filter_state->reload || + appState.active_tab->state_change) { std::vector pairs; From 922f1a849f974633e54991ea2bddfe458ac0ca85 Mon Sep 17 00:00:00 2001 From: Thomas Wilshaw Date: Tue, 30 Sep 2025 17:58:54 +0100 Subject: [PATCH 16/21] Move state change code to AppUpdate function and piggy back existing reload flags Signed-off-by: Thomas Wilshaw --- app.cpp | 15 ++++++++++----- app.h | 4 ++-- inspector.cpp | 6 ++---- 3 files changed, 14 insertions(+), 11 deletions(-) diff --git a/app.cpp b/app.cpp index c5e5d54..f105a89 100644 --- a/app.cpp +++ b/app.cpp @@ -626,7 +626,16 @@ bool IconButton(const char* label, const ImVec2 size = ImVec2(0, 0)) { return result; } -void AppUpdate() { } +void AppUpdate() { + // If something ahs happend that changed=s the active tabs state + // then handle any redraw/recalculation flags here + if (appState.active_tab && appState.active_tab->state_change) { + appState.active_tab->marker_filter_state.reload = true; + appState.active_tab->effect_filter_state.reload = true; + + appState.active_tab->state_change = false; + } +} void DrawRoot(otio::SerializableObjectWithMetadata* root) { if (!root){ @@ -946,10 +955,6 @@ void MainGui() { if (appState.show_implot_demo_window) { ImPlot::ShowDemoWindow(); } - - if (appState.active_tab && appState.active_tab->state_change) { - appState.active_tab->state_change = false; - } } void SaveTheme() { diff --git a/app.h b/app.h index 1cb5178..29d624e 100644 --- a/app.h +++ b/app.h @@ -101,7 +101,7 @@ typedef std::pair, // if the filter options haven't changed struct EffectFilterState { std::string filter_text = ""; // Text in filter box - bool reload = false; // Trigger from loading a new file + bool reload = false; // Trigger from loading a new file or state change bool name_check = true; // State of filter by Name checkbox bool item_check = false; // State of filter by Item checkbox bool effect_check = true; // State of filter by Effect checkbox @@ -132,7 +132,7 @@ struct TabData { // This should be set to true whenever something happens that changes to state // of the tab . Then on the next draw loop we can check this and update things // as required. See the Effects Inspector for an example. If set to true it is - // reset at the end of MainGui() in app.c. + // things are handled in AppUpdate() in app.c // TODO: Could use to add a "file changed" indicator to the tab headers bool state_change = false; }; diff --git a/inspector.cpp b/inspector.cpp index 3f5b1c5..33c46f5 100644 --- a/inspector.cpp +++ b/inspector.cpp @@ -1011,8 +1011,7 @@ void DrawMarkersInspector() { active_tab_filter_state->filter_text != marker_filter.InputBuf || active_tab_filter_state->name_check != name_check || active_tab_filter_state->item_check != item_check || - active_tab_filter_state->reload || - appState.active_tab->state_change){ + active_tab_filter_state->reload){ std::vector pairs; @@ -1246,8 +1245,7 @@ void DrawEffectsInspector() { active_tab_filter_state->effect_check != effect_check || active_tab_filter_state->item_check != item_check || active_tab_filter_state->name_check != name_check || - active_tab_filter_state->reload || - appState.active_tab->state_change) { + active_tab_filter_state->reload) { std::vector pairs; From ec24ae310d04d9a7bace2e81cbbf544240caf700 Mon Sep 17 00:00:00 2001 From: Thomas Wilshaw Date: Tue, 30 Sep 2025 18:00:09 +0100 Subject: [PATCH 17/21] Fix crash that may be unrelated. Empty strings for labels are not allowed. Use ## instead Signed-off-by: Thomas Wilshaw --- inspector.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/inspector.cpp b/inspector.cpp index 33c46f5..f92c3c5 100644 --- a/inspector.cpp +++ b/inspector.cpp @@ -803,7 +803,7 @@ void DrawInspector() { } // Set the active media ref key based on user selection - if (ImGui::Combo("", &appState.selected_reference_index, reference_names.data(), num_references)) { + if (ImGui::Combo("##", &appState.selected_reference_index, reference_names.data(), num_references)) { if (appState.selected_reference_index >= 0 && appState.selected_reference_index < num_references) { clip->set_active_media_reference_key(reference_names[appState.selected_reference_index]); } From 4b05d51d93e76146f77373417e0a45c4d0315163 Mon Sep 17 00:00:00 2001 From: Thomas Wilshaw Date: Tue, 30 Sep 2025 18:21:01 +0100 Subject: [PATCH 18/21] Fix typos Signed-off-by: Thomas Wilshaw --- app.cpp | 2 +- app.h | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app.cpp b/app.cpp index f105a89..1fbf335 100644 --- a/app.cpp +++ b/app.cpp @@ -627,7 +627,7 @@ bool IconButton(const char* label, const ImVec2 size = ImVec2(0, 0)) { } void AppUpdate() { - // If something ahs happend that changed=s the active tabs state + // If something has happend that changed the active tabs state // then handle any redraw/recalculation flags here if (appState.active_tab && appState.active_tab->state_change) { appState.active_tab->marker_filter_state.reload = true; diff --git a/app.h b/app.h index 29d624e..e133c19 100644 --- a/app.h +++ b/app.h @@ -90,7 +90,7 @@ struct MarkerFilterState { bool name_check = true; // State of filter by Name checkbox bool item_check = false; // State of filter by Item checkbox std::vector pairs; // List of Markers the passed filtering - bool reload = false; // Trigger from loading a new file + bool reload = false; // Trigger from loading a new file or state change std::string filter_marker_color; // Stores the selected color in the combo box }; @@ -131,7 +131,7 @@ struct TabData { // This should be set to true whenever something happens that changes to state // of the tab . Then on the next draw loop we can check this and update things - // as required. See the Effects Inspector for an example. If set to true it is + // as required. See the Effects Inspector for an example. If set to true // things are handled in AppUpdate() in app.c // TODO: Could use to add a "file changed" indicator to the tab headers bool state_change = false; From 12d618b252d41bd78be776a4d5c1d182a793ee07 Mon Sep 17 00:00:00 2001 From: Thomas Wilshaw Date: Wed, 3 Dec 2025 10:52:17 +0000 Subject: [PATCH 19/21] Fix crash when deleting object with marker Signed-off-by: Thomas Wilshaw --- app.h | 2 +- editing.cpp | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/app.h b/app.h index e133c19..7d3234c 100644 --- a/app.h +++ b/app.h @@ -130,7 +130,7 @@ struct TabData { EffectFilterState effect_filter_state; // Persistant state of Effect filtering // This should be set to true whenever something happens that changes to state - // of the tab . Then on the next draw loop we can check this and update things + // of the tab. Then on the next draw loop we can check this and update things // as required. See the Effects Inspector for an example. If set to true // things are handled in AppUpdate() in app.c // TODO: Could use to add a "file changed" indicator to the tab headers diff --git a/editing.cpp b/editing.cpp index 78469bf..2c261bd 100644 --- a/editing.cpp +++ b/editing.cpp @@ -12,8 +12,15 @@ void DeleteSelectedObject() { return; } + // Flag general state change appState.active_tab->state_change = true; + // This function is called from DrawMenu() which is called before + // DrawInspector() in the main loop. THerefore we have to force update + // Marker and Filter redrawing here + appState.active_tab->marker_filter_state.reload = true; + appState.active_tab->effect_filter_state.reload = true; + if (appState.selected_object == GetActiveRoot()) { CloseTab(appState.active_tab); return; From 41f302ab61bb97ee60c355fce8d790fe8ef34b98 Mon Sep 17 00:00:00 2001 From: Thomas Wilshaw Date: Wed, 3 Dec 2025 10:54:25 +0000 Subject: [PATCH 20/21] Ensure JSON edits trigger a Marker/Filter redraw Signed-off-by: Thomas Wilshaw --- inspector.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/inspector.cpp b/inspector.cpp index f92c3c5..df516d7 100644 --- a/inspector.cpp +++ b/inspector.cpp @@ -209,6 +209,7 @@ void DrawJSONApplyEditButtons() { SelectObject(replacement_object); UpdateJSONInspector(); Message("Edits applied."); + appState.active_tab->state_change = true; } } } @@ -218,6 +219,7 @@ void DrawJSONApplyEditButtons() { if (ImGui::Button("Revert")) { UpdateJSONInspector(); Message("Edits reverted."); + appState.active_tab->state_change = true; } } From 48e6ca8c6b52ae1dd1af5922e7b5338a04c2d7b6 Mon Sep 17 00:00:00 2001 From: Thomas Wilshaw Date: Wed, 3 Dec 2025 10:58:58 +0000 Subject: [PATCH 21/21] Ensure adding a marker causes a redraw Signed-off-by: Thomas Wilshaw --- editing.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/editing.cpp b/editing.cpp index 2c261bd..19b544d 100644 --- a/editing.cpp +++ b/editing.cpp @@ -188,6 +188,9 @@ void AddMarkerAtPlayhead(otio::Item* item, std::string name, std::string color) otio::SerializableObject::Retainer marker = new otio::Marker(name, marked_range, color); item->markers().push_back(marker); + + // Force Marker inpsector to redraw + appState.active_tab->marker_filter_state.reload = true; } void AddTrack(std::string kind) {