| 887 | } |
| 888 | |
| 889 | FramesSpecificationConstPtr Assets::bestFramesSpecification(String const& image) const { |
| 890 | if (auto framesSpecification = m_framesSpecifications.maybe(image)) { |
| 891 | return *framesSpecification; |
| 892 | } |
| 893 | |
| 894 | String framesFile; |
| 895 | |
| 896 | if (auto bestFramesFile = m_bestFramesFiles.maybe(image)) { |
| 897 | framesFile = *bestFramesFile; |
| 898 | |
| 899 | } else { |
| 900 | String searchPath = AssetPath::directory(image); |
| 901 | String filePrefix = AssetPath::filename(image); |
| 902 | filePrefix = filePrefix.substr(0, filePrefix.findLast('.')); |
| 903 | |
| 904 | auto subdir = [](String const& dir) -> String { |
| 905 | auto dirsplit = dir.substr(0, dir.size() - 1).rsplit("/", 1); |
| 906 | if (dirsplit.size() < 2) |
| 907 | return ""; |
| 908 | else |
| 909 | return dirsplit[0] + "/"; |
| 910 | }; |
| 911 | |
| 912 | Maybe<String> foundFramesFile; |
| 913 | |
| 914 | // look for <full-path-minus-extension>.frames or default.frames up to root |
| 915 | while (!searchPath.empty()) { |
| 916 | String framesPath = searchPath + filePrefix + ".frames"; |
| 917 | if (m_files.contains(framesPath)) { |
| 918 | foundFramesFile = framesPath; |
| 919 | break; |
| 920 | } |
| 921 | |
| 922 | framesPath = searchPath + "default.frames"; |
| 923 | if (m_files.contains(framesPath)) { |
| 924 | foundFramesFile = framesPath; |
| 925 | break; |
| 926 | } |
| 927 | |
| 928 | searchPath = subdir(searchPath); |
| 929 | } |
| 930 | |
| 931 | if (foundFramesFile) { |
| 932 | framesFile = foundFramesFile.take(); |
| 933 | m_bestFramesFiles[image] = framesFile; |
| 934 | |
| 935 | } else { |
| 936 | return {}; |
| 937 | } |
| 938 | } |
| 939 | |
| 940 | auto framesSpecification = unlockDuring([&]() { |
| 941 | return make_shared<FramesSpecification>(parseFramesSpecification(readJson(framesFile), framesFile)); |
| 942 | }); |
| 943 | m_framesSpecifications[image] = framesSpecification; |
| 944 | |
| 945 | return framesSpecification; |
| 946 | } |