~libavg-team/planarity/master

« back to all changes in this revision

Viewing changes to planarity.py

  • Committer: scotty007
  • Date: 2011-01-30 19:53:35 UTC
  • Revision ID: git-v1:2d3de0f94cb4ac5bac1fc6ffd55bc370930ef953
avg_media/mtc/planarity: prepared for distutils/deb packaging; exit button now stops the application if started standalone

svn path=/trunk/avg_media/mtc/planarity/; revision=5440

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
#!/usr/bin/env python
2
 
# Copyright (C) 2009
3
 
#    Martin Heistermann, <mh at sponc dot de>
4
 
#
5
 
# planarity (aka untangle) is free software: you can redistribute it and/or
6
 
# modify it under the terms of the GNU General Public License as published
7
 
# by the Free Software Foundation, either version 3 of the License, or
8
 
# (at your option) any later version.
9
 
#
10
 
# planarity is distributed in the hope that it will be useful,
11
 
# but WITHOUT ANY WARRANTY; without even the implied warranty of
12
 
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13
 
# GNU General Public License for more details.
14
 
#
15
 
# You should have received a copy of the GNU General Public License
16
 
# along with planarity.  If not, see <http://www.gnu.org/licenses/>.
17
 
 
18
 
from libavg import avg, Point2D, AVGApp
19
 
from libavg.AVGAppUtil import getMediaDir
20
 
 
21
 
import math
22
 
import gzip
23
 
import cPickle
24
 
 
25
 
from buttons import *
26
 
 
27
 
BASE_SIZE = Point2D(1280, 720)
28
 
 
29
 
g_player = avg.Player.get()
30
 
g_exitButton = True
31
 
g_scale = 1.0
32
 
 
33
 
def getDelta(motion, topLeft, bottomRight, boundingSize):
34
 
    xDelta = min(max(motion.x, -topLeft.x), boundingSize.x - bottomRight.x)
35
 
    yDelta = min(max(motion.y, -topLeft.y), boundingSize.y - bottomRight.y)
36
 
    return Point2D(xDelta, yDelta)
37
 
 
38
 
class VertexGroup(object):
39
 
    def __init__(self, gameController, polygon, vertices):
40
 
        self._polygon = g_player.createNode("polygon", {
41
 
            'color': 'ffff00',
42
 
            'strokewidth': 3*g_scale,
43
 
            'opacity': 0.3,
44
 
            'pos': polygon
45
 
            })
46
 
        self._vertices = vertices
47
 
        self._gameController = gameController
48
 
        self._gameController.level.addVertexGroup(self)
49
 
        self._gameController.vertexDiv.appendChild(self._polygon)
50
 
 
51
 
        self._button = g_player.createNode('image', {'href': 'close-button.png'})
52
 
        self._gameController.vertexDiv.appendChild(self._button)
53
 
        self._button.size *= g_scale
54
 
        self._button.pos = polygon[0] - self._button.size/2
55
 
        self._button.setEventHandler(avg.CURSORDOWN, avg.TOUCH | avg.MOUSE,
56
 
                lambda event: self.delete())
57
 
 
58
 
        xCoords = [vertex.pos.x for vertex in vertices]
59
 
        yCoords = [vertex.pos.y for vertex in vertices]
60
 
        self.topLeft = Point2D(min(xCoords), min(yCoords)) - vertices[0].size/2
61
 
        self.bottomRight = Point2D(max(xCoords), max(yCoords)) + vertices[0].size/2
62
 
 
63
 
        def onMotion(event):
64
 
            delta = getDelta(event.motion, self.topLeft, self.bottomRight,
65
 
                self._gameController.vertexDiv.size)
66
 
            for i, vertex in enumerate(self._vertices):
67
 
                vertex.pos += delta
68
 
            self._polygon.pos = [pos + delta for pos in self._polygon.pos]
69
 
            self._button.pos += delta
70
 
            self.topLeft += delta
71
 
            self.bottomRight += delta
