diff --git a/Cargo.lock b/Cargo.lock index be1df68..9824bfa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8669,6 +8669,8 @@ version = "0.1.0" dependencies = [ "bevy", "bevy_asset_loader", + "bevy_flurx", + "bevy_flurx_ipc", "bevy_kira_audio", "bevy_webview_wry", "components", @@ -8676,6 +8678,8 @@ dependencies = [ "image", "log", "rand 0.8.5", + "serde", + "serde_json", "systems", "tokio", "wasm-bindgen", diff --git a/bun.lock b/bun.lock index 3c49c57..d281027 100644 --- a/bun.lock +++ b/bun.lock @@ -23,9 +23,11 @@ "@chakra-ui/react": "^3.21.1", "@emotion/react": "^11.14.0", "@eslint/js": "^9.29.0", + "@types/js-cookie": "^3.0.6", "@types/react": "^19.1.8", "@types/react-dom": "^19.1.6", "@vitejs/plugin-react-swc": "^3.10.2", + "bevy_flurx_api": "^0.1.0", "eslint": "^9.29.0", "eslint-plugin-react-hooks": "^5.2.0", "eslint-plugin-react-refresh": "^0.4.20", @@ -287,6 +289,8 @@ "@types/geojson-vt": ["@types/geojson-vt@3.2.5", "", { "dependencies": { "@types/geojson": "*" } }, "sha512-qDO7wqtprzlpe8FfQ//ClPV9xiuoh2nkIgiouIptON9w5jvD/fA4szvP9GBlDVdJ5dldAl0kX/sy3URbWwLx0g=="], + "@types/js-cookie": ["@types/js-cookie@3.0.6", "", {}, "sha512-wkw9yd1kEXOPnvEeEV1Go1MmxtBJL0RR79aOTAApecWFVu7w0NNXNqhcWgvw2YgZDYadliXkl14pa3WXw5jlCQ=="], + "@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="], "@types/mapbox__point-geometry": ["@types/mapbox__point-geometry@0.1.4", "", {}, "sha512-mUWlSxAmYLfwnRBmgYV86tgYmMIICX4kza8YnE/eIlywGe2XoOxlpVnXWwir92xRLjwyarqwpu2EJKD2pk0IUA=="], @@ -485,6 +489,8 @@ "base-map": ["base-map@workspace:packages/base-map"], + "bevy_flurx_api": ["bevy_flurx_api@0.1.0", "", { "dependencies": { "global": "^4.4.0", "pnpm": "^9.15.0" } }, "sha512-h74CxORqMa7iwTGTPe5ItkABEf7ZQIRlUSbeYPSBkF1SXnFCAaeF7FE4FklFmpRCKTYhryqVez02h75QACr45g=="], + "brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="], "braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="], @@ -521,6 +527,8 @@ "deep-is": ["deep-is@0.1.4", "", {}, "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="], + "dom-walk": ["dom-walk@0.1.2", "", {}, "sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w=="], + "earcut": ["earcut@3.0.1", "", {}, "sha512-0l1/0gOjESMeQyYaK5IDiPNvFeu93Z/cO0TjZh9eZ1vyCtZnA7KMZ8rQggpsJHIbGSdrqYq9OhuveadOVHCshw=="], "error-ex": ["error-ex@1.3.2", "", { "dependencies": { "is-arrayish": "^0.2.1" } }, "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g=="], @@ -589,6 +597,8 @@ "glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="], + "global": ["global@4.4.0", "", { "dependencies": { "min-document": "^2.19.0", "process": "^0.11.10" } }, "sha512-wv/LAoHdRE3BeTGz53FAamhGlPLhlssK45usmGFThIi4XqnBmjKQ16u+RNbP7WvigRZDxUsM0J3gcQ5yicaL0w=="], + "globals": ["globals@16.3.0", "", {}, "sha512-bqWEnJ1Nt3neqx2q5SFfGS8r/ahumIakg3HcwtNlrVlwXIeNumWn/c7Pn/wKzGhf6SaW6H6uWXLqC30STCMchQ=="], "globrex": ["globrex@0.1.2", "", {}, "sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg=="], @@ -667,6 +677,8 @@ "micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="], + "min-document": ["min-document@2.19.0", "", { "dependencies": { "dom-walk": "^0.1.0" } }, "sha512-9Wy1B3m3f66bPPmU5hdA4DR4PB2OfDU/+GS3yAB7IQozE3tqXaVv2zOjgla7MEGSRv95+ILmOuvhLkOK6wJtCQ=="], + "minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="], "minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="], @@ -707,12 +719,16 @@ "picomatch": ["picomatch@4.0.2", "", {}, "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg=="], + "pnpm": ["pnpm@9.15.9", "", { "bin": { "pnpm": "bin/pnpm.cjs", "pnpx": "bin/pnpx.cjs" } }, "sha512-aARhQYk8ZvrQHAeSMRKOmvuJ74fiaR1p5NQO7iKJiClf1GghgbrlW1hBjDolO95lpQXsfF+UA+zlzDzTfc8lMQ=="], + "postcss": ["postcss@8.5.6", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg=="], "potpack": ["potpack@2.0.0", "", {}, "sha512-Q+/tYsFU9r7xoOJ+y/ZTtdVQwTWfzjbiXBDMM/JKUux3+QPP02iUuIoeBQ+Ot6oEDlC+/PGjB/5A3K7KKb7hcw=="], "prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="], + "process": ["process@0.11.10", "", {}, "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A=="], + "protocol-buffers-schema": ["protocol-buffers-schema@3.6.0", "", {}, "sha512-TdDRD+/QNdrCGCE7v8340QyuXd4kIWIgapsE2+n/SaGiSSbomYl4TjHlvIoCWRpE7wFt02EpB35VVA2ImcBVqw=="], "proxy-compare": ["proxy-compare@3.0.1", "", {}, "sha512-V9plBAt3qjMlS1+nC8771KNf6oJ12gExvaxnNzN/9yVRLdTv/lc+oJlnSzrdYDAvBfTStPCoiaCOTmTs0adv7Q=="], diff --git a/crates/yachtpit/Cargo.toml b/crates/yachtpit/Cargo.toml index 0d0fdc4..c6fb8e9 100644 --- a/crates/yachtpit/Cargo.toml +++ b/crates/yachtpit/Cargo.toml @@ -6,8 +6,6 @@ authors = ["seemueller-io "] edition = "2021" exclude = ["dist", "build", "assets", "credits"] - - [profile.dev.package."*"] opt-level = 3 @@ -63,16 +61,8 @@ systems = { path = "../systems" } components = { path = "../components" } wasm-bindgen = "0.2" web-sys = { version = "0.3.53", features = ["Document", "Element", "HtmlElement", "Window"] } - -# Platform-specific tokio features -[target.'cfg(not(target_arch = "wasm32"))'.dependencies] -tokio = { version = "1.0", features = ["rt", "rt-multi-thread"] } -image = "0.25" -winit = "0.30" -bevy_webview_wry = { version = "0.4", default-features = false, features = ["api"] } - -[target.'cfg(target_arch = "wasm32")'.dependencies] -tokio = { version = "1.0", features = ["rt"] } +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" # keep the following in sync with Bevy's dependencies winit = { version = "0.30", default-features = false } @@ -80,5 +70,17 @@ image = { version = "0.25", default-features = false } ## This greatly improves WGPU's performance due to its heavy use of trace! calls log = { version = "0.4", features = ["max_level_debug", "release_max_level_warn"] } +# Platform-specific tokio features +[target.'cfg(not(target_arch = "wasm32"))'.dependencies] +tokio = { version = "1.0", features = ["rt", "rt-multi-thread"] } +image = "0.25" +winit = "0.30" +bevy_webview_wry = { version = "0.4", default-features = false, features = ["api"] } +bevy_flurx = "0.11" +bevy_flurx_ipc = "0.4.0" + +[target.'cfg(target_arch = "wasm32")'.dependencies] +tokio = { version = "1.0", features = ["rt"] } + [build-dependencies] embed-resource = "1" diff --git a/crates/yachtpit/src/main.rs b/crates/yachtpit/src/main.rs index 7364974..07ab245 100644 --- a/crates/yachtpit/src/main.rs +++ b/crates/yachtpit/src/main.rs @@ -14,31 +14,6 @@ use winit::window::Icon; use bevy_webview_wry::WebviewWryPlugin; fn main() { - #[cfg(target_arch = "wasm32")] - App::new() - .insert_resource(ClearColor(Color::NONE)) - .add_plugins( - DefaultPlugins - .set(WindowPlugin { - primary_window: Some(Window { - // Bind to canvas included in `index.html` - canvas: Some("#yachtpit-canvas".to_owned()), - fit_canvas_to_parent: true, - // Tells wasm not to override default event handling, like F5 and Ctrl+R - prevent_default_event_handling: false, - ..default() - }), - ..default() - }) - .set(AssetPlugin { - meta_check: AssetMetaCheck::Never, - ..default() - }), - ) - .add_plugins(GamePlugin) - .add_systems(Startup, set_window_icon) - .run(); - #[cfg(not(target_arch = "wasm32"))] App::new() .insert_resource(ClearColor(Color::NONE)) @@ -65,7 +40,33 @@ fn main() { .add_plugins(WebviewWryPlugin::default()) .run(); + #[cfg(target_arch = "wasm32")] + App::new() + .insert_resource(ClearColor(Color::NONE)) + .add_plugins( + DefaultPlugins + .set(WindowPlugin { + primary_window: Some(Window { + // Bind to canvas included in `index.html` + canvas: Some("#yachtpit-canvas".to_owned()), + fit_canvas_to_parent: true, + // Tells wasm not to override default event handling, like F5 and Ctrl+R + prevent_default_event_handling: false, + ..default() + }), + ..default() + }) + .set(AssetPlugin { + meta_check: AssetMetaCheck::Never, + ..default() + }), + ) + .add_plugins(GamePlugin) + .add_systems(Startup, set_window_icon) + .run(); + } + // Sets the icon on windows and X11 fn set_window_icon( windows: NonSend, diff --git a/crates/yachtpit/src/ui/gps_map.rs b/crates/yachtpit/src/ui/gps_map.rs index 85008b1..692b296 100644 --- a/crates/yachtpit/src/ui/gps_map.rs +++ b/crates/yachtpit/src/ui/gps_map.rs @@ -2,7 +2,9 @@ use bevy::prelude::*; use bevy::render::view::RenderLayers; use bevy::window::Window; use std::collections::HashMap; +use bevy_flurx::prelude::*; use bevy_webview_wry::prelude::*; +use serde::{Deserialize, Serialize}; /// Render layer for GPS map entities to isolate them from other cameras @@ -12,6 +14,38 @@ use bevy_webview_wry::prelude::*; /// Render layer for GPS map entities to isolate them from other cameras const GPS_MAP_LAYER: usize = 1; +/// GPS position data +#[derive(Serialize, Debug, Clone)] +pub struct GpsPosition { + pub latitude: f64, + pub longitude: f64, + pub zoom: u8, +} + +/// Vessel position and status data +#[derive(Serialize, Debug, Clone)] +pub struct VesselStatus { + pub latitude: f64, + pub longitude: f64, + pub heading: f64, + pub speed: f64, +} + +/// Map view change parameters +#[derive(Deserialize, Debug, Clone)] +pub struct MapViewParams { + pub latitude: f64, + pub longitude: f64, + pub zoom: u8, +} + +/// Authentication parameters +#[derive(Deserialize, Debug, Clone)] +pub struct AuthParams { + pub authenticated: bool, + pub token: Option, +} + /// Component to mark the GPS map window #[derive(Component)] pub struct GpsMapWindow; @@ -32,6 +66,10 @@ pub struct GpsMapState { pub center_lon: f64, pub zoom_level: u8, pub tile_cache: HashMap>, + pub vessel_lat: f64, + pub vessel_lon: f64, + pub vessel_heading: f64, + pub vessel_speed: f64, } impl GpsMapState { @@ -42,6 +80,10 @@ impl GpsMapState { center_lon: -1.4497, zoom_level: 10, tile_cache: HashMap::new(), + vessel_lat: 43.6377, // Default vessel position + vessel_lon: -1.4497, + vessel_heading: 0.0, + vessel_speed: 0.0, } } } @@ -52,7 +94,11 @@ pub struct GpsMapPlugin; impl Plugin for GpsMapPlugin { fn build(&self, app: &mut App) { app.init_resource::() - .add_systems(Update, (handle_gps_map_window_events, update_map_tiles)); + .add_systems(Update, ( + handle_gps_map_window_events, + update_map_tiles, + send_periodic_gps_updates, + )); } } @@ -184,9 +230,128 @@ pub fn spawn_gps_map_window(commands: &mut Commands, gps_map_state: &mut ResMut< #[cfg(not(target_arch = "wasm32"))] fn spawn_gps_webview(commands: &mut Commands, gps_map_state: &mut ResMut) { if let Some(win) = gps_map_state.window_id { - commands.entity(win).insert(Webview::Uri(WebviewUri::relative_local( - // Using the build output of the base-map package - "packages/base-map/dist/index.html", - ))); + + + commands.entity(win).insert(( + IpcHandlers::new([ + navigation_clicked, + search_clicked, + map_view_changed, + auth_status_changed, + get_map_init, + get_vessel_status + ]), + Webview::Uri(WebviewUri::relative_local( + // Using the build output of the base-map package + "packages/base-map/dist/index.html", + )) + )); } -} \ No newline at end of file +} + +// GPS Map IPC Commands using bevy_flurx_ipc + +/// Handle navigation button click +#[command] +fn navigation_clicked( + WebviewEntity(_entity): WebviewEntity, +) -> Action<(), ()> { + once::run(|_: In<()>| { + info!("Navigation button clicked in React"); + // Handle navigation logic here + }).into() +} + +/// Handle search button click +#[command] +fn search_clicked( + WebviewEntity(_entity): WebviewEntity, +) -> Action<(), ()> { + once::run(|_: In<()>| { + info!("Search button clicked in React"); + // Handle search logic here + }).into() +} + +/// Handle map view change +#[command] +fn map_view_changed( + In(params): In, + WebviewEntity(_entity): WebviewEntity, +) -> Action<(f64, f64, u8), ()> { + once::run(|In((latitude, longitude, zoom)): In<(f64, f64, u8)>, mut gps_map_state: ResMut| { + info!("Map view changed: lat={}, lon={}, zoom={}", latitude, longitude, zoom); + gps_map_state.center_lat = latitude; + gps_map_state.center_lon = longitude; + gps_map_state.zoom_level = zoom; + }).with((params.latitude, params.longitude, params.zoom)).into() +} + +/// Handle authentication status change +#[command] +fn auth_status_changed( + In(params): In, + WebviewEntity(_entity): WebviewEntity, +) -> Action<(bool, Option), ()> { + once::run(|In((authenticated, token)): In<(bool, Option)>| { + info!("Auth status changed: authenticated={}, token={:?}", authenticated, token); + // Handle authentication status change + }).with((params.authenticated, params.token)).into() +} + +/// Get map initialization data +#[command] +async fn get_map_init( + WebviewEntity(_entity): WebviewEntity, + task: ReactorTask, +) -> GpsPosition { + task.will(Update, once::run(|gps_map_state: Res| { + GpsPosition { + latitude: gps_map_state.center_lat, + longitude: gps_map_state.center_lon, + zoom: gps_map_state.zoom_level, + } + })).await +} + +/// Get current vessel status +#[command] +async fn get_vessel_status( + WebviewEntity(_entity): WebviewEntity, + task: ReactorTask, +) -> VesselStatus { + task.will(Update, once::run(|gps_map_state: Res| { + VesselStatus { + latitude: gps_map_state.vessel_lat, + longitude: gps_map_state.vessel_lon, + heading: gps_map_state.vessel_heading, + speed: gps_map_state.vessel_speed, + } + })).await +} + +/// System to send periodic GPS updates for testing +fn send_periodic_gps_updates( + mut gps_map_state: ResMut, + time: Res