~facundo/enjuewemela/trunk

« back to all changes in this revision

Viewing changes to cocos/menu.py

  • Committer: facundo at com
  • Date: 2010-11-20 01:42:31 UTC
  • mfrom: (62.1.3 lint-issues)
  • Revision ID: facundo@taniquetil.com.ar-20101120014231-b2tkyc3mwr84ggcc
Project reorder and lint issues

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# ----------------------------------------------------------------------------
 
2
# cocos2d
 
3
# Copyright (c) 2008 Daniel Moisset, Ricardo Quesada, Rayentray Tappa, Lucio Torre
 
4
# All rights reserved.
 
5
#
 
6
# Redistribution and use in source and binary forms, with or without
 
7
# modification, are permitted provided that the following conditions are met:
 
8
#
 
9
#   * Redistributions of source code must retain the above copyright
 
10
#     notice, this list of conditions and the following disclaimer.
 
11
#   * Redistributions in binary form must reproduce the above copyright
 
12
#     notice, this list of conditions and the following disclaimer in
 
13
#     the documentation and/or other materials provided with the
 
14
#     distribution.
 
15
#   * Neither the name of cocos2d nor the names of its
 
16
#     contributors may be used to endorse or promote products
 
17
#     derived from this software without specific prior written
 
18
#     permission.
 
19
#
 
20
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 
21
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 
22
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 
23
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 
24
# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 
25
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 
26
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 
27
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 
28
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 
29
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
 
30
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 
31
# POSSIBILITY OF SUCH DAMAGE.
 
32
# ----------------------------------------------------------------------------
 
33
#
 
34
# Ideas borrowed from:
 
35
#    pygext: http://opioid-interactive.com/~shang/projects/pygext/
 
36
#    pyglet astraea: http://www.pyglet.org
 
37
#    Grossini's Hell: http://www.pyweek.org/e/Pywiii/
 
38
#
 
39
"""A `Layer` that implements a simple menu
 
40
 
 
41
Menu
 
42
====
 
43
 
 
44
This module provides a Menu class. Menus can contain regular items
 
45
(which trigger a function when selected), toggle items (which toggle a flag when selected),
 
46
or entry items (which lets you enter alphanumeric data).
 
47
 
 
48
To use a menu in your code, just subclass `Menu` and add the menu to an `Scene` or
 
49
another `Layer`.
 
50
"""
 
51
 
 
52
__docformat__ = 'restructuredtext'
 
53
 
 
54
import pyglet
 
55
from pyglet import font
 
56
from pyglet.window import key
 
57
from pyglet.gl import *
 
58
import pyglet.graphics
 
59
 
 
60
from layer import *
 
61
from director import *
 
62
from cocosnode import *
 
63
from actions import *
 
64
from sprite import Sprite
 
65
import rect
 
66
 
 
67
__all__ = [ 'Menu',                                         # menu class
 
68
 
 
69
            'MenuItem', 'ToggleMenuItem',  # menu items classes
 
70
            'MultipleMenuItem', 'EntryMenuItem', 'ImageMenuItem',
 
71
            'ColorMenuItem',
 
72
            'verticalMenuLayout', 'fixedPositionMenuLayout',   # Different menu layou functions
 
73
            'CENTER', 'LEFT', 'RIGHT', 'TOP', 'BOTTOM',     # menu aligment
 
74
 
 
75
            'shake', 'shake_back','zoom_in','zoom_out'      # Some useful actions for the menu items
 
76
            ]
 
77
 
 
78
#
 
79
# Class Menu
 
80
#
 
81
 
 
82
# Horizontal Align
 
83
CENTER = font.Text.CENTER
 
84
LEFT = font.Text.LEFT
 
85
RIGHT = font.Text.RIGHT
 
86
 
 
87
# Vertical Align
 
88
TOP = font.Text.TOP
 
89
BOTTOM = font.Text.BOTTOM
 
90
 
 
91
def verticalMenuLayout (menu):
 
92
    width, height = director.get_window_size()
 
