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"