72
 
 
73
 
        self._mover = MoveButton(self._polygon, onMotion=onMotion)
74
 
 
75
 
    def delete(self):
76
 
        self._mover.delete()
77
 
        self._gameController.ungroupVertices(self._vertices)
78
 
        self._polygon.unlink()
79
 
        self._button.unlink()
80
 
 
81
 
class GroupDetector(object):
82
 
    """use this as an event handler"""
83
 
    def __init__(self, gameController, event):
84
 
        self._gameController = gameController
85
 
        self._polyline = g_player.createNode("polyline", {
86
 
            'color': 'ffff00',
87
 
            'strokewidth': 1
88
 
            })
89
 
        gameController.groupDiv.appendChild(self._polyline)
90
 
 
91
 
        self._cursorid = event.cursorid
92
 
        self._polyline.setEventCapture(self._cursorid)
93
 
        self._polyline.setEventHandler(avg.CURSORMOTION, avg.TOUCH | avg.MOUSE,
94
 
            self._onMotion)
95
 
        self._polyline.setEventHandler(avg.CURSORUP, avg.TOUCH | avg.MOUSE,
96
 
            lambda event: self.delete())
97
 
 
98
 
        self._onMotion(event)
99
 
        pass
100
 
 
101
 
    def getClosedPolygon(self):
102
 
        """If the last edge intersects any edge, return a cleaned-up polygon
103
 
        representing the enclosed region."""
104
 
        points = self._polyline.pos  # in-Python object copy
105
 
        if len(points) < 4:
106
 
            return False
107
 
        last_edge = points[-2:]
108
 
        for i in range(len(points) - 2):
109
 
            edge = [points[i], points[i + 1]]
110
 
            intersection = line_intersect(edge, last_edge)
111
 
            if intersection:
112
 
                # include the intersection point itself, plus all the edges
113
 
                # after the intersecting edge, omitting the last edge
114
 
                return [intersection] + points[i + 1:-1]
115
 
        return False
116
 
 
117
 
    def _onMotion(self, event):
118
 
        self._polyline.pos += [event.pos]
119
 
        polygon = self.getClosedPolygon()
120
 
        if polygon:
121
 
            vertices = self._gameController.groupVertices(polygon)
122
 
            if vertices:
123
 
                self.delete()
124
 
                VertexGroup(self._gameController, polygon, vertices)
125
 
 
126
 
    def delete(self):
127
 
        self._polyline.releaseEventCapture(self._cursorid)
128
 
        self._polyline.setEventHandler(avg.CURSORMOTION, avg.TOUCH | avg.MOUSE, None)
129
 
        self._polyline.unlink()
130
 
 
131
 
def in_between(val,b1,b2):
132
 
    """return True if val is between b1 and b2"""
133
 
    return((b1>=val and val>=b2) or (b1<=val and val<=b2))
134
 
 
135
 
def line_collide(line1,line2):
136
 
    a=line1[0].x
137
 
    b=line1[0].y
138
 
    c=line1[1].x-line1[0].x
139
 
    d=line1[1].y-line1[0].y
140
 
    e=line2[0].x
141
 
    f=line2[0].y
142
 
    g=line2[1].x-line2[0].x
143
 
    h=line2[1].y-line2[0].y
144
 
 
145
 
    dem=g*d-c*h
146
 
    if dem==0: # parallel 
147
 
        return False
148
 
 
149
 
    s=(a*d+f*c-b*c-e*d)/dem
150
 
    x=e+s*g
151
 
    y=f+s*h
152
 
    return Point2D(x,y)
153
 
 
154
 
def line_intersect(line1, line2):
155
 
    ka, kb = line1
156
 
    la, lb = line2
157
 
    if (ka == la
158
 
            or ka == lb
159
 
            or kb == la
160
 
            or kb == lb):
161
 
        return False
162
 
    p = line_collide(line1, line2)
163
 
    if(p and in_between(p.x,ka.x,kb.x) # do line segments match?
164
 
            and in_between(p.x,la.x,lb.x)
165
 
            and in_between(p.y,ka.y,kb.y)
166
 
            and in_between(p.y,la.y,lb.y)):
