1use std::collections::HashMap;
4use std::sync::{OnceLock, RwLock};
5
6use glam::Vec3;
7
8use crate::error::{PolyscopeError, Result};
9use crate::gizmo::GizmoConfig;
10use crate::group::Group;
11use crate::options::Options;
12use crate::quantity::Quantity;
13use crate::registry::Registry;
14use crate::slice_plane::SlicePlane;
15
16pub type FileDropCallback = Box<dyn FnMut(&[std::path::PathBuf]) + Send + Sync>;
18
19#[derive(Debug, Clone)]
21pub enum MaterialLoadRequest {
22 Static { name: String, path: String },
24 Blendable {
26 name: String,
27 filenames: [String; 4],
28 },
29}
30
31static CONTEXT: OnceLock<RwLock<Context>> = OnceLock::new();
33
34pub struct Context {
36 pub initialized: bool,
38
39 pub registry: Registry,
41
42 pub groups: HashMap<String, Group>,
44
45 pub slice_planes: HashMap<String, SlicePlane>,
47
48 pub gizmo_config: GizmoConfig,
50
51 pub selected_structure: Option<(String, String)>,
53
54 pub selected_slice_plane: Option<String>,
56
57 pub options: Options,
59
60 pub length_scale: f32,
62
63 pub bounding_box: (Vec3, Vec3),
65
66 pub floating_quantities: Vec<Box<dyn Quantity>>,
68
69 pub file_drop_callback: Option<FileDropCallback>,
71
72 pub material_load_queue: Vec<MaterialLoadRequest>,
74}
75
76impl Default for Context {
77 fn default() -> Self {
78 Self {
79 initialized: false,
80 registry: Registry::new(),
81 groups: HashMap::new(),
82 slice_planes: HashMap::new(),
83 gizmo_config: GizmoConfig::default(),
84 selected_structure: None,
85 selected_slice_plane: None,
86 options: Options::default(),
87 length_scale: 1.0,
88 bounding_box: (Vec3::ZERO, Vec3::ONE),
89 floating_quantities: Vec::new(),
90 file_drop_callback: None,
91 material_load_queue: Vec::new(),
92 }
93 }
94}
95
96impl Context {
97 #[must_use]
99 pub fn center(&self) -> Vec3 {
100 (self.bounding_box.0 + self.bounding_box.1) * 0.5
101 }
102
103 pub fn update_extents(&mut self) {
109 if !self.options.auto_compute_scene_extents {
110 return;
111 }
112 self.recompute_extents();
113 }
114
115 pub fn recompute_extents(&mut self) {
120 let mut min = Vec3::splat(f32::MAX);
121 let mut max = Vec3::splat(f32::MIN);
122 let mut has_extent = false;
123
124 for structure in self.registry.iter() {
125 if let Some((bb_min, bb_max)) = structure.bounding_box() {
126 min = min.min(bb_min);
127 max = max.max(bb_max);
128 has_extent = true;
129 }
130 }
131
132 if has_extent {
133 self.bounding_box = (min, max);
134 self.length_scale = (max - min).length();
135 } else {
136 self.bounding_box = (Vec3::ZERO, Vec3::ONE);
137 self.length_scale = 1.0;
138 }
139 }
140
141 pub fn create_group(&mut self, name: &str) -> &mut Group {
143 self.groups
144 .entry(name.to_string())
145 .or_insert_with(|| Group::new(name))
146 }
147
148 #[must_use]
150 pub fn get_group(&self, name: &str) -> Option<&Group> {
151 self.groups.get(name)
152 }
153
154 pub fn get_group_mut(&mut self, name: &str) -> Option<&mut Group> {
156 self.groups.get_mut(name)
157 }
158
159 pub fn remove_group(&mut self, name: &str) -> Option<Group> {
161 self.groups.remove(name)
162 }
163
164 #[must_use]
166 pub fn has_group(&self, name: &str) -> bool {
167 self.groups.contains_key(name)
168 }
169
170 #[must_use]
172 pub fn group_names(&self) -> Vec<&str> {
173 self.groups
174 .keys()
175 .map(std::string::String::as_str)
176 .collect()
177 }
178
179 #[must_use]
186 pub fn is_structure_visible(&self, structure: &dyn crate::Structure) -> bool {
187 structure.is_enabled()
188 && self.is_structure_visible_in_groups(structure.type_name(), structure.name())
189 }
190
191 #[must_use]
197 pub fn is_structure_visible_in_groups(&self, type_name: &str, name: &str) -> bool {
198 for group in self.groups.values() {
200 if group.contains_structure(type_name, name) {
201 if !self.is_group_and_ancestors_enabled(group.name()) {
203 return false;
204 }
205 }
206 }
207 true
208 }
209
210 fn is_group_and_ancestors_enabled(&self, group_name: &str) -> bool {
212 let mut current = group_name;
213 while let Some(group) = self.groups.get(current) {
214 if !group.is_enabled() {
215 return false;
216 }
217 if let Some(parent) = group.parent_group() {
218 current = parent;
219 } else {
220 break;
221 }
222 }
223 true
224 }
225
226 pub fn add_slice_plane(&mut self, name: &str) -> &mut SlicePlane {
232 self.slice_planes
233 .entry(name.to_string())
234 .or_insert_with(|| SlicePlane::new(name))
235 }
236
237 #[must_use]
239 pub fn get_slice_plane(&self, name: &str) -> Option<&SlicePlane> {
240 self.slice_planes.get(name)
241 }
242
243 pub fn get_slice_plane_mut(&mut self, name: &str) -> Option<&mut SlicePlane> {
245 self.slice_planes.get_mut(name)
246 }
247
248 pub fn remove_slice_plane(&mut self, name: &str) -> Option<SlicePlane> {
250 self.slice_planes.remove(name)
251 }
252
253 #[must_use]
255 pub fn has_slice_plane(&self, name: &str) -> bool {
256 self.slice_planes.contains_key(name)
257 }
258
259 #[must_use]
261 pub fn slice_plane_names(&self) -> Vec<&str> {
262 self.slice_planes
263 .keys()
264 .map(std::string::String::as_str)
265 .collect()
266 }
267
268 #[must_use]
270 pub fn num_slice_planes(&self) -> usize {
271 self.slice_planes.len()
272 }
273
274 pub fn slice_planes(&self) -> impl Iterator<Item = &SlicePlane> {
276 self.slice_planes.values()
277 }
278
279 pub fn enabled_slice_planes(&self) -> impl Iterator<Item = &SlicePlane> {
281 self.slice_planes.values().filter(|sp| sp.is_enabled())
282 }
283
284 pub fn select_structure(&mut self, type_name: &str, name: &str) {
291 self.selected_slice_plane = None; self.selected_structure = Some((type_name.to_string(), name.to_string()));
293 }
294
295 pub fn deselect_structure(&mut self) {
297 self.selected_structure = None;
298 }
299
300 #[must_use]
302 pub fn selected_structure(&self) -> Option<(&str, &str)> {
303 self.selected_structure
304 .as_ref()
305 .map(|(t, n)| (t.as_str(), n.as_str()))
306 }
307
308 #[must_use]
310 pub fn has_selection(&self) -> bool {
311 self.selected_structure.is_some()
312 }
313
314 pub fn select_slice_plane(&mut self, name: &str) {
317 self.selected_structure = None; self.selected_slice_plane = Some(name.to_string());
319 }
320
321 pub fn deselect_slice_plane(&mut self) {
323 self.selected_slice_plane = None;
324 }
325
326 #[must_use]
328 pub fn selected_slice_plane(&self) -> Option<&str> {
329 self.selected_slice_plane.as_deref()
330 }
331
332 #[must_use]
334 pub fn has_slice_plane_selection(&self) -> bool {
335 self.selected_slice_plane.is_some()
336 }
337
338 #[must_use]
340 pub fn gizmo(&self) -> &GizmoConfig {
341 &self.gizmo_config
342 }
343
344 pub fn gizmo_mut(&mut self) -> &mut GizmoConfig {
346 &mut self.gizmo_config
347 }
348}
349
350pub fn init_context() -> Result<()> {
354 let context = RwLock::new(Context::default());
355
356 CONTEXT
357 .set(context)
358 .map_err(|_| PolyscopeError::AlreadyInitialized)?;
359
360 with_context_mut(|ctx| {
361 ctx.initialized = true;
362 });
363
364 Ok(())
365}
366
367pub fn is_initialized() -> bool {
369 CONTEXT
370 .get()
371 .and_then(|lock| lock.read().ok())
372 .is_some_and(|ctx| ctx.initialized)
373}
374
375pub fn with_context<F, R>(f: F) -> R
381where
382 F: FnOnce(&Context) -> R,
383{
384 let lock = CONTEXT.get().expect("polyscope not initialized");
385 let guard = lock.read().expect("context lock poisoned");
386 f(&guard)
387}
388
389pub fn with_context_mut<F, R>(f: F) -> R
395where
396 F: FnOnce(&mut Context) -> R,
397{
398 let lock = CONTEXT.get().expect("polyscope not initialized");
399 let mut guard = lock.write().expect("context lock poisoned");
400 f(&mut guard)
401}
402
403pub fn try_with_context<F, R>(f: F) -> Option<R>
407where
408 F: FnOnce(&Context) -> R,
409{
410 let lock = CONTEXT.get()?;
411 let guard = lock.read().ok()?;
412 Some(f(&guard))
413}
414
415pub fn try_with_context_mut<F, R>(f: F) -> Option<R>
419where
420 F: FnOnce(&mut Context) -> R,
421{
422 let lock = CONTEXT.get()?;
423 let mut guard = lock.write().ok()?;
424 Some(f(&mut guard))
425}
426
427pub fn shutdown_context() {
432 if let Some(lock) = CONTEXT.get() {
433 if let Ok(mut ctx) = lock.write() {
434 ctx.initialized = false;
435 ctx.registry.clear();
436 ctx.groups.clear();
437 ctx.slice_planes.clear();
438 ctx.selected_structure = None;
439 ctx.selected_slice_plane = None;
440 ctx.floating_quantities.clear();
441 ctx.material_load_queue.clear();
442 }
443 }
444}