]>
Commit | Line | Data |
---|---|---|
2421eb82 RD |
1 | #---------------------------------------------------------------------------- |
2 | # Name: Joystick.py | |
3 | # Purpose: Demonstrate use of wx.Joystick | |
4 | # | |
5 | # Author: Jeff Grimmett (grimmtoo@softhome.net), adapted from original | |
6 | # .wdr-derived demo | |
7 | # | |
08b89fa8 | 8 | # Created: 02-Jan-2004 |
2421eb82 RD |
9 | # RCS-ID: $Id$ |
10 | # Copyright: | |
11 | # Licence: wxWindows license | |
12 | #---------------------------------------------------------------------------- | |
13 | # | |
14 | ||
15 | import math | |
16 | import wx | |
ac346f50 | 17 | |
08b89fa8 RD |
18 | haveJoystick = True |
19 | if wx.Platform == "__WXMAC__": | |
20 | haveJoystick = False | |
21 | ||
ac346f50 RD |
22 | #---------------------------------------------------------------------------- |
23 | ||
60d8ee39 RD |
24 | # Once all supported versions of Python support 32-bit integers on all |
25 | # platforms, this can go up to 32. | |
2421eb82 | 26 | MAX_BUTTONS = 16 |
ac346f50 | 27 | |
2421eb82 | 28 | #---------------------------------------------------------------------------- |
ac346f50 | 29 | |
2421eb82 RD |
30 | class Label(wx.StaticText): |
31 | # A derived StaticText that always aligns right and renders | |
32 | # in a bold font. | |
33 | def __init__(self, parent, label): | |
34 | wx.StaticText.__init__(self, parent, -1, label, style=wx.ALIGN_RIGHT) | |
ac346f50 | 35 | |
2421eb82 RD |
36 | self.SetFont( |
37 | wx.Font( | |
38 | parent.GetFont().GetPointSize(), | |
39 | parent.GetFont().GetFamily(), | |
40 | parent.GetFont().GetStyle(), | |
41 | wx.BOLD | |
42 | )) | |
43 | ||
44 | #---------------------------------------------------------------------------- | |
45 | ||
46 | ||
47 | class JoyGauge(wx.Panel): | |
48 | def __init__(self, parent, stick): | |
49 | ||
50 | self.stick = stick | |
51 | size = (100,100) | |
52 | ||
53 | wx.Panel.__init__(self, parent, -1, size=size) | |
54 | ||
55 | self.Bind(wx.EVT_PAINT, self.OnPaint) | |
56 | self.Bind(wx.EVT_SIZE, self.OnSize) | |
57 | self.Bind(wx.EVT_ERASE_BACKGROUND, lambda e: None) | |
58 | ||
59 | self.buffer = wx.EmptyBitmap(*size) | |
60 | dc = wx.BufferedDC(None, self.buffer) | |
61 | self.DrawFace(dc) | |
62 | self.DrawJoystick(dc) | |
63 | ||
64 | ||
65 | def OnSize(self, event): | |
66 | # The face Bitmap init is done here, to make sure the buffer is always | |
67 | # the same size as the Window | |
68 | w, h = self.GetClientSize() | |
69 | self.buffer = wx.EmptyBitmap(w,h) | |
70 | dc = wx.BufferedDC(wx.ClientDC(self), self.buffer) | |
71 | self.DrawFace(dc) | |
72 | self.DrawJoystick(dc) | |
73 | ||
74 | ||
75 | def DrawFace(self, dc): | |
76 | dc.SetBackground(wx.Brush(self.GetBackgroundColour())) | |
77 | dc.Clear() | |
78 | ||
79 | ||
80 | def OnPaint(self, evt): | |
81 | # When dc is destroyed it will blit self.buffer to the window, | |
82 | # since no other drawing is needed we'll just return and let it | |
83 | # do it's thing | |
84 | dc = wx.BufferedPaintDC(self, self.buffer) | |
85 | ||
86 | ||
87 | def DrawJoystick(self, dc): | |
88 | # draw the guage as a maxed square in the center of this window. | |
89 | w, h = self.GetClientSize() | |
90 | edgeSize = min(w, h) | |
91 | ||
92 | xorigin = (w - edgeSize) / 2 | |
93 | yorigin = (h - edgeSize) / 2 | |
94 | center = edgeSize / 2 | |
95 | ||
96 | # Restrict our drawing activities to the square defined | |
97 | # above. | |
98 | dc.SetClippingRegion((xorigin, yorigin), (edgeSize, edgeSize)) | |
99 | ||
100 | # Optimize drawing a bit (for Win) | |
101 | dc.BeginDrawing() | |
102 | ||
103 | dc.SetBrush(wx.Brush(wx.Colour(251, 252, 237))) | |
104 | dc.DrawRectangle((xorigin, yorigin), (edgeSize, edgeSize)) | |
105 | ||
106 | dc.SetPen(wx.Pen(wx.BLACK, 1, wx.DOT_DASH)) | |
107 | ||
108 | dc.DrawLine((xorigin, yorigin + center), (xorigin + edgeSize, yorigin + center)) | |
109 | dc.DrawLine((xorigin + center, yorigin), (xorigin + center, yorigin + edgeSize)) | |
110 | ||
111 | if self.stick: | |
112 | # Get the joystick position as a float | |
113 | joyx = float(self.stick.GetPosition().x) | |
114 | joyy = float(self.stick.GetPosition().y) | |
115 | ||
116 | # Get the joystick range of motion | |
117 | xrange = self.stick.GetXMax() - self.stick.GetXMin() | |
118 | yrange = self.stick.GetYMax() - self.stick.GetYMin() | |
119 | ||
120 | # calc a ratio of our range versus the joystick range | |
121 | xratio = float(edgeSize) / xrange | |
122 | yratio = float(edgeSize) / yrange | |
123 | ||
124 | # calc the displayable value based on position times ratio | |
125 | xval = int(joyx * xratio) | |
126 | yval = int(joyy * xratio) | |
127 | ||
128 | # and normalize the value from our brush's origin | |
129 | x = xval + xorigin | |
130 | y = yval + yorigin | |
131 | ||
132 | # Now to draw it. | |
133 | dc.SetPen(wx.Pen(wx.RED, 2)) | |
134 | dc.CrossHair((x, y)) | |
135 | ||
136 | # Turn off drawing optimization | |
137 | dc.EndDrawing() | |
138 | ||
139 | ||
140 | def Update(self): | |
141 | dc = wx.BufferedDC(wx.ClientDC(self), self.buffer) | |
142 | self.DrawFace(dc) | |
143 | self.DrawJoystick(dc) | |
144 | ||
145 | ||
146 | #---------------------------------------------------------------------------- | |
147 | ||
148 | class JoyPanel(wx.Panel): | |
149 | def __init__(self, parent, stick): | |
150 | ||
151 | self.stick = stick | |
152 | ||
153 | wx.Panel.__init__(self, parent, -1) | |
154 | ||
155 | sizer = wx.BoxSizer(wx.VERTICAL) | |
156 | ||
157 | fn = wx.Font( | |
158 | parent.GetFont().GetPointSize() + 3, | |
159 | parent.GetFont().GetFamily(), | |
160 | parent.GetFont().GetStyle(), | |
161 | wx.BOLD | |
162 | ) | |
163 | ||
164 | t = wx.StaticText(self, -1, "X - Y Axes", style = wx.ALIGN_CENTRE) | |
165 | t.SetFont(fn) | |
166 | sizer.Add(t, 0, wx.ALL | wx.EXPAND | wx.ALIGN_CENTER | wx.ALIGN_CENTER_HORIZONTAL, 1) | |
167 | ||
168 | self.control = JoyGauge(self, self.stick) | |
169 | sizer.Add(self.control, 1, wx.ALL | wx.EXPAND | wx.ALIGN_CENTER | wx.ALIGN_CENTER_HORIZONTAL, 1) | |
170 | ||
171 | self.SetSizer(sizer) | |
172 | sizer.Fit(self) | |
173 | ||
174 | def Update(self): | |
175 | self.control.Update() | |
ac346f50 | 176 | |
ac346f50 | 177 | |
2421eb82 RD |
178 | #---------------------------------------------------------------------------- |
179 | ||
180 | class POVGauge(wx.Panel): | |
181 | # | |
182 | # Display the current postion of the POV control | |
183 | # | |
184 | def __init__(self, parent, stick): | |
185 | ||
186 | self.stick = stick | |
187 | self.size = (100, 100) | |
188 | self.avail = False | |
189 | self.fourDir = False | |
190 | self.cts = False | |
191 | ||
192 | wx.Panel.__init__(self, parent, -1, size=self.size) | |
193 | ||
194 | self.Bind(wx.EVT_PAINT, self.OnPaint) | |
195 | self.Bind(wx.EVT_SIZE, self.OnSize) | |
196 | self.Bind(wx.EVT_ERASE_BACKGROUND, lambda e: None) | |
197 | ||
198 | self.buffer = wx.EmptyBitmap(*self.size) | |
199 | dc = wx.BufferedDC(None, self.buffer) | |
200 | self.DrawFace(dc) | |
201 | self.DrawPOV(dc) | |
202 | ||
203 | ||
204 | def OnSize(self, event): | |
205 | # calculate the size of our display and make a buffer for it. | |
206 | w, h = self.GetClientSize() | |
207 | s = min(w, h) | |
208 | self.size = (s, s) | |
209 | self.buffer = wx.EmptyBitmap(w,h) | |
210 | dc = wx.BufferedDC(wx.ClientDC(self), self.buffer) | |
211 | self.DrawFace(dc) | |
212 | self.DrawPOV(dc) | |
213 | ||
214 | ||
215 | def DrawFace(self, dc): | |
216 | dc.SetBackground(wx.Brush(self.GetBackgroundColour())) | |
217 | dc.Clear() | |
218 | ||
219 | ||
220 | def OnPaint(self, evt): | |
221 | # When dc is destroyed it will blit self.buffer to the window, | |
222 | # since no other drawing is needed we'll just return and let it | |
223 | # do it's thing | |
224 | dc = wx.BufferedPaintDC(self, self.buffer) | |
225 | ||
226 | ||
227 | def DrawPOV(self, dc): | |
228 | # draw the guage as a maxed circle in the center of this window. | |
229 | w, h = self.GetClientSize() | |
230 | diameter = min(w, h) | |
231 | ||
232 | xorigin = (w - diameter) / 2 | |
233 | yorigin = (h - diameter) / 2 | |
234 | xcenter = xorigin + diameter / 2 | |
235 | ycenter = yorigin + diameter / 2 | |
236 | ||
237 | # Optimize drawing a bit (for Win) | |
238 | dc.BeginDrawing() | |
239 | ||
240 | # our 'raster'. | |
241 | dc.SetBrush(wx.Brush(wx.WHITE)) | |
242 | dc.DrawCircle((xcenter, ycenter), diameter/2) | |
243 | dc.SetBrush(wx.Brush(wx.BLACK)) | |
244 | dc.DrawCircle((xcenter, ycenter), 10) | |
245 | ||
246 | # fancy decorations | |
247 | dc.SetPen(wx.Pen(wx.BLACK, 1, wx.DOT_DASH)) | |
248 | dc.DrawLine((xorigin, ycenter), (xorigin + diameter, ycenter)) | |
249 | dc.DrawLine((xcenter, yorigin), (xcenter, yorigin + diameter)) | |
250 | ||
251 | if self.stick: | |
252 | if self.avail: | |
253 | ||
254 | pos = -1 | |
255 | ||
256 | # use the appropriate function to get the POV position | |
257 | if self.fourDir: | |
258 | pos = self.stick.GetPOVPosition() | |
259 | ||
260 | if self.cts: | |
261 | pos = self.stick.GetPOVCTSPosition() | |
262 | ||
263 | # trap invalid values | |
264 | if 0 <= pos <= 36000: | |
265 | vector = 30 | |
266 | else: | |
267 | vector = 0 | |
268 | ||
269 | # rotate CCW by 90 so that 0 is up. | |
270 | pos = (pos / 100) - 90 | |
271 | ||
272 | # Normalize | |
273 | if pos < 0: | |
274 | pos = pos + 360 | |
275 | ||
276 | # Stolen from wx.lib.analogclock :-) | |
277 | radiansPerDegree = math.pi / 180 | |
278 | pointX = int(round(vector * math.cos(pos * radiansPerDegree))) | |
279 | pointY = int(round(vector * math.sin(pos * radiansPerDegree))) | |
ac346f50 | 280 | |
2421eb82 RD |
281 | # normalise value to match our actual center. |
282 | nx = pointX + xcenter | |
283 | ny = pointY + ycenter | |
284 | ||
285 | # Draw the line | |
286 | dc.SetPen(wx.Pen(wx.BLUE, 2)) | |
287 | dc.DrawLine((xcenter, ycenter), (nx, ny)) | |
288 | ||
289 | # And a little thing to show the endpoint | |
290 | dc.SetBrush(wx.Brush(wx.BLUE)) | |
291 | dc.DrawCircle((nx, ny), 8) | |
292 | ||
293 | # Turn off drawing optimization | |
294 | dc.EndDrawing() | |
295 | ||
296 | ||
297 | def Update(self): | |
298 | dc = wx.BufferedDC(wx.ClientDC(self), self.buffer) | |
299 | self.DrawFace(dc) | |
300 | self.DrawPOV(dc) | |
301 | ||
302 | ||
303 | def Calibrate(self): | |
304 | s = self.stick | |
305 | self.avail = s.HasPOV() | |
306 | self.fourDir = s.HasPOV4Dir() | |
307 | self.cts = s.HasPOVCTS() | |
308 | ||
309 | ||
310 | #---------------------------------------------------------------------------- | |
311 | ||
312 | class POVStatus(wx.Panel): | |
313 | # | |
314 | # Displays static info about the POV control | |
315 | # | |
316 | def __init__(self, parent, stick): | |
317 | ||
318 | self.stick = stick | |
319 | ||
320 | wx.Panel.__init__(self, parent, -1, size=(100, 100)) | |
321 | ||
322 | sizer = wx.BoxSizer(wx.VERTICAL) | |
323 | sizer.Add((20,20)) | |
324 | ||
325 | self.avail = wx.CheckBox(self, -1, "Available") | |
326 | sizer.Add(self.avail, 0, wx.ALL | wx.EXPAND | wx.ALIGN_LEFT, 2) | |
327 | ||
328 | self.fourDir = wx.CheckBox(self, -1, "4-Way Only") | |
329 | sizer.Add(self.fourDir, 0, wx.ALL | wx.EXPAND | wx.ALIGN_LEFT, 2) | |
330 | ||
331 | self.cts = wx.CheckBox(self, -1, "Continuous") | |
332 | sizer.Add(self.cts, 0, wx.ALL | wx.EXPAND | wx.ALIGN_LEFT, 2) | |
333 | ||
334 | self.SetSizer(sizer) | |
335 | sizer.Fit(self) | |
336 | ||
337 | # Effectively makes the checkboxes read-only. | |
338 | self.Bind(wx.EVT_CHECKBOX, self.Calibrate) | |
339 | ||
340 | ||
341 | def Calibrate(self, evt=None): | |
ac346f50 | 342 | s = self.stick |
2421eb82 RD |
343 | self.avail.SetValue(s.HasPOV()) |
344 | self.fourDir.SetValue(s.HasPOV4Dir()) | |
345 | self.cts.SetValue(s.HasPOVCTS()) | |
ac346f50 RD |
346 | |
347 | ||
2421eb82 | 348 | #---------------------------------------------------------------------------- |
ac346f50 | 349 | |
2421eb82 RD |
350 | class POVPanel(wx.Panel): |
351 | def __init__(self, parent, stick): | |
ac346f50 | 352 | |
2421eb82 | 353 | self.stick = stick |
ac346f50 | 354 | |
2421eb82 | 355 | wx.Panel.__init__(self, parent, -1, size=(100, 100)) |
ac346f50 | 356 | |
2421eb82 RD |
357 | sizer = wx.BoxSizer(wx.HORIZONTAL) |
358 | gsizer = wx.BoxSizer(wx.VERTICAL) | |
ac346f50 | 359 | |
2421eb82 RD |
360 | sizer.Add((25,25)) |
361 | ||
362 | fn = wx.Font( | |
363 | parent.GetFont().GetPointSize() + 3, | |
364 | parent.GetFont().GetFamily(), | |
365 | parent.GetFont().GetStyle(), | |
366 | wx.BOLD | |
367 | ) | |
368 | t = wx.StaticText(self, -1, "POV Control", style = wx.ALIGN_CENTER) | |
369 | t.SetFont(fn) | |
370 | gsizer.Add(t, 0, wx.ALL | wx.EXPAND, 1) | |
371 | ||
372 | self.display = POVGauge(self, stick) | |
373 | gsizer.Add(self.display, 1, wx.ALL | wx.EXPAND | wx.ALIGN_CENTER, 1) | |
374 | sizer.Add(gsizer, 1, wx.ALL | wx.EXPAND | wx.ALIGN_CENTER, 1) | |
375 | ||
376 | self.status = POVStatus(self, stick) | |
377 | sizer.Add(self.status, 1, wx.ALL | wx.EXPAND | wx.ALIGN_CENTER, 1) | |
ac346f50 | 378 | |
2421eb82 RD |
379 | self.SetSizer(sizer) |
380 | sizer.Fit(self) | |
ac346f50 | 381 | |
ac346f50 | 382 | |
2421eb82 RD |
383 | def Calibrate(self): |
384 | self.display.Calibrate() | |
385 | self.status.Calibrate() | |
ac346f50 | 386 | |
ac346f50 | 387 | |
2421eb82 RD |
388 | def Update(self): |
389 | self.display.Update() | |
ac346f50 | 390 | |
ac346f50 | 391 | |
2421eb82 | 392 | #---------------------------------------------------------------------------- |
ac346f50 | 393 | |
2421eb82 RD |
394 | class LED(wx.Panel): |
395 | def __init__(self, parent, number): | |
ac346f50 | 396 | |
2421eb82 RD |
397 | self.state = -1 |
398 | self.size = (20, 20) | |
399 | self.number = number | |
ac346f50 | 400 | |
2421eb82 RD |
401 | self.fn = wx.Font( |
402 | parent.GetFont().GetPointSize() - 1, | |
403 | parent.GetFont().GetFamily(), | |
404 | parent.GetFont().GetStyle(), | |
405 | wx.BOLD | |
406 | ) | |
ac346f50 | 407 | |
2421eb82 | 408 | wx.Panel.__init__(self, parent, -1, size=self.size) |
ac346f50 | 409 | |
2421eb82 RD |
410 | self.Bind(wx.EVT_PAINT, self.OnPaint) |
411 | self.Bind(wx.EVT_SIZE, self.OnSize) | |
412 | self.Bind(wx.EVT_ERASE_BACKGROUND, lambda e: None) | |
ac346f50 | 413 | |
2421eb82 RD |
414 | self.buffer = wx.EmptyBitmap(*self.size) |
415 | dc = wx.BufferedDC(None, self.buffer) | |
416 | self.DrawFace(dc) | |
417 | self.DrawLED(dc) | |
ac346f50 | 418 | |
ac346f50 | 419 | |
2421eb82 RD |
420 | def OnSize(self, event): |
421 | # calculate the size of our display. | |
422 | w, h = self.GetClientSize() | |
423 | s = min(w, h) | |
424 | self.size = (s, s) | |
425 | self.buffer = wx.EmptyBitmap(*self.size) | |
426 | dc = wx.BufferedDC(wx.ClientDC(self), self.buffer) | |
427 | self.DrawFace(dc) | |
428 | self.DrawLED(dc) | |
ac346f50 | 429 | |
ac346f50 | 430 | |
2421eb82 RD |
431 | def DrawFace(self, dc): |
432 | dc.SetBackground(wx.Brush(self.GetBackgroundColour())) | |
433 | dc.Clear() | |
ac346f50 | 434 | |
ac346f50 | 435 | |
2421eb82 RD |
436 | def OnPaint(self, evt): |
437 | # When dc is destroyed it will blit self.buffer to the window, | |
438 | # since no other drawing is needed we'll just return and let it | |
439 | # do it's thing | |
440 | dc = wx.BufferedPaintDC(self, self.buffer) | |
ac346f50 | 441 | |
ac346f50 | 442 | |
2421eb82 RD |
443 | def DrawLED(self, dc): |
444 | # bitmap size | |
445 | bw, bh = self.size | |
ac346f50 | 446 | |
2421eb82 RD |
447 | # center of bitmap |
448 | center = bw / 2 | |
ac346f50 | 449 | |
2421eb82 RD |
450 | # calc the 0, 0 origin of the bitmap |
451 | xorigin = center - (bw / 2) | |
452 | yorigin = center - (bh / 2) | |
ac346f50 | 453 | |
2421eb82 RD |
454 | # Optimize drawing a bit (for Win) |
455 | dc.BeginDrawing() | |
ac346f50 | 456 | |
2421eb82 RD |
457 | # our 'raster'. |
458 | if self.state == 0: | |
459 | dc.SetBrush(wx.Brush(wx.RED)) | |
460 | elif self.state == 1: | |
461 | dc.SetBrush(wx.Brush(wx.GREEN)) | |
462 | else: | |
463 | dc.SetBrush(wx.Brush(wx.BLACK)) | |
ac346f50 | 464 | |
2421eb82 | 465 | dc.DrawCircle((center, center), bw/2) |
ac346f50 | 466 | |
2421eb82 | 467 | txt = str(self.number) |
ac346f50 | 468 | |
2421eb82 RD |
469 | # Set the font for the DC ... |
470 | dc.SetFont(self.fn) | |
471 | # ... and calculate how much space our value | |
472 | # will take up. | |
473 | fw, fh = dc.GetTextExtent(txt) | |
ac346f50 | 474 | |
2421eb82 RD |
475 | # Calc the center of the LED, and from that |
476 | # derive the origin of our value. | |
477 | tx = center - (fw/2) | |
478 | ty = center - (fh/2) | |
ac346f50 | 479 | |
2421eb82 RD |
480 | # I draw the value twice so as to give it a pseudo-shadow. |
481 | # This is (mostly) because I'm too lazy to figure out how | |
482 | # to blit my text onto the gauge using one of the logical | |
483 | # functions. The pseudo-shadow gives the text contrast | |
484 | # regardless of whether the bar is under it or not. | |
485 | dc.SetTextForeground(wx.WHITE) | |
486 | dc.DrawText(txt, (tx, ty)) | |
ac346f50 | 487 | |
2421eb82 RD |
488 | # Turn off drawing optimization |
489 | dc.EndDrawing() | |
ac346f50 | 490 | |
ac346f50 | 491 | |
2421eb82 RD |
492 | def Update(self): |
493 | dc = wx.BufferedDC(wx.ClientDC(self), self.buffer) | |
494 | self.DrawFace(dc) | |
495 | self.DrawLED(dc) | |
ac346f50 | 496 | |
ac346f50 | 497 | |
2421eb82 | 498 | #---------------------------------------------------------------------------- |
ac346f50 | 499 | |
2421eb82 RD |
500 | class JoyButtons(wx.Panel): |
501 | def __init__(self, parent, stick): | |
ac346f50 | 502 | |
2421eb82 RD |
503 | self.stick = stick |
504 | self.leds = {} | |
ac346f50 | 505 | |
2421eb82 RD |
506 | wx.Panel.__init__(self, parent, -1) |
507 | ||
508 | tsizer = wx.BoxSizer(wx.VERTICAL) | |
509 | ||
510 | fn = wx.Font( | |
511 | parent.GetFont().GetPointSize() + 3, | |
512 | parent.GetFont().GetFamily(), | |
513 | parent.GetFont().GetStyle(), | |
514 | wx.BOLD | |
515 | ) | |
516 | ||
517 | t = wx.StaticText(self, -1, "Buttons", style = wx.ALIGN_LEFT) | |
518 | t.SetFont(fn) | |
519 | tsizer.Add(t, 0, wx.ALL | wx.EXPAND | wx.ALIGN_LEFT, 1) | |
520 | ||
521 | sizer = wx.FlexGridSizer(4, 16, 2, 2) | |
522 | ||
523 | fn.SetPointSize(parent.GetFont().GetPointSize() + 1) | |
524 | ||
525 | for i in range(0, MAX_BUTTONS): | |
526 | t = LED(self, i) | |
527 | self.leds[i] = t | |
528 | sizer.Add(t, 1, wx.ALL|wx.ALIGN_CENTER|wx.ALIGN_CENTER_VERTICAL, 1) | |
529 | sizer.AddGrowableCol(i) | |
530 | ||
531 | tsizer.Add(sizer, 1, wx.ALL | wx.EXPAND | wx.ALIGN_LEFT, 1) | |
532 | ||
533 | self.SetSizer(tsizer) | |
534 | tsizer.Fit(self) | |
535 | ||
536 | def Calibrate(self): | |
537 | for i in range(0, MAX_BUTTONS): | |
538 | self.leds[i].state = -1 | |
539 | ||
540 | t = self.stick.GetNumberButtons() | |
541 | ||
542 | for i in range(0, t): | |
543 | self.leds[i].state = 0 | |
544 | ||
545 | def Update(self): | |
546 | t = self.stick.GetButtonState() | |
547 | ||
548 | for i in range(0, MAX_BUTTONS): | |
549 | if self.leds[i].state == 1: | |
550 | self.leds[i].state = 0 | |
551 | ||
552 | if (t & (1<<i)): | |
553 | self.leds[i].state = 1 | |
554 | ||
555 | self.leds[i].Update() | |
556 | ||
557 | ||
558 | #---------------------------------------------------------------------------- | |
559 | ||
560 | class InfoPanel(wx.Panel): | |
561 | def __init__(self, parent, stick): | |
562 | ||
563 | self.stick = stick | |
564 | ||
565 | wx.Panel.__init__(self, parent, -1) | |
566 | ||
567 | sizer = wx.GridBagSizer(1, 1) | |
568 | ||
569 | sizer.Add(Label(self, 'Mfr ID: '), (0, 0), (1, 1), wx.ALL | wx.GROW | wx.ALIGN_RIGHT, 2) | |
570 | self.MfgID = wx.TextCtrl(self, -1, value='', size=(45, -1), style=wx.TE_READONLY) | |
571 | sizer.Add(self.MfgID, (0, 1), (1, 1), wx.ALL | wx.GROW | wx.ALIGN_LEFT, 2) | |
572 | ||
573 | sizer.Add(Label(self, 'Prod Name: '), (0, 2), (1, 1), wx.ALL | wx.GROW | wx.ALIGN_RIGHT, 2) | |
574 | self.ProdName = wx.TextCtrl(self, -1, value='', style=wx.TE_READONLY) | |
575 | sizer.Add(self.ProdName, (0, 3), (1, 3), wx.ALL | wx.GROW | wx.ALIGN_LEFT, 2) | |
576 | ||
577 | sizer.Add(Label(self, 'Threshold: '), (0, 6), (1, 1), wx.ALL | wx.GROW | wx.ALIGN_RIGHT, 2) | |
578 | self.Threshold = wx.TextCtrl(self, -1, value='', size=(45, -1), style=wx.TE_READONLY) | |
579 | sizer.Add(self.Threshold, (0, 7), (1, 1), wx.ALL | wx.GROW | wx.ALIGN_LEFT, 2) | |
580 | ||
581 | #---------------------------------------------------------------------------- | |
582 | b = wx.Button(self, -1, "Calibrate") | |
583 | sizer.Add(b, (1, 0), (2, 2), wx.ALL | wx.ALIGN_CENTER, 2) | |
584 | ||
585 | sizer.Add(Label(self, '# of Sticks: '), (1, 2), (1, 1), wx.ALL | wx.GROW | wx.ALIGN_RIGHT, 2) | |
586 | self.NumJoysticks = wx.TextCtrl(self, -1, value='', size=(45, -1), style=wx.TE_READONLY) | |
587 | sizer.Add(self.NumJoysticks, (1, 3), (1, 1), wx.ALL | wx.GROW | wx.ALIGN_LEFT, 2) | |
588 | ||
589 | sizer.Add(Label(self, '# of Axes: '), (1, 4), (1, 1), wx.ALL | wx.GROW | wx.ALIGN_RIGHT, 2) | |
590 | self.NumAxis = wx.TextCtrl(self, -1, value='', size=(45, -1), style=wx.TE_READONLY) | |
591 | sizer.Add(self.NumAxis, (1, 5), (1, 1), wx.ALL | wx.GROW | wx.ALIGN_LEFT, 2) | |
592 | ||
593 | sizer.Add(Label(self, 'Max # Axes: '), (1, 6), (1, 1), wx.ALL | wx.GROW | wx.ALIGN_RIGHT, 2) | |
594 | self.MaxAxis = wx.TextCtrl(self, -1, value='', size=(45, -1), style=wx.TE_READONLY) | |
595 | sizer.Add(self.MaxAxis, (1, 7), (1, 1), wx.ALL | wx.GROW | wx.ALIGN_LEFT, 2) | |
596 | ||
597 | #---------------------------------------------------------------------------- | |
598 | ||
599 | sizer.Add(Label(self, 'Polling -- '), (2, 3), (1, 1), wx.ALL | wx.GROW, 2) | |
600 | ||
601 | sizer.Add(Label(self, 'Min: '), (2, 4), (1, 1), wx.ALL | wx.GROW | wx.ALIGN_RIGHT, 2) | |
602 | self.PollMin = wx.TextCtrl(self, -1, value='', size=(45, -1), style=wx.TE_READONLY) | |
603 | sizer.Add(self.PollMin, (2, 5), (1, 1), wx.ALL | wx.GROW | wx.ALIGN_LEFT, 2) | |
604 | ||
605 | sizer.Add(Label(self, 'Max: '), (2, 6), (1, 1), wx.ALL | wx.GROW | wx.ALIGN_RIGHT, 2) | |
606 | self.PollMax = wx.TextCtrl(self, -1, value='', size=(45, -1), style=wx.TE_READONLY) | |
607 | sizer.Add(self.PollMax, (2, 7), (1, 1), wx.ALL | wx.GROW | wx.ALIGN_LEFT, 2) | |
608 | ||
609 | #---------------------------------------------------------------------------- | |
610 | ||
611 | self.SetSizer(sizer) | |
612 | sizer.Fit(self) | |
613 | ||
614 | ||
615 | def Calibrate(self): | |
616 | if not self.stick: | |
617 | return | |
618 | ||
619 | s = self.stick | |
620 | ||
621 | self.MfgID.SetValue(str(s.GetManufacturerId())) | |
622 | self.ProdName.SetValue(str(s.GetProductName())) | |
623 | self.Threshold.SetValue(str(s.GetMovementThreshold())) | |
624 | self.NumJoysticks.SetValue(str(s.GetNumberJoysticks())) | |
625 | self.NumAxis.SetValue(str(s.GetNumberAxes())) | |
626 | self.MaxAxis.SetValue(str(s.GetMaxAxes())) | |
627 | self.PollMin.SetValue(str(s.GetPollingMin())) | |
628 | self.PollMax.SetValue(str(s.GetPollingMax())) | |
629 | ||
630 | ||
631 | #---------------------------------------------------------------------------- | |
632 | ||
633 | class AxisBar(wx.Gauge): | |
634 | # | |
635 | # This class allows us to use a wx.Gauge to display the axis value | |
636 | # with a fancy label overlayed onto the guage itself. Two values are | |
637 | # used to do things: first of all, since the gauge is limited to | |
638 | # positive numbers, the scale is fixed at 0 to 1000. We will receive | |
639 | # an adjusted value to use to render the gauge itself. The other value | |
640 | # is a raw value and actually reflects the value from the joystick itself, | |
641 | # which is then drawn over the gauge. | |
642 | # | |
643 | def __init__(self, parent): | |
644 | wx.Gauge.__init__(self, parent, -1, 1000, size=(-1, 20), style = wx.GA_HORIZONTAL | wx.GA_SMOOTH ) | |
645 | ||
646 | # This is the value we will display. | |
647 | self.rawvalue = 0 | |
648 | ||
649 | self.SetBackgroundColour('light blue') | |
650 | self.SetForegroundColour('orange') | |
651 | ||
652 | # Capture paint events for purpose of updating | |
653 | # the displayed value. | |
654 | self.Bind(wx.EVT_PAINT, self.onPaint) | |
655 | ||
656 | def Update(self, value, rawvalue): | |
657 | # Updates the gauge itself, sets the raw value for | |
658 | # the next EVT_PAINT | |
659 | self.SetValue(value) | |
660 | self.rawvalue = rawvalue | |
661 | ||
662 | def onPaint(self, evt): | |
663 | # Must always create a PaintDC when capturing | |
664 | # an EVT_PAINT event | |
665 | self.ShowValue(wx.PaintDC(self), evt) | |
666 | ||
667 | def ShowValue(self, dc, evt): | |
668 | # This method handles actual painting of and drawing | |
669 | # on the gauge. | |
670 | ||
671 | # Clear out the gauge | |
672 | dc.Clear() | |
673 | # and then carry out business as usual | |
674 | wx.Gauge.OnPaint(self, evt) | |
675 | ||
676 | # This is the size available to us. | |
677 | w, h = dc.GetSize() | |
678 | ||
679 | # This is what we will overlay on the gauge. | |
680 | # It reflects the actual value received from the | |
681 | # wx.Joystick. | |
682 | txt = str(self.rawvalue) | |
683 | ||
684 | # Copy the default font, make it bold. | |
685 | fn = wx.Font( | |
686 | self.GetFont().GetPointSize(), | |
687 | self.GetFont().GetFamily(), | |
688 | self.GetFont().GetStyle(), | |
689 | wx.BOLD | |
690 | ) | |
691 | ||
692 | # Set the font for the DC ... | |
693 | dc.SetFont(fn) | |
694 | # ... and calculate how much space our value | |
695 | # will take up. | |
696 | fw, fh = dc.GetTextExtent(txt) | |
697 | ||
698 | # Calc the center of the gauge, and from that | |
699 | # derive the origin of our value. | |
700 | center = w / 2 | |
701 | tx = center - (fw/2) | |
702 | ||
703 | center = h / 2 | |
704 | ty = center - (fh/2) | |
705 | ||
706 | # I draw the value twice so as to give it a pseudo-shadow. | |
707 | # This is (mostly) because I'm too lazy to figure out how | |
708 | # to blit my text onto the gauge using one of the logical | |
709 | # functions. The pseudo-shadow gives the text contrast | |
710 | # regardless of whether the bar is under it or not. | |
711 | dc.SetTextForeground(wx.BLACK) | |
712 | dc.DrawText(txt, (tx, ty)) | |
713 | ||
714 | dc.SetTextForeground('white') | |
715 | dc.DrawText(txt, (tx-1, ty-1)) | |
716 | ||
717 | ||
718 | #---------------------------------------------------------------------------- | |
719 | ||
720 | class Axis(wx.Panel): | |
721 | # | |
722 | # This class is a container for the min, max, and current | |
723 | # values of the joystick axis in question. It contains | |
724 | # also special features to render a 'dummy' if the axis | |
725 | # in question is not available. | |
726 | # | |
727 | def __init__(self, parent, token, stick): | |
728 | ||
729 | self.stick = stick | |
730 | ||
731 | # | |
732 | # token represents the type of axis we're displaying. | |
733 | # | |
734 | self.token = token | |
735 | ||
736 | # | |
737 | # Create a call to the 'Has*()' method for the stick. | |
738 | # X and Y are always there, so we tie the Has* method | |
739 | # to a hardwired True value. | |
740 | # | |
741 | if token not in ['X', 'Y']: | |
742 | self.HasFunc = eval('stick.Has%s' % token) | |
743 | else: | |
744 | self.HasFunc = self.alwaysTrue | |
745 | ||
746 | # Now init the panel. | |
747 | wx.Panel.__init__(self, parent, -1) | |
748 | ||
749 | sizer = wx.BoxSizer(wx.HORIZONTAL) | |
750 | ||
751 | if self.HasFunc(): | |
752 | # | |
753 | # Tie our calibration functions to the appropriate | |
754 | # stick method. If we don't have the axis in question, | |
755 | # we won't need them. | |
756 | # | |
757 | self.GetMin = eval('stick.Get%sMin' % token) | |
758 | self.GetMax = eval('stick.Get%sMax' % token) | |
759 | ||
760 | # Create our displays and set them up. | |
761 | self.Min = wx.StaticText(self, -1, str(self.GetMin()), | |
762 | size=(40,-1), style=wx.ALIGN_RIGHT | wx.ST_NO_AUTORESIZE) | |
763 | self.Max = wx.StaticText(self, -1, str(self.GetMax()), | |
764 | size=(40,-1), style=wx.ALIGN_LEFT | wx.ST_NO_AUTORESIZE) | |
765 | self.bar = AxisBar(self) | |
766 | ||
767 | sizer.Add(self.Min, 0, wx.ALL | wx.ALIGN_RIGHT | wx.ALIGN_CENTER_VERTICAL, 1) | |
768 | sizer.Add(self.bar, 1, wx.ALL | wx.ALIGN_CENTER | wx.ALIGN_CENTER_VERTICAL, 1) | |
769 | sizer.Add(self.Max, 0, wx.ALL | wx.ALIGN_LEFT | wx.ALIGN_CENTER_VERTICAL, 1) | |
770 | ||
771 | else: | |
772 | # We go here if the axis in question is not available. | |
773 | self.control = wx.StaticText(self, -1, ' *** Not Present ***') | |
774 | sizer.Add(self.control, 1, wx.ALL | wx.ALIGN_CENTER | wx.ALIGN_CENTER_VERTICAL, 1) | |
775 | ||
776 | #---------------------------------------------------------------------------- | |
777 | ||
778 | self.SetSizer(sizer) | |
779 | sizer.Fit(self) | |
780 | wx.CallAfter(self.Update) | |
781 | ||
782 | ||
783 | def Calibrate(self): | |
784 | if not self.HasFunc(): | |
785 | return | |
786 | ||
787 | self.Min.SetLabel(str(self.GetMin())) | |
788 | self.Max.SetLabel(str(self.GetMax())) | |
789 | ||
790 | ||
791 | def Update(self): | |
792 | # Don't bother if the axis doesn't exist. | |
793 | if not self.HasFunc(): | |
794 | return | |
795 | ||
796 | min = int(self.Min.GetLabel()) | |
797 | max = int(self.Max.GetLabel()) | |
798 | ||
799 | # | |
800 | # Not all values are available from a wx.JoystickEvent, so I've elected | |
801 | # to not use it at all. Therefore, we are getting our values direct from | |
802 | # the stick. These values also seem to be more stable and reliable than | |
803 | # those received from the event itself, so maybe it's a good idea to | |
804 | # use the stick directly for your program. | |
805 | # | |
806 | # Here we either select the appropriate member of stick.GetPosition() or | |
807 | # apply the appropriate Get*Position method call. | |
808 | # | |
809 | if self.token == 'X': | |
810 | val = self.stick.GetPosition().x | |
811 | elif self.token == 'Y': | |
812 | val = self.stick.GetPosition().y | |
813 | else: | |
814 | val = eval('self.stick.Get%sPosition()' % self.token) | |
815 | ||
816 | # | |
817 | # While we might be able to rely on a range of 0-FFFFFF on Win, that might | |
818 | # not be true of all drivers on all platforms. Thus, calc the actual full | |
819 | # range first. | |
820 | # | |
821 | range = float(max - min) | |
822 | ||
823 | # | |
824 | # The relative value is used by the derived wx.Gauge since it is a | |
825 | # positive-only control. | |
826 | # | |
827 | relative = 0 | |
828 | if range: | |
829 | relative = int(val / range * 1000) | |
830 | ||
831 | # | |
832 | # Pass both the raw and relative values to the derived Gauge | |
833 | # | |
834 | self.bar.Update(relative, val) | |
835 | ||
836 | ||
837 | def alwaysTrue(self): | |
838 | # a dummy method used for X and Y axis. | |
839 | return True | |
840 | ||
841 | ||
842 | #---------------------------------------------------------------------------- | |
843 | ||
844 | class AxisPanel(wx.Panel): | |
845 | # | |
846 | # Contained herein is a panel that offers a graphical display | |
847 | # of the levels for all axes supported by wx.Joystick. If | |
848 | # your system doesn't have a particular axis, it will be | |
849 | # 'dummied' for transparent use. | |
850 | # | |
851 | def __init__(self, parent, stick): | |
852 | ||
853 | self.stick = stick | |
854 | ||
855 | # Defines labels and 'tokens' to identify each | |
856 | # supporte axis. | |
857 | axesList = [ | |
858 | ('X Axis ', 'X'), ('Y Axis ', 'Y'), | |
859 | ('Z Axis ', 'Z'), ('Rudder ', 'Rudder'), | |
860 | ('U Axis ', 'U'), ('V Axis ', 'V') | |
861 | ] | |
862 | ||
863 | # Contains a list of all axis initialized. | |
864 | self.axes = [] | |
865 | ||
866 | wx.Panel.__init__(self, parent, -1) | |
867 | ||
868 | sizer = wx.FlexGridSizer(3, 4, 1, 1) | |
869 | sizer.AddGrowableCol(1) | |
870 | sizer.AddGrowableCol(3) | |
871 | ||
872 | #---------------------------------------------------------------------------- | |
873 | ||
874 | # Go through the list of labels and tokens and add a label and | |
875 | # axis display to the sizer for each. | |
876 | for label, token in axesList: | |
877 | sizer.Add(Label(self, label), 0, wx.ALL | wx.ALIGN_RIGHT, 2) | |
878 | t = Axis(self, token, self.stick) | |
879 | self.axes.append(t) | |
880 | sizer.Add(t, 1, wx.ALL | wx.EXPAND | wx.ALIGN_LEFT, 2) | |
881 | ||
882 | #---------------------------------------------------------------------------- | |
883 | ||
884 | self.SetSizer(sizer) | |
885 | sizer.Fit(self) | |
886 | wx.CallAfter(self.Update) | |
887 | ||
888 | def Calibrate(self): | |
889 | for i in self.axes: | |
890 | i.Calibrate() | |
891 | ||
892 | def Update(self): | |
893 | for i in self.axes: | |
894 | i.Update() | |
895 | ||
896 | ||
897 | #---------------------------------------------------------------------------- | |
898 | ||
899 | class JoystickDemoPanel(wx.Panel): | |
900 | ||
901 | def __init__(self, parent, log): | |
902 | ||
903 | self.log = log | |
904 | ||
905 | wx.Panel.__init__(self, parent, -1) | |
906 | ||
907 | # Try to grab the control. If we get it, capture the stick. | |
908 | # Otherwise, throw up an exception message and play stupid. | |
909 | try: | |
910 | self.stick = wx.Joystick() | |
911 | self.stick.SetCapture(self) | |
912 | # Calibrate our controls | |
913 | wx.CallAfter(self.Calibrate) | |
914 | wx.CallAfter(self.OnJoystick) | |
915 | except NotImplementedError, v: | |
916 | wx.MessageBox(str(v), "Exception Message") | |
917 | self.stick = None | |
918 | ||
919 | # One Sizer to Rule Them All... | |
920 | sizer = wx.GridBagSizer(2,2) | |
921 | ||
922 | self.info = InfoPanel(self, self.stick) | |
923 | sizer.Add(self.info, (0, 0), (1, 3), wx.ALL | wx.GROW, 2) | |
924 | ||
925 | self.info.Bind(wx.EVT_BUTTON, self.Calibrate) | |
926 | ||
927 | self.joy = JoyPanel(self, self.stick) | |
928 | sizer.Add(self.joy, (1, 0), (1, 1), wx.ALL | wx.GROW, 2) | |
929 | ||
930 | self.pov = POVPanel(self, self.stick) | |
931 | sizer.Add(self.pov, (1, 1), (1, 2), wx.ALL | wx.GROW, 2) | |
932 | ||
933 | self.axes = AxisPanel(self, self.stick) | |
934 | sizer.Add(self.axes, (2, 0), (1, 3), wx.ALL | wx.GROW, 2) | |
935 | ||
936 | self.buttons = JoyButtons(self, self.stick) | |
937 | sizer.Add(self.buttons, (3, 0), (1, 3), wx.ALL | wx.EXPAND | wx.ALIGN_CENTER | wx.ALIGN_CENTER_VERTICAL, 1) | |
938 | ||
939 | self.SetSizer(sizer) | |
940 | sizer.Fit(self) | |
941 | ||
942 | # Capture Joystick events (if they happen) | |
943 | self.Bind(wx.EVT_JOYSTICK_EVENTS, self.OnJoystick) | |
944 | ||
945 | self.stick.SetMovementThreshold(10) | |
946 | ||
947 | def Calibrate(self, evt=None): | |
948 | # Do not try this without a stick | |
949 | if not self.stick: | |
950 | return | |
951 | ||
952 | self.info.Calibrate() | |
953 | self.axes.Calibrate() | |
954 | self.pov.Calibrate() | |
955 | self.buttons.Calibrate() | |
956 | ||
957 | def OnJoystick(self, evt=None): | |
958 | if not self.stick: | |
959 | return | |
960 | ||
961 | self.axes.Update() | |
962 | self.joy.Update() | |
963 | self.pov.Update() | |
964 | self.buttons.Update() | |
965 | ||
966 | ||
967 | #---------------------------------------------------------------------------- | |
ac346f50 RD |
968 | |
969 | def runTest(frame, nb, log): | |
08b89fa8 RD |
970 | if haveJoystick: |
971 | win = JoystickDemoPanel(nb, log) | |
972 | return win | |
973 | else: | |
974 | dlg = wx.MessageDialog( | |
975 | frame, 'wx.Joystick is not available on this platform.', | |
976 | 'Sorry', wx.OK | wx.ICON_INFORMATION | |
977 | ) | |
978 | dlg.ShowModal() | |
979 | dlg.Destroy() | |
980 | ||
ac346f50 | 981 | |
2421eb82 | 982 | #---------------------------------------------------------------------------- |
ac346f50 RD |
983 | |
984 | overview = """\ | |
05fd1c9e RD |
985 | <html> |
986 | <body> | |
987 | <h1>wx.Joystick</h1> | |
988 | This demo illustrates the use of the wx.Joystick class, which is an interface to | |
989 | one or more joysticks attached to your system. | |
990 | ||
991 | <p>The data that can be retrieved from the joystick comes in four basic flavors. | |
992 | All of these are illustrated in the demo. In fact, this demo illustrates everything | |
993 | you <b>can</b> get from the wx.Joystick control. | |
994 | ||
995 | <ul> | |
996 | <li>Static information such as Manufacturer ID and model name, | |
997 | <li>Analog input from up to six axes, including X and Y for the actual stick, | |
998 | <li>Button input from the fire button and any other buttons that the stick has, | |
999 | <li>and the POV control (a kind of mini-joystick on top of the joystick) that many sticks come with. | |
1000 | </ul> | |
1001 | ||
1002 | <p>Getting data from the joystick can be event-driven thanks to four event types associated | |
1003 | with wx.JoystickEvent, or the joystick can be polled programatically to get data on | |
1004 | a regular basis. | |
1005 | ||
1006 | <h2>Data types</h2> | |
1007 | ||
1008 | Data from the joystick comes in two flavors: that which defines the boundaries, and that | |
1009 | which defines the current state of the stick. Thus, we have Get*Max() and Get*Min() | |
1010 | methods for all axes, the max number of axes, the max number of buttons, and so on. In | |
1011 | general, this data can be read once and stored to speed computation up. | |
1012 | ||
1013 | <h3>Analog Input</h3> | |
1014 | ||
1015 | Analog input (the axes) is delivered as a whole, positive number. If you need to know | |
1016 | if the axis is at zero (centered) or not, you will first have to calculate that center | |
1017 | based on the max and min values. The demo shows a bar graph for each axis expressed | |
60d8ee39 RD |
1018 | in native numerical format, plus a 'centered' X-Y axis compass showing the relationship |
1019 | of that input to the calculated stick position. | |
05fd1c9e RD |
1020 | |
1021 | Analog input may be jumpy and spurious, so the control has a means of 'smoothing' the | |
1022 | analog data by setting a movement threshold. This demo sets the threshold to 10, but | |
1023 | you can set it at any valid value between the min and max. | |
1024 | ||
1025 | <h3>Button Input</h3> | |
1026 | ||
1027 | Button state is retrieved as one int that contains each button state mapped to a bit. | |
1028 | You get the state of a button by AND-ing its bit against the returned value, in the form | |
1029 | ||
1030 | <pre> | |
1031 | # assume buttonState is what the stick returned, and buttonBit | |
1032 | # is the bit you want to examine | |
1033 | ||
1034 | if (buttonState & ( 1 << buttonBit )) : | |
1035 | # button pressed, do something with it | |
1036 | </pre> | |
1037 | ||
1038 | <p>The problem here is that some OSs return a 32-bit value for up to 32 buttons | |
1039 | (imagine <i>that</i> stick!). Python V2.3 will generate an exception for bit | |
1040 | values over 30. For that reason, this demo is limited to 16 buttons. | |
1041 | ||
1042 | <p>Note that more than one button can be pressed at a time, so be sure to check all of them! | |
1043 | ||
1044 | ||
1045 | <h3>POV Input</h3> | |
1046 | ||
1047 | POV hats come in two flavors: four-way, and continuous. four-way POVs are restricted to | |
1048 | the cardinal points of the compass; continuous, or CTS POV hats can deliver input in | |
1049 | .01 degree increments, theoreticaly. The data is returned as a whole number; the last | |
60d8ee39 RD |
1050 | two digits are considered to be to the right of the decimal point, so in order to |
1051 | use this information, you need to divide by 100 right off the bat. | |
05fd1c9e RD |
1052 | |
1053 | <p>Different methods are provided to retrieve the POV data for a CTS hat | |
1054 | versus a four-way hat. | |
1055 | ||
1056 | <h2>Caveats</h2> | |
1057 | ||
1058 | The wx.Joystick control is in many ways incomplete at the C++ library level, but it is | |
1059 | not insurmountable. In short, while the joystick interface <i>can</i> be event-driven, | |
1060 | the wx.JoystickEvent class lacks event binders for all event types. Thus, you cannot | |
1061 | rely on wx.JoystickEvents to tell you when something has changed, necessarilly. | |
1062 | ||
1063 | <ul> | |
1064 | <li>There are no events associated with the POV control. | |
1065 | <li>There are no events associated with the Rudder | |
1066 | <li>There are no events associated with the U and V axes. | |
1067 | </ul> | |
1068 | ||
1069 | <p>Fortunately, there is an easy workaround. In the top level frame, create a wx.Timer | |
1070 | that will poll the stick at a set interval. Of course, if you do this, you might as | |
1071 | well forgo catching wxEVT_JOYSTICK_* events at all and rely on the timer to do the | |
1072 | polling. | |
1073 | ||
1074 | <p>Ideally, the timer should be a one-shot; after it fires, collect and process data as | |
1075 | needed, then re-start the timer, possibly using wx.CallAfter(). | |
1076 | ||
1077 | </body> | |
1078 | </html> | |
2421eb82 | 1079 | """ |
1fded56b | 1080 | |
2421eb82 | 1081 | #---------------------------------------------------------------------------- |
1fded56b RD |
1082 | |
1083 | if __name__ == '__main__': | |
1084 | import sys,os | |
1085 | import run | |
1086 | run.main(['', os.path.basename(sys.argv[0])]) |