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 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}