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