167
 
        return p
168
 
 
169
 
    return False
170
 
 
171
 
 
172
 
class Clash(object):
173
 
    def __init__(self, gameController, pos, edge1, edge2):
174
 
        self.__edges = edge1, edge2
175
 
        self.__gameController = gameController
176
 
        gameController.level.addClash() #XXX
177
 
        edge1.addClash(edge2, self)
178
 
        edge2.addClash(edge1, self)
179
 
        #self.__node = g_player.createNode('image',{
180
 
        #    'href':'clash.png',
181
 
        #    'opacity': 0.7})
182
 
        """
183
 
        self.__node = g_player.createNode('circle',{
184
 
            'r': 10,
185
 
            'strokewidth': 5,
186
 
            'color': 'aa0000'})
187
 
        """
188
 
        self.__node = g_player.createNode('rect', {
189
 
                'size':Point2D(20,20)*g_scale,
190
 
                'strokewidth':3*g_scale,
191
 
                'color':'aa0000'})
192
 
        gameController.clashDiv.appendChild(self.__node)
193
 
        self.goto(pos)
194
 
 
195
 
    def goto(self, pos):
196
 
        self.__node.pos = pos - self.__node.size/2
197
 
 
198
 
    def delete(self):
199
 
        edge1, edge2 = self.__edges
200
 
        edge1.removeClash(edge2)
201
 
        edge2.removeClash(edge1)
202
 
        self.__node.unlink()
203
 
        self.__node = None
204
 
        self.__gameController.level.removeClash() #XXX
205
 
 
206
 
 
207
 
class Edge(object):
208
 
    def __init__(self, gameController, vertex1, vertex2):
209
 
        self.__vertices = vertex1, vertex2
210
 
        for vertex in self.__vertices:
211
 
            vertex.addEdge(self)
212
 
        self.__clashes = {}
213
 
        self.__gameController = gameController
214
 
 
215
 
        self.__line = g_player.createNode('line', {'strokewidth':3*g_scale})
216
 
        gameController.edgeDiv.appendChild(self.__line)
217
 
        self.__draw()
218
 
        self.__clashState = False
219
 
 
220
 
    def getLine(self):
221
 
        return [v.pos for v in self.__vertices]
222
 
 
223
 
    def checkCollisions(self):
224
 
        for other in self.__gameController.getEdges():
225
 
            pos = line_intersect(self.getLine(), other.getLine())
226
 
            if other in self.__clashes.keys():
227
 
                if pos:
228
 
                    self.__clashes[other].goto(pos)
229
 
                else:
230
 
                    self.__clashes[other].delete()
231
 
                    return True
232
 
            elif pos: # new clash
233
 
                Clash(self.__gameController, pos, self, other)
234
 
        return False
235
 
 
236
 
    def onVertexMotion(self):
237
 
        clashRemoved = self.checkCollisions()
238
 
        self.__draw()
239
 
        return clashRemoved
240
 
 
241
 
    def addClash(self, other, clash):
242
 
        assert other not in self.__clashes.keys()
243
 
        self.__clashes[other] = clash
244
 
        self.updateClashState()
245
 
 
246
 
    def removeClash(self, other):
247
 
        del self.__clashes[other]
248
 
        self.updateClashState()
249
 
 
250
 
    def __draw(self):
251
 
        self.__line.pos1 = self.__vertices[0].pos
252
 
        self.__line.pos2 = self.__vertices[1].pos
253
 
        if self.isClashed():
254
 
            self.__line.color = 'ff6000' # red
255
 
        else:
256
 
            self.__line.color = 'ffffff' # white
257
 
 
258
 
    def updateClashState(self):
259
 
        clashState = self.isClashed()
260
 
        if clashState != self.__clashState:
261
 
            self.__clashState = clashState
262
 
            self.__draw()
263
 
            for vertex in self.__vertices:
264
 
                vertex.updateClashState()
