File size: 41,395 Bytes
d82cf6a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
"""Interface classes for `pyglet.input`.

.. versionadded:: 1.2
"""

import sys

from pyglet.event import EventDispatcher


_is_pyglet_doc_run = hasattr(sys, "is_pyglet_doc_run") and sys.is_pyglet_doc_run


class DeviceException(Exception):
    pass


class DeviceOpenException(DeviceException):
    pass


class DeviceExclusiveException(DeviceException):
    pass


class Device:
    """Input device.

    :Ivariables:
        display : `pyglet.canvas.Display`
            Display this device is connected to.
        name : str
            Name of the device, as described by the device firmware.
        manufacturer : str
            Name of the device manufacturer, or ``None`` if the information is
            not available.
    """

    def __init__(self, display, name):
        self.display = display
        self.name = name
        self.manufacturer = None
        self._is_open = False

    @property
    def is_open(self):
        return self._is_open

    def open(self, window=None, exclusive=False):
        """Open the device to begin receiving input from it.

        :Parameters:
            `window` : Window
                Optional window to associate with the device.  The behaviour
                of this parameter is device and operating system dependant.
                It can usually be omitted for most devices.
            `exclusive` : bool
                If ``True`` the device will be opened exclusively so that no
                other application can use it.  The method will raise
                `DeviceExclusiveException` if the device cannot be opened this
                way (for example, because another application has already
                opened it).
        """

        if self._is_open:
            raise DeviceOpenException('Device is already open.')

        self._is_open = True

    def close(self):
        """Close the device. """
        self._is_open = False

    def get_controls(self):
        """Get a list of controls provided by the device.

        :rtype: list of `Control`
        """
        raise NotImplementedError('abstract')

    def get_guid(self):
        """Get the device GUID, in SDL2 format.

        Return a str containing a unique device identification
        string. This is generated from the hardware identifiers,
        and is in the same format as was popularized by SDL2.
        GUIDs differ between platforms, but are generally 32
        hexidecimal characters.

        :rtype: str containing the device's GUID.
        """
        raise NotImplementedError('abstract')

    def __repr__(self):
        return f"{self.__class__.__name__}(name={self.name})"


class Control(EventDispatcher):
    """Single value input provided by a device.

    A control's value can be queried when the device is open.  Event handlers
    can be attached to the control to be called when the value changes.

    The `min` and `max` properties are provided as advertised by the
    device; in some cases the control's value will be outside this range.

    :Ivariables:
        `name` : str
            Name of the control, or ``None`` if unknown
        `raw_name` : str
            Unmodified name of the control, as presented by the operating
            system; or ``None`` if unknown.
        `inverted` : bool
            If ``True``, the value reported is actually inverted from what the
            device reported; usually this is to provide consistency across
            operating systems.
    """

    def __init__(self, name, raw_name=None, inverted=False):
        self.name = name
        self.raw_name = raw_name
        self.inverted = inverted
        self._value = None

    @property
    def value(self):
        """Current value of the control.

        The range of the value is device-dependent; for absolute controls
        the range is given by ``min`` and ``max`` (however the value may exceed
        this range); for relative controls the range is undefined.

        :type: float
        """
        return self._value

    @value.setter
    def value(self, newvalue):
        if newvalue == self._value:
            return
        self._value = newvalue
        self.dispatch_event('on_change', newvalue)

    def __repr__(self):
        if self.name:
            return f"{self.__class__.__name__}(name={self.name}, raw_name={self.raw_name})"
        else:
            return f"{self.__class__.__name__}(raw_name={self.raw_name})"

    def on_change(self, value):
        """The value changed.

        :Parameters:
            `value` : float
                Current value of the control.

        :event:
        """


Control.register_event_type('on_change')


class RelativeAxis(Control):
    """An axis whose value represents a relative change from the previous
    value.
    """

    #: Name of the horizontal axis control
    X = 'x'
    #: Name of the vertical axis control
    Y = 'y'
    #: Name of the Z axis control.
    Z = 'z'
    #: Name of the rotational-X axis control
    RX = 'rx'
    #: Name of the rotational-Y axis control
    RY = 'ry'
    #: Name of the rotational-Z axis control
    RZ = 'rz'
    #: Name of the scroll wheel control
    WHEEL = 'wheel'

    @property
    def value(self):
        return self._value

    @value.setter
    def value(self, value):
        self._value = value
        self.dispatch_event('on_change', value)


