Modularize (#1)
* configure workspaces * Modularize domain logic by creating a new `models` crate. * Moved `LoadingPlugin` and `MenuPlugin` from `core` to a new `ui` module. Updated imports accordingly. * add theme for instruments * trunk serve works, remove audio and textures * remove loading indicator and assets * rename models to systems * seperate systems and components from models * Refactor instrument cluster to leverage reusable composition utilities. --------- Co-authored-by: geoffsee <>
17
crates/components/Cargo.toml
Normal file
@@ -0,0 +1,17 @@
|
||||
[package]
|
||||
name = "components"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
publish = false
|
||||
|
||||
[dependencies]
|
||||
bevy = { workspace = true, features = [
|
||||
"bevy_asset",
|
||||
"bevy_color",
|
||||
"bevy_core_pipeline",
|
||||
"bevy_render",
|
||||
"bevy_sprite",
|
||||
"bevy_text",
|
||||
"bevy_ui",
|
||||
"bevy_window",
|
||||
] }
|
228
crates/components/src/cluster.rs
Normal file
@@ -0,0 +1,228 @@
|
||||
use bevy::prelude::*;
|
||||
use super::instruments::*;
|
||||
use super::theme::*;
|
||||
use super::composition::*;
|
||||
|
||||
#[derive(Component)]
|
||||
pub struct InstrumentCluster;
|
||||
|
||||
#[derive(Component)]
|
||||
pub struct GpsIndicator;
|
||||
|
||||
#[derive(Component)]
|
||||
pub struct RadarIndicator;
|
||||
|
||||
#[derive(Component)]
|
||||
pub struct AisIndicator;
|
||||
|
||||
#[derive(Component)]
|
||||
pub struct SystemDisplay;
|
||||
|
||||
#[derive(Component)]
|
||||
pub struct WindDisplay;
|
||||
|
||||
/// Sets up the main instrument cluster UI using composable components
|
||||
pub fn setup_instrument_cluster(mut commands: Commands) {
|
||||
// Spawn camera since we're bypassing the menu system
|
||||
commands.spawn((Camera2d, Msaa::Off));
|
||||
|
||||
// Create main container using composition
|
||||
commands.spawn((
|
||||
main_container_node(),
|
||||
BackgroundColor(BACKGROUND_COLOR_PRIMARY),
|
||||
InstrumentCluster,
|
||||
))
|
||||
.with_children(|parent| {
|
||||
// Top row - Main navigation and speed (60% height)
|
||||
parent.spawn(row_container_node(60.0, 20.0))
|
||||
.with_children(|row| {
|
||||
// Speed Gauge
|
||||
row.spawn((
|
||||
circular_gauge_node(),
|
||||
BackgroundColor(BACKGROUND_COLOR_PRIMARY),
|
||||
BorderColor(BORDER_COLOR_PRIMARY),
|
||||
SpeedGauge,
|
||||
))
|
||||
.with_children(|gauge| {
|
||||
gauge.spawn(create_text("SPEED", FONT_SIZE_SMALL, TEXT_COLOR_PRIMARY));
|
||||
gauge.spawn(create_text("12.5", FONT_SIZE_LARGE, TEXT_COLOR_SUCCESS));
|
||||
gauge.spawn(create_text("KTS", FONT_SIZE_SMALL, TEXT_COLOR_PRIMARY));
|
||||
});
|
||||
|
||||
// Central Navigation Display
|
||||
row.spawn((
|
||||
navigation_display_node(),
|
||||
BackgroundColor(BACKGROUND_COLOR_ACCENT),
|
||||
BorderColor(BORDER_COLOR_PRIMARY),
|
||||
NavigationDisplay,
|
||||
))
|
||||
.with_children(|nav| {
|
||||
nav.spawn(create_text("NAVIGATION", FONT_SIZE_NORMAL, TEXT_COLOR_PRIMARY));
|
||||
nav.spawn((
|
||||
create_text("045°", FONT_SIZE_LARGE, TEXT_COLOR_PRIMARY).0,
|
||||
create_text("045°", FONT_SIZE_LARGE, TEXT_COLOR_PRIMARY).1,
|
||||
create_text("045°", FONT_SIZE_LARGE, TEXT_COLOR_PRIMARY).2,
|
||||
CompassGauge,
|
||||
));
|
||||
nav.spawn(create_text("HEADING", FONT_SIZE_NORMAL, TEXT_COLOR_PRIMARY));
|
||||
});
|
||||
|
||||
// Depth Gauge
|
||||
row.spawn((
|
||||
circular_gauge_node(),
|
||||
BackgroundColor(BACKGROUND_COLOR_PRIMARY),
|
||||
BorderColor(BORDER_COLOR_PRIMARY),
|
||||
DepthGauge,
|
||||
))
|
||||
.with_children(|gauge| {
|
||||
gauge.spawn(create_text("DEPTH", FONT_SIZE_SMALL, TEXT_COLOR_PRIMARY));
|
||||
gauge.spawn(create_text("15.2", FONT_SIZE_LARGE, Color::linear_rgb(0.0, 1.0, 0.8)));
|
||||
gauge.spawn(create_text("M", FONT_SIZE_SMALL, TEXT_COLOR_PRIMARY));
|
||||
});
|
||||
});
|
||||
|
||||
// Bottom row - Engine and system status (40% height)
|
||||
parent.spawn(row_container_node(40.0, 20.0))
|
||||
.with_children(|row| {
|
||||
// Engine Status Panel
|
||||
row.spawn((
|
||||
status_panel_node(200.0, 150.0),
|
||||
BackgroundColor(BACKGROUND_COLOR_PRIMARY),
|
||||
BorderColor(BORDER_COLOR_PRIMARY),
|
||||
EngineStatus,
|
||||
))
|
||||
.with_children(|panel| {
|
||||
panel.spawn(create_text("ENGINE", FONT_SIZE_NORMAL, TEXT_COLOR_PRIMARY));
|
||||
panel.spawn(create_text("82°C", FONT_SIZE_LARGE, TEXT_COLOR_SUCCESS));
|
||||
panel.spawn(create_text("TEMP NORMAL", FONT_SIZE_SMALL, TEXT_COLOR_PRIMARY));
|
||||
});
|
||||
|
||||
// System Status Grid
|
||||
row.spawn((
|
||||
status_panel_node(250.0, 150.0),
|
||||
BackgroundColor(BACKGROUND_COLOR_SECONDARY),
|
||||
BorderColor(BORDER_COLOR_SECONDARY),
|
||||
))
|
||||
.with_children(|grid| {
|
||||
grid.spawn(create_text("SYSTEMS", 12.0, TEXT_COLOR_SECONDARY));
|
||||
|
||||
// Fuel Level Bar
|
||||
grid.spawn(progress_bar_node())
|
||||
.with_children(|bar| {
|
||||
bar.spawn(create_text("FUEL", FONT_SIZE_SMALL, TEXT_COLOR_PRIMARY));
|
||||
bar.spawn((
|
||||
progress_bar_background_node(),
|
||||
BackgroundColor(BACKGROUND_COLOR_PRIMARY),
|
||||
BorderColor(TEXT_COLOR_PRIMARY),
|
||||
))
|
||||
.with_children(|bar_bg| {
|
||||
bar_bg.spawn((
|
||||
progress_bar_fill_node(75.0),
|
||||
BackgroundColor(TEXT_COLOR_SUCCESS),
|
||||
));
|
||||
});
|
||||
bar.spawn(create_text("75%", FONT_SIZE_SMALL, TEXT_COLOR_PRIMARY));
|
||||
});
|
||||
|
||||
// Battery Level Bar
|
||||
grid.spawn(progress_bar_node())
|
||||
.with_children(|bar| {
|
||||
bar.spawn(create_text("BATTERY", FONT_SIZE_SMALL, TEXT_COLOR_PRIMARY));
|
||||
bar.spawn((
|
||||
progress_bar_background_node(),
|
||||
BackgroundColor(BACKGROUND_COLOR_SECONDARY),
|
||||
BorderColor(BORDER_COLOR_SECONDARY),
|
||||
))
|
||||
.with_children(|bar_bg| {
|
||||
bar_bg.spawn((
|
||||
progress_bar_fill_node(88.0),
|
||||
BackgroundColor(BACKGROUND_COLOR_SECONDARY),
|
||||
));
|
||||
});
|
||||
bar.spawn(create_text("88%", FONT_SIZE_SMALL, TEXT_COLOR_PRIMARY));
|
||||
});
|
||||
|
||||
// System Status Indicators
|
||||
grid.spawn(Node {
|
||||
flex_direction: FlexDirection::Row,
|
||||
justify_content: JustifyContent::SpaceBetween,
|
||||
width: Val::Percent(100.0),
|
||||
..default()
|
||||
})
|
||||
.with_children(|indicators| {
|
||||
// GPS Indicator
|
||||
indicators.spawn((
|
||||
Button,
|
||||
system_indicator_node(),
|
||||
BackgroundColor(BACKGROUND_COLOR_SECONDARY),
|
||||
BorderColor(BORDER_COLOR_SECONDARY),
|
||||
GpsIndicator,
|
||||
))
|
||||
.with_children(|indicator| {
|
||||
indicator.spawn(create_text("🛰️", FONT_SIZE_NORMAL, TEXT_COLOR_PRIMARY));
|
||||
indicator.spawn(create_text("GPS", FONT_SIZE_SMALL, TEXT_COLOR_PRIMARY));
|
||||
});
|
||||
|
||||
// RADAR Indicator
|
||||
indicators.spawn((
|
||||
Button,
|
||||
system_indicator_node(),
|
||||
BackgroundColor(BACKGROUND_COLOR_SECONDARY),
|
||||
BorderColor(BORDER_COLOR_SECONDARY),
|
||||
RadarIndicator,
|
||||
))
|
||||
.with_children(|indicator| {
|
||||
indicator.spawn(create_text("📡", FONT_SIZE_NORMAL, Color::linear_rgb(0.0, 1.0, 0.0)));
|
||||
indicator.spawn(create_text("RADAR", FONT_SIZE_SMALL, TEXT_COLOR_PRIMARY));
|
||||
});
|
||||
|
||||
// AIS Indicator
|
||||
indicators.spawn((
|
||||
Button,
|
||||
system_indicator_node(),
|
||||
BackgroundColor(BACKGROUND_COLOR_SECONDARY),
|
||||
BorderColor(BORDER_COLOR_SECONDARY),
|
||||
AisIndicator,
|
||||
))
|
||||
.with_children(|indicator| {
|
||||
indicator.spawn(create_text("🚢", FONT_SIZE_NORMAL, TEXT_COLOR_PRIMARY));
|
||||
indicator.spawn(create_text("AIS", FONT_SIZE_SMALL, TEXT_COLOR_PRIMARY));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// Wind Information
|
||||
row.spawn((
|
||||
status_panel_node(200.0, 150.0),
|
||||
BackgroundColor(BACKGROUND_COLOR_ACCENT),
|
||||
BorderColor(BORDER_COLOR_PRIMARY),
|
||||
WindDisplay,
|
||||
))
|
||||
.with_children(|panel| {
|
||||
panel.spawn(create_text("WIND", FONT_SIZE_NORMAL, TEXT_COLOR_PRIMARY));
|
||||
panel.spawn(create_text("8.3 KTS", FONT_SIZE_NORMAL, TEXT_COLOR_SUCCESS));
|
||||
panel.spawn(create_text("120° REL", FONT_SIZE_NORMAL, TEXT_COLOR_PRIMARY));
|
||||
});
|
||||
});
|
||||
|
||||
// System Display Area
|
||||
parent.spawn((
|
||||
Node {
|
||||
width: Val::Percent(100.0),
|
||||
height: Val::Px(200.0),
|
||||
border: UiRect::all(Val::Px(2.0)),
|
||||
flex_direction: FlexDirection::Column,
|
||||
justify_content: JustifyContent::Center,
|
||||
align_items: AlignItems::Center,
|
||||
padding: UiRect::all(Val::Px(20.0)),
|
||||
..default()
|
||||
},
|
||||
BackgroundColor(BACKGROUND_COLOR_PRIMARY),
|
||||
BorderColor(BORDER_COLOR_PRIMARY),
|
||||
SystemDisplay,
|
||||
))
|
||||
.with_children(|display| {
|
||||
display.spawn(create_text("Select a system above to view details", FONT_SIZE_SMALL, TEXT_COLOR_SECONDARY));
|
||||
});
|
||||
});
|
||||
}
|
123
crates/components/src/composition.rs
Normal file
@@ -0,0 +1,123 @@
|
||||
use bevy::prelude::*;
|
||||
|
||||
/// Composition utilities for building instrument cluster components
|
||||
/// This module provides reusable building blocks for creating UI components
|
||||
|
||||
/// Creates a circular gauge node bundle
|
||||
pub fn circular_gauge_node() -> Node {
|
||||
Node {
|
||||
width: Val::Px(180.0),
|
||||
height: Val::Px(180.0),
|
||||
border: UiRect::all(Val::Px(2.0)),
|
||||
flex_direction: FlexDirection::Column,
|
||||
justify_content: JustifyContent::Center,
|
||||
align_items: AlignItems::Center,
|
||||
..default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a status panel node bundle
|
||||
pub fn status_panel_node(width: f32, height: f32) -> Node {
|
||||
Node {
|
||||
width: Val::Px(width),
|
||||
height: Val::Px(height),
|
||||
border: UiRect::all(Val::Px(1.0)),
|
||||
flex_direction: FlexDirection::Column,
|
||||
justify_content: JustifyContent::SpaceEvenly,
|
||||
align_items: AlignItems::Center,
|
||||
padding: UiRect::all(Val::Px(10.0)),
|
||||
..default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a progress bar container node
|
||||
pub fn progress_bar_node() -> Node {
|
||||
Node {
|
||||
flex_direction: FlexDirection::Row,
|
||||
align_items: AlignItems::Center,
|
||||
justify_content: JustifyContent::SpaceBetween,
|
||||
width: Val::Percent(100.0),
|
||||
..default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a progress bar background node
|
||||
pub fn progress_bar_background_node() -> Node {
|
||||
Node {
|
||||
width: Val::Px(80.0),
|
||||
height: Val::Px(8.0),
|
||||
border: UiRect::all(Val::Px(1.0)),
|
||||
..default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a progress bar fill node
|
||||
pub fn progress_bar_fill_node(percentage: f32) -> Node {
|
||||
Node {
|
||||
width: Val::Percent(percentage),
|
||||
height: Val::Percent(100.0),
|
||||
..default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a system indicator button node
|
||||
pub fn system_indicator_node() -> Node {
|
||||
Node {
|
||||
flex_direction: FlexDirection::Column,
|
||||
align_items: AlignItems::Center,
|
||||
justify_content: JustifyContent::Center,
|
||||
padding: UiRect::all(Val::Px(8.0)),
|
||||
width: Val::Px(60.0),
|
||||
height: Val::Px(40.0),
|
||||
border: UiRect::all(Val::Px(1.0)),
|
||||
..default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a navigation display node
|
||||
pub fn navigation_display_node() -> Node {
|
||||
Node {
|
||||
width: Val::Px(300.0),
|
||||
height: Val::Px(300.0),
|
||||
border: UiRect::all(Val::Px(2.0)),
|
||||
flex_direction: FlexDirection::Column,
|
||||
justify_content: JustifyContent::Center,
|
||||
align_items: AlignItems::Center,
|
||||
..default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a row container node
|
||||
pub fn row_container_node(height_percent: f32, padding: f32) -> Node {
|
||||
Node {
|
||||
width: Val::Percent(100.0),
|
||||
height: Val::Percent(height_percent),
|
||||
flex_direction: FlexDirection::Row,
|
||||
justify_content: JustifyContent::SpaceEvenly,
|
||||
align_items: AlignItems::Center,
|
||||
padding: UiRect::all(Val::Px(padding)),
|
||||
..default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates the main container node
|
||||
pub fn main_container_node() -> Node {
|
||||
Node {
|
||||
width: Val::Percent(100.0),
|
||||
height: Val::Percent(100.0),
|
||||
flex_direction: FlexDirection::Column,
|
||||
..default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a text bundle with specified content, font size, and color
|
||||
pub fn create_text(content: &str, font_size: f32, color: Color) -> (Text, TextFont, TextColor) {
|
||||
(
|
||||
Text::new(content),
|
||||
TextFont {
|
||||
font_size,
|
||||
..default()
|
||||
},
|
||||
TextColor(color),
|
||||
)
|
||||
}
|
106
crates/components/src/instruments.rs
Normal file
@@ -0,0 +1,106 @@
|
||||
use bevy::prelude::*;
|
||||
|
||||
/// Individual instrument components
|
||||
#[derive(Component)]
|
||||
pub struct SpeedGauge;
|
||||
|
||||
#[derive(Component)]
|
||||
pub struct DepthGauge;
|
||||
|
||||
#[derive(Component)]
|
||||
pub struct CompassGauge;
|
||||
|
||||
#[derive(Component)]
|
||||
pub struct EngineStatus;
|
||||
|
||||
#[derive(Component)]
|
||||
pub struct NavigationDisplay;
|
||||
|
||||
/// Yacht data resource containing all sensor readings
|
||||
#[derive(Resource)]
|
||||
pub struct YachtData {
|
||||
pub speed: f32, // knots
|
||||
pub depth: f32, // meters
|
||||
pub heading: f32, // degrees
|
||||
pub engine_temp: f32, // celsius
|
||||
pub fuel_level: f32, // percentage
|
||||
pub battery_level: f32, // percentage
|
||||
pub wind_speed: f32, // knots
|
||||
pub wind_direction: f32, // degrees
|
||||
}
|
||||
|
||||
impl Default for YachtData {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
speed: 12.5,
|
||||
depth: 15.2,
|
||||
heading: 045.0,
|
||||
engine_temp: 82.0,
|
||||
fuel_level: 75.0,
|
||||
battery_level: 88.0,
|
||||
wind_speed: 8.3,
|
||||
wind_direction: 120.0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Updates yacht data with simulated sensor readings
|
||||
pub fn update_yacht_data(mut yacht_data: ResMut<YachtData>, time: Res<Time>) {
|
||||
let t = time.elapsed_secs();
|
||||
|
||||
// Simulate realistic yacht data with some variation
|
||||
yacht_data.speed = 12.5 + (t * 0.3).sin() * 2.0;
|
||||
yacht_data.depth = 15.2 + (t * 0.1).sin() * 3.0;
|
||||
yacht_data.heading = (yacht_data.heading + time.delta_secs() * 5.0) % 360.0;
|
||||
yacht_data.engine_temp = 82.0 + (t * 0.2).sin() * 3.0;
|
||||
yacht_data.wind_speed = 8.3 + (t * 0.4).sin() * 1.5;
|
||||
yacht_data.wind_direction = (yacht_data.wind_direction + time.delta_secs() * 10.0) % 360.0;
|
||||
|
||||
// Slowly drain fuel and battery (very slowly for demo purposes)
|
||||
yacht_data.fuel_level = (yacht_data.fuel_level - time.delta_secs() * 0.01).max(0.0);
|
||||
yacht_data.battery_level = (yacht_data.battery_level - time.delta_secs() * 0.005).max(0.0);
|
||||
}
|
||||
|
||||
/// Updates the display values for all instrument gauges
|
||||
pub fn update_instrument_displays(
|
||||
yacht_data: Res<YachtData>,
|
||||
mut speed_query: Query<&mut Text, (With<SpeedGauge>, Without<DepthGauge>, Without<CompassGauge>)>,
|
||||
mut depth_query: Query<&mut Text, (With<DepthGauge>, Without<SpeedGauge>, Without<CompassGauge>)>,
|
||||
mut compass_query: Query<&mut Text, (With<CompassGauge>, Without<SpeedGauge>, Without<DepthGauge>)>,
|
||||
) {
|
||||
// Update speed display
|
||||
for mut text in speed_query.iter_mut() {
|
||||
if text.0.contains('.') {
|
||||
text.0 = format!("{:.1}", yacht_data.speed);
|
||||
}
|
||||
}
|
||||
|
||||
// Update depth display
|
||||
for mut text in depth_query.iter_mut() {
|
||||
if text.0.contains('.') {
|
||||
text.0 = format!("{:.1}", yacht_data.depth);
|
||||
}
|
||||
}
|
||||
|
||||
// Update compass display
|
||||
for mut text in compass_query.iter_mut() {
|
||||
if text.0.contains('°') {
|
||||
text.0 = format!("{:03.0}°", yacht_data.heading);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_yacht_data_default() {
|
||||
let yacht_data = YachtData::default();
|
||||
assert_eq!(yacht_data.speed, 12.5);
|
||||
assert_eq!(yacht_data.depth, 15.2);
|
||||
assert_eq!(yacht_data.heading, 45.0);
|
||||
assert_eq!(yacht_data.fuel_level, 75.0);
|
||||
assert_eq!(yacht_data.battery_level, 88.0);
|
||||
}
|
||||
}
|
16
crates/components/src/lib.rs
Normal file
@@ -0,0 +1,16 @@
|
||||
#![allow(clippy::type_complexity)]
|
||||
|
||||
// Components crate for yacht pit application
|
||||
// This crate contains reusable UI and game components
|
||||
|
||||
pub mod ui;
|
||||
pub mod instruments;
|
||||
pub mod theme;
|
||||
pub mod cluster;
|
||||
pub mod composition;
|
||||
|
||||
pub use ui::*;
|
||||
pub use instruments::*;
|
||||
pub use theme::*;
|
||||
pub use cluster::*;
|
||||
pub use composition::*;
|
34
crates/components/src/theme.rs
Normal file
@@ -0,0 +1,34 @@
|
||||
use bevy::prelude::*;
|
||||
use bevy::color::Color;
|
||||
|
||||
pub const BACKGROUND_COLOR_PRIMARY: Color = Color::linear_rgb(0.05, 0.05, 0.1);
|
||||
pub const BACKGROUND_COLOR_SECONDARY: Color = Color::linear_rgb(0.1, 0.1, 0.15);
|
||||
pub const BACKGROUND_COLOR_ACCENT: Color = Color::linear_rgb(0.1, 0.15, 0.2);
|
||||
|
||||
pub const BORDER_COLOR_PRIMARY: Color = Color::linear_rgb(0.0, 0.8, 1.0);
|
||||
pub const BORDER_COLOR_SECONDARY: Color = Color::linear_rgb(0.8, 0.4, 0.0);
|
||||
pub const BORDER_COLOR_TERTIARY: Color = Color::linear_rgb(0.4, 0.4, 0.6);
|
||||
|
||||
pub const TEXT_COLOR_PRIMARY: Color = Color::linear_rgb(0.0, 0.8, 1.0);
|
||||
pub const TEXT_COLOR_SECONDARY: Color = Color::linear_rgb(0.6, 0.6, 0.6);
|
||||
pub const TEXT_COLOR_SUCCESS: Color = Color::linear_rgb(0.0, 1.0, 0.0);
|
||||
pub const TEXT_COLOR_WARNING: Color = Color::linear_rgb(0.8, 0.4, 0.0);
|
||||
pub const TEXT_COLOR_DANGER: Color = Color::linear_rgb(0.8, 0.0, 0.0);
|
||||
|
||||
pub const FONT_SIZE_SMALL: f32 = 10.0;
|
||||
pub const FONT_SIZE_NORMAL: f32 = 14.0;
|
||||
pub const FONT_SIZE_LARGE: f32 = 32.0;
|
||||
|
||||
pub const PADDING_DEFAULT: f32 = 20.0;
|
||||
pub const BORDER_WIDTH_DEFAULT: f32 = 2.0;
|
||||
|
||||
pub fn create_node_style(width: Val, height: Val, direction: FlexDirection) -> Node {
|
||||
Node {
|
||||
width,
|
||||
height,
|
||||
flex_direction: direction,
|
||||
justify_content: JustifyContent::Center,
|
||||
align_items: AlignItems::Center,
|
||||
..default()
|
||||
}
|
||||
}
|
14
crates/components/src/ui.rs
Normal file
@@ -0,0 +1,14 @@
|
||||
// UI components for the yacht pit application
|
||||
|
||||
use bevy::prelude::*;
|
||||
|
||||
// Placeholder for UI components
|
||||
// This module will contain reusable UI components for the yacht pit application
|
||||
|
||||
pub struct ComponentsPlugin;
|
||||
|
||||
impl Plugin for ComponentsPlugin {
|
||||
fn build(&self, _app: &mut App) {
|
||||
// Add systems and resources for components here
|
||||
}
|
||||
}
|
19
crates/systems/Cargo.toml
Normal file
@@ -0,0 +1,19 @@
|
||||
[package]
|
||||
name = "systems"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
publish = false
|
||||
|
||||
[dependencies]
|
||||
bevy = { workspace = true, features = [
|
||||
"bevy_asset",
|
||||
"bevy_color",
|
||||
"bevy_core_pipeline",
|
||||
"bevy_render",
|
||||
"bevy_sprite",
|
||||
"bevy_text",
|
||||
"bevy_ui",
|
||||
"bevy_window",
|
||||
] }
|
||||
rand = { version = "0.8.3" }
|
||||
components = { path = "../components" }
|
15
crates/systems/src/lib.rs
Normal file
@@ -0,0 +1,15 @@
|
||||
#![allow(clippy::type_complexity)]
|
||||
|
||||
pub mod player;
|
||||
pub mod systems;
|
||||
pub mod yacht_systems;
|
||||
|
||||
// Re-export components from the components crate
|
||||
pub use components::{
|
||||
setup_instrument_cluster, update_instrument_displays, update_yacht_data, YachtData,
|
||||
SpeedGauge, DepthGauge, CompassGauge, EngineStatus, NavigationDisplay,
|
||||
InstrumentCluster, GpsIndicator, RadarIndicator, AisIndicator, SystemDisplay
|
||||
};
|
||||
|
||||
pub use player::{get_yacht_systems, setup_instrument_cluster_system, PlayerPlugin};
|
||||
pub use yacht_systems::{create_yacht_systems, AisSystem, GpsSystem, RadarSystem, SystemInteraction, SystemStatus, YachtSystem};
|
27
crates/systems/src/player.rs
Normal file
@@ -0,0 +1,27 @@
|
||||
use bevy::prelude::*;
|
||||
use components::{setup_instrument_cluster, YachtData, update_yacht_data, update_instrument_displays};
|
||||
use super::yacht_systems::{create_yacht_systems, YachtSystem};
|
||||
|
||||
pub struct PlayerPlugin;
|
||||
|
||||
/// This plugin handles the futuristic yacht instrument cluster
|
||||
/// The main app should handle state management and system registration
|
||||
impl Plugin for PlayerPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.init_resource::<YachtData>()
|
||||
.add_systems(
|
||||
Update,
|
||||
(update_yacht_data, update_instrument_displays)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Setup function for instrument cluster - to be called by the main app
|
||||
pub fn setup_instrument_cluster_system() -> impl Fn(Commands) {
|
||||
setup_instrument_cluster
|
||||
}
|
||||
|
||||
/// Initialize yacht systems - returns the systems for registration
|
||||
pub fn get_yacht_systems() -> Vec<Box<dyn YachtSystem>> {
|
||||
create_yacht_systems()
|
||||
}
|
149
crates/systems/src/systems.rs
Normal file
@@ -0,0 +1,149 @@
|
||||
use bevy::prelude::*;
|
||||
use components::{YachtData, GpsIndicator, RadarIndicator, AisIndicator, SystemDisplay};
|
||||
|
||||
/// Resource to track which system is currently selected
|
||||
#[derive(Resource, Default)]
|
||||
pub struct SelectedSystem {
|
||||
pub current: Option<SystemType>,
|
||||
}
|
||||
|
||||
/// Types of navigation systems available
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub enum SystemType {
|
||||
Gps,
|
||||
Radar,
|
||||
Ais,
|
||||
}
|
||||
|
||||
/// Handles user interactions with system indicator buttons
|
||||
pub fn handle_system_interactions(
|
||||
mut selected_system: ResMut<SelectedSystem>,
|
||||
mut interaction_query: Query<
|
||||
(&Interaction, &mut BackgroundColor, Option<&GpsIndicator>, Option<&RadarIndicator>, Option<&AisIndicator>),
|
||||
(Changed<Interaction>, With<Button>),
|
||||
>,
|
||||
) {
|
||||
for (interaction, mut background_color, gps, radar, ais) in &mut interaction_query {
|
||||
match *interaction {
|
||||
Interaction::Pressed => {
|
||||
if gps.is_some() {
|
||||
selected_system.current = Some(SystemType::Gps);
|
||||
*background_color = BackgroundColor(Color::linear_rgb(0.0, 0.3, 0.5));
|
||||
} else if radar.is_some() {
|
||||
selected_system.current = Some(SystemType::Radar);
|
||||
*background_color = BackgroundColor(Color::linear_rgb(0.0, 0.3, 0.5));
|
||||
} else if ais.is_some() {
|
||||
selected_system.current = Some(SystemType::Ais);
|
||||
*background_color = BackgroundColor(Color::linear_rgb(0.0, 0.3, 0.5));
|
||||
}
|
||||
}
|
||||
Interaction::Hovered => {
|
||||
*background_color = BackgroundColor(Color::linear_rgb(0.15, 0.15, 0.2));
|
||||
}
|
||||
Interaction::None => {
|
||||
*background_color = BackgroundColor(Color::linear_rgb(0.1, 0.1, 0.15));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Updates the system display area with detailed information about the selected system
|
||||
pub fn update_system_display(
|
||||
selected_system: Res<SelectedSystem>,
|
||||
mut display_query: Query<&mut Text, With<SystemDisplay>>,
|
||||
yacht_data: Res<YachtData>,
|
||||
time: Res<Time>,
|
||||
) {
|
||||
if let Ok(mut text) = display_query.single_mut() {
|
||||
match selected_system.current {
|
||||
Some(SystemType::Gps) => {
|
||||
text.0 = format!(
|
||||
"GPS NAVIGATION SYSTEM\n\n\
|
||||
Position: 43°38'19.5\"N 1°26'58.3\"W\n\
|
||||
Heading: {:.0}°\n\
|
||||
Speed: {:.1} knots\n\
|
||||
Course Over Ground: {:.0}°\n\
|
||||
Satellites: 12 connected\n\
|
||||
HDOP: 0.8 (Excellent)\n\
|
||||
\n\
|
||||
Next Waypoint: MONACO HARBOR\n\
|
||||
Distance: 127.3 NM\n\
|
||||
ETA: 10h 12m",
|
||||
yacht_data.heading,
|
||||
yacht_data.speed,
|
||||
yacht_data.heading + 5.0
|
||||
);
|
||||
}
|
||||
Some(SystemType::Radar) => {
|
||||
let sweep_angle = (time.elapsed_secs() * 60.0) % 360.0;
|
||||
text.0 = format!(
|
||||
"RADAR SYSTEM - 12 NM RANGE\n\n\
|
||||
Status: ACTIVE\n\
|
||||
Sweep: {:.0}°\n\
|
||||
Gain: AUTO\n\
|
||||
Sea Clutter: -15 dB\n\
|
||||
Rain Clutter: OFF\n\
|
||||
\n\
|
||||
CONTACTS DETECTED:\n\
|
||||
• Vessel 1: 2.3 NM @ 045° (15 kts)\n\
|
||||
• Vessel 2: 5.7 NM @ 180° (8 kts)\n\
|
||||
• Land Mass: 8.2 NM @ 270°\n\
|
||||
• Buoy: 1.1 NM @ 315°",
|
||||
sweep_angle
|
||||
);
|
||||
}
|
||||
Some(SystemType::Ais) => {
|
||||
text.0 = format!(
|
||||
"AIS - AUTOMATIC IDENTIFICATION SYSTEM\n\n\
|
||||
Status: RECEIVING\n\
|
||||
Own Ship MMSI: 123456789\n\
|
||||
\n\
|
||||
NEARBY VESSELS:\n\
|
||||
\n\
|
||||
🛥️ M/Y SERENITY\n\
|
||||
MMSI: 987654321\n\
|
||||
Distance: 2.1 NM @ 045°\n\
|
||||
Speed: 12.5 kts\n\
|
||||
Course: 180°\n\
|
||||
\n\
|
||||
🚢 CARGO VESSEL ATLANTIS\n\
|
||||
MMSI: 456789123\n\
|
||||
Distance: 5.8 NM @ 270°\n\
|
||||
Speed: 18.2 kts\n\
|
||||
Course: 090°\n\
|
||||
\n\
|
||||
⛵ S/Y WIND DANCER\n\
|
||||
MMSI: 789123456\n\
|
||||
Distance: 1.3 NM @ 135°\n\
|
||||
Speed: 6.8 kts\n\
|
||||
Course: 225°"
|
||||
);
|
||||
}
|
||||
None => {
|
||||
text.0 = "Select a system above to view details".to_string();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_system_type_enum() {
|
||||
let gps = SystemType::Gps;
|
||||
let radar = SystemType::Radar;
|
||||
let ais = SystemType::Ais;
|
||||
|
||||
assert_ne!(gps, radar);
|
||||
assert_ne!(radar, ais);
|
||||
assert_ne!(ais, gps);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_selected_system_default() {
|
||||
let selected_system = SelectedSystem::default();
|
||||
assert_eq!(selected_system.current, None);
|
||||
}
|
||||
}
|
406
crates/systems/src/yacht_systems.rs
Normal file
@@ -0,0 +1,406 @@
|
||||
//! Concrete implementations of yacht systems using the SystemManager abstraction
|
||||
//!
|
||||
//! This module provides implementations of the YachtSystem trait for GPS, Radar, and AIS systems,
|
||||
//! bridging the existing functionality with the new higher-level abstraction.
|
||||
|
||||
use bevy::prelude::*;
|
||||
use components::YachtData;
|
||||
|
||||
/// Status of a yacht system
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum SystemStatus {
|
||||
Active,
|
||||
Inactive,
|
||||
Error(String),
|
||||
Maintenance,
|
||||
}
|
||||
|
||||
/// Interaction types for yacht systems
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum SystemInteraction {
|
||||
Select,
|
||||
Toggle,
|
||||
Reset,
|
||||
Configure(String, String),
|
||||
}
|
||||
|
||||
/// Common trait for all yacht systems
|
||||
pub trait YachtSystem: Send + Sync {
|
||||
fn id(&self) -> &'static str;
|
||||
fn display_name(&self) -> &'static str;
|
||||
fn update(&mut self, yacht_data: &YachtData, time: &Time);
|
||||
fn render_display(&self, yacht_data: &YachtData) -> String;
|
||||
fn handle_interaction(&mut self, interaction: SystemInteraction) -> bool;
|
||||
fn status(&self) -> SystemStatus;
|
||||
}
|
||||
|
||||
/// GPS Navigation System implementation
|
||||
pub struct GpsSystem {
|
||||
status: SystemStatus,
|
||||
satellites_connected: u8,
|
||||
hdop: f32,
|
||||
}
|
||||
|
||||
impl GpsSystem {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
status: SystemStatus::Active,
|
||||
satellites_connected: 12,
|
||||
hdop: 0.8,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl YachtSystem for GpsSystem {
|
||||
fn id(&self) -> &'static str {
|
||||
"gps"
|
||||
}
|
||||
|
||||
fn display_name(&self) -> &'static str {
|
||||
"GPS Navigation"
|
||||
}
|
||||
|
||||
fn update(&mut self, _yacht_data: &YachtData, time: &Time) {
|
||||
// Simulate satellite connection variations
|
||||
let t = time.elapsed_secs();
|
||||
self.satellites_connected = (12.0 + (t * 0.1).sin() * 2.0).max(8.0) as u8;
|
||||
self.hdop = 0.8 + (t * 0.05).sin() * 0.2;
|
||||
}
|
||||
|
||||
fn render_display(&self, yacht_data: &YachtData) -> String {
|
||||
format!(
|
||||
"GPS NAVIGATION SYSTEM\n\n\
|
||||
Position: 43°38'19.5\"N 1°26'58.3\"W\n\
|
||||
Heading: {:.0}°\n\
|
||||
Speed: {:.1} knots\n\
|
||||
Course Over Ground: {:.0}°\n\
|
||||
Satellites: {} connected\n\
|
||||
HDOP: {:.1} ({})\n\
|
||||
\n\
|
||||
Next Waypoint: MONACO HARBOR\n\
|
||||
Distance: 127.3 NM\n\
|
||||
ETA: 10h 12m",
|
||||
yacht_data.heading,
|
||||
yacht_data.speed,
|
||||
yacht_data.heading + 5.0,
|
||||
self.satellites_connected,
|
||||
self.hdop,
|
||||
if self.hdop < 1.0 { "Excellent" } else if self.hdop < 2.0 { "Good" } else { "Fair" }
|
||||
)
|
||||
}
|
||||
|
||||
fn handle_interaction(&mut self, interaction: SystemInteraction) -> bool {
|
||||
match interaction {
|
||||
SystemInteraction::Select => {
|
||||
self.status = SystemStatus::Active;
|
||||
true
|
||||
}
|
||||
SystemInteraction::Reset => {
|
||||
self.satellites_connected = 12;
|
||||
self.hdop = 0.8;
|
||||
true
|
||||
}
|
||||
SystemInteraction::Toggle => {
|
||||
self.status = match self.status {
|
||||
SystemStatus::Active => SystemStatus::Inactive,
|
||||
SystemStatus::Inactive => SystemStatus::Active,
|
||||
_ => SystemStatus::Active,
|
||||
};
|
||||
true
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn status(&self) -> SystemStatus {
|
||||
self.status.clone()
|
||||
}
|
||||
}
|
||||
|
||||
/// Radar System implementation
|
||||
pub struct RadarSystem {
|
||||
status: SystemStatus,
|
||||
range_nm: f32,
|
||||
gain: String,
|
||||
sea_clutter_db: i8,
|
||||
rain_clutter: bool,
|
||||
sweep_angle: f32,
|
||||
}
|
||||
|
||||
impl RadarSystem {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
status: SystemStatus::Active,
|
||||
range_nm: 12.0,
|
||||
gain: "AUTO".to_string(),
|
||||
sea_clutter_db: -15,
|
||||
rain_clutter: false,
|
||||
sweep_angle: 0.0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl YachtSystem for RadarSystem {
|
||||
fn id(&self) -> &'static str {
|
||||
"radar"
|
||||
}
|
||||
|
||||
fn display_name(&self) -> &'static str {
|
||||
"Radar System"
|
||||
}
|
||||
|
||||
fn update(&mut self, _yacht_data: &YachtData, time: &Time) {
|
||||
// Update radar sweep angle
|
||||
self.sweep_angle = (time.elapsed_secs() * 60.0) % 360.0;
|
||||
}
|
||||
|
||||
fn render_display(&self, _yacht_data: &YachtData) -> String {
|
||||
format!(
|
||||
"RADAR SYSTEM - {:.0} NM RANGE\n\n\
|
||||
Status: {}\n\
|
||||
Sweep: {:.0}°\n\
|
||||
Gain: {}\n\
|
||||
Sea Clutter: {} dB\n\
|
||||
Rain Clutter: {}\n\
|
||||
\n\
|
||||
CONTACTS DETECTED:\n\
|
||||
• Vessel 1: 2.3 NM @ 045° (15 kts)\n\
|
||||
• Vessel 2: 5.7 NM @ 180° (8 kts)\n\
|
||||
• Land Mass: 8.2 NM @ 270°\n\
|
||||
• Buoy: 1.1 NM @ 315°",
|
||||
self.range_nm,
|
||||
match self.status {
|
||||
SystemStatus::Active => "ACTIVE",
|
||||
SystemStatus::Inactive => "STANDBY",
|
||||
SystemStatus::Error(_) => "ERROR",
|
||||
SystemStatus::Maintenance => "MAINTENANCE",
|
||||
},
|
||||
self.sweep_angle,
|
||||
self.gain,
|
||||
self.sea_clutter_db,
|
||||
if self.rain_clutter { "ON" } else { "OFF" }
|
||||
)
|
||||
}
|
||||
|
||||
fn handle_interaction(&mut self, interaction: SystemInteraction) -> bool {
|
||||
match interaction {
|
||||
SystemInteraction::Select => {
|
||||
self.status = SystemStatus::Active;
|
||||
true
|
||||
}
|
||||
SystemInteraction::Configure(key, value) => {
|
||||
match key.as_str() {
|
||||
"range" => {
|
||||
if let Ok(range) = value.parse::<f32>() {
|
||||
self.range_nm = range.clamp(1.0, 48.0);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
"gain" => {
|
||||
self.gain = value;
|
||||
true
|
||||
}
|
||||
"sea_clutter" => {
|
||||
if let Ok(db) = value.parse::<i8>() {
|
||||
self.sea_clutter_db = db.clamp(-30, 0);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
"rain_clutter" => {
|
||||
self.rain_clutter = value.to_lowercase() == "on" || value == "true";
|
||||
true
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
SystemInteraction::Reset => {
|
||||
self.range_nm = 12.0;
|
||||
self.gain = "AUTO".to_string();
|
||||
self.sea_clutter_db = -15;
|
||||
self.rain_clutter = false;
|
||||
true
|
||||
}
|
||||
SystemInteraction::Toggle => {
|
||||
self.status = match self.status {
|
||||
SystemStatus::Active => SystemStatus::Inactive,
|
||||
SystemStatus::Inactive => SystemStatus::Active,
|
||||
_ => SystemStatus::Active,
|
||||
};
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn status(&self) -> SystemStatus {
|
||||
self.status.clone()
|
||||
}
|
||||
}
|
||||
|
||||
/// AIS (Automatic Identification System) implementation
|
||||
pub struct AisSystem {
|
||||
status: SystemStatus,
|
||||
own_mmsi: u32,
|
||||
receiving: bool,
|
||||
}
|
||||
|
||||
impl AisSystem {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
status: SystemStatus::Active,
|
||||
own_mmsi: 123456789,
|
||||
receiving: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl YachtSystem for AisSystem {
|
||||
fn id(&self) -> &'static str {
|
||||
"ais"
|
||||
}
|
||||
|
||||
fn display_name(&self) -> &'static str {
|
||||
"AIS System"
|
||||
}
|
||||
|
||||
fn update(&mut self, _yacht_data: &YachtData, _time: &Time) {
|
||||
// AIS system is relatively static, but we could simulate
|
||||
// vessel movements or signal strength variations here
|
||||
}
|
||||
|
||||
fn render_display(&self, _yacht_data: &YachtData) -> String {
|
||||
format!(
|
||||
"AIS - AUTOMATIC IDENTIFICATION SYSTEM\n\n\
|
||||
Status: {}\n\
|
||||
Own Ship MMSI: {}\n\
|
||||
\n\
|
||||
NEARBY VESSELS:\n\
|
||||
\n\
|
||||
🛥️ M/Y SERENITY\n\
|
||||
MMSI: 987654321\n\
|
||||
Distance: 2.1 NM @ 045°\n\
|
||||
Speed: 12.5 kts\n\
|
||||
Course: 180°\n\
|
||||
\n\
|
||||
🚢 CARGO VESSEL ATLANTIS\n\
|
||||
MMSI: 456789123\n\
|
||||
Distance: 5.8 NM @ 270°\n\
|
||||
Speed: 18.2 kts\n\
|
||||
Course: 090°\n\
|
||||
\n\
|
||||
⛵ S/Y WIND DANCER\n\
|
||||
MMSI: 789123456\n\
|
||||
Distance: 1.3 NM @ 135°\n\
|
||||
Speed: 6.8 kts\n\
|
||||
Course: 225°",
|
||||
if self.receiving { "RECEIVING" } else { "STANDBY" },
|
||||
self.own_mmsi
|
||||
)
|
||||
}
|
||||
|
||||
fn handle_interaction(&mut self, interaction: SystemInteraction) -> bool {
|
||||
match interaction {
|
||||
SystemInteraction::Select => {
|
||||
self.status = SystemStatus::Active;
|
||||
self.receiving = true;
|
||||
true
|
||||
}
|
||||
SystemInteraction::Configure(key, value) => {
|
||||
match key.as_str() {
|
||||
"mmsi" => {
|
||||
if let Ok(mmsi) = value.parse::<u32>() {
|
||||
self.own_mmsi = mmsi;
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
SystemInteraction::Toggle => {
|
||||
self.receiving = !self.receiving;
|
||||
self.status = if self.receiving {
|
||||
SystemStatus::Active
|
||||
} else {
|
||||
SystemStatus::Inactive
|
||||
};
|
||||
true
|
||||
}
|
||||
SystemInteraction::Reset => {
|
||||
self.own_mmsi = 123456789;
|
||||
self.receiving = true;
|
||||
self.status = SystemStatus::Active;
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn status(&self) -> SystemStatus {
|
||||
self.status.clone()
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper function to create and register all yacht systems
|
||||
pub fn create_yacht_systems() -> Vec<Box<dyn YachtSystem>> {
|
||||
vec![
|
||||
Box::new(GpsSystem::new()),
|
||||
Box::new(RadarSystem::new()),
|
||||
Box::new(AisSystem::new()),
|
||||
]
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_gps_system() {
|
||||
let gps = GpsSystem::new();
|
||||
assert_eq!(gps.id(), "gps");
|
||||
assert_eq!(gps.display_name(), "GPS Navigation");
|
||||
assert_eq!(gps.status(), SystemStatus::Active);
|
||||
|
||||
let yacht_data = YachtData::default();
|
||||
let display = gps.render_display(&yacht_data);
|
||||
assert!(display.contains("GPS NAVIGATION SYSTEM"));
|
||||
assert!(display.contains("Satellites: 12 connected"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_radar_system() {
|
||||
let mut radar = RadarSystem::new();
|
||||
assert_eq!(radar.id(), "radar");
|
||||
assert_eq!(radar.display_name(), "Radar System");
|
||||
|
||||
// Test configuration
|
||||
assert!(radar.handle_interaction(SystemInteraction::Configure("range".to_string(), "24".to_string())));
|
||||
let display = radar.render_display(&YachtData::default());
|
||||
assert!(display.contains("24 NM RANGE"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ais_system() {
|
||||
let mut ais = AisSystem::new();
|
||||
assert_eq!(ais.id(), "ais");
|
||||
assert_eq!(ais.display_name(), "AIS System");
|
||||
|
||||
// Test toggle
|
||||
assert!(ais.handle_interaction(SystemInteraction::Toggle));
|
||||
assert_eq!(ais.status(), SystemStatus::Inactive);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_create_yacht_systems() {
|
||||
let systems = create_yacht_systems();
|
||||
assert_eq!(systems.len(), 3);
|
||||
|
||||
let ids: Vec<&str> = systems.iter().map(|s| s.id()).collect();
|
||||
assert!(ids.contains(&"gps"));
|
||||
assert!(ids.contains(&"radar"));
|
||||
assert!(ids.contains(&"ais"));
|
||||
}
|
||||
}
|
5
crates/yachtpit/.gitignore
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
../../target/
|
||||
.idea/
|
||||
.DS_Store
|
||||
|
||||
dist/
|
5519
crates/yachtpit/Cargo.lock
generated
Normal file
90
crates/yachtpit/Cargo.toml
Normal file
@@ -0,0 +1,90 @@
|
||||
[package]
|
||||
name = "yachtpit"
|
||||
version = "0.1.0"
|
||||
publish = false
|
||||
authors = ["seemueller-io <git@github.geoffsee>"]
|
||||
edition = "2021"
|
||||
exclude = ["dist", "build", "assets", "credits"]
|
||||
|
||||
|
||||
|
||||
[profile.dev.package."*"]
|
||||
opt-level = 3
|
||||
|
||||
[profile.dev]
|
||||
opt-level = 1
|
||||
|
||||
# This is used by trunk as it doesn't support custom profiles: https://github.com/trunk-rs/trunk/issues/605
|
||||
# xbuild also uses this profile for building android AABs because I couldn't find a configuration for it
|
||||
[profile.release]
|
||||
opt-level = "z"
|
||||
lto = 'thin'
|
||||
codegen-units = 1
|
||||
strip = true
|
||||
|
||||
# Profile for distribution
|
||||
[profile.dist]
|
||||
inherits = "release"
|
||||
opt-level = 3
|
||||
lto = true
|
||||
codegen-units = 1
|
||||
strip = true
|
||||
|
||||
[features]
|
||||
dev = [
|
||||
"bevy/dynamic_linking",
|
||||
]
|
||||
|
||||
# All of Bevy's default features exept for the audio related ones (bevy_audio, vorbis), since they clash with bevy_kira_audio
|
||||
# and android_shared_stdcxx/android-game-activity, since those are covered in `mobile`
|
||||
[dependencies]
|
||||
bevy = { version = "0.16", default-features = false, features = [
|
||||
"animation",
|
||||
"bevy_asset",
|
||||
"bevy_color",
|
||||
"bevy_core_pipeline",
|
||||
"bevy_gilrs",
|
||||
"bevy_gizmos",
|
||||
"bevy_gltf",
|
||||
"bevy_log",
|
||||
"bevy_mesh_picking_backend",
|
||||
"bevy_pbr",
|
||||
"bevy_picking",
|
||||
"bevy_render",
|
||||
"bevy_scene",
|
||||
"bevy_sprite",
|
||||
"bevy_sprite_picking_backend",
|
||||
"bevy_state",
|
||||
"bevy_text",
|
||||
"bevy_ui",
|
||||
"bevy_ui_picking_backend",
|
||||
"bevy_window",
|
||||
"bevy_winit",
|
||||
"custom_cursor",
|
||||
"default_font",
|
||||
"hdr",
|
||||
"multi_threaded",
|
||||
"png",
|
||||
"smaa_luts",
|
||||
"sysinfo_plugin",
|
||||
"tonemapping_luts",
|
||||
"webgl2",
|
||||
"x11",
|
||||
] }
|
||||
bevy_kira_audio = { version = "0.23.0", features = ["android_shared_stdcxx"] }
|
||||
bevy_asset_loader = { version = "0.23.0" }
|
||||
rand = { version = "0.8.3" }
|
||||
webbrowser = { version = "1", features = ["hardened"] }
|
||||
systems = { path = "../systems" }
|
||||
components = { path = "../components" }
|
||||
wasm-bindgen = "0.2"
|
||||
web-sys = { version = "0.3", features = ["Document", "Element", "HtmlElement", "Window"] }
|
||||
|
||||
# keep the following in sync with Bevy's dependencies
|
||||
winit = { version = "0.30", default-features = false }
|
||||
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"] }
|
||||
|
||||
[build-dependencies]
|
||||
embed-resource = "1"
|
161
crates/yachtpit/ICONS_NEEDED.md
Normal file
@@ -0,0 +1,161 @@
|
||||
# Icons Required for Yacht yachtpit Application
|
||||
|
||||
This document lists all the icons that need to be generated for the yacht yachtpit application based on the current UI implementation.
|
||||
|
||||
## Navigation & Compass Icons
|
||||
|
||||
### Primary Navigation
|
||||
- **Compass Rose Icon** - For the central navigation display
|
||||
- **North Arrow Icon** - Directional indicator
|
||||
- **GPS Satellite Icon** - GPS status indicator
|
||||
- **Waypoint Icon** - Navigation waypoints
|
||||
- **Route Line Icon** - Planned route visualization
|
||||
|
||||
## Instrument Gauge Icons
|
||||
|
||||
### Speed Gauge
|
||||
- **Speedometer Icon** - Circular gauge background
|
||||
- **Speed Needle Icon** - Gauge pointer/needle
|
||||
- **Knots Unit Icon** - "KTS" stylized icon
|
||||
|
||||
### Depth Gauge
|
||||
- **Depth Sounder Icon** - Sonar/depth measurement icon
|
||||
- **Water Depth Icon** - Underwater depth visualization
|
||||
- **Meter Unit Icon** - "M" stylized icon
|
||||
|
||||
## Engine & Systems Icons
|
||||
|
||||
### Engine Status
|
||||
- **Engine Icon** - Marine engine representation
|
||||
- **Temperature Gauge Icon** - Engine temperature indicator
|
||||
- **Cooling System Icon** - Engine cooling status
|
||||
- **Engine Alert Icon** - Warning/alert indicator
|
||||
|
||||
### Fuel System
|
||||
- **Fuel Tank Icon** - Fuel level indicator
|
||||
- **Fuel Pump Icon** - Fuel system status
|
||||
- **Fuel Drop Icon** - Fuel consumption indicator
|
||||
|
||||
### Electrical System
|
||||
- **Battery Icon** - Battery level indicator
|
||||
- **Charging Icon** - Battery charging status
|
||||
- **Power Icon** - Electrical system status
|
||||
- **Voltage Meter Icon** - Electrical measurement
|
||||
|
||||
## Communication & Navigation Systems
|
||||
|
||||
### GPS System
|
||||
- **GPS Icon** - Global positioning system
|
||||
- **Satellite Signal Icon** - Signal strength indicator
|
||||
- **Location Pin Icon** - Current position marker
|
||||
|
||||
### Radar System
|
||||
- **Radar Dish Icon** - Radar antenna representation
|
||||
- **Radar Sweep Icon** - Radar scanning animation
|
||||
- **Target Blip Icon** - Radar contact indicator
|
||||
|
||||
### AIS (Automatic Identification System)
|
||||
- **AIS Icon** - Ship identification system
|
||||
- **Ship Icon** - Other vessel representation
|
||||
- **Radio Wave Icon** - Communication signal
|
||||
|
||||
## Weather & Environmental Icons
|
||||
|
||||
### Wind Information
|
||||
- **Wind Vane Icon** - Wind direction indicator
|
||||
- **Wind Speed Icon** - Anemometer representation
|
||||
- **Wind Arrow Icon** - Directional wind indicator
|
||||
- **Beaufort Scale Icon** - Wind force scale
|
||||
|
||||
### Weather Conditions
|
||||
- **Barometer Icon** - Atmospheric pressure
|
||||
- **Temperature Icon** - Air temperature
|
||||
- **Humidity Icon** - Relative humidity indicator
|
||||
|
||||
## Status & Alert Icons
|
||||
|
||||
### System Status Indicators
|
||||
- **Green Status Dot** - System operational
|
||||
- **Red Status Dot** - System fault/offline
|
||||
- **Yellow Status Dot** - System warning
|
||||
- **Blue Status Dot** - System standby
|
||||
|
||||
### Alert Icons
|
||||
- **Warning Triangle** - General warning
|
||||
- **Critical Alert** - Emergency situation
|
||||
- **Information Icon** - General information
|
||||
- **Maintenance Icon** - Service required
|
||||
|
||||
## UI Control Icons
|
||||
|
||||
### Navigation Controls
|
||||
- **Menu Icon** - Main menu access
|
||||
- **Settings Icon** - Configuration access
|
||||
- **Home Icon** - Return to main display
|
||||
- **Back Arrow** - Navigation back
|
||||
|
||||
### Display Controls
|
||||
- **Brightness Icon** - Screen brightness control
|
||||
- **Contrast Icon** - Display contrast
|
||||
- **Night Mode Icon** - Low-light display mode
|
||||
- **Full Screen Icon** - Display mode toggle
|
||||
|
||||
## Chart & Mapping Icons
|
||||
|
||||
### Chart Elements
|
||||
- **Chart Icon** - Nautical chart representation
|
||||
- **Depth Contour Icon** - Underwater topography
|
||||
- **Buoy Icon** - Navigation aids
|
||||
- **Harbor Icon** - Port/marina indicator
|
||||
- **Anchor Icon** - Anchorage areas
|
||||
|
||||
### Measurement Tools
|
||||
- **Ruler Icon** - Distance measurement
|
||||
- **Protractor Icon** - Bearing measurement
|
||||
- **Scale Icon** - Chart scale indicator
|
||||
|
||||
## Safety & Emergency Icons
|
||||
|
||||
### Safety Equipment
|
||||
- **Life Ring Icon** - Safety equipment
|
||||
- **Fire Extinguisher Icon** - Emergency equipment
|
||||
- **First Aid Icon** - Medical supplies
|
||||
- **Emergency Radio Icon** - Distress communication
|
||||
|
||||
### Emergency Procedures
|
||||
- **SOS Icon** - Distress signal
|
||||
- **Mayday Icon** - Emergency call
|
||||
- **Coast Guard Icon** - Emergency services
|
||||
- **Evacuation Icon** - Emergency procedures
|
||||
|
||||
## File Formats Required
|
||||
|
||||
All icons should be generated in the following formats:
|
||||
- **PNG**: 16x16, 24x24, 32x32, 48x48, 64x64, 128x128, 256x256 pixels
|
||||
- **SVG**: Scalable vector format for high-DPI displays
|
||||
- **ICO**: Windows icon format (for desktop application)
|
||||
|
||||
## Design Guidelines
|
||||
|
||||
### Style Requirements
|
||||
- **Nautical Theme**: Maritime-inspired design language
|
||||
- **High Contrast**: Suitable for marine lighting conditions
|
||||
- **Monochromatic**: Primary colors should be cyan/blue theme
|
||||
- **Clean Lines**: Minimalist, professional appearance
|
||||
- **Scalable**: Must remain legible at small sizes
|
||||
|
||||
### Color Palette
|
||||
- **Primary**: Cyan (#00CCFF) - Main UI elements
|
||||
- **Secondary**: Green (#00FF80) - Operational status
|
||||
- **Warning**: Orange (#FF8000) - Caution states
|
||||
- **Alert**: Red (#FF0040) - Critical alerts
|
||||
- **Neutral**: Gray (#999999) - Inactive elements
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
These icons will replace the current text-based placeholders in:
|
||||
- `src/player.rs` - Main instrument cluster
|
||||
- `src/menu.rs` - Menu system icons
|
||||
- `src/loading.rs` - Loading screen elements
|
||||
|
||||
The icons should be placed in the `assets/textures/icons/` directory and loaded through the existing `TextureAssets` resource system.
|
6
crates/yachtpit/Trunk.toml
Normal file
@@ -0,0 +1,6 @@
|
||||
[build]
|
||||
public_url = "./"
|
||||
wasm_bindgen = "0.2.100"
|
||||
|
||||
[serve]
|
||||
port = 8080
|
BIN
crates/yachtpit/assets/audio/flying.ogg
Normal file
BIN
crates/yachtpit/assets/textures/bevy.png
Normal file
After Width: | Height: | Size: 7.1 KiB |
BIN
crates/yachtpit/assets/textures/github.png
Normal file
After Width: | Height: | Size: 6.2 KiB |
10
crates/yachtpit/build.rs
Normal file
@@ -0,0 +1,10 @@
|
||||
extern crate embed_resource;
|
||||
use std::env;
|
||||
|
||||
fn main() {
|
||||
let target = env::var("TARGET").unwrap();
|
||||
if target.contains("windows") {
|
||||
// on windows we will set our game icon as icon for the executable
|
||||
embed_resource::compile("build/windows/icon.rc");
|
||||
}
|
||||
}
|
BIN
crates/yachtpit/build/android/res/mipmap-mdpi/icon.png
Normal file
After Width: | Height: | Size: 44 KiB |
BIN
crates/yachtpit/build/icon_1024x1024.png
Normal file
After Width: | Height: | Size: 99 KiB |
BIN
crates/yachtpit/build/macos/AppIcon.iconset/icon_128x128.png
Normal file
After Width: | Height: | Size: 13 KiB |
BIN
crates/yachtpit/build/macos/AppIcon.iconset/icon_128x128@2x.png
Normal file
After Width: | Height: | Size: 22 KiB |
BIN
crates/yachtpit/build/macos/AppIcon.iconset/icon_16x16.png
Normal file
After Width: | Height: | Size: 7.7 KiB |
BIN
crates/yachtpit/build/macos/AppIcon.iconset/icon_16x16@2x.png
Normal file
After Width: | Height: | Size: 8.3 KiB |
BIN
crates/yachtpit/build/macos/AppIcon.iconset/icon_256x256.png
Normal file
After Width: | Height: | Size: 22 KiB |
BIN
crates/yachtpit/build/macos/AppIcon.iconset/icon_256x256@2x.png
Normal file
After Width: | Height: | Size: 44 KiB |
BIN
crates/yachtpit/build/macos/AppIcon.iconset/icon_32x32.png
Normal file
After Width: | Height: | Size: 8.3 KiB |
BIN
crates/yachtpit/build/macos/AppIcon.iconset/icon_32x32@2x.png
Normal file
After Width: | Height: | Size: 9.7 KiB |
BIN
crates/yachtpit/build/macos/AppIcon.iconset/icon_512x512.png
Normal file
After Width: | Height: | Size: 44 KiB |
BIN
crates/yachtpit/build/macos/AppIcon.iconset/icon_512x512@2x.png
Normal file
After Width: | Height: | Size: 99 KiB |
17
crates/yachtpit/build/macos/create_icns.sh
Executable file
@@ -0,0 +1,17 @@
|
||||
#!/usr/bin/env sh
|
||||
|
||||
rm -rf AppIcon.iconset/*
|
||||
mkdir -p AppIcon.iconset
|
||||
sips -z 16 16 ../icon_1024x1024.png --out AppIcon.iconset/icon_16x16.png
|
||||
sips -z 32 32 ../icon_1024x1024.png --out AppIcon.iconset/icon_16x16@2x.png
|
||||
sips -z 32 32 ../icon_1024x1024.png --out AppIcon.iconset/icon_32x32.png
|
||||
sips -z 64 64 ../icon_1024x1024.png --out AppIcon.iconset/icon_32x32@2x.png
|
||||
sips -z 128 128 ../icon_1024x1024.png --out AppIcon.iconset/icon_128x128.png
|
||||
sips -z 256 256 ../icon_1024x1024.png --out AppIcon.iconset/icon_128x128@2x.png
|
||||
sips -z 256 256 ../icon_1024x1024.png --out AppIcon.iconset/icon_256x256.png
|
||||
sips -z 512 512 ../icon_1024x1024.png --out AppIcon.iconset/icon_256x256@2x.png
|
||||
sips -z 512 512 ../icon_1024x1024.png --out AppIcon.iconset/icon_512x512.png
|
||||
cp ../icon_1024x1024.png AppIcon.iconset/icon_512x512@2x.png
|
||||
iconutil -c icns AppIcon.iconset
|
||||
mkdir -p src/Game.app/Contents/Resources
|
||||
mv AppIcon.icns src/Game.app/Contents/Resources/
|
17
crates/yachtpit/build/macos/create_icns_linux.sh
Executable file
@@ -0,0 +1,17 @@
|
||||
#!/usr/bin/env sh
|
||||
|
||||
rm -rf AppIcon.iconset/*
|
||||
mkdir -p AppIcon.iconset
|
||||
convert ../icon_1024x1024.png -resize 16x16 AppIcon.iconset/icon_16x16.png
|
||||
convert ../icon_1024x1024.png -resize 32x32 AppIcon.iconset/icon_16x16@2x.png
|
||||
convert ../icon_1024x1024.png -resize 32x32 AppIcon.iconset/icon_32x32.png
|
||||
convert ../icon_1024x1024.png -resize 64x64 AppIcon.iconset/icon_32x32@2x.png
|
||||
convert ../icon_1024x1024.png -resize 128x128 AppIcon.iconset/icon_128x128.png
|
||||
convert ../icon_1024x1024.png -resize 256x256 AppIcon.iconset/icon_128x128@2x.png
|
||||
convert ../icon_1024x1024.png -resize 256x256 AppIcon.iconset/icon_256x256.png
|
||||
convert ../icon_1024x1024.png -resize 512x512 AppIcon.iconset/icon_256x256@2x.png
|
||||
convert ../icon_1024x1024.png -resize 512x512 AppIcon.iconset/icon_512x512.png
|
||||
cp ../icon_1024x1024.png AppIcon.iconset/icon_512x512@2x.png
|
||||
png2icns ./AppIcon.icns AppIcon.iconset/icon_16x16.png AppIcon.iconset/icon_32x32.png AppIcon.iconset/icon_128x128.png AppIcon.iconset/icon_256x256.png AppIcon.iconset/icon_512x512.png
|
||||
mkdir -p src/Game.app/Contents/Resources
|
||||
mv AppIcon.icns src/Game.app/Contents/Resources/
|
29
crates/yachtpit/build/macos/src/Game.app/Contents/Info.plist
Normal file
@@ -0,0 +1,29 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>en</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>yachtpit</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>yachtpit</string>
|
||||
<key>CFBundleIconFile</key>
|
||||
<string>AppIcon.icns</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>io.gs.yachtpit</string> <!-- ToDo replace all instances with io.gs.yachtpit -->
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>yachtpit</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<!-- Version -->
|
||||
<string>0.1.0</string>
|
||||
<key>CFBundleSupportedPlatforms</key>
|
||||
<array>
|
||||
<string>MacOSX</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
62
crates/yachtpit/build/web/sound.js
Normal file
@@ -0,0 +1,62 @@
|
||||
// Insert hack to make sound autoplay on Chrome as soon as the user interacts with the tab:
|
||||
// https://developers.google.com/web/updates/2018/11/web-audio-autoplay#moving-forward
|
||||
|
||||
// the following function keeps track of all AudioContexts and resumes them on the first user
|
||||
// interaction with the page. If the function is called and all contexts are already running,
|
||||
// it will remove itself from all event listeners.
|
||||
(function () {
|
||||
// An array of all contexts to resume on the page
|
||||
const audioContextList = [];
|
||||
|
||||
// An array of various user interaction events we should listen for
|
||||
const userInputEventNames = [
|
||||
"click",
|
||||
"contextmenu",
|
||||
"auxclick",
|
||||
"dblclick",
|
||||
"mousedown",
|
||||
"mouseup",
|
||||
"pointerup",
|
||||
"touchend",
|
||||
"keydown",
|
||||
"keyup",
|
||||
];
|
||||
|
||||
// A proxy object to intercept AudioContexts and
|
||||
// add them to the array for tracking and resuming later
|
||||
self.AudioContext = new Proxy(self.AudioContext, {
|
||||
construct(target, args) {
|
||||
const result = new target(...args);
|
||||
audioContextList.push(result);
|
||||
return result;
|
||||
},
|
||||
});
|
||||
|
||||
// To resume all AudioContexts being tracked
|
||||
function resumeAllContexts(_event) {
|
||||
let count = 0;
|
||||
|
||||
audioContextList.forEach((context) => {
|
||||
if (context.state !== "running") {
|
||||
context.resume();
|
||||
} else {
|
||||
count++;
|
||||
}
|
||||
});
|
||||
|
||||
// If all the AudioContexts have now resumed then we unbind all
|
||||
// the event listeners from the page to prevent unnecessary resume attempts
|
||||
// Checking count > 0 ensures that the user interaction happens AFTER the game started up
|
||||
if (count > 0 && count === audioContextList.length) {
|
||||
userInputEventNames.forEach((eventName) => {
|
||||
document.removeEventListener(eventName, resumeAllContexts);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// We bind the resume function for each user interaction
|
||||
// event on the page
|
||||
userInputEventNames.forEach((eventName) => {
|
||||
document.addEventListener(eventName, resumeAllContexts);
|
||||
});
|
||||
})();
|
53
crates/yachtpit/build/web/styles.css
Normal file
@@ -0,0 +1,53 @@
|
||||
body, html {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: transparent;
|
||||
margin: 0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.game-container {
|
||||
width: 800px;
|
||||
height: 600px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.lds-dual-ring {
|
||||
display: inline-block;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
margin: auto;
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
}
|
||||
|
||||
.lds-dual-ring:after {
|
||||
content: " ";
|
||||
display: block;
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
border-radius: 50%;
|
||||
border: 6px solid #fff;
|
||||
border-color: #fff transparent #fff transparent;
|
||||
animation: lds-dual-ring 1.2s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes lds-dual-ring {
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
#bevy {
|
||||
z-index: 2;
|
||||
}
|
BIN
crates/yachtpit/build/windows/icon.ico
Normal file
After Width: | Height: | Size: 153 KiB |
1
crates/yachtpit/build/windows/icon.rc
Normal file
@@ -0,0 +1 @@
|
||||
app_icon ICON "icon.ico"
|
2
crates/yachtpit/build/windows/installer/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
/bin/
|
||||
/obj/
|
73
crates/yachtpit/build/windows/installer/InstallDirUi.wxs
Normal file
@@ -0,0 +1,73 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"
|
||||
xmlns:ui="http://wixtoolset.org/schemas/v4/wxs/ui">
|
||||
<!-- Copied from https://github.com/wixtoolset/wix4/blob/6f2511f58f60e63a15357a2fe37f83343dea3090/src/ext/UI/wixlib/WixUI_InstallDir.wxs#L9
|
||||
but with the license dialog disabled.
|
||||
See: https://wixtoolset.org/docs/v3/wixui/wixui_customizations/#changing-the-ui-sequence-of-a-built-in-dialog-set. -->
|
||||
<Fragment>
|
||||
<UI Id="WixUI_CustomInstallDir">
|
||||
<TextStyle Id="WixUI_Font_Normal" FaceName="Tahoma" Size="8" />
|
||||
<TextStyle Id="WixUI_Font_Bigger" FaceName="Tahoma" Size="12" />
|
||||
<TextStyle Id="WixUI_Font_Title" FaceName="Tahoma" Size="9" Bold="yes" />
|
||||
|
||||
<Property Id="DefaultUIFont" Value="WixUI_Font_Normal" />
|
||||
|
||||
<DialogRef Id="BrowseDlg" />
|
||||
<DialogRef Id="DiskCostDlg" />
|
||||
<DialogRef Id="ErrorDlg" />
|
||||
<DialogRef Id="FatalError" />
|
||||
<DialogRef Id="FilesInUse" />
|
||||
<DialogRef Id="MsiRMFilesInUse" />
|
||||
<DialogRef Id="PrepareDlg" />
|
||||
<DialogRef Id="ProgressDlg" />
|
||||
<DialogRef Id="ResumeDlg" />
|
||||
<DialogRef Id="UserExit" />
|
||||
<Publish Dialog="BrowseDlg" Control="OK" Event="SpawnDialog" Value="InvalidDirDlg" Order="4" Condition="NOT WIXUI_DONTVALIDATEPATH AND WIXUI_INSTALLDIR_VALID<>"1"" />
|
||||
|
||||
<Publish Dialog="ExitDialog" Control="Finish" Event="EndDialog" Value="Return" Order="999" />
|
||||
|
||||
<Publish Dialog="WelcomeDlg" Control="Next" Event="NewDialog" Value="InstallDirDlg" Condition="NOT Installed" />
|
||||
<Publish Dialog="WelcomeDlg" Control="Next" Event="NewDialog" Value="VerifyReadyDlg" Condition="Installed AND PATCH" />
|
||||
|
||||
<Publish Dialog="InstallDirDlg" Control="Back" Event="NewDialog" Value="WelcomeDlg" />
|
||||
<Publish Dialog="InstallDirDlg" Control="Next" Event="SetTargetPath" Value="[WIXUI_INSTALLDIR]" Order="1" />
|
||||
<Publish Dialog="InstallDirDlg" Control="Next" Event="SpawnDialog" Value="InvalidDirDlg" Order="3" Condition="NOT WIXUI_DONTVALIDATEPATH AND WIXUI_INSTALLDIR_VALID<>"1"" />
|
||||
<Publish Dialog="InstallDirDlg" Control="Next" Event="NewDialog" Value="VerifyReadyDlg" Order="4" Condition="WIXUI_DONTVALIDATEPATH OR WIXUI_INSTALLDIR_VALID="1"" />
|
||||
<Publish Dialog="InstallDirDlg" Control="ChangeFolder" Property="_BrowseProperty" Value="[WIXUI_INSTALLDIR]" Order="1" />
|
||||
<Publish Dialog="InstallDirDlg" Control="ChangeFolder" Event="SpawnDialog" Value="BrowseDlg" Order="2" />
|
||||
<Publish Dialog="VerifyReadyDlg" Control="Back" Event="NewDialog" Value="InstallDirDlg" Order="1" Condition="NOT Installed" />
|
||||
<Publish Dialog="VerifyReadyDlg" Control="Back" Event="NewDialog" Value="MaintenanceTypeDlg" Order="2" Condition="Installed AND NOT PATCH" />
|
||||
<Publish Dialog="VerifyReadyDlg" Control="Back" Event="NewDialog" Value="WelcomeDlg" Order="2" Condition="Installed AND PATCH" />
|
||||
|
||||
<Publish Dialog="MaintenanceWelcomeDlg" Control="Next" Event="NewDialog" Value="MaintenanceTypeDlg" />
|
||||
|
||||
<Publish Dialog="MaintenanceTypeDlg" Control="RepairButton" Event="NewDialog" Value="VerifyReadyDlg" />
|
||||
<Publish Dialog="MaintenanceTypeDlg" Control="RemoveButton" Event="NewDialog" Value="VerifyReadyDlg" />
|
||||
<Publish Dialog="MaintenanceTypeDlg" Control="Back" Event="NewDialog" Value="MaintenanceWelcomeDlg" />
|
||||
|
||||
<Property Id="ARPNOMODIFY" Value="1" />
|
||||
|
||||
<InstallUISequence>
|
||||
<Show Dialog="FatalError" OnExit="error" />
|
||||
<Show Dialog="UserExit" OnExit="cancel" />
|
||||
<Show Dialog="ExitDialog" OnExit="success" />
|
||||
</InstallUISequence>
|
||||
|
||||
<AdminUISequence>
|
||||
<Show Dialog="FatalError" OnExit="error" />
|
||||
<Show Dialog="UserExit" OnExit="cancel" />
|
||||
<Show Dialog="ExitDialog" OnExit="success" />
|
||||
</AdminUISequence>
|
||||
</UI>
|
||||
|
||||
<UIRef Id="WixUI_Common" />
|
||||
</Fragment>
|
||||
<?foreach WIXUIARCH in X86;X64;A64 ?>
|
||||
<Fragment>
|
||||
<UI Id="WixUI_CustomInstallDir_$(WIXUIARCH)">
|
||||
<Publish Dialog="BrowseDlg" Control="OK" Event="DoAction" Value="WixUIValidatePath_$(WIXUIARCH)" Order="3" Condition="NOT WIXUI_DONTVALIDATEPATH" />
|
||||
<Publish Dialog="InstallDirDlg" Control="Next" Event="DoAction" Value="WixUIValidatePath_$(WIXUIARCH)" Order="2" Condition="NOT WIXUI_DONTVALIDATEPATH" />
|
||||
</UI>
|
||||
</Fragment>
|
||||
<?endforeach?>
|
||||
</Wix>
|
40
crates/yachtpit/build/windows/installer/Installer.sln
Normal file
@@ -0,0 +1,40 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.0.31903.59
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{B7DD6F7E-DEF8-4E67-B5B7-07EF123DB6F0}") = "Installer", "Installer.wixproj", "{340293B0-F46C-46A0-88D8-4BB2F3465C53}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Debug|ARM64 = Debug|ARM64
|
||||
Debug|x64 = Debug|x64
|
||||
Debug|x86 = Debug|x86
|
||||
Release|Any CPU = Release|Any CPU
|
||||
Release|ARM64 = Release|ARM64
|
||||
Release|x64 = Release|x64
|
||||
Release|x86 = Release|x86
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{340293B0-F46C-46A0-88D8-4BB2F3465C53}.Debug|Any CPU.ActiveCfg = Debug|x64
|
||||
{340293B0-F46C-46A0-88D8-4BB2F3465C53}.Debug|Any CPU.Build.0 = Debug|x64
|
||||
{340293B0-F46C-46A0-88D8-4BB2F3465C53}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||
{340293B0-F46C-46A0-88D8-4BB2F3465C53}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||
{340293B0-F46C-46A0-88D8-4BB2F3465C53}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{340293B0-F46C-46A0-88D8-4BB2F3465C53}.Debug|x64.Build.0 = Debug|x64
|
||||
{340293B0-F46C-46A0-88D8-4BB2F3465C53}.Debug|x86.ActiveCfg = Debug|x86
|
||||
{340293B0-F46C-46A0-88D8-4BB2F3465C53}.Debug|x86.Build.0 = Debug|x86
|
||||
{340293B0-F46C-46A0-88D8-4BB2F3465C53}.Release|Any CPU.ActiveCfg = Release|x64
|
||||
{340293B0-F46C-46A0-88D8-4BB2F3465C53}.Release|Any CPU.Build.0 = Release|x64
|
||||
{340293B0-F46C-46A0-88D8-4BB2F3465C53}.Release|ARM64.ActiveCfg = Release|ARM64
|
||||
{340293B0-F46C-46A0-88D8-4BB2F3465C53}.Release|ARM64.Build.0 = Release|ARM64
|
||||
{340293B0-F46C-46A0-88D8-4BB2F3465C53}.Release|x64.ActiveCfg = Release|x64
|
||||
{340293B0-F46C-46A0-88D8-4BB2F3465C53}.Release|x64.Build.0 = Release|x64
|
||||
{340293B0-F46C-46A0-88D8-4BB2F3465C53}.Release|x86.ActiveCfg = Release|x86
|
||||
{340293B0-F46C-46A0-88D8-4BB2F3465C53}.Release|x86.Build.0 = Release|x86
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
EndGlobal
|
29
crates/yachtpit/build/windows/installer/Installer.wixproj
Normal file
@@ -0,0 +1,29 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" Sdk="WixToolset.Sdk/4.0.0">
|
||||
<PropertyGroup>
|
||||
<DebugType>none</DebugType>
|
||||
<OutputName>installer</OutputName>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="WixToolset.UI.wixext" Version="4.0.0" />
|
||||
<PackageReference Include="WixToolset.Heat" Version="4.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<HarvestDirectory Include="..\..\..\assets">
|
||||
<ComponentGroupName>AssetsDirectory</ComponentGroupName>
|
||||
<DirectoryRefId>INSTALLFOLDER</DirectoryRefId>
|
||||
<SuppressRootDirectory>false</SuppressRootDirectory>
|
||||
</HarvestDirectory>
|
||||
<BindPath Include="..\..\..\assets" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<HarvestDirectory Include="..\..\..\credits">
|
||||
<ComponentGroupName>CreditsDirectory</ComponentGroupName>
|
||||
<DirectoryRefId>INSTALLFOLDER</DirectoryRefId>
|
||||
<SuppressRootDirectory>false</SuppressRootDirectory>
|
||||
</HarvestDirectory>
|
||||
<BindPath Include="..\..\..\credits" />
|
||||
</ItemGroup>
|
||||
</Project>
|
@@ -0,0 +1,6 @@
|
||||
<!--
|
||||
This file contains the declaration of all the localizable strings.
|
||||
-->
|
||||
<WixLocalization xmlns="http://wixtoolset.org/schemas/v4/wxl" Culture="en-US">
|
||||
<String Id="DowngradeError" Value="A newer version of [ProductName] is already installed." />
|
||||
</WixLocalization>
|
50
crates/yachtpit/build/windows/installer/Package.wxs
Normal file
@@ -0,0 +1,50 @@
|
||||
<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs" xmlns:ui="http://wixtoolset.org/schemas/v4/wxs/ui">
|
||||
<Package
|
||||
Name="yachtpit"
|
||||
Manufacturer="gsio"
|
||||
UpgradeCode="ac8709c2-1d6f-4440-b424-386e4e315425"
|
||||
Version="0.0.1"
|
||||
|
||||
Scope="perUserOrMachine">
|
||||
<MajorUpgrade DowngradeErrorMessage="!(loc.DowngradeError)" />
|
||||
|
||||
<Media Id="1" Cabinet="myapplication.cab" EmbedCab="yes" />
|
||||
|
||||
<Property Id="ApplicationFolderName" Value="!(bind.Property.ProductName)" />
|
||||
|
||||
<!-- Installer Icon -->
|
||||
<Icon Id="icon.ico" SourceFile="..\icon.ico"/>
|
||||
<Property Id="ARPPRODUCTICON" Value="icon.ico" />
|
||||
|
||||
<!-- Sets the default installation folder -->
|
||||
<StandardDirectory Id="ProgramFiles6432Folder">
|
||||
<Directory Id="INSTALLFOLDER" Name="!(bind.Property.ProductName)" />
|
||||
</StandardDirectory>
|
||||
|
||||
<!-- Shows a UI that allows customizing the install location -->
|
||||
<ui:WixUI Id="WixUI_CustomInstallDir" InstallDirectory="INSTALLFOLDER" />
|
||||
|
||||
<Feature Id="Main">
|
||||
<ComponentGroupRef Id="MainComponent" />
|
||||
<ComponentRef Id="StartMenuShortcut" />
|
||||
<ComponentGroupRef Id="AssetsDirectory" />
|
||||
<ComponentGroupRef Id="CreditsDirectory" />
|
||||
</Feature>
|
||||
|
||||
<!-- Installs the actual files -->
|
||||
<ComponentGroup Id="MainComponent" Directory="INSTALLFOLDER">
|
||||
<Component>
|
||||
<File Id="Executable" Source="..\..\..\target\dist\yachtpit.exe" Vital="true" />
|
||||
</Component>
|
||||
</ComponentGroup>
|
||||
|
||||
<!-- Start menu shortcut -->
|
||||
<!-- Source: https://wixtoolset.org/docs/v3/howtos/files_and_registry/create_start_menu_shortcut/ -->
|
||||
<StandardDirectory Id="ProgramMenuFolder">
|
||||
<Component Id="StartMenuShortcut" Guid="*">
|
||||
<Shortcut Id="ApplicationStartMenuShortcut" Name="!(bind.Property.ProductName)" Target="[!Executable]" WorkingDirectory="INSTALLFOLDER" />
|
||||
<RegistryValue Root="HKCU" Key="Software\!(bind.Property.ProductName)" Name="installed" Type="integer" Value="1" KeyPath="yes"/>
|
||||
</Component>
|
||||
</StandardDirectory>
|
||||
</Package>
|
||||
</Wix>
|
6
crates/yachtpit/build/windows/installer/global.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"sdk": {
|
||||
"version": "9.0.100",
|
||||
"rollForward": "latestFeature"
|
||||
}
|
||||
}
|
5
crates/yachtpit/credits/CREDITS.md
Normal file
@@ -0,0 +1,5 @@
|
||||
# Credits
|
||||
|
||||
## Assets
|
||||
|
||||
* Bevy icon: [MIT License](licenses/Bevy_MIT_License.md);
|
19
crates/yachtpit/credits/licenses/Bevy_MIT_License.md
Normal file
@@ -0,0 +1,19 @@
|
||||
MIT License
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
22
crates/yachtpit/index.html
Normal file
@@ -0,0 +1,22 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
|
||||
<title>yachtpit</title>
|
||||
<link data-trunk rel="copy-dir" href="assets"/>
|
||||
<link data-trunk rel="copy-dir" href="credits"/>
|
||||
<link data-trunk rel="copy-file" href="build/windows/icon.ico"/>
|
||||
<link rel="icon" href="icon.ico">
|
||||
<link data-trunk rel="inline" href="build/web/styles.css"/>
|
||||
</head>
|
||||
<body>
|
||||
<link data-trunk rel="inline" href="build/web/sound.js"/>
|
||||
<div class="game-container">
|
||||
<div class="lds-dual-ring"></div>
|
||||
<canvas id="yachtpit-canvas">
|
||||
Javascript and support for canvas is required
|
||||
</canvas>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
5
crates/yachtpit/mobile/.cargo/config.toml
Normal file
@@ -0,0 +1,5 @@
|
||||
# Flag to notify the compiler we're building for the iOS simulator from an Apple silicon mac
|
||||
# This needs some workarounds for now
|
||||
# See https://github.com/bevyengine/bevy/pull/10178 - remove if it's not needed anymore.
|
||||
[target.aarch64-apple-ios-sim]
|
||||
rustflags = ["--cfg=ios_simulator"]
|
3
crates/yachtpit/mobile/.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
mobile.xcodeproj/xcuserdata/
|
||||
mobile.xcodeproj/project.xcworkspace/
|
||||
build/
|
34
crates/yachtpit/mobile/Cargo.toml
Normal file
@@ -0,0 +1,34 @@
|
||||
[package]
|
||||
name = "mobile"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
publish = false
|
||||
|
||||
[lib]
|
||||
name = "mobile"
|
||||
crate-type = ["staticlib", "cdylib"]
|
||||
|
||||
[dependencies]
|
||||
yachtpit = { path = "../" }
|
||||
bevy = { version = "0.16.0", default-features = false, features = ["android-native-activity"] }
|
||||
|
||||
[target."cfg(target_os = \"ios\")".dependencies]
|
||||
objc2-avf-audio = { version = "0.3.0", features = [
|
||||
"AVAudioSession",
|
||||
"AVAudioSessionTypes",
|
||||
] }
|
||||
|
||||
[package.metadata.android]
|
||||
package = "io.gs.yachtpit"
|
||||
apk_name = "yachtpit"
|
||||
assets = "../assets"
|
||||
strip = "strip"
|
||||
resources = "../build/android/res"
|
||||
build_targets = ["aarch64-linux-android"]
|
||||
|
||||
[package.metadata.android.sdk]
|
||||
target_sdk_version = 35
|
||||
|
||||
[package.metadata.android.application]
|
||||
icon = "@mipmap/icon"
|
||||
label = "yachtpit"
|
28
crates/yachtpit/mobile/Makefile
Normal file
@@ -0,0 +1,28 @@
|
||||
.PHONY: xcodebuild run install boot-sim generate clean
|
||||
|
||||
DEVICE = ${DEVICE_ID}
|
||||
ifndef DEVICE_ID
|
||||
DEVICE=$(shell xcrun simctl list devices 'iOS' | grep -v 'unavailable' | grep -v '^--' | grep -v '==' | head -n 1 | grep -E -o -i "([0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12})")
|
||||
endif
|
||||
|
||||
run: install
|
||||
xcrun simctl launch --console $(DEVICE) io.gs.yachtpit
|
||||
|
||||
boot-sim:
|
||||
xcrun simctl boot $(DEVICE) || true
|
||||
|
||||
install: xcodebuild-simulator boot-sim
|
||||
xcrun simctl install $(DEVICE) build/Build/Products/Debug-iphonesimulator/mobile.app
|
||||
|
||||
xcodebuild-simulator:
|
||||
IOS_TARGETS=x86_64-apple-ios xcodebuild -scheme mobile -configuration Debug -derivedDataPath build -destination "id=$(DEVICE)"
|
||||
|
||||
xcodebuild-iphone:
|
||||
IOS_TARGETS=aarch64-apple-ios xcodebuild -scheme mobile -configuration Debug -derivedDataPath build -arch arm64
|
||||
|
||||
xcodebuild-iphone-release:
|
||||
IOS_TARGETS=aarch64-apple-ios xcodebuild -scheme mobile -configuration Release -derivedDataPath build -arch arm64
|
||||
|
||||
clean:
|
||||
rm -r build
|
||||
cargo clean
|
55
crates/yachtpit/mobile/build_rust_deps.sh
Normal file
@@ -0,0 +1,55 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# based on https://github.com/mozilla/glean/blob/main/build-scripts/xc-universal-binary.sh
|
||||
|
||||
set -eux
|
||||
|
||||
PATH=$PATH:$HOME/.cargo/bin
|
||||
|
||||
RELFLAG=
|
||||
if [[ "$CONFIGURATION" != "Debug" ]]; then
|
||||
RELFLAG="--profile dist"
|
||||
fi
|
||||
|
||||
set -euvx
|
||||
|
||||
if [[ -n "${DEVELOPER_SDK_DIR:-}" ]]; then
|
||||
# Assume we're in Xcode, which means we're probably cross-compiling.
|
||||
# In this case, we need to add an extra library search path for build scripts and proc-macros,
|
||||
# which run on the host instead of the target.
|
||||
# (macOS Big Sur does not have linkable libraries in /usr/lib/.)
|
||||
export LIBRARY_PATH="${DEVELOPER_SDK_DIR}/MacOSX.sdk/usr/lib:${LIBRARY_PATH:-}"
|
||||
fi
|
||||
|
||||
# add homebrew bin path, as it's the most commonly used package manager on macOS
|
||||
# this is needed for cmake on apple arm processors as it's not available by default
|
||||
export PATH="$PATH:/opt/homebrew/bin"
|
||||
|
||||
IS_SIMULATOR=0
|
||||
if [ "${LLVM_TARGET_TRIPLE_SUFFIX-}" = "-simulator" ]; then
|
||||
IS_SIMULATOR=1
|
||||
fi
|
||||
|
||||
for arch in $ARCHS; do
|
||||
case "$arch" in
|
||||
x86_64)
|
||||
if [ $IS_SIMULATOR -eq 0 ]; then
|
||||
echo "Building for x86_64, but not a simulator build. What's going on?" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
# Intel iOS simulator
|
||||
export CFLAGS_x86_64_apple_ios="-target x86_64-apple-ios"
|
||||
cargo rustc --crate-type staticlib --lib $RELFLAG --target x86_64-apple-ios
|
||||
;;
|
||||
|
||||
arm64)
|
||||
if [ $IS_SIMULATOR -eq 0 ]; then
|
||||
# Hardware iOS targets
|
||||
cargo rustc --crate-type staticlib --lib $RELFLAG --target aarch64-apple-ios
|
||||
else
|
||||
# M1 iOS simulator -- currently in Nightly only and requires to build `libstd`
|
||||
cargo rustc --crate-type staticlib --lib $RELFLAG --target aarch64-apple-ios-sim
|
||||
fi
|
||||
esac
|
||||
done
|
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "icon_1024x1024.png",
|
||||
"idiom" : "universal",
|
||||
"platform" : "ios",
|
||||
"size" : "1024x1024"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
After Width: | Height: | Size: 49 KiB |
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
35
crates/yachtpit/mobile/ios-src/Info.plist
Normal file
@@ -0,0 +1,35 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>$(PRODUCT_NAME)</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>0.1.1</string>
|
||||
<key>CFBundleIconName</key>
|
||||
<string>AppIcon</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>0.1.1</string>
|
||||
<key>UILaunchStoryboardName</key>
|
||||
<string>LaunchScreen</string>
|
||||
<key>UIRequiresFullScreen</key>
|
||||
<false/>
|
||||
<key>UISupportedInterfaceOrientations</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
37
crates/yachtpit/mobile/ios-src/LaunchScreen.storyboard
Normal file
@@ -0,0 +1,37 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="21701" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
|
||||
<device id="retina6_12" orientation="portrait" appearance="light"/>
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="21679"/>
|
||||
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--View Controller-->
|
||||
<scene sceneID="EHf-IW-A2E">
|
||||
<objects>
|
||||
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
|
||||
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
|
||||
<rect key="frame" x="0.0" y="0.0" width="393" height="842"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" fixedFrame="YES" image="bevy.png" translatesAutoresizingMaskIntoConstraints="NO" id="sic-lC-kjy">
|
||||
<rect key="frame" x="113" y="346" width="164" height="153"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" flexibleMinY="YES" heightSizable="YES" flexibleMaxY="YES"/>
|
||||
</imageView>
|
||||
</subviews>
|
||||
<viewLayoutGuide key="safeArea" id="Bcu-3y-fUS"/>
|
||||
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
</view>
|
||||
<modalPageSheetSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="53" y="375"/>
|
||||
</scene>
|
||||
</scenes>
|
||||
<resources>
|
||||
<image name="bevy.png" width="152.33058166503906" height="152.33058166503906"/>
|
||||
</resources>
|
||||
</document>
|
1
crates/yachtpit/mobile/ios-src/bindings.h
Normal file
@@ -0,0 +1 @@
|
||||
void main_rs(void);
|
6
crates/yachtpit/mobile/ios-src/main.m
Normal file
@@ -0,0 +1,6 @@
|
||||
#import "bindings.h"
|
||||
|
||||
int main() {
|
||||
main_rs();
|
||||
return 0;
|
||||
}
|
11
crates/yachtpit/mobile/manifest.yaml
Normal file
@@ -0,0 +1,11 @@
|
||||
android:
|
||||
gradle: true
|
||||
# this assets configuration is currently only used without gradle!
|
||||
#assets:
|
||||
# - "../assets/*"
|
||||
icon: "ios-src/Assets.xcassets/AppIcon.appiconset/icon_1024x1024.png"
|
||||
manifest:
|
||||
package: "io.gs.yachtpit"
|
||||
version_code: 1
|
||||
application:
|
||||
label: "yachtpit"
|
473
crates/yachtpit/mobile/mobile.xcodeproj/project.pbxproj
Normal file
@@ -0,0 +1,473 @@
|
||||
// !$*UTF8*$!
|
||||
{
|
||||
archiveVersion = 1;
|
||||
classes = {
|
||||
};
|
||||
objectVersion = 51;
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
134866208A035F8615C99114 /* Metal.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 96A1E5B62F48B379829E8A0D /* Metal.framework */; };
|
||||
2469A4292A6F9AC200ACF4EF /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 2469A4282A6F9AC200ACF4EF /* Assets.xcassets */; };
|
||||
2469A42B2A6FAC7000ACF4EF /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 2469A42A2A6FAC7000ACF4EF /* LaunchScreen.storyboard */; };
|
||||
2604C99FAB5A8322EDCABB9F /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FE71FBCAA714DB4F42459106 /* UIKit.framework */; };
|
||||
442540D056ADB9AE61A0A590 /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9F1B41978FA53999AA836D0F /* Security.framework */; };
|
||||
55892F1396056740E1AF9685 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = AF7DE91055EBD05ED77E57F9 /* main.m */; };
|
||||
55B7188F81C3C4183F81D3AE /* libc++.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = A39528EB2CCB182F5328223A /* libc++.tbd */; };
|
||||
57CD6306253C7A940098CD4A /* AudioToolbox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 57CD6305253C7A940098CD4A /* AudioToolbox.framework */; };
|
||||
57CD630E253C80EC0098CD4A /* assets in Resources */ = {isa = PBXBuildFile; fileRef = 57CD630A253C7F5F0098CD4A /* assets */; };
|
||||
6ADF1AB92CCDA73A00AF5F8E /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6ADF1AB82CCDA73A00AF5F8E /* QuartzCore.framework */; };
|
||||
D4A53EFF2DDD2FF70035BC01 /* AVFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D4A53EFE2DDD2FF70035BC01 /* AVFoundation.framework */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
26BF2C4863C966DABAB40DC8 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 8DBF1E2B5C613DA41701F6D9 /* Project object */;
|
||||
proxyType = 1;
|
||||
remoteGlobalIDString = D08AEBE0B1A9C9A7B8C7B33F;
|
||||
remoteInfo = cargo_ios;
|
||||
};
|
||||
/* End PBXContainerItemProxy section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
160DB77300A3F1806F024D47 /* bindings.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = bindings.h; sourceTree = "<group>"; };
|
||||
2469A4282A6F9AC200ACF4EF /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "ios-src/Assets.xcassets"; sourceTree = SOURCE_ROOT; };
|
||||
2469A42A2A6FAC7000ACF4EF /* LaunchScreen.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = ios-src/LaunchScreen.storyboard; sourceTree = "<group>"; };
|
||||
55EAC02897847195D2F44C15 /* mobile.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = mobile.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
57CD6305253C7A940098CD4A /* AudioToolbox.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AudioToolbox.framework; path = System/Library/Frameworks/AudioToolbox.framework; sourceTree = SDKROOT; };
|
||||
57CD630A253C7F5F0098CD4A /* assets */ = {isa = PBXFileReference; lastKnownFileType = folder; name = assets; path = ../../assets; sourceTree = "<group>"; };
|
||||
6ADF1AB82CCDA73A00AF5F8E /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; };
|
||||
8EE7F1E3B0303533925D7E33 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = "<group>"; };
|
||||
96A1E5B62F48B379829E8A0D /* Metal.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Metal.framework; path = System/Library/Frameworks/Metal.framework; sourceTree = SDKROOT; };
|
||||
9F1B41978FA53999AA836D0F /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = System/Library/Frameworks/Security.framework; sourceTree = SDKROOT; };
|
||||
A39528EB2CCB182F5328223A /* libc++.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = "libc++.tbd"; path = "usr/lib/libc++.tbd"; sourceTree = SDKROOT; };
|
||||
AF7DE91055EBD05ED77E57F9 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; };
|
||||
D4A53EFE2DDD2FF70035BC01 /* AVFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AVFoundation.framework; path = System/Library/Frameworks/AVFoundation.framework; sourceTree = SDKROOT; };
|
||||
FE71FBCAA714DB4F42459106 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
D5A822CB2D6847BA8800BE4C /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
6ADF1AB92CCDA73A00AF5F8E /* QuartzCore.framework in Frameworks */,
|
||||
442540D056ADB9AE61A0A590 /* Security.framework in Frameworks */,
|
||||
134866208A035F8615C99114 /* Metal.framework in Frameworks */,
|
||||
2604C99FAB5A8322EDCABB9F /* UIKit.framework in Frameworks */,
|
||||
55B7188F81C3C4183F81D3AE /* libc++.tbd in Frameworks */,
|
||||
D4A53EFF2DDD2FF70035BC01 /* AVFoundation.framework in Frameworks */,
|
||||
57CD6306253C7A940098CD4A /* AudioToolbox.framework in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
321F7D6A765B38E746C35105 /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
55EAC02897847195D2F44C15 /* mobile.app */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4F1D6F28B8A5D1927AB0ADED /* ios-src */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
2469A4282A6F9AC200ACF4EF /* Assets.xcassets */,
|
||||
57CD630A253C7F5F0098CD4A /* assets */,
|
||||
160DB77300A3F1806F024D47 /* bindings.h */,
|
||||
8EE7F1E3B0303533925D7E33 /* Info.plist */,
|
||||
AF7DE91055EBD05ED77E57F9 /* main.m */,
|
||||
);
|
||||
path = "ios-src";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
8F2E3E6040EAD2EC9F3FA530 = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
2469A42A2A6FAC7000ACF4EF /* LaunchScreen.storyboard */,
|
||||
4F1D6F28B8A5D1927AB0ADED /* ios-src */,
|
||||
EB028409C2D0655412DA6E44 /* Frameworks */,
|
||||
321F7D6A765B38E746C35105 /* Products */,
|
||||
);
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
EB028409C2D0655412DA6E44 /* Frameworks */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
D4A53EFE2DDD2FF70035BC01 /* AVFoundation.framework */,
|
||||
6ADF1AB82CCDA73A00AF5F8E /* QuartzCore.framework */,
|
||||
57CD6305253C7A940098CD4A /* AudioToolbox.framework */,
|
||||
A39528EB2CCB182F5328223A /* libc++.tbd */,
|
||||
96A1E5B62F48B379829E8A0D /* Metal.framework */,
|
||||
9F1B41978FA53999AA836D0F /* Security.framework */,
|
||||
FE71FBCAA714DB4F42459106 /* UIKit.framework */,
|
||||
);
|
||||
name = Frameworks;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXLegacyTarget section */
|
||||
D08AEBE0B1A9C9A7B8C7B33F /* cargo_ios */ = {
|
||||
isa = PBXLegacyTarget;
|
||||
buildArgumentsString = build_rust_deps.sh;
|
||||
buildConfigurationList = AA00A0CFDB11F37F2BA3FC2E /* Build configuration list for PBXLegacyTarget "cargo_ios" */;
|
||||
buildPhases = (
|
||||
FE045B3D04D57B713A565FF8 /* Sources */,
|
||||
);
|
||||
buildToolPath = /bin/sh;
|
||||
buildWorkingDirectory = .;
|
||||
dependencies = (
|
||||
);
|
||||
name = cargo_ios;
|
||||
passBuildSettingsInEnvironment = 1;
|
||||
productName = cargo_ios;
|
||||
};
|
||||
/* End PBXLegacyTarget section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
3BDB8152E4962373181B4FE5 /* mobile */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = E714A1AEAAE517C348B5BD27 /* Build configuration list for PBXNativeTarget "mobile" */;
|
||||
buildPhases = (
|
||||
9F13800790AD9DBC2BC0F116 /* Sources */,
|
||||
D5A822CB2D6847BA8800BE4C /* Frameworks */,
|
||||
57CD630D253C80E60098CD4A /* Resources */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
19D4B9C22ADC6705B5132B4C /* PBXTargetDependency */,
|
||||
);
|
||||
name = mobile;
|
||||
productName = mobile;
|
||||
productReference = 55EAC02897847195D2F44C15 /* mobile.app */;
|
||||
productType = "com.apple.product-type.application";
|
||||
};
|
||||
/* End PBXNativeTarget section */
|
||||
|
||||
/* Begin PBXProject section */
|
||||
8DBF1E2B5C613DA41701F6D9 /* Project object */ = {
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
LastUpgradeCheck = 1240;
|
||||
};
|
||||
buildConfigurationList = 9D43D41707A5C30B227B83F9 /* Build configuration list for PBXProject "mobile" */;
|
||||
compatibilityVersion = "Xcode 10.0";
|
||||
developmentRegion = en;
|
||||
hasScannedForEncodings = 0;
|
||||
knownRegions = (
|
||||
en,
|
||||
);
|
||||
mainGroup = 8F2E3E6040EAD2EC9F3FA530;
|
||||
projectDirPath = "";
|
||||
projectRoot = "";
|
||||
targets = (
|
||||
3BDB8152E4962373181B4FE5 /* mobile */,
|
||||
D08AEBE0B1A9C9A7B8C7B33F /* cargo_ios */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
|
||||
/* Begin PBXResourcesBuildPhase section */
|
||||
57CD630D253C80E60098CD4A /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
2469A42B2A6FAC7000ACF4EF /* LaunchScreen.storyboard in Resources */,
|
||||
2469A4292A6F9AC200ACF4EF /* Assets.xcassets in Resources */,
|
||||
57CD630E253C80EC0098CD4A /* assets in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXResourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
9F13800790AD9DBC2BC0F116 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
55892F1396056740E1AF9685 /* main.m in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
FE045B3D04D57B713A565FF8 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXSourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXTargetDependency section */
|
||||
19D4B9C22ADC6705B5132B4C /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = D08AEBE0B1A9C9A7B8C7B33F /* cargo_ios */;
|
||||
targetProxy = 26BF2C4863C966DABAB40DC8 /* PBXContainerItemProxy */;
|
||||
};
|
||||
/* End PBXTargetDependency section */
|
||||
|
||||
/* Begin XCBuildConfiguration section */
|
||||
4AD7BC6FDD56FF18FA6DA7D7 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
CODE_SIGN_IDENTITY = "";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_TESTABILITY = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
GCC_DYNAMIC_NO_PIC = NO;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_OPTIMIZATION_LEVEL = 0;
|
||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||
"$(inherited)",
|
||||
"DEBUG=1",
|
||||
);
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
MTL_ENABLE_DEBUG_INFO = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SDKROOT = iphoneos;
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_VERSION = 5.0;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
5B14EC4ADC81FBF1F8CF20E9 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "c++11";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CODE_SIGN_IDENTITY = "";
|
||||
DEVELOPMENT_TEAM = "";
|
||||
ENABLE_BITCODE = NO;
|
||||
HEADER_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"ios-src/",
|
||||
);
|
||||
INFOPLIST_FILE = "ios-src/Info.plist";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
"LIBRARY_SEARCH_PATHS[sdk=iphoneos*][arch=arm64]" = (
|
||||
"$(inherited)",
|
||||
"../target/aarch64-apple-ios/dist",
|
||||
);
|
||||
"LIBRARY_SEARCH_PATHS[sdk=iphonesimulator*][arch=arm64]" = (
|
||||
"$(inherited)",
|
||||
"../target/aarch64-apple-ios-sim/dist",
|
||||
);
|
||||
"LIBRARY_SEARCH_PATHS[sdk=iphonesimulator*][arch=x86_64]" = (
|
||||
"$(inherited)",
|
||||
"../target/x86_64-apple-ios/dist",
|
||||
);
|
||||
OTHER_LDFLAGS = (
|
||||
"$(inherited)",
|
||||
"-lmobile",
|
||||
"-lc++abi",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER=io.gs.yachtpit;
|
||||
SDKROOT = iphoneos;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
79E3C28F06346EA58420A93D /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
CODE_SIGN_IDENTITY = "";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SDKROOT = iphoneos;
|
||||
SWIFT_COMPILATION_MODE = wholemodule;
|
||||
SWIFT_VERSION = 5.0;
|
||||
VALIDATE_PRODUCT = YES;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
8265913A25816D964A847F1B /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "com.rust.cargo-ios";
|
||||
SDKROOT = iphoneos;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
A2D5B73DD30D562B6F366526 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "c++11";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CODE_SIGN_IDENTITY = "";
|
||||
DEVELOPMENT_TEAM = "";
|
||||
ENABLE_BITCODE = NO;
|
||||
HEADER_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"ios-src/",
|
||||
);
|
||||
INFOPLIST_FILE = "ios-src/Info.plist";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
"LIBRARY_SEARCH_PATHS[sdk=iphoneos*][arch=arm64]" = (
|
||||
"$(inherited)",
|
||||
"../target/aarch64-apple-ios/debug",
|
||||
);
|
||||
"LIBRARY_SEARCH_PATHS[sdk=iphonesimulator*][arch=arm64]" = (
|
||||
"$(inherited)",
|
||||
"../target/aarch64-apple-ios-sim/debug",
|
||||
);
|
||||
"LIBRARY_SEARCH_PATHS[sdk=iphonesimulator*][arch=x86_64]" = (
|
||||
"$(inherited)",
|
||||
"../target/x86_64-apple-ios/debug",
|
||||
);
|
||||
OTHER_LDFLAGS = (
|
||||
"$(inherited)",
|
||||
"-lmobile",
|
||||
"-lc++abi",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER=io.gs.yachtpit;
|
||||
SDKROOT = iphoneos;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
FEA9B18D9236F9F6DC6DF799 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "com.rust.cargo-ios";
|
||||
SDKROOT = iphoneos;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
/* End XCBuildConfiguration section */
|
||||
|
||||
/* Begin XCConfigurationList section */
|
||||
9D43D41707A5C30B227B83F9 /* Build configuration list for PBXProject "mobile" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
4AD7BC6FDD56FF18FA6DA7D7 /* Debug */,
|
||||
79E3C28F06346EA58420A93D /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Debug;
|
||||
};
|
||||
AA00A0CFDB11F37F2BA3FC2E /* Build configuration list for PBXLegacyTarget "cargo_ios" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
8265913A25816D964A847F1B /* Debug */,
|
||||
FEA9B18D9236F9F6DC6DF799 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Debug;
|
||||
};
|
||||
E714A1AEAAE517C348B5BD27 /* Build configuration list for PBXNativeTarget "mobile" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
A2D5B73DD30D562B6F366526 /* Debug */,
|
||||
5B14EC4ADC81FBF1F8CF20E9 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Debug;
|
||||
};
|
||||
/* End XCConfigurationList section */
|
||||
};
|
||||
rootObject = 8DBF1E2B5C613DA41701F6D9 /* Project object */;
|
||||
}
|
@@ -0,0 +1,78 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1310"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "3BDB8152E4962373181B4FE5"
|
||||
BuildableName = "mobile.app"
|
||||
BlueprintName = "mobile"
|
||||
ReferencedContainer = "container:mobile.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<Testables>
|
||||
</Testables>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "3BDB8152E4962373181B4FE5"
|
||||
BuildableName = "mobile.app"
|
||||
BlueprintName = "mobile"
|
||||
ReferencedContainer = "container:mobile.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "3BDB8152E4962373181B4FE5"
|
||||
BuildableName = "mobile.app"
|
||||
BlueprintName = "mobile"
|
||||
ReferencedContainer = "container:mobile.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
41
crates/yachtpit/mobile/src/lib.rs
Normal file
@@ -0,0 +1,41 @@
|
||||
use bevy::prelude::*;
|
||||
use bevy::window::WindowMode;
|
||||
use bevy::winit::WinitSettings;
|
||||
use yachtpit::GamePlugin;
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
unsafe extern "C" fn main_rs() {
|
||||
main();
|
||||
}
|
||||
|
||||
// this macro is a no-op on ios and only needed for anroid since bevy 0.16
|
||||
// see https://github.com/bevyengine/bevy/pull/14780
|
||||
#[bevy_main]
|
||||
fn main() {
|
||||
#[cfg(target_os = "ios")]
|
||||
unsafe {
|
||||
// Sets our audio session to Ambient mode to prevent background music from stopping.
|
||||
// The default for iOS apps is SoloAmbient, which stops background music.
|
||||
// See apple docs: https://developer.apple.com/documentation/avfaudio/avaudiosession/category-swift.struct/ambient
|
||||
if let Err(e) = objc2_avf_audio::AVAudioSession::sharedInstance()
|
||||
.setCategory_error(objc2_avf_audio::AVAudioSessionCategoryAmbient.unwrap())
|
||||
{
|
||||
println!("Error setting audio session category: {:?}", e);
|
||||
}
|
||||
}
|
||||
|
||||
App::new()
|
||||
.insert_resource(WinitSettings::mobile())
|
||||
.add_plugins((
|
||||
DefaultPlugins.set(WindowPlugin {
|
||||
primary_window: Some(Window {
|
||||
resizable: false,
|
||||
mode: WindowMode::BorderlessFullscreen(MonitorSelection::Current),
|
||||
..default()
|
||||
}),
|
||||
..default()
|
||||
}),
|
||||
GamePlugin,
|
||||
))
|
||||
.run();
|
||||
}
|
16
crates/yachtpit/package.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"name": "yachtpit",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"files": [
|
||||
"../../dist"
|
||||
],
|
||||
"scripts": {
|
||||
"clean": "../../cleanup.sh",
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC"
|
||||
}
|
35
crates/yachtpit/src/core/actions/game_control.rs
Normal file
@@ -0,0 +1,35 @@
|
||||
use bevy::prelude::{ButtonInput, KeyCode, Res};
|
||||
|
||||
pub enum GameControl {
|
||||
Up,
|
||||
Down,
|
||||
Left,
|
||||
Right,
|
||||
}
|
||||
|
||||
impl GameControl {
|
||||
pub fn pressed(&self, keyboard_input: &Res<ButtonInput<KeyCode>>) -> bool {
|
||||
match self {
|
||||
GameControl::Up => {
|
||||
keyboard_input.pressed(KeyCode::KeyW) || keyboard_input.pressed(KeyCode::ArrowUp)
|
||||
}
|
||||
GameControl::Down => {
|
||||
keyboard_input.pressed(KeyCode::KeyS) || keyboard_input.pressed(KeyCode::ArrowDown)
|
||||
}
|
||||
GameControl::Left => {
|
||||
keyboard_input.pressed(KeyCode::KeyA) || keyboard_input.pressed(KeyCode::ArrowLeft)
|
||||
}
|
||||
GameControl::Right => {
|
||||
keyboard_input.pressed(KeyCode::KeyD) || keyboard_input.pressed(KeyCode::ArrowRight)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_movement(control: GameControl, input: &Res<ButtonInput<KeyCode>>) -> f32 {
|
||||
if control.pressed(input) {
|
||||
1.0
|
||||
} else {
|
||||
0.0
|
||||
}
|
||||
}
|
44
crates/yachtpit/src/core/actions/mod.rs
Normal file
@@ -0,0 +1,44 @@
|
||||
use bevy::prelude::*;
|
||||
|
||||
use crate::core::actions::game_control::{get_movement, GameControl};
|
||||
use crate::GameState;
|
||||
|
||||
mod game_control;
|
||||
|
||||
pub const FOLLOW_EPSILON: f32 = 5.;
|
||||
|
||||
pub struct ActionsPlugin;
|
||||
|
||||
// This plugin listens for keyboard input and converts the input into Actions.
|
||||
// Actions can then be used as a resource in other systems to act on the player input.
|
||||
impl Plugin for ActionsPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.init_resource::<Actions>().add_systems(
|
||||
Update,
|
||||
set_movement_actions.run_if(in_state(GameState::Playing)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Resource)]
|
||||
pub struct Actions {
|
||||
pub player_movement: Option<Vec2>,
|
||||
}
|
||||
|
||||
pub fn set_movement_actions(
|
||||
mut actions: ResMut<Actions>,
|
||||
keyboard_input: Res<ButtonInput<KeyCode>>,
|
||||
) {
|
||||
let player_movement = Vec2::new(
|
||||
get_movement(GameControl::Right, &keyboard_input)
|
||||
- get_movement(GameControl::Left, &keyboard_input),
|
||||
get_movement(GameControl::Up, &keyboard_input)
|
||||
- get_movement(GameControl::Down, &keyboard_input),
|
||||
);
|
||||
|
||||
if player_movement != Vec2::ZERO {
|
||||
actions.player_movement = Some(player_movement.normalize());
|
||||
} else {
|
||||
actions.player_movement = None;
|
||||
}
|
||||
}
|
5
crates/yachtpit/src/core/mod.rs
Normal file
@@ -0,0 +1,5 @@
|
||||
pub mod actions;
|
||||
pub mod system_manager;
|
||||
|
||||
pub use actions::ActionsPlugin;
|
||||
pub use system_manager::{SystemManagerPlugin};
|
223
crates/yachtpit/src/core/system_manager.rs
Normal file
@@ -0,0 +1,223 @@
|
||||
//! Higher-level abstraction for managing yacht systems and their interactions
|
||||
//!
|
||||
//! This module provides a unified approach to handling different yacht systems
|
||||
//! (GPS, Radar, AIS, etc.) with common patterns for state management, UI updates,
|
||||
//! and user interactions.
|
||||
|
||||
use bevy::prelude::*;
|
||||
use std::collections::HashMap;
|
||||
use systems::{YachtSystem, SystemInteraction, SystemStatus};
|
||||
use components::YachtData;
|
||||
|
||||
/// Resource for managing all yacht systems
|
||||
#[derive(Resource)]
|
||||
pub struct SystemManager {
|
||||
systems: HashMap<String, Box<dyn YachtSystem>>,
|
||||
active_system: Option<String>,
|
||||
system_order: Vec<String>,
|
||||
}
|
||||
|
||||
impl SystemManager {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
systems: HashMap::new(),
|
||||
active_system: None,
|
||||
system_order: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Register a new yacht system
|
||||
pub fn register_system(&mut self, system: Box<dyn YachtSystem>) {
|
||||
let id = system.id().to_string();
|
||||
self.system_order.push(id.clone());
|
||||
self.systems.insert(id, system);
|
||||
}
|
||||
|
||||
/// Get the currently active system
|
||||
pub fn active_system(&self) -> Option<&dyn YachtSystem> {
|
||||
self.active_system.as_ref()
|
||||
.and_then(|id| self.systems.get(id))
|
||||
.map(|system| system.as_ref())
|
||||
}
|
||||
|
||||
/// Set the active system by ID
|
||||
pub fn set_active_system(&mut self, system_id: &str) -> bool {
|
||||
if self.systems.contains_key(system_id) {
|
||||
self.active_system = Some(system_id.to_string());
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// Get all registered systems in order
|
||||
pub fn get_systems(&self) -> Vec<&dyn YachtSystem> {
|
||||
self.system_order.iter()
|
||||
.filter_map(|id| self.systems.get(id))
|
||||
.map(|system| system.as_ref())
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Update all systems
|
||||
pub fn update_systems(&mut self, yacht_data: &YachtData, time: &Time) {
|
||||
for system in self.systems.values_mut() {
|
||||
system.update(yacht_data, time);
|
||||
}
|
||||
}
|
||||
|
||||
/// Handle interaction with a specific system
|
||||
pub fn handle_system_interaction(&mut self, system_id: &str, interaction: SystemInteraction) -> bool {
|
||||
if let Some(system) = self.systems.get_mut(system_id) {
|
||||
system.handle_interaction(interaction)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// Get system by ID
|
||||
pub fn get_system(&self, system_id: &str) -> Option<&dyn YachtSystem> {
|
||||
self.systems.get(system_id).map(|s| s.as_ref())
|
||||
}
|
||||
|
||||
/// Get mutable system by ID
|
||||
pub fn get_system_mut(&mut self, system_id: &str) -> Option<&mut Box<dyn YachtSystem>> {
|
||||
self.systems.get_mut(system_id)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for SystemManager {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
/// Component for marking UI elements as system indicators
|
||||
#[derive(Component)]
|
||||
pub struct SystemIndicator {
|
||||
pub system_id: String,
|
||||
}
|
||||
|
||||
/// Component for marking the main system display area
|
||||
#[derive(Component)]
|
||||
pub struct SystemDisplayArea;
|
||||
|
||||
/// Plugin for the system manager
|
||||
pub struct SystemManagerPlugin;
|
||||
|
||||
impl Plugin for SystemManagerPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.init_resource::<SystemManager>()
|
||||
.add_systems(
|
||||
Update,
|
||||
(
|
||||
update_all_systems,
|
||||
handle_system_indicator_interactions,
|
||||
update_system_display_content,
|
||||
).run_if(in_state(crate::GameState::Playing))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// System to update all yacht systems
|
||||
fn update_all_systems(
|
||||
mut system_manager: ResMut<SystemManager>,
|
||||
yacht_data: Res<components::YachtData>,
|
||||
time: Res<Time>,
|
||||
) {
|
||||
system_manager.update_systems(&yacht_data, &time);
|
||||
}
|
||||
|
||||
/// System to handle interactions with system indicator buttons
|
||||
fn handle_system_indicator_interactions(
|
||||
mut system_manager: ResMut<SystemManager>,
|
||||
mut interaction_query: Query<
|
||||
(&Interaction, &mut BackgroundColor, &SystemIndicator),
|
||||
(Changed<Interaction>, With<Button>),
|
||||
>,
|
||||
) {
|
||||
for (interaction, mut background_color, indicator) in &mut interaction_query {
|
||||
match *interaction {
|
||||
Interaction::Pressed => {
|
||||
system_manager.set_active_system(&indicator.system_id);
|
||||
system_manager.handle_system_interaction(
|
||||
&indicator.system_id,
|
||||
SystemInteraction::Select
|
||||
);
|
||||
*background_color = BackgroundColor(Color::linear_rgb(0.0, 0.3, 0.5));
|
||||
}
|
||||
Interaction::Hovered => {
|
||||
*background_color = BackgroundColor(Color::linear_rgb(0.15, 0.15, 0.2));
|
||||
}
|
||||
Interaction::None => {
|
||||
*background_color = BackgroundColor(Color::linear_rgb(0.1, 0.1, 0.15));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// System to update the main display area with active system content
|
||||
fn update_system_display_content(
|
||||
system_manager: Res<SystemManager>,
|
||||
mut display_query: Query<&mut Text, With<SystemDisplayArea>>,
|
||||
yacht_data: Res<components::YachtData>,
|
||||
) {
|
||||
if let Ok(mut text) = display_query.single_mut() {
|
||||
if let Some(active_system) = system_manager.active_system() {
|
||||
text.0 = active_system.render_display(&yacht_data);
|
||||
} else {
|
||||
text.0 = "Select a system above to view details".to_string();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use components::YachtData;
|
||||
|
||||
struct MockSystem {
|
||||
id: &'static str,
|
||||
name: &'static str,
|
||||
status: SystemStatus,
|
||||
}
|
||||
|
||||
impl YachtSystem for MockSystem {
|
||||
fn id(&self) -> &'static str { self.id }
|
||||
fn display_name(&self) -> &'static str { self.name }
|
||||
fn update(&mut self, _yacht_data: &YachtData, _time: &Time) {}
|
||||
fn render_display(&self, _yacht_data: &YachtData) -> String {
|
||||
format!("Mock system: {}", self.name)
|
||||
}
|
||||
fn handle_interaction(&mut self, _interaction: SystemInteraction) -> bool { true }
|
||||
fn status(&self) -> SystemStatus { self.status.clone() }
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_system_manager_registration() {
|
||||
let mut manager = SystemManager::new();
|
||||
let mock_system = Box::new(MockSystem {
|
||||
id: "test",
|
||||
name: "Test System",
|
||||
status: SystemStatus::Active,
|
||||
});
|
||||
|
||||
manager.register_system(mock_system);
|
||||
assert!(manager.get_system("test").is_some());
|
||||
assert_eq!(manager.get_systems().len(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_active_system_management() {
|
||||
let mut manager = SystemManager::new();
|
||||
let mock_system = Box::new(MockSystem {
|
||||
id: "test",
|
||||
name: "Test System",
|
||||
status: SystemStatus::Active,
|
||||
});
|
||||
|
||||
manager.register_system(mock_system);
|
||||
assert!(manager.set_active_system("test"));
|
||||
assert!(manager.active_system().is_some());
|
||||
assert_eq!(manager.active_system().unwrap().id(), "test");
|
||||
}
|
||||
}
|
59
crates/yachtpit/src/lib.rs
Normal file
@@ -0,0 +1,59 @@
|
||||
#![allow(clippy::type_complexity)]
|
||||
|
||||
|
||||
mod core;
|
||||
mod ui;
|
||||
|
||||
use bevy::app::App;
|
||||
#[cfg(debug_assertions)]
|
||||
use bevy::diagnostic::{FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin};
|
||||
use bevy::prelude::*;
|
||||
use crate::core::{ActionsPlugin, SystemManagerPlugin};
|
||||
use crate::core::system_manager::SystemManager;
|
||||
use crate::ui::{LoadingPlugin, MenuPlugin};
|
||||
use systems::{PlayerPlugin, setup_instrument_cluster, get_yacht_systems};
|
||||
|
||||
// This game uses States to separate logic
|
||||
// See https://bevy-cheatbook.github.io/programming/states.html
|
||||
// Or https://github.com/bevyengine/bevy/blob/main/examples/ecs/state.rs
|
||||
#[derive(States, Default, Clone, Eq, PartialEq, Debug, Hash)]
|
||||
enum GameState {
|
||||
// During the loading State the LoadingPlugin will load our assets
|
||||
#[default]
|
||||
Loading,
|
||||
// During this State the actual game logic is executed
|
||||
Playing,
|
||||
// Here the menu is drawn and waiting for player interaction
|
||||
Menu,
|
||||
}
|
||||
|
||||
pub struct GamePlugin;
|
||||
|
||||
/// Initialize yacht systems in the SystemManager
|
||||
fn initialize_yacht_systems(mut system_manager: ResMut<SystemManager>) {
|
||||
let systems = get_yacht_systems();
|
||||
for system in systems {
|
||||
system_manager.register_system(system);
|
||||
}
|
||||
}
|
||||
|
||||
impl Plugin for GamePlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.init_state::<GameState>().add_plugins((
|
||||
LoadingPlugin,
|
||||
MenuPlugin,
|
||||
ActionsPlugin,
|
||||
SystemManagerPlugin,
|
||||
PlayerPlugin,
|
||||
))
|
||||
.add_systems(OnEnter(GameState::Playing), (setup_instrument_cluster, initialize_yacht_systems));
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
{
|
||||
app.add_plugins((
|
||||
FrameTimeDiagnosticsPlugin::default(),
|
||||
LogDiagnosticsPlugin::default(),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
60
crates/yachtpit/src/main.rs
Normal file
@@ -0,0 +1,60 @@
|
||||
// disable console on windows for release builds
|
||||
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
|
||||
|
||||
use bevy::asset::AssetMetaCheck;
|
||||
use bevy::prelude::*;
|
||||
use bevy::window::PrimaryWindow;
|
||||
use bevy::winit::WinitWindows;
|
||||
use bevy::DefaultPlugins;
|
||||
use yachtpit::GamePlugin;
|
||||
use std::io::Cursor;
|
||||
use winit::window::Icon;
|
||||
|
||||
fn main() {
|
||||
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<WinitWindows>,
|
||||
primary_window: Query<Entity, With<PrimaryWindow>>,
|
||||
) -> Result {
|
||||
let primary_entity = primary_window.single()?;
|
||||
let Some(primary) = windows.get_window(primary_entity) else {
|
||||
return Err(BevyError::from("No primary window!"));
|
||||
};
|
||||
let icon_buf = Cursor::new(include_bytes!(
|
||||
"../build/macos/AppIcon.iconset/icon_256x256.png"
|
||||
));
|
||||
if let Ok(image) = image::load(icon_buf, image::ImageFormat::Png) {
|
||||
let image = image.into_rgba8();
|
||||
let (width, height) = image.dimensions();
|
||||
let rgba = image.into_raw();
|
||||
let icon = Icon::from_rgba(rgba, width, height).unwrap();
|
||||
primary.set_window_icon(Some(icon));
|
||||
};
|
||||
|
||||
Ok(())
|
||||
}
|
65
crates/yachtpit/src/ui/loading.rs
Normal file
@@ -0,0 +1,65 @@
|
||||
use crate::GameState;
|
||||
use bevy::prelude::*;
|
||||
use bevy_asset_loader::prelude::*;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
use wasm_bindgen::prelude::*;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
use web_sys::{Document, Element, HtmlElement, Window};
|
||||
|
||||
pub struct LoadingPlugin;
|
||||
|
||||
/// This plugin loads all assets using [`AssetLoader`] from a third party bevy plugin
|
||||
/// Alternatively you can write the logic to load assets yourself
|
||||
/// If interested, take a look at <https://bevy-cheatbook.github.io/features/assets.html>
|
||||
impl Plugin for LoadingPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.add_loading_state(
|
||||
LoadingState::new(GameState::Loading)
|
||||
.continue_to_state(GameState::Playing)
|
||||
// .load_collection::<AudioAssets>()
|
||||
// .load_collection::<TextureAssets>(),
|
||||
);
|
||||
|
||||
// Add a system to hide the loading indicator when transitioning to the Playing state
|
||||
app.add_systems(OnEnter(GameState::Playing), hide_loading_indicator);
|
||||
}
|
||||
}
|
||||
|
||||
/// Hides the loading indicator when transitioning to the Playing state
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
fn hide_loading_indicator() {
|
||||
info!("Hiding loading indicator");
|
||||
|
||||
// Get the window object
|
||||
let window = web_sys::window().expect("Failed to get window");
|
||||
|
||||
// Get the document object
|
||||
let document = window.document().expect("Failed to get document");
|
||||
|
||||
// Get the loading indicator element
|
||||
if let Some(loading_indicator) = document.query_selector(".lds-dual-ring").ok().flatten() {
|
||||
// Set its display property to "none" to hide it
|
||||
let element = loading_indicator.dyn_into::<HtmlElement>().expect("Failed to cast to HtmlElement");
|
||||
element.style().set_property("display", "none").expect("Failed to set style property");
|
||||
} else {
|
||||
warn!("Could not find loading indicator element");
|
||||
}
|
||||
}
|
||||
|
||||
/// No-op implementation for non-wasm32 targets
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
fn hide_loading_indicator() {
|
||||
info!("Hiding loading indicator (no-op on non-wasm32 targets)");
|
||||
}
|
||||
|
||||
// the following asset collections will be loaded during the State `GameState::Loading`
|
||||
// when done loading, they will be inserted as resources (see <https://github.com/NiklasEi/bevy_asset_loader>)
|
||||
|
||||
|
||||
// #[derive(AssetCollection, Resource)]
|
||||
// pub struct TextureAssets {
|
||||
// #[asset(path = "assets/textures/bevy.png")]
|
||||
// pub bevy: Handle<Image>,
|
||||
// #[asset(path = "assets/textures/github.png")]
|
||||
// pub github: Handle<Image>,
|
||||
// }
|
203
crates/yachtpit/src/ui/menu.rs
Normal file
@@ -0,0 +1,203 @@
|
||||
use crate::GameState;
|
||||
use bevy::prelude::*;
|
||||
|
||||
pub struct MenuPlugin;
|
||||
|
||||
/// This plugin is responsible for the game menu (containing only one button...)
|
||||
/// The menu is only drawn during the State `GameState::Menu` and is removed when that state is exited
|
||||
impl Plugin for MenuPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.add_systems(OnEnter(GameState::Menu), setup_menu)
|
||||
.add_systems(Update, click_play_button.run_if(in_state(GameState::Menu)))
|
||||
.add_systems(OnExit(GameState::Menu), cleanup_menu);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Component)]
|
||||
struct ButtonColors {
|
||||
normal: Color,
|
||||
hovered: Color,
|
||||
}
|
||||
|
||||
impl Default for ButtonColors {
|
||||
fn default() -> Self {
|
||||
ButtonColors {
|
||||
normal: Color::linear_rgb(0.15, 0.15, 0.15),
|
||||
hovered: Color::linear_rgb(0.25, 0.25, 0.25),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Component)]
|
||||
struct Menu;
|
||||
|
||||
fn setup_menu(mut commands: Commands) {
|
||||
info!("menu");
|
||||
commands.spawn((Camera2d, Msaa::Off));
|
||||
commands
|
||||
.spawn((
|
||||
Node {
|
||||
width: Val::Percent(100.0),
|
||||
height: Val::Percent(100.0),
|
||||
flex_direction: FlexDirection::Column,
|
||||
align_items: AlignItems::Center,
|
||||
justify_content: JustifyContent::Center,
|
||||
..default()
|
||||
},
|
||||
Menu,
|
||||
))
|
||||
.with_children(|children| {
|
||||
let button_colors = ButtonColors::default();
|
||||
children
|
||||
.spawn((
|
||||
Button,
|
||||
Node {
|
||||
width: Val::Px(140.0),
|
||||
height: Val::Px(50.0),
|
||||
justify_content: JustifyContent::Center,
|
||||
align_items: AlignItems::Center,
|
||||
..Default::default()
|
||||
},
|
||||
BackgroundColor(button_colors.normal),
|
||||
button_colors,
|
||||
ChangeState(GameState::Playing),
|
||||
))
|
||||
.with_child((
|
||||
Text::new("Play"),
|
||||
TextFont {
|
||||
font_size: 40.0,
|
||||
..default()
|
||||
},
|
||||
TextColor(Color::linear_rgb(0.9, 0.9, 0.9)),
|
||||
));
|
||||
});
|
||||
commands
|
||||
.spawn((
|
||||
Node {
|
||||
flex_direction: FlexDirection::Row,
|
||||
align_items: AlignItems::Center,
|
||||
justify_content: JustifyContent::SpaceAround,
|
||||
bottom: Val::Px(5.),
|
||||
width: Val::Percent(100.),
|
||||
position_type: PositionType::Absolute,
|
||||
..default()
|
||||
},
|
||||
Menu,
|
||||
))
|
||||
.with_children(|children| {
|
||||
children
|
||||
.spawn((
|
||||
Button,
|
||||
Node {
|
||||
width: Val::Px(170.0),
|
||||
height: Val::Px(50.0),
|
||||
justify_content: JustifyContent::SpaceAround,
|
||||
align_items: AlignItems::Center,
|
||||
padding: UiRect::all(Val::Px(5.)),
|
||||
..Default::default()
|
||||
},
|
||||
BackgroundColor(Color::NONE),
|
||||
ButtonColors {
|
||||
normal: Color::NONE,
|
||||
..default()
|
||||
},
|
||||
OpenLink("https://bevyengine.org"),
|
||||
))
|
||||
.with_children(|parent| {
|
||||
parent.spawn((
|
||||
Text::new("Made with Bevy"),
|
||||
TextFont {
|
||||
font_size: 15.0,
|
||||
..default()
|
||||
},
|
||||
TextColor(Color::linear_rgb(0.9, 0.9, 0.9)),
|
||||
));
|
||||
parent.spawn((
|
||||
Node {
|
||||
width: Val::Px(32.),
|
||||
..default()
|
||||
},
|
||||
));
|
||||
});
|
||||
children
|
||||
.spawn((
|
||||
Button,
|
||||
Node {
|
||||
width: Val::Px(170.0),
|
||||
height: Val::Px(50.0),
|
||||
justify_content: JustifyContent::SpaceAround,
|
||||
align_items: AlignItems::Center,
|
||||
padding: UiRect::all(Val::Px(5.)),
|
||||
..default()
|
||||
},
|
||||
BackgroundColor(Color::NONE),
|
||||
ButtonColors {
|
||||
normal: Color::NONE,
|
||||
hovered: Color::linear_rgb(0.25, 0.25, 0.25),
|
||||
},
|
||||
OpenLink("https://github.com/NiklasEi/bevy_game_template"),
|
||||
))
|
||||
.with_children(|parent| {
|
||||
parent.spawn((
|
||||
Text::new("Open source"),
|
||||
TextFont {
|
||||
font_size: 15.0,
|
||||
..default()
|
||||
},
|
||||
TextColor(Color::linear_rgb(0.9, 0.9, 0.9)),
|
||||
));
|
||||
parent.spawn((
|
||||
Node {
|
||||
width: Val::Px(32.),
|
||||
..default()
|
||||
},
|
||||
));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
#[derive(Component)]
|
||||
struct ChangeState(GameState);
|
||||
|
||||
#[derive(Component)]
|
||||
struct OpenLink(&'static str);
|
||||
|
||||
fn click_play_button(
|
||||
mut next_state: ResMut<NextState<GameState>>,
|
||||
mut interaction_query: Query<
|
||||
(
|
||||
&Interaction,
|
||||
&mut BackgroundColor,
|
||||
&ButtonColors,
|
||||
Option<&ChangeState>,
|
||||
Option<&OpenLink>,
|
||||
),
|
||||
(Changed<Interaction>, With<Button>),
|
||||
>,
|
||||
) {
|
||||
for (interaction, mut color, button_colors, change_state, open_link) in &mut interaction_query {
|
||||
match *interaction {
|
||||
Interaction::Pressed => {
|
||||
if let Some(state) = change_state {
|
||||
next_state.set(state.0.clone());
|
||||
} else if let Some(link) = open_link {
|
||||
if let Err(error) = webbrowser::open(link.0) {
|
||||
warn!("Failed to open link {error:?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
Interaction::Hovered => {
|
||||
*color = button_colors.hovered.into();
|
||||
}
|
||||
Interaction::None => {
|
||||
*color = button_colors.normal.into();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn cleanup_menu(mut commands: Commands, menu: Query<Entity, With<Menu>>) {
|
||||
for entity in menu.iter() {
|
||||
commands.entity(entity).despawn();
|
||||
}
|
||||
}
|
5
crates/yachtpit/src/ui/mod.rs
Normal file
@@ -0,0 +1,5 @@
|
||||
pub mod loading;
|
||||
pub mod menu;
|
||||
|
||||
pub use loading::LoadingPlugin;
|
||||
pub use menu::MenuPlugin;
|