diff --git a/CMakeLists.txt b/CMakeLists.txt index 7f2c0a0..b40012d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -45,12 +45,12 @@ else() endif() set(TOUCAN_PLUGINS - toucanColorSpace - toucanDraw - toucanFilter - toucanGenerator - toucanTransform - toucanTransition) + toucanColorPlugin + toucanDrawPlugin + toucanFilterPlugin + toucanGeneratorPlugin + toucanTransformPlugin + toucanTransitionPlugin) enable_testing() @@ -66,10 +66,8 @@ find_package(OpenColorIO) find_package(OpenImageIO) find_package(OTIO) find_package(OpenFX) -if(toucan_VIEW) - find_package(lunasvg) - find_package(dtk) -endif() +find_package(lunasvg) +find_package(dtk) include_directories(lib) include_directories(tests) diff --git a/bin/toucan-filmstrip/App.cpp b/bin/toucan-filmstrip/App.cpp index dbe1419..db52a51 100644 --- a/bin/toucan-filmstrip/App.cpp +++ b/bin/toucan-filmstrip/App.cpp @@ -3,72 +3,67 @@ #include "App.h" -#include +#include + +#include +#include #include namespace toucan { - App::App(std::vector& argv) + void App::_init( + const std::shared_ptr& context, + std::vector& argv) { - _exe = argv.front(); - argv.erase(argv.begin()); - - _args.list.push_back(std::make_shared >( + std::vector > args; + args.push_back(dtk::CmdLineValueArg::create( _args.input, "input", "Input .otio file.")); - auto outArg = std::make_shared >( + auto outArg = dtk::CmdLineValueArg::create( _args.output, "output", "Output image file."); - _args.list.push_back(outArg); + args.push_back(outArg); - _options.list.push_back(std::make_shared( + std::vector > options; + options.push_back(dtk::CmdLineFlagOption::create( _options.verbose, std::vector{ "-v" }, "Print verbose output.")); - _options.list.push_back(std::make_shared( + options.push_back(dtk::CmdLineFlagOption::create( _options.help, std::vector{ "-h" }, "Print help.")); - if (!argv.empty()) - { - for (const auto& option : _options.list) - { - option->parse(argv); - } - if (!_options.help) - { - for (const auto& arg : _args.list) - { - arg->parse(argv); - } - if (argv.size()) - { - _options.help = true; - } - } - } - else - { - _options.help = true; - } + IApp::_init( + context, + argv, + "toucan-filmstrip", + "Render timeline files into filmstrips", + args, + options); } - + + App::App() + {} + App::~App() {} + + std::shared_ptr App::create( + const std::shared_ptr&context, + std::vector&argv) + { + auto out = std::shared_ptr(new App); + out->_init(context, argv); + return out; + } - int App::run() + void App::run() { - if (_options.help) - { - _printHelp(); - return 1; - } - - const std::filesystem::path parentPath = std::filesystem::path(_exe).parent_path(); + const std::filesystem::path parentPath = std::filesystem::path(getExeName()).parent_path(); const std::filesystem::path inputPath(_args.input); const std::filesystem::path outputPath(_args.output); const auto outputSplit = splitFileNameNumber(outputPath.stem().string()); @@ -84,17 +79,10 @@ namespace toucan const int frames = timeRange.duration().value(); // Create the image graph. - std::shared_ptr log; - if (_options.verbose) - { - log = std::make_shared(); - } - ImageGraphOptions imageGraphOptions; - imageGraphOptions.log = log; _graph = std::make_shared( + _context, inputPath.parent_path(), - _timelineWrapper, - imageGraphOptions); + _timelineWrapper); const IMATH_NAMESPACE::V2d imageSize = _graph->getImageSize(); // Create the image host. @@ -105,11 +93,7 @@ namespace toucan #else // _WINDOWS searchPath.push_back(parentPath / ".." / ".."); #endif // _WINDOWS - ImageEffectHostOptions imageHostOptions; - imageHostOptions.log = log; - _host = std::make_shared( - searchPath, - imageHostOptions); + _host = std::make_shared(_context, searchPath); // Initialize the filmstrip. OIIO::ImageBuf filmstripBuf; @@ -162,34 +146,6 @@ namespace toucan // Write the image. filmstripBuf.write(outputPath.string()); - - return 0; - } - - void App::_printHelp() - { - std::cout << "Usage:" << std::endl; - std::cout << std::endl; - std::cout << " toucan-filmstrip (input) (output) [options...]" << std::endl; - std::cout << std::endl; - std::cout << "Arguments:" << std::endl; - std::cout << std::endl; - for (const auto& arg : _args.list) - { - std::cout << " " << arg->getName() << " - " << arg->getHelp() << std::endl; - std::cout << std::endl; - } - std::cout << std::endl; - std::cout << "Options:" << std::endl; - std::cout << std::endl; - for (const auto& option : _options.list) - { - for (const auto& line : option->getHelp()) - { - std::cout << " " << line << std::endl; - } - std::cout << std::endl; - } } } diff --git a/bin/toucan-filmstrip/App.h b/bin/toucan-filmstrip/App.h index 3191cb2..70cbf77 100644 --- a/bin/toucan-filmstrip/App.h +++ b/bin/toucan-filmstrip/App.h @@ -3,35 +3,39 @@ #pragma once +#include +#include +#include -#include -#include -#include -#include +#include #include namespace toucan { - class App : public std::enable_shared_from_this + class App : public dtk::IApp { + protected: + void _init( + const std::shared_ptr&, + std::vector&); + + App(); + public: - App(std::vector&); - ~App(); + + static std::shared_ptr create( + const std::shared_ptr&, + std::vector&); - int run(); + void run() override; private: - void _printHelp(); - - std::string _exe; - struct Args { std::string input; std::string output; - std::vector > list; }; Args _args; @@ -39,7 +43,6 @@ namespace toucan { bool verbose = false; bool help = false; - std::vector > list; }; Options _options; @@ -48,4 +51,3 @@ namespace toucan std::shared_ptr _host; }; } - diff --git a/bin/toucan-filmstrip/CMakeLists.txt b/bin/toucan-filmstrip/CMakeLists.txt index 2222bcc..3383d9e 100644 --- a/bin/toucan-filmstrip/CMakeLists.txt +++ b/bin/toucan-filmstrip/CMakeLists.txt @@ -5,7 +5,7 @@ set(SOURCE main.cpp) add_executable(toucan-filmstrip ${HEADERS} ${SOURCE}) -target_link_libraries(toucan-filmstrip toucan) +target_link_libraries(toucan-filmstrip toucanRender) set_target_properties(toucan-filmstrip PROPERTIES FOLDER bin) add_dependencies(toucan-filmstrip ${TOUCAN_PLUGINS}) diff --git a/bin/toucan-filmstrip/main.cpp b/bin/toucan-filmstrip/main.cpp index 44739a1..d34dd89 100644 --- a/bin/toucan-filmstrip/main.cpp +++ b/bin/toucan-filmstrip/main.cpp @@ -3,6 +3,9 @@ #include "App.h" +#include +#include + #include using namespace toucan; @@ -17,8 +20,11 @@ int main(int argc, char** argv) } try { - auto app = std::make_shared(args); - out = app->run(); + auto context = dtk::Context::create(); + dtk::coreInit(context); + auto app = App::create(context, args); + app->run(); + out = app->getExit(); } catch (const std::exception& e) { diff --git a/bin/toucan-render/App.cpp b/bin/toucan-render/App.cpp index 8802969..f54137b 100644 --- a/bin/toucan-render/App.cpp +++ b/bin/toucan-render/App.cpp @@ -3,8 +3,11 @@ #include "App.h" -#include -#include +#include +#include + +#include +#include #include @@ -40,21 +43,22 @@ namespace toucan }; } - App::App(std::vector& argv) + void App::_init( + const std::shared_ptr& context, + std::vector& argv) { - _exe = argv.front(); - argv.erase(argv.begin()); - - _args.list.push_back(std::make_shared >( + std::vector > args; + args.push_back(dtk::CmdLineValueArg::create( _args.input, "input", "Input .otio file.")); - auto outArg = std::make_shared >( + auto outArg = dtk::CmdLineValueArg::create( _args.output, "output", "Output image or movie file. Use a dash ('-') to write raw frames or y4m to stdout."); - _args.list.push_back(outArg); + args.push_back(outArg); + std::vector > options; std::vector rawList; for (const auto& spec : rawSpecs) { @@ -65,88 +69,62 @@ namespace toucan { y4mList.push_back(spec.first); } - _options.list.push_back(std::make_shared >( + options.push_back(dtk::CmdLineValueOption::create( _options.videoCodec, std::vector{ "-vcodec" }, "Set the video codec.", _options.videoCodec, - join(ffmpeg::getVideoCodecStrings(), ", "))); - _options.list.push_back(std::make_shared( + dtk::join(ffmpeg::getVideoCodecStrings(), ", "))); + options.push_back(dtk::CmdLineFlagOption::create( _options.printStart, std::vector{ "-print_start" }, "Print the timeline start time and exit.")); - _options.list.push_back(std::make_shared( + options.push_back(dtk::CmdLineFlagOption::create( _options.printDuration, std::vector{ "-print_duration" }, "Print the timeline duration and exit.")); - _options.list.push_back(std::make_shared( + options.push_back(dtk::CmdLineFlagOption::create( _options.printRate, std::vector{ "-print_rate" }, "Print the timeline frame rate and exit.")); - _options.list.push_back(std::make_shared( + options.push_back(dtk::CmdLineFlagOption::create( _options.printSize, std::vector{ "-print_size" }, "Print the timeline image size.")); - _options.list.push_back(std::make_shared >( + options.push_back(dtk::CmdLineValueOption::create( _options.raw, std::vector{ "-raw" }, "Raw pixel format to send to stdout.", _options.raw, - join(rawList, ", "))); - _options.list.push_back(std::make_shared >( + dtk::join(rawList, ", "))); + options.push_back(dtk::CmdLineValueOption::create( _options.y4m, std::vector{ "-y4m" }, "y4m format to send to stdout.", _options.y4m, - join(y4mList, ", "))); - _options.list.push_back(std::make_shared( + dtk::join(y4mList, ", "))); + options.push_back(dtk::CmdLineFlagOption::create( _options.verbose, std::vector{ "-v" }, "Print verbose output.")); - _options.list.push_back(std::make_shared( - _options.help, - std::vector{ "-h" }, - "Print help.")); - if (!argv.empty()) - { - for (const auto& option : _options.list) - { - option->parse(argv); - } - if (_options.printStart || - _options.printDuration || - _options.printRate || - _options.printSize) - { - auto i = std::find(_args.list.begin(), _args.list.end(), outArg); - if (i != _args.list.end()) - { - _args.list.erase(i); - } - } - if (!_options.help) - { - for (const auto& arg : _args.list) - { - arg->parse(argv); - } - _args.outputRaw = "-" == _args.output; - if (_args.outputRaw) - { - _options.verbose = false; - } - if (argv.size()) - { - _options.help = true; - } - } - } - else + IApp::_init( + context, + argv, + "toucan-render", + "Render timeline files", + args, + options); + + _args.outputRaw = "-" == _args.output; + if (_args.outputRaw) { - _options.help = true; + _options.verbose = false; } } + + App::App() + {} App::~App() { @@ -163,16 +141,19 @@ namespace toucan av_frame_free(&_avFrame); } } + + std::shared_ptr App::create( + const std::shared_ptr& context, + std::vector& argv) + { + auto out = std::shared_ptr(new App); + out->_init(context, argv); + return out; + } - int App::run() + void App::run() { - if (_options.help) - { - _printHelp(); - return 1; - } - - const std::filesystem::path parentPath = std::filesystem::path(_exe).parent_path(); + const std::filesystem::path parentPath = std::filesystem::path(getExeName()).parent_path(); const std::filesystem::path inputPath(_args.input); const std::filesystem::path outputPath(_args.output); const auto outputSplit = splitFileNameNumber(outputPath.stem().string()); @@ -188,39 +169,32 @@ namespace toucan const int frames = timeRange.duration().value(); // Create the image graph. - std::shared_ptr log; - if (_options.verbose) - { - log = std::make_shared(); - } - ImageGraphOptions imageGraphOptions; - imageGraphOptions.log = log; _graph = std::make_shared( + _context, inputPath.parent_path(), - _timelineWrapper, - imageGraphOptions); + _timelineWrapper); const IMATH_NAMESPACE::V2d imageSize = _graph->getImageSize(); // Print information. if (_options.printStart) { std::cout << timeRange.start_time().value() << std::endl; - return 0; + return; } else if (_options.printDuration) { std::cout << timeRange.duration().value() << std::endl; - return 0; + return; } else if (_options.printRate) { std::cout << timeRange.duration().rate() << std::endl; - return 0; + return; } else if (_options.printSize) { std::cout << imageSize.x << "x" << imageSize.y << std::endl; - return 0; + return; } // Create the image host. @@ -231,11 +205,7 @@ namespace toucan #else // _WINDOWS searchPath.push_back(parentPath / ".." / ".."); #endif // _WINDOWS - ImageEffectHostOptions imageHostOptions; - imageHostOptions.log = log; - _host = std::make_shared( - searchPath, - imageHostOptions); + _host = std::make_shared(_context, searchPath); // Open the movie file. std::shared_ptr ffWrite; @@ -298,8 +268,6 @@ namespace toucan } } } - - return 0; } void App::_writeRawFrame(const OIIO::ImageBuf& buf) @@ -358,7 +326,7 @@ namespace toucan { const OTIO_NS::TimeRange timeRange = _timelineWrapper->getTimeRange(); - const auto r = toRational(timeRange.duration().rate()); + const auto r = dtk::toRational(timeRange.duration().rate()); std::stringstream ss; ss << " F" << r.first << ":" << r.second; s = ss.str(); @@ -521,33 +489,5 @@ namespace toucan stdout); } } - - void App::_printHelp() - { - std::cout << "Usage:" << std::endl; - std::cout << std::endl; - std::cout << " toucan-render (input) (output) [options...]" << std::endl; - std::cout << std::endl; - std::cout << " toucan-render (input) (-print_start|-print_duration|-print_rate|-print_size)" << std::endl; - std::cout << std::endl; - std::cout << "Arguments:" << std::endl; - std::cout << std::endl; - for (const auto& arg : _args.list) - { - std::cout << " " << arg->getName() << " - " << arg->getHelp() << std::endl; - std::cout << std::endl; - } - std::cout << std::endl; - std::cout << "Options:" << std::endl; - std::cout << std::endl; - for (const auto& option : _options.list) - { - for (const auto& line : option->getHelp()) - { - std::cout << " " << line << std::endl; - } - std::cout << std::endl; - } - } } diff --git a/bin/toucan-render/App.h b/bin/toucan-render/App.h index 3036ce1..436fe86 100644 --- a/bin/toucan-render/App.h +++ b/bin/toucan-render/App.h @@ -3,10 +3,11 @@ #pragma once -#include -#include -#include -#include +#include +#include +#include + +#include #include @@ -18,30 +19,34 @@ extern "C" namespace toucan { - class App : public std::enable_shared_from_this + class App : public dtk::IApp { + protected: + void _init( + const std::shared_ptr&, + std::vector&); + + App(); + public: - App(std::vector&); - ~App(); - int run(); + static std::shared_ptr create( + const std::shared_ptr&, + std::vector&); + + void run() override; private: void _writeRawFrame(const OIIO::ImageBuf&); void _writeY4mHeader(); void _writeY4mFrame(const OIIO::ImageBuf&); - - void _printHelp(); - - std::string _exe; - + struct Args { std::string input; std::string output; bool outputRaw = false; - std::vector > list; }; Args _args; @@ -55,8 +60,6 @@ namespace toucan std::string raw; std::string y4m; bool verbose = false; - bool help = false; - std::vector > list; }; Options _options; diff --git a/bin/toucan-render/CMakeLists.txt b/bin/toucan-render/CMakeLists.txt index c6837d4..cc2a16f 100644 --- a/bin/toucan-render/CMakeLists.txt +++ b/bin/toucan-render/CMakeLists.txt @@ -5,7 +5,7 @@ set(SOURCE main.cpp) add_executable(toucan-render ${HEADERS} ${SOURCE}) -target_link_libraries(toucan-render toucan) +target_link_libraries(toucan-render toucanRender) set_target_properties(toucan-render PROPERTIES FOLDER bin) add_dependencies(toucan-render ${TOUCAN_PLUGINS}) diff --git a/bin/toucan-render/main.cpp b/bin/toucan-render/main.cpp index 44739a1..d34dd89 100644 --- a/bin/toucan-render/main.cpp +++ b/bin/toucan-render/main.cpp @@ -3,6 +3,9 @@ #include "App.h" +#include +#include + #include using namespace toucan; @@ -17,8 +20,11 @@ int main(int argc, char** argv) } try { - auto app = std::make_shared(args); - out = app->run(); + auto context = dtk::Context::create(); + dtk::coreInit(context); + auto app = App::create(context, args); + app->run(); + out = app->getExit(); } catch (const std::exception& e) { diff --git a/cmake/SuperBuild/Builddtk-deps.cmake b/cmake/SuperBuild/Builddtk-deps.cmake index a0107f0..18ce1d6 100644 --- a/cmake/SuperBuild/Builddtk-deps.cmake +++ b/cmake/SuperBuild/Builddtk-deps.cmake @@ -1,10 +1,11 @@ include(ExternalProject) set(dtk_GIT_REPOSITORY "https://github.com/darbyjohnston/dtk.git") -set(dtk_GIT_TAG "b5d7a808efae236ee1a4635956cf500fae9528e2") +set(dtk_GIT_TAG "60fe172aebdffbeda24adc2f0a5237140a90529b") set(dtk-deps_ARGS ${toucan_EXTERNAL_PROJECT_ARGS} + -Ddtk_UI_LIB=${toucan_dtk_UI_LIB} -Ddtk_ZLIB=OFF -Ddtk_PNG=OFF -Ddtk_Freetype=OFF diff --git a/cmake/SuperBuild/Builddtk.cmake b/cmake/SuperBuild/Builddtk.cmake index 8981a6a..bc8995d 100644 --- a/cmake/SuperBuild/Builddtk.cmake +++ b/cmake/SuperBuild/Builddtk.cmake @@ -1,11 +1,12 @@ include(ExternalProject) set(dtk_GIT_REPOSITORY "https://github.com/darbyjohnston/dtk.git") -set(dtk_GIT_TAG "b5d7a808efae236ee1a4635956cf500fae9528e2") +set(dtk_GIT_TAG "60fe172aebdffbeda24adc2f0a5237140a90529b") set(dtk_DEPS dtk-deps) set(dtk_ARGS ${toucan_EXTERNAL_PROJECT_ARGS} + -Ddtk_UI_LIB=${toucan_dtk_UI_LIB} -Ddtk_PYTHON=OFF -Ddtk_TESTS=OFF -Ddtk_EXAMPLES=OFF) diff --git a/cmake/SuperBuild/CMakeLists.txt b/cmake/SuperBuild/CMakeLists.txt index 2372537..93f7d44 100644 --- a/cmake/SuperBuild/CMakeLists.txt +++ b/cmake/SuperBuild/CMakeLists.txt @@ -27,6 +27,7 @@ set(toucan_OpenImageIO ON CACHE BOOL "Build OpenImageIO") set(toucan_OpenTimelineIO ON CACHE BOOL "Build OpenTimelineIO") set(toucan_OpenFX ON CACHE BOOL "Build OpenFX") set(toucan_dtk ON CACHE BOOL "Build dtk") +set(toucan_dtk_UI_LIB ON CACHE BOOL "Build dtk user interface library") list(PREPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}) diff --git a/data/Markers.otio b/data/Markers.otio new file mode 100644 index 0000000..3519175 --- /dev/null +++ b/data/Markers.otio @@ -0,0 +1,250 @@ +{ + "OTIO_SCHEMA": "Timeline.1", + "metadata": {}, + "name": "Markers", + "global_start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 24.0, + "value": 100.0 + }, + "tracks": { + "OTIO_SCHEMA": "Stack.1", + "markers": [ + { + "OTIO_SCHEMA": "Marker.2", + "metadata": {}, + "name": "Stack Marker", + "color": "GREEN", + "marked_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 24.0, + "value": 9.0 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 24.0, + "value": 0.0 + } + } + } + ], + "children": [ + { + "OTIO_SCHEMA": "Track.1", + "markers": [ + { + "OTIO_SCHEMA": "Marker.2", + "metadata": {}, + "name": "Track Marker 0", + "color": "ORANGE", + "marked_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 24.0, + "value": 4.0 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 24.0, + "value": 0.0 + } + } + }, + { + "OTIO_SCHEMA": "Marker.2", + "metadata": {}, + "name": "Track Marker 1", + "color": "YELLOW", + "marked_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 24.0, + "value": 5.0 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 24.0, + "value": 4.0 + } + } + } + ], + "children": [ + { + "OTIO_SCHEMA": "Clip.1", + "media_reference": { + "OTIO_SCHEMA": "ExternalReference.1", + "available_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 24, + "value": 3 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 24, + "value": 0 + } + }, + "target_url": "Letter_A.png" + }, + "source_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 24, + "value": 3 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 24, + "value": 0 + } + }, + "name": "Letter_A", + "markers": [ + { + "OTIO_SCHEMA": "Marker.2", + "metadata": {}, + "name": "Letter_A Marker", + "color": "RED", + "marked_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 24.0, + "value": 0.0 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 24.0, + "value": 0.0 + } + } + } + ] + }, { + "OTIO_SCHEMA": "Gap.1", + "name": "Gap", + "source_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 24, + "value": 3 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 24, + "value": 0 + } + }, + "markers": [ + { + "OTIO_SCHEMA": "Marker.2", + "metadata": {}, + "name": "Gap Marker", + "color": "GREEN", + "marked_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 24.0, + "value": 3.0 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 24.0, + "value": 0.0 + } + } + } + ] + }, + { + "OTIO_SCHEMA": "Clip.1", + "media_reference": { + "OTIO_SCHEMA": "ExternalReference.1", + "available_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 24, + "value": 3 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 24, + "value": 0 + } + }, + "target_url": "Letter_C.png" + }, + "source_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 24, + "value": 3 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 24, + "value": 0 + } + }, + "name": "Letter_C", + "markers": [ + { + "OTIO_SCHEMA": "Marker.2", + "metadata": {}, + "name": "Letter_C Marker 0", + "color": "BLUE", + "marked_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 24.0, + "value": 2.0 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 24.0, + "value": 0.0 + } + } + }, + { + "OTIO_SCHEMA": "Marker.2", + "metadata": {}, + "name": "Letter_C Marker 1", + "color": "PURPLE", + "marked_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 24.0, + "value": 2.0 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 24.0, + "value": 1.0 + } + } + } + ] + } + ], + "kind": "Video", + "name": "Video" + } + ], + "name": "Stack" + } +} diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index efd9eab..3deb957 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -1,4 +1,4 @@ -add_subdirectory(toucan) +add_subdirectory(toucanRender) if(toucan_VIEW) add_subdirectory(toucanView) endif() diff --git a/lib/toucan/CmdLine.cpp b/lib/toucan/CmdLine.cpp deleted file mode 100644 index 053295e..0000000 --- a/lib/toucan/CmdLine.cpp +++ /dev/null @@ -1,97 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright Contributors to the toucan project. - -#include "CmdLine.h" - -#include - -namespace toucan -{ - ICmdLineOption::ICmdLineOption( - const std::vector& names, - const std::string& help) : - _names(names), - _help(help) - {} - - ICmdLineOption::~ICmdLineOption() - {} - - CmdLineFlagOption::CmdLineFlagOption( - bool& value, - const std::vector& names, - const std::string& help) : - ICmdLineOption(names, help), - _value(value) - {} - - void CmdLineFlagOption::parse(std::vector& args) - { - for (const auto& name : _names) - { - auto i = std::find(args.begin(), args.end(), name); - if (i != args.end()) - { - _matchedName = name; - _value = true; - i = args.erase(i); - } - } - } - - std::vector CmdLineFlagOption::getHelp() const - { - std::vector out; - out.push_back(join(_names, ", ")); - out.push_back(_help); - return out; - } - - bool cmdLineParse(std::vector& args, std::vector::iterator& it, std::string& value) - { - bool out = false; - if (it != args.end()) - { - value = *it; - it = args.erase(it); - out = true; - } - return out; - } - - bool cmdLineParse(std::vector& args, std::vector::iterator& it, bool& value) - { - bool out = false; - if (it != args.end()) - { - value = std::atoi(it->c_str()); - it = args.erase(it); - out = true; - } - return out; - } - - bool cmdLineParse(std::vector& args, std::vector::iterator& it, int& value) - { - bool out = false; - if (it != args.end()) - { - value = std::atoi(it->c_str()); - it = args.erase(it); - out = true; - } - return out; - } - - bool cmdLineParse(std::vector& args, std::vector::iterator& it, float& value) - { - bool out = false; - if (it != args.end()) - { - value = std::atof(it->c_str()); - it = args.erase(it); - out = true; - } - return out; - } -} diff --git a/lib/toucan/CmdLine.h b/lib/toucan/CmdLine.h deleted file mode 100644 index 27637a1..0000000 --- a/lib/toucan/CmdLine.h +++ /dev/null @@ -1,127 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright Contributors to the toucan project. - -#pragma once - -#include -#include -#include -#include - -namespace toucan -{ - //! Base class for command line options. - class ICmdLineOption : public std::enable_shared_from_this - { - public: - ICmdLineOption( - const std::vector& names, - const std::string& help); - - virtual ~ICmdLineOption() = 0; - - //! Parse the option. - virtual void parse(std::vector& args) = 0; - - //! Get the option name that was matched. - const std::string& getMatchedName() const; - - //! Get the help. - virtual std::vector getHelp() const = 0; - - protected: - std::vector _names; - std::string _matchedName; - std::string _help; - }; - - //! Command line flag option. - class CmdLineFlagOption : public ICmdLineOption - { - public: - CmdLineFlagOption( - bool& value, - const std::vector& names, - const std::string& help); - - void parse(std::vector& args) override; - std::vector getHelp() const override; - - private: - bool& _value; - }; - - //! Command line value option. - template - class CmdLineValueOption : public ICmdLineOption - { - public: - CmdLineValueOption( - T& value, - const std::vector& names, - const std::string& help, - const std::string& defaultValue = std::string(), - const std::string& possibleValues = std::string()); - - void parse(std::vector& args) override; - std::vector getHelp() const override; - - private: - T& _value; - std::string _defaultValue; - std::string _possibleValues; - }; - - //! Base class for command line arguments. - class ICmdLineArg : public std::enable_shared_from_this - { - public: - ICmdLineArg( - const std::string& name, - const std::string& help, - bool optional = false); - - virtual ~ICmdLineArg() = 0; - - //! Parse the argument. - virtual void parse(std::vector& args) = 0; - - //! Get the argument name. - const std::string& getName() const; - - //! Get the help. - const std::string& getHelp() const; - - //! Get whether this argument is optional. - bool isOptional() const; - - protected: - std::string _name; - std::string _help; - bool _optional = false; - }; - - //! Command line value argument. - template - class CmdLineValueArg : public ICmdLineArg - { - public: - CmdLineValueArg( - T& value, - const std::string& name, - const std::string& help, - bool optional = false); - - void parse(std::vector& args) override; - - private: - T& _value; - }; - - bool cmdLineParse(std::vector&, std::vector::iterator&, std::string&); - bool cmdLineParse(std::vector&, std::vector::iterator&, bool&); - bool cmdLineParse(std::vector&, std::vector::iterator&, int&); - bool cmdLineParse(std::vector&, std::vector::iterator&, float&); -} - -#include "CmdLineInline.h" diff --git a/lib/toucan/CmdLineInline.h b/lib/toucan/CmdLineInline.h deleted file mode 100644 index f5ce664..0000000 --- a/lib/toucan/CmdLineInline.h +++ /dev/null @@ -1,147 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright Contributors to the toucan project. - -#include "Util.h" - -#include - -namespace toucan -{ - inline const std::string& ICmdLineOption::getMatchedName() const - { - return _matchedName; - } - - template - inline CmdLineValueOption::CmdLineValueOption( - T& value, - const std::vector& names, - const std::string& help, - const std::string& defaultValue, - const std::string& possibleValues) : - ICmdLineOption(names, help), - _value(value), - _defaultValue(defaultValue), - _possibleValues(possibleValues) - {} - - template - inline void CmdLineValueOption::parse(std::vector& args) - { - for (const auto& name : _names) - { - auto i = std::find(args.begin(), args.end(), name); - if (i != args.end()) - { - _matchedName = name; - i = args.erase(i); - if (!cmdLineParse(args, i, _value)) - { - throw std::runtime_error("Cannot parse the option"); - } - } - } - } - - template<> - inline void CmdLineValueOption::parse(std::vector& args) - { - for (const auto& name : _names) - { - auto i = std::find(args.begin(), args.end(), name); - if (i != args.end()) - { - _matchedName = name; - i = args.erase(i); - if (i != args.end()) - { - _value = *i; - i = args.erase(i); - } - else - { - throw std::runtime_error("Cannot parse the option"); - } - } - } - } - - template - inline std::vector CmdLineValueOption::getHelp() const - { - std::vector out; - out.push_back(join(_names, ", ") + " (value)"); - out.push_back(_help); - if (!_defaultValue.empty()) - { - out.push_back("Default value: " + _defaultValue); - } - if (!_possibleValues.empty()) - { - out.push_back("Possible values: " + _possibleValues); - } - return out; - } - - inline ICmdLineArg::ICmdLineArg( - const std::string& name, - const std::string& help, - bool optional) : - _name(name), - _help(help), - _optional(optional) - {} - - inline ICmdLineArg::~ICmdLineArg() - {} - - inline const std::string& ICmdLineArg::getName() const - { - return _name; - } - - inline const std::string& ICmdLineArg::getHelp() const - { - return _help; - } - - inline bool ICmdLineArg::isOptional() const - { - return _optional; - } - - template - inline CmdLineValueArg::CmdLineValueArg( - T& value, - const std::string& name, - const std::string& help, - bool optional) : - ICmdLineArg(name, help, optional), - _value(value) - {} - - template - inline void CmdLineValueArg::parse(std::vector& args) - { - auto i = args.begin(); - if (!cmdLineParse(args, i, _value)) - { - throw std::runtime_error("Cannot parse the argument"); - } - } - - template<> - inline void CmdLineValueArg::parse(std::vector& args) - { - auto i = args.begin(); - if (i != args.end()) - { - _value = *i; - i = args.erase(i); - } - else - { - throw std::runtime_error("Cannot parse the argument"); - } - } -} diff --git a/lib/toucan/LRUCache.h b/lib/toucan/LRUCache.h deleted file mode 100644 index 66da511..0000000 --- a/lib/toucan/LRUCache.h +++ /dev/null @@ -1,54 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright Contributors to the toucan project. - -#pragma once - -#include -#include -#include - -namespace toucan -{ - //! Least recently used (LRU) cache. - template - class LRUCache - { - public: - //! \name Size - ///@{ - - size_t getMax() const; - size_t getSize() const; - size_t getCount() const; - float getPercentage() const; - - void setMax(size_t); - - ///@} - - //! \name Contents - ///@{ - - bool contains(const T& key) const; - bool get(const T& key, U& value) const; - - void add(const T& key, const U& value, size_t size = 1); - void remove(const T& key); - void clear(); - - std::vector getKeys() const; - std::vector getValues() const; - - ///@} - - private: - void _maxUpdate(); - - size_t _max = 10000; - std::map > _map; - mutable std::map _counts; - mutable int64_t _counter = 0; - }; -} - -#include diff --git a/lib/toucan/LRUCacheInline.h b/lib/toucan/LRUCacheInline.h deleted file mode 100644 index b0691fa..0000000 --- a/lib/toucan/LRUCacheInline.h +++ /dev/null @@ -1,151 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright Contributors to the toucan project. - -#include - -namespace toucan -{ - template - inline std::size_t LRUCache::getMax() const - { - return _max; - } - - template - inline std::size_t LRUCache::getSize() const - { - size_t out = 0; - for (const auto& i : _map) - { - out += i.second.second; - } - return out; - } - - template - inline std::size_t LRUCache::getCount() const - { - return _map.size(); - } - - template - inline float LRUCache::getPercentage() const - { - return getSize() / static_cast(_max) * 100.F; - } - - template - inline void LRUCache::setMax(std::size_t value) - { - if (value == _max) - return; - _max = value; - _maxUpdate(); - } - - template - inline bool LRUCache::contains(const T& key) const - { - return _map.find(key) != _map.end(); - } - - template - inline bool LRUCache::get(const T& key, U& value) const - { - auto i = _map.find(key); - if (i != _map.end()) - { - value = i->second.first; - auto j = _counts.find(key); - if (j != _counts.end()) - { - ++_counter; - j->second = _counter; - } - return true; - } - return i != _map.end(); - } - - template - inline void LRUCache::add(const T& key, const U& value, size_t size) - { - _map[key] = std::make_pair(value, size); - ++_counter; - _counts[key] = _counter; - _maxUpdate(); - } - - template - inline void LRUCache::remove(const T& key) - { - const auto i = _map.find(key); - if (i != _map.end()) - { - _map.erase(i); - } - const auto j = _counts.find(key); - if (j != _counts.end()) - { - _counts.erase(j); - } - _maxUpdate(); - } - - template - inline void LRUCache::clear() - { - _map.clear(); - } - - template - inline std::vector LRUCache::getKeys() const - { - std::vector out; - for (const auto& i : _map) - { - out.push_back(i.first); - } - return out; - } - - template - inline std::vector LRUCache::getValues() const - { - std::vector out; - for (const auto& i : _map) - { - out.push_back(i.second.first); - } - return out; - } - - template - inline void LRUCache::_maxUpdate() - { - size_t size = getSize(); - if (size > _max) - { - std::map sorted; - for (const auto& i : _counts) - { - sorted[i.second] = i.first; - } - while (getSize() > _max) - { - auto begin = sorted.begin(); - auto i = _map.find(begin->second); - if (i != _map.end()) - { - _map.erase(i); - } - auto j = _counts.find(begin->second); - if (j != _counts.end()) - { - _counts.erase(j); - } - sorted.erase(begin); - } - } - } -} diff --git a/lib/toucan/MessageLog.cpp b/lib/toucan/MessageLog.cpp deleted file mode 100644 index 4995e5f..0000000 --- a/lib/toucan/MessageLog.cpp +++ /dev/null @@ -1,29 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright Contributors to the toucan project. - -#include "MessageLog.h" - -#include - -namespace toucan -{ - void MessageLog::log( - const std::string& prefix, - const std::string& message, - MessageLogType type) - { - std::unique_lock lock(_mutex); - switch (type) - { - case MessageLogType::Info: - std::cout << prefix << ": " << message << std::endl; - break; - case MessageLogType::Warning: - std::cout << "Warning: " << message << std::endl; - break; - case MessageLogType::Error: - std::cout << "ERROR: " << message << std::endl; - break; - } - } -} diff --git a/lib/toucan/MessageLog.h b/lib/toucan/MessageLog.h deleted file mode 100644 index 3e867f1..0000000 --- a/lib/toucan/MessageLog.h +++ /dev/null @@ -1,32 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright Contributors to the toucan project. - -#pragma once - -#include -#include -#include - -namespace toucan -{ - //! Log message type. - enum class MessageLogType - { - Info, - Warning, - Error - }; - - //! Message logging. - class MessageLog : public std::enable_shared_from_this - { - public: - void log( - const std::string& prefix, - const std::string& message, - MessageLogType = MessageLogType::Info); - - private: - std::mutex _mutex; - }; -} diff --git a/lib/toucan/Plugin.cpp b/lib/toucan/Plugin.cpp deleted file mode 100644 index a33ca51..0000000 --- a/lib/toucan/Plugin.cpp +++ /dev/null @@ -1,17 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright Contributors to the toucan project. - -#include "Plugin.h" - -namespace toucan -{ - int Plugin::getCount() - { - return _getNumberOfPlugins ? _getNumberOfPlugins() : 0; - } - - OfxPlugin* Plugin::getPlugin(int index) - { - return _getPlugin ? _getPlugin(index) : nullptr; - } -} diff --git a/lib/toucan/TimelineAlgo.cpp b/lib/toucan/TimelineAlgo.cpp deleted file mode 100644 index 07f9aa6..0000000 --- a/lib/toucan/TimelineAlgo.cpp +++ /dev/null @@ -1,67 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright Contributors to the toucan project. - -#include "TimelineAlgo.h" - -#include - -namespace toucan -{ - std::vector > - getVideoClips(const OTIO_NS::SerializableObject::Retainer& timeline) - { - std::vector > out; - for (const auto& child : timeline->tracks()->children()) - { - if (auto track = OTIO_NS::dynamic_retainer_cast(child)) - { - if (OTIO_NS::Track::Kind::video == track->kind()) - { - const auto clips = track->find_clips(nullptr, std::nullopt, true); - out.insert(out.end(), clips.begin(), clips.end()); - } - } - } - return out; - } - - std::optional getTimeRange( - const std::vector >& items, - const OTIO_NS::RationalTime& startTime, - double rate) - { - std::optional out; - for (const auto& item : items) - { - //! \bug Shouldn't trimmed_range_in_parent() check whether the parent - //! is null? - if (item->parent()) - { - const auto timeRangeOpt = item->trimmed_range_in_parent(); - if (timeRangeOpt.has_value()) - { - const OTIO_NS::TimeRange timeRange( - timeRangeOpt.value().start_time() + startTime, - timeRangeOpt.value().duration()); - if (out.has_value()) - { - out = OTIO_NS::TimeRange::range_from_start_end_time_inclusive( - std::min( - out.value().start_time(), - timeRange.start_time().rescaled_to(rate).round()), - std::max( - out.value().end_time_inclusive(), - timeRange.end_time_inclusive().rescaled_to(rate).round())); - } - else - { - out = OTIO_NS::TimeRange( - timeRange.start_time().rescaled_to(rate).round(), - timeRange.duration().rescaled_to(rate).round()); - } - } - } - } - return out; - } -} diff --git a/lib/toucan/Util.cpp b/lib/toucan/Util.cpp deleted file mode 100644 index 0fb062a..0000000 --- a/lib/toucan/Util.cpp +++ /dev/null @@ -1,221 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright Contributors to the toucan project. - -#include "Util.h" - -#include - -#include - -#include -#include -#include - -namespace toucan -{ - std::string toLower(std::string value) - { - std::transform( - value.begin(), - value.end(), - value.begin(), - [](unsigned char c) { return std::tolower(c); }); - return value; - } - - namespace - { - void _findPlugins( - const std::filesystem::path& path, - std::vector& out, - int depth, - int maxDepth) - { - try - { - for (auto const& entry : std::filesystem::directory_iterator(path)) - { - const auto& entryPath = entry.path(); - if (entry.is_regular_file() && entryPath.extension() == ".ofx") - { - out.push_back(entryPath); - } - else if (entry.is_directory() && depth < maxDepth) - { - _findPlugins(entryPath, out, depth + 1, maxDepth); - } - } - } - catch (const std::exception&) - { - //! \bug How should this be handled? - } - } - } - - void findPlugins( - const std::filesystem::path& path, - std::vector& out) - { - _findPlugins(path, out, 0, 2); - } - - OTIO_NS::AnyVector vecToAny(const IMATH_NAMESPACE::V2i& vec) - { - return OTIO_NS::AnyVector - { - static_cast(vec.x), - static_cast(vec.y) - }; - } - - OTIO_NS::AnyVector vecToAny(const IMATH_NAMESPACE::V4f& vec) - { - return OTIO_NS::AnyVector - { - static_cast(vec.x), - static_cast(vec.y), - static_cast(vec.z), - static_cast(vec.w) - }; - } - - void anyToVec(const OTIO_NS::AnyVector& any, IMATH_NAMESPACE::V2i& out) - { - if (2 == any.size()) - { - out.x = std::any_cast(any[0]); - out.y = std::any_cast(any[1]); - } - } - - void anyToVec(const OTIO_NS::AnyVector& any, IMATH_NAMESPACE::V4f& out) - { - if (4 == any.size()) - { - out.x = std::any_cast(any[0]); - out.y = std::any_cast(any[1]); - out.z = std::any_cast(any[2]); - out.w = std::any_cast(any[3]); - } - } - - std::pair splitURLProtocol(const std::string& url) - { - std::pair out; - const size_t size = url.size(); - size_t pos = url.find("://"); - if (pos != std::string::npos) - { - out.first = url.substr(0, pos + 2); - out.second = url.substr(pos + 3); - } - else - { - out.second = url; - } - return out; - } - - std::string join(const std::vector& values, char delimeter) - { - std::string out; - const std::size_t size = values.size(); - for (std::size_t i = 0; i < size; ++i) - { - out += values[i]; - if (i < size - 1) - { - out += delimeter; - } - } - return out; - } - - std::string join(const std::vector& values, const std::string& delimeter) - { - std::string out; - const std::size_t size = values.size(); - for (std::size_t i = 0; i < size; ++i) - { - out += values[i]; - if (i < size - 1) - { - out += delimeter; - } - } - return out; - } - - std::string getSequenceFrame( - const std::filesystem::path& path, - const std::string& namePrefix, - int frame, - int padding, - const std::string& nameSuffix) - { - std::stringstream ss; - ss << namePrefix << - std::setw(padding) << std::setfill('0') << frame << - nameSuffix; - return (path / ss.str()).string(); - } - - namespace - { - inline bool isNumber(char c) - { - return c >= '0' && c <= '9'; - } - } - - std::pair splitFileNameNumber(const std::string& stem) - { - std::pair out; - size_t i = stem.size(); - if (i > 0) - { - for (; i > 0 && isNumber(stem[i - 1]); --i) - ; - } - if (i >= 0) - { - out.first = stem.substr(0, i); - out.second = stem.substr(i, stem.size() - i); - } - return out; - } - - size_t getNumberPadding(const std::string& value) - { - size_t out = 0; - if (!value.empty() && '0' == value[0]) - { - out = value.size(); - } - return out; - } - - std::pair toRational(double value) - { - const std::array, 6> common = - { - std::make_pair(24, 1), - std::make_pair(30, 1), - std::make_pair(60, 1), - std::make_pair(24000, 1001), - std::make_pair(30000, 1001), - std::make_pair(60000, 1001) - }; - const double tolerance = 0.01; - for (const auto& i : common) - { - const double diff = fabs(value - i.first / static_cast(i.second)); - if (diff < tolerance) - { - return i; - } - } - return std::make_pair(static_cast(value), 1); - } -} diff --git a/lib/toucan/UtilUnix.cpp b/lib/toucan/UtilUnix.cpp deleted file mode 100644 index 0e8722d..0000000 --- a/lib/toucan/UtilUnix.cpp +++ /dev/null @@ -1,22 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright Contributors to the toucan project. - -#include "Util.h" - -#include -#include - -namespace toucan -{ - std::filesystem::path makeUniqueTemp() - { - const std::filesystem::path path = std::filesystem::temp_directory_path() / "XXXXXX"; - std::vector buf; - for (const char c : path.string()) - { - buf.push_back(c); - } - buf.push_back(0); - return mkdtemp(buf.data()); - } -} diff --git a/lib/toucan/UtilWin32.cpp b/lib/toucan/UtilWin32.cpp deleted file mode 100644 index 9c15b94..0000000 --- a/lib/toucan/UtilWin32.cpp +++ /dev/null @@ -1,55 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright Contributors to the toucan project. - -#include "Util.h" - -#ifndef WIN32_LEAN_AND_MEAN -#define WIN32_LEAN_AND_MEAN -#endif // WIN32_LEAN_AND_MEAN -#ifndef NOMINMAX -#define NOMINMAX -#endif // NOMINMAX -#include -#include - -namespace toucan -{ - std::filesystem::path makeUniqueTemp() - { - std::string unique; - GUID guid; - CoCreateGuid(&guid); - const uint8_t* guidP = reinterpret_cast(&guid); - for (int i = 0; i < 16; ++i) - { - char buf[3] = ""; - sprintf_s(buf, 3, "%02x", guidP[i]); - unique += buf; - } - std::filesystem::path path = std::filesystem::temp_directory_path() / unique; - std::filesystem::create_directory(path); - return path; - } - - std::string getLastError() - { - std::string out; - const DWORD dw = GetLastError(); - if (dw != ERROR_SUCCESS) - { - const size_t bufSize = 4096; - TCHAR buf[bufSize]; - FormatMessage( - FORMAT_MESSAGE_FROM_SYSTEM | - FORMAT_MESSAGE_IGNORE_INSERTS, - NULL, - dw, - MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), - buf, - bufSize, - NULL); - out = std::string(buf, lstrlen(buf)); - } - return out; - } -} diff --git a/lib/toucan/CMakeLists.txt b/lib/toucanRender/CMakeLists.txt similarity index 59% rename from lib/toucan/CMakeLists.txt rename to lib/toucanRender/CMakeLists.txt index 301f2c9..21f5191 100644 --- a/lib/toucan/CMakeLists.txt +++ b/lib/toucanRender/CMakeLists.txt @@ -1,6 +1,4 @@ set(HEADERS - CmdLine.h - CmdLineInline.h Comp.h FFmpeg.h FFmpegRead.h @@ -9,10 +7,7 @@ set(HEADERS ImageEffectHost.h ImageGraph.h ImageNode.h - LRUCache.h - LRUCacheInline.h MemoryMap.h - MessageLog.h Plugin.h PropertySet.h Read.h @@ -23,7 +18,6 @@ set(HEADERS set(HEADERS_PRIVATE ImageEffect_p.h) set(SOURCE - CmdLine.cpp Comp.cpp FFmpeg.cpp FFmpegRead.cpp @@ -33,7 +27,6 @@ set(SOURCE ImageGraph.cpp ImageNode.cpp MemoryMap.cpp - MessageLog.cpp Plugin.cpp PropertySet.cpp Read.cpp @@ -44,33 +37,31 @@ set(SOURCE if(WIN32) list(APPEND SOURCE MemoryMapWin32.cpp - PluginWin32.cpp - UtilWin32.cpp) + PluginWin32.cpp) else() list(APPEND SOURCE MemoryMapUnix.cpp - PluginUnix.cpp - UtilUnix.cpp) + PluginUnix.cpp) endif() -add_library(toucan ${HEADERS} ${HEADERS_PRIVATE} ${SOURCE}) -set(LIBS_PUBLIC OTIO::opentimelineio OTIO::opentime OpenImageIO::OpenImageIO MINIZIP::minizip) +add_library(toucanRender ${HEADERS} ${HEADERS_PRIVATE} ${SOURCE}) +set(LIBS_PUBLIC dtk::dtkCore OTIO::opentimelineio OTIO::opentime OpenImageIO::OpenImageIO MINIZIP::minizip) if(CMAKE_COMPILER_IS_GNUCXX AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS 9) list(APPEND LIBS_PUBLIC stdc++fs) endif() -target_link_libraries(toucan PUBLIC ${LIBS_PUBLIC}) -set_target_properties(toucan PROPERTIES FOLDER lib) -set_target_properties(toucan PROPERTIES PUBLIC_HEADER "${HEADERS}") +target_link_libraries(toucanRender PUBLIC ${LIBS_PUBLIC}) +set_target_properties(toucanRender PROPERTIES FOLDER lib) +set_target_properties(toucanRender PROPERTIES PUBLIC_HEADER "${HEADERS}") install( - TARGETS toucan - EXPORT toucanTargets + TARGETS toucanRender + EXPORT toucanRenderTargets ARCHIVE DESTINATION lib LIBRARY DESTINATION lib RUNTIME DESTINATION bin - PUBLIC_HEADER DESTINATION include/toucan) + PUBLIC_HEADER DESTINATION include/toucanRender) install( - EXPORT toucanTargets - FILE toucanTargets.cmake + EXPORT toucanRenderTargets + FILE toucanRenderTargets.cmake DESTINATION "lib/cmake/toucan" NAMESPACE toucan::) diff --git a/lib/toucan/Comp.cpp b/lib/toucanRender/Comp.cpp similarity index 72% rename from lib/toucan/Comp.cpp rename to lib/toucanRender/Comp.cpp index ff5ef46..8972efb 100644 --- a/lib/toucan/Comp.cpp +++ b/lib/toucanRender/Comp.cpp @@ -37,14 +37,18 @@ namespace toucan _inputs[0]->setTime(offsetTime); auto fg = _inputs[0]->exec(); _inputs[1]->setTime(offsetTime); - auto bg = _inputs[1]->exec(); - if (_premult) + buf = _inputs[1]->exec(); + const auto fgSpec = fg.spec(); + if (_premult && + fgSpec.width > 0 && + fgSpec.height > 0) { fg = OIIO::ImageBufAlgo::premult(fg); } - const auto& fgSpec = fg.spec(); - const auto& bgSpec = bg.spec(); - if (bgSpec.width > 0 && + const auto& bgSpec = buf.spec(); + if (fgSpec.width > 0 && + fgSpec.height > 0 && + bgSpec.width > 0 && bgSpec.height > 0 && (fgSpec.width != bgSpec.width || fgSpec.height != bgSpec.height)) { @@ -54,17 +58,20 @@ namespace toucan 0.0, OIIO::ROI(0, bgSpec.width, 0, bgSpec.height)); } - buf = OIIO::ImageBufAlgo::over(fg, bg); + if (fgSpec.width > 0 && + fgSpec.height > 0) + { + buf = OIIO::ImageBufAlgo::over(fg, buf); + } } else if (1 == _inputs.size() && _inputs[0]) { _inputs[0]->setTime(offsetTime); - auto fg = _inputs[0]->exec(); + buf = _inputs[0]->exec(); if (_premult) { - fg = OIIO::ImageBufAlgo::premult(fg); + buf = OIIO::ImageBufAlgo::premult(buf); } - buf = fg; } return buf; } diff --git a/lib/toucan/Comp.h b/lib/toucanRender/Comp.h similarity index 94% rename from lib/toucan/Comp.h rename to lib/toucanRender/Comp.h index 80d7a5c..bb0137a 100644 --- a/lib/toucan/Comp.h +++ b/lib/toucanRender/Comp.h @@ -3,7 +3,7 @@ #pragma once -#include +#include namespace toucan { diff --git a/lib/toucan/FFmpeg.cpp b/lib/toucanRender/FFmpeg.cpp similarity index 99% rename from lib/toucan/FFmpeg.cpp rename to lib/toucanRender/FFmpeg.cpp index 1f6e5bc..a494320 100644 --- a/lib/toucan/FFmpeg.cpp +++ b/lib/toucanRender/FFmpeg.cpp @@ -3,8 +3,6 @@ #include "FFmpeg.h" -#include "Util.h" - extern "C" { #include diff --git a/lib/toucan/FFmpeg.h b/lib/toucanRender/FFmpeg.h similarity index 100% rename from lib/toucan/FFmpeg.h rename to lib/toucanRender/FFmpeg.h diff --git a/lib/toucan/FFmpegRead.cpp b/lib/toucanRender/FFmpegRead.cpp similarity index 98% rename from lib/toucan/FFmpegRead.cpp rename to lib/toucanRender/FFmpegRead.cpp index fc5a940..ce843af 100644 --- a/lib/toucan/FFmpegRead.cpp +++ b/lib/toucanRender/FFmpegRead.cpp @@ -3,7 +3,7 @@ #include "FFmpegRead.h" -#include "Util.h" +#include #include #include @@ -146,7 +146,7 @@ namespace toucan tag, AV_DICT_IGNORE_SUFFIX))) { - if ("timecode" == toLower(tag->key)) + if ("timecode" == dtk::toLower(tag->key)) { timecode = tag->value; break; @@ -296,7 +296,7 @@ namespace toucan { const std::string key(tag->key); const std::string value(tag->value); - if ("timecode" == toLower(key)) + if ("timecode" == dtk::toLower(key)) { timecode = value; } @@ -541,7 +541,7 @@ namespace toucan 1); sws_scale_frame(_swsContext, _avFrame2, _avFrame); - _currentTime += OTIO_NS::RationalTime(1.0, _timeRange.duration().rate()); + _currentTime = frameTime + OTIO_NS::RationalTime(1.0, _timeRange.duration().rate()); decoding = 1; break; diff --git a/lib/toucan/FFmpegRead.h b/lib/toucanRender/FFmpegRead.h similarity index 97% rename from lib/toucan/FFmpegRead.h rename to lib/toucanRender/FFmpegRead.h index 549c792..88b5f4a 100644 --- a/lib/toucan/FFmpegRead.h +++ b/lib/toucanRender/FFmpegRead.h @@ -3,9 +3,8 @@ #pragma once -#include - -#include +#include +#include #include diff --git a/lib/toucan/FFmpegWrite.cpp b/lib/toucanRender/FFmpegWrite.cpp similarity index 98% rename from lib/toucan/FFmpegWrite.cpp rename to lib/toucanRender/FFmpegWrite.cpp index 42bacc6..c3d3b76 100644 --- a/lib/toucan/FFmpegWrite.cpp +++ b/lib/toucanRender/FFmpegWrite.cpp @@ -3,10 +3,10 @@ #include "FFmpegWrite.h" -#include "Util.h" - #include +#include + #include #include @@ -67,7 +67,7 @@ namespace toucan _avCodecContext->height = spec.height; _avCodecContext->sample_aspect_ratio = AVRational({ 1, 1 }); _avCodecContext->pix_fmt = avCodec->pix_fmts[0]; - const auto rational = toRational(timeRange.duration().rate()); + const auto rational = dtk::toRational(timeRange.duration().rate()); _avCodecContext->time_base = { rational.second, rational.first }; _avCodecContext->framerate = { rational.first, rational.second }; _avCodecContext->profile = avProfile; @@ -291,7 +291,7 @@ namespace toucan _avFrame->data, _avFrame->linesize); - const auto timeRational = toRational(time.rate()); + const auto timeRational = dtk::toRational(time.rate()); _avFrame->pts = av_rescale_q( (time - _timeRange.start_time()).value(), { timeRational.second, timeRational.first }, diff --git a/lib/toucan/FFmpegWrite.h b/lib/toucanRender/FFmpegWrite.h similarity index 97% rename from lib/toucan/FFmpegWrite.h rename to lib/toucanRender/FFmpegWrite.h index 05d234d..13f797c 100644 --- a/lib/toucan/FFmpegWrite.h +++ b/lib/toucanRender/FFmpegWrite.h @@ -3,7 +3,7 @@ #pragma once -#include +#include #include diff --git a/lib/toucan/ImageEffect.cpp b/lib/toucanRender/ImageEffect.cpp similarity index 99% rename from lib/toucan/ImageEffect.cpp rename to lib/toucanRender/ImageEffect.cpp index 456d948..beefa20 100644 --- a/lib/toucan/ImageEffect.cpp +++ b/lib/toucanRender/ImageEffect.cpp @@ -3,7 +3,7 @@ #include "ImageEffect_p.h" -#include "Util.h" +#include namespace toucan { diff --git a/lib/toucan/ImageEffect.h b/lib/toucanRender/ImageEffect.h similarity index 90% rename from lib/toucan/ImageEffect.h rename to lib/toucanRender/ImageEffect.h index 103a66c..72e7757 100644 --- a/lib/toucan/ImageEffect.h +++ b/lib/toucanRender/ImageEffect.h @@ -3,9 +3,9 @@ #pragma once -#include -#include -#include +#include +#include +#include #include diff --git a/lib/toucan/ImageEffectHost.cpp b/lib/toucanRender/ImageEffectHost.cpp similarity index 90% rename from lib/toucan/ImageEffectHost.cpp rename to lib/toucanRender/ImageEffectHost.cpp index 9a550ea..637f736 100644 --- a/lib/toucan/ImageEffectHost.cpp +++ b/lib/toucanRender/ImageEffectHost.cpp @@ -5,7 +5,7 @@ #include "ImageEffect_p.h" -#include "Util.h" +#include #include #include @@ -20,9 +20,9 @@ namespace toucan } ImageEffectHost::ImageEffectHost( - const std::vector& searchPath, - const ImageEffectHostOptions& options) : - _options(options) + const std::shared_ptr& context, + const std::vector& searchPath) : + _context(context) { _propSet.setPointer("host", 0, this); @@ -100,38 +100,34 @@ namespace toucan void ImageEffectHost::_pluginInit(const std::vector& searchPath) { // Find the plugins. - if (_options.log) - { - _options.log->log(logPrefix, "Searching for plugins..."); - } + auto logSystem = _context.lock()->getSystem(); + logSystem->print(logPrefix, "Searching for plugins..."); std::vector pluginPaths; for (const auto& path : searchPath) { - if (_options.log) { std::stringstream ss; ss << " Search path: " << path.string(); - _options.log->log(logPrefix, ss.str()); + logSystem->print(logPrefix, ss.str()); } findPlugins(path, pluginPaths); } - if (pluginPaths.empty() && _options.log) + if (pluginPaths.empty()) { - _options.log->log(logPrefix, " No plugins found"); + logSystem->print(logPrefix, " No plugins found"); } // Load the plugins. - if (!pluginPaths.empty() && _options.log) + if (!pluginPaths.empty()) { - _options.log->log(logPrefix, "Loading plugins..."); + logSystem->print(logPrefix, "Loading plugins..."); } for (const auto& path : pluginPaths) { - if (_options.log) { std::stringstream ss; ss << " Path: " << path.string(); - _options.log->log(logPrefix, ss.str()); + logSystem->print(logPrefix, ss.str()); } try { @@ -175,17 +171,16 @@ namespace toucan } // Initialize the plugins. - if (!_plugins.empty() && _options.log) + if (!_plugins.empty()) { - _options.log->log(logPrefix, "Initializing plugins..."); + logSystem->print(logPrefix, "Initializing plugins..."); } for (auto& plugin : _plugins) { - if (_options.log) { std::stringstream ss; ss << " Plugin: " << plugin.ofxPlugin->pluginIdentifier; - _options.log->log(logPrefix, ss.str()); + logSystem->print(logPrefix, ss.str()); } ImageEffectHandle handle = { &plugin }; OfxStatus ofxStatus = plugin.ofxPlugin->mainEntry( @@ -203,11 +198,10 @@ namespace toucan { PropertySet propSet; propSet.setString(kOfxImageEffectPropContext, 0, context); - if (_options.log) { std::stringstream ss; ss << " Context: " << context; - _options.log->log(logPrefix, ss.str()); + logSystem->print(logPrefix, ss.str()); } ofxStatus = plugin.ofxPlugin->mainEntry( kOfxImageEffectActionDescribeInContext, @@ -216,14 +210,11 @@ namespace toucan nullptr); } } - if (_options.log) + for (const auto& param : plugin.paramTypes) { - for (const auto& param : plugin.paramTypes) - { - std::stringstream ss; - ss << " \"" << param.first << "\": " << param.second; - _options.log->log(logPrefix, ss.str()); - } + std::stringstream ss; + ss << " \"" << param.first << "\": " << param.second; + logSystem->print(logPrefix, ss.str()); } } } diff --git a/lib/toucan/ImageEffectHost.h b/lib/toucanRender/ImageEffectHost.h similarity index 86% rename from lib/toucan/ImageEffectHost.h rename to lib/toucanRender/ImageEffectHost.h index 0ab1c01..6dcb7b6 100644 --- a/lib/toucan/ImageEffectHost.h +++ b/lib/toucanRender/ImageEffectHost.h @@ -3,8 +3,9 @@ #pragma once -#include -#include +#include + +#include #include @@ -12,19 +13,13 @@ namespace toucan { - //! Image effect host options. - struct ImageEffectHostOptions - { - std::shared_ptr log; - }; - //! Image effect host. class ImageEffectHost : public std::enable_shared_from_this { public: ImageEffectHost( - const std::vector& searchPath, - const ImageEffectHostOptions& = ImageEffectHostOptions()); + const std::shared_ptr&, + const std::vector& searchPath); ~ImageEffectHost(); @@ -49,7 +44,7 @@ namespace toucan static OfxStatus _clipGetImage(OfxImageClipHandle, OfxTime, const OfxRectD*, OfxPropertySetHandle*); static OfxStatus _clipReleaseImage(OfxPropertySetHandle); - ImageEffectHostOptions _options; + std::weak_ptr _context; PropertySet _propSet; OfxHost _host; OfxPropertySuiteV1 _propertySuite; diff --git a/lib/toucan/ImageEffect_p.h b/lib/toucanRender/ImageEffect_p.h similarity index 93% rename from lib/toucan/ImageEffect_p.h rename to lib/toucanRender/ImageEffect_p.h index 3c7fa99..813a274 100644 --- a/lib/toucan/ImageEffect_p.h +++ b/lib/toucanRender/ImageEffect_p.h @@ -3,7 +3,7 @@ #pragma once -#include "ImageEffect.h" +#include namespace toucan { diff --git a/lib/toucan/ImageGraph.cpp b/lib/toucanRender/ImageGraph.cpp similarity index 91% rename from lib/toucan/ImageGraph.cpp rename to lib/toucanRender/ImageGraph.cpp index 44b7b8c..ca1278f 100644 --- a/lib/toucan/ImageGraph.cpp +++ b/lib/toucanRender/ImageGraph.cpp @@ -10,6 +10,8 @@ #include "TimelineAlgo.h" #include "Util.h" +#include + #include #include #include @@ -38,13 +40,13 @@ namespace toucan } ImageGraph::ImageGraph( + const std::shared_ptr& context, const std::filesystem::path& path, - const std::shared_ptr& timelineWrapper, - const ImageGraphOptions& options) : + const std::shared_ptr& timelineWrapper) : + _context(context), _path(path), _timelineWrapper(timelineWrapper), - _timeRange(timelineWrapper->getTimeRange()), - _options(options) + _timeRange(timelineWrapper->getTimeRange()) { _loadCache.setMax(10); @@ -70,13 +72,10 @@ namespace toucan } catch (const std::exception& e) { - if (_options.log) - { - _options.log->log( - "ImageGraph", - e.what(), - MessageLogType::Error); - } + _context.lock()->getSystem()->print( + logPrefix, + e.what(), + dtk::LogType::Error); } } else if (auto sequenceRef = dynamic_cast(clip->media_reference())) @@ -104,13 +103,10 @@ namespace toucan } catch (const std::exception& e) { - if (_options.log) - { - _options.log->log( - "ImageGraph", - e.what(), - MessageLogType::Error); - } + _context.lock()->getSystem()->print( + logPrefix, + e.what(), + dtk::LogType::Error); } } else if (auto generatorRef = dynamic_cast(clip->media_reference())) @@ -337,10 +333,6 @@ namespace toucan { std::shared_ptr out; - OTIO_NS::RationalTime timeOffset = - trimmedRangeInParent.start_time() - - item->trimmed_range().start_time(); - if (auto clip = OTIO_NS::dynamic_retainer_cast(item)) { // Get the media reference. @@ -357,21 +349,19 @@ namespace toucan } catch (const std::exception& e) { - if (_options.log) - { - _options.log->log( - "ImageGraph", - e.what(), - MessageLogType::Error); - } + _context.lock()->getSystem()->print( + logPrefix, + e.what(), + dtk::LogType::Error); } _loadCache.add(externalRef, read); } - - //! \bug Workaround for when the available range does not match - //! the range in the media. if (read) { + OTIO_NS::RationalTime timeOffset = -item->trimmed_range().start_time(); + + //! \bug Workaround for when the available range does not match + //! the range in the media. const OTIO_NS::TimeRange& timeRange = read->getTimeRange(); const auto availableOpt = externalRef->available_range(); if (availableOpt.has_value() && @@ -379,8 +369,9 @@ namespace toucan { timeOffset += availableOpt.value().start_time() - timeRange.start_time(); } - } + read->setTimeOffset(timeOffset); + } out = read; } else if (auto sequenceRef = dynamic_cast(clip->media_reference())) @@ -400,13 +391,14 @@ namespace toucan } catch (const std::exception& e) { - if (_options.log) - { - _options.log->log( - "ImageGraph", - e.what(), - MessageLogType::Error); - } + _context.lock()->getSystem()->print( + logPrefix, + e.what(), + dtk::LogType::Error); + } + if (read) + { + read->setTimeOffset(-item->trimmed_range().start_time()); } out = read; } @@ -428,9 +420,11 @@ namespace toucan { out = _effects(host, effects, out); } + + // Set the time offset. if (out) { - out->setTimeOffset(timeOffset); + out->setTimeOffset(out->getTimeOffset() + trimmedRangeInParent.start_time()); } return out; diff --git a/lib/toucan/ImageGraph.h b/lib/toucanRender/ImageGraph.h similarity index 81% rename from lib/toucan/ImageGraph.h rename to lib/toucanRender/ImageGraph.h index db6978c..fe95be0 100644 --- a/lib/toucan/ImageGraph.h +++ b/lib/toucanRender/ImageGraph.h @@ -3,10 +3,11 @@ #pragma once -#include -#include -#include -#include +#include +#include + +#include +#include #include #include @@ -19,20 +20,14 @@ namespace toucan { class ReadNode; - //! Image graph options. - struct ImageGraphOptions - { - std::shared_ptr log; - }; - //! Create image graphs from a timeline. class ImageGraph : public std::enable_shared_from_this { public: ImageGraph( + const std::shared_ptr&, const std::filesystem::path&, - const std::shared_ptr&, - const ImageGraphOptions& = ImageGraphOptions()); + const std::shared_ptr&); ~ImageGraph(); @@ -67,13 +62,13 @@ namespace toucan const std::vector >&, const std::shared_ptr&); + std::weak_ptr _context; std::filesystem::path _path; std::shared_ptr _timelineWrapper; OTIO_NS::TimeRange _timeRange; - ImageGraphOptions _options; IMATH_NAMESPACE::V2i _imageSize = IMATH_NAMESPACE::V2i(0, 0); int _imageChannels = 0; std::string _imageDataType; - LRUCache > _loadCache; + dtk::LRUCache > _loadCache; }; } diff --git a/lib/toucan/ImageNode.cpp b/lib/toucanRender/ImageNode.cpp similarity index 100% rename from lib/toucan/ImageNode.cpp rename to lib/toucanRender/ImageNode.cpp diff --git a/lib/toucan/ImageNode.h b/lib/toucanRender/ImageNode.h similarity index 100% rename from lib/toucan/ImageNode.h rename to lib/toucanRender/ImageNode.h diff --git a/lib/toucan/MemoryMap.cpp b/lib/toucanRender/MemoryMap.cpp similarity index 100% rename from lib/toucan/MemoryMap.cpp rename to lib/toucanRender/MemoryMap.cpp diff --git a/lib/toucan/MemoryMap.h b/lib/toucanRender/MemoryMap.h similarity index 85% rename from lib/toucan/MemoryMap.h rename to lib/toucanRender/MemoryMap.h index 40dfe05..936d7bc 100644 --- a/lib/toucan/MemoryMap.h +++ b/lib/toucanRender/MemoryMap.h @@ -7,12 +7,11 @@ #include #include -#include +#include namespace toucan { - //! A memory-mapped file for reading .otioz files without - //! extracting the contents. + //! A read-only memory mapped file. class MemoryMap { public: @@ -29,7 +28,7 @@ namespace toucan std::unique_ptr _p; }; - //! A reference to media within a memory-mapped .otioz file. + //! A reference within a memory mapped file. class MemoryReference { public: diff --git a/lib/toucan/MemoryMapUnix.cpp b/lib/toucanRender/MemoryMapUnix.cpp similarity index 100% rename from lib/toucan/MemoryMapUnix.cpp rename to lib/toucanRender/MemoryMapUnix.cpp diff --git a/lib/toucan/MemoryMapWin32.cpp b/lib/toucanRender/MemoryMapWin32.cpp similarity index 100% rename from lib/toucan/MemoryMapWin32.cpp rename to lib/toucanRender/MemoryMapWin32.cpp diff --git a/lib/toucanRender/Plugin.cpp b/lib/toucanRender/Plugin.cpp new file mode 100644 index 0000000..86bde8f --- /dev/null +++ b/lib/toucanRender/Plugin.cpp @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Contributors to the toucan project. + +#include "Plugin.h" + +namespace toucan +{ + int Plugin::getCount() + { + return _getNumberOfPlugins ? _getNumberOfPlugins() : 0; + } + + OfxPlugin* Plugin::getPlugin(int index) + { + return _getPlugin ? _getPlugin(index) : nullptr; + } + + namespace + { + void _findPlugins( + const std::filesystem::path& path, + std::vector& out, + int depth, + int maxDepth) + { + try + { + for (auto const& entry : std::filesystem::directory_iterator(path)) + { + const auto& entryPath = entry.path(); + if (entry.is_regular_file() && entryPath.extension() == ".ofx") + { + out.push_back(entryPath); + } + else if (entry.is_directory() && depth < maxDepth) + { + _findPlugins(entryPath, out, depth + 1, maxDepth); + } + } + } + catch (const std::exception&) + { + //! \bug How should this be handled? + } + } + } + + void findPlugins( + const std::filesystem::path& path, + std::vector& out) + { + _findPlugins(path, out, 0, 2); + } + +} diff --git a/lib/toucan/Plugin.h b/lib/toucanRender/Plugin.h similarity index 74% rename from lib/toucan/Plugin.h rename to lib/toucanRender/Plugin.h index 534097f..57780f4 100644 --- a/lib/toucan/Plugin.h +++ b/lib/toucanRender/Plugin.h @@ -3,7 +3,7 @@ #pragma once -#include +#include #include @@ -12,6 +12,7 @@ namespace toucan { + //! Plugin. class Plugin : std::enable_shared_from_this { public: @@ -19,8 +20,10 @@ namespace toucan ~Plugin(); + //! Get the number of plugins. int getCount(); + //! Get a plugin. OfxPlugin* getPlugin(int); private: @@ -33,4 +36,7 @@ namespace toucan struct Private; std::unique_ptr _p; }; + + //! Find plugins. + void findPlugins(const std::filesystem::path&, std::vector&); } diff --git a/lib/toucan/PluginUnix.cpp b/lib/toucanRender/PluginUnix.cpp similarity index 97% rename from lib/toucan/PluginUnix.cpp rename to lib/toucanRender/PluginUnix.cpp index 793bbe0..4c6ab69 100644 --- a/lib/toucan/PluginUnix.cpp +++ b/lib/toucanRender/PluginUnix.cpp @@ -3,8 +3,6 @@ #include "Plugin.h" -#include "Util.h" - #include #include diff --git a/lib/toucan/PluginWin32.cpp b/lib/toucanRender/PluginWin32.cpp similarity index 98% rename from lib/toucan/PluginWin32.cpp rename to lib/toucanRender/PluginWin32.cpp index ca005bb..209e263 100644 --- a/lib/toucan/PluginWin32.cpp +++ b/lib/toucanRender/PluginWin32.cpp @@ -3,8 +3,6 @@ #include "Plugin.h" -#include "Util.h" - #include #include diff --git a/lib/toucan/PropertySet.cpp b/lib/toucanRender/PropertySet.cpp similarity index 100% rename from lib/toucan/PropertySet.cpp rename to lib/toucanRender/PropertySet.cpp diff --git a/lib/toucan/PropertySet.h b/lib/toucanRender/PropertySet.h similarity index 100% rename from lib/toucan/PropertySet.h rename to lib/toucanRender/PropertySet.h diff --git a/lib/toucan/Read.cpp b/lib/toucanRender/Read.cpp similarity index 97% rename from lib/toucan/Read.cpp rename to lib/toucanRender/Read.cpp index 7df5b71..6caa8d9 100644 --- a/lib/toucan/Read.cpp +++ b/lib/toucanRender/Read.cpp @@ -80,7 +80,8 @@ namespace toucan // Read the image. out = _ffRead->getImage(offsetTime); - if (3 == _spec.nchannels) + const auto& spec = out.spec(); + if (3 == spec.nchannels) { // Add an alpha channel. const int channelOrder[] = { 0, 1, 2, -1 }; @@ -108,7 +109,9 @@ namespace toucan _spec.nchannels * _spec.channel_bytes(), _spec.width * _spec.nchannels * _spec.channel_bytes(), 0); - if (3 == _spec.nchannels) + + const auto& spec = buf.spec(); + if (3 == spec.nchannels) { // Add an alpha channel. const int channelOrder[] = { 0, 1, 2, -1 }; diff --git a/lib/toucan/Read.h b/lib/toucanRender/Read.h similarity index 94% rename from lib/toucan/Read.h rename to lib/toucanRender/Read.h index b61ea98..ad059a1 100644 --- a/lib/toucan/Read.h +++ b/lib/toucanRender/Read.h @@ -3,10 +3,9 @@ #pragma once -#include "MemoryMap.h" - -#include -#include +#include +#include +#include #include diff --git a/lib/toucan/TimeWarp.cpp b/lib/toucanRender/TimeWarp.cpp similarity index 92% rename from lib/toucan/TimeWarp.cpp rename to lib/toucanRender/TimeWarp.cpp index 53421f3..12edec8 100644 --- a/lib/toucan/TimeWarp.cpp +++ b/lib/toucanRender/TimeWarp.cpp @@ -25,7 +25,7 @@ namespace toucan { offsetTime -= _timeOffset; } - const OTIO_NS::RationalTime scaledTime = OTIO_NS::RationalTime(offsetTime.value() * _timeScalar, offsetTime.rate()).floor(); + const OTIO_NS::RationalTime scaledTime = OTIO_NS::RationalTime(offsetTime.value() * _timeScalar, _time.rate()).floor(); _inputs[0]->setTime(scaledTime); buf = _inputs[0]->exec(); } diff --git a/lib/toucan/TimeWarp.h b/lib/toucanRender/TimeWarp.h similarity index 93% rename from lib/toucan/TimeWarp.h rename to lib/toucanRender/TimeWarp.h index e43bb64..8d9c2c4 100644 --- a/lib/toucan/TimeWarp.h +++ b/lib/toucanRender/TimeWarp.h @@ -3,7 +3,7 @@ #pragma once -#include +#include namespace toucan { diff --git a/lib/toucanRender/TimelineAlgo.cpp b/lib/toucanRender/TimelineAlgo.cpp new file mode 100644 index 0000000..81207d4 --- /dev/null +++ b/lib/toucanRender/TimelineAlgo.cpp @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Contributors to the toucan project. + +#include "TimelineAlgo.h" + +#include + +namespace toucan +{ + std::vector > + getVideoClips(const OTIO_NS::SerializableObject::Retainer& timeline) + { + std::vector > out; + for (const auto& child : timeline->tracks()->children()) + { + if (auto track = OTIO_NS::dynamic_retainer_cast(child)) + { + if (OTIO_NS::Track::Kind::video == track->kind()) + { + const auto clips = track->find_clips(nullptr, std::nullopt, true); + out.insert(out.end(), clips.begin(), clips.end()); + } + } + } + return out; + } +} diff --git a/lib/toucan/TimelineAlgo.h b/lib/toucanRender/TimelineAlgo.h similarity index 59% rename from lib/toucan/TimelineAlgo.h rename to lib/toucanRender/TimelineAlgo.h index 5d4ef93..b47a79e 100644 --- a/lib/toucan/TimelineAlgo.h +++ b/lib/toucanRender/TimelineAlgo.h @@ -10,10 +10,4 @@ namespace toucan //! Get the video clips in a timeline. std::vector > getVideoClips(const OTIO_NS::SerializableObject::Retainer&); - - //! Get the range of multiple items. - std::optional getTimeRange( - const std::vector >&, - const OTIO_NS::RationalTime& startTime, - double rate); } diff --git a/lib/toucan/TimelineWrapper.cpp b/lib/toucanRender/TimelineWrapper.cpp similarity index 99% rename from lib/toucan/TimelineWrapper.cpp rename to lib/toucanRender/TimelineWrapper.cpp index 8fb07fd..e4f29e0 100644 --- a/lib/toucan/TimelineWrapper.cpp +++ b/lib/toucanRender/TimelineWrapper.cpp @@ -5,6 +5,8 @@ #include "Util.h" +#include + #include #include #include @@ -65,7 +67,7 @@ namespace toucan TimelineWrapper::TimelineWrapper(const std::filesystem::path& path) : _path(path) { - const std::string extension = toLower(_path.extension().string()); + const std::string extension = dtk::toLower(_path.extension().string()); if (".otio" == extension) { OTIO_NS::ErrorStatus errorStatus; diff --git a/lib/toucan/TimelineWrapper.h b/lib/toucanRender/TimelineWrapper.h similarity index 96% rename from lib/toucan/TimelineWrapper.h rename to lib/toucanRender/TimelineWrapper.h index 9760c3a..be60348 100644 --- a/lib/toucan/TimelineWrapper.h +++ b/lib/toucanRender/TimelineWrapper.h @@ -3,7 +3,7 @@ #pragma once -#include "MemoryMap.h" +#include #include diff --git a/lib/toucanRender/Util.cpp b/lib/toucanRender/Util.cpp new file mode 100644 index 0000000..ddbd7c4 --- /dev/null +++ b/lib/toucanRender/Util.cpp @@ -0,0 +1,115 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Contributors to the toucan project. + +#include "Util.h" + +#include + +namespace toucan +{ + std::pair splitURLProtocol(const std::string& url) + { + std::pair out; + const size_t size = url.size(); + size_t pos = url.find("://"); + if (pos != std::string::npos) + { + out.first = url.substr(0, pos + 2); + out.second = url.substr(pos + 3); + } + else + { + out.second = url; + } + return out; + } + + std::string getSequenceFrame( + const std::filesystem::path& path, + const std::string& namePrefix, + int frame, + int padding, + const std::string& nameSuffix) + { + std::stringstream ss; + ss << namePrefix << + std::setw(padding) << std::setfill('0') << frame << + nameSuffix; + return (path / ss.str()).string(); + } + + namespace + { + inline bool isNumber(char c) + { + return c >= '0' && c <= '9'; + } + } + + std::pair splitFileNameNumber(const std::string& stem) + { + std::pair out; + size_t i = stem.size(); + if (i > 0) + { + for (; i > 0 && isNumber(stem[i - 1]); --i) + ; + } + if (i >= 0) + { + out.first = stem.substr(0, i); + out.second = stem.substr(i, stem.size() - i); + } + return out; + } + + size_t getNumberPadding(const std::string& value) + { + size_t out = 0; + if (!value.empty() && '0' == value[0]) + { + out = value.size(); + } + return out; + } + + OTIO_NS::AnyVector vecToAny(const IMATH_NAMESPACE::V2i& vec) + { + return OTIO_NS::AnyVector + { + static_cast(vec.x), + static_cast(vec.y) + }; + } + + OTIO_NS::AnyVector vecToAny(const IMATH_NAMESPACE::V4f& vec) + { + return OTIO_NS::AnyVector + { + static_cast(vec.x), + static_cast(vec.y), + static_cast(vec.z), + static_cast(vec.w) + }; + } + + void anyToVec(const OTIO_NS::AnyVector& any, IMATH_NAMESPACE::V2i& out) + { + if (2 == any.size()) + { + out.x = std::any_cast(any[0]); + out.y = std::any_cast(any[1]); + } + } + + void anyToVec(const OTIO_NS::AnyVector& any, IMATH_NAMESPACE::V4f& out) + { + if (4 == any.size()) + { + out.x = std::any_cast(any[0]); + out.y = std::any_cast(any[1]); + out.z = std::any_cast(any[2]); + out.w = std::any_cast(any[3]); + } + } +} diff --git a/lib/toucan/Util.h b/lib/toucanRender/Util.h similarity index 53% rename from lib/toucan/Util.h rename to lib/toucanRender/Util.h index db3d9af..8859ebf 100644 --- a/lib/toucan/Util.h +++ b/lib/toucanRender/Util.h @@ -3,43 +3,18 @@ #pragma once -#include - -#include - #include #include #include #include -#include namespace toucan { - //! Convert a string to lowercase. - std::string toLower(std::string); - - //! Find plugins. - void findPlugins(const std::filesystem::path&, std::vector&); - - //! Conversion to any vector. - OTIO_NS::AnyVector vecToAny(const IMATH_NAMESPACE::V2i&); - OTIO_NS::AnyVector vecToAny(const IMATH_NAMESPACE::V4f&); - - //! Conversion from any vector. - void anyToVec(const OTIO_NS::AnyVector&, IMATH_NAMESPACE::V2i&); - void anyToVec(const OTIO_NS::AnyVector&, IMATH_NAMESPACE::V4f&); - //! Split the URL protocol. std::pair splitURLProtocol(const std::string&); - //! Join a list of strings. - std::string join(const std::vector&, char delimeter); - - //! Join a list of strings. - std::string join(const std::vector&, const std::string& delimeter); - //! Get an image sequence file name. std::string getSequenceFrame( const std::filesystem::path&, @@ -48,20 +23,17 @@ namespace toucan int padding, const std::string& nameSuffix); - //! Split the number from a file path stem. + //! Split the number from a file name. std::pair splitFileNameNumber(const std::string&); - //! Return the zero padding for the given number. + //! Get the zero padding for the given number. size_t getNumberPadding(const std::string&); - //! Convert a floating point rate to a rational. - std::pair toRational(double); - - //! Make a unique temp directory. - std::filesystem::path makeUniqueTemp(); - -#if defined(_WINDOWS) - //! Get an error string from a Windows system call. - std::string getLastError(); -#endif // _WINDOWS + //! Conversion to any vector. + OTIO_NS::AnyVector vecToAny(const IMATH_NAMESPACE::V2i&); + OTIO_NS::AnyVector vecToAny(const IMATH_NAMESPACE::V4f&); + + //! Conversion from any vector. + void anyToVec(const OTIO_NS::AnyVector&, IMATH_NAMESPACE::V2i&); + void anyToVec(const OTIO_NS::AnyVector&, IMATH_NAMESPACE::V4f&); } diff --git a/lib/toucanView/App.cpp b/lib/toucanView/App.cpp index 35a5538..e4800c9 100644 --- a/lib/toucanView/App.cpp +++ b/lib/toucanView/App.cpp @@ -11,7 +11,6 @@ #include #include #include -#include #include #include @@ -24,8 +23,6 @@ namespace toucan const std::shared_ptr& context, std::vector& argv) { - _messageLog = std::make_shared(); - dtk::App::_init( context, argv, @@ -51,9 +48,7 @@ namespace toucan #else // _WINDOWS searchPath.push_back(parentPath / ".." / ".."); #endif // _WINDOWS - ImageEffectHostOptions hostOptions; - //hostOptions.log = _messageLog; - _host = std::make_shared(searchPath, hostOptions); + _host = std::make_shared(context, searchPath); auto fileBrowserSystem = context->getSystem(); fileBrowserSystem->setNativeFileDialog(false); diff --git a/lib/toucanView/App.h b/lib/toucanView/App.h index 3cd3655..ac0609a 100644 --- a/lib/toucanView/App.h +++ b/lib/toucanView/App.h @@ -12,7 +12,6 @@ namespace toucan class FilesModel; class ImageEffectHost; class MainWindow; - class MessageLog; class TimeUnitsModel; class WindowModel; @@ -48,7 +47,6 @@ namespace toucan void open(const std::filesystem::path&); private: - std::shared_ptr _messageLog; std::string _path; std::shared_ptr _timeUnitsModel; std::shared_ptr _host; diff --git a/lib/toucanView/CMakeLists.txt b/lib/toucanView/CMakeLists.txt index df3a0fd..5d6c88b 100644 --- a/lib/toucanView/CMakeLists.txt +++ b/lib/toucanView/CMakeLists.txt @@ -10,14 +10,20 @@ set(HEADERS IItem.h IToolWidget.h InfoBar.h + InfoTool.h + ItemLabel.h JSONTool.h + LogTool.h MainWindow.h + MarkerItem.h + MarkersTool.h MenuBar.h PlaybackBar.h PlaybackModel.h SelectionModel.h StackItem.h ThumbnailGenerator.h + TimeLayout.h TimeUnitsModel.h TimeWidgets.h TimelineItem.h @@ -40,14 +46,20 @@ set(SOURCE IItem.cpp IToolWidget.cpp InfoBar.cpp + InfoTool.cpp + ItemLabel.cpp JSONTool.cpp + LogTool.cpp MainWindow.cpp + MarkerItem.cpp + MarkersTool.cpp MenuBar.cpp PlaybackBar.cpp PlaybackModel.cpp SelectionModel.cpp StackItem.cpp ThumbnailGenerator.cpp + TimeLayout.cpp TimeUnitsModel.cpp TimeWidgets.cpp TimelineItem.cpp @@ -59,6 +71,6 @@ set(SOURCE WindowModel.cpp) add_library(toucanView ${HEADERS} ${SOURCE}) -target_link_libraries(toucanView PUBLIC toucan dtk::dtkUI) +target_link_libraries(toucanView PUBLIC toucanRender dtk::dtkUI) set_target_properties(toucanView PROPERTIES FOLDER lib) add_dependencies(toucanView ${TOUCAN_PLUGINS}) diff --git a/lib/toucanView/ClipItem.cpp b/lib/toucanView/ClipItem.cpp index 92806e2..5561b9e 100644 --- a/lib/toucanView/ClipItem.cpp +++ b/lib/toucanView/ClipItem.cpp @@ -4,7 +4,6 @@ #include "ClipItem.h" #include -#include namespace toucan { @@ -12,25 +11,65 @@ namespace toucan const std::shared_ptr& context, const std::shared_ptr& app, const OTIO_NS::SerializableObject::Retainer& clip, + const OTIO_NS::SerializableObject::Retainer& timeline, const dtk::Color4F& color, const std::shared_ptr& parent) { - auto opt = clip->trimmed_range_in_parent(); + OTIO_NS::TimeRange timeRange = clip->transformed_time_range( + clip->trimmed_range(), + timeline->tracks()); + if (timeline->global_start_time().has_value()) + { + timeRange = OTIO_NS::TimeRange( + timeline->global_start_time().value() + timeRange.start_time(), + timeRange.duration()); + } IItem::_init( context, app, - OTIO_NS::dynamic_retainer_cast(clip), - opt.has_value() ? opt.value() : OTIO_NS::TimeRange(), + OTIO_NS::dynamic_retainer_cast(clip), + timeRange, "toucan::ClipItem", parent); - _setMousePressEnabled(0, 0); - _clip = clip; _text = !clip->name().empty() ? clip->name() : "Clip"; _color = color; - setTooltip(_text); + setTooltip(clip->schema_name() + ": " + _text); + + _layout = dtk::VerticalLayout::create(context, shared_from_this()); + _layout->setSpacingRole(dtk::SizeRole::SpacingTool); + + _label = ItemLabel::create(context, _layout); + _label->setName(_text); + + const auto& markers = clip->markers(); + if (!markers.empty()) + { + _markerLayout = TimeLayout::create(context, timeRange, _layout); + for (const auto& marker : markers) + { + OTIO_NS::TimeRange markerTimeRange = clip->transformed_time_range( + marker->marked_range(), + timeline->tracks()); + if (timeline->global_start_time().has_value()) + { + markerTimeRange = OTIO_NS::TimeRange( + timeline->global_start_time().value() + markerTimeRange.start_time(), + markerTimeRange.duration()); + } + auto markerItem = MarkerItem::create( + context, + app, + marker, + markerTimeRange, + _markerLayout); + _markerItems.push_back(markerItem); + } + } + + _textUpdate(); } ClipItem::~ClipItem() @@ -40,14 +79,38 @@ namespace toucan const std::shared_ptr& context, const std::shared_ptr& app, const OTIO_NS::SerializableObject::Retainer& clip, + const OTIO_NS::SerializableObject::Retainer& timeline, const dtk::Color4F& color, const std::shared_ptr& parent) { auto out = std::make_shared(); - out->_init(context, app, clip, color, parent); + out->_init(context, app, clip, timeline, color, parent); return out; } + void ClipItem::setScale(double value) + { + IItem::setScale(value); + if (_markerLayout) + { + _markerLayout->setScale(value); + } + } + + void ClipItem::setGeometry(const dtk::Box2I& value) + { + IItem::setGeometry(value); + _layout->setGeometry(value); + _geom.g2 = dtk::margin(value, -_size.border, 0, -_size.border, 0); + _geom.g3 = dtk::margin(_label->getGeometry(), -_size.border, 0, -_size.border, 0); + _selectionRect = _geom.g3; + } + + dtk::Box2I ClipItem::getChildrenClipRect() const + { + return _geom.g2; + } + void ClipItem::sizeHintEvent(const dtk::SizeHintEvent& event) { IItem::sizeHintEvent(event); @@ -56,26 +119,9 @@ namespace toucan { _size.init = false; _size.displayScale = event.displayScale; - _size.margin = event.style->getSizeRole(dtk::SizeRole::MarginInside, event.displayScale); _size.border = event.style->getSizeRole(dtk::SizeRole::Border, event.displayScale); - _size.fontInfo = event.style->getFontRole(dtk::FontRole::Label , event.displayScale); - _size.fontMetrics = event.fontSystem->getMetrics(_size.fontInfo); - _size.textSize = event.fontSystem->getSize(_text, _size.fontInfo); - _draw.glyphs.clear(); - } - dtk::Size2I sizeHint( - _timeRange.duration().rescaled_to(1.0).value() * _scale, - _size.textSize.h + _size.margin * 2 + _size.border * 2); - _setSizeHint(sizeHint); - } - - void ClipItem::clipEvent(const dtk::Box2I& clipRect, bool clipped) - { - IItem::clipEvent(clipRect, clipped); - if (clipped) - { - _draw.glyphs.clear(); } + _setSizeHint(_layout->getSizeHint()); } void ClipItem::drawEvent( @@ -83,26 +129,22 @@ namespace toucan const dtk::DrawEvent& event) { IItem::drawEvent(drawRect, event); - const dtk::Box2I& g = getGeometry(); - - const dtk::Box2I g2 = dtk::margin(g, -_size.border, 0, -_size.border, 0); event.render->drawRect( - g2, + _geom.g3, _selected ? event.style->getColorRole(dtk::ColorRole::Yellow) : _color); + } - const dtk::Box2I g3 = dtk::margin(g2, -_size.margin); - if (!_text.empty() && _draw.glyphs.empty()) + void ClipItem::_timeUnitsUpdate() + { + _textUpdate(); + } + + void ClipItem::_textUpdate() + { + if (_label) { - _draw.glyphs = event.fontSystem->getGlyphs(_text, _size.fontInfo); + std::string text = toString(_timeRange.duration(), _timeUnits); + _label->setDuration(text); } - dtk::ClipRectEnabledState clipRectEnabledState(event.render); - dtk::ClipRectState clipRectState(event.render); - event.render->setClipRectEnabled(true); - event.render->setClipRect(intersect(g3, drawRect)); - event.render->drawText( - _draw.glyphs, - _size.fontMetrics, - dtk::V2I(g3.min.x, g3.min.y + g3.h() / 2 - _size.fontMetrics.lineHeight / 2), - event.style->getColorRole(dtk::ColorRole::Text)); } } diff --git a/lib/toucanView/ClipItem.h b/lib/toucanView/ClipItem.h index 35bc1b2..c7180b0 100644 --- a/lib/toucanView/ClipItem.h +++ b/lib/toucanView/ClipItem.h @@ -3,7 +3,11 @@ #pragma once -#include "IItem.h" +#include +#include +#include + +#include #include @@ -17,6 +21,7 @@ namespace toucan const std::shared_ptr&, const std::shared_ptr&, const OTIO_NS::SerializableObject::Retainer&, + const OTIO_NS::SerializableObject::Retainer&, const dtk::Color4F&, const std::shared_ptr& parent); @@ -28,34 +33,45 @@ namespace toucan const std::shared_ptr&, const std::shared_ptr&, const OTIO_NS::SerializableObject::Retainer&, + const OTIO_NS::SerializableObject::Retainer&, const dtk::Color4F&, const std::shared_ptr& parent = nullptr); + void setScale(double) override; + + void setGeometry(const dtk::Box2I&) override; + dtk::Box2I getChildrenClipRect() const override; void sizeHintEvent(const dtk::SizeHintEvent&) override; - void clipEvent(const dtk::Box2I&, bool) override; void drawEvent(const dtk::Box2I&, const dtk::DrawEvent&) override; + protected: + void _timeUnitsUpdate() override; + private: + void _textUpdate(); + OTIO_NS::SerializableObject::Retainer _clip; std::string _text; dtk::Color4F _color; + std::shared_ptr _layout; + std::shared_ptr _label; + std::shared_ptr _markerLayout; + std::vector > _markerItems; + struct SizeData { bool init = true; float displayScale = 0.F; - int margin = 0; int border = 0; - dtk::FontInfo fontInfo; - dtk::FontMetrics fontMetrics; - dtk::Size2I textSize; }; SizeData _size; - struct DrawData + struct GeomData { - std::vector > glyphs; + dtk::Box2I g2; + dtk::Box2I g3; }; - DrawData _draw; + GeomData _geom; }; } diff --git a/lib/toucanView/ExportTool.cpp b/lib/toucanView/ExportTool.cpp index fa75c2a..538d8d0 100644 --- a/lib/toucanView/ExportTool.cpp +++ b/lib/toucanView/ExportTool.cpp @@ -7,7 +7,7 @@ #include "FilesModel.h" #include "PlaybackModel.h" -#include +#include #include #include @@ -290,6 +290,7 @@ namespace toucan return; _graph = std::make_shared( + getContext(), _file->getPath(), _file->getTimelineWrapper()); _imageSize = _graph->getImageSize(); diff --git a/lib/toucanView/ExportTool.h b/lib/toucanView/ExportTool.h index ac80c80..8fef598 100644 --- a/lib/toucanView/ExportTool.h +++ b/lib/toucanView/ExportTool.h @@ -3,10 +3,10 @@ #pragma once -#include "IToolWidget.h" +#include -#include -#include +#include +#include #include #include diff --git a/lib/toucanView/File.cpp b/lib/toucanView/File.cpp index 9e73eab..b0a412b 100644 --- a/lib/toucanView/File.cpp +++ b/lib/toucanView/File.cpp @@ -8,7 +8,7 @@ #include "ThumbnailGenerator.h" #include "ViewModel.h" -#include +#include #include @@ -33,6 +33,7 @@ namespace toucan _selectionModel = std::make_shared(); _thumbnailGenerator = std::make_shared( + context, _path.parent_path(), _timelineWrapper, _host); @@ -42,11 +43,10 @@ namespace toucan _rootNode = dtk::ObservableValue >::create(); _currentNode = dtk::ObservableValue >::create(); - ImageGraphOptions graphOptions; _graph = std::make_shared( + context, path.parent_path(), - _timelineWrapper, - graphOptions); + _timelineWrapper); _currentTimeObserver = dtk::ValueObserver::create( _playbackModel->observeCurrentTime(), diff --git a/lib/toucanView/File.h b/lib/toucanView/File.h index 509da4a..8055e0a 100644 --- a/lib/toucanView/File.h +++ b/lib/toucanView/File.h @@ -3,9 +3,9 @@ #pragma once -#include -#include -#include +#include +#include +#include #include #include diff --git a/lib/toucanView/FilesModel.cpp b/lib/toucanView/FilesModel.cpp index 65914e2..cb76055 100644 --- a/lib/toucanView/FilesModel.cpp +++ b/lib/toucanView/FilesModel.cpp @@ -3,8 +3,6 @@ #include "FilesModel.h" -#include - #include namespace toucan diff --git a/lib/toucanView/FilesModel.h b/lib/toucanView/FilesModel.h index 8f14017..0df8622 100644 --- a/lib/toucanView/FilesModel.h +++ b/lib/toucanView/FilesModel.h @@ -3,7 +3,7 @@ #pragma once -#include "File.h" +#include #include #include diff --git a/lib/toucanView/GapItem.cpp b/lib/toucanView/GapItem.cpp index 6608ddf..7253330 100644 --- a/lib/toucanView/GapItem.cpp +++ b/lib/toucanView/GapItem.cpp @@ -4,10 +4,6 @@ #include "GapItem.h" #include -#include -#include - -#include namespace toucan { @@ -15,24 +11,64 @@ namespace toucan const std::shared_ptr& context, const std::shared_ptr& app, const OTIO_NS::SerializableObject::Retainer& gap, + const OTIO_NS::SerializableObject::Retainer& timeline, const std::shared_ptr& parent) { - auto opt = gap->trimmed_range_in_parent(); + OTIO_NS::TimeRange timeRange = gap->transformed_time_range( + gap->trimmed_range(), + timeline->tracks()); + if (timeline->global_start_time().has_value()) + { + timeRange = OTIO_NS::TimeRange( + timeline->global_start_time().value() + timeRange.start_time(), + timeRange.duration()); + } IItem::_init( context, app, - OTIO_NS::dynamic_retainer_cast(gap), - opt.has_value() ? opt.value() : OTIO_NS::TimeRange(), + OTIO_NS::dynamic_retainer_cast(gap), + timeRange, "toucan::ClipItem", parent); - setTooltip(gap->name()); - _gap = gap; _text = !gap->name().empty() ? gap->name() : "Gap"; _color = dtk::Color4F(.3F, .3F, .3F); - setTooltip(_text); + setTooltip(gap->schema_name() + ": " + _text); + + _layout = dtk::VerticalLayout::create(context, shared_from_this()); + _layout->setSpacingRole(dtk::SizeRole::SpacingTool); + + _label = ItemLabel::create(context, _layout); + _label->setName(_text); + + const auto& markers = gap->markers(); + if (!markers.empty()) + { + _markerLayout = TimeLayout::create(context, timeRange, _layout); + for (const auto& marker : markers) + { + OTIO_NS::TimeRange markerTimeRange = gap->transformed_time_range( + marker->marked_range(), + timeline->tracks()); + if (timeline->global_start_time().has_value()) + { + markerTimeRange = OTIO_NS::TimeRange( + timeline->global_start_time().value() + markerTimeRange.start_time(), + markerTimeRange.duration()); + } + auto markerItem = MarkerItem::create( + context, + app, + marker, + markerTimeRange, + _markerLayout); + _markerItems.push_back(markerItem); + } + } + + _textUpdate(); } GapItem::~GapItem() @@ -42,13 +78,37 @@ namespace toucan const std::shared_ptr& context, const std::shared_ptr& app, const OTIO_NS::SerializableObject::Retainer& gap, + const OTIO_NS::SerializableObject::Retainer& timeline, const std::shared_ptr& parent) { auto out = std::make_shared(); - out->_init(context, app, gap, parent); + out->_init(context, app, gap, timeline, parent); return out; } + void GapItem::setScale(double value) + { + IItem::setScale(value); + if (_markerLayout) + { + _markerLayout->setScale(value); + } + } + + void GapItem::setGeometry(const dtk::Box2I& value) + { + IItem::setGeometry(value); + _layout->setGeometry(value); + _geom.g2 = dtk::margin(value, -_size.border, 0, -_size.border, 0); + _geom.g3 = dtk::margin(_label->getGeometry(), -_size.border, 0, -_size.border, 0); + _selectionRect = _geom.g3; + } + + dtk::Box2I GapItem::getChildrenClipRect() const + { + return _geom.g2; + } + void GapItem::sizeHintEvent(const dtk::SizeHintEvent& event) { IItem::sizeHintEvent(event); @@ -57,26 +117,9 @@ namespace toucan { _size.init = false; _size.displayScale = event.displayScale; - _size.margin = event.style->getSizeRole(dtk::SizeRole::MarginInside, event.displayScale); _size.border = event.style->getSizeRole(dtk::SizeRole::Border, event.displayScale); - _size.fontInfo = event.style->getFontRole(dtk::FontRole::Label, event.displayScale); - _size.fontMetrics = event.fontSystem->getMetrics(_size.fontInfo); - _size.textSize = event.fontSystem->getSize(_text, _size.fontInfo); - _draw.glyphs.clear(); - } - dtk::Size2I sizeHint( - _timeRange.duration().rescaled_to(1.0).value() * _scale, - _size.textSize.h + _size.margin * 2 + _size.border * 2); - _setSizeHint(sizeHint); - } - - void GapItem::clipEvent(const dtk::Box2I& clipRect, bool clipped) - { - IItem::clipEvent(clipRect, clipped); - if (clipped) - { - _draw.glyphs.clear(); } + _setSizeHint(_layout->getSizeHint()); } void GapItem::drawEvent( @@ -84,26 +127,22 @@ namespace toucan const dtk::DrawEvent& event) { IItem::drawEvent(drawRect, event); - const dtk::Box2I& g = getGeometry(); - - const dtk::Box2I g2 = dtk::margin(g, -_size.border, 0, -_size.border, 0); event.render->drawRect( - g2, + _geom.g3, _selected ? event.style->getColorRole(dtk::ColorRole::Yellow) : _color); + } - const dtk::Box2I g3 = dtk::margin(g2, -_size.margin); - if (!_text.empty() && _draw.glyphs.empty()) + void GapItem::_timeUnitsUpdate() + { + _textUpdate(); + } + + void GapItem::_textUpdate() + { + if (_label) { - _draw.glyphs = event.fontSystem->getGlyphs(_text, _size.fontInfo); + std::string text = toString(_timeRange.duration(), _timeUnits); + _label->setDuration(text); } - dtk::ClipRectEnabledState clipRectEnabledState(event.render); - dtk::ClipRectState clipRectState(event.render); - event.render->setClipRectEnabled(true); - event.render->setClipRect(intersect(g3, drawRect)); - event.render->drawText( - _draw.glyphs, - _size.fontMetrics, - dtk::V2I(g3.min.x, g3.min.y + g3.h() / 2 - _size.fontMetrics.lineHeight / 2), - event.style->getColorRole(dtk::ColorRole::Text)); } } diff --git a/lib/toucanView/GapItem.h b/lib/toucanView/GapItem.h index 2243a7b..bdeb186 100644 --- a/lib/toucanView/GapItem.h +++ b/lib/toucanView/GapItem.h @@ -3,7 +3,9 @@ #pragma once -#include "IItem.h" +#include + +#include #include @@ -17,6 +19,7 @@ namespace toucan const std::shared_ptr&, const std::shared_ptr&, const OTIO_NS::SerializableObject::Retainer&, + const OTIO_NS::SerializableObject::Retainer&, const std::shared_ptr& parent); public: @@ -27,33 +30,44 @@ namespace toucan const std::shared_ptr&, const std::shared_ptr&, const OTIO_NS::SerializableObject::Retainer&, + const OTIO_NS::SerializableObject::Retainer&, const std::shared_ptr& parent = nullptr); + void setScale(double) override; + + void setGeometry(const dtk::Box2I&) override; + dtk::Box2I getChildrenClipRect() const override; void sizeHintEvent(const dtk::SizeHintEvent&) override; - void clipEvent(const dtk::Box2I&, bool) override; void drawEvent(const dtk::Box2I&, const dtk::DrawEvent&) override; + protected: + void _timeUnitsUpdate() override; + private: + void _textUpdate(); + OTIO_NS::SerializableObject::Retainer _gap; std::string _text; dtk::Color4F _color; + std::shared_ptr _layout; + std::shared_ptr _label; + std::shared_ptr _markerLayout; + std::vector > _markerItems; + struct SizeData { bool init = true; float displayScale = 0.F; - int margin = 0; int border = 0; - dtk::FontInfo fontInfo; - dtk::FontMetrics fontMetrics; - dtk::Size2I textSize; }; SizeData _size; - struct DrawData + struct GeomData { - std::vector > glyphs; + dtk::Box2I g2; + dtk::Box2I g3; }; - DrawData _draw; + GeomData _geom; }; } diff --git a/lib/toucanView/GraphTool.h b/lib/toucanView/GraphTool.h index 8f4cfa3..e8a11a8 100644 --- a/lib/toucanView/GraphTool.h +++ b/lib/toucanView/GraphTool.h @@ -3,9 +3,9 @@ #pragma once -#include "IToolWidget.h" +#include -#include +#include #include #include diff --git a/lib/toucanView/IItem.cpp b/lib/toucanView/IItem.cpp index 74115f5..16fc8e0 100644 --- a/lib/toucanView/IItem.cpp +++ b/lib/toucanView/IItem.cpp @@ -3,47 +3,42 @@ #include "IItem.h" +#include "App.h" + namespace toucan { void IItem::_init( const std::shared_ptr& context, const std::shared_ptr& app, - const OTIO_NS::SerializableObject::Retainer& item, + const OTIO_NS::SerializableObject::Retainer& object, const OTIO_NS::TimeRange& timeRange, - const std::string& name, + const std::string& objectName, const std::shared_ptr& parent) { - IWidget::_init(context, name, parent); - _item = item; - _timeRange = timeRange; + ITimeWidget::_init(context, timeRange, objectName, parent); + + _object = object; + + _timeUnitsObserver = dtk::ValueObserver::create( + app->getTimeUnitsModel()->observeTimeUnits(), + [this](TimeUnits value) + { + _timeUnits = value; + _timeUnitsUpdate(); + }); } IItem::~IItem() {} - const OTIO_NS::SerializableObject::Retainer& IItem::getItem() const - { - return _item; - } - - const OTIO_NS::TimeRange& IItem::getTimeRange() + const OTIO_NS::SerializableObject::Retainer& IItem::getObject() const { - return _timeRange; + return _object; } - void IItem::setScale(double value) + const dtk::Box2I& IItem::getSelectionRect() const { - if (value == _scale) - return; - _scale = value; - for (const auto& child : getChildren()) - { - if (auto item = std::dynamic_pointer_cast(child)) - { - item->setScale(value); - } - } - _setSizeUpdate(); + return _selectionRect; } bool IItem::isSelected() const @@ -59,32 +54,12 @@ namespace toucan _setDrawUpdate(); } - OTIO_NS::RationalTime IItem::posToTime(double value) const + void IItem::setGeometry(const dtk::Box2I& value) { - OTIO_NS::RationalTime out; - const dtk::Box2I& g = getGeometry(); - if (g.w() > 0) - { - const double normalized = (value - g.min.x) / - static_cast(_timeRange.duration().rescaled_to(1.0).value() * _scale); - out = OTIO_NS::RationalTime( - _timeRange.start_time() + - OTIO_NS::RationalTime( - _timeRange.duration().value() * normalized, - _timeRange.duration().rate())). - round(); - out = dtk::clamp( - out, - _timeRange.start_time(), - _timeRange.end_time_inclusive()); - } - return out; + ITimeWidget::setGeometry(value); + _selectionRect = value; } - int IItem::timeToPos(const OTIO_NS::RationalTime& value) const - { - const dtk::Box2I& g = getGeometry(); - const OTIO_NS::RationalTime t = value - _timeRange.start_time(); - return g.min.x + t.rescaled_to(1.0).value() * _scale; - } + void IItem::_timeUnitsUpdate() + {} } diff --git a/lib/toucanView/IItem.h b/lib/toucanView/IItem.h index a88a02b..9a506d2 100644 --- a/lib/toucanView/IItem.h +++ b/lib/toucanView/IItem.h @@ -3,6 +3,9 @@ #pragma once +#include +#include + #include #include @@ -12,45 +15,43 @@ namespace toucan class App; //! Base class for timeline items. - class IItem : public dtk::IWidget + class IItem : public ITimeWidget { protected: void _init( const std::shared_ptr&, const std::shared_ptr&, - const OTIO_NS::SerializableObject::Retainer&, + const OTIO_NS::SerializableObject::Retainer&, const OTIO_NS::TimeRange&, - const std::string&, + const std::string& objectName, const std::shared_ptr& parent); public: virtual ~IItem() = 0; - //! Get the OTIO item. - const OTIO_NS::SerializableObject::Retainer& getItem() const; - - //! Get the time range. - const OTIO_NS::TimeRange& getTimeRange(); - - //! Set the item scale. - void setScale(double); + //! Get the OTIO object. + const OTIO_NS::SerializableObject::Retainer& getObject() const; + //! Get the item selection rectangle. + const dtk::Box2I& getSelectionRect() const; + //! Get whether the item is selected. bool isSelected() const; //! Set whether the item is selected. void setSelected(bool); - //! Convert a position to a time. - OTIO_NS::RationalTime posToTime(double) const; - - //! Convert a time to a position. - int timeToPos(const OTIO_NS::RationalTime&) const; + void setGeometry(const dtk::Box2I&) override; protected: - OTIO_NS::SerializableObject::Retainer _item; - OTIO_NS::TimeRange _timeRange; - double _scale = 100.0; + virtual void _timeUnitsUpdate(); + + OTIO_NS::SerializableObject::Retainer _object; + TimeUnits _timeUnits = TimeUnits::First; + dtk::Box2I _selectionRect; bool _selected = false; + + private: + std::shared_ptr > _timeUnitsObserver; }; } diff --git a/lib/toucanView/InfoTool.cpp b/lib/toucanView/InfoTool.cpp new file mode 100644 index 0000000..e39edf5 --- /dev/null +++ b/lib/toucanView/InfoTool.cpp @@ -0,0 +1,302 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Contributors to the toucan project. + +#include "InfoTool.h" + +#include "App.h" +#include "FilesModel.h" + +#include +#include +#include +#include +#include + +#include + +namespace toucan +{ + void InfoItemWidget::_init( + const std::shared_ptr& context, + const OTIO_NS::SerializableObject::Retainer& object, + const std::shared_ptr& parent) + { + IWidget::_init(context, "toucan::InfoItemWidget", parent); + + _object = object; + + _bellows = dtk::Bellows::create(context, object->name(), shared_from_this()); + _bellows->setOpen(true); + + _layout = dtk::GridLayout::create(context); + _layout->setRowBackgroundRole(dtk::ColorRole::Base); + _layout->setSpacingRole(dtk::SizeRole::None); + _bellows->setWidget(_layout); + + _text.push_back(std::make_pair("Schema:", object->schema_name())); + _text.push_back(std::make_pair("Name:", object->name())); + + if (auto item = OTIO_NS::dynamic_retainer_cast(object)) + { + _text.push_back(std::make_pair( + "Enabled:", + dtk::Format("{0}").arg(item->enabled()))); + + std::string text; + if (item->source_range().has_value()) + { + OTIO_NS::TimeRange timeRange = item->source_range().value(); + text = dtk::Format("{0} @ {1} / {2} @ {3}"). + arg(timeRange.start_time().value()). + arg(timeRange.start_time().rate()). + arg(timeRange.duration().value()). + arg(timeRange.duration().rate()); + } + _text.push_back(std::make_pair("Source range:", text)); + + OTIO_NS::TimeRange timeRange = item->available_range(); + text = dtk::Format("{0} @ {1} / {2} @ {3}"). + arg(timeRange.start_time().value()). + arg(timeRange.start_time().rate()). + arg(timeRange.duration().value()). + arg(timeRange.duration().rate()); + _text.push_back(std::make_pair("Available range:", text)); + + timeRange = item->trimmed_range(); + text = dtk::Format("{0} @ {1} / {2} @ {3}"). + arg(timeRange.start_time().value()). + arg(timeRange.start_time().rate()). + arg(timeRange.duration().value()). + arg(timeRange.duration().rate()); + _text.push_back(std::make_pair("Trimmed range:", text)); + + text.clear(); + //! \todo Calling trimmed_range_in_parent() on a stack causes a crash? + auto stack = OTIO_NS::dynamic_retainer_cast(item); + if (!stack && item->trimmed_range_in_parent().has_value()) + { + timeRange = item->trimmed_range_in_parent().value(); + text = dtk::Format("{0} @ {1} / {2} @ {3}"). + arg(timeRange.start_time().value()). + arg(timeRange.start_time().rate()). + arg(timeRange.duration().value()). + arg(timeRange.duration().rate()); + } + _text.push_back(std::make_pair("Trimmed range in parent:", text)); + } + else if (auto marker = OTIO_NS::dynamic_retainer_cast(object)) + { + _text.push_back(std::make_pair("Color:", marker->color())); + + OTIO_NS::TimeRange timeRange = marker->marked_range(); + std::string text = dtk::Format("{0} @ {1} / {2} @ {3}"). + arg(timeRange.start_time().value()). + arg(timeRange.start_time().rate()). + arg(timeRange.duration().value()). + arg(timeRange.duration().rate()); + _text.push_back(std::make_pair("Range:", text)); + + _text.push_back(std::make_pair("Comment:", marker->comment())); + } + + int row = 0; + for (const auto& text : _text) + { + auto label = dtk::Label::create(context, text.first, _layout); + label->setMarginRole(dtk::SizeRole::MarginSmall); + _layout->setGridPos(label, row, 0); + std::shared_ptr label2; + if (!text.second.empty()) + { + label2 = dtk::Label::create(context, text.second, _layout); + label2->setMarginRole(dtk::SizeRole::MarginSmall); + _layout->setGridPos(label2, row, 1); + } + _labels.push_back(std::make_pair(label, label2)); + ++row; + } + + _textUpdate(); + } + + InfoItemWidget::~InfoItemWidget() + {} + + std::shared_ptr InfoItemWidget::create( + const std::shared_ptr& context, + const OTIO_NS::SerializableObject::Retainer& object, + const std::shared_ptr& parent) + { + auto out = std::shared_ptr(new InfoItemWidget); + out->_init(context, object, parent); + return out; + } + + void InfoItemWidget::setOpen(bool value) + { + _bellows->setOpen(value); + } + + void InfoItemWidget::setSearch(const std::string& value) + { + if (value == _search) + return; + _search = value; + _textUpdate(); + } + + void InfoItemWidget::setGeometry(const dtk::Box2I& value) + { + IWidget::setGeometry(value); + _bellows->setGeometry(value); + } + + void InfoItemWidget::sizeHintEvent(const dtk::SizeHintEvent& event) + { + IWidget::sizeHintEvent(event); + _setSizeHint(_bellows->getSizeHint()); + } + + void InfoItemWidget::_textUpdate() + { + for (size_t i = 0; i < _text.size() && i < _labels.size(); ++i) + { + bool visible = true; + if (!_search.empty()) + { + visible &= + dtk::contains(_text[i].first, _search, dtk::CaseCompare::Insensitive) || + dtk::contains(_text[i].second, _search, dtk::CaseCompare::Insensitive); + } + _labels[i].first->setVisible(visible); + if (_labels[i].second) + { + _labels[i].second->setVisible(visible); + } + } + } + + void InfoTool::_init( + const std::shared_ptr& context, + const std::shared_ptr& app, + const std::shared_ptr& parent) + { + IToolWidget::_init(context, app, "toucan::InfoTool", "Info", parent); + + _layout = dtk::VerticalLayout::create(context, shared_from_this()); + _layout->setSpacingRole(dtk::SizeRole::None); + + _scrollWidget = dtk::ScrollWidget::create(context, dtk::ScrollType::Both, _layout); + _scrollWidget->setBorder(false); + _scrollWidget->setVStretch(dtk::Stretch::Expanding); + + _scrollLayout = dtk::VerticalLayout::create(context); + _scrollLayout->setSpacingRole(dtk::SizeRole::None); + _scrollWidget->setWidget(_scrollLayout); + + dtk::Divider::create(context, dtk::Orientation::Vertical, _layout); + + _bottomLayout = dtk::HorizontalLayout::create(context, _layout); + _bottomLayout->setMarginRole(dtk::SizeRole::MarginSmall); + _bottomLayout->setSpacingRole(dtk::SizeRole::SpacingSmall); + + _searchBox = dtk::SearchBox::create(context, _bottomLayout); + _searchBox->setHStretch(dtk::Stretch::Expanding); + _searchBox->setTooltip("Search the JSON text"); + + auto hLayout = dtk::HorizontalLayout::create(context, _bottomLayout); + hLayout->setSpacingRole(dtk::SizeRole::SpacingTool); + auto openButton = dtk::ToolButton::create(context, hLayout); + openButton->setIcon("BellowsOpen"); + openButton->setTooltip("Open all"); + auto closeButton = dtk::ToolButton::create(context, hLayout); + closeButton->setIcon("BellowsClosed"); + closeButton->setTooltip("Close all"); + + _searchBox->setCallback( + [this](const std::string& text) + { + for (const auto& widget : _widgets) + { + widget->setSearch(text); + } + }); + + openButton->setClickedCallback( + [this] + { + for (const auto& widget : _widgets) + { + widget->setOpen(true); + } + }); + + closeButton->setClickedCallback( + [this] + { + for (const auto& widget : _widgets) + { + widget->setOpen(false); + } + }); + + _fileObserver = dtk::ValueObserver >::create( + app->getFilesModel()->observeCurrent(), + [this](const std::shared_ptr& file) + { + if (file) + { + _selectionObserver = dtk::ListObserver::create( + file->getSelectionModel()->observeSelection(), + [this](const std::vector& selection) + { + for (const auto& widget : _widgets) + { + widget->setParent(nullptr); + } + _widgets.clear(); + auto context = getContext(); + for (const auto& item : selection) + { + auto widget = InfoItemWidget::create(context, item.object, _scrollLayout); + _widgets.push_back(widget); + } + }); + } + else + { + for (const auto& widget : _widgets) + { + widget->setParent(nullptr); + } + _widgets.clear(); + _selectionObserver.reset(); + } + }); + } + + InfoTool::~InfoTool() + {} + + std::shared_ptr InfoTool::create( + const std::shared_ptr& context, + const std::shared_ptr& app, + const std::shared_ptr& parent) + { + auto out = std::shared_ptr(new InfoTool); + out->_init(context, app, parent); + return out; + } + + void InfoTool::setGeometry(const dtk::Box2I& value) + { + IToolWidget::setGeometry(value); + _layout->setGeometry(value); + } + + void InfoTool::sizeHintEvent(const dtk::SizeHintEvent& event) + { + IToolWidget::sizeHintEvent(event); + _setSizeHint(_layout->getSizeHint()); + } +} diff --git a/lib/toucanView/InfoTool.h b/lib/toucanView/InfoTool.h new file mode 100644 index 0000000..b2852e6 --- /dev/null +++ b/lib/toucanView/InfoTool.h @@ -0,0 +1,98 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Contributors to the toucan project. + +#pragma once + +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace toucan +{ + class File; + + //! Information item widget. + class InfoItemWidget : public dtk::IWidget + { + protected: + void _init( + const std::shared_ptr&, + const OTIO_NS::SerializableObject::Retainer&, + const std::shared_ptr& parent); + + public: + virtual ~InfoItemWidget(); + + //! Create a new widget. + static std::shared_ptr create( + const std::shared_ptr&, + const OTIO_NS::SerializableObject::Retainer&, + const std::shared_ptr& parent = nullptr); + + //! Set whether the widget is open. + void setOpen(bool); + + //! Set the search. + void setSearch(const std::string&); + + void setGeometry(const dtk::Box2I&) override; + void sizeHintEvent(const dtk::SizeHintEvent&) override; + + private: + void _textUpdate(); + + OTIO_NS::SerializableObject::Retainer _object; + std::vector > _text; + std::string _search; + std::shared_ptr _bellows; + std::shared_ptr _layout; + std::vector, std::shared_ptr > > _labels; + }; + + //! Infoformation tool. + class InfoTool : public IToolWidget + { + protected: + void _init( + const std::shared_ptr&, + const std::shared_ptr&, + const std::shared_ptr& parent); + + public: + virtual ~InfoTool(); + + //! Create a new tool. + static std::shared_ptr create( + const std::shared_ptr&, + const std::shared_ptr&, + const std::shared_ptr& parent = nullptr); + + void setGeometry(const dtk::Box2I&) override; + void sizeHintEvent(const dtk::SizeHintEvent&) override; + + private: + std::shared_ptr _layout; + std::shared_ptr _scrollWidget; + std::shared_ptr _scrollLayout; + std::vector > _widgets; + std::shared_ptr _bottomLayout; + std::shared_ptr _searchBox; + + std::shared_ptr > > _fileObserver; + std::shared_ptr > _selectionObserver; + }; +} + diff --git a/lib/toucanView/ItemLabel.cpp b/lib/toucanView/ItemLabel.cpp new file mode 100644 index 0000000..a2a4c53 --- /dev/null +++ b/lib/toucanView/ItemLabel.cpp @@ -0,0 +1,137 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Contributors to the toucan project. + +#include "ItemLabel.h" + +namespace toucan +{ + void ItemLabel::_init( + const std::shared_ptr& context, + const std::shared_ptr& parent) + { + dtk::IWidget::_init(context, "toucan::ItemLabel", parent); + } + + ItemLabel::~ItemLabel() + {} + + std::shared_ptr ItemLabel::create( + const std::shared_ptr& context, + const std::shared_ptr& parent) + { + auto out = std::shared_ptr(new ItemLabel); + out->_init(context, parent); + return out; + } + + void ItemLabel::setName(const std::string& value) + { + if (value == _name) + return; + _name = value; + _setSizeUpdate(); + _setDrawUpdate(); + } + + void ItemLabel::setDuration(const std::string& value) + { + if (value == _duration) + return; + _duration = value; + _setSizeUpdate(); + _setDrawUpdate(); + } + + void ItemLabel::setMarginRole(dtk::SizeRole value) + { + if (value == _marginRole) + return; + _marginRole = value; + _setSizeUpdate(); + _setDrawUpdate(); + } + + void ItemLabel::setGeometry(const dtk::Box2I& value) + { + IWidget::setGeometry(value); + } + + void ItemLabel::sizeHintEvent(const dtk::SizeHintEvent& event) + { + IWidget::sizeHintEvent(event); + const bool displayScaleChanged = event.displayScale != _size.displayScale; + if (_size.init || displayScaleChanged) + { + _size.init = false; + _size.displayScale = event.displayScale; + _size.margin = event.style->getSizeRole(_marginRole, event.displayScale); + _size.margin2 = event.style->getSizeRole(dtk::SizeRole::MarginInside, event.displayScale); + _size.fontInfo = event.style->getFontRole(dtk::FontRole::Label, event.displayScale); + _size.fontMetrics = event.fontSystem->getMetrics(_size.fontInfo); + _size.nameSize = event.fontSystem->getSize(_name, _size.fontInfo); + _size.durationSize = event.fontSystem->getSize(_duration, _size.fontInfo); + _draw.nameGlyphs.clear(); + _draw.durationGlyphs.clear(); + } + dtk::Size2I sizeHint; + sizeHint.w = + _size.nameSize.w + _size.margin2 * 2 + + _size.durationSize.w + _size.margin2 * 2 + + _size.margin * 2; + sizeHint.h = std::max(_size.nameSize.h, _size.durationSize.h) + _size.margin * 2; + _setSizeHint(sizeHint); + } + + void ItemLabel::clipEvent(const dtk::Box2I& clipRect, bool clipped) + { + IWidget::clipEvent(clipRect, clipped); + if (clipped) + { + _draw.nameGlyphs.clear(); + _draw.durationGlyphs.clear(); + } + } + + void ItemLabel::drawEvent(const dtk::Box2I& drawRect, const dtk::DrawEvent& event) + { + IWidget::drawEvent(drawRect, event); + const dtk::Box2I& g = getGeometry(); + const dtk::Box2I g2 = margin(g, -_size.margin); + + int w = _size.nameSize.w + _size.margin * 2; + int h = _size.fontMetrics.lineHeight; + const dtk::Box2I g3( + g2.min.x, + g2.min.y + g2.h() / 2 - h / 2, + w, + h); + if (!_name.empty() && _draw.nameGlyphs.empty()) + { + _draw.nameGlyphs = event.fontSystem->getGlyphs(_name, _size.fontInfo); + } + event.render->drawText( + _draw.nameGlyphs, + _size.fontMetrics, + dtk::V2I(g3.min.x + _size.margin2, g3.min.y), + event.style->getColorRole(dtk::ColorRole::Text)); + + w = _size.durationSize.w + _size.margin2 * 2; + const dtk::Box2I g4( + g2.max.x - w, + g2.min.y + g2.h() / 2 - h / 2, + w, + h); + if (!dtk::intersects(g4, g3)) + { + if (!_duration.empty() && _draw.durationGlyphs.empty()) + { + _draw.durationGlyphs = event.fontSystem->getGlyphs(_duration, _size.fontInfo); + } + event.render->drawText( + _draw.durationGlyphs, + _size.fontMetrics, + dtk::V2I(g4.min.x + _size.margin2, g4.min.y), + event.style->getColorRole(dtk::ColorRole::Text)); + } + } +} diff --git a/lib/toucanView/ItemLabel.h b/lib/toucanView/ItemLabel.h new file mode 100644 index 0000000..9284480 --- /dev/null +++ b/lib/toucanView/ItemLabel.h @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Contributors to the toucan project. + +#pragma once + +#include + +namespace toucan +{ + //! Timeline item label. + class ItemLabel : public dtk::IWidget + { + protected: + void _init( + const std::shared_ptr&, + const std::shared_ptr& parent); + + public: + virtual ~ItemLabel(); + + //! Create a new label. + static std::shared_ptr create( + const std::shared_ptr&, + const std::shared_ptr& parent = nullptr); + + //! Set the name. + void setName(const std::string&); + + //! Set the duration. + void setDuration(const std::string&); + + //! Set the margin size role. + void setMarginRole(dtk::SizeRole); + + void setGeometry(const dtk::Box2I&) override; + void sizeHintEvent(const dtk::SizeHintEvent&) override; + void clipEvent(const dtk::Box2I&, bool) override; + void drawEvent(const dtk::Box2I&, const dtk::DrawEvent&) override; + + private: + std::string _name; + std::string _duration; + dtk::SizeRole _marginRole = dtk::SizeRole::MarginInside; + + struct SizeData + { + bool init = true; + float displayScale = 0.F; + int margin = 0; + int margin2 = 0; + dtk::FontInfo fontInfo; + dtk::FontMetrics fontMetrics; + dtk::Size2I nameSize; + dtk::Size2I durationSize; + }; + SizeData _size; + + struct DrawData + { + std::vector > nameGlyphs; + std::vector > durationGlyphs; + }; + DrawData _draw; + }; +} + diff --git a/lib/toucanView/JSONTool.cpp b/lib/toucanView/JSONTool.cpp index 79a17d6..5519d8a 100644 --- a/lib/toucanView/JSONTool.cpp +++ b/lib/toucanView/JSONTool.cpp @@ -5,33 +5,46 @@ #include "App.h" #include "FilesModel.h" -#include "SelectionModel.h" #include #include #include +#include #include namespace toucan { void JSONWidget::_init( const std::shared_ptr& context, - const OTIO_NS::SerializableObject::Retainer& item, + const OTIO_NS::SerializableObject::Retainer& object, const std::shared_ptr& parent) { IWidget::_init(context, "toucan::JSONWidget", parent); - _item = item; + _object = object; + _text = dtk::split(object->to_json_string(), { '\n' }); + for (int i = 0; i < _text.size(); ++i) + { + _lineNumbers.push_back(dtk::Format("{0}").arg(i, dtk::digits(_text.size()), '0')); + } - _text = dtk::split(item->to_json_string(), { '\n' }); + _bellows = dtk::Bellows::create(context, object->name(), shared_from_this()); + _bellows->setOpen(true); - _label = dtk::Label::create(context); - _label->setFontRole(dtk::FontRole::Mono); - _label->setMarginRole(dtk::SizeRole::MarginSmall); + auto hLayout = dtk::HorizontalLayout::create(context); + hLayout->setSpacingRole(dtk::SizeRole::None); + _bellows->setWidget(hLayout); - _bellows = dtk::Bellows::create(context, item->name(), shared_from_this()); - _bellows->setWidget(_label); - _bellows->setOpen(true); + _lineNumbersLabel = dtk::Label::create(context, hLayout); + _lineNumbersLabel->setBackgroundRole(dtk::ColorRole::Base); + _lineNumbersLabel->setFontRole(dtk::FontRole::Mono); + _lineNumbersLabel->setMarginRole(dtk::SizeRole::MarginSmall); + _lineNumbersLabel->setHStretch(dtk::Stretch::Fixed); + + _textLabel = dtk::Label::create(context, hLayout); + _textLabel->setFontRole(dtk::FontRole::Mono); + _textLabel->setMarginRole(dtk::SizeRole::MarginSmall); + _textLabel->setHStretch(dtk::Stretch::Expanding); _textUpdate(); } @@ -41,11 +54,11 @@ namespace toucan std::shared_ptr JSONWidget::create( const std::shared_ptr& context, - const OTIO_NS::SerializableObject::Retainer& item, + const OTIO_NS::SerializableObject::Retainer& object, const std::shared_ptr& parent) { auto out = std::shared_ptr(new JSONWidget); - out->_init(context, item, parent); + out->_init(context, object, parent); return out; } @@ -54,11 +67,11 @@ namespace toucan _bellows->setOpen(value); } - void JSONWidget::setFilter(const std::string& value) + void JSONWidget::setSearch(const std::string& value) { - if (value == _filter) + if (value == _search) return; - _filter = value; + _search = value; _textUpdate(); } @@ -76,21 +89,25 @@ namespace toucan void JSONWidget::_textUpdate() { - if (!_filter.empty()) + if (!_search.empty()) { + std::vector lineNumbers; std::vector text; - for (const auto& line : _text) + for (size_t i = 0; i < _lineNumbers.size() && i < _text.size(); ++i) { - if (dtk::contains(line, _filter, dtk::CaseCompare::Insensitive)) + if (dtk::contains(_text[i], _search, dtk::CaseCompare::Insensitive)) { - text.push_back(line); + lineNumbers.push_back(_lineNumbers[i]); + text.push_back(_text[i]); } } - _label->setText(dtk::join(text, '\n')); + _lineNumbersLabel->setText(dtk::join(lineNumbers, '\n')); + _textLabel->setText(dtk::join(text, '\n')); } else { - _label->setText(dtk::join(_text, '\n')); + _lineNumbersLabel->setText(dtk::join(_lineNumbers, '\n')); + _textLabel->setText(dtk::join(_text, '\n')); } } @@ -115,12 +132,12 @@ namespace toucan dtk::Divider::create(context, dtk::Orientation::Vertical, _layout); _bottomLayout = dtk::HorizontalLayout::create(context, _layout); - _bottomLayout->setMarginRole(dtk::SizeRole::MarginInside); + _bottomLayout->setMarginRole(dtk::SizeRole::MarginSmall); _bottomLayout->setSpacingRole(dtk::SizeRole::SpacingSmall); _searchBox = dtk::SearchBox::create(context, _bottomLayout); _searchBox->setHStretch(dtk::Stretch::Expanding); - _searchBox->setTooltip("Filter the JSON text"); + _searchBox->setTooltip("Search the JSON text"); auto hLayout = dtk::HorizontalLayout::create(context, _bottomLayout); hLayout->setSpacingRole(dtk::SizeRole::SpacingTool); @@ -131,30 +148,30 @@ namespace toucan closeButton->setIcon("BellowsClosed"); closeButton->setTooltip("Close all"); - openButton->setClickedCallback( - [this] + _searchBox->setCallback( + [this](const std::string& text) { for (const auto& widget : _widgets) { - widget->setOpen(true); + widget->setSearch(text); } }); - closeButton->setClickedCallback( + openButton->setClickedCallback( [this] { for (const auto& widget : _widgets) { - widget->setOpen(false); + widget->setOpen(true); } }); - _searchBox->setCallback( - [this](const std::string& text) + closeButton->setClickedCallback( + [this] { for (const auto& widget : _widgets) { - widget->setFilter(text); + widget->setOpen(false); } }); @@ -164,9 +181,9 @@ namespace toucan { if (file) { - _selectionObserver = dtk::ListObserver >::create( + _selectionObserver = dtk::ListObserver::create( file->getSelectionModel()->observeSelection(), - [this](const std::vector >& selection) + [this](const std::vector& selection) { for (const auto& widget : _widgets) { @@ -176,8 +193,8 @@ namespace toucan auto context = getContext(); for (const auto& item : selection) { - auto widget = JSONWidget::create(context, item, _scrollLayout); - widget->setFilter(_searchBox->getText()); + auto widget = JSONWidget::create(context, item.object, _scrollLayout); + widget->setSearch(_searchBox->getText()); _widgets.push_back(widget); } }); diff --git a/lib/toucanView/JSONTool.h b/lib/toucanView/JSONTool.h index e341184..01e9f62 100644 --- a/lib/toucanView/JSONTool.h +++ b/lib/toucanView/JSONTool.h @@ -3,7 +3,8 @@ #pragma once -#include "IToolWidget.h" +#include +#include #include #include @@ -24,7 +25,7 @@ namespace toucan protected: void _init( const std::shared_ptr&, - const OTIO_NS::SerializableObject::Retainer&, + const OTIO_NS::SerializableObject::Retainer&, const std::shared_ptr& parent); public: @@ -33,14 +34,14 @@ namespace toucan //! Create a new widget. static std::shared_ptr create( const std::shared_ptr&, - const OTIO_NS::SerializableObject::Retainer&, + const OTIO_NS::SerializableObject::Retainer&, const std::shared_ptr& parent = nullptr); //! Set whether the widget is open. void setOpen(bool); - //! Set the filter. - void setFilter(const std::string&); + //! Set the search. + void setSearch(const std::string&); void setGeometry(const dtk::Box2I&) override; void sizeHintEvent(const dtk::SizeHintEvent&) override; @@ -48,11 +49,13 @@ namespace toucan private: void _textUpdate(); - OTIO_NS::SerializableObject::Retainer _item; + OTIO_NS::SerializableObject::Retainer _object; + std::vector _lineNumbers; std::vector _text; - std::string _filter; - std::shared_ptr _label; + std::string _search; std::shared_ptr _bellows; + std::shared_ptr _lineNumbersLabel; + std::shared_ptr _textLabel; }; //! JSON tool. @@ -80,14 +83,14 @@ namespace toucan std::shared_ptr _file; std::shared_ptr _layout; - std::shared_ptr _searchBox; std::shared_ptr _scrollWidget; std::shared_ptr _scrollLayout; std::vector > _widgets; std::shared_ptr _bottomLayout; + std::shared_ptr _searchBox; std::shared_ptr > > _fileObserver; - std::shared_ptr > > _selectionObserver; + std::shared_ptr > _selectionObserver; }; } diff --git a/lib/toucanView/LogTool.cpp b/lib/toucanView/LogTool.cpp new file mode 100644 index 0000000..6c70322 --- /dev/null +++ b/lib/toucanView/LogTool.cpp @@ -0,0 +1,138 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Contributors to the toucan project. + +#include "LogTool.h" + +#include "App.h" +#include "FilesModel.h" +#include "SelectionModel.h" + +#include +#include +#include +#include + +namespace toucan +{ + namespace + { + const size_t textMax = 200; + } + + void LogTool::_init( + const std::shared_ptr& context, + const std::shared_ptr& app, + const std::shared_ptr& parent) + { + IToolWidget::_init(context, app, "toucan::LogTool", "Log", parent); + + _layout = dtk::VerticalLayout::create(context, shared_from_this()); + _layout->setSpacingRole(dtk::SizeRole::None); + + _scrollWidget = dtk::ScrollWidget::create(context, dtk::ScrollType::Both, _layout); + _scrollWidget->setBorder(false); + _scrollWidget->setVStretch(dtk::Stretch::Expanding); + + _label = dtk::Label::create(context); + _label->setVAlign(dtk::VAlign::Top); + _label->setFontRole(dtk::FontRole::Mono); + _label->setMarginRole(dtk::SizeRole::MarginSmall); + _scrollWidget->setWidget(_label); + + dtk::Divider::create(context, dtk::Orientation::Vertical, _layout); + + _bottomLayout = dtk::HorizontalLayout::create(context, _layout); + _bottomLayout->setMarginRole(dtk::SizeRole::MarginSmall); + _bottomLayout->setSpacingRole(dtk::SizeRole::SpacingSmall); + + _searchBox = dtk::SearchBox::create(context, _bottomLayout); + _searchBox->setHStretch(dtk::Stretch::Expanding); + _searchBox->setTooltip("Search the log"); + + auto hLayout = dtk::HorizontalLayout::create(context, _bottomLayout); + hLayout->setSpacingRole(dtk::SizeRole::SpacingTool); + auto clearButton = dtk::ToolButton::create(context, hLayout); + clearButton->setIcon("Clear"); + clearButton->setTooltip("Clear the log"); + + _textUpdate(); + + clearButton->setClickedCallback( + [this] + { + _text.clear(); + _textUpdate(); + }); + + _searchBox->setCallback( + [this](const std::string&) + { + _textUpdate(); + }); + + _logObserver = dtk::ListObserver::create( + context->getSystem()->observeLogItems(), + [this](const std::vector& items) + { + for (const auto& item : items) + { + for (const auto& line : dtk::split(dtk::toString(item), '\n')) + { + _text.push_back(line); + } + } + _textUpdate(); + }); + } + + LogTool::~LogTool() + {} + + std::shared_ptr LogTool::create( + const std::shared_ptr& context, + const std::shared_ptr& app, + const std::shared_ptr& parent) + { + auto out = std::shared_ptr(new LogTool); + out->_init(context, app, parent); + return out; + } + + void LogTool::setGeometry(const dtk::Box2I& value) + { + IToolWidget::setGeometry(value); + _layout->setGeometry(value); + } + + void LogTool::sizeHintEvent(const dtk::SizeHintEvent& event) + { + IToolWidget::sizeHintEvent(event); + _setSizeHint(_layout->getSizeHint()); + } + + void LogTool::_textUpdate() + { + while (_text.size() > textMax) + { + _text.pop_front(); + } + + std::vector text; + const std::string search = _searchBox->getText(); + if (search.empty()) + { + text.insert(text.begin(), _text.begin(), _text.end()); + } + else + { + for (const auto& line : _text) + { + if (dtk::contains(line, search, dtk::CaseCompare::Insensitive)) + { + text.push_back(line); + } + } + } + _label->setText(dtk::join(text, '\n')); + } +} diff --git a/lib/toucanView/LogTool.h b/lib/toucanView/LogTool.h new file mode 100644 index 0000000..00d3dab --- /dev/null +++ b/lib/toucanView/LogTool.h @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Contributors to the toucan project. + +#pragma once + +#include + +#include +#include +#include +#include +#include + +namespace toucan +{ + class File; + + //! Log tool. + class LogTool : public IToolWidget + { + protected: + void _init( + const std::shared_ptr&, + const std::shared_ptr&, + const std::shared_ptr& parent); + + public: + virtual ~LogTool(); + + //! Create a new tool. + static std::shared_ptr create( + const std::shared_ptr&, + const std::shared_ptr&, + const std::shared_ptr& parent = nullptr); + + void setGeometry(const dtk::Box2I&) override; + void sizeHintEvent(const dtk::SizeHintEvent&) override; + + private: + void _textUpdate(); + + std::list _text; + + std::shared_ptr _layout; + std::shared_ptr _scrollWidget; + std::shared_ptr _label; + std::shared_ptr _bottomLayout; + std::shared_ptr _searchBox; + + std::shared_ptr > > _fileObserver; + std::shared_ptr > _logObserver; + }; +} + diff --git a/lib/toucanView/MainWindow.cpp b/lib/toucanView/MainWindow.cpp index c47113f..e943077 100644 --- a/lib/toucanView/MainWindow.cpp +++ b/lib/toucanView/MainWindow.cpp @@ -9,7 +9,10 @@ #include "FilesModel.h" #include "GraphTool.h" #include "InfoBar.h" +#include "InfoTool.h" #include "JSONTool.h" +#include "MarkersTool.h" +#include "LogTool.h" #include "MenuBar.h" #include "PlaybackBar.h" #include "TimelineWidget.h" @@ -78,22 +81,25 @@ namespace toucan _tabWidget->setVStretch(dtk::Stretch::Expanding); _toolWidget = dtk::TabWidget::create(context, _hSplitter); + _toolWidgets.push_back(InfoTool::create(context, app)); _toolWidgets.push_back(JSONTool::create(context, app)); _toolWidgets.push_back(GraphTool::create(context, app)); + _toolWidgets.push_back(MarkersTool::create(context, app)); _toolWidgets.push_back(ExportTool::create(context, app)); + _toolWidgets.push_back(LogTool::create(context, app)); for (const auto& toolWidget : _toolWidgets) { _toolWidget->addTab(toolWidget->getText(), toolWidget); } - _bottomLayout = dtk::VerticalLayout::create(context, _vSplitter); - _bottomLayout->setSpacingRole(dtk::SizeRole::None); + _playbackLayout = dtk::VerticalLayout::create(context, _vSplitter); + _playbackLayout->setSpacingRole(dtk::SizeRole::None); - _playbackBar = PlaybackBar::create(context, app, _bottomLayout); + _playbackBar = PlaybackBar::create(context, app, _playbackLayout); - auto divider = dtk::Divider::create(context, dtk::Orientation::Vertical, _bottomLayout); + auto divider = dtk::Divider::create(context, dtk::Orientation::Vertical, _playbackLayout); - _timelineWidget = TimelineWidget::create(context, app, _bottomLayout); + _timelineWidget = TimelineWidget::create(context, app, _playbackLayout); _timelineWidget->setVStretch(dtk::Stretch::Expanding); divider = dtk::Divider::create(context, dtk::Orientation::Vertical, _layout); @@ -174,11 +180,11 @@ namespace toucan _toolBar->setVisible(i->second); _toolBarDivider->setVisible(i->second); - i = value.find(WindowComponent::ToolsPanel); + i = value.find(WindowComponent::Tools); _toolWidget->setVisible(i->second); - i = value.find(WindowComponent::PlaybackPanel); - _bottomLayout->setVisible(i->second); + i = value.find(WindowComponent::Playback); + _playbackLayout->setVisible(i->second); }); _tooltipsObserver = dtk::ValueObserver::create( diff --git a/lib/toucanView/MainWindow.h b/lib/toucanView/MainWindow.h index 550cb12..c79a4cf 100644 --- a/lib/toucanView/MainWindow.h +++ b/lib/toucanView/MainWindow.h @@ -3,7 +3,7 @@ #pragma once -#include "WindowModel.h" +#include #include #include @@ -66,7 +66,7 @@ namespace toucan std::map, std::shared_ptr > _fileTabs; std::shared_ptr _toolWidget; std::vector > _toolWidgets; - std::shared_ptr _bottomLayout; + std::shared_ptr _playbackLayout; std::shared_ptr _playbackBar; std::shared_ptr _timelineWidget; std::shared_ptr _infoBar; diff --git a/lib/toucanView/MarkerItem.cpp b/lib/toucanView/MarkerItem.cpp new file mode 100644 index 0000000..b9b1620 --- /dev/null +++ b/lib/toucanView/MarkerItem.cpp @@ -0,0 +1,158 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Contributors to the toucan project. + +#include "MarkerItem.h" + +#include + +namespace toucan +{ + dtk::Color4F getMarkerColor(const std::string& color) + { + dtk::Color4F out(1.F, 0.F, 0.F); + if (color == OTIO_NS::Marker::Color::pink) + { + out = dtk::Color4F(1.F, 0.F, .5F); + } + else if (color == OTIO_NS::Marker::Color::red) + { + out = dtk::Color4F(1.F, 0.F, 0.F); + } + else if (color == OTIO_NS::Marker::Color::orange) + { + out = dtk::Color4F(1.F, .6F, 0.F); + } + else if (color == OTIO_NS::Marker::Color::yellow) + { + out = dtk::Color4F(1.F, 1.F, 0.F); + } + else if (color == OTIO_NS::Marker::Color::green) + { + out = dtk::Color4F(0.F, 1.F, 0.F); + } + else if (color == OTIO_NS::Marker::Color::cyan) + { + out = dtk::Color4F(0.F, 1.F, 1.F); + } + else if (color == OTIO_NS::Marker::Color::blue) + { + out = dtk::Color4F(0.F, 0.F, 1.F); + } + else if (color == OTIO_NS::Marker::Color::purple) + { + out = dtk::Color4F(.5F, 0.F, 1.F); + } + else if (color == OTIO_NS::Marker::Color::magenta) + { + out = dtk::Color4F(1.F, 0.F, 1.F); + } + else if (color == OTIO_NS::Marker::Color::black) + { + out = dtk::Color4F(0.F, 0.F, 0.F); + } + else if (color == OTIO_NS::Marker::Color::white) + { + out = dtk::Color4F(1.F, 1.F, 1.F); + } + return out; + } + + void MarkerItem::_init( + const std::shared_ptr& context, + const std::shared_ptr& app, + const OTIO_NS::SerializableObject::Retainer& marker, + const OTIO_NS::TimeRange& timeRange, + const std::shared_ptr& parent) + { + IItem::_init( + context, + app, + OTIO_NS::dynamic_retainer_cast(marker), + timeRange, + "toucan::ClipItem", + parent); + + _marker = marker; + _text = !marker->name().empty() ? marker->name() : "Clip"; + _color = getMarkerColor(marker->color()); + + setTooltip(marker->schema_name() + ": " + _text); + + _label = ItemLabel::create(context, shared_from_this()); + _label->setName(_text); + + _textUpdate(); + } + + MarkerItem::~MarkerItem() + {} + + std::shared_ptr MarkerItem::create( + const std::shared_ptr& context, + const std::shared_ptr& app, + const OTIO_NS::SerializableObject::Retainer& marker, + const OTIO_NS::TimeRange& timeRange, + const std::shared_ptr& parent) + { + auto out = std::make_shared(); + out->_init(context, app, marker, timeRange, parent); + return out; + } + + void MarkerItem::setGeometry(const dtk::Box2I& value) + { + IItem::setGeometry(value); + const dtk::Box2I g( + value.min.x + value.h(), + value.min.y, + value.w() - value.h(), + value.h()); + _label->setGeometry(g); + } + + void MarkerItem::sizeHintEvent(const dtk::SizeHintEvent& event) + { + IItem::sizeHintEvent(event); + const bool displayScaleChanged = event.displayScale != _size.displayScale; + if (_size.init || displayScaleChanged) + { + _size.init = false; + _size.displayScale = event.displayScale; + _size.margin = event.style->getSizeRole(dtk::SizeRole::MarginInside, event.displayScale); + _size.border = event.style->getSizeRole(dtk::SizeRole::Border, event.displayScale); + } + dtk::Size2I sizeHint = _label->getSizeHint(); + sizeHint.h += _size.border * 2; + _setSizeHint(sizeHint); + _minWidth = sizeHint.h; + } + + void MarkerItem::drawEvent( + const dtk::Box2I& drawRect, + const dtk::DrawEvent& event) + { + IItem::drawEvent(drawRect, event); + const dtk::Box2I& g = getGeometry(); + const dtk::Box2I g2 = dtk::margin(g, -_size.border, 0, -_size.border, 0); + event.render->drawRect( + g2, + _selected ? event.style->getColorRole(dtk::ColorRole::Yellow) : dtk::Color4F(.3F, .3F, .3F)); + + const dtk::Box2I g3(g.min.x, g.min.y, g.h(), g.h()); + event.render->drawMesh(dtk::circle(dtk::center(g3), g3.h() / 4), _color); + } + + void MarkerItem::_timeUnitsUpdate() + { + _textUpdate(); + } + + void MarkerItem::_textUpdate() + { + if (_label) + { + std::string text = toString(_timeRange.duration(), _timeUnits); + _label->setDuration(text); + } + } +} diff --git a/lib/toucanView/MarkerItem.h b/lib/toucanView/MarkerItem.h new file mode 100644 index 0000000..a0c2e5a --- /dev/null +++ b/lib/toucanView/MarkerItem.h @@ -0,0 +1,65 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Contributors to the toucan project. + +#pragma once + +#include +#include + +#include + +#include + +namespace toucan +{ + //! Get a marker color. + dtk::Color4F getMarkerColor(const std::string&); + + //! Timeline marker item. + class MarkerItem : public IItem + { + protected: + void _init( + const std::shared_ptr&, + const std::shared_ptr&, + const OTIO_NS::SerializableObject::Retainer&, + const OTIO_NS::TimeRange&, + const std::shared_ptr& parent); + + public: + virtual ~MarkerItem(); + + //! Create a new item. + static std::shared_ptr create( + const std::shared_ptr&, + const std::shared_ptr&, + const OTIO_NS::SerializableObject::Retainer&, + const OTIO_NS::TimeRange&, + const std::shared_ptr& parent = nullptr); + + void setGeometry(const dtk::Box2I&) override; + void sizeHintEvent(const dtk::SizeHintEvent&) override; + void drawEvent(const dtk::Box2I&, const dtk::DrawEvent&) override; + + protected: + void _timeUnitsUpdate() override; + + private: + void _textUpdate(); + + OTIO_NS::SerializableObject::Retainer _marker; + std::string _text; + dtk::Color4F _color; + + std::shared_ptr _label; + + struct SizeData + { + bool init = true; + float displayScale = 0.F; + int margin = 0; + int border = 0; + }; + SizeData _size; + }; +} diff --git a/lib/toucanView/MarkersTool.cpp b/lib/toucanView/MarkersTool.cpp new file mode 100644 index 0000000..ac3ecae --- /dev/null +++ b/lib/toucanView/MarkersTool.cpp @@ -0,0 +1,88 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Contributors to the toucan project. + +#include "MarkersTool.h" + +#include "App.h" +#include "FilesModel.h" + +#include +#include +#include +#include + +namespace toucan +{ + void MarkersTool::_init( + const std::shared_ptr& context, + const std::shared_ptr& app, + const std::shared_ptr& parent) + { + IToolWidget::_init(context, app, "toucan::MarkersTool", "Markers", parent); + + _layout = dtk::VerticalLayout::create(context, shared_from_this()); + _layout->setSpacingRole(dtk::SizeRole::None); + + _scrollWidget = dtk::ScrollWidget::create(context, dtk::ScrollType::Both, _layout); + _scrollWidget->setBorder(false); + _scrollWidget->setVStretch(dtk::Stretch::Expanding); + + dtk::Divider::create(context, dtk::Orientation::Vertical, _layout); + + _bottomLayout = dtk::HorizontalLayout::create(context, _layout); + _bottomLayout->setMarginRole(dtk::SizeRole::MarginInside); + _bottomLayout->setSpacingRole(dtk::SizeRole::SpacingSmall); + + _searchBox = dtk::SearchBox::create(context, _bottomLayout); + _searchBox->setHStretch(dtk::Stretch::Expanding); + _searchBox->setTooltip("Search the markers"); + + _searchBox->setCallback( + [this](const std::string& text) + { + }); + + _fileObserver = dtk::ValueObserver >::create( + app->getFilesModel()->observeCurrent(), + [this](const std::shared_ptr& file) + { + if (file) + { + _selectionObserver = dtk::ListObserver::create( + file->getSelectionModel()->observeSelection(), + [this](const std::vector& selection) + { + }); + } + else + { + _selectionObserver.reset(); + } + }); + } + + MarkersTool::~MarkersTool() + {} + + std::shared_ptr MarkersTool::create( + const std::shared_ptr& context, + const std::shared_ptr& app, + const std::shared_ptr& parent) + { + auto out = std::shared_ptr(new MarkersTool); + out->_init(context, app, parent); + return out; + } + + void MarkersTool::setGeometry(const dtk::Box2I& value) + { + IToolWidget::setGeometry(value); + _layout->setGeometry(value); + } + + void MarkersTool::sizeHintEvent(const dtk::SizeHintEvent& event) + { + IToolWidget::sizeHintEvent(event); + _setSizeHint(_layout->getSizeHint()); + } +} diff --git a/lib/toucanView/MarkersTool.h b/lib/toucanView/MarkersTool.h new file mode 100644 index 0000000..f31c024 --- /dev/null +++ b/lib/toucanView/MarkersTool.h @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Contributors to the toucan project. + +#pragma once + +#include +#include + +#include +#include +#include +#include + +#include + +namespace toucan +{ + class File; + + //! Markers tool. + class MarkersTool : public IToolWidget + { + protected: + void _init( + const std::shared_ptr&, + const std::shared_ptr&, + const std::shared_ptr& parent); + + public: + virtual ~MarkersTool(); + + //! Create a new tool. + static std::shared_ptr create( + const std::shared_ptr&, + const std::shared_ptr&, + const std::shared_ptr& parent = nullptr); + + void setGeometry(const dtk::Box2I&) override; + void sizeHintEvent(const dtk::SizeHintEvent&) override; + + private: + std::shared_ptr _file; + + std::shared_ptr _layout; + std::shared_ptr _searchBox; + std::shared_ptr _scrollWidget; + std::shared_ptr _bottomLayout; + + std::shared_ptr > > _fileObserver; + std::shared_ptr > _selectionObserver; + }; +} + diff --git a/lib/toucanView/MenuBar.cpp b/lib/toucanView/MenuBar.cpp index 08dbd78..9eb71d8 100644 --- a/lib/toucanView/MenuBar.cpp +++ b/lib/toucanView/MenuBar.cpp @@ -9,7 +9,7 @@ #include "SelectionModel.h" #include "ViewModel.h" -#include +#include #include #include @@ -266,6 +266,19 @@ namespace toucan }); _menus["Select"]->addItem(_actions["Select/AllClips"]); + _actions["Select/AllMarkers"] = std::make_shared( + "All Markers", + [this] + { + if (_file) + { + _file->getSelectionModel()->selectAll( + _file->getTimeline(), + SelectionType::Markers); + } + }); + _menus["Select"]->addItem(_actions["Select/AllMarkers"]); + _actions["Select/None"] = std::make_shared( "None", dtk::Key::A, @@ -483,6 +496,7 @@ namespace toucan if (timeRangeOpt.has_value()) { _file->getPlaybackModel()->setInOutRange(timeRangeOpt.value()); + _file->getPlaybackModel()->setCurrentTime(timeRangeOpt.value().start_time()); } else { @@ -665,8 +679,8 @@ namespace toucan const std::vector components = { { WindowComponent::ToolBar, "ToolBar", "Tool Bar", "", "" }, - { WindowComponent::ToolsPanel, "ToolsPanel", "Tools Panel", "PanelRight", "Toggle the tools panel" }, - { WindowComponent::PlaybackPanel, "PlaybackPanel", "Playback Panel", "PanelBottom", "Toggle the playback panel" } + { WindowComponent::Tools, "Tools", "Tools", "PanelRight", "Toggle the tools" }, + { WindowComponent::Playback, "Playback", "Playback", "PanelBottom", "Toggle the playback controls" } }; std::weak_ptr appWeak(app); for (const auto& component : components) @@ -793,10 +807,10 @@ namespace toucan { auto i = value.find(WindowComponent::ToolBar); _menus["Window"]->setItemChecked(_actions["Window/ToolBar"], i->second); - i = value.find(WindowComponent::ToolsPanel); - _menus["Window"]->setItemChecked(_actions["Window/ToolsPanel"], i->second); - i = value.find(WindowComponent::PlaybackPanel); - _menus["Window"]->setItemChecked(_actions["Window/PlaybackPanel"], i->second); + i = value.find(WindowComponent::Tools); + _menus["Window"]->setItemChecked(_actions["Window/Tools"], i->second); + i = value.find(WindowComponent::Playback); + _menus["Window"]->setItemChecked(_actions["Window/Playback"], i->second); }); _displayScaleObserver = dtk::ValueObserver::create( @@ -833,6 +847,7 @@ namespace toucan _menus["Select"]->setItemEnabled(_actions["Select/All"], file); _menus["Select"]->setItemEnabled(_actions["Select/AllTracks"], file); _menus["Select"]->setItemEnabled(_actions["Select/AllClips"], file); + _menus["Select"]->setItemEnabled(_actions["Select/AllMarkers"], file); _menus["Select"]->setItemEnabled(_actions["Select/None"], file); _menus["Select"]->setItemEnabled(_actions["Select/Invert"], file); } diff --git a/lib/toucanView/MenuBar.h b/lib/toucanView/MenuBar.h index e073bb4..3ac0a07 100644 --- a/lib/toucanView/MenuBar.h +++ b/lib/toucanView/MenuBar.h @@ -3,8 +3,8 @@ #pragma once -#include "PlaybackModel.h" -#include "WindowModel.h" +#include +#include #include #include diff --git a/lib/toucanView/PlaybackBar.h b/lib/toucanView/PlaybackBar.h index d035b43..e39bb96 100644 --- a/lib/toucanView/PlaybackBar.h +++ b/lib/toucanView/PlaybackBar.h @@ -3,8 +3,8 @@ #pragma once -#include "TimeUnitsModel.h" -#include "TimeWidgets.h" +#include +#include #include #include diff --git a/lib/toucanView/PlaybackModel.cpp b/lib/toucanView/PlaybackModel.cpp index 73e4667..5378839 100644 --- a/lib/toucanView/PlaybackModel.cpp +++ b/lib/toucanView/PlaybackModel.cpp @@ -3,7 +3,7 @@ #include "PlaybackModel.h" -#include +#include #include diff --git a/lib/toucanView/SelectionModel.cpp b/lib/toucanView/SelectionModel.cpp index 39c39e1..0765f9a 100644 --- a/lib/toucanView/SelectionModel.cpp +++ b/lib/toucanView/SelectionModel.cpp @@ -4,84 +4,90 @@ #include "SelectionModel.h" #include +#include +#include -#include - -namespace OTIO_NS +namespace toucan { - bool operator == ( - const OTIO_NS::SerializableObject::Retainer& a, - const OTIO_NS::SerializableObject::Retainer& b) + bool SelectionItem::operator == (const SelectionItem& other) const { - return a.value == b.value; + return + object.value == other.object.value && + timeRange == other.timeRange; } - bool operator < ( - const OTIO_NS::SerializableObject::Retainer& a, - const OTIO_NS::SerializableObject::Retainer& b) + bool SelectionItem::operator != (const SelectionItem& other) const { - return a.value < b.value; + return !(*this == other); + } + + std::optional getTimeRange( + const std::vector& items, + const OTIO_NS::RationalTime& startTime, + double rate) + { + std::optional out; + if (!items.empty()) + { + OTIO_NS::TimeRange timeRange = items.front().timeRange; + for (size_t i = 1; i < items.size(); ++i) + { + timeRange = timeRange.extended_by(items[i].timeRange); + } + out = timeRange; + } + return out; } -} -namespace toucan -{ SelectionModel::SelectionModel() { - _selection = dtk::ObservableList >::create(); + _selection = dtk::ObservableList::create(); } SelectionModel::~SelectionModel() {} - const std::vector >& SelectionModel::getSelection() const + const std::vector& SelectionModel::getSelection() const { return _selection->get(); } - std::shared_ptr > > SelectionModel::observeSelection() const + std::shared_ptr > SelectionModel::observeSelection() const { return _selection; } - void SelectionModel::setSelection(const std::vector >& selection) + void SelectionModel::setSelection(const std::vector& selection) { - std::set > set; - set.insert(selection.begin(), selection.end()); - std::vector > tmp; - tmp.insert(tmp.end(), set.begin(), set.end()); - _selection->setIfChanged(tmp); + _selection->setIfChanged(selection); } void SelectionModel::selectAll( const OTIO_NS::SerializableObject::Retainer& timeline, SelectionType type) { - std::vector > items; + std::vector objects; switch (type) { case SelectionType::All: - //! \bug The stack is not returned by find_children? - items.push_back(timeline->tracks()); - for (auto& item : timeline->find_children()) - { - items.push_back(item); - } + objects.push_back({ timeline->tracks(), timeline->tracks()->trimmed_range() }); + _getTracks(timeline, objects); + _getClips(timeline, objects); + _getGaps(timeline, objects); + _getMarkers(timeline, objects); break; case SelectionType::Tracks: - for (auto& track : timeline->find_children()) - { - items.push_back(OTIO_NS::dynamic_retainer_cast(track)); - } + _getTracks(timeline, objects); break; case SelectionType::Clips: - for (auto& clip : timeline->find_children()) - { - items.push_back(OTIO_NS::dynamic_retainer_cast(clip)); - } + _getClips(timeline, objects); + break; + case SelectionType::Markers: + _getMarkers(timeline, objects); break; + default: break; } - _selection->setIfChanged(items); + _selection->setIfChanged(objects); } void SelectionModel::clearSelection() @@ -91,25 +97,108 @@ namespace toucan void SelectionModel::invertSelection(const OTIO_NS::SerializableObject::Retainer& timeline) { - std::vector > items; - for (auto& item : timeline->find_children()) - { - items.push_back(item); - } + std::vector objects; + objects.push_back({ timeline->tracks(), timeline->tracks()->trimmed_range() }); + _getTracks(timeline, objects); + _getClips(timeline, objects); + _getGaps(timeline, objects); + _getMarkers(timeline, objects); const auto& selection = _selection->get(); - auto i = items.begin(); - while (i != items.end()) + auto i = objects.begin(); + while (i != objects.end()) { auto j = std::find(selection.begin(), selection.end(), *i); if (j != selection.end()) { - i = items.erase(i); + i = objects.erase(i); } else { ++i; } } - _selection->setIfChanged(items); + _selection->setIfChanged(objects); + } + + void SelectionModel::_getTracks( + const OTIO_NS::SerializableObject::Retainer& timeline, + std::vector& out) + { + for (const auto& track : timeline->find_children()) + { + OTIO_NS::TimeRange timeRange; + if (track->trimmed_range_in_parent().has_value()) + { + timeRange = track->trimmed_range_in_parent().value(); + } + out.push_back({ + OTIO_NS::dynamic_retainer_cast(track), + timeRange }); + } + } + + void SelectionModel::_getClips( + const OTIO_NS::SerializableObject::Retainer& timeline, + std::vector& out) + { + for (const auto& clip : timeline->find_children()) + { + OTIO_NS::TimeRange timeRange; + if (clip->trimmed_range_in_parent().has_value()) + { + timeRange = clip->trimmed_range_in_parent().value(); + } + out.push_back({ + OTIO_NS::dynamic_retainer_cast(clip), + timeRange }); + } + } + + void SelectionModel::_getGaps( + const OTIO_NS::SerializableObject::Retainer& timeline, + std::vector& out) + { + for (const auto& gap : timeline->find_children()) + { + OTIO_NS::TimeRange timeRange; + if (gap->trimmed_range_in_parent().has_value()) + { + timeRange = gap->trimmed_range_in_parent().value(); + } + out.push_back({ + OTIO_NS::dynamic_retainer_cast(gap), + timeRange }); + } + } + + void SelectionModel::_getMarkers( + const OTIO_NS::SerializableObject::Retainer& timeline, + std::vector& out) + { + //! \bug timeline->find_children() does not include the stack? + { + const auto& markers = timeline->tracks()->markers(); + for (const auto& marker : markers) + { + out.push_back({ + OTIO_NS::dynamic_retainer_cast(marker), + marker->marked_range() }); + } + } + for (const auto& item : timeline->find_children()) + { + const auto& markers = item->markers(); + for (const auto& marker : markers) + { + OTIO_NS::TimeRange timeRange = marker->marked_range(); + if (auto parent = item->parent()) + { + timeRange = item->transformed_time_range(timeRange, parent); + } + out.push_back({ + OTIO_NS::dynamic_retainer_cast(marker), + marker->marked_range() }); + } + } } } diff --git a/lib/toucanView/SelectionModel.h b/lib/toucanView/SelectionModel.h index 978f9df..17a2aea 100644 --- a/lib/toucanView/SelectionModel.h +++ b/lib/toucanView/SelectionModel.h @@ -7,17 +7,6 @@ #include -namespace OTIO_NS -{ - //! \todo Move to OTIO? - bool operator == ( - const OTIO_NS::SerializableObject::Retainer&, - const OTIO_NS::SerializableObject::Retainer&); - bool operator < ( - const OTIO_NS::SerializableObject::Retainer&, - const OTIO_NS::SerializableObject::Retainer&); -} - namespace toucan { //! Selection type. @@ -25,9 +14,26 @@ namespace toucan { All, Tracks, - Clips + Clips, + Markers + }; + + //! Selection item. + struct SelectionItem + { + OTIO_NS::SerializableObject::Retainer object; + OTIO_NS::TimeRange timeRange; + + bool operator == (const SelectionItem&) const; + bool operator != (const SelectionItem&) const; }; + //! Get the range of multiple items. + std::optional getTimeRange( + const std::vector&, + const OTIO_NS::RationalTime& startTime, + double rate); + //! Selection model. class SelectionModel : public std::enable_shared_from_this { @@ -37,13 +43,13 @@ namespace toucan virtual ~SelectionModel(); //! Get the selection. - const std::vector >& getSelection() const; + const std::vector& getSelection() const; //! Observe the selection. - std::shared_ptr > > observeSelection() const; + std::shared_ptr > observeSelection() const; //! Set the seldction. - void setSelection(const std::vector >&); + void setSelection(const std::vector&); //! Select all of the given type. void selectAll( @@ -57,6 +63,19 @@ namespace toucan void invertSelection(const OTIO_NS::SerializableObject::Retainer&); private: - std::shared_ptr > > _selection; + void _getTracks( + const OTIO_NS::SerializableObject::Retainer&, + std::vector&); + void _getClips( + const OTIO_NS::SerializableObject::Retainer&, + std::vector&); + void _getGaps( + const OTIO_NS::SerializableObject::Retainer&, + std::vector&); + void _getMarkers( + const OTIO_NS::SerializableObject::Retainer&, + std::vector&); + + std::shared_ptr > _selection; }; } diff --git a/lib/toucanView/StackItem.cpp b/lib/toucanView/StackItem.cpp index 37ba1b3..f3dac90 100644 --- a/lib/toucanView/StackItem.cpp +++ b/lib/toucanView/StackItem.cpp @@ -14,13 +14,21 @@ namespace toucan const std::shared_ptr& context, const std::shared_ptr& app, const OTIO_NS::SerializableObject::Retainer& stack, + const OTIO_NS::SerializableObject::Retainer& timeline , const std::shared_ptr& parent) { + OTIO_NS::TimeRange timeRange = stack->trimmed_range(); + if (timeline->global_start_time().has_value()) + { + timeRange = OTIO_NS::TimeRange( + timeline->global_start_time().value() + timeRange.start_time(), + timeRange.duration()); + } IItem::_init( context, app, - OTIO_NS::dynamic_retainer_cast(stack), - stack->trimmed_range(), + OTIO_NS::dynamic_retainer_cast(stack), + timeRange, "toucan::StackItem", parent); @@ -28,15 +36,52 @@ namespace toucan _text = !stack->name().empty() ? stack->name() : "Stack"; _color = dtk::Color4F(.2F, .2F, .2F); - setTooltip(_text); + setTooltip(stack->schema_name() + ": " + _text); + + _layout = dtk::VerticalLayout::create(context, shared_from_this()); + _layout->setSpacingRole(dtk::SizeRole::SpacingTool); + + _label = ItemLabel::create(context, _layout); + _label->setName(_text); + + const auto& markers = stack->markers(); + if (!markers.empty()) + { + _markerLayout = TimeLayout::create(context, timeRange, _layout); + for (const auto& marker : markers) + { + OTIO_NS::TimeRange markerTimeRange = marker->marked_range(); + if (timeline->global_start_time().has_value()) + { + markerTimeRange = OTIO_NS::TimeRange( + timeline->global_start_time().value() + markerTimeRange.start_time(), + markerTimeRange.duration()); + } + auto markerItem = MarkerItem::create( + context, + app, + marker, + markerTimeRange, + _markerLayout); + _markerItems.push_back(markerItem); + } + } + _timeLayout = TimeStackLayout::create(context, timeRange, _layout); for (const auto& child : stack->children()) { if (auto track = OTIO_NS::dynamic_retainer_cast(child)) { - TrackItem::create(context, app, track, shared_from_this()); + TrackItem::create( + context, + app, + track, + timeline, + _timeLayout); } } + + _textUpdate(); } StackItem::~StackItem() @@ -46,25 +91,36 @@ namespace toucan const std::shared_ptr& context, const std::shared_ptr& app, const OTIO_NS::SerializableObject::Retainer& stack, + const OTIO_NS::SerializableObject::Retainer& timeline, const std::shared_ptr& parent) { auto out = std::make_shared(); - out->_init(context, app, stack, parent); + out->_init(context, app, stack, timeline, parent); return out; } - void StackItem::setGeometry(const dtk::Box2I& value) + void StackItem::setScale(double value) { - IItem::setGeometry(value); - const dtk::Box2I& g = getGeometry(); - dtk::V2I pos = g.min; - pos.y += _size.fontMetrics.lineHeight + _size.margin * 2 + _size.border * 2; - for (const auto& child : getChildren()) + IItem::setScale(value); + if (_markerLayout) { - const dtk::Size2I& sizeHint = child->getSizeHint(); - child->setGeometry(dtk::Box2I(pos.x, pos.y, sizeHint.w, sizeHint.h)); - pos.y += sizeHint.h; + _markerLayout->setScale(value); } + _timeLayout->setScale(value); + } + + void StackItem::setGeometry(const dtk::Box2I& value) + { + IItem::setGeometry(value); + _layout->setGeometry(value); + _geom.g2 = dtk::margin(value, -_size.border, 0, -_size.border, 0); + _geom.g3 = dtk::margin(_label->getGeometry(), -_size.border, 0, -_size.border, 0); + _selectionRect = _geom.g3; + } + + dtk::Box2I StackItem::getChildrenClipRect() const + { + return _geom.g2; } void StackItem::sizeHintEvent(const dtk::SizeHintEvent& event) @@ -75,33 +131,9 @@ namespace toucan { _size.init = false; _size.displayScale = event.displayScale; - _size.margin = event.style->getSizeRole(dtk::SizeRole::MarginInside, event.displayScale); _size.border = event.style->getSizeRole(dtk::SizeRole::Border, event.displayScale); - _size.fontInfo = event.style->getFontRole(dtk::FontRole::Label, event.displayScale); - _size.fontMetrics = event.fontSystem->getMetrics(_size.fontInfo); - _size.textSize = event.fontSystem->getSize(_text, _size.fontInfo); - _draw.glyphs.clear(); - } - dtk::Size2I sizeHint( - _timeRange.duration().rescaled_to(1.0).value() * _scale, - 0); - const auto& children = getChildren(); - for (const auto& child : children) - { - const dtk::Size2I& childSizeHint = child->getSizeHint(); - sizeHint.h += childSizeHint.h; - } - sizeHint.h += _size.textSize.h + _size.margin * 2 + _size.border * 4; - _setSizeHint(sizeHint); - } - - void StackItem::clipEvent(const dtk::Box2I& clipRect, bool clipped) - { - IItem::clipEvent(clipRect, clipped); - if (clipped) - { - _draw.glyphs.clear(); } + _setSizeHint(_layout->getSizeHint()); } void StackItem::drawEvent( @@ -109,30 +141,22 @@ namespace toucan const dtk::DrawEvent& event) { IItem::drawEvent(drawRect, event); - const dtk::Box2I& g = getGeometry(); - - const dtk::Box2I g2( - g.min.x + _size.border, - g.min.y, - g.w() - _size.border * 2, - _size.fontMetrics.lineHeight + _size.margin * 2); event.render->drawRect( - g2, + _geom.g3, _selected ? event.style->getColorRole(dtk::ColorRole::Yellow) : _color); + } - const dtk::Box2I g3 = dtk::margin(g2, -_size.margin); - if (!_text.empty() && _draw.glyphs.empty()) + void StackItem::_timeUnitsUpdate() + { + _textUpdate(); + } + + void StackItem::_textUpdate() + { + if (_label) { - _draw.glyphs = event.fontSystem->getGlyphs(_text, _size.fontInfo); + std::string text = toString(_timeRange.duration(), _timeUnits); + _label->setDuration(text); } - dtk::ClipRectEnabledState clipRectEnabledState(event.render); - dtk::ClipRectState clipRectState(event.render); - event.render->setClipRectEnabled(true); - event.render->setClipRect(intersect(g3, drawRect)); - event.render->drawText( - _draw.glyphs, - _size.fontMetrics, - g3.min, - event.style->getColorRole(dtk::ColorRole::Text)); } } diff --git a/lib/toucanView/StackItem.h b/lib/toucanView/StackItem.h index 6d85c05..35e7a2b 100644 --- a/lib/toucanView/StackItem.h +++ b/lib/toucanView/StackItem.h @@ -3,7 +3,9 @@ #pragma once -#include "IItem.h" +#include + +#include #include @@ -17,6 +19,7 @@ namespace toucan const std::shared_ptr&, const std::shared_ptr&, const OTIO_NS::SerializableObject::Retainer&, + const OTIO_NS::SerializableObject::Retainer&, const std::shared_ptr& parent); public: @@ -27,34 +30,45 @@ namespace toucan const std::shared_ptr&, const std::shared_ptr&, const OTIO_NS::SerializableObject::Retainer&, + const OTIO_NS::SerializableObject::Retainer&, const std::shared_ptr& parent = nullptr); + void setScale(double) override; + void setGeometry(const dtk::Box2I&) override; + dtk::Box2I getChildrenClipRect() const override; void sizeHintEvent(const dtk::SizeHintEvent&) override; - void clipEvent(const dtk::Box2I&, bool) override; void drawEvent(const dtk::Box2I&, const dtk::DrawEvent&) override; + protected: + void _timeUnitsUpdate() override; + private: + void _textUpdate(); + OTIO_NS::SerializableObject::Retainer _stack; std::string _text; dtk::Color4F _color; + std::shared_ptr _layout; + std::shared_ptr _label; + std::shared_ptr _markerLayout; + std::vector > _markerItems; + std::shared_ptr _timeLayout; + struct SizeData { bool init = true; float displayScale = 0.F; - int margin = 0; int border = 0; - dtk::FontInfo fontInfo; - dtk::FontMetrics fontMetrics; - dtk::Size2I textSize; }; SizeData _size; - struct DrawData + struct GeomData { - std::vector > glyphs; + dtk::Box2I g2; + dtk::Box2I g3; }; - DrawData _draw; + GeomData _geom; }; } diff --git a/lib/toucanView/ThumbnailGenerator.cpp b/lib/toucanView/ThumbnailGenerator.cpp index 34cdb26..f107e13 100644 --- a/lib/toucanView/ThumbnailGenerator.cpp +++ b/lib/toucanView/ThumbnailGenerator.cpp @@ -3,29 +3,29 @@ #include "ThumbnailGenerator.h" -#include - #include namespace toucan { ThumbnailGenerator::ThumbnailGenerator( + const std::shared_ptr& context, const std::filesystem::path& path, const std::shared_ptr& timelineWrapper, - const std::shared_ptr& host, - const std::shared_ptr& log) : + const std::shared_ptr& host) : _path(path), _timelineWrapper(timelineWrapper), - _host(host), - _log(log) + _host(host) { + _logSystem = context->getSystem(); + + _graph = std::make_shared(context, _path, _timelineWrapper); + _thread.running = true; _thread.thread = std::thread( [this] { try { - _graph = std::make_shared(_path, _timelineWrapper); const IMATH_NAMESPACE::V2i& imageSize = _graph->getImageSize(); _aspect = imageSize.y > 0 ? (imageSize.x / static_cast(imageSize.y)) : @@ -33,13 +33,10 @@ namespace toucan } catch (const std::exception& e) { - if (_log) - { - _log->log( - "ThumbnailGenerator", - e.what(), - MessageLogType::Error); - } + _logSystem->print( + "toucan::ThumbnailGenerator", + e.what(), + dtk::LogType::Error); } while (_thread.running) @@ -219,13 +216,10 @@ namespace toucan } catch (const std::exception& e) { - if (_log) - { - _log->log( - "ThumbnailGenerator", - e.what(), - MessageLogType::Error); - } + _logSystem->print( + "toucan::ThumbnailGenerator", + e.what(), + dtk::LogType::Error); } _thread.cache[std::make_pair(request->time, request->height)] = thumbnail; diff --git a/lib/toucanView/ThumbnailGenerator.h b/lib/toucanView/ThumbnailGenerator.h index 1068601..afec607 100644 --- a/lib/toucanView/ThumbnailGenerator.h +++ b/lib/toucanView/ThumbnailGenerator.h @@ -3,12 +3,12 @@ #pragma once -#include -#include -#include -#include +#include +#include +#include #include +#include #include #include @@ -33,10 +33,10 @@ namespace toucan { public: ThumbnailGenerator( + const std::shared_ptr&, const std::filesystem::path&, const std::shared_ptr&, - const std::shared_ptr&, - const std::shared_ptr& = nullptr); + const std::shared_ptr&); ~ThumbnailGenerator(); @@ -55,10 +55,10 @@ namespace toucan void _run(); void _cancel(); + std::shared_ptr _logSystem; std::filesystem::path _path; std::shared_ptr _timelineWrapper; std::shared_ptr _host; - std::shared_ptr _log; std::shared_ptr _graph; float _aspect = 1.F; diff --git a/lib/toucanView/TimeLayout.cpp b/lib/toucanView/TimeLayout.cpp new file mode 100644 index 0000000..c799df6 --- /dev/null +++ b/lib/toucanView/TimeLayout.cpp @@ -0,0 +1,204 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Contributors to the toucan project. + +#include "TimeLayout.h" + +namespace toucan +{ + void ITimeWidget::_init( + const std::shared_ptr& context, + const OTIO_NS::TimeRange& timeRange, + const std::string& objectName, + const std::shared_ptr& parent) + { + dtk::IWidget::_init(context, objectName, parent); + _timeRange = timeRange; + } + + ITimeWidget::~ITimeWidget() + {} + + const OTIO_NS::TimeRange& ITimeWidget::getTimeRange() + { + return _timeRange; + } + + double ITimeWidget::getScale() const + { + return _scale; + } + + void ITimeWidget::setScale(double value) + { + if (value == _scale) + return; + _scale = value; + for (const auto& child : getChildren()) + { + if (auto item = std::dynamic_pointer_cast(child)) + { + item->setScale(value); + } + } + _setSizeUpdate(); + } + + int ITimeWidget::getMinWidth() const + { + return _minWidth; + } + + OTIO_NS::RationalTime ITimeWidget::posToTime(double value) const + { + OTIO_NS::RationalTime out; + const dtk::Box2I& g = getGeometry(); + if (g.w() > 0) + { + const double normalized = (value - g.min.x) / + static_cast(_timeRange.duration().rescaled_to(1.0).value() * _scale); + out = OTIO_NS::RationalTime( + _timeRange.start_time() + + OTIO_NS::RationalTime( + _timeRange.duration().value() * normalized, + _timeRange.duration().rate())). + round(); + out = dtk::clamp( + out, + _timeRange.start_time(), + _timeRange.end_time_inclusive()); + } + return out; + } + + int ITimeWidget::timeToPos(const OTIO_NS::RationalTime& value) const + { + const dtk::Box2I& g = getGeometry(); + const OTIO_NS::RationalTime t = value - _timeRange.start_time(); + return g.min.x + t.rescaled_to(1.0).value() * _scale; + } + + void TimeLayout::_init( + const std::shared_ptr& context, + const OTIO_NS::TimeRange& timeRange, + const std::shared_ptr& parent) + { + ITimeWidget::_init(context, timeRange, "toucan::TimeLayout", parent); + } + + TimeLayout::~TimeLayout() + {} + + std::shared_ptr TimeLayout::create( + const std::shared_ptr& context, + const OTIO_NS::TimeRange& timeRange, + const std::shared_ptr& parent) + { + auto out = std::shared_ptr(new TimeLayout); + out->_init(context, timeRange, parent); + return out; + } + + void TimeLayout::setGeometry(const dtk::Box2I& value) + { + ITimeWidget::setGeometry(value); + for (const auto& child : getChildren()) + { + if (auto timeWidget = std::dynamic_pointer_cast(child)) + { + const OTIO_NS::TimeRange& timeRange = timeWidget->getTimeRange(); + const int t0 = timeToPos(timeRange.start_time()); + const int t1 = timeToPos(timeRange.end_time_exclusive()); + const dtk::Size2I& childSizeHint = child->getSizeHint(); + child->setGeometry(dtk::Box2I( + t0, + value.min.y, + std::max(t1 - t0, timeWidget->getMinWidth()), + childSizeHint.h)); + }; + } + } + + void TimeLayout::sizeHintEvent(const dtk::SizeHintEvent& event) + { + ITimeWidget::sizeHintEvent(event); + dtk::Size2I sizeHint; + for (const auto& child : getChildren()) + { + if (auto timeWidget = std::dynamic_pointer_cast(child)) + { + const dtk::Size2I& childSizeHint = timeWidget->getSizeHint(); + sizeHint.h = std::max(sizeHint.h, childSizeHint.h); + } + } + sizeHint.w = _timeRange.duration().rescaled_to(1.0).value() * _scale; + _setSizeHint(sizeHint); + } + + void TimeStackLayout::_init( + const std::shared_ptr& context, + const OTIO_NS::TimeRange& timeRange, + const std::shared_ptr& parent) + { + ITimeWidget::_init(context, timeRange, "toucan::TimeStackLayout", parent); + } + + TimeStackLayout::~TimeStackLayout() + {} + + std::shared_ptr TimeStackLayout::create( + const std::shared_ptr& context, + const OTIO_NS::TimeRange& timeRange, + const std::shared_ptr& parent) + { + auto out = std::shared_ptr(new TimeStackLayout); + out->_init(context, timeRange, parent); + return out; + } + + void TimeStackLayout::setGeometry(const dtk::Box2I& value) + { + ITimeWidget::setGeometry(value); + int y = value.min.y; + for (const auto& child : getChildren()) + { + if (auto timeWidget = std::dynamic_pointer_cast(child)) + { + const OTIO_NS::TimeRange& timeRange = timeWidget->getTimeRange(); + const double t0 = timeToPos(timeRange.start_time()); + const double t1 = timeToPos(timeRange.end_time_exclusive()); + const dtk::Size2I& childSizeHint = child->getSizeHint(); + child->setGeometry(dtk::Box2I(t0, y, t1 - t0, childSizeHint.h)); + y += childSizeHint.h + _size.spacing; + }; + } + } + + void TimeStackLayout::sizeHintEvent(const dtk::SizeHintEvent& event) + { + ITimeWidget::sizeHintEvent(event); + const bool displayScaleChanged = event.displayScale != _size.displayScale; + if (_size.init || displayScaleChanged) + { + _size.init = false; + _size.displayScale = event.displayScale; + _size.spacing = event.style->getSizeRole(dtk::SizeRole::SpacingTool, event.displayScale); + } + + dtk::Size2I sizeHint; + const auto& children = getChildren(); + if (!children.empty()) + { + for (const auto& child : children) + { + if (auto timeWidget = std::dynamic_pointer_cast(child)) + { + const dtk::Size2I& childSizeHint = timeWidget->getSizeHint(); + sizeHint.h += childSizeHint.h; + } + } + sizeHint.h += (children.size() - 1) * _size.spacing; + } + sizeHint.w = _timeRange.duration().rescaled_to(1.0).value() * _scale; + _setSizeHint(sizeHint); + } +} diff --git a/lib/toucanView/TimeLayout.h b/lib/toucanView/TimeLayout.h new file mode 100644 index 0000000..95ae29b --- /dev/null +++ b/lib/toucanView/TimeLayout.h @@ -0,0 +1,102 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Contributors to the toucan project. + +#pragma once + +#include + +#include + +namespace toucan +{ + //! Base class for widgets in a time layout. + class ITimeWidget : public dtk::IWidget + { + protected: + void _init( + const std::shared_ptr&, + const OTIO_NS::TimeRange&, + const std::string& objectName, + const std::shared_ptr& parent); + + public: + virtual ~ITimeWidget() = 0; + + //! Get the time range. + const OTIO_NS::TimeRange& getTimeRange(); + + //! Get the scale. + double getScale() const; + + //! Set the scale. + virtual void setScale(double); + + //! Get the minimum width. + int getMinWidth() const; + + //! Convert a position to a time. + OTIO_NS::RationalTime posToTime(double) const; + + //! Convert a time to a position. + int timeToPos(const OTIO_NS::RationalTime&) const; + + protected: + OTIO_NS::TimeRange _timeRange; + double _scale = 100.0; + int _minWidth = 0; + }; + + //! Time layout. + class TimeLayout : public ITimeWidget + { + protected: + void _init( + const std::shared_ptr&, + const OTIO_NS::TimeRange&, + const std::shared_ptr& parent); + + public: + virtual ~TimeLayout(); + + //! Create a new layout. + static std::shared_ptr create( + const std::shared_ptr&, + const OTIO_NS::TimeRange&, + const std::shared_ptr& parent = nullptr); + + void setGeometry(const dtk::Box2I&) override; + void sizeHintEvent(const dtk::SizeHintEvent&) override; + }; + + //! Time stack layout. + class TimeStackLayout : public ITimeWidget + { + protected: + void _init( + const std::shared_ptr&, + const OTIO_NS::TimeRange&, + const std::shared_ptr& parent); + + public: + virtual ~TimeStackLayout(); + + //! Create a new layout. + static std::shared_ptr create( + const std::shared_ptr&, + const OTIO_NS::TimeRange&, + const std::shared_ptr& parent = nullptr); + + void setGeometry(const dtk::Box2I&) override; + void sizeHintEvent(const dtk::SizeHintEvent&) override; + + private: + struct SizeData + { + bool init = true; + float displayScale = 0.F; + int spacing = 0; + }; + SizeData _size; + }; +} + diff --git a/lib/toucanView/TimeUnitsModel.cpp b/lib/toucanView/TimeUnitsModel.cpp index 63e77c8..20ed5fe 100644 --- a/lib/toucanView/TimeUnitsModel.cpp +++ b/lib/toucanView/TimeUnitsModel.cpp @@ -20,6 +20,53 @@ namespace toucan "Frames", "Seconds"); + std::string toString(const OTIO_NS::RationalTime& time, TimeUnits units) + { + std::string out; + switch (units) + { + case TimeUnits::Timecode: + out = time.to_timecode(); + break; + case TimeUnits::Frames: + { + std::stringstream ss; + ss << time.to_frames(); + out = ss.str(); + break; + } + case TimeUnits::Seconds: + { + std::stringstream ss; + ss.precision(2); + ss << std::fixed << time.to_seconds(); + out = ss.str(); + break; + } + default: break; + } + return out; + } + + OTIO_NS::RationalTime fromString(const std::string& text, TimeUnits units, double rate) + { + OTIO_NS::RationalTime out; + switch (units) + { + case TimeUnits::Timecode: + out = OTIO_NS::RationalTime::from_timecode(text, rate); + break; + case TimeUnits::Frames: + out = OTIO_NS::RationalTime::from_frames(std::atof(text.c_str()), rate); + break; + case TimeUnits::Seconds: + out = OTIO_NS::RationalTime::from_seconds(std::atof(text.c_str()), rate); + break; + default: break; + } + return out; + } + TimeUnitsModel::TimeUnitsModel(const std::shared_ptr& context) { _context = context; @@ -67,51 +114,4 @@ namespace toucan { _timeUnits->setIfChanged(value); } - - std::string TimeUnitsModel::toString(const OTIO_NS::RationalTime& time) const - { - std::string out; - switch (_timeUnits->get()) - { - case TimeUnits::Timecode: - out = time.to_timecode(); - break; - case TimeUnits::Frames: - { - std::stringstream ss; - ss << time.to_frames(); - out = ss.str(); - break; - } - case TimeUnits::Seconds: - { - std::stringstream ss; - ss.precision(2); - ss << std::fixed << time.to_seconds(); - out = ss.str(); - break; - } - default: break; - } - return out; - } - - OTIO_NS::RationalTime TimeUnitsModel::fromString(const std::string& text, double rate) const - { - OTIO_NS::RationalTime out; - switch (_timeUnits->get()) - { - case TimeUnits::Timecode: - out = OTIO_NS::RationalTime::from_timecode(text, rate); - break; - case TimeUnits::Frames: - out = OTIO_NS::RationalTime::from_frames(std::atof(text.c_str()), rate); - break; - case TimeUnits::Seconds: - out = OTIO_NS::RationalTime::from_seconds(std::atof(text.c_str()), rate); - break; - default: break; - } - return out; - } } diff --git a/lib/toucanView/TimeUnitsModel.h b/lib/toucanView/TimeUnitsModel.h index e1d27d4..feb6ceb 100644 --- a/lib/toucanView/TimeUnitsModel.h +++ b/lib/toucanView/TimeUnitsModel.h @@ -22,11 +22,11 @@ namespace toucan }; DTK_ENUM(TimeUnits); - //! Convert to a string. - std::string toString(TimeUnits); + //! Convert a time to a string. + std::string toString(const OTIO_NS::RationalTime&, TimeUnits); - //! Convert from a string. - TimeUnits fromString(const std::string&); + //! Convert a string to a time. + OTIO_NS::RationalTime fromString(const std::string&, TimeUnits, double rate); //! Time units model. class TimeUnitsModel : public std::enable_shared_from_this @@ -45,12 +45,6 @@ namespace toucan //! Set the time units. void setTimeUnits(TimeUnits); - //! Convert a time to a string. - std::string toString(const OTIO_NS::RationalTime&) const; - - //! Convert a string to a time. - OTIO_NS::RationalTime fromString(const std::string&, double rate) const; - private: std::weak_ptr _context; std::shared_ptr > _timeUnits; diff --git a/lib/toucanView/TimeWidgets.cpp b/lib/toucanView/TimeWidgets.cpp index 1534245..c648159 100644 --- a/lib/toucanView/TimeWidgets.cpp +++ b/lib/toucanView/TimeWidgets.cpp @@ -171,8 +171,6 @@ namespace toucan { IWidget::_init(context, "toucan::TimeEdit", parent); - _timeUnitsModel = timeUnitsModel; - _layout = dtk::HorizontalLayout::create(context, shared_from_this()); _layout->setSpacingRole(dtk::SizeRole::SpacingTool); @@ -189,7 +187,7 @@ namespace toucan { if (_callback) { - _callback(_timeUnitsModel->fromString(text, _time.rate())); + _callback(fromString(text, _timeUnits, _time.rate())); } }); @@ -206,8 +204,9 @@ namespace toucan _timeUnitsObserver = dtk::ValueObserver::create( timeUnitsModel->observeTimeUnits(), - [this](TimeUnits) + [this](TimeUnits value) { + _timeUnits = value; _timeUpdate(); }); } @@ -303,7 +302,7 @@ namespace toucan void TimeEdit::_timeUpdate() { - _lineEdit->setText(_timeUnitsModel->toString(_time)); + _lineEdit->setText(toString(_time, _timeUnits)); } void TimeEdit::_timeInc(int value) @@ -331,8 +330,6 @@ namespace toucan { IWidget::_init(context, "toucan::TimeLabel", parent); - _timeUnitsModel = timeUnitsModel; - _label = dtk::Label::create(context, shared_from_this()); _label->setFontRole(dtk::FontRole::Mono); _label->setMarginRole(dtk::SizeRole::MarginInside); @@ -343,6 +340,7 @@ namespace toucan timeUnitsModel->observeTimeUnits(), [this](TimeUnits value) { + _timeUnits = value; _timeUpdate(); }); } @@ -387,6 +385,6 @@ namespace toucan void TimeLabel::_timeUpdate() { - _label->setText(_timeUnitsModel->toString(_time)); + _label->setText(toString(_time, _timeUnits)); } } diff --git a/lib/toucanView/TimeWidgets.h b/lib/toucanView/TimeWidgets.h index de22e8b..0c6f3dc 100644 --- a/lib/toucanView/TimeWidgets.h +++ b/lib/toucanView/TimeWidgets.h @@ -3,8 +3,8 @@ #pragma once -#include "PlaybackModel.h" -#include "TimeUnitsModel.h" +#include +#include #include #include @@ -93,7 +93,7 @@ namespace toucan //! Create a new widget. static std::shared_ptr create( const std::shared_ptr&, - const std::shared_ptr& timeUnitsModel, + const std::shared_ptr&, const std::shared_ptr& parent = nullptr); //! Set the time. @@ -117,7 +117,7 @@ namespace toucan OTIO_NS::RationalTime _time; OTIO_NS::TimeRange _timeRange; - std::shared_ptr _timeUnitsModel; + TimeUnits _timeUnits = TimeUnits::First; std::shared_ptr _layout; std::shared_ptr _lineEdit; @@ -142,7 +142,7 @@ namespace toucan //! Create a new widget. static std::shared_ptr create( const std::shared_ptr&, - const std::shared_ptr& timeUnitsModel, + const std::shared_ptr&, const std::shared_ptr& parent = nullptr); //! Set the time. @@ -158,7 +158,7 @@ namespace toucan void _timeUpdate(); OTIO_NS::RationalTime _time; - std::shared_ptr _timeUnitsModel; + TimeUnits _timeUnits = TimeUnits::First; std::shared_ptr _label; diff --git a/lib/toucanView/TimelineItem.cpp b/lib/toucanView/TimelineItem.cpp index 5194ab4..d4ad795 100644 --- a/lib/toucanView/TimelineItem.cpp +++ b/lib/toucanView/TimelineItem.cpp @@ -5,7 +5,6 @@ #include "App.h" #include "FilesModel.h" -#include "SelectionModel.h" #include "StackItem.h" #include @@ -34,27 +33,18 @@ namespace toucan _timeline = file->getTimeline(); _timeRange = file->getTimelineWrapper()->getTimeRange(); - _timeUnitsModel = app->getTimeUnitsModel(); _selectionModel = file->getSelectionModel(); _thumbnails.setMax(100); _thumbnailGenerator = file->getThumbnailGenerator(); - StackItem::create(context, app, _timeline->tracks(), shared_from_this()); + StackItem::create(context, app, _timeline->tracks(), _timeline, shared_from_this()); - _timeUnitsObserver = dtk::ValueObserver::create( - _timeUnitsModel->observeTimeUnits(), - [this](TimeUnits) - { - _setDrawUpdate(); - }); - - _selectionObserver = dtk::ListObserver >::create( + _selectionObserver = dtk::ListObserver::create( _selectionModel->observeSelection(), - [this](const std::vector >& selection) + [this](const std::vector& selection) { _select(shared_from_this(), selection); }); - } TimelineItem::~TimelineItem() @@ -272,7 +262,7 @@ namespace toucan dtk::Box2I(pos, g.min.y + _size.scrollPos.y, _size.border * 2, g.h()), event.style->getColorRole(dtk::ColorRole::Red)); - std::string s = _timeUnitsModel->toString(_currentTime); + std::string s = toString(_currentTime, _timeUnits); dtk::Size2I size = event.fontSystem->getSize(s, _size.fontInfo); dtk::Box2I g3( pos + _size.border * 2 + _size.margin, @@ -313,19 +303,21 @@ namespace toucan static_cast(dtk::KeyModifier::Shift) == event.modifiers || static_cast(dtk::KeyModifier::Control) == event.modifiers)) { - auto selection = _select(shared_from_this(), event.pos); - OTIO_NS::SerializableObject::Retainer item; + std::shared_ptr selection; + _select(shared_from_this(), event.pos, selection); + SelectionItem item; if (selection) { - item = selection->getItem(); + item.object = selection->getObject(); + item.timeRange = selection->getTimeRange(); } - if (selection && item) + if (selection && item.object) { event.accept = true; takeKeyFocus(); _mouse.mode = MouseMode::Select; auto selectionPrev = _selectionModel->getSelection(); - std::vector > selectionNew; + std::vector selectionNew; if (static_cast(dtk::KeyModifier::Shift) == event.modifiers) { selectionNew = selectionPrev; @@ -371,10 +363,16 @@ namespace toucan } } + void TimelineItem::_timeUnitsUpdate() + { + _setSizeUpdate(); + _setDrawUpdate(); + } + dtk::Size2I TimelineItem::_getLabelMaxSize( const std::shared_ptr& fontSystem) const { - const std::string labelMax = _timeUnitsModel->toString(_timeRange.duration()); + const std::string labelMax = toString(_timeRange.duration(), _timeUnits); const dtk::Size2I labelMaxSize = fontSystem->getSize(labelMax, _size.fontInfo); return labelMaxSize; } @@ -537,7 +535,7 @@ namespace toucan _size.fontMetrics.lineHeight); if (time != _currentTime && intersects(box, drawRect)) { - const std::string label = _timeUnitsModel->toString(time); + const std::string label = toString(time, _timeUnits); event.render->drawText( event.fontSystem->getGlyphs(label, _size.fontInfo), _size.fontMetrics, @@ -549,33 +547,41 @@ namespace toucan } } - std::shared_ptr TimelineItem::_select( + void TimelineItem::_select( const std::shared_ptr& widget, - const dtk::V2I& pos) + const dtk::V2I& pos, + std::shared_ptr& out) { - std::shared_ptr out; if (auto iitem = std::dynamic_pointer_cast(widget)) { - out = iitem; + if (dtk::contains(iitem->getSelectionRect(), pos)) + { + out = iitem; + } } for (const auto& child : widget->getChildren()) { if (dtk::contains(child->getGeometry(), pos)) { - out = _select(child, pos); - break; + _select(child, pos, out); } } - return out; } void TimelineItem::_select( const std::shared_ptr& widget, - const std::vector >& selection) + const std::vector& selection) { if (auto iitem = std::dynamic_pointer_cast(widget)) { - const auto i = std::find(selection.begin(), selection.end(), iitem->getItem()); + auto object = iitem->getObject(); + const auto i = std::find_if( + selection.begin(), + selection.end(), + [object](const SelectionItem& item) + { + return object.value == item.object.value; + }); iitem->setSelected(i != selection.end()); } for (const auto& child : widget->getChildren()) diff --git a/lib/toucanView/TimelineItem.h b/lib/toucanView/TimelineItem.h index b5664b2..f5f0f75 100644 --- a/lib/toucanView/TimelineItem.h +++ b/lib/toucanView/TimelineItem.h @@ -3,9 +3,9 @@ #pragma once -#include "IItem.h" -#include "ThumbnailGenerator.h" -#include "TimeUnitsModel.h" +#include +#include +#include #include #include @@ -59,6 +59,9 @@ namespace toucan void mousePressEvent(dtk::MouseClickEvent&) override; void mouseReleaseEvent(dtk::MouseClickEvent&) override; + protected: + void _timeUnitsUpdate() override; + private: dtk::Size2I _getLabelMaxSize( const std::shared_ptr&) const; @@ -73,19 +76,19 @@ namespace toucan const dtk::Box2I&, const dtk::DrawEvent&); - std::shared_ptr _select( + void _select( const std::shared_ptr&, - const dtk::V2I&); + const dtk::V2I&, + std::shared_ptr&); void _select( const std::shared_ptr&, - const std::vector >&); + const std::vector&); OTIO_NS::SerializableObject::Retainer _timeline; OTIO_NS::TimeRange _timeRange; OTIO_NS::RationalTime _currentTime = OTIO_NS::RationalTime(-1.0, -1.0); std::function _currentTimeCallback; OTIO_NS::TimeRange _inOutRange; - std::shared_ptr _timeUnitsModel; std::shared_ptr _selectionModel; std::shared_ptr _thumbnailGenerator; dtk::LRUCache > _thumbnails; @@ -117,7 +120,6 @@ namespace toucan }; MouseData _mouse; - std::shared_ptr > _timeUnitsObserver; - std::shared_ptr > > _selectionObserver; + std::shared_ptr > _selectionObserver; }; } diff --git a/lib/toucanView/TimelineWidget.cpp b/lib/toucanView/TimelineWidget.cpp index 8bd3ad9..8f18dc9 100644 --- a/lib/toucanView/TimelineWidget.cpp +++ b/lib/toucanView/TimelineWidget.cpp @@ -26,14 +26,13 @@ namespace toucan _setMousePressEnabled( true, 0, - static_cast(dtk::KeyModifier::Control)); + static_cast(dtk::KeyModifier::Alt)); _frameView = dtk::ObservableValue::create(true); _scrollWidget = dtk::ScrollWidget::create(context, dtk::ScrollType::Both, shared_from_this()); _scrollWidget->setScrollEventsEnabled(false); _scrollWidget->setBorder(false); - _scrollWidget->setScrollBarsVisible(false); auto appWeak = std::weak_ptr(app); _fileObserver = dtk::ValueObserver >::create( @@ -126,7 +125,9 @@ namespace toucan void TimelineWidget::frameView() { - _scrollWidget->setScrollPos(dtk::V2I()); + dtk::V2I pos = _scrollWidget->getScrollPos(); + pos.x = 0; + _scrollWidget->setScrollPos(pos); _scale = _getTimelineScale(); if (_timelineItem) { @@ -201,7 +202,7 @@ namespace toucan { IWidget::mousePressEvent(event); if (0 == event.button && - static_cast(dtk::KeyModifier::Control) == event.modifiers) + static_cast(dtk::KeyModifier::Alt) == event.modifiers) { event.accept = true; takeKeyFocus(); diff --git a/lib/toucanView/TimelineWidget.h b/lib/toucanView/TimelineWidget.h index de9c9b3..9bc0b26 100644 --- a/lib/toucanView/TimelineWidget.h +++ b/lib/toucanView/TimelineWidget.h @@ -3,7 +3,7 @@ #pragma once -#include "TimeUnitsModel.h" +#include #include #include diff --git a/lib/toucanView/ToolBar.cpp b/lib/toucanView/ToolBar.cpp index 8b2fe36..2dac3bf 100644 --- a/lib/toucanView/ToolBar.cpp +++ b/lib/toucanView/ToolBar.cpp @@ -111,7 +111,7 @@ namespace toucan }); _buttons["Window/FullScreen"] = button; - i = actions.find("Window/ToolsPanel"); + i = actions.find("Window/Tools"); button = dtk::ToolButton::create(context, hLayout); button->setIcon(i->second->icon); button->setCheckable(true); @@ -124,9 +124,9 @@ namespace toucan i->second->checkedCallback(value); } }); - _buttons["Window/ToolsPanel"] = button; + _buttons["Window/Tools"] = button; - i = actions.find("Window/PlaybackPanel"); + i = actions.find("Window/Playback"); button = dtk::ToolButton::create(context, hLayout); button->setIcon(i->second->icon); button->setCheckable(true); @@ -139,7 +139,7 @@ namespace toucan i->second->checkedCallback(value); } }); - _buttons["Window/PlaybackPanel"] = button; + _buttons["Window/Playback"] = button; _widgetUpdate(); @@ -170,10 +170,10 @@ namespace toucan app->getWindowModel()->observeComponents(), [this](const std::map value) { - auto i = value.find(WindowComponent::ToolsPanel); - _buttons["Window/ToolsPanel"]->setChecked(i != value.end() ? i->second : false); - i = value.find(WindowComponent::PlaybackPanel); - _buttons["Window/PlaybackPanel"]->setChecked(i != value.end() ? i->second : false); + auto i = value.find(WindowComponent::Tools); + _buttons["Window/Tools"]->setChecked(i != value.end() ? i->second : false); + i = value.find(WindowComponent::Playback); + _buttons["Window/Playback"]->setChecked(i != value.end() ? i->second : false); }); } diff --git a/lib/toucanView/ToolBar.h b/lib/toucanView/ToolBar.h index dd151b5..e728f46 100644 --- a/lib/toucanView/ToolBar.h +++ b/lib/toucanView/ToolBar.h @@ -3,7 +3,7 @@ #pragma once -#include "WindowModel.h" +#include #include #include diff --git a/lib/toucanView/TrackItem.cpp b/lib/toucanView/TrackItem.cpp index 1adba6b..b56d782 100644 --- a/lib/toucanView/TrackItem.cpp +++ b/lib/toucanView/TrackItem.cpp @@ -15,14 +15,23 @@ namespace toucan const std::shared_ptr& context, const std::shared_ptr& app, const OTIO_NS::SerializableObject::Retainer& track, + const OTIO_NS::SerializableObject::Retainer& timeline, const std::shared_ptr& parent) { - auto opt = track->trimmed_range_in_parent(); + OTIO_NS::TimeRange timeRange = track->transformed_time_range( + track->trimmed_range(), + timeline->tracks()); + if (timeline->global_start_time().has_value()) + { + timeRange = OTIO_NS::TimeRange( + timeline->global_start_time().value() + timeRange.start_time(), + timeRange.duration()); + } IItem::_init( context, app, - OTIO_NS::dynamic_retainer_cast(track), - opt.has_value() ? opt.value() : OTIO_NS::TimeRange(), + OTIO_NS::dynamic_retainer_cast(track), + timeRange, "toucan::TrackItem", parent); @@ -32,8 +41,40 @@ namespace toucan dtk::Color4F(.2F, .2F, .3F) : dtk::Color4F(.2F, .3F, .2F); - setTooltip(_text); + setTooltip(track->schema_name() + ": " + _text); + + _layout = dtk::VerticalLayout::create(context, shared_from_this()); + _layout->setSpacingRole(dtk::SizeRole::SpacingTool); + + _label = ItemLabel::create(context, _layout); + _label->setName(_text); + + const auto& markers = track->markers(); + if (!markers.empty()) + { + _markerLayout = TimeLayout::create(context, timeRange, _layout); + for (const auto& marker : markers) + { + OTIO_NS::TimeRange markerTimeRange = track->transformed_time_range( + marker->marked_range(), + timeline->tracks()); + if (timeline->global_start_time().has_value()) + { + markerTimeRange = OTIO_NS::TimeRange( + timeline->global_start_time().value() + markerTimeRange.start_time(), + markerTimeRange.duration()); + } + auto markerItem = MarkerItem::create( + context, + app, + marker, + markerTimeRange, + _markerLayout); + _markerItems.push_back(markerItem); + } + } + _timeLayout = TimeLayout::create(context, timeRange, _layout); for (const auto& child : track->children()) { if (auto clip = OTIO_NS::dynamic_retainer_cast(child)) @@ -42,13 +83,15 @@ namespace toucan OTIO_NS::Track::Kind::video == track->kind() ? dtk::Color4F(.4F, .4F, .6F) : dtk::Color4F(.4F, .6F, .4F); - ClipItem::create(context, app, clip, color, shared_from_this()); + ClipItem::create(context, app, clip, timeline , color, _timeLayout); } else if (auto gap = OTIO_NS::dynamic_retainer_cast(child)) { - GapItem::create( context, app, gap, shared_from_this()); + GapItem::create( context, app, gap, timeline, _timeLayout); } } + + _textUpdate(); } TrackItem::~TrackItem() @@ -58,29 +101,36 @@ namespace toucan const std::shared_ptr& context, const std::shared_ptr& app, const OTIO_NS::SerializableObject::Retainer& track, + const OTIO_NS::SerializableObject::Retainer& timeline, const std::shared_ptr& parent) { auto out = std::make_shared(); - out->_init(context, app, track, parent); + out->_init(context, app, track, timeline, parent); return out; } - void TrackItem::setGeometry(const dtk::Box2I& value) + void TrackItem::setScale(double value) { - IItem::setGeometry(value); - const dtk::Box2I& g = getGeometry(); - const int y = g.min.y + _size.fontMetrics.lineHeight + _size.margin * 2 + _size.border * 2; - for (const auto& child : getChildren()) + IItem::setScale(value); + if (_markerLayout) { - if (auto item = std::dynamic_pointer_cast(child)) - { - const OTIO_NS::TimeRange& timeRange = item->getTimeRange(); - const int x0 = timeToPos(timeRange.start_time()); - const int x1 = timeToPos(timeRange.end_time_exclusive()); - const dtk::Size2I& sizeHint = child->getSizeHint(); - child->setGeometry(dtk::Box2I(x0, y, x1 - x0 + 1, sizeHint.h)); - } + _markerLayout->setScale(value); } + _timeLayout->setScale(value); + } + + void TrackItem::setGeometry(const dtk::Box2I& value) + { + IItem::setGeometry(value); + _layout->setGeometry(value); + _geom.g2 = dtk::margin(value, -_size.border, 0, -_size.border, 0); + _geom.g3 = dtk::margin(_label->getGeometry(), -_size.border, 0, -_size.border, 0); + _selectionRect = _geom.g3; + } + + dtk::Box2I TrackItem::getChildrenClipRect() const + { + return _geom.g2; } void TrackItem::sizeHintEvent(const dtk::SizeHintEvent& event) @@ -91,32 +141,9 @@ namespace toucan { _size.init = false; _size.displayScale = event.displayScale; - _size.margin = event.style->getSizeRole(dtk::SizeRole::MarginInside, event.displayScale); _size.border = event.style->getSizeRole(dtk::SizeRole::Border, event.displayScale); - _size.fontInfo = event.style->getFontRole(dtk::FontRole::Label, event.displayScale); - _size.fontMetrics = event.fontSystem->getMetrics(_size.fontInfo); - _size.textSize = event.fontSystem->getSize(_text, _size.fontInfo); - _draw.glyphs.clear(); - } - dtk::Size2I sizeHint( - _timeRange.duration().rescaled_to(1.0).value() * _scale, - 0); - for (const auto& child : getChildren()) - { - const dtk::Size2I& childSizeHint = child->getSizeHint(); - sizeHint.h = std::max(sizeHint.h, childSizeHint.h); - } - sizeHint.h += _size.textSize.h + _size.margin * 2 + _size.border * 4; - _setSizeHint(sizeHint); - } - - void TrackItem::clipEvent(const dtk::Box2I& clipRect, bool clipped) - { - IItem::clipEvent(clipRect, clipped); - if (clipped) - { - _draw.glyphs.clear(); } + _setSizeHint(_layout->getSizeHint()); } void TrackItem::drawEvent( @@ -124,30 +151,22 @@ namespace toucan const dtk::DrawEvent& event) { IItem::drawEvent(drawRect, event); - const dtk::Box2I& g = getGeometry(); - - const dtk::Box2I g2( - g.min.x + _size.border, - g.min.y, - g.w() - _size.border * 2, - _size.fontMetrics.lineHeight + _size.margin * 2); event.render->drawRect( - g2, + _geom.g3, _selected ? event.style->getColorRole(dtk::ColorRole::Yellow) : _color); + } - const dtk::Box2I g3 = dtk::margin(g2, -_size.margin); - if (!_text.empty() && _draw.glyphs.empty()) + void TrackItem::_timeUnitsUpdate() + { + _textUpdate(); + } + + void TrackItem::_textUpdate() + { + if (_label) { - _draw.glyphs = event.fontSystem->getGlyphs(_text, _size.fontInfo); + std::string text = toString(_timeRange.duration(), _timeUnits); + _label->setDuration(text); } - dtk::ClipRectEnabledState clipRectEnabledState(event.render); - dtk::ClipRectState clipRectState(event.render); - event.render->setClipRectEnabled(true); - event.render->setClipRect(intersect(g3, drawRect)); - event.render->drawText( - _draw.glyphs, - _size.fontMetrics, - g3.min, - event.style->getColorRole(dtk::ColorRole::Text)); } } diff --git a/lib/toucanView/TrackItem.h b/lib/toucanView/TrackItem.h index ecdbfff..232aabd 100644 --- a/lib/toucanView/TrackItem.h +++ b/lib/toucanView/TrackItem.h @@ -3,7 +3,9 @@ #pragma once -#include "IItem.h" +#include + +#include #include @@ -17,6 +19,7 @@ namespace toucan const std::shared_ptr&, const std::shared_ptr&, const OTIO_NS::SerializableObject::Retainer&, + const OTIO_NS::SerializableObject::Retainer&, const std::shared_ptr& parent); public: @@ -27,34 +30,45 @@ namespace toucan const std::shared_ptr&, const std::shared_ptr&, const OTIO_NS::SerializableObject::Retainer&, + const OTIO_NS::SerializableObject::Retainer&, const std::shared_ptr& parent = nullptr); + void setScale(double) override; + void setGeometry(const dtk::Box2I&) override; + dtk::Box2I getChildrenClipRect() const override; void sizeHintEvent(const dtk::SizeHintEvent&) override; - void clipEvent(const dtk::Box2I&, bool) override; void drawEvent(const dtk::Box2I&, const dtk::DrawEvent&) override; + protected: + void _timeUnitsUpdate() override; + private: + void _textUpdate(); + OTIO_NS::SerializableObject::Retainer _track; std::string _text; dtk::Color4F _color; + std::shared_ptr _layout; + std::shared_ptr _label; + std::shared_ptr _markerLayout; + std::vector > _markerItems; + std::shared_ptr _timeLayout; + struct SizeData { bool init = true; float displayScale = 0.F; - int margin = 0; int border = 0; - dtk::FontInfo fontInfo; - dtk::FontMetrics fontMetrics; - dtk::Size2I textSize; }; SizeData _size; - struct DrawData + struct GeomData { - std::vector > glyphs; + dtk::Box2I g2; + dtk::Box2I g3; }; - DrawData _draw; + GeomData _geom; }; } diff --git a/lib/toucanView/WindowModel.cpp b/lib/toucanView/WindowModel.cpp index 903495b..50014a6 100644 --- a/lib/toucanView/WindowModel.cpp +++ b/lib/toucanView/WindowModel.cpp @@ -16,8 +16,8 @@ namespace toucan DTK_ENUM_IMPL( WindowComponent, "ToolBar", - "ToolsPanel", - "PlaybackPanel"); + "Tools", + "Playback"); WindowModel::WindowModel(const std::shared_ptr& context) { @@ -26,8 +26,8 @@ namespace toucan std::map components = { { WindowComponent::ToolBar, true }, - { WindowComponent::ToolsPanel, true }, - { WindowComponent::PlaybackPanel, true } + { WindowComponent::Tools, true }, + { WindowComponent::Playback, true } }; bool tooltips = true; try diff --git a/lib/toucanView/WindowModel.h b/lib/toucanView/WindowModel.h index 2263cc1..babbe0d 100644 --- a/lib/toucanView/WindowModel.h +++ b/lib/toucanView/WindowModel.h @@ -13,8 +13,8 @@ namespace toucan enum class WindowComponent { ToolBar, - ToolsPanel, - PlaybackPanel, + Tools, + Playback, Count, First = ToolBar diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 5788e58..033af43 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -1,19 +1,32 @@ -foreach(PLUGIN ColorSpace Draw Filter Generator Transform Transition) +add_library(toucanPlugin Plugin.h Util.h Plugin.cpp Util.cpp) +set(LIBS_PUBLIC OpenImageIO::OpenImageIO MINIZIP::minizip) +if(CMAKE_COMPILER_IS_GNUCXX AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS 9) + list(APPEND LIBS_PUBLIC stdc++fs) +endif() +target_link_libraries(toucanPlugin PUBLIC ${LIBS_PUBLIC}) +set_target_properties(toucanPlugin PROPERTIES FOLDER plugin) +set_target_properties(toucanPlugin PROPERTIES PUBLIC_HEADER "${HEADERS}") + +install( + TARGETS toucanPlugin + ARCHIVE DESTINATION lib + LIBRARY DESTINATION lib + RUNTIME DESTINATION bin + PUBLIC_HEADER DESTINATION include/toucan) + +foreach(PLUGIN Color Draw Filter Generator Transform Transition) + set(PLUGIN_NAME toucan${PLUGIN}Plugin) add_library( - toucan${PLUGIN} MODULE - ${PLUGIN}Plugin.h Plugin.h Util.h - ${PLUGIN}Plugin.cpp Plugin.cpp Util.cpp) - set(LIBS_PUBLIC OpenImageIO::OpenImageIO MINIZIP::minizip) - if(CMAKE_COMPILER_IS_GNUCXX AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS 9) - list(APPEND LIBS_PUBLIC stdc++fs) - endif() - target_link_libraries(toucan${PLUGIN} PUBLIC ${LIBS_PUBLIC}) + ${PLUGIN_NAME} MODULE + ${PLUGIN}Plugin.h + ${PLUGIN}Plugin.cpp ) + target_link_libraries(${PLUGIN_NAME} PUBLIC toucanPlugin) set_target_properties( - toucan${PLUGIN} PROPERTIES + ${PLUGIN_NAME} PROPERTIES FOLDER plugin SUFFIX .ofx) - install(TARGETS toucan${PLUGIN} + install(TARGETS ${PLUGIN_NAME} ARCHIVE DESTINATION lib LIBRARY DESTINATION bin RUNTIME DESTINATION bin) diff --git a/plugins/ColorSpacePlugin.cpp b/plugins/ColorPlugin.cpp similarity index 94% rename from plugins/ColorSpacePlugin.cpp rename to plugins/ColorPlugin.cpp index e8b3778..1c10158 100644 --- a/plugins/ColorSpacePlugin.cpp +++ b/plugins/ColorPlugin.cpp @@ -1,20 +1,20 @@ // SPDX-License-Identifier: Apache-2.0 // Copyright Contributors to the toucan project. -#include "ColorSpacePlugin.h" +#include "ColorPlugin.h" #include "Util.h" #include -ColorSpacePlugin::ColorSpacePlugin(const std::string& group, const std::string& name) : +ColorPlugin::ColorPlugin(const std::string& group, const std::string& name) : Plugin(group, name) {} -ColorSpacePlugin::~ColorSpacePlugin() +ColorPlugin::~ColorPlugin() {} -OfxStatus ColorSpacePlugin::_describeAction(OfxImageEffectHandle handle) +OfxStatus ColorPlugin::_describeAction(OfxImageEffectHandle handle) { Plugin::_describeAction(handle); @@ -29,7 +29,7 @@ OfxStatus ColorSpacePlugin::_describeAction(OfxImageEffectHandle handle) return kOfxStatOK; } -OfxStatus ColorSpacePlugin::_describeInContextAction(OfxImageEffectHandle handle, OfxPropertySetHandle inArgs) +OfxStatus ColorPlugin::_describeInContextAction(OfxImageEffectHandle handle, OfxPropertySetHandle inArgs) { Plugin::_describeInContextAction(handle, inArgs); @@ -79,7 +79,7 @@ OfxStatus ColorSpacePlugin::_describeInContextAction(OfxImageEffectHandle handle return kOfxStatOK; } -OfxStatus ColorSpacePlugin::_renderAction( +OfxStatus ColorPlugin::_renderAction( OfxImageEffectHandle handle , OfxPropertySetHandle inArgs, OfxPropertySetHandle outArgs) @@ -121,7 +121,7 @@ OfxStatus ColorSpacePlugin::_renderAction( ColorConvertPlugin* ColorConvertPlugin::_plugin = nullptr; ColorConvertPlugin::ColorConvertPlugin() : - ColorSpacePlugin("toucan", "ColorConvert") + ColorPlugin("toucan", "ColorConvert") {} ColorConvertPlugin::~ColorConvertPlugin() @@ -149,7 +149,7 @@ OfxStatus ColorConvertPlugin::_describeInContextAction( OfxImageEffectHandle handle, OfxPropertySetHandle inArgs) { - ColorSpacePlugin::_describeInContextAction(handle, inArgs); + ColorPlugin::_describeInContextAction(handle, inArgs); OfxParamSetHandle paramSet; _effectSuite->getParamSet(handle, ¶mSet); @@ -183,7 +183,7 @@ OfxStatus ColorConvertPlugin::_describeInContextAction( OfxStatus ColorConvertPlugin::_createInstance(OfxImageEffectHandle handle) { - ColorSpacePlugin::_createInstance(handle); + ColorPlugin::_createInstance(handle); OfxParamSetHandle paramSet; _effectSuite->getParamSet(handle, ¶mSet); @@ -252,7 +252,7 @@ OfxStatus ColorConvertPlugin::_render( PremultPlugin* PremultPlugin::_plugin = nullptr; PremultPlugin::PremultPlugin() : - ColorSpacePlugin("toucan", "Premult") + ColorPlugin("toucan", "Premult") {} PremultPlugin::~PremultPlugin() @@ -290,7 +290,7 @@ OfxStatus PremultPlugin::_render( UnpremultPlugin* UnpremultPlugin::_plugin = nullptr; UnpremultPlugin::UnpremultPlugin() : - ColorSpacePlugin("toucan", "Unpremult") + ColorPlugin("toucan", "Unpremult") {} UnpremultPlugin::~UnpremultPlugin() diff --git a/plugins/ColorSpacePlugin.h b/plugins/ColorPlugin.h similarity index 91% rename from plugins/ColorSpacePlugin.h rename to plugins/ColorPlugin.h index 2c5dd5f..ccb2126 100644 --- a/plugins/ColorSpacePlugin.h +++ b/plugins/ColorPlugin.h @@ -8,12 +8,12 @@ #include #include -class ColorSpacePlugin : public Plugin +class ColorPlugin : public Plugin { public: - ColorSpacePlugin(const std::string& group, const std::string& name); + ColorPlugin(const std::string& group, const std::string& name); - virtual ~ColorSpacePlugin() = 0; + virtual ~ColorPlugin() = 0; protected: virtual OfxStatus _render( @@ -33,7 +33,7 @@ class ColorSpacePlugin : public Plugin OfxPropertySetHandle outArgs) override; }; -class ColorConvertPlugin : public ColorSpacePlugin +class ColorConvertPlugin : public ColorPlugin { public: ColorConvertPlugin(); @@ -71,7 +71,7 @@ class ColorConvertPlugin : public ColorSpacePlugin std::map > _colorConfigs; }; -class PremultPlugin : public ColorSpacePlugin +class PremultPlugin : public ColorPlugin { public: PremultPlugin(); @@ -98,7 +98,7 @@ class PremultPlugin : public ColorSpacePlugin static PremultPlugin* _plugin; }; -class UnpremultPlugin : public ColorSpacePlugin +class UnpremultPlugin : public ColorPlugin { public: UnpremultPlugin(); diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 9f39a36..e14e5e8 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,2 +1,2 @@ -add_subdirectory(toucanTest) +add_subdirectory(toucanRenderTest) add_subdirectory(toucan-test) diff --git a/tests/toucan-test/CMakeLists.txt b/tests/toucan-test/CMakeLists.txt index d4c3344..a5787dc 100644 --- a/tests/toucan-test/CMakeLists.txt +++ b/tests/toucan-test/CMakeLists.txt @@ -1,7 +1,5 @@ -set(LIBS toucanTest) - add_executable(toucan-test main.cpp) -target_link_libraries(toucan-test ${LIBS}) +target_link_libraries(toucan-test toucanRenderTest) set_target_properties(toucan-test PROPERTIES FOLDER tests) add_dependencies(toucan-test ${TOUCAN_PLUGINS}) diff --git a/tests/toucan-test/main.cpp b/tests/toucan-test/main.cpp index dbef4a9..1718c01 100644 --- a/tests/toucan-test/main.cpp +++ b/tests/toucan-test/main.cpp @@ -5,13 +5,13 @@ #include #endif // toucan_EDIT -#include -#include -#include -#include -#include +#include +#include +#include +#include -#include +#include +#include #include @@ -26,6 +26,9 @@ int main(int argc, char** argv) } const std::filesystem::path parentPath = std::filesystem::path(argv[0]).parent_path(); const std::filesystem::path path(argv[1]); + + auto context = dtk::Context::create(); + dtk::coreInit(context); std::vector searchPath; searchPath.push_back(parentPath); @@ -34,13 +37,12 @@ int main(int argc, char** argv) #else // _WINDOWS searchPath.push_back(parentPath / ".." / ".."); #endif // _WINDOWS - auto host = std::make_shared(searchPath); - + auto host = std::make_shared(context, searchPath); + compTest(path); propertySetTest(); readTest(path); - imageGraphTest(path, host); - utilTest(path); + imageGraphTest(context, path, host); #if defined(toucan_EDIT) stackTest(); diff --git a/tests/toucanRenderTest/CMakeLists.txt b/tests/toucanRenderTest/CMakeLists.txt new file mode 100644 index 0000000..5b13a88 --- /dev/null +++ b/tests/toucanRenderTest/CMakeLists.txt @@ -0,0 +1,15 @@ +set(HEADERS + CompTest.h + ImageGraphTest.h + PropertySetTest.h + ReadTest.h) + +set(SOURCE + CompTest.cpp + ImageGraphTest.cpp + PropertySetTest.cpp + ReadTest.cpp) + +add_library(toucanRenderTest ${SOURCE} ${HEADERS}) +target_link_libraries(toucanRenderTest toucanRender) +set_target_properties(toucanRenderTest PROPERTIES FOLDER tests) diff --git a/tests/toucanTest/CompTest.cpp b/tests/toucanRenderTest/CompTest.cpp similarity index 90% rename from tests/toucanTest/CompTest.cpp rename to tests/toucanRenderTest/CompTest.cpp index 021f68b..9f336c6 100644 --- a/tests/toucanTest/CompTest.cpp +++ b/tests/toucanRenderTest/CompTest.cpp @@ -3,8 +3,8 @@ #include "CompTest.h" -#include -#include +#include +#include namespace toucan { diff --git a/tests/toucanTest/CompTest.h b/tests/toucanRenderTest/CompTest.h similarity index 100% rename from tests/toucanTest/CompTest.h rename to tests/toucanRenderTest/CompTest.h diff --git a/tests/toucanTest/ImageGraphTest.cpp b/tests/toucanRenderTest/ImageGraphTest.cpp similarity index 90% rename from tests/toucanTest/ImageGraphTest.cpp rename to tests/toucanRenderTest/ImageGraphTest.cpp index 654d749..0dccb8a 100644 --- a/tests/toucanTest/ImageGraphTest.cpp +++ b/tests/toucanRenderTest/ImageGraphTest.cpp @@ -3,15 +3,16 @@ #include "ImageGraphTest.h" -#include -#include -#include +#include +#include +#include #include namespace toucan { void imageGraphTest( + const std::shared_ptr& context, const std::filesystem::path& path, const std::shared_ptr& host) { @@ -51,7 +52,7 @@ namespace toucan const OTIO_NS::RationalTime timeInc(1.0, timeRange.duration().rate()); // Render the timeline frames. - const auto graph = std::make_shared(path, timelineWrapper); + const auto graph = std::make_shared(context, path, timelineWrapper); for (OTIO_NS::RationalTime time = timeRange.start_time(); time <= timeRange.end_time_inclusive(); time += timeInc) diff --git a/tests/toucanTest/ImageGraphTest.h b/tests/toucanRenderTest/ImageGraphTest.h similarity index 73% rename from tests/toucanTest/ImageGraphTest.h rename to tests/toucanRenderTest/ImageGraphTest.h index a8d48fd..7be255c 100644 --- a/tests/toucanTest/ImageGraphTest.h +++ b/tests/toucanRenderTest/ImageGraphTest.h @@ -3,11 +3,12 @@ #pragma once -#include +#include namespace toucan { void imageGraphTest( + const std::shared_ptr&, const std::filesystem::path&, const std::shared_ptr&); } diff --git a/tests/toucanTest/PropertySetTest.cpp b/tests/toucanRenderTest/PropertySetTest.cpp similarity index 98% rename from tests/toucanTest/PropertySetTest.cpp rename to tests/toucanRenderTest/PropertySetTest.cpp index 6122499..6995a68 100644 --- a/tests/toucanTest/PropertySetTest.cpp +++ b/tests/toucanRenderTest/PropertySetTest.cpp @@ -3,7 +3,7 @@ #include "PropertySetTest.h" -#include +#include #include diff --git a/tests/toucanTest/PropertySetTest.h b/tests/toucanRenderTest/PropertySetTest.h similarity index 100% rename from tests/toucanTest/PropertySetTest.h rename to tests/toucanRenderTest/PropertySetTest.h diff --git a/tests/toucanTest/ReadTest.cpp b/tests/toucanRenderTest/ReadTest.cpp similarity index 93% rename from tests/toucanTest/ReadTest.cpp rename to tests/toucanRenderTest/ReadTest.cpp index 5b96c5b..b5c6f56 100644 --- a/tests/toucanTest/ReadTest.cpp +++ b/tests/toucanRenderTest/ReadTest.cpp @@ -3,7 +3,7 @@ #include "ReadTest.h" -#include +#include #include diff --git a/tests/toucanTest/ReadTest.h b/tests/toucanRenderTest/ReadTest.h similarity index 100% rename from tests/toucanTest/ReadTest.h rename to tests/toucanRenderTest/ReadTest.h diff --git a/tests/toucanTest/CMakeLists.txt b/tests/toucanTest/CMakeLists.txt deleted file mode 100644 index 1c01332..0000000 --- a/tests/toucanTest/CMakeLists.txt +++ /dev/null @@ -1,17 +0,0 @@ -set(HEADERS - CompTest.h - ImageGraphTest.h - PropertySetTest.h - ReadTest.h - UtilTest.h) - -set(SOURCE - CompTest.cpp - ImageGraphTest.cpp - PropertySetTest.cpp - ReadTest.cpp - UtilTest.cpp) - -add_library(toucanTest ${SOURCE} ${HEADERS}) -target_link_libraries(toucanTest toucan) -set_target_properties(toucanTest PROPERTIES FOLDER tests) diff --git a/tests/toucanTest/UtilTest.cpp b/tests/toucanTest/UtilTest.cpp deleted file mode 100644 index d87da33..0000000 --- a/tests/toucanTest/UtilTest.cpp +++ /dev/null @@ -1,47 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright Contributors to the toucan project. - -#include "UtilTest.h" - -#include - -#include - -namespace toucan -{ - void utilTest(const std::filesystem::path& path) - { - { - const auto split = splitFileNameNumber(""); - assert(split.first.empty()); - assert(split.second.empty()); - } - { - const auto split = splitFileNameNumber("0000"); - assert(split.first.empty()); - assert("0000" == split.second); - } - { - const auto split = splitFileNameNumber("render0000"); - assert("render" == split.first); - assert("0000" == split.second); - } - { - const auto split = splitFileNameNumber("render.0000"); - assert("render." == split.first); - assert("0000" == split.second); - } - { - const int padding = getNumberPadding(""); - assert(0 == padding); - } - { - const int padding = getNumberPadding("1234"); - assert(0 == padding); - } - { - const int padding = getNumberPadding("0001"); - assert(4 == padding); - } - } -} diff --git a/tests/toucanTest/UtilTest.h b/tests/toucanTest/UtilTest.h deleted file mode 100644 index 7730598..0000000 --- a/tests/toucanTest/UtilTest.h +++ /dev/null @@ -1,11 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright Contributors to the toucan project. - -#pragma once - -#include - -namespace toucan -{ - void utilTest(const std::filesystem::path&); -} diff --git a/toucanConfig.cmake.in b/toucanConfig.cmake.in index 380e9b3..4e2ff78 100644 --- a/toucanConfig.cmake.in +++ b/toucanConfig.cmake.in @@ -17,10 +17,12 @@ find_dependency(OpenImageIO) find_dependency(OTIO) find_dependency(OpenFX) -include("${CMAKE_CURRENT_LIST_DIR}/toucanTargets.cmake") +include("${CMAKE_CURRENT_LIST_DIR}/toucanCoreTargets.cmake") +include("${CMAKE_CURRENT_LIST_DIR}/toucanRenderTargets.cmake") # \todo Is this the correct way to add the include directory? include_directories("@PACKAGE_INCLUDE_INSTALL_DIR@") include_directories("@PACKAGE_INCLUDE_INSTALL_DIR@/../Imath") -check_required_components(toucan) +check_required_components(toucanCore) +check_required_components(toucanRender)