6
									
								
								.gitmodules
									
										
									
									
										vendored
									
									
								
							
							
						
						|  | @ -31,3 +31,9 @@ | ||||||
| [submodule "external/date"] | [submodule "external/date"] | ||||||
| 	path = external/date | 	path = external/date | ||||||
| 	url = https://github.com/HowardHinnant/date.git | 	url = https://github.com/HowardHinnant/date.git | ||||||
|  | [submodule "external/units"] | ||||||
|  | 	path = external/units | ||||||
|  | 	url = https://github.com/nholthaus/units.git | ||||||
|  | [submodule "external/textflowcpp"] | ||||||
|  | 	path = external/textflowcpp | ||||||
|  | 	url = https://github.com/catchorg/textflowcpp.git | ||||||
|  |  | ||||||
|  | @ -18,6 +18,7 @@ Supercell Wx uses code from the following dependencies: | ||||||
| | [CSS Color Parser](https://github.com/deanm/css-color-parser-js) | [MIT License](https://spdx.org/licenses/MIT.html) | Ported to C++ for MapLibre Native | | | [CSS Color Parser](https://github.com/deanm/css-color-parser-js) | [MIT License](https://spdx.org/licenses/MIT.html) | Ported to C++ for MapLibre Native | | ||||||
| | [Date](https://github.com/HowardHinnant/date) | [MIT License](https://spdx.org/licenses/MIT.html) | | | [Date](https://github.com/HowardHinnant/date) | [MIT License](https://spdx.org/licenses/MIT.html) | | ||||||
| | [Dear ImGui](https://github.com/ocornut/imgui) | [MIT License](https://spdx.org/licenses/MIT.html) | | | [Dear ImGui](https://github.com/ocornut/imgui) | [MIT License](https://spdx.org/licenses/MIT.html) | | ||||||
|  | | [fontconfig](http://fontconfig.org/) | [MIT License](https://spdx.org/licenses/MIT.html) | | ||||||
| | [FreeType](https://freetype.org/) | [Freetype Project License](https://spdx.org/licenses/FTL.html) | | | [FreeType](https://freetype.org/) | [Freetype Project License](https://spdx.org/licenses/FTL.html) | | ||||||
| | [FreeType GL](https://github.com/rougier/freetype-gl) | [BSD 2-Clause with views sentence](https://spdx.org/licenses/BSD-2-Clause-Views.html) | | | [FreeType GL](https://github.com/rougier/freetype-gl) | [BSD 2-Clause with views sentence](https://spdx.org/licenses/BSD-2-Clause-Views.html) | | ||||||
| | [GeographicLib](https://geographiclib.sourceforge.io/) | [MIT License](https://spdx.org/licenses/MIT.html) | | | [GeographicLib](https://geographiclib.sourceforge.io/) | [MIT License](https://spdx.org/licenses/MIT.html) | | ||||||
|  | @ -36,6 +37,8 @@ Supercell Wx uses code from the following dependencies: | ||||||
| | [spdlog](https://github.com/gabime/spdlog) | [MIT License](https://spdx.org/licenses/MIT.html) | | | [spdlog](https://github.com/gabime/spdlog) | [MIT License](https://spdx.org/licenses/MIT.html) | | ||||||
| | [SQLite](https://www.sqlite.org/) | Public Domain | | | [SQLite](https://www.sqlite.org/) | Public Domain | | ||||||
| | [stb](https://github.com/nothings/stb) | Public Domain | | | [stb](https://github.com/nothings/stb) | Public Domain | | ||||||
|  | | [TextFlowCpp](https://github.com/catchorg/textflowcpp) | [Boost Software License 1.0](https://spdx.org/licenses/BSL-1.0.html) | | ||||||
|  | | [Units](https://github.com/nholthaus/units) | [MIT License](https://spdx.org/licenses/MIT.html) | | ||||||
| | [Vulkan SDK](https://www.vulkan.org/) | [Apache License 2.0](https://spdx.org/licenses/Apache-2.0.html) | | | [Vulkan SDK](https://www.vulkan.org/) | [Apache License 2.0](https://spdx.org/licenses/Apache-2.0.html) | | ||||||
| | [zlib](https://zlib.net/) | [zlib License](https://spdx.org/licenses/Zlib.html) | | | [zlib](https://zlib.net/) | [zlib License](https://spdx.org/licenses/Zlib.html) | | ||||||
| 
 | 
 | ||||||
|  | @ -55,7 +58,9 @@ Supercell Wx uses assets from the following sources: | ||||||
| 
 | 
 | ||||||
| | Source | License | Notes | | | Source | License | Notes | | ||||||
| | ------ | ------- | ----- | | | ------ | ------- | ----- | | ||||||
|  | | Alte DIN 1451 Mittelschrift | SIL Open Font License | | ||||||
| | [Font Awesome Free](https://fontawesome.com/) | CC BY 4.0 License | | | [Font Awesome Free](https://fontawesome.com/) | CC BY 4.0 License | | ||||||
|  | | [Inconsolata](https://fonts.google.com/specimen/Inconsolata) | SIL Open Font License | | ||||||
| | [NOAA's Weather and Climate Toolkit](https://www.ncdc.noaa.gov/wct/) | Public Domain | Default Color Tables | | | [NOAA's Weather and Climate Toolkit](https://www.ncdc.noaa.gov/wct/) | Public Domain | Default Color Tables | | ||||||
| | [Supercell thunderstorm with dramatic clouds](https://www.shutterstock.com/image-photo/supercell-thunderstorm-dramatic-clouds-1354353521) | Shutterstock Standard License | Photo by John Sirlin | | [Supercell thunderstorm with dramatic clouds](https://www.shutterstock.com/image-photo/supercell-thunderstorm-dramatic-clouds-1354353521) | Shutterstock Standard License | Photo by John Sirlin | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -4,6 +4,7 @@ class SupercellWxConan(ConanFile): | ||||||
|     settings   = ("os", "compiler", "build_type", "arch") |     settings   = ("os", "compiler", "build_type", "arch") | ||||||
|     requires   = ("boost/1.81.0", |     requires   = ("boost/1.81.0", | ||||||
|                   "cpr/1.9.3", |                   "cpr/1.9.3", | ||||||
|  |                   "fontconfig/2.14.2", | ||||||
|                   "freetype/2.12.1", |                   "freetype/2.12.1", | ||||||
|                   "geographiclib/1.52", |                   "geographiclib/1.52", | ||||||
|                   "glew/2.2.0", |                   "glew/2.2.0", | ||||||
|  |  | ||||||
							
								
								
									
										6
									
								
								external/CMakeLists.txt
									
										
									
									
										vendored
									
									
								
							
							
						
						|  | @ -10,7 +10,9 @@ set_property(DIRECTORY | ||||||
|              hsluv-c.cmake |              hsluv-c.cmake | ||||||
|              imgui.cmake |              imgui.cmake | ||||||
|              mapbox-gl-native.cmake |              mapbox-gl-native.cmake | ||||||
|              stb.cmake) |              stb.cmake | ||||||
|  |              textflowcpp.cmake | ||||||
|  |              units.cmake) | ||||||
| 
 | 
 | ||||||
| include(aws-sdk-cpp.cmake) | include(aws-sdk-cpp.cmake) | ||||||
| include(date.cmake) | include(date.cmake) | ||||||
|  | @ -19,3 +21,5 @@ include(hsluv-c.cmake) | ||||||
| include(imgui.cmake) | include(imgui.cmake) | ||||||
| include(mapbox-gl-native.cmake) | include(mapbox-gl-native.cmake) | ||||||
| include(stb.cmake) | include(stb.cmake) | ||||||
|  | include(textflowcpp.cmake) | ||||||
|  | include(units.cmake) | ||||||
|  |  | ||||||
							
								
								
									
										2
									
								
								external/mapbox-gl-native
									
										
									
									
										vendored
									
									
								
							
							
						
						|  | @ -1 +1 @@ | ||||||
| Subproject commit fbb06ff53e74d3a81b434b84fff1a5dfe4b2d3c7 | Subproject commit 3e85454fe5e571e7b235131912bb867ef9d75c3c | ||||||
							
								
								
									
										2
									
								
								external/stb
									
										
									
									
										vendored
									
									
								
							
							
						
						|  | @ -1 +1 @@ | ||||||
| Subproject commit 8b5f1f37b5b75829fc72d38e7b5d4bcbf8a26d55 | Subproject commit 5736b15f7ea0ffb08dd38af21067c314d6a3aae9 | ||||||
							
								
								
									
										1
									
								
								external/textflowcpp
									
										
									
									
										vendored
									
									
										Submodule
									
								
							
							
						
						|  | @ -0,0 +1 @@ | ||||||
|  | Subproject commit 12010ddc8d15538ceea20622d22977e7c5a25da5 | ||||||
							
								
								
									
										4
									
								
								external/textflowcpp.cmake
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						|  | @ -0,0 +1,4 @@ | ||||||
|  | cmake_minimum_required(VERSION 3.20) | ||||||
|  | set(PROJECT_NAME scwx-textflowcpp) | ||||||
|  | 
 | ||||||
|  | set(TEXTFLOWCPP_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/textflowcpp PARENT_SCOPE) | ||||||
							
								
								
									
										1
									
								
								external/units
									
										
									
									
										vendored
									
									
										Submodule
									
								
							
							
						
						|  | @ -0,0 +1 @@ | ||||||
|  | Subproject commit da6dd9176e8515323c75030d5e51ee19cf6c9afd | ||||||
							
								
								
									
										4
									
								
								external/units.cmake
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						|  | @ -0,0 +1,4 @@ | ||||||
|  | cmake_minimum_required(VERSION 3.20) | ||||||
|  | set(PROJECT_NAME scwx-units) | ||||||
|  | 
 | ||||||
|  | add_subdirectory(units) | ||||||
|  | @ -1,5 +1,5 @@ | ||||||
| #version 330 core | #version 330 core | ||||||
| in vec4 color; | smooth in vec4 color; | ||||||
| 
 | 
 | ||||||
| layout (location = 0) out vec4 fragColor; | layout (location = 0) out vec4 fragColor; | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -4,7 +4,7 @@ layout (location = 1) in vec4 aColor; | ||||||
| 
 | 
 | ||||||
| uniform mat4 uMVPMatrix; | uniform mat4 uMVPMatrix; | ||||||
| 
 | 
 | ||||||
| out vec4 color; | smooth out vec4 color; | ||||||
| 
 | 
 | ||||||
| void main() | void main() | ||||||
| { | { | ||||||
|  |  | ||||||
|  | @ -8,15 +8,15 @@ | ||||||
| 
 | 
 | ||||||
| layout (location = 0) in vec2 aLatLong; | layout (location = 0) in vec2 aLatLong; | ||||||
| layout (location = 1) in vec2 aXYOffset; | layout (location = 1) in vec2 aXYOffset; | ||||||
| layout (location = 2) in vec2 aTexCoord; | layout (location = 2) in vec3 aTexCoord; | ||||||
| layout (location = 3) in vec4 aModulate; | layout (location = 3) in vec4 aModulate; | ||||||
| 
 | 
 | ||||||
| uniform mat4 uMVPMatrix; | uniform mat4 uMVPMatrix; | ||||||
| uniform mat4 uMapMatrix; | uniform mat4 uMapMatrix; | ||||||
| uniform vec2 uMapScreenCoord; | uniform vec2 uMapScreenCoord; | ||||||
| 
 | 
 | ||||||
| smooth out vec2 texCoord; | smooth out vec3 texCoord; | ||||||
| flat   out vec4 modulate; | smooth out vec4 color; | ||||||
| 
 | 
 | ||||||
| vec2 latLngToScreenCoordinate(in vec2 latLng) | vec2 latLngToScreenCoordinate(in vec2 latLng) | ||||||
| { | { | ||||||
|  | @ -31,7 +31,7 @@ void main() | ||||||
| { | { | ||||||
|    // Pass the texture coordinate and color modulate to the fragment shader |    // Pass the texture coordinate and color modulate to the fragment shader | ||||||
|    texCoord = aTexCoord; |    texCoord = aTexCoord; | ||||||
|    modulate = aModulate; |    color    = aModulate; | ||||||
| 
 | 
 | ||||||
|    vec2 p = latLngToScreenCoordinate(aLatLong) - uMapScreenCoord; |    vec2 p = latLngToScreenCoordinate(aLatLong) - uMapScreenCoord; | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
							
								
								
									
										65
									
								
								scwx-qt/gl/geo_texture2d.vert
									
										
									
									
									
										Normal file
									
								
							
							
						
						|  | @ -0,0 +1,65 @@ | ||||||
|  | #version 330 core | ||||||
|  | 
 | ||||||
|  | #define DEGREES_MAX   360.0f | ||||||
|  | #define LATITUDE_MAX  85.051128779806604f | ||||||
|  | #define LONGITUDE_MAX 180.0f | ||||||
|  | #define PI            3.1415926535897932384626433f | ||||||
|  | #define RAD2DEG       57.295779513082320876798156332941f | ||||||
|  | #define DEG2RAD       0.0174532925199432957692369055556f | ||||||
|  | 
 | ||||||
|  | layout (location = 0) in vec2  aLatLong; | ||||||
|  | layout (location = 1) in vec2  aXYOffset; | ||||||
|  | layout (location = 2) in vec3  aTexCoord; | ||||||
|  | layout (location = 3) in vec4  aModulate; | ||||||
|  | layout (location = 4) in float aAngleDeg; | ||||||
|  | layout (location = 5) in int   aThreshold; | ||||||
|  | layout (location = 6) in ivec2 aTimeRange; | ||||||
|  | 
 | ||||||
|  | uniform mat4 uMVPMatrix; | ||||||
|  | uniform mat4 uMapMatrix; | ||||||
|  | uniform vec2 uMapScreenCoord; | ||||||
|  | 
 | ||||||
|  | out VertexData | ||||||
|  | { | ||||||
|  |    int   threshold; | ||||||
|  |    vec3  texCoord; | ||||||
|  |    vec4  color; | ||||||
|  |    ivec2 timeRange; | ||||||
|  | } vsOut; | ||||||
|  | 
 | ||||||
|  | smooth out vec3 texCoord; | ||||||
|  | smooth out vec4 color; | ||||||
|  | 
 | ||||||
|  | vec2 latLngToScreenCoordinate(in vec2 latLng) | ||||||
|  | { | ||||||
|  |    vec2 p; | ||||||
|  |    latLng.x = clamp(latLng.x, -LATITUDE_MAX, LATITUDE_MAX); | ||||||
|  |    p.xy     = vec2(LONGITUDE_MAX + latLng.y, | ||||||
|  |                    -(LONGITUDE_MAX - RAD2DEG * log(tan(PI / 4 + latLng.x * PI / DEGREES_MAX)))); | ||||||
|  |    return p; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void main() | ||||||
|  | { | ||||||
|  |    // Pass the threshold and time range to the geometry shader | ||||||
|  |    vsOut.threshold = aThreshold; | ||||||
|  |    vsOut.timeRange = aTimeRange; | ||||||
|  | 
 | ||||||
|  |    // Pass the texture coordinate and color modulate to the geometry and | ||||||
|  |    // fragment shaders | ||||||
|  |    vsOut.texCoord = aTexCoord; | ||||||
|  |    vsOut.color    = aModulate; | ||||||
|  |    texCoord       = aTexCoord; | ||||||
|  |    color          = aModulate; | ||||||
|  | 
 | ||||||
|  |    vec2 p = latLngToScreenCoordinate(aLatLong) - uMapScreenCoord; | ||||||
|  | 
 | ||||||
|  |    // Rotate clockwise | ||||||
|  |    float angle  = aAngleDeg * DEG2RAD; | ||||||
|  |    mat2  rotate = mat2(cos(angle), -sin(angle), | ||||||
|  |                        sin(angle), cos(angle)); | ||||||
|  | 
 | ||||||
|  |    // Transform the position to screen coordinates | ||||||
|  |    gl_Position = uMapMatrix * vec4(p, 0.0f, 1.0f) + | ||||||
|  |                  uMVPMatrix * vec4(rotate * aXYOffset, 0.0f, 0.0f); | ||||||
|  | } | ||||||
							
								
								
									
										38
									
								
								scwx-qt/gl/map_color.vert
									
										
									
									
									
										Normal file
									
								
							
							
						
						|  | @ -0,0 +1,38 @@ | ||||||
|  | #version 330 core | ||||||
|  | 
 | ||||||
|  | layout (location = 0) in vec2  aScreenCoord; | ||||||
|  | layout (location = 1) in vec2  aXYOffset; | ||||||
|  | layout (location = 2) in vec4  aColor; | ||||||
|  | layout (location = 3) in int   aThreshold; | ||||||
|  | layout (location = 4) in ivec2 aTimeRange; | ||||||
|  | 
 | ||||||
|  | uniform mat4 uMVPMatrix; | ||||||
|  | uniform mat4 uMapMatrix; | ||||||
|  | uniform vec2 uMapScreenCoord; | ||||||
|  | 
 | ||||||
|  | out VertexData | ||||||
|  | { | ||||||
|  |    int   threshold; | ||||||
|  |    vec3  texCoord; | ||||||
|  |    vec4  color; | ||||||
|  |    ivec2 timeRange; | ||||||
|  | } vsOut; | ||||||
|  | 
 | ||||||
|  | smooth out vec4 color; | ||||||
|  | 
 | ||||||
|  | void main() | ||||||
|  | { | ||||||
|  |    // Pass the threshold and time range to the geometry shader | ||||||
|  |    vsOut.threshold = aThreshold; | ||||||
|  |    vsOut.timeRange = aTimeRange; | ||||||
|  | 
 | ||||||
|  |    // Pass the color to the geometry and fragment shaders | ||||||
|  |    vsOut.color = aColor; | ||||||
|  |    color       = aColor; | ||||||
|  | 
 | ||||||
|  |    vec2 p = aScreenCoord - uMapScreenCoord; | ||||||
|  | 
 | ||||||
|  |    // Transform the position to screen coordinates | ||||||
|  |    gl_Position = uMapMatrix * vec4(p, 0.0f, 1.0f) + | ||||||
|  |                  uMVPMatrix * vec4(aXYOffset, 0.0f, 0.0f); | ||||||
|  | } | ||||||
|  | @ -6,11 +6,11 @@ precision mediump float; | ||||||
| uniform sampler2D uTexture; | uniform sampler2D uTexture; | ||||||
| 
 | 
 | ||||||
| smooth in vec2 texCoord; | smooth in vec2 texCoord; | ||||||
| flat   in vec4 modulate; | smooth in vec4 color; | ||||||
| 
 | 
 | ||||||
| layout (location = 0) out vec4 fragColor; | layout (location = 0) out vec4 fragColor; | ||||||
| 
 | 
 | ||||||
| void main() | void main() | ||||||
| { | { | ||||||
|    fragColor = texture(uTexture, texCoord) * modulate; |    fragColor = texture(uTexture, texCoord) * color; | ||||||
| } | } | ||||||
|  |  | ||||||
							
								
								
									
										16
									
								
								scwx-qt/gl/texture2d_array.frag
									
										
									
									
									
										Normal file
									
								
							
							
						
						|  | @ -0,0 +1,16 @@ | ||||||
|  | #version 330 core | ||||||
|  | 
 | ||||||
|  | // Lower the default precision to medium | ||||||
|  | precision mediump float; | ||||||
|  | 
 | ||||||
|  | uniform sampler2DArray uTexture; | ||||||
|  | 
 | ||||||
|  | smooth in vec3 texCoord; | ||||||
|  | smooth in vec4 color; | ||||||
|  | 
 | ||||||
|  | layout (location = 0) out vec4 fragColor; | ||||||
|  | 
 | ||||||
|  | void main() | ||||||
|  | { | ||||||
|  |    fragColor = texture(uTexture, texCoord) * color; | ||||||
|  | } | ||||||
							
								
								
									
										46
									
								
								scwx-qt/gl/threshold.geom
									
										
									
									
									
										Normal file
									
								
							
							
						
						|  | @ -0,0 +1,46 @@ | ||||||
|  | #version 330 core | ||||||
|  | 
 | ||||||
|  | layout (triangles) in; | ||||||
|  | layout (triangle_strip, max_vertices = 3) out; | ||||||
|  | 
 | ||||||
|  | uniform float uMapDistance; | ||||||
|  | uniform int   uSelectedTime; | ||||||
|  | 
 | ||||||
|  | in VertexData | ||||||
|  | { | ||||||
|  |    int   threshold; | ||||||
|  |    vec3  texCoord; | ||||||
|  |    vec4  color; | ||||||
|  |    ivec2 timeRange; | ||||||
|  | } gsIn[]; | ||||||
|  | 
 | ||||||
|  | smooth out vec3 texCoord; | ||||||
|  | smooth out vec4 color; | ||||||
|  | 
 | ||||||
|  | void main() | ||||||
|  | { | ||||||
|  |    if ((gsIn[0].threshold <= 0 ||            // If Threshold: 0 was specified, no threshold | ||||||
|  |         gsIn[0].threshold >= uMapDistance || // If Threshold is above current map distance | ||||||
|  |         gsIn[0].threshold >= 999) &&         // If Threshold: 999 was specified (or greater), no threshold | ||||||
|  |        (gsIn[0].timeRange[0] == 0 ||              // If there is no start time specified | ||||||
|  |         (gsIn[0].timeRange[0] <= uSelectedTime && // If the selected time is after the start time | ||||||
|  |          uSelectedTime < gsIn[0].timeRange[1])))  // If the selected time is before the end time | ||||||
|  |    { | ||||||
|  |       gl_Position = gl_in[0].gl_Position; | ||||||
|  |       texCoord    = gsIn[0].texCoord; | ||||||
|  |       color       = gsIn[0].color; | ||||||
|  |       EmitVertex(); | ||||||
|  | 
 | ||||||
|  |       gl_Position = gl_in[1].gl_Position; | ||||||
|  |       texCoord    = gsIn[1].texCoord; | ||||||
|  |       color       = gsIn[1].color; | ||||||
|  | 
 | ||||||
|  |       EmitVertex(); | ||||||
|  |       gl_Position = gl_in[2].gl_Position; | ||||||
|  |       texCoord    = gsIn[2].texCoord; | ||||||
|  |       color       = gsIn[2].color; | ||||||
|  | 
 | ||||||
|  |       EmitVertex(); | ||||||
|  |       EndPrimitive(); | ||||||
|  |    } | ||||||
|  | } | ||||||
							
								
								
									
										
											BIN
										
									
								
								scwx-qt/res/fonts/Inconsolata-Regular.ttf
									
										
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										1
									
								
								scwx-qt/res/icons/font-awesome-6/angle-down-solid.svg
									
										
									
									
									
										Normal file
									
								
							
							
						
						|  | @ -0,0 +1 @@ | ||||||
|  | <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!--! Font Awesome Pro 6.4.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. --><path d="M201.4 342.6c12.5 12.5 32.8 12.5 45.3 0l160-160c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L224 274.7 86.6 137.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3l160 160z"/></svg> | ||||||
| After Width: | Height: | Size: 416 B | 
							
								
								
									
										1
									
								
								scwx-qt/res/icons/font-awesome-6/angle-up-solid.svg
									
										
									
									
									
										Normal file
									
								
							
							
						
						|  | @ -0,0 +1 @@ | ||||||
|  | <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!--! Font Awesome Pro 6.4.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. --><path d="M201.4 137.4c12.5-12.5 32.8-12.5 45.3 0l160 160c12.5 12.5 12.5 32.8 0 45.3s-32.8 12.5-45.3 0L224 205.3 86.6 342.6c-12.5 12.5-32.8 12.5-45.3 0s-12.5-32.8 0-45.3l160-160z"/></svg> | ||||||
| After Width: | Height: | Size: 416 B | 
							
								
								
									
										1
									
								
								scwx-qt/res/icons/font-awesome-6/angles-down-solid.svg
									
										
									
									
									
										Normal file
									
								
							
							
						
						|  | @ -0,0 +1 @@ | ||||||
|  | <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!--! Font Awesome Pro 6.4.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. --><path d="M246.6 470.6c-12.5 12.5-32.8 12.5-45.3 0l-160-160c-12.5-12.5-12.5-32.8 0-45.3s32.8-12.5 45.3 0L224 402.7 361.4 265.4c12.5-12.5 32.8-12.5 45.3 0s12.5 32.8 0 45.3l-160 160zm160-352l-160 160c-12.5 12.5-32.8 12.5-45.3 0l-160-160c-12.5-12.5-12.5-32.8 0-45.3s32.8-12.5 45.3 0L224 210.7 361.4 73.4c12.5-12.5 32.8-12.5 45.3 0s12.5 32.8 0 45.3z"/></svg> | ||||||
| After Width: | Height: | Size: 583 B | 
							
								
								
									
										1
									
								
								scwx-qt/res/icons/font-awesome-6/angles-up-solid.svg
									
										
									
									
									
										Normal file
									
								
							
							
						
						|  | @ -0,0 +1 @@ | ||||||
|  | <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!--! Font Awesome Pro 6.4.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. --><path d="M246.6 41.4c-12.5-12.5-32.8-12.5-45.3 0l-160 160c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L224 109.3 361.4 246.6c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3l-160-160zm160 352l-160-160c-12.5-12.5-32.8-12.5-45.3 0l-160 160c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L224 301.3 361.4 438.6c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3z"/></svg> | ||||||
| After Width: | Height: | Size: 583 B | 
|  | @ -0,0 +1 @@ | ||||||
|  | <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--! Font Awesome Pro 6.4.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. --><path d="M57.7 193l9.4 16.4c8.3 14.5 21.9 25.2 38 29.8L163 255.7c17.2 4.9 29 20.6 29 38.5v39.9c0 11 6.2 21 16 25.9s16 14.9 16 25.9v39c0 15.6 14.9 26.9 29.9 22.6c16.1-4.6 28.6-17.5 32.7-33.8l2.8-11.2c4.2-16.9 15.2-31.4 30.3-40l8.1-4.6c15-8.5 24.2-24.5 24.2-41.7v-8.3c0-12.7-5.1-24.9-14.1-33.9l-3.9-3.9c-9-9-21.2-14.1-33.9-14.1H257c-11.1 0-22.1-2.9-31.8-8.4l-34.5-19.7c-4.3-2.5-7.6-6.5-9.2-11.2c-3.2-9.6 1.1-20 10.2-24.5l5.9-3c6.6-3.3 14.3-3.9 21.3-1.5l23.2 7.7c8.2 2.7 17.2-.4 21.9-7.5c4.7-7 4.2-16.3-1.2-22.8l-13.6-16.3c-10-12-9.9-29.5 .3-41.3l15.7-18.3c8.8-10.3 10.2-25 3.5-36.7l-2.4-4.2c-3.5-.2-6.9-.3-10.4-.3C163.1 48 84.4 108.9 57.7 193zM464 256c0-36.8-9.6-71.4-26.4-101.5L412 164.8c-15.7 6.3-23.8 23.8-18.5 39.8l16.9 50.7c3.5 10.4 12 18.3 22.6 20.9l29.1 7.3c1.2-9 1.8-18.2 1.8-27.5zM0 256a256 256 0 1 1 512 0A256 256 0 1 1 0 256z"/></svg> | ||||||
| After Width: | Height: | Size: 1 KiB | 
							
								
								
									
										1
									
								
								scwx-qt/res/icons/font-awesome-6/font-solid.svg
									
										
									
									
									
										Normal file
									
								
							
							
						
						|  | @ -0,0 +1 @@ | ||||||
|  | <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!--! Font Awesome Pro 6.4.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. --><path d="M254 52.8C249.3 40.3 237.3 32 224 32s-25.3 8.3-30 20.8L57.8 416H32c-17.7 0-32 14.3-32 32s14.3 32 32 32h96c17.7 0 32-14.3 32-32s-14.3-32-32-32h-1.8l18-48H303.8l18 48H320c-17.7 0-32 14.3-32 32s14.3 32 32 32h96c17.7 0 32-14.3 32-32s-14.3-32-32-32H390.2L254 52.8zM279.8 304H168.2L224 155.1 279.8 304z"/></svg> | ||||||
| After Width: | Height: | Size: 544 B | 
							
								
								
									
										1
									
								
								scwx-qt/res/icons/font-awesome-6/layer-group-solid.svg
									
										
									
									
									
										Normal file
									
								
							
							
						
						|  | @ -0,0 +1 @@ | ||||||
|  | <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><!--! Font Awesome Pro 6.4.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. --><path d="M264.5 5.2c14.9-6.9 32.1-6.9 47 0l218.6 101c8.5 3.9 13.9 12.4 13.9 21.8s-5.4 17.9-13.9 21.8l-218.6 101c-14.9 6.9-32.1 6.9-47 0L45.9 149.8C37.4 145.8 32 137.3 32 128s5.4-17.9 13.9-21.8L264.5 5.2zM476.9 209.6l53.2 24.6c8.5 3.9 13.9 12.4 13.9 21.8s-5.4 17.9-13.9 21.8l-218.6 101c-14.9 6.9-32.1 6.9-47 0L45.9 277.8C37.4 273.8 32 265.3 32 256s5.4-17.9 13.9-21.8l53.2-24.6 152 70.2c23.4 10.8 50.4 10.8 73.8 0l152-70.2zm-152 198.2l152-70.2 53.2 24.6c8.5 3.9 13.9 12.4 13.9 21.8s-5.4 17.9-13.9 21.8l-218.6 101c-14.9 6.9-32.1 6.9-47 0L45.9 405.8C37.4 401.8 32 393.3 32 384s5.4-17.9 13.9-21.8l53.2-24.6 152 70.2c23.4 10.8 50.4 10.8 73.8 0z"/></svg> | ||||||
| After Width: | Height: | Size: 877 B | 
|  | @ -12,6 +12,7 @@ set(CMAKE_CXX_STANDARD 20) | ||||||
| set(CMAKE_CXX_STANDARD_REQUIRED ON) | set(CMAKE_CXX_STANDARD_REQUIRED ON) | ||||||
| 
 | 
 | ||||||
| find_package(Boost) | find_package(Boost) | ||||||
|  | find_package(Fontconfig) | ||||||
| find_package(Freetype) | find_package(Freetype) | ||||||
| find_package(geographiclib) | find_package(geographiclib) | ||||||
| find_package(glm) | find_package(glm) | ||||||
|  | @ -46,7 +47,8 @@ set(HDR_CONFIG source/scwx/qt/config/county_database.hpp | ||||||
|                source/scwx/qt/config/radar_site.hpp) |                source/scwx/qt/config/radar_site.hpp) | ||||||
| set(SRC_CONFIG source/scwx/qt/config/county_database.cpp | set(SRC_CONFIG source/scwx/qt/config/county_database.cpp | ||||||
|                source/scwx/qt/config/radar_site.cpp) |                source/scwx/qt/config/radar_site.cpp) | ||||||
| set(SRC_EXTERNAL source/scwx/qt/external/stb_rect_pack.cpp) | set(SRC_EXTERNAL source/scwx/qt/external/stb_image.cpp | ||||||
|  |                  source/scwx/qt/external/stb_rect_pack.cpp) | ||||||
| set(HDR_GL source/scwx/qt/gl/gl.hpp | set(HDR_GL source/scwx/qt/gl/gl.hpp | ||||||
|            source/scwx/qt/gl/gl_context.hpp |            source/scwx/qt/gl/gl_context.hpp | ||||||
|            source/scwx/qt/gl/shader_program.hpp |            source/scwx/qt/gl/shader_program.hpp | ||||||
|  | @ -56,18 +58,34 @@ set(SRC_GL source/scwx/qt/gl/gl_context.cpp | ||||||
|            source/scwx/qt/gl/text_shader.cpp) |            source/scwx/qt/gl/text_shader.cpp) | ||||||
| set(HDR_GL_DRAW source/scwx/qt/gl/draw/draw_item.hpp | set(HDR_GL_DRAW source/scwx/qt/gl/draw/draw_item.hpp | ||||||
|                 source/scwx/qt/gl/draw/geo_line.hpp |                 source/scwx/qt/gl/draw/geo_line.hpp | ||||||
|  |                 source/scwx/qt/gl/draw/placefile_icons.hpp | ||||||
|  |                 source/scwx/qt/gl/draw/placefile_images.hpp | ||||||
|  |                 source/scwx/qt/gl/draw/placefile_lines.hpp | ||||||
|  |                 source/scwx/qt/gl/draw/placefile_polygons.hpp | ||||||
|  |                 source/scwx/qt/gl/draw/placefile_text.hpp | ||||||
|  |                 source/scwx/qt/gl/draw/placefile_triangles.hpp | ||||||
|                 source/scwx/qt/gl/draw/rectangle.hpp) |                 source/scwx/qt/gl/draw/rectangle.hpp) | ||||||
| set(SRC_GL_DRAW source/scwx/qt/gl/draw/draw_item.cpp | set(SRC_GL_DRAW source/scwx/qt/gl/draw/draw_item.cpp | ||||||
|                 source/scwx/qt/gl/draw/geo_line.cpp |                 source/scwx/qt/gl/draw/geo_line.cpp | ||||||
|  |                 source/scwx/qt/gl/draw/placefile_icons.cpp | ||||||
|  |                 source/scwx/qt/gl/draw/placefile_images.cpp | ||||||
|  |                 source/scwx/qt/gl/draw/placefile_lines.cpp | ||||||
|  |                 source/scwx/qt/gl/draw/placefile_polygons.cpp | ||||||
|  |                 source/scwx/qt/gl/draw/placefile_text.cpp | ||||||
|  |                 source/scwx/qt/gl/draw/placefile_triangles.cpp | ||||||
|                 source/scwx/qt/gl/draw/rectangle.cpp) |                 source/scwx/qt/gl/draw/rectangle.cpp) | ||||||
| set(HDR_MANAGER source/scwx/qt/manager/radar_product_manager.hpp | set(HDR_MANAGER source/scwx/qt/manager/font_manager.hpp | ||||||
|  |                 source/scwx/qt/manager/placefile_manager.hpp | ||||||
|  |                 source/scwx/qt/manager/radar_product_manager.hpp | ||||||
|                 source/scwx/qt/manager/radar_product_manager_notifier.hpp |                 source/scwx/qt/manager/radar_product_manager_notifier.hpp | ||||||
|                 source/scwx/qt/manager/resource_manager.hpp |                 source/scwx/qt/manager/resource_manager.hpp | ||||||
|                 source/scwx/qt/manager/settings_manager.hpp |                 source/scwx/qt/manager/settings_manager.hpp | ||||||
|                 source/scwx/qt/manager/text_event_manager.hpp |                 source/scwx/qt/manager/text_event_manager.hpp | ||||||
|                 source/scwx/qt/manager/timeline_manager.hpp |                 source/scwx/qt/manager/timeline_manager.hpp | ||||||
|                 source/scwx/qt/manager/update_manager.hpp) |                 source/scwx/qt/manager/update_manager.hpp) | ||||||
| set(SRC_MANAGER source/scwx/qt/manager/radar_product_manager.cpp | set(SRC_MANAGER source/scwx/qt/manager/font_manager.cpp | ||||||
|  |                 source/scwx/qt/manager/placefile_manager.cpp | ||||||
|  |                 source/scwx/qt/manager/radar_product_manager.cpp | ||||||
|                 source/scwx/qt/manager/radar_product_manager_notifier.cpp |                 source/scwx/qt/manager/radar_product_manager_notifier.cpp | ||||||
|                 source/scwx/qt/manager/resource_manager.cpp |                 source/scwx/qt/manager/resource_manager.cpp | ||||||
|                 source/scwx/qt/manager/settings_manager.cpp |                 source/scwx/qt/manager/settings_manager.cpp | ||||||
|  | @ -84,6 +102,7 @@ set(HDR_MAP source/scwx/qt/map/alert_layer.hpp | ||||||
|             source/scwx/qt/map/map_settings.hpp |             source/scwx/qt/map/map_settings.hpp | ||||||
|             source/scwx/qt/map/map_widget.hpp |             source/scwx/qt/map/map_widget.hpp | ||||||
|             source/scwx/qt/map/overlay_layer.hpp |             source/scwx/qt/map/overlay_layer.hpp | ||||||
|  |             source/scwx/qt/map/placefile_layer.hpp | ||||||
|             source/scwx/qt/map/radar_product_layer.hpp |             source/scwx/qt/map/radar_product_layer.hpp | ||||||
|             source/scwx/qt/map/radar_range_layer.hpp) |             source/scwx/qt/map/radar_range_layer.hpp) | ||||||
| set(SRC_MAP source/scwx/qt/map/alert_layer.cpp | set(SRC_MAP source/scwx/qt/map/alert_layer.cpp | ||||||
|  | @ -95,11 +114,14 @@ set(SRC_MAP source/scwx/qt/map/alert_layer.cpp | ||||||
|             source/scwx/qt/map/map_provider.cpp |             source/scwx/qt/map/map_provider.cpp | ||||||
|             source/scwx/qt/map/map_widget.cpp |             source/scwx/qt/map/map_widget.cpp | ||||||
|             source/scwx/qt/map/overlay_layer.cpp |             source/scwx/qt/map/overlay_layer.cpp | ||||||
|  |             source/scwx/qt/map/placefile_layer.cpp | ||||||
|             source/scwx/qt/map/radar_product_layer.cpp |             source/scwx/qt/map/radar_product_layer.cpp | ||||||
|             source/scwx/qt/map/radar_range_layer.cpp) |             source/scwx/qt/map/radar_range_layer.cpp) | ||||||
| set(HDR_MODEL source/scwx/qt/model/alert_model.hpp | set(HDR_MODEL source/scwx/qt/model/alert_model.hpp | ||||||
|               source/scwx/qt/model/alert_proxy_model.hpp |               source/scwx/qt/model/alert_proxy_model.hpp | ||||||
|               source/scwx/qt/model/imgui_context_model.hpp |               source/scwx/qt/model/imgui_context_model.hpp | ||||||
|  |               source/scwx/qt/model/layer_model.hpp | ||||||
|  |               source/scwx/qt/model/placefile_model.hpp | ||||||
|               source/scwx/qt/model/radar_product_model.hpp |               source/scwx/qt/model/radar_product_model.hpp | ||||||
|               source/scwx/qt/model/radar_site_model.hpp |               source/scwx/qt/model/radar_site_model.hpp | ||||||
|               source/scwx/qt/model/tree_item.hpp |               source/scwx/qt/model/tree_item.hpp | ||||||
|  | @ -107,6 +129,8 @@ set(HDR_MODEL source/scwx/qt/model/alert_model.hpp | ||||||
| set(SRC_MODEL source/scwx/qt/model/alert_model.cpp | set(SRC_MODEL source/scwx/qt/model/alert_model.cpp | ||||||
|               source/scwx/qt/model/alert_proxy_model.cpp |               source/scwx/qt/model/alert_proxy_model.cpp | ||||||
|               source/scwx/qt/model/imgui_context_model.cpp |               source/scwx/qt/model/imgui_context_model.cpp | ||||||
|  |               source/scwx/qt/model/layer_model.cpp | ||||||
|  |               source/scwx/qt/model/placefile_model.cpp | ||||||
|               source/scwx/qt/model/radar_product_model.cpp |               source/scwx/qt/model/radar_product_model.cpp | ||||||
|               source/scwx/qt/model/radar_site_model.cpp |               source/scwx/qt/model/radar_site_model.cpp | ||||||
|               source/scwx/qt/model/tree_item.cpp |               source/scwx/qt/model/tree_item.cpp | ||||||
|  | @ -122,6 +146,7 @@ set(HDR_SETTINGS source/scwx/qt/settings/general_settings.hpp | ||||||
|                  source/scwx/qt/settings/settings_interface_base.hpp |                  source/scwx/qt/settings/settings_interface_base.hpp | ||||||
|                  source/scwx/qt/settings/settings_variable.hpp |                  source/scwx/qt/settings/settings_variable.hpp | ||||||
|                  source/scwx/qt/settings/settings_variable_base.hpp |                  source/scwx/qt/settings/settings_variable_base.hpp | ||||||
|  |                  source/scwx/qt/settings/text_settings.hpp | ||||||
|                  source/scwx/qt/settings/ui_settings.hpp) |                  source/scwx/qt/settings/ui_settings.hpp) | ||||||
| set(SRC_SETTINGS source/scwx/qt/settings/general_settings.cpp | set(SRC_SETTINGS source/scwx/qt/settings/general_settings.cpp | ||||||
|                  source/scwx/qt/settings/map_settings.cpp |                  source/scwx/qt/settings/map_settings.cpp | ||||||
|  | @ -132,19 +157,26 @@ set(SRC_SETTINGS source/scwx/qt/settings/general_settings.cpp | ||||||
|                  source/scwx/qt/settings/settings_interface_base.cpp |                  source/scwx/qt/settings/settings_interface_base.cpp | ||||||
|                  source/scwx/qt/settings/settings_variable.cpp |                  source/scwx/qt/settings/settings_variable.cpp | ||||||
|                  source/scwx/qt/settings/settings_variable_base.cpp |                  source/scwx/qt/settings/settings_variable_base.cpp | ||||||
|  |                  source/scwx/qt/settings/text_settings.cpp | ||||||
|                  source/scwx/qt/settings/ui_settings.cpp) |                  source/scwx/qt/settings/ui_settings.cpp) | ||||||
| set(HDR_TYPES source/scwx/qt/types/alert_types.hpp | set(HDR_TYPES source/scwx/qt/types/alert_types.hpp | ||||||
|               source/scwx/qt/types/font_types.hpp |               source/scwx/qt/types/font_types.hpp | ||||||
|               source/scwx/qt/types/github_types.hpp |               source/scwx/qt/types/github_types.hpp | ||||||
|  |               source/scwx/qt/types/imgui_font.hpp | ||||||
|  |               source/scwx/qt/types/layer_types.hpp | ||||||
|               source/scwx/qt/types/map_types.hpp |               source/scwx/qt/types/map_types.hpp | ||||||
|               source/scwx/qt/types/qt_types.hpp |               source/scwx/qt/types/qt_types.hpp | ||||||
|               source/scwx/qt/types/radar_product_record.hpp |               source/scwx/qt/types/radar_product_record.hpp | ||||||
|               source/scwx/qt/types/text_event_key.hpp) |               source/scwx/qt/types/text_event_key.hpp | ||||||
|  |               source/scwx/qt/types/text_types.hpp) | ||||||
| set(SRC_TYPES source/scwx/qt/types/alert_types.cpp | set(SRC_TYPES source/scwx/qt/types/alert_types.cpp | ||||||
|               source/scwx/qt/types/github_types.cpp |               source/scwx/qt/types/github_types.cpp | ||||||
|  |               source/scwx/qt/types/imgui_font.cpp | ||||||
|  |               source/scwx/qt/types/layer_types.cpp | ||||||
|               source/scwx/qt/types/map_types.cpp |               source/scwx/qt/types/map_types.cpp | ||||||
|               source/scwx/qt/types/radar_product_record.cpp |               source/scwx/qt/types/radar_product_record.cpp | ||||||
|               source/scwx/qt/types/text_event_key.cpp) |               source/scwx/qt/types/text_event_key.cpp | ||||||
|  |               source/scwx/qt/types/text_types.cpp) | ||||||
| set(HDR_UI source/scwx/qt/ui/about_dialog.hpp | set(HDR_UI source/scwx/qt/ui/about_dialog.hpp | ||||||
|            source/scwx/qt/ui/alert_dialog.hpp |            source/scwx/qt/ui/alert_dialog.hpp | ||||||
|            source/scwx/qt/ui/alert_dock_widget.hpp |            source/scwx/qt/ui/alert_dock_widget.hpp | ||||||
|  | @ -153,9 +185,14 @@ set(HDR_UI source/scwx/qt/ui/about_dialog.hpp | ||||||
|            source/scwx/qt/ui/flow_layout.hpp |            source/scwx/qt/ui/flow_layout.hpp | ||||||
|            source/scwx/qt/ui/imgui_debug_dialog.hpp |            source/scwx/qt/ui/imgui_debug_dialog.hpp | ||||||
|            source/scwx/qt/ui/imgui_debug_widget.hpp |            source/scwx/qt/ui/imgui_debug_widget.hpp | ||||||
|  |            source/scwx/qt/ui/layer_dialog.hpp | ||||||
|  |            source/scwx/qt/ui/left_elided_item_delegate.hpp | ||||||
|            source/scwx/qt/ui/level2_products_widget.hpp |            source/scwx/qt/ui/level2_products_widget.hpp | ||||||
|            source/scwx/qt/ui/level2_settings_widget.hpp |            source/scwx/qt/ui/level2_settings_widget.hpp | ||||||
|            source/scwx/qt/ui/level3_products_widget.hpp |            source/scwx/qt/ui/level3_products_widget.hpp | ||||||
|  |            source/scwx/qt/ui/open_url_dialog.hpp | ||||||
|  |            source/scwx/qt/ui/placefile_dialog.hpp | ||||||
|  |            source/scwx/qt/ui/placefile_settings_widget.hpp | ||||||
|            source/scwx/qt/ui/radar_site_dialog.hpp |            source/scwx/qt/ui/radar_site_dialog.hpp | ||||||
|            source/scwx/qt/ui/settings_dialog.hpp |            source/scwx/qt/ui/settings_dialog.hpp | ||||||
|            source/scwx/qt/ui/update_dialog.hpp) |            source/scwx/qt/ui/update_dialog.hpp) | ||||||
|  | @ -167,9 +204,14 @@ set(SRC_UI source/scwx/qt/ui/about_dialog.cpp | ||||||
|            source/scwx/qt/ui/flow_layout.cpp |            source/scwx/qt/ui/flow_layout.cpp | ||||||
|            source/scwx/qt/ui/imgui_debug_dialog.cpp |            source/scwx/qt/ui/imgui_debug_dialog.cpp | ||||||
|            source/scwx/qt/ui/imgui_debug_widget.cpp |            source/scwx/qt/ui/imgui_debug_widget.cpp | ||||||
|  |            source/scwx/qt/ui/layer_dialog.cpp | ||||||
|  |            source/scwx/qt/ui/left_elided_item_delegate.cpp | ||||||
|            source/scwx/qt/ui/level2_products_widget.cpp |            source/scwx/qt/ui/level2_products_widget.cpp | ||||||
|            source/scwx/qt/ui/level2_settings_widget.cpp |            source/scwx/qt/ui/level2_settings_widget.cpp | ||||||
|            source/scwx/qt/ui/level3_products_widget.cpp |            source/scwx/qt/ui/level3_products_widget.cpp | ||||||
|  |            source/scwx/qt/ui/open_url_dialog.cpp | ||||||
|  |            source/scwx/qt/ui/placefile_dialog.cpp | ||||||
|  |            source/scwx/qt/ui/placefile_settings_widget.cpp | ||||||
|            source/scwx/qt/ui/radar_site_dialog.cpp |            source/scwx/qt/ui/radar_site_dialog.cpp | ||||||
|            source/scwx/qt/ui/settings_dialog.cpp |            source/scwx/qt/ui/settings_dialog.cpp | ||||||
|            source/scwx/qt/ui/update_dialog.cpp) |            source/scwx/qt/ui/update_dialog.cpp) | ||||||
|  | @ -179,6 +221,10 @@ set(UI_UI  source/scwx/qt/ui/about_dialog.ui | ||||||
|            source/scwx/qt/ui/animation_dock_widget.ui |            source/scwx/qt/ui/animation_dock_widget.ui | ||||||
|            source/scwx/qt/ui/collapsible_group.ui |            source/scwx/qt/ui/collapsible_group.ui | ||||||
|            source/scwx/qt/ui/imgui_debug_dialog.ui |            source/scwx/qt/ui/imgui_debug_dialog.ui | ||||||
|  |            source/scwx/qt/ui/layer_dialog.ui | ||||||
|  |            source/scwx/qt/ui/open_url_dialog.ui | ||||||
|  |            source/scwx/qt/ui/placefile_dialog.ui | ||||||
|  |            source/scwx/qt/ui/placefile_settings_widget.ui | ||||||
|            source/scwx/qt/ui/radar_site_dialog.ui |            source/scwx/qt/ui/radar_site_dialog.ui | ||||||
|            source/scwx/qt/ui/settings_dialog.ui |            source/scwx/qt/ui/settings_dialog.ui | ||||||
|            source/scwx/qt/ui/update_dialog.ui) |            source/scwx/qt/ui/update_dialog.ui) | ||||||
|  | @ -187,22 +233,30 @@ set(HDR_UTIL source/scwx/qt/util/color.hpp | ||||||
|              source/scwx/qt/util/font.hpp |              source/scwx/qt/util/font.hpp | ||||||
|              source/scwx/qt/util/font_buffer.hpp |              source/scwx/qt/util/font_buffer.hpp | ||||||
|              source/scwx/qt/util/geographic_lib.hpp |              source/scwx/qt/util/geographic_lib.hpp | ||||||
|  |              source/scwx/qt/util/imgui.hpp | ||||||
|              source/scwx/qt/util/json.hpp |              source/scwx/qt/util/json.hpp | ||||||
|  |              source/scwx/qt/util/maplibre.hpp | ||||||
|  |              source/scwx/qt/util/network.hpp | ||||||
|              source/scwx/qt/util/streams.hpp |              source/scwx/qt/util/streams.hpp | ||||||
|              source/scwx/qt/util/texture_atlas.hpp |              source/scwx/qt/util/texture_atlas.hpp | ||||||
|              source/scwx/qt/util/q_file_buffer.hpp |              source/scwx/qt/util/q_file_buffer.hpp | ||||||
|              source/scwx/qt/util/q_file_input_stream.hpp |              source/scwx/qt/util/q_file_input_stream.hpp | ||||||
|              source/scwx/qt/util/time.hpp) |              source/scwx/qt/util/time.hpp | ||||||
|  |              source/scwx/qt/util/tooltip.hpp) | ||||||
| set(SRC_UTIL source/scwx/qt/util/color.cpp | set(SRC_UTIL source/scwx/qt/util/color.cpp | ||||||
|              source/scwx/qt/util/file.cpp |              source/scwx/qt/util/file.cpp | ||||||
|              source/scwx/qt/util/font.cpp |              source/scwx/qt/util/font.cpp | ||||||
|              source/scwx/qt/util/font_buffer.cpp |              source/scwx/qt/util/font_buffer.cpp | ||||||
|              source/scwx/qt/util/geographic_lib.cpp |              source/scwx/qt/util/geographic_lib.cpp | ||||||
|  |              source/scwx/qt/util/imgui.cpp | ||||||
|              source/scwx/qt/util/json.cpp |              source/scwx/qt/util/json.cpp | ||||||
|  |              source/scwx/qt/util/maplibre.cpp | ||||||
|  |              source/scwx/qt/util/network.cpp | ||||||
|              source/scwx/qt/util/texture_atlas.cpp |              source/scwx/qt/util/texture_atlas.cpp | ||||||
|              source/scwx/qt/util/q_file_buffer.cpp |              source/scwx/qt/util/q_file_buffer.cpp | ||||||
|              source/scwx/qt/util/q_file_input_stream.cpp |              source/scwx/qt/util/q_file_input_stream.cpp | ||||||
|              source/scwx/qt/util/time.cpp) |              source/scwx/qt/util/time.cpp | ||||||
|  |              source/scwx/qt/util/tooltip.cpp) | ||||||
| set(HDR_VIEW source/scwx/qt/view/level2_product_view.hpp | set(HDR_VIEW source/scwx/qt/view/level2_product_view.hpp | ||||||
|              source/scwx/qt/view/level3_product_view.hpp |              source/scwx/qt/view/level3_product_view.hpp | ||||||
|              source/scwx/qt/view/level3_radial_view.hpp |              source/scwx/qt/view/level3_radial_view.hpp | ||||||
|  | @ -221,13 +275,17 @@ set(RESOURCE_FILES scwx-qt.qrc) | ||||||
| set(SHADER_FILES gl/color.frag | set(SHADER_FILES gl/color.frag | ||||||
|                  gl/color.vert |                  gl/color.vert | ||||||
|                  gl/geo_line.vert |                  gl/geo_line.vert | ||||||
|  |                  gl/geo_texture2d.vert | ||||||
|  |                  gl/map_color.vert | ||||||
|                  gl/radar.frag |                  gl/radar.frag | ||||||
|                  gl/radar.vert |                  gl/radar.vert | ||||||
|                  gl/text.frag |                  gl/text.frag | ||||||
|                  gl/text.vert |                  gl/text.vert | ||||||
|                  gl/texture1d.frag |                  gl/texture1d.frag | ||||||
|                  gl/texture1d.vert |                  gl/texture1d.vert | ||||||
|                  gl/texture2d.frag) |                  gl/texture2d.frag | ||||||
|  |                  gl/texture2d_array.frag | ||||||
|  |                  gl/threshold.geom) | ||||||
| 
 | 
 | ||||||
| set(CMAKE_FILES scwx-qt.cmake) | set(CMAKE_FILES scwx-qt.cmake) | ||||||
| 
 | 
 | ||||||
|  | @ -386,7 +444,8 @@ target_include_directories(scwx-qt PUBLIC ${scwx-qt_SOURCE_DIR}/source | ||||||
|                                           ${FTGL_INCLUDE_DIR} |                                           ${FTGL_INCLUDE_DIR} | ||||||
|                                           ${IMGUI_INCLUDE_DIRS} |                                           ${IMGUI_INCLUDE_DIRS} | ||||||
|                                           ${MBGL_INCLUDE_DIR} |                                           ${MBGL_INCLUDE_DIR} | ||||||
|                                           ${STB_INCLUDE_DIR}) |                                           ${STB_INCLUDE_DIR} | ||||||
|  |                                           ${TEXTFLOWCPP_INCLUDE_DIR}) | ||||||
| 
 | 
 | ||||||
| target_include_directories(supercell-wx PUBLIC ${scwx-qt_SOURCE_DIR}/source) | target_include_directories(supercell-wx PUBLIC ${scwx-qt_SOURCE_DIR}/source) | ||||||
| 
 | 
 | ||||||
|  | @ -432,6 +491,7 @@ target_link_libraries(scwx-qt PUBLIC Qt${QT_VERSION_MAJOR}::Widgets | ||||||
|                                      Boost::timer |                                      Boost::timer | ||||||
|                                      qmaplibregl |                                      qmaplibregl | ||||||
|                                      $<$<CXX_COMPILER_ID:MSVC>:opengl32> |                                      $<$<CXX_COMPILER_ID:MSVC>:opengl32> | ||||||
|  |                                      Fontconfig::Fontconfig | ||||||
|                                      freetype-gl |                                      freetype-gl | ||||||
|                                      GeographicLib::GeographicLib |                                      GeographicLib::GeographicLib | ||||||
|                                      glm::glm |                                      glm::glm | ||||||
|  |  | ||||||
|  | @ -3,6 +3,8 @@ | ||||||
|         <file>gl/color.frag</file> |         <file>gl/color.frag</file> | ||||||
|         <file>gl/color.vert</file> |         <file>gl/color.vert</file> | ||||||
|         <file>gl/geo_line.vert</file> |         <file>gl/geo_line.vert</file> | ||||||
|  |         <file>gl/geo_texture2d.vert</file> | ||||||
|  |         <file>gl/map_color.vert</file> | ||||||
|         <file>gl/radar.frag</file> |         <file>gl/radar.frag</file> | ||||||
|         <file>gl/radar.vert</file> |         <file>gl/radar.vert</file> | ||||||
|         <file>gl/text.frag</file> |         <file>gl/text.frag</file> | ||||||
|  | @ -10,19 +12,29 @@ | ||||||
|         <file>gl/texture1d.frag</file> |         <file>gl/texture1d.frag</file> | ||||||
|         <file>gl/texture1d.vert</file> |         <file>gl/texture1d.vert</file> | ||||||
|         <file>gl/texture2d.frag</file> |         <file>gl/texture2d.frag</file> | ||||||
|  |         <file>gl/texture2d_array.frag</file> | ||||||
|  |         <file>gl/threshold.geom</file> | ||||||
|         <file>res/config/radar_sites.json</file> |         <file>res/config/radar_sites.json</file> | ||||||
|         <file>res/fonts/din1451alt.ttf</file> |         <file>res/fonts/din1451alt.ttf</file> | ||||||
|         <file>res/fonts/din1451alt_g.ttf</file> |         <file>res/fonts/din1451alt_g.ttf</file> | ||||||
|  |         <file>res/fonts/Inconsolata-Regular.ttf</file> | ||||||
|         <file>res/icons/scwx-256.ico</file> |         <file>res/icons/scwx-256.ico</file> | ||||||
|         <file>res/icons/scwx-256.png</file> |         <file>res/icons/scwx-256.png</file> | ||||||
|  |         <file>res/icons/font-awesome-6/angle-down-solid.svg</file> | ||||||
|         <file>res/icons/font-awesome-6/angle-left-solid.svg</file> |         <file>res/icons/font-awesome-6/angle-left-solid.svg</file> | ||||||
|         <file>res/icons/font-awesome-6/angle-right-solid.svg</file> |         <file>res/icons/font-awesome-6/angle-right-solid.svg</file> | ||||||
|  |         <file>res/icons/font-awesome-6/angle-up-solid.svg</file> | ||||||
|  |         <file>res/icons/font-awesome-6/angles-down-solid.svg</file> | ||||||
|  |         <file>res/icons/font-awesome-6/angles-up-solid.svg</file> | ||||||
|         <file>res/icons/font-awesome-6/backward-step-solid.svg</file> |         <file>res/icons/font-awesome-6/backward-step-solid.svg</file> | ||||||
|         <file>res/icons/font-awesome-6/book-solid.svg</file> |         <file>res/icons/font-awesome-6/book-solid.svg</file> | ||||||
|         <file>res/icons/font-awesome-6/discord.svg</file> |         <file>res/icons/font-awesome-6/discord.svg</file> | ||||||
|  |         <file>res/icons/font-awesome-6/earth-americas-solid.svg</file> | ||||||
|  |         <file>res/icons/font-awesome-6/font-solid.svg</file> | ||||||
|         <file>res/icons/font-awesome-6/forward-step-solid.svg</file> |         <file>res/icons/font-awesome-6/forward-step-solid.svg</file> | ||||||
|         <file>res/icons/font-awesome-6/gears-solid.svg</file> |         <file>res/icons/font-awesome-6/gears-solid.svg</file> | ||||||
|         <file>res/icons/font-awesome-6/github.svg</file> |         <file>res/icons/font-awesome-6/github.svg</file> | ||||||
|  |         <file>res/icons/font-awesome-6/layer-group-solid.svg</file> | ||||||
|         <file>res/icons/font-awesome-6/palette-solid.svg</file> |         <file>res/icons/font-awesome-6/palette-solid.svg</file> | ||||||
|         <file>res/icons/font-awesome-6/pause-solid.svg</file> |         <file>res/icons/font-awesome-6/pause-solid.svg</file> | ||||||
|         <file>res/icons/font-awesome-6/play-solid.svg</file> |         <file>res/icons/font-awesome-6/play-solid.svg</file> | ||||||
|  |  | ||||||
							
								
								
									
										22
									
								
								scwx-qt/source/scwx/qt/external/stb_image.cpp
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						|  | @ -0,0 +1,22 @@ | ||||||
|  | #define STB_IMAGE_IMPLEMENTATION | ||||||
|  | #define STBI_ASSERT(x) | ||||||
|  | #define STBI_FAILURE_USERMSG | ||||||
|  | 
 | ||||||
|  | #if defined(__GNUC__) | ||||||
|  | #   pragma GCC diagnostic push | ||||||
|  | #   pragma GCC diagnostic ignored "-Wunused-but-set-variable" | ||||||
|  | #endif | ||||||
|  | 
 | ||||||
|  | #if defined(_MSC_VER) | ||||||
|  | #   pragma warning(push, 0) | ||||||
|  | #endif | ||||||
|  | 
 | ||||||
|  | #include <stb_image.h> | ||||||
|  | 
 | ||||||
|  | #if defined(__GNUC__) | ||||||
|  | #   pragma GCC diagnostic pop | ||||||
|  | #endif | ||||||
|  | 
 | ||||||
|  | #if defined(_MSC_VER) | ||||||
|  | #   pragma warning(pop) | ||||||
|  | #endif | ||||||
|  | @ -1,4 +1,5 @@ | ||||||
| #include <scwx/qt/gl/draw/draw_item.hpp> | #include <scwx/qt/gl/draw/draw_item.hpp> | ||||||
|  | #include <scwx/qt/util/maplibre.hpp> | ||||||
| 
 | 
 | ||||||
| #include <string> | #include <string> | ||||||
| 
 | 
 | ||||||
|  | @ -41,6 +42,27 @@ DrawItem::~DrawItem() = default; | ||||||
| DrawItem::DrawItem(DrawItem&&) noexcept            = default; | DrawItem::DrawItem(DrawItem&&) noexcept            = default; | ||||||
| DrawItem& DrawItem::operator=(DrawItem&&) noexcept = default; | DrawItem& DrawItem::operator=(DrawItem&&) noexcept = default; | ||||||
| 
 | 
 | ||||||
|  | void DrawItem::Render( | ||||||
|  |    const QMapLibreGL::CustomLayerRenderParameters& /* params */) | ||||||
|  | { | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void DrawItem::Render(const QMapLibreGL::CustomLayerRenderParameters& params, | ||||||
|  |                       bool /* textureAtlasChanged */) | ||||||
|  | { | ||||||
|  |    Render(params); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool DrawItem::RunMousePicking( | ||||||
|  |    const QMapLibreGL::CustomLayerRenderParameters& /* params */, | ||||||
|  |    const QPointF& /* mouseLocalPos */, | ||||||
|  |    const QPointF& /* mouseGlobalPos */, | ||||||
|  |    const glm::vec2& /* mouseCoords */) | ||||||
|  | { | ||||||
|  |    // By default, the draw item is not picked
 | ||||||
|  |    return false; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| void DrawItem::UseDefaultProjection( | void DrawItem::UseDefaultProjection( | ||||||
|    const QMapLibreGL::CustomLayerRenderParameters& params, |    const QMapLibreGL::CustomLayerRenderParameters& params, | ||||||
|    GLint                                           uMVPMatrixLocation) |    GLint                                           uMVPMatrixLocation) | ||||||
|  | @ -54,21 +76,21 @@ void DrawItem::UseDefaultProjection( | ||||||
|       uMVPMatrixLocation, 1, GL_FALSE, glm::value_ptr(projection)); |       uMVPMatrixLocation, 1, GL_FALSE, glm::value_ptr(projection)); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // TODO: Refactor to utility class
 | void DrawItem::UseRotationProjection( | ||||||
| static glm::vec2 |    const QMapLibreGL::CustomLayerRenderParameters& params, | ||||||
| LatLongToScreenCoordinate(const QMapLibreGL::Coordinate& coordinate) |    GLint                                           uMVPMatrixLocation) | ||||||
| { | { | ||||||
|    static constexpr double RAD2DEG_D = 180.0 / M_PI; |    glm::mat4 projection = glm::ortho(0.0f, | ||||||
|  |                                      static_cast<float>(params.width), | ||||||
|  |                                      0.0f, | ||||||
|  |                                      static_cast<float>(params.height)); | ||||||
| 
 | 
 | ||||||
|    double latitude = std::clamp( |    projection = glm::rotate(projection, | ||||||
|       coordinate.first, -mbgl::util::LATITUDE_MAX, mbgl::util::LATITUDE_MAX); |                             glm::radians<float>(params.bearing), | ||||||
|    glm::vec2 screen { |                             glm::vec3(0.0f, 0.0f, 1.0f)); | ||||||
|       mbgl::util::LONGITUDE_MAX + coordinate.second, | 
 | ||||||
|       -(mbgl::util::LONGITUDE_MAX - |    p->gl_.glUniformMatrix4fv( | ||||||
|         RAD2DEG_D * |       uMVPMatrixLocation, 1, GL_FALSE, glm::value_ptr(projection)); | ||||||
|            std::log(std::tan(M_PI / 4.0 + |  | ||||||
|                              latitude * M_PI / mbgl::util::DEGREES_MAX)))}; |  | ||||||
|    return screen; |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void DrawItem::UseMapProjection( | void DrawItem::UseMapProjection( | ||||||
|  | @ -78,21 +100,11 @@ void DrawItem::UseMapProjection( | ||||||
| { | { | ||||||
|    OpenGLFunctions& gl = p->gl_; |    OpenGLFunctions& gl = p->gl_; | ||||||
| 
 | 
 | ||||||
|    // TODO: Refactor to utility class
 |    const glm::mat4 uMVPMatrix = util::maplibre::GetMapMatrix(params); | ||||||
|    const float scale = std::pow(2.0, params.zoom) * 2.0f * |  | ||||||
|                        mbgl::util::tileSize_D / mbgl::util::DEGREES_MAX; |  | ||||||
|    const float xScale = scale / params.width; |  | ||||||
|    const float yScale = scale / params.height; |  | ||||||
| 
 |  | ||||||
|    glm::mat4 uMVPMatrix(1.0f); |  | ||||||
|    uMVPMatrix = glm::scale(uMVPMatrix, glm::vec3(xScale, yScale, 1.0f)); |  | ||||||
|    uMVPMatrix = glm::rotate(uMVPMatrix, |  | ||||||
|                             glm::radians<float>(params.bearing), |  | ||||||
|                             glm::vec3(0.0f, 0.0f, 1.0f)); |  | ||||||
| 
 | 
 | ||||||
|    gl.glUniform2fv(uMapScreenCoordLocation, |    gl.glUniform2fv(uMapScreenCoordLocation, | ||||||
|                    1, |                    1, | ||||||
|                    glm::value_ptr(LatLongToScreenCoordinate( |                    glm::value_ptr(util::maplibre::LatLongToScreenCoordinate( | ||||||
|                       {params.latitude, params.longitude}))); |                       {params.latitude, params.longitude}))); | ||||||
| 
 | 
 | ||||||
|    gl.glUniformMatrix4fv( |    gl.glUniformMatrix4fv( | ||||||
|  |  | ||||||
|  | @ -5,6 +5,7 @@ | ||||||
| #include <memory> | #include <memory> | ||||||
| 
 | 
 | ||||||
| #include <QMapLibreGL/QMapLibreGL> | #include <QMapLibreGL/QMapLibreGL> | ||||||
|  | #include <glm/gtc/type_ptr.hpp> | ||||||
| 
 | 
 | ||||||
| namespace scwx | namespace scwx | ||||||
| { | { | ||||||
|  | @ -28,14 +29,34 @@ public: | ||||||
|    DrawItem& operator=(DrawItem&&) noexcept; |    DrawItem& operator=(DrawItem&&) noexcept; | ||||||
| 
 | 
 | ||||||
|    virtual void Initialize() = 0; |    virtual void Initialize() = 0; | ||||||
|    virtual void |    virtual void Render(const QMapLibreGL::CustomLayerRenderParameters& params); | ||||||
|    Render(const QMapLibreGL::CustomLayerRenderParameters& params) = 0; |    virtual void Render(const QMapLibreGL::CustomLayerRenderParameters& params, | ||||||
|    virtual void Deinitialize()                                    = 0; |                        bool textureAtlasChanged); | ||||||
|  |    virtual void Deinitialize() = 0; | ||||||
|  | 
 | ||||||
|  |    /**
 | ||||||
|  |     * @brief Run mouse picking on the draw item. | ||||||
|  |     * | ||||||
|  |     * @param [in] params Custom layer render parameters | ||||||
|  |     * @param [in] mouseLocalPos Mouse cursor widget position | ||||||
|  |     * @param [in] mouseGlobalPos Mouse cursor screen position | ||||||
|  |     * @param [in] mouseCoords Mouse cursor location in map screen coordinates | ||||||
|  |     * | ||||||
|  |     * @return true if the draw item was picked, otherwise false | ||||||
|  |     */ | ||||||
|  |    virtual bool | ||||||
|  |    RunMousePicking(const QMapLibreGL::CustomLayerRenderParameters& params, | ||||||
|  |                    const QPointF&   mouseLocalPos, | ||||||
|  |                    const QPointF&   mouseGlobalPos, | ||||||
|  |                    const glm::vec2& mouseCoords); | ||||||
| 
 | 
 | ||||||
| protected: | protected: | ||||||
|    void |    void | ||||||
|    UseDefaultProjection(const QMapLibreGL::CustomLayerRenderParameters& params, |    UseDefaultProjection(const QMapLibreGL::CustomLayerRenderParameters& params, | ||||||
|                         GLint uMVPMatrixLocation); |                         GLint uMVPMatrixLocation); | ||||||
|  |    void | ||||||
|  |    UseRotationProjection(const QMapLibreGL::CustomLayerRenderParameters& params, | ||||||
|  |                          GLint uMVPMatrixLocation); | ||||||
|    void UseMapProjection(const QMapLibreGL::CustomLayerRenderParameters& params, |    void UseMapProjection(const QMapLibreGL::CustomLayerRenderParameters& params, | ||||||
|                          GLint uMVPMatrixLocation, |                          GLint uMVPMatrixLocation, | ||||||
|                          GLint uMapScreenCoordLocation); |                          GLint uMapScreenCoordLocation); | ||||||
|  |  | ||||||
|  | @ -23,10 +23,12 @@ static constexpr size_t kNumRectangles        = 1; | ||||||
| static constexpr size_t kNumTriangles         = kNumRectangles * 2; | static constexpr size_t kNumTriangles         = kNumRectangles * 2; | ||||||
| static constexpr size_t kVerticesPerTriangle  = 3; | static constexpr size_t kVerticesPerTriangle  = 3; | ||||||
| static constexpr size_t kVerticesPerRectangle = kVerticesPerTriangle * 2; | static constexpr size_t kVerticesPerRectangle = kVerticesPerTriangle * 2; | ||||||
| static constexpr size_t kPointsPerVertex      = 10; | static constexpr size_t kPointsPerVertex      = 11; | ||||||
| static constexpr size_t kBufferLength = | static constexpr size_t kBufferLength = | ||||||
|    kNumTriangles * kVerticesPerTriangle * kPointsPerVertex; |    kNumTriangles * kVerticesPerTriangle * kPointsPerVertex; | ||||||
| 
 | 
 | ||||||
|  | static const std::string kTextureName = "lines/default-1x7"; | ||||||
|  | 
 | ||||||
| class GeoLine::Impl | class GeoLine::Impl | ||||||
| { | { | ||||||
| public: | public: | ||||||
|  | @ -90,8 +92,8 @@ void GeoLine::Initialize() | ||||||
| { | { | ||||||
|    gl::OpenGLFunctions& gl = p->context_->gl(); |    gl::OpenGLFunctions& gl = p->context_->gl(); | ||||||
| 
 | 
 | ||||||
|    p->shaderProgram_ = p->context_->GetShaderProgram(":/gl/geo_line.vert", |    p->shaderProgram_ = p->context_->GetShaderProgram( | ||||||
|                                                      ":/gl/texture2d.frag"); |       ":/gl/geo_line.vert", ":/gl/texture2d_array.frag"); | ||||||
| 
 | 
 | ||||||
|    p->uMVPMatrixLocation_ = |    p->uMVPMatrixLocation_ = | ||||||
|       gl.glGetUniformLocation(p->shaderProgram_->id(), "uMVPMatrix"); |       gl.glGetUniformLocation(p->shaderProgram_->id(), "uMVPMatrix"); | ||||||
|  | @ -115,7 +117,7 @@ void GeoLine::Initialize() | ||||||
|    } |    } | ||||||
| 
 | 
 | ||||||
|    p->texture_ = |    p->texture_ = | ||||||
|       util::TextureAtlas::Instance().GetTextureAttributes("lines/default-1x7"); |       util::TextureAtlas::Instance().GetTextureAttributes(kTextureName); | ||||||
| 
 | 
 | ||||||
|    gl.glGenVertexArrays(1, &p->vao_); |    gl.glGenVertexArrays(1, &p->vao_); | ||||||
|    gl.glGenBuffers(1, &p->vbo_); |    gl.glGenBuffers(1, &p->vbo_); | ||||||
|  | @ -145,7 +147,7 @@ void GeoLine::Initialize() | ||||||
| 
 | 
 | ||||||
|    // aTexCoord
 |    // aTexCoord
 | ||||||
|    gl.glVertexAttribPointer(2, |    gl.glVertexAttribPointer(2, | ||||||
|                             2, |                             3, | ||||||
|                             GL_FLOAT, |                             GL_FLOAT, | ||||||
|                             GL_FALSE, |                             GL_FALSE, | ||||||
|                             kPointsPerVertex * sizeof(float), |                             kPointsPerVertex * sizeof(float), | ||||||
|  | @ -158,7 +160,7 @@ void GeoLine::Initialize() | ||||||
|                             GL_FLOAT, |                             GL_FLOAT, | ||||||
|                             GL_FALSE, |                             GL_FALSE, | ||||||
|                             kPointsPerVertex * sizeof(float), |                             kPointsPerVertex * sizeof(float), | ||||||
|                             reinterpret_cast<void*>(6 * sizeof(float))); |                             reinterpret_cast<void*>(7 * sizeof(float))); | ||||||
|    gl.glEnableVertexAttribArray(3); |    gl.glEnableVertexAttribArray(3); | ||||||
| 
 | 
 | ||||||
|    p->dirty_ = true; |    p->dirty_ = true; | ||||||
|  | @ -248,6 +250,9 @@ void GeoLine::Impl::Update() | ||||||
|    { |    { | ||||||
|       gl::OpenGLFunctions& gl = context_->gl(); |       gl::OpenGLFunctions& gl = context_->gl(); | ||||||
| 
 | 
 | ||||||
|  |       texture_ = | ||||||
|  |          util::TextureAtlas::Instance().GetTextureAttributes(kTextureName); | ||||||
|  | 
 | ||||||
|       // Latitude and longitude coordinates in degrees
 |       // Latitude and longitude coordinates in degrees
 | ||||||
|       const float lx = points_[0].latitude_; |       const float lx = points_[0].latitude_; | ||||||
|       const float rx = points_[1].latitude_; |       const float rx = points_[1].latitude_; | ||||||
|  | @ -259,6 +264,8 @@ void GeoLine::Impl::Update() | ||||||
|       const float oy = width_ * 0.5f * sinf(angle_); |       const float oy = width_ * 0.5f * sinf(angle_); | ||||||
| 
 | 
 | ||||||
|       // Texture coordinates
 |       // Texture coordinates
 | ||||||
|  |       static constexpr float r = 0.0f; | ||||||
|  | 
 | ||||||
|       const float ls = texture_.sLeft_; |       const float ls = texture_.sLeft_; | ||||||
|       const float rs = texture_.sRight_; |       const float rs = texture_.sRight_; | ||||||
|       const float tt = texture_.tTop_; |       const float tt = texture_.tTop_; | ||||||
|  | @ -284,12 +291,12 @@ void GeoLine::Impl::Update() | ||||||
|          {                                   //
 |          {                                   //
 | ||||||
|           // Line
 |           // Line
 | ||||||
|           { |           { | ||||||
|              {lx, by, -ox, -oy, ls, bt, mc0, mc1, mc2, mc3}, // BL
 |              {lx, by, -ox, -oy, ls, bt, r, mc0, mc1, mc2, mc3}, // BL
 | ||||||
|              {lx, by, +ox, +oy, ls, tt, mc0, mc1, mc2, mc3}, // TL
 |              {lx, by, +ox, +oy, ls, tt, r, mc0, mc1, mc2, mc3}, // TL
 | ||||||
|              {rx, ty, -ox, -oy, rs, bt, mc0, mc1, mc2, mc3}, // BR
 |              {rx, ty, -ox, -oy, rs, bt, r, mc0, mc1, mc2, mc3}, // BR
 | ||||||
|              {rx, ty, -ox, -oy, rs, bt, mc0, mc1, mc2, mc3}, // BR
 |              {rx, ty, -ox, -oy, rs, bt, r, mc0, mc1, mc2, mc3}, // BR
 | ||||||
|              {rx, ty, +ox, +oy, rs, tt, mc0, mc1, mc2, mc3}, // TR
 |              {rx, ty, +ox, +oy, rs, tt, r, mc0, mc1, mc2, mc3}, // TR
 | ||||||
|              {lx, by, +ox, +oy, ls, tt, mc0, mc1, mc2, mc3}  // TL
 |              {lx, by, +ox, +oy, ls, tt, r, mc0, mc1, mc2, mc3}  // TL
 | ||||||
|           }}; |           }}; | ||||||
| 
 | 
 | ||||||
|       gl.glBufferData(GL_ARRAY_BUFFER, |       gl.glBufferData(GL_ARRAY_BUFFER, | ||||||
|  |  | ||||||
							
								
								
									
										788
									
								
								scwx-qt/source/scwx/qt/gl/draw/placefile_icons.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						|  | @ -0,0 +1,788 @@ | ||||||
|  | #include <scwx/qt/gl/draw/placefile_icons.hpp> | ||||||
|  | #include <scwx/qt/util/maplibre.hpp> | ||||||
|  | #include <scwx/qt/util/texture_atlas.hpp> | ||||||
|  | #include <scwx/qt/util/tooltip.hpp> | ||||||
|  | #include <scwx/util/logger.hpp> | ||||||
|  | 
 | ||||||
|  | #include <execution> | ||||||
|  | 
 | ||||||
|  | #include <QDir> | ||||||
|  | #include <QUrl> | ||||||
|  | #include <boost/unordered/unordered_flat_map.hpp> | ||||||
|  | 
 | ||||||
|  | namespace scwx | ||||||
|  | { | ||||||
|  | namespace qt | ||||||
|  | { | ||||||
|  | namespace gl | ||||||
|  | { | ||||||
|  | namespace draw | ||||||
|  | { | ||||||
|  | 
 | ||||||
|  | static const std::string logPrefix_ = "scwx::qt::gl::draw::placefile_icons"; | ||||||
|  | static const auto        logger_    = scwx::util::Logger::Create(logPrefix_); | ||||||
|  | 
 | ||||||
|  | static constexpr std::size_t kNumRectangles        = 1; | ||||||
|  | static constexpr std::size_t kNumTriangles         = kNumRectangles * 2; | ||||||
|  | static constexpr std::size_t kVerticesPerTriangle  = 3; | ||||||
|  | static constexpr std::size_t kVerticesPerRectangle = kVerticesPerTriangle * 2; | ||||||
|  | static constexpr std::size_t kPointsPerVertex      = 9; | ||||||
|  | static constexpr std::size_t kPointsPerTexCoord    = 3; | ||||||
|  | static constexpr std::size_t kIconBufferLength = | ||||||
|  |    kNumTriangles * kVerticesPerTriangle * kPointsPerVertex; | ||||||
|  | static constexpr std::size_t kTextureBufferLength = | ||||||
|  |    kNumTriangles * kVerticesPerTriangle * kPointsPerTexCoord; | ||||||
|  | 
 | ||||||
|  | // Threshold, start time, end time
 | ||||||
|  | static constexpr std::size_t kIntegersPerVertex_ = 3; | ||||||
|  | 
 | ||||||
|  | struct PlacefileIconInfo | ||||||
|  | { | ||||||
|  |    PlacefileIconInfo( | ||||||
|  |       const std::shared_ptr<const gr::Placefile::IconFile>& iconFile, | ||||||
|  |       const std::string&                                    baseUrlString) : | ||||||
|  |        iconFile_ {iconFile} | ||||||
|  |    { | ||||||
|  |       // Resolve using base URL
 | ||||||
|  |       auto baseUrl = QUrl::fromUserInput(QString::fromStdString(baseUrlString)); | ||||||
|  |       auto relativeUrl = QUrl(QDir::fromNativeSeparators( | ||||||
|  |          QString::fromStdString(iconFile->filename_))); | ||||||
|  |       resolvedUrl_     = baseUrl.resolved(relativeUrl).toString().toStdString(); | ||||||
|  |    } | ||||||
|  | 
 | ||||||
|  |    void UpdateTextureInfo(); | ||||||
|  | 
 | ||||||
|  |    std::string                                    resolvedUrl_; | ||||||
|  |    std::shared_ptr<const gr::Placefile::IconFile> iconFile_; | ||||||
|  |    util::TextureAttributes                        texture_ {}; | ||||||
|  |    std::size_t                                    rows_ {}; | ||||||
|  |    std::size_t                                    columns_ {}; | ||||||
|  |    std::size_t                                    numIcons_ {}; | ||||||
|  |    float                                          scaledWidth_ {}; | ||||||
|  |    float                                          scaledHeight_ {}; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | class PlacefileIcons::Impl | ||||||
|  | { | ||||||
|  | public: | ||||||
|  |    struct IconHoverEntry | ||||||
|  |    { | ||||||
|  |       std::shared_ptr<const gr::Placefile::IconDrawItem> di_; | ||||||
|  | 
 | ||||||
|  |       glm::vec2 p_; | ||||||
|  |       glm::vec2 otl_; | ||||||
|  |       glm::vec2 otr_; | ||||||
|  |       glm::vec2 obl_; | ||||||
|  |       glm::vec2 obr_; | ||||||
|  |    }; | ||||||
|  | 
 | ||||||
|  |    explicit Impl(const std::shared_ptr<GlContext>& context) : | ||||||
|  |        context_ {context}, | ||||||
|  |        shaderProgram_ {nullptr}, | ||||||
|  |        uMVPMatrixLocation_(GL_INVALID_INDEX), | ||||||
|  |        uMapMatrixLocation_(GL_INVALID_INDEX), | ||||||
|  |        uMapScreenCoordLocation_(GL_INVALID_INDEX), | ||||||
|  |        uMapDistanceLocation_(GL_INVALID_INDEX), | ||||||
|  |        uSelectedTimeLocation_(GL_INVALID_INDEX), | ||||||
|  |        vao_ {GL_INVALID_INDEX}, | ||||||
|  |        vbo_ {GL_INVALID_INDEX}, | ||||||
|  |        numVertices_ {0} | ||||||
|  |    { | ||||||
|  |    } | ||||||
|  | 
 | ||||||
|  |    ~Impl() {} | ||||||
|  | 
 | ||||||
|  |    void UpdateBuffers(); | ||||||
|  |    void UpdateTextureBuffer(); | ||||||
|  |    void Update(bool textureAtlasChanged); | ||||||
|  | 
 | ||||||
|  |    std::shared_ptr<GlContext> context_; | ||||||
|  | 
 | ||||||
|  |    bool dirty_ {false}; | ||||||
|  |    bool thresholded_ {false}; | ||||||
|  | 
 | ||||||
|  |    std::chrono::system_clock::time_point selectedTime_ {}; | ||||||
|  | 
 | ||||||
|  |    std::mutex iconMutex_; | ||||||
|  | 
 | ||||||
|  |    boost::unordered_flat_map<std::size_t, PlacefileIconInfo> | ||||||
|  |       currentIconFiles_ {}; | ||||||
|  |    boost::unordered_flat_map<std::size_t, PlacefileIconInfo> newIconFiles_ {}; | ||||||
|  | 
 | ||||||
|  |    std::vector<std::shared_ptr<const gr::Placefile::IconDrawItem>> | ||||||
|  |       currentIconList_ {}; | ||||||
|  |    std::vector<std::shared_ptr<const gr::Placefile::IconDrawItem>> | ||||||
|  |       newIconList_ {}; | ||||||
|  |    std::vector<std::shared_ptr<const gr::Placefile::IconDrawItem>> | ||||||
|  |       newValidIconList_ {}; | ||||||
|  | 
 | ||||||
|  |    std::vector<float> currentIconBuffer_ {}; | ||||||
|  |    std::vector<GLint> currentIntegerBuffer_ {}; | ||||||
|  |    std::vector<float> newIconBuffer_ {}; | ||||||
|  |    std::vector<GLint> newIntegerBuffer_ {}; | ||||||
|  | 
 | ||||||
|  |    std::vector<float> textureBuffer_ {}; | ||||||
|  | 
 | ||||||
|  |    std::vector<IconHoverEntry> currentHoverIcons_ {}; | ||||||
|  |    std::vector<IconHoverEntry> newHoverIcons_ {}; | ||||||
|  | 
 | ||||||
|  |    std::shared_ptr<ShaderProgram> shaderProgram_; | ||||||
|  |    GLint                          uMVPMatrixLocation_; | ||||||
|  |    GLint                          uMapMatrixLocation_; | ||||||
|  |    GLint                          uMapScreenCoordLocation_; | ||||||
|  |    GLint                          uMapDistanceLocation_; | ||||||
|  |    GLint                          uSelectedTimeLocation_; | ||||||
|  | 
 | ||||||
|  |    GLuint                vao_; | ||||||
|  |    std::array<GLuint, 3> vbo_; | ||||||
|  | 
 | ||||||
|  |    GLsizei numVertices_; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | PlacefileIcons::PlacefileIcons(const std::shared_ptr<GlContext>& context) : | ||||||
|  |     DrawItem(context->gl()), p(std::make_unique<Impl>(context)) | ||||||
|  | { | ||||||
|  | } | ||||||
|  | PlacefileIcons::~PlacefileIcons() = default; | ||||||
|  | 
 | ||||||
|  | PlacefileIcons::PlacefileIcons(PlacefileIcons&&) noexcept            = default; | ||||||
|  | PlacefileIcons& PlacefileIcons::operator=(PlacefileIcons&&) noexcept = default; | ||||||
|  | 
 | ||||||
|  | void PlacefileIcons::set_selected_time( | ||||||
|  |    std::chrono::system_clock::time_point selectedTime) | ||||||
|  | { | ||||||
|  |    p->selectedTime_ = selectedTime; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void PlacefileIcons::set_thresholded(bool thresholded) | ||||||
|  | { | ||||||
|  |    p->thresholded_ = thresholded; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void PlacefileIcons::Initialize() | ||||||
|  | { | ||||||
|  |    gl::OpenGLFunctions& gl = p->context_->gl(); | ||||||
|  | 
 | ||||||
|  |    p->shaderProgram_ = p->context_->GetShaderProgram( | ||||||
|  |       {{GL_VERTEX_SHADER, ":/gl/geo_texture2d.vert"}, | ||||||
|  |        {GL_GEOMETRY_SHADER, ":/gl/threshold.geom"}, | ||||||
|  |        {GL_FRAGMENT_SHADER, ":/gl/texture2d_array.frag"}}); | ||||||
|  | 
 | ||||||
|  |    p->uMVPMatrixLocation_ = p->shaderProgram_->GetUniformLocation("uMVPMatrix"); | ||||||
|  |    p->uMapMatrixLocation_ = p->shaderProgram_->GetUniformLocation("uMapMatrix"); | ||||||
|  |    p->uMapScreenCoordLocation_ = | ||||||
|  |       p->shaderProgram_->GetUniformLocation("uMapScreenCoord"); | ||||||
|  |    p->uMapDistanceLocation_ = | ||||||
|  |       p->shaderProgram_->GetUniformLocation("uMapDistance"); | ||||||
|  |    p->uSelectedTimeLocation_ = | ||||||
|  |       p->shaderProgram_->GetUniformLocation("uSelectedTime"); | ||||||
|  | 
 | ||||||
|  |    gl.glGenVertexArrays(1, &p->vao_); | ||||||
|  |    gl.glGenBuffers(static_cast<GLsizei>(p->vbo_.size()), p->vbo_.data()); | ||||||
|  | 
 | ||||||
|  |    gl.glBindVertexArray(p->vao_); | ||||||
|  |    gl.glBindBuffer(GL_ARRAY_BUFFER, p->vbo_[0]); | ||||||
|  |    gl.glBufferData(GL_ARRAY_BUFFER, 0u, nullptr, GL_DYNAMIC_DRAW); | ||||||
|  | 
 | ||||||
|  |    // aLatLong
 | ||||||
|  |    gl.glVertexAttribPointer(0, | ||||||
|  |                             2, | ||||||
|  |                             GL_FLOAT, | ||||||
|  |                             GL_FALSE, | ||||||
|  |                             kPointsPerVertex * sizeof(float), | ||||||
|  |                             static_cast<void*>(0)); | ||||||
|  |    gl.glEnableVertexAttribArray(0); | ||||||
|  | 
 | ||||||
|  |    // aXYOffset
 | ||||||
|  |    gl.glVertexAttribPointer(1, | ||||||
|  |                             2, | ||||||
|  |                             GL_FLOAT, | ||||||
|  |                             GL_FALSE, | ||||||
|  |                             kPointsPerVertex * sizeof(float), | ||||||
|  |                             reinterpret_cast<void*>(2 * sizeof(float))); | ||||||
|  |    gl.glEnableVertexAttribArray(1); | ||||||
|  | 
 | ||||||
|  |    // aModulate
 | ||||||
|  |    gl.glVertexAttribPointer(3, | ||||||
|  |                             4, | ||||||
|  |                             GL_FLOAT, | ||||||
|  |                             GL_FALSE, | ||||||
|  |                             kPointsPerVertex * sizeof(float), | ||||||
|  |                             reinterpret_cast<void*>(4 * sizeof(float))); | ||||||
|  |    gl.glEnableVertexAttribArray(3); | ||||||
|  | 
 | ||||||
|  |    // aAngle
 | ||||||
|  |    gl.glVertexAttribPointer(4, | ||||||
|  |                             1, | ||||||
|  |                             GL_FLOAT, | ||||||
|  |                             GL_FALSE, | ||||||
|  |                             kPointsPerVertex * sizeof(float), | ||||||
|  |                             reinterpret_cast<void*>(8 * sizeof(float))); | ||||||
|  |    gl.glEnableVertexAttribArray(4); | ||||||
|  | 
 | ||||||
|  |    gl.glBindBuffer(GL_ARRAY_BUFFER, p->vbo_[1]); | ||||||
|  |    gl.glBufferData(GL_ARRAY_BUFFER, 0u, nullptr, GL_DYNAMIC_DRAW); | ||||||
|  | 
 | ||||||
|  |    // aTexCoord
 | ||||||
|  |    gl.glVertexAttribPointer(2, | ||||||
|  |                             3, | ||||||
|  |                             GL_FLOAT, | ||||||
|  |                             GL_FALSE, | ||||||
|  |                             kPointsPerTexCoord * sizeof(float), | ||||||
|  |                             static_cast<void*>(0)); | ||||||
|  |    gl.glEnableVertexAttribArray(2); | ||||||
|  | 
 | ||||||
|  |    gl.glBindBuffer(GL_ARRAY_BUFFER, p->vbo_[2]); | ||||||
|  |    gl.glBufferData(GL_ARRAY_BUFFER, 0u, nullptr, GL_DYNAMIC_DRAW); | ||||||
|  | 
 | ||||||
|  |    // aThreshold
 | ||||||
|  |    gl.glVertexAttribIPointer(5, //
 | ||||||
|  |                              1, | ||||||
|  |                              GL_INT, | ||||||
|  |                              0, | ||||||
|  |                              static_cast<void*>(0)); | ||||||
|  |    gl.glEnableVertexAttribArray(5); | ||||||
|  | 
 | ||||||
|  |    // aTimeRange
 | ||||||
|  |    gl.glVertexAttribIPointer(6, //
 | ||||||
|  |                              2, | ||||||
|  |                              GL_INT, | ||||||
|  |                              kIntegersPerVertex_ * sizeof(GLint), | ||||||
|  |                              reinterpret_cast<void*>(1 * sizeof(GLint))); | ||||||
|  |    gl.glEnableVertexAttribArray(6); | ||||||
|  | 
 | ||||||
|  |    p->dirty_ = true; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void PlacefileIcons::Render( | ||||||
|  |    const QMapLibreGL::CustomLayerRenderParameters& params, | ||||||
|  |    bool                                            textureAtlasChanged) | ||||||
|  | { | ||||||
|  |    std::unique_lock lock {p->iconMutex_}; | ||||||
|  | 
 | ||||||
|  |    if (!p->currentIconList_.empty()) | ||||||
|  |    { | ||||||
|  |       gl::OpenGLFunctions& gl = p->context_->gl(); | ||||||
|  | 
 | ||||||
|  |       gl.glBindVertexArray(p->vao_); | ||||||
|  | 
 | ||||||
|  |       p->Update(textureAtlasChanged); | ||||||
|  |       p->shaderProgram_->Use(); | ||||||
|  |       UseRotationProjection(params, p->uMVPMatrixLocation_); | ||||||
|  |       UseMapProjection( | ||||||
|  |          params, p->uMapMatrixLocation_, p->uMapScreenCoordLocation_); | ||||||
|  | 
 | ||||||
|  |       if (p->thresholded_) | ||||||
|  |       { | ||||||
|  |          // If thresholding is enabled, set the map distance
 | ||||||
|  |          units::length::nautical_miles<float> mapDistance = | ||||||
|  |             util::maplibre::GetMapDistance(params); | ||||||
|  |          gl.glUniform1f(p->uMapDistanceLocation_, mapDistance.value()); | ||||||
|  |       } | ||||||
|  |       else | ||||||
|  |       { | ||||||
|  |          // If thresholding is disabled, set the map distance to 0
 | ||||||
|  |          gl.glUniform1f(p->uMapDistanceLocation_, 0.0f); | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       // Selected time
 | ||||||
|  |       std::chrono::system_clock::time_point selectedTime = | ||||||
|  |          (p->selectedTime_ == std::chrono::system_clock::time_point {}) ? | ||||||
|  |             std::chrono::system_clock::now() : | ||||||
|  |             p->selectedTime_; | ||||||
|  |       gl.glUniform1i( | ||||||
|  |          p->uSelectedTimeLocation_, | ||||||
|  |          static_cast<GLint>(std::chrono::duration_cast<std::chrono::minutes>( | ||||||
|  |                                selectedTime.time_since_epoch()) | ||||||
|  |                                .count())); | ||||||
|  | 
 | ||||||
|  |       // Interpolate texture coordinates
 | ||||||
|  |       gl.glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_LINEAR); | ||||||
|  |       gl.glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_LINEAR); | ||||||
|  | 
 | ||||||
|  |       // Draw icons
 | ||||||
|  |       gl.glDrawArrays(GL_TRIANGLES, 0, p->numVertices_); | ||||||
|  |    } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void PlacefileIcons::Deinitialize() | ||||||
|  | { | ||||||
|  |    gl::OpenGLFunctions& gl = p->context_->gl(); | ||||||
|  | 
 | ||||||
|  |    gl.glDeleteVertexArrays(1, &p->vao_); | ||||||
|  |    gl.glDeleteBuffers(static_cast<GLsizei>(p->vbo_.size()), p->vbo_.data()); | ||||||
|  | 
 | ||||||
|  |    std::unique_lock lock {p->iconMutex_}; | ||||||
|  | 
 | ||||||
|  |    p->currentIconList_.clear(); | ||||||
|  |    p->currentIconFiles_.clear(); | ||||||
|  |    p->currentHoverIcons_.clear(); | ||||||
|  |    p->currentIconBuffer_.clear(); | ||||||
|  |    p->currentIntegerBuffer_.clear(); | ||||||
|  |    p->textureBuffer_.clear(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void PlacefileIconInfo::UpdateTextureInfo() | ||||||
|  | { | ||||||
|  |    texture_ = util::TextureAtlas::Instance().GetTextureAttributes(resolvedUrl_); | ||||||
|  | 
 | ||||||
|  |    if (iconFile_->iconWidth_ > 0 && iconFile_->iconHeight_ > 0) | ||||||
|  |    { | ||||||
|  |       columns_ = texture_.size_.x / iconFile_->iconWidth_; | ||||||
|  |       rows_    = texture_.size_.y / iconFile_->iconHeight_; | ||||||
|  |    } | ||||||
|  |    else | ||||||
|  |    { | ||||||
|  |       columns_ = 0u; | ||||||
|  |       rows_    = 0u; | ||||||
|  |    } | ||||||
|  | 
 | ||||||
|  |    numIcons_ = columns_ * rows_; | ||||||
|  | 
 | ||||||
|  |    // Pixel size
 | ||||||
|  |    float xFactor = 0.0f; | ||||||
|  |    float yFactor = 0.0f; | ||||||
|  | 
 | ||||||
|  |    if (texture_.size_.x > 0 && texture_.size_.y > 0) | ||||||
|  |    { | ||||||
|  |       xFactor = (texture_.sRight_ - texture_.sLeft_) / texture_.size_.x; | ||||||
|  |       yFactor = (texture_.tBottom_ - texture_.tTop_) / texture_.size_.y; | ||||||
|  |    } | ||||||
|  | 
 | ||||||
|  |    scaledWidth_  = iconFile_->iconWidth_ * xFactor; | ||||||
|  |    scaledHeight_ = iconFile_->iconHeight_ * yFactor; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void PlacefileIcons::StartIcons() | ||||||
|  | { | ||||||
|  |    // Clear the new buffer
 | ||||||
|  |    p->newIconList_.clear(); | ||||||
|  |    p->newValidIconList_.clear(); | ||||||
|  |    p->newIconFiles_.clear(); | ||||||
|  |    p->newIconBuffer_.clear(); | ||||||
|  |    p->newIntegerBuffer_.clear(); | ||||||
|  |    p->newHoverIcons_.clear(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void PlacefileIcons::SetIconFiles( | ||||||
|  |    const std::vector<std::shared_ptr<const gr::Placefile::IconFile>>& iconFiles, | ||||||
|  |    const std::string&                                                 baseUrl) | ||||||
|  | { | ||||||
|  |    // Populate icon file map
 | ||||||
|  |    for (auto& file : iconFiles) | ||||||
|  |    { | ||||||
|  |       p->newIconFiles_.emplace( | ||||||
|  |          std::piecewise_construct, | ||||||
|  |          std::tuple {file->fileNumber_}, | ||||||
|  |          std::forward_as_tuple(PlacefileIconInfo {file, baseUrl})); | ||||||
|  |    } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void PlacefileIcons::AddIcon( | ||||||
|  |    const std::shared_ptr<gr::Placefile::IconDrawItem>& di) | ||||||
|  | { | ||||||
|  |    if (di != nullptr) | ||||||
|  |    { | ||||||
|  |       p->newIconList_.emplace_back(di); | ||||||
|  |    } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void PlacefileIcons::FinishIcons() | ||||||
|  | { | ||||||
|  |    // Update icon files
 | ||||||
|  |    for (auto& iconFile : p->newIconFiles_) | ||||||
|  |    { | ||||||
|  |       iconFile.second.UpdateTextureInfo(); | ||||||
|  |    } | ||||||
|  | 
 | ||||||
|  |    // Update buffers
 | ||||||
|  |    p->UpdateBuffers(); | ||||||
|  | 
 | ||||||
|  |    std::unique_lock lock {p->iconMutex_}; | ||||||
|  | 
 | ||||||
|  |    // Swap buffers
 | ||||||
|  |    p->currentIconList_.swap(p->newValidIconList_); | ||||||
|  |    p->currentIconFiles_.swap(p->newIconFiles_); | ||||||
|  |    p->currentIconBuffer_.swap(p->newIconBuffer_); | ||||||
|  |    p->currentIntegerBuffer_.swap(p->newIntegerBuffer_); | ||||||
|  |    p->currentHoverIcons_.swap(p->newHoverIcons_); | ||||||
|  | 
 | ||||||
|  |    // Clear the new buffers
 | ||||||
|  |    p->newIconList_.clear(); | ||||||
|  |    p->newValidIconList_.clear(); | ||||||
|  |    p->newIconFiles_.clear(); | ||||||
|  |    p->newIconBuffer_.clear(); | ||||||
|  |    p->newIntegerBuffer_.clear(); | ||||||
|  |    p->newHoverIcons_.clear(); | ||||||
|  | 
 | ||||||
|  |    // Mark the draw item dirty
 | ||||||
|  |    p->dirty_ = true; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void PlacefileIcons::Impl::UpdateBuffers() | ||||||
|  | { | ||||||
|  |    newIconBuffer_.clear(); | ||||||
|  |    newIconBuffer_.reserve(newIconList_.size() * kIconBufferLength); | ||||||
|  |    newIntegerBuffer_.clear(); | ||||||
|  |    newIntegerBuffer_.reserve(newIconList_.size() * kVerticesPerRectangle * | ||||||
|  |                              kIntegersPerVertex_); | ||||||
|  | 
 | ||||||
|  |    for (auto& di : newIconList_) | ||||||
|  |    { | ||||||
|  |       auto it = newIconFiles_.find(di->fileNumber_); | ||||||
|  |       if (it == newIconFiles_.cend()) | ||||||
|  |       { | ||||||
|  |          // No file found
 | ||||||
|  |          logger_->trace("Could not find file number: {}", di->fileNumber_); | ||||||
|  |          continue; | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       auto& icon = it->second; | ||||||
|  | 
 | ||||||
|  |       // Validate icon
 | ||||||
|  |       if (di->iconNumber_ == 0 || di->iconNumber_ > icon.numIcons_) | ||||||
|  |       { | ||||||
|  |          // No icon found
 | ||||||
|  |          logger_->trace("Invalid icon number: {}", di->iconNumber_); | ||||||
|  |          continue; | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       // Icon is valid, add to valid icon list
 | ||||||
|  |       newValidIconList_.push_back(di); | ||||||
|  | 
 | ||||||
|  |       // Threshold value
 | ||||||
|  |       units::length::nautical_miles<double> threshold = di->threshold_; | ||||||
|  |       GLint thresholdValue = static_cast<GLint>(std::round(threshold.value())); | ||||||
|  | 
 | ||||||
|  |       // Start and end time
 | ||||||
|  |       GLint startTime = | ||||||
|  |          static_cast<GLint>(std::chrono::duration_cast<std::chrono::minutes>( | ||||||
|  |                                di->startTime_.time_since_epoch()) | ||||||
|  |                                .count()); | ||||||
|  |       GLint endTime = | ||||||
|  |          static_cast<GLint>(std::chrono::duration_cast<std::chrono::minutes>( | ||||||
|  |                                di->endTime_.time_since_epoch()) | ||||||
|  |                                .count()); | ||||||
|  | 
 | ||||||
|  |       // Latitude and longitude coordinates in degrees
 | ||||||
|  |       const float lat = static_cast<float>(di->latitude_); | ||||||
|  |       const float lon = static_cast<float>(di->longitude_); | ||||||
|  | 
 | ||||||
|  |       // Base X/Y offsets in pixels
 | ||||||
|  |       const float x = static_cast<float>(di->x_); | ||||||
|  |       const float y = static_cast<float>(di->y_); | ||||||
|  | 
 | ||||||
|  |       // Icon size
 | ||||||
|  |       const float iw = static_cast<float>(icon.iconFile_->iconWidth_); | ||||||
|  |       const float ih = static_cast<float>(icon.iconFile_->iconHeight_); | ||||||
|  | 
 | ||||||
|  |       // Hot X/Y (zero-based icon center)
 | ||||||
|  |       const float hx = static_cast<float>(icon.iconFile_->hotX_); | ||||||
|  |       const float hy = static_cast<float>(icon.iconFile_->hotY_); | ||||||
|  | 
 | ||||||
|  |       // Final X/Y offsets in pixels
 | ||||||
|  |       const float lx = std::roundf(x - hx); | ||||||
|  |       const float rx = std::roundf(lx + iw); | ||||||
|  |       const float ty = std::roundf(y + hy); | ||||||
|  |       const float by = std::roundf(ty - ih); | ||||||
|  | 
 | ||||||
|  |       // Angle in degrees
 | ||||||
|  |       units::angle::degrees<float> angle = di->angle_; | ||||||
|  |       const float                  a     = angle.value(); | ||||||
|  | 
 | ||||||
|  |       // Modulate color
 | ||||||
|  |       const float mc0 = di->modulate_[0] / 255.0f; | ||||||
|  |       const float mc1 = di->modulate_[1] / 255.0f; | ||||||
|  |       const float mc2 = di->modulate_[2] / 255.0f; | ||||||
|  |       const float mc3 = di->modulate_[3] / 255.0f; | ||||||
|  | 
 | ||||||
|  |       newIconBuffer_.insert(newIconBuffer_.end(), | ||||||
|  |                             { | ||||||
|  |                                // Icon
 | ||||||
|  |                                lat, lon, lx, by, mc0, mc1, mc2, mc3, a, // BL
 | ||||||
|  |                                lat, lon, lx, ty, mc0, mc1, mc2, mc3, a, // TL
 | ||||||
|  |                                lat, lon, rx, by, mc0, mc1, mc2, mc3, a, // BR
 | ||||||
|  |                                lat, lon, rx, by, mc0, mc1, mc2, mc3, a, // BR
 | ||||||
|  |                                lat, lon, rx, ty, mc0, mc1, mc2, mc3, a, // TR
 | ||||||
|  |                                lat, lon, lx, ty, mc0, mc1, mc2, mc3, a  // TL
 | ||||||
|  |                             }); | ||||||
|  |       newIntegerBuffer_.insert(newIntegerBuffer_.end(), | ||||||
|  |                                {thresholdValue, | ||||||
|  |                                 startTime, | ||||||
|  |                                 endTime, | ||||||
|  |                                 thresholdValue, | ||||||
|  |                                 startTime, | ||||||
|  |                                 endTime, | ||||||
|  |                                 thresholdValue, | ||||||
|  |                                 startTime, | ||||||
|  |                                 endTime, | ||||||
|  |                                 thresholdValue, | ||||||
|  |                                 startTime, | ||||||
|  |                                 endTime, | ||||||
|  |                                 thresholdValue, | ||||||
|  |                                 startTime, | ||||||
|  |                                 endTime, | ||||||
|  |                                 thresholdValue, | ||||||
|  |                                 startTime, | ||||||
|  |                                 endTime}); | ||||||
|  | 
 | ||||||
|  |       if (!di->hoverText_.empty()) | ||||||
|  |       { | ||||||
|  |          const units::angle::radians<double> radians = angle; | ||||||
|  | 
 | ||||||
|  |          const auto sc = util::maplibre::LatLongToScreenCoordinate({lat, lon}); | ||||||
|  | 
 | ||||||
|  |          const float cosAngle = cosf(static_cast<float>(radians.value())); | ||||||
|  |          const float sinAngle = sinf(static_cast<float>(radians.value())); | ||||||
|  | 
 | ||||||
|  |          const glm::mat2 rotate {cosAngle, -sinAngle, sinAngle, cosAngle}; | ||||||
|  | 
 | ||||||
|  |          const glm::vec2 otl = rotate * glm::vec2 {lx, ty}; | ||||||
|  |          const glm::vec2 otr = rotate * glm::vec2 {rx, ty}; | ||||||
|  |          const glm::vec2 obl = rotate * glm::vec2 {lx, by}; | ||||||
|  |          const glm::vec2 obr = rotate * glm::vec2 {rx, by}; | ||||||
|  | 
 | ||||||
|  |          newHoverIcons_.emplace_back( | ||||||
|  |             IconHoverEntry {di, sc, otl, otr, obl, obr}); | ||||||
|  |       } | ||||||
|  |    } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void PlacefileIcons::Impl::UpdateTextureBuffer() | ||||||
|  | { | ||||||
|  |    textureBuffer_.clear(); | ||||||
|  |    textureBuffer_.reserve(currentIconList_.size() * kTextureBufferLength); | ||||||
|  | 
 | ||||||
|  |    for (auto& di : currentIconList_) | ||||||
|  |    { | ||||||
|  |       auto it = currentIconFiles_.find(di->fileNumber_); | ||||||
|  |       if (it == currentIconFiles_.cend()) | ||||||
|  |       { | ||||||
|  |          // No file found. Should not get here, but insert empty data to match
 | ||||||
|  |          // up with data already buffered
 | ||||||
|  |          logger_->error("Could not find file number: {}", di->fileNumber_); | ||||||
|  | 
 | ||||||
|  |          // clang-format off
 | ||||||
|  |          textureBuffer_.insert( | ||||||
|  |             textureBuffer_.end(), | ||||||
|  |             { | ||||||
|  |                // Icon
 | ||||||
|  |                0.0f, 0.0f, 0.0f, // BL
 | ||||||
|  |                0.0f, 0.0f, 0.0f, // TL
 | ||||||
|  |                0.0f, 0.0f, 0.0f, // BR
 | ||||||
|  |                0.0f, 0.0f, 0.0f, // BR
 | ||||||
|  |                0.0f, 0.0f, 0.0f, // TR
 | ||||||
|  |                0.0f, 0.0f, 0.0f  // TL
 | ||||||
|  |             }); | ||||||
|  |          // clang-format on
 | ||||||
|  | 
 | ||||||
|  |          continue; | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       auto& icon = it->second; | ||||||
|  | 
 | ||||||
|  |       // Validate icon
 | ||||||
|  |       if (di->iconNumber_ == 0 || di->iconNumber_ > icon.numIcons_) | ||||||
|  |       { | ||||||
|  |          // No icon found
 | ||||||
|  |          logger_->trace("Invalid icon number: {}", di->iconNumber_); | ||||||
|  | 
 | ||||||
|  |          // Will get here if a texture changes, and the texture shrunk such that
 | ||||||
|  |          // the icon is no longer found
 | ||||||
|  | 
 | ||||||
|  |          // clang-format off
 | ||||||
|  |          textureBuffer_.insert( | ||||||
|  |             textureBuffer_.end(), | ||||||
|  |             { | ||||||
|  |                // Icon
 | ||||||
|  |                0.0f, 0.0f, 0.0f, // BL
 | ||||||
|  |                0.0f, 0.0f, 0.0f, // TL
 | ||||||
|  |                0.0f, 0.0f, 0.0f, // BR
 | ||||||
|  |                0.0f, 0.0f, 0.0f, // BR
 | ||||||
|  |                0.0f, 0.0f, 0.0f, // TR
 | ||||||
|  |                0.0f, 0.0f, 0.0f  // TL
 | ||||||
|  |             }); | ||||||
|  |          // clang-format on
 | ||||||
|  | 
 | ||||||
|  |          continue; | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       // Texture coordinates
 | ||||||
|  |       const std::size_t iconRow    = (di->iconNumber_ - 1) / icon.columns_; | ||||||
|  |       const std::size_t iconColumn = (di->iconNumber_ - 1) % icon.columns_; | ||||||
|  | 
 | ||||||
|  |       const float iconX = iconColumn * icon.scaledWidth_; | ||||||
|  |       const float iconY = iconRow * icon.scaledHeight_; | ||||||
|  | 
 | ||||||
|  |       const float ls = icon.texture_.sLeft_ + iconX; | ||||||
|  |       const float rs = ls + icon.scaledWidth_; | ||||||
|  |       const float tt = icon.texture_.tTop_ + iconY; | ||||||
|  |       const float bt = tt + icon.scaledHeight_; | ||||||
|  |       const float r  = static_cast<float>(icon.texture_.layerId_); | ||||||
|  | 
 | ||||||
|  |       // clang-format off
 | ||||||
|  |       textureBuffer_.insert( | ||||||
|  |          textureBuffer_.end(), | ||||||
|  |          { | ||||||
|  |             // Icon
 | ||||||
|  |             ls, bt, r, // BL
 | ||||||
|  |             ls, tt, r, // TL
 | ||||||
|  |             rs, bt, r, // BR
 | ||||||
|  |             rs, bt, r, // BR
 | ||||||
|  |             rs, tt, r, // TR
 | ||||||
|  |             ls, tt, r  // TL
 | ||||||
|  |          }); | ||||||
|  |       // clang-format on
 | ||||||
|  |    } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void PlacefileIcons::Impl::Update(bool textureAtlasChanged) | ||||||
|  | { | ||||||
|  |    gl::OpenGLFunctions& gl = context_->gl(); | ||||||
|  | 
 | ||||||
|  |    // If the texture atlas has changed
 | ||||||
|  |    if (dirty_ || textureAtlasChanged) | ||||||
|  |    { | ||||||
|  |       // Update texture coordinates
 | ||||||
|  |       for (auto& iconFile : currentIconFiles_) | ||||||
|  |       { | ||||||
|  |          iconFile.second.UpdateTextureInfo(); | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       // Update OpenGL texture buffer data
 | ||||||
|  |       UpdateTextureBuffer(); | ||||||
|  | 
 | ||||||
|  |       // Buffer texture data
 | ||||||
|  |       gl.glBindBuffer(GL_ARRAY_BUFFER, vbo_[1]); | ||||||
|  |       gl.glBufferData(GL_ARRAY_BUFFER, | ||||||
|  |                       sizeof(float) * textureBuffer_.size(), | ||||||
|  |                       textureBuffer_.data(), | ||||||
|  |                       GL_DYNAMIC_DRAW); | ||||||
|  |    } | ||||||
|  | 
 | ||||||
|  |    // If buffers need updating
 | ||||||
|  |    if (dirty_) | ||||||
|  |    { | ||||||
|  |       // Buffer vertex data
 | ||||||
|  |       gl.glBindBuffer(GL_ARRAY_BUFFER, vbo_[0]); | ||||||
|  |       gl.glBufferData(GL_ARRAY_BUFFER, | ||||||
|  |                       sizeof(float) * currentIconBuffer_.size(), | ||||||
|  |                       currentIconBuffer_.data(), | ||||||
|  |                       GL_DYNAMIC_DRAW); | ||||||
|  | 
 | ||||||
|  |       // Buffer threshold data
 | ||||||
|  |       gl.glBindBuffer(GL_ARRAY_BUFFER, vbo_[2]); | ||||||
|  |       gl.glBufferData(GL_ARRAY_BUFFER, | ||||||
|  |                       sizeof(GLint) * currentIntegerBuffer_.size(), | ||||||
|  |                       currentIntegerBuffer_.data(), | ||||||
|  |                       GL_DYNAMIC_DRAW); | ||||||
|  | 
 | ||||||
|  |       numVertices_ = | ||||||
|  |          static_cast<GLsizei>(currentIconBuffer_.size() / kPointsPerVertex); | ||||||
|  |    } | ||||||
|  | 
 | ||||||
|  |    dirty_ = false; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool PlacefileIcons::RunMousePicking( | ||||||
|  |    const QMapLibreGL::CustomLayerRenderParameters& params, | ||||||
|  |    const QPointF& /* mouseLocalPos */, | ||||||
|  |    const QPointF&   mouseGlobalPos, | ||||||
|  |    const glm::vec2& mouseCoords) | ||||||
|  | { | ||||||
|  |    std::unique_lock lock {p->iconMutex_}; | ||||||
|  | 
 | ||||||
|  |    bool itemPicked = false; | ||||||
|  | 
 | ||||||
|  |    // Calculate map scale, remove width and height from original calculation
 | ||||||
|  |    glm::vec2 scale = util::maplibre::GetMapScale(params); | ||||||
|  |    scale = 2.0f / glm::vec2 {scale.x * params.width, scale.y * params.height}; | ||||||
|  | 
 | ||||||
|  |    // Scale and rotate the identity matrix to create the map matrix
 | ||||||
|  |    glm::mat4 mapMatrix {1.0f}; | ||||||
|  |    mapMatrix = glm::scale(mapMatrix, glm::vec3 {scale, 1.0f}); | ||||||
|  |    mapMatrix = glm::rotate(mapMatrix, | ||||||
|  |                            glm::radians<float>(params.bearing), | ||||||
|  |                            glm::vec3(0.0f, 0.0f, 1.0f)); | ||||||
|  | 
 | ||||||
|  |    units::length::meters<double> mapDistance = | ||||||
|  |       (p->thresholded_) ? util::maplibre::GetMapDistance(params) : | ||||||
|  |                           units::length::meters<double> {0.0}; | ||||||
|  | 
 | ||||||
|  |    // If no time has been selected, use the current time
 | ||||||
|  |    std::chrono::system_clock::time_point selectedTime = | ||||||
|  |       (p->selectedTime_ == std::chrono::system_clock::time_point {}) ? | ||||||
|  |          std::chrono::system_clock::now() : | ||||||
|  |          p->selectedTime_; | ||||||
|  | 
 | ||||||
|  |    // For each pickable icon
 | ||||||
|  |    auto it = std::find_if( | ||||||
|  |       std::execution::par_unseq, | ||||||
|  |       p->currentHoverIcons_.crbegin(), | ||||||
|  |       p->currentHoverIcons_.crend(), | ||||||
|  |       [&mapDistance, &selectedTime, &mapMatrix, &mouseCoords](const auto& icon) | ||||||
|  |       { | ||||||
|  |          if (( | ||||||
|  |                 // Placefile is thresholded
 | ||||||
|  |                 mapDistance > units::length::meters<double> {0.0} && | ||||||
|  | 
 | ||||||
|  |                 // Placefile threshold is < 999 nmi
 | ||||||
|  |                 static_cast<int>(std::round( | ||||||
|  |                    units::length::nautical_miles<double> {icon.di_->threshold_} | ||||||
|  |                       .value())) < 999 && | ||||||
|  | 
 | ||||||
|  |                 // Map distance is beyond the threshold
 | ||||||
|  |                 icon.di_->threshold_ < mapDistance) || | ||||||
|  | 
 | ||||||
|  |              ( | ||||||
|  |                 // Line has a start time
 | ||||||
|  |                 icon.di_->startTime_ != | ||||||
|  |                    std::chrono::system_clock::time_point {} && | ||||||
|  | 
 | ||||||
|  |                 // The time range has not yet started
 | ||||||
|  |                 (selectedTime < icon.di_->startTime_ || | ||||||
|  | 
 | ||||||
|  |                  // The time range has ended
 | ||||||
|  |                  icon.di_->endTime_ <= selectedTime))) | ||||||
|  |          { | ||||||
|  |             // Icon is not pickable
 | ||||||
|  |             return false; | ||||||
|  |          } | ||||||
|  | 
 | ||||||
|  |          // Initialize vertices
 | ||||||
|  |          glm::vec2 bl = icon.p_; | ||||||
|  |          glm::vec2 br = bl; | ||||||
|  |          glm::vec2 tl = br; | ||||||
|  |          glm::vec2 tr = tl; | ||||||
|  | 
 | ||||||
|  |          // Calculate offsets
 | ||||||
|  |          // - Rotated offset is based on final X/Y offsets (pixels)
 | ||||||
|  |          // - Multiply the offset by the scaled and rotated map matrix
 | ||||||
|  |          const glm::vec2 otl = mapMatrix * glm::vec4 {icon.otl_, 0.0f, 1.0f}; | ||||||
|  |          const glm::vec2 obl = mapMatrix * glm::vec4 {icon.obl_, 0.0f, 1.0f}; | ||||||
|  |          const glm::vec2 obr = mapMatrix * glm::vec4 {icon.obr_, 0.0f, 1.0f}; | ||||||
|  |          const glm::vec2 otr = mapMatrix * glm::vec4 {icon.otr_, 0.0f, 1.0f}; | ||||||
|  | 
 | ||||||
|  |          // Offset vertices
 | ||||||
|  |          tl += otl; | ||||||
|  |          bl += obl; | ||||||
|  |          br += obr; | ||||||
|  |          tr += otr; | ||||||
|  | 
 | ||||||
|  |          // Test point against polygon bounds
 | ||||||
|  |          return util::maplibre::IsPointInPolygon({tl, bl, br, tr}, mouseCoords); | ||||||
|  |       }); | ||||||
|  | 
 | ||||||
|  |    if (it != p->currentHoverIcons_.crend()) | ||||||
|  |    { | ||||||
|  |       itemPicked = true; | ||||||
|  |       util::tooltip::Show(it->di_->hoverText_, mouseGlobalPos); | ||||||
|  |    } | ||||||
|  | 
 | ||||||
|  |    return itemPicked; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | } // namespace draw
 | ||||||
|  | } // namespace gl
 | ||||||
|  | } // namespace qt
 | ||||||
|  | } // namespace scwx
 | ||||||
							
								
								
									
										80
									
								
								scwx-qt/source/scwx/qt/gl/draw/placefile_icons.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						|  | @ -0,0 +1,80 @@ | ||||||
|  | #pragma once | ||||||
|  | 
 | ||||||
|  | #include <scwx/qt/gl/gl_context.hpp> | ||||||
|  | #include <scwx/qt/gl/draw/draw_item.hpp> | ||||||
|  | #include <scwx/gr/placefile.hpp> | ||||||
|  | 
 | ||||||
|  | #include <boost/gil.hpp> | ||||||
|  | 
 | ||||||
|  | namespace scwx | ||||||
|  | { | ||||||
|  | namespace qt | ||||||
|  | { | ||||||
|  | namespace gl | ||||||
|  | { | ||||||
|  | namespace draw | ||||||
|  | { | ||||||
|  | 
 | ||||||
|  | class PlacefileIcons : public DrawItem | ||||||
|  | { | ||||||
|  | public: | ||||||
|  |    explicit PlacefileIcons(const std::shared_ptr<GlContext>& context); | ||||||
|  |    ~PlacefileIcons(); | ||||||
|  | 
 | ||||||
|  |    PlacefileIcons(const PlacefileIcons&)            = delete; | ||||||
|  |    PlacefileIcons& operator=(const PlacefileIcons&) = delete; | ||||||
|  | 
 | ||||||
|  |    PlacefileIcons(PlacefileIcons&&) noexcept; | ||||||
|  |    PlacefileIcons& operator=(PlacefileIcons&&) noexcept; | ||||||
|  | 
 | ||||||
|  |    void set_selected_time(std::chrono::system_clock::time_point selectedTime); | ||||||
|  |    void set_thresholded(bool thresholded); | ||||||
|  | 
 | ||||||
|  |    void Initialize() override; | ||||||
|  |    void Render(const QMapLibreGL::CustomLayerRenderParameters& params, | ||||||
|  |                bool textureAtlasChanged) override; | ||||||
|  |    void Deinitialize() override; | ||||||
|  | 
 | ||||||
|  |    bool RunMousePicking(const QMapLibreGL::CustomLayerRenderParameters& params, | ||||||
|  |                         const QPointF&   mouseLocalPos, | ||||||
|  |                         const QPointF&   mouseGlobalPos, | ||||||
|  |                         const glm::vec2& mouseCoords) override; | ||||||
|  | 
 | ||||||
|  |    /**
 | ||||||
|  |     * Resets and prepares the draw item for adding a new set of icons. | ||||||
|  |     */ | ||||||
|  |    void StartIcons(); | ||||||
|  | 
 | ||||||
|  |    /**
 | ||||||
|  |     * Configures the textures for drawing the placefile icons. | ||||||
|  |     * | ||||||
|  |     * @param [in] iconFiles A list of icon files | ||||||
|  |     * @param [in] baseUrl The base URL of the placefile | ||||||
|  |     */ | ||||||
|  |    void SetIconFiles( | ||||||
|  |       const std::vector<std::shared_ptr<const gr::Placefile::IconFile>>& | ||||||
|  |                          iconFiles, | ||||||
|  |       const std::string& baseUrl); | ||||||
|  | 
 | ||||||
|  |    /**
 | ||||||
|  |     * Adds a placefile icon to the internal draw list. | ||||||
|  |     * | ||||||
|  |     * @param [in] di Placefile icon | ||||||
|  |     */ | ||||||
|  |    void AddIcon(const std::shared_ptr<gr::Placefile::IconDrawItem>& di); | ||||||
|  | 
 | ||||||
|  |    /**
 | ||||||
|  |     * Finalizes the draw item after adding new icons. | ||||||
|  |     */ | ||||||
|  |    void FinishIcons(); | ||||||
|  | 
 | ||||||
|  | private: | ||||||
|  |    class Impl; | ||||||
|  | 
 | ||||||
|  |    std::unique_ptr<Impl> p; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | } // namespace draw
 | ||||||
|  | } // namespace gl
 | ||||||
|  | } // namespace qt
 | ||||||
|  | } // namespace scwx
 | ||||||
							
								
								
									
										487
									
								
								scwx-qt/source/scwx/qt/gl/draw/placefile_images.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						|  | @ -0,0 +1,487 @@ | ||||||
|  | #include <scwx/qt/gl/draw/placefile_images.hpp> | ||||||
|  | #include <scwx/qt/util/maplibre.hpp> | ||||||
|  | #include <scwx/qt/util/texture_atlas.hpp> | ||||||
|  | #include <scwx/util/logger.hpp> | ||||||
|  | 
 | ||||||
|  | #include <QDir> | ||||||
|  | #include <QUrl> | ||||||
|  | #include <boost/unordered/unordered_flat_map.hpp> | ||||||
|  | 
 | ||||||
|  | namespace scwx | ||||||
|  | { | ||||||
|  | namespace qt | ||||||
|  | { | ||||||
|  | namespace gl | ||||||
|  | { | ||||||
|  | namespace draw | ||||||
|  | { | ||||||
|  | 
 | ||||||
|  | static const std::string logPrefix_ = "scwx::qt::gl::draw::placefile_images"; | ||||||
|  | static const auto        logger_    = scwx::util::Logger::Create(logPrefix_); | ||||||
|  | 
 | ||||||
|  | static constexpr std::size_t kNumRectangles        = 1; | ||||||
|  | static constexpr std::size_t kNumTriangles         = kNumRectangles * 2; | ||||||
|  | static constexpr std::size_t kVerticesPerTriangle  = 3; | ||||||
|  | static constexpr std::size_t kVerticesPerRectangle = kVerticesPerTriangle * 2; | ||||||
|  | static constexpr std::size_t kPointsPerVertex      = 8; | ||||||
|  | static constexpr std::size_t kPointsPerTexCoord    = 3; | ||||||
|  | static constexpr std::size_t kImageBufferLength = | ||||||
|  |    kNumTriangles * kVerticesPerTriangle * kPointsPerVertex; | ||||||
|  | static constexpr std::size_t kTextureBufferLength = | ||||||
|  |    kNumTriangles * kVerticesPerTriangle * kPointsPerTexCoord; | ||||||
|  | 
 | ||||||
|  | // Threshold, start time, end time
 | ||||||
|  | static constexpr std::size_t kIntegersPerVertex_ = 3; | ||||||
|  | 
 | ||||||
|  | struct PlacefileImageInfo | ||||||
|  | { | ||||||
|  |    PlacefileImageInfo(const std::string& imageFile, | ||||||
|  |                       const std::string& baseUrlString) | ||||||
|  |    { | ||||||
|  |       // Resolve using base URL
 | ||||||
|  |       auto baseUrl = QUrl::fromUserInput(QString::fromStdString(baseUrlString)); | ||||||
|  |       auto relativeUrl = | ||||||
|  |          QUrl(QDir::fromNativeSeparators(QString::fromStdString(imageFile))); | ||||||
|  |       resolvedUrl_ = baseUrl.resolved(relativeUrl).toString().toStdString(); | ||||||
|  |    } | ||||||
|  | 
 | ||||||
|  |    void UpdateTextureInfo(); | ||||||
|  | 
 | ||||||
|  |    std::string             resolvedUrl_; | ||||||
|  |    util::TextureAttributes texture_ {}; | ||||||
|  |    float                   scaledWidth_ {}; | ||||||
|  |    float                   scaledHeight_ {}; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | class PlacefileImages::Impl | ||||||
|  | { | ||||||
|  | public: | ||||||
|  |    explicit Impl(const std::shared_ptr<GlContext>& context) : | ||||||
|  |        context_ {context}, | ||||||
|  |        shaderProgram_ {nullptr}, | ||||||
|  |        uMVPMatrixLocation_(GL_INVALID_INDEX), | ||||||
|  |        uMapMatrixLocation_(GL_INVALID_INDEX), | ||||||
|  |        uMapScreenCoordLocation_(GL_INVALID_INDEX), | ||||||
|  |        uMapDistanceLocation_(GL_INVALID_INDEX), | ||||||
|  |        uSelectedTimeLocation_(GL_INVALID_INDEX), | ||||||
|  |        vao_ {GL_INVALID_INDEX}, | ||||||
|  |        vbo_ {GL_INVALID_INDEX}, | ||||||
|  |        numVertices_ {0} | ||||||
|  |    { | ||||||
|  |    } | ||||||
|  | 
 | ||||||
|  |    ~Impl() {} | ||||||
|  | 
 | ||||||
|  |    void UpdateBuffers(); | ||||||
|  |    void UpdateTextureBuffer(); | ||||||
|  |    void Update(bool textureAtlasChanged); | ||||||
|  | 
 | ||||||
|  |    std::shared_ptr<GlContext> context_; | ||||||
|  | 
 | ||||||
|  |    std::string baseUrl_ {}; | ||||||
|  | 
 | ||||||
|  |    bool dirty_ {false}; | ||||||
|  |    bool thresholded_ {false}; | ||||||
|  | 
 | ||||||
|  |    std::chrono::system_clock::time_point selectedTime_ {}; | ||||||
|  | 
 | ||||||
|  |    std::mutex imageMutex_; | ||||||
|  | 
 | ||||||
|  |    boost::unordered_flat_map<std::string, PlacefileImageInfo> | ||||||
|  |       currentImageFiles_ {}; | ||||||
|  |    boost::unordered_flat_map<std::string, PlacefileImageInfo> newImageFiles_ {}; | ||||||
|  | 
 | ||||||
|  |    std::vector<std::shared_ptr<const gr::Placefile::ImageDrawItem>> | ||||||
|  |       currentImageList_ {}; | ||||||
|  |    std::vector<std::shared_ptr<const gr::Placefile::ImageDrawItem>> | ||||||
|  |       newImageList_ {}; | ||||||
|  | 
 | ||||||
|  |    std::vector<float> currentImageBuffer_ {}; | ||||||
|  |    std::vector<GLint> currentIntegerBuffer_ {}; | ||||||
|  |    std::vector<float> newImageBuffer_ {}; | ||||||
|  |    std::vector<GLint> newIntegerBuffer_ {}; | ||||||
|  | 
 | ||||||
|  |    std::vector<float> textureBuffer_ {}; | ||||||
|  | 
 | ||||||
|  |    std::shared_ptr<ShaderProgram> shaderProgram_; | ||||||
|  |    GLint                          uMVPMatrixLocation_; | ||||||
|  |    GLint                          uMapMatrixLocation_; | ||||||
|  |    GLint                          uMapScreenCoordLocation_; | ||||||
|  |    GLint                          uMapDistanceLocation_; | ||||||
|  |    GLint                          uSelectedTimeLocation_; | ||||||
|  | 
 | ||||||
|  |    GLuint                vao_; | ||||||
|  |    std::array<GLuint, 3> vbo_; | ||||||
|  | 
 | ||||||
|  |    GLsizei numVertices_; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | PlacefileImages::PlacefileImages(const std::shared_ptr<GlContext>& context) : | ||||||
|  |     DrawItem(context->gl()), p(std::make_unique<Impl>(context)) | ||||||
|  | { | ||||||
|  | } | ||||||
|  | PlacefileImages::~PlacefileImages() = default; | ||||||
|  | 
 | ||||||
|  | PlacefileImages::PlacefileImages(PlacefileImages&&) noexcept = default; | ||||||
|  | PlacefileImages& | ||||||
|  | PlacefileImages::operator=(PlacefileImages&&) noexcept = default; | ||||||
|  | 
 | ||||||
|  | void PlacefileImages::set_selected_time( | ||||||
|  |    std::chrono::system_clock::time_point selectedTime) | ||||||
|  | { | ||||||
|  |    p->selectedTime_ = selectedTime; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void PlacefileImages::set_thresholded(bool thresholded) | ||||||
|  | { | ||||||
|  |    p->thresholded_ = thresholded; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void PlacefileImages::Initialize() | ||||||
|  | { | ||||||
|  |    gl::OpenGLFunctions& gl = p->context_->gl(); | ||||||
|  | 
 | ||||||
|  |    p->shaderProgram_ = p->context_->GetShaderProgram( | ||||||
|  |       {{GL_VERTEX_SHADER, ":/gl/geo_texture2d.vert"}, | ||||||
|  |        {GL_GEOMETRY_SHADER, ":/gl/threshold.geom"}, | ||||||
|  |        {GL_FRAGMENT_SHADER, ":/gl/texture2d_array.frag"}}); | ||||||
|  | 
 | ||||||
|  |    p->uMVPMatrixLocation_ = p->shaderProgram_->GetUniformLocation("uMVPMatrix"); | ||||||
|  |    p->uMapMatrixLocation_ = p->shaderProgram_->GetUniformLocation("uMapMatrix"); | ||||||
|  |    p->uMapScreenCoordLocation_ = | ||||||
|  |       p->shaderProgram_->GetUniformLocation("uMapScreenCoord"); | ||||||
|  |    p->uMapDistanceLocation_ = | ||||||
|  |       p->shaderProgram_->GetUniformLocation("uMapDistance"); | ||||||
|  |    p->uSelectedTimeLocation_ = | ||||||
|  |       p->shaderProgram_->GetUniformLocation("uSelectedTime"); | ||||||
|  | 
 | ||||||
|  |    gl.glGenVertexArrays(1, &p->vao_); | ||||||
|  |    gl.glGenBuffers(static_cast<GLsizei>(p->vbo_.size()), p->vbo_.data()); | ||||||
|  | 
 | ||||||
|  |    gl.glBindVertexArray(p->vao_); | ||||||
|  |    gl.glBindBuffer(GL_ARRAY_BUFFER, p->vbo_[0]); | ||||||
|  |    gl.glBufferData(GL_ARRAY_BUFFER, 0u, nullptr, GL_DYNAMIC_DRAW); | ||||||
|  | 
 | ||||||
|  |    // aLatLong
 | ||||||
|  |    gl.glVertexAttribPointer(0, | ||||||
|  |                             2, | ||||||
|  |                             GL_FLOAT, | ||||||
|  |                             GL_FALSE, | ||||||
|  |                             kPointsPerVertex * sizeof(float), | ||||||
|  |                             static_cast<void*>(0)); | ||||||
|  |    gl.glEnableVertexAttribArray(0); | ||||||
|  | 
 | ||||||
|  |    // aXYOffset
 | ||||||
|  |    gl.glVertexAttribPointer(1, | ||||||
|  |                             2, | ||||||
|  |                             GL_FLOAT, | ||||||
|  |                             GL_FALSE, | ||||||
|  |                             kPointsPerVertex * sizeof(float), | ||||||
|  |                             reinterpret_cast<void*>(2 * sizeof(float))); | ||||||
|  |    gl.glEnableVertexAttribArray(1); | ||||||
|  | 
 | ||||||
|  |    // aModulate
 | ||||||
|  |    gl.glVertexAttribPointer(3, | ||||||
|  |                             4, | ||||||
|  |                             GL_FLOAT, | ||||||
|  |                             GL_FALSE, | ||||||
|  |                             kPointsPerVertex * sizeof(float), | ||||||
|  |                             reinterpret_cast<void*>(4 * sizeof(float))); | ||||||
|  |    gl.glEnableVertexAttribArray(3); | ||||||
|  | 
 | ||||||
|  |    gl.glBindBuffer(GL_ARRAY_BUFFER, p->vbo_[1]); | ||||||
|  |    gl.glBufferData(GL_ARRAY_BUFFER, 0u, nullptr, GL_DYNAMIC_DRAW); | ||||||
|  | 
 | ||||||
|  |    // aTexCoord
 | ||||||
|  |    gl.glVertexAttribPointer(2, | ||||||
|  |                             3, | ||||||
|  |                             GL_FLOAT, | ||||||
|  |                             GL_FALSE, | ||||||
|  |                             kPointsPerTexCoord * sizeof(float), | ||||||
|  |                             static_cast<void*>(0)); | ||||||
|  |    gl.glEnableVertexAttribArray(2); | ||||||
|  | 
 | ||||||
|  |    gl.glBindBuffer(GL_ARRAY_BUFFER, p->vbo_[2]); | ||||||
|  |    gl.glBufferData(GL_ARRAY_BUFFER, 0u, nullptr, GL_DYNAMIC_DRAW); | ||||||
|  | 
 | ||||||
|  |    // aThreshold
 | ||||||
|  |    gl.glVertexAttribIPointer(5, //
 | ||||||
|  |                              1, | ||||||
|  |                              GL_INT, | ||||||
|  |                              kIntegersPerVertex_ * sizeof(GLint), | ||||||
|  |                              static_cast<void*>(0)); | ||||||
|  |    gl.glEnableVertexAttribArray(5); | ||||||
|  | 
 | ||||||
|  |    // aTimeRange
 | ||||||
|  |    gl.glVertexAttribIPointer(6, //
 | ||||||
|  |                              2, | ||||||
|  |                              GL_INT, | ||||||
|  |                              kIntegersPerVertex_ * sizeof(GLint), | ||||||
|  |                              reinterpret_cast<void*>(1 * sizeof(GLint))); | ||||||
|  |    gl.glEnableVertexAttribArray(6); | ||||||
|  | 
 | ||||||
|  |    p->dirty_ = true; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void PlacefileImages::Render( | ||||||
|  |    const QMapLibreGL::CustomLayerRenderParameters& params, | ||||||
|  |    bool                                            textureAtlasChanged) | ||||||
|  | { | ||||||
|  |    std::unique_lock lock {p->imageMutex_}; | ||||||
|  | 
 | ||||||
|  |    if (!p->currentImageList_.empty()) | ||||||
|  |    { | ||||||
|  |       gl::OpenGLFunctions& gl = p->context_->gl(); | ||||||
|  | 
 | ||||||
|  |       gl.glBindVertexArray(p->vao_); | ||||||
|  | 
 | ||||||
|  |       p->Update(textureAtlasChanged); | ||||||
|  |       p->shaderProgram_->Use(); | ||||||
|  |       UseRotationProjection(params, p->uMVPMatrixLocation_); | ||||||
|  |       UseMapProjection( | ||||||
|  |          params, p->uMapMatrixLocation_, p->uMapScreenCoordLocation_); | ||||||
|  | 
 | ||||||
|  |       if (p->thresholded_) | ||||||
|  |       { | ||||||
|  |          // If thresholding is enabled, set the map distance
 | ||||||
|  |          units::length::nautical_miles<float> mapDistance = | ||||||
|  |             util::maplibre::GetMapDistance(params); | ||||||
|  |          gl.glUniform1f(p->uMapDistanceLocation_, mapDistance.value()); | ||||||
|  |       } | ||||||
|  |       else | ||||||
|  |       { | ||||||
|  |          // If thresholding is disabled, set the map distance to 0
 | ||||||
|  |          gl.glUniform1f(p->uMapDistanceLocation_, 0.0f); | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       // Selected time
 | ||||||
|  |       std::chrono::system_clock::time_point selectedTime = | ||||||
|  |          (p->selectedTime_ == std::chrono::system_clock::time_point {}) ? | ||||||
|  |             std::chrono::system_clock::now() : | ||||||
|  |             p->selectedTime_; | ||||||
|  |       gl.glUniform1i( | ||||||
|  |          p->uSelectedTimeLocation_, | ||||||
|  |          static_cast<GLint>(std::chrono::duration_cast<std::chrono::minutes>( | ||||||
|  |                                selectedTime.time_since_epoch()) | ||||||
|  |                                .count())); | ||||||
|  | 
 | ||||||
|  |       // Interpolate texture coordinates
 | ||||||
|  |       gl.glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_LINEAR); | ||||||
|  |       gl.glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_LINEAR); | ||||||
|  | 
 | ||||||
|  |       // Draw images
 | ||||||
|  |       gl.glDrawArrays(GL_TRIANGLES, 0, p->numVertices_); | ||||||
|  |    } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void PlacefileImages::Deinitialize() | ||||||
|  | { | ||||||
|  |    gl::OpenGLFunctions& gl = p->context_->gl(); | ||||||
|  | 
 | ||||||
|  |    gl.glDeleteVertexArrays(1, &p->vao_); | ||||||
|  |    gl.glDeleteBuffers(static_cast<GLsizei>(p->vbo_.size()), p->vbo_.data()); | ||||||
|  | 
 | ||||||
|  |    std::unique_lock lock {p->imageMutex_}; | ||||||
|  | 
 | ||||||
|  |    p->currentImageList_.clear(); | ||||||
|  |    p->currentImageFiles_.clear(); | ||||||
|  |    p->currentImageBuffer_.clear(); | ||||||
|  |    p->currentIntegerBuffer_.clear(); | ||||||
|  |    p->textureBuffer_.clear(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void PlacefileImageInfo::UpdateTextureInfo() | ||||||
|  | { | ||||||
|  |    texture_ = util::TextureAtlas::Instance().GetTextureAttributes(resolvedUrl_); | ||||||
|  | 
 | ||||||
|  |    scaledWidth_  = texture_.sRight_ - texture_.sLeft_; | ||||||
|  |    scaledHeight_ = texture_.tBottom_ - texture_.tTop_; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void PlacefileImages::StartImages(const std::string& baseUrl) | ||||||
|  | { | ||||||
|  |    p->baseUrl_ = baseUrl; | ||||||
|  | 
 | ||||||
|  |    // Clear the new buffer
 | ||||||
|  |    p->newImageList_.clear(); | ||||||
|  |    p->newImageFiles_.clear(); | ||||||
|  |    p->newImageBuffer_.clear(); | ||||||
|  |    p->newIntegerBuffer_.clear(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void PlacefileImages::AddImage( | ||||||
|  |    const std::shared_ptr<gr::Placefile::ImageDrawItem>& di) | ||||||
|  | { | ||||||
|  |    if (di != nullptr) | ||||||
|  |    { | ||||||
|  |       p->newImageList_.emplace_back(di); | ||||||
|  |    } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void PlacefileImages::FinishImages() | ||||||
|  | { | ||||||
|  |    // Update buffers
 | ||||||
|  |    p->UpdateBuffers(); | ||||||
|  | 
 | ||||||
|  |    std::unique_lock lock {p->imageMutex_}; | ||||||
|  | 
 | ||||||
|  |    // Swap buffers
 | ||||||
|  |    p->currentImageList_.swap(p->newImageList_); | ||||||
|  |    p->currentImageFiles_.swap(p->newImageFiles_); | ||||||
|  |    p->currentImageBuffer_.swap(p->newImageBuffer_); | ||||||
|  |    p->currentIntegerBuffer_.swap(p->newIntegerBuffer_); | ||||||
|  | 
 | ||||||
|  |    // Clear the new buffers
 | ||||||
|  |    p->newImageList_.clear(); | ||||||
|  |    p->newImageFiles_.clear(); | ||||||
|  |    p->newImageBuffer_.clear(); | ||||||
|  |    p->newIntegerBuffer_.clear(); | ||||||
|  | 
 | ||||||
|  |    // Mark the draw item dirty
 | ||||||
|  |    p->dirty_ = true; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void PlacefileImages::Impl::UpdateBuffers() | ||||||
|  | { | ||||||
|  |    newImageBuffer_.clear(); | ||||||
|  |    newImageBuffer_.reserve(newImageList_.size() * kImageBufferLength); | ||||||
|  |    newIntegerBuffer_.clear(); | ||||||
|  |    newIntegerBuffer_.reserve(newImageList_.size() * kVerticesPerRectangle * | ||||||
|  |                              kIntegersPerVertex_); | ||||||
|  |    newImageFiles_.clear(); | ||||||
|  | 
 | ||||||
|  |    // Fixed modulate color
 | ||||||
|  |    static const float mc0 = 1.0f; | ||||||
|  |    static const float mc1 = 1.0f; | ||||||
|  |    static const float mc2 = 1.0f; | ||||||
|  |    static const float mc3 = 1.0f; | ||||||
|  | 
 | ||||||
|  |    for (auto& di : newImageList_) | ||||||
|  |    { | ||||||
|  |       // Populate image file map
 | ||||||
|  |       newImageFiles_.emplace( | ||||||
|  |          std::piecewise_construct, | ||||||
|  |          std::tuple {di->imageFile_}, | ||||||
|  |          std::forward_as_tuple(PlacefileImageInfo {di->imageFile_, baseUrl_})); | ||||||
|  | 
 | ||||||
|  |       // Threshold value
 | ||||||
|  |       units::length::nautical_miles<double> threshold = di->threshold_; | ||||||
|  |       GLint thresholdValue = static_cast<GLint>(std::round(threshold.value())); | ||||||
|  | 
 | ||||||
|  |       // Start and end time
 | ||||||
|  |       GLint startTime = | ||||||
|  |          static_cast<GLint>(std::chrono::duration_cast<std::chrono::minutes>( | ||||||
|  |                                di->startTime_.time_since_epoch()) | ||||||
|  |                                .count()); | ||||||
|  |       GLint endTime = | ||||||
|  |          static_cast<GLint>(std::chrono::duration_cast<std::chrono::minutes>( | ||||||
|  |                                di->endTime_.time_since_epoch()) | ||||||
|  |                                .count()); | ||||||
|  | 
 | ||||||
|  |       // Limit processing to groups of 3 (triangles)
 | ||||||
|  |       std::size_t numElements = di->elements_.size() - di->elements_.size() % 3; | ||||||
|  |       for (std::size_t i = 0; i < numElements; ++i) | ||||||
|  |       { | ||||||
|  |          auto& element = di->elements_[i]; | ||||||
|  | 
 | ||||||
|  |          // Latitude and longitude coordinates in degrees
 | ||||||
|  |          const float lat = static_cast<float>(element.latitude_); | ||||||
|  |          const float lon = static_cast<float>(element.longitude_); | ||||||
|  | 
 | ||||||
|  |          // Base X/Y offsets in pixels
 | ||||||
|  |          const float x = static_cast<float>(element.x_); | ||||||
|  |          const float y = static_cast<float>(element.y_); | ||||||
|  | 
 | ||||||
|  |          newImageBuffer_.insert(newImageBuffer_.end(), | ||||||
|  |                                 {lat, lon, x, y, mc0, mc1, mc2, mc3}); | ||||||
|  |          newIntegerBuffer_.insert(newIntegerBuffer_.end(), | ||||||
|  |                                   {thresholdValue, startTime, endTime}); | ||||||
|  |       } | ||||||
|  |    } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void PlacefileImages::Impl::UpdateTextureBuffer() | ||||||
|  | { | ||||||
|  |    textureBuffer_.clear(); | ||||||
|  |    textureBuffer_.reserve(currentImageList_.size() * kTextureBufferLength); | ||||||
|  | 
 | ||||||
|  |    for (auto& di : currentImageList_) | ||||||
|  |    { | ||||||
|  |       // Get placefile image info. The key should always be found in the map, as
 | ||||||
|  |       // it is populated when the placefile is updated.
 | ||||||
|  |       auto                      it    = currentImageFiles_.find(di->imageFile_); | ||||||
|  |       const PlacefileImageInfo& image = (it == currentImageFiles_.cend()) ? | ||||||
|  |                                            currentImageFiles_.cbegin()->second : | ||||||
|  |                                            it->second; | ||||||
|  | 
 | ||||||
|  |       const float r = static_cast<float>(image.texture_.layerId_); | ||||||
|  | 
 | ||||||
|  |       // Limit processing to groups of 3 (triangles)
 | ||||||
|  |       std::size_t numElements = di->elements_.size() - di->elements_.size() % 3; | ||||||
|  |       for (std::size_t i = 0; i < numElements; ++i) | ||||||
|  |       { | ||||||
|  |          auto& element = di->elements_[i]; | ||||||
|  | 
 | ||||||
|  |          // Texture coordinates
 | ||||||
|  |          const float s = | ||||||
|  |             image.texture_.sLeft_ + (image.scaledWidth_ * element.tu_); | ||||||
|  |          const float t = | ||||||
|  |             image.texture_.tTop_ + (image.scaledHeight_ * element.tv_); | ||||||
|  | 
 | ||||||
|  |          textureBuffer_.insert(textureBuffer_.end(), {s, t, r}); | ||||||
|  |       } | ||||||
|  |    } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void PlacefileImages::Impl::Update(bool textureAtlasChanged) | ||||||
|  | { | ||||||
|  |    gl::OpenGLFunctions& gl = context_->gl(); | ||||||
|  | 
 | ||||||
|  |    // If the texture atlas has changed
 | ||||||
|  |    if (dirty_ || textureAtlasChanged) | ||||||
|  |    { | ||||||
|  |       // Update texture coordinates
 | ||||||
|  |       for (auto& imageFile : currentImageFiles_) | ||||||
|  |       { | ||||||
|  |          imageFile.second.UpdateTextureInfo(); | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       // Update OpenGL texture buffer data
 | ||||||
|  |       UpdateTextureBuffer(); | ||||||
|  | 
 | ||||||
|  |       // Buffer texture data
 | ||||||
|  |       gl.glBindBuffer(GL_ARRAY_BUFFER, vbo_[1]); | ||||||
|  |       gl.glBufferData(GL_ARRAY_BUFFER, | ||||||
|  |                       sizeof(float) * textureBuffer_.size(), | ||||||
|  |                       textureBuffer_.data(), | ||||||
|  |                       GL_DYNAMIC_DRAW); | ||||||
|  |    } | ||||||
|  | 
 | ||||||
|  |    // If buffers need updating
 | ||||||
|  |    if (dirty_) | ||||||
|  |    { | ||||||
|  |       // Buffer vertex data
 | ||||||
|  |       gl.glBindBuffer(GL_ARRAY_BUFFER, vbo_[0]); | ||||||
|  |       gl.glBufferData(GL_ARRAY_BUFFER, | ||||||
|  |                       sizeof(float) * currentImageBuffer_.size(), | ||||||
|  |                       currentImageBuffer_.data(), | ||||||
|  |                       GL_DYNAMIC_DRAW); | ||||||
|  | 
 | ||||||
|  |       // Buffer threshold data
 | ||||||
|  |       gl.glBindBuffer(GL_ARRAY_BUFFER, vbo_[2]); | ||||||
|  |       gl.glBufferData(GL_ARRAY_BUFFER, | ||||||
|  |                       sizeof(GLint) * currentIntegerBuffer_.size(), | ||||||
|  |                       currentIntegerBuffer_.data(), | ||||||
|  |                       GL_DYNAMIC_DRAW); | ||||||
|  | 
 | ||||||
|  |       numVertices_ = | ||||||
|  |          static_cast<GLsizei>(currentImageBuffer_.size() / kPointsPerVertex); | ||||||
|  |    } | ||||||
|  | 
 | ||||||
|  |    dirty_ = false; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | } // namespace draw
 | ||||||
|  | } // namespace gl
 | ||||||
|  | } // namespace qt
 | ||||||
|  | } // namespace scwx
 | ||||||
							
								
								
									
										62
									
								
								scwx-qt/source/scwx/qt/gl/draw/placefile_images.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						|  | @ -0,0 +1,62 @@ | ||||||
|  | #pragma once | ||||||
|  | 
 | ||||||
|  | #include <scwx/qt/gl/gl_context.hpp> | ||||||
|  | #include <scwx/qt/gl/draw/draw_item.hpp> | ||||||
|  | #include <scwx/gr/placefile.hpp> | ||||||
|  | 
 | ||||||
|  | namespace scwx | ||||||
|  | { | ||||||
|  | namespace qt | ||||||
|  | { | ||||||
|  | namespace gl | ||||||
|  | { | ||||||
|  | namespace draw | ||||||
|  | { | ||||||
|  | 
 | ||||||
|  | class PlacefileImages : public DrawItem | ||||||
|  | { | ||||||
|  | public: | ||||||
|  |    explicit PlacefileImages(const std::shared_ptr<GlContext>& context); | ||||||
|  |    ~PlacefileImages(); | ||||||
|  | 
 | ||||||
|  |    PlacefileImages(const PlacefileImages&)            = delete; | ||||||
|  |    PlacefileImages& operator=(const PlacefileImages&) = delete; | ||||||
|  | 
 | ||||||
|  |    PlacefileImages(PlacefileImages&&) noexcept; | ||||||
|  |    PlacefileImages& operator=(PlacefileImages&&) noexcept; | ||||||
|  | 
 | ||||||
|  |    void set_selected_time(std::chrono::system_clock::time_point selectedTime); | ||||||
|  |    void set_thresholded(bool thresholded); | ||||||
|  | 
 | ||||||
|  |    void Initialize() override; | ||||||
|  |    void Render(const QMapLibreGL::CustomLayerRenderParameters& params, | ||||||
|  |                bool textureAtlasChanged) override; | ||||||
|  |    void Deinitialize() override; | ||||||
|  | 
 | ||||||
|  |    /**
 | ||||||
|  |     * Resets and prepares the draw item for adding a new set of images. | ||||||
|  |     */ | ||||||
|  |    void StartImages(const std::string& baseUrl); | ||||||
|  | 
 | ||||||
|  |    /**
 | ||||||
|  |     * Adds a placefile image to the internal draw list. | ||||||
|  |     * | ||||||
|  |     * @param [in] di Placefile image | ||||||
|  |     */ | ||||||
|  |    void AddImage(const std::shared_ptr<gr::Placefile::ImageDrawItem>& di); | ||||||
|  | 
 | ||||||
|  |    /**
 | ||||||
|  |     * Finalizes the draw item after adding new images. | ||||||
|  |     */ | ||||||
|  |    void FinishImages(); | ||||||
|  | 
 | ||||||
|  | private: | ||||||
|  |    class Impl; | ||||||
|  | 
 | ||||||
|  |    std::unique_ptr<Impl> p; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | } // namespace draw
 | ||||||
|  | } // namespace gl
 | ||||||
|  | } // namespace qt
 | ||||||
|  | } // namespace scwx
 | ||||||
							
								
								
									
										601
									
								
								scwx-qt/source/scwx/qt/gl/draw/placefile_lines.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						|  | @ -0,0 +1,601 @@ | ||||||
|  | #include <scwx/qt/gl/draw/placefile_lines.hpp> | ||||||
|  | #include <scwx/qt/util/geographic_lib.hpp> | ||||||
|  | #include <scwx/qt/util/maplibre.hpp> | ||||||
|  | #include <scwx/qt/util/tooltip.hpp> | ||||||
|  | #include <scwx/util/logger.hpp> | ||||||
|  | 
 | ||||||
|  | #include <execution> | ||||||
|  | 
 | ||||||
|  | namespace scwx | ||||||
|  | { | ||||||
|  | namespace qt | ||||||
|  | { | ||||||
|  | namespace gl | ||||||
|  | { | ||||||
|  | namespace draw | ||||||
|  | { | ||||||
|  | 
 | ||||||
|  | static const std::string logPrefix_ = "scwx::qt::gl::draw::placefile_lines"; | ||||||
|  | static const auto        logger_    = scwx::util::Logger::Create(logPrefix_); | ||||||
|  | 
 | ||||||
|  | static constexpr std::size_t kNumRectangles        = 1; | ||||||
|  | static constexpr std::size_t kNumTriangles         = kNumRectangles * 2; | ||||||
|  | static constexpr std::size_t kVerticesPerTriangle  = 3; | ||||||
|  | static constexpr std::size_t kVerticesPerRectangle = kVerticesPerTriangle * 2; | ||||||
|  | static constexpr std::size_t kPointsPerVertex      = 9; | ||||||
|  | static constexpr std::size_t kBufferLength = | ||||||
|  |    kNumTriangles * kVerticesPerTriangle * kPointsPerVertex; | ||||||
|  | 
 | ||||||
|  | // Threshold, start time, end time
 | ||||||
|  | static constexpr std::size_t kIntegersPerVertex_ = 3; | ||||||
|  | 
 | ||||||
|  | static const boost::gil::rgba8_pixel_t kBlack_ {0, 0, 0, 255}; | ||||||
|  | 
 | ||||||
|  | class PlacefileLines::Impl | ||||||
|  | { | ||||||
|  | public: | ||||||
|  |    struct LineHoverEntry | ||||||
|  |    { | ||||||
|  |       std::shared_ptr<const gr::Placefile::LineDrawItem> di_; | ||||||
|  | 
 | ||||||
|  |       glm::vec2 p1_; | ||||||
|  |       glm::vec2 p2_; | ||||||
|  |       glm::vec2 otl_; | ||||||
|  |       glm::vec2 otr_; | ||||||
|  |       glm::vec2 obl_; | ||||||
|  |       glm::vec2 obr_; | ||||||
|  |    }; | ||||||
|  | 
 | ||||||
|  |    explicit Impl(const std::shared_ptr<GlContext>& context) : | ||||||
|  |        context_ {context}, | ||||||
|  |        shaderProgram_ {nullptr}, | ||||||
|  |        uMVPMatrixLocation_(GL_INVALID_INDEX), | ||||||
|  |        uMapMatrixLocation_(GL_INVALID_INDEX), | ||||||
|  |        uMapScreenCoordLocation_(GL_INVALID_INDEX), | ||||||
|  |        uMapDistanceLocation_(GL_INVALID_INDEX), | ||||||
|  |        uSelectedTimeLocation_(GL_INVALID_INDEX), | ||||||
|  |        vao_ {GL_INVALID_INDEX}, | ||||||
|  |        vbo_ {GL_INVALID_INDEX}, | ||||||
|  |        numVertices_ {0} | ||||||
|  |    { | ||||||
|  |    } | ||||||
|  | 
 | ||||||
|  |    ~Impl() {} | ||||||
|  | 
 | ||||||
|  |    void BufferLine(const std::shared_ptr<const gr::Placefile::LineDrawItem>& di, | ||||||
|  |                    const gr::Placefile::LineDrawItem::Element&               e1, | ||||||
|  |                    const gr::Placefile::LineDrawItem::Element&               e2, | ||||||
|  |                    const float                         width, | ||||||
|  |                    const units::angle::degrees<double> angle, | ||||||
|  |                    const boost::gil::rgba8_pixel_t     color, | ||||||
|  |                    const GLint                         threshold, | ||||||
|  |                    const GLint                         startTime, | ||||||
|  |                    const GLint                         endTime, | ||||||
|  |                    bool                                bufferHover = false); | ||||||
|  |    void | ||||||
|  |    UpdateBuffers(const std::shared_ptr<const gr::Placefile::LineDrawItem>& di); | ||||||
|  |    void Update(); | ||||||
|  | 
 | ||||||
|  |    std::shared_ptr<GlContext> context_; | ||||||
|  | 
 | ||||||
|  |    bool dirty_ {false}; | ||||||
|  |    bool thresholded_ {false}; | ||||||
|  | 
 | ||||||
|  |    std::chrono::system_clock::time_point selectedTime_ {}; | ||||||
|  | 
 | ||||||
|  |    std::mutex lineMutex_ {}; | ||||||
|  | 
 | ||||||
|  |    std::size_t currentNumLines_ {}; | ||||||
|  |    std::size_t newNumLines_ {}; | ||||||
|  | 
 | ||||||
|  |    std::vector<float> currentLinesBuffer_ {}; | ||||||
|  |    std::vector<GLint> currentIntegerBuffer_ {}; | ||||||
|  |    std::vector<float> newLinesBuffer_ {}; | ||||||
|  |    std::vector<GLint> newIntegerBuffer_ {}; | ||||||
|  | 
 | ||||||
|  |    std::vector<LineHoverEntry> currentHoverLines_ {}; | ||||||
|  |    std::vector<LineHoverEntry> newHoverLines_ {}; | ||||||
|  | 
 | ||||||
|  |    std::shared_ptr<ShaderProgram> shaderProgram_; | ||||||
|  |    GLint                          uMVPMatrixLocation_; | ||||||
|  |    GLint                          uMapMatrixLocation_; | ||||||
|  |    GLint                          uMapScreenCoordLocation_; | ||||||
|  |    GLint                          uMapDistanceLocation_; | ||||||
|  |    GLint                          uSelectedTimeLocation_; | ||||||
|  | 
 | ||||||
|  |    GLuint                vao_; | ||||||
|  |    std::array<GLuint, 2> vbo_; | ||||||
|  | 
 | ||||||
|  |    GLsizei numVertices_; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | PlacefileLines::PlacefileLines(const std::shared_ptr<GlContext>& context) : | ||||||
|  |     DrawItem(context->gl()), p(std::make_unique<Impl>(context)) | ||||||
|  | { | ||||||
|  | } | ||||||
|  | PlacefileLines::~PlacefileLines() = default; | ||||||
|  | 
 | ||||||
|  | PlacefileLines::PlacefileLines(PlacefileLines&&) noexcept            = default; | ||||||
|  | PlacefileLines& PlacefileLines::operator=(PlacefileLines&&) noexcept = default; | ||||||
|  | 
 | ||||||
|  | void PlacefileLines::set_selected_time( | ||||||
|  |    std::chrono::system_clock::time_point selectedTime) | ||||||
|  | { | ||||||
|  |    p->selectedTime_ = selectedTime; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void PlacefileLines::set_thresholded(bool thresholded) | ||||||
|  | { | ||||||
|  |    p->thresholded_ = thresholded; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void PlacefileLines::Initialize() | ||||||
|  | { | ||||||
|  |    gl::OpenGLFunctions& gl = p->context_->gl(); | ||||||
|  | 
 | ||||||
|  |    p->shaderProgram_ = p->context_->GetShaderProgram( | ||||||
|  |       {{GL_VERTEX_SHADER, ":/gl/geo_texture2d.vert"}, | ||||||
|  |        {GL_GEOMETRY_SHADER, ":/gl/threshold.geom"}, | ||||||
|  |        {GL_FRAGMENT_SHADER, ":/gl/color.frag"}}); | ||||||
|  | 
 | ||||||
|  |    p->uMVPMatrixLocation_ = p->shaderProgram_->GetUniformLocation("uMVPMatrix"); | ||||||
|  |    p->uMapMatrixLocation_ = p->shaderProgram_->GetUniformLocation("uMapMatrix"); | ||||||
|  |    p->uMapScreenCoordLocation_ = | ||||||
|  |       p->shaderProgram_->GetUniformLocation("uMapScreenCoord"); | ||||||
|  |    p->uMapDistanceLocation_ = | ||||||
|  |       p->shaderProgram_->GetUniformLocation("uMapDistance"); | ||||||
|  |    p->uSelectedTimeLocation_ = | ||||||
|  |       p->shaderProgram_->GetUniformLocation("uSelectedTime"); | ||||||
|  | 
 | ||||||
|  |    gl.glGenVertexArrays(1, &p->vao_); | ||||||
|  |    gl.glGenBuffers(2, p->vbo_.data()); | ||||||
|  | 
 | ||||||
|  |    gl.glBindVertexArray(p->vao_); | ||||||
|  |    gl.glBindBuffer(GL_ARRAY_BUFFER, p->vbo_[0]); | ||||||
|  |    gl.glBufferData(GL_ARRAY_BUFFER, 0u, nullptr, GL_DYNAMIC_DRAW); | ||||||
|  | 
 | ||||||
|  |    // aLatLong
 | ||||||
|  |    gl.glVertexAttribPointer(0, | ||||||
|  |                             2, | ||||||
|  |                             GL_FLOAT, | ||||||
|  |                             GL_FALSE, | ||||||
|  |                             kPointsPerVertex * sizeof(float), | ||||||
|  |                             static_cast<void*>(0)); | ||||||
|  |    gl.glEnableVertexAttribArray(0); | ||||||
|  | 
 | ||||||
|  |    // aXYOffset
 | ||||||
|  |    gl.glVertexAttribPointer(1, | ||||||
|  |                             2, | ||||||
|  |                             GL_FLOAT, | ||||||
|  |                             GL_FALSE, | ||||||
|  |                             kPointsPerVertex * sizeof(float), | ||||||
|  |                             reinterpret_cast<void*>(2 * sizeof(float))); | ||||||
|  |    gl.glEnableVertexAttribArray(1); | ||||||
|  | 
 | ||||||
|  |    // aModulate
 | ||||||
|  |    gl.glVertexAttribPointer(3, | ||||||
|  |                             4, | ||||||
|  |                             GL_FLOAT, | ||||||
|  |                             GL_FALSE, | ||||||
|  |                             kPointsPerVertex * sizeof(float), | ||||||
|  |                             reinterpret_cast<void*>(4 * sizeof(float))); | ||||||
|  |    gl.glEnableVertexAttribArray(3); | ||||||
|  | 
 | ||||||
|  |    // aAngle
 | ||||||
|  |    gl.glVertexAttribPointer(4, | ||||||
|  |                             1, | ||||||
|  |                             GL_FLOAT, | ||||||
|  |                             GL_FALSE, | ||||||
|  |                             kPointsPerVertex * sizeof(float), | ||||||
|  |                             reinterpret_cast<void*>(8 * sizeof(float))); | ||||||
|  |    gl.glEnableVertexAttribArray(4); | ||||||
|  | 
 | ||||||
|  |    gl.glBindBuffer(GL_ARRAY_BUFFER, p->vbo_[1]); | ||||||
|  |    gl.glBufferData(GL_ARRAY_BUFFER, 0u, nullptr, GL_DYNAMIC_DRAW); | ||||||
|  | 
 | ||||||
|  |    // aThreshold
 | ||||||
|  |    gl.glVertexAttribIPointer(5, //
 | ||||||
|  |                              1, | ||||||
|  |                              GL_INT, | ||||||
|  |                              kIntegersPerVertex_ * sizeof(GLint), | ||||||
|  |                              static_cast<void*>(0)); | ||||||
|  |    gl.glEnableVertexAttribArray(5); | ||||||
|  | 
 | ||||||
|  |    // aTimeRange
 | ||||||
|  |    gl.glVertexAttribIPointer(6, //
 | ||||||
|  |                              2, | ||||||
|  |                              GL_INT, | ||||||
|  |                              kIntegersPerVertex_ * sizeof(GLint), | ||||||
|  |                              reinterpret_cast<void*>(1 * sizeof(GLint))); | ||||||
|  |    gl.glEnableVertexAttribArray(6); | ||||||
|  | 
 | ||||||
|  |    p->dirty_ = true; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void PlacefileLines::Render( | ||||||
|  |    const QMapLibreGL::CustomLayerRenderParameters& params) | ||||||
|  | { | ||||||
|  |    std::unique_lock lock {p->lineMutex_}; | ||||||
|  | 
 | ||||||
|  |    if (p->currentNumLines_ > 0) | ||||||
|  |    { | ||||||
|  |       gl::OpenGLFunctions& gl = p->context_->gl(); | ||||||
|  | 
 | ||||||
|  |       gl.glBindVertexArray(p->vao_); | ||||||
|  | 
 | ||||||
|  |       p->Update(); | ||||||
|  |       p->shaderProgram_->Use(); | ||||||
|  |       UseRotationProjection(params, p->uMVPMatrixLocation_); | ||||||
|  |       UseMapProjection( | ||||||
|  |          params, p->uMapMatrixLocation_, p->uMapScreenCoordLocation_); | ||||||
|  | 
 | ||||||
|  |       if (p->thresholded_) | ||||||
|  |       { | ||||||
|  |          // If thresholding is enabled, set the map distance
 | ||||||
|  |          units::length::nautical_miles<float> mapDistance = | ||||||
|  |             util::maplibre::GetMapDistance(params); | ||||||
|  |          gl.glUniform1f(p->uMapDistanceLocation_, mapDistance.value()); | ||||||
|  |       } | ||||||
|  |       else | ||||||
|  |       { | ||||||
|  |          // If thresholding is disabled, set the map distance to 0
 | ||||||
|  |          gl.glUniform1f(p->uMapDistanceLocation_, 0.0f); | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       // Selected time
 | ||||||
|  |       std::chrono::system_clock::time_point selectedTime = | ||||||
|  |          (p->selectedTime_ == std::chrono::system_clock::time_point {}) ? | ||||||
|  |             std::chrono::system_clock::now() : | ||||||
|  |             p->selectedTime_; | ||||||
|  |       gl.glUniform1i( | ||||||
|  |          p->uSelectedTimeLocation_, | ||||||
|  |          static_cast<GLint>(std::chrono::duration_cast<std::chrono::minutes>( | ||||||
|  |                                selectedTime.time_since_epoch()) | ||||||
|  |                                .count())); | ||||||
|  | 
 | ||||||
|  |       // Draw icons
 | ||||||
|  |       gl.glDrawArrays(GL_TRIANGLES, 0, p->numVertices_); | ||||||
|  |    } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void PlacefileLines::Deinitialize() | ||||||
|  | { | ||||||
|  |    gl::OpenGLFunctions& gl = p->context_->gl(); | ||||||
|  | 
 | ||||||
|  |    gl.glDeleteVertexArrays(1, &p->vao_); | ||||||
|  |    gl.glDeleteBuffers(2, p->vbo_.data()); | ||||||
|  | 
 | ||||||
|  |    std::unique_lock lock {p->lineMutex_}; | ||||||
|  | 
 | ||||||
|  |    p->currentLinesBuffer_.clear(); | ||||||
|  |    p->currentIntegerBuffer_.clear(); | ||||||
|  |    p->currentHoverLines_.clear(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void PlacefileLines::StartLines() | ||||||
|  | { | ||||||
|  |    // Clear the new buffers
 | ||||||
|  |    p->newLinesBuffer_.clear(); | ||||||
|  |    p->newIntegerBuffer_.clear(); | ||||||
|  |    p->newHoverLines_.clear(); | ||||||
|  | 
 | ||||||
|  |    p->newNumLines_ = 0u; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void PlacefileLines::AddLine( | ||||||
|  |    const std::shared_ptr<gr::Placefile::LineDrawItem>& di) | ||||||
|  | { | ||||||
|  |    if (di != nullptr && !di->elements_.empty()) | ||||||
|  |    { | ||||||
|  |       p->UpdateBuffers(di); | ||||||
|  |       p->newNumLines_ += (di->elements_.size() - 1) * 2; | ||||||
|  |    } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void PlacefileLines::FinishLines() | ||||||
|  | { | ||||||
|  |    std::unique_lock lock {p->lineMutex_}; | ||||||
|  | 
 | ||||||
|  |    // Swap buffers
 | ||||||
|  |    p->currentLinesBuffer_.swap(p->newLinesBuffer_); | ||||||
|  |    p->currentIntegerBuffer_.swap(p->newIntegerBuffer_); | ||||||
|  |    p->currentHoverLines_.swap(p->newHoverLines_); | ||||||
|  | 
 | ||||||
|  |    // Clear the new buffers
 | ||||||
|  |    p->newLinesBuffer_.clear(); | ||||||
|  |    p->newIntegerBuffer_.clear(); | ||||||
|  |    p->newHoverLines_.clear(); | ||||||
|  | 
 | ||||||
|  |    // Update the number of lines
 | ||||||
|  |    p->currentNumLines_ = p->newNumLines_; | ||||||
|  |    p->numVertices_ = | ||||||
|  |       static_cast<GLsizei>(p->currentNumLines_ * kVerticesPerRectangle); | ||||||
|  | 
 | ||||||
|  |    // Mark the draw item dirty
 | ||||||
|  |    p->dirty_ = true; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void PlacefileLines::Impl::UpdateBuffers( | ||||||
|  |    const std::shared_ptr<const gr::Placefile::LineDrawItem>& di) | ||||||
|  | { | ||||||
|  |    // Threshold value
 | ||||||
|  |    units::length::nautical_miles<double> threshold = di->threshold_; | ||||||
|  |    GLint thresholdValue = static_cast<GLint>(std::round(threshold.value())); | ||||||
|  | 
 | ||||||
|  |    // Start and end time
 | ||||||
|  |    GLint startTime = | ||||||
|  |       static_cast<GLint>(std::chrono::duration_cast<std::chrono::minutes>( | ||||||
|  |                             di->startTime_.time_since_epoch()) | ||||||
|  |                             .count()); | ||||||
|  |    GLint endTime = | ||||||
|  |       static_cast<GLint>(std::chrono::duration_cast<std::chrono::minutes>( | ||||||
|  |                             di->endTime_.time_since_epoch()) | ||||||
|  |                             .count()); | ||||||
|  | 
 | ||||||
|  |    std::vector<units::angle::degrees<double>> angles {}; | ||||||
|  |    angles.reserve(di->elements_.size() - 1); | ||||||
|  | 
 | ||||||
|  |    // For each element pair inside a Line statement, render a black line
 | ||||||
|  |    for (std::size_t i = 0; i < di->elements_.size() - 1; ++i) | ||||||
|  |    { | ||||||
|  |       // Latitude and longitude coordinates in degrees
 | ||||||
|  |       const float lat1 = static_cast<float>(di->elements_[i].latitude_); | ||||||
|  |       const float lon1 = static_cast<float>(di->elements_[i].longitude_); | ||||||
|  |       const float lat2 = static_cast<float>(di->elements_[i + 1].latitude_); | ||||||
|  |       const float lon2 = static_cast<float>(di->elements_[i + 1].longitude_); | ||||||
|  | 
 | ||||||
|  |       // Calculate angle
 | ||||||
|  |       const units::angle::degrees<double> angle = | ||||||
|  |          util::GeographicLib::GetAngle(lat1, lon1, lat2, lon2); | ||||||
|  |       angles.push_back(angle); | ||||||
|  | 
 | ||||||
|  |       // Buffer line with hover text
 | ||||||
|  |       BufferLine(di, | ||||||
|  |                  di->elements_[i], | ||||||
|  |                  di->elements_[i + 1], | ||||||
|  |                  di->width_ + 2, | ||||||
|  |                  angle, | ||||||
|  |                  kBlack_, | ||||||
|  |                  thresholdValue, | ||||||
|  |                  startTime, | ||||||
|  |                  endTime, | ||||||
|  |                  true); | ||||||
|  |    } | ||||||
|  | 
 | ||||||
|  |    // For each element pair inside a Line statement, render a colored line
 | ||||||
|  |    for (std::size_t i = 0; i < di->elements_.size() - 1; ++i) | ||||||
|  |    { | ||||||
|  |       BufferLine(di, | ||||||
|  |                  di->elements_[i], | ||||||
|  |                  di->elements_[i + 1], | ||||||
|  |                  di->width_, | ||||||
|  |                  angles[i], | ||||||
|  |                  di->color_, | ||||||
|  |                  thresholdValue, | ||||||
|  |                  startTime, | ||||||
|  |                  endTime); | ||||||
|  |    } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void PlacefileLines::Impl::BufferLine( | ||||||
|  |    const std::shared_ptr<const gr::Placefile::LineDrawItem>& di, | ||||||
|  |    const gr::Placefile::LineDrawItem::Element&               e1, | ||||||
|  |    const gr::Placefile::LineDrawItem::Element&               e2, | ||||||
|  |    const float                                               width, | ||||||
|  |    const units::angle::degrees<double>                       angle, | ||||||
|  |    const boost::gil::rgba8_pixel_t                           color, | ||||||
|  |    const GLint                                               threshold, | ||||||
|  |    const GLint                                               startTime, | ||||||
|  |    const GLint                                               endTime, | ||||||
|  |    bool                                                      bufferHover) | ||||||
|  | { | ||||||
|  |    // Latitude and longitude coordinates in degrees
 | ||||||
|  |    const float lat1 = static_cast<float>(e1.latitude_); | ||||||
|  |    const float lon1 = static_cast<float>(e1.longitude_); | ||||||
|  |    const float lat2 = static_cast<float>(e2.latitude_); | ||||||
|  |    const float lon2 = static_cast<float>(e2.longitude_); | ||||||
|  | 
 | ||||||
|  |    // TODO: Base X/Y offsets in pixels
 | ||||||
|  |    // const float x1 = static_cast<float>(e1.x_);
 | ||||||
|  |    // const float y1 = static_cast<float>(e1.y_);
 | ||||||
|  |    // const float x2 = static_cast<float>(e2.x_);
 | ||||||
|  |    // const float y2 = static_cast<float>(e2.y_);
 | ||||||
|  | 
 | ||||||
|  |    // Angle
 | ||||||
|  |    const float a = static_cast<float>(angle.value()); | ||||||
|  | 
 | ||||||
|  |    // Final X/Y offsets in pixels
 | ||||||
|  |    const float hw = width * 0.5f; | ||||||
|  |    const float lx = -hw; | ||||||
|  |    const float rx = +hw; | ||||||
|  |    const float ty = +hw; | ||||||
|  |    const float by = -hw; | ||||||
|  | 
 | ||||||
|  |    // Modulate color
 | ||||||
|  |    const float mc0 = color[0] / 255.0f; | ||||||
|  |    const float mc1 = color[1] / 255.0f; | ||||||
|  |    const float mc2 = color[2] / 255.0f; | ||||||
|  |    const float mc3 = color[3] / 255.0f; | ||||||
|  | 
 | ||||||
|  |    // Update buffers
 | ||||||
|  |    newLinesBuffer_.insert(newLinesBuffer_.end(), | ||||||
|  |                           { | ||||||
|  |                              // Line
 | ||||||
|  |                              lat1, lon1, lx, by, mc0, mc1, mc2, mc3, a, // BL
 | ||||||
|  |                              lat2, lon2, lx, ty, mc0, mc1, mc2, mc3, a, // TL
 | ||||||
|  |                              lat1, lon1, rx, by, mc0, mc1, mc2, mc3, a, // BR
 | ||||||
|  |                              lat1, lon1, rx, by, mc0, mc1, mc2, mc3, a, // BR
 | ||||||
|  |                              lat2, lon2, rx, ty, mc0, mc1, mc2, mc3, a, // TR
 | ||||||
|  |                              lat2, lon2, lx, ty, mc0, mc1, mc2, mc3, a  // TL
 | ||||||
|  |                           }); | ||||||
|  |    newIntegerBuffer_.insert(newIntegerBuffer_.end(), | ||||||
|  |                             {threshold, | ||||||
|  |                              startTime, | ||||||
|  |                              endTime, | ||||||
|  |                              threshold, | ||||||
|  |                              startTime, | ||||||
|  |                              endTime, | ||||||
|  |                              threshold, | ||||||
|  |                              startTime, | ||||||
|  |                              endTime, | ||||||
|  |                              threshold, | ||||||
|  |                              startTime, | ||||||
|  |                              endTime, | ||||||
|  |                              threshold, | ||||||
|  |                              startTime, | ||||||
|  |                              endTime, | ||||||
|  |                              threshold, | ||||||
|  |                              startTime, | ||||||
|  |                              endTime}); | ||||||
|  | 
 | ||||||
|  |    if (bufferHover && !di->hoverText_.empty()) | ||||||
|  |    { | ||||||
|  |       const units::angle::radians<double> radians = angle; | ||||||
|  | 
 | ||||||
|  |       const auto sc1 = util::maplibre::LatLongToScreenCoordinate({lat1, lon1}); | ||||||
|  |       const auto sc2 = util::maplibre::LatLongToScreenCoordinate({lat2, lon2}); | ||||||
|  | 
 | ||||||
|  |       const float cosAngle = cosf(static_cast<float>(radians.value())); | ||||||
|  |       const float sinAngle = sinf(static_cast<float>(radians.value())); | ||||||
|  | 
 | ||||||
|  |       const glm::mat2 rotate {cosAngle, -sinAngle, sinAngle, cosAngle}; | ||||||
|  | 
 | ||||||
|  |       const glm::vec2 otl = rotate * glm::vec2 {-hw, +hw}; | ||||||
|  |       const glm::vec2 otr = rotate * glm::vec2 {+hw, +hw}; | ||||||
|  |       const glm::vec2 obl = rotate * glm::vec2 {-hw, -hw}; | ||||||
|  |       const glm::vec2 obr = rotate * glm::vec2 {+hw, -hw}; | ||||||
|  | 
 | ||||||
|  |       newHoverLines_.emplace_back( | ||||||
|  |          LineHoverEntry {di, sc1, sc2, otl, otr, obl, obr}); | ||||||
|  |    } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void PlacefileLines::Impl::Update() | ||||||
|  | { | ||||||
|  |    // If the placefile has been updated
 | ||||||
|  |    if (dirty_) | ||||||
|  |    { | ||||||
|  |       gl::OpenGLFunctions& gl = context_->gl(); | ||||||
|  | 
 | ||||||
|  |       // Buffer lines data
 | ||||||
|  |       gl.glBindBuffer(GL_ARRAY_BUFFER, vbo_[0]); | ||||||
|  |       gl.glBufferData(GL_ARRAY_BUFFER, | ||||||
|  |                       sizeof(float) * currentLinesBuffer_.size(), | ||||||
|  |                       currentLinesBuffer_.data(), | ||||||
|  |                       GL_DYNAMIC_DRAW); | ||||||
|  | 
 | ||||||
|  |       // Buffer threshold data
 | ||||||
|  |       gl.glBindBuffer(GL_ARRAY_BUFFER, vbo_[1]); | ||||||
|  |       gl.glBufferData(GL_ARRAY_BUFFER, | ||||||
|  |                       sizeof(GLint) * currentIntegerBuffer_.size(), | ||||||
|  |                       currentIntegerBuffer_.data(), | ||||||
|  |                       GL_DYNAMIC_DRAW); | ||||||
|  |    } | ||||||
|  | 
 | ||||||
|  |    dirty_ = false; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool PlacefileLines::RunMousePicking( | ||||||
|  |    const QMapLibreGL::CustomLayerRenderParameters& params, | ||||||
|  |    const QPointF& /* mouseLocalPos */, | ||||||
|  |    const QPointF&   mouseGlobalPos, | ||||||
|  |    const glm::vec2& mouseCoords) | ||||||
|  | { | ||||||
|  |    std::unique_lock lock {p->lineMutex_}; | ||||||
|  | 
 | ||||||
|  |    bool itemPicked = false; | ||||||
|  | 
 | ||||||
|  |    // Calculate map scale, remove width and height from original calculation
 | ||||||
|  |    glm::vec2 scale = util::maplibre::GetMapScale(params); | ||||||
|  |    scale = 2.0f / glm::vec2 {scale.x * params.width, scale.y * params.height}; | ||||||
|  | 
 | ||||||
|  |    // Scale and rotate the identity matrix to create the map matrix
 | ||||||
|  |    glm::mat4 mapMatrix {1.0f}; | ||||||
|  |    mapMatrix = glm::scale(mapMatrix, glm::vec3 {scale, 1.0f}); | ||||||
|  |    mapMatrix = glm::rotate(mapMatrix, | ||||||
|  |                            glm::radians<float>(params.bearing), | ||||||
|  |                            glm::vec3(0.0f, 0.0f, 1.0f)); | ||||||
|  | 
 | ||||||
|  |    units::length::meters<double> mapDistance = | ||||||
|  |       (p->thresholded_) ? util::maplibre::GetMapDistance(params) : | ||||||
|  |                           units::length::meters<double> {0.0}; | ||||||
|  | 
 | ||||||
|  |    // If no time has been selected, use the current time
 | ||||||
|  |    std::chrono::system_clock::time_point selectedTime = | ||||||
|  |       (p->selectedTime_ == std::chrono::system_clock::time_point {}) ? | ||||||
|  |          std::chrono::system_clock::now() : | ||||||
|  |          p->selectedTime_; | ||||||
|  | 
 | ||||||
|  |    // For each pickable line
 | ||||||
|  |    auto it = std::find_if( | ||||||
|  |       std::execution::par_unseq, | ||||||
|  |       p->currentHoverLines_.crbegin(), | ||||||
|  |       p->currentHoverLines_.crend(), | ||||||
|  |       [&mapDistance, &selectedTime, &mapMatrix, &mouseCoords](const auto& line) | ||||||
|  |       { | ||||||
|  |          if (( | ||||||
|  |                 // Placefile is thresholded
 | ||||||
|  |                 mapDistance > units::length::meters<double> {0.0} && | ||||||
|  | 
 | ||||||
|  |                 // Placefile threshold is < 999 nmi
 | ||||||
|  |                 static_cast<int>(std::round( | ||||||
|  |                    units::length::nautical_miles<double> {line.di_->threshold_} | ||||||
|  |                       .value())) < 999 && | ||||||
|  | 
 | ||||||
|  |                 // Map distance is beyond the threshold
 | ||||||
|  |                 line.di_->threshold_ < mapDistance) || | ||||||
|  | 
 | ||||||
|  |              ( | ||||||
|  |                 // Line has a start time
 | ||||||
|  |                 line.di_->startTime_ != | ||||||
|  |                    std::chrono::system_clock::time_point {} && | ||||||
|  | 
 | ||||||
|  |                 // The time range has not yet started
 | ||||||
|  |                 (selectedTime < line.di_->startTime_ || | ||||||
|  | 
 | ||||||
|  |                  // The time range has ended
 | ||||||
|  |                  line.di_->endTime_ <= selectedTime))) | ||||||
|  |          { | ||||||
|  |             // Line is not pickable
 | ||||||
|  |             return false; | ||||||
|  |          } | ||||||
|  | 
 | ||||||
|  |          // Initialize vertices
 | ||||||
|  |          glm::vec2 bl = line.p1_; | ||||||
|  |          glm::vec2 br = bl; | ||||||
|  |          glm::vec2 tl = line.p2_; | ||||||
|  |          glm::vec2 tr = tl; | ||||||
|  | 
 | ||||||
|  |          // Calculate offsets
 | ||||||
|  |          // - Rotated offset is half the line width (pixels) in each direction
 | ||||||
|  |          // - Multiply the offset by the scaled and rotated map matrix
 | ||||||
|  |          const glm::vec2 otl = mapMatrix * glm::vec4 {line.otl_, 0.0f, 1.0f}; | ||||||
|  |          const glm::vec2 obl = mapMatrix * glm::vec4 {line.obl_, 0.0f, 1.0f}; | ||||||
|  |          const glm::vec2 obr = mapMatrix * glm::vec4 {line.obr_, 0.0f, 1.0f}; | ||||||
|  |          const glm::vec2 otr = mapMatrix * glm::vec4 {line.otr_, 0.0f, 1.0f}; | ||||||
|  | 
 | ||||||
|  |          // Offset vertices
 | ||||||
|  |          tl += otl; | ||||||
|  |          bl += obl; | ||||||
|  |          br += obr; | ||||||
|  |          tr += otr; | ||||||
|  | 
 | ||||||
|  |          // TODO: X/Y offsets
 | ||||||
|  | 
 | ||||||
|  |          // Test point against polygon bounds
 | ||||||
|  |          return util::maplibre::IsPointInPolygon({tl, bl, br, tr}, mouseCoords); | ||||||
|  |       }); | ||||||
|  | 
 | ||||||
|  |    if (it != p->currentHoverLines_.crend()) | ||||||
|  |    { | ||||||
|  |       itemPicked = true; | ||||||
|  |       util::tooltip::Show(it->di_->hoverText_, mouseGlobalPos); | ||||||
|  |    } | ||||||
|  | 
 | ||||||
|  |    return itemPicked; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | } // namespace draw
 | ||||||
|  | } // namespace gl
 | ||||||
|  | } // namespace qt
 | ||||||
|  | } // namespace scwx
 | ||||||
							
								
								
									
										66
									
								
								scwx-qt/source/scwx/qt/gl/draw/placefile_lines.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						|  | @ -0,0 +1,66 @@ | ||||||
|  | #pragma once | ||||||
|  | 
 | ||||||
|  | #include <scwx/qt/gl/gl_context.hpp> | ||||||
|  | #include <scwx/qt/gl/draw/draw_item.hpp> | ||||||
|  | #include <scwx/gr/placefile.hpp> | ||||||
|  | 
 | ||||||
|  | namespace scwx | ||||||
|  | { | ||||||
|  | namespace qt | ||||||
|  | { | ||||||
|  | namespace gl | ||||||
|  | { | ||||||
|  | namespace draw | ||||||
|  | { | ||||||
|  | 
 | ||||||
|  | class PlacefileLines : public DrawItem | ||||||
|  | { | ||||||
|  | public: | ||||||
|  |    explicit PlacefileLines(const std::shared_ptr<GlContext>& context); | ||||||
|  |    ~PlacefileLines(); | ||||||
|  | 
 | ||||||
|  |    PlacefileLines(const PlacefileLines&)            = delete; | ||||||
|  |    PlacefileLines& operator=(const PlacefileLines&) = delete; | ||||||
|  | 
 | ||||||
|  |    PlacefileLines(PlacefileLines&&) noexcept; | ||||||
|  |    PlacefileLines& operator=(PlacefileLines&&) noexcept; | ||||||
|  | 
 | ||||||
|  |    void set_selected_time(std::chrono::system_clock::time_point selectedTime); | ||||||
|  |    void set_thresholded(bool thresholded); | ||||||
|  | 
 | ||||||
|  |    void Initialize() override; | ||||||
|  |    void Render(const QMapLibreGL::CustomLayerRenderParameters& params) override; | ||||||
|  |    void Deinitialize() override; | ||||||
|  | 
 | ||||||
|  |    bool RunMousePicking(const QMapLibreGL::CustomLayerRenderParameters& params, | ||||||
|  |                         const QPointF&   mouseLocalPos, | ||||||
|  |                         const QPointF&   mouseGlobalPos, | ||||||
|  |                         const glm::vec2& mouseCoords) override; | ||||||
|  | 
 | ||||||
|  |    /**
 | ||||||
|  |     * Resets and prepares the draw item for adding a new set of lines. | ||||||
|  |     */ | ||||||
|  |    void StartLines(); | ||||||
|  | 
 | ||||||
|  |    /**
 | ||||||
|  |     * Adds a placefile line to the internal draw list. | ||||||
|  |     * | ||||||
|  |     * @param [in] di Placefile line | ||||||
|  |     */ | ||||||
|  |    void AddLine(const std::shared_ptr<gr::Placefile::LineDrawItem>& di); | ||||||
|  | 
 | ||||||
|  |    /**
 | ||||||
|  |     * Finalizes the draw item after adding new lines. | ||||||
|  |     */ | ||||||
|  |    void FinishLines(); | ||||||
|  | 
 | ||||||
|  | private: | ||||||
|  |    class Impl; | ||||||
|  | 
 | ||||||
|  |    std::unique_ptr<Impl> p; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | } // namespace draw
 | ||||||
|  | } // namespace gl
 | ||||||
|  | } // namespace qt
 | ||||||
|  | } // namespace scwx
 | ||||||
							
								
								
									
										486
									
								
								scwx-qt/source/scwx/qt/gl/draw/placefile_polygons.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						|  | @ -0,0 +1,486 @@ | ||||||
|  | #include <scwx/qt/gl/draw/placefile_polygons.hpp> | ||||||
|  | #include <scwx/qt/util/maplibre.hpp> | ||||||
|  | #include <scwx/util/logger.hpp> | ||||||
|  | 
 | ||||||
|  | #include <mutex> | ||||||
|  | 
 | ||||||
|  | #include <GL/glu.h> | ||||||
|  | #include <boost/container/stable_vector.hpp> | ||||||
|  | 
 | ||||||
|  | #if defined(_WIN32) | ||||||
|  | typedef void (*_GLUfuncptr)(void); | ||||||
|  | #endif | ||||||
|  | 
 | ||||||
|  | namespace scwx | ||||||
|  | { | ||||||
|  | namespace qt | ||||||
|  | { | ||||||
|  | namespace gl | ||||||
|  | { | ||||||
|  | namespace draw | ||||||
|  | { | ||||||
|  | 
 | ||||||
|  | static const std::string logPrefix_ = "scwx::qt::gl::draw::placefile_polygons"; | ||||||
|  | static const auto        logger_    = scwx::util::Logger::Create(logPrefix_); | ||||||
|  | 
 | ||||||
|  | static constexpr std::size_t kVerticesPerTriangle = 3; | ||||||
|  | static constexpr std::size_t kPointsPerVertex     = 8; | ||||||
|  | 
 | ||||||
|  | // Threshold, start time, end time
 | ||||||
|  | static constexpr std::size_t kIntegersPerVertex_ = 3; | ||||||
|  | 
 | ||||||
|  | static constexpr std::size_t kTessVertexScreenX_ = 0; | ||||||
|  | static constexpr std::size_t kTessVertexScreenY_ = 1; | ||||||
|  | static constexpr std::size_t kTessVertexScreenZ_ = 2; | ||||||
|  | static constexpr std::size_t kTessVertexXOffset_ = 3; | ||||||
|  | static constexpr std::size_t kTessVertexYOffset_ = 4; | ||||||
|  | static constexpr std::size_t kTessVertexR_       = 5; | ||||||
|  | static constexpr std::size_t kTessVertexG_       = 6; | ||||||
|  | static constexpr std::size_t kTessVertexB_       = 7; | ||||||
|  | static constexpr std::size_t kTessVertexA_       = 8; | ||||||
|  | static constexpr std::size_t kTessVertexSize_    = kTessVertexA_ + 1; | ||||||
|  | 
 | ||||||
|  | typedef std::array<GLdouble, kTessVertexSize_> TessVertexArray; | ||||||
|  | 
 | ||||||
|  | class PlacefilePolygons::Impl | ||||||
|  | { | ||||||
|  | public: | ||||||
|  |    explicit Impl(const std::shared_ptr<GlContext>& context) : | ||||||
|  |        context_ {context}, | ||||||
|  |        shaderProgram_ {nullptr}, | ||||||
|  |        uMVPMatrixLocation_(GL_INVALID_INDEX), | ||||||
|  |        uMapMatrixLocation_(GL_INVALID_INDEX), | ||||||
|  |        uMapScreenCoordLocation_(GL_INVALID_INDEX), | ||||||
|  |        uMapDistanceLocation_(GL_INVALID_INDEX), | ||||||
|  |        uSelectedTimeLocation_(GL_INVALID_INDEX), | ||||||
|  |        vao_ {GL_INVALID_INDEX}, | ||||||
|  |        vbo_ {GL_INVALID_INDEX}, | ||||||
|  |        numVertices_ {0} | ||||||
|  |    { | ||||||
|  |       tessellator_ = gluNewTess(); | ||||||
|  | 
 | ||||||
|  |       gluTessCallback(tessellator_, //
 | ||||||
|  |                       GLU_TESS_COMBINE_DATA, | ||||||
|  |                       (_GLUfuncptr) &TessellateCombineCallback); | ||||||
|  |       gluTessCallback(tessellator_, //
 | ||||||
|  |                       GLU_TESS_VERTEX_DATA, | ||||||
|  |                       (_GLUfuncptr) &TessellateVertexCallback); | ||||||
|  | 
 | ||||||
|  |       // Force GLU_TRIANGLES
 | ||||||
|  |       gluTessCallback(tessellator_, //
 | ||||||
|  |                       GLU_TESS_EDGE_FLAG, | ||||||
|  |                       []() {}); | ||||||
|  | 
 | ||||||
|  |       gluTessCallback(tessellator_, //
 | ||||||
|  |                       GLU_TESS_ERROR, | ||||||
|  |                       (_GLUfuncptr) &TessellateErrorCallback); | ||||||
|  |    } | ||||||
|  | 
 | ||||||
|  |    ~Impl() { gluDeleteTess(tessellator_); } | ||||||
|  | 
 | ||||||
|  |    void Update(); | ||||||
|  | 
 | ||||||
|  |    void Tessellate(const std::shared_ptr<gr::Placefile::PolygonDrawItem>& di); | ||||||
|  | 
 | ||||||
|  |    static void TessellateCombineCallback(GLdouble coords[3], | ||||||
|  |                                          void*    vertexData[4], | ||||||
|  |                                          GLfloat  weight[4], | ||||||
|  |                                          void**   outData, | ||||||
|  |                                          void*    polygonData); | ||||||
|  |    static void TessellateVertexCallback(void* vertexData, void* polygonData); | ||||||
|  |    static void TessellateErrorCallback(GLenum errorCode); | ||||||
|  | 
 | ||||||
|  |    std::shared_ptr<GlContext> context_; | ||||||
|  | 
 | ||||||
|  |    bool dirty_ {false}; | ||||||
|  |    bool thresholded_ {false}; | ||||||
|  | 
 | ||||||
|  |    std::chrono::system_clock::time_point selectedTime_ {}; | ||||||
|  | 
 | ||||||
|  |    boost::container::stable_vector<TessVertexArray> tessCombineBuffer_ {}; | ||||||
|  | 
 | ||||||
|  |    std::mutex           bufferMutex_ {}; | ||||||
|  |    std::vector<GLfloat> currentBuffer_ {}; | ||||||
|  |    std::vector<GLint>   currentIntegerBuffer_ {}; | ||||||
|  |    std::vector<GLfloat> newBuffer_ {}; | ||||||
|  |    std::vector<GLint>   newIntegerBuffer_ {}; | ||||||
|  | 
 | ||||||
|  |    GLUtesselator* tessellator_; | ||||||
|  | 
 | ||||||
|  |    std::shared_ptr<ShaderProgram> shaderProgram_; | ||||||
|  |    GLint                          uMVPMatrixLocation_; | ||||||
|  |    GLint                          uMapMatrixLocation_; | ||||||
|  |    GLint                          uMapScreenCoordLocation_; | ||||||
|  |    GLint                          uMapDistanceLocation_; | ||||||
|  |    GLint                          uSelectedTimeLocation_; | ||||||
|  | 
 | ||||||
|  |    GLuint                vao_; | ||||||
|  |    std::array<GLuint, 2> vbo_; | ||||||
|  | 
 | ||||||
|  |    GLsizei numVertices_; | ||||||
|  | 
 | ||||||
|  |    GLint currentThreshold_ {}; | ||||||
|  |    GLint currentStartTime_ {}; | ||||||
|  |    GLint currentEndTime_ {}; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | PlacefilePolygons::PlacefilePolygons( | ||||||
|  |    const std::shared_ptr<GlContext>& context) : | ||||||
|  |     DrawItem(context->gl()), p(std::make_unique<Impl>(context)) | ||||||
|  | { | ||||||
|  | } | ||||||
|  | PlacefilePolygons::~PlacefilePolygons() = default; | ||||||
|  | 
 | ||||||
|  | PlacefilePolygons::PlacefilePolygons(PlacefilePolygons&&) noexcept = default; | ||||||
|  | PlacefilePolygons& | ||||||
|  | PlacefilePolygons::operator=(PlacefilePolygons&&) noexcept = default; | ||||||
|  | 
 | ||||||
|  | void PlacefilePolygons::set_selected_time( | ||||||
|  |    std::chrono::system_clock::time_point selectedTime) | ||||||
|  | { | ||||||
|  |    p->selectedTime_ = selectedTime; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void PlacefilePolygons::set_thresholded(bool thresholded) | ||||||
|  | { | ||||||
|  |    p->thresholded_ = thresholded; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void PlacefilePolygons::Initialize() | ||||||
|  | { | ||||||
|  |    gl::OpenGLFunctions& gl = p->context_->gl(); | ||||||
|  | 
 | ||||||
|  |    p->shaderProgram_ = p->context_->GetShaderProgram( | ||||||
|  |       {{GL_VERTEX_SHADER, ":/gl/map_color.vert"}, | ||||||
|  |        {GL_GEOMETRY_SHADER, ":/gl/threshold.geom"}, | ||||||
|  |        {GL_FRAGMENT_SHADER, ":/gl/color.frag"}}); | ||||||
|  | 
 | ||||||
|  |    p->uMVPMatrixLocation_ = p->shaderProgram_->GetUniformLocation("uMVPMatrix"); | ||||||
|  |    p->uMapMatrixLocation_ = p->shaderProgram_->GetUniformLocation("uMapMatrix"); | ||||||
|  |    p->uMapScreenCoordLocation_ = | ||||||
|  |       p->shaderProgram_->GetUniformLocation("uMapScreenCoord"); | ||||||
|  |    p->uMapDistanceLocation_ = | ||||||
|  |       p->shaderProgram_->GetUniformLocation("uMapDistance"); | ||||||
|  |    p->uSelectedTimeLocation_ = | ||||||
|  |       p->shaderProgram_->GetUniformLocation("uSelectedTime"); | ||||||
|  | 
 | ||||||
|  |    gl.glGenVertexArrays(1, &p->vao_); | ||||||
|  |    gl.glGenBuffers(2, p->vbo_.data()); | ||||||
|  | 
 | ||||||
|  |    gl.glBindVertexArray(p->vao_); | ||||||
|  |    gl.glBindBuffer(GL_ARRAY_BUFFER, p->vbo_[0]); | ||||||
|  |    gl.glBufferData(GL_ARRAY_BUFFER, 0u, nullptr, GL_DYNAMIC_DRAW); | ||||||
|  | 
 | ||||||
|  |    // aScreenCoord
 | ||||||
|  |    gl.glVertexAttribPointer(0, | ||||||
|  |                             2, | ||||||
|  |                             GL_FLOAT, | ||||||
|  |                             GL_FALSE, | ||||||
|  |                             kPointsPerVertex * sizeof(float), | ||||||
|  |                             static_cast<void*>(0)); | ||||||
|  |    gl.glEnableVertexAttribArray(0); | ||||||
|  | 
 | ||||||
|  |    // aXYOffset
 | ||||||
|  |    gl.glVertexAttribPointer(1, | ||||||
|  |                             2, | ||||||
|  |                             GL_FLOAT, | ||||||
|  |                             GL_FALSE, | ||||||
|  |                             kPointsPerVertex * sizeof(float), | ||||||
|  |                             reinterpret_cast<void*>(2 * sizeof(float))); | ||||||
|  |    gl.glEnableVertexAttribArray(1); | ||||||
|  | 
 | ||||||
|  |    // aColor
 | ||||||
|  |    gl.glVertexAttribPointer(2, | ||||||
|  |                             4, | ||||||
|  |                             GL_FLOAT, | ||||||
|  |                             GL_FALSE, | ||||||
|  |                             kPointsPerVertex * sizeof(float), | ||||||
|  |                             reinterpret_cast<void*>(4 * sizeof(float))); | ||||||
|  |    gl.glEnableVertexAttribArray(2); | ||||||
|  | 
 | ||||||
|  |    gl.glBindBuffer(GL_ARRAY_BUFFER, p->vbo_[1]); | ||||||
|  |    gl.glBufferData(GL_ARRAY_BUFFER, 0u, nullptr, GL_DYNAMIC_DRAW); | ||||||
|  | 
 | ||||||
|  |    // aThreshold
 | ||||||
|  |    gl.glVertexAttribIPointer(3, //
 | ||||||
|  |                              1, | ||||||
|  |                              GL_INT, | ||||||
|  |                              kIntegersPerVertex_ * sizeof(GLint), | ||||||
|  |                              static_cast<void*>(0)); | ||||||
|  |    gl.glEnableVertexAttribArray(3); | ||||||
|  | 
 | ||||||
|  |    // aTimeRange
 | ||||||
|  |    gl.glVertexAttribIPointer(4, //
 | ||||||
|  |                              2, | ||||||
|  |                              GL_INT, | ||||||
|  |                              kIntegersPerVertex_ * sizeof(GLint), | ||||||
|  |                              reinterpret_cast<void*>(1 * sizeof(GLint))); | ||||||
|  |    gl.glEnableVertexAttribArray(4); | ||||||
|  | 
 | ||||||
|  |    p->dirty_ = true; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void PlacefilePolygons::Render( | ||||||
|  |    const QMapLibreGL::CustomLayerRenderParameters& params) | ||||||
|  | { | ||||||
|  |    if (!p->currentBuffer_.empty()) | ||||||
|  |    { | ||||||
|  |       gl::OpenGLFunctions& gl = p->context_->gl(); | ||||||
|  | 
 | ||||||
|  |       gl.glBindVertexArray(p->vao_); | ||||||
|  | 
 | ||||||
|  |       p->Update(); | ||||||
|  |       p->shaderProgram_->Use(); | ||||||
|  |       UseRotationProjection(params, p->uMVPMatrixLocation_); | ||||||
|  |       UseMapProjection( | ||||||
|  |          params, p->uMapMatrixLocation_, p->uMapScreenCoordLocation_); | ||||||
|  | 
 | ||||||
|  |       if (p->thresholded_) | ||||||
|  |       { | ||||||
|  |          // If thresholding is enabled, set the map distance
 | ||||||
|  |          units::length::nautical_miles<float> mapDistance = | ||||||
|  |             util::maplibre::GetMapDistance(params); | ||||||
|  |          gl.glUniform1f(p->uMapDistanceLocation_, mapDistance.value()); | ||||||
|  |       } | ||||||
|  |       else | ||||||
|  |       { | ||||||
|  |          // If thresholding is disabled, set the map distance to 0
 | ||||||
|  |          gl.glUniform1f(p->uMapDistanceLocation_, 0.0f); | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       // Selected time
 | ||||||
|  |       std::chrono::system_clock::time_point selectedTime = | ||||||
|  |          (p->selectedTime_ == std::chrono::system_clock::time_point {}) ? | ||||||
|  |             std::chrono::system_clock::now() : | ||||||
|  |             p->selectedTime_; | ||||||
|  |       gl.glUniform1i( | ||||||
|  |          p->uSelectedTimeLocation_, | ||||||
|  |          static_cast<GLint>(std::chrono::duration_cast<std::chrono::minutes>( | ||||||
|  |                                selectedTime.time_since_epoch()) | ||||||
|  |                                .count())); | ||||||
|  | 
 | ||||||
|  |       // Draw icons
 | ||||||
|  |       gl.glDrawArrays(GL_TRIANGLES, 0, p->numVertices_); | ||||||
|  |    } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void PlacefilePolygons::Deinitialize() | ||||||
|  | { | ||||||
|  |    gl::OpenGLFunctions& gl = p->context_->gl(); | ||||||
|  | 
 | ||||||
|  |    gl.glDeleteVertexArrays(1, &p->vao_); | ||||||
|  |    gl.glDeleteBuffers(2, p->vbo_.data()); | ||||||
|  | 
 | ||||||
|  |    std::unique_lock lock {p->bufferMutex_}; | ||||||
|  | 
 | ||||||
|  |    // Clear the current buffers
 | ||||||
|  |    p->currentBuffer_.clear(); | ||||||
|  |    p->currentIntegerBuffer_.clear(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void PlacefilePolygons::StartPolygons() | ||||||
|  | { | ||||||
|  |    // Clear the new buffers
 | ||||||
|  |    p->newBuffer_.clear(); | ||||||
|  |    p->newIntegerBuffer_.clear(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void PlacefilePolygons::AddPolygon( | ||||||
|  |    const std::shared_ptr<gr::Placefile::PolygonDrawItem>& di) | ||||||
|  | { | ||||||
|  |    if (di != nullptr) | ||||||
|  |    { | ||||||
|  |       p->Tessellate(di); | ||||||
|  |    } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void PlacefilePolygons::FinishPolygons() | ||||||
|  | { | ||||||
|  |    std::unique_lock lock {p->bufferMutex_}; | ||||||
|  | 
 | ||||||
|  |    // Swap buffers
 | ||||||
|  |    p->currentBuffer_.swap(p->newBuffer_); | ||||||
|  |    p->currentIntegerBuffer_.swap(p->newIntegerBuffer_); | ||||||
|  | 
 | ||||||
|  |    // Clear the new buffers
 | ||||||
|  |    p->newBuffer_.clear(); | ||||||
|  |    p->newIntegerBuffer_.clear(); | ||||||
|  | 
 | ||||||
|  |    // Mark the draw item dirty
 | ||||||
|  |    p->dirty_ = true; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void PlacefilePolygons::Impl::Update() | ||||||
|  | { | ||||||
|  |    if (dirty_) | ||||||
|  |    { | ||||||
|  |       gl::OpenGLFunctions& gl = context_->gl(); | ||||||
|  | 
 | ||||||
|  |       std::unique_lock lock {bufferMutex_}; | ||||||
|  | 
 | ||||||
|  |       // Buffer vertex data
 | ||||||
|  |       gl.glBindBuffer(GL_ARRAY_BUFFER, vbo_[0]); | ||||||
|  |       gl.glBufferData(GL_ARRAY_BUFFER, | ||||||
|  |                       sizeof(GLfloat) * currentBuffer_.size(), | ||||||
|  |                       currentBuffer_.data(), | ||||||
|  |                       GL_DYNAMIC_DRAW); | ||||||
|  | 
 | ||||||
|  |       // Buffer threshold data
 | ||||||
|  |       gl.glBindBuffer(GL_ARRAY_BUFFER, vbo_[1]); | ||||||
|  |       gl.glBufferData(GL_ARRAY_BUFFER, | ||||||
|  |                       sizeof(GLint) * currentIntegerBuffer_.size(), | ||||||
|  |                       currentIntegerBuffer_.data(), | ||||||
|  |                       GL_DYNAMIC_DRAW); | ||||||
|  | 
 | ||||||
|  |       numVertices_ = | ||||||
|  |          static_cast<GLsizei>(currentBuffer_.size() / kPointsPerVertex); | ||||||
|  | 
 | ||||||
|  |       dirty_ = false; | ||||||
|  |    } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void PlacefilePolygons::Impl::Tessellate( | ||||||
|  |    const std::shared_ptr<gr::Placefile::PolygonDrawItem>& di) | ||||||
|  | { | ||||||
|  |    // Vertex storage
 | ||||||
|  |    boost::container::stable_vector<TessVertexArray> vertices {}; | ||||||
|  | 
 | ||||||
|  |    // Default color to "Color" statement
 | ||||||
|  |    boost::gil::rgba8_pixel_t lastColor = di->color_; | ||||||
|  | 
 | ||||||
|  |    // Current threshold
 | ||||||
|  |    units::length::nautical_miles<double> threshold = di->threshold_; | ||||||
|  |    currentThreshold_ = static_cast<GLint>(std::round(threshold.value())); | ||||||
|  | 
 | ||||||
|  |    // Start and end time
 | ||||||
|  |    currentStartTime_ = | ||||||
|  |       static_cast<GLint>(std::chrono::duration_cast<std::chrono::minutes>( | ||||||
|  |                             di->startTime_.time_since_epoch()) | ||||||
|  |                             .count()); | ||||||
|  |    currentEndTime_ = | ||||||
|  |       static_cast<GLint>(std::chrono::duration_cast<std::chrono::minutes>( | ||||||
|  |                             di->endTime_.time_since_epoch()) | ||||||
|  |                             .count()); | ||||||
|  | 
 | ||||||
|  |    gluTessBeginPolygon(tessellator_, this); | ||||||
|  | 
 | ||||||
|  |    for (auto& contour : di->contours_) | ||||||
|  |    { | ||||||
|  |       gluTessBeginContour(tessellator_); | ||||||
|  | 
 | ||||||
|  |       for (auto& element : contour) | ||||||
|  |       { | ||||||
|  |          // Calculate screen coordinate
 | ||||||
|  |          auto screenCoordinate = util::maplibre::LatLongToScreenCoordinate( | ||||||
|  |             {element.latitude_, element.longitude_}); | ||||||
|  | 
 | ||||||
|  |          // Update the most recent color if specified
 | ||||||
|  |          if (element.color_.has_value()) | ||||||
|  |          { | ||||||
|  |             lastColor = element.color_.value(); | ||||||
|  |          } | ||||||
|  | 
 | ||||||
|  |          // Add vertex to temporary storage
 | ||||||
|  |          auto& vertex = | ||||||
|  |             vertices.emplace_back(TessVertexArray {screenCoordinate.x, | ||||||
|  |                                                    screenCoordinate.y, | ||||||
|  |                                                    0.0, // z
 | ||||||
|  |                                                    element.x_, | ||||||
|  |                                                    element.y_, | ||||||
|  |                                                    lastColor[0] / 255.0, | ||||||
|  |                                                    lastColor[1] / 255.0, | ||||||
|  |                                                    lastColor[2] / 255.0, | ||||||
|  |                                                    lastColor[3] / 255.0}); | ||||||
|  | 
 | ||||||
|  |          // Tessellate vertex
 | ||||||
|  |          gluTessVertex(tessellator_, vertex.data(), vertex.data()); | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       gluTessEndContour(tessellator_); | ||||||
|  |    } | ||||||
|  | 
 | ||||||
|  |    gluTessEndPolygon(tessellator_); | ||||||
|  | 
 | ||||||
|  |    // Clear temporary storage
 | ||||||
|  |    tessCombineBuffer_.clear(); | ||||||
|  | 
 | ||||||
|  |    // Remove extra vertices that don't correspond to a full triangle
 | ||||||
|  |    while (newBuffer_.size() % kVerticesPerTriangle != 0) | ||||||
|  |    { | ||||||
|  |       newBuffer_.pop_back(); | ||||||
|  |       newIntegerBuffer_.pop_back(); | ||||||
|  |    } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void PlacefilePolygons::Impl::TessellateCombineCallback(GLdouble coords[3], | ||||||
|  |                                                         void*    vertexData[4], | ||||||
|  |                                                         GLfloat  w[4], | ||||||
|  |                                                         void**   outData, | ||||||
|  |                                                         void*    polygonData) | ||||||
|  | { | ||||||
|  |    static constexpr std::size_t r = kTessVertexR_; | ||||||
|  |    static constexpr std::size_t a = kTessVertexA_; | ||||||
|  | 
 | ||||||
|  |    Impl* self = static_cast<Impl*>(polygonData); | ||||||
|  | 
 | ||||||
|  |    // Create new vertex data with given coordinates and interpolated color
 | ||||||
|  |    auto& newVertexData = self->tessCombineBuffer_.emplace_back( //
 | ||||||
|  |       TessVertexArray { | ||||||
|  |          coords[0], | ||||||
|  |          coords[1], | ||||||
|  |          coords[2], | ||||||
|  |          0.0, // offsetX
 | ||||||
|  |          0.0, // offsetY
 | ||||||
|  |          0.0, // r
 | ||||||
|  |          0.0, // g
 | ||||||
|  |          0.0, // b
 | ||||||
|  |          0.0  // a
 | ||||||
|  |       }); | ||||||
|  | 
 | ||||||
|  |    for (std::size_t i = 0; i < 4; ++i) | ||||||
|  |    { | ||||||
|  |       GLdouble* d = static_cast<GLdouble*>(vertexData[i]); | ||||||
|  |       if (d != nullptr) | ||||||
|  |       { | ||||||
|  |          for (std::size_t color = r; color <= a; ++color) | ||||||
|  |          { | ||||||
|  |             newVertexData[color] += w[i] * d[color]; | ||||||
|  |          } | ||||||
|  |       } | ||||||
|  |    } | ||||||
|  | 
 | ||||||
|  |    // Return new vertex data
 | ||||||
|  |    *outData = &newVertexData; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void PlacefilePolygons::Impl::TessellateVertexCallback(void* vertexData, | ||||||
|  |                                                        void* polygonData) | ||||||
|  | { | ||||||
|  |    Impl*     self = static_cast<Impl*>(polygonData); | ||||||
|  |    GLdouble* data = static_cast<GLdouble*>(vertexData); | ||||||
|  | 
 | ||||||
|  |    // Buffer vertex
 | ||||||
|  |    self->newBuffer_.insert(self->newBuffer_.end(), | ||||||
|  |                            {static_cast<float>(data[kTessVertexScreenX_]), | ||||||
|  |                             static_cast<float>(data[kTessVertexScreenY_]), | ||||||
|  |                             static_cast<float>(data[kTessVertexXOffset_]), | ||||||
|  |                             static_cast<float>(data[kTessVertexYOffset_]), | ||||||
|  |                             static_cast<float>(data[kTessVertexR_]), | ||||||
|  |                             static_cast<float>(data[kTessVertexG_]), | ||||||
|  |                             static_cast<float>(data[kTessVertexB_]), | ||||||
|  |                             static_cast<float>(data[kTessVertexA_])}); | ||||||
|  |    self->newIntegerBuffer_.insert(self->newIntegerBuffer_.end(), | ||||||
|  |                                   {self->currentThreshold_, | ||||||
|  |                                    self->currentStartTime_, | ||||||
|  |                                    self->currentEndTime_}); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void PlacefilePolygons::Impl::TessellateErrorCallback(GLenum errorCode) | ||||||
|  | { | ||||||
|  |    logger_->error("GL Error: {}", errorCode); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | } // namespace draw
 | ||||||
|  | } // namespace gl
 | ||||||
|  | } // namespace qt
 | ||||||
|  | } // namespace scwx
 | ||||||
							
								
								
									
										63
									
								
								scwx-qt/source/scwx/qt/gl/draw/placefile_polygons.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						|  | @ -0,0 +1,63 @@ | ||||||
|  | #pragma once | ||||||
|  | 
 | ||||||
|  | #include <scwx/qt/gl/gl_context.hpp> | ||||||
|  | #include <scwx/qt/gl/draw/draw_item.hpp> | ||||||
|  | #include <scwx/gr/placefile.hpp> | ||||||
|  | 
 | ||||||
|  | #include <boost/gil.hpp> | ||||||
|  | 
 | ||||||
|  | namespace scwx | ||||||
|  | { | ||||||
|  | namespace qt | ||||||
|  | { | ||||||
|  | namespace gl | ||||||
|  | { | ||||||
|  | namespace draw | ||||||
|  | { | ||||||
|  | 
 | ||||||
|  | class PlacefilePolygons : public DrawItem | ||||||
|  | { | ||||||
|  | public: | ||||||
|  |    explicit PlacefilePolygons(const std::shared_ptr<GlContext>& context); | ||||||
|  |    ~PlacefilePolygons(); | ||||||
|  | 
 | ||||||
|  |    PlacefilePolygons(const PlacefilePolygons&)            = delete; | ||||||
|  |    PlacefilePolygons& operator=(const PlacefilePolygons&) = delete; | ||||||
|  | 
 | ||||||
|  |    PlacefilePolygons(PlacefilePolygons&&) noexcept; | ||||||
|  |    PlacefilePolygons& operator=(PlacefilePolygons&&) noexcept; | ||||||
|  | 
 | ||||||
|  |    void set_selected_time(std::chrono::system_clock::time_point selectedTime); | ||||||
|  |    void set_thresholded(bool thresholded); | ||||||
|  | 
 | ||||||
|  |    void Initialize() override; | ||||||
|  |    void Render(const QMapLibreGL::CustomLayerRenderParameters& params) override; | ||||||
|  |    void Deinitialize() override; | ||||||
|  | 
 | ||||||
|  |    /**
 | ||||||
|  |     * Resets and prepares the draw item for adding a new set of polygons. | ||||||
|  |     */ | ||||||
|  |    void StartPolygons(); | ||||||
|  | 
 | ||||||
|  |    /**
 | ||||||
|  |     * Adds a placefile polygon to the internal draw list. | ||||||
|  |     * | ||||||
|  |     * @param [in] di Placefile polygon | ||||||
|  |     */ | ||||||
|  |    void AddPolygon(const std::shared_ptr<gr::Placefile::PolygonDrawItem>& di); | ||||||
|  | 
 | ||||||
|  |    /**
 | ||||||
|  |     * Finalizes the draw item after adding new polygons. | ||||||
|  |     */ | ||||||
|  |    void FinishPolygons(); | ||||||
|  | 
 | ||||||
|  | private: | ||||||
|  |    class Impl; | ||||||
|  | 
 | ||||||
|  |    std::unique_ptr<Impl> p; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | } // namespace draw
 | ||||||
|  | } // namespace gl
 | ||||||
|  | } // namespace qt
 | ||||||
|  | } // namespace scwx
 | ||||||
							
								
								
									
										311
									
								
								scwx-qt/source/scwx/qt/gl/draw/placefile_text.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						|  | @ -0,0 +1,311 @@ | ||||||
|  | #include <scwx/qt/gl/draw/placefile_text.hpp> | ||||||
|  | #include <scwx/qt/manager/font_manager.hpp> | ||||||
|  | #include <scwx/qt/manager/placefile_manager.hpp> | ||||||
|  | #include <scwx/qt/settings/text_settings.hpp> | ||||||
|  | #include <scwx/qt/util/maplibre.hpp> | ||||||
|  | #include <scwx/qt/util/tooltip.hpp> | ||||||
|  | #include <scwx/util/logger.hpp> | ||||||
|  | 
 | ||||||
|  | #include <fmt/format.h> | ||||||
|  | #include <imgui.h> | ||||||
|  | #include <mbgl/util/constants.hpp> | ||||||
|  | 
 | ||||||
|  | namespace scwx | ||||||
|  | { | ||||||
|  | namespace qt | ||||||
|  | { | ||||||
|  | namespace gl | ||||||
|  | { | ||||||
|  | namespace draw | ||||||
|  | { | ||||||
|  | 
 | ||||||
|  | static const std::string logPrefix_ = "scwx::qt::gl::draw::placefile_text"; | ||||||
|  | static const auto        logger_    = scwx::util::Logger::Create(logPrefix_); | ||||||
|  | 
 | ||||||
|  | class PlacefileText::Impl | ||||||
|  | { | ||||||
|  | public: | ||||||
|  |    explicit Impl(const std::shared_ptr<GlContext>& context, | ||||||
|  |                  const std::string&                placefileName) : | ||||||
|  |        context_ {context}, placefileName_ {placefileName} | ||||||
|  |    { | ||||||
|  |    } | ||||||
|  | 
 | ||||||
|  |    ~Impl() {} | ||||||
|  | 
 | ||||||
|  |    void RenderTextDrawItem( | ||||||
|  |       const QMapLibreGL::CustomLayerRenderParameters&           params, | ||||||
|  |       const std::shared_ptr<const gr::Placefile::TextDrawItem>& di); | ||||||
|  |    void RenderText(const QMapLibreGL::CustomLayerRenderParameters& params, | ||||||
|  |                    const std::string&                              text, | ||||||
|  |                    const std::string&                              hoverText, | ||||||
|  |                    boost::gil::rgba8_pixel_t                       color, | ||||||
|  |                    float                                           x, | ||||||
|  |                    float                                           y); | ||||||
|  | 
 | ||||||
|  |    std::shared_ptr<GlContext> context_; | ||||||
|  | 
 | ||||||
|  |    std::string placefileName_; | ||||||
|  | 
 | ||||||
|  |    bool thresholded_ {false}; | ||||||
|  | 
 | ||||||
|  |    std::chrono::system_clock::time_point selectedTime_ {}; | ||||||
|  | 
 | ||||||
|  |    std::uint32_t textId_ {}; | ||||||
|  |    glm::vec2     mapScreenCoordLocation_ {}; | ||||||
|  |    float         mapScale_ {1.0f}; | ||||||
|  |    float         mapBearingCos_ {1.0f}; | ||||||
|  |    float         mapBearingSin_ {0.0f}; | ||||||
|  |    float         halfWidth_ {}; | ||||||
|  |    float         halfHeight_ {}; | ||||||
|  |    std::string   hoverText_ {}; | ||||||
|  | 
 | ||||||
|  |    units::length::nautical_miles<double> mapDistance_ {}; | ||||||
|  | 
 | ||||||
|  |    std::mutex listMutex_ {}; | ||||||
|  |    std::vector<std::shared_ptr<const gr::Placefile::TextDrawItem>> textList_ {}; | ||||||
|  |    std::vector<std::shared_ptr<const gr::Placefile::TextDrawItem>> newList_ {}; | ||||||
|  | 
 | ||||||
|  |    std::vector<std::shared_ptr<types::ImGuiFont>> fonts_ {}; | ||||||
|  |    std::vector<std::shared_ptr<types::ImGuiFont>> newFonts_ {}; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | PlacefileText::PlacefileText(const std::shared_ptr<GlContext>& context, | ||||||
|  |                              const std::string&                placefileName) : | ||||||
|  |     DrawItem(context->gl()), p(std::make_unique<Impl>(context, placefileName)) | ||||||
|  | { | ||||||
|  | } | ||||||
|  | PlacefileText::~PlacefileText() = default; | ||||||
|  | 
 | ||||||
|  | PlacefileText::PlacefileText(PlacefileText&&) noexcept            = default; | ||||||
|  | PlacefileText& PlacefileText::operator=(PlacefileText&&) noexcept = default; | ||||||
|  | 
 | ||||||
|  | void PlacefileText::set_placefile_name(const std::string& placefileName) | ||||||
|  | { | ||||||
|  |    p->placefileName_ = placefileName; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void PlacefileText::set_selected_time( | ||||||
|  |    std::chrono::system_clock::time_point selectedTime) | ||||||
|  | { | ||||||
|  |    p->selectedTime_ = selectedTime; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void PlacefileText::set_thresholded(bool thresholded) | ||||||
|  | { | ||||||
|  |    p->thresholded_ = thresholded; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void PlacefileText::Initialize() {} | ||||||
|  | 
 | ||||||
|  | void PlacefileText::Render( | ||||||
|  |    const QMapLibreGL::CustomLayerRenderParameters& params) | ||||||
|  | { | ||||||
|  |    std::unique_lock lock {p->listMutex_}; | ||||||
|  | 
 | ||||||
|  |    if (!p->textList_.empty()) | ||||||
|  |    { | ||||||
|  |       // Reset text ID per frame
 | ||||||
|  |       p->textId_ = 0; | ||||||
|  |       p->hoverText_.clear(); | ||||||
|  | 
 | ||||||
|  |       // Update map screen coordinate and scale information
 | ||||||
|  |       p->mapScreenCoordLocation_ = util::maplibre::LatLongToScreenCoordinate( | ||||||
|  |          {params.latitude, params.longitude}); | ||||||
|  |       p->mapScale_ = std::pow(2.0, params.zoom) * mbgl::util::tileSize_D / | ||||||
|  |                      mbgl::util::DEGREES_MAX; | ||||||
|  |       p->mapBearingCos_ = cosf(params.bearing * common::kDegreesToRadians); | ||||||
|  |       p->mapBearingSin_ = sinf(params.bearing * common::kDegreesToRadians); | ||||||
|  |       p->halfWidth_     = params.width * 0.5f; | ||||||
|  |       p->halfHeight_    = params.height * 0.5f; | ||||||
|  |       p->mapDistance_   = util::maplibre::GetMapDistance(params); | ||||||
|  | 
 | ||||||
|  |       for (auto& di : p->textList_) | ||||||
|  |       { | ||||||
|  |          p->RenderTextDrawItem(params, di); | ||||||
|  |       } | ||||||
|  |    } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void PlacefileText::Impl::RenderTextDrawItem( | ||||||
|  |    const QMapLibreGL::CustomLayerRenderParameters&           params, | ||||||
|  |    const std::shared_ptr<const gr::Placefile::TextDrawItem>& di) | ||||||
|  | { | ||||||
|  |    // If no time has been selected, use the current time
 | ||||||
|  |    std::chrono::system_clock::time_point selectedTime = | ||||||
|  |       (selectedTime_ == std::chrono::system_clock::time_point {}) ? | ||||||
|  |          std::chrono::system_clock::now() : | ||||||
|  |          selectedTime_; | ||||||
|  | 
 | ||||||
|  |    if ((!thresholded_ || mapDistance_ <= di->threshold_) && | ||||||
|  |        (di->startTime_ == std::chrono::system_clock::time_point {} || | ||||||
|  |         (di->startTime_ <= selectedTime && selectedTime < di->endTime_))) | ||||||
|  |    { | ||||||
|  |       const auto screenCoordinates = (util::maplibre::LatLongToScreenCoordinate( | ||||||
|  |                                          {di->latitude_, di->longitude_}) - | ||||||
|  |                                       mapScreenCoordLocation_) * | ||||||
|  |                                      mapScale_; | ||||||
|  | 
 | ||||||
|  |       // Rotate text according to map rotation
 | ||||||
|  |       float rotatedX = screenCoordinates.x; | ||||||
|  |       float rotatedY = screenCoordinates.y; | ||||||
|  |       if (params.bearing != 0.0) | ||||||
|  |       { | ||||||
|  |          rotatedX = screenCoordinates.x * mapBearingCos_ - | ||||||
|  |                     screenCoordinates.y * mapBearingSin_; | ||||||
|  |          rotatedY = screenCoordinates.x * mapBearingSin_ + | ||||||
|  |                     screenCoordinates.y * mapBearingCos_; | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       // Clamp font number to 0-8
 | ||||||
|  |       std::size_t fontNumber = std::clamp<std::size_t>(di->fontNumber_, 0, 8); | ||||||
|  | 
 | ||||||
|  |       // Set the font for the drop shadow and text
 | ||||||
|  |       ImGui::PushFont(fonts_[fontNumber]->font()); | ||||||
|  | 
 | ||||||
|  |       if (settings::TextSettings::Instance() | ||||||
|  |              .placefile_text_drop_shadow_enabled() | ||||||
|  |              .GetValue()) | ||||||
|  |       { | ||||||
|  |          // Draw a drop shadow 1 pixel to the lower right, in black, with the
 | ||||||
|  |          // original transparency level
 | ||||||
|  |          RenderText(params, | ||||||
|  |                     di->text_, | ||||||
|  |                     {}, | ||||||
|  |                     boost::gil::rgba8_pixel_t {0, 0, 0, di->color_[3]}, | ||||||
|  |                     rotatedX + di->x_ + halfWidth_ + 1.0f, | ||||||
|  |                     rotatedY + di->y_ + halfHeight_ - 1.0f); | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       // Draw the text
 | ||||||
|  |       RenderText(params, | ||||||
|  |                  di->text_, | ||||||
|  |                  di->hoverText_, | ||||||
|  |                  di->color_, | ||||||
|  |                  rotatedX + di->x_ + halfWidth_, | ||||||
|  |                  rotatedY + di->y_ + halfHeight_); | ||||||
|  | 
 | ||||||
|  |       // Reset the font
 | ||||||
|  |       ImGui::PopFont(); | ||||||
|  |    } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void PlacefileText::Impl::RenderText( | ||||||
|  |    const QMapLibreGL::CustomLayerRenderParameters& params, | ||||||
|  |    const std::string&                              text, | ||||||
|  |    const std::string&                              hoverText, | ||||||
|  |    boost::gil::rgba8_pixel_t                       color, | ||||||
|  |    float                                           x, | ||||||
|  |    float                                           y) | ||||||
|  | { | ||||||
|  |    const std::string windowName { | ||||||
|  |       fmt::format("PlacefileText-{}-{}", placefileName_, ++textId_)}; | ||||||
|  | 
 | ||||||
|  |    // Convert screen to ImGui coordinates
 | ||||||
|  |    y = params.height - y; | ||||||
|  | 
 | ||||||
|  |    // Setup "window" to hold text
 | ||||||
|  |    ImGui::SetNextWindowPos( | ||||||
|  |       ImVec2 {x, y}, ImGuiCond_Always, ImVec2 {0.5f, 0.5f}); | ||||||
|  |    ImGui::Begin(windowName.c_str(), | ||||||
|  |                 nullptr, | ||||||
|  |                 ImGuiWindowFlags_AlwaysAutoResize | | ||||||
|  |                    ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoNav | | ||||||
|  |                    ImGuiWindowFlags_NoBackground); | ||||||
|  | 
 | ||||||
|  |    // Render text
 | ||||||
|  |    ImGui::PushStyleColor(ImGuiCol_Text, | ||||||
|  |                          IM_COL32(color[0], color[1], color[2], color[3])); | ||||||
|  |    ImGui::TextUnformatted(text.c_str()); | ||||||
|  |    ImGui::PopStyleColor(); | ||||||
|  | 
 | ||||||
|  |    // Store hover text for mouse picking pass
 | ||||||
|  |    if (!hoverText.empty() && ImGui::IsItemHovered()) | ||||||
|  |    { | ||||||
|  |       hoverText_ = hoverText; | ||||||
|  |    } | ||||||
|  | 
 | ||||||
|  |    // End window
 | ||||||
|  |    ImGui::End(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void PlacefileText::Deinitialize() | ||||||
|  | { | ||||||
|  |    std::unique_lock lock {p->listMutex_}; | ||||||
|  | 
 | ||||||
|  |    // Clear the text list
 | ||||||
|  |    p->textList_.clear(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool PlacefileText::RunMousePicking( | ||||||
|  |    const QMapLibreGL::CustomLayerRenderParameters& /* params */, | ||||||
|  |    const QPointF& /* mouseLocalPos */, | ||||||
|  |    const QPointF& mouseGlobalPos, | ||||||
|  |    const glm::vec2& /* mouseCoords */) | ||||||
|  | { | ||||||
|  |    bool itemPicked = false; | ||||||
|  | 
 | ||||||
|  |    // Create tooltip for hover text
 | ||||||
|  |    if (!p->hoverText_.empty()) | ||||||
|  |    { | ||||||
|  |       itemPicked = true; | ||||||
|  |       util::tooltip::Show(p->hoverText_, mouseGlobalPos); | ||||||
|  |    } | ||||||
|  | 
 | ||||||
|  |    return itemPicked; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void PlacefileText::StartText() | ||||||
|  | { | ||||||
|  |    // Clear the new list
 | ||||||
|  |    p->newList_.clear(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void PlacefileText::SetFonts( | ||||||
|  |    const boost::unordered_flat_map<std::size_t, | ||||||
|  |                                    std::shared_ptr<types::ImGuiFont>>& fonts) | ||||||
|  | { | ||||||
|  |    auto defaultFont = manager::FontManager::Instance().GetImGuiFont( | ||||||
|  |       types::FontCategory::Default); | ||||||
|  | 
 | ||||||
|  |    // Valid font numbers are from 1 to 8, use 0 for the default font
 | ||||||
|  |    for (std::size_t i = 0; i <= 8; ++i) | ||||||
|  |    { | ||||||
|  |       auto it = (i > 0) ? fonts.find(i) : fonts.cend(); | ||||||
|  |       if (it != fonts.cend()) | ||||||
|  |       { | ||||||
|  |          p->newFonts_.push_back(it->second); | ||||||
|  |       } | ||||||
|  |       else | ||||||
|  |       { | ||||||
|  |          p->newFonts_.push_back(defaultFont); | ||||||
|  |       } | ||||||
|  |    } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void PlacefileText::AddText( | ||||||
|  |    const std::shared_ptr<gr::Placefile::TextDrawItem>& di) | ||||||
|  | { | ||||||
|  |    if (di != nullptr) | ||||||
|  |    { | ||||||
|  |       p->newList_.emplace_back(di); | ||||||
|  |    } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void PlacefileText::FinishText() | ||||||
|  | { | ||||||
|  |    std::unique_lock lock {p->listMutex_}; | ||||||
|  | 
 | ||||||
|  |    // Swap text lists
 | ||||||
|  |    p->textList_.swap(p->newList_); | ||||||
|  |    p->fonts_.swap(p->newFonts_); | ||||||
|  | 
 | ||||||
|  |    // Clear the new list
 | ||||||
|  |    p->newList_.clear(); | ||||||
|  |    p->newFonts_.clear(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | } // namespace draw
 | ||||||
|  | } // namespace gl
 | ||||||
|  | } // namespace qt
 | ||||||
|  | } // namespace scwx
 | ||||||
							
								
								
									
										81
									
								
								scwx-qt/source/scwx/qt/gl/draw/placefile_text.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						|  | @ -0,0 +1,81 @@ | ||||||
|  | #pragma once | ||||||
|  | 
 | ||||||
|  | #include <scwx/qt/gl/gl_context.hpp> | ||||||
|  | #include <scwx/qt/gl/draw/draw_item.hpp> | ||||||
|  | #include <scwx/qt/types/imgui_font.hpp> | ||||||
|  | #include <scwx/gr/placefile.hpp> | ||||||
|  | 
 | ||||||
|  | #include <boost/unordered/unordered_flat_map.hpp> | ||||||
|  | 
 | ||||||
|  | namespace scwx | ||||||
|  | { | ||||||
|  | namespace qt | ||||||
|  | { | ||||||
|  | namespace gl | ||||||
|  | { | ||||||
|  | namespace draw | ||||||
|  | { | ||||||
|  | 
 | ||||||
|  | class PlacefileText : public DrawItem | ||||||
|  | { | ||||||
|  | public: | ||||||
|  |    explicit PlacefileText(const std::shared_ptr<GlContext>& context, | ||||||
|  |                           const std::string&                placefileName); | ||||||
|  |    ~PlacefileText(); | ||||||
|  | 
 | ||||||
|  |    PlacefileText(const PlacefileText&)            = delete; | ||||||
|  |    PlacefileText& operator=(const PlacefileText&) = delete; | ||||||
|  | 
 | ||||||
|  |    PlacefileText(PlacefileText&&) noexcept; | ||||||
|  |    PlacefileText& operator=(PlacefileText&&) noexcept; | ||||||
|  | 
 | ||||||
|  |    void set_placefile_name(const std::string& placefileName); | ||||||
|  |    void set_selected_time(std::chrono::system_clock::time_point selectedTime); | ||||||
|  |    void set_thresholded(bool thresholded); | ||||||
|  | 
 | ||||||
|  |    void Initialize() override; | ||||||
|  |    void Render(const QMapLibreGL::CustomLayerRenderParameters& params) override; | ||||||
|  |    void Deinitialize() override; | ||||||
|  | 
 | ||||||
|  |    bool RunMousePicking(const QMapLibreGL::CustomLayerRenderParameters& params, | ||||||
|  |                         const QPointF&   mouseLocalPos, | ||||||
|  |                         const QPointF&   mouseGlobalPos, | ||||||
|  |                         const glm::vec2& mouseCoords) override; | ||||||
|  | 
 | ||||||
|  |    /**
 | ||||||
|  |     * Resets and prepares the draw item for adding a new set of text. | ||||||
|  |     */ | ||||||
|  |    void StartText(); | ||||||
|  | 
 | ||||||
|  |    /**
 | ||||||
|  |     * Configures the fonts for drawing the placefile text. | ||||||
|  |     * | ||||||
|  |     * @param [in] fonts A map of ImGui fonts | ||||||
|  |     */ | ||||||
|  |    void | ||||||
|  |    SetFonts(const boost::unordered_flat_map<std::size_t, | ||||||
|  |                                             std::shared_ptr<types::ImGuiFont>>& | ||||||
|  |                fonts); | ||||||
|  | 
 | ||||||
|  |    /**
 | ||||||
|  |     * Adds placefile text to the internal draw list. | ||||||
|  |     * | ||||||
|  |     * @param [in] di Placefile icon | ||||||
|  |     */ | ||||||
|  |    void AddText(const std::shared_ptr<gr::Placefile::TextDrawItem>& di); | ||||||
|  | 
 | ||||||
|  |    /**
 | ||||||
|  |     * Finalizes the draw item after adding new text. | ||||||
|  |     */ | ||||||
|  |    void FinishText(); | ||||||
|  | 
 | ||||||
|  | private: | ||||||
|  |    class Impl; | ||||||
|  | 
 | ||||||
|  |    std::unique_ptr<Impl> p; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | } // namespace draw
 | ||||||
|  | } // namespace gl
 | ||||||
|  | } // namespace qt
 | ||||||
|  | } // namespace scwx
 | ||||||
							
								
								
									
										351
									
								
								scwx-qt/source/scwx/qt/gl/draw/placefile_triangles.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						|  | @ -0,0 +1,351 @@ | ||||||
|  | #include <scwx/qt/gl/draw/placefile_triangles.hpp> | ||||||
|  | #include <scwx/qt/util/maplibre.hpp> | ||||||
|  | #include <scwx/util/logger.hpp> | ||||||
|  | 
 | ||||||
|  | #include <mutex> | ||||||
|  | 
 | ||||||
|  | namespace scwx | ||||||
|  | { | ||||||
|  | namespace qt | ||||||
|  | { | ||||||
|  | namespace gl | ||||||
|  | { | ||||||
|  | namespace draw | ||||||
|  | { | ||||||
|  | 
 | ||||||
|  | static const std::string logPrefix_ = "scwx::qt::gl::draw::placefile_triangles"; | ||||||
|  | static const auto        logger_    = scwx::util::Logger::Create(logPrefix_); | ||||||
|  | 
 | ||||||
|  | static constexpr std::size_t kVerticesPerTriangle = 3; | ||||||
|  | static constexpr std::size_t kPointsPerVertex     = 8; | ||||||
|  | 
 | ||||||
|  | // Threshold, start time, end time
 | ||||||
|  | static constexpr std::size_t kIntegersPerVertex_ = 3; | ||||||
|  | 
 | ||||||
|  | class PlacefileTriangles::Impl | ||||||
|  | { | ||||||
|  | public: | ||||||
|  |    explicit Impl(const std::shared_ptr<GlContext>& context) : | ||||||
|  |        context_ {context}, | ||||||
|  |        shaderProgram_ {nullptr}, | ||||||
|  |        uMVPMatrixLocation_(GL_INVALID_INDEX), | ||||||
|  |        uMapMatrixLocation_(GL_INVALID_INDEX), | ||||||
|  |        uMapScreenCoordLocation_(GL_INVALID_INDEX), | ||||||
|  |        uMapDistanceLocation_(GL_INVALID_INDEX), | ||||||
|  |        uSelectedTimeLocation_(GL_INVALID_INDEX), | ||||||
|  |        vao_ {GL_INVALID_INDEX}, | ||||||
|  |        vbo_ {GL_INVALID_INDEX}, | ||||||
|  |        numVertices_ {0} | ||||||
|  |    { | ||||||
|  |    } | ||||||
|  | 
 | ||||||
|  |    ~Impl() {} | ||||||
|  | 
 | ||||||
|  |    void UpdateBuffers( | ||||||
|  |       const std::shared_ptr<const gr::Placefile::TrianglesDrawItem>& di); | ||||||
|  |    void Update(); | ||||||
|  | 
 | ||||||
|  |    std::shared_ptr<GlContext> context_; | ||||||
|  | 
 | ||||||
|  |    bool dirty_ {false}; | ||||||
|  |    bool thresholded_ {false}; | ||||||
|  | 
 | ||||||
|  |    std::chrono::system_clock::time_point selectedTime_ {}; | ||||||
|  | 
 | ||||||
|  |    std::mutex bufferMutex_ {}; | ||||||
|  | 
 | ||||||
|  |    std::vector<GLfloat> currentBuffer_ {}; | ||||||
|  |    std::vector<GLint>   currentIntegerBuffer_ {}; | ||||||
|  |    std::vector<GLfloat> newBuffer_ {}; | ||||||
|  |    std::vector<GLint>   newIntegerBuffer_ {}; | ||||||
|  | 
 | ||||||
|  |    std::shared_ptr<ShaderProgram> shaderProgram_; | ||||||
|  |    GLint                          uMVPMatrixLocation_; | ||||||
|  |    GLint                          uMapMatrixLocation_; | ||||||
|  |    GLint                          uMapScreenCoordLocation_; | ||||||
|  |    GLint                          uMapDistanceLocation_; | ||||||
|  |    GLint                          uSelectedTimeLocation_; | ||||||
|  | 
 | ||||||
|  |    GLuint                vao_; | ||||||
|  |    std::array<GLuint, 2> vbo_; | ||||||
|  | 
 | ||||||
|  |    GLsizei numVertices_; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | PlacefileTriangles::PlacefileTriangles( | ||||||
|  |    const std::shared_ptr<GlContext>& context) : | ||||||
|  |     DrawItem(context->gl()), p(std::make_unique<Impl>(context)) | ||||||
|  | { | ||||||
|  | } | ||||||
|  | PlacefileTriangles::~PlacefileTriangles() = default; | ||||||
|  | 
 | ||||||
|  | PlacefileTriangles::PlacefileTriangles(PlacefileTriangles&&) noexcept = default; | ||||||
|  | PlacefileTriangles& | ||||||
|  | PlacefileTriangles::operator=(PlacefileTriangles&&) noexcept = default; | ||||||
|  | 
 | ||||||
|  | void PlacefileTriangles::set_selected_time( | ||||||
|  |    std::chrono::system_clock::time_point selectedTime) | ||||||
|  | { | ||||||
|  |    p->selectedTime_ = selectedTime; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void PlacefileTriangles::set_thresholded(bool thresholded) | ||||||
|  | { | ||||||
|  |    p->thresholded_ = thresholded; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void PlacefileTriangles::Initialize() | ||||||
|  | { | ||||||
|  |    gl::OpenGLFunctions& gl = p->context_->gl(); | ||||||
|  | 
 | ||||||
|  |    p->shaderProgram_ = p->context_->GetShaderProgram( | ||||||
|  |       {{GL_VERTEX_SHADER, ":/gl/map_color.vert"}, | ||||||
|  |        {GL_GEOMETRY_SHADER, ":/gl/threshold.geom"}, | ||||||
|  |        {GL_FRAGMENT_SHADER, ":/gl/color.frag"}}); | ||||||
|  | 
 | ||||||
|  |    p->uMVPMatrixLocation_ = p->shaderProgram_->GetUniformLocation("uMVPMatrix"); | ||||||
|  |    p->uMapMatrixLocation_ = p->shaderProgram_->GetUniformLocation("uMapMatrix"); | ||||||
|  |    p->uMapScreenCoordLocation_ = | ||||||
|  |       p->shaderProgram_->GetUniformLocation("uMapScreenCoord"); | ||||||
|  |    p->uMapDistanceLocation_ = | ||||||
|  |       p->shaderProgram_->GetUniformLocation("uMapDistance"); | ||||||
|  |    p->uSelectedTimeLocation_ = | ||||||
|  |       p->shaderProgram_->GetUniformLocation("uSelectedTime"); | ||||||
|  | 
 | ||||||
|  |    gl.glGenVertexArrays(1, &p->vao_); | ||||||
|  |    gl.glGenBuffers(2, p->vbo_.data()); | ||||||
|  | 
 | ||||||
|  |    gl.glBindVertexArray(p->vao_); | ||||||
|  |    gl.glBindBuffer(GL_ARRAY_BUFFER, p->vbo_[0]); | ||||||
|  |    gl.glBufferData(GL_ARRAY_BUFFER, 0u, nullptr, GL_DYNAMIC_DRAW); | ||||||
|  | 
 | ||||||
|  |    // aScreenCoord
 | ||||||
|  |    gl.glVertexAttribPointer(0, | ||||||
|  |                             2, | ||||||
|  |                             GL_FLOAT, | ||||||
|  |                             GL_FALSE, | ||||||
|  |                             kPointsPerVertex * sizeof(float), | ||||||
|  |                             static_cast<void*>(0)); | ||||||
|  |    gl.glEnableVertexAttribArray(0); | ||||||
|  | 
 | ||||||
|  |    // aXYOffset
 | ||||||
|  |    gl.glVertexAttribPointer(1, | ||||||
|  |                             2, | ||||||
|  |                             GL_FLOAT, | ||||||
|  |                             GL_FALSE, | ||||||
|  |                             kPointsPerVertex * sizeof(float), | ||||||
|  |                             reinterpret_cast<void*>(2 * sizeof(float))); | ||||||
|  |    gl.glEnableVertexAttribArray(1); | ||||||
|  | 
 | ||||||
|  |    // aColor
 | ||||||
|  |    gl.glVertexAttribPointer(2, | ||||||
|  |                             4, | ||||||
|  |                             GL_FLOAT, | ||||||
|  |                             GL_FALSE, | ||||||
|  |                             kPointsPerVertex * sizeof(float), | ||||||
|  |                             reinterpret_cast<void*>(4 * sizeof(float))); | ||||||
|  |    gl.glEnableVertexAttribArray(2); | ||||||
|  | 
 | ||||||
|  |    gl.glBindBuffer(GL_ARRAY_BUFFER, p->vbo_[1]); | ||||||
|  |    gl.glBufferData(GL_ARRAY_BUFFER, 0u, nullptr, GL_DYNAMIC_DRAW); | ||||||
|  | 
 | ||||||
|  |    // aThreshold
 | ||||||
|  |    gl.glVertexAttribIPointer(3, //
 | ||||||
|  |                              1, | ||||||
|  |                              GL_INT, | ||||||
|  |                              kIntegersPerVertex_ * sizeof(GLint), | ||||||
|  |                              static_cast<void*>(0)); | ||||||
|  |    gl.glEnableVertexAttribArray(3); | ||||||
|  | 
 | ||||||
|  |    // aTimeRange
 | ||||||
|  |    gl.glVertexAttribIPointer(4, //
 | ||||||
|  |                              2, | ||||||
|  |                              GL_INT, | ||||||
|  |                              kIntegersPerVertex_ * sizeof(GLint), | ||||||
|  |                              reinterpret_cast<void*>(1 * sizeof(GLint))); | ||||||
|  |    gl.glEnableVertexAttribArray(4); | ||||||
|  | 
 | ||||||
|  |    p->dirty_ = true; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void PlacefileTriangles::Render( | ||||||
|  |    const QMapLibreGL::CustomLayerRenderParameters& params) | ||||||
|  | { | ||||||
|  |    if (!p->currentBuffer_.empty()) | ||||||
|  |    { | ||||||
|  |       gl::OpenGLFunctions& gl = p->context_->gl(); | ||||||
|  | 
 | ||||||
|  |       gl.glBindVertexArray(p->vao_); | ||||||
|  | 
 | ||||||
|  |       p->Update(); | ||||||
|  |       p->shaderProgram_->Use(); | ||||||
|  |       UseRotationProjection(params, p->uMVPMatrixLocation_); | ||||||
|  |       UseMapProjection( | ||||||
|  |          params, p->uMapMatrixLocation_, p->uMapScreenCoordLocation_); | ||||||
|  | 
 | ||||||
|  |       if (p->thresholded_) | ||||||
|  |       { | ||||||
|  |          // If thresholding is enabled, set the map distance
 | ||||||
|  |          units::length::nautical_miles<float> mapDistance = | ||||||
|  |             util::maplibre::GetMapDistance(params); | ||||||
|  |          gl.glUniform1f(p->uMapDistanceLocation_, mapDistance.value()); | ||||||
|  |       } | ||||||
|  |       else | ||||||
|  |       { | ||||||
|  |          // If thresholding is disabled, set the map distance to 0
 | ||||||
|  |          gl.glUniform1f(p->uMapDistanceLocation_, 0.0f); | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       // Selected time
 | ||||||
|  |       std::chrono::system_clock::time_point selectedTime = | ||||||
|  |          (p->selectedTime_ == std::chrono::system_clock::time_point {}) ? | ||||||
|  |             std::chrono::system_clock::now() : | ||||||
|  |             p->selectedTime_; | ||||||
|  |       gl.glUniform1i( | ||||||
|  |          p->uSelectedTimeLocation_, | ||||||
|  |          static_cast<GLint>(std::chrono::duration_cast<std::chrono::minutes>( | ||||||
|  |                                selectedTime.time_since_epoch()) | ||||||
|  |                                .count())); | ||||||
|  | 
 | ||||||
|  |       // Draw icons
 | ||||||
|  |       gl.glDrawArrays(GL_TRIANGLES, 0, p->numVertices_); | ||||||
|  |    } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void PlacefileTriangles::Deinitialize() | ||||||
|  | { | ||||||
|  |    gl::OpenGLFunctions& gl = p->context_->gl(); | ||||||
|  | 
 | ||||||
|  |    gl.glDeleteVertexArrays(1, &p->vao_); | ||||||
|  |    gl.glDeleteBuffers(2, p->vbo_.data()); | ||||||
|  | 
 | ||||||
|  |    std::unique_lock lock {p->bufferMutex_}; | ||||||
|  | 
 | ||||||
|  |    // Clear the current buffers
 | ||||||
|  |    p->currentBuffer_.clear(); | ||||||
|  |    p->currentIntegerBuffer_.clear(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void PlacefileTriangles::StartTriangles() | ||||||
|  | { | ||||||
|  |    // Clear the new buffers
 | ||||||
|  |    p->newBuffer_.clear(); | ||||||
|  |    p->newIntegerBuffer_.clear(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void PlacefileTriangles::AddTriangles( | ||||||
|  |    const std::shared_ptr<gr::Placefile::TrianglesDrawItem>& di) | ||||||
|  | { | ||||||
|  |    if (di != nullptr) | ||||||
|  |    { | ||||||
|  |       p->UpdateBuffers(di); | ||||||
|  |    } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void PlacefileTriangles::FinishTriangles() | ||||||
|  | { | ||||||
|  |    std::unique_lock lock {p->bufferMutex_}; | ||||||
|  | 
 | ||||||
|  |    // Swap buffers
 | ||||||
|  |    p->currentBuffer_.swap(p->newBuffer_); | ||||||
|  |    p->currentIntegerBuffer_.swap(p->newIntegerBuffer_); | ||||||
|  | 
 | ||||||
|  |    // Clear the new buffers
 | ||||||
|  |    p->newBuffer_.clear(); | ||||||
|  |    p->newIntegerBuffer_.clear(); | ||||||
|  | 
 | ||||||
|  |    // Mark the draw item dirty
 | ||||||
|  |    p->dirty_ = true; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void PlacefileTriangles::Impl::UpdateBuffers( | ||||||
|  |    const std::shared_ptr<const gr::Placefile::TrianglesDrawItem>& di) | ||||||
|  | { | ||||||
|  |    // Threshold value
 | ||||||
|  |    units::length::nautical_miles<double> threshold = di->threshold_; | ||||||
|  |    GLint thresholdValue = static_cast<GLint>(std::round(threshold.value())); | ||||||
|  | 
 | ||||||
|  |    // Start and end time
 | ||||||
|  |    GLint startTime = | ||||||
|  |       static_cast<GLint>(std::chrono::duration_cast<std::chrono::minutes>( | ||||||
|  |                             di->startTime_.time_since_epoch()) | ||||||
|  |                             .count()); | ||||||
|  |    GLint endTime = | ||||||
|  |       static_cast<GLint>(std::chrono::duration_cast<std::chrono::minutes>( | ||||||
|  |                             di->endTime_.time_since_epoch()) | ||||||
|  |                             .count()); | ||||||
|  | 
 | ||||||
|  |    // Default color to "Color" statement
 | ||||||
|  |    boost::gil::rgba8_pixel_t lastColor = di->color_; | ||||||
|  | 
 | ||||||
|  |    // For each element inside a Triangles statement, add a vertex
 | ||||||
|  |    for (auto& element : di->elements_) | ||||||
|  |    { | ||||||
|  |       // Calculate screen coordinate
 | ||||||
|  |       auto screenCoordinate = util::maplibre::LatLongToScreenCoordinate( | ||||||
|  |          {element.latitude_, element.longitude_}); | ||||||
|  | 
 | ||||||
|  |       // X/Y offset in pixels
 | ||||||
|  |       const float x = static_cast<float>(element.x_); | ||||||
|  |       const float y = static_cast<float>(element.y_); | ||||||
|  | 
 | ||||||
|  |       // Update the most recent color if specified
 | ||||||
|  |       if (element.color_.has_value()) | ||||||
|  |       { | ||||||
|  |          lastColor = element.color_.value(); | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       // Color value
 | ||||||
|  |       const float r = lastColor[0] / 255.0f; | ||||||
|  |       const float g = lastColor[1] / 255.0f; | ||||||
|  |       const float b = lastColor[2] / 255.0f; | ||||||
|  |       const float a = lastColor[3] / 255.0f; | ||||||
|  | 
 | ||||||
|  |       newBuffer_.insert( | ||||||
|  |          newBuffer_.end(), | ||||||
|  |          {screenCoordinate.x, screenCoordinate.y, x, y, r, g, b, a}); | ||||||
|  |       newIntegerBuffer_.insert(newIntegerBuffer_.end(), | ||||||
|  |                                {thresholdValue, startTime, endTime}); | ||||||
|  |    } | ||||||
|  | 
 | ||||||
|  |    // Remove extra vertices that don't correspond to a full triangle
 | ||||||
|  |    while (newBuffer_.size() % kVerticesPerTriangle != 0) | ||||||
|  |    { | ||||||
|  |       newBuffer_.pop_back(); | ||||||
|  |       newIntegerBuffer_.pop_back(); | ||||||
|  |    } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void PlacefileTriangles::Impl::Update() | ||||||
|  | { | ||||||
|  |    if (dirty_) | ||||||
|  |    { | ||||||
|  |       gl::OpenGLFunctions& gl = context_->gl(); | ||||||
|  | 
 | ||||||
|  |       std::unique_lock lock {bufferMutex_}; | ||||||
|  | 
 | ||||||
|  |       // Buffer vertex data
 | ||||||
|  |       gl.glBindBuffer(GL_ARRAY_BUFFER, vbo_[0]); | ||||||
|  |       gl.glBufferData(GL_ARRAY_BUFFER, | ||||||
|  |                       sizeof(GLfloat) * currentBuffer_.size(), | ||||||
|  |                       currentBuffer_.data(), | ||||||
|  |                       GL_DYNAMIC_DRAW); | ||||||
|  | 
 | ||||||
|  |       // Buffer threshold data
 | ||||||
|  |       gl.glBindBuffer(GL_ARRAY_BUFFER, vbo_[1]); | ||||||
|  |       gl.glBufferData(GL_ARRAY_BUFFER, | ||||||
|  |                       sizeof(GLint) * currentIntegerBuffer_.size(), | ||||||
|  |                       currentIntegerBuffer_.data(), | ||||||
|  |                       GL_DYNAMIC_DRAW); | ||||||
|  | 
 | ||||||
|  |       numVertices_ = | ||||||
|  |          static_cast<GLsizei>(currentBuffer_.size() / kPointsPerVertex); | ||||||
|  | 
 | ||||||
|  |       dirty_ = false; | ||||||
|  |    } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | } // namespace draw
 | ||||||
|  | } // namespace gl
 | ||||||
|  | } // namespace qt
 | ||||||
|  | } // namespace scwx
 | ||||||
							
								
								
									
										62
									
								
								scwx-qt/source/scwx/qt/gl/draw/placefile_triangles.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						|  | @ -0,0 +1,62 @@ | ||||||
|  | #pragma once | ||||||
|  | 
 | ||||||
|  | #include <scwx/qt/gl/gl_context.hpp> | ||||||
|  | #include <scwx/qt/gl/draw/draw_item.hpp> | ||||||
|  | #include <scwx/gr/placefile.hpp> | ||||||
|  | 
 | ||||||
|  | namespace scwx | ||||||
|  | { | ||||||
|  | namespace qt | ||||||
|  | { | ||||||
|  | namespace gl | ||||||
|  | { | ||||||
|  | namespace draw | ||||||
|  | { | ||||||
|  | 
 | ||||||
|  | class PlacefileTriangles : public DrawItem | ||||||
|  | { | ||||||
|  | public: | ||||||
|  |    explicit PlacefileTriangles(const std::shared_ptr<GlContext>& context); | ||||||
|  |    ~PlacefileTriangles(); | ||||||
|  | 
 | ||||||
|  |    PlacefileTriangles(const PlacefileTriangles&)            = delete; | ||||||
|  |    PlacefileTriangles& operator=(const PlacefileTriangles&) = delete; | ||||||
|  | 
 | ||||||
|  |    PlacefileTriangles(PlacefileTriangles&&) noexcept; | ||||||
|  |    PlacefileTriangles& operator=(PlacefileTriangles&&) noexcept; | ||||||
|  | 
 | ||||||
|  |    void set_selected_time(std::chrono::system_clock::time_point selectedTime); | ||||||
|  |    void set_thresholded(bool thresholded); | ||||||
|  | 
 | ||||||
|  |    void Initialize() override; | ||||||
|  |    void Render(const QMapLibreGL::CustomLayerRenderParameters& params) override; | ||||||
|  |    void Deinitialize() override; | ||||||
|  | 
 | ||||||
|  |    /**
 | ||||||
|  |     * Resets and prepares the draw item for adding a new set of triangles. | ||||||
|  |     */ | ||||||
|  |    void StartTriangles(); | ||||||
|  | 
 | ||||||
|  |    /**
 | ||||||
|  |     * Adds placefile triangles to the internal draw list. | ||||||
|  |     * | ||||||
|  |     * @param [in] di Placefile triangles | ||||||
|  |     */ | ||||||
|  |    void | ||||||
|  |    AddTriangles(const std::shared_ptr<gr::Placefile::TrianglesDrawItem>& di); | ||||||
|  | 
 | ||||||
|  |    /**
 | ||||||
|  |     * Finalizes the draw item after adding new triangles. | ||||||
|  |     */ | ||||||
|  |    void FinishTriangles(); | ||||||
|  | 
 | ||||||
|  | private: | ||||||
|  |    class Impl; | ||||||
|  | 
 | ||||||
|  |    std::unique_ptr<Impl> p; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | } // namespace draw
 | ||||||
|  | } // namespace gl
 | ||||||
|  | } // namespace qt
 | ||||||
|  | } // namespace scwx
 | ||||||
|  | @ -1,8 +1,9 @@ | ||||||
| #include <scwx/qt/gl/gl_context.hpp> | #include <scwx/qt/gl/gl_context.hpp> | ||||||
| #include <scwx/qt/util/texture_atlas.hpp> | #include <scwx/qt/util/texture_atlas.hpp> | ||||||
| #include <scwx/util/hash.hpp> |  | ||||||
| #include <scwx/util/logger.hpp> | #include <scwx/util/logger.hpp> | ||||||
| 
 | 
 | ||||||
|  | #include <boost/container_hash/hash.hpp> | ||||||
|  | 
 | ||||||
| namespace scwx | namespace scwx | ||||||
| { | { | ||||||
| namespace qt | namespace qt | ||||||
|  | @ -25,16 +26,23 @@ public: | ||||||
|    } |    } | ||||||
|    ~Impl() {} |    ~Impl() {} | ||||||
| 
 | 
 | ||||||
|  |    void InitializeGL(); | ||||||
|  | 
 | ||||||
|  |    static std::size_t | ||||||
|  |    GetShaderKey(std::initializer_list<std::pair<GLenum, std::string>> shaders); | ||||||
|  | 
 | ||||||
|    gl::OpenGLFunctions gl_; |    gl::OpenGLFunctions gl_; | ||||||
| 
 | 
 | ||||||
|    std::unordered_map<std::pair<std::string, std::string>, |    bool glInitialized_ {false}; | ||||||
|                       std::shared_ptr<gl::ShaderProgram>, | 
 | ||||||
|                       scwx::util::hash<std::pair<std::string, std::string>>> |    std::unordered_map<std::size_t, std::shared_ptr<gl::ShaderProgram>> | ||||||
|               shaderProgramMap_; |               shaderProgramMap_; | ||||||
|    std::mutex shaderProgramMutex_; |    std::mutex shaderProgramMutex_; | ||||||
| 
 | 
 | ||||||
|    GLuint     textureAtlas_; |    GLuint     textureAtlas_; | ||||||
|    std::mutex textureMutex_; |    std::mutex textureMutex_; | ||||||
|  | 
 | ||||||
|  |    std::uint64_t textureBufferCount_ {}; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| GlContext::GlContext() : p(std::make_unique<Impl>()) {} | GlContext::GlContext() : p(std::make_unique<Impl>()) {} | ||||||
|  | @ -48,12 +56,36 @@ gl::OpenGLFunctions& GlContext::gl() | ||||||
|    return p->gl_; |    return p->gl_; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | std::uint64_t GlContext::texture_buffer_count() const | ||||||
|  | { | ||||||
|  |    return p->textureBufferCount_; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void GlContext::Impl::InitializeGL() | ||||||
|  | { | ||||||
|  |    if (glInitialized_) | ||||||
|  |    { | ||||||
|  |       return; | ||||||
|  |    } | ||||||
|  | 
 | ||||||
|  |    gl_.glGenTextures(1, &textureAtlas_); | ||||||
|  | 
 | ||||||
|  |    glInitialized_ = true; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| std::shared_ptr<gl::ShaderProgram> | std::shared_ptr<gl::ShaderProgram> | ||||||
| GlContext::GetShaderProgram(const std::string& vertexPath, | GlContext::GetShaderProgram(const std::string& vertexPath, | ||||||
|                             const std::string& fragmentPath) |                             const std::string& fragmentPath) | ||||||
| { | { | ||||||
|    const std::pair<std::string, std::string> key {vertexPath, fragmentPath}; |    return GetShaderProgram( | ||||||
|    std::shared_ptr<gl::ShaderProgram>        shaderProgram; |       {{GL_VERTEX_SHADER, vertexPath}, {GL_FRAGMENT_SHADER, fragmentPath}}); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | std::shared_ptr<gl::ShaderProgram> GlContext::GetShaderProgram( | ||||||
|  |    std::initializer_list<std::pair<GLenum, std::string>> shaders) | ||||||
|  | { | ||||||
|  |    const auto                         key = Impl::GetShaderKey(shaders); | ||||||
|  |    std::shared_ptr<gl::ShaderProgram> shaderProgram; | ||||||
| 
 | 
 | ||||||
|    std::unique_lock lock(p->shaderProgramMutex_); |    std::unique_lock lock(p->shaderProgramMutex_); | ||||||
| 
 | 
 | ||||||
|  | @ -62,7 +94,7 @@ GlContext::GetShaderProgram(const std::string& vertexPath, | ||||||
|    if (it == p->shaderProgramMap_.end()) |    if (it == p->shaderProgramMap_.end()) | ||||||
|    { |    { | ||||||
|       shaderProgram = std::make_shared<gl::ShaderProgram>(p->gl_); |       shaderProgram = std::make_shared<gl::ShaderProgram>(p->gl_); | ||||||
|       shaderProgram->Load(vertexPath, fragmentPath); |       shaderProgram->Load(shaders); | ||||||
|       p->shaderProgramMap_[key] = shaderProgram; |       p->shaderProgramMap_[key] = shaderProgram; | ||||||
|    } |    } | ||||||
|    else |    else | ||||||
|  | @ -75,16 +107,33 @@ GlContext::GetShaderProgram(const std::string& vertexPath, | ||||||
| 
 | 
 | ||||||
| GLuint GlContext::GetTextureAtlas() | GLuint GlContext::GetTextureAtlas() | ||||||
| { | { | ||||||
|  |    p->InitializeGL(); | ||||||
|  | 
 | ||||||
|    std::unique_lock lock(p->textureMutex_); |    std::unique_lock lock(p->textureMutex_); | ||||||
| 
 | 
 | ||||||
|    if (p->textureAtlas_ == GL_INVALID_INDEX) |    auto& textureAtlas = util::TextureAtlas::Instance(); | ||||||
|  | 
 | ||||||
|  |    if (p->textureBufferCount_ != textureAtlas.BuildCount()) | ||||||
|    { |    { | ||||||
|       p->textureAtlas_ = util::TextureAtlas::Instance().BufferAtlas(p->gl_); |       p->textureBufferCount_ = textureAtlas.BuildCount(); | ||||||
|  |       textureAtlas.BufferAtlas(p->gl_, p->textureAtlas_); | ||||||
|    } |    } | ||||||
| 
 | 
 | ||||||
|    return p->textureAtlas_; |    return p->textureAtlas_; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | std::size_t GlContext::Impl::GetShaderKey( | ||||||
|  |    std::initializer_list<std::pair<GLenum, std::string>> shaders) | ||||||
|  | { | ||||||
|  |    std::size_t seed = 0; | ||||||
|  |    for (auto& shader : shaders) | ||||||
|  |    { | ||||||
|  |       boost::hash_combine(seed, shader.first); | ||||||
|  |       boost::hash_combine(seed, shader.second); | ||||||
|  |    } | ||||||
|  |    return seed; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| } // namespace gl
 | } // namespace gl
 | ||||||
| } // namespace qt
 | } // namespace qt
 | ||||||
| } // namespace scwx
 | } // namespace scwx
 | ||||||
|  |  | ||||||
|  | @ -24,9 +24,13 @@ public: | ||||||
| 
 | 
 | ||||||
|    gl::OpenGLFunctions& gl(); |    gl::OpenGLFunctions& gl(); | ||||||
| 
 | 
 | ||||||
|  |    std::uint64_t texture_buffer_count() const; | ||||||
|  | 
 | ||||||
|    std::shared_ptr<gl::ShaderProgram> |    std::shared_ptr<gl::ShaderProgram> | ||||||
|    GetShaderProgram(const std::string& vertexPath, |    GetShaderProgram(const std::string& vertexPath, | ||||||
|                     const std::string& fragmentPath); |                     const std::string& fragmentPath); | ||||||
|  |    std::shared_ptr<gl::ShaderProgram> GetShaderProgram( | ||||||
|  |       std::initializer_list<std::pair<GLenum, std::string>> shaders); | ||||||
| 
 | 
 | ||||||
|    GLuint GetTextureAtlas(); |    GLuint GetTextureAtlas(); | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -15,6 +15,11 @@ static const auto        logger_    = scwx::util::Logger::Create(logPrefix_); | ||||||
| 
 | 
 | ||||||
| static constexpr GLsizei kInfoLogBufSize = 512; | static constexpr GLsizei kInfoLogBufSize = 512; | ||||||
| 
 | 
 | ||||||
|  | static const std::unordered_map<GLenum, std::string> kShaderNames_ { | ||||||
|  |    {GL_VERTEX_SHADER, "vertex"}, | ||||||
|  |    {GL_GEOMETRY_SHADER, "geometry"}, | ||||||
|  |    {GL_FRAGMENT_SHADER, "fragment"}}; | ||||||
|  | 
 | ||||||
| class ShaderProgram::Impl | class ShaderProgram::Impl | ||||||
| { | { | ||||||
| public: | public: | ||||||
|  | @ -30,6 +35,8 @@ public: | ||||||
|       gl_.glDeleteProgram(id_); |       gl_.glDeleteProgram(id_); | ||||||
|    } |    } | ||||||
| 
 | 
 | ||||||
|  |    static std::string ShaderName(GLenum type); | ||||||
|  | 
 | ||||||
|    OpenGLFunctions& gl_; |    OpenGLFunctions& gl_; | ||||||
| 
 | 
 | ||||||
|    GLuint id_; |    GLuint id_; | ||||||
|  | @ -49,10 +56,37 @@ GLuint ShaderProgram::id() const | ||||||
|    return p->id_; |    return p->id_; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | GLint ShaderProgram::GetUniformLocation(const std::string& name) | ||||||
|  | { | ||||||
|  |    GLint location = p->gl_.glGetUniformLocation(p->id_, name.c_str()); | ||||||
|  |    if (location == -1) | ||||||
|  |    { | ||||||
|  |       logger_->warn("Could not find {}", name); | ||||||
|  |    } | ||||||
|  |    return location; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | std::string ShaderProgram::Impl::ShaderName(GLenum type) | ||||||
|  | { | ||||||
|  |    auto it = kShaderNames_.find(type); | ||||||
|  |    if (it != kShaderNames_.cend()) | ||||||
|  |    { | ||||||
|  |       return it->second; | ||||||
|  |    } | ||||||
|  |    return fmt::format("{:#06x}", type); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| bool ShaderProgram::Load(const std::string& vertexPath, | bool ShaderProgram::Load(const std::string& vertexPath, | ||||||
|                          const std::string& fragmentPath) |                          const std::string& fragmentPath) | ||||||
| { | { | ||||||
|    logger_->debug("Load: {}, {}", vertexPath, fragmentPath); |    return Load({{GL_VERTEX_SHADER, vertexPath}, //
 | ||||||
|  |                 {GL_FRAGMENT_SHADER, fragmentPath}}); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool ShaderProgram::Load( | ||||||
|  |    std::initializer_list<std::pair<GLenum, std::string>> shaders) | ||||||
|  | { | ||||||
|  |    logger_->debug("Load()"); | ||||||
| 
 | 
 | ||||||
|    OpenGLFunctions& gl = p->gl_; |    OpenGLFunctions& gl = p->gl_; | ||||||
| 
 | 
 | ||||||
|  | @ -61,81 +95,59 @@ bool ShaderProgram::Load(const std::string& vertexPath, | ||||||
|    char    infoLog[kInfoLogBufSize]; |    char    infoLog[kInfoLogBufSize]; | ||||||
|    GLsizei logLength; |    GLsizei logLength; | ||||||
| 
 | 
 | ||||||
|    QFile vertexFile(vertexPath.c_str()); |    std::vector<GLuint> shaderIds {}; | ||||||
|    QFile fragmentFile(fragmentPath.c_str()); |  | ||||||
| 
 | 
 | ||||||
|    vertexFile.open(QIODevice::ReadOnly | QIODevice::Text); |    for (auto& shader : shaders) | ||||||
|    fragmentFile.open(QIODevice::ReadOnly | QIODevice::Text); |  | ||||||
| 
 |  | ||||||
|    if (!vertexFile.isOpen()) |  | ||||||
|    { |    { | ||||||
|       logger_->error("Could not load vertex shader: {}", vertexPath); |       logger_->debug("Loading {} shader: {}", | ||||||
|       return false; |                      Impl::ShaderName(shader.first), | ||||||
|    } |                      shader.second); | ||||||
| 
 | 
 | ||||||
|    if (!fragmentFile.isOpen()) |       QFile file(shader.second.c_str()); | ||||||
|    { |       file.open(QIODevice::ReadOnly | QIODevice::Text); | ||||||
|       logger_->error("Could not load fragment shader: {}", fragmentPath); |  | ||||||
|       return false; |  | ||||||
|    } |  | ||||||
| 
 | 
 | ||||||
|    QTextStream vertexShaderStream(&vertexFile); |       if (!file.isOpen()) | ||||||
|    QTextStream fragmentShaderStream(&fragmentFile); |       { | ||||||
|  |          logger_->error("Could not load shader"); | ||||||
|  |          success = false; | ||||||
|  |          break; | ||||||
|  |       } | ||||||
| 
 | 
 | ||||||
|    vertexShaderStream.setEncoding(QStringConverter::Utf8); |       QTextStream shaderStream(&file); | ||||||
|    fragmentShaderStream.setEncoding(QStringConverter::Utf8); |       shaderStream.setEncoding(QStringConverter::Utf8); | ||||||
| 
 | 
 | ||||||
|    std::string vertexShaderSource = vertexShaderStream.readAll().toStdString(); |       std::string shaderSource  = shaderStream.readAll().toStdString(); | ||||||
|    std::string fragmentShaderSource = |       const char* shaderSourceC = shaderSource.c_str(); | ||||||
|       fragmentShaderStream.readAll().toStdString(); |  | ||||||
| 
 | 
 | ||||||
|    const char* vertexShaderSourceC   = vertexShaderSource.c_str(); |       // Create a shader
 | ||||||
|    const char* fragmentShaderSourceC = fragmentShaderSource.c_str(); |       GLuint shaderId = gl.glCreateShader(shader.first); | ||||||
|  |       shaderIds.push_back(shaderId); | ||||||
| 
 | 
 | ||||||
|    // Create a vertex shader
 |       // Attach the shader source code and compile the shader
 | ||||||
|    GLuint vertexShader = gl.glCreateShader(GL_VERTEX_SHADER); |       gl.glShaderSource(shaderId, 1, &shaderSourceC, NULL); | ||||||
|  |       gl.glCompileShader(shaderId); | ||||||
| 
 | 
 | ||||||
|    // Attach the shader source code and compile the shader
 |       // Check for errors
 | ||||||
|    gl.glShaderSource(vertexShader, 1, &vertexShaderSourceC, NULL); |       gl.glGetShaderiv(shaderId, GL_COMPILE_STATUS, &glSuccess); | ||||||
|    gl.glCompileShader(vertexShader); |       gl.glGetShaderInfoLog(shaderId, kInfoLogBufSize, &logLength, infoLog); | ||||||
| 
 |       if (!glSuccess) | ||||||
|    // Check for errors
 |       { | ||||||
|    gl.glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &glSuccess); |          logger_->error("Shader compilation failed: {}", infoLog); | ||||||
|    gl.glGetShaderInfoLog(vertexShader, kInfoLogBufSize, &logLength, infoLog); |          success = false; | ||||||
|    if (!glSuccess) |          break; | ||||||
|    { |       } | ||||||
|       logger_->error("Vertex shader compilation failed: {}", infoLog); |       else if (logLength > 0) | ||||||
|       success = false; |       { | ||||||
|    } |          logger_->error("Shader compiled with warnings: {}", infoLog); | ||||||
|    else if (logLength > 0) |       } | ||||||
|    { |  | ||||||
|       logger_->error("Vertex shader compiled with warnings: {}", infoLog); |  | ||||||
|    } |  | ||||||
| 
 |  | ||||||
|    // Create a fragment shader
 |  | ||||||
|    GLuint fragmentShader = gl.glCreateShader(GL_FRAGMENT_SHADER); |  | ||||||
| 
 |  | ||||||
|    // Attach the shader source and compile the shader
 |  | ||||||
|    gl.glShaderSource(fragmentShader, 1, &fragmentShaderSourceC, NULL); |  | ||||||
|    gl.glCompileShader(fragmentShader); |  | ||||||
| 
 |  | ||||||
|    // Check for errors
 |  | ||||||
|    gl.glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &glSuccess); |  | ||||||
|    gl.glGetShaderInfoLog(fragmentShader, kInfoLogBufSize, &logLength, infoLog); |  | ||||||
|    if (!glSuccess) |  | ||||||
|    { |  | ||||||
|       logger_->error("Fragment shader compilation failed: {}", infoLog); |  | ||||||
|       success = false; |  | ||||||
|    } |  | ||||||
|    else if (logLength > 0) |  | ||||||
|    { |  | ||||||
|       logger_->error("Fragment shader compiled with warnings: {}", infoLog); |  | ||||||
|    } |    } | ||||||
| 
 | 
 | ||||||
|    if (success) |    if (success) | ||||||
|    { |    { | ||||||
|       gl.glAttachShader(p->id_, vertexShader); |       for (auto& shaderId : shaderIds) | ||||||
|       gl.glAttachShader(p->id_, fragmentShader); |       { | ||||||
|  |          gl.glAttachShader(p->id_, shaderId); | ||||||
|  |       } | ||||||
|       gl.glLinkProgram(p->id_); |       gl.glLinkProgram(p->id_); | ||||||
| 
 | 
 | ||||||
|       // Check for errors
 |       // Check for errors
 | ||||||
|  | @ -153,8 +165,10 @@ bool ShaderProgram::Load(const std::string& vertexPath, | ||||||
|    } |    } | ||||||
| 
 | 
 | ||||||
|    // Delete shaders
 |    // Delete shaders
 | ||||||
|    gl.glDeleteShader(vertexShader); |    for (auto& shaderId : shaderIds) | ||||||
|    gl.glDeleteShader(fragmentShader); |    { | ||||||
|  |       gl.glDeleteShader(shaderId); | ||||||
|  |    } | ||||||
| 
 | 
 | ||||||
|    return success; |    return success; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -30,7 +30,10 @@ public: | ||||||
| 
 | 
 | ||||||
|    GLuint id() const; |    GLuint id() const; | ||||||
| 
 | 
 | ||||||
|  |    GLint GetUniformLocation(const std::string& name); | ||||||
|  | 
 | ||||||
|    bool Load(const std::string& vertexPath, const std::string& fragmentPath); |    bool Load(const std::string& vertexPath, const std::string& fragmentPath); | ||||||
|  |    bool Load(std::initializer_list<std::pair<GLenum, std::string>> shaderPaths); | ||||||
| 
 | 
 | ||||||
|    void Use() const; |    void Use() const; | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1,13 +1,18 @@ | ||||||
|  | #define NOMINMAX | ||||||
|  | 
 | ||||||
| #include <scwx/qt/config/radar_site.hpp> | #include <scwx/qt/config/radar_site.hpp> | ||||||
| #include <scwx/qt/main/main_window.hpp> | #include <scwx/qt/main/main_window.hpp> | ||||||
|  | #include <scwx/qt/main/versions.hpp> | ||||||
| #include <scwx/qt/manager/radar_product_manager.hpp> | #include <scwx/qt/manager/radar_product_manager.hpp> | ||||||
| #include <scwx/qt/manager/resource_manager.hpp> | #include <scwx/qt/manager/resource_manager.hpp> | ||||||
| #include <scwx/qt/manager/settings_manager.hpp> | #include <scwx/qt/manager/settings_manager.hpp> | ||||||
|  | #include <scwx/network/cpr.hpp> | ||||||
| #include <scwx/util/logger.hpp> | #include <scwx/util/logger.hpp> | ||||||
| #include <scwx/util/threads.hpp> | #include <scwx/util/threads.hpp> | ||||||
| 
 | 
 | ||||||
| #include <aws/core/Aws.h> | #include <aws/core/Aws.h> | ||||||
| #include <boost/asio.hpp> | #include <boost/asio.hpp> | ||||||
|  | #include <fmt/format.h> | ||||||
| #include <spdlog/spdlog.h> | #include <spdlog/spdlog.h> | ||||||
| #include <QApplication> | #include <QApplication> | ||||||
| #include <QTranslator> | #include <QTranslator> | ||||||
|  | @ -26,6 +31,8 @@ int main(int argc, char* argv[]) | ||||||
|    QApplication a(argc, argv); |    QApplication a(argc, argv); | ||||||
| 
 | 
 | ||||||
|    QCoreApplication::setApplicationName("Supercell Wx"); |    QCoreApplication::setApplicationName("Supercell Wx"); | ||||||
|  |    scwx::network::cpr::SetUserAgent( | ||||||
|  |       fmt::format("SupercellWx/{}", scwx::qt::main::kVersionString_)); | ||||||
| 
 | 
 | ||||||
|    // Enable internationalization support
 |    // Enable internationalization support
 | ||||||
|    QTranslator translator; |    QTranslator translator; | ||||||
|  | @ -62,7 +69,7 @@ int main(int argc, char* argv[]) | ||||||
| 
 | 
 | ||||||
|    // Initialize application
 |    // Initialize application
 | ||||||
|    scwx::qt::config::RadarSite::Initialize(); |    scwx::qt::config::RadarSite::Initialize(); | ||||||
|    scwx::qt::manager::SettingsManager::Initialize(); |    scwx::qt::manager::SettingsManager::Instance().Initialize(); | ||||||
|    scwx::qt::manager::ResourceManager::Initialize(); |    scwx::qt::manager::ResourceManager::Initialize(); | ||||||
| 
 | 
 | ||||||
|    // Run Qt main loop
 |    // Run Qt main loop
 | ||||||
|  | @ -82,7 +89,7 @@ int main(int argc, char* argv[]) | ||||||
| 
 | 
 | ||||||
|    // Shutdown application
 |    // Shutdown application
 | ||||||
|    scwx::qt::manager::ResourceManager::Shutdown(); |    scwx::qt::manager::ResourceManager::Shutdown(); | ||||||
|    scwx::qt::manager::SettingsManager::Shutdown(); |    scwx::qt::manager::SettingsManager::Instance().Shutdown(); | ||||||
| 
 | 
 | ||||||
|    // Shutdown AWS SDK
 |    // Shutdown AWS SDK
 | ||||||
|    Aws::ShutdownAPI(awsSdkOptions); |    Aws::ShutdownAPI(awsSdkOptions); | ||||||
|  |  | ||||||
|  | @ -5,14 +5,16 @@ | ||||||
| 
 | 
 | ||||||
| #include <scwx/qt/main/application.hpp> | #include <scwx/qt/main/application.hpp> | ||||||
| #include <scwx/qt/main/versions.hpp> | #include <scwx/qt/main/versions.hpp> | ||||||
|  | #include <scwx/qt/manager/placefile_manager.hpp> | ||||||
| #include <scwx/qt/manager/radar_product_manager.hpp> | #include <scwx/qt/manager/radar_product_manager.hpp> | ||||||
| #include <scwx/qt/manager/settings_manager.hpp> |  | ||||||
| #include <scwx/qt/manager/text_event_manager.hpp> | #include <scwx/qt/manager/text_event_manager.hpp> | ||||||
| #include <scwx/qt/manager/timeline_manager.hpp> | #include <scwx/qt/manager/timeline_manager.hpp> | ||||||
| #include <scwx/qt/manager/update_manager.hpp> | #include <scwx/qt/manager/update_manager.hpp> | ||||||
| #include <scwx/qt/map/map_provider.hpp> | #include <scwx/qt/map/map_provider.hpp> | ||||||
| #include <scwx/qt/map/map_widget.hpp> | #include <scwx/qt/map/map_widget.hpp> | ||||||
| #include <scwx/qt/model/radar_product_model.hpp> | #include <scwx/qt/model/radar_product_model.hpp> | ||||||
|  | #include <scwx/qt/settings/general_settings.hpp> | ||||||
|  | #include <scwx/qt/settings/map_settings.hpp> | ||||||
| #include <scwx/qt/settings/ui_settings.hpp> | #include <scwx/qt/settings/ui_settings.hpp> | ||||||
| #include <scwx/qt/ui/about_dialog.hpp> | #include <scwx/qt/ui/about_dialog.hpp> | ||||||
| #include <scwx/qt/ui/alert_dock_widget.hpp> | #include <scwx/qt/ui/alert_dock_widget.hpp> | ||||||
|  | @ -20,9 +22,11 @@ | ||||||
| #include <scwx/qt/ui/collapsible_group.hpp> | #include <scwx/qt/ui/collapsible_group.hpp> | ||||||
| #include <scwx/qt/ui/flow_layout.hpp> | #include <scwx/qt/ui/flow_layout.hpp> | ||||||
| #include <scwx/qt/ui/imgui_debug_dialog.hpp> | #include <scwx/qt/ui/imgui_debug_dialog.hpp> | ||||||
|  | #include <scwx/qt/ui/layer_dialog.hpp> | ||||||
| #include <scwx/qt/ui/level2_products_widget.hpp> | #include <scwx/qt/ui/level2_products_widget.hpp> | ||||||
| #include <scwx/qt/ui/level2_settings_widget.hpp> | #include <scwx/qt/ui/level2_settings_widget.hpp> | ||||||
| #include <scwx/qt/ui/level3_products_widget.hpp> | #include <scwx/qt/ui/level3_products_widget.hpp> | ||||||
|  | #include <scwx/qt/ui/placefile_dialog.hpp> | ||||||
| #include <scwx/qt/ui/radar_site_dialog.hpp> | #include <scwx/qt/ui/radar_site_dialog.hpp> | ||||||
| #include <scwx/qt/ui/settings_dialog.hpp> | #include <scwx/qt/ui/settings_dialog.hpp> | ||||||
| #include <scwx/qt/ui/update_dialog.hpp> | #include <scwx/qt/ui/update_dialog.hpp> | ||||||
|  | @ -75,10 +79,13 @@ public: | ||||||
|        animationDockWidget_ {nullptr}, |        animationDockWidget_ {nullptr}, | ||||||
|        aboutDialog_ {nullptr}, |        aboutDialog_ {nullptr}, | ||||||
|        imGuiDebugDialog_ {nullptr}, |        imGuiDebugDialog_ {nullptr}, | ||||||
|  |        layerDialog_ {nullptr}, | ||||||
|  |        placefileDialog_ {nullptr}, | ||||||
|        radarSiteDialog_ {nullptr}, |        radarSiteDialog_ {nullptr}, | ||||||
|        settingsDialog_ {nullptr}, |        settingsDialog_ {nullptr}, | ||||||
|        updateDialog_ {nullptr}, |        updateDialog_ {nullptr}, | ||||||
|        radarProductModel_ {nullptr}, |        radarProductModel_ {nullptr}, | ||||||
|  |        placefileManager_ {manager::PlacefileManager::Instance()}, | ||||||
|        textEventManager_ {manager::TextEventManager::Instance()}, |        textEventManager_ {manager::TextEventManager::Instance()}, | ||||||
|        timelineManager_ {manager::TimelineManager::Instance()}, |        timelineManager_ {manager::TimelineManager::Instance()}, | ||||||
|        updateManager_ {manager::UpdateManager::Instance()}, |        updateManager_ {manager::UpdateManager::Instance()}, | ||||||
|  | @ -87,10 +94,8 @@ public: | ||||||
|        elevationButtonsChanged_ {false}, |        elevationButtonsChanged_ {false}, | ||||||
|        resizeElevationButtons_ {false} |        resizeElevationButtons_ {false} | ||||||
|    { |    { | ||||||
|       mapProvider_ = |       mapProvider_ = map::GetMapProvider( | ||||||
|          map::GetMapProvider(manager::SettingsManager::general_settings() |          settings::GeneralSettings::Instance().map_provider().GetValue()); | ||||||
|                                 .map_provider() |  | ||||||
|                                 .GetValue()); |  | ||||||
|       const map::MapProviderInfo& mapProviderInfo = |       const map::MapProviderInfo& mapProviderInfo = | ||||||
|          map::GetMapProviderInfo(mapProvider_); |          map::GetMapProviderInfo(mapProvider_); | ||||||
| 
 | 
 | ||||||
|  | @ -164,11 +169,14 @@ public: | ||||||
|    ui::AnimationDockWidget* animationDockWidget_; |    ui::AnimationDockWidget* animationDockWidget_; | ||||||
|    ui::AboutDialog*         aboutDialog_; |    ui::AboutDialog*         aboutDialog_; | ||||||
|    ui::ImGuiDebugDialog*    imGuiDebugDialog_; |    ui::ImGuiDebugDialog*    imGuiDebugDialog_; | ||||||
|  |    ui::LayerDialog*         layerDialog_; | ||||||
|  |    ui::PlacefileDialog*     placefileDialog_; | ||||||
|    ui::RadarSiteDialog*     radarSiteDialog_; |    ui::RadarSiteDialog*     radarSiteDialog_; | ||||||
|    ui::SettingsDialog*      settingsDialog_; |    ui::SettingsDialog*      settingsDialog_; | ||||||
|    ui::UpdateDialog*        updateDialog_; |    ui::UpdateDialog*        updateDialog_; | ||||||
| 
 | 
 | ||||||
|    std::unique_ptr<model::RadarProductModel>  radarProductModel_; |    std::unique_ptr<model::RadarProductModel>  radarProductModel_; | ||||||
|  |    std::shared_ptr<manager::PlacefileManager> placefileManager_; | ||||||
|    std::shared_ptr<manager::TextEventManager> textEventManager_; |    std::shared_ptr<manager::TextEventManager> textEventManager_; | ||||||
|    std::shared_ptr<manager::TimelineManager>  timelineManager_; |    std::shared_ptr<manager::TimelineManager>  timelineManager_; | ||||||
|    std::shared_ptr<manager::UpdateManager>    updateManager_; |    std::shared_ptr<manager::UpdateManager>    updateManager_; | ||||||
|  | @ -227,7 +235,7 @@ MainWindow::MainWindow(QWidget* parent) : | ||||||
|    ui->actionAlerts->setVisible(false); |    ui->actionAlerts->setVisible(false); | ||||||
| 
 | 
 | ||||||
|    ui->menuDebug->menuAction()->setVisible( |    ui->menuDebug->menuAction()->setVisible( | ||||||
|       manager::SettingsManager::general_settings().debug_enabled().GetValue()); |       settings::GeneralSettings::Instance().debug_enabled().GetValue()); | ||||||
| 
 | 
 | ||||||
|    // Configure Resource Explorer Dock
 |    // Configure Resource Explorer Dock
 | ||||||
|    ui->resourceExplorerDock->setVisible(false); |    ui->resourceExplorerDock->setVisible(false); | ||||||
|  | @ -241,6 +249,12 @@ MainWindow::MainWindow(QWidget* parent) : | ||||||
|    // Radar Site Dialog
 |    // Radar Site Dialog
 | ||||||
|    p->radarSiteDialog_ = new ui::RadarSiteDialog(this); |    p->radarSiteDialog_ = new ui::RadarSiteDialog(this); | ||||||
| 
 | 
 | ||||||
|  |    // Placefile Manager Dialog
 | ||||||
|  |    p->placefileDialog_ = new ui::PlacefileDialog(this); | ||||||
|  | 
 | ||||||
|  |    // Layer Dialog
 | ||||||
|  |    p->layerDialog_ = new ui::LayerDialog(this); | ||||||
|  | 
 | ||||||
|    // Settings Dialog
 |    // Settings Dialog
 | ||||||
|    p->settingsDialog_ = new ui::SettingsDialog(this); |    p->settingsDialog_ = new ui::SettingsDialog(this); | ||||||
| 
 | 
 | ||||||
|  | @ -303,7 +317,7 @@ MainWindow::MainWindow(QWidget* parent) : | ||||||
|    // Update Dialog
 |    // Update Dialog
 | ||||||
|    p->updateDialog_ = new ui::UpdateDialog(this); |    p->updateDialog_ = new ui::UpdateDialog(this); | ||||||
| 
 | 
 | ||||||
|    auto& mapSettings = manager::SettingsManager::map_settings(); |    auto& mapSettings = settings::MapSettings::Instance(); | ||||||
|    for (size_t i = 0; i < p->maps_.size(); i++) |    for (size_t i = 0; i < p->maps_.size(); i++) | ||||||
|    { |    { | ||||||
|       p->SelectRadarProduct(p->maps_.at(i), |       p->SelectRadarProduct(p->maps_.at(i), | ||||||
|  | @ -441,11 +455,26 @@ void MainWindow::on_actionExit_triggered() | ||||||
|    close(); |    close(); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | void MainWindow::on_actionPlacefileManager_triggered() | ||||||
|  | { | ||||||
|  |    p->placefileDialog_->show(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void MainWindow::on_actionLayerManager_triggered() | ||||||
|  | { | ||||||
|  |    p->layerDialog_->show(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| void MainWindow::on_actionImGuiDebug_triggered() | void MainWindow::on_actionImGuiDebug_triggered() | ||||||
| { | { | ||||||
|    p->imGuiDebugDialog_->show(); |    p->imGuiDebugDialog_->show(); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | void MainWindow::on_actionDumpLayerList_triggered() | ||||||
|  | { | ||||||
|  |    p->activeMap_->DumpLayerList(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| void MainWindow::on_actionDumpRadarProductRecords_triggered() | void MainWindow::on_actionDumpRadarProductRecords_triggered() | ||||||
| { | { | ||||||
|    manager::RadarProductManager::DumpRecords(); |    manager::RadarProductManager::DumpRecords(); | ||||||
|  | @ -579,7 +608,7 @@ void MainWindow::on_resourceTreeView_doubleClicked(const QModelIndex& index) | ||||||
| 
 | 
 | ||||||
| void MainWindowImpl::AsyncSetup() | void MainWindowImpl::AsyncSetup() | ||||||
| { | { | ||||||
|    auto& generalSettings = manager::SettingsManager::general_settings(); |    auto& generalSettings = settings::GeneralSettings::Instance(); | ||||||
| 
 | 
 | ||||||
|    // Check for updates
 |    // Check for updates
 | ||||||
|    if (generalSettings.update_notifications_enabled().GetValue()) |    if (generalSettings.update_notifications_enabled().GetValue()) | ||||||
|  | @ -592,7 +621,7 @@ void MainWindowImpl::AsyncSetup() | ||||||
| 
 | 
 | ||||||
| void MainWindowImpl::ConfigureMapLayout() | void MainWindowImpl::ConfigureMapLayout() | ||||||
| { | { | ||||||
|    auto& generalSettings = manager::SettingsManager::general_settings(); |    auto& generalSettings = settings::GeneralSettings::Instance(); | ||||||
| 
 | 
 | ||||||
|    const int64_t gridWidth  = generalSettings.grid_width().GetValue(); |    const int64_t gridWidth  = generalSettings.grid_width().GetValue(); | ||||||
|    const int64_t gridHeight = generalSettings.grid_height().GetValue(); |    const int64_t gridHeight = generalSettings.grid_height().GetValue(); | ||||||
|  | @ -626,7 +655,7 @@ void MainWindowImpl::ConfigureMapLayout() | ||||||
|       { |       { | ||||||
|          if (maps_.at(mapIndex) == nullptr) |          if (maps_.at(mapIndex) == nullptr) | ||||||
|          { |          { | ||||||
|             maps_[mapIndex] = new map::MapWidget(settings_); |             maps_[mapIndex] = new map::MapWidget(mapIndex, settings_); | ||||||
|          } |          } | ||||||
| 
 | 
 | ||||||
|          hs->addWidget(maps_[mapIndex]); |          hs->addWidget(maps_[mapIndex]); | ||||||
|  | @ -643,7 +672,7 @@ void MainWindowImpl::ConfigureMapLayout() | ||||||
| void MainWindowImpl::ConfigureMapStyles() | void MainWindowImpl::ConfigureMapStyles() | ||||||
| { | { | ||||||
|    const auto& mapProviderInfo = map::GetMapProviderInfo(mapProvider_); |    const auto& mapProviderInfo = map::GetMapProviderInfo(mapProvider_); | ||||||
|    auto&       mapSettings     = manager::SettingsManager::map_settings(); |    auto&       mapSettings     = settings::MapSettings::Instance(); | ||||||
| 
 | 
 | ||||||
|    for (std::size_t i = 0; i < maps_.size(); i++) |    for (std::size_t i = 0; i < maps_.size(); i++) | ||||||
|    { |    { | ||||||
|  | @ -816,6 +845,15 @@ void MainWindowImpl::ConnectAnimationSignals() | ||||||
|            timelineManager_.get(), |            timelineManager_.get(), | ||||||
|            &manager::TimelineManager::AnimationStepEnd); |            &manager::TimelineManager::AnimationStepEnd); | ||||||
| 
 | 
 | ||||||
|  |    connect(timelineManager_.get(), | ||||||
|  |            &manager::TimelineManager::SelectedTimeUpdated, | ||||||
|  |            [this]() | ||||||
|  |            { | ||||||
|  |               for (auto map : maps_) | ||||||
|  |               { | ||||||
|  |                  map->update(); | ||||||
|  |               } | ||||||
|  |            }); | ||||||
|    connect(timelineManager_.get(), |    connect(timelineManager_.get(), | ||||||
|            &manager::TimelineManager::VolumeTimeUpdated, |            &manager::TimelineManager::VolumeTimeUpdated, | ||||||
|            [this](std::chrono::system_clock::time_point dateTime) |            [this](std::chrono::system_clock::time_point dateTime) | ||||||
|  | @ -885,8 +923,7 @@ void MainWindowImpl::ConnectOtherSignals() | ||||||
|               { |               { | ||||||
|                  if (maps_[i] == activeMap_) |                  if (maps_[i] == activeMap_) | ||||||
|                  { |                  { | ||||||
|                     auto& mapSettings = |                     auto& mapSettings = settings::MapSettings::Instance(); | ||||||
|                        manager::SettingsManager::map_settings(); |  | ||||||
|                     mapSettings.map_style(i).StageValue(text.toStdString()); |                     mapSettings.map_style(i).StageValue(text.toStdString()); | ||||||
|                     break; |                     break; | ||||||
|                  } |                  } | ||||||
|  | @ -1063,7 +1100,7 @@ void MainWindowImpl::UpdateMapStyle(const std::string& styleName) | ||||||
|       { |       { | ||||||
|          if (maps_[i] == activeMap_) |          if (maps_[i] == activeMap_) | ||||||
|          { |          { | ||||||
|             auto& mapSettings = manager::SettingsManager::map_settings(); |             auto& mapSettings = settings::MapSettings::Instance(); | ||||||
|             mapSettings.map_style(i).StageValue(styleName); |             mapSettings.map_style(i).StageValue(styleName); | ||||||
|             break; |             break; | ||||||
|          } |          } | ||||||
|  | @ -1113,6 +1150,8 @@ void MainWindowImpl::UpdateRadarSite() | ||||||
| 
 | 
 | ||||||
|       timelineManager_->SetRadarSite("?"); |       timelineManager_->SetRadarSite("?"); | ||||||
|    } |    } | ||||||
|  | 
 | ||||||
|  |    placefileManager_->SetRadarSite(radarSite); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void MainWindowImpl::UpdateVcp() | void MainWindowImpl::UpdateVcp() | ||||||
|  |  | ||||||
|  | @ -36,7 +36,10 @@ private slots: | ||||||
|    void on_actionOpenTextEvent_triggered(); |    void on_actionOpenTextEvent_triggered(); | ||||||
|    void on_actionSettings_triggered(); |    void on_actionSettings_triggered(); | ||||||
|    void on_actionExit_triggered(); |    void on_actionExit_triggered(); | ||||||
|  |    void on_actionPlacefileManager_triggered(); | ||||||
|  |    void on_actionLayerManager_triggered(); | ||||||
|    void on_actionImGuiDebug_triggered(); |    void on_actionImGuiDebug_triggered(); | ||||||
|  |    void on_actionDumpLayerList_triggered(); | ||||||
|    void on_actionDumpRadarProductRecords_triggered(); |    void on_actionDumpRadarProductRecords_triggered(); | ||||||
|    void on_actionUserManual_triggered(); |    void on_actionUserManual_triggered(); | ||||||
|    void on_actionDiscord_triggered(); |    void on_actionDiscord_triggered(); | ||||||
|  |  | ||||||
|  | @ -85,10 +85,19 @@ | ||||||
|     </property> |     </property> | ||||||
|     <addaction name="actionImGuiDebug"/> |     <addaction name="actionImGuiDebug"/> | ||||||
|     <addaction name="separator"/> |     <addaction name="separator"/> | ||||||
|  |     <addaction name="actionDumpLayerList"/> | ||||||
|     <addaction name="actionDumpRadarProductRecords"/> |     <addaction name="actionDumpRadarProductRecords"/> | ||||||
|    </widget> |    </widget> | ||||||
|  |    <widget class="QMenu" name="menuTools"> | ||||||
|  |     <property name="title"> | ||||||
|  |      <string>&Tools</string> | ||||||
|  |     </property> | ||||||
|  |     <addaction name="actionPlacefileManager"/> | ||||||
|  |     <addaction name="actionLayerManager"/> | ||||||
|  |    </widget> | ||||||
|    <addaction name="menuFile"/> |    <addaction name="menuFile"/> | ||||||
|    <addaction name="menuView"/> |    <addaction name="menuView"/> | ||||||
|  |    <addaction name="menuTools"/> | ||||||
|    <addaction name="menuDebug"/> |    <addaction name="menuDebug"/> | ||||||
|    <addaction name="menuHelp"/> |    <addaction name="menuHelp"/> | ||||||
|   </widget> |   </widget> | ||||||
|  | @ -415,6 +424,29 @@ | ||||||
|     <string>&Check for Updates</string> |     <string>&Check for Updates</string> | ||||||
|    </property> |    </property> | ||||||
|   </action> |   </action> | ||||||
|  |   <action name="actionPlacefileManager"> | ||||||
|  |    <property name="icon"> | ||||||
|  |     <iconset resource="../../../../scwx-qt.qrc"> | ||||||
|  |      <normaloff>:/res/icons/font-awesome-6/earth-americas-solid.svg</normaloff>:/res/icons/font-awesome-6/earth-americas-solid.svg</iconset> | ||||||
|  |    </property> | ||||||
|  |    <property name="text"> | ||||||
|  |     <string>&Placefile Manager</string> | ||||||
|  |    </property> | ||||||
|  |   </action> | ||||||
|  |   <action name="actionLayerManager"> | ||||||
|  |    <property name="icon"> | ||||||
|  |     <iconset resource="../../../../scwx-qt.qrc"> | ||||||
|  |      <normaloff>:/res/icons/font-awesome-6/layer-group-solid.svg</normaloff>:/res/icons/font-awesome-6/layer-group-solid.svg</iconset> | ||||||
|  |    </property> | ||||||
|  |    <property name="text"> | ||||||
|  |     <string>&Layer Manager</string> | ||||||
|  |    </property> | ||||||
|  |   </action> | ||||||
|  |   <action name="actionDumpLayerList"> | ||||||
|  |    <property name="text"> | ||||||
|  |     <string>Dump &Layer List</string> | ||||||
|  |    </property> | ||||||
|  |   </action> | ||||||
|  </widget> |  </widget> | ||||||
|  <resources> |  <resources> | ||||||
|   <include location="../../../../scwx-qt.qrc"/> |   <include location="../../../../scwx-qt.qrc"/> | ||||||
|  |  | ||||||
							
								
								
									
										550
									
								
								scwx-qt/source/scwx/qt/manager/font_manager.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						|  | @ -0,0 +1,550 @@ | ||||||
|  | #include <scwx/qt/manager/font_manager.hpp> | ||||||
|  | #include <scwx/qt/manager/settings_manager.hpp> | ||||||
|  | #include <scwx/qt/settings/text_settings.hpp> | ||||||
|  | #include <scwx/util/environment.hpp> | ||||||
|  | #include <scwx/util/logger.hpp> | ||||||
|  | 
 | ||||||
|  | #include <filesystem> | ||||||
|  | #include <fstream> | ||||||
|  | 
 | ||||||
|  | #include <QFile> | ||||||
|  | #include <QFileInfo> | ||||||
|  | #include <QFontDatabase> | ||||||
|  | #include <QGuiApplication> | ||||||
|  | #include <QStandardPaths> | ||||||
|  | #include <boost/container_hash/hash.hpp> | ||||||
|  | #include <boost/unordered/unordered_flat_map.hpp> | ||||||
|  | #include <boost/unordered/unordered_flat_set.hpp> | ||||||
|  | #include <fontconfig/fontconfig.h> | ||||||
|  | 
 | ||||||
|  | namespace scwx | ||||||
|  | { | ||||||
|  | namespace qt | ||||||
|  | { | ||||||
|  | namespace manager | ||||||
|  | { | ||||||
|  | 
 | ||||||
|  | static const std::string logPrefix_ = "scwx::qt::manager::font_manager"; | ||||||
|  | static const auto        logger_    = scwx::util::Logger::Create(logPrefix_); | ||||||
|  | 
 | ||||||
|  | static const std::string kFcTrueType_ {"TrueType"}; | ||||||
|  | 
 | ||||||
|  | struct FontRecord | ||||||
|  | { | ||||||
|  |    std::string family_ {}; | ||||||
|  |    std::string style_ {}; | ||||||
|  |    std::string filename_ {}; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | typedef std::pair<FontRecord, units::font_size::pixels<int>> FontRecordPair; | ||||||
|  | 
 | ||||||
|  | template<class Key> | ||||||
|  | struct FontRecordHash; | ||||||
|  | 
 | ||||||
|  | template<> | ||||||
|  | struct FontRecordHash<FontRecordPair> | ||||||
|  | { | ||||||
|  |    size_t operator()(const FontRecordPair& x) const; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | class FontManager::Impl | ||||||
|  | { | ||||||
|  | public: | ||||||
|  |    explicit Impl(FontManager* self) : self_ {self} | ||||||
|  |    { | ||||||
|  |       InitializeEnvironment(); | ||||||
|  |       InitializeFontCache(); | ||||||
|  |       InitializeFontconfig(); | ||||||
|  |       ConnectSignals(); | ||||||
|  |    } | ||||||
|  |    ~Impl() { FinalizeFontconfig(); } | ||||||
|  | 
 | ||||||
|  |    void ConnectSignals(); | ||||||
|  |    void FinalizeFontconfig(); | ||||||
|  |    void InitializeEnvironment(); | ||||||
|  |    void InitializeFontCache(); | ||||||
|  |    void InitializeFontconfig(); | ||||||
|  |    void UpdateImGuiFont(types::FontCategory fontCategory); | ||||||
|  |    void UpdateQFont(types::FontCategory fontCategory); | ||||||
|  | 
 | ||||||
|  |    const std::vector<char>& GetRawFontData(const std::string& filename); | ||||||
|  | 
 | ||||||
|  |    static FontRecord MatchFontFile(const std::string&              family, | ||||||
|  |                                    const std::vector<std::string>& styles); | ||||||
|  | 
 | ||||||
|  |    FontManager* self_; | ||||||
|  | 
 | ||||||
|  |    std::string fontCachePath_ {}; | ||||||
|  | 
 | ||||||
|  |    std::shared_mutex imguiFontAtlasMutex_ {}; | ||||||
|  | 
 | ||||||
|  |    std::uint64_t imguiFontsBuildCount_ {}; | ||||||
|  | 
 | ||||||
|  |    boost::unordered_flat_map<FontRecordPair, | ||||||
|  |                              std::shared_ptr<types::ImGuiFont>, | ||||||
|  |                              FontRecordHash<FontRecordPair>> | ||||||
|  |                      imguiFonts_ {}; | ||||||
|  |    std::shared_mutex imguiFontsMutex_ {}; | ||||||
|  | 
 | ||||||
|  |    boost::unordered_flat_map<std::string, std::vector<char>> rawFontData_ {}; | ||||||
|  |    std::mutex rawFontDataMutex_ {}; | ||||||
|  | 
 | ||||||
|  |    std::shared_ptr<types::ImGuiFont> defaultFont_ {}; | ||||||
|  |    boost::unordered_flat_map<types::FontCategory, | ||||||
|  |                              std::shared_ptr<types::ImGuiFont>> | ||||||
|  |       fontCategoryImguiFontMap_ {}; | ||||||
|  |    boost::unordered_flat_map<types::FontCategory, QFont> | ||||||
|  |               fontCategoryQFontMap_ {}; | ||||||
|  |    std::mutex fontCategoryMutex_ {}; | ||||||
|  | 
 | ||||||
|  |    boost::unordered_flat_set<types::FontCategory> dirtyFonts_ {}; | ||||||
|  |    std::mutex                                     dirtyFontsMutex_ {}; | ||||||
|  | 
 | ||||||
|  |    boost::unordered_flat_map<types::Font, int> fontIds_ {}; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | FontManager::FontManager() : p(std::make_unique<Impl>(this)) {} | ||||||
|  | 
 | ||||||
|  | FontManager::~FontManager() {}; | ||||||
|  | 
 | ||||||
|  | void FontManager::Impl::ConnectSignals() | ||||||
|  | { | ||||||
|  |    auto& textSettings = settings::TextSettings::Instance(); | ||||||
|  | 
 | ||||||
|  |    for (auto fontCategory : types::FontCategoryIterator()) | ||||||
|  |    { | ||||||
|  |       textSettings.font_family(fontCategory) | ||||||
|  |          .RegisterValueChangedCallback( | ||||||
|  |             [this, fontCategory](const auto&) | ||||||
|  |             { | ||||||
|  |                std::unique_lock lock {dirtyFontsMutex_}; | ||||||
|  |                dirtyFonts_.insert(fontCategory); | ||||||
|  |             }); | ||||||
|  |       textSettings.font_style(fontCategory) | ||||||
|  |          .RegisterValueChangedCallback( | ||||||
|  |             [this, fontCategory](const auto&) | ||||||
|  |             { | ||||||
|  |                std::unique_lock lock {dirtyFontsMutex_}; | ||||||
|  |                dirtyFonts_.insert(fontCategory); | ||||||
|  |             }); | ||||||
|  |       textSettings.font_point_size(fontCategory) | ||||||
|  |          .RegisterValueChangedCallback( | ||||||
|  |             [this, fontCategory](const auto&) | ||||||
|  |             { | ||||||
|  |                std::unique_lock lock {dirtyFontsMutex_}; | ||||||
|  |                dirtyFonts_.insert(fontCategory); | ||||||
|  |             }); | ||||||
|  |    } | ||||||
|  | 
 | ||||||
|  |    QObject::connect( | ||||||
|  |       &SettingsManager::Instance(), | ||||||
|  |       &SettingsManager::SettingsSaved, | ||||||
|  |       self_, | ||||||
|  |       [this]() | ||||||
|  |       { | ||||||
|  |          std::scoped_lock lock {dirtyFontsMutex_, fontCategoryMutex_}; | ||||||
|  | 
 | ||||||
|  |          for (auto fontCategory : dirtyFonts_) | ||||||
|  |          { | ||||||
|  |             UpdateImGuiFont(fontCategory); | ||||||
|  |             UpdateQFont(fontCategory); | ||||||
|  |          } | ||||||
|  | 
 | ||||||
|  |          dirtyFonts_.clear(); | ||||||
|  |       }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void FontManager::InitializeFonts() | ||||||
|  | { | ||||||
|  |    for (auto fontCategory : types::FontCategoryIterator()) | ||||||
|  |    { | ||||||
|  |       p->UpdateImGuiFont(fontCategory); | ||||||
|  |       p->UpdateQFont(fontCategory); | ||||||
|  |    } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void FontManager::Impl::UpdateImGuiFont(types::FontCategory fontCategory) | ||||||
|  | { | ||||||
|  |    auto& textSettings = settings::TextSettings::Instance(); | ||||||
|  | 
 | ||||||
|  |    auto family = textSettings.font_family(fontCategory).GetValue(); | ||||||
|  |    auto styles = textSettings.font_style(fontCategory).GetValue(); | ||||||
|  |    units::font_size::points<double> size { | ||||||
|  |       textSettings.font_point_size(fontCategory).GetValue()}; | ||||||
|  | 
 | ||||||
|  |    fontCategoryImguiFontMap_.insert_or_assign( | ||||||
|  |       fontCategory, self_->LoadImGuiFont(family, {styles}, size)); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void FontManager::Impl::UpdateQFont(types::FontCategory fontCategory) | ||||||
|  | { | ||||||
|  |    auto& textSettings = settings::TextSettings::Instance(); | ||||||
|  | 
 | ||||||
|  |    auto family = textSettings.font_family(fontCategory).GetValue(); | ||||||
|  |    auto styles = textSettings.font_style(fontCategory).GetValue(); | ||||||
|  |    units::font_size::points<double> size { | ||||||
|  |       textSettings.font_point_size(fontCategory).GetValue()}; | ||||||
|  | 
 | ||||||
|  |    QFont font = QFontDatabase::font(QString::fromStdString(family), | ||||||
|  |                                     QString::fromStdString(styles), | ||||||
|  |                                     static_cast<int>(size.value())); | ||||||
|  |    font.setPointSizeF(size.value()); | ||||||
|  | 
 | ||||||
|  |    fontCategoryQFontMap_.insert_or_assign(fontCategory, font); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | std::shared_mutex& FontManager::imgui_font_atlas_mutex() | ||||||
|  | { | ||||||
|  |    return p->imguiFontAtlasMutex_; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | std::uint64_t FontManager::imgui_fonts_build_count() const | ||||||
|  | { | ||||||
|  |    return p->imguiFontsBuildCount_; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | int FontManager::GetFontId(types::Font font) const | ||||||
|  | { | ||||||
|  |    auto it = p->fontIds_.find(font); | ||||||
|  |    if (it != p->fontIds_.cend()) | ||||||
|  |    { | ||||||
|  |       return it->second; | ||||||
|  |    } | ||||||
|  |    return -1; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | std::shared_ptr<types::ImGuiFont> | ||||||
|  | FontManager::GetImGuiFont(types::FontCategory fontCategory) | ||||||
|  | { | ||||||
|  |    std::unique_lock lock {p->fontCategoryMutex_}; | ||||||
|  | 
 | ||||||
|  |    auto it = p->fontCategoryImguiFontMap_.find(fontCategory); | ||||||
|  |    if (it != p->fontCategoryImguiFontMap_.cend()) | ||||||
|  |    { | ||||||
|  |       return it->second; | ||||||
|  |    } | ||||||
|  | 
 | ||||||
|  |    return p->defaultFont_; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | QFont FontManager::GetQFont(types::FontCategory fontCategory) | ||||||
|  | { | ||||||
|  |    std::unique_lock lock {p->fontCategoryMutex_}; | ||||||
|  | 
 | ||||||
|  |    auto it = p->fontCategoryQFontMap_.find(fontCategory); | ||||||
|  |    if (it != p->fontCategoryQFontMap_.cend()) | ||||||
|  |    { | ||||||
|  |       return it->second; | ||||||
|  |    } | ||||||
|  | 
 | ||||||
|  |    return QGuiApplication::font(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | std::shared_ptr<types::ImGuiFont> | ||||||
|  | FontManager::LoadImGuiFont(const std::string&               family, | ||||||
|  |                            const std::vector<std::string>&  styles, | ||||||
|  |                            units::font_size::points<double> size, | ||||||
|  |                            bool                             loadIfNotFound) | ||||||
|  | { | ||||||
|  |    const std::string styleString = fmt::format("{}", fmt::join(styles, " ")); | ||||||
|  |    const std::string fontString = | ||||||
|  |       fmt::format("{}-{}:{}", family, size.value(), styleString); | ||||||
|  | 
 | ||||||
|  |    logger_->debug("LoadFontResource: {}", fontString); | ||||||
|  | 
 | ||||||
|  |    FontRecord fontRecord = Impl::MatchFontFile(family, styles); | ||||||
|  | 
 | ||||||
|  |    // Only allow whole pixels, and clamp to 6-72 pt
 | ||||||
|  |    units::font_size::pixels<double> pixels {size}; | ||||||
|  |    units::font_size::pixels<int>    imFontSize { | ||||||
|  |       std::clamp(static_cast<int>(pixels.value()), 8, 96)}; | ||||||
|  |    auto imguiFontKey = std::make_pair(fontRecord, imFontSize); | ||||||
|  | 
 | ||||||
|  |    // Search for a loaded ImGui font
 | ||||||
|  |    { | ||||||
|  |       std::shared_lock imguiFontLock {p->imguiFontsMutex_}; | ||||||
|  | 
 | ||||||
|  |       // Search for the associated ImGui font
 | ||||||
|  |       auto it = p->imguiFonts_.find(imguiFontKey); | ||||||
|  |       if (it != p->imguiFonts_.end()) | ||||||
|  |       { | ||||||
|  |          return it->second; | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       // No ImGui font was found, we need to create one
 | ||||||
|  |    } | ||||||
|  | 
 | ||||||
|  |    // No font was found, return an empty shared pointer if not loading
 | ||||||
|  |    if (!loadIfNotFound) | ||||||
|  |    { | ||||||
|  |       return nullptr; | ||||||
|  |    } | ||||||
|  | 
 | ||||||
|  |    // Get raw font data
 | ||||||
|  |    const auto& rawFontData = p->GetRawFontData(fontRecord.filename_); | ||||||
|  | 
 | ||||||
|  |    // The font atlas mutex might already be locked within an ImGui render frame.
 | ||||||
|  |    // Lock the font atlas mutex before the fonts mutex to prevent deadlock.
 | ||||||
|  |    std::unique_lock imguiFontAtlasLock {p->imguiFontAtlasMutex_}; | ||||||
|  |    std::unique_lock imguiFontsLock {p->imguiFontsMutex_}; | ||||||
|  | 
 | ||||||
|  |    // Search for the associated ImGui font again, to prevent loading the same
 | ||||||
|  |    // font twice
 | ||||||
|  |    auto it = p->imguiFonts_.find(imguiFontKey); | ||||||
|  |    if (it != p->imguiFonts_.end()) | ||||||
|  |    { | ||||||
|  |       return it->second; | ||||||
|  |    } | ||||||
|  | 
 | ||||||
|  |    // Define a name for the ImGui font
 | ||||||
|  |    std::string fontName; | ||||||
|  |    try | ||||||
|  |    { | ||||||
|  |       fontName = fmt::format( | ||||||
|  |          "{}:{}", | ||||||
|  |          std::filesystem::path(fontRecord.filename_).filename().string(), | ||||||
|  |          imFontSize.value()); | ||||||
|  |    } | ||||||
|  |    catch (const std::exception& ex) | ||||||
|  |    { | ||||||
|  |       logger_->warn(ex.what()); | ||||||
|  |       fontName = fmt::format("{}:{}", fontRecord.filename_, imFontSize.value()); | ||||||
|  |    } | ||||||
|  | 
 | ||||||
|  |    // Create an ImGui font
 | ||||||
|  |    std::shared_ptr<types::ImGuiFont> imguiFont = | ||||||
|  |       std::make_shared<types::ImGuiFont>(fontName, rawFontData, imFontSize); | ||||||
|  | 
 | ||||||
|  |    // Store the ImGui font
 | ||||||
|  |    p->imguiFonts_.insert_or_assign(imguiFontKey, imguiFont); | ||||||
|  | 
 | ||||||
|  |    // Increment ImGui font build count
 | ||||||
|  |    ++p->imguiFontsBuildCount_; | ||||||
|  | 
 | ||||||
|  |    // Return the ImGui font
 | ||||||
|  |    return imguiFont; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | const std::vector<char>& | ||||||
|  | FontManager::Impl::GetRawFontData(const std::string& filename) | ||||||
|  | { | ||||||
|  |    std::unique_lock rawFontDataLock {rawFontDataMutex_}; | ||||||
|  | 
 | ||||||
|  |    auto it = rawFontData_.find(filename); | ||||||
|  |    if (it != rawFontData_.end()) | ||||||
|  |    { | ||||||
|  |       // Raw font data has already been loaded
 | ||||||
|  |       return it->second; | ||||||
|  |    } | ||||||
|  | 
 | ||||||
|  |    // Raw font data needs to be loaded
 | ||||||
|  |    std::basic_ifstream<char> ifs {filename, std::ios::binary}; | ||||||
|  |    ifs.seekg(0, std::ios_base::end); | ||||||
|  |    std::size_t dataSize = ifs.tellg(); | ||||||
|  |    ifs.seekg(0, std::ios_base::beg); | ||||||
|  | 
 | ||||||
|  |    // Store the font data in a buffer
 | ||||||
|  |    std::vector<char> buffer {}; | ||||||
|  |    buffer.reserve(dataSize); | ||||||
|  |    std::copy(std::istreambuf_iterator<char>(ifs), | ||||||
|  |              std::istreambuf_iterator<char>(), | ||||||
|  |              std::back_inserter(buffer)); | ||||||
|  | 
 | ||||||
|  |    // Place the buffer in the cache
 | ||||||
|  |    auto result = rawFontData_.emplace(filename, std::move(buffer)); | ||||||
|  | 
 | ||||||
|  |    // Return the cached buffer
 | ||||||
|  |    return result.first->second; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void FontManager::LoadApplicationFont(types::Font        font, | ||||||
|  |                                       const std::string& filename) | ||||||
|  | { | ||||||
|  |    // If the font cache failed to create, don't attempt to cache any fonts
 | ||||||
|  |    if (p->fontCachePath_.empty()) | ||||||
|  |    { | ||||||
|  |       return; | ||||||
|  |    } | ||||||
|  | 
 | ||||||
|  |    // Make a copy of the font in the cache (if it doesn't exist)
 | ||||||
|  |    QFile     fontFile(QString::fromStdString(filename)); | ||||||
|  |    QFileInfo fontFileInfo(fontFile); | ||||||
|  | 
 | ||||||
|  |    QFile       cacheFile(QString::fromStdString(p->fontCachePath_) + | ||||||
|  |                    fontFileInfo.fileName()); | ||||||
|  |    QFileInfo   cacheFileInfo(cacheFile); | ||||||
|  |    std::string cacheFilename = cacheFile.fileName().toStdString(); | ||||||
|  | 
 | ||||||
|  |    if (fontFile.exists()) | ||||||
|  |    { | ||||||
|  |       // If the file has not been cached, or the font file size has changed
 | ||||||
|  |       if (!cacheFile.exists() || fontFileInfo.size() != cacheFileInfo.size()) | ||||||
|  |       { | ||||||
|  |          logger_->info("Caching font: {}", filename); | ||||||
|  |          if (!fontFile.copy(cacheFile.fileName())) | ||||||
|  |          { | ||||||
|  |             logger_->error("Could not cache font: {}", filename); | ||||||
|  |             return; | ||||||
|  |          } | ||||||
|  |       } | ||||||
|  |    } | ||||||
|  |    else | ||||||
|  |    { | ||||||
|  |       logger_->error("Font does not exist: {}", filename); | ||||||
|  |       return; | ||||||
|  |    } | ||||||
|  | 
 | ||||||
|  |    // Load the file into the Qt Font Database
 | ||||||
|  |    int fontId = | ||||||
|  |       QFontDatabase::addApplicationFont(QString::fromStdString(cacheFilename)); | ||||||
|  |    p->fontIds_.emplace(font, fontId); | ||||||
|  | 
 | ||||||
|  |    // Load the file into fontconfig
 | ||||||
|  |    FcBool result = FcConfigAppFontAddFile( | ||||||
|  |       nullptr, reinterpret_cast<const FcChar8*>(cacheFilename.c_str())); | ||||||
|  |    if (!result) | ||||||
|  |    { | ||||||
|  |       logger_->error("Could not load font into fontconfig database", filename); | ||||||
|  |    } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void FontManager::Impl::InitializeEnvironment() | ||||||
|  | { | ||||||
|  | #if defined(__linux__) | ||||||
|  |    // Because of the way Fontconfig is built with Conan, FONTCONFIG_PATH must be
 | ||||||
|  |    // defined on Linux to ensure fonts can be found
 | ||||||
|  |    static const std::string kFontconfigPathKey {"FONTCONFIG_PATH"}; | ||||||
|  | 
 | ||||||
|  |    std::string fontconfigPath = scwx::util::GetEnvironment(kFontconfigPathKey); | ||||||
|  |    if (fontconfigPath.empty()) | ||||||
|  |    { | ||||||
|  |       scwx::util::SetEnvironment(kFontconfigPathKey, "/etc/fonts"); | ||||||
|  |    } | ||||||
|  | #endif | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void FontManager::Impl::InitializeFontCache() | ||||||
|  | { | ||||||
|  |    std::string cachePath { | ||||||
|  |       QStandardPaths::writableLocation(QStandardPaths::CacheLocation) | ||||||
|  |          .toStdString() + | ||||||
|  |       "/fonts"}; | ||||||
|  | 
 | ||||||
|  |    fontCachePath_ = cachePath + "/"; | ||||||
|  | 
 | ||||||
|  |    if (!std::filesystem::exists(cachePath)) | ||||||
|  |    { | ||||||
|  |       std::error_code error; | ||||||
|  |       if (!std::filesystem::create_directories(cachePath, error)) | ||||||
|  |       { | ||||||
|  |          logger_->error("Unable to create font cache directory: \"{}\" ({})", | ||||||
|  |                         cachePath, | ||||||
|  |                         error.message()); | ||||||
|  |          fontCachePath_.clear(); | ||||||
|  |       } | ||||||
|  |    } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void FontManager::Impl::InitializeFontconfig() | ||||||
|  | { | ||||||
|  |    FcConfig* fcConfig = FcInitLoadConfigAndFonts(); | ||||||
|  |    FcConfigSetCurrent(fcConfig); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void FontManager::Impl::FinalizeFontconfig() | ||||||
|  | { | ||||||
|  |    FcFini(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | FontRecord | ||||||
|  | FontManager::Impl::MatchFontFile(const std::string&              family, | ||||||
|  |                                  const std::vector<std::string>& styles) | ||||||
|  | { | ||||||
|  |    const std::string styleString = fmt::format("{}", fmt::join(styles, " ")); | ||||||
|  |    const std::string fontString  = fmt::format("{}:{}", family, styleString); | ||||||
|  | 
 | ||||||
|  |    // Build fontconfig pattern
 | ||||||
|  |    FcPattern* pattern = FcPatternCreate(); | ||||||
|  | 
 | ||||||
|  |    FcPatternAddString( | ||||||
|  |       pattern, FC_FAMILY, reinterpret_cast<const FcChar8*>(family.c_str())); | ||||||
|  |    FcPatternAddString(pattern, | ||||||
|  |                       FC_FONTFORMAT, | ||||||
|  |                       reinterpret_cast<const FcChar8*>(kFcTrueType_.c_str())); | ||||||
|  | 
 | ||||||
|  |    if (!styles.empty()) | ||||||
|  |    { | ||||||
|  |       FcPatternAddString(pattern, | ||||||
|  |                          FC_STYLE, | ||||||
|  |                          reinterpret_cast<const FcChar8*>(styleString.c_str())); | ||||||
|  |    } | ||||||
|  | 
 | ||||||
|  |    // Perform font pattern match substitution
 | ||||||
|  |    FcConfigSubstitute(nullptr, pattern, FcMatchPattern); | ||||||
|  |    FcDefaultSubstitute(pattern); | ||||||
|  | 
 | ||||||
|  |    // Find matching font
 | ||||||
|  |    FcResult   result; | ||||||
|  |    FcPattern* match = FcFontMatch(nullptr, pattern, &result); | ||||||
|  |    FontRecord record {}; | ||||||
|  | 
 | ||||||
|  |    if (match != nullptr) | ||||||
|  |    { | ||||||
|  |       FcChar8* fcFamily; | ||||||
|  |       FcChar8* fcStyle; | ||||||
|  |       FcChar8* fcFile; | ||||||
|  | 
 | ||||||
|  |       // Match was found, get properties
 | ||||||
|  |       if (FcPatternGetString(match, FC_FAMILY, 0, &fcFamily) == FcResultMatch && | ||||||
|  |           FcPatternGetString(match, FC_STYLE, 0, &fcStyle) == FcResultMatch && | ||||||
|  |           FcPatternGetString(match, FC_FILE, 0, &fcFile) == FcResultMatch) | ||||||
|  |       { | ||||||
|  |          record.family_   = reinterpret_cast<char*>(fcFamily); | ||||||
|  |          record.style_    = reinterpret_cast<char*>(fcStyle); | ||||||
|  |          record.filename_ = reinterpret_cast<char*>(fcFile); | ||||||
|  | 
 | ||||||
|  |          logger_->debug("Found matching font: {}:{} ({})", | ||||||
|  |                         record.family_, | ||||||
|  |                         record.style_, | ||||||
|  |                         record.filename_); | ||||||
|  |       } | ||||||
|  |    } | ||||||
|  | 
 | ||||||
|  |    if (record.filename_.empty()) | ||||||
|  |    { | ||||||
|  |       logger_->warn("Could not find matching font: {}", fontString); | ||||||
|  |    } | ||||||
|  | 
 | ||||||
|  |    // Cleanup
 | ||||||
|  |    FcPatternDestroy(match); | ||||||
|  |    FcPatternDestroy(pattern); | ||||||
|  | 
 | ||||||
|  |    return record; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | FontManager& FontManager::Instance() | ||||||
|  | { | ||||||
|  |    static FontManager instance_ {}; | ||||||
|  |    return instance_; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | size_t FontRecordHash<FontRecordPair>::operator()(const FontRecordPair& x) const | ||||||
|  | { | ||||||
|  |    size_t seed = 0; | ||||||
|  |    boost::hash_combine(seed, x.first.family_); | ||||||
|  |    boost::hash_combine(seed, x.first.style_); | ||||||
|  |    boost::hash_combine(seed, x.first.filename_); | ||||||
|  |    boost::hash_combine(seed, x.second.value()); | ||||||
|  |    return seed; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool operator==(const FontRecord& lhs, const FontRecord& rhs) | ||||||
|  | { | ||||||
|  |    return lhs.family_ == rhs.family_ && //
 | ||||||
|  |           lhs.style_ == rhs.style_ &&   //
 | ||||||
|  |           lhs.filename_ == rhs.filename_; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | } // namespace manager
 | ||||||
|  | } // namespace qt
 | ||||||
|  | } // namespace scwx
 | ||||||
							
								
								
									
										53
									
								
								scwx-qt/source/scwx/qt/manager/font_manager.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						|  | @ -0,0 +1,53 @@ | ||||||
|  | #pragma once | ||||||
|  | 
 | ||||||
|  | #include <scwx/qt/types/imgui_font.hpp> | ||||||
|  | #include <scwx/qt/types/font_types.hpp> | ||||||
|  | #include <scwx/qt/types/text_types.hpp> | ||||||
|  | 
 | ||||||
|  | #include <shared_mutex> | ||||||
|  | 
 | ||||||
|  | #include <QFont> | ||||||
|  | #include <QObject> | ||||||
|  | 
 | ||||||
|  | namespace scwx | ||||||
|  | { | ||||||
|  | namespace qt | ||||||
|  | { | ||||||
|  | namespace manager | ||||||
|  | { | ||||||
|  | 
 | ||||||
|  | class FontManager : public QObject | ||||||
|  | { | ||||||
|  |    Q_OBJECT | ||||||
|  |    Q_DISABLE_COPY_MOVE(FontManager) | ||||||
|  | 
 | ||||||
|  | public: | ||||||
|  |    explicit FontManager(); | ||||||
|  |    ~FontManager(); | ||||||
|  | 
 | ||||||
|  |    std::shared_mutex& imgui_font_atlas_mutex(); | ||||||
|  |    std::uint64_t      imgui_fonts_build_count() const; | ||||||
|  | 
 | ||||||
|  |    int GetFontId(types::Font font) const; | ||||||
|  |    std::shared_ptr<types::ImGuiFont> | ||||||
|  |          GetImGuiFont(types::FontCategory fontCategory); | ||||||
|  |    QFont GetQFont(types::FontCategory fontCategory); | ||||||
|  |    std::shared_ptr<types::ImGuiFont> | ||||||
|  |    LoadImGuiFont(const std::string&               family, | ||||||
|  |                  const std::vector<std::string>&  styles, | ||||||
|  |                  units::font_size::points<double> size, | ||||||
|  |                  bool                             loadIfNotFound = true); | ||||||
|  | 
 | ||||||
|  |    void LoadApplicationFont(types::Font font, const std::string& filename); | ||||||
|  |    void InitializeFonts(); | ||||||
|  | 
 | ||||||
|  |    static FontManager& Instance(); | ||||||
|  | 
 | ||||||
|  | private: | ||||||
|  |    class Impl; | ||||||
|  |    std::unique_ptr<Impl> p; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | } // namespace manager
 | ||||||
|  | } // namespace qt
 | ||||||
|  | } // namespace scwx
 | ||||||
							
								
								
									
										808
									
								
								scwx-qt/source/scwx/qt/manager/placefile_manager.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						|  | @ -0,0 +1,808 @@ | ||||||
|  | #include <scwx/qt/manager/placefile_manager.hpp> | ||||||
|  | #include <scwx/qt/manager/font_manager.hpp> | ||||||
|  | #include <scwx/qt/manager/resource_manager.hpp> | ||||||
|  | #include <scwx/qt/main/application.hpp> | ||||||
|  | #include <scwx/qt/util/json.hpp> | ||||||
|  | #include <scwx/qt/util/network.hpp> | ||||||
|  | #include <scwx/gr/placefile.hpp> | ||||||
|  | #include <scwx/network/cpr.hpp> | ||||||
|  | #include <scwx/util/logger.hpp> | ||||||
|  | 
 | ||||||
|  | #include <shared_mutex> | ||||||
|  | #include <vector> | ||||||
|  | 
 | ||||||
|  | #include <QDir> | ||||||
|  | #include <QGuiApplication> | ||||||
|  | #include <QScreen> | ||||||
|  | #include <QStandardPaths> | ||||||
|  | #include <QUrl> | ||||||
|  | #include <boost/algorithm/string.hpp> | ||||||
|  | #include <boost/asio/post.hpp> | ||||||
|  | #include <boost/asio/steady_timer.hpp> | ||||||
|  | #include <boost/asio/thread_pool.hpp> | ||||||
|  | #include <boost/json.hpp> | ||||||
|  | #include <boost/tokenizer.hpp> | ||||||
|  | #include <cpr/cpr.h> | ||||||
|  | #include <fmt/chrono.h> | ||||||
|  | 
 | ||||||
|  | namespace scwx | ||||||
|  | { | ||||||
|  | namespace qt | ||||||
|  | { | ||||||
|  | namespace manager | ||||||
|  | { | ||||||
|  | 
 | ||||||
|  | static const std::string logPrefix_ = "scwx::qt::manager::placefile_manager"; | ||||||
|  | static const auto        logger_    = scwx::util::Logger::Create(logPrefix_); | ||||||
|  | 
 | ||||||
|  | static const std::string kEnabledName_     = "enabled"; | ||||||
|  | static const std::string kThresholdedName_ = "thresholded"; | ||||||
|  | static const std::string kTitleName_       = "title"; | ||||||
|  | static const std::string kNameName_        = "name"; | ||||||
|  | 
 | ||||||
|  | class PlacefileManager::Impl | ||||||
|  | { | ||||||
|  | public: | ||||||
|  |    class PlacefileRecord; | ||||||
|  | 
 | ||||||
|  |    explicit Impl(PlacefileManager* self) : self_ {self} {} | ||||||
|  |    ~Impl() { threadPool_.join(); } | ||||||
|  | 
 | ||||||
|  |    void InitializePlacefileSettings(); | ||||||
|  |    void ReadPlacefileSettings(); | ||||||
|  |    void WritePlacefileSettings(); | ||||||
|  | 
 | ||||||
|  |    static boost::unordered_flat_map<std::size_t, | ||||||
|  |                                     std::shared_ptr<types::ImGuiFont>> | ||||||
|  |    LoadFontResources(const std::shared_ptr<gr::Placefile>& placefile); | ||||||
|  |    static std::vector<std::shared_ptr<boost::gil::rgba8_image_t>> | ||||||
|  |    LoadImageResources(const std::shared_ptr<gr::Placefile>& placefile); | ||||||
|  | 
 | ||||||
|  |    boost::asio::thread_pool threadPool_ {1u}; | ||||||
|  | 
 | ||||||
|  |    PlacefileManager* self_; | ||||||
|  | 
 | ||||||
|  |    std::string placefileSettingsPath_ {}; | ||||||
|  | 
 | ||||||
|  |    std::shared_ptr<config::RadarSite> radarSite_ {}; | ||||||
|  | 
 | ||||||
|  |    std::vector<std::shared_ptr<PlacefileRecord>> placefileRecords_ {}; | ||||||
|  |    boost::unordered_flat_map<std::string, std::shared_ptr<PlacefileRecord>> | ||||||
|  |                      placefileRecordMap_ {}; | ||||||
|  |    std::shared_mutex placefileRecordLock_ {}; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | class PlacefileManager::Impl::PlacefileRecord | ||||||
|  | { | ||||||
|  | public: | ||||||
|  |    explicit PlacefileRecord(Impl*                          impl, | ||||||
|  |                             const std::string&             name, | ||||||
|  |                             std::shared_ptr<gr::Placefile> placefile, | ||||||
|  |                             const std::string&             title   = {}, | ||||||
|  |                             bool                           enabled = false, | ||||||
|  |                             bool thresholded                       = false) : | ||||||
|  |        p {impl}, | ||||||
|  |        name_ {name}, | ||||||
|  |        title_ {title}, | ||||||
|  |        placefile_ {placefile}, | ||||||
|  |        enabled_ {enabled}, | ||||||
|  |        thresholded_ {thresholded} | ||||||
|  |    { | ||||||
|  |    } | ||||||
|  |    ~PlacefileRecord() | ||||||
|  |    { | ||||||
|  |       std::unique_lock refreshLock(refreshMutex_); | ||||||
|  |       std::unique_lock timerLock(timerMutex_); | ||||||
|  |       refreshTimer_.cancel(); | ||||||
|  |       timerLock.unlock(); | ||||||
|  |       refreshLock.unlock(); | ||||||
|  | 
 | ||||||
|  |       threadPool_.join(); | ||||||
|  |    } | ||||||
|  | 
 | ||||||
|  |    bool                 refresh_enabled() const; | ||||||
|  |    std::chrono::seconds refresh_time() const; | ||||||
|  | 
 | ||||||
|  |    void CancelRefresh(); | ||||||
|  |    void ScheduleRefresh(); | ||||||
|  |    void Update(); | ||||||
|  |    void UpdateAsync(); | ||||||
|  | 
 | ||||||
|  |    friend void tag_invoke(boost::json::value_from_tag, | ||||||
|  |                           boost::json::value&                     jv, | ||||||
|  |                           const std::shared_ptr<PlacefileRecord>& record) | ||||||
|  |    { | ||||||
|  |       jv = {{kEnabledName_, record->enabled_}, | ||||||
|  |             {kThresholdedName_, record->thresholded_}, | ||||||
|  |             {kTitleName_, record->title_}, | ||||||
|  |             {kNameName_, record->name_}}; | ||||||
|  |    } | ||||||
|  | 
 | ||||||
|  |    friend PlacefileRecord tag_invoke(boost::json::value_to_tag<PlacefileRecord>, | ||||||
|  |                                      const boost::json::value& jv) | ||||||
|  |    { | ||||||
|  |       return PlacefileRecord { | ||||||
|  |          nullptr, | ||||||
|  |          boost::json::value_to<std::string>(jv.at(kNameName_)), | ||||||
|  |          nullptr, | ||||||
|  |          boost::json::value_to<std::string>(jv.at(kTitleName_)), | ||||||
|  |          jv.at(kEnabledName_).as_bool(), | ||||||
|  |          jv.at(kThresholdedName_).as_bool()}; | ||||||
|  |    } | ||||||
|  | 
 | ||||||
|  |    Impl* p; | ||||||
|  | 
 | ||||||
|  |    std::string                    name_; | ||||||
|  |    std::string                    title_; | ||||||
|  |    std::shared_ptr<gr::Placefile> placefile_; | ||||||
|  |    bool                           enabled_; | ||||||
|  |    bool                           thresholded_; | ||||||
|  |    boost::asio::thread_pool       threadPool_ {1u}; | ||||||
|  |    boost::asio::steady_timer      refreshTimer_ {threadPool_}; | ||||||
|  |    std::mutex                     refreshMutex_ {}; | ||||||
|  |    std::mutex                     timerMutex_ {}; | ||||||
|  | 
 | ||||||
|  |    boost::unordered_flat_map<std::size_t, std::shared_ptr<types::ImGuiFont>> | ||||||
|  |               fonts_ {}; | ||||||
|  |    std::mutex fontsMutex_ {}; | ||||||
|  | 
 | ||||||
|  |    std::vector<std::shared_ptr<boost::gil::rgba8_image_t>> images_ {}; | ||||||
|  | 
 | ||||||
|  |    std::string                           lastRadarSite_ {}; | ||||||
|  |    std::chrono::system_clock::time_point lastUpdateTime_ {}; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | PlacefileManager::PlacefileManager() : p(std::make_unique<Impl>(this)) | ||||||
|  | { | ||||||
|  |    boost::asio::post(p->threadPool_, | ||||||
|  |                      [this]() | ||||||
|  |                      { | ||||||
|  |                         p->InitializePlacefileSettings(); | ||||||
|  | 
 | ||||||
|  |                         // Read placefile settings on startup
 | ||||||
|  |                         main::Application::WaitForInitialization(); | ||||||
|  |                         p->ReadPlacefileSettings(); | ||||||
|  |                         Q_EMIT PlacefilesInitialized(); | ||||||
|  |                      }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | PlacefileManager::~PlacefileManager() | ||||||
|  | { | ||||||
|  |    // Write placefile settings on shutdown
 | ||||||
|  |    p->WritePlacefileSettings(); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | bool PlacefileManager::placefile_enabled(const std::string& name) | ||||||
|  | { | ||||||
|  |    std::shared_lock lock(p->placefileRecordLock_); | ||||||
|  | 
 | ||||||
|  |    auto it = p->placefileRecordMap_.find(name); | ||||||
|  |    if (it != p->placefileRecordMap_.cend()) | ||||||
|  |    { | ||||||
|  |       return it->second->enabled_; | ||||||
|  |    } | ||||||
|  |    return false; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool PlacefileManager::placefile_thresholded(const std::string& name) | ||||||
|  | { | ||||||
|  |    std::shared_lock lock(p->placefileRecordLock_); | ||||||
|  | 
 | ||||||
|  |    auto it = p->placefileRecordMap_.find(name); | ||||||
|  |    if (it != p->placefileRecordMap_.cend()) | ||||||
|  |    { | ||||||
|  |       return it->second->thresholded_; | ||||||
|  |    } | ||||||
|  |    return false; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | std::string PlacefileManager::placefile_title(const std::string& name) | ||||||
|  | { | ||||||
|  |    std::shared_lock lock(p->placefileRecordLock_); | ||||||
|  | 
 | ||||||
|  |    auto it = p->placefileRecordMap_.find(name); | ||||||
|  |    if (it != p->placefileRecordMap_.cend()) | ||||||
|  |    { | ||||||
|  |       return it->second->title_; | ||||||
|  |    } | ||||||
|  |    return {}; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | std::shared_ptr<gr::Placefile> | ||||||
|  | PlacefileManager::placefile(const std::string& name) | ||||||
|  | { | ||||||
|  |    std::shared_lock lock(p->placefileRecordLock_); | ||||||
|  | 
 | ||||||
|  |    auto it = p->placefileRecordMap_.find(name); | ||||||
|  |    if (it != p->placefileRecordMap_.cend()) | ||||||
|  |    { | ||||||
|  |       return it->second->placefile_; | ||||||
|  |    } | ||||||
|  |    return nullptr; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | boost::unordered_flat_map<std::size_t, std::shared_ptr<types::ImGuiFont>> | ||||||
|  | PlacefileManager::placefile_fonts(const std::string& name) | ||||||
|  | { | ||||||
|  |    std::shared_lock lock(p->placefileRecordLock_); | ||||||
|  | 
 | ||||||
|  |    auto it = p->placefileRecordMap_.find(name); | ||||||
|  |    if (it != p->placefileRecordMap_.cend()) | ||||||
|  |    { | ||||||
|  |       std::unique_lock fontsLock {it->second->fontsMutex_}; | ||||||
|  |       return it->second->fonts_; | ||||||
|  |    } | ||||||
|  |    return {}; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void PlacefileManager::set_placefile_enabled(const std::string& name, | ||||||
|  |                                              bool               enabled) | ||||||
|  | { | ||||||
|  |    std::shared_lock lock(p->placefileRecordLock_); | ||||||
|  | 
 | ||||||
|  |    auto it = p->placefileRecordMap_.find(name); | ||||||
|  |    if (it != p->placefileRecordMap_.cend()) | ||||||
|  |    { | ||||||
|  |       auto record      = it->second; | ||||||
|  |       record->enabled_ = enabled; | ||||||
|  | 
 | ||||||
|  |       lock.unlock(); | ||||||
|  | 
 | ||||||
|  |       Q_EMIT PlacefileEnabled(name, enabled); | ||||||
|  | 
 | ||||||
|  |       using namespace std::chrono_literals; | ||||||
|  | 
 | ||||||
|  |       // Update the placefile
 | ||||||
|  |       if (enabled) | ||||||
|  |       { | ||||||
|  |          if (p->radarSite_ != nullptr && | ||||||
|  |              record->lastRadarSite_ != p->radarSite_->id()) | ||||||
|  |          { | ||||||
|  |             // If the radar site has changed, update now
 | ||||||
|  |             record->UpdateAsync(); | ||||||
|  |          } | ||||||
|  |          else | ||||||
|  |          { | ||||||
|  |             // Otherwise, schedule an update
 | ||||||
|  |             record->ScheduleRefresh(); | ||||||
|  |          } | ||||||
|  |       } | ||||||
|  |       else if (!enabled) | ||||||
|  |       { | ||||||
|  |          record->CancelRefresh(); | ||||||
|  |       } | ||||||
|  |    } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void PlacefileManager::set_placefile_thresholded(const std::string& name, | ||||||
|  |                                                  bool               thresholded) | ||||||
|  | { | ||||||
|  |    std::shared_lock lock(p->placefileRecordLock_); | ||||||
|  | 
 | ||||||
|  |    auto it = p->placefileRecordMap_.find(name); | ||||||
|  |    if (it != p->placefileRecordMap_.cend()) | ||||||
|  |    { | ||||||
|  |       it->second->thresholded_ = thresholded; | ||||||
|  | 
 | ||||||
|  |       lock.unlock(); | ||||||
|  | 
 | ||||||
|  |       Q_EMIT PlacefileUpdated(name); | ||||||
|  |    } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void PlacefileManager::set_placefile_url(const std::string& name, | ||||||
|  |                                          const std::string& newUrl) | ||||||
|  | { | ||||||
|  |    std::string normalizedUrl = util::network::NormalizeUrl(newUrl); | ||||||
|  | 
 | ||||||
|  |    std::unique_lock lock(p->placefileRecordLock_); | ||||||
|  | 
 | ||||||
|  |    auto it    = p->placefileRecordMap_.find(name); | ||||||
|  |    auto itNew = p->placefileRecordMap_.find(normalizedUrl); | ||||||
|  |    if (it != p->placefileRecordMap_.cend() && | ||||||
|  |        itNew == p->placefileRecordMap_.cend()) | ||||||
|  |    { | ||||||
|  |       auto placefileRecord        = it->second; | ||||||
|  |       placefileRecord->name_      = normalizedUrl; | ||||||
|  |       placefileRecord->placefile_ = nullptr; | ||||||
|  |       placefileRecord->fonts_.clear(); | ||||||
|  |       placefileRecord->images_.clear(); | ||||||
|  |       p->placefileRecordMap_.erase(it); | ||||||
|  |       p->placefileRecordMap_.insert_or_assign(normalizedUrl, placefileRecord); | ||||||
|  | 
 | ||||||
|  |       lock.unlock(); | ||||||
|  | 
 | ||||||
|  |       Q_EMIT PlacefileRenamed(name, normalizedUrl); | ||||||
|  | 
 | ||||||
|  |       // Queue a placefile update
 | ||||||
|  |       placefileRecord->UpdateAsync(); | ||||||
|  |    } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool PlacefileManager::Impl::PlacefileRecord::refresh_enabled() const | ||||||
|  | { | ||||||
|  |    if (placefile_ != nullptr) | ||||||
|  |    { | ||||||
|  |       using namespace std::chrono_literals; | ||||||
|  |       return placefile_->refresh() > 0s; | ||||||
|  |    } | ||||||
|  | 
 | ||||||
|  |    return false; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | std::chrono::seconds | ||||||
|  | PlacefileManager::Impl::PlacefileRecord::refresh_time() const | ||||||
|  | { | ||||||
|  |    using namespace std::chrono_literals; | ||||||
|  | 
 | ||||||
|  |    if (refresh_enabled()) | ||||||
|  |    { | ||||||
|  |       // Don't refresh more often than every 15 seconds
 | ||||||
|  |       return std::max(placefile_->refresh(), 15s); | ||||||
|  |    } | ||||||
|  | 
 | ||||||
|  |    return -1s; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void PlacefileManager::Impl::InitializePlacefileSettings() | ||||||
|  | { | ||||||
|  |    std::string appDataPath { | ||||||
|  |       QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) | ||||||
|  |          .toStdString()}; | ||||||
|  | 
 | ||||||
|  |    if (!std::filesystem::exists(appDataPath)) | ||||||
|  |    { | ||||||
|  |       if (!std::filesystem::create_directories(appDataPath)) | ||||||
|  |       { | ||||||
|  |          logger_->error("Unable to create application data directory: \"{}\"", | ||||||
|  |                         appDataPath); | ||||||
|  |       } | ||||||
|  |    } | ||||||
|  | 
 | ||||||
|  |    placefileSettingsPath_ = appDataPath + "/placefiles.json"; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void PlacefileManager::Impl::ReadPlacefileSettings() | ||||||
|  | { | ||||||
|  |    logger_->info("Reading placefile settings"); | ||||||
|  | 
 | ||||||
|  |    boost::json::value placefileJson = nullptr; | ||||||
|  | 
 | ||||||
|  |    // Determine if placefile settings exists
 | ||||||
|  |    if (std::filesystem::exists(placefileSettingsPath_)) | ||||||
|  |    { | ||||||
|  |       placefileJson = util::json::ReadJsonFile(placefileSettingsPath_); | ||||||
|  |    } | ||||||
|  | 
 | ||||||
|  |    // If placefile settings was successfully read
 | ||||||
|  |    if (placefileJson != nullptr && placefileJson.is_array()) | ||||||
|  |    { | ||||||
|  |       // For each placefile entry
 | ||||||
|  |       auto& placefileArray = placefileJson.as_array(); | ||||||
|  |       for (auto& placefileEntry : placefileArray) | ||||||
|  |       { | ||||||
|  |          try | ||||||
|  |          { | ||||||
|  |             // Convert placefile entry to a record
 | ||||||
|  |             PlacefileRecord record = | ||||||
|  |                boost::json::value_to<PlacefileRecord>(placefileEntry); | ||||||
|  | 
 | ||||||
|  |             if (!record.name_.empty()) | ||||||
|  |             { | ||||||
|  |                self_->AddUrl(record.name_, | ||||||
|  |                              record.title_, | ||||||
|  |                              record.enabled_, | ||||||
|  |                              record.thresholded_); | ||||||
|  |             } | ||||||
|  |          } | ||||||
|  |          catch (const std::exception& ex) | ||||||
|  |          { | ||||||
|  |             logger_->warn("Invalid placefile entry: {}", ex.what()); | ||||||
|  |          } | ||||||
|  |       } | ||||||
|  |    } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void PlacefileManager::Impl::WritePlacefileSettings() | ||||||
|  | { | ||||||
|  |    logger_->info("Saving placefile settings"); | ||||||
|  | 
 | ||||||
|  |    std::shared_lock lock {placefileRecordLock_}; | ||||||
|  |    auto             placefileJson = boost::json::value_from(placefileRecords_); | ||||||
|  |    util::json::WriteJsonFile(placefileSettingsPath_, placefileJson); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void PlacefileManager::SetRadarSite( | ||||||
|  |    std::shared_ptr<config::RadarSite> radarSite) | ||||||
|  | { | ||||||
|  |    if (p->radarSite_ == radarSite || radarSite == nullptr) | ||||||
|  |    { | ||||||
|  |       // No action needed
 | ||||||
|  |       return; | ||||||
|  |    } | ||||||
|  | 
 | ||||||
|  |    logger_->debug("SetRadarSite: {}", radarSite->id()); | ||||||
|  | 
 | ||||||
|  |    p->radarSite_ = radarSite; | ||||||
|  | 
 | ||||||
|  |    // Update all enabled records
 | ||||||
|  |    std::shared_lock lock(p->placefileRecordLock_); | ||||||
|  |    for (auto& record : p->placefileRecords_) | ||||||
|  |    { | ||||||
|  |       if (record->enabled_) | ||||||
|  |       { | ||||||
|  |          record->UpdateAsync(); | ||||||
|  |       } | ||||||
|  |    } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void PlacefileManager::AddUrl(const std::string& urlString, | ||||||
|  |                               const std::string& title, | ||||||
|  |                               bool               enabled, | ||||||
|  |                               bool               thresholded) | ||||||
|  | { | ||||||
|  |    std::string normalizedUrl = util::network::NormalizeUrl(urlString); | ||||||
|  | 
 | ||||||
|  |    std::unique_lock lock(p->placefileRecordLock_); | ||||||
|  | 
 | ||||||
|  |    // Determine if the placefile has been loaded previously
 | ||||||
|  |    auto it = std::find_if(p->placefileRecords_.begin(), | ||||||
|  |                           p->placefileRecords_.end(), | ||||||
|  |                           [&normalizedUrl](auto& record) | ||||||
|  |                           { return record->name_ == normalizedUrl; }); | ||||||
|  |    if (it != p->placefileRecords_.end()) | ||||||
|  |    { | ||||||
|  |       logger_->debug("Placefile already added: {}", normalizedUrl); | ||||||
|  |       return; | ||||||
|  |    } | ||||||
|  | 
 | ||||||
|  |    // Placefile is new, proceed with adding
 | ||||||
|  |    logger_->info("AddUrl: {}", normalizedUrl); | ||||||
|  | 
 | ||||||
|  |    // Add an empty placefile record for the new URL
 | ||||||
|  |    auto& record = | ||||||
|  |       p->placefileRecords_.emplace_back(std::make_shared<Impl::PlacefileRecord>( | ||||||
|  |          p.get(), normalizedUrl, nullptr, title, enabled, thresholded)); | ||||||
|  |    p->placefileRecordMap_.insert_or_assign(normalizedUrl, record); | ||||||
|  | 
 | ||||||
|  |    lock.unlock(); | ||||||
|  | 
 | ||||||
|  |    if (enabled) | ||||||
|  |    { | ||||||
|  |       Q_EMIT PlacefileEnabled(normalizedUrl, record->enabled_); | ||||||
|  |    } | ||||||
|  | 
 | ||||||
|  |    Q_EMIT PlacefileUpdated(normalizedUrl); | ||||||
|  | 
 | ||||||
|  |    // Queue a placefile update, either if enabled, or if we don't know the title
 | ||||||
|  |    if (enabled || title.empty()) | ||||||
|  |    { | ||||||
|  |       record->UpdateAsync(); | ||||||
|  |    } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void PlacefileManager::RemoveUrl(const std::string& urlString) | ||||||
|  | { | ||||||
|  |    std::unique_lock lock(p->placefileRecordLock_); | ||||||
|  | 
 | ||||||
|  |    // Determine if the placefile has been loaded previously
 | ||||||
|  |    auto it = std::find_if(p->placefileRecords_.begin(), | ||||||
|  |                           p->placefileRecords_.end(), | ||||||
|  |                           [&urlString](auto& record) | ||||||
|  |                           { return record->name_ == urlString; }); | ||||||
|  |    if (it == p->placefileRecords_.end()) | ||||||
|  |    { | ||||||
|  |       logger_->debug("Placefile doesn't exist: {}", urlString); | ||||||
|  |       return; | ||||||
|  |    } | ||||||
|  | 
 | ||||||
|  |    // Placefile exists, proceed with removing
 | ||||||
|  |    logger_->info("RemoveUrl: {}", urlString); | ||||||
|  | 
 | ||||||
|  |    // Remove record
 | ||||||
|  |    p->placefileRecords_.erase(it); | ||||||
|  |    p->placefileRecordMap_.erase(urlString); | ||||||
|  | 
 | ||||||
|  |    lock.unlock(); | ||||||
|  | 
 | ||||||
|  |    Q_EMIT PlacefileRemoved(urlString); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void PlacefileManager::Refresh(const std::string& name) | ||||||
|  | { | ||||||
|  |    std::shared_lock lock {p->placefileRecordLock_}; | ||||||
|  | 
 | ||||||
|  |    auto it = p->placefileRecordMap_.find(name); | ||||||
|  |    if (it != p->placefileRecordMap_.cend()) | ||||||
|  |    { | ||||||
|  |       it->second->UpdateAsync(); | ||||||
|  |    } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void PlacefileManager::Impl::PlacefileRecord::Update() | ||||||
|  | { | ||||||
|  |    logger_->debug("Update: {}", name_); | ||||||
|  | 
 | ||||||
|  |    // Take unique lock before refreshing
 | ||||||
|  |    std::unique_lock lock {refreshMutex_}; | ||||||
|  | 
 | ||||||
|  |    // Make a copy of name in the event it changes.
 | ||||||
|  |    const std::string name {name_}; | ||||||
|  | 
 | ||||||
|  |    std::shared_ptr<gr::Placefile> updatedPlacefile {}; | ||||||
|  | 
 | ||||||
|  |    QUrl url = QUrl::fromUserInput(QString::fromStdString(name)); | ||||||
|  |    if (url.isLocalFile()) | ||||||
|  |    { | ||||||
|  |       updatedPlacefile = gr::Placefile::Load(name); | ||||||
|  |    } | ||||||
|  |    else | ||||||
|  |    { | ||||||
|  |       std::string decodedUrl {name}; | ||||||
|  |       auto        queryPos = decodedUrl.find('?'); | ||||||
|  |       if (queryPos != std::string::npos) | ||||||
|  |       { | ||||||
|  |          decodedUrl.erase(queryPos); | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       if (p->radarSite_ == nullptr) | ||||||
|  |       { | ||||||
|  |          // Wait to process until a radar site is selected
 | ||||||
|  |          return; | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       auto dpi = QGuiApplication::primaryScreen()->logicalDotsPerInch(); | ||||||
|  | 
 | ||||||
|  |       // Specify parameters
 | ||||||
|  |       auto parameters = cpr::Parameters { | ||||||
|  |          {"version", "1.5"}, // Placefile Version Supported
 | ||||||
|  |          {"dpi", fmt::format("{:0.0f}", dpi)}, | ||||||
|  |          {"lat", fmt::format("{:0.3f}", p->radarSite_->latitude())}, | ||||||
|  |          {"lon", fmt::format("{:0.3f}", p->radarSite_->longitude())}}; | ||||||
|  | 
 | ||||||
|  |       // Iterate through each query parameter in the URL
 | ||||||
|  |       if (url.hasQuery()) | ||||||
|  |       { | ||||||
|  |          auto query = url.query(QUrl::ComponentFormattingOption::PrettyDecoded) | ||||||
|  |                          .toStdString(); | ||||||
|  | 
 | ||||||
|  |          boost::char_separator<char> delimiter("&"); | ||||||
|  |          boost::tokenizer            tokens(query, delimiter); | ||||||
|  | 
 | ||||||
|  |          for (auto& token : tokens) | ||||||
|  |          { | ||||||
|  |             std::vector<std::string> split {}; | ||||||
|  |             boost::split(split, token, boost::is_any_of("=")); | ||||||
|  |             if (split.size() >= 2) | ||||||
|  |             { | ||||||
|  |                // Token is a key=value parameter
 | ||||||
|  |                parameters.Add({split[0], split[1]}); | ||||||
|  |             } | ||||||
|  |             else | ||||||
|  |             { | ||||||
|  |                // Token is a single key with no value
 | ||||||
|  |                parameters.Add({token, {}}); | ||||||
|  |             } | ||||||
|  |          } | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       // Send HTTP GET request
 | ||||||
|  |       auto response = | ||||||
|  |          cpr::Get(cpr::Url {decodedUrl}, network::cpr::GetHeader(), parameters); | ||||||
|  | 
 | ||||||
|  |       if (cpr::status::is_success(response.status_code)) | ||||||
|  |       { | ||||||
|  |          std::istringstream responseBody {response.text}; | ||||||
|  |          updatedPlacefile = gr::Placefile::Load(name, responseBody); | ||||||
|  |       } | ||||||
|  |       else if (response.status_code == 0) | ||||||
|  |       { | ||||||
|  |          logger_->error("Error loading placefile: {}", response.error.message); | ||||||
|  |       } | ||||||
|  |       else | ||||||
|  |       { | ||||||
|  |          logger_->error("Error loading placefile: {}", response.status_line); | ||||||
|  |       } | ||||||
|  |    } | ||||||
|  | 
 | ||||||
|  |    if (updatedPlacefile != nullptr) | ||||||
|  |    { | ||||||
|  |       // Load placefile resources
 | ||||||
|  |       auto newFonts  = Impl::LoadFontResources(updatedPlacefile); | ||||||
|  |       auto newImages = Impl::LoadImageResources(updatedPlacefile); | ||||||
|  | 
 | ||||||
|  |       // Check the name matches, in case the name updated
 | ||||||
|  |       if (name_ == name) | ||||||
|  |       { | ||||||
|  |          // Update the placefile
 | ||||||
|  |          placefile_      = updatedPlacefile; | ||||||
|  |          title_          = placefile_->title(); | ||||||
|  |          lastUpdateTime_ = std::chrono::system_clock::now(); | ||||||
|  | 
 | ||||||
|  |          // Update font resources
 | ||||||
|  |          { | ||||||
|  |             std::unique_lock fontsLock {fontsMutex_}; | ||||||
|  |             fonts_.swap(newFonts); | ||||||
|  |             newFonts.clear(); | ||||||
|  |          } | ||||||
|  | 
 | ||||||
|  |          // Update image resources
 | ||||||
|  |          images_.swap(newImages); | ||||||
|  |          newImages.clear(); | ||||||
|  | 
 | ||||||
|  |          if (p->radarSite_ != nullptr) | ||||||
|  |          { | ||||||
|  |             lastRadarSite_ = p->radarSite_->id(); | ||||||
|  |          } | ||||||
|  | 
 | ||||||
|  |          // Notify slots of the placefile update
 | ||||||
|  |          Q_EMIT p->self_->PlacefileUpdated(name); | ||||||
|  |       } | ||||||
|  |    } | ||||||
|  | 
 | ||||||
|  |    // Update refresh timer
 | ||||||
|  |    ScheduleRefresh(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void PlacefileManager::Impl::PlacefileRecord::ScheduleRefresh() | ||||||
|  | { | ||||||
|  |    using namespace std::chrono_literals; | ||||||
|  | 
 | ||||||
|  |    if (!enabled_ || !refresh_enabled()) | ||||||
|  |    { | ||||||
|  |       // Refresh is disabled
 | ||||||
|  |       return; | ||||||
|  |    } | ||||||
|  | 
 | ||||||
|  |    std::unique_lock lock {timerMutex_}; | ||||||
|  | 
 | ||||||
|  |    auto nextUpdateTime      = lastUpdateTime_ + refresh_time(); | ||||||
|  |    auto timeUntilNextUpdate = nextUpdateTime - std::chrono::system_clock::now(); | ||||||
|  | 
 | ||||||
|  |    logger_->debug( | ||||||
|  |       "Scheduled refresh in {:%M:%S} ({})", | ||||||
|  |       std::chrono::duration_cast<std::chrono::seconds>(timeUntilNextUpdate), | ||||||
|  |       name_); | ||||||
|  | 
 | ||||||
|  |    refreshTimer_.expires_after(timeUntilNextUpdate); | ||||||
|  |    refreshTimer_.async_wait( | ||||||
|  |       [this](const boost::system::error_code& e) | ||||||
|  |       { | ||||||
|  |          if (e == boost::asio::error::operation_aborted) | ||||||
|  |          { | ||||||
|  |             logger_->debug("Refresh timer cancelled"); | ||||||
|  |          } | ||||||
|  |          else if (e != boost::system::errc::success) | ||||||
|  |          { | ||||||
|  |             logger_->warn("Refresh timer error: {}", e.message()); | ||||||
|  |          } | ||||||
|  |          else | ||||||
|  |          { | ||||||
|  |             Update(); | ||||||
|  |          } | ||||||
|  |       }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void PlacefileManager::Impl::PlacefileRecord::CancelRefresh() | ||||||
|  | { | ||||||
|  |    std::unique_lock lock {timerMutex_}; | ||||||
|  |    refreshTimer_.cancel(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void PlacefileManager::Impl::PlacefileRecord::UpdateAsync() | ||||||
|  | { | ||||||
|  |    boost::asio::post(threadPool_, [this]() { Update(); }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | std::shared_ptr<PlacefileManager> PlacefileManager::Instance() | ||||||
|  | { | ||||||
|  |    static std::weak_ptr<PlacefileManager> placefileManagerReference_ {}; | ||||||
|  |    static std::mutex                      instanceMutex_ {}; | ||||||
|  | 
 | ||||||
|  |    std::unique_lock lock(instanceMutex_); | ||||||
|  | 
 | ||||||
|  |    std::shared_ptr<PlacefileManager> placefileManager = | ||||||
|  |       placefileManagerReference_.lock(); | ||||||
|  | 
 | ||||||
|  |    if (placefileManager == nullptr) | ||||||
|  |    { | ||||||
|  |       placefileManager           = std::make_shared<PlacefileManager>(); | ||||||
|  |       placefileManagerReference_ = placefileManager; | ||||||
|  |    } | ||||||
|  | 
 | ||||||
|  |    return placefileManager; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | boost::unordered_flat_map<std::size_t, std::shared_ptr<types::ImGuiFont>> | ||||||
|  | PlacefileManager::Impl::LoadFontResources( | ||||||
|  |    const std::shared_ptr<gr::Placefile>& placefile) | ||||||
|  | { | ||||||
|  |    boost::unordered_flat_map<std::size_t, std::shared_ptr<types::ImGuiFont>> | ||||||
|  |         imGuiFonts {}; | ||||||
|  |    auto fonts = placefile->fonts(); | ||||||
|  | 
 | ||||||
|  |    for (auto& font : fonts) | ||||||
|  |    { | ||||||
|  |       units::font_size::pixels<double> size {font.second->pixels_}; | ||||||
|  |       std::vector<std::string>         styles {}; | ||||||
|  | 
 | ||||||
|  |       if (font.second->IsBold()) | ||||||
|  |       { | ||||||
|  |          styles.push_back("bold"); | ||||||
|  |       } | ||||||
|  |       if (font.second->IsItalic()) | ||||||
|  |       { | ||||||
|  |          styles.push_back("italic"); | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       auto imGuiFont = FontManager::Instance().LoadImGuiFont( | ||||||
|  |          font.second->face_, styles, size); | ||||||
|  |       imGuiFonts.emplace(font.first, std::move(imGuiFont)); | ||||||
|  |    } | ||||||
|  | 
 | ||||||
|  |    return imGuiFonts; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | std::vector<std::shared_ptr<boost::gil::rgba8_image_t>> | ||||||
|  | PlacefileManager::Impl::LoadImageResources( | ||||||
|  |    const std::shared_ptr<gr::Placefile>& placefile) | ||||||
|  | { | ||||||
|  |    const auto iconFiles = placefile->icon_files(); | ||||||
|  |    const auto drawItems = placefile->GetDrawItems(); | ||||||
|  | 
 | ||||||
|  |    const QUrl baseUrl = | ||||||
|  |       QUrl::fromUserInput(QString::fromStdString(placefile->name())); | ||||||
|  | 
 | ||||||
|  |    std::vector<std::string> urlStrings {}; | ||||||
|  |    urlStrings.reserve(iconFiles.size()); | ||||||
|  | 
 | ||||||
|  |    // Resolve Icon Files
 | ||||||
|  |    std::transform(iconFiles.cbegin(), | ||||||
|  |                   iconFiles.cend(), | ||||||
|  |                   std::back_inserter(urlStrings), | ||||||
|  |                   [&baseUrl](auto& iconFile) | ||||||
|  |                   { | ||||||
|  |                      // Resolve target URL relative to base URL
 | ||||||
|  |                      QString filePath = | ||||||
|  |                         QString::fromStdString(iconFile->filename_); | ||||||
|  |                      QUrl fileUrl = QUrl(QDir::fromNativeSeparators(filePath)); | ||||||
|  |                      QUrl resolvedUrl = baseUrl.resolved(fileUrl); | ||||||
|  | 
 | ||||||
|  |                      return resolvedUrl.toString().toStdString(); | ||||||
|  |                   }); | ||||||
|  | 
 | ||||||
|  |    // Resolve Image Files
 | ||||||
|  |    for (auto& di : drawItems) | ||||||
|  |    { | ||||||
|  |       switch (di->itemType_) | ||||||
|  |       { | ||||||
|  |       case gr::Placefile::ItemType::Image: | ||||||
|  |       { | ||||||
|  |          const std::string& imageFile = | ||||||
|  |             std::static_pointer_cast<gr::Placefile::ImageDrawItem>(di) | ||||||
|  |                ->imageFile_; | ||||||
|  | 
 | ||||||
|  |          QString     filePath    = QString::fromStdString(imageFile); | ||||||
|  |          QUrl        fileUrl     = QUrl(QDir::fromNativeSeparators(filePath)); | ||||||
|  |          QUrl        resolvedUrl = baseUrl.resolved(fileUrl); | ||||||
|  |          std::string urlString   = resolvedUrl.toString().toStdString(); | ||||||
|  | 
 | ||||||
|  |          if (std::find(urlStrings.cbegin(), urlStrings.cend(), urlString) == | ||||||
|  |              urlStrings.cend()) | ||||||
|  |          { | ||||||
|  |             urlStrings.push_back(urlString); | ||||||
|  |          } | ||||||
|  |          break; | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       default: | ||||||
|  |          break; | ||||||
|  |       } | ||||||
|  |    } | ||||||
|  | 
 | ||||||
|  |    return ResourceManager::LoadImageResources(urlStrings); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | } // namespace manager
 | ||||||
|  | } // namespace qt
 | ||||||
|  | } // namespace scwx
 | ||||||
							
								
								
									
										63
									
								
								scwx-qt/source/scwx/qt/manager/placefile_manager.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						|  | @ -0,0 +1,63 @@ | ||||||
|  | #pragma once | ||||||
|  | 
 | ||||||
|  | #include <scwx/gr/placefile.hpp> | ||||||
|  | #include <scwx/qt/config/radar_site.hpp> | ||||||
|  | #include <scwx/qt/types/imgui_font.hpp> | ||||||
|  | 
 | ||||||
|  | #include <QObject> | ||||||
|  | #include <boost/unordered/unordered_flat_map.hpp> | ||||||
|  | 
 | ||||||
|  | namespace scwx | ||||||
|  | { | ||||||
|  | namespace qt | ||||||
|  | { | ||||||
|  | namespace manager | ||||||
|  | { | ||||||
|  | 
 | ||||||
|  | class PlacefileManager : public QObject | ||||||
|  | { | ||||||
|  |    Q_OBJECT | ||||||
|  | 
 | ||||||
|  | public: | ||||||
|  |    explicit PlacefileManager(); | ||||||
|  |    ~PlacefileManager(); | ||||||
|  | 
 | ||||||
|  |    bool        placefile_enabled(const std::string& name); | ||||||
|  |    bool        placefile_thresholded(const std::string& name); | ||||||
|  |    std::string placefile_title(const std::string& name); | ||||||
|  |    std::shared_ptr<gr::Placefile> placefile(const std::string& name); | ||||||
|  |    boost::unordered_flat_map<std::size_t, std::shared_ptr<types::ImGuiFont>> | ||||||
|  |    placefile_fonts(const std::string& name); | ||||||
|  | 
 | ||||||
|  |    void set_placefile_enabled(const std::string& name, bool enabled); | ||||||
|  |    void set_placefile_thresholded(const std::string& name, bool thresholded); | ||||||
|  |    void set_placefile_url(const std::string& name, const std::string& newUrl); | ||||||
|  | 
 | ||||||
|  |    void SetRadarSite(std::shared_ptr<config::RadarSite> radarSite); | ||||||
|  | 
 | ||||||
|  |    void AddUrl(const std::string& urlString, | ||||||
|  |                const std::string& title       = {}, | ||||||
|  |                bool               enabled     = false, | ||||||
|  |                bool               thresholded = false); | ||||||
|  |    void RemoveUrl(const std::string& urlString); | ||||||
|  | 
 | ||||||
|  |    void Refresh(const std::string& name); | ||||||
|  | 
 | ||||||
|  |    static std::shared_ptr<PlacefileManager> Instance(); | ||||||
|  | 
 | ||||||
|  | signals: | ||||||
|  |    void PlacefilesInitialized(); | ||||||
|  |    void PlacefileEnabled(const std::string& name, bool enabled); | ||||||
|  |    void PlacefileRemoved(const std::string& name); | ||||||
|  |    void PlacefileRenamed(const std::string& oldName, | ||||||
|  |                          const std::string& newName); | ||||||
|  |    void PlacefileUpdated(const std::string& name); | ||||||
|  | 
 | ||||||
|  | private: | ||||||
|  |    class Impl; | ||||||
|  |    std::unique_ptr<Impl> p; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | } // namespace manager
 | ||||||
|  | } // namespace qt
 | ||||||
|  | } // namespace scwx
 | ||||||
|  | @ -1,13 +1,14 @@ | ||||||
| #include <scwx/qt/manager/resource_manager.hpp> | #include <scwx/qt/manager/resource_manager.hpp> | ||||||
|  | #include <scwx/qt/manager/font_manager.hpp> | ||||||
| #include <scwx/qt/config/county_database.hpp> | #include <scwx/qt/config/county_database.hpp> | ||||||
| #include <scwx/qt/model/imgui_context_model.hpp> | #include <scwx/qt/model/imgui_context_model.hpp> | ||||||
| #include <scwx/qt/util/font.hpp> |  | ||||||
| #include <scwx/qt/util/texture_atlas.hpp> | #include <scwx/qt/util/texture_atlas.hpp> | ||||||
| #include <scwx/util/logger.hpp> | #include <scwx/util/logger.hpp> | ||||||
| 
 | 
 | ||||||
| #include <imgui.h> | #include <execution> | ||||||
|  | #include <mutex> | ||||||
| 
 | 
 | ||||||
| #include <QFontDatabase> | #include <imgui.h> | ||||||
| 
 | 
 | ||||||
| namespace scwx | namespace scwx | ||||||
| { | { | ||||||
|  | @ -24,11 +25,10 @@ static const auto        logger_    = scwx::util::Logger::Create(logPrefix_); | ||||||
| static void LoadFonts(); | static void LoadFonts(); | ||||||
| static void LoadTextures(); | static void LoadTextures(); | ||||||
| 
 | 
 | ||||||
| static const std::unordered_map<types::Font, std::string> fontNames_ { | static const std::vector<std::pair<types::Font, std::string>> fontNames_ { | ||||||
|    {types::Font::din1451alt, ":/res/fonts/din1451alt.ttf"}, |    {types::Font::din1451alt, ":/res/fonts/din1451alt.ttf"}, | ||||||
|    {types::Font::din1451alt_g, ":/res/fonts/din1451alt_g.ttf"}}; |    {types::Font::din1451alt_g, ":/res/fonts/din1451alt_g.ttf"}, | ||||||
| 
 |    {types::Font::Inconsolata_Regular, ":/res/fonts/Inconsolata-Regular.ttf"}}; | ||||||
| static std::unordered_map<types::Font, int> fontIds_ {}; |  | ||||||
| 
 | 
 | ||||||
| void Initialize() | void Initialize() | ||||||
| { | { | ||||||
|  | @ -40,29 +40,52 @@ void Initialize() | ||||||
| 
 | 
 | ||||||
| void Shutdown() {} | void Shutdown() {} | ||||||
| 
 | 
 | ||||||
| int FontId(types::Font font) | std::shared_ptr<boost::gil::rgba8_image_t> | ||||||
|  | LoadImageResource(const std::string& urlString) | ||||||
| { | { | ||||||
|    auto it = fontIds_.find(font); |    util::TextureAtlas& textureAtlas = util::TextureAtlas::Instance(); | ||||||
|    if (it != fontIds_.cend()) |    return textureAtlas.CacheTexture(urlString, urlString); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | std::vector<std::shared_ptr<boost::gil::rgba8_image_t>> | ||||||
|  | LoadImageResources(const std::vector<std::string>& urlStrings) | ||||||
|  | { | ||||||
|  |    std::mutex                                              m {}; | ||||||
|  |    std::vector<std::shared_ptr<boost::gil::rgba8_image_t>> images {}; | ||||||
|  | 
 | ||||||
|  |    std::for_each(std::execution::par_unseq, | ||||||
|  |                  urlStrings.begin(), | ||||||
|  |                  urlStrings.end(), | ||||||
|  |                  [&](auto& urlString) | ||||||
|  |                  { | ||||||
|  |                     auto image = LoadImageResource(urlString); | ||||||
|  | 
 | ||||||
|  |                     if (image != nullptr) | ||||||
|  |                     { | ||||||
|  |                        std::unique_lock lock {m}; | ||||||
|  |                        images.emplace_back(std::move(image)); | ||||||
|  |                     } | ||||||
|  |                  }); | ||||||
|  | 
 | ||||||
|  |    if (!images.empty()) | ||||||
|    { |    { | ||||||
|       return it->second; |       util::TextureAtlas& textureAtlas = util::TextureAtlas::Instance(); | ||||||
|  |       textureAtlas.BuildAtlas(2048, 2048); | ||||||
|    } |    } | ||||||
|    return -1; | 
 | ||||||
|  |    return images; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static void LoadFonts() | static void LoadFonts() | ||||||
| { | { | ||||||
|  |    auto& fontManager = FontManager::Instance(); | ||||||
|  | 
 | ||||||
|    for (auto& fontName : fontNames_) |    for (auto& fontName : fontNames_) | ||||||
|    { |    { | ||||||
|       int fontId = QFontDatabase::addApplicationFont( |       fontManager.LoadApplicationFont(fontName.first, fontName.second); | ||||||
|          QString::fromStdString(fontName.second)); |  | ||||||
|       fontIds_.emplace(fontName.first, fontId); |  | ||||||
| 
 |  | ||||||
|       util::Font::Create(fontName.second); |  | ||||||
|    } |    } | ||||||
| 
 | 
 | ||||||
|    ImFontAtlas* fontAtlas = model::ImGuiContextModel::Instance().font_atlas(); |    fontManager.InitializeFonts(); | ||||||
|    fontAtlas->AddFontDefault(); |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static void LoadTextures() | static void LoadTextures() | ||||||
|  | @ -72,7 +95,7 @@ static void LoadTextures() | ||||||
|                                 ":/res/textures/lines/default-1x7.png"); |                                 ":/res/textures/lines/default-1x7.png"); | ||||||
|    textureAtlas.RegisterTexture("lines/test-pattern", |    textureAtlas.RegisterTexture("lines/test-pattern", | ||||||
|                                 ":/res/textures/lines/test-pattern.png"); |                                 ":/res/textures/lines/test-pattern.png"); | ||||||
|    textureAtlas.BuildAtlas(8, 8); |    textureAtlas.BuildAtlas(2048, 2048); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| } // namespace ResourceManager
 | } // namespace ResourceManager
 | ||||||
|  |  | ||||||
|  | @ -2,6 +2,10 @@ | ||||||
| 
 | 
 | ||||||
| #include <scwx/qt/types/font_types.hpp> | #include <scwx/qt/types/font_types.hpp> | ||||||
| 
 | 
 | ||||||
|  | #include <vector> | ||||||
|  | 
 | ||||||
|  | #include <boost/gil/typedefs.hpp> | ||||||
|  | 
 | ||||||
| namespace scwx | namespace scwx | ||||||
| { | { | ||||||
| namespace qt | namespace qt | ||||||
|  | @ -14,7 +18,10 @@ namespace ResourceManager | ||||||
| void Initialize(); | void Initialize(); | ||||||
| void Shutdown(); | void Shutdown(); | ||||||
| 
 | 
 | ||||||
| int FontId(types::Font font); | std::shared_ptr<boost::gil::rgba8_image_t> | ||||||
|  | LoadImageResource(const std::string& urlString); | ||||||
|  | std::vector<std::shared_ptr<boost::gil::rgba8_image_t>> | ||||||
|  | LoadImageResources(const std::vector<std::string>& urlStrings); | ||||||
| 
 | 
 | ||||||
| } // namespace ResourceManager
 | } // namespace ResourceManager
 | ||||||
| } // namespace manager
 | } // namespace manager
 | ||||||
|  |  | ||||||
|  | @ -1,5 +1,9 @@ | ||||||
| #include <scwx/qt/manager/settings_manager.hpp> | #include <scwx/qt/manager/settings_manager.hpp> | ||||||
| #include <scwx/qt/map/map_provider.hpp> | #include <scwx/qt/map/map_provider.hpp> | ||||||
|  | #include <scwx/qt/settings/general_settings.hpp> | ||||||
|  | #include <scwx/qt/settings/map_settings.hpp> | ||||||
|  | #include <scwx/qt/settings/palette_settings.hpp> | ||||||
|  | #include <scwx/qt/settings/text_settings.hpp> | ||||||
| #include <scwx/qt/settings/ui_settings.hpp> | #include <scwx/qt/settings/ui_settings.hpp> | ||||||
| #include <scwx/qt/util/json.hpp> | #include <scwx/qt/util/json.hpp> | ||||||
| #include <scwx/util/logger.hpp> | #include <scwx/util/logger.hpp> | ||||||
|  | @ -17,21 +21,33 @@ namespace qt | ||||||
| { | { | ||||||
| namespace manager | namespace manager | ||||||
| { | { | ||||||
| namespace SettingsManager |  | ||||||
| { |  | ||||||
| 
 | 
 | ||||||
| static const std::string logPrefix_ = "scwx::qt::manager::settings_manager"; | static const std::string logPrefix_ = "scwx::qt::manager::settings_manager"; | ||||||
| static const auto        logger_    = scwx::util::Logger::Create(logPrefix_); | static const auto        logger_    = scwx::util::Logger::Create(logPrefix_); | ||||||
| 
 | 
 | ||||||
| static boost::json::value ConvertSettingsToJson(); | class SettingsManager::Impl | ||||||
| static void               GenerateDefaultSettings(); | { | ||||||
| static bool               LoadSettings(const boost::json::object& settingsJson); | public: | ||||||
| static void               ValidateSettings(); |    explicit Impl(SettingsManager* self) : self_ {self} {} | ||||||
|  |    ~Impl() = default; | ||||||
| 
 | 
 | ||||||
| static bool        initialized_ {false}; |    void ValidateSettings(); | ||||||
| static std::string settingsPath_ {}; |  | ||||||
| 
 | 
 | ||||||
| void Initialize() |    static boost::json::value ConvertSettingsToJson(); | ||||||
|  |    static void               GenerateDefaultSettings(); | ||||||
|  |    static bool LoadSettings(const boost::json::object& settingsJson); | ||||||
|  | 
 | ||||||
|  |    SettingsManager* self_; | ||||||
|  | 
 | ||||||
|  |    bool        initialized_ {false}; | ||||||
|  |    std::string settingsPath_ {}; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | SettingsManager::SettingsManager() : p(std::make_unique<Impl>(this)) {} | ||||||
|  | 
 | ||||||
|  | SettingsManager::~SettingsManager() {}; | ||||||
|  | 
 | ||||||
|  | void SettingsManager::Initialize() | ||||||
| { | { | ||||||
|    std::string appDataPath { |    std::string appDataPath { | ||||||
|       QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) |       QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) | ||||||
|  | @ -46,14 +62,14 @@ void Initialize() | ||||||
|       } |       } | ||||||
|    } |    } | ||||||
| 
 | 
 | ||||||
|    settingsPath_ = appDataPath + "/settings.json"; |    p->settingsPath_ = appDataPath + "/settings.json"; | ||||||
|    initialized_  = true; |    p->initialized_  = true; | ||||||
| 
 | 
 | ||||||
|    ReadSettings(settingsPath_); |    ReadSettings(p->settingsPath_); | ||||||
|    ValidateSettings(); |    p->ValidateSettings(); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void ReadSettings(const std::string& settingsPath) | void SettingsManager::ReadSettings(const std::string& settingsPath) | ||||||
| { | { | ||||||
|    boost::json::value settingsJson = nullptr; |    boost::json::value settingsJson = nullptr; | ||||||
| 
 | 
 | ||||||
|  | @ -64,39 +80,41 @@ void ReadSettings(const std::string& settingsPath) | ||||||
| 
 | 
 | ||||||
|    if (settingsJson == nullptr || !settingsJson.is_object()) |    if (settingsJson == nullptr || !settingsJson.is_object()) | ||||||
|    { |    { | ||||||
|       GenerateDefaultSettings(); |       Impl::GenerateDefaultSettings(); | ||||||
|       settingsJson = ConvertSettingsToJson(); |       settingsJson = Impl::ConvertSettingsToJson(); | ||||||
|       util::json::WriteJsonFile(settingsPath, settingsJson); |       util::json::WriteJsonFile(settingsPath, settingsJson); | ||||||
|    } |    } | ||||||
|    else |    else | ||||||
|    { |    { | ||||||
|       bool jsonDirty = LoadSettings(settingsJson.as_object()); |       bool jsonDirty = Impl::LoadSettings(settingsJson.as_object()); | ||||||
| 
 | 
 | ||||||
|       if (jsonDirty) |       if (jsonDirty) | ||||||
|       { |       { | ||||||
|          settingsJson = ConvertSettingsToJson(); |          settingsJson = Impl::ConvertSettingsToJson(); | ||||||
|          util::json::WriteJsonFile(settingsPath, settingsJson); |          util::json::WriteJsonFile(settingsPath, settingsJson); | ||||||
|       } |       } | ||||||
|    }; |    }; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void SaveSettings() | void SettingsManager::SaveSettings() | ||||||
| { | { | ||||||
|    if (initialized_) |    if (p->initialized_) | ||||||
|    { |    { | ||||||
|       logger_->info("Saving settings"); |       logger_->info("Saving settings"); | ||||||
| 
 | 
 | ||||||
|       boost::json::value settingsJson = ConvertSettingsToJson(); |       boost::json::value settingsJson = Impl::ConvertSettingsToJson(); | ||||||
|       util::json::WriteJsonFile(settingsPath_, settingsJson); |       util::json::WriteJsonFile(p->settingsPath_, settingsJson); | ||||||
|  | 
 | ||||||
|  |       Q_EMIT SettingsSaved(); | ||||||
|    } |    } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void Shutdown() | void SettingsManager::Shutdown() | ||||||
| { | { | ||||||
|    bool dataChanged = false; |    bool dataChanged = false; | ||||||
| 
 | 
 | ||||||
|    dataChanged |= general_settings().Shutdown(); |    dataChanged |= settings::GeneralSettings::Instance().Shutdown(); | ||||||
|    dataChanged |= map_settings().Shutdown(); |    dataChanged |= settings::MapSettings::Instance().Shutdown(); | ||||||
|    dataChanged |= settings::UiSettings::Instance().Shutdown(); |    dataChanged |= settings::UiSettings::Instance().Shutdown(); | ||||||
| 
 | 
 | ||||||
|    if (dataChanged) |    if (dataChanged) | ||||||
|  | @ -105,67 +123,53 @@ void Shutdown() | ||||||
|    } |    } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| settings::GeneralSettings& general_settings() | boost::json::value SettingsManager::Impl::ConvertSettingsToJson() | ||||||
| { |  | ||||||
|    static settings::GeneralSettings generalSettings_; |  | ||||||
|    return generalSettings_; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| settings::MapSettings& map_settings() |  | ||||||
| { |  | ||||||
|    static settings::MapSettings mapSettings_; |  | ||||||
|    return mapSettings_; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| settings::PaletteSettings& palette_settings() |  | ||||||
| { |  | ||||||
|    static settings::PaletteSettings paletteSettings_; |  | ||||||
|    return paletteSettings_; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| static boost::json::value ConvertSettingsToJson() |  | ||||||
| { | { | ||||||
|    boost::json::object settingsJson; |    boost::json::object settingsJson; | ||||||
| 
 | 
 | ||||||
|    general_settings().WriteJson(settingsJson); |    settings::GeneralSettings::Instance().WriteJson(settingsJson); | ||||||
|    map_settings().WriteJson(settingsJson); |    settings::MapSettings::Instance().WriteJson(settingsJson); | ||||||
|    palette_settings().WriteJson(settingsJson); |    settings::PaletteSettings::Instance().WriteJson(settingsJson); | ||||||
|  |    settings::TextSettings::Instance().WriteJson(settingsJson); | ||||||
|    settings::UiSettings::Instance().WriteJson(settingsJson); |    settings::UiSettings::Instance().WriteJson(settingsJson); | ||||||
| 
 | 
 | ||||||
|    return settingsJson; |    return settingsJson; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static void GenerateDefaultSettings() | void SettingsManager::Impl::GenerateDefaultSettings() | ||||||
| { | { | ||||||
|    logger_->info("Generating default settings"); |    logger_->info("Generating default settings"); | ||||||
| 
 | 
 | ||||||
|    general_settings().SetDefaults(); |    settings::GeneralSettings::Instance().SetDefaults(); | ||||||
|    map_settings().SetDefaults(); |    settings::MapSettings::Instance().SetDefaults(); | ||||||
|    palette_settings().SetDefaults(); |    settings::PaletteSettings::Instance().SetDefaults(); | ||||||
|  |    settings::TextSettings::Instance().SetDefaults(); | ||||||
|    settings::UiSettings::Instance().SetDefaults(); |    settings::UiSettings::Instance().SetDefaults(); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static bool LoadSettings(const boost::json::object& settingsJson) | bool SettingsManager::Impl::LoadSettings( | ||||||
|  |    const boost::json::object& settingsJson) | ||||||
| { | { | ||||||
|    logger_->info("Loading settings"); |    logger_->info("Loading settings"); | ||||||
| 
 | 
 | ||||||
|    bool jsonDirty = false; |    bool jsonDirty = false; | ||||||
| 
 | 
 | ||||||
|    jsonDirty |= !general_settings().ReadJson(settingsJson); |    jsonDirty |= !settings::GeneralSettings::Instance().ReadJson(settingsJson); | ||||||
|    jsonDirty |= !map_settings().ReadJson(settingsJson); |    jsonDirty |= !settings::MapSettings::Instance().ReadJson(settingsJson); | ||||||
|    jsonDirty |= !palette_settings().ReadJson(settingsJson); |    jsonDirty |= !settings::PaletteSettings::Instance().ReadJson(settingsJson); | ||||||
|  |    jsonDirty |= !settings::TextSettings::Instance().ReadJson(settingsJson); | ||||||
|    jsonDirty |= !settings::UiSettings::Instance().ReadJson(settingsJson); |    jsonDirty |= !settings::UiSettings::Instance().ReadJson(settingsJson); | ||||||
| 
 | 
 | ||||||
|    return jsonDirty; |    return jsonDirty; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static void ValidateSettings() | void SettingsManager::Impl::ValidateSettings() | ||||||
| { | { | ||||||
|    logger_->debug("Validating settings"); |    logger_->debug("Validating settings"); | ||||||
| 
 | 
 | ||||||
|    bool settingsChanged = false; |    bool settingsChanged = false; | ||||||
| 
 | 
 | ||||||
|    auto& generalSettings = general_settings(); |    auto& generalSettings = settings::GeneralSettings::Instance(); | ||||||
| 
 | 
 | ||||||
|    // Validate map provider
 |    // Validate map provider
 | ||||||
|    std::string mapProviderName = generalSettings.map_provider().GetValue(); |    std::string mapProviderName = generalSettings.map_provider().GetValue(); | ||||||
|  | @ -196,11 +200,16 @@ static void ValidateSettings() | ||||||
| 
 | 
 | ||||||
|    if (settingsChanged) |    if (settingsChanged) | ||||||
|    { |    { | ||||||
|       SaveSettings(); |       self_->SaveSettings(); | ||||||
|    } |    } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| } // namespace SettingsManager
 | SettingsManager& SettingsManager::Instance() | ||||||
|  | { | ||||||
|  |    static SettingsManager instance_ {}; | ||||||
|  |    return instance_; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| } // namespace manager
 | } // namespace manager
 | ||||||
| } // namespace qt
 | } // namespace qt
 | ||||||
| } // namespace scwx
 | } // namespace scwx
 | ||||||
|  |  | ||||||
|  | @ -1,8 +1,9 @@ | ||||||
| #pragma once | #pragma once | ||||||
| 
 | 
 | ||||||
| #include <scwx/qt/settings/general_settings.hpp> | #include <string> | ||||||
| #include <scwx/qt/settings/map_settings.hpp> | #include <memory> | ||||||
| #include <scwx/qt/settings/palette_settings.hpp> | 
 | ||||||
|  | #include <QObject> | ||||||
| 
 | 
 | ||||||
| namespace scwx | namespace scwx | ||||||
| { | { | ||||||
|  | @ -10,19 +11,31 @@ namespace qt | ||||||
| { | { | ||||||
| namespace manager | namespace manager | ||||||
| { | { | ||||||
| namespace SettingsManager | 
 | ||||||
|  | class SettingsManager : public QObject | ||||||
| { | { | ||||||
|  |    Q_OBJECT | ||||||
|  |    Q_DISABLE_COPY_MOVE(SettingsManager) | ||||||
| 
 | 
 | ||||||
| void Initialize(); | public: | ||||||
| void ReadSettings(const std::string& settingsPath); |    explicit SettingsManager(); | ||||||
| void SaveSettings(); |    ~SettingsManager(); | ||||||
| void Shutdown(); |  | ||||||
| 
 | 
 | ||||||
| settings::GeneralSettings& general_settings(); |    void Initialize(); | ||||||
| settings::MapSettings&     map_settings(); |    void ReadSettings(const std::string& settingsPath); | ||||||
| settings::PaletteSettings& palette_settings(); |    void SaveSettings(); | ||||||
|  |    void Shutdown(); | ||||||
|  | 
 | ||||||
|  |    static SettingsManager& Instance(); | ||||||
|  | 
 | ||||||
|  | signals: | ||||||
|  |    void SettingsSaved(); | ||||||
|  | 
 | ||||||
|  | private: | ||||||
|  |    class Impl; | ||||||
|  |    std::unique_ptr<Impl> p; | ||||||
|  | }; | ||||||
| 
 | 
 | ||||||
| } // namespace SettingsManager
 |  | ||||||
| } // namespace manager
 | } // namespace manager
 | ||||||
| } // namespace qt
 | } // namespace qt
 | ||||||
| } // namespace scwx
 | } // namespace scwx
 | ||||||
|  |  | ||||||
|  | @ -2,7 +2,7 @@ | ||||||
| 
 | 
 | ||||||
| #include <scwx/qt/manager/timeline_manager.hpp> | #include <scwx/qt/manager/timeline_manager.hpp> | ||||||
| #include <scwx/qt/manager/radar_product_manager.hpp> | #include <scwx/qt/manager/radar_product_manager.hpp> | ||||||
| #include <scwx/qt/manager/settings_manager.hpp> | #include <scwx/qt/settings/general_settings.hpp> | ||||||
| #include <scwx/util/logger.hpp> | #include <scwx/util/logger.hpp> | ||||||
| #include <scwx/util/map.hpp> | #include <scwx/util/map.hpp> | ||||||
| #include <scwx/util/time.hpp> | #include <scwx/util/time.hpp> | ||||||
|  | @ -39,7 +39,7 @@ class TimelineManager::Impl | ||||||
| public: | public: | ||||||
|    explicit Impl(TimelineManager* self) : self_ {self} |    explicit Impl(TimelineManager* self) : self_ {self} | ||||||
|    { |    { | ||||||
|       auto& generalSettings = SettingsManager::general_settings(); |       auto& generalSettings = settings::GeneralSettings::Instance(); | ||||||
| 
 | 
 | ||||||
|       loopDelay_ = |       loopDelay_ = | ||||||
|          std::chrono::milliseconds(generalSettings.loop_delay().GetValue()); |          std::chrono::milliseconds(generalSettings.loop_delay().GetValue()); | ||||||
|  | @ -281,7 +281,12 @@ void TimelineManager::Impl::RadarSweepMonitorReset() | ||||||
| void TimelineManager::Impl::RadarSweepMonitorWait( | void TimelineManager::Impl::RadarSweepMonitorWait( | ||||||
|    std::unique_lock<std::mutex>& lock) |    std::unique_lock<std::mutex>& lock) | ||||||
| { | { | ||||||
|    radarSweepMonitorCondition_.wait_for(lock, kRadarSweepMonitorTimeout_); |    std::cv_status status = | ||||||
|  |       radarSweepMonitorCondition_.wait_for(lock, kRadarSweepMonitorTimeout_); | ||||||
|  |    if (status == std::cv_status::timeout) | ||||||
|  |    { | ||||||
|  |       logger_->debug("Radar sweep monitor timed out"); | ||||||
|  |    } | ||||||
|    radarSweepMonitorActive_ = false; |    radarSweepMonitorActive_ = false; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1,6 +1,7 @@ | ||||||
| #include <scwx/qt/map/alert_layer.hpp> | #include <scwx/qt/map/alert_layer.hpp> | ||||||
| #include <scwx/qt/manager/settings_manager.hpp> |  | ||||||
| #include <scwx/qt/manager/text_event_manager.hpp> | #include <scwx/qt/manager/text_event_manager.hpp> | ||||||
|  | #include <scwx/qt/settings/palette_settings.hpp> | ||||||
|  | #include <scwx/qt/types/layer_types.hpp> | ||||||
| #include <scwx/qt/util/color.hpp> | #include <scwx/qt/util/color.hpp> | ||||||
| #include <scwx/util/logger.hpp> | #include <scwx/util/logger.hpp> | ||||||
| #include <scwx/util/threads.hpp> | #include <scwx/util/threads.hpp> | ||||||
|  | @ -22,10 +23,11 @@ namespace map | ||||||
| static const std::string logPrefix_ = "scwx::qt::map::alert_layer"; | static const std::string logPrefix_ = "scwx::qt::map::alert_layer"; | ||||||
| static const auto        logger_    = scwx::util::Logger::Create(logPrefix_); | static const auto        logger_    = scwx::util::Logger::Create(logPrefix_); | ||||||
| 
 | 
 | ||||||
| static void AddAlertLayer(std::shared_ptr<QMapLibreGL::Map> map, | static std::vector<std::string> | ||||||
|                           awips::Phenomenon                 phenomenon, | AddAlertLayer(std::shared_ptr<QMapLibreGL::Map> map, | ||||||
|                           bool                              alertActive, |               awips::Phenomenon                 phenomenon, | ||||||
|                           const QString&                    beforeLayer); |               bool                              alertActive, | ||||||
|  |               const QString&                    beforeLayer); | ||||||
| static QMapLibreGL::Feature | static QMapLibreGL::Feature | ||||||
| CreateFeature(const awips::CodedLocation& codedLocation); | CreateFeature(const awips::CodedLocation& codedLocation); | ||||||
| static QMapLibreGL::Coordinate | static QMapLibreGL::Coordinate | ||||||
|  | @ -132,56 +134,36 @@ public: | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| AlertLayer::AlertLayer(std::shared_ptr<MapContext> context) : | AlertLayer::AlertLayer(std::shared_ptr<MapContext> context) : | ||||||
|     DrawLayer(context), p(std::make_unique<AlertLayerImpl>(context)) |     p(std::make_unique<AlertLayerImpl>(context)) | ||||||
| { | { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| AlertLayer::~AlertLayer() = default; | AlertLayer::~AlertLayer() = default; | ||||||
| 
 | 
 | ||||||
| void AlertLayer::Initialize() | std::vector<std::string> AlertLayer::AddLayers(awips::Phenomenon  phenomenon, | ||||||
|  |                                                const std::string& before) | ||||||
| { | { | ||||||
|    logger_->debug("Initialize()"); |    logger_->debug("AddLayers(): {}", awips::GetPhenomenonCode(phenomenon)); | ||||||
| 
 | 
 | ||||||
|    DrawLayer::Initialize(); |    std::vector<std::string> layers {}; | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void AlertLayer::Render(const QMapLibreGL::CustomLayerRenderParameters& params) |  | ||||||
| { |  | ||||||
|    gl::OpenGLFunctions& gl = context()->gl(); |  | ||||||
| 
 |  | ||||||
|    DrawLayer::Render(params); |  | ||||||
| 
 |  | ||||||
|    SCWX_GL_CHECK_ERROR(); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void AlertLayer::Deinitialize() |  | ||||||
| { |  | ||||||
|    logger_->debug("Deinitialize()"); |  | ||||||
| 
 |  | ||||||
|    DrawLayer::Deinitialize(); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void AlertLayer::AddLayers(const std::string& before) |  | ||||||
| { |  | ||||||
|    logger_->debug("AddLayers()"); |  | ||||||
| 
 | 
 | ||||||
|    auto map = p->context_->map().lock(); |    auto map = p->context_->map().lock(); | ||||||
|    if (map == nullptr) |    if (map == nullptr) | ||||||
|    { |    { | ||||||
|       return; |       return layers; | ||||||
|    } |    } | ||||||
| 
 | 
 | ||||||
|    const QString beforeLayer {QString::fromStdString(before)}; |    const QString beforeLayer {QString::fromStdString(before)}; | ||||||
| 
 | 
 | ||||||
|    // Add/update GeoJSON sources and create layers
 |    // Add/update GeoJSON sources and create layers
 | ||||||
|    for (auto& phenomenon : kAlertPhenomena_) |    for (bool alertActive : {false, true}) | ||||||
|    { |    { | ||||||
|       for (bool alertActive : {false, true}) |       p->UpdateSource(phenomenon, alertActive); | ||||||
|       { |       auto newLayers = AddAlertLayer(map, phenomenon, alertActive, beforeLayer); | ||||||
|          p->UpdateSource(phenomenon, alertActive); |       layers.insert(layers.end(), newLayers.cbegin(), newLayers.cend()); | ||||||
|          AddAlertLayer(map, phenomenon, alertActive, beforeLayer); |  | ||||||
|       } |  | ||||||
|    } |    } | ||||||
|  | 
 | ||||||
|  |    return layers; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| std::list<QMapLibreGL::Feature>* | std::list<QMapLibreGL::Feature>* | ||||||
|  | @ -388,21 +370,25 @@ std::shared_ptr<AlertLayerHandler> AlertLayerHandler::Instance() | ||||||
|    return alertLayerHandler; |    return alertLayerHandler; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static void AddAlertLayer(std::shared_ptr<QMapLibreGL::Map> map, | static std::vector<std::string> | ||||||
|                           awips::Phenomenon                 phenomenon, | AddAlertLayer(std::shared_ptr<QMapLibreGL::Map> map, | ||||||
|                           bool                              alertActive, |               awips::Phenomenon                 phenomenon, | ||||||
|                           const QString&                    beforeLayer) |               bool                              alertActive, | ||||||
|  |               const QString&                    beforeLayer) | ||||||
| { | { | ||||||
|    settings::PaletteSettings& paletteSettings = |    settings::PaletteSettings& paletteSettings = | ||||||
|       manager::SettingsManager::palette_settings(); |       settings::PaletteSettings::Instance(); | ||||||
|  | 
 | ||||||
|  |    QString layerPrefix = QString::fromStdString( | ||||||
|  |       types::GetLayerName(types::LayerType::Alert, phenomenon)); | ||||||
| 
 | 
 | ||||||
|    QString sourceId     = GetSourceId(phenomenon, alertActive); |    QString sourceId     = GetSourceId(phenomenon, alertActive); | ||||||
|    QString idSuffix     = GetSuffix(phenomenon, alertActive); |    QString idSuffix     = GetSuffix(phenomenon, alertActive); | ||||||
|    auto    outlineColor = util::color::ToRgba8PixelT( |    auto    outlineColor = util::color::ToRgba8PixelT( | ||||||
|       paletteSettings.alert_color(phenomenon, alertActive).GetValue()); |       paletteSettings.alert_color(phenomenon, alertActive).GetValue()); | ||||||
| 
 | 
 | ||||||
|    QString bgLayerId = QString("alertPolygonLayerBg-%1").arg(idSuffix); |    QString bgLayerId = QString("%1::bg-%2").arg(layerPrefix).arg(idSuffix); | ||||||
|    QString fgLayerId = QString("alertPolygonLayerFg-%1").arg(idSuffix); |    QString fgLayerId = QString("%1::fg-%2").arg(layerPrefix).arg(idSuffix); | ||||||
| 
 | 
 | ||||||
|    if (map->layerExists(bgLayerId)) |    if (map->layerExists(bgLayerId)) | ||||||
|    { |    { | ||||||
|  | @ -436,6 +422,8 @@ static void AddAlertLayer(std::shared_ptr<QMapLibreGL::Map> map, | ||||||
|                             .arg(outlineColor[3])); |                             .arg(outlineColor[3])); | ||||||
|    map->setPaintProperty(fgLayerId, "line-opacity", QString("%1").arg(opacity)); |    map->setPaintProperty(fgLayerId, "line-opacity", QString("%1").arg(opacity)); | ||||||
|    map->setPaintProperty(fgLayerId, "line-width", "3"); |    map->setPaintProperty(fgLayerId, "line-width", "3"); | ||||||
|  | 
 | ||||||
|  |    return {bgLayerId.toStdString(), fgLayerId.toStdString()}; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static QMapLibreGL::Feature | static QMapLibreGL::Feature | ||||||
|  |  | ||||||
|  | @ -1,6 +1,11 @@ | ||||||
| #pragma once | #pragma once | ||||||
| 
 | 
 | ||||||
| #include <scwx/qt/map/draw_layer.hpp> | #include <scwx/awips/phenomenon.hpp> | ||||||
|  | #include <scwx/qt/map/map_context.hpp> | ||||||
|  | 
 | ||||||
|  | #include <memory> | ||||||
|  | #include <string> | ||||||
|  | #include <vector> | ||||||
| 
 | 
 | ||||||
| namespace scwx | namespace scwx | ||||||
| { | { | ||||||
|  | @ -11,17 +16,14 @@ namespace map | ||||||
| 
 | 
 | ||||||
| class AlertLayerImpl; | class AlertLayerImpl; | ||||||
| 
 | 
 | ||||||
| class AlertLayer : public DrawLayer | class AlertLayer | ||||||
| { | { | ||||||
| public: | public: | ||||||
|    explicit AlertLayer(std::shared_ptr<MapContext> context); |    explicit AlertLayer(std::shared_ptr<MapContext> context); | ||||||
|    ~AlertLayer(); |    ~AlertLayer(); | ||||||
| 
 | 
 | ||||||
|    void Initialize() override final; |    std::vector<std::string> AddLayers(awips::Phenomenon  phenomenon, | ||||||
|    void Render(const QMapLibreGL::CustomLayerRenderParameters&) override final; |                                       const std::string& before = {}); | ||||||
|    void Deinitialize() override final; |  | ||||||
| 
 |  | ||||||
|    void AddLayers(const std::string& before = {}); |  | ||||||
| 
 | 
 | ||||||
| private: | private: | ||||||
|    std::unique_ptr<AlertLayerImpl> p; |    std::unique_ptr<AlertLayerImpl> p; | ||||||
|  |  | ||||||
|  | @ -24,9 +24,11 @@ public: | ||||||
|    std::shared_ptr<MapContext>                      context_; |    std::shared_ptr<MapContext>                      context_; | ||||||
|    std::vector<std::shared_ptr<gl::draw::DrawItem>> drawList_; |    std::vector<std::shared_ptr<gl::draw::DrawItem>> drawList_; | ||||||
|    GLuint                                           textureAtlas_; |    GLuint                                           textureAtlas_; | ||||||
|  | 
 | ||||||
|  |    std::uint64_t textureAtlasBuildCount_ {}; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| DrawLayer::DrawLayer(std::shared_ptr<MapContext> context) : | DrawLayer::DrawLayer(const std::shared_ptr<MapContext>& context) : | ||||||
|     GenericLayer(context), p(std::make_unique<DrawLayerImpl>(context)) |     GenericLayer(context), p(std::make_unique<DrawLayerImpl>(context)) | ||||||
| { | { | ||||||
| } | } | ||||||
|  | @ -45,14 +47,23 @@ void DrawLayer::Initialize() | ||||||
| void DrawLayer::Render(const QMapLibreGL::CustomLayerRenderParameters& params) | void DrawLayer::Render(const QMapLibreGL::CustomLayerRenderParameters& params) | ||||||
| { | { | ||||||
|    gl::OpenGLFunctions& gl = p->context_->gl(); |    gl::OpenGLFunctions& gl = p->context_->gl(); | ||||||
|  |    p->textureAtlas_        = p->context_->GetTextureAtlas(); | ||||||
|  | 
 | ||||||
|  |    // Determine if the texture atlas changed since last render
 | ||||||
|  |    std::uint64_t newTextureAtlasBuildCount = | ||||||
|  |       p->context_->texture_buffer_count(); | ||||||
|  |    bool textureAtlasChanged = | ||||||
|  |       newTextureAtlasBuildCount != p->textureAtlasBuildCount_; | ||||||
| 
 | 
 | ||||||
|    gl.glActiveTexture(GL_TEXTURE0); |    gl.glActiveTexture(GL_TEXTURE0); | ||||||
|    gl.glBindTexture(GL_TEXTURE_2D, p->textureAtlas_); |    gl.glBindTexture(GL_TEXTURE_2D_ARRAY, p->textureAtlas_); | ||||||
| 
 | 
 | ||||||
|    for (auto& item : p->drawList_) |    for (auto& item : p->drawList_) | ||||||
|    { |    { | ||||||
|       item->Render(params); |       item->Render(params, textureAtlasChanged); | ||||||
|    } |    } | ||||||
|  | 
 | ||||||
|  |    p->textureAtlasBuildCount_ = newTextureAtlasBuildCount; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void DrawLayer::Deinitialize() | void DrawLayer::Deinitialize() | ||||||
|  | @ -65,7 +76,31 @@ void DrawLayer::Deinitialize() | ||||||
|    } |    } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void DrawLayer::AddDrawItem(std::shared_ptr<gl::draw::DrawItem> drawItem) | bool DrawLayer::RunMousePicking( | ||||||
|  |    const QMapLibreGL::CustomLayerRenderParameters& params, | ||||||
|  |    const QPointF&                                  mouseLocalPos, | ||||||
|  |    const QPointF&                                  mouseGlobalPos, | ||||||
|  |    const glm::vec2&                                mouseCoords) | ||||||
|  | { | ||||||
|  |    bool itemPicked = false; | ||||||
|  | 
 | ||||||
|  |    // For each draw item in the draw list in reverse
 | ||||||
|  |    for (auto it = p->drawList_.rbegin(); it != p->drawList_.rend(); ++it) | ||||||
|  |    { | ||||||
|  |       // Run mouse picking on each draw item
 | ||||||
|  |       if ((*it)->RunMousePicking( | ||||||
|  |              params, mouseLocalPos, mouseGlobalPos, mouseCoords)) | ||||||
|  |       { | ||||||
|  |          // If a draw item was picked, don't process additional items
 | ||||||
|  |          itemPicked = true; | ||||||
|  |          break; | ||||||
|  |       } | ||||||
|  |    } | ||||||
|  | 
 | ||||||
|  |    return itemPicked; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void DrawLayer::AddDrawItem(const std::shared_ptr<gl::draw::DrawItem>& drawItem) | ||||||
| { | { | ||||||
|    p->drawList_.push_back(drawItem); |    p->drawList_.push_back(drawItem); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -15,15 +15,22 @@ class DrawLayerImpl; | ||||||
| class DrawLayer : public GenericLayer | class DrawLayer : public GenericLayer | ||||||
| { | { | ||||||
| public: | public: | ||||||
|    explicit DrawLayer(std::shared_ptr<MapContext> context); |    explicit DrawLayer(const std::shared_ptr<MapContext>& context); | ||||||
|    virtual ~DrawLayer(); |    virtual ~DrawLayer(); | ||||||
| 
 | 
 | ||||||
|    virtual void Initialize(); |    virtual void Initialize() override; | ||||||
|    virtual void Render(const QMapLibreGL::CustomLayerRenderParameters&); |    virtual void | ||||||
|    virtual void Deinitialize(); |    Render(const QMapLibreGL::CustomLayerRenderParameters&) override; | ||||||
|  |    virtual void Deinitialize() override; | ||||||
|  | 
 | ||||||
|  |    virtual bool | ||||||
|  |    RunMousePicking(const QMapLibreGL::CustomLayerRenderParameters& params, | ||||||
|  |                    const QPointF&   mouseLocalPos, | ||||||
|  |                    const QPointF&   mouseGlobalPos, | ||||||
|  |                    const glm::vec2& mouseCoords) override; | ||||||
| 
 | 
 | ||||||
| protected: | protected: | ||||||
|    void AddDrawItem(std::shared_ptr<gl::draw::DrawItem> drawItem); |    void AddDrawItem(const std::shared_ptr<gl::draw::DrawItem>& drawItem); | ||||||
| 
 | 
 | ||||||
| private: | private: | ||||||
|    std::unique_ptr<DrawLayerImpl> p; |    std::unique_ptr<DrawLayerImpl> p; | ||||||
|  |  | ||||||
|  | @ -26,6 +26,16 @@ GenericLayer::GenericLayer(std::shared_ptr<MapContext> context) : | ||||||
| } | } | ||||||
| GenericLayer::~GenericLayer() = default; | GenericLayer::~GenericLayer() = default; | ||||||
| 
 | 
 | ||||||
|  | bool GenericLayer::RunMousePicking( | ||||||
|  |    const QMapLibreGL::CustomLayerRenderParameters& /* params */, | ||||||
|  |    const QPointF& /* mouseLocalPos */, | ||||||
|  |    const QPointF& /* mouseGlobalPos */, | ||||||
|  |    const glm::vec2& /* mousePos */) | ||||||
|  | { | ||||||
|  |    // By default, the layer has nothing to pick
 | ||||||
|  |    return false; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| std::shared_ptr<MapContext> GenericLayer::context() const | std::shared_ptr<MapContext> GenericLayer::context() const | ||||||
| { | { | ||||||
|    return p->context_; |    return p->context_; | ||||||
|  |  | ||||||
|  | @ -6,6 +6,7 @@ | ||||||
| 
 | 
 | ||||||
| #include <QObject> | #include <QObject> | ||||||
| #include <QMapLibreGL/QMapLibreGL> | #include <QMapLibreGL/QMapLibreGL> | ||||||
|  | #include <glm/gtc/type_ptr.hpp> | ||||||
| 
 | 
 | ||||||
| namespace scwx | namespace scwx | ||||||
| { | { | ||||||
|  | @ -28,6 +29,22 @@ public: | ||||||
|    virtual void Render(const QMapLibreGL::CustomLayerRenderParameters&) = 0; |    virtual void Render(const QMapLibreGL::CustomLayerRenderParameters&) = 0; | ||||||
|    virtual void Deinitialize()                                          = 0; |    virtual void Deinitialize()                                          = 0; | ||||||
| 
 | 
 | ||||||
|  |    /**
 | ||||||
|  |     * @brief Run mouse picking on the layer. | ||||||
|  |     * | ||||||
|  |     * @param [in] params Custom layer render parameters | ||||||
|  |     * @param [in] mouseLocalPos Mouse cursor widget position | ||||||
|  |     * @param [in] mouseGlobalPos Mouse cursor screen position | ||||||
|  |     * @param [in] mouseCoords Mouse cursor location in map screen coordinates | ||||||
|  |     * | ||||||
|  |     * @return true if a draw item was picked, otherwise false | ||||||
|  |     */ | ||||||
|  |    virtual bool | ||||||
|  |    RunMousePicking(const QMapLibreGL::CustomLayerRenderParameters& params, | ||||||
|  |                    const QPointF&   mouseLocalPos, | ||||||
|  |                    const QPointF&   mouseGlobalPos, | ||||||
|  |                    const glm::vec2& mouseCoords); | ||||||
|  | 
 | ||||||
| protected: | protected: | ||||||
|    std::shared_ptr<MapContext> context() const; |    std::shared_ptr<MapContext> context() const; | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -23,13 +23,14 @@ public: | ||||||
| 
 | 
 | ||||||
|    ~Impl() {} |    ~Impl() {} | ||||||
| 
 | 
 | ||||||
|    std::weak_ptr<QMapLibreGL::Map>         map_; |    std::weak_ptr<QMapLibreGL::Map>          map_; | ||||||
|    MapSettings                             settings_; |    MapSettings                              settings_; | ||||||
|    float                                   pixelRatio_; |    float                                    pixelRatio_; | ||||||
|    std::shared_ptr<view::RadarProductView> radarProductView_; |    std::shared_ptr<view::RadarProductView>  radarProductView_; | ||||||
|    common::RadarProductGroup               radarProductGroup_; |    common::RadarProductGroup                radarProductGroup_; | ||||||
|    std::string                             radarProduct_; |    std::string                              radarProduct_; | ||||||
|    int16_t                                 radarProductCode_; |    int16_t                                  radarProductCode_; | ||||||
|  |    QMapLibreGL::CustomLayerRenderParameters renderParameters_ {}; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| MapContext::MapContext( | MapContext::MapContext( | ||||||
|  | @ -77,6 +78,11 @@ int16_t MapContext::radar_product_code() const | ||||||
|    return p->radarProductCode_; |    return p->radarProductCode_; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | QMapLibreGL::CustomLayerRenderParameters MapContext::render_parameters() const | ||||||
|  | { | ||||||
|  |    return p->renderParameters_; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| void MapContext::set_map(std::shared_ptr<QMapLibreGL::Map> map) | void MapContext::set_map(std::shared_ptr<QMapLibreGL::Map> map) | ||||||
| { | { | ||||||
|    p->map_ = map; |    p->map_ = map; | ||||||
|  | @ -109,6 +115,12 @@ void MapContext::set_radar_product_code(int16_t radarProductCode) | ||||||
|    p->radarProductCode_ = radarProductCode; |    p->radarProductCode_ = radarProductCode; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | void MapContext::set_render_parameters( | ||||||
|  |    const QMapLibreGL::CustomLayerRenderParameters& params) | ||||||
|  | { | ||||||
|  |    p->renderParameters_ = params; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| } // namespace map
 | } // namespace map
 | ||||||
| } // namespace qt
 | } // namespace qt
 | ||||||
| } // namespace scwx
 | } // namespace scwx
 | ||||||
|  |  | ||||||
|  | @ -26,13 +26,14 @@ public: | ||||||
|    MapContext(MapContext&&) noexcept; |    MapContext(MapContext&&) noexcept; | ||||||
|    MapContext& operator=(MapContext&&) noexcept; |    MapContext& operator=(MapContext&&) noexcept; | ||||||
| 
 | 
 | ||||||
|    std::weak_ptr<QMapLibreGL::Map>         map() const; |    std::weak_ptr<QMapLibreGL::Map>          map() const; | ||||||
|    MapSettings&                            settings(); |    MapSettings&                             settings(); | ||||||
|    float                                   pixel_ratio() const; |    float                                    pixel_ratio() const; | ||||||
|    std::shared_ptr<view::RadarProductView> radar_product_view() const; |    std::shared_ptr<view::RadarProductView>  radar_product_view() const; | ||||||
|    common::RadarProductGroup               radar_product_group() const; |    common::RadarProductGroup                radar_product_group() const; | ||||||
|    std::string                             radar_product() const; |    std::string                              radar_product() const; | ||||||
|    int16_t                                 radar_product_code() const; |    int16_t                                  radar_product_code() const; | ||||||
|  |    QMapLibreGL::CustomLayerRenderParameters render_parameters() const; | ||||||
| 
 | 
 | ||||||
|    void set_map(std::shared_ptr<QMapLibreGL::Map> map); |    void set_map(std::shared_ptr<QMapLibreGL::Map> map); | ||||||
|    void set_pixel_ratio(float pixelRatio); |    void set_pixel_ratio(float pixelRatio); | ||||||
|  | @ -41,6 +42,8 @@ public: | ||||||
|    void set_radar_product_group(common::RadarProductGroup radarProductGroup); |    void set_radar_product_group(common::RadarProductGroup radarProductGroup); | ||||||
|    void set_radar_product(const std::string& radarProduct); |    void set_radar_product(const std::string& radarProduct); | ||||||
|    void set_radar_product_code(int16_t radarProductCode); |    void set_radar_product_code(int16_t radarProductCode); | ||||||
|  |    void set_render_parameters( | ||||||
|  |       const QMapLibreGL::CustomLayerRenderParameters& params); | ||||||
| 
 | 
 | ||||||
| private: | private: | ||||||
|    class Impl; |    class Impl; | ||||||
|  |  | ||||||
|  | @ -1,5 +1,5 @@ | ||||||
| #include <scwx/qt/map/map_provider.hpp> | #include <scwx/qt/map/map_provider.hpp> | ||||||
| #include <scwx/qt/manager/settings_manager.hpp> | #include <scwx/qt/settings/general_settings.hpp> | ||||||
| 
 | 
 | ||||||
| #include <unordered_map> | #include <unordered_map> | ||||||
| 
 | 
 | ||||||
|  | @ -128,12 +128,10 @@ std::string GetMapProviderApiKey(MapProvider mapProvider) | ||||||
|    switch (mapProvider) |    switch (mapProvider) | ||||||
|    { |    { | ||||||
|    case MapProvider::Mapbox: |    case MapProvider::Mapbox: | ||||||
|       return manager::SettingsManager::general_settings() |       return settings::GeneralSettings::Instance().mapbox_api_key().GetValue(); | ||||||
|          .mapbox_api_key() |  | ||||||
|          .GetValue(); |  | ||||||
| 
 | 
 | ||||||
|    case MapProvider::MapTiler: |    case MapProvider::MapTiler: | ||||||
|       return manager::SettingsManager::general_settings() |       return settings::GeneralSettings::Instance() | ||||||
|          .maptiler_api_key() |          .maptiler_api_key() | ||||||
|          .GetValue(); |          .GetValue(); | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1,21 +1,29 @@ | ||||||
| #include <scwx/qt/map/map_widget.hpp> | #include <scwx/qt/map/map_widget.hpp> | ||||||
| #include <scwx/qt/gl/gl.hpp> | #include <scwx/qt/gl/gl.hpp> | ||||||
|  | #include <scwx/qt/manager/font_manager.hpp> | ||||||
|  | #include <scwx/qt/manager/placefile_manager.hpp> | ||||||
| #include <scwx/qt/manager/radar_product_manager.hpp> | #include <scwx/qt/manager/radar_product_manager.hpp> | ||||||
| #include <scwx/qt/manager/settings_manager.hpp> |  | ||||||
| #include <scwx/qt/map/alert_layer.hpp> | #include <scwx/qt/map/alert_layer.hpp> | ||||||
| #include <scwx/qt/map/color_table_layer.hpp> | #include <scwx/qt/map/color_table_layer.hpp> | ||||||
| #include <scwx/qt/map/layer_wrapper.hpp> | #include <scwx/qt/map/layer_wrapper.hpp> | ||||||
| #include <scwx/qt/map/map_provider.hpp> | #include <scwx/qt/map/map_provider.hpp> | ||||||
| #include <scwx/qt/map/overlay_layer.hpp> | #include <scwx/qt/map/overlay_layer.hpp> | ||||||
|  | #include <scwx/qt/map/placefile_layer.hpp> | ||||||
| #include <scwx/qt/map/radar_product_layer.hpp> | #include <scwx/qt/map/radar_product_layer.hpp> | ||||||
| #include <scwx/qt/map/radar_range_layer.hpp> | #include <scwx/qt/map/radar_range_layer.hpp> | ||||||
| #include <scwx/qt/model/imgui_context_model.hpp> | #include <scwx/qt/model/imgui_context_model.hpp> | ||||||
|  | #include <scwx/qt/model/layer_model.hpp> | ||||||
|  | #include <scwx/qt/settings/general_settings.hpp> | ||||||
|  | #include <scwx/qt/settings/palette_settings.hpp> | ||||||
| #include <scwx/qt/util/file.hpp> | #include <scwx/qt/util/file.hpp> | ||||||
|  | #include <scwx/qt/util/maplibre.hpp> | ||||||
|  | #include <scwx/qt/util/tooltip.hpp> | ||||||
| #include <scwx/qt/view/radar_product_view_factory.hpp> | #include <scwx/qt/view/radar_product_view_factory.hpp> | ||||||
| #include <scwx/util/logger.hpp> | #include <scwx/util/logger.hpp> | ||||||
| #include <scwx/util/time.hpp> | #include <scwx/util/time.hpp> | ||||||
| 
 | 
 | ||||||
| #include <regex> | #include <regex> | ||||||
|  | #include <set> | ||||||
| 
 | 
 | ||||||
| #include <backends/imgui_impl_opengl3.h> | #include <backends/imgui_impl_opengl3.h> | ||||||
| #include <backends/imgui_impl_qt.hpp> | #include <backends/imgui_impl_qt.hpp> | ||||||
|  | @ -49,7 +57,9 @@ class MapWidgetImpl : public QObject | ||||||
| 
 | 
 | ||||||
| public: | public: | ||||||
|    explicit MapWidgetImpl(MapWidget*                   widget, |    explicit MapWidgetImpl(MapWidget*                   widget, | ||||||
|  |                           std::size_t                  id, | ||||||
|                           const QMapLibreGL::Settings& settings) : |                           const QMapLibreGL::Settings& settings) : | ||||||
|  |        id_ {id}, | ||||||
|        uuid_ {boost::uuids::random_generator()()}, |        uuid_ {boost::uuids::random_generator()()}, | ||||||
|        context_ {std::make_shared<MapContext>()}, |        context_ {std::make_shared<MapContext>()}, | ||||||
|        widget_ {widget}, |        widget_ {widget}, | ||||||
|  | @ -61,11 +71,11 @@ public: | ||||||
|        radarProductLayer_ {nullptr}, |        radarProductLayer_ {nullptr}, | ||||||
|        alertLayer_ {std::make_shared<AlertLayer>(context_)}, |        alertLayer_ {std::make_shared<AlertLayer>(context_)}, | ||||||
|        overlayLayer_ {nullptr}, |        overlayLayer_ {nullptr}, | ||||||
|  |        placefileLayer_ {nullptr}, | ||||||
|        colorTableLayer_ {nullptr}, |        colorTableLayer_ {nullptr}, | ||||||
|        autoRefreshEnabled_ {true}, |        autoRefreshEnabled_ {true}, | ||||||
|        autoUpdateEnabled_ {true}, |        autoUpdateEnabled_ {true}, | ||||||
|        selectedLevel2Product_ {common::Level2Product::Unknown}, |        selectedLevel2Product_ {common::Level2Product::Unknown}, | ||||||
|        lastPos_(), |  | ||||||
|        currentStyleIndex_ {0}, |        currentStyleIndex_ {0}, | ||||||
|        currentStyle_ {nullptr}, |        currentStyle_ {nullptr}, | ||||||
|        frameDraws_(0), |        frameDraws_(0), | ||||||
|  | @ -75,8 +85,7 @@ public: | ||||||
|        prevBearing_ {0.0}, |        prevBearing_ {0.0}, | ||||||
|        prevPitch_ {0.0} |        prevPitch_ {0.0} | ||||||
|    { |    { | ||||||
|       auto& generalSettings = |       auto& generalSettings = settings::GeneralSettings::Instance(); | ||||||
|          scwx::qt::manager::SettingsManager::general_settings(); |  | ||||||
| 
 | 
 | ||||||
|       SetRadarSite(generalSettings.default_radar_site().GetValue()); |       SetRadarSite(generalSettings.default_radar_site().GetValue()); | ||||||
| 
 | 
 | ||||||
|  | @ -92,6 +101,8 @@ public: | ||||||
| 
 | 
 | ||||||
|       // Set Map Provider Details
 |       // Set Map Provider Details
 | ||||||
|       mapProvider_ = GetMapProvider(generalSettings.map_provider().GetValue()); |       mapProvider_ = GetMapProvider(generalSettings.map_provider().GetValue()); | ||||||
|  | 
 | ||||||
|  |       ConnectSignals(); | ||||||
|    } |    } | ||||||
| 
 | 
 | ||||||
|    ~MapWidgetImpl() |    ~MapWidgetImpl() | ||||||
|  | @ -112,22 +123,37 @@ public: | ||||||
|       threadPool_.join(); |       threadPool_.join(); | ||||||
|    } |    } | ||||||
| 
 | 
 | ||||||
|  |    void AddLayer(types::LayerType        type, | ||||||
|  |                  types::LayerDescription description, | ||||||
|  |                  const std::string&      before = {}); | ||||||
|    void AddLayer(const std::string&            id, |    void AddLayer(const std::string&            id, | ||||||
|                  std::shared_ptr<GenericLayer> layer, |                  std::shared_ptr<GenericLayer> layer, | ||||||
|                  const std::string&            before = {}); |                  const std::string&            before = {}); | ||||||
|  |    void AddLayers(); | ||||||
|  |    void AddPlacefileLayer(const std::string& placefileName, | ||||||
|  |                           const std::string& before); | ||||||
|  |    void ConnectSignals(); | ||||||
|  |    void ImGuiCheckFonts(); | ||||||
|    void InitializeNewRadarProductView(const std::string& colorPalette); |    void InitializeNewRadarProductView(const std::string& colorPalette); | ||||||
|    void RadarProductManagerConnect(); |    void RadarProductManagerConnect(); | ||||||
|    void RadarProductManagerDisconnect(); |    void RadarProductManagerDisconnect(); | ||||||
|    void RadarProductViewConnect(); |    void RadarProductViewConnect(); | ||||||
|    void RadarProductViewDisconnect(); |    void RadarProductViewDisconnect(); | ||||||
|  |    void RunMousePicking(); | ||||||
|    void SetRadarSite(const std::string& radarSite); |    void SetRadarSite(const std::string& radarSite); | ||||||
|  |    void UpdateLoadedStyle(); | ||||||
|    bool UpdateStoredMapParameters(); |    bool UpdateStoredMapParameters(); | ||||||
| 
 | 
 | ||||||
|  |    std::string FindMapSymbologyLayer(); | ||||||
|  | 
 | ||||||
|    common::Level2Product |    common::Level2Product | ||||||
|    GetLevel2ProductOrDefault(const std::string& productName) const; |    GetLevel2ProductOrDefault(const std::string& productName) const; | ||||||
| 
 | 
 | ||||||
|  |    static std::string GetPlacefileLayerName(const std::string& placefileName); | ||||||
|  | 
 | ||||||
|    boost::asio::thread_pool threadPool_ {1u}; |    boost::asio::thread_pool threadPool_ {1u}; | ||||||
| 
 | 
 | ||||||
|  |    std::size_t        id_; | ||||||
|    boost::uuids::uuid uuid_; |    boost::uuids::uuid uuid_; | ||||||
| 
 | 
 | ||||||
|    std::shared_ptr<MapContext> context_; |    std::shared_ptr<MapContext> context_; | ||||||
|  | @ -138,10 +164,19 @@ public: | ||||||
|    std::shared_ptr<QMapLibreGL::Map> map_; |    std::shared_ptr<QMapLibreGL::Map> map_; | ||||||
|    std::list<std::string>            layerList_; |    std::list<std::string>            layerList_; | ||||||
| 
 | 
 | ||||||
|  |    QStringList        styleLayers_; | ||||||
|  |    types::LayerVector customLayers_; | ||||||
|  | 
 | ||||||
|    ImGuiContext* imGuiContext_; |    ImGuiContext* imGuiContext_; | ||||||
|    std::string   imGuiContextName_; |    std::string   imGuiContextName_; | ||||||
|    bool          imGuiRendererInitialized_; |    bool          imGuiRendererInitialized_; | ||||||
|  |    std::uint64_t imGuiFontsBuildCount_ {}; | ||||||
| 
 | 
 | ||||||
|  |    std::shared_ptr<model::LayerModel> layerModel_ { | ||||||
|  |       model::LayerModel::Instance()}; | ||||||
|  | 
 | ||||||
|  |    std::shared_ptr<manager::PlacefileManager> placefileManager_ { | ||||||
|  |       manager::PlacefileManager::Instance()}; | ||||||
|    std::shared_ptr<manager::RadarProductManager> radarProductManager_; |    std::shared_ptr<manager::RadarProductManager> radarProductManager_; | ||||||
| 
 | 
 | ||||||
|    std::shared_ptr<common::ColorTable> colorTable_; |    std::shared_ptr<common::ColorTable> colorTable_; | ||||||
|  | @ -149,14 +184,20 @@ public: | ||||||
|    std::shared_ptr<RadarProductLayer> radarProductLayer_; |    std::shared_ptr<RadarProductLayer> radarProductLayer_; | ||||||
|    std::shared_ptr<AlertLayer>        alertLayer_; |    std::shared_ptr<AlertLayer>        alertLayer_; | ||||||
|    std::shared_ptr<OverlayLayer>      overlayLayer_; |    std::shared_ptr<OverlayLayer>      overlayLayer_; | ||||||
|  |    std::shared_ptr<PlacefileLayer>    placefileLayer_; | ||||||
|    std::shared_ptr<ColorTableLayer>   colorTableLayer_; |    std::shared_ptr<ColorTableLayer>   colorTableLayer_; | ||||||
| 
 | 
 | ||||||
|  |    std::list<std::shared_ptr<PlacefileLayer>> placefileLayers_ {}; | ||||||
|  | 
 | ||||||
|    bool autoRefreshEnabled_; |    bool autoRefreshEnabled_; | ||||||
|    bool autoUpdateEnabled_; |    bool autoUpdateEnabled_; | ||||||
| 
 | 
 | ||||||
|    common::Level2Product selectedLevel2Product_; |    common::Level2Product selectedLevel2Product_; | ||||||
| 
 | 
 | ||||||
|    QPointF         lastPos_; |    bool            hasMouse_ {false}; | ||||||
|  |    bool            lastItemPicked_ {false}; | ||||||
|  |    QPointF         lastPos_ {}; | ||||||
|  |    QPointF         lastGlobalPos_ {}; | ||||||
|    std::size_t     currentStyleIndex_; |    std::size_t     currentStyleIndex_; | ||||||
|    const MapStyle* currentStyle_; |    const MapStyle* currentStyle_; | ||||||
|    std::string     initialStyleName_ {}; |    std::string     initialStyleName_ {}; | ||||||
|  | @ -173,9 +214,16 @@ public slots: | ||||||
|    void Update(); |    void Update(); | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| MapWidget::MapWidget(const QMapLibreGL::Settings& settings) : | MapWidget::MapWidget(std::size_t id, const QMapLibreGL::Settings& settings) : | ||||||
|     p(std::make_unique<MapWidgetImpl>(this, settings)) |     p(std::make_unique<MapWidgetImpl>(this, id, settings)) | ||||||
| { | { | ||||||
|  |    if (settings::GeneralSettings::Instance().anti_aliasing_enabled().GetValue()) | ||||||
|  |    { | ||||||
|  |       QSurfaceFormat surfaceFormat = QSurfaceFormat::defaultFormat(); | ||||||
|  |       surfaceFormat.setSamples(4); | ||||||
|  |       setFormat(surfaceFormat); | ||||||
|  |    } | ||||||
|  | 
 | ||||||
|    setFocusPolicy(Qt::StrongFocus); |    setFocusPolicy(Qt::StrongFocus); | ||||||
| 
 | 
 | ||||||
|    ImGui_ImplQt_RegisterWidget(this); |    ImGui_ImplQt_RegisterWidget(this); | ||||||
|  | @ -187,6 +235,63 @@ MapWidget::~MapWidget() | ||||||
|    makeCurrent(); |    makeCurrent(); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | void MapWidgetImpl::ConnectSignals() | ||||||
|  | { | ||||||
|  |    connect(placefileManager_.get(), | ||||||
|  |            &manager::PlacefileManager::PlacefileUpdated, | ||||||
|  |            widget_, | ||||||
|  |            [this]() { widget_->update(); }); | ||||||
|  | 
 | ||||||
|  |    // When the layer model changes, update the layers
 | ||||||
|  |    connect(layerModel_.get(), | ||||||
|  |            &QAbstractItemModel::dataChanged, | ||||||
|  |            widget_, | ||||||
|  |            [this](const QModelIndex& topLeft, | ||||||
|  |                   const QModelIndex& bottomRight, | ||||||
|  |                   const QList<int>& /* roles */) | ||||||
|  |            { | ||||||
|  |               static const int enabledColumn = | ||||||
|  |                  static_cast<int>(model::LayerModel::Column::Enabled); | ||||||
|  |               const int displayColumn = | ||||||
|  |                  static_cast<int>(model::LayerModel::Column::DisplayMap1) + | ||||||
|  |                  static_cast<int>(id_); | ||||||
|  | 
 | ||||||
|  |               // Update layers if the displayed or enabled state of the layer
 | ||||||
|  |               // has changed
 | ||||||
|  |               if ((topLeft.column() <= displayColumn && | ||||||
|  |                    displayColumn <= bottomRight.column()) || | ||||||
|  |                   (topLeft.column() <= enabledColumn && | ||||||
|  |                    enabledColumn <= bottomRight.column())) | ||||||
|  |               { | ||||||
|  |                  AddLayers(); | ||||||
|  |               } | ||||||
|  |            }); | ||||||
|  |    connect(layerModel_.get(), | ||||||
|  |            &QAbstractItemModel::modelReset, | ||||||
|  |            widget_, | ||||||
|  |            [this]() { AddLayers(); }); | ||||||
|  |    connect(layerModel_.get(), | ||||||
|  |            &QAbstractItemModel::rowsInserted, | ||||||
|  |            widget_, | ||||||
|  |            [this](const QModelIndex& /* parent */, //
 | ||||||
|  |                   int /* first */, | ||||||
|  |                   int /* last */) { AddLayers(); }); | ||||||
|  |    connect(layerModel_.get(), | ||||||
|  |            &QAbstractItemModel::rowsMoved, | ||||||
|  |            widget_, | ||||||
|  |            [this](const QModelIndex& /* sourceParent */, | ||||||
|  |                   int /* sourceStart */, | ||||||
|  |                   int /* sourceEnd */, | ||||||
|  |                   const QModelIndex& /* destinationParent */, | ||||||
|  |                   int /* destinationRow */) { AddLayers(); }); | ||||||
|  |    connect(layerModel_.get(), | ||||||
|  |            &QAbstractItemModel::rowsRemoved, | ||||||
|  |            widget_, | ||||||
|  |            [this](const QModelIndex& /* parent */, //
 | ||||||
|  |                   int /* first */, | ||||||
|  |                   int /* last */) { AddLayers(); }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| common::Level3ProductCategoryMap MapWidget::GetAvailableLevel3Categories() | common::Level3ProductCategoryMap MapWidget::GetAvailableLevel3Categories() | ||||||
| { | { | ||||||
|    if (p->radarProductManager_ != nullptr) |    if (p->radarProductManager_ != nullptr) | ||||||
|  | @ -498,7 +603,7 @@ void MapWidget::SelectRadarSite(std::shared_ptr<config::RadarSite> radarSite, | ||||||
|                             false); |                             false); | ||||||
|       } |       } | ||||||
| 
 | 
 | ||||||
|       AddLayers(); |       p->AddLayers(); | ||||||
| 
 | 
 | ||||||
|       // TODO: Disable refresh from old site
 |       // TODO: Disable refresh from old site
 | ||||||
| 
 | 
 | ||||||
|  | @ -646,62 +751,193 @@ void MapWidget::changeStyle() | ||||||
|    Q_EMIT MapStyleChanged(p->currentStyle_->name_); |    Q_EMIT MapStyleChanged(p->currentStyle_->name_); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void MapWidget::AddLayers() | void MapWidget::DumpLayerList() const | ||||||
| { | { | ||||||
|    logger_->debug("AddLayers()"); |    logger_->info("Layers: {}", p->map_->layerIds().join(", ").toStdString()); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | std::string MapWidgetImpl::FindMapSymbologyLayer() | ||||||
|  | { | ||||||
|  |    std::string before = "ferry"; | ||||||
|  | 
 | ||||||
|  |    for (const QString& qlayer : styleLayers_) | ||||||
|  |    { | ||||||
|  |       const std::string layer = qlayer.toStdString(); | ||||||
|  | 
 | ||||||
|  |       // Draw below layers defined in map style
 | ||||||
|  |       auto it = std::find_if( | ||||||
|  |          currentStyle_->drawBelow_.cbegin(), | ||||||
|  |          currentStyle_->drawBelow_.cend(), | ||||||
|  |          [&layer](const std::string& styleLayer) -> bool | ||||||
|  |          { | ||||||
|  |             std::regex re {styleLayer, std::regex_constants::icase}; | ||||||
|  |             return std::regex_match(layer, re); | ||||||
|  |          }); | ||||||
|  | 
 | ||||||
|  |       if (it != currentStyle_->drawBelow_.cend()) | ||||||
|  |       { | ||||||
|  |          before = layer; | ||||||
|  |          break; | ||||||
|  |       } | ||||||
|  |    } | ||||||
|  | 
 | ||||||
|  |    return before; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void MapWidgetImpl::AddLayers() | ||||||
|  | { | ||||||
|  |    if (styleLayers_.isEmpty()) | ||||||
|  |    { | ||||||
|  |       // Skip if the map has not yet been initialized
 | ||||||
|  |       return; | ||||||
|  |    } | ||||||
|  | 
 | ||||||
|  |    logger_->debug("Add Layers"); | ||||||
| 
 | 
 | ||||||
|    // Clear custom layers
 |    // Clear custom layers
 | ||||||
|    for (const std::string& id : p->layerList_) |    for (const std::string& id : layerList_) | ||||||
|    { |    { | ||||||
|       p->map_->removeLayer(id.c_str()); |       map_->removeLayer(id.c_str()); | ||||||
|    } |    } | ||||||
|    p->layerList_.clear(); |    layerList_.clear(); | ||||||
|  |    placefileLayers_.clear(); | ||||||
| 
 | 
 | ||||||
|    auto radarProductView = p->context_->radar_product_view(); |    // Update custom layer list from model
 | ||||||
|  |    customLayers_ = model::LayerModel::Instance()->GetLayers(); | ||||||
| 
 | 
 | ||||||
|    if (radarProductView != nullptr) |    // Start by drawing layers before any style-defined layers
 | ||||||
|  |    std::string before = styleLayers_.front().toStdString(); | ||||||
|  | 
 | ||||||
|  |    // Loop through each custom layer in reverse order
 | ||||||
|  |    for (auto it = customLayers_.crbegin(); it != customLayers_.crend(); ++it) | ||||||
|    { |    { | ||||||
|       p->radarProductLayer_ = std::make_shared<RadarProductLayer>(p->context_); |       if (it->type_ == types::LayerType::Map) | ||||||
|       p->colorTableLayer_   = std::make_shared<ColorTableLayer>(p->context_); |  | ||||||
| 
 |  | ||||||
|       std::shared_ptr<config::RadarSite> radarSite = |  | ||||||
|          p->radarProductManager_->radar_site(); |  | ||||||
| 
 |  | ||||||
|       const auto& mapStyle = *p->currentStyle_; |  | ||||||
| 
 |  | ||||||
|       std::string before = "ferry"; |  | ||||||
| 
 |  | ||||||
|       for (const QString& qlayer : p->map_->layerIds()) |  | ||||||
|       { |       { | ||||||
|          const std::string layer = qlayer.toStdString(); |          // Style-defined map layers
 | ||||||
| 
 |          switch (std::get<types::MapLayer>(it->description_)) | ||||||
|          // Draw below layers defined in map style
 |  | ||||||
|          auto it = std::find_if( |  | ||||||
|             mapStyle.drawBelow_.cbegin(), |  | ||||||
|             mapStyle.drawBelow_.cend(), |  | ||||||
|             [&layer](const std::string& styleLayer) -> bool |  | ||||||
|             { |  | ||||||
|                std::regex re {styleLayer, std::regex_constants::icase}; |  | ||||||
|                return std::regex_match(layer, re); |  | ||||||
|             }); |  | ||||||
| 
 |  | ||||||
|          if (it != mapStyle.drawBelow_.cend()) |  | ||||||
|          { |          { | ||||||
|             before = layer; |          // Subsequent layers are drawn underneath the map symbology layer
 | ||||||
|  |          case types::MapLayer::MapUnderlay: | ||||||
|  |             before = FindMapSymbologyLayer(); | ||||||
|  |             break; | ||||||
|  | 
 | ||||||
|  |          // Subsequent layers are drawn after all style-defined layers
 | ||||||
|  |          case types::MapLayer::MapSymbology: | ||||||
|  |             before = ""; | ||||||
|  |             break; | ||||||
|  | 
 | ||||||
|  |          default: | ||||||
|             break; |             break; | ||||||
|          } |          } | ||||||
|       } |       } | ||||||
| 
 |       else if (it->displayed_[id_]) | ||||||
|       p->AddLayer("radar", p->radarProductLayer_, before); |       { | ||||||
|       RadarRangeLayer::Add(p->map_, |          // If the layer is displayed for the current map, add it
 | ||||||
|                            radarProductView->range(), |          AddLayer(it->type_, it->description_, before); | ||||||
|                            {radarSite->latitude(), radarSite->longitude()}); |       } | ||||||
|       p->AddLayer("colorTable", p->colorTableLayer_); |  | ||||||
|    } |    } | ||||||
|  | } | ||||||
| 
 | 
 | ||||||
|    p->alertLayer_->AddLayers("colorTable"); | void MapWidgetImpl::AddLayer(types::LayerType        type, | ||||||
|    p->overlayLayer_ = std::make_shared<OverlayLayer>(p->context_); |                              types::LayerDescription description, | ||||||
|    p->AddLayer("overlay", p->overlayLayer_); |                              const std::string&      before) | ||||||
|  | { | ||||||
|  |    std::string layerName = types::GetLayerName(type, description); | ||||||
|  | 
 | ||||||
|  |    auto radarProductView = context_->radar_product_view(); | ||||||
|  | 
 | ||||||
|  |    if (type == types::LayerType::Radar) | ||||||
|  |    { | ||||||
|  |       // If there is a radar product view, create the radar product layer
 | ||||||
|  |       if (radarProductView != nullptr) | ||||||
|  |       { | ||||||
|  |          radarProductLayer_ = std::make_shared<RadarProductLayer>(context_); | ||||||
|  |          AddLayer(layerName, radarProductLayer_, before); | ||||||
|  |       } | ||||||
|  |    } | ||||||
|  |    else if (type == types::LayerType::Alert) | ||||||
|  |    { | ||||||
|  |       // Add the alert layer for the phenomenon
 | ||||||
|  |       auto newLayers = alertLayer_->AddLayers( | ||||||
|  |          std::get<awips::Phenomenon>(description), before); | ||||||
|  |       layerList_.insert(layerList_.end(), newLayers.cbegin(), newLayers.cend()); | ||||||
|  |    } | ||||||
|  |    else if (type == types::LayerType::Placefile) | ||||||
|  |    { | ||||||
|  |       // If the placefile is enabled, add the placefile layer
 | ||||||
|  |       std::string placefileName = std::get<std::string>(description); | ||||||
|  |       if (placefileManager_->placefile_enabled(placefileName)) | ||||||
|  |       { | ||||||
|  |          AddPlacefileLayer(placefileName, before); | ||||||
|  |       } | ||||||
|  |    } | ||||||
|  |    else if (type == types::LayerType::Information) | ||||||
|  |    { | ||||||
|  |       switch (std::get<types::InformationLayer>(description)) | ||||||
|  |       { | ||||||
|  |       // Create the map overlay layer
 | ||||||
|  |       case types::InformationLayer::MapOverlay: | ||||||
|  |          overlayLayer_ = std::make_shared<OverlayLayer>(context_); | ||||||
|  |          AddLayer(layerName, overlayLayer_, before); | ||||||
|  |          break; | ||||||
|  | 
 | ||||||
|  |       // If there is a radar product view, create the color table layer
 | ||||||
|  |       case types::InformationLayer::ColorTable: | ||||||
|  |          if (radarProductView != nullptr) | ||||||
|  |          { | ||||||
|  |             colorTableLayer_ = std::make_shared<ColorTableLayer>(context_); | ||||||
|  |             AddLayer(layerName, colorTableLayer_, before); | ||||||
|  |          } | ||||||
|  |          break; | ||||||
|  | 
 | ||||||
|  |       default: | ||||||
|  |          break; | ||||||
|  |       } | ||||||
|  |    } | ||||||
|  |    else if (type == types::LayerType::Data) | ||||||
|  |    { | ||||||
|  |       switch (std::get<types::DataLayer>(description)) | ||||||
|  |       { | ||||||
|  |       // If there is a radar product view, create the radar range layer
 | ||||||
|  |       case types::DataLayer::RadarRange: | ||||||
|  |          if (radarProductView != nullptr) | ||||||
|  |          { | ||||||
|  |             std::shared_ptr<config::RadarSite> radarSite = | ||||||
|  |                radarProductManager_->radar_site(); | ||||||
|  |             RadarRangeLayer::Add( | ||||||
|  |                map_, | ||||||
|  |                radarProductView->range(), | ||||||
|  |                {radarSite->latitude(), radarSite->longitude()}, | ||||||
|  |                QString::fromStdString(before)); | ||||||
|  |             layerList_.push_back(types::GetLayerName(type, description)); | ||||||
|  |          } | ||||||
|  |          break; | ||||||
|  | 
 | ||||||
|  |       default: | ||||||
|  |          break; | ||||||
|  |       } | ||||||
|  |    } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void MapWidgetImpl::AddPlacefileLayer(const std::string& placefileName, | ||||||
|  |                                       const std::string& before) | ||||||
|  | { | ||||||
|  |    std::shared_ptr<PlacefileLayer> placefileLayer = | ||||||
|  |       std::make_shared<PlacefileLayer>(context_, placefileName); | ||||||
|  |    placefileLayers_.push_back(placefileLayer); | ||||||
|  |    AddLayer(GetPlacefileLayerName(placefileName), placefileLayer, before); | ||||||
|  | 
 | ||||||
|  |    // When the layer updates, trigger a map widget update
 | ||||||
|  |    connect(placefileLayer.get(), | ||||||
|  |            &PlacefileLayer::DataReloaded, | ||||||
|  |            widget_, | ||||||
|  |            [this]() { widget_->update(); }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | std::string | ||||||
|  | MapWidgetImpl::GetPlacefileLayerName(const std::string& placefileName) | ||||||
|  | { | ||||||
|  |    return types::GetLayerName(types::LayerType::Placefile, placefileName); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void MapWidgetImpl::AddLayer(const std::string&            id, | void MapWidgetImpl::AddLayer(const std::string&            id, | ||||||
|  | @ -712,9 +948,26 @@ void MapWidgetImpl::AddLayer(const std::string&            id, | ||||||
|    std::unique_ptr<QMapLibreGL::CustomLayerHostInterface> pHost = |    std::unique_ptr<QMapLibreGL::CustomLayerHostInterface> pHost = | ||||||
|       std::make_unique<LayerWrapper>(layer); |       std::make_unique<LayerWrapper>(layer); | ||||||
| 
 | 
 | ||||||
|    map_->addCustomLayer(id.c_str(), std::move(pHost), before.c_str()); |    try | ||||||
|  |    { | ||||||
|  |       map_->addCustomLayer(id.c_str(), std::move(pHost), before.c_str()); | ||||||
| 
 | 
 | ||||||
|    layerList_.push_back(id); |       layerList_.push_back(id); | ||||||
|  |    } | ||||||
|  |    catch (const std::exception&) | ||||||
|  |    { | ||||||
|  |       // When dragging and dropping, a temporary duplicate layer exists
 | ||||||
|  |    } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void MapWidget::enterEvent(QEnterEvent* /* ev */) | ||||||
|  | { | ||||||
|  |    p->hasMouse_ = true; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void MapWidget::leaveEvent(QEvent* /* ev */) | ||||||
|  | { | ||||||
|  |    p->hasMouse_ = false; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void MapWidget::keyPressEvent(QKeyEvent* ev) | void MapWidget::keyPressEvent(QKeyEvent* ev) | ||||||
|  | @ -741,7 +994,8 @@ void MapWidget::keyPressEvent(QKeyEvent* ev) | ||||||
| 
 | 
 | ||||||
| void MapWidget::mousePressEvent(QMouseEvent* ev) | void MapWidget::mousePressEvent(QMouseEvent* ev) | ||||||
| { | { | ||||||
|    p->lastPos_ = ev->position(); |    p->lastPos_       = ev->position(); | ||||||
|  |    p->lastGlobalPos_ = ev->globalPosition(); | ||||||
| 
 | 
 | ||||||
|    if (ev->type() == QEvent::MouseButtonPress) |    if (ev->type() == QEvent::MouseButtonPress) | ||||||
|    { |    { | ||||||
|  | @ -787,7 +1041,8 @@ void MapWidget::mouseMoveEvent(QMouseEvent* ev) | ||||||
|       } |       } | ||||||
|    } |    } | ||||||
| 
 | 
 | ||||||
|    p->lastPos_ = ev->position(); |    p->lastPos_       = ev->position(); | ||||||
|  |    p->lastGlobalPos_ = ev->globalPosition(); | ||||||
|    ev->accept(); |    ev->accept(); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -816,9 +1071,15 @@ void MapWidget::initializeGL() | ||||||
|    makeCurrent(); |    makeCurrent(); | ||||||
|    p->context_->gl().initializeOpenGLFunctions(); |    p->context_->gl().initializeOpenGLFunctions(); | ||||||
| 
 | 
 | ||||||
|  |    // Lock ImGui font atlas prior to new ImGui frame
 | ||||||
|  |    std::shared_lock imguiFontAtlasLock { | ||||||
|  |       manager::FontManager::Instance().imgui_font_atlas_mutex()}; | ||||||
|  | 
 | ||||||
|    // Initialize ImGui OpenGL3 backend
 |    // Initialize ImGui OpenGL3 backend
 | ||||||
|    ImGui::SetCurrentContext(p->imGuiContext_); |    ImGui::SetCurrentContext(p->imGuiContext_); | ||||||
|    ImGui_ImplOpenGL3_Init(); |    ImGui_ImplOpenGL3_Init(); | ||||||
|  |    p->imGuiFontsBuildCount_ = | ||||||
|  |       manager::FontManager::Instance().imgui_fonts_build_count(); | ||||||
|    p->imGuiRendererInitialized_ = true; |    p->imGuiRendererInitialized_ = true; | ||||||
| 
 | 
 | ||||||
|    p->map_.reset( |    p->map_.reset( | ||||||
|  | @ -859,16 +1120,27 @@ void MapWidget::initializeGL() | ||||||
| 
 | 
 | ||||||
| void MapWidget::paintGL() | void MapWidget::paintGL() | ||||||
| { | { | ||||||
|  |    auto defaultFont = manager::FontManager::Instance().GetImGuiFont( | ||||||
|  |       types::FontCategory::Default); | ||||||
|  | 
 | ||||||
|    p->frameDraws_++; |    p->frameDraws_++; | ||||||
| 
 | 
 | ||||||
|    // Setup ImGui Frame
 |    // Setup ImGui Frame
 | ||||||
|    ImGui::SetCurrentContext(p->imGuiContext_); |    ImGui::SetCurrentContext(p->imGuiContext_); | ||||||
| 
 | 
 | ||||||
|  |    // Lock ImGui font atlas prior to new ImGui frame
 | ||||||
|  |    std::shared_lock imguiFontAtlasLock { | ||||||
|  |       manager::FontManager::Instance().imgui_font_atlas_mutex()}; | ||||||
|  | 
 | ||||||
|    // Start ImGui Frame
 |    // Start ImGui Frame
 | ||||||
|    ImGui_ImplQt_NewFrame(this); |    ImGui_ImplQt_NewFrame(this); | ||||||
|    ImGui_ImplOpenGL3_NewFrame(); |    ImGui_ImplOpenGL3_NewFrame(); | ||||||
|  |    p->ImGuiCheckFonts(); | ||||||
|    ImGui::NewFrame(); |    ImGui::NewFrame(); | ||||||
| 
 | 
 | ||||||
|  |    // Set default font
 | ||||||
|  |    ImGui::PushFont(defaultFont->font()); | ||||||
|  | 
 | ||||||
|    // Update pixel ratio
 |    // Update pixel ratio
 | ||||||
|    p->context_->set_pixel_ratio(pixelRatio()); |    p->context_->set_pixel_ratio(pixelRatio()); | ||||||
| 
 | 
 | ||||||
|  | @ -878,20 +1150,90 @@ void MapWidget::paintGL() | ||||||
|                                  size() * pixelRatio()); |                                  size() * pixelRatio()); | ||||||
|    p->map_->render(); |    p->map_->render(); | ||||||
| 
 | 
 | ||||||
|  |    // Perform mouse picking
 | ||||||
|  |    if (p->hasMouse_) | ||||||
|  |    { | ||||||
|  |       p->RunMousePicking(); | ||||||
|  |    } | ||||||
|  |    else if (p->lastItemPicked_) | ||||||
|  |    { | ||||||
|  |       // Hide the tooltip when losing focus
 | ||||||
|  |       util::tooltip::Hide(); | ||||||
|  | 
 | ||||||
|  |       p->lastItemPicked_ = false; | ||||||
|  |    } | ||||||
|  | 
 | ||||||
|  |    // Pop default font
 | ||||||
|  |    ImGui::PopFont(); | ||||||
|  | 
 | ||||||
|    // Render ImGui Frame
 |    // Render ImGui Frame
 | ||||||
|    ImGui::Render(); |    ImGui::Render(); | ||||||
|    ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); |    ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); | ||||||
| 
 | 
 | ||||||
|  |    // Unlock ImGui font atlas after rendering
 | ||||||
|  |    imguiFontAtlasLock.unlock(); | ||||||
|  | 
 | ||||||
|    // Paint complete
 |    // Paint complete
 | ||||||
|    Q_EMIT WidgetPainted(); |    Q_EMIT WidgetPainted(); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | void MapWidgetImpl::ImGuiCheckFonts() | ||||||
|  | { | ||||||
|  |    // Update ImGui Fonts if required
 | ||||||
|  |    std::uint64_t currentImGuiFontsBuildCount = | ||||||
|  |       manager::FontManager::Instance().imgui_fonts_build_count(); | ||||||
|  | 
 | ||||||
|  |    if (imGuiFontsBuildCount_ != currentImGuiFontsBuildCount || | ||||||
|  |        !model::ImGuiContextModel::Instance().font_atlas()->IsBuilt()) | ||||||
|  |    { | ||||||
|  |       ImGui_ImplOpenGL3_DestroyFontsTexture(); | ||||||
|  |       ImGui_ImplOpenGL3_CreateFontsTexture(); | ||||||
|  |    } | ||||||
|  | 
 | ||||||
|  |    imGuiFontsBuildCount_ = currentImGuiFontsBuildCount; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void MapWidgetImpl::RunMousePicking() | ||||||
|  | { | ||||||
|  |    const QMapLibreGL::CustomLayerRenderParameters params = | ||||||
|  |       context_->render_parameters(); | ||||||
|  | 
 | ||||||
|  |    auto coordinate = map_->coordinateForPixel(lastPos_); | ||||||
|  |    auto mouseScreenCoordinate = | ||||||
|  |       util::maplibre::LatLongToScreenCoordinate(coordinate); | ||||||
|  | 
 | ||||||
|  |    // For each layer in reverse
 | ||||||
|  |    // TODO: All Generic Layers, not just Placefile Layers
 | ||||||
|  |    bool itemPicked = false; | ||||||
|  |    for (auto it = placefileLayers_.rbegin(); it != placefileLayers_.rend(); | ||||||
|  |         ++it) | ||||||
|  |    { | ||||||
|  |       // Run mouse picking for each layer
 | ||||||
|  |       if ((*it)->RunMousePicking( | ||||||
|  |              params, lastPos_, lastGlobalPos_, mouseScreenCoordinate)) | ||||||
|  |       { | ||||||
|  |          // If a draw item was picked, don't process additional layers
 | ||||||
|  |          itemPicked = true; | ||||||
|  |          break; | ||||||
|  |       } | ||||||
|  |    } | ||||||
|  | 
 | ||||||
|  |    // If no draw item was picked, hide the tooltip
 | ||||||
|  |    if (!itemPicked) | ||||||
|  |    { | ||||||
|  |       util::tooltip::Hide(); | ||||||
|  |    } | ||||||
|  | 
 | ||||||
|  |    lastItemPicked_ = itemPicked; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| void MapWidget::mapChanged(QMapLibreGL::Map::MapChange mapChange) | void MapWidget::mapChanged(QMapLibreGL::Map::MapChange mapChange) | ||||||
| { | { | ||||||
|    switch (mapChange) |    switch (mapChange) | ||||||
|    { |    { | ||||||
|    case QMapLibreGL::Map::MapChangeDidFinishLoadingStyle: |    case QMapLibreGL::Map::MapChangeDidFinishLoadingStyle: | ||||||
|       AddLayers(); |       p->UpdateLoadedStyle(); | ||||||
|  |       p->AddLayers(); | ||||||
|       break; |       break; | ||||||
| 
 | 
 | ||||||
|    default: |    default: | ||||||
|  | @ -899,6 +1241,11 @@ void MapWidget::mapChanged(QMapLibreGL::Map::MapChange mapChange) | ||||||
|    } |    } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | void MapWidgetImpl::UpdateLoadedStyle() | ||||||
|  | { | ||||||
|  |    styleLayers_ = map_->layerIds(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| void MapWidgetImpl::RadarProductManagerConnect() | void MapWidgetImpl::RadarProductManagerConnect() | ||||||
| { | { | ||||||
|    if (radarProductManager_ != nullptr) |    if (radarProductManager_ != nullptr) | ||||||
|  | @ -992,7 +1339,7 @@ void MapWidgetImpl::InitializeNewRadarProductView( | ||||||
|                         auto radarProductView = context_->radar_product_view(); |                         auto radarProductView = context_->radar_product_view(); | ||||||
| 
 | 
 | ||||||
|                         std::string colorTableFile = |                         std::string colorTableFile = | ||||||
|                            manager::SettingsManager::palette_settings() |                            settings::PaletteSettings::Instance() | ||||||
|                               .palette(colorPalette) |                               .palette(colorPalette) | ||||||
|                               .GetValue(); |                               .GetValue(); | ||||||
|                         if (!colorTableFile.empty()) |                         if (!colorTableFile.empty()) | ||||||
|  | @ -1009,7 +1356,7 @@ void MapWidgetImpl::InitializeNewRadarProductView( | ||||||
| 
 | 
 | ||||||
|    if (map_ != nullptr) |    if (map_ != nullptr) | ||||||
|    { |    { | ||||||
|       widget_->AddLayers(); |       AddLayers(); | ||||||
|    } |    } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -32,9 +32,11 @@ class MapWidget : public QOpenGLWidget | ||||||
|    Q_OBJECT |    Q_OBJECT | ||||||
| 
 | 
 | ||||||
| public: | public: | ||||||
|    explicit MapWidget(const QMapLibreGL::Settings&); |    explicit MapWidget(std::size_t id, const QMapLibreGL::Settings&); | ||||||
|    ~MapWidget(); |    ~MapWidget(); | ||||||
| 
 | 
 | ||||||
|  |    void DumpLayerList() const; | ||||||
|  | 
 | ||||||
|    common::Level3ProductCategoryMap      GetAvailableLevel3Categories(); |    common::Level3ProductCategoryMap      GetAvailableLevel3Categories(); | ||||||
|    float                                 GetElevation() const; |    float                                 GetElevation() const; | ||||||
|    std::vector<float>                    GetElevationCuts() const; |    std::vector<float>                    GetElevationCuts() const; | ||||||
|  | @ -119,7 +121,9 @@ private: | ||||||
|    qreal pixelRatio(); |    qreal pixelRatio(); | ||||||
| 
 | 
 | ||||||
|    // QWidget implementation.
 |    // QWidget implementation.
 | ||||||
|  |    void enterEvent(QEnterEvent* ev) override final; | ||||||
|    void keyPressEvent(QKeyEvent* ev) override final; |    void keyPressEvent(QKeyEvent* ev) override final; | ||||||
|  |    void leaveEvent(QEvent* ev) override final; | ||||||
|    void mousePressEvent(QMouseEvent* ev) override final; |    void mousePressEvent(QMouseEvent* ev) override final; | ||||||
|    void mouseMoveEvent(QMouseEvent* ev) override final; |    void mouseMoveEvent(QMouseEvent* ev) override final; | ||||||
|    void wheelEvent(QWheelEvent* ev) override final; |    void wheelEvent(QWheelEvent* ev) override final; | ||||||
|  | @ -128,8 +132,6 @@ private: | ||||||
|    void initializeGL() override final; |    void initializeGL() override final; | ||||||
|    void paintGL() override final; |    void paintGL() override final; | ||||||
| 
 | 
 | ||||||
|    void AddLayers(); |  | ||||||
| 
 |  | ||||||
|    std::unique_ptr<MapWidgetImpl> p; |    std::unique_ptr<MapWidgetImpl> p; | ||||||
| 
 | 
 | ||||||
|    friend class MapWidgetImpl; |    friend class MapWidgetImpl; | ||||||
|  |  | ||||||
|  | @ -95,6 +95,8 @@ void OverlayLayer::Render( | ||||||
|    auto&                settings         = context()->settings(); |    auto&                settings         = context()->settings(); | ||||||
|    const float          pixelRatio       = context()->pixel_ratio(); |    const float          pixelRatio       = context()->pixel_ratio(); | ||||||
| 
 | 
 | ||||||
|  |    context()->set_render_parameters(params); | ||||||
|  | 
 | ||||||
|    if (p->sweepTimeNeedsUpdate_ && radarProductView != nullptr) |    if (p->sweepTimeNeedsUpdate_ && radarProductView != nullptr) | ||||||
|    { |    { | ||||||
|       const scwx::util::time_zone* currentZone; |       const scwx::util::time_zone* currentZone; | ||||||
|  |  | ||||||
							
								
								
									
										262
									
								
								scwx-qt/source/scwx/qt/map/placefile_layer.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						|  | @ -0,0 +1,262 @@ | ||||||
|  | #include <scwx/qt/map/placefile_layer.hpp> | ||||||
|  | #include <scwx/qt/gl/draw/placefile_icons.hpp> | ||||||
|  | #include <scwx/qt/gl/draw/placefile_images.hpp> | ||||||
|  | #include <scwx/qt/gl/draw/placefile_lines.hpp> | ||||||
|  | #include <scwx/qt/gl/draw/placefile_polygons.hpp> | ||||||
|  | #include <scwx/qt/gl/draw/placefile_triangles.hpp> | ||||||
|  | #include <scwx/qt/gl/draw/placefile_text.hpp> | ||||||
|  | #include <scwx/qt/manager/placefile_manager.hpp> | ||||||
|  | #include <scwx/qt/manager/timeline_manager.hpp> | ||||||
|  | #include <scwx/util/logger.hpp> | ||||||
|  | 
 | ||||||
|  | #include <boost/asio/post.hpp> | ||||||
|  | #include <boost/asio/thread_pool.hpp> | ||||||
|  | 
 | ||||||
|  | namespace scwx | ||||||
|  | { | ||||||
|  | namespace qt | ||||||
|  | { | ||||||
|  | namespace map | ||||||
|  | { | ||||||
|  | 
 | ||||||
|  | static const std::string logPrefix_ = "scwx::qt::map::placefile_layer"; | ||||||
|  | static const auto        logger_    = scwx::util::Logger::Create(logPrefix_); | ||||||
|  | 
 | ||||||
|  | class PlacefileLayer::Impl | ||||||
|  | { | ||||||
|  | public: | ||||||
|  |    explicit Impl(PlacefileLayer*                    self, | ||||||
|  |                  const std::shared_ptr<MapContext>& context, | ||||||
|  |                  const std::string&                 placefileName) : | ||||||
|  |        self_ {self}, | ||||||
|  |        placefileName_ {placefileName}, | ||||||
|  |        placefileIcons_ {std::make_shared<gl::draw::PlacefileIcons>(context)}, | ||||||
|  |        placefileImages_ {std::make_shared<gl::draw::PlacefileImages>(context)}, | ||||||
|  |        placefileLines_ {std::make_shared<gl::draw::PlacefileLines>(context)}, | ||||||
|  |        placefilePolygons_ { | ||||||
|  |           std::make_shared<gl::draw::PlacefilePolygons>(context)}, | ||||||
|  |        placefileTriangles_ { | ||||||
|  |           std::make_shared<gl::draw::PlacefileTriangles>(context)}, | ||||||
|  |        placefileText_ { | ||||||
|  |           std::make_shared<gl::draw::PlacefileText>(context, placefileName)} | ||||||
|  |    { | ||||||
|  |       ConnectSignals(); | ||||||
|  |    } | ||||||
|  |    ~Impl() { threadPool_.join(); } | ||||||
|  | 
 | ||||||
|  |    void ConnectSignals(); | ||||||
|  | 
 | ||||||
|  |    boost::asio::thread_pool threadPool_ {1}; | ||||||
|  | 
 | ||||||
|  |    PlacefileLayer* self_; | ||||||
|  | 
 | ||||||
|  |    std::string placefileName_; | ||||||
|  |    std::mutex  dataMutex_ {}; | ||||||
|  | 
 | ||||||
|  |    std::shared_ptr<gl::draw::PlacefileIcons>     placefileIcons_; | ||||||
|  |    std::shared_ptr<gl::draw::PlacefileImages>    placefileImages_; | ||||||
|  |    std::shared_ptr<gl::draw::PlacefileLines>     placefileLines_; | ||||||
|  |    std::shared_ptr<gl::draw::PlacefilePolygons>  placefilePolygons_; | ||||||
|  |    std::shared_ptr<gl::draw::PlacefileTriangles> placefileTriangles_; | ||||||
|  |    std::shared_ptr<gl::draw::PlacefileText>      placefileText_; | ||||||
|  | 
 | ||||||
|  |    std::chrono::system_clock::time_point selectedTime_ {}; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | PlacefileLayer::PlacefileLayer(const std::shared_ptr<MapContext>& context, | ||||||
|  |                                const std::string& placefileName) : | ||||||
|  |     DrawLayer(context), | ||||||
|  |     p(std::make_unique<PlacefileLayer::Impl>(this, context, placefileName)) | ||||||
|  | { | ||||||
|  |    AddDrawItem(p->placefileImages_); | ||||||
|  |    AddDrawItem(p->placefilePolygons_); | ||||||
|  |    AddDrawItem(p->placefileTriangles_); | ||||||
|  |    AddDrawItem(p->placefileLines_); | ||||||
|  |    AddDrawItem(p->placefileIcons_); | ||||||
|  |    AddDrawItem(p->placefileText_); | ||||||
|  | 
 | ||||||
|  |    ReloadData(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | PlacefileLayer::~PlacefileLayer() = default; | ||||||
|  | 
 | ||||||
|  | void PlacefileLayer::Impl::ConnectSignals() | ||||||
|  | { | ||||||
|  |    auto placefileManager = manager::PlacefileManager::Instance(); | ||||||
|  |    auto timelineManager  = manager::TimelineManager::Instance(); | ||||||
|  | 
 | ||||||
|  |    QObject::connect(placefileManager.get(), | ||||||
|  |                     &manager::PlacefileManager::PlacefileUpdated, | ||||||
|  |                     self_, | ||||||
|  |                     [this](const std::string& name) | ||||||
|  |                     { | ||||||
|  |                        if (name == placefileName_) | ||||||
|  |                        { | ||||||
|  |                           self_->ReloadData(); | ||||||
|  |                        } | ||||||
|  |                     }); | ||||||
|  | 
 | ||||||
|  |    QObject::connect(timelineManager.get(), | ||||||
|  |                     &manager::TimelineManager::SelectedTimeUpdated, | ||||||
|  |                     self_, | ||||||
|  |                     [this](std::chrono::system_clock::time_point dateTime) | ||||||
|  |                     { selectedTime_ = dateTime; }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | std::string PlacefileLayer::placefile_name() const | ||||||
|  | { | ||||||
|  |    return p->placefileName_; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void PlacefileLayer::set_placefile_name(const std::string& placefileName) | ||||||
|  | { | ||||||
|  |    p->placefileName_ = placefileName; | ||||||
|  |    p->placefileText_->set_placefile_name(placefileName); | ||||||
|  | 
 | ||||||
|  |    ReloadData(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void PlacefileLayer::Initialize() | ||||||
|  | { | ||||||
|  |    logger_->debug("Initialize()"); | ||||||
|  | 
 | ||||||
|  |    DrawLayer::Initialize(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void PlacefileLayer::Render( | ||||||
|  |    const QMapLibreGL::CustomLayerRenderParameters& params) | ||||||
|  | { | ||||||
|  |    gl::OpenGLFunctions& gl = context()->gl(); | ||||||
|  | 
 | ||||||
|  |    // Set OpenGL blend mode for transparency
 | ||||||
|  |    gl.glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); | ||||||
|  | 
 | ||||||
|  |    std::shared_ptr<manager::PlacefileManager> placefileManager = | ||||||
|  |       manager::PlacefileManager::Instance(); | ||||||
|  | 
 | ||||||
|  |    auto placefile = placefileManager->placefile(p->placefileName_); | ||||||
|  | 
 | ||||||
|  |    // Render placefile
 | ||||||
|  |    if (placefile != nullptr) | ||||||
|  |    { | ||||||
|  |       bool thresholded = | ||||||
|  |          placefileManager->placefile_thresholded(placefile->name()); | ||||||
|  |       p->placefileIcons_->set_thresholded(thresholded); | ||||||
|  |       p->placefileImages_->set_thresholded(thresholded); | ||||||
|  |       p->placefileLines_->set_thresholded(thresholded); | ||||||
|  |       p->placefilePolygons_->set_thresholded(thresholded); | ||||||
|  |       p->placefileTriangles_->set_thresholded(thresholded); | ||||||
|  |       p->placefileText_->set_thresholded(thresholded); | ||||||
|  | 
 | ||||||
|  |       p->placefileIcons_->set_selected_time(p->selectedTime_); | ||||||
|  |       p->placefileImages_->set_selected_time(p->selectedTime_); | ||||||
|  |       p->placefileLines_->set_selected_time(p->selectedTime_); | ||||||
|  |       p->placefilePolygons_->set_selected_time(p->selectedTime_); | ||||||
|  |       p->placefileTriangles_->set_selected_time(p->selectedTime_); | ||||||
|  |       p->placefileText_->set_selected_time(p->selectedTime_); | ||||||
|  |    } | ||||||
|  | 
 | ||||||
|  |    DrawLayer::Render(params); | ||||||
|  | 
 | ||||||
|  |    SCWX_GL_CHECK_ERROR(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void PlacefileLayer::Deinitialize() | ||||||
|  | { | ||||||
|  |    logger_->debug("Deinitialize()"); | ||||||
|  | 
 | ||||||
|  |    DrawLayer::Deinitialize(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void PlacefileLayer::ReloadData() | ||||||
|  | { | ||||||
|  |    boost::asio::post( | ||||||
|  |       p->threadPool_, | ||||||
|  |       [this]() | ||||||
|  |       { | ||||||
|  |          logger_->debug("ReloadData: {}", p->placefileName_); | ||||||
|  | 
 | ||||||
|  |          std::unique_lock lock {p->dataMutex_}; | ||||||
|  | 
 | ||||||
|  |          std::shared_ptr<manager::PlacefileManager> placefileManager = | ||||||
|  |             manager::PlacefileManager::Instance(); | ||||||
|  | 
 | ||||||
|  |          auto placefile = placefileManager->placefile(p->placefileName_); | ||||||
|  |          if (placefile == nullptr) | ||||||
|  |          { | ||||||
|  |             return; | ||||||
|  |          } | ||||||
|  | 
 | ||||||
|  |          // Start draw items
 | ||||||
|  |          p->placefileIcons_->StartIcons(); | ||||||
|  |          p->placefileImages_->StartImages(placefile->name()); | ||||||
|  |          p->placefileLines_->StartLines(); | ||||||
|  |          p->placefilePolygons_->StartPolygons(); | ||||||
|  |          p->placefileTriangles_->StartTriangles(); | ||||||
|  |          p->placefileText_->StartText(); | ||||||
|  | 
 | ||||||
|  |          p->placefileIcons_->SetIconFiles(placefile->icon_files(), | ||||||
|  |                                           placefile->name()); | ||||||
|  |          p->placefileText_->SetFonts( | ||||||
|  |             placefileManager->placefile_fonts(p->placefileName_)); | ||||||
|  | 
 | ||||||
|  |          for (auto& drawItem : placefile->GetDrawItems()) | ||||||
|  |          { | ||||||
|  |             switch (drawItem->itemType_) | ||||||
|  |             { | ||||||
|  |             case gr::Placefile::ItemType::Text: | ||||||
|  |                p->placefileText_->AddText( | ||||||
|  |                   std::static_pointer_cast<gr::Placefile::TextDrawItem>( | ||||||
|  |                      drawItem)); | ||||||
|  |                break; | ||||||
|  | 
 | ||||||
|  |             case gr::Placefile::ItemType::Icon: | ||||||
|  |                p->placefileIcons_->AddIcon( | ||||||
|  |                   std::static_pointer_cast<gr::Placefile::IconDrawItem>( | ||||||
|  |                      drawItem)); | ||||||
|  |                break; | ||||||
|  | 
 | ||||||
|  |             case gr::Placefile::ItemType::Line: | ||||||
|  |                p->placefileLines_->AddLine( | ||||||
|  |                   std::static_pointer_cast<gr::Placefile::LineDrawItem>( | ||||||
|  |                      drawItem)); | ||||||
|  |                break; | ||||||
|  | 
 | ||||||
|  |             case gr::Placefile::ItemType::Polygon: | ||||||
|  |                p->placefilePolygons_->AddPolygon( | ||||||
|  |                   std::static_pointer_cast<gr::Placefile::PolygonDrawItem>( | ||||||
|  |                      drawItem)); | ||||||
|  |                break; | ||||||
|  | 
 | ||||||
|  |             case gr::Placefile::ItemType::Image: | ||||||
|  |                p->placefileImages_->AddImage( | ||||||
|  |                   std::static_pointer_cast<gr::Placefile::ImageDrawItem>( | ||||||
|  |                      drawItem)); | ||||||
|  |                break; | ||||||
|  | 
 | ||||||
|  |             case gr::Placefile::ItemType::Triangles: | ||||||
|  |                p->placefileTriangles_->AddTriangles( | ||||||
|  |                   std::static_pointer_cast<gr::Placefile::TrianglesDrawItem>( | ||||||
|  |                      drawItem)); | ||||||
|  |                break; | ||||||
|  | 
 | ||||||
|  |             default: | ||||||
|  |                break; | ||||||
|  |             } | ||||||
|  |          } | ||||||
|  | 
 | ||||||
|  |          // Finish draw items
 | ||||||
|  |          p->placefileIcons_->FinishIcons(); | ||||||
|  |          p->placefileImages_->FinishImages(); | ||||||
|  |          p->placefileLines_->FinishLines(); | ||||||
|  |          p->placefilePolygons_->FinishPolygons(); | ||||||
|  |          p->placefileTriangles_->FinishTriangles(); | ||||||
|  |          p->placefileText_->FinishText(); | ||||||
|  | 
 | ||||||
|  |          Q_EMIT DataReloaded(); | ||||||
|  |       }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | } // namespace map
 | ||||||
|  | } // namespace qt
 | ||||||
|  | } // namespace scwx
 | ||||||
							
								
								
									
										43
									
								
								scwx-qt/source/scwx/qt/map/placefile_layer.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						|  | @ -0,0 +1,43 @@ | ||||||
|  | #pragma once | ||||||
|  | 
 | ||||||
|  | #include <scwx/qt/map/draw_layer.hpp> | ||||||
|  | 
 | ||||||
|  | #include <string> | ||||||
|  | 
 | ||||||
|  | namespace scwx | ||||||
|  | { | ||||||
|  | namespace qt | ||||||
|  | { | ||||||
|  | namespace map | ||||||
|  | { | ||||||
|  | 
 | ||||||
|  | class PlacefileLayer : public DrawLayer | ||||||
|  | { | ||||||
|  |    Q_OBJECT | ||||||
|  | 
 | ||||||
|  | public: | ||||||
|  |    explicit PlacefileLayer(const std::shared_ptr<MapContext>& context, | ||||||
|  |                            const std::string&                 placefileName); | ||||||
|  |    ~PlacefileLayer(); | ||||||
|  | 
 | ||||||
|  |    std::string placefile_name() const; | ||||||
|  | 
 | ||||||
|  |    void set_placefile_name(const std::string& placefileName); | ||||||
|  | 
 | ||||||
|  |    void Initialize() override final; | ||||||
|  |    void Render(const QMapLibreGL::CustomLayerRenderParameters&) override final; | ||||||
|  |    void Deinitialize() override final; | ||||||
|  | 
 | ||||||
|  |    void ReloadData(); | ||||||
|  | 
 | ||||||
|  | signals: | ||||||
|  |    void DataReloaded(); | ||||||
|  | 
 | ||||||
|  | private: | ||||||
|  |    class Impl; | ||||||
|  |    std::unique_ptr<Impl> p; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | } // namespace map
 | ||||||
|  | } // namespace qt
 | ||||||
|  | } // namespace scwx
 | ||||||
|  | @ -1,5 +1,6 @@ | ||||||
| #include <scwx/qt/map/radar_product_layer.hpp> | #include <scwx/qt/map/radar_product_layer.hpp> | ||||||
| #include <scwx/qt/gl/shader_program.hpp> | #include <scwx/qt/gl/shader_program.hpp> | ||||||
|  | #include <scwx/qt/util/maplibre.hpp> | ||||||
| #include <scwx/util/logger.hpp> | #include <scwx/util/logger.hpp> | ||||||
| 
 | 
 | ||||||
| #include <execution> | #include <execution> | ||||||
|  | @ -31,9 +32,6 @@ static constexpr uint32_t MAX_DATA_MOMENT_GATES = 1840; | ||||||
| static const std::string logPrefix_ = "scwx::qt::map::radar_product_layer"; | static const std::string logPrefix_ = "scwx::qt::map::radar_product_layer"; | ||||||
| static const auto        logger_    = scwx::util::Logger::Create(logPrefix_); | static const auto        logger_    = scwx::util::Logger::Create(logPrefix_); | ||||||
| 
 | 
 | ||||||
| static glm::vec2 |  | ||||||
| LatLongToScreenCoordinate(const QMapLibreGL::Coordinate& coordinate); |  | ||||||
| 
 |  | ||||||
| class RadarProductLayerImpl | class RadarProductLayerImpl | ||||||
| { | { | ||||||
| public: | public: | ||||||
|  | @ -290,7 +288,7 @@ void RadarProductLayer::Render( | ||||||
| 
 | 
 | ||||||
|    gl.glUniform2fv(p->uMapScreenCoordLocation_, |    gl.glUniform2fv(p->uMapScreenCoordLocation_, | ||||||
|                    1, |                    1, | ||||||
|                    glm::value_ptr(LatLongToScreenCoordinate( |                    glm::value_ptr(util::maplibre::LatLongToScreenCoordinate( | ||||||
|                       {params.latitude, params.longitude}))); |                       {params.latitude, params.longitude}))); | ||||||
| 
 | 
 | ||||||
|    gl.glUniformMatrix4fv( |    gl.glUniformMatrix4fv( | ||||||
|  | @ -358,22 +356,6 @@ void RadarProductLayer::UpdateColorTable() | ||||||
|    gl.glUniform1f(p->uDataMomentScaleLocation_, scale); |    gl.glUniform1f(p->uDataMomentScaleLocation_, scale); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static glm::vec2 |  | ||||||
| LatLongToScreenCoordinate(const QMapLibreGL::Coordinate& coordinate) |  | ||||||
| { |  | ||||||
|    static constexpr double RAD2DEG_D = 180.0 / M_PI; |  | ||||||
| 
 |  | ||||||
|    double latitude = std::clamp( |  | ||||||
|       coordinate.first, -mbgl::util::LATITUDE_MAX, mbgl::util::LATITUDE_MAX); |  | ||||||
|    glm::vec2 screen { |  | ||||||
|       mbgl::util::LONGITUDE_MAX + coordinate.second, |  | ||||||
|       -(mbgl::util::LONGITUDE_MAX - |  | ||||||
|         RAD2DEG_D * |  | ||||||
|            std::log(std::tan(M_PI / 4.0 + |  | ||||||
|                              latitude * M_PI / mbgl::util::DEGREES_MAX)))}; |  | ||||||
|    return screen; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| } // namespace map
 | } // namespace map
 | ||||||
| } // namespace qt
 | } // namespace qt
 | ||||||
| } // namespace scwx
 | } // namespace scwx
 | ||||||
|  |  | ||||||
|  | @ -1,4 +1,5 @@ | ||||||
| #include <scwx/qt/map/radar_range_layer.hpp> | #include <scwx/qt/map/radar_range_layer.hpp> | ||||||
|  | #include <scwx/qt/types/layer_types.hpp> | ||||||
| #include <scwx/qt/util/geographic_lib.hpp> | #include <scwx/qt/util/geographic_lib.hpp> | ||||||
| #include <scwx/util/logger.hpp> | #include <scwx/util/logger.hpp> | ||||||
| 
 | 
 | ||||||
|  | @ -22,11 +23,14 @@ void RadarRangeLayer::Add(std::shared_ptr<QMapLibreGL::Map> map, | ||||||
|                           QMapLibreGL::Coordinate           center, |                           QMapLibreGL::Coordinate           center, | ||||||
|                           const QString&                    before) |                           const QString&                    before) | ||||||
| { | { | ||||||
|  |    static const QString layerId = QString::fromStdString(types::GetLayerName( | ||||||
|  |       types::LayerType::Data, types::DataLayer::RadarRange)); | ||||||
|  | 
 | ||||||
|    logger_->debug("Add()"); |    logger_->debug("Add()"); | ||||||
| 
 | 
 | ||||||
|    if (map->layerExists("rangeCircleLayer")) |    if (map->layerExists(layerId)) | ||||||
|    { |    { | ||||||
|       map->removeLayer("rangeCircleLayer"); |       map->removeLayer(layerId); | ||||||
|    } |    } | ||||||
|    if (map->sourceExists("rangeCircleSource")) |    if (map->sourceExists("rangeCircleSource")) | ||||||
|    { |    { | ||||||
|  | @ -39,12 +43,10 @@ void RadarRangeLayer::Add(std::shared_ptr<QMapLibreGL::Map> map, | ||||||
|    map->addSource( |    map->addSource( | ||||||
|       "rangeCircleSource", |       "rangeCircleSource", | ||||||
|       {{"type", "geojson"}, {"data", QVariant::fromValue(*rangeCircle)}}); |       {{"type", "geojson"}, {"data", QVariant::fromValue(*rangeCircle)}}); | ||||||
|    map->addLayer({{"id", "rangeCircleLayer"}, |    map->addLayer( | ||||||
|                   {"type", "line"}, |       {{"id", layerId}, {"type", "line"}, {"source", "rangeCircleSource"}}, | ||||||
|                   {"source", "rangeCircleSource"}}, |       before); | ||||||
|                  before); |    map->setPaintProperty(layerId, "line-color", "rgba(128, 128, 128, 128)"); | ||||||
|    map->setPaintProperty( |  | ||||||
|       "rangeCircleLayer", "line-color", "rgba(128, 128, 128, 128)"); |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void RadarRangeLayer::Update(std::shared_ptr<QMapLibreGL::Map> map, | void RadarRangeLayer::Update(std::shared_ptr<QMapLibreGL::Map> map, | ||||||
|  |  | ||||||
|  | @ -1,3 +1,5 @@ | ||||||
|  | #define NOMINMAX | ||||||
|  | 
 | ||||||
| #include <scwx/qt/model/alert_model.hpp> | #include <scwx/qt/model/alert_model.hpp> | ||||||
| #include <scwx/qt/config/county_database.hpp> | #include <scwx/qt/config/county_database.hpp> | ||||||
| #include <scwx/qt/manager/text_event_manager.hpp> | #include <scwx/qt/manager/text_event_manager.hpp> | ||||||
|  |  | ||||||
							
								
								
									
										1067
									
								
								scwx-qt/source/scwx/qt/model/layer_model.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										87
									
								
								scwx-qt/source/scwx/qt/model/layer_model.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						|  | @ -0,0 +1,87 @@ | ||||||
|  | #pragma once | ||||||
|  | 
 | ||||||
|  | #include <scwx/qt/types/layer_types.hpp> | ||||||
|  | #include <scwx/util/iterator.hpp> | ||||||
|  | 
 | ||||||
|  | #include <memory> | ||||||
|  | 
 | ||||||
|  | #include <QAbstractTableModel> | ||||||
|  | 
 | ||||||
|  | namespace scwx | ||||||
|  | { | ||||||
|  | namespace qt | ||||||
|  | { | ||||||
|  | namespace model | ||||||
|  | { | ||||||
|  | 
 | ||||||
|  | class LayerModel : public QAbstractTableModel | ||||||
|  | { | ||||||
|  |    Q_DISABLE_COPY_MOVE(LayerModel) | ||||||
|  | 
 | ||||||
|  | public: | ||||||
|  |    enum class Column : int | ||||||
|  |    { | ||||||
|  |       Order       = 0, | ||||||
|  |       DisplayMap1 = 1, | ||||||
|  |       DisplayMap2 = 2, | ||||||
|  |       DisplayMap3 = 3, | ||||||
|  |       DisplayMap4 = 4, | ||||||
|  |       Type        = 5, | ||||||
|  |       Enabled     = 6, | ||||||
|  |       Description = 7 | ||||||
|  |    }; | ||||||
|  |    typedef scwx::util::Iterator<Column, Column::Order, Column::Description> | ||||||
|  |       ColumnIterator; | ||||||
|  | 
 | ||||||
|  |    explicit LayerModel(QObject* parent = nullptr); | ||||||
|  |    ~LayerModel(); | ||||||
|  | 
 | ||||||
|  |    types::LayerVector GetLayers() const; | ||||||
|  | 
 | ||||||
|  |    void ResetLayers(); | ||||||
|  | 
 | ||||||
|  |    int rowCount(const QModelIndex& parent = QModelIndex()) const override; | ||||||
|  |    int columnCount(const QModelIndex& parent = QModelIndex()) const override; | ||||||
|  | 
 | ||||||
|  |    Qt::ItemFlags   flags(const QModelIndex& index) const override; | ||||||
|  |    Qt::DropActions supportedDropActions() const override; | ||||||
|  | 
 | ||||||
|  |    bool IsMovable(int row) const; | ||||||
|  | 
 | ||||||
|  |    QVariant data(const QModelIndex& index, | ||||||
|  |                  int                role = Qt::DisplayRole) const override; | ||||||
|  |    QVariant headerData(int             section, | ||||||
|  |                        Qt::Orientation orientation, | ||||||
|  |                        int             role = Qt::DisplayRole) const override; | ||||||
|  | 
 | ||||||
|  |    bool setData(const QModelIndex& index, | ||||||
|  |                 const QVariant&    value, | ||||||
|  |                 int                role = Qt::EditRole) override; | ||||||
|  | 
 | ||||||
|  |    QStringList mimeTypes() const override; | ||||||
|  |    QMimeData*  mimeData(const QModelIndexList& indexes) const override; | ||||||
|  | 
 | ||||||
|  |    bool dropMimeData(const QMimeData*   data, | ||||||
|  |                      Qt::DropAction     action, | ||||||
|  |                      int                row, | ||||||
|  |                      int                column, | ||||||
|  |                      const QModelIndex& parent) override; | ||||||
|  |    bool removeRows(int                row, | ||||||
|  |                    int                count, | ||||||
|  |                    const QModelIndex& parent = QModelIndex()) override; | ||||||
|  |    bool moveRows(const QModelIndex& sourceParent, | ||||||
|  |                  int                sourceRow, | ||||||
|  |                  int                count, | ||||||
|  |                  const QModelIndex& destinationParent, | ||||||
|  |                  int                destinationChild) override; | ||||||
|  | 
 | ||||||
|  |    static std::shared_ptr<LayerModel> Instance(); | ||||||
|  | 
 | ||||||
|  | private: | ||||||
|  |    class Impl; | ||||||
|  |    std::unique_ptr<Impl> p; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | } // namespace model
 | ||||||
|  | } // namespace qt
 | ||||||
|  | } // namespace scwx
 | ||||||
							
								
								
									
										377
									
								
								scwx-qt/source/scwx/qt/model/placefile_model.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						|  | @ -0,0 +1,377 @@ | ||||||
|  | #include <scwx/qt/model/placefile_model.hpp> | ||||||
|  | #include <scwx/qt/manager/placefile_manager.hpp> | ||||||
|  | #include <scwx/qt/types/qt_types.hpp> | ||||||
|  | #include <scwx/util/logger.hpp> | ||||||
|  | 
 | ||||||
|  | #include <QApplication> | ||||||
|  | #include <QCheckBox> | ||||||
|  | #include <QFontMetrics> | ||||||
|  | #include <QStyle> | ||||||
|  | #include <QStyleOption> | ||||||
|  | 
 | ||||||
|  | namespace scwx | ||||||
|  | { | ||||||
|  | namespace qt | ||||||
|  | { | ||||||
|  | namespace model | ||||||
|  | { | ||||||
|  | 
 | ||||||
|  | static const std::string logPrefix_ = "scwx::qt::model::placefile_model"; | ||||||
|  | static const auto        logger_    = scwx::util::Logger::Create(logPrefix_); | ||||||
|  | 
 | ||||||
|  | static constexpr int kFirstColumn = | ||||||
|  |    static_cast<int>(PlacefileModel::Column::Enabled); | ||||||
|  | static constexpr int kLastColumn = | ||||||
|  |    static_cast<int>(PlacefileModel::Column::Placefile); | ||||||
|  | static constexpr int kNumColumns = kLastColumn - kFirstColumn + 1; | ||||||
|  | 
 | ||||||
|  | class PlacefileModelImpl | ||||||
|  | { | ||||||
|  | public: | ||||||
|  |    explicit PlacefileModelImpl() {} | ||||||
|  |    ~PlacefileModelImpl() = default; | ||||||
|  | 
 | ||||||
|  |    std::shared_ptr<manager::PlacefileManager> placefileManager_ { | ||||||
|  |       manager::PlacefileManager::Instance()}; | ||||||
|  | 
 | ||||||
|  |    std::vector<std::string> placefileNames_ {}; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | PlacefileModel::PlacefileModel(QObject* parent) : | ||||||
|  |     QAbstractTableModel(parent), p(std::make_unique<PlacefileModelImpl>()) | ||||||
|  | { | ||||||
|  |    connect(p->placefileManager_.get(), | ||||||
|  |            &manager::PlacefileManager::PlacefileEnabled, | ||||||
|  |            this, | ||||||
|  |            &PlacefileModel::HandlePlacefileUpdate); | ||||||
|  | 
 | ||||||
|  |    connect(p->placefileManager_.get(), | ||||||
|  |            &manager::PlacefileManager::PlacefileRemoved, | ||||||
|  |            this, | ||||||
|  |            &PlacefileModel::HandlePlacefileRemoved); | ||||||
|  | 
 | ||||||
|  |    connect(p->placefileManager_.get(), | ||||||
|  |            &manager::PlacefileManager::PlacefileRenamed, | ||||||
|  |            this, | ||||||
|  |            &PlacefileModel::HandlePlacefileRenamed); | ||||||
|  | 
 | ||||||
|  |    connect(p->placefileManager_.get(), | ||||||
|  |            &manager::PlacefileManager::PlacefileUpdated, | ||||||
|  |            this, | ||||||
|  |            &PlacefileModel::HandlePlacefileUpdate); | ||||||
|  | } | ||||||
|  | PlacefileModel::~PlacefileModel() = default; | ||||||
|  | 
 | ||||||
|  | int PlacefileModel::rowCount(const QModelIndex& parent) const | ||||||
|  | { | ||||||
|  |    return parent.isValid() ? 0 : static_cast<int>(p->placefileNames_.size()); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | int PlacefileModel::columnCount(const QModelIndex& parent) const | ||||||
|  | { | ||||||
|  |    return parent.isValid() ? 0 : kNumColumns; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | Qt::ItemFlags PlacefileModel::flags(const QModelIndex& index) const | ||||||
|  | { | ||||||
|  |    Qt::ItemFlags flags = QAbstractTableModel::flags(index); | ||||||
|  | 
 | ||||||
|  |    switch (index.column()) | ||||||
|  |    { | ||||||
|  |    case static_cast<int>(Column::Enabled): | ||||||
|  |    case static_cast<int>(Column::Thresholds): | ||||||
|  |       flags |= Qt::ItemFlag::ItemIsUserCheckable | Qt::ItemFlag::ItemIsEditable; | ||||||
|  |       break; | ||||||
|  | 
 | ||||||
|  |    case static_cast<int>(Column::Placefile): | ||||||
|  |       flags |= Qt::ItemFlag::ItemIsEditable; | ||||||
|  |       break; | ||||||
|  | 
 | ||||||
|  |    default: | ||||||
|  |       break; | ||||||
|  |    } | ||||||
|  | 
 | ||||||
|  |    return flags; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | QVariant PlacefileModel::data(const QModelIndex& index, int role) const | ||||||
|  | { | ||||||
|  |    static const QString enabledString  = QObject::tr("Enabled"); | ||||||
|  |    static const QString disabledString = QObject::tr("Disabled"); | ||||||
|  | 
 | ||||||
|  |    static const QString thresholdsEnabledString = | ||||||
|  |       QObject::tr("Thresholds Enabled"); | ||||||
|  |    static const QString thresholdsDisabledString = | ||||||
|  |       QObject::tr("Thresholds Disabled"); | ||||||
|  | 
 | ||||||
|  |    if (!index.isValid() || index.row() < 0 || | ||||||
|  |        static_cast<std::size_t>(index.row()) >= p->placefileNames_.size()) | ||||||
|  |    { | ||||||
|  |       return QVariant(); | ||||||
|  |    } | ||||||
|  | 
 | ||||||
|  |    const auto& placefileName = p->placefileNames_.at(index.row()); | ||||||
|  | 
 | ||||||
|  |    switch (index.column()) | ||||||
|  |    { | ||||||
|  |    case static_cast<int>(Column::Enabled): | ||||||
|  |       if (role == Qt::ItemDataRole::ToolTipRole) | ||||||
|  |       { | ||||||
|  |          return p->placefileManager_->placefile_enabled(placefileName) ? | ||||||
|  |                    enabledString : | ||||||
|  |                    disabledString; | ||||||
|  |       } | ||||||
|  |       else if (role == Qt::ItemDataRole::CheckStateRole) | ||||||
|  |       { | ||||||
|  |          return static_cast<int>( | ||||||
|  |             p->placefileManager_->placefile_enabled(placefileName) ? | ||||||
|  |                Qt::CheckState::Checked : | ||||||
|  |                Qt::CheckState::Unchecked); | ||||||
|  |       } | ||||||
|  |       else if (role == types::ItemDataRole::SortRole) | ||||||
|  |       { | ||||||
|  |          return p->placefileManager_->placefile_enabled(placefileName); | ||||||
|  |       } | ||||||
|  |       break; | ||||||
|  | 
 | ||||||
|  |    case static_cast<int>(Column::Thresholds): | ||||||
|  |       if (role == Qt::ItemDataRole::ToolTipRole) | ||||||
|  |       { | ||||||
|  |          return p->placefileManager_->placefile_thresholded(placefileName) ? | ||||||
|  |                    thresholdsEnabledString : | ||||||
|  |                    thresholdsDisabledString; | ||||||
|  |       } | ||||||
|  |       else if (role == Qt::ItemDataRole::CheckStateRole) | ||||||
|  |       { | ||||||
|  |          return static_cast<int>( | ||||||
|  |             p->placefileManager_->placefile_thresholded(placefileName) ? | ||||||
|  |                Qt::CheckState::Checked : | ||||||
|  |                Qt::CheckState::Unchecked); | ||||||
|  |       } | ||||||
|  |       else if (role == types::ItemDataRole::SortRole) | ||||||
|  |       { | ||||||
|  |          return p->placefileManager_->placefile_thresholded(placefileName); | ||||||
|  |       } | ||||||
|  |       break; | ||||||
|  | 
 | ||||||
|  |    case static_cast<int>(Column::Placefile): | ||||||
|  |       if (role == Qt::ItemDataRole::DisplayRole || | ||||||
|  |           role == Qt::ItemDataRole::ToolTipRole) | ||||||
|  |       { | ||||||
|  |          std::string description = placefileName; | ||||||
|  |          std::string title = | ||||||
|  |             p->placefileManager_->placefile_title(placefileName); | ||||||
|  |          if (!title.empty()) | ||||||
|  |          { | ||||||
|  |             description = title + '\n' + description; | ||||||
|  |          } | ||||||
|  | 
 | ||||||
|  |          return QString::fromStdString(description); | ||||||
|  |       } | ||||||
|  |       else if (role == Qt::ItemDataRole::EditRole || | ||||||
|  |                role == types::ItemDataRole::SortRole) | ||||||
|  |       { | ||||||
|  |          return QString::fromStdString(placefileName); | ||||||
|  |       } | ||||||
|  |       break; | ||||||
|  | 
 | ||||||
|  |    default: | ||||||
|  |       break; | ||||||
|  |    } | ||||||
|  | 
 | ||||||
|  |    return QVariant(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | QVariant PlacefileModel::headerData(int             section, | ||||||
|  |                                     Qt::Orientation orientation, | ||||||
|  |                                     int             role) const | ||||||
|  | { | ||||||
|  |    if (role == Qt::ItemDataRole::DisplayRole) | ||||||
|  |    { | ||||||
|  |       if (orientation == Qt::Horizontal) | ||||||
|  |       { | ||||||
|  |          switch (section) | ||||||
|  |          { | ||||||
|  |          case static_cast<int>(Column::Enabled): | ||||||
|  |             return tr("E"); | ||||||
|  | 
 | ||||||
|  |          case static_cast<int>(Column::Thresholds): | ||||||
|  |             return tr("T"); | ||||||
|  | 
 | ||||||
|  |          case static_cast<int>(Column::Placefile): | ||||||
|  |             return tr("Placefile"); | ||||||
|  | 
 | ||||||
|  |          default: | ||||||
|  |             break; | ||||||
|  |          } | ||||||
|  |       } | ||||||
|  |    } | ||||||
|  |    else if (role == Qt::ItemDataRole::ToolTipRole) | ||||||
|  |    { | ||||||
|  |       switch (section) | ||||||
|  |       { | ||||||
|  |       case static_cast<int>(Column::Enabled): | ||||||
|  |          return tr("Enabled"); | ||||||
|  | 
 | ||||||
|  |       case static_cast<int>(Column::Thresholds): | ||||||
|  |          return tr("Thresholds"); | ||||||
|  | 
 | ||||||
|  |       default: | ||||||
|  |          break; | ||||||
|  |       } | ||||||
|  |    } | ||||||
|  |    else if (role == Qt::ItemDataRole::SizeHintRole) | ||||||
|  |    { | ||||||
|  |       switch (section) | ||||||
|  |       { | ||||||
|  |       case static_cast<int>(Column::Enabled): | ||||||
|  |       case static_cast<int>(Column::Thresholds): | ||||||
|  |       { | ||||||
|  |          static const QCheckBox checkBox {}; | ||||||
|  |          QStyleOptionButton     option {}; | ||||||
|  |          option.initFrom(&checkBox); | ||||||
|  | 
 | ||||||
|  |          // Width values from QCheckBox
 | ||||||
|  |          return QApplication::style()->sizeFromContents( | ||||||
|  |             QStyle::ContentsType::CT_CheckBox, | ||||||
|  |             &option, | ||||||
|  |             {option.iconSize.width() + 4, 0}); | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       default: | ||||||
|  |          break; | ||||||
|  |       } | ||||||
|  |    } | ||||||
|  | 
 | ||||||
|  |    return QVariant(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool PlacefileModel::setData(const QModelIndex& index, | ||||||
|  |                              const QVariant&    value, | ||||||
|  |                              int                role) | ||||||
|  | { | ||||||
|  |    if (!index.isValid() || index.row() < 0 || | ||||||
|  |        static_cast<std::size_t>(index.row()) >= p->placefileNames_.size()) | ||||||
|  |    { | ||||||
|  |       return false; | ||||||
|  |    } | ||||||
|  | 
 | ||||||
|  |    const auto& placefileName = p->placefileNames_.at(index.row()); | ||||||
|  |    bool        result        = false; | ||||||
|  | 
 | ||||||
|  |    switch (index.column()) | ||||||
|  |    { | ||||||
|  |    case static_cast<int>(Column::Enabled): | ||||||
|  |       if (role == Qt::ItemDataRole::CheckStateRole) | ||||||
|  |       { | ||||||
|  |          p->placefileManager_->set_placefile_enabled(placefileName, | ||||||
|  |                                                      value.toBool()); | ||||||
|  |          result = true; | ||||||
|  |       } | ||||||
|  |       break; | ||||||
|  | 
 | ||||||
|  |    case static_cast<int>(Column::Thresholds): | ||||||
|  |       if (role == Qt::ItemDataRole::CheckStateRole) | ||||||
|  |       { | ||||||
|  |          p->placefileManager_->set_placefile_thresholded(placefileName, | ||||||
|  |                                                          value.toBool()); | ||||||
|  |          result = true; | ||||||
|  |       } | ||||||
|  |       break; | ||||||
|  | 
 | ||||||
|  |    case static_cast<int>(Column::Placefile): | ||||||
|  |       if (role == Qt::ItemDataRole::EditRole) | ||||||
|  |       { | ||||||
|  |          QString str = value.toString(); | ||||||
|  |          if (!str.isEmpty()) | ||||||
|  |          { | ||||||
|  |             p->placefileManager_->set_placefile_url(placefileName, | ||||||
|  |                                                     str.toStdString()); | ||||||
|  |             result = true; | ||||||
|  |          } | ||||||
|  |       } | ||||||
|  |       break; | ||||||
|  | 
 | ||||||
|  |    default: | ||||||
|  |       break; | ||||||
|  |    } | ||||||
|  | 
 | ||||||
|  |    if (result) | ||||||
|  |    { | ||||||
|  |       Q_EMIT dataChanged(index, index); | ||||||
|  |    } | ||||||
|  | 
 | ||||||
|  |    return result; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void PlacefileModel::HandlePlacefileRemoved(const std::string& name) | ||||||
|  | { | ||||||
|  |    auto it = | ||||||
|  |       std::find(p->placefileNames_.begin(), p->placefileNames_.end(), name); | ||||||
|  | 
 | ||||||
|  |    if (it != p->placefileNames_.end()) | ||||||
|  |    { | ||||||
|  |       // Placefile exists, delete row
 | ||||||
|  |       const int row = std::distance(p->placefileNames_.begin(), it); | ||||||
|  | 
 | ||||||
|  |       beginRemoveRows(QModelIndex(), row, row); | ||||||
|  |       p->placefileNames_.erase(it); | ||||||
|  |       endRemoveRows(); | ||||||
|  |    } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void PlacefileModel::HandlePlacefileRenamed(const std::string& oldName, | ||||||
|  |                                             const std::string& newName) | ||||||
|  | { | ||||||
|  |    auto it = | ||||||
|  |       std::find(p->placefileNames_.begin(), p->placefileNames_.end(), oldName); | ||||||
|  | 
 | ||||||
|  |    if (it != p->placefileNames_.end()) | ||||||
|  |    { | ||||||
|  |       // Placefile exists, mark row as updated
 | ||||||
|  |       const int   row         = std::distance(p->placefileNames_.begin(), it); | ||||||
|  |       QModelIndex topLeft     = createIndex(row, kFirstColumn); | ||||||
|  |       QModelIndex bottomRight = createIndex(row, kLastColumn); | ||||||
|  | 
 | ||||||
|  |       // Rename placefile
 | ||||||
|  |       *it = newName; | ||||||
|  | 
 | ||||||
|  |       Q_EMIT dataChanged(topLeft, bottomRight); | ||||||
|  |    } | ||||||
|  |    else | ||||||
|  |    { | ||||||
|  |       // Placefile is new, append row
 | ||||||
|  |       const int newIndex = static_cast<int>(p->placefileNames_.size()); | ||||||
|  |       beginInsertRows(QModelIndex(), newIndex, newIndex); | ||||||
|  |       p->placefileNames_.push_back(newName); | ||||||
|  |       endInsertRows(); | ||||||
|  |    } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void PlacefileModel::HandlePlacefileUpdate(const std::string& name) | ||||||
|  | { | ||||||
|  |    auto it = | ||||||
|  |       std::find(p->placefileNames_.begin(), p->placefileNames_.end(), name); | ||||||
|  | 
 | ||||||
|  |    if (it != p->placefileNames_.end()) | ||||||
|  |    { | ||||||
|  |       // Placefile exists, mark row as updated
 | ||||||
|  |       const int   row         = std::distance(p->placefileNames_.begin(), it); | ||||||
|  |       QModelIndex topLeft     = createIndex(row, kFirstColumn); | ||||||
|  |       QModelIndex bottomRight = createIndex(row, kLastColumn); | ||||||
|  | 
 | ||||||
|  |       Q_EMIT dataChanged(topLeft, bottomRight); | ||||||
|  |    } | ||||||
|  |    else | ||||||
|  |    { | ||||||
|  |       // Placefile is new, append row
 | ||||||
|  |       const int newIndex = static_cast<int>(p->placefileNames_.size()); | ||||||
|  |       beginInsertRows(QModelIndex(), newIndex, newIndex); | ||||||
|  |       p->placefileNames_.push_back(name); | ||||||
|  |       endInsertRows(); | ||||||
|  |    } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | } // namespace model
 | ||||||
|  | } // namespace qt
 | ||||||
|  | } // namespace scwx
 | ||||||
							
								
								
									
										60
									
								
								scwx-qt/source/scwx/qt/model/placefile_model.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						|  | @ -0,0 +1,60 @@ | ||||||
|  | #pragma once | ||||||
|  | 
 | ||||||
|  | #include <scwx/qt/types/text_event_key.hpp> | ||||||
|  | #include <scwx/common/geographic.hpp> | ||||||
|  | 
 | ||||||
|  | #include <memory> | ||||||
|  | 
 | ||||||
|  | #include <QAbstractTableModel> | ||||||
|  | 
 | ||||||
|  | namespace scwx | ||||||
|  | { | ||||||
|  | namespace qt | ||||||
|  | { | ||||||
|  | namespace model | ||||||
|  | { | ||||||
|  | 
 | ||||||
|  | class PlacefileModelImpl; | ||||||
|  | 
 | ||||||
|  | class PlacefileModel : public QAbstractTableModel | ||||||
|  | { | ||||||
|  | public: | ||||||
|  |    enum class Column : int | ||||||
|  |    { | ||||||
|  |       Enabled    = 0, | ||||||
|  |       Thresholds = 1, | ||||||
|  |       Placefile  = 2 | ||||||
|  |    }; | ||||||
|  | 
 | ||||||
|  |    explicit PlacefileModel(QObject* parent = nullptr); | ||||||
|  |    ~PlacefileModel(); | ||||||
|  | 
 | ||||||
|  |    int rowCount(const QModelIndex& parent = QModelIndex()) const override; | ||||||
|  |    int columnCount(const QModelIndex& parent = QModelIndex()) const override; | ||||||
|  | 
 | ||||||
|  |    Qt::ItemFlags flags(const QModelIndex& index) const override; | ||||||
|  | 
 | ||||||
|  |    QVariant data(const QModelIndex& index, | ||||||
|  |                  int                role = Qt::DisplayRole) const override; | ||||||
|  |    QVariant headerData(int             section, | ||||||
|  |                        Qt::Orientation orientation, | ||||||
|  |                        int             role = Qt::DisplayRole) const override; | ||||||
|  | 
 | ||||||
|  |    bool setData(const QModelIndex& index, | ||||||
|  |                 const QVariant&    value, | ||||||
|  |                 int                role = Qt::EditRole) override; | ||||||
|  | 
 | ||||||
|  | public slots: | ||||||
|  |    void HandlePlacefileRemoved(const std::string& name); | ||||||
|  |    void HandlePlacefileRenamed(const std::string& oldName, | ||||||
|  |                                const std::string& newName); | ||||||
|  |    void HandlePlacefileUpdate(const std::string& name); | ||||||
|  | 
 | ||||||
|  | private: | ||||||
|  |    friend class PlacefileModelImpl; | ||||||
|  |    std::unique_ptr<PlacefileModelImpl> p; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | } // namespace model
 | ||||||
|  | } // namespace qt
 | ||||||
|  | } // namespace scwx
 | ||||||
|  | @ -16,10 +16,10 @@ namespace settings | ||||||
| 
 | 
 | ||||||
| static const std::string logPrefix_ = "scwx::qt::settings::general_settings"; | static const std::string logPrefix_ = "scwx::qt::settings::general_settings"; | ||||||
| 
 | 
 | ||||||
| class GeneralSettingsImpl | class GeneralSettings::Impl | ||||||
| { | { | ||||||
| public: | public: | ||||||
|    explicit GeneralSettingsImpl() |    explicit Impl() | ||||||
|    { |    { | ||||||
|       std::string defaultDefaultAlertActionValue = |       std::string defaultDefaultAlertActionValue = | ||||||
|          types::GetAlertActionName(types::AlertAction::Go); |          types::GetAlertActionName(types::AlertAction::Go); | ||||||
|  | @ -29,6 +29,7 @@ public: | ||||||
|       boost::to_lower(defaultDefaultAlertActionValue); |       boost::to_lower(defaultDefaultAlertActionValue); | ||||||
|       boost::to_lower(defaultMapProviderValue); |       boost::to_lower(defaultMapProviderValue); | ||||||
| 
 | 
 | ||||||
|  |       antiAliasingEnabled_.SetDefault(true); | ||||||
|       debugEnabled_.SetDefault(false); |       debugEnabled_.SetDefault(false); | ||||||
|       defaultAlertAction_.SetDefault(defaultDefaultAlertActionValue); |       defaultAlertAction_.SetDefault(defaultDefaultAlertActionValue); | ||||||
|       defaultRadarSite_.SetDefault("KLSX"); |       defaultRadarSite_.SetDefault("KLSX"); | ||||||
|  | @ -102,8 +103,9 @@ public: | ||||||
|                                    { return !value.empty(); }); |                                    { return !value.empty(); }); | ||||||
|    } |    } | ||||||
| 
 | 
 | ||||||
|    ~GeneralSettingsImpl() {} |    ~Impl() {} | ||||||
| 
 | 
 | ||||||
|  |    SettingsVariable<bool>        antiAliasingEnabled_ {"anti_aliasing_enabled"}; | ||||||
|    SettingsVariable<bool>        debugEnabled_ {"debug_enabled"}; |    SettingsVariable<bool>        debugEnabled_ {"debug_enabled"}; | ||||||
|    SettingsVariable<std::string> defaultAlertAction_ {"default_alert_action"}; |    SettingsVariable<std::string> defaultAlertAction_ {"default_alert_action"}; | ||||||
|    SettingsVariable<std::string> defaultRadarSite_ {"default_radar_site"}; |    SettingsVariable<std::string> defaultRadarSite_ {"default_radar_site"}; | ||||||
|  | @ -120,9 +122,10 @@ public: | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| GeneralSettings::GeneralSettings() : | GeneralSettings::GeneralSettings() : | ||||||
|     SettingsCategory("general"), p(std::make_unique<GeneralSettingsImpl>()) |     SettingsCategory("general"), p(std::make_unique<Impl>()) | ||||||
| { | { | ||||||
|    RegisterVariables({&p->debugEnabled_, |    RegisterVariables({&p->antiAliasingEnabled_, | ||||||
|  |                       &p->debugEnabled_, | ||||||
|                       &p->defaultAlertAction_, |                       &p->defaultAlertAction_, | ||||||
|                       &p->defaultRadarSite_, |                       &p->defaultRadarSite_, | ||||||
|                       &p->fontSizes_, |                       &p->fontSizes_, | ||||||
|  | @ -143,6 +146,11 @@ GeneralSettings::GeneralSettings(GeneralSettings&&) noexcept = default; | ||||||
| GeneralSettings& | GeneralSettings& | ||||||
| GeneralSettings::operator=(GeneralSettings&&) noexcept = default; | GeneralSettings::operator=(GeneralSettings&&) noexcept = default; | ||||||
| 
 | 
 | ||||||
|  | SettingsVariable<bool>& GeneralSettings::anti_aliasing_enabled() const | ||||||
|  | { | ||||||
|  |    return p->antiAliasingEnabled_; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| SettingsVariable<bool>& GeneralSettings::debug_enabled() const | SettingsVariable<bool>& GeneralSettings::debug_enabled() const | ||||||
| { | { | ||||||
|    return p->debugEnabled_; |    return p->debugEnabled_; | ||||||
|  | @ -221,9 +229,16 @@ bool GeneralSettings::Shutdown() | ||||||
|    return dataChanged; |    return dataChanged; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | GeneralSettings& GeneralSettings::Instance() | ||||||
|  | { | ||||||
|  |    static GeneralSettings generalSettings_; | ||||||
|  |    return generalSettings_; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| bool operator==(const GeneralSettings& lhs, const GeneralSettings& rhs) | bool operator==(const GeneralSettings& lhs, const GeneralSettings& rhs) | ||||||
| { | { | ||||||
|    return (lhs.p->debugEnabled_ == rhs.p->debugEnabled_ && |    return (lhs.p->antiAliasingEnabled_ == rhs.p->antiAliasingEnabled_ && | ||||||
|  |            lhs.p->debugEnabled_ == rhs.p->debugEnabled_ && | ||||||
|            lhs.p->defaultAlertAction_ == rhs.p->defaultAlertAction_ && |            lhs.p->defaultAlertAction_ == rhs.p->defaultAlertAction_ && | ||||||
|            lhs.p->defaultRadarSite_ == rhs.p->defaultRadarSite_ && |            lhs.p->defaultRadarSite_ == rhs.p->defaultRadarSite_ && | ||||||
|            lhs.p->fontSizes_ == rhs.p->fontSizes_ && |            lhs.p->fontSizes_ == rhs.p->fontSizes_ && | ||||||
|  |  | ||||||
|  | @ -13,8 +13,6 @@ namespace qt | ||||||
| namespace settings | namespace settings | ||||||
| { | { | ||||||
| 
 | 
 | ||||||
| class GeneralSettingsImpl; |  | ||||||
| 
 |  | ||||||
| class GeneralSettings : public SettingsCategory | class GeneralSettings : public SettingsCategory | ||||||
| { | { | ||||||
| public: | public: | ||||||
|  | @ -27,6 +25,7 @@ public: | ||||||
|    GeneralSettings(GeneralSettings&&) noexcept; |    GeneralSettings(GeneralSettings&&) noexcept; | ||||||
|    GeneralSettings& operator=(GeneralSettings&&) noexcept; |    GeneralSettings& operator=(GeneralSettings&&) noexcept; | ||||||
| 
 | 
 | ||||||
|  |    SettingsVariable<bool>&                       anti_aliasing_enabled() const; | ||||||
|    SettingsVariable<bool>&                       debug_enabled() const; |    SettingsVariable<bool>&                       debug_enabled() const; | ||||||
|    SettingsVariable<std::string>&                default_alert_action() const; |    SettingsVariable<std::string>&                default_alert_action() const; | ||||||
|    SettingsVariable<std::string>&                default_radar_site() const; |    SettingsVariable<std::string>&                default_radar_site() const; | ||||||
|  | @ -41,13 +40,16 @@ public: | ||||||
|    SettingsVariable<std::string>&                maptiler_api_key() const; |    SettingsVariable<std::string>&                maptiler_api_key() const; | ||||||
|    SettingsVariable<bool>& update_notifications_enabled() const; |    SettingsVariable<bool>& update_notifications_enabled() const; | ||||||
| 
 | 
 | ||||||
|  |    static GeneralSettings& Instance(); | ||||||
|  | 
 | ||||||
|    friend bool operator==(const GeneralSettings& lhs, |    friend bool operator==(const GeneralSettings& lhs, | ||||||
|                           const GeneralSettings& rhs); |                           const GeneralSettings& rhs); | ||||||
| 
 | 
 | ||||||
|    bool Shutdown(); |    bool Shutdown(); | ||||||
| 
 | 
 | ||||||
| private: | private: | ||||||
|    std::unique_ptr<GeneralSettingsImpl> p; |    class Impl; | ||||||
|  |    std::unique_ptr<Impl> p; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| } // namespace settings
 | } // namespace settings
 | ||||||
|  |  | ||||||
|  | @ -35,7 +35,7 @@ static const std::string kDefaultRadarProductGroupString_ = "L3"; | ||||||
| static const std::array<std::string, kCount_> kDefaultRadarProduct_ { | static const std::array<std::string, kCount_> kDefaultRadarProduct_ { | ||||||
|    "N0B", "N0G", "N0C", "N0X"}; |    "N0B", "N0G", "N0C", "N0X"}; | ||||||
| 
 | 
 | ||||||
| class MapSettingsImpl | class MapSettings::Impl | ||||||
| { | { | ||||||
| public: | public: | ||||||
|    struct MapData |    struct MapData | ||||||
|  | @ -47,7 +47,7 @@ public: | ||||||
|       SettingsVariable<std::string> radarProduct_ {kRadarProductName_}; |       SettingsVariable<std::string> radarProduct_ {kRadarProductName_}; | ||||||
|    }; |    }; | ||||||
| 
 | 
 | ||||||
|    explicit MapSettingsImpl() |    explicit Impl() | ||||||
|    { |    { | ||||||
|       for (std::size_t i = 0; i < kCount_; i++) |       for (std::size_t i = 0; i < kCount_; i++) | ||||||
|       { |       { | ||||||
|  | @ -101,7 +101,7 @@ public: | ||||||
|       } |       } | ||||||
|    } |    } | ||||||
| 
 | 
 | ||||||
|    ~MapSettingsImpl() {} |    ~Impl() {} | ||||||
| 
 | 
 | ||||||
|    void SetDefaults(std::size_t i) |    void SetDefaults(std::size_t i) | ||||||
|    { |    { | ||||||
|  | @ -111,12 +111,30 @@ public: | ||||||
|       map_[i].radarProduct_.SetValueToDefault(); |       map_[i].radarProduct_.SetValueToDefault(); | ||||||
|    } |    } | ||||||
| 
 | 
 | ||||||
|  |    friend void tag_invoke(boost::json::value_from_tag, | ||||||
|  |                           boost::json::value& jv, | ||||||
|  |                           const MapData&      data) | ||||||
|  |    { | ||||||
|  |       jv = {{kMapStyleName_, data.mapStyle_.GetValue()}, | ||||||
|  |             {kRadarSiteName_, data.radarSite_.GetValue()}, | ||||||
|  |             {kRadarProductGroupName_, data.radarProductGroup_.GetValue()}, | ||||||
|  |             {kRadarProductName_, data.radarProduct_.GetValue()}}; | ||||||
|  |    } | ||||||
|  | 
 | ||||||
|  |    friend bool operator==(const MapData& lhs, const MapData& rhs) | ||||||
|  |    { | ||||||
|  |       return (lhs.mapStyle_ == rhs.mapStyle_ && //
 | ||||||
|  |               lhs.radarSite_ == rhs.radarSite_ && | ||||||
|  |               lhs.radarProductGroup_ == rhs.radarProductGroup_ && | ||||||
|  |               lhs.radarProduct_ == rhs.radarProduct_); | ||||||
|  |    } | ||||||
|  | 
 | ||||||
|    std::array<MapData, kCount_>       map_ {}; |    std::array<MapData, kCount_>       map_ {}; | ||||||
|    std::vector<SettingsVariableBase*> variables_ {}; |    std::vector<SettingsVariableBase*> variables_ {}; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| MapSettings::MapSettings() : | MapSettings::MapSettings() : | ||||||
|     SettingsCategory("maps"), p(std::make_unique<MapSettingsImpl>()) |     SettingsCategory("maps"), p(std::make_unique<Impl>()) | ||||||
| { | { | ||||||
|    RegisterVariables(p->variables_); |    RegisterVariables(p->variables_); | ||||||
|    SetDefaults(); |    SetDefaults(); | ||||||
|  | @ -161,7 +179,7 @@ bool MapSettings::Shutdown() | ||||||
|    // Commit settings that are managed separate from the settings dialog
 |    // Commit settings that are managed separate from the settings dialog
 | ||||||
|    for (std::size_t i = 0; i < kCount_; ++i) |    for (std::size_t i = 0; i < kCount_; ++i) | ||||||
|    { |    { | ||||||
|       MapSettingsImpl::MapData& mapRecordSettings = p->map_[i]; |       Impl::MapData& mapRecordSettings = p->map_[i]; | ||||||
| 
 | 
 | ||||||
|       dataChanged |= mapRecordSettings.mapStyle_.Commit(); |       dataChanged |= mapRecordSettings.mapStyle_.Commit(); | ||||||
|    } |    } | ||||||
|  | @ -184,7 +202,7 @@ bool MapSettings::ReadJson(const boost::json::object& json) | ||||||
|          if (i < mapArray.size() && mapArray.at(i).is_object()) |          if (i < mapArray.size() && mapArray.at(i).is_object()) | ||||||
|          { |          { | ||||||
|             const boost::json::object& mapRecord = mapArray.at(i).as_object(); |             const boost::json::object& mapRecord = mapArray.at(i).as_object(); | ||||||
|             MapSettingsImpl::MapData&  mapRecordSettings = p->map_[i]; |             Impl::MapData&             mapRecordSettings = p->map_[i]; | ||||||
| 
 | 
 | ||||||
|             // Load JSON Elements
 |             // Load JSON Elements
 | ||||||
|             validated &= mapRecordSettings.mapStyle_.ReadValue(mapRecord); |             validated &= mapRecordSettings.mapStyle_.ReadValue(mapRecord); | ||||||
|  | @ -234,14 +252,10 @@ void MapSettings::WriteJson(boost::json::object& json) const | ||||||
|    json.insert_or_assign(name(), object); |    json.insert_or_assign(name(), object); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void tag_invoke(boost::json::value_from_tag, | MapSettings& MapSettings::Instance() | ||||||
|                 boost::json::value&             jv, |  | ||||||
|                 const MapSettingsImpl::MapData& data) |  | ||||||
| { | { | ||||||
|    jv = {{kMapStyleName_, data.mapStyle_.GetValue()}, |    static MapSettings mapSettings_; | ||||||
|          {kRadarSiteName_, data.radarSite_.GetValue()}, |    return mapSettings_; | ||||||
|          {kRadarProductGroupName_, data.radarProductGroup_.GetValue()}, |  | ||||||
|          {kRadarProductName_, data.radarProduct_.GetValue()}}; |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| bool operator==(const MapSettings& lhs, const MapSettings& rhs) | bool operator==(const MapSettings& lhs, const MapSettings& rhs) | ||||||
|  | @ -249,15 +263,6 @@ bool operator==(const MapSettings& lhs, const MapSettings& rhs) | ||||||
|    return (lhs.p->map_ == rhs.p->map_); |    return (lhs.p->map_ == rhs.p->map_); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| bool operator==(const MapSettingsImpl::MapData& lhs, |  | ||||||
|                 const MapSettingsImpl::MapData& rhs) |  | ||||||
| { |  | ||||||
|    return (lhs.mapStyle_ == rhs.mapStyle_ && //
 |  | ||||||
|            lhs.radarSite_ == rhs.radarSite_ && |  | ||||||
|            lhs.radarProductGroup_ == rhs.radarProductGroup_ && |  | ||||||
|            lhs.radarProduct_ == rhs.radarProduct_); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| } // namespace settings
 | } // namespace settings
 | ||||||
| } // namespace qt
 | } // namespace qt
 | ||||||
| } // namespace scwx
 | } // namespace scwx
 | ||||||
|  |  | ||||||
|  | @ -13,8 +13,6 @@ namespace qt | ||||||
| namespace settings | namespace settings | ||||||
| { | { | ||||||
| 
 | 
 | ||||||
| class MapSettingsImpl; |  | ||||||
| 
 |  | ||||||
| class MapSettings : public SettingsCategory | class MapSettings : public SettingsCategory | ||||||
| { | { | ||||||
| public: | public: | ||||||
|  | @ -52,10 +50,13 @@ public: | ||||||
|     */ |     */ | ||||||
|    void WriteJson(boost::json::object& json) const override; |    void WriteJson(boost::json::object& json) const override; | ||||||
| 
 | 
 | ||||||
|  |    static MapSettings& Instance(); | ||||||
|  | 
 | ||||||
|    friend bool operator==(const MapSettings& lhs, const MapSettings& rhs); |    friend bool operator==(const MapSettings& lhs, const MapSettings& rhs); | ||||||
| 
 | 
 | ||||||
| private: | private: | ||||||
|    std::unique_ptr<MapSettingsImpl> p; |    class Impl; | ||||||
|  |    std::unique_ptr<Impl> p; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| } // namespace settings
 | } // namespace settings
 | ||||||
|  |  | ||||||
|  | @ -72,10 +72,10 @@ static const std::map< | ||||||
| static const std::string       kDefaultKey_ {"???"}; | static const std::string       kDefaultKey_ {"???"}; | ||||||
| static const awips::Phenomenon kDefaultPhenomenon_ {awips::Phenomenon::Marine}; | static const awips::Phenomenon kDefaultPhenomenon_ {awips::Phenomenon::Marine}; | ||||||
| 
 | 
 | ||||||
| class PaletteSettingsImpl | class PaletteSettings::Impl | ||||||
| { | { | ||||||
| public: | public: | ||||||
|    explicit PaletteSettingsImpl() |    explicit Impl() | ||||||
|    { |    { | ||||||
|       for (const auto& name : kPaletteKeys_) |       for (const auto& name : kPaletteKeys_) | ||||||
|       { |       { | ||||||
|  | @ -120,7 +120,7 @@ public: | ||||||
|       } |       } | ||||||
|    } |    } | ||||||
| 
 | 
 | ||||||
|    ~PaletteSettingsImpl() {} |    ~Impl() {} | ||||||
| 
 | 
 | ||||||
|    static bool ValidateColor(const std::string& value); |    static bool ValidateColor(const std::string& value); | ||||||
| 
 | 
 | ||||||
|  | @ -132,14 +132,14 @@ public: | ||||||
|    std::vector<SettingsVariableBase*> variables_ {}; |    std::vector<SettingsVariableBase*> variables_ {}; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| bool PaletteSettingsImpl::ValidateColor(const std::string& value) | bool PaletteSettings::Impl::ValidateColor(const std::string& value) | ||||||
| { | { | ||||||
|    static const std::regex re {"#[0-9A-Za-z]{8}"}; |    static const std::regex re {"#[0-9A-Za-z]{8}"}; | ||||||
|    return std::regex_match(value, re); |    return std::regex_match(value, re); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| PaletteSettings::PaletteSettings() : | PaletteSettings::PaletteSettings() : | ||||||
|     SettingsCategory("palette"), p(std::make_unique<PaletteSettingsImpl>()) |     SettingsCategory("palette"), p(std::make_unique<Impl>()) | ||||||
| { | { | ||||||
|    RegisterVariables(p->variables_); |    RegisterVariables(p->variables_); | ||||||
|    SetDefaults(); |    SetDefaults(); | ||||||
|  | @ -200,6 +200,12 @@ const std::vector<awips::Phenomenon>& PaletteSettings::alert_phenomena() | ||||||
|    return kAlertPhenomena_; |    return kAlertPhenomena_; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | PaletteSettings& PaletteSettings::Instance() | ||||||
|  | { | ||||||
|  |    static PaletteSettings paletteSettings_; | ||||||
|  |    return paletteSettings_; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| bool operator==(const PaletteSettings& lhs, const PaletteSettings& rhs) | bool operator==(const PaletteSettings& lhs, const PaletteSettings& rhs) | ||||||
| { | { | ||||||
|    return lhs.p->palette_ == rhs.p->palette_; |    return lhs.p->palette_ == rhs.p->palette_; | ||||||
|  |  | ||||||
|  | @ -14,8 +14,6 @@ namespace qt | ||||||
| namespace settings | namespace settings | ||||||
| { | { | ||||||
| 
 | 
 | ||||||
| class PaletteSettingsImpl; |  | ||||||
| 
 |  | ||||||
| class PaletteSettings : public SettingsCategory | class PaletteSettings : public SettingsCategory | ||||||
| { | { | ||||||
| public: | public: | ||||||
|  | @ -34,11 +32,14 @@ public: | ||||||
| 
 | 
 | ||||||
|    static const std::vector<awips::Phenomenon>& alert_phenomena(); |    static const std::vector<awips::Phenomenon>& alert_phenomena(); | ||||||
| 
 | 
 | ||||||
|  |    static PaletteSettings& Instance(); | ||||||
|  | 
 | ||||||
|    friend bool operator==(const PaletteSettings& lhs, |    friend bool operator==(const PaletteSettings& lhs, | ||||||
|                           const PaletteSettings& rhs); |                           const PaletteSettings& rhs); | ||||||
| 
 | 
 | ||||||
| private: | private: | ||||||
|    std::unique_ptr<PaletteSettingsImpl> p; |    class Impl; | ||||||
|  |    std::unique_ptr<Impl> p; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| } // namespace settings
 | } // namespace settings
 | ||||||
|  |  | ||||||
|  | @ -2,6 +2,8 @@ | ||||||
| #include <scwx/qt/util/json.hpp> | #include <scwx/qt/util/json.hpp> | ||||||
| #include <scwx/util/logger.hpp> | #include <scwx/util/logger.hpp> | ||||||
| 
 | 
 | ||||||
|  | #include <algorithm> | ||||||
|  | 
 | ||||||
| namespace scwx | namespace scwx | ||||||
| { | { | ||||||
| namespace qt | namespace qt | ||||||
|  | @ -21,6 +23,8 @@ public: | ||||||
| 
 | 
 | ||||||
|    const std::string name_; |    const std::string name_; | ||||||
| 
 | 
 | ||||||
|  |    std::vector<std::pair<std::string, std::vector<SettingsCategory*>>> | ||||||
|  |                                       subcategoryArrays_; | ||||||
|    std::vector<SettingsVariableBase*> variables_; |    std::vector<SettingsVariableBase*> variables_; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | @ -41,6 +45,16 @@ std::string SettingsCategory::name() const | ||||||
| 
 | 
 | ||||||
| void SettingsCategory::SetDefaults() | void SettingsCategory::SetDefaults() | ||||||
| { | { | ||||||
|  |    // Set subcategory array defaults
 | ||||||
|  |    for (auto& subcategoryArray : p->subcategoryArrays_) | ||||||
|  |    { | ||||||
|  |       for (auto& subcategory : subcategoryArray.second) | ||||||
|  |       { | ||||||
|  |          subcategory->SetDefaults(); | ||||||
|  |       } | ||||||
|  |    } | ||||||
|  | 
 | ||||||
|  |    // Set variable defaults
 | ||||||
|    for (auto& variable : p->variables_) |    for (auto& variable : p->variables_) | ||||||
|    { |    { | ||||||
|       variable->SetValueToDefault(); |       variable->SetValueToDefault(); | ||||||
|  | @ -57,6 +71,47 @@ bool SettingsCategory::ReadJson(const boost::json::object& json) | ||||||
|    { |    { | ||||||
|       const boost::json::object& object = value->as_object(); |       const boost::json::object& object = value->as_object(); | ||||||
| 
 | 
 | ||||||
|  |       // Read subcategory arrays
 | ||||||
|  |       for (auto& subcategoryArray : p->subcategoryArrays_) | ||||||
|  |       { | ||||||
|  |          const boost::json::value* arrayValue = | ||||||
|  |             object.if_contains(subcategoryArray.first); | ||||||
|  | 
 | ||||||
|  |          if (arrayValue != nullptr && arrayValue->is_object()) | ||||||
|  |          { | ||||||
|  |             const boost::json::object& arrayObject = arrayValue->as_object(); | ||||||
|  | 
 | ||||||
|  |             for (auto& subcategory : subcategoryArray.second) | ||||||
|  |             { | ||||||
|  |                validated &= subcategory->ReadJson(arrayObject); | ||||||
|  |             } | ||||||
|  |          } | ||||||
|  |          else | ||||||
|  |          { | ||||||
|  |             if (arrayValue == nullptr) | ||||||
|  |             { | ||||||
|  |                logger_->debug( | ||||||
|  |                   "Subcategory array key {} is not present, resetting to " | ||||||
|  |                   "defaults", | ||||||
|  |                   subcategoryArray.first); | ||||||
|  |             } | ||||||
|  |             else if (!arrayValue->is_object()) | ||||||
|  |             { | ||||||
|  |                logger_->warn( | ||||||
|  |                   "Invalid json for subcategory array key {}, resetting to " | ||||||
|  |                   "defaults", | ||||||
|  |                   p->name_); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             for (auto& subcategory : subcategoryArray.second) | ||||||
|  |             { | ||||||
|  |                subcategory->SetDefaults(); | ||||||
|  |             } | ||||||
|  |             validated = false; | ||||||
|  |          } | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       // Read variables
 | ||||||
|       for (auto& variable : p->variables_) |       for (auto& variable : p->variables_) | ||||||
|       { |       { | ||||||
|          validated &= variable->ReadValue(object); |          validated &= variable->ReadValue(object); | ||||||
|  | @ -66,8 +121,8 @@ bool SettingsCategory::ReadJson(const boost::json::object& json) | ||||||
|    { |    { | ||||||
|       if (value == nullptr) |       if (value == nullptr) | ||||||
|       { |       { | ||||||
|          logger_->warn("Key {} is not present, resetting to defaults", |          logger_->debug("Key {} is not present, resetting to defaults", | ||||||
|                        p->name_); |                         p->name_); | ||||||
|       } |       } | ||||||
|       else if (!value->is_object()) |       else if (!value->is_object()) | ||||||
|       { |       { | ||||||
|  | @ -86,6 +141,20 @@ void SettingsCategory::WriteJson(boost::json::object& json) const | ||||||
| { | { | ||||||
|    boost::json::object object; |    boost::json::object object; | ||||||
| 
 | 
 | ||||||
|  |    // Write subcategory arrays
 | ||||||
|  |    for (auto& subcategoryArray : p->subcategoryArrays_) | ||||||
|  |    { | ||||||
|  |       boost::json::object arrayObject; | ||||||
|  | 
 | ||||||
|  |       for (auto& subcategory : subcategoryArray.second) | ||||||
|  |       { | ||||||
|  |          subcategory->WriteJson(arrayObject); | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       object.insert_or_assign(subcategoryArray.first, arrayObject); | ||||||
|  |    } | ||||||
|  | 
 | ||||||
|  |    // Write variables
 | ||||||
|    for (auto& variable : p->variables_) |    for (auto& variable : p->variables_) | ||||||
|    { |    { | ||||||
|       variable->WriteValue(object); |       variable->WriteValue(object); | ||||||
|  | @ -94,6 +163,18 @@ void SettingsCategory::WriteJson(boost::json::object& json) const | ||||||
|    json.insert_or_assign(p->name_, object); |    json.insert_or_assign(p->name_, object); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | void SettingsCategory::RegisterSubcategoryArray( | ||||||
|  |    const std::string& name, std::vector<SettingsCategory>& subcategories) | ||||||
|  | { | ||||||
|  |    auto& newSubcategories = p->subcategoryArrays_.emplace_back( | ||||||
|  |       name, std::vector<SettingsCategory*> {}); | ||||||
|  | 
 | ||||||
|  |    std::transform(subcategories.begin(), | ||||||
|  |                   subcategories.end(), | ||||||
|  |                   std::back_inserter(newSubcategories.second), | ||||||
|  |                   [](SettingsCategory& subcategory) { return &subcategory; }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| void SettingsCategory::RegisterVariables( | void SettingsCategory::RegisterVariables( | ||||||
|    std::initializer_list<SettingsVariableBase*> variables) |    std::initializer_list<SettingsVariableBase*> variables) | ||||||
| { | { | ||||||
|  |  | ||||||
|  | @ -50,7 +50,8 @@ public: | ||||||
|     */ |     */ | ||||||
|    virtual void WriteJson(boost::json::object& json) const; |    virtual void WriteJson(boost::json::object& json) const; | ||||||
| 
 | 
 | ||||||
| protected: |    void RegisterSubcategoryArray(const std::string&             name, | ||||||
|  |                                  std::vector<SettingsCategory>& subcategories); | ||||||
|    void |    void | ||||||
|    RegisterVariables(std::initializer_list<SettingsVariableBase*> variables); |    RegisterVariables(std::initializer_list<SettingsVariableBase*> variables); | ||||||
|    void RegisterVariables(std::vector<SettingsVariableBase*> variables); |    void RegisterVariables(std::vector<SettingsVariableBase*> variables); | ||||||
|  |  | ||||||
|  | @ -9,6 +9,7 @@ | ||||||
| #include <QCheckBox> | #include <QCheckBox> | ||||||
| #include <QComboBox> | #include <QComboBox> | ||||||
| #include <QCoreApplication> | #include <QCoreApplication> | ||||||
|  | #include <QLabel> | ||||||
| #include <QLineEdit> | #include <QLineEdit> | ||||||
| #include <QSpinBox> | #include <QSpinBox> | ||||||
| #include <QWidget> | #include <QWidget> | ||||||
|  | @ -26,16 +27,21 @@ template<class T> | ||||||
| class SettingsInterface<T>::Impl | class SettingsInterface<T>::Impl | ||||||
| { | { | ||||||
| public: | public: | ||||||
|    explicit Impl() |    explicit Impl(SettingsInterface* self) : self_ {self} | ||||||
|    { |    { | ||||||
|       context_->moveToThread(QCoreApplication::instance()->thread()); |       context_->moveToThread(QCoreApplication::instance()->thread()); | ||||||
|    } |    } | ||||||
| 
 | 
 | ||||||
|    ~Impl() {} |    ~Impl() {} | ||||||
| 
 | 
 | ||||||
|  |    template<class U> | ||||||
|  |    void SetWidgetText(U* widget, const T& currentValue); | ||||||
|  | 
 | ||||||
|    void UpdateEditWidget(); |    void UpdateEditWidget(); | ||||||
|    void UpdateResetButton(); |    void UpdateResetButton(); | ||||||
| 
 | 
 | ||||||
|  |    SettingsInterface<T>* self_; | ||||||
|  | 
 | ||||||
|    SettingsVariable<T>* variable_ {nullptr}; |    SettingsVariable<T>* variable_ {nullptr}; | ||||||
|    bool                 stagedValid_ {true}; |    bool                 stagedValid_ {true}; | ||||||
| 
 | 
 | ||||||
|  | @ -49,17 +55,27 @@ public: | ||||||
| 
 | 
 | ||||||
| template<class T> | template<class T> | ||||||
| SettingsInterface<T>::SettingsInterface() : | SettingsInterface<T>::SettingsInterface() : | ||||||
|     SettingsInterfaceBase(), p(std::make_unique<Impl>()) |     SettingsInterfaceBase(), p(std::make_unique<Impl>(this)) | ||||||
| { | { | ||||||
| } | } | ||||||
| template<class T> | template<class T> | ||||||
| SettingsInterface<T>::~SettingsInterface() = default; | SettingsInterface<T>::~SettingsInterface() = default; | ||||||
| 
 | 
 | ||||||
| template<class T> | template<class T> | ||||||
| SettingsInterface<T>::SettingsInterface(SettingsInterface&&) noexcept = default; | SettingsInterface<T>::SettingsInterface(SettingsInterface&& o) noexcept : | ||||||
|  |     p {std::move(o.p)} | ||||||
|  | { | ||||||
|  |    p->self_ = this; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| template<class T> | template<class T> | ||||||
| SettingsInterface<T>& | SettingsInterface<T>& | ||||||
| SettingsInterface<T>::operator=(SettingsInterface&&) noexcept = default; | SettingsInterface<T>::operator=(SettingsInterface&& o) noexcept | ||||||
|  | { | ||||||
|  |    p        = std::move(o.p); | ||||||
|  |    p->self_ = this; | ||||||
|  |    return *this; | ||||||
|  | } | ||||||
| 
 | 
 | ||||||
| template<class T> | template<class T> | ||||||
| void SettingsInterface<T>::SetSettingsVariable(SettingsVariable<T>& variable) | void SettingsInterface<T>::SetSettingsVariable(SettingsVariable<T>& variable) | ||||||
|  | @ -73,6 +89,27 @@ SettingsVariable<T>* SettingsInterface<T>::GetSettingsVariable() const | ||||||
|    return p->variable_; |    return p->variable_; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | template<class T> | ||||||
|  | bool SettingsInterface<T>::IsDefault() | ||||||
|  | { | ||||||
|  |    bool isDefault = false; | ||||||
|  | 
 | ||||||
|  |    const std::optional<T> staged       = p->variable_->GetStaged(); | ||||||
|  |    const T                defaultValue = p->variable_->GetDefault(); | ||||||
|  |    const T                value        = p->variable_->GetValue(); | ||||||
|  | 
 | ||||||
|  |    if (staged.has_value()) | ||||||
|  |    { | ||||||
|  |       isDefault = (p->stagedValid_ && *staged == defaultValue); | ||||||
|  |    } | ||||||
|  |    else | ||||||
|  |    { | ||||||
|  |       isDefault = (value == defaultValue); | ||||||
|  |    } | ||||||
|  | 
 | ||||||
|  |    return isDefault; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| template<class T> | template<class T> | ||||||
| bool SettingsInterface<T>::Commit() | bool SettingsInterface<T>::Commit() | ||||||
| { | { | ||||||
|  | @ -95,6 +132,14 @@ void SettingsInterface<T>::StageDefault() | ||||||
|    p->UpdateResetButton(); |    p->UpdateResetButton(); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | template<class T> | ||||||
|  | void SettingsInterface<T>::StageValue(const T& value) | ||||||
|  | { | ||||||
|  |    p->variable_->StageValue(value); | ||||||
|  |    p->UpdateEditWidget(); | ||||||
|  |    p->UpdateResetButton(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| template<class T> | template<class T> | ||||||
| void SettingsInterface<T>::SetEditWidget(QWidget* widget) | void SettingsInterface<T>::SetEditWidget(QWidget* widget) | ||||||
| { | { | ||||||
|  | @ -105,6 +150,11 @@ void SettingsInterface<T>::SetEditWidget(QWidget* widget) | ||||||
| 
 | 
 | ||||||
|    p->editWidget_ = widget; |    p->editWidget_ = widget; | ||||||
| 
 | 
 | ||||||
|  |    if (widget == nullptr) | ||||||
|  |    { | ||||||
|  |       return; | ||||||
|  |    } | ||||||
|  | 
 | ||||||
|    if (QLineEdit* lineEdit = dynamic_cast<QLineEdit*>(widget)) |    if (QLineEdit* lineEdit = dynamic_cast<QLineEdit*>(widget)) | ||||||
|    { |    { | ||||||
|       if constexpr (std::is_same_v<T, std::string>) |       if constexpr (std::is_same_v<T, std::string>) | ||||||
|  | @ -274,33 +324,36 @@ void SettingsInterface<T>::SetResetButton(QAbstractButton* button) | ||||||
| 
 | 
 | ||||||
|    p->resetButton_ = button; |    p->resetButton_ = button; | ||||||
| 
 | 
 | ||||||
|    QObject::connect(p->resetButton_, |    if (p->resetButton_ != nullptr) | ||||||
|                     &QAbstractButton::clicked, |    { | ||||||
|                     p->context_.get(), |       QObject::connect(p->resetButton_, | ||||||
|                     [this]() |                        &QAbstractButton::clicked, | ||||||
|                     { |                        p->context_.get(), | ||||||
|                        T defaultValue = p->variable_->GetDefault(); |                        [this]() | ||||||
| 
 |  | ||||||
|                        if (p->variable_->GetValue() == defaultValue) |  | ||||||
|                        { |                        { | ||||||
|                           // If the current value is default, reset the staged
 |                           T defaultValue = p->variable_->GetDefault(); | ||||||
|                           // value
 |  | ||||||
|                           p->variable_->Reset(); |  | ||||||
|                           p->stagedValid_ = true; |  | ||||||
|                           p->UpdateEditWidget(); |  | ||||||
|                           p->UpdateResetButton(); |  | ||||||
|                        } |  | ||||||
|                        else |  | ||||||
|                        { |  | ||||||
|                           // Stage the default value
 |  | ||||||
|                           p->stagedValid_ = |  | ||||||
|                              p->variable_->StageValue(defaultValue); |  | ||||||
|                           p->UpdateEditWidget(); |  | ||||||
|                           p->UpdateResetButton(); |  | ||||||
|                        } |  | ||||||
|                     }); |  | ||||||
| 
 | 
 | ||||||
|    p->UpdateResetButton(); |                           if (p->variable_->GetValue() == defaultValue) | ||||||
|  |                           { | ||||||
|  |                              // If the current value is default, reset the
 | ||||||
|  |                              // staged value
 | ||||||
|  |                              p->variable_->Reset(); | ||||||
|  |                              p->stagedValid_ = true; | ||||||
|  |                              p->UpdateEditWidget(); | ||||||
|  |                              p->UpdateResetButton(); | ||||||
|  |                           } | ||||||
|  |                           else | ||||||
|  |                           { | ||||||
|  |                              // Stage the default value
 | ||||||
|  |                              p->stagedValid_ = | ||||||
|  |                                 p->variable_->StageValue(defaultValue); | ||||||
|  |                              p->UpdateEditWidget(); | ||||||
|  |                              p->UpdateResetButton(); | ||||||
|  |                           } | ||||||
|  |                        }); | ||||||
|  | 
 | ||||||
|  |       p->UpdateResetButton(); | ||||||
|  |    } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| template<class T> | template<class T> | ||||||
|  | @ -317,6 +370,39 @@ void SettingsInterface<T>::SetMapToValueFunction( | ||||||
|    p->mapToValue_ = function; |    p->mapToValue_ = function; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | template<class T> | ||||||
|  | template<class U> | ||||||
|  | void SettingsInterface<T>::Impl::SetWidgetText(U* widget, const T& currentValue) | ||||||
|  | { | ||||||
|  |    if constexpr (std::is_integral_v<T>) | ||||||
|  |    { | ||||||
|  |       widget->setText(QString::number(currentValue)); | ||||||
|  |    } | ||||||
|  |    else if constexpr (std::is_same_v<T, std::string>) | ||||||
|  |    { | ||||||
|  |       if (mapFromValue_ != nullptr) | ||||||
|  |       { | ||||||
|  |          widget->setText(QString::fromStdString(mapFromValue_(currentValue))); | ||||||
|  |       } | ||||||
|  |       else | ||||||
|  |       { | ||||||
|  |          widget->setText(QString::fromStdString(currentValue)); | ||||||
|  |       } | ||||||
|  |    } | ||||||
|  |    else if constexpr (std::is_same_v<T, std::vector<std::int64_t>>) | ||||||
|  |    { | ||||||
|  |       if (mapFromValue_ != nullptr) | ||||||
|  |       { | ||||||
|  |          widget->setText(QString::fromStdString(mapFromValue_(currentValue))); | ||||||
|  |       } | ||||||
|  |       else | ||||||
|  |       { | ||||||
|  |          widget->setText(QString::fromStdString( | ||||||
|  |             fmt::format("{}", fmt::join(currentValue, ", ")))); | ||||||
|  |       } | ||||||
|  |    } | ||||||
|  | } | ||||||
|  | 
 | ||||||
| template<class T> | template<class T> | ||||||
| void SettingsInterface<T>::Impl::UpdateEditWidget() | void SettingsInterface<T>::Impl::UpdateEditWidget() | ||||||
| { | { | ||||||
|  | @ -327,35 +413,11 @@ void SettingsInterface<T>::Impl::UpdateEditWidget() | ||||||
| 
 | 
 | ||||||
|    if (QLineEdit* lineEdit = dynamic_cast<QLineEdit*>(editWidget_)) |    if (QLineEdit* lineEdit = dynamic_cast<QLineEdit*>(editWidget_)) | ||||||
|    { |    { | ||||||
|       if constexpr (std::is_integral_v<T>) |       SetWidgetText(lineEdit, currentValue); | ||||||
|       { |    } | ||||||
|          lineEdit->setText(QString::number(currentValue)); |    else if (QLabel* label = dynamic_cast<QLabel*>(editWidget_)) | ||||||
|       } |    { | ||||||
|       else if constexpr (std::is_same_v<T, std::string>) |       SetWidgetText(label, currentValue); | ||||||
|       { |  | ||||||
|          if (mapFromValue_ != nullptr) |  | ||||||
|          { |  | ||||||
|             lineEdit->setText( |  | ||||||
|                QString::fromStdString(mapFromValue_(currentValue))); |  | ||||||
|          } |  | ||||||
|          else |  | ||||||
|          { |  | ||||||
|             lineEdit->setText(QString::fromStdString(currentValue)); |  | ||||||
|          } |  | ||||||
|       } |  | ||||||
|       else if constexpr (std::is_same_v<T, std::vector<std::int64_t>>) |  | ||||||
|       { |  | ||||||
|          if (mapFromValue_ != nullptr) |  | ||||||
|          { |  | ||||||
|             lineEdit->setText( |  | ||||||
|                QString::fromStdString(mapFromValue_(currentValue))); |  | ||||||
|          } |  | ||||||
|          else |  | ||||||
|          { |  | ||||||
|             lineEdit->setText(QString::fromStdString( |  | ||||||
|                fmt::format("{}", fmt::join(currentValue, ", ")))); |  | ||||||
|          } |  | ||||||
|       } |  | ||||||
|    } |    } | ||||||
|    else if (QCheckBox* checkBox = dynamic_cast<QCheckBox*>(editWidget_)) |    else if (QCheckBox* checkBox = dynamic_cast<QCheckBox*>(editWidget_)) | ||||||
|    { |    { | ||||||
|  | @ -391,20 +453,9 @@ void SettingsInterface<T>::Impl::UpdateEditWidget() | ||||||
| template<class T> | template<class T> | ||||||
| void SettingsInterface<T>::Impl::UpdateResetButton() | void SettingsInterface<T>::Impl::UpdateResetButton() | ||||||
| { | { | ||||||
|    const std::optional<T> staged       = variable_->GetStaged(); |  | ||||||
|    const T                defaultValue = variable_->GetDefault(); |  | ||||||
|    const T                value        = variable_->GetValue(); |  | ||||||
| 
 |  | ||||||
|    if (resetButton_ != nullptr) |    if (resetButton_ != nullptr) | ||||||
|    { |    { | ||||||
|       if (staged.has_value()) |       resetButton_->setVisible(!self_->IsDefault()); | ||||||
|       { |  | ||||||
|          resetButton_->setVisible(!stagedValid_ || *staged != defaultValue); |  | ||||||
|       } |  | ||||||
|       else |  | ||||||
|       { |  | ||||||
|          resetButton_->setVisible(value != defaultValue); |  | ||||||
|       } |  | ||||||
|    } |    } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -45,6 +45,14 @@ public: | ||||||
|     */ |     */ | ||||||
|    SettingsVariable<T>* GetSettingsVariable() const; |    SettingsVariable<T>* GetSettingsVariable() const; | ||||||
| 
 | 
 | ||||||
|  |    /**
 | ||||||
|  |     * Gets whether the staged value (or current value, if none staged) is | ||||||
|  |     * set to the default value. | ||||||
|  |     * | ||||||
|  |     * @return true if the settings variable is set to default, otherwise false. | ||||||
|  |     */ | ||||||
|  |    bool IsDefault() override; | ||||||
|  | 
 | ||||||
|    /**
 |    /**
 | ||||||
|     * Sets the current value of the associated settings variable to the staged |     * Sets the current value of the associated settings variable to the staged | ||||||
|     * value. |     * value. | ||||||
|  | @ -64,6 +72,11 @@ public: | ||||||
|     */ |     */ | ||||||
|    void StageDefault() override; |    void StageDefault() override; | ||||||
| 
 | 
 | ||||||
|  |    /**
 | ||||||
|  |     * Stages a value to the associated settings variable. | ||||||
|  |     */ | ||||||
|  |    void StageValue(const T& value); | ||||||
|  | 
 | ||||||
|    /**
 |    /**
 | ||||||
|     * Sets the edit widget from the settings dialog. |     * Sets the edit widget from the settings dialog. | ||||||
|     * |     * | ||||||
|  | @ -103,6 +116,7 @@ private: | ||||||
| 
 | 
 | ||||||
| #ifdef SETTINGS_INTERFACE_IMPLEMENTATION | #ifdef SETTINGS_INTERFACE_IMPLEMENTATION | ||||||
| template class SettingsInterface<bool>; | template class SettingsInterface<bool>; | ||||||
|  | template class SettingsInterface<double>; | ||||||
| template class SettingsInterface<std::int64_t>; | template class SettingsInterface<std::int64_t>; | ||||||
| template class SettingsInterface<std::string>; | template class SettingsInterface<std::string>; | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -24,6 +24,14 @@ public: | ||||||
|    SettingsInterfaceBase(SettingsInterfaceBase&&) noexcept; |    SettingsInterfaceBase(SettingsInterfaceBase&&) noexcept; | ||||||
|    SettingsInterfaceBase& operator=(SettingsInterfaceBase&&) noexcept; |    SettingsInterfaceBase& operator=(SettingsInterfaceBase&&) noexcept; | ||||||
| 
 | 
 | ||||||
|  |    /**
 | ||||||
|  |     * Gets whether the staged value (or current value, if none staged) is | ||||||
|  |     * set to the default value. | ||||||
|  |     * | ||||||
|  |     * @return true if the settings variable is set to default, otherwise false. | ||||||
|  |     */ | ||||||
|  |    virtual bool IsDefault() = 0; | ||||||
|  | 
 | ||||||
|    /**
 |    /**
 | ||||||
|     * Sets the current value of the associated settings variable to the staged |     * Sets the current value of the associated settings variable to the staged | ||||||
|     * value. |     * value. | ||||||
|  |  | ||||||
|  | @ -239,6 +239,12 @@ std::optional<T> SettingsVariable<T>::GetStaged() const | ||||||
|    return p->staged_; |    return p->staged_; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | template<class T> | ||||||
|  | T SettingsVariable<T>::GetStagedOrValue() const | ||||||
|  | { | ||||||
|  |    return p->staged_.value_or(GetValue()); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| template<class T> | template<class T> | ||||||
| T SettingsVariable<T>::GetDefault() const | T SettingsVariable<T>::GetDefault() const | ||||||
| { | { | ||||||
|  |  | ||||||
|  | @ -103,6 +103,14 @@ public: | ||||||
|     */ |     */ | ||||||
|    std::optional<T> GetStaged() const; |    std::optional<T> GetStaged() const; | ||||||
| 
 | 
 | ||||||
|  |    /**
 | ||||||
|  |     * Gets the staged value of the settings variable, if defined, otherwise the | ||||||
|  |     * current value. | ||||||
|  |     * | ||||||
|  |     * @return Staged value or current value | ||||||
|  |     */ | ||||||
|  |    T GetStagedOrValue() const; | ||||||
|  | 
 | ||||||
|    /**
 |    /**
 | ||||||
|     * Validate the value against the defined parameters of the settings |     * Validate the value against the defined parameters of the settings | ||||||
|     * variable. |     * variable. | ||||||
|  |  | ||||||
							
								
								
									
										198
									
								
								scwx-qt/source/scwx/qt/settings/text_settings.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						|  | @ -0,0 +1,198 @@ | ||||||
|  | #include <scwx/qt/settings/text_settings.hpp> | ||||||
|  | #include <scwx/qt/types/text_types.hpp> | ||||||
|  | 
 | ||||||
|  | #include <boost/algorithm/string.hpp> | ||||||
|  | 
 | ||||||
|  | namespace scwx | ||||||
|  | { | ||||||
|  | namespace qt | ||||||
|  | { | ||||||
|  | namespace settings | ||||||
|  | { | ||||||
|  | 
 | ||||||
|  | static const std::string logPrefix_ = "scwx::qt::settings::text_settings"; | ||||||
|  | 
 | ||||||
|  | static const std::string kAlteDIN1451Mittelscrhift_ { | ||||||
|  |    "Alte DIN 1451 Mittelschrift"}; | ||||||
|  | static const std::string kInconsolata_ {"Inconsolata"}; | ||||||
|  | 
 | ||||||
|  | static const std::string kRegular_ {"Regular"}; | ||||||
|  | 
 | ||||||
|  | static const std::unordered_map<types::FontCategory, std::string> | ||||||
|  |    kDefaultFontFamily_ { | ||||||
|  |       {types::FontCategory::Default, kAlteDIN1451Mittelscrhift_}, | ||||||
|  |       {types::FontCategory::Tooltip, kInconsolata_}}; | ||||||
|  | static const std::unordered_map<types::FontCategory, std::string> | ||||||
|  |    kDefaultFontStyle_ {{types::FontCategory::Default, kRegular_}, | ||||||
|  |                        {types::FontCategory::Tooltip, kRegular_}}; | ||||||
|  | static const std::unordered_map<types::FontCategory, double> | ||||||
|  |    kDefaultFontPointSize_ {{types::FontCategory::Default, 12.0}, | ||||||
|  |                            {types::FontCategory::Tooltip, 10.5}}; | ||||||
|  | 
 | ||||||
|  | class TextSettings::Impl | ||||||
|  | { | ||||||
|  | public: | ||||||
|  |    struct FontData | ||||||
|  |    { | ||||||
|  |       SettingsVariable<std::string> fontFamily_ {"font_family"}; | ||||||
|  |       SettingsVariable<std::string> fontStyle_ {"font_style"}; | ||||||
|  |       SettingsVariable<double>      fontPointSize_ {"font_point_size"}; | ||||||
|  |    }; | ||||||
|  | 
 | ||||||
|  |    explicit Impl(TextSettings* self) : self_ {self} | ||||||
|  |    { | ||||||
|  |       std::string defaultTooltipMethodValue = | ||||||
|  |          types::GetTooltipMethodName(types::TooltipMethod::ImGui); | ||||||
|  | 
 | ||||||
|  |       boost::to_lower(defaultTooltipMethodValue); | ||||||
|  | 
 | ||||||
|  |       hoverTextWrap_.SetDefault(80); | ||||||
|  |       hoverTextWrap_.SetMinimum(0); | ||||||
|  |       hoverTextWrap_.SetMaximum(999); | ||||||
|  |       placefileTextDropShadowEnabled_.SetDefault(true); | ||||||
|  |       tooltipMethod_.SetDefault(defaultTooltipMethodValue); | ||||||
|  | 
 | ||||||
|  |       tooltipMethod_.SetValidator( | ||||||
|  |          [](const std::string& value) | ||||||
|  |          { | ||||||
|  |             for (types::TooltipMethod tooltipMethod : | ||||||
|  |                  types::TooltipMethodIterator()) | ||||||
|  |             { | ||||||
|  |                // If the value is equal to a lower case alert action name
 | ||||||
|  |                std::string tooltipMethodName = | ||||||
|  |                   types::GetTooltipMethodName(tooltipMethod); | ||||||
|  |                boost::to_lower(tooltipMethodName); | ||||||
|  |                if (value == tooltipMethodName) | ||||||
|  |                { | ||||||
|  |                   // Regard as a match, valid
 | ||||||
|  |                   return true; | ||||||
|  |                } | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             // No match found, invalid
 | ||||||
|  |             return false; | ||||||
|  |          }); | ||||||
|  | 
 | ||||||
|  |       InitializeFontVariables(); | ||||||
|  |    } | ||||||
|  | 
 | ||||||
|  |    ~Impl() {} | ||||||
|  | 
 | ||||||
|  |    void InitializeFontVariables(); | ||||||
|  | 
 | ||||||
|  |    friend bool operator==(const FontData& lhs, const FontData& rhs) | ||||||
|  |    { | ||||||
|  |       return (lhs.fontFamily_ == rhs.fontFamily_ && | ||||||
|  |               lhs.fontStyle_ == rhs.fontStyle_ && | ||||||
|  |               lhs.fontPointSize_ == rhs.fontPointSize_); | ||||||
|  |    } | ||||||
|  | 
 | ||||||
|  |    TextSettings* self_; | ||||||
|  | 
 | ||||||
|  |    std::unordered_map<types::FontCategory, FontData> fontData_ {}; | ||||||
|  |    std::vector<SettingsCategory>                     fontSettings_ {}; | ||||||
|  | 
 | ||||||
|  |    SettingsVariable<std::int64_t> hoverTextWrap_ {"hover_text_wrap"}; | ||||||
|  |    SettingsVariable<std::string>  tooltipMethod_ {"tooltip_method"}; | ||||||
|  | 
 | ||||||
|  |    SettingsVariable<bool> placefileTextDropShadowEnabled_ { | ||||||
|  |       "placefile_text_drop_shadow_enabled"}; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | TextSettings::TextSettings() : | ||||||
|  |     SettingsCategory("text"), p(std::make_unique<Impl>(this)) | ||||||
|  | { | ||||||
|  |    RegisterVariables({&p->hoverTextWrap_, | ||||||
|  |                       &p->placefileTextDropShadowEnabled_, | ||||||
|  |                       &p->tooltipMethod_}); | ||||||
|  |    SetDefaults(); | ||||||
|  | } | ||||||
|  | TextSettings::~TextSettings() = default; | ||||||
|  | 
 | ||||||
|  | TextSettings::TextSettings(TextSettings&&) noexcept            = default; | ||||||
|  | TextSettings& TextSettings::operator=(TextSettings&&) noexcept = default; | ||||||
|  | 
 | ||||||
|  | void TextSettings::Impl::InitializeFontVariables() | ||||||
|  | { | ||||||
|  |    for (auto fontCategory : types::FontCategoryIterator()) | ||||||
|  |    { | ||||||
|  |       auto  result = fontData_.emplace(fontCategory, FontData {}); | ||||||
|  |       auto& pair   = *result.first; | ||||||
|  |       auto& font   = pair.second; | ||||||
|  | 
 | ||||||
|  |       font.fontFamily_.SetDefault(kDefaultFontFamily_.at(fontCategory)); | ||||||
|  |       font.fontStyle_.SetDefault(kDefaultFontStyle_.at(fontCategory)); | ||||||
|  |       font.fontPointSize_.SetDefault(kDefaultFontPointSize_.at(fontCategory)); | ||||||
|  | 
 | ||||||
|  |       // String values must not be empty
 | ||||||
|  |       font.fontFamily_.SetValidator([](const std::string& value) | ||||||
|  |                                     { return !value.empty(); }); | ||||||
|  |       font.fontStyle_.SetValidator([](const std::string& value) | ||||||
|  |                                    { return !value.empty(); }); | ||||||
|  | 
 | ||||||
|  |       // Font point size must be between 6 and 72
 | ||||||
|  |       font.fontPointSize_.SetMinimum(6.0); | ||||||
|  |       font.fontPointSize_.SetMaximum(72.0); | ||||||
|  | 
 | ||||||
|  |       // Variable registration
 | ||||||
|  |       auto& settings = fontSettings_.emplace_back( | ||||||
|  |          SettingsCategory {types::GetFontCategoryName(fontCategory)}); | ||||||
|  | 
 | ||||||
|  |       settings.RegisterVariables( | ||||||
|  |          {&font.fontFamily_, &font.fontStyle_, &font.fontPointSize_}); | ||||||
|  |    } | ||||||
|  | 
 | ||||||
|  |    self_->RegisterSubcategoryArray("fonts", fontSettings_); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | SettingsVariable<std::string>& | ||||||
|  | TextSettings::font_family(types::FontCategory fontCategory) const | ||||||
|  | { | ||||||
|  |    return p->fontData_.at(fontCategory).fontFamily_; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | SettingsVariable<std::string>& | ||||||
|  | TextSettings::font_style(types::FontCategory fontCategory) const | ||||||
|  | { | ||||||
|  |    return p->fontData_.at(fontCategory).fontStyle_; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | SettingsVariable<double>& | ||||||
|  | TextSettings::font_point_size(types::FontCategory fontCategory) const | ||||||
|  | { | ||||||
|  |    return p->fontData_.at(fontCategory).fontPointSize_; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | SettingsVariable<std::int64_t>& TextSettings::hover_text_wrap() const | ||||||
|  | { | ||||||
|  |    return p->hoverTextWrap_; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | SettingsVariable<bool>& TextSettings::placefile_text_drop_shadow_enabled() const | ||||||
|  | { | ||||||
|  |    return p->placefileTextDropShadowEnabled_; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | SettingsVariable<std::string>& TextSettings::tooltip_method() const | ||||||
|  | { | ||||||
|  |    return p->tooltipMethod_; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | TextSettings& TextSettings::Instance() | ||||||
|  | { | ||||||
|  |    static TextSettings textSettings_; | ||||||
|  |    return textSettings_; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool operator==(const TextSettings& lhs, const TextSettings& rhs) | ||||||
|  | { | ||||||
|  |    return (lhs.p->fontData_ == rhs.p->fontData_ && | ||||||
|  |            lhs.p->hoverTextWrap_ == rhs.p->hoverTextWrap_ && | ||||||
|  |            lhs.p->placefileTextDropShadowEnabled_ == | ||||||
|  |               rhs.p->placefileTextDropShadowEnabled_ && | ||||||
|  |            lhs.p->tooltipMethod_ == rhs.p->tooltipMethod_); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | } // namespace settings
 | ||||||
|  | } // namespace qt
 | ||||||
|  | } // namespace scwx
 | ||||||
							
								
								
									
										52
									
								
								scwx-qt/source/scwx/qt/settings/text_settings.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						|  | @ -0,0 +1,52 @@ | ||||||
|  | #pragma once | ||||||
|  | 
 | ||||||
|  | #include <scwx/qt/settings/settings_category.hpp> | ||||||
|  | #include <scwx/qt/settings/settings_variable.hpp> | ||||||
|  | #include <scwx/qt/types/text_types.hpp> | ||||||
|  | 
 | ||||||
|  | #include <memory> | ||||||
|  | #include <string> | ||||||
|  | 
 | ||||||
|  | namespace scwx | ||||||
|  | { | ||||||
|  | namespace qt | ||||||
|  | { | ||||||
|  | namespace settings | ||||||
|  | { | ||||||
|  | 
 | ||||||
|  | class TextSettings : public SettingsCategory | ||||||
|  | { | ||||||
|  | public: | ||||||
|  |    explicit TextSettings(); | ||||||
|  |    ~TextSettings(); | ||||||
|  | 
 | ||||||
|  |    TextSettings(const TextSettings&)            = delete; | ||||||
|  |    TextSettings& operator=(const TextSettings&) = delete; | ||||||
|  | 
 | ||||||
|  |    TextSettings(TextSettings&&) noexcept; | ||||||
|  |    TextSettings& operator=(TextSettings&&) noexcept; | ||||||
|  | 
 | ||||||
|  |    SettingsVariable<std::string>& | ||||||
|  |    font_family(types::FontCategory fontCategory) const; | ||||||
|  |    SettingsVariable<std::string>& | ||||||
|  |    font_style(types::FontCategory fontCategory) const; | ||||||
|  |    SettingsVariable<double>& | ||||||
|  |    font_point_size(types::FontCategory fontCategory) const; | ||||||
|  | 
 | ||||||
|  |    SettingsVariable<std::int64_t>& hover_text_wrap() const; | ||||||
|  |    SettingsVariable<bool>&         placefile_text_drop_shadow_enabled() const; | ||||||
|  |    SettingsVariable<std::string>&  tooltip_method() const; | ||||||
|  | 
 | ||||||
|  |    static TextSettings& Instance(); | ||||||
|  | 
 | ||||||
|  |    friend bool operator==(const TextSettings& lhs, const TextSettings& rhs); | ||||||
|  | 
 | ||||||
|  | private: | ||||||
|  |    class Impl; | ||||||
|  | 
 | ||||||
|  |    std::unique_ptr<Impl> p; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | } // namespace settings
 | ||||||
|  | } // namespace qt
 | ||||||
|  | } // namespace scwx
 | ||||||
|  | @ -1,5 +1,31 @@ | ||||||
| #pragma once | #pragma once | ||||||
| 
 | 
 | ||||||
|  | #include <units/core.h> | ||||||
|  | 
 | ||||||
|  | namespace units | ||||||
|  | { | ||||||
|  | 
 | ||||||
|  | namespace dimension | ||||||
|  | { | ||||||
|  | 
 | ||||||
|  | struct font_size_tag | ||||||
|  | { | ||||||
|  |    static constexpr const char* const name         = "font size"; | ||||||
|  |    static constexpr const char* const abbreviation = "px"; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | using font_size = make_dimension<font_size_tag>; | ||||||
|  | 
 | ||||||
|  | } // namespace dimension
 | ||||||
|  | 
 | ||||||
|  | UNIT_ADD(font_size, | ||||||
|  |          pixels, | ||||||
|  |          px, | ||||||
|  |          conversion_factor<std::ratio<1>, dimension::font_size>) | ||||||
|  | UNIT_ADD(font_size, points, pt, conversion_factor<std::ratio<4, 3>, pixels<>>) | ||||||
|  | 
 | ||||||
|  | } // namespace units
 | ||||||
|  | 
 | ||||||
| namespace scwx | namespace scwx | ||||||
| { | { | ||||||
| namespace qt | namespace qt | ||||||
|  | @ -10,7 +36,8 @@ namespace types | ||||||
| enum class Font | enum class Font | ||||||
| { | { | ||||||
|    din1451alt, |    din1451alt, | ||||||
|    din1451alt_g |    din1451alt_g, | ||||||
|  |    Inconsolata_Regular | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| } // namespace types
 | } // namespace types
 | ||||||
|  |  | ||||||
							
								
								
									
										83
									
								
								scwx-qt/source/scwx/qt/types/imgui_font.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						|  | @ -0,0 +1,83 @@ | ||||||
|  | // Disable strncpy warning
 | ||||||
|  | #define _CRT_SECURE_NO_WARNINGS | ||||||
|  | 
 | ||||||
|  | #include <scwx/qt/types/imgui_font.hpp> | ||||||
|  | #include <scwx/qt/model/imgui_context_model.hpp> | ||||||
|  | #include <scwx/util/logger.hpp> | ||||||
|  | 
 | ||||||
|  | #include <algorithm> | ||||||
|  | #include <limits> | ||||||
|  | 
 | ||||||
|  | #include <imgui.h> | ||||||
|  | 
 | ||||||
|  | namespace scwx | ||||||
|  | { | ||||||
|  | namespace qt | ||||||
|  | { | ||||||
|  | namespace types | ||||||
|  | { | ||||||
|  | 
 | ||||||
|  | static const std::string logPrefix_ = "scwx::qt::types::imgui_font"; | ||||||
|  | static const auto        logger_    = scwx::util::Logger::Create(logPrefix_); | ||||||
|  | 
 | ||||||
|  | class ImGuiFont::Impl | ||||||
|  | { | ||||||
|  | public: | ||||||
|  |    explicit Impl(const std::string&            fontName, | ||||||
|  |                  const std::vector<char>&      fontData, | ||||||
|  |                  units::font_size::pixels<int> size) : | ||||||
|  |        fontName_ {fontName}, size_ {size} | ||||||
|  |    { | ||||||
|  |       CreateImGuiFont(fontData); | ||||||
|  |    } | ||||||
|  | 
 | ||||||
|  |    ~Impl() {} | ||||||
|  | 
 | ||||||
|  |    void CreateImGuiFont(const std::vector<char>& fontData); | ||||||
|  | 
 | ||||||
|  |    const std::string                   fontName_; | ||||||
|  |    const units::font_size::pixels<int> size_; | ||||||
|  | 
 | ||||||
|  |    ImFont* imFont_ {nullptr}; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | ImGuiFont::ImGuiFont(const std::string&            fontName, | ||||||
|  |                      const std::vector<char>&      fontData, | ||||||
|  |                      units::font_size::pixels<int> size) : | ||||||
|  |     p(std::make_unique<Impl>(fontName, fontData, size)) | ||||||
|  | { | ||||||
|  | } | ||||||
|  | ImGuiFont::~ImGuiFont() = default; | ||||||
|  | 
 | ||||||
|  | void ImGuiFont::Impl::CreateImGuiFont(const std::vector<char>& fontData) | ||||||
|  | { | ||||||
|  |    logger_->debug("Creating Font: {}", fontName_); | ||||||
|  | 
 | ||||||
|  |    ImFontAtlas* fontAtlas = model::ImGuiContextModel::Instance().font_atlas(); | ||||||
|  |    ImFontConfig fontConfig {}; | ||||||
|  | 
 | ||||||
|  |    const float sizePixels = static_cast<float>(size_.value()); | ||||||
|  | 
 | ||||||
|  |    // Do not transfer ownership of font data to ImGui, makes const_cast safe
 | ||||||
|  |    fontConfig.FontDataOwnedByAtlas = false; | ||||||
|  | 
 | ||||||
|  |    // Assign name to font
 | ||||||
|  |    strncpy(fontConfig.Name, fontName_.c_str(), sizeof(fontConfig.Name) - 1); | ||||||
|  |    fontConfig.Name[sizeof(fontConfig.Name) - 1] = 0; | ||||||
|  | 
 | ||||||
|  |    imFont_ = fontAtlas->AddFontFromMemoryTTF( | ||||||
|  |       const_cast<void*>(static_cast<const void*>(fontData.data())), | ||||||
|  |       static_cast<int>(std::clamp<std::size_t>( | ||||||
|  |          fontData.size(), 0, std::numeric_limits<int>::max())), | ||||||
|  |       sizePixels, | ||||||
|  |       &fontConfig); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | ImFont* ImGuiFont::font() | ||||||
|  | { | ||||||
|  |    return p->imFont_; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | } // namespace types
 | ||||||
|  | } // namespace qt
 | ||||||
|  | } // namespace scwx
 | ||||||
							
								
								
									
										41
									
								
								scwx-qt/source/scwx/qt/types/imgui_font.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						|  | @ -0,0 +1,41 @@ | ||||||
|  | #pragma once | ||||||
|  | 
 | ||||||
|  | #include <memory> | ||||||
|  | #include <string> | ||||||
|  | #include <vector> | ||||||
|  | 
 | ||||||
|  | #include <scwx/qt/types/font_types.hpp> | ||||||
|  | 
 | ||||||
|  | struct ImFont; | ||||||
|  | 
 | ||||||
|  | namespace scwx | ||||||
|  | { | ||||||
|  | namespace qt | ||||||
|  | { | ||||||
|  | namespace types | ||||||
|  | { | ||||||
|  | 
 | ||||||
|  | class ImGuiFont | ||||||
|  | { | ||||||
|  | public: | ||||||
|  |    explicit ImGuiFont(const std::string&            fontName, | ||||||
|  |                       const std::vector<char>&      fontData, | ||||||
|  |                       units::font_size::pixels<int> size); | ||||||
|  |    ~ImGuiFont(); | ||||||
|  | 
 | ||||||
|  |    ImGuiFont(const ImGuiFont&)            = delete; | ||||||
|  |    ImGuiFont& operator=(const ImGuiFont&) = delete; | ||||||
|  | 
 | ||||||
|  |    ImGuiFont(ImGuiFont&&)            = delete; | ||||||
|  |    ImGuiFont& operator=(ImGuiFont&&) = delete; | ||||||
|  | 
 | ||||||
|  |    ImFont* font(); | ||||||
|  | 
 | ||||||
|  | private: | ||||||
|  |    class Impl; | ||||||
|  |    std::unique_ptr<Impl> p; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | } // namespace types
 | ||||||
|  | } // namespace qt
 | ||||||
|  | } // namespace scwx
 | ||||||
 Dan Paulat
						Dan Paulat