From 72d57a795925ffc9d77baaeec424a977df6e5e03 Mon Sep 17 00:00:00 2001 From: Yaash Jain Date: Thu, 26 Sep 2024 18:30:09 -0700 Subject: [PATCH 1/7] Improve clip and transition color changes on hover and select (#67) Signed-off-by: Yaash Jain --- timeline.cpp | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/timeline.cpp b/timeline.cpp index 8b9925d..00e1828 100644 --- a/timeline.cpp +++ b/timeline.cpp @@ -98,14 +98,15 @@ void DrawItem( auto item_color = GetItemColor(item); if (item_color != "") { fill_color = UIColorFromName(item_color); - fill_color = TintedColorForUI(fill_color); + selected_fill_color = TintedColorForUI(fill_color); + hover_fill_color = TintedColorForUI(fill_color); } if (auto gap = dynamic_cast(item)) { // different colors & style fill_color = appTheme.colors[AppThemeCol_Background]; - selected_fill_color = appTheme.colors[AppThemeCol_GapSelected]; - hover_fill_color = appTheme.colors[AppThemeCol_GapHovered]; + selected_fill_color = TintedColorForUI(fill_color); + hover_fill_color = TintedColorForUI(fill_color); label_str = ""; fancy_corners = false; show_time_range = false; @@ -273,6 +274,12 @@ void DrawTransition( auto selected_fill_color = appTheme.colors[AppThemeCol_TransitionSelected]; auto hover_fill_color = appTheme.colors[AppThemeCol_TransitionHovered]; + if (ColorIsBright(fill_color)) { + auto inverted_fill_color = ColorInvert(fill_color); + selected_fill_color = TintedColorForUI(inverted_fill_color); + hover_fill_color = TintedColorForUI(inverted_fill_color); + } + auto old_pos = ImGui::GetCursorPos(); ImGui::SetCursorPos(render_pos); @@ -584,8 +591,8 @@ void DrawObjectLabel(otio::SerializableObjectWithMetadata* object, float height) auto label_color = appTheme.colors[AppThemeCol_Label]; auto fill_color = appTheme.colors[AppThemeCol_Track]; - auto selected_fill_color = appTheme.colors[AppThemeCol_TrackSelected]; - auto hover_fill_color = appTheme.colors[AppThemeCol_TrackHovered]; + auto selected_fill_color = TintedColorForUI(fill_color); + auto hover_fill_color = TintedColorForUI(fill_color); ImVec2 text_offset(5.0f, 5.0f); From d551b35ccb09f75ba104e1c7ba5346e0cc058a96 Mon Sep 17 00:00:00 2001 From: Fernando Ortega Date: Fri, 27 Sep 2024 15:07:05 -0700 Subject: [PATCH 2/7] Add drag & drop functionality for opening files (in macOS) (#69) Signed-off-by: Fernando Ortega --- app.cpp | 68 +++++++++++++++++++++++++++++++++++++++++++++++++++ main.h | 2 +- main_macos.mm | 8 ++++++ 3 files changed, 77 insertions(+), 1 deletion(-) diff --git a/app.cpp b/app.cpp index 5e2dad8..e81e17f 100644 --- a/app.cpp +++ b/app.cpp @@ -22,6 +22,7 @@ void DrawMenu(); void DrawToolbar(ImVec2 buttonSize); +void DrawDroppedFilesPrompt(); #define DEFINE_APP_THEME_NAMES #include "app.h" @@ -37,6 +38,9 @@ AppTheme appTheme; ImFont* gFont = nullptr; +// Variable to store dropped file to load +std::string prompt_dropped_file = ""; + // Log a message to the terminal void Log(const char* format, ...) { va_list args; @@ -315,6 +319,43 @@ void MainInit(int argc, char** argv, int initial_width, int initial_height) { void MainCleanup() { } +// Validate that a file has the .otio extension +bool is_valid_file(const std::string& filepath) { + size_t last_dot = filepath.find_last_of('.'); + + // If no dot is found, it's not a valid file + if (last_dot == std::string::npos) { + return false; + } + + // Get and check the extension + std::string extension = filepath.substr(last_dot + 1); + return extension == "otio"; +} + +// Accept and open a file path +void FileDropCallback(int count, const char** filepaths) { + if (count > 1){ + Message("Cannot open multiple files."); + return; + } + + else if (count == 0) { + return; + } + + std::string file_path = filepaths[0]; + + if (!is_valid_file(file_path)){ + Message("Invalid file: %s", file_path.c_str()); + return; + } + + // Loading is done in DrawDroppedFilesPrompt() + prompt_dropped_file = file_path; + +} + // Make a button using the fancy icon font bool IconButton(const char* label, const ImVec2 size = ImVec2(0, 0)) { bool result = ImGui::Button(label, size); @@ -383,6 +424,7 @@ void MainGui() { exit(0); } + DrawDroppedFilesPrompt(); DrawMenu(); // ImGui::SameLine(ImGui::GetContentRegionAvailWidth() - button_size.x + @@ -785,6 +827,32 @@ void DrawToolbar(ImVec2 button_size) { #endif } +// Prompt the user to confirm file loading +void DrawDroppedFilesPrompt() { + if (prompt_dropped_file == "") { + return; + } + + ImGui::OpenPopup("Open File?"); + // Modal window for confirmation + if (ImGui::BeginPopupModal("Open File?", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) { + ImGui::Text("Open file \n%s?", prompt_dropped_file.c_str()); + + if (ImGui::Button("Yes")) { + LoadFile(prompt_dropped_file); + prompt_dropped_file = ""; + ImGui::CloseCurrentPopup(); + } + ImGui::SameLine(); + if (ImGui::Button("No")) { + Message(""); // Reset last message + prompt_dropped_file = ""; + ImGui::CloseCurrentPopup(); + } + ImGui::EndPopup(); + } +} + void SelectObject( otio::SerializableObject* object, otio::SerializableObject* context) { diff --git a/main.h b/main.h index 269598b..f05143f 100644 --- a/main.h +++ b/main.h @@ -2,4 +2,4 @@ void MainInit(int argc, char** argv, int initial_width, int initial_height); void MainGui(); void MainCleanup(); - +void FileDropCallback(int count, const char** paths); diff --git a/main_macos.mm b/main_macos.mm index 8af61c1..7536b9f 100644 --- a/main_macos.mm +++ b/main_macos.mm @@ -18,6 +18,11 @@ #import #import +// Accept and open a file path +void file_drop_callback(GLFWwindow* window, int count, const char** paths) { + FileDropCallback(count, paths); +} + static void glfw_error_callback(int error, const char* description) { fprintf(stderr, "Glfw Error %d: %s\n", error, description); @@ -100,6 +105,9 @@ int main(int argc, char** argv) // Our state float clear_color[4] = {0.45f, 0.55f, 0.60f, 1.00f}; + // Set the drop callback + glfwSetDropCallback(window, file_drop_callback); + // Main loop while (!glfwWindowShouldClose(window)) { From ebbf1283c2eb6af748fc369905c8f3da9a67e319 Mon Sep 17 00:00:00 2001 From: Yaash Jain Date: Fri, 27 Sep 2024 23:11:29 -0700 Subject: [PATCH 3/7] Add instruction to do a recursive clone before attempting a build (#66) * Merge in changes from #55 + Improve build steps formatting Co-authored-by: David Douglas @ddouglas Signed-off-by: Yaash Jain --- README.md | 31 +++++++++++++++++++++---------- 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 64de337..f2f7ff8 100644 --- a/README.md +++ b/README.md @@ -26,23 +26,34 @@ Linux (Ubuntu, or similar): - A recent version of CMake - You can get this via `sudo snap install cmake` or by downloading from https://cmake.org/download/ +__Note__: Before building, please ensure that you clone this project with the `--recursive` flag. +This will also clone and initialize all of the submodules that this project depends on. + ## Building (macOS, Windows, Linux) - % mkdir build - % cd build - % cmake .. - % cmake --build . -j - % ./raven ../example.otio +Spin up your favourite terminal and follow these steps: + +```shell + git submodule update --init --recursive + mkdir build + cd build + cmake .. + cmake --build . -j + ./raven ../example.otio +``` ## Building (WASM via Emscripten) You will need to install the [Emscripten toolchain](https://emscripten.org) first. - % mkdir build-web - % cd build-web - % emcmake cmake .. - % cmake --build . - % emrun ./raven.html +```shell + git submodule update --init --recursive + mkdir build-web + cd build-web + emcmake cmake .. + cmake --build . + emrun ./raven.html +``` See also: `serve.py` as an alternative to `emrun`, and as a reference for which HTTP headers are needed to host the WASM build. From b72b4dee55094494af21945b6af1cc114288602d Mon Sep 17 00:00:00 2001 From: Austin Witherspoon <46503991+austinwitherspoon@users.noreply.github.com> Date: Fri, 27 Sep 2024 23:18:40 -0700 Subject: [PATCH 4/7] Expose methods to load OTIO files in WASM through Javascript (#70) * Add LoadString function * Add LoadUrl function * Export LoadUrl and LoadString through emscriptem * Update html template to add OTIO load helpers * Document WASM load methods Signed-off-by: Austin Witherspoon <46503991+austinwitherspoon@users.noreply.github.com> --- CMakeLists.txt | 2 +- README.md | 6 ++++++ app.cpp | 26 ++++++++++++++++++++++++ app.h | 2 ++ main.h | 7 +++++++ main_emscripten.cpp | 48 ++++++++++++++++++++++++++++++++++++++++++--- shell_minimal.html | 37 ++++++++++++++++++++++++++++++++-- 7 files changed, 122 insertions(+), 6 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index d284534..efcebde 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -36,7 +36,7 @@ elseif(EMSCRIPTEN) target_sources(raven PUBLIC main_emscripten.cpp) set(LIBS ${CMAKE_DL_LIBS} SDL2) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -s USE_SDL=2 -s DISABLE_EXCEPTION_CATCHING=1") - set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s WASM=1 -s NO_EXIT_RUNTIME=0 -s ASSERTIONS=1 -s NO_FILESYSTEM=1 -s USE_PTHREADS=1 -s ALLOW_MEMORY_GROWTH=1 -Wl,--shared-memory,--no-check-features --shell-file ${CMAKE_CURRENT_LIST_DIR}/shell_minimal.html") + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s WASM=1 -s NO_EXIT_RUNTIME=0 -s ASSERTIONS=1 -s FETCH -s USE_PTHREADS=1 -s ALLOW_MEMORY_GROWTH=1 -s EXPORTED_RUNTIME_METHODS=ccall,cwrap -Wl,--shared-memory,--no-check-features --shell-file ${CMAKE_CURRENT_LIST_DIR}/shell_minimal.html") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DIMGUI_DISABLE_FILE_FUNCTIONS") set_target_properties(raven PROPERTIES SUFFIX .html) target_link_libraries(raven PUBLIC ${LIBS}) diff --git a/README.md b/README.md index f2f7ff8..53ef4ed 100644 --- a/README.md +++ b/README.md @@ -58,6 +58,12 @@ You will need to install the [Emscripten toolchain](https://emscripten.org) firs See also: `serve.py` as an alternative to `emrun`, and as a reference for which HTTP headers are needed to host the WASM build. +You can load a file into WASM Raven a few ways: +- Add a JSON string to Module.otioLoadString in the HTML file +- Add a URL to Module.otioLoadURL in the HTML file +- Call Module.LoadString(otio_data) at runtime +- Call Module.LoadURL(otio_url) at runtime + Note: The WASM build of raven is missing some features - see the Help Wanted section below. ## Troubleshooting diff --git a/app.cpp b/app.cpp index e81e17f..adab59a 100644 --- a/app.cpp +++ b/app.cpp @@ -245,6 +245,32 @@ void LoadTimeline(otio::Timeline* timeline) { SelectObject(timeline); } +void LoadString(std::string json) { + auto start = std::chrono::high_resolution_clock::now(); + + otio::ErrorStatus error_status; + auto timeline = dynamic_cast( + otio::Timeline::from_json_string(json, &error_status)); + if (!timeline || otio::is_error(error_status)) { + Message( + "Error loading JSON: %s", + otio_error_string(error_status).c_str()); + return; + } + + LoadTimeline(timeline); + + appState.file_path = timeline->name().c_str(); + + auto end = std::chrono::high_resolution_clock::now(); + std::chrono::duration elapsed = (end - start); + double elapsed_seconds = elapsed.count(); + Message( + "Loaded \"%s\" in %.3f seconds", + timeline->name().c_str(), + elapsed_seconds); +} + void LoadFile(std::string path) { auto start = std::chrono::high_resolution_clock::now(); diff --git a/app.h b/app.h index 59ce916..07213df 100644 --- a/app.h +++ b/app.h @@ -128,6 +128,8 @@ void Log(const char* format, ...); void Message(const char* format, ...); std::string Format(const char* format, ...); +void LoadString(std::string json); + std::string otio_error_string(otio::ErrorStatus const& error_status); void SelectObject( diff --git a/main.h b/main.h index f05143f..b57a742 100644 --- a/main.h +++ b/main.h @@ -3,3 +3,10 @@ void MainInit(int argc, char** argv, int initial_width, int initial_height); void MainGui(); void MainCleanup(); void FileDropCallback(int count, const char** paths); + +#ifdef EMSCRIPTEN +extern "C" { +void js_LoadUrl(char* url); +void js_LoadString(char* json); +} +#endif diff --git a/main_emscripten.cpp b/main_emscripten.cpp index 589b838..f4c5760 100644 --- a/main_emscripten.cpp +++ b/main_emscripten.cpp @@ -8,13 +8,16 @@ // See https://github.com/ocornut/imgui/pull/2492 as an example on how to do just that. #include "imgui.h" -#include "imgui_impl_sdl2.h" #include "imgui_impl_opengl3.h" -#include -#include +#include "imgui_impl_sdl2.h" #include #include +#include +#include +#include +#include +#include "app.h" #include "main.h" // Emscripten requires to have full control over the main loop. We're going to store our SDL book-keeping variables globally. @@ -149,3 +152,42 @@ static void main_loop(void* arg) ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); SDL_GL_SwapWindow(g_Window); } + +void LoadUrlSuccess(emscripten_fetch_t* fetch) { + printf("Finished downloading %llu bytes from URL %s.\n", fetch->numBytes, fetch->url); + + std::string otio_string = std::string(fetch->data, fetch->numBytes); + emscripten_fetch_close(fetch); + + LoadString(otio_string); + + appState.file_path = fetch->url; +} + +void LoadUrlFailure(emscripten_fetch_t* fetch) { + printf("Downloading %s failed, HTTP failure status code: %d.\n", fetch->url, fetch->status); + emscripten_fetch_close(fetch); +} + +void LoadUrl(std::string url) { + printf("Downloading %s...\n", url.c_str()); + emscripten_fetch_attr_t attr; + emscripten_fetch_attr_init(&attr); + strcpy(attr.requestMethod, "GET"); + attr.attributes = EMSCRIPTEN_FETCH_LOAD_TO_MEMORY; + // This is async, so we can provide callbacks to handle the result + attr.onsuccess = LoadUrlSuccess; + attr.onerror = LoadUrlFailure; + emscripten_fetch(&attr, url.c_str()); +} + +extern "C" { +EMSCRIPTEN_KEEPALIVE +void js_LoadUrl(char* url) { + LoadUrl(std::string(url)); +} +EMSCRIPTEN_KEEPALIVE +void js_LoadString(char* json) { + LoadString(std::string(json)); +} +} \ No newline at end of file diff --git a/shell_minimal.html b/shell_minimal.html index f2bd0c7..d45b812 100644 --- a/shell_minimal.html +++ b/shell_minimal.html @@ -30,9 +30,42 @@