diff --git a/app.h b/app.h index 8522e73..0818dfd 100644 --- a/app.h +++ b/app.h @@ -92,6 +92,8 @@ struct AppState { float track_height = 30.0f; // current track height (pixels) otio::RationalTime playhead; bool scroll_to_playhead = false; // temporary flag, only true until next frame + bool scroll_left = false; // temporary flag, only true until next frame + bool scroll_right = false; // temporary flag, only true until next frame otio::TimeRange playhead_limit; // min/max limit for moving the playhead, auto-calculated float zebra_factor = 0.1; // opacity of the per-frame zebra stripes diff --git a/main_glfw.cpp b/main_glfw.cpp index e974208..a5e7830 100644 --- a/main_glfw.cpp +++ b/main_glfw.cpp @@ -121,7 +121,7 @@ int main(int argc, char** argv) ImGui::CreateContext(); ImPlot::CreateContext(); ImGuiIO& io = ImGui::GetIO(); (void)io; - io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls + //io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls //io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; // Enable Gamepad Controls io.ConfigFlags |= ImGuiConfigFlags_DockingEnable; // Enable Docking io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable; // Enable Multi-Viewport / Platform Windows diff --git a/main_win32.cpp b/main_win32.cpp index 1cb1924..a31632b 100644 --- a/main_win32.cpp +++ b/main_win32.cpp @@ -87,7 +87,7 @@ int main(int argc, char** argv) IMGUI_CHECKVERSION(); ImGui::CreateContext(); ImGuiIO& io = ImGui::GetIO(); (void)io; - io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls + //io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls //io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; // Enable Gamepad Controls io.ConfigFlags |= ImGuiConfigFlags_DockingEnable; // Enable Docking io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable; // Enable Multi-Viewport / Platform Windows diff --git a/timeline.cpp b/timeline.cpp index ccddf0e..42595fe 100644 --- a/timeline.cpp +++ b/timeline.cpp @@ -119,6 +119,23 @@ void DrawItem( ImGui::BeginGroup(); ImGui::InvisibleButton("##Item", size); + + // Don't skip invisible item if it is the item we have just selected + if (!ImGui::IsItemVisible() + && appState.selected_object == item + && (appState.scroll_left || appState.scroll_right)) { + + otio::Item* selected_item = dynamic_cast(appState.selected_object); + if (appState.scroll_right) { + ImGui::SetScrollX( ImGui::GetScrollX() + + selected_item->range_in_parent().duration().to_seconds() * scale); + appState.scroll_right = false; + } + if (appState.scroll_left) { + ImGui::SetScrollX(selected_item->range_in_parent().start_time().to_seconds() * scale); + appState.scroll_left = false; + } + } if (!ImGui::IsItemVisible()) { // exit early if this item is off-screen ImGui::EndGroup(); @@ -290,6 +307,24 @@ void DrawTransition( ImVec2 p0 = ImGui::GetItemRectMin(); ImVec2 p1 = ImGui::GetItemRectMax(); + + // Don't skip invisible item if it is the item we have just selected + if (!ImGui::IsItemVisible() + && appState.selected_object == transition + && (appState.scroll_left || appState.scroll_right)) { + + otio::Transition* selected_item = dynamic_cast(appState.selected_object); + if (appState.scroll_right) { + ImGui::SetScrollX( ImGui::GetScrollX() + + selected_item->range_in_parent()->duration().to_seconds() * scale); + appState.scroll_right = false; + } + if (appState.scroll_left) { + ImGui::SetScrollX( selected_item->range_in_parent()->start_time().to_seconds() * scale); + appState.scroll_left = false; + } + } + if (!ImGui::IsRectVisible(p0, p1)) { ImGui::EndGroup(); ImGui::PopID(); @@ -1490,6 +1525,125 @@ void DrawTimeline(otio::Timeline* timeline) { appState.scroll_to_playhead = false; } + if (ImGui::IsWindowFocused()){ + // Right arrow + if (ImGui::IsKeyPressed(ImGuiKey::ImGuiKey_RightArrow)) { + std::string selected_type = appState.selected_object->schema_name(); + if (selected_type == "Clip" || selected_type == "Gap" || selected_type == "Transition") { + // Loop through selected items parent track to find the next item + auto parent = dynamic_cast(appState.selected_object)->parent(); + for(auto it = parent->children().begin(); it != parent->children().end(); it++ ){ + // If last item then do nothing + if (std::next(it) == parent->children().end()) { + break; + } + if (*it == appState.selected_object) { + std::advance(it, 1); + SelectObject(*it); + appState.scroll_right = true; + break; + } + } + } + } + // Left Arrow + if (ImGui::IsKeyPressed(ImGuiKey::ImGuiKey_LeftArrow)) { + std::string selected_type = appState.selected_object->schema_name(); + if (selected_type == "Clip" || selected_type == "Gap" || selected_type == "Transition") { + // Loop through selected items parent track to find the previous item + auto parent = dynamic_cast(appState.selected_object)->parent(); + for(auto it = parent->children().begin(); it != parent->children().end(); it++ ){ + if (*it == appState.selected_object) { + // If first item do nothing + if (it == parent->children().begin()) { + break; + } + std::advance(it, -1); + SelectObject(*it); + appState.scroll_left = true; + break; + } + } + } + } + + // The Stacks of video and audio Tracks go in opposite directions + // therefore the logic for the for the Up Arrow on Video tracks is + // the same as the logic for Down Arrow on Audio tracks and vice versa + + auto parent = dynamic_cast(appState.selected_object)->parent(); + auto selected_track = dynamic_cast(parent); + std::string selected_track_type = selected_track->kind(); + + if ((ImGui::IsKeyPressed(ImGuiKey::ImGuiKey_DownArrow)) || + (ImGui::IsKeyPressed(ImGuiKey::ImGuiKey_UpArrow))) { + + // Only run if the right type is selected + std::string selected_type = appState.selected_object->schema_name(); + if (selected_type == "Clip" || selected_type == "Gap" || selected_type == "Transition") { + + // Finding start time varies depending on object type + otio::RationalTime start_time; + if (selected_type == "Clip" || selected_type == "Gap") { + auto child = dynamic_cast(appState.selected_object); + start_time = child->range_in_parent().start_time(); + } else if (selected_type == "Transition") { + auto child = dynamic_cast(appState.selected_object); + start_time = child->range_in_parent().value().start_time(); + } + + // Get the parent object and track stack + auto parent = dynamic_cast(appState.selected_object)->parent(); + auto tracks = dynamic_cast(parent->parent()); + + // Loop through tracks until we find the current one + for(auto it = tracks->children().begin(); it != tracks->children().end(); it++ ){ + otio::Composable* track = *it; + if (track == parent) { + // Down Arrow and Video or Up Arrow and Audio + if ((ImGui::IsKeyPressed(ImGuiKey::ImGuiKey_DownArrow) && selected_track_type == "Video") || + (ImGui::IsKeyPressed(ImGuiKey::ImGuiKey_UpArrow) && selected_track_type == "Audio")) { + // If first item then do nothing + if (it == tracks->children().begin()) { + break; + } + // Select the next track up + std::advance(it, -1); + + // Up Arrow and Video or Down Arrow and Audio + } else if ((ImGui::IsKeyPressed(ImGuiKey::ImGuiKey_UpArrow) && selected_track_type == "Video") || + (ImGui::IsKeyPressed(ImGuiKey::ImGuiKey_DownArrow) && selected_track_type == "Audio")) { + // If last item then do nothing + if (std::next(it) == tracks->children().end()) { + break; + } + // Select the next track up + std::advance(it, 1); + } else{ + break; + } + + otio::Composable* next_it = *it; + otio::Track* next_track = dynamic_cast(next_it); + + // Only iterate over tracks of the same kind + if(next_track->kind() != selected_track_type){ + break; + } + + // If there is an iten that overlaps with the current selection's start time + // select it + if (next_track->child_at_time(start_time)){ + SelectObject(next_track->child_at_time(start_time)); + break; + } + } + } + } + } + + } + // This is helpful when debugging visibility performance optimization // ImGui::SetTooltip("Tracks rendered: %d\nItems rendered: %d", // __tracks_rendered, __items_rendered);