93
    fo = font.load(menu.font_item['font_name'], menu.font_item['font_size'])
 
94
    fo_height = int( (fo.ascent - fo.descent) * 0.9 )
 
95
 
 
96
    if menu.menu_halign == CENTER:
 
97
        pos_x = width // 2
 
98
    elif menu.menu_halign == RIGHT:
 
99
        pos_x = width - menu.menu_hmargin
 
100
    elif menu.menu_halign == LEFT:
 
101
        pos_x = menu.menu_hmargin
 
102
    else:
 
103
        raise Exception("Invalid anchor_x value for menu")
 
104
 
 
105
    for idx,i in enumerate( menu.children):
 
106
        item = i[1]
 
107
        if menu.menu_valign == CENTER:
 
108
            pos_y = (height + (len(menu.children) - 2 * idx)
 
109
                         * fo_height - menu.title_height) * 0.5
 
110
        elif menu.menu_valign == TOP:
 
111
            pos_y = (height - (idx * fo_height )
 
112
                         - menu.title_height - menu.menu_vmargin)
 
113
        elif menu.menu_valign == BOTTOM:
 
114
            pos_y = (0 + fo_height * (len(menu.children) - idx) +
 
115
                         menu.menu_vmargin)
 
116
        item.transform_anchor = (pos_x, pos_y)
 
117
        item.generateWidgets (pos_x, pos_y, menu.font_item,
 
118
                              menu.font_item_selected)
 
119
 
 
120
def fixedPositionMenuLayout (positions):
 
121
    def fixedMenuLayout(menu):
 
122
        width, height = director.get_window_size()
 
123
        for idx,i in enumerate( menu.children):
 
124
            item = i[1]
 
125
            pos_x = positions[idx][0]
 
126
            pos_y = positions[idx][1]
 
127
            item.transform_anchor = (pos_x, pos_y)
 
128
            item.generateWidgets (pos_x, pos_y, menu.font_item,
 
129
                                        menu.font_item_selected)
 
130
    return fixedMenuLayout
 
131
 
 
132
class Menu(Layer):
 
133
    """Abstract base class for menu layers.
 
134
 
 
135
    Normal usage is:
 
136
 
 
137
     - create a subclass
 
138
     - override __init__ to set all style attributes,
 
139
       and then call `create_menu()`
 
140
     - Finally you shall add the menu to an `Scene` or another `Layer`
 
141
    """
 
142
 
 
143
    is_event_handler = True #: Receives pyglet events
 
144
 
 
145
    select_sound = None
 
146
    activate_sound = None
 
147
    def __init__( self, title = ''):
 
148
        super(Menu, self).__init__()
 
149
 
 
150
        #
 
151
        # Items and Title
 
152
        #
 
153
        self.title = title
 
154
        self.title_text = None
 
155
 
 
156
        self.menu_halign = CENTER
 
157
        self.menu_valign = CENTER
 
158
 
 
159
        self.menu_hmargin = 2 # Variable margins for left and right alignment
 
160
        self.menu_vmargin = 2 # Variable margins for top and bottom alignment
 
161
 
 
162
        #
 
163
        # Menu default options
 
164
        # Menus can be customized changing these variables
 
165
        #
 
166
 
 
167
        # Title
 
168
        self.font_title = {
 
169
            'text':'title',
 
170
            'font_name':'Arial',
 
171
            'font_size':56,
 
172
            'color':(192,192,192,255),
 
173
            'bold':False,
 
174
            'italic':False,
 
175
            'anchor_y':'center',
 
176
            'anchor_x':'center',
 
177
            'dpi':96,
 
178
            'x':0, 'y':0,
 
179
        }
 
180
 
 
181
        self.font_item= {
 
182
            'font_name':'Arial',
 
183
            'font_size':32,
 
184
            'bold':False,
 
185
            'italic':False,
 
186
            'anchor_y':'center',
 
187
            'anchor_x':'center',
 
188
            'color':(192,192,192,255),
 
189
            'dpi':96,
 
190
        }
 
