#---------------------------------------------------------------------------- # Name: Joystick.py # Purpose: Demonstrate use of wx.Joystick # # Author: Jeff Grimmett (grimmtoo@softhome.net), adapted from original # .wdr-derived demo # # Created: 01/02/04 # RCS-ID: $Id$ # Copyright: # Licence: wxWindows license #---------------------------------------------------------------------------- # import math import wx #---------------------------------------------------------------------------- # For convenience spacer = (10, 10) MAX_BUTTONS = 16 #---------------------------------------------------------------------------- class Label(wx.StaticText): # A derived StaticText that always aligns right and renders # in a bold font. def __init__(self, parent, label): wx.StaticText.__init__(self, parent, -1, label, style=wx.ALIGN_RIGHT) self.SetFont( wx.Font( parent.GetFont().GetPointSize(), parent.GetFont().GetFamily(), parent.GetFont().GetStyle(), wx.BOLD )) #---------------------------------------------------------------------------- class JoyGauge(wx.Panel): def __init__(self, parent, stick): self.stick = stick size = (100,100) wx.Panel.__init__(self, parent, -1, size=size) self.Bind(wx.EVT_PAINT, self.OnPaint) self.Bind(wx.EVT_SIZE, self.OnSize) self.Bind(wx.EVT_ERASE_BACKGROUND, lambda e: None) self.buffer = wx.EmptyBitmap(*size) dc = wx.BufferedDC(None, self.buffer) self.DrawFace(dc) self.DrawJoystick(dc) def OnSize(self, event): # The face Bitmap init is done here, to make sure the buffer is always # the same size as the Window w, h = self.GetClientSize() self.buffer = wx.EmptyBitmap(w,h) dc = wx.BufferedDC(wx.ClientDC(self), self.buffer) self.DrawFace(dc) self.DrawJoystick(dc) def DrawFace(self, dc): dc.SetBackground(wx.Brush(self.GetBackgroundColour())) dc.Clear() def OnPaint(self, evt): # When dc is destroyed it will blit self.buffer to the window, # since no other drawing is needed we'll just return and let it # do it's thing dc = wx.BufferedPaintDC(self, self.buffer) def DrawJoystick(self, dc): # draw the guage as a maxed square in the center of this window. w, h = self.GetClientSize() edgeSize = min(w, h) xorigin = (w - edgeSize) / 2 yorigin = (h - edgeSize) / 2 center = edgeSize / 2 # Restrict our drawing activities to the square defined # above. dc.SetClippingRegion((xorigin, yorigin), (edgeSize, edgeSize)) # Optimize drawing a bit (for Win) dc.BeginDrawing() dc.SetBrush(wx.Brush(wx.Colour(251, 252, 237))) dc.DrawRectangle((xorigin, yorigin), (edgeSize, edgeSize)) dc.SetPen(wx.Pen(wx.BLACK, 1, wx.DOT_DASH)) dc.DrawLine((xorigin, yorigin + center), (xorigin + edgeSize, yorigin + center)) dc.DrawLine((xorigin + center, yorigin), (xorigin + center, yorigin + edgeSize)) if self.stick: # Get the joystick position as a float joyx = float(self.stick.GetPosition().x) joyy = float(self.stick.GetPosition().y) # Get the joystick range of motion xrange = self.stick.GetXMax() - self.stick.GetXMin() yrange = self.stick.GetYMax() - self.stick.GetYMin() # calc a ratio of our range versus the joystick range xratio = float(edgeSize) / xrange yratio = float(edgeSize) / yrange # calc the displayable value based on position times ratio xval = int(joyx * xratio) yval = int(joyy * xratio) # and normalize the value from our brush's origin x = xval + xorigin y = yval + yorigin # Now to draw it. dc.SetPen(wx.Pen(wx.RED, 2)) dc.CrossHair((x, y)) # Turn off drawing optimization dc.EndDrawing() def Update(self): dc = wx.BufferedDC(wx.ClientDC(self), self.buffer) self.DrawFace(dc) self.DrawJoystick(dc) #---------------------------------------------------------------------------- class JoyPanel(wx.Panel): def __init__(self, parent, stick): self.stick = stick wx.Panel.__init__(self, parent, -1) sizer = wx.BoxSizer(wx.VERTICAL) fn = wx.Font( parent.GetFont().GetPointSize() + 3, parent.GetFont().GetFamily(), parent.GetFont().GetStyle(), wx.BOLD ) t = wx.StaticText(self, -1, "X - Y Axes", style = wx.ALIGN_CENTRE) t.SetFont(fn) sizer.Add(t, 0, wx.ALL | wx.EXPAND | wx.ALIGN_CENTER | wx.ALIGN_CENTER_HORIZONTAL, 1) self.control = JoyGauge(self, self.stick) sizer.Add(self.control, 1, wx.ALL | wx.EXPAND | wx.ALIGN_CENTER | wx.ALIGN_CENTER_HORIZONTAL, 1) self.SetSizer(sizer) sizer.Fit(self) def Update(self): self.control.Update() #---------------------------------------------------------------------------- class POVGauge(wx.Panel): # # Display the current postion of the POV control # def __init__(self, parent, stick): self.stick = stick self.size = (100, 100) self.avail = False self.fourDir = False self.cts = False wx.Panel.__init__(self, parent, -1, size=self.size) self.Bind(wx.EVT_PAINT, self.OnPaint) self.Bind(wx.EVT_SIZE, self.OnSize) self.Bind(wx.EVT_ERASE_BACKGROUND, lambda e: None) self.buffer = wx.EmptyBitmap(*self.size) dc = wx.BufferedDC(None, self.buffer) self.DrawFace(dc) self.DrawPOV(dc) def OnSize(self, event): # calculate the size of our display and make a buffer for it. w, h = self.GetClientSize() s = min(w, h) self.size = (s, s) self.buffer = wx.EmptyBitmap(w,h) dc = wx.BufferedDC(wx.ClientDC(self), self.buffer) self.DrawFace(dc) self.DrawPOV(dc) def DrawFace(self, dc): dc.SetBackground(wx.Brush(self.GetBackgroundColour())) dc.Clear() def OnPaint(self, evt): # When dc is destroyed it will blit self.buffer to the window, # since no other drawing is needed we'll just return and let it # do it's thing dc = wx.BufferedPaintDC(self, self.buffer) def DrawPOV(self, dc): # draw the guage as a maxed circle in the center of this window. w, h = self.GetClientSize() diameter = min(w, h) xorigin = (w - diameter) / 2 yorigin = (h - diameter) / 2 xcenter = xorigin + diameter / 2 ycenter = yorigin + diameter / 2 # Optimize drawing a bit (for Win) dc.BeginDrawing() # our 'raster'. dc.SetBrush(wx.Brush(wx.WHITE)) dc.DrawCircle((xcenter, ycenter), diameter/2) dc.SetBrush(wx.Brush(wx.BLACK)) dc.DrawCircle((xcenter, ycenter), 10) # fancy decorations dc.SetPen(wx.Pen(wx.BLACK, 1, wx.DOT_DASH)) dc.DrawLine((xorigin, ycenter), (xorigin + diameter, ycenter)) dc.DrawLine((xcenter, yorigin), (xcenter, yorigin + diameter)) if self.stick: if self.avail: pos = -1 # use the appropriate function to get the POV position if self.fourDir: pos = self.stick.GetPOVPosition() if self.cts: pos = self.stick.GetPOVCTSPosition() # trap invalid values if 0 <= pos <= 36000: vector = 30 else: vector = 0 # rotate CCW by 90 so that 0 is up. pos = (pos / 100) - 90 # Normalize if pos < 0: pos = pos + 360 # Stolen from wx.lib.analogclock :-) radiansPerDegree = math.pi / 180 pointX = int(round(vector * math.cos(pos * radiansPerDegree))) pointY = int(round(vector * math.sin(pos * radiansPerDegree))) # normalise value to match our actual center. nx = pointX + xcenter ny = pointY + ycenter # Draw the line dc.SetPen(wx.Pen(wx.BLUE, 2)) dc.DrawLine((xcenter, ycenter), (nx, ny)) # And a little thing to show the endpoint dc.SetBrush(wx.Brush(wx.BLUE)) dc.DrawCircle((nx, ny), 8) # Turn off drawing optimization dc.EndDrawing() def Update(self): dc = wx.BufferedDC(wx.ClientDC(self), self.buffer) self.DrawFace(dc) self.DrawPOV(dc) def Calibrate(self): s = self.stick self.avail = s.HasPOV() self.fourDir = s.HasPOV4Dir() self.cts = s.HasPOVCTS() #---------------------------------------------------------------------------- class POVStatus(wx.Panel): # # Displays static info about the POV control # def __init__(self, parent, stick): self.stick = stick wx.Panel.__init__(self, parent, -1, size=(100, 100)) sizer = wx.BoxSizer(wx.VERTICAL) sizer.Add((20,20)) self.avail = wx.CheckBox(self, -1, "Available") sizer.Add(self.avail, 0, wx.ALL | wx.EXPAND | wx.ALIGN_LEFT, 2) self.fourDir = wx.CheckBox(self, -1, "4-Way Only") sizer.Add(self.fourDir, 0, wx.ALL | wx.EXPAND | wx.ALIGN_LEFT, 2) self.cts = wx.CheckBox(self, -1, "Continuous") sizer.Add(self.cts, 0, wx.ALL | wx.EXPAND | wx.ALIGN_LEFT, 2) self.SetSizer(sizer) sizer.Fit(self) # Effectively makes the checkboxes read-only. self.Bind(wx.EVT_CHECKBOX, self.Calibrate) def Calibrate(self, evt=None): s = self.stick self.avail.SetValue(s.HasPOV()) self.fourDir.SetValue(s.HasPOV4Dir()) self.cts.SetValue(s.HasPOVCTS()) #---------------------------------------------------------------------------- class POVPanel(wx.Panel): def __init__(self, parent, stick): self.stick = stick wx.Panel.__init__(self, parent, -1, size=(100, 100)) sizer = wx.BoxSizer(wx.HORIZONTAL) gsizer = wx.BoxSizer(wx.VERTICAL) sizer.Add((25,25)) fn = wx.Font( parent.GetFont().GetPointSize() + 3, parent.GetFont().GetFamily(), parent.GetFont().GetStyle(), wx.BOLD ) t = wx.StaticText(self, -1, "POV Control", style = wx.ALIGN_CENTER) t.SetFont(fn) gsizer.Add(t, 0, wx.ALL | wx.EXPAND, 1) self.display = POVGauge(self, stick) gsizer.Add(self.display, 1, wx.ALL | wx.EXPAND | wx.ALIGN_CENTER, 1) sizer.Add(gsizer, 1, wx.ALL | wx.EXPAND | wx.ALIGN_CENTER, 1) self.status = POVStatus(self, stick) sizer.Add(self.status, 1, wx.ALL | wx.EXPAND | wx.ALIGN_CENTER, 1) self.SetSizer(sizer) sizer.Fit(self) def Calibrate(self): self.display.Calibrate() self.status.Calibrate() def Update(self): self.display.Update() #---------------------------------------------------------------------------- class LED(wx.Panel): def __init__(self, parent, number): self.state = -1 self.size = (20, 20) self.number = number self.fn = wx.Font( parent.GetFont().GetPointSize() - 1, parent.GetFont().GetFamily(), parent.GetFont().GetStyle(), wx.BOLD ) wx.Panel.__init__(self, parent, -1, size=self.size) self.Bind(wx.EVT_PAINT, self.OnPaint) self.Bind(wx.EVT_SIZE, self.OnSize) self.Bind(wx.EVT_ERASE_BACKGROUND, lambda e: None) self.buffer = wx.EmptyBitmap(*self.size) dc = wx.BufferedDC(None, self.buffer) self.DrawFace(dc) self.DrawLED(dc) def OnSize(self, event): # calculate the size of our display. w, h = self.GetClientSize() s = min(w, h) self.size = (s, s) self.buffer = wx.EmptyBitmap(*self.size) dc = wx.BufferedDC(wx.ClientDC(self), self.buffer) self.DrawFace(dc) self.DrawLED(dc) def DrawFace(self, dc): dc.SetBackground(wx.Brush(self.GetBackgroundColour())) dc.Clear() def OnPaint(self, evt): # When dc is destroyed it will blit self.buffer to the window, # since no other drawing is needed we'll just return and let it # do it's thing dc = wx.BufferedPaintDC(self, self.buffer) def DrawLED(self, dc): # bitmap size bw, bh = self.size # center of bitmap center = bw / 2 # calc the 0, 0 origin of the bitmap xorigin = center - (bw / 2) yorigin = center - (bh / 2) # Optimize drawing a bit (for Win) dc.BeginDrawing() # our 'raster'. if self.state == 0: dc.SetBrush(wx.Brush(wx.RED)) elif self.state == 1: dc.SetBrush(wx.Brush(wx.GREEN)) else: dc.SetBrush(wx.Brush(wx.BLACK)) dc.DrawCircle((center, center), bw/2) txt = str(self.number) # Set the font for the DC ... dc.SetFont(self.fn) # ... and calculate how much space our value # will take up. fw, fh = dc.GetTextExtent(txt) # Calc the center of the LED, and from that # derive the origin of our value. tx = center - (fw/2) ty = center - (fh/2) # I draw the value twice so as to give it a pseudo-shadow. # This is (mostly) because I'm too lazy to figure out how # to blit my text onto the gauge using one of the logical # functions. The pseudo-shadow gives the text contrast # regardless of whether the bar is under it or not. dc.SetTextForeground(wx.WHITE) dc.DrawText(txt, (tx, ty)) # Turn off drawing optimization dc.EndDrawing() def Update(self): dc = wx.BufferedDC(wx.ClientDC(self), self.buffer) self.DrawFace(dc) self.DrawLED(dc) #---------------------------------------------------------------------------- class JoyButtons(wx.Panel): def __init__(self, parent, stick): self.stick = stick self.leds = {} wx.Panel.__init__(self, parent, -1) tsizer = wx.BoxSizer(wx.VERTICAL) fn = wx.Font( parent.GetFont().GetPointSize() + 3, parent.GetFont().GetFamily(), parent.GetFont().GetStyle(), wx.BOLD ) t = wx.StaticText(self, -1, "Buttons", style = wx.ALIGN_LEFT) t.SetFont(fn) tsizer.Add(t, 0, wx.ALL | wx.EXPAND | wx.ALIGN_LEFT, 1) sizer = wx.FlexGridSizer(4, 16, 2, 2) fn.SetPointSize(parent.GetFont().GetPointSize() + 1) for i in range(0, MAX_BUTTONS): t = LED(self, i) self.leds[i] = t sizer.Add(t, 1, wx.ALL|wx.ALIGN_CENTER|wx.ALIGN_CENTER_VERTICAL, 1) sizer.AddGrowableCol(i) tsizer.Add(sizer, 1, wx.ALL | wx.EXPAND | wx.ALIGN_LEFT, 1) self.SetSizer(tsizer) tsizer.Fit(self) def Calibrate(self): for i in range(0, MAX_BUTTONS): self.leds[i].state = -1 t = self.stick.GetNumberButtons() for i in range(0, t): self.leds[i].state = 0 def Update(self): t = self.stick.GetButtonState() for i in range(0, MAX_BUTTONS): if self.leds[i].state == 1: self.leds[i].state = 0 if (t & (1<