diff --git a/examples/DemoRunner/Builds/Android/app/CMakeLists.txt b/examples/DemoRunner/Builds/Android/app/CMakeLists.txt index c6e45fc783..bd02caedf4 100644 --- a/examples/DemoRunner/Builds/Android/app/CMakeLists.txt +++ b/examples/DemoRunner/Builds/Android/app/CMakeLists.txt @@ -1474,6 +1474,8 @@ add_library( ${BINARY_NAME} "../../../../../modules/juce_graphics/contexts/juce_LowLevelGraphicsPostScriptRenderer.h" "../../../../../modules/juce_graphics/contexts/juce_LowLevelGraphicsSoftwareRenderer.cpp" "../../../../../modules/juce_graphics/contexts/juce_LowLevelGraphicsSoftwareRenderer.h" + "../../../../../modules/juce_graphics/detail/juce_Ranges.cpp" + "../../../../../modules/juce_graphics/detail/juce_Ranges.h" "../../../../../modules/juce_graphics/effects/juce_DropShadowEffect.cpp" "../../../../../modules/juce_graphics/effects/juce_DropShadowEffect.h" "../../../../../modules/juce_graphics/effects/juce_GlowEffect.cpp" @@ -3939,6 +3941,8 @@ set_source_files_properties( "../../../../../modules/juce_graphics/contexts/juce_LowLevelGraphicsPostScriptRenderer.h" "../../../../../modules/juce_graphics/contexts/juce_LowLevelGraphicsSoftwareRenderer.cpp" "../../../../../modules/juce_graphics/contexts/juce_LowLevelGraphicsSoftwareRenderer.h" + "../../../../../modules/juce_graphics/detail/juce_Ranges.cpp" + "../../../../../modules/juce_graphics/detail/juce_Ranges.h" "../../../../../modules/juce_graphics/effects/juce_DropShadowEffect.cpp" "../../../../../modules/juce_graphics/effects/juce_DropShadowEffect.h" "../../../../../modules/juce_graphics/effects/juce_GlowEffect.cpp" diff --git a/examples/DemoRunner/Builds/VisualStudio2017/DemoRunner_App.vcxproj b/examples/DemoRunner/Builds/VisualStudio2017/DemoRunner_App.vcxproj index b8de083eaa..0f3ec2b1bd 100644 --- a/examples/DemoRunner/Builds/VisualStudio2017/DemoRunner_App.vcxproj +++ b/examples/DemoRunner/Builds/VisualStudio2017/DemoRunner_App.vcxproj @@ -1874,6 +1874,9 @@ true + + true + true @@ -3865,6 +3868,7 @@ + diff --git a/examples/DemoRunner/Builds/VisualStudio2017/DemoRunner_App.vcxproj.filters b/examples/DemoRunner/Builds/VisualStudio2017/DemoRunner_App.vcxproj.filters index f9c307fda5..2b57cf6a32 100644 --- a/examples/DemoRunner/Builds/VisualStudio2017/DemoRunner_App.vcxproj.filters +++ b/examples/DemoRunner/Builds/VisualStudio2017/DemoRunner_App.vcxproj.filters @@ -575,6 +575,9 @@ {20DC13F6-2369-8841-9F0B-D13FA14EEE74} + + {0B30279D-5CEF-3E12-EA90-7D6CE4D52669} + {A302A8DB-120F-9EBB-A3D5-2C29963AA56B} @@ -2617,6 +2620,9 @@ JUCE Modules\juce_graphics\contexts + + JUCE Modules\juce_graphics\detail + JUCE Modules\juce_graphics\effects @@ -6327,6 +6333,9 @@ JUCE Modules\juce_graphics\contexts + + JUCE Modules\juce_graphics\detail + JUCE Modules\juce_graphics\effects diff --git a/examples/DemoRunner/Builds/VisualStudio2019/DemoRunner_App.vcxproj b/examples/DemoRunner/Builds/VisualStudio2019/DemoRunner_App.vcxproj index d207bf8faa..3a854ab3d6 100644 --- a/examples/DemoRunner/Builds/VisualStudio2019/DemoRunner_App.vcxproj +++ b/examples/DemoRunner/Builds/VisualStudio2019/DemoRunner_App.vcxproj @@ -1874,6 +1874,9 @@ true + + true + true @@ -3865,6 +3868,7 @@ + diff --git a/examples/DemoRunner/Builds/VisualStudio2019/DemoRunner_App.vcxproj.filters b/examples/DemoRunner/Builds/VisualStudio2019/DemoRunner_App.vcxproj.filters index 61cba2da36..4f4e610ce8 100644 --- a/examples/DemoRunner/Builds/VisualStudio2019/DemoRunner_App.vcxproj.filters +++ b/examples/DemoRunner/Builds/VisualStudio2019/DemoRunner_App.vcxproj.filters @@ -575,6 +575,9 @@ {20DC13F6-2369-8841-9F0B-D13FA14EEE74} + + {0B30279D-5CEF-3E12-EA90-7D6CE4D52669} + {A302A8DB-120F-9EBB-A3D5-2C29963AA56B} @@ -2617,6 +2620,9 @@ JUCE Modules\juce_graphics\contexts + + JUCE Modules\juce_graphics\detail + JUCE Modules\juce_graphics\effects @@ -6327,6 +6333,9 @@ JUCE Modules\juce_graphics\contexts + + JUCE Modules\juce_graphics\detail + JUCE Modules\juce_graphics\effects diff --git a/examples/DemoRunner/Builds/VisualStudio2022/DemoRunner_App.vcxproj b/examples/DemoRunner/Builds/VisualStudio2022/DemoRunner_App.vcxproj index 828aa0a63b..edd7e938c0 100644 --- a/examples/DemoRunner/Builds/VisualStudio2022/DemoRunner_App.vcxproj +++ b/examples/DemoRunner/Builds/VisualStudio2022/DemoRunner_App.vcxproj @@ -1874,6 +1874,9 @@ true + + true + true @@ -3865,6 +3868,7 @@ + diff --git a/examples/DemoRunner/Builds/VisualStudio2022/DemoRunner_App.vcxproj.filters b/examples/DemoRunner/Builds/VisualStudio2022/DemoRunner_App.vcxproj.filters index baea853b94..5eefb125db 100644 --- a/examples/DemoRunner/Builds/VisualStudio2022/DemoRunner_App.vcxproj.filters +++ b/examples/DemoRunner/Builds/VisualStudio2022/DemoRunner_App.vcxproj.filters @@ -575,6 +575,9 @@ {20DC13F6-2369-8841-9F0B-D13FA14EEE74} + + {0B30279D-5CEF-3E12-EA90-7D6CE4D52669} + {A302A8DB-120F-9EBB-A3D5-2C29963AA56B} @@ -2617,6 +2620,9 @@ JUCE Modules\juce_graphics\contexts + + JUCE Modules\juce_graphics\detail + JUCE Modules\juce_graphics\effects @@ -6327,6 +6333,9 @@ JUCE Modules\juce_graphics\contexts + + JUCE Modules\juce_graphics\detail + JUCE Modules\juce_graphics\effects diff --git a/extras/AudioPerformanceTest/Builds/Android/app/CMakeLists.txt b/extras/AudioPerformanceTest/Builds/Android/app/CMakeLists.txt index 1706b15855..24ec153d78 100644 --- a/extras/AudioPerformanceTest/Builds/Android/app/CMakeLists.txt +++ b/extras/AudioPerformanceTest/Builds/Android/app/CMakeLists.txt @@ -1236,6 +1236,8 @@ add_library( ${BINARY_NAME} "../../../../../modules/juce_graphics/contexts/juce_LowLevelGraphicsPostScriptRenderer.h" "../../../../../modules/juce_graphics/contexts/juce_LowLevelGraphicsSoftwareRenderer.cpp" "../../../../../modules/juce_graphics/contexts/juce_LowLevelGraphicsSoftwareRenderer.h" + "../../../../../modules/juce_graphics/detail/juce_Ranges.cpp" + "../../../../../modules/juce_graphics/detail/juce_Ranges.h" "../../../../../modules/juce_graphics/effects/juce_DropShadowEffect.cpp" "../../../../../modules/juce_graphics/effects/juce_DropShadowEffect.h" "../../../../../modules/juce_graphics/effects/juce_GlowEffect.cpp" @@ -3383,6 +3385,8 @@ set_source_files_properties( "../../../../../modules/juce_graphics/contexts/juce_LowLevelGraphicsPostScriptRenderer.h" "../../../../../modules/juce_graphics/contexts/juce_LowLevelGraphicsSoftwareRenderer.cpp" "../../../../../modules/juce_graphics/contexts/juce_LowLevelGraphicsSoftwareRenderer.h" + "../../../../../modules/juce_graphics/detail/juce_Ranges.cpp" + "../../../../../modules/juce_graphics/detail/juce_Ranges.h" "../../../../../modules/juce_graphics/effects/juce_DropShadowEffect.cpp" "../../../../../modules/juce_graphics/effects/juce_DropShadowEffect.h" "../../../../../modules/juce_graphics/effects/juce_GlowEffect.cpp" diff --git a/extras/AudioPerformanceTest/Builds/VisualStudio2022/AudioPerformanceTest_App.vcxproj b/extras/AudioPerformanceTest/Builds/VisualStudio2022/AudioPerformanceTest_App.vcxproj index 8de28ff544..6c6b6171e0 100644 --- a/extras/AudioPerformanceTest/Builds/VisualStudio2022/AudioPerformanceTest_App.vcxproj +++ b/extras/AudioPerformanceTest/Builds/VisualStudio2022/AudioPerformanceTest_App.vcxproj @@ -1567,6 +1567,9 @@ true + + true + true @@ -3336,6 +3339,7 @@ + diff --git a/extras/AudioPerformanceTest/Builds/VisualStudio2022/AudioPerformanceTest_App.vcxproj.filters b/extras/AudioPerformanceTest/Builds/VisualStudio2022/AudioPerformanceTest_App.vcxproj.filters index 117daacffe..21d1d475f9 100644 --- a/extras/AudioPerformanceTest/Builds/VisualStudio2022/AudioPerformanceTest_App.vcxproj.filters +++ b/extras/AudioPerformanceTest/Builds/VisualStudio2022/AudioPerformanceTest_App.vcxproj.filters @@ -485,6 +485,9 @@ {20DC13F6-2369-8841-9F0B-D13FA14EEE74} + + {0B30279D-5CEF-3E12-EA90-7D6CE4D52669} + {A302A8DB-120F-9EBB-A3D5-2C29963AA56B} @@ -2161,6 +2164,9 @@ JUCE Modules\juce_graphics\contexts + + JUCE Modules\juce_graphics\detail + JUCE Modules\juce_graphics\effects @@ -5382,6 +5388,9 @@ JUCE Modules\juce_graphics\contexts + + JUCE Modules\juce_graphics\detail + JUCE Modules\juce_graphics\effects diff --git a/extras/AudioPluginHost/Builds/Android/app/CMakeLists.txt b/extras/AudioPluginHost/Builds/Android/app/CMakeLists.txt index 0f8287fc49..90fe96883a 100644 --- a/extras/AudioPluginHost/Builds/Android/app/CMakeLists.txt +++ b/extras/AudioPluginHost/Builds/Android/app/CMakeLists.txt @@ -1366,6 +1366,8 @@ add_library( ${BINARY_NAME} "../../../../../modules/juce_graphics/contexts/juce_LowLevelGraphicsPostScriptRenderer.h" "../../../../../modules/juce_graphics/contexts/juce_LowLevelGraphicsSoftwareRenderer.cpp" "../../../../../modules/juce_graphics/contexts/juce_LowLevelGraphicsSoftwareRenderer.h" + "../../../../../modules/juce_graphics/detail/juce_Ranges.cpp" + "../../../../../modules/juce_graphics/detail/juce_Ranges.h" "../../../../../modules/juce_graphics/effects/juce_DropShadowEffect.cpp" "../../../../../modules/juce_graphics/effects/juce_DropShadowEffect.h" "../../../../../modules/juce_graphics/effects/juce_GlowEffect.cpp" @@ -3666,6 +3668,8 @@ set_source_files_properties( "../../../../../modules/juce_graphics/contexts/juce_LowLevelGraphicsPostScriptRenderer.h" "../../../../../modules/juce_graphics/contexts/juce_LowLevelGraphicsSoftwareRenderer.cpp" "../../../../../modules/juce_graphics/contexts/juce_LowLevelGraphicsSoftwareRenderer.h" + "../../../../../modules/juce_graphics/detail/juce_Ranges.cpp" + "../../../../../modules/juce_graphics/detail/juce_Ranges.h" "../../../../../modules/juce_graphics/effects/juce_DropShadowEffect.cpp" "../../../../../modules/juce_graphics/effects/juce_DropShadowEffect.h" "../../../../../modules/juce_graphics/effects/juce_GlowEffect.cpp" diff --git a/extras/AudioPluginHost/Builds/VisualStudio2017/AudioPluginHost_App.vcxproj b/extras/AudioPluginHost/Builds/VisualStudio2017/AudioPluginHost_App.vcxproj index 61fe6a6794..7d626e177c 100644 --- a/extras/AudioPluginHost/Builds/VisualStudio2017/AudioPluginHost_App.vcxproj +++ b/extras/AudioPluginHost/Builds/VisualStudio2017/AudioPluginHost_App.vcxproj @@ -1701,6 +1701,9 @@ true + + true + true @@ -3569,6 +3572,7 @@ + diff --git a/extras/AudioPluginHost/Builds/VisualStudio2017/AudioPluginHost_App.vcxproj.filters b/extras/AudioPluginHost/Builds/VisualStudio2017/AudioPluginHost_App.vcxproj.filters index d2b4b10391..936d1ef379 100644 --- a/extras/AudioPluginHost/Builds/VisualStudio2017/AudioPluginHost_App.vcxproj.filters +++ b/extras/AudioPluginHost/Builds/VisualStudio2017/AudioPluginHost_App.vcxproj.filters @@ -527,6 +527,9 @@ {20DC13F6-2369-8841-9F0B-D13FA14EEE74} + + {0B30279D-5CEF-3E12-EA90-7D6CE4D52669} + {A302A8DB-120F-9EBB-A3D5-2C29963AA56B} @@ -2368,6 +2371,9 @@ JUCE Modules\juce_graphics\contexts + + JUCE Modules\juce_graphics\detail + JUCE Modules\juce_graphics\effects @@ -5817,6 +5823,9 @@ JUCE Modules\juce_graphics\contexts + + JUCE Modules\juce_graphics\detail + JUCE Modules\juce_graphics\effects diff --git a/extras/AudioPluginHost/Builds/VisualStudio2019/AudioPluginHost_App.vcxproj b/extras/AudioPluginHost/Builds/VisualStudio2019/AudioPluginHost_App.vcxproj index 73644dde1e..4668af2d54 100644 --- a/extras/AudioPluginHost/Builds/VisualStudio2019/AudioPluginHost_App.vcxproj +++ b/extras/AudioPluginHost/Builds/VisualStudio2019/AudioPluginHost_App.vcxproj @@ -1701,6 +1701,9 @@ true + + true + true @@ -3569,6 +3572,7 @@ + diff --git a/extras/AudioPluginHost/Builds/VisualStudio2019/AudioPluginHost_App.vcxproj.filters b/extras/AudioPluginHost/Builds/VisualStudio2019/AudioPluginHost_App.vcxproj.filters index 2a47b5ca5a..2ebb1d8ac5 100644 --- a/extras/AudioPluginHost/Builds/VisualStudio2019/AudioPluginHost_App.vcxproj.filters +++ b/extras/AudioPluginHost/Builds/VisualStudio2019/AudioPluginHost_App.vcxproj.filters @@ -527,6 +527,9 @@ {20DC13F6-2369-8841-9F0B-D13FA14EEE74} + + {0B30279D-5CEF-3E12-EA90-7D6CE4D52669} + {A302A8DB-120F-9EBB-A3D5-2C29963AA56B} @@ -2368,6 +2371,9 @@ JUCE Modules\juce_graphics\contexts + + JUCE Modules\juce_graphics\detail + JUCE Modules\juce_graphics\effects @@ -5817,6 +5823,9 @@ JUCE Modules\juce_graphics\contexts + + JUCE Modules\juce_graphics\detail + JUCE Modules\juce_graphics\effects diff --git a/extras/AudioPluginHost/Builds/VisualStudio2022/AudioPluginHost_App.vcxproj b/extras/AudioPluginHost/Builds/VisualStudio2022/AudioPluginHost_App.vcxproj index c15e7d054d..3bbe7ef09c 100644 --- a/extras/AudioPluginHost/Builds/VisualStudio2022/AudioPluginHost_App.vcxproj +++ b/extras/AudioPluginHost/Builds/VisualStudio2022/AudioPluginHost_App.vcxproj @@ -1701,6 +1701,9 @@ true + + true + true @@ -3569,6 +3572,7 @@ + diff --git a/extras/AudioPluginHost/Builds/VisualStudio2022/AudioPluginHost_App.vcxproj.filters b/extras/AudioPluginHost/Builds/VisualStudio2022/AudioPluginHost_App.vcxproj.filters index 7fb8c1db63..18edd907e1 100644 --- a/extras/AudioPluginHost/Builds/VisualStudio2022/AudioPluginHost_App.vcxproj.filters +++ b/extras/AudioPluginHost/Builds/VisualStudio2022/AudioPluginHost_App.vcxproj.filters @@ -527,6 +527,9 @@ {20DC13F6-2369-8841-9F0B-D13FA14EEE74} + + {0B30279D-5CEF-3E12-EA90-7D6CE4D52669} + {A302A8DB-120F-9EBB-A3D5-2C29963AA56B} @@ -2368,6 +2371,9 @@ JUCE Modules\juce_graphics\contexts + + JUCE Modules\juce_graphics\detail + JUCE Modules\juce_graphics\effects @@ -5817,6 +5823,9 @@ JUCE Modules\juce_graphics\contexts + + JUCE Modules\juce_graphics\detail + JUCE Modules\juce_graphics\effects diff --git a/extras/NetworkGraphicsDemo/Builds/Android/app/CMakeLists.txt b/extras/NetworkGraphicsDemo/Builds/Android/app/CMakeLists.txt index bd2c40150c..5f74a91ad0 100644 --- a/extras/NetworkGraphicsDemo/Builds/Android/app/CMakeLists.txt +++ b/extras/NetworkGraphicsDemo/Builds/Android/app/CMakeLists.txt @@ -1255,6 +1255,8 @@ add_library( ${BINARY_NAME} "../../../../../modules/juce_graphics/contexts/juce_LowLevelGraphicsPostScriptRenderer.h" "../../../../../modules/juce_graphics/contexts/juce_LowLevelGraphicsSoftwareRenderer.cpp" "../../../../../modules/juce_graphics/contexts/juce_LowLevelGraphicsSoftwareRenderer.h" + "../../../../../modules/juce_graphics/detail/juce_Ranges.cpp" + "../../../../../modules/juce_graphics/detail/juce_Ranges.h" "../../../../../modules/juce_graphics/effects/juce_DropShadowEffect.cpp" "../../../../../modules/juce_graphics/effects/juce_DropShadowEffect.h" "../../../../../modules/juce_graphics/effects/juce_GlowEffect.cpp" @@ -3482,6 +3484,8 @@ set_source_files_properties( "../../../../../modules/juce_graphics/contexts/juce_LowLevelGraphicsPostScriptRenderer.h" "../../../../../modules/juce_graphics/contexts/juce_LowLevelGraphicsSoftwareRenderer.cpp" "../../../../../modules/juce_graphics/contexts/juce_LowLevelGraphicsSoftwareRenderer.h" + "../../../../../modules/juce_graphics/detail/juce_Ranges.cpp" + "../../../../../modules/juce_graphics/detail/juce_Ranges.h" "../../../../../modules/juce_graphics/effects/juce_DropShadowEffect.cpp" "../../../../../modules/juce_graphics/effects/juce_DropShadowEffect.h" "../../../../../modules/juce_graphics/effects/juce_GlowEffect.cpp" diff --git a/extras/NetworkGraphicsDemo/Builds/VisualStudio2022/NetworkGraphicsDemo_App.vcxproj b/extras/NetworkGraphicsDemo/Builds/VisualStudio2022/NetworkGraphicsDemo_App.vcxproj index e9a28335de..f41a0b1b50 100644 --- a/extras/NetworkGraphicsDemo/Builds/VisualStudio2022/NetworkGraphicsDemo_App.vcxproj +++ b/extras/NetworkGraphicsDemo/Builds/VisualStudio2022/NetworkGraphicsDemo_App.vcxproj @@ -1588,6 +1588,9 @@ true + + true + true @@ -3434,6 +3437,7 @@ + diff --git a/extras/NetworkGraphicsDemo/Builds/VisualStudio2022/NetworkGraphicsDemo_App.vcxproj.filters b/extras/NetworkGraphicsDemo/Builds/VisualStudio2022/NetworkGraphicsDemo_App.vcxproj.filters index ba8d0c9238..d62be98448 100644 --- a/extras/NetworkGraphicsDemo/Builds/VisualStudio2022/NetworkGraphicsDemo_App.vcxproj.filters +++ b/extras/NetworkGraphicsDemo/Builds/VisualStudio2022/NetworkGraphicsDemo_App.vcxproj.filters @@ -494,6 +494,9 @@ {20DC13F6-2369-8841-9F0B-D13FA14EEE74} + + {0B30279D-5CEF-3E12-EA90-7D6CE4D52669} + {A302A8DB-120F-9EBB-A3D5-2C29963AA56B} @@ -2215,6 +2218,9 @@ JUCE Modules\juce_graphics\contexts + + JUCE Modules\juce_graphics\detail + JUCE Modules\juce_graphics\effects @@ -5544,6 +5550,9 @@ JUCE Modules\juce_graphics\contexts + + JUCE Modules\juce_graphics\detail + JUCE Modules\juce_graphics\effects diff --git a/extras/Projucer/Builds/VisualStudio2017/Projucer_App.vcxproj b/extras/Projucer/Builds/VisualStudio2017/Projucer_App.vcxproj index e95b2691a4..ba755b72da 100644 --- a/extras/Projucer/Builds/VisualStudio2017/Projucer_App.vcxproj +++ b/extras/Projucer/Builds/VisualStudio2017/Projucer_App.vcxproj @@ -770,6 +770,9 @@ true + + true + true @@ -2251,6 +2254,7 @@ + diff --git a/extras/Projucer/Builds/VisualStudio2017/Projucer_App.vcxproj.filters b/extras/Projucer/Builds/VisualStudio2017/Projucer_App.vcxproj.filters index 20fd36b78e..51582ef6e7 100644 --- a/extras/Projucer/Builds/VisualStudio2017/Projucer_App.vcxproj.filters +++ b/extras/Projucer/Builds/VisualStudio2017/Projucer_App.vcxproj.filters @@ -218,6 +218,9 @@ {20DC13F6-2369-8841-9F0B-D13FA14EEE74} + + {0B30279D-5CEF-3E12-EA90-7D6CE4D52669} + {A302A8DB-120F-9EBB-A3D5-2C29963AA56B} @@ -1159,6 +1162,9 @@ JUCE Modules\juce_graphics\contexts + + JUCE Modules\juce_graphics\detail + JUCE Modules\juce_graphics\effects @@ -3522,6 +3528,9 @@ JUCE Modules\juce_graphics\contexts + + JUCE Modules\juce_graphics\detail + JUCE Modules\juce_graphics\effects diff --git a/extras/Projucer/Builds/VisualStudio2019/Projucer_App.vcxproj b/extras/Projucer/Builds/VisualStudio2019/Projucer_App.vcxproj index d87438d34f..aa8cce2e6e 100644 --- a/extras/Projucer/Builds/VisualStudio2019/Projucer_App.vcxproj +++ b/extras/Projucer/Builds/VisualStudio2019/Projucer_App.vcxproj @@ -770,6 +770,9 @@ true + + true + true @@ -2251,6 +2254,7 @@ + diff --git a/extras/Projucer/Builds/VisualStudio2019/Projucer_App.vcxproj.filters b/extras/Projucer/Builds/VisualStudio2019/Projucer_App.vcxproj.filters index e1dbaedfd8..be02e304aa 100644 --- a/extras/Projucer/Builds/VisualStudio2019/Projucer_App.vcxproj.filters +++ b/extras/Projucer/Builds/VisualStudio2019/Projucer_App.vcxproj.filters @@ -218,6 +218,9 @@ {20DC13F6-2369-8841-9F0B-D13FA14EEE74} + + {0B30279D-5CEF-3E12-EA90-7D6CE4D52669} + {A302A8DB-120F-9EBB-A3D5-2C29963AA56B} @@ -1159,6 +1162,9 @@ JUCE Modules\juce_graphics\contexts + + JUCE Modules\juce_graphics\detail + JUCE Modules\juce_graphics\effects @@ -3522,6 +3528,9 @@ JUCE Modules\juce_graphics\contexts + + JUCE Modules\juce_graphics\detail + JUCE Modules\juce_graphics\effects diff --git a/extras/Projucer/Builds/VisualStudio2022/Projucer_App.vcxproj b/extras/Projucer/Builds/VisualStudio2022/Projucer_App.vcxproj index eaedbc99e6..04a7e1e18c 100644 --- a/extras/Projucer/Builds/VisualStudio2022/Projucer_App.vcxproj +++ b/extras/Projucer/Builds/VisualStudio2022/Projucer_App.vcxproj @@ -770,6 +770,9 @@ true + + true + true @@ -2251,6 +2254,7 @@ + diff --git a/extras/Projucer/Builds/VisualStudio2022/Projucer_App.vcxproj.filters b/extras/Projucer/Builds/VisualStudio2022/Projucer_App.vcxproj.filters index 3dccdef069..d6601bcb6c 100644 --- a/extras/Projucer/Builds/VisualStudio2022/Projucer_App.vcxproj.filters +++ b/extras/Projucer/Builds/VisualStudio2022/Projucer_App.vcxproj.filters @@ -218,6 +218,9 @@ {20DC13F6-2369-8841-9F0B-D13FA14EEE74} + + {0B30279D-5CEF-3E12-EA90-7D6CE4D52669} + {A302A8DB-120F-9EBB-A3D5-2C29963AA56B} @@ -1159,6 +1162,9 @@ JUCE Modules\juce_graphics\contexts + + JUCE Modules\juce_graphics\detail + JUCE Modules\juce_graphics\effects @@ -3522,6 +3528,9 @@ JUCE Modules\juce_graphics\contexts + + JUCE Modules\juce_graphics\detail + JUCE Modules\juce_graphics\effects diff --git a/extras/Projucer/JuceLibraryCode/BinaryData.cpp b/extras/Projucer/JuceLibraryCode/BinaryData.cpp index 6d4e83ad69..ebd6c3600f 100644 --- a/extras/Projucer/JuceLibraryCode/BinaryData.cpp +++ b/extras/Projucer/JuceLibraryCode/BinaryData.cpp @@ -6919,7 +6919,7 @@ static const unsigned char temp_binary_data_38[] = " g.fillAll (getLookAndFeel().findColour (juce::ResizableWindow::backgroundColourId));\r\n" "\r\n" " g.setColour (juce::Colours::white);\r\n" -" g.setFont (15.0f);\r\n" +" g.setFont (juce::FontOptions (15.0f));\r\n" " g.drawFittedText (\"Hello World!\", getLocalBounds(), juce::Justification::centred, 1);\r\n" "}\r\n" "\r\n" @@ -7239,7 +7239,7 @@ static const unsigned char temp_binary_data_43[] = " g.fillAll (getLookAndFeel().findColour (juce::ResizableWindow::backgroundColourId));\r\n" "\r\n" " g.setColour (juce::Colours::white);\r\n" -" g.setFont (15.0f);\r\n" +" g.setFont (juce::FontOptions (15.0f));\r\n" " g.drawFittedText (\"Hello World!\", getLocalBounds(), juce::Justification::centred, 1);\r\n" "}\r\n" "\r\n" @@ -7722,7 +7722,7 @@ static const unsigned char temp_binary_data_49[] = " // (Our component is opaque, so we must completely fill the background with a solid colour)\r\n" " g.fillAll (getLookAndFeel().findColour (juce::ResizableWindow::backgroundColourId));\r\n" "\r\n" -" g.setFont (juce::Font (16.0f));\r\n" +" g.setFont (juce::FontOptions (16.0f));\r\n" " g.setColour (juce::Colours::white);\r\n" " g.drawText (\"Hello World!\", getLocalBounds(), juce::Justification::centred, true);\r\n" " }\r\n" @@ -7765,7 +7765,7 @@ static const unsigned char temp_binary_data_50[] = " // (Our component is opaque, so we must completely fill the background with a solid colour)\r\n" " g.fillAll (getLookAndFeel().findColour (juce::ResizableWindow::backgroundColourId));\r\n" "\r\n" -" g.setFont (juce::Font (16.0f));\r\n" +" g.setFont (juce::FontOptions (16.0f));\r\n" " g.setColour (juce::Colours::white);\r\n" " g.drawText (\"Hello World!\", getLocalBounds(), juce::Justification::centred, true);\r\n" "}\r\n" @@ -7838,7 +7838,7 @@ static const unsigned char temp_binary_data_52[] = " g.drawRect (getLocalBounds(), 1); // draw an outline around the component\r\n" "\r\n" " g.setColour (juce::Colours::white);\r\n" -" g.setFont (14.0f);\r\n" +" g.setFont (juce::FontOptions (14.0f));\r\n" " g.drawText (\"%%component_class%%\", getLocalBounds(),\r\n" " juce::Justification::centred, true); // draw some placeholder text\r\n" " }\r\n" @@ -8087,7 +8087,7 @@ static const unsigned char temp_binary_data_56[] = " g.drawRect (getLocalBounds(), 1); // draw an outline around the component\r\n" "\r\n" " g.setColour (juce::Colours::white);\r\n" -" g.setFont (14.0f);\r\n" +" g.setFont (juce::FontOptions (14.0f));\r\n" " g.drawText (\"%%component_class%%\", getLocalBounds(),\r\n" " juce::Justification::centred, true); // draw some placeholder text\r\n" "}\r\n" @@ -8215,7 +8215,7 @@ static const unsigned char temp_binary_data_60[] = " g.drawRect (getLocalBounds(), 1); // draw an outline around the component\r\n" "\r\n" " g.setColour (juce::Colours::white);\r\n" -" g.setFont (14.0f);\r\n" +" g.setFont (juce::FontOptions (14.0f));\r\n" " g.drawText (\"%%component_class%%\", getLocalBounds(),\r\n" " juce::Justification::centred, true); // draw some placeholder text\r\n" " }\r\n" @@ -9178,29 +9178,29 @@ const char* getNamedResource (const char* resourceNameUTF8, int& numBytes) case 0x915d7304: numBytes = 1187; return jucer_AudioComponentTemplate_h; case 0x744d44d6: numBytes = 1916; return jucer_AudioPluginARADocumentControllerTemplate_cpp; case 0x3eb8f45b: numBytes = 1445; return jucer_AudioPluginARADocumentControllerTemplate_h; - case 0x851ac3ac: numBytes = 1593; return jucer_AudioPluginARAEditorTemplate_cpp; + case 0x851ac3ac: numBytes = 1613; return jucer_AudioPluginARAEditorTemplate_cpp; case 0x4d6430b1: numBytes = 1142; return jucer_AudioPluginARAEditorTemplate_h; case 0x48e808fc: numBytes = 2462; return jucer_AudioPluginARAFilterTemplate_h; case 0xea35a37d: numBytes = 5322; return jucer_AudioPluginARAPlaybackRendererTemplate_cpp; case 0x78a6d0c2: numBytes = 1757; return jucer_AudioPluginARAPlaybackRendererTemplate_h; - case 0x27c5a93a: numBytes = 1355; return jucer_AudioPluginEditorTemplate_cpp; + case 0x27c5a93a: numBytes = 1375; return jucer_AudioPluginEditorTemplate_cpp; case 0x4d0721bf: numBytes = 973; return jucer_AudioPluginEditorTemplate_h; case 0x51b49ac5: numBytes = 6218; return jucer_AudioPluginFilterTemplate_cpp; case 0x488afa0a: numBytes = 2299; return jucer_AudioPluginFilterTemplate_h; case 0xabad7041: numBytes = 2147; return jucer_ComponentTemplate_cpp; case 0xfc72fe86: numBytes = 2065; return jucer_ComponentTemplate_h; - case 0x1657b643: numBytes = 1524; return jucer_ContentCompSimpleTemplate_h; - case 0x0b66646c: numBytes = 1007; return jucer_ContentCompTemplate_cpp; + case 0x1657b643: numBytes = 1531; return jucer_ContentCompSimpleTemplate_h; + case 0x0b66646c: numBytes = 1014; return jucer_ContentCompTemplate_cpp; case 0x6fa10171: numBytes = 878; return jucer_ContentCompTemplate_h; - case 0x28d496ad: numBytes = 1276; return jucer_InlineComponentTemplate_h; + case 0x28d496ad: numBytes = 1296; return jucer_InlineComponentTemplate_h; case 0x8905395b: numBytes = 443; return jucer_MainConsoleAppTemplate_cpp; case 0x5e5ea047: numBytes = 1999; return jucer_MainTemplate_NoWindow_cpp; case 0x400bc026: numBytes = 4081; return jucer_MainTemplate_Window_cpp; - case 0xf4842835: numBytes = 1521; return jucer_NewComponentTemplate_cpp; + case 0xf4842835: numBytes = 1541; return jucer_NewComponentTemplate_cpp; case 0xe7bf237a: numBytes = 665; return jucer_NewComponentTemplate_h; case 0x02a2a077: numBytes = 278; return jucer_NewCppFileTemplate_cpp; case 0x0842c43c: numBytes = 258; return jucer_NewCppFileTemplate_h; - case 0x36e634a1: numBytes = 1719; return jucer_NewInlineComponentTemplate_h; + case 0x36e634a1: numBytes = 1739; return jucer_NewInlineComponentTemplate_h; case 0x6bdeb129: numBytes = 1987; return jucer_OpenGLComponentSimpleTemplate_h; case 0x7fbac252: numBytes = 1470; return jucer_OpenGLComponentTemplate_cpp; case 0x491fa0d7: numBytes = 1070; return jucer_OpenGLComponentTemplate_h; diff --git a/extras/Projucer/JuceLibraryCode/BinaryData.h b/extras/Projucer/JuceLibraryCode/BinaryData.h index d9d0ead409..1758b92fd9 100644 --- a/extras/Projucer/JuceLibraryCode/BinaryData.h +++ b/extras/Projucer/JuceLibraryCode/BinaryData.h @@ -123,7 +123,7 @@ namespace BinaryData const int jucer_AudioPluginARADocumentControllerTemplate_hSize = 1445; extern const char* jucer_AudioPluginARAEditorTemplate_cpp; - const int jucer_AudioPluginARAEditorTemplate_cppSize = 1593; + const int jucer_AudioPluginARAEditorTemplate_cppSize = 1613; extern const char* jucer_AudioPluginARAEditorTemplate_h; const int jucer_AudioPluginARAEditorTemplate_hSize = 1142; @@ -138,7 +138,7 @@ namespace BinaryData const int jucer_AudioPluginARAPlaybackRendererTemplate_hSize = 1757; extern const char* jucer_AudioPluginEditorTemplate_cpp; - const int jucer_AudioPluginEditorTemplate_cppSize = 1355; + const int jucer_AudioPluginEditorTemplate_cppSize = 1375; extern const char* jucer_AudioPluginEditorTemplate_h; const int jucer_AudioPluginEditorTemplate_hSize = 973; @@ -156,16 +156,16 @@ namespace BinaryData const int jucer_ComponentTemplate_hSize = 2065; extern const char* jucer_ContentCompSimpleTemplate_h; - const int jucer_ContentCompSimpleTemplate_hSize = 1524; + const int jucer_ContentCompSimpleTemplate_hSize = 1531; extern const char* jucer_ContentCompTemplate_cpp; - const int jucer_ContentCompTemplate_cppSize = 1007; + const int jucer_ContentCompTemplate_cppSize = 1014; extern const char* jucer_ContentCompTemplate_h; const int jucer_ContentCompTemplate_hSize = 878; extern const char* jucer_InlineComponentTemplate_h; - const int jucer_InlineComponentTemplate_hSize = 1276; + const int jucer_InlineComponentTemplate_hSize = 1296; extern const char* jucer_MainConsoleAppTemplate_cpp; const int jucer_MainConsoleAppTemplate_cppSize = 443; @@ -177,7 +177,7 @@ namespace BinaryData const int jucer_MainTemplate_Window_cppSize = 4081; extern const char* jucer_NewComponentTemplate_cpp; - const int jucer_NewComponentTemplate_cppSize = 1521; + const int jucer_NewComponentTemplate_cppSize = 1541; extern const char* jucer_NewComponentTemplate_h; const int jucer_NewComponentTemplate_hSize = 665; @@ -189,7 +189,7 @@ namespace BinaryData const int jucer_NewCppFileTemplate_hSize = 258; extern const char* jucer_NewInlineComponentTemplate_h; - const int jucer_NewInlineComponentTemplate_hSize = 1719; + const int jucer_NewInlineComponentTemplate_hSize = 1739; extern const char* jucer_OpenGLComponentSimpleTemplate_h; const int jucer_OpenGLComponentSimpleTemplate_hSize = 1987; diff --git a/extras/UnitTestRunner/Builds/VisualStudio2017/UnitTestRunner_ConsoleApp.vcxproj b/extras/UnitTestRunner/Builds/VisualStudio2017/UnitTestRunner_ConsoleApp.vcxproj index aabfed027d..abb1c4a151 100644 --- a/extras/UnitTestRunner/Builds/VisualStudio2017/UnitTestRunner_ConsoleApp.vcxproj +++ b/extras/UnitTestRunner/Builds/VisualStudio2017/UnitTestRunner_ConsoleApp.vcxproj @@ -1709,6 +1709,9 @@ true + + true + true @@ -3665,6 +3668,7 @@ + diff --git a/extras/UnitTestRunner/Builds/VisualStudio2017/UnitTestRunner_ConsoleApp.vcxproj.filters b/extras/UnitTestRunner/Builds/VisualStudio2017/UnitTestRunner_ConsoleApp.vcxproj.filters index c73acc02a1..5e59635cc0 100644 --- a/extras/UnitTestRunner/Builds/VisualStudio2017/UnitTestRunner_ConsoleApp.vcxproj.filters +++ b/extras/UnitTestRunner/Builds/VisualStudio2017/UnitTestRunner_ConsoleApp.vcxproj.filters @@ -527,6 +527,9 @@ {20DC13F6-2369-8841-9F0B-D13FA14EEE74} + + {0B30279D-5CEF-3E12-EA90-7D6CE4D52669} + {A302A8DB-120F-9EBB-A3D5-2C29963AA56B} @@ -2389,6 +2392,9 @@ JUCE Modules\juce_graphics\contexts + + JUCE Modules\juce_graphics\detail + JUCE Modules\juce_graphics\effects @@ -5931,6 +5937,9 @@ JUCE Modules\juce_graphics\contexts + + JUCE Modules\juce_graphics\detail + JUCE Modules\juce_graphics\effects diff --git a/extras/UnitTestRunner/Builds/VisualStudio2019/UnitTestRunner_ConsoleApp.vcxproj b/extras/UnitTestRunner/Builds/VisualStudio2019/UnitTestRunner_ConsoleApp.vcxproj index 4f7d46d0b1..1d4bca0512 100644 --- a/extras/UnitTestRunner/Builds/VisualStudio2019/UnitTestRunner_ConsoleApp.vcxproj +++ b/extras/UnitTestRunner/Builds/VisualStudio2019/UnitTestRunner_ConsoleApp.vcxproj @@ -1709,6 +1709,9 @@ true + + true + true @@ -3665,6 +3668,7 @@ + diff --git a/extras/UnitTestRunner/Builds/VisualStudio2019/UnitTestRunner_ConsoleApp.vcxproj.filters b/extras/UnitTestRunner/Builds/VisualStudio2019/UnitTestRunner_ConsoleApp.vcxproj.filters index acfc3f64a6..d2c3a222c6 100644 --- a/extras/UnitTestRunner/Builds/VisualStudio2019/UnitTestRunner_ConsoleApp.vcxproj.filters +++ b/extras/UnitTestRunner/Builds/VisualStudio2019/UnitTestRunner_ConsoleApp.vcxproj.filters @@ -527,6 +527,9 @@ {20DC13F6-2369-8841-9F0B-D13FA14EEE74} + + {0B30279D-5CEF-3E12-EA90-7D6CE4D52669} + {A302A8DB-120F-9EBB-A3D5-2C29963AA56B} @@ -2389,6 +2392,9 @@ JUCE Modules\juce_graphics\contexts + + JUCE Modules\juce_graphics\detail + JUCE Modules\juce_graphics\effects @@ -5931,6 +5937,9 @@ JUCE Modules\juce_graphics\contexts + + JUCE Modules\juce_graphics\detail + JUCE Modules\juce_graphics\effects diff --git a/extras/UnitTestRunner/Builds/VisualStudio2022/UnitTestRunner_ConsoleApp.vcxproj b/extras/UnitTestRunner/Builds/VisualStudio2022/UnitTestRunner_ConsoleApp.vcxproj index 467bcde3e2..24ef0ecc25 100644 --- a/extras/UnitTestRunner/Builds/VisualStudio2022/UnitTestRunner_ConsoleApp.vcxproj +++ b/extras/UnitTestRunner/Builds/VisualStudio2022/UnitTestRunner_ConsoleApp.vcxproj @@ -1709,6 +1709,9 @@ true + + true + true @@ -3665,6 +3668,7 @@ + diff --git a/extras/UnitTestRunner/Builds/VisualStudio2022/UnitTestRunner_ConsoleApp.vcxproj.filters b/extras/UnitTestRunner/Builds/VisualStudio2022/UnitTestRunner_ConsoleApp.vcxproj.filters index 1eafbe3025..df59c80670 100644 --- a/extras/UnitTestRunner/Builds/VisualStudio2022/UnitTestRunner_ConsoleApp.vcxproj.filters +++ b/extras/UnitTestRunner/Builds/VisualStudio2022/UnitTestRunner_ConsoleApp.vcxproj.filters @@ -527,6 +527,9 @@ {20DC13F6-2369-8841-9F0B-D13FA14EEE74} + + {0B30279D-5CEF-3E12-EA90-7D6CE4D52669} + {A302A8DB-120F-9EBB-A3D5-2C29963AA56B} @@ -2389,6 +2392,9 @@ JUCE Modules\juce_graphics\contexts + + JUCE Modules\juce_graphics\detail + JUCE Modules\juce_graphics\effects @@ -5931,6 +5937,9 @@ JUCE Modules\juce_graphics\contexts + + JUCE Modules\juce_graphics\detail + JUCE Modules\juce_graphics\effects diff --git a/extras/WindowsDLL/Builds/VisualStudio2022/WindowsDLL_StaticLibrary.vcxproj b/extras/WindowsDLL/Builds/VisualStudio2022/WindowsDLL_StaticLibrary.vcxproj index f0d9cc2549..f5fcb509ef 100644 --- a/extras/WindowsDLL/Builds/VisualStudio2022/WindowsDLL_StaticLibrary.vcxproj +++ b/extras/WindowsDLL/Builds/VisualStudio2022/WindowsDLL_StaticLibrary.vcxproj @@ -1587,6 +1587,9 @@ true + + true + true @@ -3410,6 +3413,7 @@ + diff --git a/extras/WindowsDLL/Builds/VisualStudio2022/WindowsDLL_StaticLibrary.vcxproj.filters b/extras/WindowsDLL/Builds/VisualStudio2022/WindowsDLL_StaticLibrary.vcxproj.filters index 439a969381..fa7ac63b6a 100644 --- a/extras/WindowsDLL/Builds/VisualStudio2022/WindowsDLL_StaticLibrary.vcxproj.filters +++ b/extras/WindowsDLL/Builds/VisualStudio2022/WindowsDLL_StaticLibrary.vcxproj.filters @@ -488,6 +488,9 @@ {20DC13F6-2369-8841-9F0B-D13FA14EEE74} + + {0B30279D-5CEF-3E12-EA90-7D6CE4D52669} + {A302A8DB-120F-9EBB-A3D5-2C29963AA56B} @@ -2212,6 +2215,9 @@ JUCE Modules\juce_graphics\contexts + + JUCE Modules\juce_graphics\detail + JUCE Modules\juce_graphics\effects @@ -5511,6 +5517,9 @@ JUCE Modules\juce_graphics\contexts + + JUCE Modules\juce_graphics\detail + JUCE Modules\juce_graphics\effects diff --git a/modules/juce_graphics/detail/juce_Ranges.cpp b/modules/juce_graphics/detail/juce_Ranges.cpp new file mode 100644 index 0000000000..3faecf8ebe --- /dev/null +++ b/modules/juce_graphics/detail/juce_Ranges.cpp @@ -0,0 +1,566 @@ +/* + ============================================================================== + + This file is part of the JUCE framework. + Copyright (c) Raw Material Software Limited + + JUCE is an open source framework subject to commercial or open source + licensing. + + By downloading, installing, or using the JUCE framework, or combining the + JUCE framework with any other source code, object code, content or any other + copyrightable work, you agree to the terms of the JUCE End User Licence + Agreement, and all incorporated terms including the JUCE Privacy Policy and + the JUCE Website Terms of Service, as applicable, which will bind you. If you + do not agree to the terms of these agreements, we will not license the JUCE + framework to you, and you must discontinue the installation or download + process and cease use of the JUCE framework. + + JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/ + JUCE Privacy Policy: https://juce.com/juce-privacy-policy + JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/ + + Or: + + You may also use this code under the terms of the AGPLv3: + https://www.gnu.org/licenses/agpl-3.0.en.html + + THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL + WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF + MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED. + + ============================================================================== +*/ + +namespace juce::detail +{ + +#if JUCE_UNIT_TESTS + +template +constexpr auto hasGetStartFunction = false; + +template +constexpr auto hasGetStartFunction().getStart())>> = true; + +template , int>::type = 0> +std::ostream& operator<< (std::ostream& os, const RangeType& range) +{ + os << "[" << range.getStart() << ", " << range.getEnd() << ")"; + return os; +} + +static String& operator<< (String& s, Range r) +{ + return s += "[" + String { r.getStart() } + ", " + String { r.getEnd() } + ")"; +} + +template +static auto getCumulativeRangeLengths (const RangedValues& rv) +{ + int64 totalLength{}; + + for (size_t i = 0; i < rv.size(); ++i) + totalLength += rv.getItem (i).range.getLength(); + + return totalLength; +} + +template +static auto toString (const RangedValues& rv) +{ + String s {}; + + for (size_t i = 0; i < rv.size(); ++i) + { + auto item = rv.getItem (i); + s << item.range << ": " << item.value << "\n"; + } + + return s; +} + +class RangesTestsBase : public UnitTest +{ +public: + using UnitTest::UnitTest; + + void expectRange (Range actual, Range expected) + { + String failureMessage { "range " }; + failureMessage << actual << " did not equal expected range " << expected; + expect (actual == expected, failureMessage); + } +}; + +class RangesTests : public RangesTestsBase +{ +public: + RangesTests() : RangesTestsBase ("Ranges", UnitTestCategories::containers) {} + + void runTest() override + { + beginTest ("Ranges::set() - basics"); + { + Ranges ranges; + + ranges.set ({ -3, 14 }); + expectRange (ranges.get (0), { -3, 14 }); + + ranges.set ({ 7, 20 }); + expectRange (ranges.get (0), { -3, 7 }); + expectRange (ranges.get (1), { 7, 20 }); + } + + beginTest ("Ranges::set() - neighbouring ranges extents are modified"); + { + Ranges ranges; + ranges.set ({ -3, 14 }); + ranges.set ({ 19, 30 }); + ranges.set ({ 10, 25 }); + + // size_t doesn't always map to an existing overload for String::operator<< on all platforms + expectEquals ((int64) ranges.size(), (int64) 3); + expectRange (ranges.get (0), { -3, 10 }); + expectRange (ranges.get (1), { 10, 25 }); + expectRange (ranges.get (2), { 25, 30 }); + } + + beginTest ("Ranges::set() - setting a range inside another one splits that range"); + { + Ranges ranges; + ranges.set ({ -3, 14 }); + + expectEquals ((int64) ranges.size(), (int64) 1); + + //============================================================================== + ranges.set ({ 3, 7 }); + + expectEquals ((int64) ranges.size(), (int64) 3); + expectRange (ranges.get (0), { -3, 3 }); + expectRange (ranges.get (1), { 3, 7 }); + expectRange (ranges.get (2), { 7, 14 }); + } + + beginTest ("Ranges::set() - old ranges falling within the bounds of a newly set are erased"); + { + Ranges ranges; + ranges.set ({ 0, 5 }); + ranges.set ({ 5, 10 }); + ranges.set ({ 15, 20 }); + ranges.set ({ 25, 30 }); + ranges.set ({ 35, 50 }); + + expectEquals ((int64) ranges.size(), (int64) 5); + + //============================================================================== + ranges.set ({ 4, 36 }); + + expectEquals ((int64) ranges.size(), (int64) 3); + expectRange (ranges.get (0), { 0, 4 }); + expectRange (ranges.get (1), { 4, 36 }); + expectRange (ranges.get (2), { 36, 50 }); + } + + beginTest ("Ranges::set() - setting an empty range should be a no-op"); + { + Ranges ranges; + + auto ops = ranges.set ({ 0, 0 }); + expect (ranges.isEmpty()); + expect (ops.empty()); + } + + beginTest ("Ranges::set() - setting a range inside another range"); + { + Ranges ranges; + + ranges.set ({ 0, 48 }); + ranges.set ({ 48, 127 }); + ranges.set ({ 49, 94 }); + + expectEquals ((int64) ranges.size(), (int64) 4, ""); + expectRange (ranges.get (0), { 0, 48 }); + expectRange (ranges.get (1), { 48, 49 }); + expectRange (ranges.get (2), { 49, 94 }); + expectRange (ranges.get (3), { 94, 127 }); + } + + beginTest ("Ranges::split()"); + { + Ranges ranges; + + ranges.set ({ 0, 48 }); + ranges.set ({ 48, 127 }); + + auto ops = ranges.split (47); + + expectEquals ((int64) ops.size(), (int64) 2, ""); + expect (std::get_if (&ops[0]) != nullptr); + expect (std::get_if (&ops[1]) != nullptr); + + expectEquals ((int64) ranges.size(), (int64) 3, ""); + expectRange (ranges.get (0), { 0, 47 }); + expectRange (ranges.get (1), { 47, 48 }); + expectRange (ranges.get (2), { 48, 127 }); + } + + beginTest ("Ranges::split() - splitting has no effect when no range begins before and ends after the location"); + { + Ranges ranges; + + ranges.set ({ 0, 48 }); + ranges.set ({ 48, 127 }); + + auto ops = ranges.split (48); + expectEquals ((int64) ops.size(), (int64) 0, ""); + + expectEquals ((int64) ranges.size(), (int64) 2, ""); + expectRange (ranges.get (0), { 0, 48 }); + expectRange (ranges.get (1), { 48, 127 }); + } + + beginTest ("Ranges::insert() - basics"); + { + Ranges ranges; + + ranges.insert ({ -3, 14 }); + expectRange (ranges.get (0), { -3, 14 }); + + ranges.insert ({ 7, 20 }); + expectRange (ranges.get (0), { -3, 7 }); + expectRange (ranges.get (1), { 7, 20 }); + expectRange (ranges.get (2), { 20, 27 }); + } + + beginTest ("Ranges::insert() - inserting shifts all following ranges"); + { + Ranges ranges; + + ranges.insert ({ 10, 11 }); + ranges.insert ({ 0, 1 }); + + expectRange (ranges.get (0), { 0, 1 }); + expectRange (ranges.get (1), { 11, 12 }); + } + + beginTest ("Ranges::insert() - inserting an empty range should be a no-op"); + { + Ranges ranges; + + auto ops = ranges.insert ({ 0, 0 }); + expect (ranges.isEmpty()); + expect (ops.empty()); + } + } +}; + +class RangedValuesTests : public UnitTest +{ +public: + RangedValuesTests() : UnitTest ("RangedValues", UnitTestCategories::containers) {} + + template + void expectRangedValuesItem (ItemType item, Range range, char value) + { + { + String failureMessage { "range " }; + failureMessage << item.range << " did not equal expected range " << range; + expect (item.range == range, failureMessage); + } + + { + String failureMessage { "value '" }; + failureMessage << item.value << "' in range " << range << " did not equal expected value '" << value << "'"; + expect (item.value == value, failureMessage); + } + } + + void runTest() override + { + auto random = getRandom(); + + const auto createRangedValuesObject = [&] + { + RangedValues rangedValues; + + rangedValues.set ({ 0, 10 }, 'a'); + rangedValues.set ({ 11, 20 }, 'b'); + rangedValues.set ({ 23, 30 }, 'c'); + + return rangedValues; + }; + + beginTest ("RangedValues::set() with distinct value overlapping other ranges"); + { + auto rangedValues = createRangedValuesObject(); + + rangedValues.set ({ 5, 15 }, 'd'); + + expect (! rangedValues.isEmpty()); + + expectRangedValuesItem (rangedValues.getItem (0), { 0, 5 }, 'a'); + expectRangedValuesItem (rangedValues.getItem (1), { 5, 15 }, 'd'); + expectRangedValuesItem (rangedValues.getItem (2), { 15, 20 }, 'b'); + expectRangedValuesItem (rangedValues.getItem (3), { 23, 30 }, 'c'); + + rangedValues.set ({ 19, 24 }, 'e'); + + expectRangedValuesItem (rangedValues.getItem (2), { 15, 19 }, 'b'); + expectRangedValuesItem (rangedValues.getItem (3), { 19, 24 }, 'e'); + expectRangedValuesItem (rangedValues.getItem (4), { 24, 30 }, 'c'); + } + + beginTest ("RangedValues::set() with distinct value in corner cases"); + { + auto rangedValues = createRangedValuesObject(); + + rangedValues.set ({ -1, 0 }, 'd'); + + expectRangedValuesItem (rangedValues.getItem (0), { -1, 0 }, 'd'); + expectRangedValuesItem (rangedValues.getItem (1), { 0, 10 }, 'a'); + } + + beginTest ("RangedValues::set() with same value with merging disallowed"); + { + auto rangedValues = createRangedValuesObject(); + + rangedValues.set ({ 5, 15 }, 'b'); + + expectRangedValuesItem (rangedValues.getItem (0), { 0, 5 }, 'a'); + expectRangedValuesItem (rangedValues.getItem (1), { 5, 15 }, 'b'); + expectRangedValuesItem (rangedValues.getItem (2), { 15, 20 }, 'b'); + expectRangedValuesItem (rangedValues.getItem (3), { 23, 30 }, 'c'); + } + + beginTest ("RangedValues::set() with same value with merging allowed"); + { + auto rangedValues = createRangedValuesObject(); + + rangedValues.set ({ 5, 15 }, 'b'); + + expectRangedValuesItem (rangedValues.getItem (0), { 0, 5 }, 'a'); + expectRangedValuesItem (rangedValues.getItem (1), { 5, 20 }, 'b'); + expectRangedValuesItem (rangedValues.getItem (2), { 23, 30 }, 'c'); + } + + beginTest ("RangedValues::set() - setting an empty Range should be a no-op"); + { + RangedValues rangedValues; + + auto ops = rangedValues.set ({ 0, 0 }, 'a'); + expect (rangedValues.isEmpty()); + expect (ops.empty()); + } + + beginTest ("RangedValues::set() - setting a range inside another range"); + { + RangedValues rangedValues; + + rangedValues.set ({ 0, 48 }, 'a'); + rangedValues.set ({ 48, 127 }, 'b'); + rangedValues.set ({ 49, 94 }, 'c'); + + expectEquals ((int64) rangedValues.size(), (int64) 4, ""); + + expectRangedValuesItem (rangedValues.getItem (0), { 0, 48 }, 'a'); + expectRangedValuesItem (rangedValues.getItem (1), { 48, 49 }, 'b'); + expectRangedValuesItem (rangedValues.getItem (2), { 49, 94 }, 'c'); + expectRangedValuesItem (rangedValues.getItem (3), { 94, 127 }, 'b'); + } + + beginTest ("RangedValues::getIntersectionsWith()"); + { + auto rangedValues = createRangedValuesObject(); + + { + const auto intersections = rangedValues.getIntersectionsWith ({ 5, 43 }); + + expectRangedValuesItem (intersections[0], { 5, 10 }, 'a'); + expectRangedValuesItem (intersections[1], { 11, 20 }, 'b'); + expectRangedValuesItem (intersections[2], { 23, 30 }, 'c'); + } + + { + const auto intersections = rangedValues.getIntersectionsWith ({ -10, 3 }); + + expectRangedValuesItem (intersections[0], { 0, 3 }, 'a'); + } + } + + beginTest ("RangedValues::insert() fuzzing - insert always increases the total covered range"); + { + for (auto i = 0; i != 100; ++i) + { + auto rangedValuesNotMerged = createRangedValuesObject(); + auto rangedValuesMerged = createRangedValuesObject(); + + const auto totalLengthBeforeInsert = getCumulativeRangeLengths (rangedValuesNotMerged); + + const auto beginInsertionAt = (int64) random.nextInt (100) - 50; + const auto numElemsToInsert = (int64) random.nextInt (1000); + + rangedValuesNotMerged.insert ({ Range::withStartAndLength (beginInsertionAt, numElemsToInsert) }, + 'a' + (char) random.nextInt (25)); + + expectEquals (getCumulativeRangeLengths (rangedValuesNotMerged) - totalLengthBeforeInsert, numElemsToInsert); + + rangedValuesMerged.insert ({ Range::withStartAndLength (beginInsertionAt, numElemsToInsert) }, + 'a' + (char) random.nextInt (25)); + + expectEquals (getCumulativeRangeLengths (rangedValuesMerged) - totalLengthBeforeInsert, numElemsToInsert); + } + } + + beginTest ("RangedValues::insert() with distinct value inside another range"); + { + auto rangedValues = createRangedValuesObject(); + + expectEquals ((int64) rangedValues.size(), (int64) 3); + + rangedValues.insert ({ 2, 4 }, 'd'); + + expectEquals ((int64) rangedValues.size(), (int64) 5); + + expectRangedValuesItem (rangedValues.getItem (0), { 0, 2 }, 'a'); + expectRangedValuesItem (rangedValues.getItem (1), { 2, 4 }, 'd'); + expectRangedValuesItem (rangedValues.getItem (2), { 4, 12 }, 'a'); + expectRangedValuesItem (rangedValues.getItem (3), { 13, 22 }, 'b'); + expectRangedValuesItem (rangedValues.getItem (4), { 25, 32 }, 'c'); + } + + beginTest ("RangedValues::insert() with same value inside another range"); + { + { + auto rangedValues = createRangedValuesObject(); + + expectEquals ((int64) rangedValues.size(), (int64) 3); + + rangedValues.insert ({ 2, 4 }, 'a'); + + expectEquals ((int64) rangedValues.size(), (int64) 3); + + expectRangedValuesItem (rangedValues.getItem (0), { 0, 12 }, 'a'); + expectRangedValuesItem (rangedValues.getItem (1), { 13, 22 }, 'b'); + expectRangedValuesItem (rangedValues.getItem (2), { 25, 32 }, 'c'); + } + { + auto rangedValues = createRangedValuesObject(); + + expectEquals ((int64) rangedValues.size(), (int64) 3); + + rangedValues.insert ({ 2, 4 }, 'a'); + + expectEquals ((int64) rangedValues.size(), (int64) 5); + + expectRangedValuesItem (rangedValues.getItem (0), { 0, 2 }, 'a'); + expectRangedValuesItem (rangedValues.getItem (1), { 2, 4 }, 'a'); + expectRangedValuesItem (rangedValues.getItem (2), { 4, 12 }, 'a'); + expectRangedValuesItem (rangedValues.getItem (3), { 13, 22 }, 'b'); + expectRangedValuesItem (rangedValues.getItem (4), { 25, 32 }, 'c'); + } + } + + beginTest ("RangedValues::insert() - inserting an empty Range should be a no-op"); + { + { + RangedValues emptyRangedValues; + + auto ops = emptyRangedValues.insert ({ 0, 0 }, 'a'); + expect (emptyRangedValues.isEmpty()); + expect (ops.empty()); + } + + { + RangedValues rangedValues; + rangedValues.set ({ 0, 10 }, 'a'); + + auto ops = rangedValues.insert ({ 0, 0 }, 'a'); + expect (ops.empty()); + expectEquals ((int64) rangedValues.size(), (int64) 1); + expectRangedValuesItem (rangedValues.getItem (0), { 0, 10 }, 'a'); + } + } + } +}; + +class IntersectingRangedValuesTests : public RangesTestsBase +{ +public: + IntersectingRangedValuesTests() : RangesTestsBase ("IntersectingRangedValues", UnitTestCategories::containers) {} + + void runTest() override + { + beginTest ("IntersectingRangedValuesTests - iterating over multiple RangedValues"); + { + RangedValues rv1; + rv1.set ({ 3, 8}, 1); + rv1.set ({ 9, 16}, 2); + rv1.set ({ 30, 40}, 3); + + RangedValues rv2; + rv2.set ({ 0, 4}, 7); + rv2.set ({ 4, 6}, 11); + rv2.set ({ 6, 25}, 13); + rv2.set ({ 27, 55}, 17); + + RangedValues rv3; + rv3.set ({ -2, 10}, -1); + rv3.set ({ 15, 19}, -2); + rv3.set ({ 22, 36}, -3); + + int iteration = 0; + + for (const auto [range, v1, v2, v3] : makeIntersectingRangedValues (&rv1, &rv2, &rv3)) + { + if (iteration == 0) + { + expectRange (range, Range { 3, 4 }); + expect (v1 == 1 && v2 == 7 && v3 == -1); + } + + if (iteration == 1) + { + expectRange (range, Range { 4, 6 }); + expect (v1 == 1 && v2 == 11 && v3 == -1); + } + + if (iteration == 2) + { + expectRange (range, Range { 6, 8 }); + expect (v1 == 1 && v2 == 13 && v3 == -1); + } + + if (iteration == 3) + { + expectRange (range, Range { 9, 10 }); + expect (v1 == 2 && v2 == 13 && v3 == -1); + } + + if (iteration == 4) + { + expectRange (range, Range { 15, 16 }); + expect (v1 == 2 && v2 == 13 && v3 == -2); + } + + if (iteration == 5) + { + expectRange (range, Range { 30, 36 }); + expect (v1 == 3 && v2 == 17 && v3 == -3); + } + + ++iteration; + } + + expectEquals (iteration, 6); + } + } +}; + +static RangesTests rangesTests; +static RangedValuesTests rangedValuesTests; +static IntersectingRangedValuesTests intersectingRangedValuesTests; + +#endif + +} // namespace juce::detail diff --git a/modules/juce_graphics/detail/juce_Ranges.h b/modules/juce_graphics/detail/juce_Ranges.h new file mode 100644 index 0000000000..e84f496e56 --- /dev/null +++ b/modules/juce_graphics/detail/juce_Ranges.h @@ -0,0 +1,1157 @@ +/* + ============================================================================== + + This file is part of the JUCE framework. + Copyright (c) Raw Material Software Limited + + JUCE is an open source framework subject to commercial or open source + licensing. + + By downloading, installing, or using the JUCE framework, or combining the + JUCE framework with any other source code, object code, content or any other + copyrightable work, you agree to the terms of the JUCE End User Licence + Agreement, and all incorporated terms including the JUCE Privacy Policy and + the JUCE Website Terms of Service, as applicable, which will bind you. If you + do not agree to the terms of these agreements, we will not license the JUCE + framework to you, and you must discontinue the installation or download + process and cease use of the JUCE framework. + + JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/ + JUCE Privacy Policy: https://juce.com/juce-privacy-policy + JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/ + + Or: + + You may also use this code under the terms of the AGPLv3: + https://www.gnu.org/licenses/agpl-3.0.en.html + + THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL + WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF + MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED. + + ============================================================================== +*/ + +namespace juce::detail +{ + +inline std::optional> getRangeIntersectionWith (Range r1, Range r2) +{ + auto intersection = r1.getIntersectionWith (r2); + + if (intersection.getLength() == 0) + return std::nullopt; + + return Range { intersection }; +} + +inline size_t clampCast (int64 v) +{ + return v < 0 ? 0 : (size_t) v; +} + +/* Used in Ranges::getAffectedElements(). Yes means that if a change occurs to range i, we also + want ranges i - 1 and i + 1 included in the affected elements. +*/ +enum class IncludeAdjacentRanges +{ + no, + yes +}; + +struct Ranges final +{ + struct Ops + { + Ops() = delete; + + struct Erased { Range range; }; + struct Inserted { size_t index; }; + struct Reinserted { size_t index; }; + struct Changed { size_t index; }; + }; + + using Op = std::variant; + + using Operations = std::vector; + + Ranges() = default; + + explicit Ranges (std::vector> rangesIn) + : ranges (std::move (rangesIn)) + { + #if JUCE_DEBUG + jassert (std::is_sorted (ranges.cbegin(), ranges.cend(), [] (const auto& a, const auto& b) + { + return a.getEnd() < b.getStart(); + })); + #endif + } + + void clear() + { + ranges.clear(); + } + + /* Returns the ranges that have an intersection with the provided range. */ + std::vector> getIntersectionsWith (Range r) const + { + std::vector> result; + + const auto firstOverlapping = [&] + { + auto it = std::lower_bound (ranges.cbegin(), + ranges.cend(), + r, + [] (auto& elem, auto& value) + { return elem.getEnd() <= value.getStart(); }); + + return it; + }(); + + const auto lastOverlapping = [&] + { + auto it = std::lower_bound (firstOverlapping, + ranges.cend(), + r, + [] (auto& elem, auto& value) + { return elem.getEnd() < value.getEnd(); }); + + return it != std::cend (ranges) ? it + 1 : it; + }(); + + for (auto it = firstOverlapping; it != lastOverlapping; ++it) + if (auto intersection = getRangeIntersectionWith (Range { *it }, r)) + result.push_back (*intersection); + + return result; + } + + Operations set (Range newRange) + { + if (newRange.isEmpty()) + return {}; + + Operations ops; + + const auto firstStartingBeforeNewRange = [&] + { + auto it = std::lower_bound (ranges.begin(), + ranges.end(), + newRange, + [] (auto& elem, auto& value) + { return elem.getStart() < value.getStart(); }); + + if (! ranges.empty() && it != ranges.begin()) + return it - 1; + + return ranges.end(); + }(); + + const auto getFirstEndingAfterNewRange = [&] + { + auto it = std::lower_bound (ranges.begin(), + ranges.end(), + newRange, + [] (auto& elem, auto& value) + { return elem.getEnd() <= value.getEnd(); }); + + return it; + }; + + auto firstEndingAfterNewRange = getFirstEndingAfterNewRange(); + + // This variable helps with handling the corner case, when the newValue to be set lies + // entirely inside an existing range. The set() operation in this case is expected to split + // the existing range. + auto remainderRangeBecauseOfSplit = [&]() -> std::optional> + { + if (firstStartingBeforeNewRange == ranges.end() || firstStartingBeforeNewRange != firstEndingAfterNewRange) + return std::nullopt; + + return Range { std::max (newRange.getEnd(), firstEndingAfterNewRange->getStart()), + firstEndingAfterNewRange->getEnd() }; + }(); + + if (firstStartingBeforeNewRange != ranges.end()) + { + const auto oldEnd = firstStartingBeforeNewRange->getEnd(); + const auto newEnd = std::min (oldEnd, newRange.getStart()); + + firstStartingBeforeNewRange->setEnd (newEnd); + + if (oldEnd != newEnd) + ops.push_back (Ops::Changed { getIndex (firstStartingBeforeNewRange) }); + } + + if (! remainderRangeBecauseOfSplit.has_value() + && firstEndingAfterNewRange != ranges.end()) + { + const auto oldStart = firstEndingAfterNewRange->getStart(); + const auto newStart = std::max (firstEndingAfterNewRange->getStart(), newRange.getEnd()); + + firstEndingAfterNewRange->setStart (newStart); + + if (oldStart != newStart) + ops.push_back (Ops::Changed { getIndex (firstStartingBeforeNewRange) }); + } + + const auto firstToDelete = std::lower_bound (ranges.begin(), + ranges.end(), + newRange, + [] (auto& elem, auto& value) + { return elem.getStart() < value.getStart(); }); + + firstEndingAfterNewRange = getFirstEndingAfterNewRange(); + + if (firstToDelete != ranges.end() && firstToDelete != firstEndingAfterNewRange) + ops.push_back (Ops::Erased { { getIndex (firstToDelete), + getIndex (firstEndingAfterNewRange) } }); + + const auto beyondLastRemoved = ranges.erase (firstToDelete, firstEndingAfterNewRange); + + const auto insertIt = ranges.insert (beyondLastRemoved, newRange); + ops.push_back (Ops::Inserted { getIndex (insertIt) }); + + if (remainderRangeBecauseOfSplit.has_value()) + { + const auto it = ranges.insert (insertIt + 1, *remainderRangeBecauseOfSplit); + ops.push_back (Ops::Reinserted { getIndex (it) }); + } + + return ops; + } + + size_t getIndex (std::vector>::const_iterator it) const + { + return (size_t) std::distance (ranges.cbegin(), it); + } + + Operations insert (Range newRange) + { + if (newRange.isEmpty()) + return {}; + + Operations ops; + + auto it = std::lower_bound (ranges.begin(), + ranges.end(), + newRange, + [] (auto& elem, auto& value) + { return elem.getEnd() <= value.getStart(); }); + + if (it != ranges.end() && it->getStart() < newRange.getStart()) + { + const auto oldEnd = it->getEnd(); + it->setEnd (newRange.getStart()); + ops.push_back (Ops::Changed { getIndex (it) }); + + Range newItems[] = { newRange, + { newRange.getEnd(), newRange.getEnd() + oldEnd - it->getEnd() } }; + + it = ranges.insert (it + 1, std::begin (newItems), std::end (newItems)); + + ops.push_back (Ops::Inserted { getIndex (it) }); + ops.push_back (Ops::Inserted { getIndex (it + 1) }); + + ++it; + } + else + { + it = ranges.insert (it, newRange); + + ops.push_back (Ops::Inserted { getIndex (it) }); + } + + for (auto& range : makeRange (std::next (it), ranges.end())) + range += newRange.getLength(); + + return ops; + } + + Operations split (int64 i) + { + Operations ops; + + const auto elemIndex = getIndexForEnclosingRange (i); + + if (! elemIndex.has_value()) + return ops; + + auto& elem = ranges[*elemIndex]; + + if (elem.getStart() == i) + return ops; + + const auto oldLength = elem.getLength(); + elem.setEnd (i); + ops.push_back (Ops::Changed { *elemIndex }); + + auto setOps = set (Range { Range { i, i + oldLength - elem.getLength() } }); + ops.insert (ops.end(), setOps.begin(), setOps.end()); + + return ops; + } + + Operations eraseFrom (int64 i) + { + Operations ops; + + const auto elemIndex = getIndexForEnclosingRange (i); + + if (elemIndex.has_value()) + { + ranges[*elemIndex].setEnd (i); + ops.push_back (Ops::Changed { *elemIndex }); + } + + const auto firstToDelete = std::lower_bound (ranges.begin(), + ranges.end(), + i, + [] (auto& elem, auto& value) + { return elem.getStart() < value; }); + + if (firstToDelete != ranges.end()) + ops.push_back (Ops::Erased { { getIndex (firstToDelete), getIndex (ranges.end()) } }); + + ranges.erase (firstToDelete, ranges.end()); + + return ops; + } + + Operations merge (Range elements) + { + jassert (elements.getEnd() <= ranges.size()); + + Operations ops; + + for (auto i = elements.getStart(), j = i + 1; j < elements.getEnd(); ++j) + { + const auto inLastIteration = j == elements.getEnd() - 1; + + if (inLastIteration || ranges[j].getEnd() != ranges[j + 1].getStart()) + { + ranges[i].setEnd (ranges[j].getEnd()); + ranges.erase (ranges.begin() + (int) i + 1, ranges.begin() + (int) j + 1); + + // I like that the merging algorithm works regardless of i being equal to j, so + // I didn't add this if earlier. No need to handle a corner case where there is + // none. However, I don't want to omit events if nothing changed. + if (i != j) + { + ops.push_back (Ops::Changed { i }); + ops.push_back (Ops::Erased { { i + 1, j + 1 } }); + } + + const auto numItemsDeleted = j - i; + elements.setEnd (elements.getEnd() - numItemsDeleted); + i = j + 1 - numItemsDeleted; + j = i; + } + } + + return ops; + } + + std::optional getIndexForEnclosingRange (int64 positionInTextRange) const + { + auto it = std::lower_bound (ranges.begin(), + ranges.end(), + positionInTextRange, + [] (auto& elem, auto& value) { return elem.getEnd() <= value; }); + + if (it != ranges.end() && it->getStart() <= positionInTextRange) + return getIndex (it); + + return std::nullopt; + } + + Range get (size_t rangeIndex) const + { + return ranges[rangeIndex]; + } + + size_t size() const + { + return ranges.size(); + } + + bool isEmpty() const + { + return ranges.empty(); + } + + auto& operator[] (size_t rangeIndex) + { + return ranges[rangeIndex]; + } + + auto& operator[] (size_t rangeIndex) const + { + return ranges[rangeIndex]; + } + + [[nodiscard]] Range getAffectedElements (const Ranges::Operations& ops, + IncludeAdjacentRanges includeAdjacent = IncludeAdjacentRanges::yes) const + { + if (ops.empty()) + return {}; + + int64 start = std::numeric_limits::max(); + int64 end = std::numeric_limits::min(); + + const auto startIsValid = [&start] { return start != std::numeric_limits::max(); }; + + const int64 includeAdjacentOffset = includeAdjacent == IncludeAdjacentRanges::yes ? 1 : 0; + + const auto adjacentOffsetFor = [includeAdjacent, this] (size_t index, int64 offset) -> int64 + { + if (includeAdjacent == IncludeAdjacentRanges::no) + return 0; + + const auto adjacentRangeIndex = (int64) index + offset; + + if (! isPositiveAndBelow (adjacentRangeIndex, (int64) ranges.size())) + return 0; + + if (offset < 0) + return ranges[(size_t) adjacentRangeIndex].getEnd() == ranges[index].getStart() ? offset : 0; + + return ranges[index].getEnd() == ranges[(size_t) adjacentRangeIndex].getStart() ? offset : 0; + }; + + for (auto& op : ops) + { + if (auto* inserted = std::get_if (&op)) + { + if (startIsValid() && (int64) inserted->index < start) + start += 1; + + if ((int64) inserted->index < end) + end += 1; + + start = std::min (start, (int64) inserted->index + adjacentOffsetFor (inserted->index, -1)); + end = std::max (end, (int64) inserted->index + adjacentOffsetFor (inserted->index, 1) + 1); + } + else if (auto* reinserted = std::get_if (&op)) + { + if (startIsValid() && (int64) reinserted->index < start) + start += 1; + + if ((int64) reinserted->index < end) + end += 1; + + start = std::min (start, (int64) reinserted->index + adjacentOffsetFor (reinserted->index, -1)); + end = std::max (end, (int64) reinserted->index + adjacentOffsetFor (reinserted->index, 1) + 1); + } + else if (auto* erased = std::get_if (&op)) + { + const auto eraseStart = (int64) erased->range.getStart(); + + if (startIsValid() && eraseStart < start) + start -= (int64) erased->range.getLength(); + + if (eraseStart < end - 1) + end -= (int64) erased->range.getLength(); + } + else if (auto* changed = std::get_if (&op)) + { + start = std::min (start, (int64) changed->index - includeAdjacentOffset); + end = std::max (end, (int64) changed->index + includeAdjacentOffset); + } + } + + return { clampCast (start), std::min (clampCast (end), ranges.size()) }; + } + + [[nodiscard]] Range getSpannedRange (Range r) const + { + auto start = (int64) r.getStart(); + auto end = (int64) r.getEnd(); + + jassert (start < (int64) ranges.size() && end <= (int64) ranges.size()); + + return { ranges[(size_t) start].getStart(), + ranges[(size_t) std::max (start, end - 1)].getEnd() }; + } + + auto begin() const + { + return ranges.cbegin(); + } + + auto cbegin() const + { + return ranges.cbegin(); + } + + auto end() const + { + return ranges.cend(); + } + + auto cend() const + { + return ranges.cend(); + } + +private: + std::vector> ranges; +}; + +//============================================================================== +enum class MergeEqualItems +{ + no, + yes +}; + +template +constexpr auto hasEqualityOperator = false; + +template +constexpr auto hasEqualityOperator() == std::declval())>> = true; + +/* This is to get rid of the warning where advance isn't of type difference_type. + */ +template +auto iteratorWithAdvance (Iterator&& it, Value advance) +{ + auto outIt = std::move (it); + std::advance (outIt, static_cast::difference_type> (advance)); + return outIt; +} + +/* Data structure for storing values associated with non-overlapping ranges. + + Has set() and insert() operations with the optional merging of ranges that contain equal values. + These operations emit a sequence of simpler operations, that are more easily executable on an + external container to bring it in sync with the ranges and values stored in this class. + + These operations are communicating things such as + - "the range associated with element 5 of your container have changed" + - "a new element was inserted at index 5, that refers to new data" + + The second example operation also implies, that all elements beyond the new index 5 element + still refer to the same data, since when the data associated with a given element changes, a + change operation is emitted. +*/ +template +class RangedValues +{ + template + static auto getItemWithEnclosingRangeImpl (Self& self, int64 i) + { + const auto j = self.ranges.getIndexForEnclosingRange (i); + return j ? std::make_optional (self.getItem (*j)) : std::nullopt; + } + +public: + static constexpr bool canMergeEqualItems = hasEqualityOperator; + + template + class RangedValuesIterator + { + private: + using InternalIterator = decltype (std::declval().ranges.cbegin()); + + public: + using value_type = std::pair, T>; + using difference_type = typename std::iterator_traits::iterator>::difference_type; + using reference = decltype (std::declval().getItem (0)); + + struct PointerProxy + { + PointerProxy (reference r) : ref { r } {} + + auto operator->() const { return &ref; } + + reference ref; + }; + + using pointer = PointerProxy; + using iterator_category = std::random_access_iterator_tag; + + RangedValuesIterator (RangedValuesType* ownerIn, InternalIterator iteratorIn) + : owner { ownerIn }, + iterator { iteratorIn } + {} + + RangedValuesIterator& operator+= (difference_type distance) + { + iterator += distance; + return *this; + } + + friend RangedValuesIterator operator+ (RangedValuesIterator i, difference_type d) { return i += d; } + friend RangedValuesIterator operator+ (difference_type d, RangedValuesIterator i) { return i += d; } + + RangedValuesIterator& operator-= (difference_type distance) + { + iterator -= distance; + return *this; + } + + friend RangedValuesIterator operator- (RangedValuesIterator i, difference_type d) { return i -= d; } + + reference makeReference (const InternalIterator& it) const + { + const auto valueIt = iteratorWithAdvance (owner->values.begin(), std::distance (owner->ranges.cbegin(), it)); + + return { *it, *valueIt }; + } + + reference operator[] (difference_type d) const + { + auto it = iterator[d]; + return makeReference (it); + } + + friend difference_type operator- (RangedValuesIterator a, RangedValuesIterator b) { return a.iterator - b.iterator; } + + friend bool operator< (RangedValuesIterator a, RangedValuesIterator b) { return a.iterator < b.iterator; } + friend bool operator<= (RangedValuesIterator a, RangedValuesIterator b) { return a.iterator <= b.iterator; } + friend bool operator> (RangedValuesIterator a, RangedValuesIterator b) { return a.iterator > b.iterator; } + friend bool operator>= (RangedValuesIterator a, RangedValuesIterator b) { return a.iterator >= b.iterator; } + friend bool operator== (RangedValuesIterator a, RangedValuesIterator b) { return a.iterator == b.iterator; } + friend bool operator!= (RangedValuesIterator a, RangedValuesIterator b) { return a.iterator != b.iterator; } + + RangedValuesIterator& operator++() { ++iterator; return *this; } + RangedValuesIterator& operator--() { --iterator; return *this; } + RangedValuesIterator operator++ (int) const { RangedValuesIterator copy (*this); ++(*this); return copy; } + RangedValuesIterator operator-- (int) const { RangedValuesIterator copy (*this); --(*this); return copy; } + + reference operator* () const { return makeReference (iterator); } + pointer operator->() const { return PointerProxy { makeReference (iterator) }; } + + private: + RangedValuesType* owner{}; + InternalIterator iterator; + }; + + template + static auto makeIterator (X* x, Y y) { return RangedValuesIterator (x, y); } + + auto begin() + { + return makeIterator (this, ranges.cbegin()); + } + + auto begin() const + { + return makeIterator (this, ranges.cbegin()); + } + + auto cbegin() const + { + return makeIterator (this, ranges.cbegin()); + } + + auto end() + { + return makeIterator (this, ranges.cend()); + } + + auto end() const + { + return makeIterator (this, ranges.cend()); + } + + auto cend() const + { + return makeIterator (this, ranges.cend()); + } + + struct Item + { + Range range; + T& value; + }; + + struct ConstItem + { + Range range; + const T& value; + }; + + void clear() + { + ranges.clear(); + values.clear(); + } + + template + auto set (Range r, T v) + { + static_assert (mergeEquals == MergeEqualItems::no || canMergeEqualItems, + "You can't use MergeEqualItems::yes if your type doesn't have operator==."); + + auto ops = ranges.set (r); + + // We use the copy constructor to avoid any dependency or restriction on the types default + // constructor. + T changedValue = v; + + #if JUCE_DEBUG + int numInsert{}; + #endif + + for (auto& op : ops) + { + if (auto* changed = std::get_if (&op)) + { + if (changed->index < values.size()) + changedValue = values[changed->index]; + } + else if (auto* inserted = std::get_if (&op)) + { + #if JUCE_DEBUG + ++numInsert; + #endif + + values.insert (iteratorWithAdvance (values.begin(), inserted->index), v); + } + else if (auto* reinserted = std::get_if (&op)) + { + values.insert (iteratorWithAdvance (values.begin(), reinserted->index), changedValue); + } + else if (auto* erased = std::get_if (&op)) + { + values.erase (iteratorWithAdvance (values.begin(), erased->range.getStart()), + iteratorWithAdvance (values.begin(), erased->range.getEnd())); + } + } + + #if JUCE_DEBUG + jassert (numInsert <= 1); + #endif + + if constexpr (mergeEquals == MergeEqualItems::yes) + { + const auto mergeOps = mergeEqualItems (ranges.getAffectedElements (ops)); + ops.insert (ops.begin(), mergeOps.begin(), mergeOps.end()); + } + + return ops; + } + + /** Create a RangedValues object from non-overlapping ranges. */ + template + auto setForEach (Iterable begin, Iterable end) + { + Ranges::Operations ops; + + for (auto it = begin; it != end; ++it) + { + const auto& [range, value] = *it; + const auto subOps = set (range, value); + ops.insert (ops.end(), subOps.begin(), subOps.end()); + } + + return ops; + } + + template + auto insert (Range r, T v) + { + static_assert (mergeEquals == MergeEqualItems::no || canMergeEqualItems, + "You can't use MergeEqualItems::yes if your type doesn't have operator==."); + + auto ops = ranges.insert (r); + + std::optional oldValue; + auto newValueInserted = false; + + for (auto& op : ops) + { + if (auto* changed = std::get_if (&op)) + { + oldValue = values[changed->index]; + } + else if (auto* inserted = std::get_if (&op)) + { + if (! std::exchange (newValueInserted, true)) + { + values.insert (values.begin() + (int) inserted->index, v); + } + else + { + jassert (oldValue.has_value()); + values.insert (values.begin() + (int) inserted->index, *oldValue); + oldValue.reset(); + } + } + else + jassertfalse; + } + + if constexpr (mergeEquals == MergeEqualItems::yes) + { + const auto mergeOps = mergeEqualItems (ranges.getAffectedElements (ops)); + ops.insert (ops.begin(), mergeOps.begin(), mergeOps.end()); + } + + return ops; + } + + auto eraseFrom (int64 i) + { + auto ops = ranges.eraseFrom (i); + + for (auto& op : ops) + { + if (auto* erased = std::get_if (&op)) + { + values.erase (iteratorWithAdvance (values.begin(), erased->range.getStart()), + iteratorWithAdvance (values.begin(), erased->range.getEnd())); + } + } + } + + Range getEqualElements (Range rangesToCheck) + { + if constexpr (canMergeEqualItems) + { + std::optional start; + size_t end{}; + + for (auto i = rangesToCheck.getStart(); i < rangesToCheck.getEnd() - 1; ++i) + { + if (exactlyEqual (values[i], values[i + 1])) + { + if (! start.has_value()) + start = i; + + end = i + 2; + } + else + { + if (start.has_value()) + break; + } + } + + return start.has_value() ? Range { *start, end } + : Range { rangesToCheck.getStart(), + rangesToCheck.getStart() }; + } + else + { + return {}; + } + } + + auto mergeEqualItems (Range elements) + { + if (elements.isEmpty()) + return Ranges::Operations{}; + + auto ops = ranges.merge (getEqualElements (elements)); + + for (const auto& op : ops) + { + if (auto* erased = std::get_if (&op)) + { + values.erase (values.begin() + (int) erased->range.getStart(), + values.begin() + (int) erased->range.getEnd()); + } + } + + return ops; + } + + auto getItemWithEnclosingRange (int64 i) + { + return getItemWithEnclosingRangeImpl (*this, i); + } + + auto getItemWithEnclosingRange (int64 i) const + { + return getItemWithEnclosingRangeImpl (*this, i); + } + + Item getItem (size_t i) + { + jassert (i < values.size()); + + return { ranges.get (i), values[i] }; + } + + ConstItem getItem (size_t i) const + { + jassert (i < values.size()); + + return { ranges.get (i), values[i] }; + } + + Item front() + { + jassert (! ranges.isEmpty()); + return getItem (0); + } + + ConstItem front() const + { + jassert (! ranges.isEmpty()); + return getItem (0); + } + + Item back() + { + jassert (! ranges.isEmpty()); + return getItem (values.size() - 1); + } + + ConstItem back() const + { + jassert (! ranges.isEmpty()); + return getItem (values.size() - 1); + } + + /* Returns the stored values together with the overlapping range, that overlap with the + provided range. + */ + std::vector getIntersectionsWith (Range r) const + { + const auto intersections = ranges.getIntersectionsWith (r); + + std::vector result; + result.reserve (intersections.size()); + + for (const auto& is : intersections) + { + auto valueIndex = ranges.getIndexForEnclosingRange (is.getStart()); + jassert (valueIndex.has_value()); + result.push_back ({ is, values[*valueIndex] }); + } + + return result; + } + + const auto& getRanges() const { return ranges; } + + size_t size() const + { + return ranges.size(); + } + + bool isEmpty() const + { + return ranges.isEmpty(); + } + +private: + Ranges ranges; + std::vector values; +}; + +struct RangedIterator +{ + RangedIterator() = default; + RangedIterator (const RangedIterator&) = default; + RangedIterator (RangedIterator&&) noexcept = default; + RangedIterator& operator= (const RangedIterator&) = default; + RangedIterator& operator= (RangedIterator&&) noexcept = default; + + virtual ~RangedIterator() = default; + virtual Range getRange() const = 0; + virtual bool isValid() const = 0; + virtual void operator++() = 0; +}; + +template +class RangedIteratorWrapper final : public RangedIterator +{ +public: + /* We pass a pointer rather than a reference here to make it clearer that the pointed-to object + must outlive the RangedIteratorWrapper, otherwise the wrapped iterators will dangle. + */ + explicit RangedIteratorWrapper (const RangedValues* rv) + : iterator { rv->cbegin() }, + end { rv->cend() } + {} + + //============================================================================== + Range getRange() const override { return iterator->range; } + bool isValid() const override { return iterator != end; } + void operator++() override { ++iterator; } + + //============================================================================== + const T& getValue() const { return iterator->value; } + +private: + decltype (std::declval&>().cbegin()) iterator, end; +}; + +template +class IntersectingRangedValues; + +/* A wrapper type encapsulating multiple RangedValues objects and providing iterator support. + + The iterator will advance through Ranges that are intersections with homogeneous values in each + respective RangedValues object. + + @code + RangedValues characters; + characters.insert ({ -2, 12 }, 'a'); + characters.insert ({ 12, 44 }, 'b'); + characters.insert ({ 63, 81 }, 'c'); + + RangedValues numbers; + numbers.insert ({ -1, 0 }, 99); + numbers.insert ({ 9, 12 }, 823); + numbers.insert ({ 14, 16 }, 112); + + for (auto [range, character, number] : makeIntersectingRangedValues (characters, numbers)) + std::cout << toString (range) << ", " << character << ", " << number << std::endl; + + // Prints: + // [-1, 0), a, 99 + // [9, 12), a, 823 + // [14, 16), b, 112 + @endcode +*/ +template +class IntersectingRangedValues...> +{ +private: + static_assert (sizeof...(Values) > 0, "IntersectingRangedValues() must wrap at least one RangedValues object"); + + static auto createIteratorWrappers (const RangedValues*... containers) + { + return std::make_tuple (RangedIteratorWrapper { containers }...); + } + +public: + /* This constructor takes a pointer rather than a reference to make it clearer that the pointed-to + objects must outlive the IntersectingRangedValues instance. Passing a pointer also makes + it harder to accidentally reference a temporary when constructing IntersectingRangedValues. + */ + explicit IntersectingRangedValues (const RangedValues*... t) + : items { t... } + { + } + + struct IntersectionIteratorSentinel {}; + + struct IntersectionIterator + { + using reference = std::tuple, const Values&...>; + using iterator_category = std::forward_iterator_tag; + + using IteratorWrappersType = decltype (createIteratorWrappers (std::declval*>()...)); + + explicit IntersectionIterator (IteratorWrappersType&& wrappers) + : iteratorWrappers { std::move (wrappers) } + { + std::apply ([this] (auto&&... args) + { + iterators = std::list { static_cast (&args)... }; + }, + iteratorWrappers); + + if (! isValid()) + return; + + maxStart = std::accumulate (iterators.cbegin(), + iterators.cend(), + std::numeric_limits::min(), + [] (auto acc, auto& item) { return std::max (acc, item->getRange().getStart()); }); + + minEnd = std::accumulate (iterators.cbegin(), + iterators.cend(), + std::numeric_limits::max(), + [] (auto acc, auto& item) { return std::min (acc, item->getRange().getEnd()); }); + + iterators.sort ([] (auto& a, auto& b) + { return a->getRange().getEnd() < b->getRange().getEnd(); }); + + if (Range { maxStart, minEnd }.isEmpty()) + advance(); + } + + friend bool operator!= (IntersectionIterator a, IntersectionIteratorSentinel) { return a.isValid(); } + + IntersectionIterator& operator++() { advance(); return *this; } + IntersectionIterator operator++ (int) const { IntersectionIterator copy (*this); ++(*this); return copy; } + + reference operator* () const + { + return std::tuple_cat (std::make_tuple (Range { maxStart, minEnd }), makeReference()); + } + + private: + auto makeReference() const + { + return std::apply ([] (auto&... args) { return std::tie (args.getValue()...); }, iteratorWrappers); + } + + void advance() + { + do + { + minEnd = std::numeric_limits::max(); + + for (auto it = iterators.begin(); it != iterators.end(); ++it) + { + auto& elem = *(*it); + + if (it == iterators.begin() || elem.getRange().getEnd() <= maxStart) + { + ++elem; + + if (! elem.isValid()) + return; + + maxStart = std::max (maxStart, elem.getRange().getStart()); + } + + minEnd = std::min (minEnd, elem.getRange().getEnd()); + } + + iterators.sort ([] (auto& a, auto& b) + { return a->getRange().getEnd() < b->getRange().getEnd(); }); + } + while (Range { maxStart, minEnd }.isEmpty()); + } + + bool isValid() const + { + return std::all_of (iterators.cbegin(), iterators.cend(), [] (auto& elem) { return elem->isValid(); }); + } + + IteratorWrappersType iteratorWrappers; + std::list iterators; + + int64 maxStart, minEnd; + }; + + auto begin() const + { + auto wrappers = std::apply ([](auto&&... args) + { return createIteratorWrappers (std::forward (args)...); }, + items); + + return IntersectionIterator { std::move (wrappers) }; + } + + auto end() const + { + return IntersectionIteratorSentinel{}; + } + +private: + std::tuple*...> items; +}; + +/* See IntersectingRangedValues. +*/ +template +[[nodiscard]] auto makeIntersectingRangedValues (const RangedValues*... rvs) +{ + static_assert (sizeof...(Values) > 0, "makeIntersectingRangedValues() requires at least one parameter"); + + return IntersectingRangedValues...> { rvs... }; +} + +} // namespace juce::detail diff --git a/modules/juce_graphics/juce_graphics.cpp b/modules/juce_graphics/juce_graphics.cpp index 85f4f3c931..b36197e59f 100644 --- a/modules/juce_graphics/juce_graphics.cpp +++ b/modules/juce_graphics/juce_graphics.cpp @@ -145,6 +145,7 @@ #include "fonts/juce_Typeface.cpp" #include "fonts/juce_FontOptions.cpp" #include "fonts/juce_Font.cpp" +#include "detail/juce_Ranges.cpp" #include "fonts/juce_GlyphArrangement.cpp" #include "fonts/juce_TextLayout.cpp" #include "effects/juce_DropShadowEffect.cpp" diff --git a/modules/juce_graphics/juce_graphics.h b/modules/juce_graphics/juce_graphics.h index 683f98cef9..2bc4ff8652 100644 --- a/modules/juce_graphics/juce_graphics.h +++ b/modules/juce_graphics/juce_graphics.h @@ -146,6 +146,7 @@ namespace juce #include "fonts/juce_Typeface.h" #include "fonts/juce_FontOptions.h" #include "fonts/juce_Font.h" +#include "detail/juce_Ranges.h" #include "fonts/juce_AttributedString.h" #include "fonts/juce_GlyphArrangement.h" #include "fonts/juce_TextLayout.h"