265
 
 
266
 
    def isClashed(self):
267
 
        return len(self.__clashes) > 0
268
 
 
269
 
    def delete(self):
270
 
        for clash in self.__clashes.values():
271
 
            clash.delete()
272
 
        self.__line.unlink()
273
 
        self.__line = None
274
 
        self.__clashes = {}
275
 
 
276
 
 
277
 
class Vertex(object):
278
 
    def __init__(self, gameController, pos):
279
 
        self._gameController = gameController
280
 
        self.__edges = []
281
 
        self.__node = g_player.createNode('image', {'href':'vertex.png'})
282
 
        parent = gameController.vertexDiv
283
 
        parent.appendChild(self.__node)
284
 
        self.__node.size *= g_scale
285
 
        self.__nodeOffset = self.__node.size / 2
286
 
        self.__node.pos = pos - self.__nodeOffset
287
 
        self.__clashState = False
288
 
        self._highlight = False
289
 
        self.draggable = True
290
 
 
291
 
        def onMotion(event):
292
 
            if not self.draggable:
293
 
                return
294
 
            delta = getDelta(event.motion, self.__node.pos,
295
 
                self.__node.pos + self.__node.size, parent.size)
296
 
            self.pos += delta
297
 
 
298
 
        self.__button = MoveButton(self.__node, onMotion=onMotion)
299
 
 
300
 
    def addEdge(self, edge):
301
 
        self.__edges.append(edge)
302
 
 
303
 
    def updateClashState(self):
304
 
        clashState = False
305
 
        for edge in self.__edges:
306
 
            if edge.isClashed():
307
 
                clashState = True
308
 
                break
309
 
 
310
 
        if clashState != self.__clashState:
311
 
            self.__clashState = clashState
312
 
            self.__setNodeImage()
313
 
 
314
 
    def highlight(self, addHighlighter):
315
 
        self._highlight = addHighlighter
316
 
        self.__setNodeImage()
317
 
 
318
 
    def __setNodeImage(self):
319
 
        if self._highlight:
320
 
            href = 'vertex_hl'
321
 
        else:
322
 
            href = 'vertex'
323
 
        if self.__clashState:
324
 
            self.__node.href = href + '_clash.png'
325
 
        else:
326
 
            self.__node.href = href + '.png'
327
 
 
328
 
    @property
329
 
    def pos(self):
330
 
        return self.__node.pos + self.__nodeOffset
331
 
 
332
 
    @pos.setter
333
 
    def pos(self, value):
334
 
        self.__node.pos = value - self.__nodeOffset
335
 
        clashRemoved = False
336
 
        for edge in self.__edges:
337
 
            clashRemoved |= edge.onVertexMotion()
338
 
        if clashRemoved:
339
 
            self._gameController.level.checkWin()
340
 
 
341
 
    @property
342
 
    def size(self):
343
 
        return self.__node.size
344
 
 
345
 
    def delete(self):
346
 
        self.__button.delete()
347
 
        self.__button = None
348
 
        self.__node.unlink()
349
 
        self.__node = None
350
 
        self.__edges = None
351
 
 
352
 
 
353
 
class Level(object):
354
 
    def __init__(self, gameController):
355
 
        self.__gameController = gameController
356
 
        self.__isRunning = False
357
 
        self.__numClashes = 0
358
 
        self._vertexGroups = []
359
 
 
360
 
    def addClash(self):
361
 
        self.__numClashes +=1
362
 
        self.__gameController.updateStatus()
363
 
 
364
 
    def removeClash(self):
365
 
        assert self.__numClashes > 0
366
 
        self.__numClashes -=1
367
 
        self.__gameController.updateStatus()
368
 
 
369
 
    def getStatus(self):
370
 
        type_, number = self.__scoring[2:4]
371
 
        return "clashes left: %u<br/>goal %c %u" %(self.__numClashes, type_, number)
372
 
 
373
 
    def getName(self):
374
 
        return self.__levelData['name']
375
 
 
376
 
    def checkWin(self):