class AbsoluteAxis(Control):
    """An axis whose value represents a physical measurement from the device.

    The value is advertised to range over ``minimum`` and ``maximum``.

    :Ivariables:
        `minimum` : float
            Minimum advertised value.
        `maximum` : float
            Maximum advertised value.
    """

    #: Name of the horizontal axis control
    X = 'x'
    #: Name of the vertical axis control
    Y = 'y'
    #: Name of the Z axis control.
    Z = 'z'
    #: Name of the rotational-X axis control
    RX = 'rx'
    #: Name of the rotational-Y axis control
    RY = 'ry'
    #: Name of the rotational-Z axis control
    RZ = 'rz'
    #: Name of the hat (POV) control, when a single control enumerates all of
    #: the hat's positions.
    HAT = 'hat'
    #: Name of the hat's (POV's) horizontal control, when the hat position is
    #: described by two orthogonal controls.
    HAT_X = 'hat_x'
    #: Name of the hat's (POV's) vertical control, when the hat position is
    #: described by two orthogonal controls.
    HAT_Y = 'hat_y'

    def __init__(self, name, minimum, maximum, raw_name=None, inverted=False):
        super().__init__(name, raw_name, inverted)
        self.min = minimum
        self.max = maximum


class Button(Control):
    """A control whose value is boolean. """

    @property
    def value(self):
        return bool(self._value)

    @value.setter
    def value(self, newvalue):
        if newvalue == self._value:
            return
        self._value = newvalue
        self.dispatch_event('on_change', bool(newvalue))
        if newvalue:
            self.dispatch_event('on_press')
        else:
            self.dispatch_event('on_release')

    if _is_pyglet_doc_run:
        def on_press(self):
            """The button was pressed.

            :event:
            """

        def on_release(self):
            """The button was released.

            :event:
            """


Button.register_event_type('on_press')
Button.register_event_type('on_release')


