1use ak_core::{Structure, Trajectory};
2use bevy::{input_focus::InputDispatchPlugin, prelude::*, ui_widgets::UiWidgetsPlugins};
3use bevy_panorbit_camera::{PanOrbitCameraPlugin, PanOrbitCameraSystemSet};
4use std::sync::{Arc, Mutex, mpsc};
5use std::thread;
6
7use crate::ui::{
8 handle_playback_buttons, set_camera_input_enabled, setup_ui, sync_inspector_camera,
9 sync_inspector_state, sync_inspector_text, sync_playback_camera, sync_playback_slider_value,
10 sync_playback_state, sync_playback_text, sync_playback_visibility, toggle_hints_visibility,
11};
12use crate::viewer::ViewerConfig;
13use crate::viewer::controls::{
14 keyboard_controls, navigate_frames, screenshot_on_spacebar, screenshot_saving,
15 toggle_ui_visibility, toggle_view,
16};
17use crate::viewer::orientation_widget::{
18 setup_orientation_widget, sync_orientation_widget, sync_orientation_widget_letter_strokes,
19 update_orientation_widget_viewport,
20};
21use crate::viewer::runtime::{asset_root, configure_shared_app};
22use crate::viewer::session::{ViewerCommand, ViewerReadiness, ViewerSessionHandle};
23use crate::viewer::systems::{
24 MarqueeSelectionState, handle_atom_clicks, handle_marquee_selection, setup_marquee_overlay,
25 sync_marquee_overlay,
26};
27
28#[derive(Resource, Clone)]
29struct ViewerLifecycle {
30 readiness: Arc<ViewerReadiness>,
31 ready_signaled: bool,
32}
33
34impl Drop for ViewerLifecycle {
35 fn drop(&mut self) {
36 self.readiness.mark_closed();
37 }
38}
39
40fn build_app(
41 trajectory: Trajectory,
42 config: ViewerConfig,
43 receiver: Option<mpsc::Receiver<ViewerCommand>>,
44 readiness: Arc<ViewerReadiness>,
45 snapshot: Arc<Mutex<crate::viewer::session::ViewerSnapshot>>,
46) -> App {
47 let mut app = App::new();
48 let mut plugins = DefaultPlugins.build().disable::<bevy::audio::AudioPlugin>();
49 plugins = plugins.set(AssetPlugin {
50 file_path: asset_root(),
51 ..default()
52 });
53
54 if config.window_width.is_some() || config.window_height.is_some() {
55 let width = config.window_width.unwrap_or(750);
56 let height = config.window_height.unwrap_or(750);
57 plugins = plugins.set(WindowPlugin {
58 primary_window: Some(Window {
59 resolution: (width, height).into(),
60 ..default()
61 }),
62 ..default()
63 });
64 }
65
66 app.add_plugins((
67 plugins,
68 MeshPickingPlugin,
69 PanOrbitCameraPlugin,
70 UiWidgetsPlugins,
71 InputDispatchPlugin,
72 ));
73 configure_shared_app(&mut app, trajectory, config, receiver, snapshot);
74 app.insert_resource(MarqueeSelectionState::default());
75 app.insert_resource(ViewerLifecycle {
76 readiness,
77 ready_signaled: false,
78 })
79 .add_systems(Startup, (setup_orientation_widget, setup_marquee_overlay))
80 .add_systems(
81 Update,
82 (
83 (
84 handle_marquee_selection,
85 handle_atom_clicks,
86 sync_marquee_overlay,
87 )
88 .chain(),
89 toggle_view,
90 keyboard_controls,
91 screenshot_on_spacebar,
92 screenshot_saving,
93 navigate_frames,
94 sync_orientation_widget,
95 sync_orientation_widget_letter_strokes,
96 update_orientation_widget_viewport,
97 signal_viewer_ready,
98 ),
99 );
100
101 if app.world().resource::<ViewerConfig>().render.show_ui {
102 app.add_systems(Startup, setup_ui);
103 app.add_systems(
104 Update,
105 (
106 toggle_ui_visibility,
107 toggle_hints_visibility,
108 handle_playback_buttons,
109 sync_playback_slider_value,
110 ),
111 );
112 app.add_systems(
113 PostUpdate,
114 set_camera_input_enabled.before(PanOrbitCameraSystemSet),
115 );
116 app.add_systems(
117 PostUpdate,
118 (
119 sync_inspector_camera,
120 sync_inspector_state,
121 sync_inspector_text,
122 sync_playback_camera,
123 sync_playback_state,
124 sync_playback_visibility,
125 sync_playback_text,
126 )
127 .chain(),
128 );
129 }
130
131 app
132}
133
134fn run_app(
135 trajectory: Trajectory,
136 config: ViewerConfig,
137 receiver: Option<mpsc::Receiver<ViewerCommand>>,
138 readiness: Arc<ViewerReadiness>,
139 snapshot: Arc<Mutex<crate::viewer::session::ViewerSnapshot>>,
140) {
141 let mut app = build_app(trajectory, config, receiver, readiness, snapshot);
142 app.run();
143}
144
145fn signal_viewer_ready(
146 mut lifecycle: ResMut<'_, ViewerLifecycle>,
147 windows: Query<'_, '_, Entity, With<Window>>,
148) {
149 if lifecycle.ready_signaled || windows.is_empty() {
150 return;
151 }
152
153 lifecycle.ready_signaled = true;
154 lifecycle.readiness.mark_ready();
155}
156
157pub fn run_prepared(
158 trajectory: Trajectory,
159 config: ViewerConfig,
160 receiver: mpsc::Receiver<ViewerCommand>,
161 readiness: Arc<ViewerReadiness>,
162 snapshot: Arc<Mutex<crate::viewer::session::ViewerSnapshot>>,
163) {
164 run_app(trajectory, config, Some(receiver), readiness, snapshot);
165}
166
167pub fn launch(trajectory: Trajectory, config: ViewerConfig) -> ViewerSessionHandle {
168 let (sender, receiver) = mpsc::channel();
169 let handle = ViewerSessionHandle::new(sender);
170 let launch_readiness = handle.readiness().clone();
171 let launch_snapshot = handle.snapshot().clone();
172
173 thread::Builder::new()
174 .name("ak-viewer-session".to_string())
175 .spawn(move || {
176 run_app(
177 trajectory,
178 config,
179 Some(receiver),
180 launch_readiness,
181 launch_snapshot,
182 );
183 })
184 .expect("failed to launch viewer session thread");
185
186 handle
187}
188
189pub fn run_with_session<F>(trajectory: Trajectory, config: ViewerConfig, driver: F)
190where
191 F: FnOnce(ViewerSessionHandle) + Send + 'static,
192{
193 let (sender, receiver) = mpsc::channel();
194 let handle = ViewerSessionHandle::new(sender);
195 let readiness = handle.readiness().clone();
196 let snapshot = handle.snapshot().clone();
197
198 thread::Builder::new()
199 .name("ak-viewer-session-driver".to_string())
200 .spawn(move || {
201 driver(handle);
202 })
203 .expect("failed to launch viewer session driver thread");
204
205 run_app(trajectory, config, Some(receiver), readiness, snapshot);
206}
207
208pub fn run(trajectory: Trajectory, config: ViewerConfig) {
209 run_app(
210 trajectory,
211 config,
212 None,
213 Arc::new(ViewerReadiness::new()),
214 Arc::new(Mutex::new(crate::viewer::session::ViewerSnapshot::default())),
215 );
216}
217
218pub fn run_default(trajectory: Trajectory) {
219 let config = ViewerConfig::default();
220 run(trajectory, config)
221}
222
223pub fn run_structure(structure: Structure, config: ViewerConfig) {
224 let trajectory = Trajectory::new(vec![structure]);
225 run(trajectory, config)
226}
227
228pub fn run_structure_default(structure: Structure) {
229 let trajectory = Trajectory::new(vec![structure]);
230 run_default(trajectory);
231}