Bubbles!
Here's a fun little toy program.
It's a little piece of interactive art.
When you run it:
- Try moving your mouse over the panel, at different speeds.
Try the left & right mouse buttons.
Press upper case & lower case letters, repeatedly, over the display area, as you move the mouse over 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()