class Joystick(EventDispatcher):
    """High-level interface for joystick-like devices.  This includes a wide range
    of analog and digital joysticks, gamepads, controllers, and possibly even
    steering wheels and other input devices. There is unfortunately no easy way to
    distinguish between most of these different device types.

    For a simplified subset of Joysticks, see the :py:class:`~pyglet.input.Controller`
    interface. This covers a variety of popular game console controllers. Unlike
    Joysticks, Controllers have strictly defined layouts and inputs.

    To use a joystick, first call `open`, then in your game loop examine
    the values of `x`, `y`, and so on.  These values are normalized to the
    range [-1.0, 1.0]. 

    To receive events when the value of an axis changes, attach an 
    on_joyaxis_motion event handler to the joystick.  The :py:class:`~pyglet.input.Joystick` instance,
    axis name, and current value are passed as parameters to this event.

    To handle button events, you should attach on_joybutton_press and 
    on_joy_button_release event handlers to the joystick.  Both the :py:class:`~pyglet.input.Joystick`
    instance and the index of the changed button are passed as parameters to 
    these events.

    Alternately, you may attach event handlers to each individual button in 
    `button_controls` to receive on_press or on_release events.
    
    To use the hat switch, attach an on_joyhat_motion event handler to the
    joystick.  The handler will be called with both the hat_x and hat_y values
    whenever the value of the hat switch changes.

    The device name can be queried to get the name of the joystick.

    :Ivariables:
        `device` : `Device`
            The underlying device used by this joystick interface.
        `x` : float
            Current X (horizontal) value ranging from -1.0 (left) to 1.0
            (right).
        `y` : float
            Current y (vertical) value ranging from -1.0 (top) to 1.0
            (bottom).
        `z` : float
            Current Z value ranging from -1.0 to 1.0.  On joysticks the Z
            value is usually the throttle control.  On controllers the Z
            value is usually the secondary thumb vertical axis.
        `rx` : float
            Current rotational X value ranging from -1.0 to 1.0.
        `ry` : float
            Current rotational Y value ranging from -1.0 to 1.0.
        `rz` : float
            Current rotational Z value ranging from -1.0 to 1.0.  On joysticks
            the RZ value is usually the twist of the stick.  On game
            controllers the RZ value is usually the secondary thumb horizontal
            axis.
        `hat_x` : int
            Current hat (POV) horizontal position; one of -1 (left), 0
            (centered) or 1 (right).
        `hat_y` : int
            Current hat (POV) vertical position; one of -1 (bottom), 0
            (centered) or 1 (top).
        `buttons` : list of bool
            List of boolean values representing current states of the buttons.
            These are in order, so that button 1 has value at ``buttons[0]``,
            and so on.
        `x_control` : `AbsoluteAxis`
            Underlying control for `x` value, or ``None`` if not available.
        `y_control` : `AbsoluteAxis`
            Underlying control for `y` value, or ``None`` if not available.
        `z_control` : `AbsoluteAxis`
            Underlying control for `z` value, or ``None`` if not available.
        `rx_control` : `AbsoluteAxis`
            Underlying control for `rx` value, or ``None`` if not available.
        `ry_control` : `AbsoluteAxis`
            Underlying control for `ry` value, or ``None`` if not available.
        `rz_control` : `AbsoluteAxis`
            Underlying control for `rz` value, or ``None`` if not available.
        `hat_x_control` : `AbsoluteAxis`
            Underlying control for `hat_x` value, or ``None`` if not available.
        `hat_y_control` : `AbsoluteAxis`
            Underlying control for `hat_y` value, or ``None`` if not available.
        `button_controls` : list of `Button`
            Underlying controls for `buttons` values.
    """

    def __init__(self, device):
        self.device = device

        self.x = 0
        self.y = 0
        self.z = 0
        self.rx = 0
        self.ry = 0
        self.rz = 0
        self.hat_x = 0
        self.hat_y = 0
        self.buttons = []

        self.x_control = None
        self.y_control = None
        self.z_control = None
        self.rx_control = None
        self.ry_control = None
        self.rz_control = None
        self.hat_x_control = None
        self.hat_y_control = None
        self.button_controls = []

        def add_axis(control):
            name = control.name
            scale = 2.0 / (control.max - control.min)
            bias = -1.0 - control.min * scale
            if control.inverted:
                scale = -scale
                bias = -bias
            setattr(self, name + '_control', control)

            @control.event
            def on_change(value):
                normalized_value = value * scale + bias
                setattr(self, name, normalized_value)
                self.dispatch_event('on_joyaxis_motion', self, name, normalized_value)

        def add_button(control):
            i = len(self.buttons)
            self.buttons.append(False)
            self.button_controls.append(control)

            @control.event
            def on_change(value):
                self.buttons[i] = value
            
            @control.event
            def on_press():
                self.dispatch_event('on_joybutton_press', self, i)

            @control.event
            def on_release():
                self.dispatch_event('on_joybutton_release', self, i)

        def add_hat(control):
            # 8-directional hat encoded as a single control (Windows/Mac)
            self.hat_x_control = control
            self.hat_y_control = control
            
            @control.event
            def on_change(value):
                if value & 0xffff == 0xffff:
                    self.hat_x = self.hat_y = 0
                else:
                    if control.max > 8:  # DirectInput: scale value
                        value //= 0xfff
                    if 0 <= value < 8:
                        self.hat_x, self.hat_y = (( 0,  1),
                                                  ( 1,  1),
                                                  ( 1,  0),
                                                  ( 1, -1),
                                                  ( 0, -1),
                                                  (-1, -1),
                                                  (-1,  0),
                                                  (-1,  1))[value]
                    else:
                        # Out of range
                        self.hat_x = self.hat_y = 0
                self.dispatch_event('on_joyhat_motion', self, self.hat_x, self.hat_y)

        for control in device.get_controls():
            if isinstance(control, AbsoluteAxis):
                if control.name in ('x', 'y', 'z', 'rx', 'ry', 'rz', 'hat_x', 'hat_y'):
                    add_axis(control)
                elif control.name == 'hat':
                    add_hat(control)
            elif isinstance(control, Button):
                add_button(control)

    def open(self, window=None, exclusive=False):
        """Open the joystick device.  See `Device.open`. """
        self.device.open(window, exclusive)

    def close(self):
        """Close the joystick device.  See `Device.close`. """
        self.device.close()

    def on_joyaxis_motion(self, joystick, axis, value):
        """The value of a joystick axis changed.

        :Parameters:
            `joystick` : `Joystick`
                The joystick device whose axis changed.
            `axis` : string
                The name of the axis that changed.
            `value` : float
                The current value of the axis, normalized to [-1, 1].
        """

    def on_joybutton_press(self, joystick, button):
        """A button on the joystick was pressed.

        :Parameters:
            `joystick` : `Joystick`
                The joystick device whose button was pressed.
            `button` : int
                The index (in `button_controls`) of the button that was pressed.
        """
        
    def on_joybutton_release(self, joystick, button):
        """A button on the joystick was released.

        :Parameters:
            `joystick` : `Joystick`
                The joystick device whose button was released.
            `button` : int
                The index (in `button_controls`) of the button that was released.
        """

    def on_joyhat_motion(self, joystick, hat_x, hat_y):
        """The value of the joystick hat switch changed.

        :Parameters:
            `joystick` : `Joystick`
                The joystick device whose hat control changed.
            `hat_x` : int
                Current hat (POV) horizontal position; one of -1 (left), 0
                (centered) or 1 (right).
            `hat_y` : int
                Current hat (POV) vertical position; one of -1 (bottom), 0
                (centered) or 1 (top).
        """

    def __repr__(self):
        return f"Joystick(device={self.device.name})"


