Skip to content
Open
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
e9e1528
Add basic search and color select for markers
ThomasWilshaw Mar 4, 2025
2f1c0fb
Add 'Filter By' selction and cleanup
ThomasWilshaw Mar 5, 2025
2021d55
Add Effect filtering
ThomasWilshaw Mar 5, 2025
d053029
Add state information to the marker filter to improve performance
ThomasWilshaw Mar 5, 2025
ae92002
Merge branch 'main' into marker_search
ThomasWilshaw Mar 14, 2025
0c3ce05
Fix merge
ThomasWilshaw Mar 14, 2025
2b5c3e3
Redraw list when marker colour selection is cleared
ThomasWilshaw Mar 14, 2025
cf6a058
Improve comemnting
ThomasWilshaw Mar 14, 2025
769e07d
Merge branch 'main' into marker_search
ThomasWilshaw Sep 25, 2025
ae00360
Move marker filter state to tab data structure
ThomasWilshaw Sep 25, 2025
2cdd4ff
Add usage box and tooltip to the marker filter UI
ThomasWilshaw Sep 25, 2025
6d191ca
Add place holder text when no file is loaded
ThomasWilshaw Sep 26, 2025
4a990f8
Move effect state to tab data and fix marker checkboxes
ThomasWilshaw Sep 26, 2025
edc97cf
Remove Usage box and move text to help icon
ThomasWilshaw Sep 27, 2025
02a6234
Tidy up code
ThomasWilshaw Sep 27, 2025
1a7bd5a
Fix marker and effect filter logic
ThomasWilshaw Sep 30, 2025
e5a9c6f
Add a state change variable to TabData that can be used to check if w…
ThomasWilshaw Sep 30, 2025
922f1a8
Move state change code to AppUpdate function and piggy back existing …
ThomasWilshaw Sep 30, 2025
ec24ae3
Fix crash that may be unrelated. Empty strings for labels are not all…
ThomasWilshaw Sep 30, 2025
4b05d51
Fix typos
ThomasWilshaw Sep 30, 2025
12d618b
Fix crash when deleting object with marker
ThomasWilshaw Dec 3, 2025
41f302a
Ensure JSON edits trigger a Marker/Filter redraw
ThomasWilshaw Dec 3, 2025
48e6ca8
Ensure adding a marker causes a redraw
ThomasWilshaw Dec 3, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions app.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -519,6 +519,9 @@ void LoadFile(std::string path) {
return;
}

// Force inspector to relaod marker list
appState.marker_filter_state.reload = true;

appState.active_tab->file_path = path;

auto end = std::chrono::high_resolution_clock::now();
Expand Down
20 changes: 20 additions & 0 deletions app.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#include "imgui.h"
#include "imgui_internal.h"

#include <opentimelineio/marker.h>
#include <opentimelineio/timeline.h>
#include <opentimelineio/serializableObjectWithMetadata.h>
namespace otio = opentimelineio::OPENTIMELINEIO_VERSION;
Expand Down Expand Up @@ -77,6 +78,22 @@ struct AppTheme {
ImU32 colors[AppThemeCol_COUNT];
};


typedef std::pair<otio::SerializableObject::Retainer<otio::Marker>,
otio::SerializableObject::Retainer<otio::Item>> 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; // 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<marker_parent_pair> 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 data specific to individual tabs.
struct TabData {
// This holds the main Schema object. Pretty much everything drills into
Expand Down Expand Up @@ -127,6 +144,9 @@ 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;

Expand Down
165 changes: 146 additions & 19 deletions inspector.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -888,12 +888,59 @@ 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];
// Clear color selction button
if (ImGui::Button("X##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;
int num_color_choices = IM_ARRAYSIZE(marker_color_names);

int current_index = -1;
for (int i = 0; i < num_color_choices; i++) {
if (appState.marker_filter_state.filter_marker_color == color_choices[i]) {
current_index = i;
break;
}
}
if (ImGui::Combo("Color", &current_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;
}
}

// Show selected marker color
ImGui::SameLine();
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();
}

ImGui::SameLine();
marker_filter.Draw("Filter (inc,-exc)");
Comment thread
ThomasWilshaw marked this conversation as resolved.
Outdated

// "Filter By" selection
ImGui::TextUnformatted("Filter By:");
ImGui::SameLine();

static bool name_check = true;
ImGui::Checkbox("Name##filter", &name_check);
ImGui::SameLine();

typedef std::pair<otio::SerializableObject::Retainer<otio::Marker>, otio::SerializableObject::Retainer<otio::Item>> marker_parent_pair;
std::vector<marker_parent_pair> pairs;
static bool item_check = false;
ImGui::Checkbox("Item##filter", &item_check);

auto root = new otio::Stack();
auto global_start = otio::RationalTime(0.0);
Expand All @@ -902,22 +949,62 @@ void DrawMarkersInspector() {
root = timeline->tracks();
global_start = timeline->global_start_time().value_or(otio::RationalTime());

for (const auto& marker : root->markers()) {
pairs.push_back(marker_parent_pair(marker, root));
}
// 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){

for (const auto& child :
timeline->tracks()->find_children())
{
if (const auto& item = dynamic_cast<otio::Item*>(&*child))
std::vector<marker_parent_pair> 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){
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));
}
}

for (const auto& child :
root->find_children())
{
for (const auto& marker : item->markers()) {
pairs.push_back(marker_parent_pair(marker, item));
if (const auto& item = dynamic_cast<otio::Item*>(&*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){
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));
}
}
}
}

// 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", appState.marker_filter_state.pairs.size());

// Draw list
auto selectable_flags = ImGuiSelectableFlags_SpanAllColumns | ImGuiSelectableFlags_AllowItemOverlap;

if (ImGui::BeginTable("Markers",
Expand All @@ -941,13 +1028,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;

Expand Down Expand Up @@ -1009,6 +1096,33 @@ void DrawEffectsInspector() {
typedef std::pair<otio::SerializableObject::Retainer<otio::Effect>, otio::SerializableObject::Retainer<otio::Item>> effect_parent_pair;
std::vector<effect_parent_pair> pairs;

// Filter box
static ImGuiTextFilter effect_filter;

// lear filter button
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);

// Build list of filtered effects
auto root = new otio::Stack();
auto global_start = otio::RationalTime(0.0);

Expand All @@ -1017,21 +1131,34 @@ void DrawEffectsInspector() {
global_start = 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 :
timeline->tracks()->find_children())
root->find_children())
{
if (const auto& item = dynamic_cast<otio::Item*>(&*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",
Expand Down
Loading