1use std::{any::type_name, ops::Range};
2
3use bevy_math::InvalidDirectionError;
4use leafwing_input_manager::action_state::ActionData;
5
6use crate::prelude::*;
7
8pub fn value<A: Actionlike>(
11 action: A,
12 bounds: Range<f32>,
13) -> impl EntityTrigger<Out = Result<f32, f32>> {
14 (move |In(entity): In<Entity>, actors: Query<&ActionState<A>>| {
15 let value = actors
16 .get(entity)
17 .unwrap_or_else(|_| {
18 panic!(
19 "entity {entity:?} with `ValueTrigger<{0}>` is missing `ActionState<{0}>`",
20 type_name::<A>()
21 )
22 })
23 .value(&action);
24
25 if bounds.contains(&value) {
26 Ok(value)
27 } else {
28 Err(value)
29 }
30 })
31 .into_trigger()
32}
33
34pub fn value_unbounded(action: impl Actionlike) -> impl EntityTrigger<Out = Result<f32, f32>> {
36 value(action, f32::NEG_INFINITY..f32::INFINITY)
37}
38
39pub fn value_min(action: impl Actionlike, min: f32) -> impl EntityTrigger<Out = Result<f32, f32>> {
41 value(action, min..f32::INFINITY)
42}
43
44pub fn value_max(action: impl Actionlike, max: f32) -> impl EntityTrigger<Out = Result<f32, f32>> {
46 value(action, f32::NEG_INFINITY..max)
47}
48
49pub fn clamped_value<A: Actionlike>(
51 action: A,
52 bounds: Range<f32>,
53) -> impl EntityTrigger<Out = Result<f32, f32>> {
54 (move |In(entity): In<Entity>, actors: Query<&ActionState<A>>| {
55 let value = actors
56 .get(entity)
57 .unwrap_or_else(|_| {
58 panic!(
59 "entity {entity:?} with `ClampedValueTrigger<{0}>` is missing `ActionState<{0}>`",
60 type_name::<A>()
61 )
62 })
63 .clamped_value(&action);
64
65 if bounds.contains(&value) {
66 Ok(value)
67 } else {
68 Err(value)
69 }
70 })
71 .into_trigger()
72}
73
74pub fn clamped_value_unbounded(
76 action: impl Actionlike,
77) -> impl EntityTrigger<Out = Result<f32, f32>> {
78 clamped_value(action, f32::NEG_INFINITY..f32::INFINITY)
79}
80
81pub fn clamped_value_min(
83 action: impl Actionlike,
84 min: f32,
85) -> impl EntityTrigger<Out = Result<f32, f32>> {
86 clamped_value(action, min..f32::INFINITY)
87}
88
89pub fn clamped_value_max(
91 action: impl Actionlike,
92 max: f32,
93) -> impl EntityTrigger<Out = Result<f32, f32>> {
94 clamped_value(action, f32::NEG_INFINITY..max)
95}
96
97pub fn axis_pair<A: Actionlike>(
105 action: A,
106 length_bounds: Range<f32>,
107 rotation_bounds: Range<Dir2>,
108) -> impl EntityTrigger<Out = Result<Vec2, Vec2>> {
109 (move |In(entity): In<Entity>, actors: Query<&ActionState<A>>| {
110 let axis_pair = actors
111 .get(entity)
112 .unwrap_or_else(|_| {
113 panic!(
114 "entity {entity:?} with `AxisPairTrigger<{0}>` is missing `ActionState<{0}>`",
115 type_name::<A>()
116 )
117 })
118 .axis_pair(&action);
119
120 match Dir2::new_and_length(axis_pair) {
121 Ok((rotation, length)) => {
122 length_bounds.contains(&length)
123 && (rotation_bounds.start == rotation_bounds.end
124 || ({
125 let start = rotation_bounds.start.to_angle();
126 let end = rotation_bounds.end.to_angle();
127 let rotation = rotation.to_angle();
128
129 if start < end {
130 rotation >= start && rotation <= end
131 } else {
132 rotation >= start || rotation <= end
133 }
134 }))
135 }
136 Err(InvalidDirectionError::Zero) => length_bounds.contains(&0.),
137 Err(InvalidDirectionError::Infinite | InvalidDirectionError::NaN) => false,
138 }
139 .then_some(axis_pair)
140 .ok_or(axis_pair)
141 })
142 .into_trigger()
143}
144
145pub fn axis_pair_unbounded(
147 action: impl Actionlike,
148) -> impl EntityTrigger<Out = Result<Vec2, Vec2>> {
149 axis_pair(action, 0.0..f32::INFINITY, Dir2::Y..Dir2::Y)
150}
151
152pub fn axis_pair_min_length(
154 action: impl Actionlike,
155 min_length: f32,
156) -> impl EntityTrigger<Out = Result<Vec2, Vec2>> {
157 axis_pair(action, min_length..f32::INFINITY, Dir2::Y..Dir2::Y)
158}
159
160pub fn axis_pair_max_length(
162 action: impl Actionlike,
163 max_length: f32,
164) -> impl EntityTrigger<Out = Result<Vec2, Vec2>> {
165 axis_pair(action, 0.0..max_length, Dir2::Y..Dir2::Y)
166}
167
168pub fn axis_pair_length_bounds(
170 action: impl Actionlike,
171 length_bounds: Range<f32>,
172) -> impl EntityTrigger<Out = Result<Vec2, Vec2>> {
173 axis_pair(action, length_bounds, Dir2::Y..Dir2::Y)
174}
175
176pub fn axis_pair_rotation_bounds(
178 action: impl Actionlike,
179 rotation_bounds: Range<Dir2>,
180) -> impl EntityTrigger<Out = Result<Vec2, Vec2>> {
181 axis_pair(action, 0.0..f32::INFINITY, rotation_bounds)
182}
183
184pub fn clamped_axis_pair<A: Actionlike>(
186 action: A,
187 length_bounds: Range<f32>,
188 rotation_bounds: Range<Dir2>,
189) -> impl EntityTrigger<Out = Result<Vec2, Vec2>> {
190 (move |In(entity): In<Entity>, actors: Query<&ActionState<A>>| {
191 let axis_pair = actors
192 .get(entity)
193 .unwrap_or_else(|_| {
194 panic!(
195 "entity {entity:?} with `AxisPairTrigger<{0}>` is missing `ActionState<{0}>`",
196 type_name::<A>()
197 )
198 })
199 .clamped_axis_pair(&action);
200
201 match Dir2::new_and_length(axis_pair) {
202 Ok((rotation, length)) => {
203 length_bounds.contains(&length)
204 && (rotation_bounds.start == rotation_bounds.end
205 || ({
206 let start = rotation_bounds.start.to_angle();
207 let end = rotation_bounds.end.to_angle();
208 let rotation = rotation.to_angle();
209
210 if start < end {
211 rotation >= start && rotation <= end
212 } else {
213 rotation >= start || rotation <= end
214 }
215 }))
216 }
217 Err(InvalidDirectionError::Zero) => length_bounds.contains(&0.),
218 Err(InvalidDirectionError::Infinite | InvalidDirectionError::NaN) => false,
219 }
220 .then_some(axis_pair)
221 .ok_or(axis_pair)
222 })
223 .into_trigger()
224}
225
226pub fn clamped_axis_pair_unbounded(
228 action: impl Actionlike,
229) -> impl EntityTrigger<Out = Result<Vec2, Vec2>> {
230 clamped_axis_pair(action, 0.0..f32::INFINITY, Dir2::Y..Dir2::Y)
231}
232
233pub fn clamped_axis_pair_min_length(
235 action: impl Actionlike,
236 min_length: f32,
237) -> impl EntityTrigger<Out = Result<Vec2, Vec2>> {
238 clamped_axis_pair(action, min_length..f32::INFINITY, Dir2::Y..Dir2::Y)
239}
240
241pub fn clamped_axis_pair_max_length(
243 action: impl Actionlike,
244 max_length: f32,
245) -> impl EntityTrigger<Out = Result<Vec2, Vec2>> {
246 clamped_axis_pair(action, 0.0..max_length, Dir2::Y..Dir2::Y)
247}
248
249pub fn clamped_axis_pair_length_bounds(
251 action: impl Actionlike,
252 length_bounds: Range<f32>,
253) -> impl EntityTrigger<Out = Result<Vec2, Vec2>> {
254 clamped_axis_pair(action, length_bounds, Dir2::Y..Dir2::Y)
255}
256
257pub fn clamped_axis_pair_rotation_bounds(
259 action: impl Actionlike,
260 rotation_bounds: Range<Dir2>,
261) -> impl EntityTrigger<Out = Result<Vec2, Vec2>> {
262 clamped_axis_pair(action, 0.0..f32::INFINITY, rotation_bounds)
263}
264
265pub fn just_pressed<A: Actionlike>(action: A) -> impl EntityTrigger<Out = bool> {
267 (move |In(entity): In<Entity>, actors: Query<&ActionState<A>>| {
268 actors
269 .get(entity)
270 .unwrap_or_else(|_| {
271 panic!(
272 "entity {entity:?} with `JustPressedTrigger<{0}>` is missing `ActionState<{0}>`",
273 type_name::<A>()
274 )
275 })
276 .just_pressed(&action)
277 })
278 .into_trigger()
279}
280
281pub fn pressed<A: Actionlike>(action: A) -> impl EntityTrigger<Out = bool> {
283 (move |In(entity): In<Entity>, actors: Query<&ActionState<A>>| {
284 actors
285 .get(entity)
286 .unwrap_or_else(|_| {
287 panic!(
288 "entity {entity:?} with `JustPressedTrigger<{0}>` is missing `ActionState<{0}>`",
289 type_name::<A>()
290 )
291 })
292 .pressed(&action)
293 })
294 .into_trigger()
295}
296
297pub fn just_released<A: Actionlike>(action: A) -> impl EntityTrigger<Out = bool> {
299 (move |In(entity): In<Entity>, actors: Query<&ActionState<A>>| {
300 actors
301 .get(entity)
302 .unwrap_or_else(|_| {
303 panic!(
304 "entity {entity:?} with `JustPressedTrigger<{0}>` is missing `ActionState<{0}>`",
305 type_name::<A>()
306 )
307 })
308 .just_released(&action)
309 })
310 .into_trigger()
311}
312
313pub fn action_data<A: Actionlike>(action: A) -> impl EntityTrigger<Out = Option<ActionData>> {
315 (move |In(entity): In<Entity>, actors: Query<&ActionState<A>>| {
316 actors
317 .get(entity)
318 .unwrap_or_else(|_| {
319 panic!(
320 "entity {entity:?} with `ActionDataTrigger<{0}>` is missing `ActionState<{0}>`",
321 type_name::<A>()
322 )
323 })
324 .action_data(&action)
325 .cloned()
326 })
327 .into_trigger()
328}