Joystick.register_event_type('on_joyaxis_motion')
Joystick.register_event_type('on_joybutton_press')
Joystick.register_event_type('on_joybutton_release')
Joystick.register_event_type('on_joyhat_motion')


class Controller(EventDispatcher):

    __slots__ = ('device', 'guid', '_mapping', 'name', 'a', 'b', 'x', 'y',
                 'back', 'start', 'guide', 'leftshoulder', 'rightshoulder',
                 'leftstick', 'rightstick', 'lefttrigger', 'righttrigger',
                 'leftx', 'lefty', 'rightx', 'righty', 'dpup', 'dpdown', 'dpleft',
                 'dpright', '_button_controls', '_axis_controls', '_hat_control',
                 '_hat_x_control', '_hat_y_control')

    def __init__(self, device, mapping):
        """High-level interface for Game Controllers.

        Unlike Joysticks, Controllers have a strictly defined set of inputs
        that matches the layout of popular home video game console Controllers.
        This includes a variety of face and shoulder buttons, analog sticks and
        triggers, a directional pad, and optional rumble (force feedback)
        effects.

        To use a Controller, you must first call `open`. Controllers will then
        dispatch a variety of events whenever the inputs change. They can also
        be polled at any time to find the current value of any inputs. Analog
        inputs are normalized to the range [-1.0, 1.0].

        :note: A running application event loop is required

        The following event types are dispatched:
            `on_button_press`
            `on_button_release`
            `on_stick_motion`
            `on_dpad_motion`
            `on_trigger_motion`

        The device name can be queried to get the name of the joystick.

        :Ivariables:
            `device` : `Device`
                The underlying device used by this joystick interface.
            `name` : str
                The name of the Controller as reported by the OS.
            `guid` : str
                The unique device identification string, in SDL2 format.
            `a` : bool
            `b` : bool
            `x` : bool
            `x` : bool
            `back` : bool
            `start` : bool
            `guide` : bool
            `leftshoulder` : bool
            `rightshoulder` : bool
            `leftstick` : bool
            `rightstick` : bool
            `leftx` : float
            `lefty` : float
            `rightx` : float
            `righty` : float
            `lefttrigger` : float
            `righttrigger` : float
            `dpup` : bool
            `dpdown` : bool
            `dpleft` : bool
            `dpright` : bool

        .. versionadded:: 2.0
        """

        self.device = device
        self._mapping = mapping

        self.name = mapping.get('name')
        self.guid = mapping.get('guid')

        self.a = False
        self.b = False
        self.x = False
        self.y = False
        self.back = False
        self.start = False
        self.guide = False
        self.leftshoulder = False
        self.rightshoulder = False
        self.leftstick = False          # stick press button
        self.rightstick = False         # stick press button
        self.lefttrigger = 0
        self.righttrigger = 0
        self.leftx = 0
        self.lefty = 0
        self.rightx = 0
        self.righty = 0
        self.dpup = False
        self.dpdown = False
        self.dpleft = False
        self.dpright = False

        self._button_controls = []
        self._axis_controls = []
        self._hat_control = None
        self._hat_x_control = None
        self._hat_y_control = None

        self._initialize_controls()

    def _initialize_controls(self):

        def add_axis(control, axis_name):
            tscale = 1.0 / (control.max - control.min)
            scale = 2.0 / (control.max - control.min)
            bias = -1.0 - control.min * scale
            if control.inverted:
                scale = -scale
                bias = -bias

            if axis_name in ("dpup", "dpdown"):
                @control.event
                def on_change(value):
                    normalized_value = value * scale + bias
                    self.dpup = self.dpdown = False
                    if normalized_value > 0.1:
                        self.dpup = True
                    if normalized_value < -0.1:
                        self.dpdown = True
                    self.dispatch_event('on_dpad_motion', self,
                                        self.dpleft, self.dpright, self.dpup, self.dpdown)

            elif axis_name in ("dpleft", "dpright"):
                @control.event
                def on_change(value):
                    normalized_value = value * scale + bias
                    self.dpleft = self.dpright = False
                    if normalized_value > 0.1:
                        self.dpright = True
                    if normalized_value < -0.1:
                        self.dpleft = True
                    self.dispatch_event('on_dpad_motion', self, self.dpleft, self.dpright, self.dpup, self.dpdown)

            elif axis_name in ("lefttrigger", "righttrigger"):
                @control.event
                def on_change(value):
                    normalized_value = value * tscale
                    setattr(self, axis_name, normalized_value)
                    self.dispatch_event('on_trigger_motion', self, axis_name, normalized_value)

            elif axis_name in ("leftx", "lefty"):
                @control.event
                def on_change(value):
                    normalized_value = value * scale + bias
                    setattr(self, axis_name, normalized_value)
                    self.dispatch_event('on_stick_motion', self,
                                        "leftstick", self.leftx, -self.lefty)

            elif axis_name in ("rightx", "righty"):
                @control.event
                def on_change(value):
                    normalized_value = value * scale + bias
                    setattr(self, axis_name, normalized_value)
                    self.dispatch_event('on_stick_motion', self,
                                        "rightstick", self.rightx, -self.righty)

        def add_button(control, button_name):
            if button_name in ("dpleft", "dpright", "dpup", "dpdown"):
                @control.event
                def on_change(value):
                    setattr(self, button_name, value)
                    self.dispatch_event('on_dpad_motion', self,
                                        self.dpleft, self.dpright, self.dpup, self.dpdown)
            else:
                @control.event
                def on_change(value):
                    setattr(self, button_name, value)

                @control.event
                def on_press():
                    self.dispatch_event('on_button_press', self, button_name)

                @control.event
                def on_release():
                    self.dispatch_event('on_button_release', self, button_name)

        def add_dedicated_hat(control):
            # 8-directional hat encoded as a single control (Windows/Mac)
            @control.event
            def on_change(value):
                if value & 0xffff == 0xffff:
                    self.dpleft = self.dpright = self.dpup = self.dpdown = False
                else:
                    if control.max > 8:  # DirectInput: scale value
                        value //= 0xfff
                    if 0 <= value < 8:
                        self.dpleft, self.dpright, self.dpup, self.dpdown = (
                            (False, False, True,  False),
                            (False, True,  True,  False),
                            (False, True,  False, False),
                            (False, True,  False, True),
                            (False, False, False, True),
                            (True,  False, False, True),
                            (True,  False, False, False),
                            (True,  False, True,  False))[value]
                    else:
                        # Out of range
                        self.dpleft = self.dpright = self.dpup = self.dpdown = False
                self.dispatch_event('on_dpad_motion', self,
                                    self.dpleft, self.dpright, self.dpup, self.dpdown)

        for control in self.device.get_controls():
            """Categorize the various control types"""
            if isinstance(control, Button):
                self._button_controls.append(control)

            elif isinstance(control, AbsoluteAxis):
                if control.name in ('x', 'y', 'z', 'rx', 'ry', 'rz'):
                    self._axis_controls.append(control)
                elif control.name == "hat_x":
                    self._hat_x_control = control
                elif control.name == "hat_y":
                    self._hat_y_control = control
                elif control.name == "hat":
                    self._hat_control = control

        for name, relation in self._mapping.items():

            if relation is None or type(relation) is str:
                continue

            if relation.control_type == "button":
                try:
                    add_button(self._button_controls[relation.index], name)
                except IndexError:
                    continue
            elif relation.control_type == "axis":
                try:
                    add_axis(self._axis_controls[relation.index], name)
                except IndexError:
                    continue
            elif relation.control_type == "hat0":
                if self._hat_control:
                    # TODO: test this on Windows/Mac.
                    add_dedicated_hat(self._hat_control)
                else:
                    if relation.index == 1:       # 1 == UP
                        add_axis(self._hat_y_control, "dpup")
                    elif relation.index == 2:     # 2 == RIGHT
                        add_axis(self._hat_x_control, "dpright")
                    elif relation.index == 4:     # 4 == DOWN
                        add_axis(self._hat_y_control, "dpdown")
                    elif relation.index == 8:     # 8 == LEFT
                        add_axis(self._hat_x_control, "dpleft")

    def open(self, window=None, exclusive=False):
        """Open the controller.  See `Device.open`. """
        self.device.open(window, exclusive)

    def close(self):
        """Close the controller.  See `Device.close`. """
        self.device.close()

    # Rumble (force feedback) methods:

    def rumble_play_weak(self, strength=1.0, duration=0.5):
        """Play a rumble effect on the weak motor.

        :Parameters:
            `strength` : float
                The strength of the effect, from 0 to 1.
            `duration` : float
                The duration of the effect in seconds.
        """

    def rumble_play_strong(self, strength=1.0, duration=0.5):
        """Play a rumble effect on the strong motor.

        :Parameters:
            `strength` : float
                The strength of the effect, from 0 to 1.
            `duration` : float
                The duration of the effect in seconds.
        """

    def rumble_stop_weak(self):
        """Stop playing rumble effects on the weak motor."""

    def rumble_stop_strong(self):
        """Stop playing rumble effects on the strong motor."""

    # Input Event types:

    def on_stick_motion(self, controller, stick, xvalue, yvalue):
        """The value of a controller analogue stick changed.

        :Parameters:
            `controller` : `Controller`
                The controller whose analogue stick changed.
            `stick` : string
                The name of the stick that changed.
            `xvalue` : float
                The current X axis value, normalized to [-1, 1].
            `yvalue` : float
                The current Y axis value, normalized to [-1, 1].
        """

    def on_dpad_motion(self, controller, dpleft, dpright, dpup, dpdown):
        """The direction pad of the controller changed.

        :Parameters:
            `controller` : `Controller`
                The controller whose hat control changed.
            `dpleft` : boolean
                True if left is pressed on the directional pad.
            `dpright` : boolean
                True if right is pressed on the directional pad.
            `dpup` : boolean
                True if up is pressed on the directional pad.
            `dpdown` : boolean
                True if down is pressed on the directional pad.
        """

    def on_trigger_motion(self, controller, trigger, value):
        """The value of a controller analogue stick changed.

        :Parameters:
            `controller` : `Controller`
                The controller whose analogue stick changed.
            `trigger` : string
                The name of the trigger that changed.
            `value` : float
                The current value of the trigger, normalized to [-1, 1].
        """

    def on_button_press(self, controller, button):
        """A button on the controller was pressed.

        :Parameters:
            `controller` :  :py:class:`Controller`
                The controller whose button was pressed.
            `button` : string
                The name of the button that was pressed.
        """

    def on_button_release(self, controller, button):
        """A button on the joystick was released.

        :Parameters:
            `controller` : `Controller`
                The controller whose button was released.
            `button` : string
                The name of the button that was released.
        """

    def __repr__(self):
        return f"Controller(name={self.name})"