377
 
        if self.__isRunning:
378
 
            type_, number = self.__scoring[2:4]
379
 
            if ((type_=='=' and self.__numClashes == number)
380
 
                    or (type_=='<' and self.__numClashes < number)
381
 
                    or (self.__numClashes <= number)):
382
 
                self.__gameController.levelWon()
383
 
 
384
 
    def start(self, levelData):
385
 
        self.__levelData = levelData
386
 
        self.__scoring = levelData["scoring"]
387
 
        self.__levelData['menuItem'].color = 'ffffff' # unlock level -> white
388
 
        self.vertices = []
389
 
        for vertexCoord in levelData["vertices"]:
390
 
            self.vertices.append(Vertex(self.__gameController, vertexCoord))
391
 
 
392
 
        self.edges = []
393
 
        for v1, v2 in levelData["edges"]:
394
 
            self.edges.append(Edge(self.__gameController, self.vertices[v1], self.vertices[v2]))
395
 
 
396
 
        for edge in self.edges:
397
 
            edge.checkCollisions()
398
 
 
399
 
        self.__isRunning = True
400
 
 
401
 
    def pause(self):
402
 
        self.__isRunning = False
403
 
 
404
 
    def stop(self):
405
 
        self.__isRunning = False
406
 
        for edge in self.edges:
407
 
            edge.delete()
408
 
        self.edges = []
409
 
        for group in self._vertexGroups:
410
 
            group.delete()
411
 
        self._vertexGroups = []
412
 
 
413
 
        for vertex in self.vertices:
414
 
            vertex.delete()
415
 
        self.vertices = []
416
 
 
417
 
    def getEnclosedVertices(self, polygon):
418
 
        return [vertex for vertex in self.vertices
419
 
                if avg.pointInPolygon(vertex.pos, polygon)]
420
 
 
421
 
    def addVertexGroup(self, group):
422
 
        self._vertexGroups.append(group)
423
 
 
424
 
 
425
 
def loadLevels(size):
426
 
    fp = gzip.open(getMediaDir(__file__, 'data/levels.pickle.gz'))
427
 
    levels = cPickle.load(fp)
428
 
    fp.close()
429
 
 
430
 
    for level in levels:
431
 
        vertices = level['vertices']
432
 
        minPos = Point2D(size)
433
 
        maxPos = Point2D(0, 0)
434
 
        for i in xrange(len(vertices)):
435
 
            vertices[i] = Point2D(vertices[i][0]*g_scale, vertices[i][1]*g_scale)
436
 
            if vertices[i].x < minPos.x:
437
 
                minPos.x = vertices[i].x
438
 
            if vertices[i].y < minPos.y:
439
 
                minPos.y = vertices[i].y
440
 
            if vertices[i].x > maxPos.x:
441
 
                maxPos.x = vertices[i].x
442
 
            if vertices[i].y > maxPos.y:
443
 
                maxPos.y = vertices[i].y
444
 
        # center level on screen
445
 
        levelSize = maxPos - minPos
446
 
        levelOffset = (size - levelSize) / 2 - minPos
447
 
        for i in xrange(len(vertices)):
448
 
            vertices[i] += levelOffset
449
 
 
450
 
    return levels
451
 
 
452
 
 
453
 
class GameController(object):
454
 
    def __init__(self, parentNode, onExit):
455
 
        self.node = parentNode
456
 
        self.__levels = loadLevels(parentNode.size)
457
 
 
458
 
        background = g_player.createNode('image', {'href':'black.png'})
459
 
        background.size = parentNode.size
460
 
        parentNode.appendChild(background)
461
 
 
462
 
        self.gameDiv = g_player.createNode('div', {})
463
 
        parentNode.appendChild(self.gameDiv)
464
 
 
465
 
        self.edgeDiv = g_player.createNode('div', {'sensitive':False})
466
 
        self.groupDiv = g_player.createNode('div', {'sensitive':False})
467
 
        self.vertexDiv = g_player.createNode('div', {})