191
        self.font_item_selected = {
 
192
            'font_name':'Arial',
 
193
            'font_size':42,
 
194
            'bold':False,
 
195
            'italic':False,
 
196
            'anchor_y':'center',
 
197
            'anchor_x':'center',
 
198
            'color':(255,255,255,255),
 
199
            'dpi':96,
 
200
        }
 
201
 
 
202
        self.title_height = 0
 
203
        self.schedule(lambda dt: None)
 
204
 
 
205
 
 
206
    def _generate_title( self ):
 
207
        width, height = director.get_window_size()
 
208
 
 
209
        self.font_title['x'] = width // 2
 
210
        self.font_title['text'] = self.title
 
211
        self.title_label = pyglet.text.Label( **self.font_title )
 
212
        self.title_label.y = height - self.title_label.content_height //2
 
213
 
 
214
        fo = font.load( self.font_title['font_name'], self.font_title['font_size'] )
 
215
        self.title_height = self.title_label.content_height
 
216
 
 
217
    def _build_items(self, layout_strategy):
 
218
        self.font_item_selected['anchor_x'] = self.menu_halign
 
219
        self.font_item_selected['anchor_y'] = 'center'
 
220
 
 
221
        self.font_item['anchor_x'] = self.menu_halign
 
222
        self.font_item['anchor_y'] = 'center'
 
223
 
 
224
        layout_strategy(self)
 
225
        self.selected_index = 0
 
226
        self.children[ self.selected_index ][1].is_selected = True
 
227
 
 
228
    def _select_item(self, new_idx):
 
229
        if new_idx == self.selected_index:
 
230
            return
 
231
 
 
232
        if self.select_sound:
 
233
            self.select_sound.play()
 
234
 
 
235
        self.children[ self.selected_index][1].is_selected = False
 
236
        self.children[ self.selected_index][1].on_unselected()
 
237
 
 
238
        self.children[ new_idx ][1].is_selected = True
 
239
        self.children[ new_idx ][1].on_selected()
 
240
 
 
241
        self.selected_index = new_idx
 
242
 
 
243
    def _activate_item( self ):
 
244
        if self.activate_sound:
 
245
            self.activate_sound.play()
 
246
        self.children[ self.selected_index][1].on_activated()
 
247
        self.children[ self.selected_index ][1].on_key_press( key.ENTER, 0 )
 
248
 
 
249
    def create_menu(self, items, selected_effect=None, unselected_effect=None,
 
250
                    activated_effect=None, layout_strategy=verticalMenuLayout):
 
251
        """Creates the menu
 
252
 
 
253
        The order of the list important since the
 
254
        first one will be shown first.
 
255
 
 
256
        Example::
 
257
 
 
258
            l = []
 
259
            l.append( MenuItem('Options', self.on_new_game ) )
 
260
            l.append( MenuItem('Quit', self.on_quit ) )
 
261
            self.create_menu( l, zoom_in(), zoom_out() )
 
262
 
 
263
        :Parameters:
 
264
            `items` : list
 
265
                list of `BaseMenuItem` that will be part of the `Menu`
 
266
            `selected_effect` : function
 
267
                This action will be executed when the `BaseMenuItem` is selected
 
268
            `unselected_effect` : function
 
269
                This action will be executed when the `BaseMenuItem` is unselected
 
270
            `activated_effect` : function
 
271
                this action will executed when the `BaseMenuItem` is activated (pressing Enter or by clicking on it)
 
272
        """
 
273
        z=0
 
274
        for i in items:
 
275
            # calling super.add(). Z is important to mantain order
 
276
            self.add( i, z=z )
 
277
 
 
278
            i.activated_effect = activated_effect
 
279
            i.selected_effect = selected_effect
 
280
            i.unselected_effect = unselected_effect
 
281
            i.item_halign = self.menu_halign
 
282
            i.item_valign = self.menu_valign
 
283
            z += 1
 
284
 
 
285
        self._generate_title() # If you generate the title after the items
 