Controller.register_event_type('on_button_press')
Controller.register_event_type('on_button_release')
Controller.register_event_type('on_stick_motion')
Controller.register_event_type('on_dpad_motion')
Controller.register_event_type('on_trigger_motion')


class AppleRemote(EventDispatcher):
    """High-level interface for Apple remote control.

    This interface provides access to the 6 button controls on the remote.
    Pressing and holding certain buttons on the remote is interpreted as
    a separate control.

    :Ivariables:
        `device` : `Device`
            The underlying device used by this interface.
        `left_control` : `Button`
            Button control for the left (prev) button.
        `left_hold_control` : `Button`
            Button control for holding the left button (rewind).
        `right_control` : `Button`
            Button control for the right (next) button.
        `right_hold_control` : `Button`
            Button control for holding the right button (fast forward).
        `up_control` : `Button`
            Button control for the up (volume increase) button.
        `down_control` : `Button`
            Button control for the down (volume decrease) button.
        `select_control` : `Button`
            Button control for the select (play/pause) button.
        `select_hold_control` : `Button`
            Button control for holding the select button.
        `menu_control` : `Button`
            Button control for the menu button.
        `menu_hold_control` : `Button`
            Button control for holding the menu button.
    """
    
    def __init__(self, device):
        def add_button(control):
            setattr(self, control.name + '_control', control)

            @control.event
            def on_press():
                self.dispatch_event('on_button_press', control.name)

            @control.event
            def on_release():
                self.dispatch_event('on_button_release', control.name)
            
        self.device = device
        for control in device.get_controls():
            if control.name in ('left', 'left_hold', 'right', 'right_hold', 'up', 'down', 
                                'menu', 'select', 'menu_hold', 'select_hold'):
                add_button(control)

    def open(self, window=None, exclusive=False):
        """Open the device.  See `Device.open`. """
        self.device.open(window, exclusive)

    def close(self):
        """Close the device.  See `Device.close`. """
        self.device.close()

    def on_button_press(self, button):
        """A button on the remote was pressed.

        Only the 'up' and 'down' buttons will generate an event when the
        button is first pressed.  All other buttons on the remote will wait
        until the button is released and then send both the press and release
        events at the same time.

        :Parameters:
            `button` : unicode
                The name of the button that was pressed. The valid names are
                'up', 'down', 'left', 'right', 'left_hold', 'right_hold',
                'menu', 'menu_hold', 'select', and 'select_hold'
                
        :event:
        """

    def on_button_release(self, button):
        """A button on the remote was released.

        The 'select_hold' and 'menu_hold' button release events are sent
        immediately after the corresponding press events regardless of
        whether the user has released the button.

        :Parameters:
            `button` : str
                The name of the button that was released. The valid names are
                'up', 'down', 'left', 'right', 'left_hold', 'right_hold',
                'menu', 'menu_hold', 'select', and 'select_hold'

        :event:
        """


