1use bevy::{
4 app::{Plugin, Update},
5 ecs::{
6 component::Component,
7 entity::Entity,
8 query::{With, Without},
9 resource::Resource,
10 schedule::IntoScheduleConfigs,
11 system::{Commands, Query, Res, ResMut, Single},
12 },
13 input::{
14 ButtonInput, common_conditions::input_just_pressed, keyboard::KeyCode, mouse::MouseButton,
15 },
16 math::Vec3,
17 mesh::Mesh2d,
18 sprite_render::MeshMaterial2d,
19 state::state::{NextState, OnEnter, OnExit},
20 transform::components::Transform,
21 ui::widget::Text,
22};
23use bezier::Bezier;
24use shapes::{Derivable, lerp::Lerp};
25
26use crate::{
27 AssetType, AssetsManager,
28 editor::{EditorState, StateText},
29 game::cursor::CursorPos,
30 sim::{
31 signal::{self, SignalPos, Signals},
32 track::{
33 pos::{IgnoreTrackPosUpdate, TrackPos},
34 segment::{TrackSegment, signals_manager::SignalsManager},
35 },
36 },
37 ui::UiState,
38 utils::Dir,
39};
40
41#[derive(Resource)]
43pub struct TargetSegment(pub Entity);
44
45#[derive(Component)]
47pub struct PositionIndicator;
48
49pub fn update_pos_indicator(
51 mut pos_indicator: Single<(&mut TrackPos, &mut Transform), With<PositionIndicator>>,
52 q_segments: Query<(&TrackSegment, &Transform), Without<PositionIndicator>>,
53 target_segment: Res<TargetSegment>,
54 cursor_pos: Res<CursorPos>,
55) {
56 let (ts, ts_transform) = q_segments.get(target_segment.0).unwrap();
57 let segment_pos_vec2 = ts_transform.translation.truncate();
58 let proj = ts.shape.project(cursor_pos.0 - segment_pos_vec2);
59 pos_indicator.0.idx = proj;
60
61 let signal_trans = segment_pos_vec2 + ts.shape.lerp(proj);
62 let normal = ts.shape.derivative().lerp(proj).normalize().perp()
63 * signal::TRACK_OFFSET
64 * pos_indicator.0.dir.unwrap().sig_f32()
65 * -1.0;
66 pos_indicator.1.translation = (signal_trans + normal).extend(0.0);
67}
68
69pub fn place_signal(
71 mut cmd: Commands,
72 pos_indicator: Single<&TrackPos, With<PositionIndicator>>,
73 mut q_segments: Query<&mut SignalsManager>,
74 target_segment: Res<TargetSegment>,
75 assets_manager: Res<AssetsManager>,
76 mut next_editor_state: ResMut<NextState<EditorState>>,
77) {
78 let mut ts_signals = q_segments.get_mut(target_segment.0).unwrap();
79
80 let signal_idx = SignalPos(pos_indicator.idx);
81 if ts_signals.inner.contains_key(&signal_idx) {
82 return; }
84
85 let spawned_signal = cmd
86 .spawn((Signals::Stop, **pos_indicator, Transform::default()))
87 .with_child((
88 Transform::from_translation(Vec3::new(0.0, signal::TRACK_OFFSET, 0.0)),
89 Mesh2d(
90 assets_manager
91 .meshes
92 .get(&AssetType::SignalNormal)
93 .unwrap()
94 .clone(),
95 ),
96 MeshMaterial2d(
97 assets_manager
98 .materials
99 .get(&AssetType::SignalNormal)
100 .unwrap()
101 .clone(),
102 ),
103 ))
104 .id();
105 ts_signals.inner.insert(signal_idx, spawned_signal);
106
107 next_editor_state.set(EditorState::Viewing);
108}
109
110pub fn input_handler(
112 input: Res<ButtonInput<KeyCode>>,
113 mut pos_indicator: Single<&mut TrackPos, With<PositionIndicator>>,
114 mut next_editor_state: ResMut<NextState<EditorState>>,
115) {
116 if input.just_pressed(KeyCode::KeyR) {
117 pos_indicator.dir = Some(pos_indicator.dir.as_ref().unwrap().reverse());
118 }
119
120 if input.just_pressed(KeyCode::Escape) {
121 next_editor_state.set(EditorState::Viewing);
122 }
123}
124
125pub fn on_enter_signal(
127 mut cmd: Commands,
128 mut text: Single<&mut Text, With<StateText>>,
129 target_segment: Res<TargetSegment>,
130 assets_manager: Res<AssetsManager>,
131) {
132 cmd.spawn((
133 PositionIndicator,
134 TrackPos {
135 segment: target_segment.0,
136 idx: 0.0,
137 dir: Some(Dir::Positive),
138 },
139 IgnoreTrackPosUpdate,
140 Mesh2d(
141 assets_manager
142 .meshes
143 .get(&AssetType::Indicator)
144 .cloned()
145 .unwrap(),
146 ),
147 MeshMaterial2d(
148 assets_manager
149 .materials
150 .get(&AssetType::Indicator)
151 .cloned()
152 .unwrap(),
153 ),
154 ));
155
156 text.0 = String::from("Placing signal\n[R] Change track orientation\n[Esc] Exit");
157}
158
159pub fn on_exit_signal(
161 mut cmd: Commands,
162 pos_indicator: Single<Entity, With<PositionIndicator>>,
163 target_segment: Res<TargetSegment>,
164 mut next_state: ResMut<NextState<UiState>>,
165) {
166 cmd.entity(*pos_indicator).despawn();
167 cmd.remove_resource::<TargetSegment>();
168
169 next_state.set(UiState::SegmentWindow {
170 target: target_segment.0,
171 });
172}
173
174pub struct SignalEditorPlugin;
176
177impl Plugin for SignalEditorPlugin {
178 fn build(&self, app: &mut bevy::app::App) {
179 app.add_systems(OnEnter(EditorState::Signal), on_enter_signal)
180 .add_systems(OnExit(EditorState::Signal), on_exit_signal)
181 .add_systems(Update, input_handler)
182 .add_systems(Update, update_pos_indicator)
183 .add_systems(
184 Update,
185 place_signal.run_if(input_just_pressed(MouseButton::Left)),
186 );
187 }
188}