spitfire_input/
lib.rs

1use gilrs::{Event as GamepadEvent, EventType as GamepadEventType, Gilrs};
2#[cfg(not(target_arch = "wasm32"))]
3use glutin::event::{ElementState, MouseScrollDelta, WindowEvent};
4use std::{
5    borrow::Cow,
6    cmp::Ordering,
7    collections::HashMap,
8    sync::{Arc, RwLock, RwLockReadGuard, RwLockWriteGuard},
9};
10use typid::ID;
11#[cfg(target_arch = "wasm32")]
12use winit::event::{ElementState, MouseScrollDelta, WindowEvent};
13
14pub use gilrs::{Axis as GamepadAxis, Button as GamepadButton, GamepadId};
15#[cfg(not(target_arch = "wasm32"))]
16pub use glutin::event::{MouseButton, VirtualKeyCode};
17#[cfg(target_arch = "wasm32")]
18pub use winit::event::{MouseButton, VirtualKeyCode};
19
20#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
21pub enum InputConsume {
22    #[default]
23    None,
24    Hit,
25    All,
26}
27
28#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
29pub enum VirtualAction {
30    KeyButton(VirtualKeyCode),
31    MouseButton(MouseButton),
32    Axis(u32),
33    GamepadButton(GamepadButton),
34    GamepadAxis(GamepadAxis),
35}
36
37#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
38pub enum VirtualAxis {
39    KeyButton(VirtualKeyCode),
40    MousePositionX,
41    MousePositionY,
42    MouseWheelX,
43    MouseWheelY,
44    MouseButton(MouseButton),
45    Axis(u32),
46    GamepadButton(GamepadButton),
47    GamepadAxis(GamepadAxis),
48}
49
50#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
51pub enum InputAction {
52    #[default]
53    Idle,
54    Pressed,
55    Hold,
56    Released,
57}
58
59impl InputAction {
60    pub fn change(self, hold: bool) -> Self {
61        match (self, hold) {
62            (Self::Idle, true) | (Self::Released, true) => Self::Pressed,
63            (Self::Pressed, true) => Self::Hold,
64            (Self::Pressed, false) | (Self::Hold, false) => Self::Released,
65            (Self::Released, false) => Self::Idle,
66            _ => self,
67        }
68    }
69
70    pub fn update(self) -> Self {
71        match self {
72            Self::Pressed => Self::Hold,
73            Self::Released => Self::Idle,
74            _ => self,
75        }
76    }
77
78    pub fn is_idle(self) -> bool {
79        matches!(self, Self::Idle)
80    }
81
82    pub fn is_pressed(self) -> bool {
83        matches!(self, Self::Pressed)
84    }
85
86    pub fn is_hold(self) -> bool {
87        matches!(self, Self::Hold)
88    }
89
90    pub fn is_released(self) -> bool {
91        matches!(self, Self::Released)
92    }
93
94    pub fn is_up(self) -> bool {
95        matches!(self, Self::Idle | Self::Released)
96    }
97
98    pub fn is_down(self) -> bool {
99        matches!(self, Self::Pressed | Self::Hold)
100    }
101
102    pub fn is_changing(self) -> bool {
103        matches!(self, Self::Pressed | Self::Released)
104    }
105
106    pub fn is_continuing(self) -> bool {
107        matches!(self, Self::Idle | Self::Hold)
108    }
109
110    pub fn to_scalar(self, falsy: f32, truthy: f32) -> f32 {
111        if self.is_down() { truthy } else { falsy }
112    }
113}
114
115#[derive(Debug, Default, Clone, Copy, PartialEq)]
116pub struct InputAxis(pub f32);
117
118impl InputAxis {
119    pub fn threshold(self, value: f32) -> bool {
120        self.0 >= value
121    }
122}
123
124#[derive(Debug, Default, Clone)]
125pub struct InputRef<T: Default>(Arc<RwLock<T>>);
126
127impl<T: Default> InputRef<T> {
128    pub fn new(data: T) -> Self {
129        Self(Arc::new(RwLock::new(data)))
130    }
131
132    pub fn read(&'_ self) -> Option<RwLockReadGuard<'_, T>> {
133        self.0.read().ok()
134    }
135
136    pub fn write(&'_ self) -> Option<RwLockWriteGuard<'_, T>> {
137        self.0.write().ok()
138    }
139
140    pub fn get(&self) -> T
141    where
142        T: Clone,
143    {
144        self.read().map(|value| value.clone()).unwrap_or_default()
145    }
146
147    pub fn set(&self, value: T) {
148        if let Some(mut data) = self.write() {
149            *data = value;
150        }
151    }
152}
153
154pub type InputActionRef = InputRef<InputAction>;
155pub type InputAxisRef = InputRef<InputAxis>;
156pub type InputCharactersRef = InputRef<InputCharacters>;
157pub type InputMappingRef = InputRef<InputMapping>;
158
159#[derive(Debug, Default, Clone)]
160pub enum InputActionOrAxisRef {
161    #[default]
162    None,
163    Action(InputActionRef),
164    Axis(InputAxisRef),
165}
166
167impl InputActionOrAxisRef {
168    pub fn is_none(&self) -> bool {
169        matches!(self, Self::None)
170    }
171
172    pub fn is_some(&self) -> bool {
173        !self.is_none()
174    }
175
176    pub fn get_scalar(&self, falsy: f32, truthy: f32) -> f32 {
177        match self {
178            Self::None => falsy,
179            Self::Action(action) => action.get().to_scalar(falsy, truthy),
180            Self::Axis(axis) => axis.get().0,
181        }
182    }
183
184    pub fn threshold(&self, value: f32) -> bool {
185        match self {
186            Self::None => false,
187            Self::Action(action) => action.get().is_down(),
188            Self::Axis(axis) => axis.get().threshold(value),
189        }
190    }
191}
192
193impl From<InputActionRef> for InputActionOrAxisRef {
194    fn from(value: InputActionRef) -> Self {
195        Self::Action(value)
196    }
197}
198
199impl From<InputAxisRef> for InputActionOrAxisRef {
200    fn from(value: InputAxisRef) -> Self {
201        Self::Axis(value)
202    }
203}
204
205pub struct InputCombinator<T> {
206    mapper: Box<dyn Fn() -> T + Send + Sync>,
207}
208
209impl<T: Default> Default for InputCombinator<T> {
210    fn default() -> Self {
211        Self::new(|| T::default())
212    }
213}
214
215impl<T> InputCombinator<T> {
216    pub fn new(mapper: impl Fn() -> T + Send + Sync + 'static) -> Self {
217        Self {
218            mapper: Box::new(mapper),
219        }
220    }
221
222    pub fn get(&self) -> T {
223        (self.mapper)()
224    }
225}
226
227#[derive(Default)]
228pub struct CardinalInputCombinator(InputCombinator<[f32; 2]>);
229
230impl CardinalInputCombinator {
231    pub fn new(
232        left: impl Into<InputActionOrAxisRef>,
233        right: impl Into<InputActionOrAxisRef>,
234        up: impl Into<InputActionOrAxisRef>,
235        down: impl Into<InputActionOrAxisRef>,
236    ) -> Self {
237        let left = left.into();
238        let right = right.into();
239        let up = up.into();
240        let down = down.into();
241        Self(InputCombinator::new(move || {
242            let left = left.get_scalar(0.0, -1.0);
243            let right = right.get_scalar(0.0, 1.0);
244            let up = up.get_scalar(0.0, -1.0);
245            let down = down.get_scalar(0.0, 1.0);
246            [left + right, up + down]
247        }))
248    }
249
250    pub fn get(&self) -> [f32; 2] {
251        self.0.get()
252    }
253}
254
255#[derive(Default)]
256pub struct DualInputCombinator(InputCombinator<f32>);
257
258impl DualInputCombinator {
259    pub fn new(
260        negative: impl Into<InputActionOrAxisRef>,
261        positive: impl Into<InputActionOrAxisRef>,
262    ) -> Self {
263        let negative = negative.into();
264        let positive = positive.into();
265        Self(InputCombinator::new(move || {
266            let negative = negative.get_scalar(0.0, -1.0);
267            let positive = positive.get_scalar(0.0, 1.0);
268            negative + positive
269        }))
270    }
271
272    pub fn get(&self) -> f32 {
273        self.0.get()
274    }
275}
276
277pub struct ArrayInputCombinator<const N: usize>(InputCombinator<[f32; N]>);
278
279impl<const N: usize> Default for ArrayInputCombinator<N> {
280    fn default() -> Self {
281        Self(InputCombinator::new(|| [0.0; N]))
282    }
283}
284
285impl<const N: usize> ArrayInputCombinator<N> {
286    pub fn new(inputs: [impl Into<InputActionOrAxisRef>; N]) -> Self {
287        let items: [InputActionOrAxisRef; N] = inputs.map(|input| input.into());
288        Self(InputCombinator::new(move || {
289            std::array::from_fn(|index| items[index].get_scalar(0.0, 1.0))
290        }))
291    }
292
293    pub fn get(&self) -> [f32; N] {
294        self.0.get()
295    }
296}
297
298#[derive(Debug, Default, Clone)]
299pub struct InputCharacters {
300    characters: String,
301}
302
303impl InputCharacters {
304    pub fn read(&self) -> &str {
305        &self.characters
306    }
307
308    pub fn write(&mut self) -> &mut String {
309        &mut self.characters
310    }
311
312    pub fn take(&mut self) -> String {
313        std::mem::take(&mut self.characters)
314    }
315}
316
317#[derive(Debug, Default, Clone)]
318pub struct InputMapping {
319    pub actions: HashMap<VirtualAction, InputActionRef>,
320    pub axes: HashMap<VirtualAxis, InputAxisRef>,
321    pub consume: InputConsume,
322    pub layer: isize,
323    pub name: Cow<'static, str>,
324    pub gamepad: Option<GamepadId>,
325}
326
327impl InputMapping {
328    pub fn action(mut self, id: VirtualAction, action: InputActionRef) -> Self {
329        self.actions.insert(id, action);
330        self
331    }
332
333    pub fn axis(mut self, id: VirtualAxis, axis: InputAxisRef) -> Self {
334        self.axes.insert(id, axis);
335        self
336    }
337
338    pub fn consume(mut self, consume: InputConsume) -> Self {
339        self.consume = consume;
340        self
341    }
342
343    pub fn layer(mut self, value: isize) -> Self {
344        self.layer = value;
345        self
346    }
347
348    pub fn name(mut self, value: impl Into<Cow<'static, str>>) -> Self {
349        self.name = value.into();
350        self
351    }
352
353    pub fn gamepad(mut self, gamepad: GamepadId) -> Self {
354        self.gamepad = Some(gamepad);
355        self
356    }
357}
358
359impl From<InputMapping> for InputMappingRef {
360    fn from(value: InputMapping) -> Self {
361        Self::new(value)
362    }
363}
364
365#[derive(Debug)]
366pub struct InputContext {
367    pub mouse_wheel_line_scale: f32,
368    /// [(id, mapping)]
369    mappings_stack: Vec<(ID<InputMapping>, InputMappingRef)>,
370    characters: InputCharactersRef,
371    gamepads: Option<Gilrs>,
372}
373
374impl Default for InputContext {
375    fn default() -> Self {
376        Self {
377            mouse_wheel_line_scale: Self::default_mouse_wheel_line_scale(),
378            mappings_stack: Default::default(),
379            characters: Default::default(),
380            gamepads: None,
381        }
382    }
383}
384
385impl Clone for InputContext {
386    fn clone(&self) -> Self {
387        Self {
388            mouse_wheel_line_scale: self.mouse_wheel_line_scale,
389            mappings_stack: self.mappings_stack.clone(),
390            characters: self.characters.clone(),
391            gamepads: None,
392        }
393    }
394}
395
396impl InputContext {
397    fn default_mouse_wheel_line_scale() -> f32 {
398        10.0
399    }
400
401    pub fn with_gamepads(mut self) -> Self {
402        self.gamepads = Gilrs::new().ok();
403        self
404    }
405
406    pub fn with_gamepads_custom(mut self, gamepads: Gilrs) -> Self {
407        self.gamepads = Some(gamepads);
408        self
409    }
410
411    pub fn gamepads(&self) -> Option<&Gilrs> {
412        self.gamepads.as_ref()
413    }
414
415    pub fn gamepads_mut(&mut self) -> Option<&mut Gilrs> {
416        self.gamepads.as_mut()
417    }
418
419    pub fn push_mapping(&mut self, mapping: impl Into<InputMappingRef>) -> ID<InputMapping> {
420        let mapping = mapping.into();
421        let id = ID::default();
422        let layer = mapping.read().unwrap().layer;
423        let index = self
424            .mappings_stack
425            .binary_search_by(|(_, mapping)| {
426                mapping
427                    .read()
428                    .unwrap()
429                    .layer
430                    .cmp(&layer)
431                    .then(Ordering::Less)
432            })
433            .unwrap_or_else(|index| index);
434        self.mappings_stack.insert(index, (id, mapping));
435        id
436    }
437
438    pub fn pop_mapping(&mut self) -> Option<InputMappingRef> {
439        self.mappings_stack.pop().map(|(_, mapping)| mapping)
440    }
441
442    pub fn top_mapping(&self) -> Option<&InputMappingRef> {
443        self.mappings_stack.last().map(|(_, mapping)| mapping)
444    }
445
446    pub fn remove_mapping(&mut self, id: ID<InputMapping>) -> Option<InputMappingRef> {
447        self.mappings_stack
448            .iter()
449            .position(|(mid, _)| mid == &id)
450            .map(|index| self.mappings_stack.remove(index).1)
451    }
452
453    pub fn mapping(&'_ self, id: ID<InputMapping>) -> Option<RwLockReadGuard<'_, InputMapping>> {
454        self.mappings_stack
455            .iter()
456            .find(|(mid, _)| mid == &id)
457            .and_then(|(_, mapping)| mapping.read())
458    }
459
460    pub fn stack(&self) -> impl Iterator<Item = &InputMappingRef> {
461        self.mappings_stack.iter().map(|(_, mapping)| mapping)
462    }
463
464    pub fn characters(&self) -> InputCharactersRef {
465        self.characters.clone()
466    }
467
468    pub fn maintain(&mut self) {
469        for (_, mapping) in &mut self.mappings_stack {
470            if let Some(mut mapping) = mapping.write() {
471                for action in mapping.actions.values_mut() {
472                    if let Some(mut action) = action.write() {
473                        *action = action.update();
474                    }
475                }
476                for (id, axis) in &mut mapping.axes {
477                    if let VirtualAxis::MouseWheelX | VirtualAxis::MouseWheelY = id
478                        && let Some(mut axis) = axis.write()
479                    {
480                        axis.0 = 0.0;
481                    }
482                }
483            }
484        }
485
486        if let Some(gamepads) = self.gamepads.as_mut() {
487            while let Some(GamepadEvent { id, event, .. }) = gamepads.next_event() {
488                match event {
489                    GamepadEventType::ButtonPressed(info, ..) => {
490                        for (_, mapping) in self.mappings_stack.iter().rev() {
491                            if let Some(mapping) = mapping.read() {
492                                if !mapping.gamepad.map(|gamepad| gamepad == id).unwrap_or(true) {
493                                    continue;
494                                }
495                                let mut consume = mapping.consume == InputConsume::All;
496                                for (id, data) in &mapping.actions {
497                                    if let VirtualAction::GamepadButton(button) = id
498                                        && *button == info
499                                        && let Some(mut data) = data.write()
500                                    {
501                                        *data = data.change(true);
502                                        if mapping.consume == InputConsume::Hit {
503                                            consume = true;
504                                        }
505                                    }
506                                }
507                                for (id, data) in &mapping.axes {
508                                    if let VirtualAxis::GamepadButton(button) = id
509                                        && *button == info
510                                        && let Some(mut data) = data.write()
511                                    {
512                                        data.0 = 1.0;
513                                        if mapping.consume == InputConsume::Hit {
514                                            consume = true;
515                                        }
516                                    }
517                                }
518                                if consume {
519                                    break;
520                                }
521                            }
522                        }
523                    }
524                    GamepadEventType::ButtonReleased(info, ..) => {
525                        for (_, mapping) in self.mappings_stack.iter().rev() {
526                            if let Some(mapping) = mapping.read() {
527                                if !mapping.gamepad.map(|gamepad| gamepad == id).unwrap_or(true) {
528                                    continue;
529                                }
530                                let mut consume = mapping.consume == InputConsume::All;
531                                for (id, data) in &mapping.actions {
532                                    if let VirtualAction::GamepadButton(button) = id
533                                        && *button == info
534                                        && let Some(mut data) = data.write()
535                                    {
536                                        *data = data.change(false);
537                                        if mapping.consume == InputConsume::Hit {
538                                            consume = true;
539                                        }
540                                    }
541                                }
542                                for (id, data) in &mapping.axes {
543                                    if let VirtualAxis::GamepadButton(button) = id
544                                        && *button == info
545                                        && let Some(mut data) = data.write()
546                                    {
547                                        data.0 = 0.0;
548                                        if mapping.consume == InputConsume::Hit {
549                                            consume = true;
550                                        }
551                                    }
552                                }
553                                if consume {
554                                    break;
555                                }
556                            }
557                        }
558                    }
559                    GamepadEventType::AxisChanged(info, value, ..) => {
560                        for (_, mapping) in self.mappings_stack.iter().rev() {
561                            if let Some(mapping) = mapping.read() {
562                                let mut consume = mapping.consume == InputConsume::All;
563                                for (id, data) in &mapping.actions {
564                                    if let VirtualAction::GamepadAxis(axis) = id
565                                        && *axis == info
566                                        && let Some(mut data) = data.write()
567                                    {
568                                        *data = data.change(value > 0.5);
569                                        if mapping.consume == InputConsume::Hit {
570                                            consume = true;
571                                        }
572                                    }
573                                }
574                                for (id, data) in &mapping.axes {
575                                    if let VirtualAxis::GamepadAxis(axis) = id
576                                        && *axis == info
577                                        && let Some(mut data) = data.write()
578                                    {
579                                        data.0 = value;
580                                        if mapping.consume == InputConsume::Hit {
581                                            consume = true;
582                                        }
583                                    }
584                                }
585                                if consume {
586                                    break;
587                                }
588                            }
589                        }
590                    }
591                    _ => {}
592                }
593            }
594            gamepads.inc();
595        }
596    }
597
598    pub fn on_event(&mut self, event: &WindowEvent) {
599        match event {
600            WindowEvent::ReceivedCharacter(character) => {
601                if let Some(mut characters) = self.characters.write() {
602                    characters.characters.push(*character);
603                }
604            }
605            WindowEvent::KeyboardInput { input, .. } => {
606                if let Some(key) = input.virtual_keycode {
607                    for (_, mapping) in self.mappings_stack.iter().rev() {
608                        if let Some(mapping) = mapping.read() {
609                            let mut consume = mapping.consume == InputConsume::All;
610                            for (id, data) in &mapping.actions {
611                                if let VirtualAction::KeyButton(button) = id
612                                    && *button == key
613                                    && let Some(mut data) = data.write()
614                                {
615                                    *data = data.change(input.state == ElementState::Pressed);
616                                    if mapping.consume == InputConsume::Hit {
617                                        consume = true;
618                                    }
619                                }
620                            }
621                            for (id, data) in &mapping.axes {
622                                if let VirtualAxis::KeyButton(button) = id
623                                    && *button == key
624                                    && let Some(mut data) = data.write()
625                                {
626                                    data.0 = if input.state == ElementState::Pressed {
627                                        1.0
628                                    } else {
629                                        0.0
630                                    };
631                                    if mapping.consume == InputConsume::Hit {
632                                        consume = true;
633                                    }
634                                }
635                            }
636                            if consume {
637                                break;
638                            }
639                        }
640                    }
641                }
642            }
643            WindowEvent::CursorMoved { position, .. } => {
644                for (_, mapping) in self.mappings_stack.iter().rev() {
645                    if let Some(mapping) = mapping.read() {
646                        let mut consume = mapping.consume == InputConsume::All;
647                        for (id, data) in &mapping.axes {
648                            match id {
649                                VirtualAxis::MousePositionX => {
650                                    if let Some(mut data) = data.write() {
651                                        data.0 = position.x as _;
652                                        if mapping.consume == InputConsume::Hit {
653                                            consume = true;
654                                        }
655                                    }
656                                }
657                                VirtualAxis::MousePositionY => {
658                                    if let Some(mut data) = data.write() {
659                                        data.0 = position.y as _;
660                                        if mapping.consume == InputConsume::Hit {
661                                            consume = true;
662                                        }
663                                    }
664                                }
665                                _ => {}
666                            }
667                        }
668                        if consume {
669                            break;
670                        }
671                    }
672                }
673            }
674            WindowEvent::MouseWheel { delta, .. } => {
675                for (_, mapping) in self.mappings_stack.iter().rev() {
676                    if let Some(mapping) = mapping.read() {
677                        let mut consume = mapping.consume == InputConsume::All;
678                        for (id, data) in &mapping.axes {
679                            match id {
680                                VirtualAxis::MouseWheelX => {
681                                    if let Some(mut data) = data.write() {
682                                        data.0 = match delta {
683                                            MouseScrollDelta::LineDelta(x, _) => *x,
684                                            MouseScrollDelta::PixelDelta(pos) => pos.x as _,
685                                        };
686                                        if mapping.consume == InputConsume::Hit {
687                                            consume = true;
688                                        }
689                                    }
690                                }
691                                VirtualAxis::MouseWheelY => {
692                                    if let Some(mut data) = data.write() {
693                                        data.0 = match delta {
694                                            MouseScrollDelta::LineDelta(_, y) => *y,
695                                            MouseScrollDelta::PixelDelta(pos) => pos.y as _,
696                                        };
697                                        if mapping.consume == InputConsume::Hit {
698                                            consume = true;
699                                        }
700                                    }
701                                }
702                                _ => {}
703                            }
704                        }
705                        if consume {
706                            break;
707                        }
708                    }
709                }
710            }
711            WindowEvent::MouseInput { state, button, .. } => {
712                for (_, mapping) in self.mappings_stack.iter().rev() {
713                    if let Some(mapping) = mapping.read() {
714                        let mut consume = mapping.consume == InputConsume::All;
715                        for (id, data) in &mapping.actions {
716                            if let VirtualAction::MouseButton(btn) = id
717                                && button == btn
718                                && let Some(mut data) = data.write()
719                            {
720                                *data = data.change(*state == ElementState::Pressed);
721                                if mapping.consume == InputConsume::Hit {
722                                    consume = true;
723                                }
724                            }
725                        }
726                        for (id, data) in &mapping.axes {
727                            if let VirtualAxis::MouseButton(btn) = id
728                                && button == btn
729                                && let Some(mut data) = data.write()
730                            {
731                                data.0 = if *state == ElementState::Pressed {
732                                    1.0
733                                } else {
734                                    0.0
735                                };
736                                if mapping.consume == InputConsume::Hit {
737                                    consume = true;
738                                }
739                            }
740                        }
741                        if consume {
742                            break;
743                        }
744                    }
745                }
746            }
747            WindowEvent::AxisMotion { axis, value, .. } => {
748                for (_, mapping) in self.mappings_stack.iter().rev() {
749                    if let Some(mapping) = mapping.read() {
750                        let mut consume = mapping.consume == InputConsume::All;
751                        for (id, data) in &mapping.actions {
752                            if let VirtualAction::Axis(index) = id
753                                && axis == index
754                                && let Some(mut data) = data.write()
755                            {
756                                *data = data.change(value.abs() > 0.5);
757                                if mapping.consume == InputConsume::Hit {
758                                    consume = true;
759                                }
760                            }
761                        }
762                        for (id, data) in &mapping.axes {
763                            if let VirtualAxis::Axis(index) = id
764                                && axis == index
765                                && let Some(mut data) = data.write()
766                            {
767                                data.0 = *value as _;
768                                if mapping.consume == InputConsume::Hit {
769                                    consume = true;
770                                }
771                            }
772                        }
773                        if consume {
774                            break;
775                        }
776                    }
777                }
778            }
779            _ => {}
780        }
781    }
782}
783
784#[cfg(test)]
785mod tests {
786    use super::*;
787
788    #[test]
789    fn test_stack() {
790        let mut context = InputContext::default();
791        context.push_mapping(InputMapping::default().name("a").layer(0));
792        context.push_mapping(InputMapping::default().name("b").layer(0));
793        context.push_mapping(InputMapping::default().name("c").layer(0));
794        context.push_mapping(InputMapping::default().name("d").layer(-1));
795        context.push_mapping(InputMapping::default().name("e").layer(1));
796        context.push_mapping(InputMapping::default().name("f").layer(-1));
797        context.push_mapping(InputMapping::default().name("g").layer(1));
798        context.push_mapping(InputMapping::default().name("h").layer(-2));
799        context.push_mapping(InputMapping::default().name("i").layer(-2));
800        context.push_mapping(InputMapping::default().name("j").layer(2));
801        context.push_mapping(InputMapping::default().name("k").layer(2));
802
803        let provided = context
804            .stack()
805            .map(|mapping| {
806                let mapping = mapping.read().unwrap();
807                (mapping.name.as_ref().to_owned(), mapping.layer)
808            })
809            .collect::<Vec<_>>();
810        assert_eq!(
811            provided,
812            vec![
813                ("h".to_owned(), -2),
814                ("i".to_owned(), -2),
815                ("d".to_owned(), -1),
816                ("f".to_owned(), -1),
817                ("a".to_owned(), 0),
818                ("b".to_owned(), 0),
819                ("c".to_owned(), 0),
820                ("e".to_owned(), 1),
821                ("g".to_owned(), 1),
822                ("j".to_owned(), 2),
823                ("k".to_owned(), 2),
824            ]
825        );
826    }
827}