286
                # the V position of the items can't consider the title's height
 
287
        if items:
 
288
            self._build_items(layout_strategy)
 
289
 
 
290
    def draw( self ):
 
291
        self.title_label.draw()
 
292
 
 
293
    def on_text( self, text ):
 
294
        if text=='\r':
 
295
            return
 
296
        return self.children[self.selected_index][1].on_text(text)
 
297
 
 
298
    def on_key_press(self, symbol, modifiers):
 
299
        if symbol == key.ESCAPE:
 
300
            self.on_quit()
 
301
            return True
 
302
        elif symbol in (key.ENTER, key.NUM_ENTER):
 
303
            self._activate_item()
 
304
            return True
 
305
        elif symbol in (key.DOWN, key.UP):
 
306
            if symbol == key.DOWN:
 
307
                new_idx = self.selected_index + 1
 
308
            elif symbol == key.UP:
 
309
                new_idx = self.selected_index - 1
 
310
 
 
311
            if new_idx < 0:
 
312
                new_idx = len(self.children) -1
 
313
            elif new_idx > len(self.children) -1:
 
314
                new_idx = 0
 
315
            self._select_item( new_idx )
 
316
            return True
 
317
        else:
 
318
            # send the menu item the rest of the keys
 
319
            ret = self.children[self.selected_index][1].on_key_press(symbol, modifiers)
 
320
 
 
321
            # play sound if key was handled
 
322
            if ret and self.activate_sound:
 
323
                self.activate_sound.play()
 
324
            return ret
 
325
 
 
326
    def on_mouse_release( self, x, y, buttons, modifiers ):
 
327
        (x,y) = director.get_virtual_coordinates(x,y)
 
328
        if self.children[ self.selected_index ][1].is_inside_box(x,y):
 
329
            self._activate_item()
 
330
 
 
331
    def on_mouse_motion( self, x, y, dx, dy ):
 
332
        (x,y) = director.get_virtual_coordinates(x,y)
 
333
        for idx,i in enumerate( self.children):
 
334
            item = i[1]
 
335
            if item.is_inside_box( x, y):
 
336
                self._select_item( idx )
 
337
                break
 
338
 
 
339
 
 
340
class BaseMenuItem( CocosNode ):
 
341
    """An abstract menu item. It triggers a function when it is activated"""
 
342
 
 
343
    selected_effect = None
 
344
    unselected_effect = None
 
345
    activated_effect = None
 
346
 
 
347
    def __init__(self, callback_func, *args, **kwargs):
 
348
        """Creates a new menu item
 
349
 
 
350
        :Parameters:
 
351
            `callback_func` : function
 
352
                The callback function
 
353
        """
 
354
 
 
355
        super( BaseMenuItem, self).__init__()
 
356
 
 
357
        self.callback_func = callback_func
 
358
        self.callback_args = args
 
359
        self.callback_kwargs = kwargs
 
360
 
 
361
        self.is_selected = False
 
362
 
 
363
        self.item_halign = None
 
364
        self.item_valign = None
 
365
 
 
366
        self.item = None
 
367
        self.item_selected = None
 
368
 
 
369
    def get_item_width (self):
 
370
        """ Returns the width of the item.
 
371
            This method should be implemented by descendents.
 
372
 
 
373
            :rtype: int
 
374
        """
 
375
        return self.item.width
 
376
 
 
377
    def get_item_height (self):
 
378
        """ Returns the width of the item.
 
379
            This method should be implemented by descendents.
 
380
 
 
381
            :rtype: int
 
382
        """
 
383
        return self.item.height
 
384
 
 
385
    def generateWidgets (self, pos_x, pos_y, font_item, font_item_selected):
 
386
        """ Generate a normal and a selected widget.
 
387
            This method should be implemented by descendents.
 
388
        """
 
389
        raise NotImplementedError
 
390
 
 
391
    def get_item_x (self):
 
392
        """ Return the x position of the item.
 
393
            This method should be implemented by descendents.
 
394
 
 
395
            :rtype: int
 
396
        """
 
