Bubbles!

Here's a fun little toy program.

It's a little piece of interactive art.

When you run it:

I thought it was cute, at any rate..!

If you add anything to the program, (pretty easy to do!,) please, by all means, put your additions here!

Note that this code demonstrates DoubleBufferedDrawing on platforms where such is needed for flicker-free animation.

Comments

I love it! I've refactored the code and made it more OO, and also fixed an AssertionError on Windows. --RobinDunn

I'm honored! Thank you! -- LionKimbro

This is a fantastic example of custom animation in wx. I've updated it to use AutoBufferedPaintDC, rather than explicitly using a double-buffered context, so that it gets optimal performance even on systems where double-buffering isn't needed (like OS X). -- JoeStrout

Code

   1 import wx
   2 import math
   3 import random
   4 
   5 
   6 class Bubble(object):
   7     def __init__(self, x, y, death_size=25, color="GREEN", grow_speed=0.2):
   8         self.x = x
   9         self.y = y
  10         self.radius = 5
  11         self.death_size = death_size
  12         self.color = color
  13         self.grow_speed = grow_speed
  14 
  15     def go(self, bubbles):
  16         growth = math.log(self.radius) * self.grow_speed
  17         self.radius = self.radius + growth
  18         if self.radius >= self.death_size:
  19             bubbles.remove(self)
  20 
  21     def draw(self, dc, bubbles):
  22         dc.SetPen(wx.Pen(self.color, 2))
  23         dc.DrawCircle(self.x, self.y, self.radius)
  24         self.go(bubbles)
  25 
  26 
  27 
  28 class BubblePanel(wx.Window):
  29     def __init__(self, parent):
  30         wx.Window.__init__(self, parent)
  31         self.bubbles = []
  32         self.marks = []
  33         self.last_pos = self.ScreenToClient(wx.GetMousePosition())
  34         self.SetBackgroundStyle(wx.BG_STYLE_CUSTOM)
  35         self.SetBackgroundColour("WHITE")
  36 
  37         self.Bind(wx.EVT_PAINT, self.on_paint)
  38         self.Bind(wx.EVT_SIZE, self.on_size)
  39 
  40         self.Bind(wx.EVT_MOTION, self.on_motion)
  41         self.Bind(wx.EVT_LEFT_UP, self.on_left_up)
  42         self.Bind(wx.EVT_RIGHT_UP, self.on_right_up)
  43         self.Bind(wx.EVT_CHAR, self.on_character)
  44 
  45         wx.FutureCall(200, self.SetFocus)
  46         
  47 
  48     def on_size(self, event):
  49         width, height = self.GetClientSize()
  50         self._buffer = wx.EmptyBitmap(width, height)
  51         self.update_drawing()
  52 
  53     def update_drawing(self):
  54         self.Refresh(False)        
  55 
  56     def on_paint(self, event):
  57         dc = wx.AutoBufferedPaintDC(self)
  58         dc.Clear()
  59         x, y = self.ScreenToClient(wx.GetMousePosition())
  60         self.draw_x(dc, x, y, 4)
  61         for bubble in self.bubbles:
  62             bubble.draw(dc, self.bubbles)
  63         self.draw_marks(dc)
  64 
  65     def draw_x(self, dc, x, y, line_width):
  66         dc.SetPen(wx.Pen("BLACK", line_width))
  67         dc.DrawLine(x-5, y-5, x+5, y+5)  # \
  68         dc.DrawLine(x-5, y+5, x+5, y-5)  # /
  69 
  70     def draw_marks(self, dc):
  71         chains = {}
  72         for (letter, x, y) in self.marks:
  73             self.draw_x(dc, x, y, 2)
  74             dc.DrawText(letter, x-3, y-28)
  75             chains.setdefault(letter, []).append(wx.Point(x,y))
  76         for (key, points) in chains.items():
  77             if len(points) > 1:
  78                 if key == key.upper() or len(points) == 2:
  79                     dc.DrawLines(points)
  80                 else:
  81                     dc.DrawSpline(points)
  82 
  83     def on_motion(self, event):
  84         x, y = event.GetPosition()
  85         motion_score = (abs(x - self.last_pos.x) + abs(y - self.last_pos.y))
  86         self.last_pos = wx.Point(x, y)
  87         if random.randint(0, motion_score) > 5:
  88             self.bubbles.append(Bubble(x, y))
  89             if random.randint(0, 100) == 0:
  90                 self.bubbles.append(
  91                     Bubble(x, y, color="PURPLE", death_size=100, grow_speed=0.5))
  92 
  93 
  94     def on_left_up(self, event):
  95         self.bubbles.append(Bubble(event.GetX(), event.GetY(),
  96                              color="YELLOW", death_size=50, grow_speed=0.1))
  97 
  98     def on_right_up(self, event):
  99         self.bubbles.append(Bubble(event.GetX(), event.GetY(),
 100                              color="BLUE", death_size=80, grow_speed=0.6))
 101 
 102     def on_character(self, event):
 103         key = event.GetKeyCode()
 104         if key==27:   # Esc key
 105             self.marks = []
 106         else:
 107             x, y = self.ScreenToClient(wx.GetMousePosition())
 108             self.marks.append( (chr(event.GetKeyCode()), x, y) )
 109 
 110 
 111 
 112 class BubbleFrame(wx.Frame):
 113     def __init__(self, *args, **kw):
 114         wx.Frame.__init__(self, *args, **kw)
 115         self.Bind(wx.EVT_CLOSE, self.on_close)
 116         self.Bind(wx.EVT_TIMER, self.on_timer)
 117 
 118         self.panel = BubblePanel(self)
 119         self.timer = wx.Timer(self)
 120         self.timer.Start(20)
 121 
 122                     
 123     def on_close(self, event):
 124         self.timer.Stop()
 125         self.Destroy()
 126 
 127     def on_timer(self, event):
 128         self.panel.update_drawing()
 129 
 130 
 131 app = wx.App(False)
 132 frame = BubbleFrame(None, -1, "Bubbles!")
 133 frame.Show(True)
 134 app.MainLoop()

BubblesToy (last edited 2008-11-20 19:53:58 by c-67-172-139-39)

NOTE: To edit pages in this wiki you must be a member of the TrustedEditorsGroup.