AppleRemote.register_event_type('on_button_press')
AppleRemote.register_event_type('on_button_release')


class Tablet:
    """High-level interface to tablet devices.

    Unlike other devices, tablets must be opened for a specific window,
    and cannot be opened exclusively.  The `open` method returns a
    `TabletCanvas` object, which supports the events provided by the tablet.

    Currently only one tablet device can be used, though it can be opened on
    multiple windows.  If more than one tablet is connected, the behaviour is
    undefined.
    """

    def open(self, window):
        """Open a tablet device for a window.

        :Parameters:
            `window` : `Window`
                The window on which the tablet will be used.

        :rtype: `TabletCanvas`
        """
        raise NotImplementedError('abstract')


class TabletCanvas(EventDispatcher):
    """Event dispatcher for tablets.

    Use `Tablet.open` to obtain this object for a particular tablet device and
    window.  Events may be generated even if the tablet stylus is outside of
    the window; this is operating-system dependent.

    The events each provide the `TabletCursor` that was used to generate the
    event; for example, to distinguish between a stylus and an eraser.  Only
    one cursor can be used at a time, otherwise the results are undefined.

    :Ivariables:
        `window` : Window
            The window on which this tablet was opened.
    """
    # OS X: Active window receives tablet events only when cursor is in window
    # Windows: Active window receives all tablet events
    #
    # Note that this means enter/leave pairs are not always consistent (normal
    # usage).

    def __init__(self, window):
        self.window = window

    def close(self):
        """Close the tablet device for this window.
        """
        raise NotImplementedError('abstract')

    if _is_pyglet_doc_run:
        def on_enter(self, cursor):
            """A cursor entered the proximity of the window.  The cursor may
            be hovering above the tablet surface, but outside of the window
            bounds, or it may have entered the window bounds.

            Note that you cannot rely on `on_enter` and `on_leave` events to
            be generated in pairs; some events may be lost if the cursor was
            out of the window bounds at the time.

            :Parameters:
                `cursor` : `TabletCursor`
                    The cursor that entered proximity.

            :event:
            """

        def on_leave(self, cursor):
            """A cursor left the proximity of the window.  The cursor may have
            moved too high above the tablet surface to be detected, or it may
            have left the bounds of the window.

            Note that you cannot rely on `on_enter` and `on_leave` events to
            be generated in pairs; some events may be lost if the cursor was
            out of the window bounds at the time.

            :Parameters:
                `cursor` : `TabletCursor`
                    The cursor that left proximity.

            :event:
            """

        def on_motion(self, cursor, x, y, pressure, tilt_x, tilt_y, buttons):
            """The cursor moved on the tablet surface.

            If `pressure` is 0, then the cursor is actually hovering above the
            tablet surface, not in contact.

            :Parameters:
                `cursor` : `TabletCursor`
                    The cursor that moved.
                `x` : int
                    The X position of the cursor, in window coordinates.
                `y` : int
                    The Y position of the cursor, in window coordinates.
                `pressure` : float
                    The pressure applied to the cursor, in range 0.0 (no
                    pressure) to 1.0 (full pressure).
                `tilt_x` : float
                    Currently undefined.
                `tilt_y` : float
                    Currently undefined.
                `buttons` : int
                    Button state may be provided if the platform supports it.
                    Supported on: Windows

            :event:
            """