397
        return self.item.x
 
398
 
 
399
    def get_item_y (self):
 
400
        """ Return the y position of the item.
 
401
            This method should be implemented by descendents.
 
402
 
 
403
            :rtype: int
 
404
        """
 
405
        return self.item.y
 
406
 
 
407
    def get_box( self ):
 
408
        """Returns the box that contains the menu item.
 
409
 
 
410
        :rtype: (x1,x2,y1,y2)
 
411
        """
 
412
        width = self.get_item_width ()
 
413
        height = self.get_item_height ()
 
414
        if self.item_halign == CENTER:
 
415
            x_diff = - width / 2
 
416
        elif self.item_halign == RIGHT:
 
417
            x_diff = - width
 
418
        elif self.item_halign == LEFT:
 
419
            x_diff = 0
 
420
        else:
 
421
            raise Exception("Invalid halign: %s" % str(self.item_halign) )
 
422
 
 
423
        y_diff = - height/ 2
 
424
 
 
425
        x1 = self.get_item_x() + x_diff
 
426
        y1 = self.get_item_y() + y_diff
 
427
#        x1 += self.parent.x
 
428
#        y1 += self.parent.y
 
429
#        x2 = x1 + width
 
430
#        y2 = y1 + height
 
431
#        return (x1,y1,x2,y2)
 
432
        return rect.Rect(x1,y1,width,height)
 
433
 
 
434
    def draw( self ):
 
435
        raise NotImplementedError
 
436
 
 
437
    def on_key_press(self, symbol, modifiers):
 
438
        if symbol == key.ENTER and self.callback_func:
 
439
            self.callback_func(*self.callback_args, **self.callback_kwargs)
 
440
            return True
 
441
 
 
442
    def on_text( self, text ):
 
443
        return True
 
444
 
 
445
    def is_inside_box( self, x, y ):
 
446
        """Returns whether the point (x,y) is inside the menu item.
 
447
 
 
448
        :rtype: bool
 
449
        """
 
450
#        (ax,ay,bx,by) = self.get_box()
 
451
#        if( x >= ax and x <= bx and y >= ay and y <= by ):
 
452
#            return True
 
453
#        return False
 
454
        rect = self.get_box()
 
455
        p = self.point_to_local( (x,y) )
 
456
        return rect.contains( p.x, p.y )
 
457
        
 
458
 
 
459
    def on_selected( self ):
 
460
        if self.selected_effect:
 
461
            self.stop()
 
462
            self.do( self.selected_effect )
 
463
 
 
464
    def on_unselected( self ):
 
465
        if self.unselected_effect:
 
466
            self.stop()
 
467
            self.do( self.unselected_effect )
 
468
 
 
469
    def on_activated( self ):
 
470
        if self.activated_effect:
 
471
            self.stop()
 
472
            self.do( self.activated_effect )
 
473
 
 
474
class MenuItem (BaseMenuItem):
 
475
    """A menu item that shows a label. """
 
476
    def __init__ (self, label, callback_func, *args, **kwargs):
 
477
        """Creates a new menu item
 
478
 
 
479
        :Parameters:
 
480
            `label` : string
 
481
                The label the of the menu item
 
482
            `callback_func` : function
 
483
                The callback function
 
484
        """
 
485
        self.label = label
 
486
        super (MenuItem, self).__init__(callback_func, *args, **kwargs)
 
487
 
 
488
    def get_item_width (self):
 
489
        return self.item.content_width
 
490
    
 
491
    def get_item_height (self):
 
492
        return self.item.content_height
 
493
 
 
494
    def generateWidgets (self, pos_x, pos_y, font_item, font_item_selected):
 
495
        font_item['x'] = int(pos_x)
 
496
        font_item['y'] = int(pos_y)
 
497
        font_item['text'] = self.label
 
498
        self.item = pyglet.text.Label(**font_item )
 
499
        font_item_selected['x'] = int(pos_x)
 