468
 
        self.vertexDiv.setEventHandler(avg.CURSORDOWN, avg.TOUCH | avg.MOUSE,
469
 
            self._onDraw)
470
 
        self.clashDiv = g_player.createNode('div', {'sensitive':False})
471
 
 
472
 
        self._groupedVertices = set()
473
 
 
474
 
        for div in (self.edgeDiv, self.vertexDiv, self.clashDiv, self.groupDiv):
475
 
            self.gameDiv.appendChild(div)
476
 
            div.size = parentNode.size
477
 
 
478
 
        self.winnerDiv = g_player.createNode('words', {
479
 
                'text':"YOU WON!",
480
 
                'fontsize':100*g_scale,
481
 
                'opacity':0,
482
 
                'sensitive':False})
483
 
        parentNode.appendChild(self.winnerDiv)
484
 
        self.winnerDiv.pos = (parentNode.size - self.winnerDiv.getMediaSize()) / 2
485
 
 
486
 
        pos = Point2D(50, 50)
487
 
        if g_exitButton:
488
 
            LabelButton(parentNode, 'exit', 30*g_scale, onExit, pos*g_scale)
489
 
            pos.x = 150
490
 
        LabelButton(parentNode, 'levels', 30*g_scale,
491
 
                lambda:self.levelMenu.open(self.__curLevel-1), pos*g_scale)
492
 
 
493
 
        statusNode = g_player.createNode('words', {
494
 
                'pos':(parentNode.width-50*g_scale, 50*g_scale),
495
 
                'fontsize':30*g_scale,
496
 
                'alignment':'right',
497
 
                'sensitive':False})
498
 
        parentNode.appendChild(statusNode)
499
 
 
500
 
        def setStatus(text):
501
 
            statusNode.text = text
502
 
        self.__statusHandler = setStatus
503
 
 
504
 
        levelNameDiv = g_player.createNode('div', {'sensitive':False})
505
 
        self.gameDiv.appendChild(levelNameDiv)
506
 
        bgImage = g_player.createNode('image', {'href':'menubg.png'})
507
 
        levelNameDiv.appendChild(bgImage)
508
 
        levelNameNode = g_player.createNode('words', {
509
 
                'fontsize':30*g_scale,
510
 
                'pos':Point2D(20, 20)*g_scale,
511
 
                'sensitive':False})
512
 
        levelNameDiv.appendChild(levelNameNode)
513
 
 
514
 
        def setLevelName(text):
515
 
            levelNameNode.text = text
516
 
            levelNameSize = levelNameNode.getMediaSize()
517
 
            bgImage.size = levelNameSize + Point2D(40, 40) * g_scale
518
 
            levelNameDiv.pos = parentNode.size / 2 - bgImage.size / 2
519
 
            levelNameDiv.opacity = 1
520
 
            avg.fadeOut(levelNameDiv, 6000)
521
 
        self.__levelNameHandler = setLevelName
522
 
 
523
 
        self.levelMenu = LevelMenu(parentNode, self.__levels, self.switchLevel)
524
 
 
525
 
        self.__curLevel = 0
526
 
        self.level = Level(self)
527
 
        self.__startNextLevel()
528
 
 
529
 
    def getEdges(self):
530
 
        return self.level.edges
531
 
 
532
 
    def updateStatus(self):
533
 
        self.__statusHandler(self.level.getStatus())
534
 
 
535
 
    def switchLevel(self, levelIndex):
536
 
        self.__curLevel = levelIndex
537
 
        self.levelWon(False)
538
 
 
539
 
    def __startNextLevel(self):
540
 
        self.__curLevel %= len(self.__levels)
541
 
        level = self.__levels[self.__curLevel]
542
 
        self.level.start(level)
543
 
        self.__levelNameHandler(self.level.getName())
544
 
        self.__curLevel += 1
545
 
 
546
 
    def levelWon(self, showWinnerDiv=True):
547
 
        def nextLevel():
548
 
            self.level.stop()
549
 
            self.__startNextLevel()
