6
.gitmodules
vendored
|
|
@ -31,3 +31,9 @@
|
|||
[submodule "external/date"]
|
||||
path = external/date
|
||||
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 |
|
||||
| [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) |
|
||||
| [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 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) |
|
||||
|
|
@ -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) |
|
||||
| [SQLite](https://www.sqlite.org/) | 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) |
|
||||
| [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 |
|
||||
| ------ | ------- | ----- |
|
||||
| Alte DIN 1451 Mittelschrift | SIL Open Font 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 |
|
||||
| [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")
|
||||
requires = ("boost/1.81.0",
|
||||
"cpr/1.9.3",
|
||||
"fontconfig/2.14.2",
|
||||
"freetype/2.12.1",
|
||||
"geographiclib/1.52",
|
||||
"glew/2.2.0",
|
||||
|
|
|
|||
6
external/CMakeLists.txt
vendored
|
|
@ -10,7 +10,9 @@ set_property(DIRECTORY
|
|||
hsluv-c.cmake
|
||||
imgui.cmake
|
||||
mapbox-gl-native.cmake
|
||||
stb.cmake)
|
||||
stb.cmake
|
||||
textflowcpp.cmake
|
||||
units.cmake)
|
||||
|
||||
include(aws-sdk-cpp.cmake)
|
||||
include(date.cmake)
|
||||
|
|
@ -19,3 +21,5 @@ include(hsluv-c.cmake)
|
|||
include(imgui.cmake)
|
||||
include(mapbox-gl-native.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
|
||||
in vec4 color;
|
||||
smooth in vec4 color;
|
||||
|
||||
layout (location = 0) out vec4 fragColor;
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ layout (location = 1) in vec4 aColor;
|
|||
|
||||
uniform mat4 uMVPMatrix;
|
||||
|
||||
out vec4 color;
|
||||
smooth out vec4 color;
|
||||
|
||||
void main()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -8,15 +8,15 @@
|
|||
|
||||
layout (location = 0) in vec2 aLatLong;
|
||||
layout (location = 1) in vec2 aXYOffset;
|
||||
layout (location = 2) in vec2 aTexCoord;
|
||||
layout (location = 2) in vec3 aTexCoord;
|
||||
layout (location = 3) in vec4 aModulate;
|
||||
|
||||
uniform mat4 uMVPMatrix;
|
||||
uniform mat4 uMapMatrix;
|
||||
uniform vec2 uMapScreenCoord;
|
||||
|
||||
smooth out vec2 texCoord;
|
||||
flat out vec4 modulate;
|
||||
smooth out vec3 texCoord;
|
||||
smooth out vec4 color;
|
||||
|
||||
vec2 latLngToScreenCoordinate(in vec2 latLng)
|
||||
{
|
||||
|
|
@ -31,7 +31,7 @@ void main()
|
|||
{
|
||||
// Pass the texture coordinate and color modulate to the fragment shader
|
||||
texCoord = aTexCoord;
|
||||
modulate = aModulate;
|
||||
color = aModulate;
|
||||
|
||||
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;
|
||||
|
||||
smooth in vec2 texCoord;
|
||||
flat in vec4 modulate;
|
||||
smooth in vec4 color;
|
||||
|
||||
layout (location = 0) out vec4 fragColor;
|
||||
|
||||
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)
|
||||
|
||||
find_package(Boost)
|
||||
find_package(Fontconfig)
|
||||
find_package(Freetype)
|
||||
find_package(geographiclib)
|
||||
find_package(glm)
|
||||
|
|
@ -46,7 +47,8 @@ set(HDR_CONFIG source/scwx/qt/config/county_database.hpp
|
|||
source/scwx/qt/config/radar_site.hpp)
|
||||
set(SRC_CONFIG source/scwx/qt/config/county_database.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
|
||||
source/scwx/qt/gl/gl_context.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)
|
||||
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/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)
|
||||
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/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)
|
||||
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/resource_manager.hpp
|
||||
source/scwx/qt/manager/settings_manager.hpp
|
||||
source/scwx/qt/manager/text_event_manager.hpp
|
||||
source/scwx/qt/manager/timeline_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/resource_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_widget.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_range_layer.hpp)
|
||||
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_widget.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_range_layer.cpp)
|
||||
set(HDR_MODEL source/scwx/qt/model/alert_model.hpp
|
||||
source/scwx/qt/model/alert_proxy_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_site_model.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
|
||||
source/scwx/qt/model/alert_proxy_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_site_model.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_variable.hpp
|
||||
source/scwx/qt/settings/settings_variable_base.hpp
|
||||
source/scwx/qt/settings/text_settings.hpp
|
||||
source/scwx/qt/settings/ui_settings.hpp)
|
||||
set(SRC_SETTINGS source/scwx/qt/settings/general_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_variable.cpp
|
||||
source/scwx/qt/settings/settings_variable_base.cpp
|
||||
source/scwx/qt/settings/text_settings.cpp
|
||||
source/scwx/qt/settings/ui_settings.cpp)
|
||||
set(HDR_TYPES source/scwx/qt/types/alert_types.hpp
|
||||
source/scwx/qt/types/font_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/qt_types.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
|
||||
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/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
|
||||
source/scwx/qt/ui/alert_dialog.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/imgui_debug_dialog.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_settings_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/settings_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/imgui_debug_dialog.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_settings_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/settings_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/collapsible_group.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/settings_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_buffer.hpp
|
||||
source/scwx/qt/util/geographic_lib.hpp
|
||||
source/scwx/qt/util/imgui.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/texture_atlas.hpp
|
||||
source/scwx/qt/util/q_file_buffer.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
|
||||
source/scwx/qt/util/file.cpp
|
||||
source/scwx/qt/util/font.cpp
|
||||
source/scwx/qt/util/font_buffer.cpp
|
||||
source/scwx/qt/util/geographic_lib.cpp
|
||||
source/scwx/qt/util/imgui.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/q_file_buffer.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
|
||||
source/scwx/qt/view/level3_product_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
|
||||
gl/color.vert
|
||||
gl/geo_line.vert
|
||||
gl/geo_texture2d.vert
|
||||
gl/map_color.vert
|
||||
gl/radar.frag
|
||||
gl/radar.vert
|
||||
gl/text.frag
|
||||
gl/text.vert
|
||||
gl/texture1d.frag
|
||||
gl/texture1d.vert
|
||||
gl/texture2d.frag)
|
||||
gl/texture2d.frag
|
||||
gl/texture2d_array.frag
|
||||
gl/threshold.geom)
|
||||
|
||||
set(CMAKE_FILES scwx-qt.cmake)
|
||||
|
||||
|
|
@ -386,7 +444,8 @@ target_include_directories(scwx-qt PUBLIC ${scwx-qt_SOURCE_DIR}/source
|
|||
${FTGL_INCLUDE_DIR}
|
||||
${IMGUI_INCLUDE_DIRS}
|
||||
${MBGL_INCLUDE_DIR}
|
||||
${STB_INCLUDE_DIR})
|
||||
${STB_INCLUDE_DIR}
|
||||
${TEXTFLOWCPP_INCLUDE_DIR})
|
||||
|
||||
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
|
||||
qmaplibregl
|
||||
$<$<CXX_COMPILER_ID:MSVC>:opengl32>
|
||||
Fontconfig::Fontconfig
|
||||
freetype-gl
|
||||
GeographicLib::GeographicLib
|
||||
glm::glm
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@
|
|||
<file>gl/color.frag</file>
|
||||
<file>gl/color.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.vert</file>
|
||||
<file>gl/text.frag</file>
|
||||
|
|
@ -10,19 +12,29 @@
|
|||
<file>gl/texture1d.frag</file>
|
||||
<file>gl/texture1d.vert</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/fonts/din1451alt.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.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-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/book-solid.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/gears-solid.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/pause-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/util/maplibre.hpp>
|
||||
|
||||
#include <string>
|
||||
|
||||
|
|
@ -41,6 +42,27 @@ DrawItem::~DrawItem() = default;
|
|||
DrawItem::DrawItem(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(
|
||||
const QMapLibreGL::CustomLayerRenderParameters& params,
|
||||
GLint uMVPMatrixLocation)
|
||||
|
|
@ -54,21 +76,21 @@ void DrawItem::UseDefaultProjection(
|
|||
uMVPMatrixLocation, 1, GL_FALSE, glm::value_ptr(projection));
|
||||
}
|
||||
|
||||
// TODO: Refactor to utility class
|
||||
static glm::vec2
|
||||
LatLongToScreenCoordinate(const QMapLibreGL::Coordinate& coordinate)
|
||||
void DrawItem::UseRotationProjection(
|
||||
const QMapLibreGL::CustomLayerRenderParameters& params,
|
||||
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(
|
||||
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;
|
||||
projection = glm::rotate(projection,
|
||||
glm::radians<float>(params.bearing),
|
||||
glm::vec3(0.0f, 0.0f, 1.0f));
|
||||
|
||||
p->gl_.glUniformMatrix4fv(
|
||||
uMVPMatrixLocation, 1, GL_FALSE, glm::value_ptr(projection));
|
||||
}
|
||||
|
||||
void DrawItem::UseMapProjection(
|
||||
|
|
@ -78,21 +100,11 @@ void DrawItem::UseMapProjection(
|
|||
{
|
||||
OpenGLFunctions& gl = p->gl_;
|
||||
|
||||
// TODO: Refactor to utility class
|
||||
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));
|
||||
const glm::mat4 uMVPMatrix = util::maplibre::GetMapMatrix(params);
|
||||
|
||||
gl.glUniform2fv(uMapScreenCoordLocation,
|
||||
1,
|
||||
glm::value_ptr(LatLongToScreenCoordinate(
|
||||
glm::value_ptr(util::maplibre::LatLongToScreenCoordinate(
|
||||
{params.latitude, params.longitude})));
|
||||
|
||||
gl.glUniformMatrix4fv(
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
#include <memory>
|
||||
|
||||
#include <QMapLibreGL/QMapLibreGL>
|
||||
#include <glm/gtc/type_ptr.hpp>
|
||||
|
||||
namespace scwx
|
||||
{
|
||||
|
|
@ -28,14 +29,34 @@ public:
|
|||
DrawItem& operator=(DrawItem&&) noexcept;
|
||||
|
||||
virtual void Initialize() = 0;
|
||||
virtual void
|
||||
Render(const QMapLibreGL::CustomLayerRenderParameters& params) = 0;
|
||||
virtual void Render(const QMapLibreGL::CustomLayerRenderParameters& params);
|
||||
virtual void Render(const QMapLibreGL::CustomLayerRenderParameters& params,
|
||||
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:
|
||||
void
|
||||
UseDefaultProjection(const QMapLibreGL::CustomLayerRenderParameters& params,
|
||||
GLint uMVPMatrixLocation);
|
||||
void
|
||||
UseRotationProjection(const QMapLibreGL::CustomLayerRenderParameters& params,
|
||||
GLint uMVPMatrixLocation);
|
||||
void UseMapProjection(const QMapLibreGL::CustomLayerRenderParameters& params,
|
||||
GLint uMVPMatrixLocation,
|
||||
GLint uMapScreenCoordLocation);
|
||||
|
|
|
|||
|
|
@ -23,10 +23,12 @@ static constexpr size_t kNumRectangles = 1;
|
|||
static constexpr size_t kNumTriangles = kNumRectangles * 2;
|
||||
static constexpr size_t kVerticesPerTriangle = 3;
|
||||
static constexpr size_t kVerticesPerRectangle = kVerticesPerTriangle * 2;
|
||||
static constexpr size_t kPointsPerVertex = 10;
|
||||
static constexpr size_t kPointsPerVertex = 11;
|
||||
static constexpr size_t kBufferLength =
|
||||
kNumTriangles * kVerticesPerTriangle * kPointsPerVertex;
|
||||
|
||||
static const std::string kTextureName = "lines/default-1x7";
|
||||
|
||||
class GeoLine::Impl
|
||||
{
|
||||
public:
|
||||
|
|
@ -90,8 +92,8 @@ void GeoLine::Initialize()
|
|||
{
|
||||
gl::OpenGLFunctions& gl = p->context_->gl();
|
||||
|
||||
p->shaderProgram_ = p->context_->GetShaderProgram(":/gl/geo_line.vert",
|
||||
":/gl/texture2d.frag");
|
||||
p->shaderProgram_ = p->context_->GetShaderProgram(
|
||||
":/gl/geo_line.vert", ":/gl/texture2d_array.frag");
|
||||
|
||||
p->uMVPMatrixLocation_ =
|
||||
gl.glGetUniformLocation(p->shaderProgram_->id(), "uMVPMatrix");
|
||||
|
|
@ -115,7 +117,7 @@ void GeoLine::Initialize()
|
|||
}
|
||||
|
||||
p->texture_ =
|
||||
util::TextureAtlas::Instance().GetTextureAttributes("lines/default-1x7");
|
||||
util::TextureAtlas::Instance().GetTextureAttributes(kTextureName);
|
||||
|
||||
gl.glGenVertexArrays(1, &p->vao_);
|
||||
gl.glGenBuffers(1, &p->vbo_);
|
||||
|
|
@ -145,7 +147,7 @@ void GeoLine::Initialize()
|
|||
|
||||
// aTexCoord
|
||||
gl.glVertexAttribPointer(2,
|
||||
2,
|
||||
3,
|
||||
GL_FLOAT,
|
||||
GL_FALSE,
|
||||
kPointsPerVertex * sizeof(float),
|
||||
|
|
@ -158,7 +160,7 @@ void GeoLine::Initialize()
|
|||
GL_FLOAT,
|
||||
GL_FALSE,
|
||||
kPointsPerVertex * sizeof(float),
|
||||
reinterpret_cast<void*>(6 * sizeof(float)));
|
||||
reinterpret_cast<void*>(7 * sizeof(float)));
|
||||
gl.glEnableVertexAttribArray(3);
|
||||
|
||||
p->dirty_ = true;
|
||||
|
|
@ -248,6 +250,9 @@ void GeoLine::Impl::Update()
|
|||
{
|
||||
gl::OpenGLFunctions& gl = context_->gl();
|
||||
|
||||
texture_ =
|
||||
util::TextureAtlas::Instance().GetTextureAttributes(kTextureName);
|
||||
|
||||
// Latitude and longitude coordinates in degrees
|
||||
const float lx = points_[0].latitude_;
|
||||
const float rx = points_[1].latitude_;
|
||||
|
|
@ -259,6 +264,8 @@ void GeoLine::Impl::Update()
|
|||
const float oy = width_ * 0.5f * sinf(angle_);
|
||||
|
||||
// Texture coordinates
|
||||
static constexpr float r = 0.0f;
|
||||
|
||||
const float ls = texture_.sLeft_;
|
||||
const float rs = texture_.sRight_;
|
||||
const float tt = texture_.tTop_;
|
||||
|
|
@ -284,12 +291,12 @@ void GeoLine::Impl::Update()
|
|||
{ //
|
||||
// Line
|
||||
{
|
||||
{lx, by, -ox, -oy, ls, bt, mc0, mc1, mc2, mc3}, // BL
|
||||
{lx, by, +ox, +oy, ls, tt, mc0, mc1, mc2, mc3}, // TL
|
||||
{rx, ty, -ox, -oy, rs, bt, mc0, mc1, mc2, mc3}, // BR
|
||||
{rx, ty, -ox, -oy, rs, bt, mc0, mc1, mc2, mc3}, // BR
|
||||
{rx, ty, +ox, +oy, rs, tt, mc0, mc1, mc2, mc3}, // TR
|
||||
{lx, by, +ox, +oy, ls, tt, mc0, mc1, mc2, mc3} // TL
|
||||
{lx, by, -ox, -oy, ls, bt, r, mc0, mc1, mc2, mc3}, // BL
|
||||
{lx, by, +ox, +oy, ls, tt, r, mc0, mc1, mc2, mc3}, // TL
|
||||
{rx, ty, -ox, -oy, rs, bt, r, mc0, mc1, mc2, mc3}, // BR
|
||||
{rx, ty, -ox, -oy, rs, bt, r, mc0, mc1, mc2, mc3}, // BR
|
||||
{rx, ty, +ox, +oy, rs, tt, r, mc0, mc1, mc2, mc3}, // TR
|
||||
{lx, by, +ox, +oy, ls, tt, r, mc0, mc1, mc2, mc3} // TL
|
||||
}};
|
||||
|
||||
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/util/texture_atlas.hpp>
|
||||
#include <scwx/util/hash.hpp>
|
||||
#include <scwx/util/logger.hpp>
|
||||
|
||||
#include <boost/container_hash/hash.hpp>
|
||||
|
||||
namespace scwx
|
||||
{
|
||||
namespace qt
|
||||
|
|
@ -25,16 +26,23 @@ public:
|
|||
}
|
||||
~Impl() {}
|
||||
|
||||
void InitializeGL();
|
||||
|
||||
static std::size_t
|
||||
GetShaderKey(std::initializer_list<std::pair<GLenum, std::string>> shaders);
|
||||
|
||||
gl::OpenGLFunctions gl_;
|
||||
|
||||
std::unordered_map<std::pair<std::string, std::string>,
|
||||
std::shared_ptr<gl::ShaderProgram>,
|
||||
scwx::util::hash<std::pair<std::string, std::string>>>
|
||||
bool glInitialized_ {false};
|
||||
|
||||
std::unordered_map<std::size_t, std::shared_ptr<gl::ShaderProgram>>
|
||||
shaderProgramMap_;
|
||||
std::mutex shaderProgramMutex_;
|
||||
|
||||
GLuint textureAtlas_;
|
||||
std::mutex textureMutex_;
|
||||
|
||||
std::uint64_t textureBufferCount_ {};
|
||||
};
|
||||
|
||||
GlContext::GlContext() : p(std::make_unique<Impl>()) {}
|
||||
|
|
@ -48,11 +56,35 @@ gl::OpenGLFunctions& GlContext::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>
|
||||
GlContext::GetShaderProgram(const std::string& vertexPath,
|
||||
const std::string& fragmentPath)
|
||||
{
|
||||
const std::pair<std::string, std::string> key {vertexPath, fragmentPath};
|
||||
return GetShaderProgram(
|
||||
{{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_);
|
||||
|
|
@ -62,7 +94,7 @@ GlContext::GetShaderProgram(const std::string& vertexPath,
|
|||
if (it == p->shaderProgramMap_.end())
|
||||
{
|
||||
shaderProgram = std::make_shared<gl::ShaderProgram>(p->gl_);
|
||||
shaderProgram->Load(vertexPath, fragmentPath);
|
||||
shaderProgram->Load(shaders);
|
||||
p->shaderProgramMap_[key] = shaderProgram;
|
||||
}
|
||||
else
|
||||
|
|
@ -75,16 +107,33 @@ GlContext::GetShaderProgram(const std::string& vertexPath,
|
|||
|
||||
GLuint GlContext::GetTextureAtlas()
|
||||
{
|
||||
p->InitializeGL();
|
||||
|
||||
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_;
|
||||
}
|
||||
|
||||
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 qt
|
||||
} // namespace scwx
|
||||
|
|
|
|||
|
|
@ -24,9 +24,13 @@ public:
|
|||
|
||||
gl::OpenGLFunctions& gl();
|
||||
|
||||
std::uint64_t texture_buffer_count() const;
|
||||
|
||||
std::shared_ptr<gl::ShaderProgram>
|
||||
GetShaderProgram(const std::string& vertexPath,
|
||||
const std::string& fragmentPath);
|
||||
std::shared_ptr<gl::ShaderProgram> GetShaderProgram(
|
||||
std::initializer_list<std::pair<GLenum, std::string>> shaders);
|
||||
|
||||
GLuint GetTextureAtlas();
|
||||
|
||||
|
|
|
|||
|
|
@ -15,6 +15,11 @@ static const auto logger_ = scwx::util::Logger::Create(logPrefix_);
|
|||
|
||||
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
|
||||
{
|
||||
public:
|
||||
|
|
@ -30,6 +35,8 @@ public:
|
|||
gl_.glDeleteProgram(id_);
|
||||
}
|
||||
|
||||
static std::string ShaderName(GLenum type);
|
||||
|
||||
OpenGLFunctions& gl_;
|
||||
|
||||
GLuint id_;
|
||||
|
|
@ -49,10 +56,37 @@ GLuint ShaderProgram::id() const
|
|||
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,
|
||||
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_;
|
||||
|
||||
|
|
@ -61,81 +95,59 @@ bool ShaderProgram::Load(const std::string& vertexPath,
|
|||
char infoLog[kInfoLogBufSize];
|
||||
GLsizei logLength;
|
||||
|
||||
QFile vertexFile(vertexPath.c_str());
|
||||
QFile fragmentFile(fragmentPath.c_str());
|
||||
std::vector<GLuint> shaderIds {};
|
||||
|
||||
vertexFile.open(QIODevice::ReadOnly | QIODevice::Text);
|
||||
fragmentFile.open(QIODevice::ReadOnly | QIODevice::Text);
|
||||
|
||||
if (!vertexFile.isOpen())
|
||||
for (auto& shader : shaders)
|
||||
{
|
||||
logger_->error("Could not load vertex shader: {}", vertexPath);
|
||||
return false;
|
||||
logger_->debug("Loading {} shader: {}",
|
||||
Impl::ShaderName(shader.first),
|
||||
shader.second);
|
||||
|
||||
QFile file(shader.second.c_str());
|
||||
file.open(QIODevice::ReadOnly | QIODevice::Text);
|
||||
|
||||
if (!file.isOpen())
|
||||
{
|
||||
logger_->error("Could not load shader");
|
||||
success = false;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!fragmentFile.isOpen())
|
||||
{
|
||||
logger_->error("Could not load fragment shader: {}", fragmentPath);
|
||||
return false;
|
||||
}
|
||||
QTextStream shaderStream(&file);
|
||||
shaderStream.setEncoding(QStringConverter::Utf8);
|
||||
|
||||
QTextStream vertexShaderStream(&vertexFile);
|
||||
QTextStream fragmentShaderStream(&fragmentFile);
|
||||
std::string shaderSource = shaderStream.readAll().toStdString();
|
||||
const char* shaderSourceC = shaderSource.c_str();
|
||||
|
||||
vertexShaderStream.setEncoding(QStringConverter::Utf8);
|
||||
fragmentShaderStream.setEncoding(QStringConverter::Utf8);
|
||||
|
||||
std::string vertexShaderSource = vertexShaderStream.readAll().toStdString();
|
||||
std::string fragmentShaderSource =
|
||||
fragmentShaderStream.readAll().toStdString();
|
||||
|
||||
const char* vertexShaderSourceC = vertexShaderSource.c_str();
|
||||
const char* fragmentShaderSourceC = fragmentShaderSource.c_str();
|
||||
|
||||
// Create a vertex shader
|
||||
GLuint vertexShader = gl.glCreateShader(GL_VERTEX_SHADER);
|
||||
// Create a shader
|
||||
GLuint shaderId = gl.glCreateShader(shader.first);
|
||||
shaderIds.push_back(shaderId);
|
||||
|
||||
// Attach the shader source code and compile the shader
|
||||
gl.glShaderSource(vertexShader, 1, &vertexShaderSourceC, NULL);
|
||||
gl.glCompileShader(vertexShader);
|
||||
gl.glShaderSource(shaderId, 1, &shaderSourceC, NULL);
|
||||
gl.glCompileShader(shaderId);
|
||||
|
||||
// Check for errors
|
||||
gl.glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &glSuccess);
|
||||
gl.glGetShaderInfoLog(vertexShader, kInfoLogBufSize, &logLength, infoLog);
|
||||
gl.glGetShaderiv(shaderId, GL_COMPILE_STATUS, &glSuccess);
|
||||
gl.glGetShaderInfoLog(shaderId, kInfoLogBufSize, &logLength, infoLog);
|
||||
if (!glSuccess)
|
||||
{
|
||||
logger_->error("Vertex shader compilation failed: {}", infoLog);
|
||||
logger_->error("Shader compilation failed: {}", infoLog);
|
||||
success = false;
|
||||
break;
|
||||
}
|
||||
else if (logLength > 0)
|
||||
{
|
||||
logger_->error("Vertex shader compiled with warnings: {}", infoLog);
|
||||
logger_->error("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)
|
||||
{
|
||||
gl.glAttachShader(p->id_, vertexShader);
|
||||
gl.glAttachShader(p->id_, fragmentShader);
|
||||
for (auto& shaderId : shaderIds)
|
||||
{
|
||||
gl.glAttachShader(p->id_, shaderId);
|
||||
}
|
||||
gl.glLinkProgram(p->id_);
|
||||
|
||||
// Check for errors
|
||||
|
|
@ -153,8 +165,10 @@ bool ShaderProgram::Load(const std::string& vertexPath,
|
|||
}
|
||||
|
||||
// Delete shaders
|
||||
gl.glDeleteShader(vertexShader);
|
||||
gl.glDeleteShader(fragmentShader);
|
||||
for (auto& shaderId : shaderIds)
|
||||
{
|
||||
gl.glDeleteShader(shaderId);
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,7 +30,10 @@ public:
|
|||
|
||||
GLuint id() const;
|
||||
|
||||
GLint GetUniformLocation(const std::string& name);
|
||||
|
||||
bool Load(const std::string& vertexPath, const std::string& fragmentPath);
|
||||
bool Load(std::initializer_list<std::pair<GLenum, std::string>> shaderPaths);
|
||||
|
||||
void Use() const;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,13 +1,18 @@
|
|||
#define NOMINMAX
|
||||
|
||||
#include <scwx/qt/config/radar_site.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/resource_manager.hpp>
|
||||
#include <scwx/qt/manager/settings_manager.hpp>
|
||||
#include <scwx/network/cpr.hpp>
|
||||
#include <scwx/util/logger.hpp>
|
||||
#include <scwx/util/threads.hpp>
|
||||
|
||||
#include <aws/core/Aws.h>
|
||||
#include <boost/asio.hpp>
|
||||
#include <fmt/format.h>
|
||||
#include <spdlog/spdlog.h>
|
||||
#include <QApplication>
|
||||
#include <QTranslator>
|
||||
|
|
@ -26,6 +31,8 @@ int main(int argc, char* argv[])
|
|||
QApplication a(argc, argv);
|
||||
|
||||
QCoreApplication::setApplicationName("Supercell Wx");
|
||||
scwx::network::cpr::SetUserAgent(
|
||||
fmt::format("SupercellWx/{}", scwx::qt::main::kVersionString_));
|
||||
|
||||
// Enable internationalization support
|
||||
QTranslator translator;
|
||||
|
|
@ -62,7 +69,7 @@ int main(int argc, char* argv[])
|
|||
|
||||
// Initialize application
|
||||
scwx::qt::config::RadarSite::Initialize();
|
||||
scwx::qt::manager::SettingsManager::Initialize();
|
||||
scwx::qt::manager::SettingsManager::Instance().Initialize();
|
||||
scwx::qt::manager::ResourceManager::Initialize();
|
||||
|
||||
// Run Qt main loop
|
||||
|
|
@ -82,7 +89,7 @@ int main(int argc, char* argv[])
|
|||
|
||||
// Shutdown application
|
||||
scwx::qt::manager::ResourceManager::Shutdown();
|
||||
scwx::qt::manager::SettingsManager::Shutdown();
|
||||
scwx::qt::manager::SettingsManager::Instance().Shutdown();
|
||||
|
||||
// Shutdown AWS SDK
|
||||
Aws::ShutdownAPI(awsSdkOptions);
|
||||
|
|
|
|||
|
|
@ -5,14 +5,16 @@
|
|||
|
||||
#include <scwx/qt/main/application.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/settings_manager.hpp>
|
||||
#include <scwx/qt/manager/text_event_manager.hpp>
|
||||
#include <scwx/qt/manager/timeline_manager.hpp>
|
||||
#include <scwx/qt/manager/update_manager.hpp>
|
||||
#include <scwx/qt/map/map_provider.hpp>
|
||||
#include <scwx/qt/map/map_widget.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/ui/about_dialog.hpp>
|
||||
#include <scwx/qt/ui/alert_dock_widget.hpp>
|
||||
|
|
@ -20,9 +22,11 @@
|
|||
#include <scwx/qt/ui/collapsible_group.hpp>
|
||||
#include <scwx/qt/ui/flow_layout.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_settings_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/settings_dialog.hpp>
|
||||
#include <scwx/qt/ui/update_dialog.hpp>
|
||||
|
|
@ -75,10 +79,13 @@ public:
|
|||
animationDockWidget_ {nullptr},
|
||||
aboutDialog_ {nullptr},
|
||||
imGuiDebugDialog_ {nullptr},
|
||||
layerDialog_ {nullptr},
|
||||
placefileDialog_ {nullptr},
|
||||
radarSiteDialog_ {nullptr},
|
||||
settingsDialog_ {nullptr},
|
||||
updateDialog_ {nullptr},
|
||||
radarProductModel_ {nullptr},
|
||||
placefileManager_ {manager::PlacefileManager::Instance()},
|
||||
textEventManager_ {manager::TextEventManager::Instance()},
|
||||
timelineManager_ {manager::TimelineManager::Instance()},
|
||||
updateManager_ {manager::UpdateManager::Instance()},
|
||||
|
|
@ -87,10 +94,8 @@ public:
|
|||
elevationButtonsChanged_ {false},
|
||||
resizeElevationButtons_ {false}
|
||||
{
|
||||
mapProvider_ =
|
||||
map::GetMapProvider(manager::SettingsManager::general_settings()
|
||||
.map_provider()
|
||||
.GetValue());
|
||||
mapProvider_ = map::GetMapProvider(
|
||||
settings::GeneralSettings::Instance().map_provider().GetValue());
|
||||
const map::MapProviderInfo& mapProviderInfo =
|
||||
map::GetMapProviderInfo(mapProvider_);
|
||||
|
||||
|
|
@ -164,11 +169,14 @@ public:
|
|||
ui::AnimationDockWidget* animationDockWidget_;
|
||||
ui::AboutDialog* aboutDialog_;
|
||||
ui::ImGuiDebugDialog* imGuiDebugDialog_;
|
||||
ui::LayerDialog* layerDialog_;
|
||||
ui::PlacefileDialog* placefileDialog_;
|
||||
ui::RadarSiteDialog* radarSiteDialog_;
|
||||
ui::SettingsDialog* settingsDialog_;
|
||||
ui::UpdateDialog* updateDialog_;
|
||||
|
||||
std::unique_ptr<model::RadarProductModel> radarProductModel_;
|
||||
std::shared_ptr<manager::PlacefileManager> placefileManager_;
|
||||
std::shared_ptr<manager::TextEventManager> textEventManager_;
|
||||
std::shared_ptr<manager::TimelineManager> timelineManager_;
|
||||
std::shared_ptr<manager::UpdateManager> updateManager_;
|
||||
|
|
@ -227,7 +235,7 @@ MainWindow::MainWindow(QWidget* parent) :
|
|||
ui->actionAlerts->setVisible(false);
|
||||
|
||||
ui->menuDebug->menuAction()->setVisible(
|
||||
manager::SettingsManager::general_settings().debug_enabled().GetValue());
|
||||
settings::GeneralSettings::Instance().debug_enabled().GetValue());
|
||||
|
||||
// Configure Resource Explorer Dock
|
||||
ui->resourceExplorerDock->setVisible(false);
|
||||
|
|
@ -241,6 +249,12 @@ MainWindow::MainWindow(QWidget* parent) :
|
|||
// Radar Site Dialog
|
||||
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
|
||||
p->settingsDialog_ = new ui::SettingsDialog(this);
|
||||
|
||||
|
|
@ -303,7 +317,7 @@ MainWindow::MainWindow(QWidget* parent) :
|
|||
// Update Dialog
|
||||
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++)
|
||||
{
|
||||
p->SelectRadarProduct(p->maps_.at(i),
|
||||
|
|
@ -441,11 +455,26 @@ void MainWindow::on_actionExit_triggered()
|
|||
close();
|
||||
}
|
||||
|
||||
void MainWindow::on_actionPlacefileManager_triggered()
|
||||
{
|
||||
p->placefileDialog_->show();
|
||||
}
|
||||
|
||||
void MainWindow::on_actionLayerManager_triggered()
|
||||
{
|
||||
p->layerDialog_->show();
|
||||
}
|
||||
|
||||
void MainWindow::on_actionImGuiDebug_triggered()
|
||||
{
|
||||
p->imGuiDebugDialog_->show();
|
||||
}
|
||||
|
||||
void MainWindow::on_actionDumpLayerList_triggered()
|
||||
{
|
||||
p->activeMap_->DumpLayerList();
|
||||
}
|
||||
|
||||
void MainWindow::on_actionDumpRadarProductRecords_triggered()
|
||||
{
|
||||
manager::RadarProductManager::DumpRecords();
|
||||
|
|
@ -579,7 +608,7 @@ void MainWindow::on_resourceTreeView_doubleClicked(const QModelIndex& index)
|
|||
|
||||
void MainWindowImpl::AsyncSetup()
|
||||
{
|
||||
auto& generalSettings = manager::SettingsManager::general_settings();
|
||||
auto& generalSettings = settings::GeneralSettings::Instance();
|
||||
|
||||
// Check for updates
|
||||
if (generalSettings.update_notifications_enabled().GetValue())
|
||||
|
|
@ -592,7 +621,7 @@ void MainWindowImpl::AsyncSetup()
|
|||
|
||||
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 gridHeight = generalSettings.grid_height().GetValue();
|
||||
|
|
@ -626,7 +655,7 @@ void MainWindowImpl::ConfigureMapLayout()
|
|||
{
|
||||
if (maps_.at(mapIndex) == nullptr)
|
||||
{
|
||||
maps_[mapIndex] = new map::MapWidget(settings_);
|
||||
maps_[mapIndex] = new map::MapWidget(mapIndex, settings_);
|
||||
}
|
||||
|
||||
hs->addWidget(maps_[mapIndex]);
|
||||
|
|
@ -643,7 +672,7 @@ void MainWindowImpl::ConfigureMapLayout()
|
|||
void MainWindowImpl::ConfigureMapStyles()
|
||||
{
|
||||
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++)
|
||||
{
|
||||
|
|
@ -816,6 +845,15 @@ void MainWindowImpl::ConnectAnimationSignals()
|
|||
timelineManager_.get(),
|
||||
&manager::TimelineManager::AnimationStepEnd);
|
||||
|
||||
connect(timelineManager_.get(),
|
||||
&manager::TimelineManager::SelectedTimeUpdated,
|
||||
[this]()
|
||||
{
|
||||
for (auto map : maps_)
|
||||
{
|
||||
map->update();
|
||||
}
|
||||
});
|
||||
connect(timelineManager_.get(),
|
||||
&manager::TimelineManager::VolumeTimeUpdated,
|
||||
[this](std::chrono::system_clock::time_point dateTime)
|
||||
|
|
@ -885,8 +923,7 @@ void MainWindowImpl::ConnectOtherSignals()
|
|||
{
|
||||
if (maps_[i] == activeMap_)
|
||||
{
|
||||
auto& mapSettings =
|
||||
manager::SettingsManager::map_settings();
|
||||
auto& mapSettings = settings::MapSettings::Instance();
|
||||
mapSettings.map_style(i).StageValue(text.toStdString());
|
||||
break;
|
||||
}
|
||||
|
|
@ -1063,7 +1100,7 @@ void MainWindowImpl::UpdateMapStyle(const std::string& styleName)
|
|||
{
|
||||
if (maps_[i] == activeMap_)
|
||||
{
|
||||
auto& mapSettings = manager::SettingsManager::map_settings();
|
||||
auto& mapSettings = settings::MapSettings::Instance();
|
||||
mapSettings.map_style(i).StageValue(styleName);
|
||||
break;
|
||||
}
|
||||
|
|
@ -1113,6 +1150,8 @@ void MainWindowImpl::UpdateRadarSite()
|
|||
|
||||
timelineManager_->SetRadarSite("?");
|
||||
}
|
||||
|
||||
placefileManager_->SetRadarSite(radarSite);
|
||||
}
|
||||
|
||||
void MainWindowImpl::UpdateVcp()
|
||||
|
|
|
|||
|
|
@ -36,7 +36,10 @@ private slots:
|
|||
void on_actionOpenTextEvent_triggered();
|
||||
void on_actionSettings_triggered();
|
||||
void on_actionExit_triggered();
|
||||
void on_actionPlacefileManager_triggered();
|
||||
void on_actionLayerManager_triggered();
|
||||
void on_actionImGuiDebug_triggered();
|
||||
void on_actionDumpLayerList_triggered();
|
||||
void on_actionDumpRadarProductRecords_triggered();
|
||||
void on_actionUserManual_triggered();
|
||||
void on_actionDiscord_triggered();
|
||||
|
|
|
|||
|
|
@ -85,10 +85,19 @@
|
|||
</property>
|
||||
<addaction name="actionImGuiDebug"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="actionDumpLayerList"/>
|
||||
<addaction name="actionDumpRadarProductRecords"/>
|
||||
</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="menuView"/>
|
||||
<addaction name="menuTools"/>
|
||||
<addaction name="menuDebug"/>
|
||||
<addaction name="menuHelp"/>
|
||||
</widget>
|
||||
|
|
@ -415,6 +424,29 @@
|
|||
<string>&Check for Updates</string>
|
||||
</property>
|
||||
</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>
|
||||
<resources>
|
||||
<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/font_manager.hpp>
|
||||
#include <scwx/qt/config/county_database.hpp>
|
||||
#include <scwx/qt/model/imgui_context_model.hpp>
|
||||
#include <scwx/qt/util/font.hpp>
|
||||
#include <scwx/qt/util/texture_atlas.hpp>
|
||||
#include <scwx/util/logger.hpp>
|
||||
|
||||
#include <imgui.h>
|
||||
#include <execution>
|
||||
#include <mutex>
|
||||
|
||||
#include <QFontDatabase>
|
||||
#include <imgui.h>
|
||||
|
||||
namespace scwx
|
||||
{
|
||||
|
|
@ -24,11 +25,10 @@ static const auto logger_ = scwx::util::Logger::Create(logPrefix_);
|
|||
static void LoadFonts();
|
||||
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_g, ":/res/fonts/din1451alt_g.ttf"}};
|
||||
|
||||
static std::unordered_map<types::Font, int> fontIds_ {};
|
||||
{types::Font::din1451alt_g, ":/res/fonts/din1451alt_g.ttf"},
|
||||
{types::Font::Inconsolata_Regular, ":/res/fonts/Inconsolata-Regular.ttf"}};
|
||||
|
||||
void Initialize()
|
||||
{
|
||||
|
|
@ -40,29 +40,52 @@ void Initialize()
|
|||
|
||||
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);
|
||||
if (it != fontIds_.cend())
|
||||
util::TextureAtlas& textureAtlas = util::TextureAtlas::Instance();
|
||||
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)
|
||||
{
|
||||
return it->second;
|
||||
auto image = LoadImageResource(urlString);
|
||||
|
||||
if (image != nullptr)
|
||||
{
|
||||
std::unique_lock lock {m};
|
||||
images.emplace_back(std::move(image));
|
||||
}
|
||||
return -1;
|
||||
});
|
||||
|
||||
if (!images.empty())
|
||||
{
|
||||
util::TextureAtlas& textureAtlas = util::TextureAtlas::Instance();
|
||||
textureAtlas.BuildAtlas(2048, 2048);
|
||||
}
|
||||
|
||||
return images;
|
||||
}
|
||||
|
||||
static void LoadFonts()
|
||||
{
|
||||
auto& fontManager = FontManager::Instance();
|
||||
|
||||
for (auto& fontName : fontNames_)
|
||||
{
|
||||
int fontId = QFontDatabase::addApplicationFont(
|
||||
QString::fromStdString(fontName.second));
|
||||
fontIds_.emplace(fontName.first, fontId);
|
||||
|
||||
util::Font::Create(fontName.second);
|
||||
fontManager.LoadApplicationFont(fontName.first, fontName.second);
|
||||
}
|
||||
|
||||
ImFontAtlas* fontAtlas = model::ImGuiContextModel::Instance().font_atlas();
|
||||
fontAtlas->AddFontDefault();
|
||||
fontManager.InitializeFonts();
|
||||
}
|
||||
|
||||
static void LoadTextures()
|
||||
|
|
@ -72,7 +95,7 @@ static void LoadTextures()
|
|||
":/res/textures/lines/default-1x7.png");
|
||||
textureAtlas.RegisterTexture("lines/test-pattern",
|
||||
":/res/textures/lines/test-pattern.png");
|
||||
textureAtlas.BuildAtlas(8, 8);
|
||||
textureAtlas.BuildAtlas(2048, 2048);
|
||||
}
|
||||
|
||||
} // namespace ResourceManager
|
||||
|
|
|
|||
|
|
@ -2,6 +2,10 @@
|
|||
|
||||
#include <scwx/qt/types/font_types.hpp>
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include <boost/gil/typedefs.hpp>
|
||||
|
||||
namespace scwx
|
||||
{
|
||||
namespace qt
|
||||
|
|
@ -14,7 +18,10 @@ namespace ResourceManager
|
|||
void Initialize();
|
||||
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 manager
|
||||
|
|
|
|||
|
|
@ -1,5 +1,9 @@
|
|||
#include <scwx/qt/manager/settings_manager.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/util/json.hpp>
|
||||
#include <scwx/util/logger.hpp>
|
||||
|
|
@ -17,21 +21,33 @@ namespace qt
|
|||
{
|
||||
namespace manager
|
||||
{
|
||||
namespace SettingsManager
|
||||
{
|
||||
|
||||
static const std::string logPrefix_ = "scwx::qt::manager::settings_manager";
|
||||
static const auto logger_ = scwx::util::Logger::Create(logPrefix_);
|
||||
|
||||
static boost::json::value ConvertSettingsToJson();
|
||||
static void GenerateDefaultSettings();
|
||||
static bool LoadSettings(const boost::json::object& settingsJson);
|
||||
static void ValidateSettings();
|
||||
class SettingsManager::Impl
|
||||
{
|
||||
public:
|
||||
explicit Impl(SettingsManager* self) : self_ {self} {}
|
||||
~Impl() = default;
|
||||
|
||||
static bool initialized_ {false};
|
||||
static std::string settingsPath_ {};
|
||||
void ValidateSettings();
|
||||
|
||||
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 {
|
||||
QStandardPaths::writableLocation(QStandardPaths::AppDataLocation)
|
||||
|
|
@ -46,14 +62,14 @@ void Initialize()
|
|||
}
|
||||
}
|
||||
|
||||
settingsPath_ = appDataPath + "/settings.json";
|
||||
initialized_ = true;
|
||||
p->settingsPath_ = appDataPath + "/settings.json";
|
||||
p->initialized_ = true;
|
||||
|
||||
ReadSettings(settingsPath_);
|
||||
ValidateSettings();
|
||||
ReadSettings(p->settingsPath_);
|
||||
p->ValidateSettings();
|
||||
}
|
||||
|
||||
void ReadSettings(const std::string& settingsPath)
|
||||
void SettingsManager::ReadSettings(const std::string& settingsPath)
|
||||
{
|
||||
boost::json::value settingsJson = nullptr;
|
||||
|
||||
|
|
@ -64,39 +80,41 @@ void ReadSettings(const std::string& settingsPath)
|
|||
|
||||
if (settingsJson == nullptr || !settingsJson.is_object())
|
||||
{
|
||||
GenerateDefaultSettings();
|
||||
settingsJson = ConvertSettingsToJson();
|
||||
Impl::GenerateDefaultSettings();
|
||||
settingsJson = Impl::ConvertSettingsToJson();
|
||||
util::json::WriteJsonFile(settingsPath, settingsJson);
|
||||
}
|
||||
else
|
||||
{
|
||||
bool jsonDirty = LoadSettings(settingsJson.as_object());
|
||||
bool jsonDirty = Impl::LoadSettings(settingsJson.as_object());
|
||||
|
||||
if (jsonDirty)
|
||||
{
|
||||
settingsJson = ConvertSettingsToJson();
|
||||
settingsJson = Impl::ConvertSettingsToJson();
|
||||
util::json::WriteJsonFile(settingsPath, settingsJson);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
void SaveSettings()
|
||||
void SettingsManager::SaveSettings()
|
||||
{
|
||||
if (initialized_)
|
||||
if (p->initialized_)
|
||||
{
|
||||
logger_->info("Saving settings");
|
||||
|
||||
boost::json::value settingsJson = ConvertSettingsToJson();
|
||||
util::json::WriteJsonFile(settingsPath_, settingsJson);
|
||||
boost::json::value settingsJson = Impl::ConvertSettingsToJson();
|
||||
util::json::WriteJsonFile(p->settingsPath_, settingsJson);
|
||||
|
||||
Q_EMIT SettingsSaved();
|
||||
}
|
||||
}
|
||||
|
||||
void Shutdown()
|
||||
void SettingsManager::Shutdown()
|
||||
{
|
||||
bool dataChanged = false;
|
||||
|
||||
dataChanged |= general_settings().Shutdown();
|
||||
dataChanged |= map_settings().Shutdown();
|
||||
dataChanged |= settings::GeneralSettings::Instance().Shutdown();
|
||||
dataChanged |= settings::MapSettings::Instance().Shutdown();
|
||||
dataChanged |= settings::UiSettings::Instance().Shutdown();
|
||||
|
||||
if (dataChanged)
|
||||
|
|
@ -105,67 +123,53 @@ void Shutdown()
|
|||
}
|
||||
}
|
||||
|
||||
settings::GeneralSettings& general_settings()
|
||||
{
|
||||
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::value SettingsManager::Impl::ConvertSettingsToJson()
|
||||
{
|
||||
boost::json::object settingsJson;
|
||||
|
||||
general_settings().WriteJson(settingsJson);
|
||||
map_settings().WriteJson(settingsJson);
|
||||
palette_settings().WriteJson(settingsJson);
|
||||
settings::GeneralSettings::Instance().WriteJson(settingsJson);
|
||||
settings::MapSettings::Instance().WriteJson(settingsJson);
|
||||
settings::PaletteSettings::Instance().WriteJson(settingsJson);
|
||||
settings::TextSettings::Instance().WriteJson(settingsJson);
|
||||
settings::UiSettings::Instance().WriteJson(settingsJson);
|
||||
|
||||
return settingsJson;
|
||||
}
|
||||
|
||||
static void GenerateDefaultSettings()
|
||||
void SettingsManager::Impl::GenerateDefaultSettings()
|
||||
{
|
||||
logger_->info("Generating default settings");
|
||||
|
||||
general_settings().SetDefaults();
|
||||
map_settings().SetDefaults();
|
||||
palette_settings().SetDefaults();
|
||||
settings::GeneralSettings::Instance().SetDefaults();
|
||||
settings::MapSettings::Instance().SetDefaults();
|
||||
settings::PaletteSettings::Instance().SetDefaults();
|
||||
settings::TextSettings::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");
|
||||
|
||||
bool jsonDirty = false;
|
||||
|
||||
jsonDirty |= !general_settings().ReadJson(settingsJson);
|
||||
jsonDirty |= !map_settings().ReadJson(settingsJson);
|
||||
jsonDirty |= !palette_settings().ReadJson(settingsJson);
|
||||
jsonDirty |= !settings::GeneralSettings::Instance().ReadJson(settingsJson);
|
||||
jsonDirty |= !settings::MapSettings::Instance().ReadJson(settingsJson);
|
||||
jsonDirty |= !settings::PaletteSettings::Instance().ReadJson(settingsJson);
|
||||
jsonDirty |= !settings::TextSettings::Instance().ReadJson(settingsJson);
|
||||
jsonDirty |= !settings::UiSettings::Instance().ReadJson(settingsJson);
|
||||
|
||||
return jsonDirty;
|
||||
}
|
||||
|
||||
static void ValidateSettings()
|
||||
void SettingsManager::Impl::ValidateSettings()
|
||||
{
|
||||
logger_->debug("Validating settings");
|
||||
|
||||
bool settingsChanged = false;
|
||||
|
||||
auto& generalSettings = general_settings();
|
||||
auto& generalSettings = settings::GeneralSettings::Instance();
|
||||
|
||||
// Validate map provider
|
||||
std::string mapProviderName = generalSettings.map_provider().GetValue();
|
||||
|
|
@ -196,11 +200,16 @@ static void ValidateSettings()
|
|||
|
||||
if (settingsChanged)
|
||||
{
|
||||
SaveSettings();
|
||||
self_->SaveSettings();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace SettingsManager
|
||||
SettingsManager& SettingsManager::Instance()
|
||||
{
|
||||
static SettingsManager instance_ {};
|
||||
return instance_;
|
||||
}
|
||||
|
||||
} // namespace manager
|
||||
} // namespace qt
|
||||
} // namespace scwx
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
#pragma once
|
||||
|
||||
#include <scwx/qt/settings/general_settings.hpp>
|
||||
#include <scwx/qt/settings/map_settings.hpp>
|
||||
#include <scwx/qt/settings/palette_settings.hpp>
|
||||
#include <string>
|
||||
#include <memory>
|
||||
|
||||
#include <QObject>
|
||||
|
||||
namespace scwx
|
||||
{
|
||||
|
|
@ -10,19 +11,31 @@ namespace qt
|
|||
{
|
||||
namespace manager
|
||||
{
|
||||
namespace SettingsManager
|
||||
|
||||
class SettingsManager : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY_MOVE(SettingsManager)
|
||||
|
||||
void Initialize();
|
||||
void ReadSettings(const std::string& settingsPath);
|
||||
void SaveSettings();
|
||||
void Shutdown();
|
||||
public:
|
||||
explicit SettingsManager();
|
||||
~SettingsManager();
|
||||
|
||||
settings::GeneralSettings& general_settings();
|
||||
settings::MapSettings& map_settings();
|
||||
settings::PaletteSettings& palette_settings();
|
||||
void Initialize();
|
||||
void ReadSettings(const std::string& settingsPath);
|
||||
void SaveSettings();
|
||||
void Shutdown();
|
||||
|
||||
static SettingsManager& Instance();
|
||||
|
||||
signals:
|
||||
void SettingsSaved();
|
||||
|
||||
private:
|
||||
class Impl;
|
||||
std::unique_ptr<Impl> p;
|
||||
};
|
||||
|
||||
} // namespace SettingsManager
|
||||
} // namespace manager
|
||||
} // namespace qt
|
||||
} // namespace scwx
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
#include <scwx/qt/manager/timeline_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/map.hpp>
|
||||
#include <scwx/util/time.hpp>
|
||||
|
|
@ -39,7 +39,7 @@ class TimelineManager::Impl
|
|||
public:
|
||||
explicit Impl(TimelineManager* self) : self_ {self}
|
||||
{
|
||||
auto& generalSettings = SettingsManager::general_settings();
|
||||
auto& generalSettings = settings::GeneralSettings::Instance();
|
||||
|
||||
loopDelay_ =
|
||||
std::chrono::milliseconds(generalSettings.loop_delay().GetValue());
|
||||
|
|
@ -281,7 +281,12 @@ void TimelineManager::Impl::RadarSweepMonitorReset()
|
|||
void TimelineManager::Impl::RadarSweepMonitorWait(
|
||||
std::unique_lock<std::mutex>& lock)
|
||||
{
|
||||
std::cv_status status =
|
||||
radarSweepMonitorCondition_.wait_for(lock, kRadarSweepMonitorTimeout_);
|
||||
if (status == std::cv_status::timeout)
|
||||
{
|
||||
logger_->debug("Radar sweep monitor timed out");
|
||||
}
|
||||
radarSweepMonitorActive_ = false;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
#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/settings/palette_settings.hpp>
|
||||
#include <scwx/qt/types/layer_types.hpp>
|
||||
#include <scwx/qt/util/color.hpp>
|
||||
#include <scwx/util/logger.hpp>
|
||||
#include <scwx/util/threads.hpp>
|
||||
|
|
@ -22,7 +23,8 @@ namespace map
|
|||
static const std::string logPrefix_ = "scwx::qt::map::alert_layer";
|
||||
static const auto logger_ = scwx::util::Logger::Create(logPrefix_);
|
||||
|
||||
static void AddAlertLayer(std::shared_ptr<QMapLibreGL::Map> map,
|
||||
static std::vector<std::string>
|
||||
AddAlertLayer(std::shared_ptr<QMapLibreGL::Map> map,
|
||||
awips::Phenomenon phenomenon,
|
||||
bool alertActive,
|
||||
const QString& beforeLayer);
|
||||
|
|
@ -132,56 +134,36 @@ public:
|
|||
};
|
||||
|
||||
AlertLayer::AlertLayer(std::shared_ptr<MapContext> context) :
|
||||
DrawLayer(context), p(std::make_unique<AlertLayerImpl>(context))
|
||||
p(std::make_unique<AlertLayerImpl>(context))
|
||||
{
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
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()");
|
||||
std::vector<std::string> layers {};
|
||||
|
||||
auto map = p->context_->map().lock();
|
||||
if (map == nullptr)
|
||||
{
|
||||
return;
|
||||
return layers;
|
||||
}
|
||||
|
||||
const QString beforeLayer {QString::fromStdString(before)};
|
||||
|
||||
// Add/update GeoJSON sources and create layers
|
||||
for (auto& phenomenon : kAlertPhenomena_)
|
||||
{
|
||||
for (bool alertActive : {false, true})
|
||||
{
|
||||
p->UpdateSource(phenomenon, alertActive);
|
||||
AddAlertLayer(map, phenomenon, alertActive, beforeLayer);
|
||||
}
|
||||
auto newLayers = AddAlertLayer(map, phenomenon, alertActive, beforeLayer);
|
||||
layers.insert(layers.end(), newLayers.cbegin(), newLayers.cend());
|
||||
}
|
||||
|
||||
return layers;
|
||||
}
|
||||
|
||||
std::list<QMapLibreGL::Feature>*
|
||||
|
|
@ -388,21 +370,25 @@ std::shared_ptr<AlertLayerHandler> AlertLayerHandler::Instance()
|
|||
return alertLayerHandler;
|
||||
}
|
||||
|
||||
static void AddAlertLayer(std::shared_ptr<QMapLibreGL::Map> map,
|
||||
static std::vector<std::string>
|
||||
AddAlertLayer(std::shared_ptr<QMapLibreGL::Map> map,
|
||||
awips::Phenomenon phenomenon,
|
||||
bool alertActive,
|
||||
const QString& beforeLayer)
|
||||
{
|
||||
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 idSuffix = GetSuffix(phenomenon, alertActive);
|
||||
auto outlineColor = util::color::ToRgba8PixelT(
|
||||
paletteSettings.alert_color(phenomenon, alertActive).GetValue());
|
||||
|
||||
QString bgLayerId = QString("alertPolygonLayerBg-%1").arg(idSuffix);
|
||||
QString fgLayerId = QString("alertPolygonLayerFg-%1").arg(idSuffix);
|
||||
QString bgLayerId = QString("%1::bg-%2").arg(layerPrefix).arg(idSuffix);
|
||||
QString fgLayerId = QString("%1::fg-%2").arg(layerPrefix).arg(idSuffix);
|
||||
|
||||
if (map->layerExists(bgLayerId))
|
||||
{
|
||||
|
|
@ -436,6 +422,8 @@ static void AddAlertLayer(std::shared_ptr<QMapLibreGL::Map> map,
|
|||
.arg(outlineColor[3]));
|
||||
map->setPaintProperty(fgLayerId, "line-opacity", QString("%1").arg(opacity));
|
||||
map->setPaintProperty(fgLayerId, "line-width", "3");
|
||||
|
||||
return {bgLayerId.toStdString(), fgLayerId.toStdString()};
|
||||
}
|
||||
|
||||
static QMapLibreGL::Feature
|
||||
|
|
|
|||
|
|
@ -1,6 +1,11 @@
|
|||
#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
|
||||
{
|
||||
|
|
@ -11,17 +16,14 @@ namespace map
|
|||
|
||||
class AlertLayerImpl;
|
||||
|
||||
class AlertLayer : public DrawLayer
|
||||
class AlertLayer
|
||||
{
|
||||
public:
|
||||
explicit AlertLayer(std::shared_ptr<MapContext> context);
|
||||
~AlertLayer();
|
||||
|
||||
void Initialize() override final;
|
||||
void Render(const QMapLibreGL::CustomLayerRenderParameters&) override final;
|
||||
void Deinitialize() override final;
|
||||
|
||||
void AddLayers(const std::string& before = {});
|
||||
std::vector<std::string> AddLayers(awips::Phenomenon phenomenon,
|
||||
const std::string& before = {});
|
||||
|
||||
private:
|
||||
std::unique_ptr<AlertLayerImpl> p;
|
||||
|
|
|
|||
|
|
@ -24,9 +24,11 @@ public:
|
|||
std::shared_ptr<MapContext> context_;
|
||||
std::vector<std::shared_ptr<gl::draw::DrawItem>> drawList_;
|
||||
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))
|
||||
{
|
||||
}
|
||||
|
|
@ -45,14 +47,23 @@ void DrawLayer::Initialize()
|
|||
void DrawLayer::Render(const QMapLibreGL::CustomLayerRenderParameters& params)
|
||||
{
|
||||
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.glBindTexture(GL_TEXTURE_2D, p->textureAtlas_);
|
||||
gl.glBindTexture(GL_TEXTURE_2D_ARRAY, p->textureAtlas_);
|
||||
|
||||
for (auto& item : p->drawList_)
|
||||
{
|
||||
item->Render(params);
|
||||
item->Render(params, textureAtlasChanged);
|
||||
}
|
||||
|
||||
p->textureAtlasBuildCount_ = newTextureAtlasBuildCount;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,15 +15,22 @@ class DrawLayerImpl;
|
|||
class DrawLayer : public GenericLayer
|
||||
{
|
||||
public:
|
||||
explicit DrawLayer(std::shared_ptr<MapContext> context);
|
||||
explicit DrawLayer(const std::shared_ptr<MapContext>& context);
|
||||
virtual ~DrawLayer();
|
||||
|
||||
virtual void Initialize();
|
||||
virtual void Render(const QMapLibreGL::CustomLayerRenderParameters&);
|
||||
virtual void Deinitialize();
|
||||
virtual void Initialize() override;
|
||||
virtual void
|
||||
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:
|
||||
void AddDrawItem(std::shared_ptr<gl::draw::DrawItem> drawItem);
|
||||
void AddDrawItem(const std::shared_ptr<gl::draw::DrawItem>& drawItem);
|
||||
|
||||
private:
|
||||
std::unique_ptr<DrawLayerImpl> p;
|
||||
|
|
|
|||
|
|
@ -26,6 +26,16 @@ GenericLayer::GenericLayer(std::shared_ptr<MapContext> context) :
|
|||
}
|
||||
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
|
||||
{
|
||||
return p->context_;
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
#include <QObject>
|
||||
#include <QMapLibreGL/QMapLibreGL>
|
||||
#include <glm/gtc/type_ptr.hpp>
|
||||
|
||||
namespace scwx
|
||||
{
|
||||
|
|
@ -28,6 +29,22 @@ public:
|
|||
virtual void Render(const QMapLibreGL::CustomLayerRenderParameters&) = 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:
|
||||
std::shared_ptr<MapContext> context() const;
|
||||
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ public:
|
|||
common::RadarProductGroup radarProductGroup_;
|
||||
std::string radarProduct_;
|
||||
int16_t radarProductCode_;
|
||||
QMapLibreGL::CustomLayerRenderParameters renderParameters_ {};
|
||||
};
|
||||
|
||||
MapContext::MapContext(
|
||||
|
|
@ -77,6 +78,11 @@ int16_t MapContext::radar_product_code() const
|
|||
return p->radarProductCode_;
|
||||
}
|
||||
|
||||
QMapLibreGL::CustomLayerRenderParameters MapContext::render_parameters() const
|
||||
{
|
||||
return p->renderParameters_;
|
||||
}
|
||||
|
||||
void MapContext::set_map(std::shared_ptr<QMapLibreGL::Map> map)
|
||||
{
|
||||
p->map_ = map;
|
||||
|
|
@ -109,6 +115,12 @@ void MapContext::set_radar_product_code(int16_t radarProductCode)
|
|||
p->radarProductCode_ = radarProductCode;
|
||||
}
|
||||
|
||||
void MapContext::set_render_parameters(
|
||||
const QMapLibreGL::CustomLayerRenderParameters& params)
|
||||
{
|
||||
p->renderParameters_ = params;
|
||||
}
|
||||
|
||||
} // namespace map
|
||||
} // namespace qt
|
||||
} // namespace scwx
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ public:
|
|||
common::RadarProductGroup radar_product_group() const;
|
||||
std::string radar_product() const;
|
||||
int16_t radar_product_code() const;
|
||||
QMapLibreGL::CustomLayerRenderParameters render_parameters() const;
|
||||
|
||||
void set_map(std::shared_ptr<QMapLibreGL::Map> map);
|
||||
void set_pixel_ratio(float pixelRatio);
|
||||
|
|
@ -41,6 +42,8 @@ public:
|
|||
void set_radar_product_group(common::RadarProductGroup radarProductGroup);
|
||||
void set_radar_product(const std::string& radarProduct);
|
||||
void set_radar_product_code(int16_t radarProductCode);
|
||||
void set_render_parameters(
|
||||
const QMapLibreGL::CustomLayerRenderParameters& params);
|
||||
|
||||
private:
|
||||
class Impl;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
#include <scwx/qt/map/map_provider.hpp>
|
||||
#include <scwx/qt/manager/settings_manager.hpp>
|
||||
#include <scwx/qt/settings/general_settings.hpp>
|
||||
|
||||
#include <unordered_map>
|
||||
|
||||
|
|
@ -128,12 +128,10 @@ std::string GetMapProviderApiKey(MapProvider mapProvider)
|
|||
switch (mapProvider)
|
||||
{
|
||||
case MapProvider::Mapbox:
|
||||
return manager::SettingsManager::general_settings()
|
||||
.mapbox_api_key()
|
||||
.GetValue();
|
||||
return settings::GeneralSettings::Instance().mapbox_api_key().GetValue();
|
||||
|
||||
case MapProvider::MapTiler:
|
||||
return manager::SettingsManager::general_settings()
|
||||
return settings::GeneralSettings::Instance()
|
||||
.maptiler_api_key()
|
||||
.GetValue();
|
||||
|
||||
|
|
|
|||
|
|
@ -1,21 +1,29 @@
|
|||
#include <scwx/qt/map/map_widget.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/settings_manager.hpp>
|
||||
#include <scwx/qt/map/alert_layer.hpp>
|
||||
#include <scwx/qt/map/color_table_layer.hpp>
|
||||
#include <scwx/qt/map/layer_wrapper.hpp>
|
||||
#include <scwx/qt/map/map_provider.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_range_layer.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/maplibre.hpp>
|
||||
#include <scwx/qt/util/tooltip.hpp>
|
||||
#include <scwx/qt/view/radar_product_view_factory.hpp>
|
||||
#include <scwx/util/logger.hpp>
|
||||
#include <scwx/util/time.hpp>
|
||||
|
||||
#include <regex>
|
||||
#include <set>
|
||||
|
||||
#include <backends/imgui_impl_opengl3.h>
|
||||
#include <backends/imgui_impl_qt.hpp>
|
||||
|
|
@ -49,7 +57,9 @@ class MapWidgetImpl : public QObject
|
|||
|
||||
public:
|
||||
explicit MapWidgetImpl(MapWidget* widget,
|
||||
std::size_t id,
|
||||
const QMapLibreGL::Settings& settings) :
|
||||
id_ {id},
|
||||
uuid_ {boost::uuids::random_generator()()},
|
||||
context_ {std::make_shared<MapContext>()},
|
||||
widget_ {widget},
|
||||
|
|
@ -61,11 +71,11 @@ public:
|
|||
radarProductLayer_ {nullptr},
|
||||
alertLayer_ {std::make_shared<AlertLayer>(context_)},
|
||||
overlayLayer_ {nullptr},
|
||||
placefileLayer_ {nullptr},
|
||||
colorTableLayer_ {nullptr},
|
||||
autoRefreshEnabled_ {true},
|
||||
autoUpdateEnabled_ {true},
|
||||
selectedLevel2Product_ {common::Level2Product::Unknown},
|
||||
lastPos_(),
|
||||
currentStyleIndex_ {0},
|
||||
currentStyle_ {nullptr},
|
||||
frameDraws_(0),
|
||||
|
|
@ -75,8 +85,7 @@ public:
|
|||
prevBearing_ {0.0},
|
||||
prevPitch_ {0.0}
|
||||
{
|
||||
auto& generalSettings =
|
||||
scwx::qt::manager::SettingsManager::general_settings();
|
||||
auto& generalSettings = settings::GeneralSettings::Instance();
|
||||
|
||||
SetRadarSite(generalSettings.default_radar_site().GetValue());
|
||||
|
||||
|
|
@ -92,6 +101,8 @@ public:
|
|||
|
||||
// Set Map Provider Details
|
||||
mapProvider_ = GetMapProvider(generalSettings.map_provider().GetValue());
|
||||
|
||||
ConnectSignals();
|
||||
}
|
||||
|
||||
~MapWidgetImpl()
|
||||
|
|
@ -112,22 +123,37 @@ public:
|
|||
threadPool_.join();
|
||||
}
|
||||
|
||||
void AddLayer(types::LayerType type,
|
||||
types::LayerDescription description,
|
||||
const std::string& before = {});
|
||||
void AddLayer(const std::string& id,
|
||||
std::shared_ptr<GenericLayer> layer,
|
||||
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 RadarProductManagerConnect();
|
||||
void RadarProductManagerDisconnect();
|
||||
void RadarProductViewConnect();
|
||||
void RadarProductViewDisconnect();
|
||||
void RunMousePicking();
|
||||
void SetRadarSite(const std::string& radarSite);
|
||||
void UpdateLoadedStyle();
|
||||
bool UpdateStoredMapParameters();
|
||||
|
||||
std::string FindMapSymbologyLayer();
|
||||
|
||||
common::Level2Product
|
||||
GetLevel2ProductOrDefault(const std::string& productName) const;
|
||||
|
||||
static std::string GetPlacefileLayerName(const std::string& placefileName);
|
||||
|
||||
boost::asio::thread_pool threadPool_ {1u};
|
||||
|
||||
std::size_t id_;
|
||||
boost::uuids::uuid uuid_;
|
||||
|
||||
std::shared_ptr<MapContext> context_;
|
||||
|
|
@ -138,10 +164,19 @@ public:
|
|||
std::shared_ptr<QMapLibreGL::Map> map_;
|
||||
std::list<std::string> layerList_;
|
||||
|
||||
QStringList styleLayers_;
|
||||
types::LayerVector customLayers_;
|
||||
|
||||
ImGuiContext* imGuiContext_;
|
||||
std::string imGuiContextName_;
|
||||
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<common::ColorTable> colorTable_;
|
||||
|
|
@ -149,14 +184,20 @@ public:
|
|||
std::shared_ptr<RadarProductLayer> radarProductLayer_;
|
||||
std::shared_ptr<AlertLayer> alertLayer_;
|
||||
std::shared_ptr<OverlayLayer> overlayLayer_;
|
||||
std::shared_ptr<PlacefileLayer> placefileLayer_;
|
||||
std::shared_ptr<ColorTableLayer> colorTableLayer_;
|
||||
|
||||
std::list<std::shared_ptr<PlacefileLayer>> placefileLayers_ {};
|
||||
|
||||
bool autoRefreshEnabled_;
|
||||
bool autoUpdateEnabled_;
|
||||
|
||||
common::Level2Product selectedLevel2Product_;
|
||||
|
||||
QPointF lastPos_;
|
||||
bool hasMouse_ {false};
|
||||
bool lastItemPicked_ {false};
|
||||
QPointF lastPos_ {};
|
||||
QPointF lastGlobalPos_ {};
|
||||
std::size_t currentStyleIndex_;
|
||||
const MapStyle* currentStyle_;
|
||||
std::string initialStyleName_ {};
|
||||
|
|
@ -173,9 +214,16 @@ public slots:
|
|||
void Update();
|
||||
};
|
||||
|
||||
MapWidget::MapWidget(const QMapLibreGL::Settings& settings) :
|
||||
p(std::make_unique<MapWidgetImpl>(this, settings))
|
||||
MapWidget::MapWidget(std::size_t id, const QMapLibreGL::Settings& 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);
|
||||
|
||||
ImGui_ImplQt_RegisterWidget(this);
|
||||
|
|
@ -187,6 +235,63 @@ MapWidget::~MapWidget()
|
|||
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()
|
||||
{
|
||||
if (p->radarProductManager_ != nullptr)
|
||||
|
|
@ -498,7 +603,7 @@ void MapWidget::SelectRadarSite(std::shared_ptr<config::RadarSite> radarSite,
|
|||
false);
|
||||
}
|
||||
|
||||
AddLayers();
|
||||
p->AddLayers();
|
||||
|
||||
// TODO: Disable refresh from old site
|
||||
|
||||
|
|
@ -646,62 +751,193 @@ void MapWidget::changeStyle()
|
|||
Q_EMIT MapStyleChanged(p->currentStyle_->name_);
|
||||
}
|
||||
|
||||
void MapWidget::AddLayers()
|
||||
void MapWidget::DumpLayerList() const
|
||||
{
|
||||
logger_->debug("AddLayers()");
|
||||
|
||||
// Clear custom layers
|
||||
for (const std::string& id : p->layerList_)
|
||||
{
|
||||
p->map_->removeLayer(id.c_str());
|
||||
}
|
||||
p->layerList_.clear();
|
||||
|
||||
auto radarProductView = p->context_->radar_product_view();
|
||||
|
||||
if (radarProductView != nullptr)
|
||||
{
|
||||
p->radarProductLayer_ = std::make_shared<RadarProductLayer>(p->context_);
|
||||
p->colorTableLayer_ = std::make_shared<ColorTableLayer>(p->context_);
|
||||
|
||||
std::shared_ptr<config::RadarSite> radarSite =
|
||||
p->radarProductManager_->radar_site();
|
||||
|
||||
const auto& mapStyle = *p->currentStyle_;
|
||||
logger_->info("Layers: {}", p->map_->layerIds().join(", ").toStdString());
|
||||
}
|
||||
|
||||
std::string MapWidgetImpl::FindMapSymbologyLayer()
|
||||
{
|
||||
std::string before = "ferry";
|
||||
|
||||
for (const QString& qlayer : p->map_->layerIds())
|
||||
for (const QString& qlayer : styleLayers_)
|
||||
{
|
||||
const std::string layer = qlayer.toStdString();
|
||||
|
||||
// Draw below layers defined in map style
|
||||
auto it = std::find_if(
|
||||
mapStyle.drawBelow_.cbegin(),
|
||||
mapStyle.drawBelow_.cend(),
|
||||
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 != mapStyle.drawBelow_.cend())
|
||||
if (it != currentStyle_->drawBelow_.cend())
|
||||
{
|
||||
before = layer;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
p->AddLayer("radar", p->radarProductLayer_, before);
|
||||
RadarRangeLayer::Add(p->map_,
|
||||
radarProductView->range(),
|
||||
{radarSite->latitude(), radarSite->longitude()});
|
||||
p->AddLayer("colorTable", p->colorTableLayer_);
|
||||
return before;
|
||||
}
|
||||
|
||||
void MapWidgetImpl::AddLayers()
|
||||
{
|
||||
if (styleLayers_.isEmpty())
|
||||
{
|
||||
// Skip if the map has not yet been initialized
|
||||
return;
|
||||
}
|
||||
|
||||
p->alertLayer_->AddLayers("colorTable");
|
||||
p->overlayLayer_ = std::make_shared<OverlayLayer>(p->context_);
|
||||
p->AddLayer("overlay", p->overlayLayer_);
|
||||
logger_->debug("Add Layers");
|
||||
|
||||
// Clear custom layers
|
||||
for (const std::string& id : layerList_)
|
||||
{
|
||||
map_->removeLayer(id.c_str());
|
||||
}
|
||||
layerList_.clear();
|
||||
placefileLayers_.clear();
|
||||
|
||||
// Update custom layer list from model
|
||||
customLayers_ = model::LayerModel::Instance()->GetLayers();
|
||||
|
||||
// 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)
|
||||
{
|
||||
if (it->type_ == types::LayerType::Map)
|
||||
{
|
||||
// Style-defined map layers
|
||||
switch (std::get<types::MapLayer>(it->description_))
|
||||
{
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
else if (it->displayed_[id_])
|
||||
{
|
||||
// If the layer is displayed for the current map, add it
|
||||
AddLayer(it->type_, it->description_, before);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MapWidgetImpl::AddLayer(types::LayerType type,
|
||||
types::LayerDescription description,
|
||||
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,
|
||||
|
|
@ -712,9 +948,26 @@ void MapWidgetImpl::AddLayer(const std::string& id,
|
|||
std::unique_ptr<QMapLibreGL::CustomLayerHostInterface> pHost =
|
||||
std::make_unique<LayerWrapper>(layer);
|
||||
|
||||
try
|
||||
{
|
||||
map_->addCustomLayer(id.c_str(), std::move(pHost), before.c_str());
|
||||
|
||||
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)
|
||||
|
|
@ -742,6 +995,7 @@ void MapWidget::keyPressEvent(QKeyEvent* ev)
|
|||
void MapWidget::mousePressEvent(QMouseEvent* ev)
|
||||
{
|
||||
p->lastPos_ = ev->position();
|
||||
p->lastGlobalPos_ = ev->globalPosition();
|
||||
|
||||
if (ev->type() == QEvent::MouseButtonPress)
|
||||
{
|
||||
|
|
@ -788,6 +1042,7 @@ void MapWidget::mouseMoveEvent(QMouseEvent* ev)
|
|||
}
|
||||
|
||||
p->lastPos_ = ev->position();
|
||||
p->lastGlobalPos_ = ev->globalPosition();
|
||||
ev->accept();
|
||||
}
|
||||
|
||||
|
|
@ -816,9 +1071,15 @@ void MapWidget::initializeGL()
|
|||
makeCurrent();
|
||||
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
|
||||
ImGui::SetCurrentContext(p->imGuiContext_);
|
||||
ImGui_ImplOpenGL3_Init();
|
||||
p->imGuiFontsBuildCount_ =
|
||||
manager::FontManager::Instance().imgui_fonts_build_count();
|
||||
p->imGuiRendererInitialized_ = true;
|
||||
|
||||
p->map_.reset(
|
||||
|
|
@ -859,16 +1120,27 @@ void MapWidget::initializeGL()
|
|||
|
||||
void MapWidget::paintGL()
|
||||
{
|
||||
auto defaultFont = manager::FontManager::Instance().GetImGuiFont(
|
||||
types::FontCategory::Default);
|
||||
|
||||
p->frameDraws_++;
|
||||
|
||||
// Setup ImGui Frame
|
||||
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
|
||||
ImGui_ImplQt_NewFrame(this);
|
||||
ImGui_ImplOpenGL3_NewFrame();
|
||||
p->ImGuiCheckFonts();
|
||||
ImGui::NewFrame();
|
||||
|
||||
// Set default font
|
||||
ImGui::PushFont(defaultFont->font());
|
||||
|
||||
// Update pixel ratio
|
||||
p->context_->set_pixel_ratio(pixelRatio());
|
||||
|
||||
|
|
@ -878,20 +1150,90 @@ void MapWidget::paintGL()
|
|||
size() * pixelRatio());
|
||||
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
|
||||
ImGui::Render();
|
||||
ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
|
||||
|
||||
// Unlock ImGui font atlas after rendering
|
||||
imguiFontAtlasLock.unlock();
|
||||
|
||||
// Paint complete
|
||||
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)
|
||||
{
|
||||
switch (mapChange)
|
||||
{
|
||||
case QMapLibreGL::Map::MapChangeDidFinishLoadingStyle:
|
||||
AddLayers();
|
||||
p->UpdateLoadedStyle();
|
||||
p->AddLayers();
|
||||
break;
|
||||
|
||||
default:
|
||||
|
|
@ -899,6 +1241,11 @@ void MapWidget::mapChanged(QMapLibreGL::Map::MapChange mapChange)
|
|||
}
|
||||
}
|
||||
|
||||
void MapWidgetImpl::UpdateLoadedStyle()
|
||||
{
|
||||
styleLayers_ = map_->layerIds();
|
||||
}
|
||||
|
||||
void MapWidgetImpl::RadarProductManagerConnect()
|
||||
{
|
||||
if (radarProductManager_ != nullptr)
|
||||
|
|
@ -992,7 +1339,7 @@ void MapWidgetImpl::InitializeNewRadarProductView(
|
|||
auto radarProductView = context_->radar_product_view();
|
||||
|
||||
std::string colorTableFile =
|
||||
manager::SettingsManager::palette_settings()
|
||||
settings::PaletteSettings::Instance()
|
||||
.palette(colorPalette)
|
||||
.GetValue();
|
||||
if (!colorTableFile.empty())
|
||||
|
|
@ -1009,7 +1356,7 @@ void MapWidgetImpl::InitializeNewRadarProductView(
|
|||
|
||||
if (map_ != nullptr)
|
||||
{
|
||||
widget_->AddLayers();
|
||||
AddLayers();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -32,9 +32,11 @@ class MapWidget : public QOpenGLWidget
|
|||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit MapWidget(const QMapLibreGL::Settings&);
|
||||
explicit MapWidget(std::size_t id, const QMapLibreGL::Settings&);
|
||||
~MapWidget();
|
||||
|
||||
void DumpLayerList() const;
|
||||
|
||||
common::Level3ProductCategoryMap GetAvailableLevel3Categories();
|
||||
float GetElevation() const;
|
||||
std::vector<float> GetElevationCuts() const;
|
||||
|
|
@ -119,7 +121,9 @@ private:
|
|||
qreal pixelRatio();
|
||||
|
||||
// QWidget implementation.
|
||||
void enterEvent(QEnterEvent* ev) override final;
|
||||
void keyPressEvent(QKeyEvent* ev) override final;
|
||||
void leaveEvent(QEvent* ev) override final;
|
||||
void mousePressEvent(QMouseEvent* ev) override final;
|
||||
void mouseMoveEvent(QMouseEvent* ev) override final;
|
||||
void wheelEvent(QWheelEvent* ev) override final;
|
||||
|
|
@ -128,8 +132,6 @@ private:
|
|||
void initializeGL() override final;
|
||||
void paintGL() override final;
|
||||
|
||||
void AddLayers();
|
||||
|
||||
std::unique_ptr<MapWidgetImpl> p;
|
||||
|
||||
friend class MapWidgetImpl;
|
||||
|
|
|
|||
|
|
@ -95,6 +95,8 @@ void OverlayLayer::Render(
|
|||
auto& settings = context()->settings();
|
||||
const float pixelRatio = context()->pixel_ratio();
|
||||
|
||||
context()->set_render_parameters(params);
|
||||
|
||||
if (p->sweepTimeNeedsUpdate_ && radarProductView != nullptr)
|
||||
{
|
||||
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/gl/shader_program.hpp>
|
||||
#include <scwx/qt/util/maplibre.hpp>
|
||||
#include <scwx/util/logger.hpp>
|
||||
|
||||
#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 auto logger_ = scwx::util::Logger::Create(logPrefix_);
|
||||
|
||||
static glm::vec2
|
||||
LatLongToScreenCoordinate(const QMapLibreGL::Coordinate& coordinate);
|
||||
|
||||
class RadarProductLayerImpl
|
||||
{
|
||||
public:
|
||||
|
|
@ -290,7 +288,7 @@ void RadarProductLayer::Render(
|
|||
|
||||
gl.glUniform2fv(p->uMapScreenCoordLocation_,
|
||||
1,
|
||||
glm::value_ptr(LatLongToScreenCoordinate(
|
||||
glm::value_ptr(util::maplibre::LatLongToScreenCoordinate(
|
||||
{params.latitude, params.longitude})));
|
||||
|
||||
gl.glUniformMatrix4fv(
|
||||
|
|
@ -358,22 +356,6 @@ void RadarProductLayer::UpdateColorTable()
|
|||
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 qt
|
||||
} // namespace scwx
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
#include <scwx/qt/map/radar_range_layer.hpp>
|
||||
#include <scwx/qt/types/layer_types.hpp>
|
||||
#include <scwx/qt/util/geographic_lib.hpp>
|
||||
#include <scwx/util/logger.hpp>
|
||||
|
||||
|
|
@ -22,11 +23,14 @@ void RadarRangeLayer::Add(std::shared_ptr<QMapLibreGL::Map> map,
|
|||
QMapLibreGL::Coordinate center,
|
||||
const QString& before)
|
||||
{
|
||||
static const QString layerId = QString::fromStdString(types::GetLayerName(
|
||||
types::LayerType::Data, types::DataLayer::RadarRange));
|
||||
|
||||
logger_->debug("Add()");
|
||||
|
||||
if (map->layerExists("rangeCircleLayer"))
|
||||
if (map->layerExists(layerId))
|
||||
{
|
||||
map->removeLayer("rangeCircleLayer");
|
||||
map->removeLayer(layerId);
|
||||
}
|
||||
if (map->sourceExists("rangeCircleSource"))
|
||||
{
|
||||
|
|
@ -39,12 +43,10 @@ void RadarRangeLayer::Add(std::shared_ptr<QMapLibreGL::Map> map,
|
|||
map->addSource(
|
||||
"rangeCircleSource",
|
||||
{{"type", "geojson"}, {"data", QVariant::fromValue(*rangeCircle)}});
|
||||
map->addLayer({{"id", "rangeCircleLayer"},
|
||||
{"type", "line"},
|
||||
{"source", "rangeCircleSource"}},
|
||||
map->addLayer(
|
||||
{{"id", layerId}, {"type", "line"}, {"source", "rangeCircleSource"}},
|
||||
before);
|
||||
map->setPaintProperty(
|
||||
"rangeCircleLayer", "line-color", "rgba(128, 128, 128, 128)");
|
||||
map->setPaintProperty(layerId, "line-color", "rgba(128, 128, 128, 128)");
|
||||
}
|
||||
|
||||
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/config/county_database.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";
|
||||
|
||||
class GeneralSettingsImpl
|
||||
class GeneralSettings::Impl
|
||||
{
|
||||
public:
|
||||
explicit GeneralSettingsImpl()
|
||||
explicit Impl()
|
||||
{
|
||||
std::string defaultDefaultAlertActionValue =
|
||||
types::GetAlertActionName(types::AlertAction::Go);
|
||||
|
|
@ -29,6 +29,7 @@ public:
|
|||
boost::to_lower(defaultDefaultAlertActionValue);
|
||||
boost::to_lower(defaultMapProviderValue);
|
||||
|
||||
antiAliasingEnabled_.SetDefault(true);
|
||||
debugEnabled_.SetDefault(false);
|
||||
defaultAlertAction_.SetDefault(defaultDefaultAlertActionValue);
|
||||
defaultRadarSite_.SetDefault("KLSX");
|
||||
|
|
@ -102,8 +103,9 @@ public:
|
|||
{ return !value.empty(); });
|
||||
}
|
||||
|
||||
~GeneralSettingsImpl() {}
|
||||
~Impl() {}
|
||||
|
||||
SettingsVariable<bool> antiAliasingEnabled_ {"anti_aliasing_enabled"};
|
||||
SettingsVariable<bool> debugEnabled_ {"debug_enabled"};
|
||||
SettingsVariable<std::string> defaultAlertAction_ {"default_alert_action"};
|
||||
SettingsVariable<std::string> defaultRadarSite_ {"default_radar_site"};
|
||||
|
|
@ -120,9 +122,10 @@ public:
|
|||
};
|
||||
|
||||
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->defaultRadarSite_,
|
||||
&p->fontSizes_,
|
||||
|
|
@ -143,6 +146,11 @@ GeneralSettings::GeneralSettings(GeneralSettings&&) noexcept = default;
|
|||
GeneralSettings&
|
||||
GeneralSettings::operator=(GeneralSettings&&) noexcept = default;
|
||||
|
||||
SettingsVariable<bool>& GeneralSettings::anti_aliasing_enabled() const
|
||||
{
|
||||
return p->antiAliasingEnabled_;
|
||||
}
|
||||
|
||||
SettingsVariable<bool>& GeneralSettings::debug_enabled() const
|
||||
{
|
||||
return p->debugEnabled_;
|
||||
|
|
@ -221,9 +229,16 @@ bool GeneralSettings::Shutdown()
|
|||
return dataChanged;
|
||||
}
|
||||
|
||||
GeneralSettings& GeneralSettings::Instance()
|
||||
{
|
||||
static GeneralSettings generalSettings_;
|
||||
return generalSettings_;
|
||||
}
|
||||
|
||||
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->defaultRadarSite_ == rhs.p->defaultRadarSite_ &&
|
||||
lhs.p->fontSizes_ == rhs.p->fontSizes_ &&
|
||||
|
|
|
|||
|
|
@ -13,8 +13,6 @@ namespace qt
|
|||
namespace settings
|
||||
{
|
||||
|
||||
class GeneralSettingsImpl;
|
||||
|
||||
class GeneralSettings : public SettingsCategory
|
||||
{
|
||||
public:
|
||||
|
|
@ -27,6 +25,7 @@ public:
|
|||
GeneralSettings(GeneralSettings&&) noexcept;
|
||||
GeneralSettings& operator=(GeneralSettings&&) noexcept;
|
||||
|
||||
SettingsVariable<bool>& anti_aliasing_enabled() const;
|
||||
SettingsVariable<bool>& debug_enabled() const;
|
||||
SettingsVariable<std::string>& default_alert_action() const;
|
||||
SettingsVariable<std::string>& default_radar_site() const;
|
||||
|
|
@ -41,13 +40,16 @@ public:
|
|||
SettingsVariable<std::string>& maptiler_api_key() const;
|
||||
SettingsVariable<bool>& update_notifications_enabled() const;
|
||||
|
||||
static GeneralSettings& Instance();
|
||||
|
||||
friend bool operator==(const GeneralSettings& lhs,
|
||||
const GeneralSettings& rhs);
|
||||
|
||||
bool Shutdown();
|
||||
|
||||
private:
|
||||
std::unique_ptr<GeneralSettingsImpl> p;
|
||||
class Impl;
|
||||
std::unique_ptr<Impl> p;
|
||||
};
|
||||
|
||||
} // namespace settings
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ static const std::string kDefaultRadarProductGroupString_ = "L3";
|
|||
static const std::array<std::string, kCount_> kDefaultRadarProduct_ {
|
||||
"N0B", "N0G", "N0C", "N0X"};
|
||||
|
||||
class MapSettingsImpl
|
||||
class MapSettings::Impl
|
||||
{
|
||||
public:
|
||||
struct MapData
|
||||
|
|
@ -47,7 +47,7 @@ public:
|
|||
SettingsVariable<std::string> radarProduct_ {kRadarProductName_};
|
||||
};
|
||||
|
||||
explicit MapSettingsImpl()
|
||||
explicit Impl()
|
||||
{
|
||||
for (std::size_t i = 0; i < kCount_; i++)
|
||||
{
|
||||
|
|
@ -101,7 +101,7 @@ public:
|
|||
}
|
||||
}
|
||||
|
||||
~MapSettingsImpl() {}
|
||||
~Impl() {}
|
||||
|
||||
void SetDefaults(std::size_t i)
|
||||
{
|
||||
|
|
@ -111,12 +111,30 @@ public:
|
|||
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::vector<SettingsVariableBase*> variables_ {};
|
||||
};
|
||||
|
||||
MapSettings::MapSettings() :
|
||||
SettingsCategory("maps"), p(std::make_unique<MapSettingsImpl>())
|
||||
SettingsCategory("maps"), p(std::make_unique<Impl>())
|
||||
{
|
||||
RegisterVariables(p->variables_);
|
||||
SetDefaults();
|
||||
|
|
@ -161,7 +179,7 @@ bool MapSettings::Shutdown()
|
|||
// Commit settings that are managed separate from the settings dialog
|
||||
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();
|
||||
}
|
||||
|
|
@ -184,7 +202,7 @@ bool MapSettings::ReadJson(const boost::json::object& json)
|
|||
if (i < mapArray.size() && mapArray.at(i).is_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
|
||||
validated &= mapRecordSettings.mapStyle_.ReadValue(mapRecord);
|
||||
|
|
@ -234,14 +252,10 @@ void MapSettings::WriteJson(boost::json::object& json) const
|
|||
json.insert_or_assign(name(), object);
|
||||
}
|
||||
|
||||
void tag_invoke(boost::json::value_from_tag,
|
||||
boost::json::value& jv,
|
||||
const MapSettingsImpl::MapData& data)
|
||||
MapSettings& MapSettings::Instance()
|
||||
{
|
||||
jv = {{kMapStyleName_, data.mapStyle_.GetValue()},
|
||||
{kRadarSiteName_, data.radarSite_.GetValue()},
|
||||
{kRadarProductGroupName_, data.radarProductGroup_.GetValue()},
|
||||
{kRadarProductName_, data.radarProduct_.GetValue()}};
|
||||
static MapSettings mapSettings_;
|
||||
return mapSettings_;
|
||||
}
|
||||
|
||||
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_);
|
||||
}
|
||||
|
||||
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 qt
|
||||
} // namespace scwx
|
||||
|
|
|
|||
|
|
@ -13,8 +13,6 @@ namespace qt
|
|||
namespace settings
|
||||
{
|
||||
|
||||
class MapSettingsImpl;
|
||||
|
||||
class MapSettings : public SettingsCategory
|
||||
{
|
||||
public:
|
||||
|
|
@ -52,10 +50,13 @@ public:
|
|||
*/
|
||||
void WriteJson(boost::json::object& json) const override;
|
||||
|
||||
static MapSettings& Instance();
|
||||
|
||||
friend bool operator==(const MapSettings& lhs, const MapSettings& rhs);
|
||||
|
||||
private:
|
||||
std::unique_ptr<MapSettingsImpl> p;
|
||||
class Impl;
|
||||
std::unique_ptr<Impl> p;
|
||||
};
|
||||
|
||||
} // namespace settings
|
||||
|
|
|
|||
|
|
@ -72,10 +72,10 @@ static const std::map<
|
|||
static const std::string kDefaultKey_ {"???"};
|
||||
static const awips::Phenomenon kDefaultPhenomenon_ {awips::Phenomenon::Marine};
|
||||
|
||||
class PaletteSettingsImpl
|
||||
class PaletteSettings::Impl
|
||||
{
|
||||
public:
|
||||
explicit PaletteSettingsImpl()
|
||||
explicit Impl()
|
||||
{
|
||||
for (const auto& name : kPaletteKeys_)
|
||||
{
|
||||
|
|
@ -120,7 +120,7 @@ public:
|
|||
}
|
||||
}
|
||||
|
||||
~PaletteSettingsImpl() {}
|
||||
~Impl() {}
|
||||
|
||||
static bool ValidateColor(const std::string& value);
|
||||
|
||||
|
|
@ -132,14 +132,14 @@ public:
|
|||
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}"};
|
||||
return std::regex_match(value, re);
|
||||
}
|
||||
|
||||
PaletteSettings::PaletteSettings() :
|
||||
SettingsCategory("palette"), p(std::make_unique<PaletteSettingsImpl>())
|
||||
SettingsCategory("palette"), p(std::make_unique<Impl>())
|
||||
{
|
||||
RegisterVariables(p->variables_);
|
||||
SetDefaults();
|
||||
|
|
@ -200,6 +200,12 @@ const std::vector<awips::Phenomenon>& PaletteSettings::alert_phenomena()
|
|||
return kAlertPhenomena_;
|
||||
}
|
||||
|
||||
PaletteSettings& PaletteSettings::Instance()
|
||||
{
|
||||
static PaletteSettings paletteSettings_;
|
||||
return paletteSettings_;
|
||||
}
|
||||
|
||||
bool operator==(const PaletteSettings& lhs, const PaletteSettings& rhs)
|
||||
{
|
||||
return lhs.p->palette_ == rhs.p->palette_;
|
||||
|
|
|
|||
|
|
@ -14,8 +14,6 @@ namespace qt
|
|||
namespace settings
|
||||
{
|
||||
|
||||
class PaletteSettingsImpl;
|
||||
|
||||
class PaletteSettings : public SettingsCategory
|
||||
{
|
||||
public:
|
||||
|
|
@ -34,11 +32,14 @@ public:
|
|||
|
||||
static const std::vector<awips::Phenomenon>& alert_phenomena();
|
||||
|
||||
static PaletteSettings& Instance();
|
||||
|
||||
friend bool operator==(const PaletteSettings& lhs,
|
||||
const PaletteSettings& rhs);
|
||||
|
||||
private:
|
||||
std::unique_ptr<PaletteSettingsImpl> p;
|
||||
class Impl;
|
||||
std::unique_ptr<Impl> p;
|
||||
};
|
||||
|
||||
} // namespace settings
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@
|
|||
#include <scwx/qt/util/json.hpp>
|
||||
#include <scwx/util/logger.hpp>
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace scwx
|
||||
{
|
||||
namespace qt
|
||||
|
|
@ -21,6 +23,8 @@ public:
|
|||
|
||||
const std::string name_;
|
||||
|
||||
std::vector<std::pair<std::string, std::vector<SettingsCategory*>>>
|
||||
subcategoryArrays_;
|
||||
std::vector<SettingsVariableBase*> variables_;
|
||||
};
|
||||
|
||||
|
|
@ -41,6 +45,16 @@ std::string SettingsCategory::name() const
|
|||
|
||||
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_)
|
||||
{
|
||||
variable->SetValueToDefault();
|
||||
|
|
@ -57,6 +71,47 @@ bool SettingsCategory::ReadJson(const boost::json::object& json)
|
|||
{
|
||||
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_)
|
||||
{
|
||||
validated &= variable->ReadValue(object);
|
||||
|
|
@ -66,7 +121,7 @@ bool SettingsCategory::ReadJson(const boost::json::object& json)
|
|||
{
|
||||
if (value == nullptr)
|
||||
{
|
||||
logger_->warn("Key {} is not present, resetting to defaults",
|
||||
logger_->debug("Key {} is not present, resetting to defaults",
|
||||
p->name_);
|
||||
}
|
||||
else if (!value->is_object())
|
||||
|
|
@ -86,6 +141,20 @@ void SettingsCategory::WriteJson(boost::json::object& json) const
|
|||
{
|
||||
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_)
|
||||
{
|
||||
variable->WriteValue(object);
|
||||
|
|
@ -94,6 +163,18 @@ void SettingsCategory::WriteJson(boost::json::object& json) const
|
|||
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(
|
||||
std::initializer_list<SettingsVariableBase*> variables)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -50,7 +50,8 @@ public:
|
|||
*/
|
||||
virtual void WriteJson(boost::json::object& json) const;
|
||||
|
||||
protected:
|
||||
void RegisterSubcategoryArray(const std::string& name,
|
||||
std::vector<SettingsCategory>& subcategories);
|
||||
void
|
||||
RegisterVariables(std::initializer_list<SettingsVariableBase*> variables);
|
||||
void RegisterVariables(std::vector<SettingsVariableBase*> variables);
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@
|
|||
#include <QCheckBox>
|
||||
#include <QComboBox>
|
||||
#include <QCoreApplication>
|
||||
#include <QLabel>
|
||||
#include <QLineEdit>
|
||||
#include <QSpinBox>
|
||||
#include <QWidget>
|
||||
|
|
@ -26,16 +27,21 @@ template<class T>
|
|||
class SettingsInterface<T>::Impl
|
||||
{
|
||||
public:
|
||||
explicit Impl()
|
||||
explicit Impl(SettingsInterface* self) : self_ {self}
|
||||
{
|
||||
context_->moveToThread(QCoreApplication::instance()->thread());
|
||||
}
|
||||
|
||||
~Impl() {}
|
||||
|
||||
template<class U>
|
||||
void SetWidgetText(U* widget, const T& currentValue);
|
||||
|
||||
void UpdateEditWidget();
|
||||
void UpdateResetButton();
|
||||
|
||||
SettingsInterface<T>* self_;
|
||||
|
||||
SettingsVariable<T>* variable_ {nullptr};
|
||||
bool stagedValid_ {true};
|
||||
|
||||
|
|
@ -49,17 +55,27 @@ public:
|
|||
|
||||
template<class T>
|
||||
SettingsInterface<T>::SettingsInterface() :
|
||||
SettingsInterfaceBase(), p(std::make_unique<Impl>())
|
||||
SettingsInterfaceBase(), p(std::make_unique<Impl>(this))
|
||||
{
|
||||
}
|
||||
template<class T>
|
||||
SettingsInterface<T>::~SettingsInterface() = default;
|
||||
|
||||
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>
|
||||
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>
|
||||
void SettingsInterface<T>::SetSettingsVariable(SettingsVariable<T>& variable)
|
||||
|
|
@ -73,6 +89,27 @@ SettingsVariable<T>* SettingsInterface<T>::GetSettingsVariable() const
|
|||
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>
|
||||
bool SettingsInterface<T>::Commit()
|
||||
{
|
||||
|
|
@ -95,6 +132,14 @@ void SettingsInterface<T>::StageDefault()
|
|||
p->UpdateResetButton();
|
||||
}
|
||||
|
||||
template<class T>
|
||||
void SettingsInterface<T>::StageValue(const T& value)
|
||||
{
|
||||
p->variable_->StageValue(value);
|
||||
p->UpdateEditWidget();
|
||||
p->UpdateResetButton();
|
||||
}
|
||||
|
||||
template<class T>
|
||||
void SettingsInterface<T>::SetEditWidget(QWidget* widget)
|
||||
{
|
||||
|
|
@ -105,6 +150,11 @@ void SettingsInterface<T>::SetEditWidget(QWidget* widget)
|
|||
|
||||
p->editWidget_ = widget;
|
||||
|
||||
if (widget == nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (QLineEdit* lineEdit = dynamic_cast<QLineEdit*>(widget))
|
||||
{
|
||||
if constexpr (std::is_same_v<T, std::string>)
|
||||
|
|
@ -274,6 +324,8 @@ void SettingsInterface<T>::SetResetButton(QAbstractButton* button)
|
|||
|
||||
p->resetButton_ = button;
|
||||
|
||||
if (p->resetButton_ != nullptr)
|
||||
{
|
||||
QObject::connect(p->resetButton_,
|
||||
&QAbstractButton::clicked,
|
||||
p->context_.get(),
|
||||
|
|
@ -283,8 +335,8 @@ void SettingsInterface<T>::SetResetButton(QAbstractButton* button)
|
|||
|
||||
if (p->variable_->GetValue() == defaultValue)
|
||||
{
|
||||
// If the current value is default, reset the staged
|
||||
// value
|
||||
// If the current value is default, reset the
|
||||
// staged value
|
||||
p->variable_->Reset();
|
||||
p->stagedValid_ = true;
|
||||
p->UpdateEditWidget();
|
||||
|
|
@ -301,6 +353,7 @@ void SettingsInterface<T>::SetResetButton(QAbstractButton* button)
|
|||
});
|
||||
|
||||
p->UpdateResetButton();
|
||||
}
|
||||
}
|
||||
|
||||
template<class T>
|
||||
|
|
@ -317,6 +370,39 @@ void SettingsInterface<T>::SetMapToValueFunction(
|
|||
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>
|
||||
void SettingsInterface<T>::Impl::UpdateEditWidget()
|
||||
{
|
||||
|
|
@ -327,35 +413,11 @@ void SettingsInterface<T>::Impl::UpdateEditWidget()
|
|||
|
||||
if (QLineEdit* lineEdit = dynamic_cast<QLineEdit*>(editWidget_))
|
||||
{
|
||||
if constexpr (std::is_integral_v<T>)
|
||||
{
|
||||
lineEdit->setText(QString::number(currentValue));
|
||||
SetWidgetText(lineEdit, currentValue);
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, std::string>)
|
||||
else if (QLabel* label = dynamic_cast<QLabel*>(editWidget_))
|
||||
{
|
||||
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, ", "))));
|
||||
}
|
||||
}
|
||||
SetWidgetText(label, currentValue);
|
||||
}
|
||||
else if (QCheckBox* checkBox = dynamic_cast<QCheckBox*>(editWidget_))
|
||||
{
|
||||
|
|
@ -391,20 +453,9 @@ void SettingsInterface<T>::Impl::UpdateEditWidget()
|
|||
template<class T>
|
||||
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 (staged.has_value())
|
||||
{
|
||||
resetButton_->setVisible(!stagedValid_ || *staged != defaultValue);
|
||||
}
|
||||
else
|
||||
{
|
||||
resetButton_->setVisible(value != defaultValue);
|
||||
}
|
||||
resetButton_->setVisible(!self_->IsDefault());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -45,6 +45,14 @@ public:
|
|||
*/
|
||||
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
|
||||
* value.
|
||||
|
|
@ -64,6 +72,11 @@ public:
|
|||
*/
|
||||
void StageDefault() override;
|
||||
|
||||
/**
|
||||
* Stages a value to the associated settings variable.
|
||||
*/
|
||||
void StageValue(const T& value);
|
||||
|
||||
/**
|
||||
* Sets the edit widget from the settings dialog.
|
||||
*
|
||||
|
|
@ -103,6 +116,7 @@ private:
|
|||
|
||||
#ifdef SETTINGS_INTERFACE_IMPLEMENTATION
|
||||
template class SettingsInterface<bool>;
|
||||
template class SettingsInterface<double>;
|
||||
template class SettingsInterface<std::int64_t>;
|
||||
template class SettingsInterface<std::string>;
|
||||
|
||||
|
|
|
|||
|
|
@ -24,6 +24,14 @@ public:
|
|||
SettingsInterfaceBase(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
|
||||
* value.
|
||||
|
|
|
|||
|
|
@ -239,6 +239,12 @@ std::optional<T> SettingsVariable<T>::GetStaged() const
|
|||
return p->staged_;
|
||||
}
|
||||
|
||||
template<class T>
|
||||
T SettingsVariable<T>::GetStagedOrValue() const
|
||||
{
|
||||
return p->staged_.value_or(GetValue());
|
||||
}
|
||||
|
||||
template<class T>
|
||||
T SettingsVariable<T>::GetDefault() const
|
||||
{
|
||||
|
|
|
|||
|
|
@ -103,6 +103,14 @@ public:
|
|||
*/
|
||||
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
|
||||
* 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
|
||||
|
||||
#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 qt
|
||||
|
|
@ -10,7 +36,8 @@ namespace types
|
|||
enum class Font
|
||||
{
|
||||
din1451alt,
|
||||
din1451alt_g
|
||||
din1451alt_g,
|
||||
Inconsolata_Regular
|
||||
};
|
||||
|
||||
} // 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
|
||||