500
        font_item_selected['y'] = int(pos_y)
 
501
        font_item_selected['text'] = self.label
 
502
        self.item_selected = pyglet.text.Label( **font_item_selected )
 
503
 
 
504
    def draw( self ):
 
505
        glPushMatrix()
 
506
        self.transform()
 
507
        if self.is_selected:
 
508
            self.item_selected.draw()
 
509
        else:
 
510
            self.item.draw()
 
511
        glPopMatrix()
 
512
 
 
513
 
 
514
class ImageMenuItem (BaseMenuItem):
 
515
    """ A menu item that shows a selectable Image """
 
516
    def __init__ (self, name, callback_func, *args, **kwargs):
 
517
        self.image = pyglet.resource.image (name)
 
518
        super (ImageMenuItem, self).__init__(callback_func, *args, **kwargs)
 
519
 
 
520
    def generateWidgets (self, pos_x, pos_y, font_item, font_item_selected):
 
521
        anchors = {'left': 0, 'center': 0.5, 'right': 1, 'top': 1, 'bottom': 0}
 
522
        anchor=(anchors[font_item['anchor_x']] * self.image.width,
 
523
                anchors[font_item['anchor_y']] * self.image.height)
 
524
        self.item = Sprite(self.image, anchor=anchor, opacity=255,
 
525
                           color=font_item['color'][:3])
 
526
        self.item.scale = font_item['font_size'] / float(self.item.height )
 
527
        self.item.position = int(pos_x), int(pos_y)
 
528
        self.selected_item = Sprite(self.image, anchor=anchor,
 
529
                                    color=font_item_selected['color'][:3])
 
530
        self.selected_item.scale = (font_item_selected['font_size'] /
 
531
                                     float(self.selected_item.height))
 
532
        self.selected_item.position = int(pos_x), int(pos_y)
 
533
 
 
534
    def draw (self):
 
535
        glPushMatrix()
 
536
        self.transform()
 
537
        if self.is_selected:
 
538
            self.selected_item.draw()
 
539
        else:
 
540
            self.item.draw()
 
541
        glPopMatrix()
 
542
 
 
543
 
 
544
class MultipleMenuItem( MenuItem ):
 
545
    """A menu item for switching between multiple values.
 
546
 
 
547
    Example::
 
548
 
 
549
        self.volumes = ['Mute','10','20','30','40','50','60','70','80','90','100']
 
550
 
 
551
        items.append( MultipleMenuItem(
 
552
                        'SFX volume: ',
 
553
                        self.on_sfx_volume,
 
554
                        self.volumes,
 
555
                        8 ) )
 
556
    """
 
557
 
 
558
    def __init__(self, label, callback_func, items, default_item=0):
 
559
        """Creates a Multiple Menu Item
 
560
 
 
561
        :Parameters:
 
562
            `label` : string
 
563
                Item's label
 
564
            `callback_func` : function
 
565
                Callback function
 
566
            `items` : list
 
567
                List of strings containing the values
 
568
            `default_item` : integer
 
569
                Default item of the list. It is an index of the list. Default: 0
 
570
        """
 
571
        self.my_label = label
 
572
        self.items = items
 
573
        self.idx = default_item
 
574
        if self.idx < 0 or self.idx >= len(self.items):
 
575
            raise Exception("Index out of bounds")
 
576
        super( MultipleMenuItem, self).__init__( self._get_label(), callback_func )
 
577
 
 
578
    def _get_label(self):
 
579
        return self.my_label+self.items[self.idx]
 
580
 
 
581
    def on_key_press(self, symbol, modifiers):
 
582
        if symbol == key.LEFT:
 
583
            self.idx = max(0, self.idx-1)
 
584
        elif symbol in (key.RIGHT, key.ENTER):
 
585
            self.idx = min(len(self.items)-1, self.idx+1)
 
586
 
 
587
        if symbol in (key.LEFT, key.RIGHT, key.ENTER):
 
588
            self.item.text = self._get_label()
 
