sync
This commit is contained in:
parent
3e7d1d08dc
commit
c102f99606
22
Cargo.lock
generated
22
Cargo.lock
generated
@ -395,7 +395,6 @@ dependencies = [
|
||||
"glib-sys",
|
||||
"libc",
|
||||
"system-deps",
|
||||
"x11",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -535,6 +534,7 @@ dependencies = [
|
||||
"svg",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"tokio-condvar",
|
||||
"toml 0.8.8",
|
||||
"topojson",
|
||||
"tracing",
|
||||
@ -1400,6 +1400,7 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"proc-macro2 1.0.76",
|
||||
"quote 1.0.35",
|
||||
"regex",
|
||||
"syn 2.0.48",
|
||||
]
|
||||
|
||||
@ -3784,6 +3785,15 @@ dependencies = [
|
||||
"tokio-macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-condvar"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d7233b09174540ef9bf9fc8326bcad6ccebc631e7c9a1e2e48d956a133056f9d"
|
||||
dependencies = [
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-macros"
|
||||
version = "2.2.0"
|
||||
@ -4545,16 +4555,6 @@ dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "x11"
|
||||
version = "2.21.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "502da5464ccd04011667b11c435cb992822c2c0dbde1770c988480d312a0db2e"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"pkg-config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "x11-dl"
|
||||
version = "2.21.0"
|
||||
|
||||
@ -7,7 +7,7 @@ edition = "2021"
|
||||
|
||||
|
||||
[dependencies]
|
||||
cairo-rs = { version = "0.17.0", features = ["xlib"] }
|
||||
cairo-rs = { version = "0.17.0" }
|
||||
glib = "0.17.9"
|
||||
gtk = { version = "0.6.6", package = "gtk4", features = ["v4_8"] }
|
||||
|
||||
@ -64,6 +64,7 @@ relm4-icons = { version = "0.6.0", features = [
|
||||
"fast-forward-filled",
|
||||
"home-filled",
|
||||
"settings-filled",
|
||||
"save-filled",
|
||||
] }
|
||||
surfman = "0.8.1"
|
||||
euclid = "0.22.9"
|
||||
@ -85,6 +86,7 @@ sorted-vec = "0.8.3"
|
||||
tracing = "0.1.40"
|
||||
tracing-subscriber = "0.3.18"
|
||||
indexmap = "2.2.2"
|
||||
tokio-condvar = "0.1.0"
|
||||
|
||||
|
||||
[build-dependencies]
|
||||
|
||||
@ -14,8 +14,8 @@ use abi_stable::{
|
||||
};
|
||||
use parser::{Record, ValueResult};
|
||||
use radarg_plugin_interface::{
|
||||
CoordType, Error, Plugin, PluginId, PluginMod, PluginMod_Ref, PluginResult, PluginResultType,
|
||||
PluginType, Plugin_TO,
|
||||
CoordType, Error, MetaData, Plugin, PluginId, PluginMod, PluginMod_Ref, PluginResult,
|
||||
PluginResultType, PluginType, Plugin_TO,
|
||||
};
|
||||
|
||||
#[export_root_module]
|
||||
@ -133,9 +133,19 @@ impl Plugin for ETWSLoader {
|
||||
}
|
||||
})
|
||||
.collect::<RVec<_>>();
|
||||
|
||||
let meta = MetaData {
|
||||
datetime: RSome(record.filetime.timestamp()),
|
||||
site_info: RNone,
|
||||
lon_range: RSome([0.0, 0.0]),
|
||||
lat_range: RSome([1.0, 1.0]),
|
||||
data_format: RSome("Eastone Washon Radar".into()),
|
||||
other_info: RNone,
|
||||
};
|
||||
ROk(PluginResult {
|
||||
datetime: RString::from(record.filetime.format("%Y%m%d%H%M").to_string()),
|
||||
blocks: result_blocks,
|
||||
meta,
|
||||
})
|
||||
} else {
|
||||
RErr(Error::UnsupportedFormat)
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
{"$message_type":"diagnostic","message":"unused imports: `Write`, `self`","code":{"code":"unused_imports","explanation":null},"level":"warning","spans":[{"file_name":"src/parser.rs","byte_start":323,"byte_end":327,"line_start":13,"line_end":13,"column_start":15,"column_end":19,"is_primary":true,"text":[{"text":"use std::io::{self, Read, Write};","highlight_start":15,"highlight_end":19}],"label":null,"suggested_replacement":null,"suggestion_applicability":null,"expansion":null},{"file_name":"src/parser.rs","byte_start":335,"byte_end":340,"line_start":13,"line_end":13,"column_start":27,"column_end":32,"is_primary":true,"text":[{"text":"use std::io::{self, Read, Write};","highlight_start":27,"highlight_end":32}],"label":null,"suggested_replacement":null,"suggestion_applicability":null,"expansion":null}],"children":[{"message":"`#[warn(unused_imports)]` on by default","code":null,"level":"note","spans":[],"children":[],"rendered":null},{"message":"remove the unused imports","code":null,"level":"help","spans":[{"file_name":"src/parser.rs","byte_start":323,"byte_end":329,"line_start":13,"line_end":13,"column_start":15,"column_end":21,"is_primary":true,"text":[{"text":"use std::io::{self, Read, Write};","highlight_start":15,"highlight_end":21}],"label":null,"suggested_replacement":"","suggestion_applicability":"MachineApplicable","expansion":null},{"file_name":"src/parser.rs","byte_start":333,"byte_end":340,"line_start":13,"line_end":13,"column_start":25,"column_end":32,"is_primary":true,"text":[{"text":"use std::io::{self, Read, Write};","highlight_start":25,"highlight_end":32}],"label":null,"suggested_replacement":"","suggestion_applicability":"MachineApplicable","expansion":null}],"children":[],"rendered":null}],"rendered":"\u001b[0m\u001b[1m\u001b[33mwarning\u001b[0m\u001b[0m\u001b[1m: unused imports: `Write`, `self`\u001b[0m\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m--> \u001b[0m\u001b[0msrc/parser.rs:13:15\u001b[0m\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\n\u001b[0m\u001b[1m\u001b[38;5;12m13\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0muse std::io::{self, Read, Write};\u001b[0m\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m| \u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33m^^^^\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33m^^^^^\u001b[0m\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m= \u001b[0m\u001b[0m\u001b[1mnote\u001b[0m\u001b[0m: `#[warn(unused_imports)]` on by default\u001b[0m\n\n"}
|
||||
{"$message_type":"diagnostic","message":"unused import: `RNone`","code":{"code":"unused_imports","explanation":null},"level":"warning","spans":[{"file_name":"src/lib.rs","byte_start":180,"byte_end":185,"line_start":9,"line_end":9,"column_start":9,"column_end":14,"is_primary":true,"text":[{"text":" RNone, ROk,","highlight_start":9,"highlight_end":14}],"label":null,"suggested_replacement":null,"suggestion_applicability":null,"expansion":null}],"children":[{"message":"remove the unused import","code":null,"level":"help","spans":[{"file_name":"src/lib.rs","byte_start":180,"byte_end":187,"line_start":9,"line_end":9,"column_start":9,"column_end":16,"is_primary":true,"text":[{"text":" RNone, ROk,","highlight_start":9,"highlight_end":16}],"label":null,"suggested_replacement":"","suggestion_applicability":"MachineApplicable","expansion":null}],"children":[],"rendered":null}],"rendered":"\u001b[0m\u001b[1m\u001b[33mwarning\u001b[0m\u001b[0m\u001b[1m: unused import: `RNone`\u001b[0m\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m--> \u001b[0m\u001b[0msrc/lib.rs:9:9\u001b[0m\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\n\u001b[0m\u001b[1m\u001b[38;5;12m9\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m RNone, ROk,\u001b[0m\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m| \u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33m^^^^^\u001b[0m\n\n"}
|
||||
{"$message_type":"diagnostic","message":"unused variable: `hlen2`","code":{"code":"unused_variables","explanation":null},"level":"warning","spans":[{"file_name":"src/parser.rs","byte_start":6651,"byte_end":6656,"line_start":198,"line_end":198,"column_start":21,"column_end":26,"is_primary":true,"text":[{"text":" let (input, hlen2) = Self::_parse_u32(input, order)?;","highlight_start":21,"highlight_end":26}],"label":null,"suggested_replacement":null,"suggestion_applicability":null,"expansion":null}],"children":[{"message":"`#[warn(unused_variables)]` on by default","code":null,"level":"note","spans":[],"children":[],"rendered":null},{"message":"if this is intentional, prefix it with an underscore","code":null,"level":"help","spans":[{"file_name":"src/parser.rs","byte_start":6651,"byte_end":6656,"line_start":198,"line_end":198,"column_start":21,"column_end":26,"is_primary":true,"text":[{"text":" let (input, hlen2) = Self::_parse_u32(input, order)?;","highlight_start":21,"highlight_end":26}],"label":null,"suggested_replacement":"_hlen2","suggestion_applicability":"MachineApplicable","expansion":null}],"children":[],"rendered":null}],"rendered":"\u001b[0m\u001b[1m\u001b[33mwarning\u001b[0m\u001b[0m\u001b[1m: unused variable: `hlen2`\u001b[0m\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m--> \u001b[0m\u001b[0msrc/parser.rs:198:21\u001b[0m\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\n\u001b[0m\u001b[1m\u001b[38;5;12m198\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m let (input, hlen2) = Self::_parse_u32(input, order)?;\u001b[0m\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m| \u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33m^^^^^\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33mhelp: if this is intentional, prefix it with an underscore: `_hlen2`\u001b[0m\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m= \u001b[0m\u001b[0m\u001b[1mnote\u001b[0m\u001b[0m: `#[warn(unused_variables)]` on by default\u001b[0m\n\n"}
|
||||
{"$message_type":"diagnostic","message":"unused variable: `order`","code":{"code":"unused_variables","explanation":null},"level":"warning","spans":[{"file_name":"src/parser.rs","byte_start":9035,"byte_end":9040,"line_start":279,"line_end":279,"column_start":9,"column_end":14,"is_primary":true,"text":[{"text":" order: Order,","highlight_start":9,"highlight_end":14}],"label":null,"suggested_replacement":null,"suggestion_applicability":null,"expansion":null}],"children":[{"message":"if this is intentional, prefix it with an underscore","code":null,"level":"help","spans":[{"file_name":"src/parser.rs","byte_start":9035,"byte_end":9040,"line_start":279,"line_end":279,"column_start":9,"column_end":14,"is_primary":true,"text":[{"text":" order: Order,","highlight_start":9,"highlight_end":14}],"label":null,"suggested_replacement":"_order","suggestion_applicability":"MachineApplicable","expansion":null}],"children":[],"rendered":null}],"rendered":"\u001b[0m\u001b[1m\u001b[33mwarning\u001b[0m\u001b[0m\u001b[1m: unused variable: `order`\u001b[0m\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m--> \u001b[0m\u001b[0msrc/parser.rs:279:9\u001b[0m\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\n\u001b[0m\u001b[1m\u001b[38;5;12m279\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m order: Order,\u001b[0m\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m| \u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33m^^^^^\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33mhelp: if this is intentional, prefix it with an underscore: `_order`\u001b[0m\n\n"}
|
||||
{"$message_type":"diagnostic","message":"unused variable: `dimension_len`","code":{"code":"unused_variables","explanation":null},"level":"warning","spans":[{"file_name":"src/lib.rs","byte_start":1019,"byte_end":1032,"line_start":41,"line_end":41,"column_start":26,"column_end":39,"is_primary":true,"text":[{"text":" let (dimension_len, data) = match b.data {","highlight_start":26,"highlight_end":39}],"label":null,"suggested_replacement":null,"suggestion_applicability":null,"expansion":null}],"children":[{"message":"if this is intentional, prefix it with an underscore","code":null,"level":"help","spans":[{"file_name":"src/lib.rs","byte_start":1019,"byte_end":1032,"line_start":41,"line_end":41,"column_start":26,"column_end":39,"is_primary":true,"text":[{"text":" let (dimension_len, data) = match b.data {","highlight_start":26,"highlight_end":39}],"label":null,"suggested_replacement":"_dimension_len","suggestion_applicability":"MachineApplicable","expansion":null}],"children":[],"rendered":null}],"rendered":"\u001b[0m\u001b[1m\u001b[33mwarning\u001b[0m\u001b[0m\u001b[1m: unused variable: `dimension_len`\u001b[0m\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m--> \u001b[0m\u001b[0msrc/lib.rs:41:26\u001b[0m\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\n\u001b[0m\u001b[1m\u001b[38;5;12m41\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m let (dimension_len, data) = match b.data {\u001b[0m\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m| \u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33m^^^^^^^^^^^^^\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33mhelp: if this is intentional, prefix it with an underscore: `_dimension_len`\u001b[0m\n\n"}
|
||||
{"$message_type":"diagnostic","message":"unused variable: `dimension_len`","code":{"code":"unused_variables","explanation":null},"level":"warning","spans":[{"file_name":"src/lib.rs","byte_start":1029,"byte_end":1042,"line_start":41,"line_end":41,"column_start":26,"column_end":39,"is_primary":true,"text":[{"text":" let (dimension_len, data) = match b.data {","highlight_start":26,"highlight_end":39}],"label":null,"suggested_replacement":null,"suggestion_applicability":null,"expansion":null}],"children":[{"message":"if this is intentional, prefix it with an underscore","code":null,"level":"help","spans":[{"file_name":"src/lib.rs","byte_start":1029,"byte_end":1042,"line_start":41,"line_end":41,"column_start":26,"column_end":39,"is_primary":true,"text":[{"text":" let (dimension_len, data) = match b.data {","highlight_start":26,"highlight_end":39}],"label":null,"suggested_replacement":"_dimension_len","suggestion_applicability":"MachineApplicable","expansion":null}],"children":[],"rendered":null}],"rendered":"\u001b[0m\u001b[1m\u001b[33mwarning\u001b[0m\u001b[0m\u001b[1m: unused variable: `dimension_len`\u001b[0m\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m--> \u001b[0m\u001b[0msrc/lib.rs:41:26\u001b[0m\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\n\u001b[0m\u001b[1m\u001b[38;5;12m41\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m let (dimension_len, data) = match b.data {\u001b[0m\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m| \u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33m^^^^^^^^^^^^^\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33mhelp: if this is intentional, prefix it with an underscore: `_dimension_len`\u001b[0m\n\n"}
|
||||
{"$message_type":"diagnostic","message":"variants `I64` and `U64` are never constructed","code":{"code":"dead_code","explanation":null},"level":"warning","spans":[{"file_name":"src/parser.rs","byte_start":563,"byte_end":573,"line_start":28,"line_end":28,"column_start":6,"column_end":16,"is_primary":false,"text":[{"text":"enum ValueTypes {","highlight_start":6,"highlight_end":16}],"label":"variants in this enum","suggested_replacement":null,"suggestion_applicability":null,"expansion":null},{"file_name":"src/parser.rs","byte_start":580,"byte_end":583,"line_start":29,"line_end":29,"column_start":5,"column_end":8,"is_primary":true,"text":[{"text":" I64,","highlight_start":5,"highlight_end":8}],"label":null,"suggested_replacement":null,"suggestion_applicability":null,"expansion":null},{"file_name":"src/parser.rs","byte_start":616,"byte_end":619,"line_start":33,"line_end":33,"column_start":5,"column_end":8,"is_primary":true,"text":[{"text":" U64,","highlight_start":5,"highlight_end":8}],"label":null,"suggested_replacement":null,"suggestion_applicability":null,"expansion":null}],"children":[{"message":"`#[warn(dead_code)]` on by default","code":null,"level":"note","spans":[],"children":[],"rendered":null}],"rendered":"\u001b[0m\u001b[1m\u001b[33mwarning\u001b[0m\u001b[0m\u001b[1m: variants `I64` and `U64` are never constructed\u001b[0m\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m--> \u001b[0m\u001b[0msrc/parser.rs:29:5\u001b[0m\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\n\u001b[0m\u001b[1m\u001b[38;5;12m28\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0menum ValueTypes {\u001b[0m\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m| \u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m----------\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12mvariants in this enum\u001b[0m\n\u001b[0m\u001b[1m\u001b[38;5;12m29\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m I64,\u001b[0m\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m| \u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33m^^^\u001b[0m\n\u001b[0m\u001b[1m\u001b[38;5;12m...\u001b[0m\n\u001b[0m\u001b[1m\u001b[38;5;12m33\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m U64,\u001b[0m\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m| \u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33m^^^\u001b[0m\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m= \u001b[0m\u001b[0m\u001b[1mnote\u001b[0m\u001b[0m: `#[warn(dead_code)]` on by default\u001b[0m\n\n"}
|
||||
{"$message_type":"diagnostic","message":"6 warnings emitted","code":null,"level":"warning","spans":[],"children":[],"rendered":"\u001b[0m\u001b[1m\u001b[33mwarning\u001b[0m\u001b[0m\u001b[1m: 6 warnings emitted\u001b[0m\n\n"}
|
||||
{"$message_type":"diagnostic","message":"5 warnings emitted","code":null,"level":"warning","spans":[],"children":[],"rendered":"\u001b[0m\u001b[1m\u001b[33mwarning\u001b[0m\u001b[0m\u001b[1m: 5 warnings emitted\u001b[0m\n\n"}
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
92
geo-macros/Cargo.lock
generated
Normal file
92
geo-macros/Cargo.lock
generated
Normal file
@ -0,0 +1,92 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "1.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "geo-macros"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"regex",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.78"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.35"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.10.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-automata",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.4.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5bb987efffd3c6d0d8f5f89510bb458559eab11e4f869acb20bf845e016259cd"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.48"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
|
||||
@ -12,3 +12,4 @@ proc-macro = true
|
||||
syn = { version = "2.0.16", features = ["full"] }
|
||||
quote = "1.0.27"
|
||||
proc-macro2 = "1.0.58"
|
||||
regex = "1.10.3"
|
||||
|
||||
@ -1,10 +1,18 @@
|
||||
extern crate proc_macro;
|
||||
use proc_macro::TokenStream;
|
||||
use proc_macro2::Ident;
|
||||
use proc_macro2::Literal;
|
||||
use proc_macro2::TokenStream as TokenStream2;
|
||||
use quote::format_ident;
|
||||
use quote::quote;
|
||||
use regex::Regex;
|
||||
use syn::parse_macro_input;
|
||||
use syn::parse_str;
|
||||
use syn::Expr;
|
||||
use syn::Fields::Named;
|
||||
use syn::ItemStruct;
|
||||
use syn::Lit;
|
||||
use syn::LitStr;
|
||||
|
||||
#[proc_macro_derive(Prj)]
|
||||
pub fn prj_macro(input: TokenStream) -> TokenStream {
|
||||
@ -42,3 +50,155 @@ pub fn prj_macro(input: TokenStream) -> TokenStream {
|
||||
.into(),
|
||||
}
|
||||
}
|
||||
|
||||
#[proc_macro_derive(StructToMap)]
|
||||
pub fn struct_to_map(input: TokenStream) -> TokenStream {
|
||||
let struct_item = parse_macro_input!(input as ItemStruct);
|
||||
let stru_name = struct_item.ident;
|
||||
|
||||
match struct_item.fields {
|
||||
Named(fields) => {
|
||||
let idents = fields
|
||||
.named
|
||||
.iter()
|
||||
.filter(|x| {
|
||||
if let syn::Visibility::Public(_) = x.vis {
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
})
|
||||
.map(|x| x.ident.as_ref().unwrap());
|
||||
|
||||
quote!(
|
||||
impl #stru_name{
|
||||
pub fn to_map(&self) -> std::collections::HashMap<String,String>{
|
||||
let mut map = std::collections::HashMap::new();
|
||||
#(
|
||||
if let Some(x) = &self.#idents{
|
||||
map.insert(stringify!(#idents).to_string(), format!("{:?}", x));
|
||||
}
|
||||
)*
|
||||
map
|
||||
}
|
||||
}
|
||||
)
|
||||
.into()
|
||||
}
|
||||
_ => quote!(
|
||||
struct A {}
|
||||
)
|
||||
.into(),
|
||||
}
|
||||
}
|
||||
|
||||
#[proc_macro_derive(ToHashMap, attributes(pformat))]
|
||||
pub fn to_hash_map(input: TokenStream) -> TokenStream {
|
||||
let input = parse_macro_input!(input as syn::DeriveInput);
|
||||
let ident = input.ident;
|
||||
let mut map_entries = Vec::new();
|
||||
|
||||
if let syn::Data::Struct(data) = input.data {
|
||||
for field in data.fields.iter() {
|
||||
let field_ident = field.ident.as_ref().unwrap();
|
||||
for attr in &field.attrs {
|
||||
if attr.path().is_ident("pformat") {
|
||||
if let Ok(args) = attr.parse_args::<Expr>() {
|
||||
match args {
|
||||
Expr::Lit(m) => {
|
||||
if let Lit::Str(s) = m.lit {
|
||||
litstr_to_format(&mut map_entries, s, field_ident, None);
|
||||
}
|
||||
}
|
||||
Expr::Tuple(m) => {
|
||||
let keyname = m.elems[0].clone();
|
||||
let value = m.elems[1].clone();
|
||||
if let Expr::Lit(m) = keyname {
|
||||
if let Lit::Str(key_name) = m.lit {
|
||||
if let Expr::Lit(m) = value {
|
||||
if let Lit::Str(s) = m.lit {
|
||||
let key_name = key_name.value();
|
||||
litstr_to_format(
|
||||
&mut map_entries,
|
||||
s,
|
||||
field_ident,
|
||||
Some(key_name),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
quote! {
|
||||
impl #ident {
|
||||
pub fn to_map(&self) -> std::collections::HashMap<String, String> {
|
||||
let mut map = std::collections::HashMap::new();
|
||||
#(#map_entries)*
|
||||
map
|
||||
}
|
||||
}
|
||||
}
|
||||
.into()
|
||||
}
|
||||
|
||||
// 定义属性宏,使其接受一个参数
|
||||
#[proc_macro_attribute]
|
||||
pub fn pformat(args: TokenStream, input: TokenStream) -> TokenStream {
|
||||
input
|
||||
}
|
||||
|
||||
fn litstr_to_format(
|
||||
map_entries: &mut Vec<TokenStream2>,
|
||||
s: LitStr,
|
||||
field_ident: &Ident,
|
||||
key_name: Option<String>,
|
||||
) {
|
||||
let field_name = field_ident.to_string();
|
||||
let re = Regex::new(r"\{.*?\}").unwrap();
|
||||
let mut tags = Vec::new();
|
||||
let mut v = s.value().clone();
|
||||
for cap in re.captures_iter(s.value().as_str()) {
|
||||
if let Some(pipe_pos) = cap[0].find('|') {
|
||||
let (expression, format_specifier) = cap[0].split_at(pipe_pos);
|
||||
v = v.replace(
|
||||
&cap[0].to_string(),
|
||||
&format!("{{{}", &format_specifier[1..]),
|
||||
);
|
||||
tags.push(Some(expression[1..].to_string()));
|
||||
} else {
|
||||
tags.push(None);
|
||||
}
|
||||
}
|
||||
let tags = tags
|
||||
.into_iter()
|
||||
.map(|v| {
|
||||
if let Some(v) = v {
|
||||
let v = v.replace("this", &format!("{}", field_name));
|
||||
let expr: Expr = parse_str(&v).unwrap();
|
||||
quote!(#expr)
|
||||
} else {
|
||||
quote!(#field_ident)
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let k = key_name
|
||||
.map(|k| format_ident!("{}", k))
|
||||
.unwrap_or(field_ident.clone());
|
||||
map_entries.push({
|
||||
let lit = Literal::string(&v);
|
||||
let lit_key = Literal::string(&k.to_string());
|
||||
quote! {
|
||||
if let Some(#field_ident) = &self.#field_ident{
|
||||
map.insert(#lit_key.to_string(), format!(#lit, #(#tags,)*));
|
||||
}
|
||||
}
|
||||
.into()
|
||||
});
|
||||
}
|
||||
|
||||
@ -55,6 +55,17 @@ pub enum DataShape {
|
||||
Matrix,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, StableAbi, Clone)]
|
||||
pub struct MetaData {
|
||||
pub datetime: ROption<i64>,
|
||||
pub site_info: ROption<RString>,
|
||||
pub lat_range: ROption<[f64; 2]>,
|
||||
pub lon_range: ROption<[f64; 2]>,
|
||||
pub data_format: ROption<RString>,
|
||||
pub other_info: ROption<RString>,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(StableAbi, Clone, Debug)]
|
||||
#[sabi(impl_InterfaceType(Sync, Send, Debug))]
|
||||
@ -85,6 +96,7 @@ pub enum PluginResultType {
|
||||
#[sabi(impl_InterfaceType(Sync, Send, Debug))]
|
||||
pub struct PluginResult {
|
||||
pub datetime: RString,
|
||||
pub meta: MetaData,
|
||||
pub blocks: RVec<Block>,
|
||||
}
|
||||
|
||||
|
||||
@ -8,112 +8,4 @@ use crate::{
|
||||
};
|
||||
|
||||
use abi_stable::std_types::{RBoxError, RResult, RStr, RString};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// Sends a json encoded command to a plugin,and returns the response by encoding it to json.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// These are all error that this function returns
|
||||
/// (this does not include error returned as part of the command):
|
||||
///
|
||||
/// - Error::Serialize:
|
||||
/// If the command/return value could not be serialized to JSON.
|
||||
///
|
||||
/// - Error::Deserialize
|
||||
/// If the command/return value could not be deserialized from JSON
|
||||
/// (this comes from the plugin).
|
||||
///
|
||||
/// - Error::UnsupportedCommand
|
||||
/// If the command is not supported by the plugin.
|
||||
///
|
||||
pub fn process_command<'de, P, C, R, F>(
|
||||
this: &mut P,
|
||||
command: RStr<'de>,
|
||||
f: F,
|
||||
) -> RResult<RString, Error>
|
||||
where
|
||||
P: Plugin,
|
||||
F: FnOnce(&mut P, C) -> Result<R, Error>,
|
||||
C: Deserialize<'de>,
|
||||
R: Serialize,
|
||||
{
|
||||
(|| -> Result<RString, Error> {
|
||||
let command = command.as_str();
|
||||
|
||||
let which_variant = serde_json::from_str::<WhichVariant>(&command)
|
||||
.map_err(|e| Error::Deserialize(RBoxError::new(e), WhichCommandRet::Command))?;
|
||||
|
||||
let command = serde_json::from_str::<CommandUnion<C>>(command).map_err(|e| {
|
||||
Error::unsupported_command(Unsupported {
|
||||
plugin_name: this.plugin_id().named.clone().into_owned(),
|
||||
command_name: which_variant.variant,
|
||||
error: RBoxError::new(e),
|
||||
supported_commands: this.list_commands(),
|
||||
})
|
||||
})?;
|
||||
|
||||
let ret: ReturnValUnion<R> = match command {
|
||||
CU::Basic(BasicCommand::GetCommands) => {
|
||||
let commands = this.list_commands();
|
||||
RVU::Basic(BasicRetVal::GetCommands(commands))
|
||||
}
|
||||
CU::ForPlugin(cmd) => RVU::ForPlugin(f(this, cmd)?),
|
||||
};
|
||||
|
||||
match serde_json::to_string(&ret) {
|
||||
Ok(v) => Ok(v.into()),
|
||||
Err(e) => Err(Error::Serialize(RBoxError::new(e), WhichCommandRet::Return)),
|
||||
}
|
||||
})()
|
||||
.into()
|
||||
}
|
||||
|
||||
/// Sends a typed command to a plugin.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// These are all error that this function returns
|
||||
/// (this does not include error returned as part of the command):
|
||||
///
|
||||
/// - Error::Serialize:
|
||||
/// If the command/return value could not be serialized to JSON.
|
||||
///
|
||||
/// - Error::Deserialize
|
||||
/// If the command/return value could not be deserialized from JSON
|
||||
/// (this comes from the plugin).
|
||||
///
|
||||
/// - Error::UnsupportedReturnValue:
|
||||
/// If the return value could not be deserialized from JSON
|
||||
/// (after checking that it has the `{"name":"...",description: ... }` format),
|
||||
/// containing the name of the command this is a return value for .
|
||||
///
|
||||
/// - Error::UnsupportedCommand
|
||||
/// If the command is not supported by the plugin.
|
||||
///
|
||||
pub fn send_command<C>(
|
||||
this: &mut PluginType,
|
||||
command: &C,
|
||||
app: ApplicationMut<'_>,
|
||||
) -> Result<C::Returns, Error>
|
||||
where
|
||||
C: CommandTrait,
|
||||
{
|
||||
let cmd = serde_json::to_string(&command)
|
||||
.map_err(|e| Error::Serialize(RBoxError::new(e), WhichCommandRet::Command))?;
|
||||
|
||||
let ret = this.json_command(RStr::from(&*cmd), app).into_result()?;
|
||||
|
||||
let which_variant = serde_json::from_str::<WhichVariant>(&*ret)
|
||||
.map_err(|e| Error::Deserialize(RBoxError::new(e), WhichCommandRet::Return))?;
|
||||
|
||||
serde_json::from_str::<C::Returns>(&ret).map_err(|e| {
|
||||
Error::unsupported_return_value(Unsupported {
|
||||
plugin_name: this.plugin_id().named.clone().into_owned(),
|
||||
command_name: which_variant.variant,
|
||||
error: RBoxError::new(e),
|
||||
supported_commands: this.list_commands(),
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@ -1,31 +1,3 @@
|
||||
use crate::{
|
||||
coords::{proj::Mercator, Mapper},
|
||||
data::{self, CoordType, Radar2d},
|
||||
errors::RenderError,
|
||||
pipeline::{
|
||||
self,
|
||||
offscreen_renderer::OffscreenRenderer,
|
||||
render_pipeline::RenderResult,
|
||||
utils::{data_to_layer, Dispatcher, Pipeline},
|
||||
},
|
||||
plugin_system::init_plugin,
|
||||
widgets::{
|
||||
render::{predefined::color_mapper::BoundaryNorm, Layer},
|
||||
CMS,
|
||||
},
|
||||
PLUGIN_MANAGER,
|
||||
};
|
||||
use adw::prelude::*;
|
||||
use gtk::prelude::*;
|
||||
use std::{
|
||||
borrow::{Borrow, BorrowMut},
|
||||
cell::RefCell,
|
||||
collections::HashMap,
|
||||
rc::Rc,
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
use tracing::{debug, error, info, warn};
|
||||
|
||||
use super::{
|
||||
control_panel::{ControlPanelInputMsg, ControlPanelModel},
|
||||
messages::{MonitorInputMsg, MonitorOutputMsg},
|
||||
@ -33,15 +5,27 @@ use super::{
|
||||
setting::SettingModel,
|
||||
ControlPanelOutputMsg, TimelineMsg,
|
||||
};
|
||||
use abi_stable::std_types::RStr;
|
||||
use chrono::{prelude::*, DateTime, Duration, Utc};
|
||||
use futures::future::{try_join_all, BoxFuture};
|
||||
use gtk::{
|
||||
prelude::{ApplicationExt, BoxExt, GtkWindowExt, WidgetExt},
|
||||
traits::OrientableExt,
|
||||
use crate::pipeline::element::Element;
|
||||
use crate::{
|
||||
coords::{
|
||||
cms::CMS,
|
||||
proj::{Mercator, ProjectionS},
|
||||
Mapper,
|
||||
},
|
||||
data::MetaInfo,
|
||||
errors::RenderError,
|
||||
pipeline::{utils::data_to_element, Dispatcher, Pipeline, RenderResult},
|
||||
plugin_system::init_plugin,
|
||||
widgets::render::Layer,
|
||||
CONFIG, PLUGIN_MANAGER,
|
||||
};
|
||||
use ndarray::{Array1, Array2, Array3};
|
||||
use radarg_plugin_interface::{Block, DataShape, PluginId, VecResult};
|
||||
use abi_stable::std_types::RStr;
|
||||
use adw::prelude::*;
|
||||
use chrono::{prelude::*, Duration};
|
||||
use futures::future::BoxFuture;
|
||||
use glib::clone;
|
||||
use gtk::prelude::*;
|
||||
use once_cell::sync::Lazy;
|
||||
use relm4::actions::{AccelsPlus, RelmAction, RelmActionGroup};
|
||||
use relm4::*;
|
||||
use relm4::{gtk, Component, ComponentParts, ComponentSender, RelmWidgetExt, SimpleComponent};
|
||||
@ -49,40 +33,54 @@ use relm4_components::open_dialog::{
|
||||
OpenDialog, OpenDialogMsg, OpenDialogResponse, OpenDialogSettings,
|
||||
};
|
||||
use smallvec::SmallVec;
|
||||
use sorted_vec::SortedSet;
|
||||
use std::{
|
||||
any::Any,
|
||||
borrow::{Borrow, BorrowMut},
|
||||
cell::RefCell,
|
||||
collections::{BTreeMap, HashMap},
|
||||
path::PathBuf,
|
||||
rc::Rc,
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
use radarg_plugin_interface::PluginResult;
|
||||
use tokio::{sync::oneshot, task};
|
||||
use tracing::{debug, error, info, warn};
|
||||
|
||||
relm4::new_action_group!(FileActionGroup, "file");
|
||||
relm4::new_stateless_action!(OpenAction, FileActionGroup, "open");
|
||||
pub static FILE_PATH_ROOT: Lazy<Mutex<PathBuf>> = Lazy::new(|| Mutex::new(PathBuf::new()));
|
||||
|
||||
pub type ElementKey = String;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum AppMsg {
|
||||
CloseRequest,
|
||||
Close,
|
||||
OpenDialog,
|
||||
SwitchTo((String, DateTime<Utc>, Option<Layer>)),
|
||||
RenderLayer((Layer, DateTime<Utc>)),
|
||||
OpenDialogMulti,
|
||||
RenderSuccess,
|
||||
NewElement(Element),
|
||||
DeleteElement(ElementKey),
|
||||
NewLayer(Layer),
|
||||
}
|
||||
pub type Buffer = Rc<RefCell<HashMap<String, HashMap<DateTime<Utc>, Option<RenderResult>>>>>;
|
||||
type RcDispatcher = Rc<RefCell<Dispatcher>>;
|
||||
pub type Buffer = Rc<RefCell<HashMap<String, BTreeMap<DateTime<Utc>, Option<RenderResult>>>>>;
|
||||
type RcDispatcher = Rc<Dispatcher>;
|
||||
#[tracker::track]
|
||||
pub struct AppModel {
|
||||
#[do_not_track]
|
||||
dispatcher: RcDispatcher,
|
||||
#[do_not_track]
|
||||
buffer: Buffer,
|
||||
cms: Arc<Mutex<CMS>>,
|
||||
waiting_for: Option<DateTime<Utc>>,
|
||||
#[do_not_track]
|
||||
open_dialog: Controller<OpenDialog>,
|
||||
#[do_not_track]
|
||||
control: Controller<ControlPanelModel>,
|
||||
#[do_not_track]
|
||||
target_pipeline: HashMap<String, Pipeline>,
|
||||
#[do_not_track]
|
||||
render: Controller<MonitorModel>,
|
||||
#[do_not_track]
|
||||
layers: Rc<RefCell<Vec<Layer>>>,
|
||||
#[do_not_track]
|
||||
elements: Vec<Arc<Mutex<Element>>>,
|
||||
#[do_not_track]
|
||||
setting: Controller<SettingModel>,
|
||||
}
|
||||
|
||||
@ -106,9 +104,8 @@ impl Component for AppModel {
|
||||
set_default_width: 1200,
|
||||
set_default_height: 900,
|
||||
set_focus_on_click:true,
|
||||
connect_close_request[sender,app] => move |_| {
|
||||
connect_close_request[sender] => move |_| {
|
||||
sender.input(AppMsg::CloseRequest);
|
||||
app.quit();
|
||||
gtk::Inhibit(true)
|
||||
},
|
||||
gtk::Box{
|
||||
@ -190,58 +187,58 @@ impl Component for AppModel {
|
||||
let control = ControlPanelModel::builder().launch(0).forward(
|
||||
sender.input_sender(),
|
||||
|msg| match msg {
|
||||
ControlPanelOutputMsg::OpenFile((key, time)) => AppMsg::SwitchTo((key, time, None)),
|
||||
ControlPanelOutputMsg::OpenFile((key, time)) => {
|
||||
// AppMsg::SwitchTo((key, time, None, None))
|
||||
AppMsg::Close
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
let render =
|
||||
MonitorModel::builder()
|
||||
.launch(())
|
||||
.forward(sender.input_sender(), |a| match a {
|
||||
MonitorOutputMsg::LayerRenderFinished => AppMsg::RenderSuccess,
|
||||
MonitorOutputMsg::LayerRenderFinished => AppMsg::Close,
|
||||
_ => AppMsg::Close,
|
||||
});
|
||||
|
||||
let setting = SettingModel::builder()
|
||||
.launch(())
|
||||
.forward(sender.input_sender(), |a| AppMsg::Close);
|
||||
|
||||
let mut dispatcher = Rc::new(Dispatcher::new(5, 5, chrono::Duration::minutes(1)));
|
||||
let cms = Arc::new(Mutex::new(CMS::new(
|
||||
Mercator::default().into(),
|
||||
(3000.0, 3000.0),
|
||||
)));
|
||||
let dialog_dispatcher = dispatcher.clone();
|
||||
let dialog_cms = cms.clone();
|
||||
let dialog = OpenDialog::builder()
|
||||
.transient_for_native(&root)
|
||||
.launch(OpenDialogSettings::default())
|
||||
.forward(sender.input_sender(), |response| match response {
|
||||
.forward(sender.input_sender(), move |response| match response {
|
||||
OpenDialogResponse::Accept(path) => {
|
||||
if let Some((a, b)) = Self::open_file(path) {
|
||||
AppMsg::SwitchTo((b.name.clone(), a, Some(b)))
|
||||
} else {
|
||||
AppMsg::Close
|
||||
}
|
||||
let data = Self::open_file_only(path);
|
||||
let mut layer = Layer::new(true, "New Layer".to_string(), None);
|
||||
AppMsg::NewLayer(layer)
|
||||
}
|
||||
_ => AppMsg::Close,
|
||||
});
|
||||
|
||||
let app = relm4::main_application();
|
||||
relm4_icons::initialize_icons();
|
||||
|
||||
let buffer: Buffer = Rc::new(RefCell::new(HashMap::new()));
|
||||
let mut dispatcher = Dispatcher::new(5, 5, chrono::Duration::minutes(1), buffer.clone());
|
||||
|
||||
let model = AppModel {
|
||||
buffer: buffer,
|
||||
dispatcher: Rc::new(RefCell::new(dispatcher)),
|
||||
cms,
|
||||
dispatcher,
|
||||
waiting_for: None,
|
||||
elements: Vec::with_capacity(20),
|
||||
open_dialog: dialog,
|
||||
target_pipeline: HashMap::new(),
|
||||
control,
|
||||
render,
|
||||
layers: Rc::new(RefCell::new(Vec::with_capacity(20))),
|
||||
setting,
|
||||
tracker: 0,
|
||||
};
|
||||
|
||||
let widgets = view_output!();
|
||||
let mut group = RelmActionGroup::<FileActionGroup>::new();
|
||||
|
||||
app.set_accelerators_for_action::<OpenAction>(&["<primary>O"]);
|
||||
relm4::main_application().set_accelerators_for_action::<OpenAction>(&["<primary>O"]);
|
||||
let action: RelmAction<OpenAction> = {
|
||||
RelmAction::new_stateless(move |_| {
|
||||
sender.input(AppMsg::OpenDialog);
|
||||
@ -262,117 +259,24 @@ impl Component for AppModel {
|
||||
) {
|
||||
self.reset();
|
||||
match msg {
|
||||
AppMsg::SwitchTo((key, datetime, layer)) => {
|
||||
println!("Switch to {}", datetime);
|
||||
self.create_pipeline(key.clone());
|
||||
self.create_buffer(key.clone());
|
||||
|
||||
{
|
||||
let mut current_buffer = (*self.buffer).borrow_mut();
|
||||
let current_buffer = current_buffer.get_mut(key.as_str()).unwrap();
|
||||
|
||||
if let Some(layer) = layer {
|
||||
self.control.emit(ControlPanelInputMsg::Disable);
|
||||
_sender.input(AppMsg::RenderLayer((layer, datetime)));
|
||||
} else {
|
||||
if let Some(v) = current_buffer.get_mut(&datetime) {
|
||||
// Task already in pipeline
|
||||
if v.is_none() {
|
||||
// Still on the way, need to show the progress
|
||||
self.control.emit(ControlPanelInputMsg::Disable);
|
||||
self.waiting_for = Some(datetime);
|
||||
info!("Task still on the way");
|
||||
let toast =
|
||||
adw::Toast::builder().title("Task still on the way").build();
|
||||
widgets.monitor_toast.add_toast(toast);
|
||||
} else {
|
||||
// Task finished, Need to show the result
|
||||
let v = v.as_ref().unwrap().clone();
|
||||
|
||||
info!("Task finished");
|
||||
_sender.input(AppMsg::RenderLayer((v.layer, datetime)));
|
||||
}
|
||||
} else {
|
||||
// Task not in pipeline, Need to open this file
|
||||
let layer = (*self.dispatcher)
|
||||
.borrow()
|
||||
.get_single_path(&key, datetime, true)
|
||||
.map(|p| Self::open_file(p))
|
||||
.flatten()
|
||||
.map(|x| x.1);
|
||||
if layer.is_none() {
|
||||
// TODO: Show Error
|
||||
} else {
|
||||
self.control.emit(ControlPanelInputMsg::Disable);
|
||||
_sender.input(AppMsg::RenderLayer((layer.unwrap(), datetime)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let tasks = self.create_tasks(key.clone(), datetime);
|
||||
let pipeline = self.target_pipeline.get_mut(key.as_str()).unwrap();
|
||||
let worker = Pipeline::run(pipeline);
|
||||
let render_sender = self.render.sender();
|
||||
let control_sender = self.control.sender();
|
||||
let new_sender = _sender.clone();
|
||||
|
||||
if let Some(tasks) = tasks {
|
||||
if tasks.len() == 0 {
|
||||
widgets.monitor_toast.add_toast(
|
||||
adw::Toast::builder()
|
||||
.title(format!("no data found: {}", datetime))
|
||||
.build(),
|
||||
);
|
||||
return;
|
||||
}
|
||||
(*self.buffer)
|
||||
.borrow_mut()
|
||||
.get_mut(key.clone().as_str())
|
||||
.unwrap()
|
||||
.extend(tasks.clone());
|
||||
let (thumb_recivers, listening_func) = self.create_listening_with_thumb(
|
||||
key.clone(),
|
||||
tasks.len(),
|
||||
new_sender.clone(),
|
||||
);
|
||||
|
||||
control_sender.emit(ControlPanelInputMsg::SetThumb(
|
||||
thumb_recivers
|
||||
.into_iter()
|
||||
.map(|p| (None, Some(p), datetime))
|
||||
.collect(),
|
||||
));
|
||||
|
||||
let listening = self
|
||||
.current_pipeline(key.clone())
|
||||
.listening_one_by_one(listening_func);
|
||||
|
||||
// Spawn the worker and listen to the result
|
||||
new_sender.oneshot_command(async move {
|
||||
worker.await;
|
||||
listening.await;
|
||||
AppCommand::Test
|
||||
});
|
||||
}
|
||||
AppMsg::NewElement(element) => {
|
||||
let (key, id) = (element.key(), element.id());
|
||||
let element = Arc::new(Mutex::new(element));
|
||||
self.elements.push(element.clone());
|
||||
// let layer = Layer::new(true, key, Some(element));
|
||||
// _sender.input(AppMsg::NewLayer(layer));
|
||||
}
|
||||
AppMsg::NewLayer(layer) => {
|
||||
(*self.layers).borrow_mut().push(layer);
|
||||
}
|
||||
AppMsg::CloseRequest => {
|
||||
relm4::main_application().quit();
|
||||
}
|
||||
AppMsg::CloseRequest => {}
|
||||
AppMsg::Close => {}
|
||||
AppMsg::OpenDialog => {
|
||||
self.open_dialog.emit(OpenDialogMsg::Open);
|
||||
}
|
||||
AppMsg::RenderLayer((layer, dt)) => {
|
||||
self.render.emit(MonitorInputMsg::AddLayer(layer));
|
||||
self.control.emit(ControlPanelInputMsg::Selection(Some(dt)));
|
||||
self.control
|
||||
.emit(ControlPanelInputMsg::TimeLine(TimelineMsg::SetStart(
|
||||
dt - Duration::minutes(30),
|
||||
)));
|
||||
}
|
||||
AppMsg::RenderSuccess => {
|
||||
self.control.emit(ControlPanelInputMsg::Enable);
|
||||
}
|
||||
AppMsg::OpenDialogMulti => {}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
@ -384,18 +288,6 @@ impl Component for AppModel {
|
||||
) {
|
||||
match message {
|
||||
AppCommand::PrepareFinished(mut v) => {}
|
||||
AppCommand::TestBuffer((key, result)) => {
|
||||
let datetime = result.time();
|
||||
let mut buffer = (*self.buffer).borrow_mut();
|
||||
let mut buffer = buffer.get_mut(key.as_str()).unwrap();
|
||||
if let Some(waiting_for) = self.waiting_for {
|
||||
if waiting_for == datetime {
|
||||
self.waiting_for = None;
|
||||
sender.input(AppMsg::RenderLayer((result.layer.clone(), datetime)));
|
||||
}
|
||||
}
|
||||
buffer.get_mut(&datetime).unwrap().replace(result);
|
||||
}
|
||||
_ => {
|
||||
println!("test");
|
||||
}
|
||||
@ -404,99 +296,27 @@ impl Component for AppModel {
|
||||
}
|
||||
|
||||
impl AppModel {
|
||||
fn create_buffer(&mut self, key: impl Borrow<str>) {
|
||||
if !(*self.buffer).borrow().contains_key(key.borrow()) {
|
||||
(*self.buffer)
|
||||
.borrow_mut()
|
||||
.insert(key.borrow().to_string(), HashMap::new());
|
||||
}
|
||||
}
|
||||
|
||||
fn create_pipeline(&mut self, key: impl Borrow<str>) -> &mut Pipeline {
|
||||
if !self.target_pipeline.contains_key(key.borrow()) {
|
||||
let mut pipeline = Pipeline::new(10, key.borrow().to_string());
|
||||
pipeline.init().set_dispatcher(self.dispatcher.clone());
|
||||
self.target_pipeline
|
||||
.insert(key.borrow().to_string(), pipeline);
|
||||
}
|
||||
self.target_pipeline.get_mut(key.borrow()).unwrap()
|
||||
}
|
||||
|
||||
fn create_tasks(
|
||||
&mut self,
|
||||
key: impl AsRef<str>,
|
||||
time: DateTime<Utc>,
|
||||
) -> Option<Vec<(DateTime<Utc>, Option<RenderResult>)>> {
|
||||
let pipeline = self.target_pipeline.get_mut(key.as_ref()).unwrap();
|
||||
let time_list = pipeline.set_current(time, true, 3);
|
||||
time_list.map(|time_list| time_list.into_iter().map(|t| (t, None)).collect())
|
||||
}
|
||||
|
||||
fn current_pipeline(&mut self, key: impl AsRef<str>) -> &mut Pipeline {
|
||||
self.target_pipeline.get_mut(key.as_ref()).unwrap()
|
||||
}
|
||||
|
||||
fn create_listening_with_thumb(
|
||||
&self,
|
||||
key: impl Borrow<str>,
|
||||
num: usize,
|
||||
_sender: ComponentSender<Self>,
|
||||
) -> (
|
||||
Vec<oneshot::Receiver<gtk::gdk::Texture>>,
|
||||
Vec<
|
||||
impl FnOnce(
|
||||
oneshot::Receiver<Result<RenderResult, RenderError>>,
|
||||
) -> BoxFuture<'static, ()>
|
||||
+ Send
|
||||
+ 'static
|
||||
+ Sync,
|
||||
>,
|
||||
) {
|
||||
let mut thumb_senders: Vec<oneshot::Sender<gtk::gdk::Texture>> = Vec::new();
|
||||
let mut thumb_recivers = Vec::new();
|
||||
let mut current_buffer = (*self.buffer).borrow_mut();
|
||||
let mut current_buffer = current_buffer.get_mut(key.borrow()).unwrap();
|
||||
|
||||
for _ in 0..num {
|
||||
let (tx, rx) = oneshot::channel();
|
||||
thumb_recivers.push(rx);
|
||||
thumb_senders.push(tx);
|
||||
}
|
||||
|
||||
let mut p = Vec::new();
|
||||
for sender in thumb_senders {
|
||||
let new_sender = _sender.clone();
|
||||
let f = move |tx: oneshot::Receiver<Result<RenderResult, RenderError>>| {
|
||||
Box::pin(async move {
|
||||
if let Ok(r) = tx.await {
|
||||
let r = r.unwrap();
|
||||
let tex = (&r)
|
||||
.layer
|
||||
.render_target()
|
||||
.lock()
|
||||
.unwrap()
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.thumbnail
|
||||
.clone();
|
||||
sender.send(tex.unwrap()).unwrap();
|
||||
new_sender
|
||||
.command_sender()
|
||||
.emit(AppCommand::TestBuffer((r.layer.name.clone(), r)));
|
||||
}
|
||||
}) as BoxFuture<'static, ()>
|
||||
};
|
||||
p.push(f);
|
||||
}
|
||||
(thumb_recivers, p)
|
||||
}
|
||||
|
||||
fn open_file(path: impl AsRef<std::path::Path>) -> Option<(DateTime<Utc>, Layer)> {
|
||||
fn open_file(
|
||||
path: impl AsRef<std::path::Path>,
|
||||
dispatcher: Rc<Dispatcher>,
|
||||
cms: Arc<Mutex<CMS>>,
|
||||
) -> Option<(Option<Box<dyn Any + Send + Sync>>, Element)> {
|
||||
let plugin = PLUGIN_MANAGER.get_plugin_by_name("etws_loader").unwrap();
|
||||
let mut result = plugin
|
||||
.load(RStr::from_str(path.as_ref().to_str().unwrap()))
|
||||
.unwrap();
|
||||
let mut block = result.blocks.pop().unwrap();
|
||||
data_to_layer(block)
|
||||
let block = result.blocks.first().unwrap();
|
||||
data_to_element(block, dispatcher, cms)
|
||||
.map(|v| (Some(Box::new(result) as Box<dyn Any + Send + Sync>), v))
|
||||
}
|
||||
|
||||
fn open_file_only(
|
||||
path: impl AsRef<std::path::Path>,
|
||||
) -> PluginResult {
|
||||
let plugin = PLUGIN_MANAGER.get_plugin_by_name("etws_loader").unwrap();
|
||||
let mut result = plugin
|
||||
.load(RStr::from_str(path.as_ref().to_str().unwrap()))
|
||||
.unwrap();
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@ -24,6 +24,7 @@ pub struct ControlPanelModel {
|
||||
enabled: bool,
|
||||
timeline_start: DateTime<Utc>,
|
||||
selection: Option<DateTime<Utc>>,
|
||||
key: Option<String>,
|
||||
#[tracker::no_eq]
|
||||
list_img_wrapper: TypedListView<ImgItem, gtk::SingleSelection>,
|
||||
}
|
||||
@ -73,8 +74,8 @@ impl SimpleComponent for ControlPanelModel {
|
||||
set_spacing:10,
|
||||
gtk::Button{
|
||||
set_icon_name: "rewind-filled",
|
||||
#[track = "model.changed(ControlPanelModel::enabled())"]
|
||||
set_sensitive: model.enabled,
|
||||
#[track = "model.changed(ControlPanelModel::enabled()) || model.changed(ControlPanelModel::key())"]
|
||||
set_sensitive: model.enabled && model.key.is_some(),
|
||||
connect_clicked[sender] => move |_| {
|
||||
sender.input(ControlPanelInputMsg::SelectionRewind);
|
||||
},
|
||||
@ -84,8 +85,8 @@ impl SimpleComponent for ControlPanelModel {
|
||||
},
|
||||
gtk::Button{
|
||||
set_icon_name: "fast-forward-filled",
|
||||
#[track = "model.changed(ControlPanelModel::enabled())"]
|
||||
set_sensitive: model.enabled,
|
||||
#[track = "model.changed(ControlPanelModel::enabled()) || model.changed(ControlPanelModel::key())"]
|
||||
set_sensitive: model.enabled && model.key.is_some(),
|
||||
connect_clicked[sender] => move |_| {
|
||||
sender.input(ControlPanelInputMsg::SelectionFastForward);
|
||||
},
|
||||
@ -216,6 +217,7 @@ impl SimpleComponent for ControlPanelModel {
|
||||
let timeline_start = Utc::now();
|
||||
let model = ControlPanelModel {
|
||||
timeline_enabled: true,
|
||||
key: None,
|
||||
enabled: true,
|
||||
selection: None,
|
||||
timeline_start,
|
||||
@ -250,7 +252,7 @@ impl SimpleComponent for ControlPanelModel {
|
||||
if let Some(current) = current {
|
||||
self.set_selection(Some(current - Duration::minutes(1)));
|
||||
_sender.output(ControlPanelOutputMsg::OpenFile((
|
||||
format!("DBZ"),
|
||||
self.key.clone().unwrap(),
|
||||
current - Duration::minutes(1),
|
||||
)));
|
||||
}
|
||||
@ -260,7 +262,7 @@ impl SimpleComponent for ControlPanelModel {
|
||||
if let Some(current) = current {
|
||||
self.set_selection(Some(current + Duration::minutes(1)));
|
||||
_sender.output(ControlPanelOutputMsg::OpenFile((
|
||||
format!("DBZ"),
|
||||
self.key.clone().unwrap(),
|
||||
current + Duration::minutes(1),
|
||||
)));
|
||||
}
|
||||
@ -282,6 +284,10 @@ impl SimpleComponent for ControlPanelModel {
|
||||
ControlPanelInputMsg::Enable => {
|
||||
self.set_enabled(true);
|
||||
}
|
||||
|
||||
ControlPanelInputMsg::SetKey(key) => {
|
||||
self.set_key(Some(key));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -24,6 +24,7 @@ pub enum ControlPanelInputMsg {
|
||||
SetThumbByDate((DateTime<Utc>, Option<gtk::gdk::Texture>)),
|
||||
SelectionRewind,
|
||||
SelectionFastForward,
|
||||
SetKey(String),
|
||||
Disable,
|
||||
Enable,
|
||||
}
|
||||
|
||||
@ -1,12 +1,20 @@
|
||||
use std::fmt::Debug;
|
||||
use std::{collections::HashMap, fmt::Debug};
|
||||
|
||||
use crate::widgets::{render::Layer, widget::Widget};
|
||||
use crate::{
|
||||
components::app::ElementKey,
|
||||
pipeline::element::ElementID,
|
||||
widgets::{render::Layer, widget::Widget},
|
||||
};
|
||||
|
||||
pub enum MonitorInputMsg {
|
||||
NewElement((ElementKey, ElementID)),
|
||||
AddWidget(Box<dyn Widget>),
|
||||
RemoveWidget,
|
||||
AddLayer(Layer),
|
||||
RemoveLayer(String),
|
||||
AddMetaItem(HashMap<String, String>),
|
||||
ClearMetaItems,
|
||||
UpdateMetaItem(HashMap<String, String>),
|
||||
UpdateLayer((String, Box<dyn Fn(&mut Layer) + 'static>)),
|
||||
None,
|
||||
}
|
||||
@ -14,12 +22,16 @@ pub enum MonitorInputMsg {
|
||||
impl Debug for MonitorInputMsg {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
MonitorInputMsg::NewElement(_) => write!(f, "MonitorInputMsg::NewElement"),
|
||||
MonitorInputMsg::AddLayer(_) => write!(f, "MonitorInputMsg::AddLayer"),
|
||||
MonitorInputMsg::RemoveLayer(_) => write!(f, "MonitorInputMsg::RemoveLayer"),
|
||||
MonitorInputMsg::UpdateLayer(_) => write!(f, "MonitorInputMsg::UpdateLayer"),
|
||||
MonitorInputMsg::None => write!(f, "MonitorInputMsg::None"),
|
||||
MonitorInputMsg::AddWidget(_) => write!(f, "MonitorInputMsg::AddWidget"),
|
||||
MonitorInputMsg::RemoveWidget => write!(f, "MonitorInputMsg::RemoveWidget"),
|
||||
MonitorInputMsg::AddMetaItem(_) => write!(f, "MonitorInputMsg::RemoveWidget"),
|
||||
MonitorInputMsg::ClearMetaItems => write!(f, "MonitorInputMsg::ClearMetaItems"),
|
||||
MonitorInputMsg::UpdateMetaItem(_) => write!(f, "MonitorInputMsg::UpdateMetaItem"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,8 +1,9 @@
|
||||
use super::messages::{MonitorInputMsg, MonitorOutputMsg};
|
||||
use crate::coords::cms::CMS;
|
||||
use crate::pipeline::offscreen_renderer::OffscreenRenderer;
|
||||
use crate::widgets::predefined::color_mapper::BoundaryNorm;
|
||||
use crate::widgets::predefined::widgets::ColorBar;
|
||||
use crate::widgets::render::{RenderConfig, Target, CMS};
|
||||
use crate::widgets::render::RenderConfig;
|
||||
use crate::widgets::widget::{Widget, WidgetType};
|
||||
use crate::widgets::WidgetFrame;
|
||||
use crate::{
|
||||
@ -10,19 +11,20 @@ use crate::{
|
||||
widgets::dynamic_col::DynamicCol,
|
||||
widgets::render::{Layer, Render},
|
||||
};
|
||||
use geo::k_nearest_concave_hull;
|
||||
use glib::{clone, PropertyGet};
|
||||
use std::cell::RefCell;
|
||||
use std::collections::HashMap;
|
||||
use std::rc::Rc;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use super::sidebar::{sidebar::SideBarModel, Msg, SideBarOutputMsg};
|
||||
use super::sidebar::{sidebar::SideBarModel, SideBarInputMsg, SideBarOutputMsg};
|
||||
use adw::prelude::*;
|
||||
use relm4::{component::Component, *};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum MonitorCommand {
|
||||
NewLayer(Layer),
|
||||
// NewLayer(Layer),
|
||||
None,
|
||||
}
|
||||
#[tracker::track]
|
||||
@ -35,7 +37,7 @@ pub struct MonitorModel {
|
||||
#[no_eq]
|
||||
widgets: Vec<WidgetFrame>,
|
||||
#[no_eq]
|
||||
layers: Rc<RefCell<HashMap<String, Layer>>>,
|
||||
layers: Rc<RefCell<Vec<Layer>>>,
|
||||
#[no_eq]
|
||||
sidebar: Controller<SideBarModel>,
|
||||
}
|
||||
@ -76,8 +78,8 @@ impl Component for MonitorModel {
|
||||
gtk::Overlay{
|
||||
#[wrap(Some)]
|
||||
set_child = &Render{
|
||||
#[track = "model.changed(MonitorModel::new_layer())"]
|
||||
set_interior_layers: model.layers.borrow().values().cloned().collect(),
|
||||
// #[track = "model.changed(MonitorModel::new_layer())"]
|
||||
// set_interior_layers: model.layers.clone(),
|
||||
#[track = "model.changed(MonitorModel::render_cfg())"]
|
||||
set_cfg: model.render_cfg,
|
||||
#[track = "model.changed(MonitorModel::render_range())"]
|
||||
@ -101,8 +103,7 @@ impl Component for MonitorModel {
|
||||
#[wrap(Some)]
|
||||
set_end_child=model.sidebar.widget(),
|
||||
}
|
||||
},
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -110,66 +111,78 @@ impl Component for MonitorModel {
|
||||
self.reset();
|
||||
match message {
|
||||
MonitorInputMsg::AddLayer(layer) => {
|
||||
let need_prepare = { layer.get_prepare().lock().unwrap().is_some() };
|
||||
{
|
||||
let mut layers = self.layers.borrow_mut();
|
||||
if !layers.contains_key(layer.name.as_str()) {
|
||||
let mut widgets = layer.widgets.lock().unwrap();
|
||||
if widgets.is_some() {
|
||||
let ws = widgets.take().unwrap();
|
||||
ws.into_iter().for_each(|w| {
|
||||
sender.input(MonitorInputMsg::AddWidget(w));
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
if need_prepare {
|
||||
sender.oneshot_command(async move {
|
||||
let mut back = OffscreenRenderer::new(3000, 3000).unwrap();
|
||||
let canvas = back.create_canvas();
|
||||
let f = {
|
||||
let p = layer.get_prepare();
|
||||
let mut _p = p.lock().unwrap();
|
||||
_p.take().unwrap()
|
||||
};
|
||||
let imp = layer.get_imp().unwrap();
|
||||
let map: Mapper = Mercator::default().into();
|
||||
let cms = CMS::new(map, (3000.0, 3000.0));
|
||||
let canvas = Arc::new(Mutex::new(canvas));
|
||||
let target = f(imp, canvas, cms);
|
||||
layer.set_render_target(target);
|
||||
MonitorCommand::NewLayer(layer)
|
||||
});
|
||||
} else {
|
||||
{
|
||||
let mut layers = self.layers.borrow_mut();
|
||||
if layers.contains_key(layer.name.as_str()) {
|
||||
let p = layers.get_mut(layer.name.as_str()).unwrap();
|
||||
*p = layer;
|
||||
} else {
|
||||
layers.insert(layer.name.clone(), layer);
|
||||
}
|
||||
}
|
||||
self.layers.borrow_mut().push(layer);
|
||||
let raw_id = self.get_new_layer();
|
||||
self.set_new_layer(*raw_id + 1);
|
||||
self.sidebar.sender().send(SideBarInputMsg::RefreshList);
|
||||
// let need_prepare = { layer.get_prepare().lock().unwrap().is_some() };
|
||||
// {
|
||||
// let mut layers = self.layers.borrow_mut();
|
||||
// if !layers.contains_key(layer.name.as_str()) {
|
||||
// let mut widgets = layer.widgets.lock().unwrap();
|
||||
// if widgets.is_some() {
|
||||
// let ws = widgets.take().unwrap();
|
||||
// ws.into_iter().for_each(|w| {
|
||||
// sender.input(MonitorInputMsg::AddWidget(w));
|
||||
// });
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// if need_prepare {
|
||||
// sender.oneshot_command(async move {
|
||||
// let mut back = OffscreenRenderer::new(3000, 3000).unwrap();
|
||||
// let canvas = back.create_canvas();
|
||||
// let f = {
|
||||
// let p = layer.get_prepare();
|
||||
// let mut _p = p.lock().unwrap();
|
||||
// _p.take().unwrap()
|
||||
// };
|
||||
// let imp = layer.get_imp().unwrap();
|
||||
// let map: Mapper = Mercator::default().into();
|
||||
// let cms = CMS::new(map, (3000.0, 3000.0));
|
||||
// let canvas = Arc::new(Mutex::new(canvas));
|
||||
// let target = f(imp, canvas, cms);
|
||||
// layer.set_render_target(target);
|
||||
// MonitorCommand::NewLayer(layer)
|
||||
// });
|
||||
// } else {
|
||||
// {
|
||||
// let mut layers = self.layers.borrow_mut();
|
||||
// if layers.contains_key(layer.name.as_str()) {
|
||||
// let p = layers.get_mut(layer.name.as_str()).unwrap();
|
||||
// *p = layer;
|
||||
// } else {
|
||||
// layers.insert(layer.name.clone(), layer);
|
||||
// }
|
||||
// }
|
||||
|
||||
let raw_id = self.get_new_layer();
|
||||
self.set_new_layer(*raw_id + 1);
|
||||
self.sidebar.sender().send(Msg::RefreshList);
|
||||
}
|
||||
// let raw_id = self.get_new_layer();
|
||||
// self.set_new_layer(*raw_id + 1);
|
||||
// self.sidebar.sender().send(SideBarInputMsg::RefreshList);
|
||||
// }
|
||||
}
|
||||
MonitorInputMsg::RemoveLayer(k) => {
|
||||
self.layers.borrow_mut().remove(&k);
|
||||
sender
|
||||
.output_sender()
|
||||
.send(MonitorOutputMsg::LayerRemoved(0))
|
||||
.unwrap();
|
||||
MonitorInputMsg::AddMetaItem(map) => {
|
||||
self.sidebar.emit(SideBarInputMsg::AddMetaItems(map))
|
||||
}
|
||||
MonitorInputMsg::UpdateLayer((k, f)) => {
|
||||
f(&mut (*self.layers.borrow_mut().get_mut(&k).unwrap()));
|
||||
sender
|
||||
.output_sender()
|
||||
.send(MonitorOutputMsg::LayerUpdated(0))
|
||||
.unwrap();
|
||||
MonitorInputMsg::ClearMetaItems => self.sidebar.emit(SideBarInputMsg::ClearMetaItems),
|
||||
MonitorInputMsg::UpdateMetaItem(map) => {
|
||||
self.sidebar.emit(SideBarInputMsg::ClearMetaItems);
|
||||
self.sidebar.emit(SideBarInputMsg::AddMetaItems(map))
|
||||
}
|
||||
// MonitorInputMsg::RemoveLayer(k) => {
|
||||
// self.layers.borrow_mut().remove(&k);
|
||||
// sender
|
||||
// .output_sender()
|
||||
// .send(MonitorOutputMsg::LayerRemoved(0))
|
||||
// .unwrap();
|
||||
// }
|
||||
// MonitorInputMsg::UpdateLayer((k, f)) => {
|
||||
// f(&mut (*self.layers.borrow_mut().get_mut(&k).unwrap()));
|
||||
// sender
|
||||
// .output_sender()
|
||||
// .send(MonitorOutputMsg::LayerUpdated(0))
|
||||
// .unwrap();
|
||||
// }
|
||||
MonitorInputMsg::AddWidget(widget) => match widget.widget_type() {
|
||||
WidgetType::Cairo => {
|
||||
let frame = WidgetFrame::new();
|
||||
@ -181,6 +194,7 @@ impl Component for MonitorModel {
|
||||
},
|
||||
MonitorInputMsg::RemoveWidget => {}
|
||||
MonitorInputMsg::None => {}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
@ -189,7 +203,7 @@ impl Component for MonitorModel {
|
||||
root: &Self::Root,
|
||||
sender: ComponentSender<Self>,
|
||||
) -> ComponentParts<Self> {
|
||||
let layers = Rc::new(RefCell::new(HashMap::new()));
|
||||
let layers = Rc::new(RefCell::new(Vec::new()));
|
||||
let sidebar: Controller<SideBarModel> = SideBarModel::builder()
|
||||
.launch(layers.clone())
|
||||
.forward(sender.input_sender(), |msg| match msg {
|
||||
@ -225,13 +239,16 @@ impl Component for MonitorModel {
|
||||
) {
|
||||
self.reset();
|
||||
match msg {
|
||||
MonitorCommand::NewLayer(layer) => {
|
||||
self.layers.borrow_mut().insert(layer.name.clone(), layer);
|
||||
self.set_render_range((29.13, 30.16, 119.53, 121.13));
|
||||
self.sidebar.sender().send(Msg::RefreshList).unwrap();
|
||||
let raw_id = self.get_new_layer();
|
||||
self.set_new_layer(*raw_id + 1);
|
||||
}
|
||||
// MonitorCommand::NewLayer(layer) => {
|
||||
// // self.layers.borrow_mut().insert(layer.name.clone(), layer);
|
||||
// // self.set_render_range((29.13, 30.16, 119.53, 121.13));
|
||||
// // self.sidebar
|
||||
// // .sender()
|
||||
// // .send(SideBarInputMsg::RefreshList)
|
||||
// // .unwrap();
|
||||
// // let raw_id = self.get_new_layer();
|
||||
// // self.set_new_layer(*raw_id + 1);
|
||||
// }
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
@ -6,7 +6,7 @@ use relm4::{
|
||||
FactorySender,
|
||||
};
|
||||
|
||||
use super::Msg;
|
||||
use super::SideBarInputMsg;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum TestMsg {
|
||||
@ -33,7 +33,7 @@ impl BottomBarModel {
|
||||
#[relm4::factory(pub)]
|
||||
impl FactoryComponent for BottomBarModel {
|
||||
type ParentWidget = gtk::Box;
|
||||
type ParentInput = Msg;
|
||||
type ParentInput = SideBarInputMsg;
|
||||
type Input = ();
|
||||
type Output = TestMsg;
|
||||
type Init = BottomBarModel;
|
||||
@ -53,14 +53,11 @@ impl FactoryComponent for BottomBarModel {
|
||||
init
|
||||
}
|
||||
|
||||
fn update(&mut self, message: Self::Input, sender: FactorySender<Self>) {
|
||||
}
|
||||
fn update(&mut self, message: Self::Input, sender: FactorySender<Self>) {}
|
||||
|
||||
fn forward_to_parent(_output: Self::Output) -> Option<Self::ParentInput> {
|
||||
Some(match _output {
|
||||
_ => Msg::None
|
||||
_ => SideBarInputMsg::None,
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
58
src/components/monitor/sidebar/meta_data_list.rs
Normal file
58
src/components/monitor/sidebar/meta_data_list.rs
Normal file
@ -0,0 +1,58 @@
|
||||
use gtk::prelude::*;
|
||||
use relm4::{
|
||||
binding::{Binding, U8Binding},
|
||||
factory::FactoryView,
|
||||
gtk,
|
||||
prelude::{DynamicIndex, FactoryComponent},
|
||||
typed_list_view::{LabelColumn, RelmColumn, TypedColumnView},
|
||||
view, FactorySender, RelmObjectExt,
|
||||
};
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub(super) struct MyListItem {
|
||||
tag: String,
|
||||
info: String,
|
||||
}
|
||||
|
||||
impl MyListItem {
|
||||
pub fn new(tag: String, info: String) -> Self {
|
||||
Self { tag, info }
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) struct TagColumn;
|
||||
|
||||
impl LabelColumn for TagColumn {
|
||||
type Item = MyListItem;
|
||||
type Value = String;
|
||||
const COLUMN_NAME: &'static str = "tag";
|
||||
const ENABLE_SORT: bool = true;
|
||||
|
||||
fn get_cell_value(item: &Self::Item) -> Self::Value {
|
||||
// item.value
|
||||
item.tag.clone()
|
||||
}
|
||||
|
||||
fn format_cell_value(value: &Self::Value) -> String {
|
||||
format!("{}", value)
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) struct InfoColumn;
|
||||
|
||||
impl RelmColumn for InfoColumn {
|
||||
type Root = gtk::Label;
|
||||
type Widgets = ();
|
||||
type Item = MyListItem;
|
||||
|
||||
const COLUMN_NAME: &'static str = "info";
|
||||
|
||||
fn setup(_item: >k::ListItem) -> (Self::Root, Self::Widgets) {
|
||||
let a = gtk::Label::new(None);
|
||||
(a, ())
|
||||
}
|
||||
|
||||
fn bind(item: &mut Self::Item, _: &mut Self::Widgets, label: &mut Self::Root) {
|
||||
label.set_text(item.info.as_str());
|
||||
}
|
||||
}
|
||||
@ -1,3 +1,4 @@
|
||||
pub mod sidebar;
|
||||
pub use sidebar::*;
|
||||
pub mod bottom_bar;
|
||||
pub mod meta_data_list;
|
||||
|
||||
@ -1,5 +1,3 @@
|
||||
use std::{cell::RefCell, collections::HashMap, rc::Rc};
|
||||
|
||||
use abi_stable::type_level::trait_marker::Hash;
|
||||
use glib::clone;
|
||||
use gtk::prelude::WidgetExt;
|
||||
@ -8,9 +6,10 @@ use relm4::{
|
||||
binding::{Binding, U8Binding},
|
||||
factory::{DynamicIndex, FactoryComponent, FactorySender, FactoryVecDeque},
|
||||
prelude::*,
|
||||
typed_list_view::{RelmListItem, TypedListView},
|
||||
typed_list_view::{RelmListItem, TypedColumnView, TypedListView},
|
||||
RelmObjectExt,
|
||||
};
|
||||
use std::{cell::RefCell, collections::HashMap, rc::Rc};
|
||||
|
||||
use crate::{
|
||||
chart::Chart,
|
||||
@ -18,16 +17,25 @@ use crate::{
|
||||
widgets::render::{predefined::color_mapper::BoundaryNorm, Layer},
|
||||
};
|
||||
|
||||
use super::bottom_bar::BottomBarModel;
|
||||
use super::{
|
||||
bottom_bar::BottomBarModel,
|
||||
meta_data_list::{InfoColumn, MyListItem, TagColumn},
|
||||
};
|
||||
|
||||
relm4::new_action_group!(FileActionGroup, "file");
|
||||
relm4::new_stateless_action!(OpenAction, FileActionGroup, "open");
|
||||
pub struct SideBarModel {
|
||||
layers: Rc<RefCell<HashMap<String, Layer>>>,
|
||||
layers: Rc<RefCell<Vec<Layer>>>,
|
||||
counter: u8,
|
||||
list_view_wrapper: TypedListView<LayerItem, gtk::SingleSelection>,
|
||||
bottom_bar_vec: FactoryVecDeque<BottomBarModel>,
|
||||
meta_list_view: TypedColumnView<MyListItem, gtk::NoSelection>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Msg {
|
||||
pub enum SideBarInputMsg {
|
||||
AddMetaItems(HashMap<String, String>),
|
||||
ClearMetaItems,
|
||||
RefreshList,
|
||||
None,
|
||||
}
|
||||
@ -39,11 +47,12 @@ pub enum SideBarOutputMsg {
|
||||
|
||||
#[relm4::component(pub)]
|
||||
impl SimpleComponent for SideBarModel {
|
||||
type Init = Rc<RefCell<HashMap<String, Layer>>>;
|
||||
type Init = Rc<RefCell<Vec<Layer>>>;
|
||||
type Output = SideBarOutputMsg;
|
||||
type Input = Msg;
|
||||
type Input = SideBarInputMsg;
|
||||
|
||||
view! {
|
||||
#[root]
|
||||
gtk::Box {
|
||||
set_orientation: gtk::Orientation::Vertical,
|
||||
set_spacing: 5,
|
||||
@ -51,37 +60,37 @@ impl SimpleComponent for SideBarModel {
|
||||
|
||||
gtk::Paned{
|
||||
set_orientation: gtk::Orientation::Vertical,
|
||||
set_position: 300,
|
||||
set_position: 200,
|
||||
#[wrap(Some)]
|
||||
set_start_child = >k::Box{
|
||||
set_orientation: gtk::Orientation::Vertical,
|
||||
set_spacing: 5,
|
||||
gtk::Frame{
|
||||
add_css_class: "rb",
|
||||
#[local]
|
||||
top_panel -> gtk::Notebook{}
|
||||
},
|
||||
gtk::Button {
|
||||
set_label: "Add Layers",
|
||||
connect_clicked[sender] => move |_| {
|
||||
},
|
||||
},
|
||||
gtk::Button {
|
||||
set_label: "Add Layer",
|
||||
connect_clicked[sender] => move |_| {
|
||||
println!("hello");
|
||||
sender.output(
|
||||
SideBarOutputMsg::NewLayer(
|
||||
Layer::grid_render_layer_with_path(
|
||||
std::path::Path::new("./test2.npz"),
|
||||
"DBZ".to_string(),
|
||||
Npz,
|
||||
BoundaryNorm::default(),
|
||||
)
|
||||
)
|
||||
).unwrap()
|
||||
},
|
||||
#[name="meta_panel"]
|
||||
gtk::Notebook::builder().vexpand(true).hexpand(true).build() -> gtk::Notebook{}
|
||||
},
|
||||
// gtk::Button {
|
||||
// set_label: "Add Layers",
|
||||
// connect_clicked[sender] => move |_| {
|
||||
// },
|
||||
// },
|
||||
// gtk::Button {
|
||||
// set_label: "Add Layer",
|
||||
// connect_clicked[sender] => move |_| {
|
||||
// println!("hello");
|
||||
// sender.output(
|
||||
// SideBarOutputMsg::NewLayer(
|
||||
// Layer::grid_render_layer_with_path(
|
||||
// std::path::Path::new("./test2.npz"),
|
||||
// "DBZ".to_string(),
|
||||
// Npz,
|
||||
// BoundaryNorm::default(),
|
||||
// )
|
||||
// )
|
||||
// ).unwrap()
|
||||
// },
|
||||
// },
|
||||
},
|
||||
|
||||
#[wrap(Some)]
|
||||
@ -89,10 +98,10 @@ impl SimpleComponent for SideBarModel {
|
||||
set_orientation: gtk::Orientation::Vertical,
|
||||
set_vexpand: true,
|
||||
set_hexpand: true,
|
||||
#[local]
|
||||
bottom_panel -> gtk::Notebook{
|
||||
#[name="bottom_panel"]
|
||||
gtk::Notebook::builder().vexpand(true).build() -> gtk::Notebook{
|
||||
set_margin_top: 10,
|
||||
set_margin_bottom: 5,
|
||||
set_margin_bottom: 5
|
||||
},
|
||||
#[local_ref]
|
||||
counter_box -> gtk::Box{
|
||||
@ -101,7 +110,32 @@ impl SimpleComponent for SideBarModel {
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
},
|
||||
layer_page = gtk::ScrolledWindow::builder()
|
||||
.vexpand(true)
|
||||
.hexpand(true)
|
||||
.build() -> gtk::ScrolledWindow{
|
||||
#[wrap(Some)]
|
||||
#[local_ref]
|
||||
set_child=my_view -> gtk::ListView{},
|
||||
set_margin_horizontal:5,
|
||||
set_margin_vertical:3
|
||||
},
|
||||
#[local_ref]
|
||||
meta_view -> gtk::ColumnView{
|
||||
set_hexpand:true,
|
||||
set_vexpand:true,
|
||||
set_show_column_separators: true,
|
||||
set_show_row_separators: true,
|
||||
set_enable_rubberband:true,
|
||||
set_reorderable:false,
|
||||
},
|
||||
bottom_panel.append_page(&layer_page, Some(>k::Label::new(Some("Layers")))),
|
||||
meta_panel.append_page(meta_view, Some(>k::Label::new(Some("Meta")))),
|
||||
meta_panel.append_page(&Chart::new(), Some(>k::Label::new(Some("Chart")))),
|
||||
#[local_ref]
|
||||
info_c -> gtk::ColumnViewColumn{
|
||||
set_expand: true
|
||||
}
|
||||
}
|
||||
|
||||
@ -123,47 +157,46 @@ impl SimpleComponent for SideBarModel {
|
||||
bottom_bar_vec_guard.push_back(BottomBarModel::new("chevron-up-filled".to_string()));
|
||||
bottom_bar_vec_guard.push_back(BottomBarModel::new("chevron-down-filled".to_string()));
|
||||
}
|
||||
let mut meta_list_view = TypedColumnView::new();
|
||||
meta_list_view.append_column::<TagColumn>();
|
||||
meta_list_view.append_column::<InfoColumn>();
|
||||
|
||||
let model = SideBarModel {
|
||||
meta_list_view,
|
||||
layers: init,
|
||||
counter: 0,
|
||||
list_view_wrapper,
|
||||
bottom_bar_vec,
|
||||
};
|
||||
|
||||
let my_view = &model.list_view_wrapper.view;
|
||||
let top_panel = gtk::Notebook::builder().vexpand(true).hexpand(true).build();
|
||||
top_panel.append_page(&Chart::new(), Some(>k::Label::new(Some("Chart"))));
|
||||
|
||||
let bottom_panel = gtk::Notebook::builder().vexpand(true).build();
|
||||
let layer_page = gtk::ScrolledWindow::builder()
|
||||
.vexpand(true)
|
||||
.hexpand(true)
|
||||
.build();
|
||||
|
||||
let counter_box = model.bottom_bar_vec.widget();
|
||||
|
||||
layer_page.set_child(Some(my_view));
|
||||
layer_page.set_margin_horizontal(5);
|
||||
layer_page.set_margin_vertical(3);
|
||||
bottom_panel.append_page(&layer_page, Some(>k::Label::new(Some("Layers"))));
|
||||
|
||||
let meta_view = &model.meta_list_view.view;
|
||||
let columns = model.meta_list_view.get_columns();
|
||||
let info_c = columns.get("info").unwrap();
|
||||
let widgets = view_output!();
|
||||
ComponentParts { model, widgets }
|
||||
}
|
||||
|
||||
fn update(&mut self, message: Self::Input, sender: ComponentSender<Self>) {
|
||||
match message {
|
||||
Msg::RefreshList => {
|
||||
SideBarInputMsg::RefreshList => {
|
||||
let mut list = self
|
||||
.layers
|
||||
.borrow()
|
||||
.iter()
|
||||
.map(|(k, v)| LayerItem::new(k.clone(), v.visiable))
|
||||
.map(|v| LayerItem::new(v.name.clone(), v.visiable, v.get_thumbnail()))
|
||||
.collect::<Vec<_>>();
|
||||
self.list_view_wrapper.clear();
|
||||
self.list_view_wrapper.extend_from_iter(list);
|
||||
}
|
||||
SideBarInputMsg::AddMetaItems(hs) => {
|
||||
for (k, v) in hs {
|
||||
self.meta_list_view.append(MyListItem::new(k, v));
|
||||
}
|
||||
}
|
||||
SideBarInputMsg::ClearMetaItems => {
|
||||
self.meta_list_view.clear();
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
@ -173,20 +206,24 @@ impl SimpleComponent for SideBarModel {
|
||||
struct LayerItem {
|
||||
layer_name: String,
|
||||
visiable: bool,
|
||||
img: Option<gtk::gdk::Texture>,
|
||||
}
|
||||
|
||||
impl LayerItem {
|
||||
fn new(name: String, visiable: bool) -> Self {
|
||||
fn new(name: String, visiable: bool, img: Option<gtk::gdk::Texture>) -> Self {
|
||||
Self {
|
||||
layer_name: name,
|
||||
visiable,
|
||||
img,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct Widgets {
|
||||
label: gtk::Label,
|
||||
screen_shot: gtk::Image,
|
||||
button: gtk::CheckButton,
|
||||
menu: gtk::PopoverMenu,
|
||||
}
|
||||
|
||||
impl RelmListItem for LayerItem {
|
||||
@ -194,8 +231,32 @@ impl RelmListItem for LayerItem {
|
||||
type Widgets = Widgets;
|
||||
|
||||
fn setup(_item: >k::ListItem) -> (gtk::Box, Widgets) {
|
||||
relm4::menu! {
|
||||
main_menu: {
|
||||
"File" {
|
||||
"Open" => OpenAction,
|
||||
"Open Folder" => OpenAction,
|
||||
},
|
||||
"Edit" {
|
||||
"New Layer" => OpenAction,
|
||||
"Undo" => OpenAction,
|
||||
"Redo" => OpenAction,
|
||||
},
|
||||
"Plugins" {
|
||||
"Plugin1" => OpenAction,
|
||||
"Plugin2" => OpenAction,
|
||||
},
|
||||
}
|
||||
}
|
||||
relm4::view! {
|
||||
my_box = gtk::Box {
|
||||
gtk::Frame{
|
||||
set_margin_end: 10,
|
||||
#[name = "screen_shot"]
|
||||
gtk::Image{
|
||||
set_size_request: (65, 40),
|
||||
}
|
||||
},
|
||||
#[name = "label"]
|
||||
gtk::Label{
|
||||
set_halign: gtk::Align::Start,
|
||||
@ -207,17 +268,40 @@ impl RelmListItem for LayerItem {
|
||||
gtk::CheckButton{
|
||||
set_halign: gtk::Align::End,
|
||||
},
|
||||
#[name = "menu"]
|
||||
gtk::PopoverMenu::from_model(Some(&main_menu)){}
|
||||
}
|
||||
}
|
||||
|
||||
let widgets = Widgets { label, button };
|
||||
let widgets = Widgets {
|
||||
screen_shot,
|
||||
label,
|
||||
button,
|
||||
menu,
|
||||
};
|
||||
|
||||
(my_box, widgets)
|
||||
}
|
||||
|
||||
fn bind(&mut self, widgets: &mut Self::Widgets, _root: &mut Self::Root) {
|
||||
let Widgets { label, button } = widgets;
|
||||
let Widgets {
|
||||
label,
|
||||
button,
|
||||
screen_shot,
|
||||
menu,
|
||||
} = widgets;
|
||||
|
||||
let gesture_click = gtk::GestureClick::new();
|
||||
gesture_click.set_button(gtk::gdk::BUTTON_SECONDARY);
|
||||
screen_shot.set_paintable(self.img.as_ref());
|
||||
|
||||
let menu = menu.clone();
|
||||
gesture_click.connect_released(clone!(@weak menu => move |gesture_click, _, x, y| {
|
||||
menu.set_pointing_to(Some(>k::gdk::Rectangle::new(x as i32, y as i32, 1, 1)));
|
||||
menu.popup();
|
||||
}));
|
||||
|
||||
_root.add_controller(gesture_click);
|
||||
label.set_label(&format!("Layer: {} ", &self.layer_name));
|
||||
button.set_active(self.visiable);
|
||||
}
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
#[derive(Debug)]
|
||||
pub enum SettingMsg {
|
||||
PathFormats((String, (String, String))),
|
||||
SaveConfig,
|
||||
}
|
||||
|
||||
@ -55,11 +55,33 @@ impl SimpleComponent for SettingModel {
|
||||
set_margin_end:10,
|
||||
}}
|
||||
},
|
||||
path_page = adw::PreferencesPage{
|
||||
set_title:"Paths",
|
||||
#[local_ref]
|
||||
add=my_view -> adw::PreferencesGroup{
|
||||
set_title:"Add",
|
||||
path_page = gtk::Box{
|
||||
set_orientation:gtk::Orientation::Vertical,
|
||||
gtk::Box{
|
||||
set_hexpand:true,
|
||||
gtk::Label::new(Some("Paths")){
|
||||
add_css_class: "h1",
|
||||
set_halign: gtk::Align::Start,
|
||||
},
|
||||
gtk::Button{
|
||||
set_halign: gtk::Align::End,
|
||||
connect_clicked[sender] => move |_| {
|
||||
sender.input(SettingMsg::SaveConfig);
|
||||
},
|
||||
#[wrap(Some)]
|
||||
set_child=&adw::ButtonContent{
|
||||
set_label: "Save",
|
||||
set_icon_name: "save-filled",
|
||||
}
|
||||
}
|
||||
|
||||
},
|
||||
adw::PreferencesPage{
|
||||
set_title:"Paths",
|
||||
#[local_ref]
|
||||
add=my_view -> adw::PreferencesGroup{
|
||||
set_title:"Add",
|
||||
}
|
||||
}
|
||||
},
|
||||
stack.add_titled(&path_page, None, "Paths"),
|
||||
@ -103,6 +125,10 @@ impl SimpleComponent for SettingModel {
|
||||
let mut list_guard = self.path_list.guard();
|
||||
// list_guard.push_back(PathItem::new(k, v));
|
||||
}
|
||||
SettingMsg::SaveConfig => {
|
||||
let mut config = CONFIG.lock().unwrap();
|
||||
config.save();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -66,4 +66,21 @@ impl Config {
|
||||
|
||||
Err(ConfigError::DefaultConfigError)
|
||||
}
|
||||
|
||||
pub fn save(&self) -> Result<(), ConfigError> {
|
||||
if let Some(dir_path) = env::var("RADARG_CONFIG")
|
||||
.ok()
|
||||
.map(|x| PathBuf::from(x))
|
||||
.or(dirs::config_dir())
|
||||
{
|
||||
let path = dir_path.join("radarg.toml");
|
||||
let mut file = std::fs::File::create(path)?;
|
||||
let ser_config = toml::to_string_pretty(&self).unwrap();
|
||||
file.write_all(ser_config.as_bytes());
|
||||
|
||||
Ok(())
|
||||
} else {
|
||||
Err(ConfigError::DefaultConfigError)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
59
src/coords/cms.rs
Normal file
59
src/coords/cms.rs
Normal file
@ -0,0 +1,59 @@
|
||||
use std::ops::Range;
|
||||
use geo_types::LineString;
|
||||
use crate::coords::Mapper;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct CMS {
|
||||
mapper: Mapper,
|
||||
window_size: (f32, f32),
|
||||
bounds: (f64, f64, f64, f64),
|
||||
}
|
||||
|
||||
unsafe impl Send for CMS {}
|
||||
unsafe impl Sync for CMS {}
|
||||
|
||||
impl CMS {
|
||||
pub fn new(mapper: Mapper, window_size: (f32, f32)) -> Self {
|
||||
let bounds = mapper.get_bounds();
|
||||
Self {
|
||||
mapper,
|
||||
window_size,
|
||||
bounds,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_lat_range(&mut self, lat_range: Range<f64>) {
|
||||
self.mapper.set_lat_range(lat_range);
|
||||
self.bounds = self.mapper.get_bounds()
|
||||
}
|
||||
|
||||
pub fn set_lon_range(&mut self, lon_range: Range<f64>) {
|
||||
self.mapper.set_lon_range(lon_range);
|
||||
self.bounds = self.mapper.get_bounds();
|
||||
}
|
||||
|
||||
pub fn map(&self, loc: (f64, f64)) -> Option<(f32, f32)> {
|
||||
self.mapper.map(loc).ok().map(|(x, y)| {
|
||||
// println!("x: {}, y: {}", x, y);
|
||||
let (w, h) = self.window_size;
|
||||
let (w, h) = (w as f64, h as f64);
|
||||
let (x, y) = (x - self.bounds.0, y - self.bounds.2);
|
||||
let (x, y) = (
|
||||
x / (self.bounds.1 - self.bounds.0),
|
||||
1.0 - y / (self.bounds.3 - self.bounds.2),
|
||||
);
|
||||
let (x, y) = (x * w, y * h);
|
||||
(x as f32, y as f32)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn ring_map(&self, line: &LineString) -> Option<LineString<f32>> {
|
||||
Some(
|
||||
line.points()
|
||||
.into_iter()
|
||||
.map(|p| self.map((p.x(), p.y())).unwrap())
|
||||
.collect::<Vec<_>>()
|
||||
.into(),
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -14,6 +14,16 @@ pub struct Mapper {
|
||||
unsafe impl Send for Mapper {}
|
||||
unsafe impl Sync for Mapper {}
|
||||
|
||||
impl std::fmt::Debug for Mapper {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("Mapper")
|
||||
.field("proj", &self.proj)
|
||||
.field("range", &self.range)
|
||||
.field("bounds", &self.bounds)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for Mapper {
|
||||
fn clone(&self) -> Self {
|
||||
let c = self.proj.proj_info();
|
||||
|
||||
@ -3,6 +3,7 @@ use num_traits::Num;
|
||||
pub mod mapper;
|
||||
pub mod proj;
|
||||
pub mod wgs84;
|
||||
pub mod cms;
|
||||
|
||||
pub use mapper::Mapper;
|
||||
// pub use wgs84::LatLonCoord;
|
||||
|
||||
35
src/data/meta.rs
Normal file
35
src/data/meta.rs
Normal file
@ -0,0 +1,35 @@
|
||||
use chrono::prelude::*;
|
||||
use geo_macros::{pformat, StructToMap, ToHashMap};
|
||||
use radarg_plugin_interface::MetaData;
|
||||
|
||||
#[derive(ToHashMap, Clone, Debug, Default)]
|
||||
pub struct MetaInfo {
|
||||
#[pformat(("日期时间", "{this.format(\"%Y-%m-%d %H:%M:%S\")|}"))]
|
||||
pub datetime: Option<DateTime<Utc>>,
|
||||
#[pformat(("站点信息","{}"))]
|
||||
pub site_info: Option<String>,
|
||||
#[pformat(("纬度范围","start: {this.0|:.2} end: {this.1|:.2}"))]
|
||||
pub lat_range: Option<(f64, f64)>,
|
||||
#[pformat(("经度范围","start: {this.0|:.2} end: {this.1|:.2}"))]
|
||||
pub lon_range: Option<(f64, f64)>,
|
||||
#[pformat(("数据格式说明","{}"))]
|
||||
pub data_format: Option<String>,
|
||||
#[pformat(("{}"))]
|
||||
pub other_info: Option<String>,
|
||||
}
|
||||
|
||||
impl From<MetaData> for MetaInfo {
|
||||
fn from(meta: MetaData) -> Self {
|
||||
Self {
|
||||
datetime: meta
|
||||
.datetime
|
||||
.into_option()
|
||||
.map(|p| Utc.timestamp_opt(p, 0).unwrap()),
|
||||
site_info: meta.site_info.into_option().map(|v| v.into_string()),
|
||||
lat_range: meta.lat_range.into_option().map(|v| (v[0], v[1])),
|
||||
lon_range: meta.lon_range.into_option().map(|v| (v[0], v[1])),
|
||||
data_format: meta.data_format.into_option().map(|v| v.into_string()),
|
||||
other_info: meta.other_info.into_option().map(|v| v.into_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,5 +1,7 @@
|
||||
pub mod meta;
|
||||
use crate::errors::DataError;
|
||||
use async_trait::async_trait;
|
||||
pub use meta::MetaInfo;
|
||||
use ndarray::{
|
||||
s, Array1, Array2, Array3, ArrayBase, DataMut, Dimension, Ix1, Ix2, OwnedRepr, RawDataClone,
|
||||
};
|
||||
|
||||
@ -16,6 +16,8 @@ pub enum PipelineError {
|
||||
#[from]
|
||||
source: ProjError,
|
||||
},
|
||||
#[error("data error")]
|
||||
DataError(String)
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
#![feature(step_trait)]
|
||||
#![allow(unused)]
|
||||
#![allow(dead_code)]
|
||||
#[macro_use]
|
||||
@ -28,13 +27,10 @@ use tracing_subscriber;
|
||||
mod widgets;
|
||||
|
||||
const APP_ID: &str = "org.tsuki.radar_g";
|
||||
|
||||
static RUNTIME: SafeLazy<Runtime> =
|
||||
SafeLazy::new(|| Runtime::new().expect("Setting up tokio runtime needs to succeed."));
|
||||
|
||||
static CONFIG: SafeLazy<Mutex<Config>> = SafeLazy::new(|| Mutex::new(Config::from_env().unwrap()));
|
||||
static PLUGIN_MANAGER: SafeLazy<PluginManager> = SafeLazy::new(|| PluginManager::new().unwrap());
|
||||
// static SETTING: UnsafeLazy<Settings> = UnsafeLazy::new(|| Settings::new(APP_ID));
|
||||
|
||||
fn main() {
|
||||
// Load GL pointers from epoxy (GL context management library used by GTK).
|
||||
|
||||
240
src/pipeline/dispatcher.rs
Normal file
240
src/pipeline/dispatcher.rs
Normal file
@ -0,0 +1,240 @@
|
||||
use super::element::TargetType;
|
||||
use super::offscreen_renderer::{CanvasWrapper, OffscreenRenderer};
|
||||
use crate::{
|
||||
components::app::{Buffer, FILE_PATH_ROOT},
|
||||
CONFIG,
|
||||
};
|
||||
use chrono::{prelude::*, Duration};
|
||||
use once_cell::sync::Lazy;
|
||||
use regex::Regex;
|
||||
use std::{cell::Ref, collections::HashMap, path::PathBuf};
|
||||
use tracing::*;
|
||||
|
||||
static REREMAP: Lazy<HashMap<&str, &str>> = Lazy::new(|| {
|
||||
let mut map = HashMap::new();
|
||||
map.insert("%Y", r"\d{4}");
|
||||
map.insert("%m", r"\d{2}");
|
||||
map.insert("%d", r"\d{2}");
|
||||
map.insert("%H", r"\d{2}");
|
||||
map.insert("%M", r"\d{2}");
|
||||
map.insert("%S", r"\d{2}");
|
||||
map.insert("{prefix}", r"(.+?)");
|
||||
map
|
||||
});
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Dispatcher {
|
||||
datetime: DateTime<Utc>,
|
||||
fore_len: usize,
|
||||
back_len: usize,
|
||||
step: Duration,
|
||||
}
|
||||
|
||||
impl Dispatcher {
|
||||
pub fn new(fore_len: usize, back_len: usize, step: Duration) -> Self {
|
||||
Self {
|
||||
datetime: Utc::now(),
|
||||
fore_len,
|
||||
back_len,
|
||||
step,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_current_time(&mut self, datetime: DateTime<Utc>) {
|
||||
self.datetime = datetime;
|
||||
}
|
||||
|
||||
pub fn set_step(&mut self, step: Duration) {
|
||||
self.step = step;
|
||||
}
|
||||
|
||||
pub fn set_fore_len(&mut self, fore_len: usize) {
|
||||
self.fore_len = fore_len;
|
||||
}
|
||||
|
||||
pub fn set_back_len(&mut self, back_len: usize) {
|
||||
self.back_len = back_len;
|
||||
}
|
||||
|
||||
pub fn get_single_path(
|
||||
&self,
|
||||
name: &str,
|
||||
current_time: DateTime<Utc>,
|
||||
check_existed: bool,
|
||||
) -> Option<String> {
|
||||
let datetime_format: regex::Regex =
|
||||
Regex::new(r"(?:%[YHMSmd](?:[-/:_]?%[YHMSmd])*)").unwrap();
|
||||
let config = CONFIG.lock().unwrap();
|
||||
let path_format = config
|
||||
.plugins
|
||||
.get("etws_loader")
|
||||
.unwrap()
|
||||
.path_formats
|
||||
.as_ref();
|
||||
if path_format.is_none() {
|
||||
return None;
|
||||
}
|
||||
let c = path_format.unwrap().get(name).map(|s| {
|
||||
let path = s.clone();
|
||||
let need_formated = datetime_format.captures_iter(&path).collect::<Vec<_>>();
|
||||
let mut result_path = path.clone();
|
||||
|
||||
for need_format in need_formated.iter() {
|
||||
let fmt = need_format.get(0).unwrap().as_str();
|
||||
let t = current_time.format(fmt).to_string();
|
||||
result_path = result_path.replace(fmt, &t);
|
||||
}
|
||||
result_path
|
||||
});
|
||||
if let Some(c) = c {
|
||||
if check_existed {
|
||||
if std::path::Path::new(&c).exists() {
|
||||
Some(c)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
Some(c)
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_path(
|
||||
&self,
|
||||
name: &str,
|
||||
current_time: DateTime<Utc>,
|
||||
check_existed: bool,
|
||||
mut max_retry_time: usize,
|
||||
) -> Option<Vec<(String, DateTime<Utc>)>> {
|
||||
let datetime_format: regex::Regex =
|
||||
Regex::new(r"%[YHMSmd](?:[-/:_]?%[YHMSmd])*").unwrap();
|
||||
let config = CONFIG.lock().unwrap();
|
||||
let path_format = config
|
||||
.plugins
|
||||
.get("etws_loader")
|
||||
.unwrap()
|
||||
.path_formats
|
||||
.as_ref();
|
||||
if path_format.is_none() {
|
||||
return None;
|
||||
}
|
||||
path_format.unwrap().get(name).map(|s| {
|
||||
let path = s.clone();
|
||||
let file_path = { FILE_PATH_ROOT.lock().unwrap().clone() };
|
||||
let path = if path.starts_with("./") {
|
||||
let file_root = file_root(&file_path, path.replace("./", ""));
|
||||
file_root.map(|root| {
|
||||
let splited = path.split_at(2);
|
||||
root.join(PathBuf::from(splited.1))
|
||||
})
|
||||
} else {
|
||||
Some(PathBuf::from(path))
|
||||
};
|
||||
|
||||
let mut result_paths = Vec::new();
|
||||
if let Some(path_uninit) = path {
|
||||
let mut path_str = path_uninit.to_string_lossy().to_string();
|
||||
let prefixs = get_prefix(&file_path, &path_str);
|
||||
|
||||
for (idx, prefix) in prefixs.iter().enumerate() {
|
||||
if let Some(s) = prefix.as_ref() {
|
||||
path_str = path_str.replacen("{prefix}", &s, 1);
|
||||
}
|
||||
}
|
||||
|
||||
let need_formated = datetime_format.captures_iter(&path_str).collect::<Vec<_>>();
|
||||
let mut fore = self.fore_len;
|
||||
let mut back = 1;
|
||||
|
||||
while fore > 0 {
|
||||
let mut result_path = path_str.clone();
|
||||
let t = current_time - self.step * fore as i32;
|
||||
for need_format in need_formated.iter() {
|
||||
let fmt = need_format.get(0).unwrap().as_str();
|
||||
let t = t.format(fmt).to_string();
|
||||
result_path = result_path.replace(fmt, &t);
|
||||
}
|
||||
|
||||
if check_existed {
|
||||
if max_retry_time == 0 {
|
||||
break;
|
||||
}
|
||||
if !std::path::Path::new(&result_path).exists() {
|
||||
max_retry_time = max_retry_time - 1;
|
||||
continue;
|
||||
} else {
|
||||
result_paths.push((result_path.clone(), t));
|
||||
}
|
||||
} else {
|
||||
result_paths.push((result_path.clone(), t));
|
||||
}
|
||||
fore = fore - 1;
|
||||
}
|
||||
|
||||
while back < self.back_len + 1 {
|
||||
let mut result_path = path_str.clone();
|
||||
let t = current_time + self.step * back as i32;
|
||||
for need_format in need_formated.iter() {
|
||||
let fmt = need_format.get(0).unwrap().as_str();
|
||||
let t = t.format(fmt).to_string();
|
||||
result_path = result_path.replace(fmt, &t);
|
||||
}
|
||||
|
||||
if check_existed {
|
||||
if max_retry_time == 0 {
|
||||
break;
|
||||
}
|
||||
if !std::path::Path::new(&result_path).exists() {
|
||||
max_retry_time = max_retry_time - 1;
|
||||
continue;
|
||||
} else {
|
||||
result_paths.push((result_path.clone(), t));
|
||||
}
|
||||
} else {
|
||||
result_paths.push((result_path.clone(), t));
|
||||
}
|
||||
back = back + 1;
|
||||
}
|
||||
}
|
||||
result_paths
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn file_root(path: &PathBuf, pat: String) -> Option<PathBuf> {
|
||||
let new_pat = rereplace(&pat);
|
||||
let path_str = path.to_string_lossy();
|
||||
let re = Regex::new(&new_pat).unwrap();
|
||||
let splited = re.split(&path_str);
|
||||
let splited = splited.collect::<Vec<_>>();
|
||||
if splited.len() == 2 {
|
||||
let root = splited.first().unwrap();
|
||||
Some(PathBuf::from(root))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn rereplace(pat: &str) -> String {
|
||||
let mut result = pat.to_string();
|
||||
for (k, v) in REREMAP.iter() {
|
||||
result = result.replace(k, v);
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
fn get_prefix(path: &PathBuf, pat: &str) -> Vec<Option<String>> {
|
||||
let replaced = rereplace(pat);
|
||||
let path_str = path.to_string_lossy();
|
||||
let re = Regex::new(&replaced).unwrap();
|
||||
let prefixs = re.captures(&path_str).unwrap();
|
||||
|
||||
let mut prefixs = prefixs.iter().collect::<Vec<_>>();
|
||||
prefixs.remove(0);
|
||||
prefixs
|
||||
.into_iter()
|
||||
.map(|x| x.map(|v| v.as_str().to_string()))
|
||||
.collect()
|
||||
}
|
||||
320
src/pipeline/element.rs
Normal file
320
src/pipeline/element.rs
Normal file
@ -0,0 +1,320 @@
|
||||
use super::{offscreen_renderer::CanvasWrapper, Dispatcher, Pipeline};
|
||||
use crate::components::app::AppCommand;
|
||||
use crate::components::ControlPanelInputMsg;
|
||||
use crate::coords::cms::CMS;
|
||||
use crate::data::MetaInfo;
|
||||
use crate::errors::{PipelineError, RenderError};
|
||||
use crate::RUNTIME;
|
||||
use crate::{coords::Range, widgets::widget::Widget};
|
||||
use chrono::{DateTime, Utc};
|
||||
use femtovg::{renderer::OpenGl, Canvas, ImageId};
|
||||
use futures::StreamExt;
|
||||
use glib::PropertyGet;
|
||||
use std::any::Any;
|
||||
use std::borrow::Borrow;
|
||||
use std::collections::{BTreeMap, HashMap};
|
||||
use std::fmt::Formatter;
|
||||
use std::rc::Rc;
|
||||
use std::sync::atomic::AtomicUsize;
|
||||
use std::{
|
||||
cell::{Ref, RefCell},
|
||||
fmt::Debug,
|
||||
future::Future,
|
||||
pin::Pin,
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
use tokio::sync::{
|
||||
oneshot::{channel, Receiver, Sender},
|
||||
Notify,
|
||||
};
|
||||
use tracing::Instrument;
|
||||
|
||||
pub type ElementID = usize;
|
||||
static ELEMENT_ID: AtomicUsize = AtomicUsize::new(0);
|
||||
|
||||
pub type Data = Box<dyn Any + Send + Sync>;
|
||||
pub type Buffer =
|
||||
Arc<Mutex<BTreeMap<DateTime<Utc>, (Option<Data>, Option<RenderResult>)>>>;
|
||||
type DrawFunc = Box<dyn Fn(&mut CanvasWrapper, &CMS)>;
|
||||
type IResult<T> = Result<T, PipelineError>;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Element {
|
||||
TimeSeries(TimeSeriesElement),
|
||||
Instant(InstantElement),
|
||||
}
|
||||
|
||||
impl Element {
|
||||
pub fn create_time_series<T: ElementImpl>(
|
||||
imp: T,
|
||||
dispatcher: Rc<Dispatcher>,
|
||||
key: String,
|
||||
cms: Arc<Mutex<CMS>>,
|
||||
) -> Self {
|
||||
Element::TimeSeries(TimeSeriesElement::new(imp, dispatcher, cms, key))
|
||||
}
|
||||
pub fn create_instant(
|
||||
_type: InstantElementDrawerType,
|
||||
dispatcher: Arc<Dispatcher>,
|
||||
key: String,
|
||||
) -> Self {
|
||||
Element::Instant(InstantElement::new(_type, dispatcher, key))
|
||||
}
|
||||
|
||||
pub fn id(&self) -> ElementID {
|
||||
match self {
|
||||
Element::TimeSeries(e) => e.id,
|
||||
Element::Instant(e) => e.id,
|
||||
}
|
||||
}
|
||||
pub fn key(&self) -> String {
|
||||
match self {
|
||||
Element::TimeSeries(e) => e.key.clone(),
|
||||
Element::Instant(e) => e.key.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
#[derive(Debug)]
|
||||
pub struct TimeSeriesElement {
|
||||
pub id: ElementID,
|
||||
pub key: String,
|
||||
cms: Arc<Mutex<CMS>>,
|
||||
imp: Arc<dyn ElementImpl>,
|
||||
registers: Arc<Mutex<HashMap<DateTime<Utc>, Vec<Arc<Notify>>>>>,
|
||||
pipeline: Pipeline,
|
||||
buffer: Buffer,
|
||||
dispatcher: Rc<Dispatcher>,
|
||||
}
|
||||
|
||||
pub enum InstantElementDrawerType {
|
||||
Draw(DrawFunc),
|
||||
Prepared(Target),
|
||||
}
|
||||
|
||||
impl Debug for InstantElementDrawerType {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("InstantElementDrawerType").finish()
|
||||
}
|
||||
}
|
||||
#[derive(Debug)]
|
||||
pub struct InstantElement {
|
||||
pub id: ElementID,
|
||||
pub key: String,
|
||||
draw_type: InstantElementDrawerType,
|
||||
dispatcher: Arc<Dispatcher>,
|
||||
}
|
||||
|
||||
pub trait ElementImpl: Debug + Send + Sync + 'static {
|
||||
fn render(&self, data: Box<dyn Any>, canvas: &mut CanvasWrapper, cms: &mut CMS) -> Target;
|
||||
}
|
||||
|
||||
impl InstantElement {
|
||||
fn new(_type: InstantElementDrawerType, dispatcher: Arc<Dispatcher>, key: String) -> Self {
|
||||
let id = ELEMENT_ID.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
|
||||
Self {
|
||||
id,
|
||||
key,
|
||||
draw_type: _type,
|
||||
dispatcher,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn render(&self, canvas: &mut CanvasWrapper, cms: &CMS) {
|
||||
match self.draw_type {
|
||||
InstantElementDrawerType::Draw(ref func) => {
|
||||
func(canvas, cms);
|
||||
}
|
||||
InstantElementDrawerType::Prepared(ref target) => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TimeSeriesElement {
|
||||
fn new<T: ElementImpl>(
|
||||
imp: T,
|
||||
dispatcher: Rc<Dispatcher>,
|
||||
cms: Arc<Mutex<CMS>>,
|
||||
key: String,
|
||||
) -> Self {
|
||||
let id = ELEMENT_ID.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
|
||||
let pipeline = Pipeline::new(20, key.clone());
|
||||
let buffer = Arc::new(Mutex::new(BTreeMap::new()));
|
||||
Self {
|
||||
id,
|
||||
key,
|
||||
imp: Arc::new(imp),
|
||||
cms,
|
||||
registers: Arc::new(Mutex::new(HashMap::new())),
|
||||
buffer,
|
||||
dispatcher,
|
||||
pipeline,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn register(
|
||||
&mut self,
|
||||
datetime: DateTime<Utc>,
|
||||
) -> IResult<Receiver<Result<RenderResult, RenderError>>> {
|
||||
use tokio::sync::Notify;
|
||||
use tokio::task;
|
||||
let notifer = Arc::new(Notify::new());
|
||||
let new_notifer = notifer.clone();
|
||||
let (sender, recv) = channel::<Result<RenderResult, RenderError>>();
|
||||
self.change_time(datetime)?;
|
||||
let buffer = self.buffer.lock().unwrap();
|
||||
if buffer.contains_key(&datetime) {
|
||||
let (_, target) = buffer.get(&datetime).unwrap();
|
||||
// Already in buffer
|
||||
if let Some(target) = target {
|
||||
sender.send(Ok(target.clone())).unwrap();
|
||||
} else {
|
||||
// self.register_noti(datetime, notifer);
|
||||
let buffer = self.buffer.clone();
|
||||
task::spawn_local(async move {
|
||||
new_notifer.notified().await;
|
||||
let result = buffer.lock().unwrap().get(&datetime).unwrap().1.clone();
|
||||
sender.send(Ok(result.unwrap())).unwrap();
|
||||
});
|
||||
}
|
||||
return Ok(recv);
|
||||
} else {
|
||||
return Err(PipelineError::DataError("No data found".to_string()));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_cms(&mut self, cms: Arc<Mutex<CMS>>) {
|
||||
self.cms = cms;
|
||||
}
|
||||
|
||||
fn register_noti(&mut self, datetime: DateTime<Utc>, noti: Arc<Notify>) {
|
||||
self.registers
|
||||
.lock()
|
||||
.unwrap()
|
||||
.entry(datetime)
|
||||
.or_insert_with(Vec::new)
|
||||
.push(noti);
|
||||
}
|
||||
|
||||
pub fn change_time(&mut self, date_time: DateTime<Utc>) -> IResult<()> {
|
||||
let imp = self.imp.clone();
|
||||
let tasks = self.pipeline.set_current(
|
||||
date_time,
|
||||
true,
|
||||
3,
|
||||
Arc::new(move |canvas, cms| imp.render(Box::new(()), canvas, cms)),
|
||||
self.cms.clone(),
|
||||
);
|
||||
|
||||
let tasks = tasks.map(|tms| tms.into_iter().map(|time| (time, (None, None))));
|
||||
if let Some(tasks) = tasks {
|
||||
if tasks.len() == 0 {
|
||||
return Err(PipelineError::DataError("No data found".to_string()));
|
||||
}
|
||||
let mut buffer = self.buffer.lock().unwrap();
|
||||
buffer.extend(tasks);
|
||||
}
|
||||
|
||||
let buffer = self.buffer.clone();
|
||||
let registers = self.registers.clone();
|
||||
let listening_func = self.pipeline.listening(move |recv, idx| {
|
||||
let buffer = buffer.clone();
|
||||
let registers = registers.clone();
|
||||
Box::pin(async move {
|
||||
let registers = registers;
|
||||
let (dt, result) = recv.await.unwrap();
|
||||
if let Ok(result) = result {
|
||||
let mut buffer = buffer.lock().unwrap();
|
||||
(*(&mut buffer.get_mut(&dt).unwrap())).1 = Some(result);
|
||||
}
|
||||
|
||||
{
|
||||
registers
|
||||
.lock()
|
||||
.unwrap()
|
||||
.get_mut(&dt)
|
||||
.unwrap()
|
||||
.into_iter()
|
||||
.for_each(|n| {
|
||||
n.notify_waiters();
|
||||
});
|
||||
}
|
||||
})
|
||||
});
|
||||
let runner = Pipeline::run(&mut self.pipeline);
|
||||
RUNTIME.spawn(listening_func);
|
||||
RUNTIME.spawn(runner);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn insert_data(&mut self, time: DateTime<Utc>, data: Box<dyn Any + Send + Sync>) {
|
||||
let mut buffer = self.buffer.lock().unwrap();
|
||||
buffer.insert(time, (Some(data), None));
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct RenderResult {
|
||||
target: Target,
|
||||
meta_info: MetaInfo,
|
||||
}
|
||||
|
||||
impl RenderResult {
|
||||
pub fn new(target: Target, meta_info: MetaInfo) -> Self {
|
||||
Self { target, meta_info }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, PartialOrd)]
|
||||
pub struct Target {
|
||||
pub target: TargetType,
|
||||
pub thumbnail: Option<gtk::gdk::Texture>,
|
||||
pub width: f32,
|
||||
pub height: f32,
|
||||
pub bounds: (Range, Range),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord)]
|
||||
pub enum TargetType {
|
||||
ImageId(ImageId),
|
||||
Mem(Vec<u8>),
|
||||
}
|
||||
|
||||
impl Target {
|
||||
pub fn new(
|
||||
target: TargetType,
|
||||
width: f32,
|
||||
height: f32,
|
||||
bounds: (Range, Range),
|
||||
thumbnail: Option<gtk::gdk::Texture>,
|
||||
) -> Self {
|
||||
Self {
|
||||
target,
|
||||
width,
|
||||
height,
|
||||
bounds,
|
||||
thumbnail,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn size(&self, cms: &CMS) -> (f32, f32) {
|
||||
let (x, y) = self.bounds;
|
||||
|
||||
let p1 = (x.0, y.0);
|
||||
let p2 = (x.1, y.1);
|
||||
|
||||
let (x1, y1) = cms.map(p1).unwrap();
|
||||
let (x2, y2) = cms.map(p2).unwrap();
|
||||
|
||||
((x2 - x1).abs(), (y2 - y1).abs())
|
||||
}
|
||||
|
||||
pub fn origin(&self, cms: &CMS) -> (f32, f32) {
|
||||
let (x, y) = self.bounds;
|
||||
let p1 = (x.0, y.1);
|
||||
cms.map(p1).unwrap()
|
||||
}
|
||||
|
||||
pub fn set_target(&mut self, target: TargetType) {
|
||||
self.target = target;
|
||||
}
|
||||
}
|
||||
57
src/pipeline/element_impl.rs
Normal file
57
src/pipeline/element_impl.rs
Normal file
@ -0,0 +1,57 @@
|
||||
use super::predefined::GridFieldRenderer;
|
||||
use super::renders::DataRenderer;
|
||||
use crate::data::Radar2d;
|
||||
use crate::pipeline::element::{ElementImpl, Target};
|
||||
use crate::pipeline::offscreen_renderer::CanvasWrapper;
|
||||
use crate::widgets::predefined::color_mapper::ColorMapper;
|
||||
use crate::widgets::{LayerImpl, CMS};
|
||||
use num_traits::{AsPrimitive, FromPrimitive, Num, NumOps};
|
||||
use std::any::Any;
|
||||
use std::fmt::Debug;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct GridElementImpl<CMAP, T>
|
||||
where
|
||||
T: Num + NumOps + PartialOrd + FromPrimitive + AsPrimitive<f64> + Send + Sync + Debug,
|
||||
CMAP: ColorMapper<T>,
|
||||
{
|
||||
renderer: GridFieldRenderer<CMAP, T>,
|
||||
}
|
||||
|
||||
impl<CMAP, T> GridElementImpl<CMAP, T>
|
||||
where
|
||||
T: Num + NumOps + PartialOrd + FromPrimitive + AsPrimitive<f64> + Send + Sync + Debug,
|
||||
CMAP: ColorMapper<T>,
|
||||
{
|
||||
pub fn new(color: CMAP) -> Self {
|
||||
Self {
|
||||
renderer: GridFieldRenderer::new(color),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<CMAP, T> ElementImpl for GridElementImpl<CMAP, T>
|
||||
where
|
||||
T: Num
|
||||
+ NumOps
|
||||
+ PartialOrd
|
||||
+ Copy
|
||||
+ Clone
|
||||
+ Debug
|
||||
+ Send
|
||||
+ Sync
|
||||
+ FromPrimitive
|
||||
+ AsPrimitive<f64>,
|
||||
CMAP: ColorMapper<T> + Debug + 'static,
|
||||
{
|
||||
fn render(
|
||||
&self,
|
||||
data: Box<dyn Any>,
|
||||
canvas: &mut CanvasWrapper,
|
||||
cms: &mut crate::coords::cms::CMS,
|
||||
) -> crate::pipeline::element::Target {
|
||||
let data = data.downcast::<Radar2d<T>>().unwrap();
|
||||
let result = self.renderer.render(canvas, cms, &data, (3000.0, 3000.0));
|
||||
result
|
||||
}
|
||||
}
|
||||
@ -1,134 +1,13 @@
|
||||
use anyhow::{Ok, Result};
|
||||
use femtovg::{self};
|
||||
use geo_types::{line_string, LineString};
|
||||
use image::RgbImage;
|
||||
use ndarray::parallel::prelude::*;
|
||||
use ndarray::{Array2, ArrayView2};
|
||||
use num_traits::{Num, AsPrimitive, FromPrimitive};
|
||||
pub mod dispatcher;
|
||||
pub mod element;
|
||||
mod element_impl;
|
||||
mod new_pipeline;
|
||||
pub mod offscreen_renderer;
|
||||
mod predefined;
|
||||
mod renders;
|
||||
pub mod utils;
|
||||
pub mod pool;
|
||||
pub mod render_pipeline;
|
||||
|
||||
use crate::{
|
||||
coords::Mapper,
|
||||
data::{Radar2d, RadarData2d},
|
||||
};
|
||||
|
||||
pub struct Color(femtovg::Color);
|
||||
|
||||
impl Color {
|
||||
pub fn new(r: u8, g: u8, b: u8, a: u8) -> Self {
|
||||
Self(femtovg::Color::rgba(r, g, b, a))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<femtovg::Color> for Color {
|
||||
fn from(value: femtovg::Color) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Pipeline<T> {
|
||||
type Output;
|
||||
fn run(&self, input: T) -> Result<Self::Output>;
|
||||
}
|
||||
pub struct ProjPipe<'a> {
|
||||
pub mapper: &'a Mapper,
|
||||
}
|
||||
|
||||
impl<'a> ProjPipe<'a> {
|
||||
pub fn new(mapper: &'a Mapper) -> Self {
|
||||
Self { mapper }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'b: 'a, T, Raw> Pipeline<&'b RadarData2d<T, Raw>> for ProjPipe<'a>
|
||||
where
|
||||
T: Num + Clone + PartialEq + PartialOrd,
|
||||
Raw: ndarray::Data<Elem = T> + Clone + ndarray::RawDataClone,
|
||||
{
|
||||
type Output = Array2<LineString>;
|
||||
|
||||
fn run(&self, input: &'b RadarData2d<T, Raw>) -> Result<Self::Output> {
|
||||
let dim1 = input.dim1.view();
|
||||
let dim2 = input.dim2.view();
|
||||
|
||||
let shape = input.data.shape();
|
||||
let mut polygons = Vec::with_capacity(dim1.len() * dim2.len());
|
||||
|
||||
let d1_dpi = dim1[1] - dim1[0];
|
||||
let d2_dpi = dim2[1] - dim2[0];
|
||||
|
||||
for d1 in dim1 {
|
||||
for d2 in dim2 {
|
||||
let line: LineString = vec![
|
||||
(*d1, *d2),
|
||||
(*d1 + d1_dpi, *d2),
|
||||
(*d1 + d1_dpi, *d2 + d2_dpi),
|
||||
(*d1, *d2 + d2_dpi),
|
||||
(*d1, *d2),
|
||||
]
|
||||
.into();
|
||||
let projed_polygon = self.mapper.ring_map(&line)?;
|
||||
polygons.push(projed_polygon);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Array2::from_shape_vec([shape[0], shape[1]], polygons)?)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ShadePipe<T: Num> {
|
||||
colors: Vec<Color>,
|
||||
levels: Vec<T>,
|
||||
}
|
||||
|
||||
struct ShaderPrepare(Array2<LineString>);
|
||||
|
||||
impl<T: Num + PartialOrd> ShadePipe<T> {
|
||||
pub fn new(levels: Vec<T>, colors: Vec<Color>) -> Self {
|
||||
Self { colors, levels }
|
||||
}
|
||||
|
||||
pub fn get_color(&self, v: T) -> &Color {
|
||||
let len = self.levels.len();
|
||||
|
||||
let mut left = 0;
|
||||
let mut right = len - 1;
|
||||
|
||||
while left < right - 1 {
|
||||
let middle = (right + left) / 2;
|
||||
if v > self.levels[middle] {
|
||||
left = middle;
|
||||
} else {
|
||||
right = middle;
|
||||
}
|
||||
}
|
||||
|
||||
&self.colors[left]
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T, Raw> Pipeline<&'a RadarData2d<T, Raw>> for ShadePipe<T>
|
||||
where
|
||||
T: Num + PartialEq + PartialOrd + Clone + FromPrimitive,
|
||||
Raw: ndarray::Data<Elem = T> + Clone + ndarray::RawDataClone,
|
||||
{
|
||||
type Output = Array2<Option<femtovg::Color>>;
|
||||
|
||||
fn run(&self, input: &'a RadarData2d<T, Raw>) -> Result<Array2<Option<femtovg::Color>>> {
|
||||
let data = input.data.view();
|
||||
|
||||
let result = data.mapv(|v| {
|
||||
if T::from_i8(-125).unwrap() == v {
|
||||
None
|
||||
} else {
|
||||
let color = self.get_color(v);
|
||||
Some(color.0)
|
||||
}
|
||||
});
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
pub use dispatcher::Dispatcher;
|
||||
pub use element::RenderResult;
|
||||
pub use new_pipeline::Pipeline;
|
||||
pub use offscreen_renderer::OffscreenRenderer;
|
||||
|
||||
208
src/pipeline/new_pipeline.rs
Normal file
208
src/pipeline/new_pipeline.rs
Normal file
@ -0,0 +1,208 @@
|
||||
use super::{
|
||||
dispatcher::Dispatcher,
|
||||
element::RenderResult,
|
||||
offscreen_renderer::{CanvasWrapper, OffscreenRenderer},
|
||||
utils::data_to_element,
|
||||
};
|
||||
use crate::coords::cms::CMS;
|
||||
use crate::pipeline::element::Target;
|
||||
use crate::{
|
||||
coords::{proj::Mercator, Mapper},
|
||||
data::MetaInfo,
|
||||
errors::RenderError,
|
||||
widgets::Layer,
|
||||
PLUGIN_MANAGER,
|
||||
};
|
||||
use chrono::prelude::*;
|
||||
use femtovg::{renderer::OpenGl, Canvas, ImageId};
|
||||
use futures::{future::BoxFuture, Future};
|
||||
use smallvec::SmallVec;
|
||||
use std::fmt::{Debug, Formatter};
|
||||
use std::{
|
||||
cell::RefCell,
|
||||
rc::Rc,
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
use tokio::{
|
||||
sync::{mpsc, oneshot},
|
||||
task,
|
||||
};
|
||||
|
||||
// #[derive(Clone, Debug)]
|
||||
// pub struct RenderResult {
|
||||
// target: Target,
|
||||
// meta_info: MetaInfo,
|
||||
// }
|
||||
|
||||
// impl RenderResult {
|
||||
// pub fn new(target: Target, meta_info: MetaInfo) -> Self {
|
||||
// Self { target, meta_info }
|
||||
// }
|
||||
// }
|
||||
|
||||
type RenderR = Result<RenderResult, RenderError>;
|
||||
pub struct Pipeline {
|
||||
pool: Vec<BoxFuture<'static, ()>>,
|
||||
results: SmallVec<[RenderResult; 20]>,
|
||||
dispatcher: Option<Rc<Dispatcher>>,
|
||||
handlers: Option<Vec<oneshot::Receiver<(DateTime<Utc>, RenderR)>>>,
|
||||
handler: Option<mpsc::Receiver<RenderR>>,
|
||||
sender: Option<mpsc::Sender<RenderR>>,
|
||||
key: String,
|
||||
}
|
||||
|
||||
impl Debug for Pipeline {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("Pipeline").finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl Pipeline {
|
||||
pub fn new(len: usize, key: String) -> Self {
|
||||
Self {
|
||||
pool: Vec::new(),
|
||||
results: SmallVec::new(),
|
||||
dispatcher: None,
|
||||
handlers: None,
|
||||
handler: None,
|
||||
sender: None,
|
||||
key,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_dispatcher(&mut self, dispatcher: Rc<Dispatcher>) {
|
||||
self.dispatcher = Some(dispatcher);
|
||||
}
|
||||
|
||||
pub fn init(&mut self) -> &mut Self {
|
||||
self
|
||||
}
|
||||
|
||||
pub fn set_current(
|
||||
&mut self,
|
||||
current_time: DateTime<Utc>,
|
||||
check_existed: bool,
|
||||
max_retry_time: usize,
|
||||
task: Arc<dyn Fn(&mut CanvasWrapper, &mut CMS) -> Target + Send + Sync>,
|
||||
cms: Arc<Mutex<CMS>>,
|
||||
) -> Option<Vec<DateTime<Utc>>> {
|
||||
let paths = {
|
||||
self.dispatcher.as_ref().unwrap().get_path(
|
||||
&self.key,
|
||||
current_time,
|
||||
check_existed,
|
||||
max_retry_time,
|
||||
)
|
||||
};
|
||||
|
||||
if let Some(paths) = paths {
|
||||
let mut recvs = Vec::new();
|
||||
let mut result = Vec::new();
|
||||
for (path, datetime) in paths.into_iter() {
|
||||
let (sender, mut receiver) = oneshot::channel();
|
||||
self.add_task(
|
||||
datetime,
|
||||
self.worker(datetime, task.clone(), cms.clone(), path),
|
||||
sender,
|
||||
);
|
||||
recvs.push(receiver);
|
||||
result.push(datetime);
|
||||
}
|
||||
self.handlers.replace(recvs);
|
||||
Some(result)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn work_num(&self) -> usize {
|
||||
self.pool.len()
|
||||
}
|
||||
|
||||
fn worker(
|
||||
&self,
|
||||
datetime: DateTime<Utc>,
|
||||
task: Arc<dyn Fn(&mut CanvasWrapper, &mut CMS) -> Target + Send + Sync>,
|
||||
cms: Arc<Mutex<CMS>>,
|
||||
path: impl AsRef<str> + Send + 'static,
|
||||
) -> BoxFuture<'static, RenderR> {
|
||||
Box::pin(async move {
|
||||
let loader = PLUGIN_MANAGER.get_plugin_by_name("etws_loader").unwrap();
|
||||
let mut loaded_data = loader.load(path.as_ref().into()).unwrap();
|
||||
let first_block = loaded_data.blocks.pop().unwrap();
|
||||
|
||||
let handle = task::spawn_blocking(move || {
|
||||
let mut offscreen_renderer = OffscreenRenderer::new(3000, 3000).unwrap();
|
||||
let mut canvas_wrapper = offscreen_renderer.create_canvas();
|
||||
let mut cms = cms.lock().unwrap();
|
||||
let target = task(&mut canvas_wrapper, &mut cms);
|
||||
target
|
||||
});
|
||||
|
||||
let target = handle.await.unwrap();
|
||||
Ok(RenderResult::new(target, loaded_data.meta.into()))
|
||||
})
|
||||
}
|
||||
|
||||
pub fn add_task<TASK>(
|
||||
&mut self,
|
||||
time: DateTime<Utc>,
|
||||
task: TASK,
|
||||
tx: oneshot::Sender<(DateTime<Utc>, RenderR)>,
|
||||
) where
|
||||
TASK: Future<Output = RenderR> + 'static + Send,
|
||||
{
|
||||
let future = async move {
|
||||
let data = task.await;
|
||||
tx.send((time, data)).unwrap();
|
||||
};
|
||||
|
||||
self.pool.push(Box::pin(future));
|
||||
}
|
||||
|
||||
pub fn run(value: &mut Self) -> BoxFuture<'static, ()> {
|
||||
let pool = value.get_pool();
|
||||
Box::pin(async move {
|
||||
for f in pool.into_iter() {
|
||||
task::spawn(f);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn listening<F>(&mut self, f: F) -> BoxFuture<'static, ()>
|
||||
where
|
||||
F: Fn(oneshot::Receiver<(DateTime<Utc>, RenderR)>, usize) -> BoxFuture<'static, ()>
|
||||
+ Send
|
||||
+ 'static
|
||||
+ Sync,
|
||||
{
|
||||
let mut handler = self.handlers.take().unwrap();
|
||||
Box::pin(async move {
|
||||
let l = handler.into_iter().enumerate().map(|(h, i)| f(i, h));
|
||||
for f in l {
|
||||
task::spawn(f);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn listening_one_by_one<F>(&mut self, f: Vec<F>) -> BoxFuture<'static, ()>
|
||||
where
|
||||
F: FnOnce(oneshot::Receiver<(DateTime<Utc>, RenderR)>) -> BoxFuture<'static, ()>
|
||||
+ Send
|
||||
+ 'static
|
||||
+ Sync,
|
||||
{
|
||||
let mut handler = self.handlers.take().unwrap();
|
||||
Box::pin(async move {
|
||||
for (h, f) in handler.into_iter().zip(f) {
|
||||
task::spawn(f(h));
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_pool(&mut self) -> Vec<BoxFuture<'static, ()>> {
|
||||
use std::mem::replace;
|
||||
let pool = replace(&mut self.pool, Vec::new());
|
||||
pool
|
||||
}
|
||||
}
|
||||
@ -1,4 +1,3 @@
|
||||
use crate::widgets::Target;
|
||||
|
||||
use super::utils::*;
|
||||
use euclid::Size2D;
|
||||
@ -10,10 +9,12 @@ use std::num::NonZeroU32;
|
||||
use std::ops::{Deref, DerefMut};
|
||||
use std::sync::{Mutex, RwLock};
|
||||
use std::{cell::RefCell, sync::Arc};
|
||||
use std::fmt::{Debug, Formatter};
|
||||
use surfman::{
|
||||
device, Adapter, Connection, Context, ContextAttributeFlags, Device, Error, GLApi,
|
||||
NativeConnection, NativeDevice,
|
||||
};
|
||||
use crate::pipeline::element::Target;
|
||||
|
||||
pub struct OffscreenRenderer {
|
||||
context: Arc<RwLock<Context>>,
|
||||
@ -23,6 +24,12 @@ pub struct OffscreenRenderer {
|
||||
size: (i32, i32), // canvas: Arc<Mutex<CanvasWrapper>>,
|
||||
}
|
||||
|
||||
impl Debug for OffscreenRenderer {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("OffscreenRenderer").finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl OffscreenRenderer {
|
||||
pub fn new(width: i32, height: i32) -> Result<Self, surfman::Error> {
|
||||
let connection = Connection::new()?;
|
||||
@ -103,7 +110,8 @@ impl OffscreenRenderer {
|
||||
CanvasWrapper::new(canvas)
|
||||
}
|
||||
|
||||
pub fn get_img(&self, target: Target) {}
|
||||
pub fn get_img(&self, target: Target) {
|
||||
}
|
||||
|
||||
pub fn get_mem_img(&self) -> Vec<u8> {
|
||||
let (w, h) = self.size;
|
||||
@ -132,7 +140,6 @@ impl Drop for OffscreenRenderer {
|
||||
let mut context = self.context.write().unwrap();
|
||||
self.device.destroy_context(&mut context).unwrap();
|
||||
let _ = self;
|
||||
println!("OffscreenRenderer dropped");
|
||||
}
|
||||
}
|
||||
|
||||
@ -140,6 +147,13 @@ unsafe impl Send for OffscreenRenderer {}
|
||||
unsafe impl Sync for OffscreenRenderer {}
|
||||
pub struct CanvasWrapper(femtovg::Canvas<OpenGl>);
|
||||
|
||||
impl Debug for CanvasWrapper {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("CanvasWrapper").finish()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
impl CanvasWrapper {
|
||||
fn new(canvas: femtovg::Canvas<OpenGl>) -> Self {
|
||||
Self(canvas)
|
||||
|
||||
@ -1,55 +0,0 @@
|
||||
use crate::errors::PoolError;
|
||||
use chrono::prelude::*;
|
||||
use smallvec::SmallVec;
|
||||
use sorted_vec::{SortedSet, SortedVec};
|
||||
use std::collections::VecDeque;
|
||||
|
||||
type PResult<T> = Result<T, PoolError>;
|
||||
|
||||
pub struct DataPool<T>
|
||||
where
|
||||
T: Send + Sync,
|
||||
{
|
||||
datetime_pool: SortedSet<(DateTime<Utc>, usize)>,
|
||||
items: Vec<T>,
|
||||
current: Option<usize>,
|
||||
len: usize,
|
||||
}
|
||||
|
||||
impl<T> DataPool<T>
|
||||
where
|
||||
T: Send + Sync + Ord,
|
||||
{
|
||||
pub fn new(len: usize) -> Self {
|
||||
DataPool {
|
||||
datetime_pool: SortedSet::with_capacity(len),
|
||||
items: Vec::with_capacity(len),
|
||||
current: None,
|
||||
len,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add(&mut self, item: T, datetime: DateTime<Utc>) -> PResult<()> {
|
||||
let len = self.items.len();
|
||||
if len == self.len {
|
||||
return Err(PoolError::PoolFull);
|
||||
}
|
||||
self.items.push(item);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn set_current(&mut self, datetime: DateTime<Utc>) -> PResult<()> {
|
||||
if self.datetime_pool.is_empty() {
|
||||
return Err(PoolError::PoolInitialized("Pool is empty"));
|
||||
}
|
||||
if let Some(t) = self.datetime_pool.iter().position(|(x, _)| *x == datetime) {
|
||||
let pre = (t - self.len / 2).max(0);
|
||||
let post = (t + self.len / 2).min(self.len - 1);
|
||||
self.datetime_pool.drain(pre..post);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn next(&self) {}
|
||||
}
|
||||
183
src/pipeline/predefined/grid_field_renderer.rs
Normal file
183
src/pipeline/predefined/grid_field_renderer.rs
Normal file
@ -0,0 +1,183 @@
|
||||
use super::super::renders::DataRenderer;
|
||||
use crate::coords::cms::CMS;
|
||||
use crate::pipeline::element::{Target, TargetType};
|
||||
use crate::widgets::predefined::color_mapper::ColorMapper;
|
||||
use crate::{data::Radar2d, utils::meshgrid};
|
||||
use femtovg::ImageInfo;
|
||||
use femtovg::{
|
||||
renderer::OpenGl, Canvas, ImageFlags, Paint, Path, PixelFormat::Rgba8, RenderTarget,
|
||||
};
|
||||
use geo_types::LineString;
|
||||
use gl::types::GLvoid;
|
||||
use image::{imageops::resize, ImageBuffer, Rgba};
|
||||
use ndarray::ArrayView2;
|
||||
use num_traits::{AsPrimitive, FromPrimitive, Num, NumOps};
|
||||
use std::{fmt::Debug, io::Cursor, marker::PhantomData};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct GridFieldRenderer<CMAP, T>
|
||||
where
|
||||
T: NumOps + PartialOrd + FromPrimitive + AsPrimitive<f64>,
|
||||
CMAP: ColorMapper<T>,
|
||||
{
|
||||
cmap: CMAP,
|
||||
value_phantom: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<T: NumOps + PartialOrd + Copy + FromPrimitive + AsPrimitive<f64>, CMAP: ColorMapper<T>>
|
||||
GridFieldRenderer<CMAP, T>
|
||||
{
|
||||
pub fn new(cmap: CMAP) -> Self {
|
||||
Self {
|
||||
cmap,
|
||||
value_phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
fn draw_2d(
|
||||
&self,
|
||||
canvas: &mut femtovg::Canvas<femtovg::renderer::OpenGl>,
|
||||
cms: &CMS,
|
||||
data: ArrayView2<T>,
|
||||
dims: (ArrayView2<f64>, ArrayView2<f64>),
|
||||
window_size: (f32, f32),
|
||||
fill_value: T,
|
||||
) {
|
||||
let shape = data.shape();
|
||||
let (rows, cols) = (shape[0], shape[1]);
|
||||
let (dim1, dim2) = dims;
|
||||
|
||||
let d1_s = dim1[[0, 0]];
|
||||
let d1_e = dim1[[0, cols - 1]];
|
||||
|
||||
let d2_s = dim2[[0, 0]];
|
||||
let d2_e = dim2[[rows - 1, 0]];
|
||||
|
||||
for r in 0..rows - 1 {
|
||||
for c in 0..cols - 1 {
|
||||
let lb_lat = dim2[[r, c]];
|
||||
let lb_lon = dim1[[r, c]];
|
||||
|
||||
let rt_lat = dim2[[r + 1, c + 1]];
|
||||
let rt_lon = dim1[[r + 1, c + 1]];
|
||||
let cell: LineString = vec![
|
||||
(lb_lon, lb_lat),
|
||||
(rt_lon + 0.001, lb_lat),
|
||||
(rt_lon + 0.001, rt_lat),
|
||||
(lb_lon, rt_lat + 0.001),
|
||||
(lb_lon, lb_lat + 0.001),
|
||||
]
|
||||
.into();
|
||||
|
||||
let v = &data[[r, c]];
|
||||
let mapped_color = self.cmap.map_value_to_color(*v, fill_value);
|
||||
|
||||
if None == mapped_color {
|
||||
continue;
|
||||
}
|
||||
|
||||
let mapped_ring = cms.ring_map(&cell).unwrap();
|
||||
|
||||
let mut path = Path::new();
|
||||
let mut points = mapped_ring.points();
|
||||
let first_point = points.next().unwrap();
|
||||
path.move_to(first_point.x(), first_point.y());
|
||||
|
||||
for point in points {
|
||||
path.line_to(point.x(), point.y());
|
||||
}
|
||||
path.close();
|
||||
canvas.fill_path(&path, &Paint::color(mapped_color.unwrap()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, CMAP> DataRenderer for GridFieldRenderer<CMAP, T>
|
||||
where
|
||||
T: Num + NumOps + PartialOrd + Copy + Clone + FromPrimitive + AsPrimitive<f64>,
|
||||
CMAP: ColorMapper<T>,
|
||||
{
|
||||
type Data = Radar2d<T>;
|
||||
|
||||
fn render(
|
||||
&self,
|
||||
canvas: &mut Canvas<OpenGl>,
|
||||
mut cms: &mut CMS,
|
||||
data: &Self::Data,
|
||||
size: (f32, f32),
|
||||
) -> Target {
|
||||
let (w, h) = size;
|
||||
let new_img = canvas
|
||||
.create_image_empty(w as usize, h as usize, Rgba8, ImageFlags::empty())
|
||||
.expect("Can't Create Image");
|
||||
|
||||
canvas.image_size(new_img).unwrap();
|
||||
canvas.set_render_target(RenderTarget::Image(new_img));
|
||||
|
||||
let _data = data.data.view();
|
||||
let (_dim1, _dim2) = meshgrid(data.dim1.view(), data.dim2.view());
|
||||
|
||||
let lat_start = data.dim2.view().first().unwrap().clone();
|
||||
let lat_end = data.dim2.view().last().unwrap().clone();
|
||||
|
||||
let lon_start = data.dim1.view().first().unwrap().clone();
|
||||
let lon_end = data.dim1.view().last().unwrap().clone();
|
||||
|
||||
cms.set_lat_range(lat_start..lat_end);
|
||||
cms.set_lon_range(lon_start..lon_end);
|
||||
|
||||
self.draw_2d(
|
||||
canvas,
|
||||
&cms,
|
||||
_data,
|
||||
(_dim1.view(), _dim2.view()),
|
||||
(w, h),
|
||||
data.fill_value,
|
||||
);
|
||||
canvas.flush();
|
||||
let mut pixels: Vec<u8> = vec![0; w as usize * h as usize * 4];
|
||||
unsafe {
|
||||
gl::ReadPixels(
|
||||
0,
|
||||
0,
|
||||
w as i32,
|
||||
h as i32,
|
||||
gl::RGBA,
|
||||
gl::UNSIGNED_BYTE,
|
||||
pixels.as_mut_ptr() as *mut GLvoid,
|
||||
);
|
||||
debug_assert_eq!(gl::GetError(), gl::NO_ERROR);
|
||||
}
|
||||
|
||||
let img: ImageBuffer<Rgba<u8>, Vec<u8>> = ImageBuffer::from_raw(w as u32, h as u32, pixels)
|
||||
.expect("Failed to create ImageBuffer");
|
||||
let thumbnail = resize(&img, 500, 500, image::imageops::FilterType::Lanczos3);
|
||||
let mut thumb_buffer = Cursor::new(Vec::new());
|
||||
img.write_to(&mut thumb_buffer, image::ImageOutputFormat::Png)
|
||||
.expect("Failed to write PNG buffer");
|
||||
let thumb_data = thumb_buffer.into_inner();
|
||||
let thumbnail_tex =
|
||||
gtk::gdk::Texture::from_bytes(>k::glib::Bytes::from(&thumb_data)).unwrap();
|
||||
|
||||
// 将 ImageBuffer 编码为 PNG
|
||||
let mut png_buffer = Cursor::new(Vec::new());
|
||||
img.write_to(&mut png_buffer, image::ImageOutputFormat::Bmp)
|
||||
.expect("Failed to write PNG buffer");
|
||||
|
||||
let png_data = png_buffer.into_inner();
|
||||
|
||||
let d1_start = (data.dim1.view()).first().unwrap().clone();
|
||||
let d1_end = (data.dim1.view()).last().unwrap().clone();
|
||||
|
||||
let d2_start = data.dim2.view().first().unwrap().clone();
|
||||
let d2_end = data.dim2.view().last().unwrap().clone();
|
||||
|
||||
Target::new(
|
||||
TargetType::Mem(png_data),
|
||||
w,
|
||||
h,
|
||||
((d1_start, d1_end).into(), (d2_start, d2_end).into()),
|
||||
Some(thumbnail_tex),
|
||||
)
|
||||
}
|
||||
}
|
||||
3
src/pipeline/predefined/mod.rs
Normal file
3
src/pipeline/predefined/mod.rs
Normal file
@ -0,0 +1,3 @@
|
||||
mod grid_field_renderer;
|
||||
|
||||
pub use grid_field_renderer::*;
|
||||
@ -1,30 +1,38 @@
|
||||
use super::{
|
||||
dispatcher::Dispatcher,
|
||||
offscreen_renderer::{CanvasWrapper, OffscreenRenderer},
|
||||
};
|
||||
use crate::{
|
||||
coords::{cms::CMS, proj::Mercator, Mapper},
|
||||
data::MetaInfo,
|
||||
errors::RenderError,
|
||||
widgets::Layer,
|
||||
PLUGIN_MANAGER,
|
||||
};
|
||||
use chrono::prelude::*;
|
||||
use femtovg::{renderer::OpenGl, Canvas, ImageId};
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use crate::widgets::{Layer, Target, TargetType};
|
||||
|
||||
use super::offscreen_renderer::CanvasWrapper;
|
||||
use futures::{future::BoxFuture, Future};
|
||||
use smallvec::SmallVec;
|
||||
use std::{
|
||||
cell::RefCell,
|
||||
rc::Rc,
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
use tokio::{
|
||||
sync::{mpsc, oneshot},
|
||||
task,
|
||||
};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct RenderResult {
|
||||
pub meta: MetaInfo,
|
||||
pub layer: Layer,
|
||||
time: DateTime<Utc>,
|
||||
}
|
||||
|
||||
// impl Drop for RenderResult {
|
||||
// fn drop(&mut self) {
|
||||
// let mut canvas = self.canvas.lock().unwrap();
|
||||
// if let TargetType::ImageId(img) = self.img.target {
|
||||
// canvas.delete_image(img);
|
||||
// }
|
||||
// let _ = self;
|
||||
// }
|
||||
// }
|
||||
|
||||
impl RenderResult {
|
||||
pub fn new(layer: Layer, time: DateTime<Utc>) -> Self {
|
||||
Self { layer, time }
|
||||
pub fn new(layer: Layer, time: DateTime<Utc>, meta: MetaInfo) -> Self {
|
||||
Self { layer, time, meta }
|
||||
}
|
||||
|
||||
pub fn timestamp(&self) -> i64 {
|
||||
@ -35,3 +43,168 @@ impl RenderResult {
|
||||
self.time
|
||||
}
|
||||
}
|
||||
|
||||
type RenderR = Result<RenderResult, RenderError>;
|
||||
pub struct Pipeline {
|
||||
pool: Vec<BoxFuture<'static, ()>>,
|
||||
results: SmallVec<[RenderResult; 20]>,
|
||||
dispatcher: Option<Rc<RefCell<Dispatcher>>>,
|
||||
handlers: Option<Vec<oneshot::Receiver<RenderR>>>,
|
||||
handler: Option<mpsc::Receiver<RenderR>>,
|
||||
sender: Option<mpsc::Sender<RenderR>>,
|
||||
key: String,
|
||||
}
|
||||
|
||||
impl Pipeline {
|
||||
pub fn new(len: usize, key: String) -> Self {
|
||||
Self {
|
||||
pool: Vec::new(),
|
||||
results: SmallVec::new(),
|
||||
dispatcher: None,
|
||||
handlers: None,
|
||||
handler: None,
|
||||
sender: None,
|
||||
key,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_dispatcher(&mut self, dispatcher: Rc<RefCell<Dispatcher>>) {
|
||||
self.dispatcher = Some(dispatcher);
|
||||
}
|
||||
|
||||
pub fn init(&mut self) -> &mut Self {
|
||||
self
|
||||
}
|
||||
|
||||
pub fn set_current(
|
||||
&mut self,
|
||||
current_time: DateTime<Utc>,
|
||||
check_existed: bool,
|
||||
max_retry_time: usize,
|
||||
) -> Option<Vec<DateTime<Utc>>> {
|
||||
let paths = {
|
||||
let dispatcher = self.dispatcher.as_ref().unwrap();
|
||||
let dispatcher = dispatcher.borrow();
|
||||
dispatcher.get_path(&self.key, current_time, check_existed, max_retry_time)
|
||||
};
|
||||
if let Some(paths) = paths {
|
||||
let mut recvs = Vec::new();
|
||||
let mut result = Vec::new();
|
||||
for (path, datetime) in paths.into_iter() {
|
||||
let (sender, mut receiver) = oneshot::channel::<RenderR>();
|
||||
self.add_task(datetime.timestamp(), self.worker(datetime, path), sender);
|
||||
recvs.push(receiver);
|
||||
result.push(datetime);
|
||||
}
|
||||
self.handlers.replace(recvs);
|
||||
Some(result)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn work_num(&self) -> usize {
|
||||
self.pool.len()
|
||||
}
|
||||
|
||||
fn worker(
|
||||
&self,
|
||||
datetime: DateTime<Utc>,
|
||||
path: impl AsRef<str> + Send + 'static,
|
||||
) -> BoxFuture<'static, RenderR> {
|
||||
Box::pin(async move {
|
||||
let loader = PLUGIN_MANAGER.get_plugin_by_name("etws_loader").unwrap();
|
||||
let mut loaded_data = loader.load(path.as_ref().into()).unwrap();
|
||||
let first_block = loaded_data.blocks.pop().unwrap();
|
||||
if let Some((_, layer)) = data_to_layer(first_block) {
|
||||
let handle = task::spawn_blocking(move || {
|
||||
let mut offscreen_renderer = OffscreenRenderer::new(3000, 3000).unwrap();
|
||||
let canvas_wrapper = offscreen_renderer.create_canvas();
|
||||
let canvas_mutex = std::sync::Arc::new(std::sync::Mutex::new(canvas_wrapper));
|
||||
|
||||
let f = {
|
||||
let p = layer.get_prepare();
|
||||
let mut _p = p.lock().unwrap();
|
||||
_p.take()
|
||||
};
|
||||
|
||||
let target = if let Some(f) = f {
|
||||
let imp = layer.get_imp().unwrap();
|
||||
let map: Mapper = Mercator::default().into();
|
||||
let cms = CMS::new(map, (3000.0, 3000.0));
|
||||
let canvas = canvas_mutex.clone();
|
||||
let c = f(imp, canvas.clone(), cms);
|
||||
let canvas = canvas.lock().unwrap();
|
||||
Some(c)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
layer.set_render_target(target.unwrap());
|
||||
layer
|
||||
});
|
||||
let target = handle.await.unwrap();
|
||||
Ok(RenderResult::new(target, datetime, loaded_data.meta.into()))
|
||||
} else {
|
||||
println!("no layer");
|
||||
Err(RenderError::None)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn add_task<TASK>(&mut self, timestamp: i64, task: TASK, tx: oneshot::Sender<RenderR>)
|
||||
where
|
||||
TASK: Future<Output = RenderR> + 'static + Send,
|
||||
{
|
||||
let future = async move {
|
||||
let data = task.await;
|
||||
tx.send(data).unwrap();
|
||||
};
|
||||
|
||||
self.pool.push(Box::pin(future));
|
||||
}
|
||||
|
||||
pub fn run(value: &mut Self) -> BoxFuture<'static, ()> {
|
||||
let pool = value.get_pool();
|
||||
Box::pin(async move {
|
||||
for f in pool.into_iter() {
|
||||
task::spawn(f);
|
||||
// f.await;
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn listening<F>(&mut self, f: F) -> BoxFuture<'static, ()>
|
||||
where
|
||||
F: Fn(oneshot::Receiver<RenderR>, usize) -> BoxFuture<'static, ()> + Send + 'static + Sync,
|
||||
{
|
||||
let mut handler = self.handlers.take().unwrap();
|
||||
Box::pin(async move {
|
||||
let l = handler.into_iter().enumerate().map(|(h, i)| f(i, h));
|
||||
for f in l.into_iter() {
|
||||
f.await;
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn listening_one_by_one<F>(&mut self, f: Vec<F>) -> BoxFuture<'static, ()>
|
||||
where
|
||||
F: FnOnce(oneshot::Receiver<RenderR>) -> BoxFuture<'static, ()> + Send + 'static + Sync,
|
||||
{
|
||||
let mut handler = self.handlers.take().unwrap();
|
||||
Box::pin(async move {
|
||||
for (h, f) in handler.into_iter().zip(f) {
|
||||
task::spawn(f(h));
|
||||
// f(h).await;
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_pool(&mut self) -> Vec<BoxFuture<'static, ()>> {
|
||||
use std::mem::replace;
|
||||
let pool = replace(&mut self.pool, Vec::new());
|
||||
pool
|
||||
}
|
||||
|
||||
pub fn cancel_task(&mut self, timestamp: i64) {}
|
||||
}
|
||||
|
||||
14
src/pipeline/renders.rs
Normal file
14
src/pipeline/renders.rs
Normal file
@ -0,0 +1,14 @@
|
||||
use crate::coords::cms::CMS;
|
||||
use crate::pipeline::element::Target;
|
||||
use femtovg::{renderer::OpenGl, Canvas};
|
||||
|
||||
pub trait DataRenderer {
|
||||
type Data;
|
||||
fn render(
|
||||
&self,
|
||||
canvas: &mut Canvas<OpenGl>,
|
||||
cms: &mut CMS,
|
||||
data: &Self::Data,
|
||||
size: (f32, f32),
|
||||
) -> Target;
|
||||
}
|
||||
@ -1,30 +1,16 @@
|
||||
use super::render_pipeline::RenderResult;
|
||||
use super::{offscreen_renderer::OffscreenRenderer, pool::DataPool};
|
||||
use crate::components::app::Buffer;
|
||||
use crate::coords::proj::Mercator;
|
||||
use crate::coords::Mapper;
|
||||
use crate::widgets::{Render, Target, TargetType, CMS};
|
||||
use crate::CONFIG;
|
||||
use crate::{data::Radar2d, errors::RenderError, widgets::Layer, PLUGIN_MANAGER};
|
||||
use chrono::{prelude::*, Duration};
|
||||
use euclid::approxord::max;
|
||||
use futures::future::*;
|
||||
use radarg_plugin_interface::*;
|
||||
use regex::Regex;
|
||||
use smallvec::SmallVec;
|
||||
use std::cell::{Ref, RefCell};
|
||||
use std::rc::Rc;
|
||||
use std::sync::Mutex;
|
||||
use std::{
|
||||
borrow::Borrow,
|
||||
collections::{HashMap, VecDeque},
|
||||
future::Future,
|
||||
ops::Deref,
|
||||
pin::Pin,
|
||||
sync::Arc,
|
||||
use crate::{
|
||||
coords::cms::CMS, data::Radar2d, errors::RenderError,
|
||||
widgets::predefined::color_mapper::BoundaryNorm,
|
||||
};
|
||||
use chrono::{prelude::*, Duration};
|
||||
use radarg_plugin_interface::*;
|
||||
use std::sync::Arc;
|
||||
use std::{rc::Rc, sync::Mutex};
|
||||
use tracing::*;
|
||||
|
||||
use super::{
|
||||
element::Element, element_impl::GridElementImpl, predefined::GridFieldRenderer, Dispatcher,
|
||||
};
|
||||
use tokio::sync::{mpsc, oneshot};
|
||||
use tokio::task;
|
||||
|
||||
pub fn ck() {
|
||||
unsafe {
|
||||
@ -32,371 +18,15 @@ pub fn ck() {
|
||||
}
|
||||
}
|
||||
|
||||
type RenderR = Result<RenderResult, RenderError>;
|
||||
|
||||
pub struct Pipeline {
|
||||
pool: Vec<BoxFuture<'static, ()>>,
|
||||
results: SmallVec<[RenderResult; 20]>,
|
||||
dispatcher: Option<Rc<RefCell<Dispatcher>>>,
|
||||
handlers: Option<Vec<oneshot::Receiver<RenderR>>>,
|
||||
handler: Option<mpsc::Receiver<RenderR>>,
|
||||
sender: Option<mpsc::Sender<RenderR>>,
|
||||
key: String,
|
||||
}
|
||||
|
||||
impl Pipeline {
|
||||
pub fn new(len: usize, key: String) -> Self {
|
||||
Self {
|
||||
pool: Vec::new(),
|
||||
results: SmallVec::new(),
|
||||
dispatcher: None,
|
||||
handlers: None,
|
||||
handler: None,
|
||||
sender: None,
|
||||
key,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_dispatcher(&mut self, dispatcher: Rc<RefCell<Dispatcher>>) {
|
||||
self.dispatcher = Some(dispatcher);
|
||||
}
|
||||
|
||||
pub fn init(&mut self) -> &mut Self {
|
||||
self
|
||||
}
|
||||
|
||||
pub fn set_current(
|
||||
&mut self,
|
||||
current_time: DateTime<Utc>,
|
||||
check_existed: bool,
|
||||
max_retry_time: usize,
|
||||
) -> Option<Vec<DateTime<Utc>>> {
|
||||
let dispatcher = self.dispatcher.clone().unwrap();
|
||||
let dispatcher = dispatcher.borrow_mut();
|
||||
let paths = dispatcher.get_path(&self.key, current_time, check_existed, max_retry_time);
|
||||
|
||||
if let Some(paths) = paths {
|
||||
let mut recvs = Vec::new();
|
||||
|
||||
let mut result = Vec::new();
|
||||
for (path, datetime) in paths.into_iter() {
|
||||
// let sender = self.sender.clone().unwrap();
|
||||
let (sender, mut receiver) = oneshot::channel::<RenderR>();
|
||||
self.add_task(datetime.timestamp(), self.worker(datetime, path), sender);
|
||||
recvs.push(receiver);
|
||||
result.push(datetime);
|
||||
}
|
||||
self.handlers.replace(recvs);
|
||||
Some(result)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn work_num(&self) -> usize {
|
||||
// self.pool.as_ref().unwrap().len()
|
||||
self.pool.len()
|
||||
}
|
||||
|
||||
fn worker(
|
||||
&self,
|
||||
datetime: DateTime<Utc>,
|
||||
path: impl AsRef<str> + Send + 'static,
|
||||
) -> BoxFuture<'static, RenderR> {
|
||||
Box::pin(async move {
|
||||
let loader = PLUGIN_MANAGER.get_plugin_by_name("etws_loader").unwrap();
|
||||
let mut loaded_data = loader.load(path.as_ref().into()).unwrap();
|
||||
let first_block = loaded_data.blocks.pop().unwrap();
|
||||
if let Some((_, layer)) = data_to_layer(first_block) {
|
||||
let handle = task::spawn_blocking(move || {
|
||||
let mut offscreen_renderer = OffscreenRenderer::new(3000, 3000).unwrap();
|
||||
let canvas_wrapper = offscreen_renderer.create_canvas();
|
||||
let canvas_mutex = std::sync::Arc::new(std::sync::Mutex::new(canvas_wrapper));
|
||||
|
||||
let f = {
|
||||
let p = layer.get_prepare();
|
||||
let mut _p = p.lock().unwrap();
|
||||
_p.take()
|
||||
};
|
||||
|
||||
let target = if let Some(f) = f {
|
||||
let imp = layer.get_imp().unwrap();
|
||||
let map: Mapper = Mercator::default().into();
|
||||
let cms = CMS::new(map, (3000.0, 3000.0));
|
||||
let canvas = canvas_mutex.clone();
|
||||
let c = f(imp, canvas.clone(), cms);
|
||||
let canvas = canvas.lock().unwrap();
|
||||
Some(c)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
layer.set_render_target(target.unwrap());
|
||||
layer
|
||||
});
|
||||
let target = handle.await.unwrap();
|
||||
Ok(RenderResult::new(target, datetime))
|
||||
} else {
|
||||
println!("no layer");
|
||||
Err(RenderError::None)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn add_task<TASK>(&mut self, timestamp: i64, task: TASK, tx: oneshot::Sender<RenderR>)
|
||||
where
|
||||
TASK: Future<Output = RenderR> + 'static + Send,
|
||||
{
|
||||
let future = async move {
|
||||
let data = task.await;
|
||||
tx.send(data).unwrap();
|
||||
};
|
||||
|
||||
self.pool.push(Box::pin(future));
|
||||
}
|
||||
|
||||
pub fn run(value: &mut Self) -> BoxFuture<'static, ()> {
|
||||
let pool = value.get_pool();
|
||||
Box::pin(async move {
|
||||
for f in pool.into_iter() {
|
||||
task::spawn(f);
|
||||
// f.await;
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn listening<F>(&mut self, f: F) -> BoxFuture<'static, ()>
|
||||
where
|
||||
F: Fn(oneshot::Receiver<RenderR>, usize) -> BoxFuture<'static, ()> + Send + 'static + Sync,
|
||||
{
|
||||
let mut handler = self.handlers.take().unwrap();
|
||||
Box::pin(async move {
|
||||
let l = handler.into_iter().enumerate().map(|(h, i)| f(i, h));
|
||||
for f in l.into_iter() {
|
||||
f.await;
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn listening_one_by_one<F>(&mut self, f: Vec<F>) -> BoxFuture<'static, ()>
|
||||
where
|
||||
F: FnOnce(oneshot::Receiver<RenderR>) -> BoxFuture<'static, ()> + Send + 'static + Sync,
|
||||
{
|
||||
let mut handler = self.handlers.take().unwrap();
|
||||
Box::pin(async move {
|
||||
for (h, f) in handler.into_iter().zip(f) {
|
||||
task::spawn(f(h));
|
||||
// f(h).await;
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_pool(&mut self) -> Vec<BoxFuture<'static, ()>> {
|
||||
// self.pool.clone()
|
||||
use std::mem::replace;
|
||||
let pool = replace(&mut self.pool, Vec::new());
|
||||
pool
|
||||
}
|
||||
|
||||
pub fn cancel_task(&mut self, timestamp: i64) {}
|
||||
}
|
||||
|
||||
pub struct Dispatcher {
|
||||
datetime: DateTime<Utc>,
|
||||
fore_len: usize,
|
||||
back_len: usize,
|
||||
step: Duration,
|
||||
registered_buffer: Buffer,
|
||||
}
|
||||
|
||||
impl Dispatcher {
|
||||
pub fn new(
|
||||
fore_len: usize,
|
||||
back_len: usize,
|
||||
step: Duration,
|
||||
registered_buffer: Buffer,
|
||||
) -> Self {
|
||||
// let config = CONFIG.lock().unwrap();
|
||||
// let config = CONFIG.lock().unwrap();
|
||||
// let etws_loader = CONFIG.lock().unwrap().plugins.get("etws_loader").unwrap();
|
||||
// let format = etws_loader.path_formats.as_ref();
|
||||
Self {
|
||||
datetime: Utc::now(),
|
||||
// path_format: format,
|
||||
fore_len,
|
||||
back_len,
|
||||
step,
|
||||
registered_buffer,
|
||||
}
|
||||
}
|
||||
|
||||
// pub fn set_path_format(&mut self, formats: HashMap<String, String>) {
|
||||
// self.path_format = formats;
|
||||
// }
|
||||
|
||||
pub fn set_current_time(&mut self, datetime: DateTime<Utc>) {
|
||||
self.datetime = datetime;
|
||||
}
|
||||
|
||||
pub fn set_step(&mut self, step: Duration) {
|
||||
self.step = step;
|
||||
}
|
||||
|
||||
pub fn set_fore_len(&mut self, fore_len: usize) {
|
||||
self.fore_len = fore_len;
|
||||
}
|
||||
|
||||
pub fn set_back_len(&mut self, back_len: usize) {
|
||||
self.back_len = back_len;
|
||||
}
|
||||
|
||||
pub fn get_single_path(
|
||||
&self,
|
||||
name: &str,
|
||||
current_time: DateTime<Utc>,
|
||||
check_existed: bool,
|
||||
) -> Option<String> {
|
||||
let datetime_format: regex::Regex =
|
||||
Regex::new(r"(?:%[YHMSmd](?:[-/:_]?%[YHMSmd])*)").unwrap();
|
||||
let config = CONFIG.lock().unwrap();
|
||||
let path_format = config
|
||||
.plugins
|
||||
.get("etws_loader")
|
||||
.unwrap()
|
||||
.path_formats
|
||||
.as_ref();
|
||||
if path_format.is_none() {
|
||||
return None;
|
||||
}
|
||||
let c = path_format.unwrap().get(name).map(|s| {
|
||||
let path = s.clone();
|
||||
let need_formated = datetime_format.captures_iter(&path).collect::<Vec<_>>();
|
||||
let mut result_path = path.clone();
|
||||
|
||||
for need_format in need_formated.iter() {
|
||||
let fmt = need_format.get(0).unwrap().as_str();
|
||||
let t = current_time.format(fmt).to_string();
|
||||
result_path = result_path.replace(fmt, &t);
|
||||
}
|
||||
result_path
|
||||
});
|
||||
if let Some(c) = c {
|
||||
if check_existed {
|
||||
if std::path::Path::new(&c).exists() {
|
||||
Some(c)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
Some(c)
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_path(
|
||||
&self,
|
||||
name: &str,
|
||||
current_time: DateTime<Utc>,
|
||||
check_existed: bool,
|
||||
mut max_retry_time: usize,
|
||||
) -> Option<Vec<(String, DateTime<Utc>)>> {
|
||||
let datetime_format: regex::Regex =
|
||||
Regex::new(r"(?:%[YHMSmd](?:[-/:_]?%[YHMSmd])*)").unwrap();
|
||||
let config = CONFIG.lock().unwrap();
|
||||
let path_format = config
|
||||
.plugins
|
||||
.get("etws_loader")
|
||||
.unwrap()
|
||||
.path_formats
|
||||
.as_ref();
|
||||
if path_format.is_none() {
|
||||
return None;
|
||||
}
|
||||
path_format.unwrap().get(name).map(|s| {
|
||||
let path = s.clone();
|
||||
let need_formated = datetime_format.captures_iter(&path).collect::<Vec<_>>();
|
||||
let mut fore = self.fore_len;
|
||||
let mut back = 0;
|
||||
let mut result_paths = Vec::new();
|
||||
let buffer: Ref<'_, HashMap<_, _>> = (*self.registered_buffer).borrow();
|
||||
let buffer = buffer.get(name).unwrap();
|
||||
while fore > 0 {
|
||||
let mut result_path = path.clone();
|
||||
let t = current_time - self.step * fore as i32;
|
||||
if buffer.get(&t).is_some() {
|
||||
fore = fore - 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
for need_format in need_formated.iter() {
|
||||
let fmt = need_format.get(0).unwrap().as_str();
|
||||
let t = t.format(fmt).to_string();
|
||||
result_path = result_path.replace(fmt, &t);
|
||||
}
|
||||
|
||||
if check_existed {
|
||||
if max_retry_time == 0 {
|
||||
break;
|
||||
}
|
||||
if !std::path::Path::new(&result_path).exists() {
|
||||
max_retry_time = max_retry_time - 1;
|
||||
continue;
|
||||
} else {
|
||||
result_paths.push((result_path.clone(), t));
|
||||
}
|
||||
} else {
|
||||
result_paths.push((result_path.clone(), t));
|
||||
}
|
||||
fore = fore - 1;
|
||||
}
|
||||
|
||||
while back < self.back_len {
|
||||
let mut result_path = path.clone();
|
||||
let t = current_time + self.step * back as i32;
|
||||
|
||||
if buffer.get(&t).is_some() {
|
||||
back = back + 1;
|
||||
continue;
|
||||
}
|
||||
for need_format in need_formated.iter() {
|
||||
let fmt = need_format.get(0).unwrap().as_str();
|
||||
let t = t.format(fmt).to_string();
|
||||
result_path = result_path.replace(fmt, &t);
|
||||
}
|
||||
|
||||
if check_existed {
|
||||
if max_retry_time == 0 {
|
||||
break;
|
||||
}
|
||||
if !std::path::Path::new(&result_path).exists() {
|
||||
max_retry_time = max_retry_time - 1;
|
||||
continue;
|
||||
} else {
|
||||
result_paths.push((result_path.clone(), t));
|
||||
}
|
||||
} else {
|
||||
result_paths.push((result_path.clone(), t));
|
||||
}
|
||||
back = back + 1;
|
||||
}
|
||||
|
||||
result_paths
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! match_in_macro {
|
||||
($block:ident,$(($branch:path,$name:literal, $t:ty, $color:expr)),+) => {
|
||||
($block:ident,$dispatcher:ident,$cms:ident,$(($branch:path,$name:literal, $t:ty, $color:expr)),+) => {
|
||||
{
|
||||
let datetime = Utc.timestamp_opt($block.datetime, 0).unwrap();
|
||||
match $block.data_type {
|
||||
$(
|
||||
$branch => {
|
||||
let data: $t = $block.into();
|
||||
let layer = Layer::grid_render_layer(data, format!($name), $color);
|
||||
Some(( datetime ,layer))
|
||||
let element = Element::create_time_series(GridElementImpl::new($color), $dispatcher, $name.to_string(), $cms);
|
||||
Some(element)
|
||||
},
|
||||
)+
|
||||
_ => None
|
||||
@ -406,12 +36,18 @@ macro_rules! match_in_macro {
|
||||
};
|
||||
}
|
||||
|
||||
pub fn data_to_layer(block: Block) -> Option<(DateTime<Utc>, Layer)> {
|
||||
pub fn data_to_element(
|
||||
block: &Block,
|
||||
dispatcher: Rc<Dispatcher>,
|
||||
cms: Arc<Mutex<CMS>>,
|
||||
) -> Option<Element> {
|
||||
use crate::utils::*;
|
||||
use radarg_plugin_interface::PluginResultType;
|
||||
match block.shape {
|
||||
DataShape::Matrix => match_in_macro!(
|
||||
block,
|
||||
dispatcher,
|
||||
cms,
|
||||
(
|
||||
PluginResultType::DBZ,
|
||||
"DBZ",
|
||||
@ -486,5 +122,3 @@ pub fn data_to_layer(block: Block) -> Option<(DateTime<Utc>, Layer)> {
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
// Pin<Box<dyn Future<Output = Result<Vec<RenderResult>, RenderError>> + Send + 'static>>
|
||||
|
||||
@ -1,7 +1,5 @@
|
||||
use std::ops::Range;
|
||||
|
||||
use geo_types::LineString;
|
||||
|
||||
use crate::coords::Mapper;
|
||||
|
||||
pub struct CMS {
|
||||
|
||||
180
src/widgets/render/interior/layers copy.rs
Normal file
180
src/widgets/render/interior/layers copy.rs
Normal file
@ -0,0 +1,180 @@
|
||||
use super::super::{Render};
|
||||
use crate::pipeline::offscreen_renderer::CanvasWrapper;
|
||||
use crate::{coords::Range, widgets::widget::Widget};
|
||||
use femtovg::{renderer::OpenGl, Canvas, ImageId};
|
||||
use std::{
|
||||
cell::{Ref, RefCell},
|
||||
fmt::Debug,
|
||||
future::Future,
|
||||
pin::Pin,
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
use crate::coords::cms::CMS;
|
||||
use crate::pipeline::element::Target;
|
||||
|
||||
type PrepareFunc = Arc<
|
||||
Mutex<
|
||||
Option<
|
||||
Box<
|
||||
dyn FnOnce(
|
||||
LayerImplSync,
|
||||
// Box<dyn LayerImpl + Send + Sync>,
|
||||
Arc<Mutex<CanvasWrapper>>,
|
||||
CMS,
|
||||
) -> Target
|
||||
+ Sync
|
||||
+ Send,
|
||||
>,
|
||||
>,
|
||||
>,
|
||||
>;
|
||||
type DrawFunc = Arc<dyn Fn(&Layer, Render, (f32, f32)) + Send + Sync>;
|
||||
pub type LayerImplSync = Arc<Mutex<Box<dyn LayerImpl + Send + Sync>>>;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Layer {
|
||||
pub visiable: bool,
|
||||
pub name: String,
|
||||
pub widgets: Arc<Mutex<Option<Vec<Box<dyn Widget>>>>>,
|
||||
target: Arc<Mutex<Option<Target>>>,
|
||||
prepare: PrepareFunc,
|
||||
imp: Option<Arc<Mutex<Box<dyn LayerImpl + Send + Sync>>>>,
|
||||
draw: DrawFunc,
|
||||
}
|
||||
|
||||
impl Debug for Layer {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("Layer")
|
||||
.field("visiable", &self.visiable)
|
||||
.field("target", &self.target)
|
||||
.field("imp", &self.imp)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
pub trait LayerImpl: Debug {
|
||||
fn draw(&self, canvas: &mut Canvas<OpenGl>, cms: &CMS) -> Option<Target>;
|
||||
}
|
||||
|
||||
impl Layer {
|
||||
pub fn new<
|
||||
F: 'static + Fn(&Self, Render, (f32, f32)) + Send + Sync,
|
||||
PREPARE: FnOnce(LayerImplSync, Arc<Mutex<CanvasWrapper>>, CMS) -> Target + Send + Sync + 'static,
|
||||
IMP: LayerImpl + Sync + Send + 'static,
|
||||
>(
|
||||
visiable: bool,
|
||||
draw: F,
|
||||
widgets: Option<Vec<Box<dyn Widget>>>,
|
||||
layer_name: String,
|
||||
prepare: Option<PREPARE>,
|
||||
imp: Option<IMP>,
|
||||
) -> Self {
|
||||
Layer {
|
||||
visiable,
|
||||
target: Arc::new(Mutex::new(None)),
|
||||
name: layer_name,
|
||||
widgets: Arc::new(Mutex::new(widgets)),
|
||||
prepare: Arc::new(Mutex::new(prepare.map(|p| {
|
||||
Box::new(move |a, b, c| p(a, b, c))
|
||||
as Box<
|
||||
dyn FnOnce(LayerImplSync, Arc<Mutex<CanvasWrapper>>, CMS) -> Target
|
||||
+ Sync
|
||||
+ Send,
|
||||
>
|
||||
}))),
|
||||
draw: Arc::new(Box::new(draw)),
|
||||
imp: imp.map(|i| {
|
||||
Arc::new(Mutex::new(
|
||||
Box::new(i) as Box<dyn LayerImpl + Send + Sync + 'static>
|
||||
))
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn draw(&self, render: &Render, window_size: (f32, f32)) {
|
||||
if self.visiable {
|
||||
let drawer = &self.draw;
|
||||
drawer(self, render.clone(), window_size);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_prepare(&self) -> PrepareFunc {
|
||||
self.prepare.clone()
|
||||
}
|
||||
|
||||
pub fn set_render_target(&self, target: Target) {
|
||||
self.target.lock().unwrap().replace(target);
|
||||
}
|
||||
|
||||
pub fn render_target(&self) -> Arc<Mutex<Option<Target>>> {
|
||||
self.target.clone()
|
||||
}
|
||||
|
||||
pub fn get_imp(&self) -> Option<Arc<Mutex<Box<dyn LayerImpl + Sync + Send + 'static>>>> {
|
||||
// self.imp.map(|p| p.clone())
|
||||
self.imp.clone()
|
||||
}
|
||||
|
||||
pub fn get_thumbnail(&self) -> Option<gtk::gdk::Texture> {
|
||||
self.target
|
||||
.lock()
|
||||
.unwrap()
|
||||
.as_ref()
|
||||
.and_then(|t| t.thumbnail.clone())
|
||||
}
|
||||
}
|
||||
|
||||
// #[derive(Clone, Debug, PartialEq, PartialOrd)]
|
||||
// pub struct Target {
|
||||
// pub target: TargetType,
|
||||
// pub thumbnail: Option<gtk::gdk::Texture>,
|
||||
// pub width: f32,
|
||||
// pub height: f32,
|
||||
// pub bounds: (Range, Range),
|
||||
// }
|
||||
//
|
||||
// #[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord)]
|
||||
// pub enum TargetType {
|
||||
// ImageId(ImageId),
|
||||
// Mem(Vec<u8>),
|
||||
// }
|
||||
//
|
||||
// impl Target {
|
||||
// pub fn new(
|
||||
// target: TargetType,
|
||||
// width: f32,
|
||||
// height: f32,
|
||||
// bounds: (Range, Range),
|
||||
// thumbnail: Option<gtk::gdk::Texture>,
|
||||
// ) -> Self {
|
||||
// Self {
|
||||
// target,
|
||||
// width,
|
||||
// height,
|
||||
// bounds,
|
||||
// thumbnail,
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// pub fn size(&self, render: &Render) -> (f32, f32) {
|
||||
// let (x, y) = self.bounds;
|
||||
//
|
||||
// let p1 = (x.0, y.0);
|
||||
// let p2 = (x.1, y.1);
|
||||
//
|
||||
// let (x1, y1) = render.map(p1).unwrap();
|
||||
// let (x2, y2) = render.map(p2).unwrap();
|
||||
//
|
||||
// ((x2 - x1).abs(), (y2 - y1).abs())
|
||||
// }
|
||||
//
|
||||
// pub fn origin(&self, render: &Render) -> (f32, f32) {
|
||||
// let (x, y) = self.bounds;
|
||||
// let p1 = (x.0, y.1);
|
||||
// render.map(p1).unwrap()
|
||||
// }
|
||||
//
|
||||
// pub fn set_target(&mut self, target: TargetType) {
|
||||
// self.target = target;
|
||||
// }
|
||||
// }
|
||||
@ -1,6 +1,9 @@
|
||||
use super::super::{cms::CMS, Render};
|
||||
use super::super::Render;
|
||||
use crate::coords::cms::CMS;
|
||||
use crate::pipeline::element::{self, Element, ElementID, Target};
|
||||
use crate::pipeline::offscreen_renderer::CanvasWrapper;
|
||||
use crate::{coords::Range, widgets::widget::Widget};
|
||||
use chrono::{prelude::*, DateTime};
|
||||
use femtovg::{renderer::OpenGl, Canvas, ImageId};
|
||||
use std::{
|
||||
cell::{Ref, RefCell},
|
||||
@ -13,159 +16,66 @@ use std::{
|
||||
type PrepareFunc = Arc<
|
||||
Mutex<
|
||||
Option<
|
||||
Box<
|
||||
dyn FnOnce(
|
||||
LayerImplSync,
|
||||
// Box<dyn LayerImpl + Send + Sync>,
|
||||
Arc<Mutex<CanvasWrapper>>,
|
||||
CMS,
|
||||
) -> Target
|
||||
+ Sync
|
||||
+ Send,
|
||||
>,
|
||||
Box<dyn FnOnce(LayerImplSync, Arc<Mutex<CanvasWrapper>>, CMS) -> Target + Sync + Send>,
|
||||
>,
|
||||
>,
|
||||
>;
|
||||
type DrawFunc = Arc<dyn Fn(&Layer, Render, (f32, f32)) + Send + Sync>;
|
||||
// type LayerImplSync = Box<dyn LayerImpl + Send + Sync>;
|
||||
pub type LayerImplSync = Arc<Mutex<Box<dyn LayerImpl + Send + Sync>>>;
|
||||
|
||||
pub enum AssoElement {
|
||||
TimeSeries(Arc<Mutex<element::TimeSeriesElement>>),
|
||||
Instant(element::InstantElement),
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Layer {
|
||||
pub visiable: bool,
|
||||
pub name: String,
|
||||
pub widgets: Arc<Mutex<Option<Vec<Box<dyn Widget>>>>>,
|
||||
target: Arc<Mutex<Option<Target>>>,
|
||||
prepare: PrepareFunc,
|
||||
imp: Option<Arc<Mutex<Box<dyn LayerImpl + Send + Sync>>>>,
|
||||
draw: DrawFunc,
|
||||
associated_element: AssoElement,
|
||||
time: Option<DateTime<Utc>>,
|
||||
}
|
||||
|
||||
impl Debug for Layer {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("Layer")
|
||||
.field("visiable", &self.visiable)
|
||||
.field("target", &self.target)
|
||||
.field("imp", &self.imp)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
pub trait LayerImpl: Debug {
|
||||
fn draw(&self, canvas: &mut Canvas<OpenGl>, cms: CMS) -> Option<Target>;
|
||||
fn draw(&self, canvas: &mut Canvas<OpenGl>, cms: &CMS) -> Option<Target>;
|
||||
}
|
||||
|
||||
impl Layer {
|
||||
pub fn new<
|
||||
F: 'static + Fn(&Self, Render, (f32, f32)) + Send + Sync,
|
||||
PREPARE: FnOnce(LayerImplSync, Arc<Mutex<CanvasWrapper>>, CMS) -> Target + Send + Sync + 'static,
|
||||
IMP: LayerImpl + Sync + Send + 'static,
|
||||
>(
|
||||
visiable: bool,
|
||||
draw: F,
|
||||
widgets: Option<Vec<Box<dyn Widget>>>,
|
||||
layer_name: String,
|
||||
prepare: Option<PREPARE>,
|
||||
imp: Option<IMP>,
|
||||
) -> Self {
|
||||
pub fn new(visiable: bool, layer_name: String, element: AssoElement) -> Self {
|
||||
Layer {
|
||||
visiable,
|
||||
target: Arc::new(Mutex::new(None)),
|
||||
name: layer_name,
|
||||
widgets: Arc::new(Mutex::new(widgets)),
|
||||
prepare: Arc::new(Mutex::new(prepare.map(|p| {
|
||||
Box::new(move |a, b, c| p(a, b, c))
|
||||
as Box<
|
||||
dyn FnOnce(LayerImplSync, Arc<Mutex<CanvasWrapper>>, CMS) -> Target
|
||||
+ Sync
|
||||
+ Send,
|
||||
>
|
||||
}))),
|
||||
draw: Arc::new(Box::new(draw)),
|
||||
imp: imp.map(|i| {
|
||||
Arc::new(Mutex::new(
|
||||
Box::new(i) as Box<dyn LayerImpl + Send + Sync + 'static>
|
||||
))
|
||||
}),
|
||||
associated_element: element,
|
||||
time: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn draw(&self, render: &Render, window_size: (f32, f32)) {
|
||||
if self.visiable {
|
||||
let drawer = &self.draw;
|
||||
drawer(self, render.clone(), window_size);
|
||||
if let Some(element) = &self.associated_element {
|
||||
let element = element.lock().unwrap();
|
||||
match *element {
|
||||
Element::TimeSeries(ref e) => {
|
||||
if self.time.is_none() {
|
||||
return;
|
||||
}
|
||||
let time = self.time.unwrap();
|
||||
}
|
||||
Element::Instant(ref e) => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_prepare(&self) -> PrepareFunc {
|
||||
self.prepare.clone()
|
||||
}
|
||||
|
||||
pub fn set_render_target(&self, target: Target) {
|
||||
self.target.lock().unwrap().replace(target);
|
||||
}
|
||||
|
||||
pub fn render_target(&self) -> Arc<Mutex<Option<Target>>> {
|
||||
self.target.clone()
|
||||
}
|
||||
|
||||
pub fn get_imp(&self) -> Option<Arc<Mutex<Box<dyn LayerImpl + Sync + Send + 'static>>>> {
|
||||
// self.imp.map(|p| p.clone())
|
||||
self.imp.clone()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, PartialOrd)]
|
||||
pub struct Target {
|
||||
pub target: TargetType,
|
||||
pub thumbnail: Option<gtk::gdk::Texture>,
|
||||
pub width: f32,
|
||||
pub height: f32,
|
||||
pub bounds: (Range, Range),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord)]
|
||||
pub enum TargetType {
|
||||
ImageId(ImageId),
|
||||
Mem(Vec<u8>),
|
||||
}
|
||||
|
||||
impl Target {
|
||||
pub fn new(
|
||||
target: TargetType,
|
||||
width: f32,
|
||||
height: f32,
|
||||
bounds: (Range, Range),
|
||||
thumbnail: Option<gtk::gdk::Texture>,
|
||||
) -> Self {
|
||||
Self {
|
||||
target,
|
||||
width,
|
||||
height,
|
||||
bounds,
|
||||
thumbnail,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn size(&self, render: &Render) -> (f32, f32) {
|
||||
let (x, y) = self.bounds;
|
||||
|
||||
let p1 = (x.0, y.0);
|
||||
let p2 = (x.1, y.1);
|
||||
|
||||
let (x1, y1) = render.map(p1).unwrap();
|
||||
let (x2, y2) = render.map(p2).unwrap();
|
||||
|
||||
((x2 - x1).abs(), (y2 - y1).abs())
|
||||
}
|
||||
|
||||
pub fn origin(&self, render: &Render) -> (f32, f32) {
|
||||
let (x, y) = self.bounds;
|
||||
let p1 = (x.0, y.1);
|
||||
render.map(p1).unwrap()
|
||||
}
|
||||
|
||||
pub fn set_target(&mut self, target: TargetType) {
|
||||
self.target = target;
|
||||
pub fn get_thumbnail(&self) -> Option<gtk::gdk::Texture> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,7 +2,7 @@ mod imp;
|
||||
mod layers;
|
||||
use super::super::Render;
|
||||
use femtovg::{renderer::OpenGl, Canvas};
|
||||
pub use layers::{Layer, LayerImpl, LayerImplSync, Target, TargetType};
|
||||
pub use layers::{Layer, LayerImpl, LayerImplSync};
|
||||
use std::cell::Ref;
|
||||
|
||||
use super::imp::{RenderConfig, RenderStatus};
|
||||
|
||||
@ -3,7 +3,7 @@ mod exterior;
|
||||
mod imp;
|
||||
mod interior;
|
||||
pub mod predefined;
|
||||
mod renders;
|
||||
pub mod renders;
|
||||
pub mod widget;
|
||||
pub use self::cms::CMS;
|
||||
pub use self::imp::{RenderConfig, RenderMotion, RenderStatus};
|
||||
|
||||
@ -1,4 +1,6 @@
|
||||
use super::color_mapper::{BoundaryNorm, ColorMapper};
|
||||
use crate::pipeline::element::{Target, TargetType};
|
||||
use femtovg::ImageInfo;
|
||||
use femtovg::{
|
||||
renderer::OpenGl, Canvas, ImageFlags, Paint, Path, PixelFormat::Rgba8, RenderTarget,
|
||||
};
|
||||
@ -10,8 +12,9 @@ use num_traits::{AsPrimitive, FromPrimitive, Num, NumOps};
|
||||
use std::{fmt::Debug, io::Cursor, marker::PhantomData};
|
||||
|
||||
use super::super::renders::DataRenderer;
|
||||
use super::super::{cms::CMS, LayerImpl, Render, Target, TargetType};
|
||||
use super::super::{LayerImpl, Render};
|
||||
use crate::{data::Radar2d, utils::meshgrid};
|
||||
use crate::coords::cms::CMS;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct GridFieldRenderer<CMAP, T>
|
||||
@ -101,7 +104,7 @@ where
|
||||
fn render(
|
||||
&self,
|
||||
canvas: &mut Canvas<OpenGl>,
|
||||
mut cms: CMS,
|
||||
mut cms: &CMS,
|
||||
data: &Self::Data,
|
||||
size: (f32, f32),
|
||||
) -> Target {
|
||||
@ -133,11 +136,8 @@ where
|
||||
(w, h),
|
||||
data.fill_value,
|
||||
);
|
||||
|
||||
canvas.flush();
|
||||
|
||||
let mut pixels: Vec<u8> = vec![0; w as usize * h as usize * 4];
|
||||
|
||||
unsafe {
|
||||
gl::ReadPixels(
|
||||
0,
|
||||
@ -153,19 +153,17 @@ where
|
||||
|
||||
let img: ImageBuffer<Rgba<u8>, Vec<u8>> = ImageBuffer::from_raw(w as u32, h as u32, pixels)
|
||||
.expect("Failed to create ImageBuffer");
|
||||
|
||||
let thumbnail = resize(&img, 500, 500, image::imageops::FilterType::Lanczos3);
|
||||
let mut thumb_buffer = Cursor::new(Vec::new());
|
||||
img.write_to(&mut thumb_buffer, image::ImageOutputFormat::Png)
|
||||
.expect("Failed to write PNG buffer");
|
||||
let thumb_data = thumb_buffer.into_inner();
|
||||
|
||||
let thumbnail_tex =
|
||||
gtk::gdk::Texture::from_bytes(>k::glib::Bytes::from(&thumb_data)).unwrap();
|
||||
|
||||
// 将 ImageBuffer 编码为 PNG
|
||||
let mut png_buffer = Cursor::new(Vec::new());
|
||||
img.write_to(&mut png_buffer, image::ImageOutputFormat::Png)
|
||||
img.write_to(&mut png_buffer, image::ImageOutputFormat::Bmp)
|
||||
.expect("Failed to write PNG buffer");
|
||||
|
||||
let png_data = png_buffer.into_inner();
|
||||
@ -189,7 +187,7 @@ where
|
||||
#[derive(Debug)]
|
||||
pub struct GridLayerImpl<CMAP, T>
|
||||
where
|
||||
T: Num +NumOps + PartialOrd + FromPrimitive + AsPrimitive<f64> + Send + Sync + Debug,
|
||||
T: Num + NumOps + PartialOrd + FromPrimitive + AsPrimitive<f64> + Send + Sync + Debug,
|
||||
CMAP: ColorMapper<T>,
|
||||
{
|
||||
renderer: GridFieldRenderer<CMAP, T>,
|
||||
@ -210,13 +208,22 @@ where
|
||||
|
||||
impl<CMAP, T> LayerImpl for GridLayerImpl<CMAP, T>
|
||||
where
|
||||
T: Num + NumOps + PartialOrd + Copy + Clone + Debug + Send + Sync + FromPrimitive + AsPrimitive<f64>,
|
||||
T: Num
|
||||
+ NumOps
|
||||
+ PartialOrd
|
||||
+ Copy
|
||||
+ Clone
|
||||
+ Debug
|
||||
+ Send
|
||||
+ Sync
|
||||
+ FromPrimitive
|
||||
+ AsPrimitive<f64>,
|
||||
CMAP: ColorMapper<T> + Debug,
|
||||
{
|
||||
fn draw(
|
||||
&self,
|
||||
canvas: &mut femtovg::Canvas<femtovg::renderer::OpenGl>,
|
||||
cms: CMS,
|
||||
cms: &CMS,
|
||||
) -> Option<Target> {
|
||||
let result = self
|
||||
.renderer
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
use femtovg::ImageFlags;
|
||||
use femtovg::PixelFormat::Rgba8;
|
||||
use femtovg::{renderer::OpenGl, Canvas, Paint};
|
||||
use glow::NativeTexture;
|
||||
use num_traits::{AsPrimitive, FromPrimitive, Num, NumOps};
|
||||
use std::fmt::Debug;
|
||||
use std::path::Path;
|
||||
@ -8,12 +9,14 @@ use std::sync::Arc;
|
||||
use std::sync::Mutex;
|
||||
|
||||
use super::super::{
|
||||
cms::CMS, widget::Widget, Layer, LayerImpl, LayerImplSync, Render, Target, TargetType,
|
||||
widget::Widget, Layer, LayerImpl, LayerImplSync, Render
|
||||
};
|
||||
use crate::coords::cms::CMS;
|
||||
use super::widgets::ColorBar;
|
||||
use crate::data::{AsyncDataLoader, DataLoader, Radar2d};
|
||||
use crate::pipeline::offscreen_renderer::CanvasWrapper;
|
||||
use tokio::task;
|
||||
use crate::pipeline::element::TargetType;
|
||||
|
||||
use super::{
|
||||
color_mapper::ColorMapper,
|
||||
@ -83,7 +86,7 @@ impl Layer {
|
||||
move |renderer: LayerImplSync, c: Arc<Mutex<CanvasWrapper>>, cms: CMS| {
|
||||
let mut canvas = c.lock().unwrap();
|
||||
let renderer = renderer.lock().unwrap();
|
||||
let img = renderer.draw(&mut canvas, cms).unwrap();
|
||||
let img = renderer.draw(&mut canvas, &cms).unwrap();
|
||||
img
|
||||
},
|
||||
),
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
pub mod color_mapper;
|
||||
pub mod gis;
|
||||
pub mod grid_field_renderer;
|
||||
pub mod layers;
|
||||
// pub mod grid_field_renderer;
|
||||
// pub mod layers;
|
||||
pub mod widgets;
|
||||
|
||||
@ -1,14 +1,14 @@
|
||||
use super::cms::CMS;
|
||||
use super::Render;
|
||||
use super::Target;
|
||||
use crate::coords::cms::CMS;
|
||||
use femtovg::{renderer::OpenGl, Canvas};
|
||||
use crate::pipeline::element::Target;
|
||||
|
||||
pub trait DataRenderer {
|
||||
type Data;
|
||||
fn render(
|
||||
&self,
|
||||
canvas: &mut Canvas<OpenGl>,
|
||||
cms: CMS,
|
||||
cms: &CMS,
|
||||
data: &Self::Data,
|
||||
size: (f32, f32),
|
||||
) -> Target;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user