bevy_yoleck/
editing.rs

1use std::ops::{Deref, DerefMut};
2
3use bevy::ecs::query::{QueryData, QueryFilter, QueryIter, QuerySingleError};
4use bevy::ecs::system::SystemParam;
5use bevy::prelude::*;
6use bevy_egui::egui;
7
8/// Marks which entities are currently being edited in the level editor.
9#[derive(Component)]
10pub struct YoleckEditMarker;
11
12/// Wrapper for writing queries in edit systems.
13///
14/// To future-proof for the multi-entity editing feature, use this instead of
15/// regular queries with `With<YoleckEditMarker>`.
16///
17/// The methods of `YoleckEdit` that have the same name as methods of a regular Bevy `Query`
18/// delegate to them, but if there are edited entities that do not fit the query they will act as
19/// if they found no match.
20#[derive(SystemParam)]
21pub struct YoleckEdit<'w, 's, Q: 'static + QueryData, F: 'static + QueryFilter = ()> {
22    query: Query<'w, 's, Q, (With<YoleckEditMarker>, F)>,
23    verification_query: Query<'w, 's, (), With<YoleckEditMarker>>,
24}
25
26impl<Q: 'static + QueryData, F: 'static + QueryFilter> YoleckEdit<'_, '_, Q, F> {
27    pub fn single(
28        &self,
29    ) -> Result<<<Q as QueryData>::ReadOnly as QueryData>::Item<'_>, QuerySingleError> {
30        let single = self.query.single()?;
31        // This will return an error if multiple entities are selected (but only one fits F and Q)
32        self.verification_query.single()?;
33        Ok(single)
34    }
35
36    pub fn single_mut(&mut self) -> Result<<Q as QueryData>::Item<'_>, QuerySingleError> {
37        let single = self.query.single_mut()?;
38        // This will return an error if multiple entities are selected (but only one fits F and Q)
39        self.verification_query.single()?;
40        Ok(single)
41    }
42
43    pub fn is_empty(&self) -> bool {
44        self.query.is_empty()
45    }
46
47    /// Check if some non-matching entities are selected for editing.
48    ///
49    /// Use this, together with [`is_empty`](Self::is_empty) for systems that can edit multiple
50    /// entities but want to not show their UI when some irrelevant entities are selected as well.
51    pub fn has_nonmatching(&self) -> bool {
52        // Note - cannot use len for query.iter() because then `F` would be limited to archetype
53        // filters only.
54        self.query.iter().count() != self.verification_query.iter().len()
55    }
56
57    /// Iterate over all the matching entities, _even_ if some selected entities do not match.
58    ///
59    /// If both matching and non-matching entities are selected, this will iterate over the
60    /// matching entities only. If it is not desired to iterate at all in such cases,
61    /// check [`has_nonmatching`](Self::has_nonmatching) must be checked manually.
62    pub fn iter_matching(
63        &mut self,
64    ) -> QueryIter<<Q as QueryData>::ReadOnly, (bevy::prelude::With<YoleckEditMarker>, F)> {
65        self.query.iter()
66    }
67
68    /// Iterate mutably over all the matching entities, _even_ if some selected entities do not match.
69    ///
70    /// If both matching and non-matching entities are selected, this will iterate over the
71    /// matching entities only. If it is not desired to iterate at all in such cases,
72    /// check [`has_nonmatching`](Self::has_nonmatching) must be checked manually.
73    pub fn iter_matching_mut(&mut self) -> QueryIter<Q, (With<YoleckEditMarker>, F)> {
74        self.query.iter_mut()
75    }
76}
77
78/// An handle for the egui UI frame used in editing systems.
79#[derive(Resource)]
80pub struct YoleckUi(pub egui::Ui);
81
82impl Deref for YoleckUi {
83    type Target = egui::Ui;
84
85    fn deref(&self) -> &Self::Target {
86        &self.0
87    }
88}
89
90impl DerefMut for YoleckUi {
91    fn deref_mut(&mut self) -> &mut Self::Target {
92        &mut self.0
93    }
94}