TabletCanvas.register_event_type('on_enter')
TabletCanvas.register_event_type('on_leave')
TabletCanvas.register_event_type('on_motion')


class TabletCursor:
    """A distinct cursor used on a tablet.

    Most tablets support at least a *stylus* and an *erasor* cursor; this
    object is used to distinguish them when tablet events are generated.

    :Ivariables:
        `name` : str
            Name of the cursor.
    """

    # TODO well-defined names for stylus and eraser.

    def __init__(self, name):
        self.name = name

    def __repr__(self):
        return '%s(%s)' % (self.__class__.__name__, self.name)


class ControllerManager(EventDispatcher):
    """High level interface for managing game Controllers.

    This class provides a convenient way to handle the
    connection and disconnection of devices. A list of all
    connected Controllers can be queried at any time with the
    `get_controllers` method. For hot-plugging, events are
    dispatched for `on_connect` and `on_disconnect`.
    To use the ControllerManager, first make an instance::

        controller_man = pyglet.input.ControllerManager()

    At the start of your game, query for any Controllers
    that are already connected::

        controllers = controller_man.get_controllers()

    To handle Controllers that are connected or disconnected
    after the start of your game, register handlers for the
    appropriate events::

        @controller_man.event
        def on_connect(controller):
            # code to handle newly connected
            # (or re-connected) controllers
            controller.open()
            print("Connect:", controller)

        @controller_man.event
        def on_disconnect(controller):
            # code to handle disconnected Controller
            print("Disconnect:", controller)

    .. versionadded:: 1.2
    """

    def get_controllers(self):
        """Get a list of all connected Controllers

        :rtype: list of :py:class:`Controller`
        """
        raise NotImplementedError

    def on_connect(self, controller):
        """A Controller has been connected. If this is
        a previously dissconnected Controller that is
        being re-connected, the same Controller instance
        will be returned.

        :Parameters:
            `controller` : :py:class:`Controller`
                An un-opened Controller instance.

        :event:
        """

    def on_disconnect(self, controller):
        """A Controller has been disconnected.

        :Parameters:
            `controller` : :py:class:`Controller`
                An un-opened Controller instance.

        :event:
        """


ControllerManager.register_event_type('on_connect')
ControllerManager.register_event_type('on_disconnect')