589
            self.item_selected.text = self._get_label()
 
590
            self.callback_func( self.idx )
 
591
            return True
 
592
 
 
593
class ToggleMenuItem( MultipleMenuItem ):
 
594
    '''A menu item for a boolean toggle option.
 
595
 
 
596
    Example::
 
597
 
 
598
        items.append( ToggleMenuItem('Show FPS:', self.on_show_fps, director.show_FPS) )
 
599
    '''
 
600
 
 
601
    def __init__(self, label, callback_func, value=False ):
 
602
        """Creates a Toggle Menu Item
 
603
 
 
604
        :Parameters:
 
605
            `label` : string
 
606
                Item's label
 
607
            `callback_func` : function
 
608
                Callback function
 
609
            `value` : bool
 
610
                Default value of the item: False is 'OFF', True is 'ON'. Default:False
 
611
        """
 
612
 
 
613
        super(ToggleMenuItem, self).__init__( label, callback_func, ['OFF','ON'],  int(value) )
 
614
 
 
615
    def on_key_press( self, symbol, mod ):
 
616
        if symbol in (key.LEFT, key.RIGHT, key.ENTER):
 
617
            self.idx += 1
 
618
            if self.idx > 1:
 
619
                self.idx = 0
 
620
            self.item.text = self._get_label()
 
621
            self.item_selected.text = self._get_label()
 
622
            self.callback_func( int(self.idx) )
 
623
            return True
 
624
 
 
625
class EntryMenuItem(MenuItem):
 
626
    """A menu item for entering a value.
 
627
 
 
628
    When selected, ``self.value`` is toggled, the callback function is
 
629
    called with ``self.value`` as argument."""
 
630
 
 
631
    value = property(lambda self: u''.join(self._value),
 
632
                     lambda self, v: setattr(self, '_value', list(v)))
 
633
 
 
634
    def __init__(self, label, callback_func, value, max_length=0 ):
 
635
        """Creates an Entry Menu Item
 
636
 
 
637
        :Parameters:
 
638
            `label` : string
 
639
                Item's label
 
640
            `callback_func` : function
 
641
                Callback function taking one argument.
 
642
            `value` : String
 
643
                Default value: any string
 
644
            `max_length` : integer
 
645
                Maximum value length (Defaults to 0 for unbound length)
 
646
        """
 
647
        self._value = list(value)
 
648
        self._label = label
 
649
        super(EntryMenuItem, self).__init__( "%s %s" %(label,value), callback_func )
 
650
        self.max_length = max_length
 
651
 
 
652
    def on_text( self, text ):
 
653
        if self.max_length == 0 or len(self._value) < self.max_length:
 
654
            self._value.append(text)
 
655
            self._calculate_value()
 
656
        return True
 
657
 
 
658
    def on_key_press(self, symbol, modifiers):
 
659
        if symbol == key.BACKSPACE:
 
660
            try:
 
661
                self._value.pop()
 
662
            except IndexError:
 
663
                pass
 
664
            self._calculate_value()
 
665
            return True
 
666
 
 
667
    def _calculate_value( self ):
 
668
        new_text = u"%s %s" % (self._label, self.value)
 
669
        self.item.text = new_text
 
670
        self.item_selected.text = new_text
 
671
        self.callback_func(self.value)
 
672
 
 
673
class ColorMenuItem( MenuItem ):
 
674
    """A menu item for selecting a color.
 
675
 
 
676
    Example::
 
677
 
 
678
        colors = [(255, 255, 255), (100, 200, 100), (200, 50, 50)]
 
679
 
 
680
        items.append( ColorMenuItem(
 
681
                        'Jacket:',
 
682
                        self.on_jacket_color,
 
683
                        colors ))
 
684
    """
 
685
 
 
686
    def __init__(self, label, callback_func, items, default_item=0):
 