550
 
            if showWinnerDiv:
551
 
                avg.fadeOut(self.winnerDiv, 400)
552
 
            avg.fadeIn(self.gameDiv, 400)
553
 
        self.level.pause()
554
 
        if showWinnerDiv:
555
 
            avg.fadeIn(self.winnerDiv, 600)
556
 
            avg.fadeOut(self.gameDiv, 600, lambda: g_player.setTimeout(1000, nextLevel))
557
 
        else:
558
 
            avg.fadeOut(self.gameDiv, 600, nextLevel)
559
 
 
560
 
    def groupVertices(self, polygon):
561
 
        vertices = set(self.level.getEnclosedVertices(polygon))
562
 
        newGroup = vertices - self._groupedVertices
563
 
        self._groupedVertices = self._groupedVertices.union(newGroup)
564
 
        for vertex in newGroup:
565
 
            vertex.highlight(True)
566
 
            vertex.draggable = False
567
 
        return list(newGroup)
568
 
 
569
 
    def ungroupVertices(self, vertices):
570
 
        for vertex in vertices:
571
 
            vertex.highlight(False)
572
 
            vertex.draggable = True
573
 
        self._groupedVertices -= set(vertices)
574
 
 
575
 
    def _onDraw(self, event):
576
 
        GroupDetector(self, event)
577
 
        return False
578
 
 
579
 
class LevelMenu:
580
 
    VISIBLE_LEVELS = 11
581
 
 
582
 
    def __init__(self, parentNode, levels, callback):
583
 
        # main div catches all clicks and disables game underneath
584
 
        mainDiv = g_player.createNode('div', {
585
 
                'size':parentNode.size,
586
 
                'active':False,
587
 
                'opacity':0})
588
 
        parentNode.appendChild(mainDiv)
589
 
 
590
 
        fontSize = round(16 * g_scale)
591
 
        itemHeight = fontSize * 3
592
 
        listHeight = itemHeight * self.VISIBLE_LEVELS
593
 
 
594
 
        menuSize = Point2D(round(mainDiv.width*0.75), listHeight+itemHeight)
595
 
        menuDiv = g_player.createNode('div', {
596
 
                'pos':(mainDiv.size-menuSize)/2,
597
 
                'size':menuSize})
598
 
        mainDiv.appendChild(menuDiv)
599
 
 
600
 
        bgImage = g_player.createNode('image', {
601
 
                'href':'menubg.png',
602
 
                'size':menuDiv.size})
603
 
        menuDiv.appendChild(bgImage)
604
 
 
605
 
        listFrameDiv = g_player.createNode('div', {
606
 
                'size':(menuDiv.width, listHeight),
607
 
                'crop':True})
608
 
        menuDiv.appendChild(listFrameDiv)
609
 
 
610
 
        selectionBg = g_player.createNode('rect', {
611
 
                'pos':(-1, (listFrameDiv.height-itemHeight)/2),
612
 
                'size':(listFrameDiv.width+2, itemHeight),
613
 
                'fillcolor':'ff6000'}) # red
614
 
        listFrameDiv.appendChild(selectionBg)
615
 
 
616
 
        listDiv = g_player.createNode('div', {
617
 
                'sensitive':False})
618
 
        listFrameDiv.appendChild(listDiv)
619
 
 
620
 
        pos = Point2D(listFrameDiv.width/2, 0)
621
 
        for level in levels:
622
 
            menuItem = g_player.createNode('words', {
623
 
                    'text':level['name'],
624
 
                    'fontsize':fontSize,
625
 
                    'color':'7f7f7f', # initially locked -> gray
626
 
                    'alignment':'center'})
627
 
            menuItem.pos = pos + Point2D(0, (itemHeight-menuItem.getMediaSize().y)/2)
628
 
            level['menuItem'] = menuItem
629
 
            listDiv.appendChild(menuItem)
630
 
            pos.y += itemHeight
