refactoring
This commit is contained in:
parent
03c57302f3
commit
d7eaa28959
29 changed files with 252 additions and 1380 deletions
1187
Cargo.lock
generated
1187
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -1,6 +1,6 @@
|
||||||
[workspace]
|
[workspace]
|
||||||
resolver = "2"
|
resolver = "2"
|
||||||
members = [ "wxbox-grib2","wxbox-pal","wxbox-tiler", "wxbox_client", "wxbox_client_native", "wxbox_client_wasm", "wxbox_common"]
|
members = ["crates/*"]
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
codegen-units = 1
|
codegen-units = 1
|
||||||
|
@ -10,3 +10,7 @@ lto = "fat"
|
||||||
opt-level = 3
|
opt-level = 3
|
||||||
[profile.dev.package.png]
|
[profile.dev.package.png]
|
||||||
opt-level = 3
|
opt-level = 3
|
||||||
|
|
||||||
|
# Optimize all dependencies even in debug builds:
|
||||||
|
[profile.dev.package."*"]
|
||||||
|
opt-level = 2
|
|
@ -1,5 +1,5 @@
|
||||||
[package]
|
[package]
|
||||||
name = "wxbox_common"
|
name = "wxbox-common"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
|
@ -13,16 +13,16 @@ tikv-jemallocator = "0.6"
|
||||||
reqwest = "0.12"
|
reqwest = "0.12"
|
||||||
flate2 = "1"
|
flate2 = "1"
|
||||||
tokio = "1"
|
tokio = "1"
|
||||||
wxbox-pal = { version = "0.1", path = "../wxbox-pal" }
|
wxbox-pal = { version = "0.1", path = "../pal" }
|
||||||
png = "0.17"
|
png = "0.17"
|
||||||
mime = "0.3.17"
|
mime = "0.3.17"
|
||||||
wxbox-grib2 = { version = "0.1", path = "../wxbox-grib2" }
|
wxbox-grib2 = { version = "0.1", path = "../grib2" }
|
||||||
tracing = "0.1"
|
tracing = "0.1"
|
||||||
tracing-subscriber = "0.3"
|
tracing-subscriber = "0.3"
|
||||||
toml = "0.8"
|
toml = "0.8"
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
serde_json = "1"
|
serde_json = "1"
|
||||||
wxbox_common = { path = "../wxbox_common" }
|
wxbox-common = { path = "../common" }
|
||||||
image = "0.25"
|
image = "0.25"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
|
@ -1,12 +0,0 @@
|
||||||
[package]
|
|
||||||
name = "wxbox_client"
|
|
||||||
version = "0.1.0"
|
|
||||||
edition = "2021"
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
walkers = "0.32"
|
|
||||||
eframe = "0.30"
|
|
||||||
egui = "0.30"
|
|
||||||
egui_extras = "0.30"
|
|
||||||
wxbox_common = { path = "../wxbox_common" }
|
|
||||||
serde_json = "1"
|
|
|
@ -1,213 +0,0 @@
|
||||||
mod toggle_switch;
|
|
||||||
|
|
||||||
use crate::toggle_switch::toggle;
|
|
||||||
use egui::Context;
|
|
||||||
use egui::{Align2, CollapsingHeader, Color32, Frame, UiBuilder, Window};
|
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::env::var;
|
|
||||||
use walkers::sources::{Attribution, TileSource};
|
|
||||||
use walkers::{HttpOptions, HttpTiles, MapMemory, Position, TileId, Tiles};
|
|
||||||
use wxbox_common::TileRequestOptions;
|
|
||||||
|
|
||||||
pub struct WxboxApp {
|
|
||||||
provider: HttpTiles,
|
|
||||||
map_memory: MapMemory,
|
|
||||||
tile_request_options: TileRequestOptions,
|
|
||||||
|
|
||||||
rf_color: Color32,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct DynamicUrlSource {
|
|
||||||
pub url_query: String,
|
|
||||||
}
|
|
||||||
impl DynamicUrlSource {
|
|
||||||
pub fn new_from(options: &TileRequestOptions) -> Self {
|
|
||||||
Self {
|
|
||||||
url_query: serde_json::to_string(options).unwrap(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl TileSource for DynamicUrlSource {
|
|
||||||
fn tile_url(&self, tile_id: TileId) -> String {
|
|
||||||
format!(
|
|
||||||
"{}/{}/{}/{}.png?settings={}",
|
|
||||||
env!("TILER_BASE_URL"),
|
|
||||||
tile_id.zoom,
|
|
||||||
tile_id.x,
|
|
||||||
tile_id.y,
|
|
||||||
self.url_query
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn attribution(&self) -> Attribution {
|
|
||||||
Attribution {
|
|
||||||
text: "OpenStreetMap contributors, NOAA, wxbox",
|
|
||||||
url: "https://copyright.wxbox.e3t.cc",
|
|
||||||
logo_light: None,
|
|
||||||
logo_dark: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl WxboxApp {
|
|
||||||
pub fn new(ctx: Context) -> Self {
|
|
||||||
egui_extras::install_image_loaders(&ctx);
|
|
||||||
|
|
||||||
let req_options = TileRequestOptions {
|
|
||||||
baselayer: "osm".to_string(),
|
|
||||||
|
|
||||||
data: "grib2/noaa_mrms_merged_composite_reflectivity_qc_CONUS".to_string(),
|
|
||||||
data_transparency: 0.9,
|
|
||||||
show_range_folded: false,
|
|
||||||
range_folded_color: 0x8d00a0ff,
|
|
||||||
};
|
|
||||||
|
|
||||||
Self {
|
|
||||||
provider: HttpTiles::with_options(
|
|
||||||
DynamicUrlSource::new_from(&req_options),
|
|
||||||
HttpOptions {
|
|
||||||
cache: if cfg!(target_arch = "wasm32") {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some(".cache".into())
|
|
||||||
},
|
|
||||||
user_agent: None,
|
|
||||||
},
|
|
||||||
ctx.clone(),
|
|
||||||
),
|
|
||||||
map_memory: MapMemory::default(),
|
|
||||||
tile_request_options: req_options,
|
|
||||||
rf_color: Color32::from_hex("#8d00a0ff").unwrap(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl eframe::App for WxboxApp {
|
|
||||||
fn update(&mut self, ctx: &Context, frame: &mut eframe::Frame) {
|
|
||||||
let rimless = Frame {
|
|
||||||
fill: ctx.style().visuals.panel_fill,
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
|
|
||||||
egui::CentralPanel::default()
|
|
||||||
.frame(rimless)
|
|
||||||
.show(ctx, |ui| {
|
|
||||||
let Self {
|
|
||||||
provider,
|
|
||||||
map_memory,
|
|
||||||
tile_request_options,
|
|
||||||
rf_color
|
|
||||||
} = self;
|
|
||||||
|
|
||||||
let position = Position::from_lat_lon(44.967243, -103.771556);
|
|
||||||
|
|
||||||
let tiles = provider;
|
|
||||||
let attribution = tiles.attribution();
|
|
||||||
|
|
||||||
let map = walkers::Map::new(Some(tiles), map_memory, position);
|
|
||||||
|
|
||||||
ui.add(map);
|
|
||||||
|
|
||||||
Window::new("Attribution")
|
|
||||||
.collapsible(false)
|
|
||||||
.resizable(false)
|
|
||||||
.title_bar(false)
|
|
||||||
.anchor(Align2::RIGHT_BOTTOM, [-10.0, -10.0])
|
|
||||||
.show(ui.ctx(), |ui| {
|
|
||||||
ui.horizontal(|ui| {
|
|
||||||
ui.hyperlink_to(attribution.text, attribution.url);
|
|
||||||
});
|
|
||||||
ui.horizontal(|ui| {
|
|
||||||
ui.label("wxbox is made with <3 by ");
|
|
||||||
ui.hyperlink_to("core", "https://coredoes.dev");
|
|
||||||
ui.label(" and ");
|
|
||||||
ui.hyperlink_to("tm85", "https://u8.lc");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
Window::new("wxbox")
|
|
||||||
.resizable(false)
|
|
||||||
.show(ui.ctx(), |ui| {
|
|
||||||
ui.heading("Welcome to wxbox!");
|
|
||||||
ui.label("We're glad you're here; Open up the Datasource panel to get started.");
|
|
||||||
ui.label("If you're using a mouse, you can click and drag to move around and Ctrl+Scroll to zoom.");
|
|
||||||
ui.label("On a touch-based device like a trackpad or touchscreen, you can tap/click and drag to move around and two-finger pinch to zoom.");
|
|
||||||
ui.label("You can close this window by clicking the arrow to the left of it's title.");
|
|
||||||
});
|
|
||||||
|
|
||||||
let mut need_to_reset_for_next_frame = false;
|
|
||||||
|
|
||||||
Window::new("Datasource")
|
|
||||||
.resizable(false)
|
|
||||||
.default_open(false)
|
|
||||||
.show(ui.ctx(), |ui| {
|
|
||||||
ui.collapsing("NOAA", |ui| {
|
|
||||||
ui.collapsing("Multi-Radar Multi-Sensor", |ui| {
|
|
||||||
for location in ["CONUS", "ALASKA", "CARIB", "GUAM", "HAWAII"] {
|
|
||||||
ui.collapsing(location, |ui| {
|
|
||||||
ui.collapsing("Composite Reflectivity", |ui| {
|
|
||||||
if ui.radio_value(&mut tile_request_options.data, format!("grib2/noaa_mrms_merged_composite_reflectivity_qc_{}", location), "Composite Reflectivity (QCd)").changed() {
|
|
||||||
need_to_reset_for_next_frame = true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if location == "CONUS" {
|
|
||||||
ui.collapsing("RhoHV (Correlation Coefficient)", |ui| {
|
|
||||||
if ui.radio_value(&mut tile_request_options.data, format!("grib2/noaa_mrms_merged_rhohv_3km_{}", location), "RhoHV @ 3.00 KM").changed() {
|
|
||||||
need_to_reset_for_next_frame = true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
ui.separator();
|
|
||||||
egui::Grid::new("dsconfig_grid")
|
|
||||||
.num_columns(2)
|
|
||||||
.spacing([40.0, 4.0])
|
|
||||||
.striped(true)
|
|
||||||
.show(ui, |ui| {
|
|
||||||
ui.label("Data opacity");
|
|
||||||
if ui.add(egui::Slider::new(&mut tile_request_options.data_transparency, 0.0..=1.0).suffix("%").custom_formatter(
|
|
||||||
|u, v| {
|
|
||||||
egui::emath::format_with_decimals_in_range(u * 100.0, v)
|
|
||||||
}
|
|
||||||
)).changed() {
|
|
||||||
need_to_reset_for_next_frame = true;
|
|
||||||
}
|
|
||||||
ui.end_row();
|
|
||||||
|
|
||||||
ui.label("Show range folded areas");
|
|
||||||
if ui.add(toggle(&mut tile_request_options.show_range_folded)).changed() {
|
|
||||||
need_to_reset_for_next_frame = true;
|
|
||||||
}
|
|
||||||
ui.end_row();
|
|
||||||
|
|
||||||
ui.label("Range folded color");
|
|
||||||
if ui.color_edit_button_srgba(rf_color).changed() {
|
|
||||||
let color = rf_color.to_array();
|
|
||||||
let single_number = u32::from_be_bytes(color);
|
|
||||||
tile_request_options.range_folded_color = single_number;
|
|
||||||
need_to_reset_for_next_frame = true;
|
|
||||||
}
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
if need_to_reset_for_next_frame {
|
|
||||||
*tiles = HttpTiles::with_options(
|
|
||||||
DynamicUrlSource::new_from(tile_request_options),
|
|
||||||
HttpOptions {
|
|
||||||
cache: if cfg!(target_arch = "wasm32") {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some(".cache".into())
|
|
||||||
},
|
|
||||||
user_agent: None,
|
|
||||||
},
|
|
||||||
ctx.clone()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,111 +0,0 @@
|
||||||
//! Source code example of how to create your own widget.
|
|
||||||
//! This is meant to be read as a tutorial, hence the plethora of comments.
|
|
||||||
|
|
||||||
/// iOS-style toggle switch:
|
|
||||||
///
|
|
||||||
/// ``` text
|
|
||||||
/// _____________
|
|
||||||
/// / /.....\
|
|
||||||
/// | |.......|
|
|
||||||
/// \_______\_____/
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// ## Example:
|
|
||||||
/// ``` ignore
|
|
||||||
/// toggle_ui(ui, &mut my_bool);
|
|
||||||
/// ```
|
|
||||||
pub fn toggle_ui(ui: &mut egui::Ui, on: &mut bool) -> egui::Response {
|
|
||||||
// Widget code can be broken up in four steps:
|
|
||||||
// 1. Decide a size for the widget
|
|
||||||
// 2. Allocate space for it
|
|
||||||
// 3. Handle interactions with the widget (if any)
|
|
||||||
// 4. Paint the widget
|
|
||||||
|
|
||||||
// 1. Deciding widget size:
|
|
||||||
// You can query the `ui` how much space is available,
|
|
||||||
// but in this example we have a fixed size widget based on the height of a standard button:
|
|
||||||
let desired_size = ui.spacing().interact_size.y * egui::vec2(2.0, 1.0);
|
|
||||||
|
|
||||||
// 2. Allocating space:
|
|
||||||
// This is where we get a region of the screen assigned.
|
|
||||||
// We also tell the Ui to sense clicks in the allocated region.
|
|
||||||
let (rect, mut response) = ui.allocate_exact_size(desired_size, egui::Sense::click());
|
|
||||||
|
|
||||||
// 3. Interact: Time to check for clicks!
|
|
||||||
if response.clicked() {
|
|
||||||
*on = !*on;
|
|
||||||
response.mark_changed(); // report back that the value changed
|
|
||||||
}
|
|
||||||
|
|
||||||
// Attach some meta-data to the response which can be used by screen readers:
|
|
||||||
response.widget_info(|| {
|
|
||||||
egui::WidgetInfo::selected(egui::WidgetType::Checkbox, ui.is_enabled(), *on, "")
|
|
||||||
});
|
|
||||||
|
|
||||||
// 4. Paint!
|
|
||||||
// Make sure we need to paint:
|
|
||||||
if ui.is_rect_visible(rect) {
|
|
||||||
// Let's ask for a simple animation from egui.
|
|
||||||
// egui keeps track of changes in the boolean associated with the id and
|
|
||||||
// returns an animated value in the 0-1 range for how much "on" we are.
|
|
||||||
let how_on = ui.ctx().animate_bool_responsive(response.id, *on);
|
|
||||||
// We will follow the current style by asking
|
|
||||||
// "how should something that is being interacted with be painted?".
|
|
||||||
// This will, for instance, give us different colors when the widget is hovered or clicked.
|
|
||||||
let visuals = ui.style().interact_selectable(&response, *on);
|
|
||||||
// All coordinates are in absolute screen coordinates so we use `rect` to place the elements.
|
|
||||||
let rect = rect.expand(visuals.expansion);
|
|
||||||
let radius = 0.5 * rect.height();
|
|
||||||
ui.painter()
|
|
||||||
.rect(rect, radius, visuals.bg_fill, visuals.bg_stroke);
|
|
||||||
// Paint the circle, animating it from left to right with `how_on`:
|
|
||||||
let circle_x = egui::lerp((rect.left() + radius)..=(rect.right() - radius), how_on);
|
|
||||||
let center = egui::pos2(circle_x, rect.center().y);
|
|
||||||
ui.painter()
|
|
||||||
.circle(center, 0.75 * radius, visuals.bg_fill, visuals.fg_stroke);
|
|
||||||
}
|
|
||||||
|
|
||||||
// All done! Return the interaction response so the user can check what happened
|
|
||||||
// (hovered, clicked, ...) and maybe show a tooltip:
|
|
||||||
response
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Here is the same code again, but a bit more compact:
|
|
||||||
#[allow(dead_code)]
|
|
||||||
fn toggle_ui_compact(ui: &mut egui::Ui, on: &mut bool) -> egui::Response {
|
|
||||||
let desired_size = ui.spacing().interact_size.y * egui::vec2(2.0, 1.0);
|
|
||||||
let (rect, mut response) = ui.allocate_exact_size(desired_size, egui::Sense::click());
|
|
||||||
if response.clicked() {
|
|
||||||
*on = !*on;
|
|
||||||
response.mark_changed();
|
|
||||||
}
|
|
||||||
response.widget_info(|| {
|
|
||||||
egui::WidgetInfo::selected(egui::WidgetType::Checkbox, ui.is_enabled(), *on, "")
|
|
||||||
});
|
|
||||||
|
|
||||||
if ui.is_rect_visible(rect) {
|
|
||||||
let how_on = ui.ctx().animate_bool_responsive(response.id, *on);
|
|
||||||
let visuals = ui.style().interact_selectable(&response, *on);
|
|
||||||
let rect = rect.expand(visuals.expansion);
|
|
||||||
let radius = 0.5 * rect.height();
|
|
||||||
ui.painter()
|
|
||||||
.rect(rect, radius, visuals.bg_fill, visuals.bg_stroke);
|
|
||||||
let circle_x = egui::lerp((rect.left() + radius)..=(rect.right() - radius), how_on);
|
|
||||||
let center = egui::pos2(circle_x, rect.center().y);
|
|
||||||
ui.painter()
|
|
||||||
.circle(center, 0.75 * radius, visuals.bg_fill, visuals.fg_stroke);
|
|
||||||
}
|
|
||||||
|
|
||||||
response
|
|
||||||
}
|
|
||||||
|
|
||||||
// A wrapper that allows the more idiomatic usage pattern: `ui.add(toggle(&mut my_bool))`
|
|
||||||
/// iOS-style toggle switch.
|
|
||||||
///
|
|
||||||
/// ## Example:
|
|
||||||
/// ``` ignore
|
|
||||||
/// ui.add(toggle(&mut my_bool));
|
|
||||||
/// ```
|
|
||||||
pub fn toggle(on: &mut bool) -> impl egui::Widget + '_ {
|
|
||||||
move |ui: &mut egui::Ui| toggle_ui(ui, on)
|
|
||||||
}
|
|
|
@ -1,10 +0,0 @@
|
||||||
[package]
|
|
||||||
name = "wxbox_client_native"
|
|
||||||
version = "0.1.0"
|
|
||||||
edition = "2021"
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
wxbox_client = { path = "../wxbox_client" }
|
|
||||||
eframe = "0.30"
|
|
||||||
egui = "0.30"
|
|
||||||
tracing-subscriber = "0.3"
|
|
|
@ -1,10 +0,0 @@
|
||||||
use wxbox_client::WxboxApp;
|
|
||||||
|
|
||||||
fn main() -> Result<(), eframe::Error> {
|
|
||||||
tracing_subscriber::fmt::init();
|
|
||||||
eframe::run_native(
|
|
||||||
"wxbox",
|
|
||||||
Default::default(),
|
|
||||||
Box::new(|cc| Ok(Box::new(WxboxApp::new(cc.egui_ctx.clone())))),
|
|
||||||
)
|
|
||||||
}
|
|
|
@ -1,12 +0,0 @@
|
||||||
[package]
|
|
||||||
name = "wxbox_client_wasm"
|
|
||||||
version = "0.1.0"
|
|
||||||
edition = "2021"
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
console_error_panic_hook = "0.1"
|
|
||||||
wasm-tracing = "1"
|
|
||||||
eframe = "0.30"
|
|
||||||
wxbox_client = { path = "../wxbox_client" }
|
|
||||||
wasm-bindgen-futures = "0.4"
|
|
||||||
web-sys = "0.3"
|
|
|
@ -1,33 +0,0 @@
|
||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<title>wxbox</title>
|
|
||||||
|
|
||||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no" />
|
|
||||||
|
|
||||||
<style>
|
|
||||||
html, body {
|
|
||||||
overflow: hidden;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
canvas {
|
|
||||||
margin-right: auto;
|
|
||||||
margin-left: auto;
|
|
||||||
display: block;
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<canvas id="target"></canvas>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
|
@ -1,30 +0,0 @@
|
||||||
use web_sys::wasm_bindgen::JsCast;
|
|
||||||
use wxbox_client::WxboxApp;
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
console_error_panic_hook::set_once();
|
|
||||||
wasm_tracing::set_as_global_default();
|
|
||||||
|
|
||||||
let web_options = eframe::WebOptions::default();
|
|
||||||
wasm_bindgen_futures::spawn_local(async {
|
|
||||||
let document = web_sys::window()
|
|
||||||
.expect("no window?")
|
|
||||||
.document()
|
|
||||||
.expect("no document?");
|
|
||||||
|
|
||||||
let canvas = document
|
|
||||||
.get_element_by_id("target")
|
|
||||||
.expect("failed to find target canvas for rendering")
|
|
||||||
.dyn_into::<web_sys::HtmlCanvasElement>()
|
|
||||||
.expect("#target is not a canvas");
|
|
||||||
|
|
||||||
eframe::WebRunner::new()
|
|
||||||
.start(
|
|
||||||
canvas,
|
|
||||||
web_options,
|
|
||||||
Box::new(|cc| Ok(Box::new(WxboxApp::new(cc.egui_ctx.clone())))),
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.expect("failed to start eframe")
|
|
||||||
})
|
|
||||||
}
|
|
Loading…
Add table
Reference in a new issue