687
        """Creates a Color Menu Item
 
688
 
 
689
        :Parameters:
 
690
            `label` : string
 
691
                Item's label
 
692
            `callback_func` : function
 
693
                Callback function
 
694
            `items` : list
 
695
                List of thre-element tuples describing the color choices
 
696
            `default_item` : integer
 
697
                Default item of the list. It is an index of the list. Default: 0
 
698
        """
 
699
        self.my_label = label
 
700
        self.items = items
 
701
        self.idx = default_item
 
702
        if self.idx < 0 or self.idx >= len(self.items):
 
703
            raise Exception("Index out of bounds")
 
704
        super( ColorMenuItem, self).__init__( self._get_label(), callback_func )
 
705
 
 
706
    def _get_label(self):
 
707
        return self.my_label + "        "
 
708
 
 
709
    def on_key_press(self, symbol, modifiers):
 
710
        if symbol == key.LEFT:
 
711
            self.idx = max(0, self.idx-1)
 
712
        elif symbol in (key.RIGHT, key.ENTER):
 
713
            self.idx = min(len(self.items)-1, self.idx+1)
 
714
 
 
715
        if symbol in (key.LEFT, key.RIGHT, key.ENTER):
 
716
            self.item.text = self._get_label()
 
717
            self.item_selected.text = self._get_label()
 
718
            self.callback_func( self.idx )
 
719
            return True
 
720
 
 
721
    def generateWidgets (self, pos_x, pos_y, font_item, font_item_selected):
 
722
        font_item['x'] = int(pos_x)
 
723
        font_item['y'] = int(pos_y)
 
724
        font_item['text'] = self.my_label
 
725
        self.item = pyglet.text.Label(**font_item )
 
726
        self.item.labelWidth=self.item.content_width
 
727
        self.item.text = self.label
 
728
        font_item_selected['x'] = int(pos_x)
 
729
        font_item_selected['y'] = int(pos_y)
 
730
        font_item_selected['text'] = self.my_label
 
731
        self.item_selected = pyglet.text.Label( **font_item_selected )
 
732
        self.item_selected.labelWidth=self.item_selected.content_width
 
733
        self.item_selected.text = self.label
 
734
 
 
735
    def draw(self, *args, **kwargs):
 
736
        super(ColorMenuItem, self).draw()
 
737
        glPushMatrix()
 
738
        self.transform()
 
739
 
 
740
        if self.is_selected:
 
741
            item = self.item_selected
 
742
        else:
 
743
            item = self.item
 
744
 
 
745
        x1 = int(item._get_left() + item.labelWidth * 1.05)
 
746
        y1 = int(item.y - item.content_height / 2)
 
747
        y2 = int(item.y + item.content_height / 3)
 
748
        x2 = int(x1 + (y2 - y1) * 2)
 
749
        pyglet.graphics.draw(4, pyglet.graphics.GL_QUADS,
 
750
                             ('v2f', (x1, y1, x1, y2, x2, y2, x2, y1)),
 
751
                             ('c3B', self.items[self.idx] * 4))
 
752
        glPopMatrix()
 
753
 
 
754
 
 
755
def shake():
 
756
    '''Predefined action that performs a slight rotation and then goes back to the original rotation
 
757
    position.
 
758
    '''
 
759
    angle = 05
 
760
    duration = 0.05
 
761
 
 
762
    rot = Accelerate(RotateBy( angle, duration ), 2)
 
763
    rot2 = Accelerate(RotateBy( -angle*2, duration), 2)
 
764
    return rot + (rot2 + Reverse(rot2)) * 2 + Reverse(rot)
 
765
 
 
766
def shake_back():
 
767
    '''Predefined action that rotates to 0 degrees in 0.1 seconds'''
 
768
    return RotateTo(0,0.1)
 
769
 
 
770
def zoom_in():
 
771
    '''Predefined action that scales to 1.5 factor in 0.2 seconds'''
 
772
    return ScaleTo( 1.5, duration=0.2 )
 
773
 
 
774
def zoom_out():
 
775
    '''Predefined action that scales to 1.0 factor in 0.2 seconds'''
 
776
    return ScaleTo( 1.0, duration=0.2 )
 
777