631
 
 
632
 
        separatorLine = g_player.createNode('line', {
633
 
                'pos1':(0, listHeight),
634
 
                'pos2':(menuDiv.width, listHeight)})
635
 
        menuDiv.appendChild(separatorLine)
636
 
 
637
 
        listDivMaxPos = selectionBg.pos.y
638
 
        listDivMinPos = -pos.y + listDivMaxPos + itemHeight
639
 
 
640
 
        def onOpen(levelIndex):
641
 
            mainDiv.active = True
642
 
            self.__selectedLevelIndex = levelIndex
643
 
            listDiv.pos = (0, listDivMaxPos - levelIndex * itemHeight)
644
 
            selectionBg.fillopacity = 0.5
645
 
            self.__motionDiff = 0
646
 
            self.__lastTargetPos = listDiv.pos.y
647
 
            avg.fadeIn(mainDiv, 400)
648
 
        self.__onOpenHandler = onOpen
649
 
 
650
 
        def onClose():
651
 
            def setInactive():
652
 
                mainDiv.active = False
653
 
            avg.fadeOut(mainDiv, 400, setInactive)
654
 
 
655
 
        def onStart():
656
 
            callback(self.__selectedLevelIndex)
657
 
            onClose()
658
 
 
659
 
        def onUpDown(event):
660
 
            self.__motionDiff = 0
661
 
 
662
 
        def onMotion(event):
663
 
            self.__motionDiff += event.motion.y
664
 
            motion = round(self.__motionDiff / itemHeight) * itemHeight
665
 
            if motion:
666
 
                pos = (0, min(max(self.__lastTargetPos+motion, listDivMinPos), listDivMaxPos))
667
 
                avg.LinearAnim(listDiv, 'pos', 200, listDiv.pos, pos).start()
668
 
                self.__motionDiff -= motion
669
 
                self.__lastTargetPos = pos[1]
670
 
                self.__selectedLevelIndex = int((listDivMaxPos-self.__lastTargetPos) / itemHeight)
671
 
                if levels[self.__selectedLevelIndex]['menuItem'].color == 'ffffff':
672
 
                    startBtn.setActive(True)
673
 
                    avg.LinearAnim(selectionBg, 'fillopacity', 200,
674
 
                            selectionBg.fillopacity, 0.5).start()
675
 
                else:
676
 
                    startBtn.setActive(False)
677
 
                    avg.LinearAnim(selectionBg, 'fillopacity', 200,
678
 
                            selectionBg.fillopacity, 0).start()
679
 
 
680
 
        MoveButton(listFrameDiv, onUpDown, onUpDown, onMotion)
681
 
        startBtn = LabelButton(menuDiv, 'start level', 20*g_scale, onStart)
682
 
        startBtn.setPos((itemHeight*2, listHeight+(itemHeight-startBtn.size.y)/2))
683
 
        closeBtn = LabelButton(menuDiv, 'close menu', 20*g_scale, onClose)
684
 
        closeBtn.setPos((menuDiv.width-itemHeight*2-closeBtn.size.x,
685
 
                listHeight+(itemHeight-closeBtn.size.y)/2))
686
 
 
687
 
    def open(self, levelIndex):
688
 
        self.__onOpenHandler(levelIndex)
689
 
 
690
 
 
691
 
class Planarity(AVGApp):
692
 
    multitouch = True
693
 
    def init(self):
694
 
        self._parentNode.mediadir = getMediaDir(__file__)
695
 
 
696
 
        global g_scale
697
 
        size = self._parentNode.size
698
 
        g_scale = min(size.x / BASE_SIZE.x, size.y / BASE_SIZE.y)
699
 
 
700
 
        self.__controller = GameController(self._parentNode, onExit = self.leave)
701
 
 
702
 
    def _enter(self):
703
 
        #self.__controller.startLevel()
704
 
        pass
705
 
 
706
 
    def _leave(self):
707
 
        pass
708
 
 
709
 
 
710
 
if __name__ == '__main__':
711
 
    g_exitButton = False
712
 
    Planarity.start(resolution = BASE_SIZE)