1use bevy::{
11 app::{Plugin, Startup, Update},
12 ecs::{
13 component::Component,
14 entity::Entity,
15 hierarchy::Children,
16 observer::On,
17 query::With,
18 system::{Commands, Query, Res, ResMut},
19 },
20 feathers::{controls::FeathersButton, theme::ThemedText},
21 picking::events::{PointerEnter, PointerLeave},
22 scene::{CommandsSceneExt, Scene, bsn, on},
23 state::{
24 app::AppExtStates,
25 state::{NextState, OnEnter, State, SubStates},
26 },
27 ui::{Display, FlexDirection, Node, PositionType, prelude::AccessibleLabel, px, widget::Text},
28 ui_widgets::Activate,
29};
30
31use crate::{
32 AppState,
33 sim::{logical_graph::LogicalBlock, train::Train},
34 ui::{
35 logical_blocks::logical_blocks_window,
36 target::Target,
37 track::{
38 node::{IsNodeWindow, open_node_window},
39 segment::{self, IsSegmentWindow, open_segment_window},
40 },
41 train::train_window::{IsTrainsWindow, open_trains_window, update_vel_indicator},
42 },
43};
44
45pub mod logical_blocks;
46pub mod target;
47pub mod track;
48pub mod train;
49
50#[derive(Clone, Debug, Default, Hash, PartialEq, Eq, SubStates)]
52#[source(AppState = AppState::Main)]
53pub enum UiState {
54 #[default]
55 Default,
56 SegmentWindow {
57 target: Entity,
58 },
59 NodeWindow {
60 target: Entity,
61 },
62 TrainWindow {
63 target: Entity,
64 },
65 BlocksWindow,
66 TrainsOverlay,
67 AddingTrain,
68}
69
70#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, SubStates)]
72#[source(AppState = AppState::Main)]
73pub struct IsHoveringUi(pub bool);
74
75#[derive(Component, Clone, Default)]
77pub struct EditingWindow;
78
79pub fn on_activate_logical_blocks(
80 _: On<Activate>,
81 mut cmd: Commands,
82 logical_blocks: Query<(Entity, &LogicalBlock)>,
83 curr_state: Res<State<UiState>>,
84 mut next_state: ResMut<NextState<UiState>>,
85) {
86 if *curr_state == UiState::BlocksWindow {
87 return;
88 }
89
90 next_state.set(UiState::BlocksWindow);
91 cmd.spawn_scene(logical_blocks_window(logical_blocks.iter()));
92}
93
94pub fn on_activate_trains(
95 _: On<Activate>,
96 mut cmd: Commands,
97 q_trains: Query<Entity, With<Train>>,
98 curr_state: Res<State<UiState>>,
99 mut next_state: ResMut<NextState<UiState>>,
100) {
101 if *curr_state == UiState::TrainsOverlay {
102 return;
103 };
104
105 next_state.set(UiState::TrainsOverlay);
106 cmd.spawn_scene(train::trains_overlay(q_trains.iter()));
107}
108
109pub fn toolbar() -> impl Scene {
110 bsn! {
111 Node {
112 position_type: PositionType::Absolute,
113 left: px(8),
114 bottom: px(8),
115 display: Display::Flex,
116 flex_direction: FlexDirection::Row,
117 column_gap: px(8),
118 padding: px(8)
119 }
120 Children [
121 @FeathersButton {
122 @caption: bsn! { Text("Logical blocks") ThemedText }
123 }
124 AccessibleLabel("Logical blocks")
125 on(on_activate_logical_blocks)
126 --
127 @FeathersButton {
128 @caption: bsn! { Text("Trains") ThemedText }
129 }
130 AccessibleLabel("Trains")
131 on(on_activate_trains)
132 ]
133 on(|_: On<PointerEnter>, mut next_state: ResMut<NextState<IsHoveringUi>>| {
134 next_state.set(IsHoveringUi(true));
135 })
136 on(|_: On<PointerLeave>, mut next_state: ResMut<NextState<IsHoveringUi>>| {
137 next_state.set(IsHoveringUi(false));
138 })
139 }
140}
141
142fn setup_ui(mut cmd: Commands) {
143 cmd.spawn_scene(toolbar());
144}
145
146fn on_enter_default(mut is_hovering_ui: ResMut<NextState<IsHoveringUi>>) {
147 is_hovering_ui.set(IsHoveringUi(false));
148}
149
150pub struct UiPlugin;
151
152impl Plugin for UiPlugin {
153 fn build(&self, app: &mut bevy::app::App) {
154 app.init_state::<UiState>()
155 .add_sub_state::<IsHoveringUi>()
156 .add_computed_state::<IsSegmentWindow>()
157 .add_computed_state::<IsNodeWindow>()
158 .add_computed_state::<IsTrainsWindow>()
159 .add_systems(OnEnter(UiState::Default), on_enter_default)
160 .add_systems(OnEnter(IsSegmentWindow), open_segment_window)
161 .add_systems(OnEnter(IsNodeWindow), open_node_window)
162 .add_systems(OnEnter(IsTrainsWindow), open_trains_window)
163 .add_systems(Startup, setup_ui)
164 .add_systems(Update, update_vel_indicator)
165 .add_systems(
166 Update,
167 segment::signals_list::item::update_signal_list_items,
168 );
169 }
170}