Commits

Anonymous committed 7c1a1e9

Launcher: Text surface caching and mouse\pen support( no scrolling yet )
Startup: Draw it black after all
Building: Compile python libraries to .pyc( PyS60 CE feature )

Comments (0)

Files changed (3)

symbian/BuildPygameRelease.py

              # Convert to int or may be converted to octal due to zero at beginning
              'sisversion'   : '"(1,%d,%d%s)"' % ( int(version[2:4]), int( version[4:6]),version[6:]),
              'pythonsis'    : sisname,
-             'libpath'      : "data/pygame/libs",             
+             'libpath'      : "data/pygame/libs",
+             'pycompiler'   : "d:\\python22\\python.exe"
              }
     
     # Add certificate stuff

symbian/app/launcher/pygame_launcher.py

 """
 pygame launcher for S60 - Application for launching others
 """
+__author__ = "Jussi Toivola"
 
 from glob import glob
 
         self.screen   = None
         self.ticdiff  = 0
         self.tics     = 0
-        
-    def get_font_title(self):
+    
+    # SDL's wants to keep the font's file handle open.
+    # Symbian's c-library (estlib at least) does not let one to have
+    # multiple handles open to a single file (even though the Symbian's 
+    # RFile implementation does ).
+    # These functions should ensure that font is always closed
+    # after use. This, of course, causes performance issues due
+    # to repeated object instantation, file openings and reading. 
+    # The issue is handled here with TextCache class, which caches the surfaces
+    # of the rendered texts.
+    def getFontTitle(self):
         return pygame.font.Font(None, 36)
-    def get_font_normal(self):
+    def getFontNormal(self):
         return pygame.font.Font(None, 25)
-        #self.font_normal   = self.font_title#
-    def get_font_normal_b(self):
-        f = self.get_font_normal()
+    def getFontNormalBold(self):
+        f = self.getFontNormal()
         f.set_bold(True)
         return f
     def get_font_small(self):
         return pygame.font.Font(None, 18)
+
+class TextCache:
+    """ Handles text rendering and caches the surfaces of the texts for speed.
+    Suitable for small static texts, such as menu items and titles.
+    To avoid memory problems with very long lists, maximum cache size can be set.
+    If the cache's size is exceeded the new surface is added into the cache and
+    the oldest is removed.
+    """
+    def __init__(self, max_size=0):
+        
+        #: id->surface mapping
+        self.map = {}
+        
+        self.max_size = max_size
+        
+        #: dict does not preseve order so we'll need to manage it with a list
+        self.order = []
+        
+    def render(self, id, string, fontgetter, renderargs, force_update = False ):
+        """ Render string on the first time, but use it's cached surface next time. 
+        @param id: ID of the string
+        @param string: The text to render.
+        @param fontgetter: Function taking no parameters which returns a Font object.
+        @param renderargs: Arguments for font.render
+        @param force_update: Force re-rendering of the surface.
+        """
+        
+        # Check if exists
+        if id in self.map and not force_update:
+            return self.map[id]
+        
+        font = fontgetter()
+        surface = font.render(string, *renderargs)
+        self.map[id] = surface
+        
+        # Update cache's max size.
+        if self.max_size != 0:
+            
+            # No need to handle order if max_size is not set
+            self.order.append(id)
+            if len(self.order) > self.max_size:
+                del self.map[self.order[0]]
+                del self.order[0]
+        
+        return surface
         
 class Background(pygame.sprite.Sprite):
     """Main background"""
         
         self.screen = sysdata.screen
         screen_size = self.screen.get_size()
-        #middle = #screen_size[0] / 2., screen_size[1] / 2.
         
         self.background = pygame.Surface(screen_size, SRCALPHA)
         self.background.fill(BLACK)
         
-        self.rect       = self.background.get_rect()
-        middle = self.rect.center
+        self.rect = self.background.get_rect()
+        middle    = self.rect.center
         
         self.img_logo, r = load_image( "logo.jpg")
         self.img_pos = [ middle[0] - r.w / 2, middle[1] - r.h / 2 ]
-        
+        # Make alpha the same size as the image
         self.alpha = pygame.Surface(r.size, SRCALPHA)
         
         self.alphaval = 200.
         self.alphadir = -20. # per second
-      
+         
     def update_alphaval(self):
         """ Update the visibility of the logo """
         min = 200.
         self.alpha.fill( (0,0,0,self.alphaval) )
         
         self.background.blit( self.img_logo, self.img_pos )
-        self.background.blit(self.alpha, self.img_pos )
+        self.background.blit( self.alpha, self.img_pos )
         
 
 class TextField(pygame.sprite.Sprite):
-    """ Shows text """
+    """ Handles text rendering and updating when necessary """
     MODE_NONE   = 0
     MODE_CENTER = 1
     
         self.textsurface = surf
         self.text_changed = False
         
-    def update_title(self):
+    def updateTitle(self):
         """ Redraw title text """
         if not self.title_changed: return
             
-        text = self.sysdata.get_font_title().render(self._title, 1, (0, 255, 0))
+        text = self.sysdata.getFontTitle().render(self._title, 1, (0, 255, 0))
         textpos = text.get_rect()
         textpos.centerx = self.parent.get_rect().centerx
         textpos.centery = textpos.size[1] / 2 + 7
         self.titlepos = (0,0)
         
     def update(self):
-        self.update_title()
+        self.updateTitle()
         self.update_text()
         
         self.parent.blit(self.titlesurface, self.titlepos )
         if event.type == pygame.KEYDOWN:
             self.exit()
             return True
+        elif event.type == pygame.MOUSEBUTTONUP:
+            self.exit()
+            return True
         
         return False
     
     def __init__(self, parent, sysdata, title, items, cancel_callback):
         pygame.sprite.Sprite.__init__(self)
         
+        #: General information about the system
         self.sysdata = sysdata
+        
+        #: Parent surface where the menu contents are blit on.
         self.parent = parent
+        
+        #: Text of the title
         self._title = title
+        
+        #: If True, title surface is updated
         self.title_changed = True
         
+        #: Surface containing the rendered menu items.
+        self.itemsurface = None
+        
+        #: Strings of the items
         self._items = items
+        
+        #: Rects of list items to be used for mouse hit checking
+        self._itemrects = []
+        
+        #: If True, the surfaces are updated
         self.items_changed = True
+        
+        #: Index of selected menu item
         self._selected_index = 0
         
+        #: Index of previous selection
+        self._prev_index = 0
+        
         #: Index of the topmost visible item
         self.visibletop = 0
+        
         #: How many items the list can display
         self.shownitems = 0
         
         #: Callback called at exit
         self.cancel_callback = cancel_callback
-        print cancel_callback
         
+        #: Cached texts
+        self.textcache = TextCache()
+        
+        #: Flag to indicate if mouse/pen is down
+        self._mousedown = False
+    
+
+    #------------------------------------------------ Selection property get/set
     def _set_selection(self,index):
+        self._prev_index = self._selected_index
         self._selected_index = index
         self.items_changed = True
-    
+        self.updateVisible()
+        
     def _get_selection(self): return self._selected_index
     selection = property(fget=_get_selection, fset=_set_selection )
-    
-    def select_next_item(self):
+
+    #---------------------------------------------------- Title property get/set
+    def _set_title(self, title):
+        self._title = title
+        self.title_changed = True
+        
+    def _get_title(self): return self._title
+    title = property(fget=_get_title, fset=_set_title)
+
+    #----------------------------------------------------- Item property get/set
+    def _set_items(self, title): 
+        self._items = items
+        self.items_changed = True
+        
+    def _get_items(self): return self._items
+    items = property(fget=_get_items, fset=_set_items)
+
+    #---------------------------------------------------------- Public functions
+    def selectNextItem(self):
         """ Select next item from list. Loops the items """
         last = len(self._items) - 1
         if last < 1: return
             next = 0
         self.selection = next
         
-        self.update_visible()
-        
-    def select_prev_item(self):
+    def selectPrevItem(self):
         """ Select previous item from list. Loops the items """
         last = len(self._items) - 1
         if last < 1: return
         if next < first:
             next = last
         self.selection = next
-        
-        self.update_visible()
-    
-    def update_visible(self):
-        """ Updates position of the topmost visible item in the list """
-        diff = abs(self.visibletop - self._selected_index)
-        if diff >= self.shownitems:
-            self.visibletop = max(0,min( self._selected_index - self.shownitems+1, self._selected_index ))
-        
-    def select_item(self):
+         
+    def doSelect(self):
         """ Handle item selection by invoking its callback function """
         title, callback,args = self._items[self._selected_index]
         callback(*args)
     
-    def _set_title(self, title):
-        self._title = title
-        self.title_changed = True
-        
-    def _get_title(self): return self._title
-    title = property(fget=_get_title, fset=_set_title)
-    
-    def _set_items(self, title): 
-        self._items = items
-        self.items_changed = True
-        
-    def _get_items(self): return self._items
-    items = property(fget=_get_items, fset=_set_items)
-    
     def cancel(self):
+        """ Invokes the menu cancellation handler """
         cb,args = self.cancel_callback
         cb(*args)
-    
-    def handleEvent(self, event ):
-        if event.type == pygame.KEYDOWN:
-            if event.key == constants.K_DOWN:
-                self.select_next_item()
-                return True
-            
-            elif event.key == constants.K_UP:
-                self.select_prev_item()
-                return True
-            
-            if event.key == constants.K_RETURN:
-                self.select_item()
-                return True
-            
-            if event.key == constants.K_ESCAPE:
-                self.cancel()
-                return True
-            
-        return False
-    
+        
     def clear(self):
         " Remove the used surfaces from memory "
         self.itemsurface = None
         self.titlesurface = None
         self.items_changed = True
         self.title_changed = True
+        
+    def checkMouseCollision(self,event):
+        """ Checks if mouse event collides with any of the menu items """
+        # Not yet known.
+        if self.itemsurface is None: return False
+        
+        # Check if we hit the surface containing the items
+        menurect = pygame.Rect(self.itemsurface.get_rect())
+        menurect.top = self.itemspos[1]
+        
+        if menurect.collidepoint(event.pos):
+            for x in xrange(len(self._itemrects)):
+                r = Rect(self._itemrects[x])
+                r.top += self.itemspos[1]
+                if r.collidepoint(event.pos):
+                    self.selection = x
+                    return True
+        
+        return False
     
-    def max_items_shown(self):
+    def handleEvent(self, event ):
+        """ Handle events of this component """
+        
+        if event.type == pygame.KEYDOWN:
+            if event.key == constants.K_DOWN:
+                self.selectNextItem()
+                return True
+            
+            elif event.key == constants.K_UP:
+                self.selectPrevItem()
+                return True
+            
+            if event.key == constants.K_RETURN:
+                self.doSelect()
+                return True
+            
+            if event.key == constants.K_ESCAPE:
+                self.cancel()
+                return True
+        
+        # Mouse button down and movement only marks the item selected
+        if event.type == pygame.MOUSEBUTTONDOWN:
+            self.checkMouseCollision(event)
+            self._mousedown = True
+        
+        # When pen is pressed on surface, user can keep changing the
+        # selection and activate the selection by releasing the pen.
+        elif event.type == pygame.MOUSEMOTION:
+           self.checkMouseCollision(event)
+        
+        # Mouse button up selects the item
+        elif event.type == pygame.MOUSEBUTTONUP:
+            self._mousedown = False
+            
+            # User can cancel selection by moving the pen outside of all items
+            if self.checkMouseCollision(event):
+                self.doSelect()
+        
+        return False
+    
+     
+    def computeVisibleItemCount(self):
         """ Calculate the amount of items that fit inside the list """
 
-        height = self.sysdata.get_font_normal().get_height() * 1.5
+        height = self.sysdata.getFontNormal().get_height()
         h = self.itemsurface.get_height()
-        return int(round((h / height))) - 1
-     
-    def update_items(self):
+        c = int(round((h / height))) - 1
+        return c
+    
+    def update(self):
+        self.updateTitle()
+        self.updateItems()
+        
+        self.parent.blit( self.titlesurface, self.titlepos )
+        self.parent.blit( self.itemsurface,  self.itemspos )
+        
+    def updateItems(self):
         """ Update list item surface """
         if not self.items_changed: return
         
         startposx = 10
         self.itemspos = ( 0, startposy )
         
+        # Create and cache the list background
         psize = self.parent.get_size()
         size = ( psize[0], psize[1] - startposy)
         surf = pygame.Surface(size, SRCALPHA)
         self.itemsurface = surf
-        self.shownitems  = self.max_items_shown()
+        self.shownitems  = self.computeVisibleItemCount()
         
-        # Create and cache the list background
         rect = pygame.Rect( 5, 0, size[0]-10, size[1]-5)
-        
         pygame.draw.rect(surf, MENU_BG, rect)
         pygame.draw.rect(surf, TITLE_STROKE, rect, 2)
         
         
         self.visibletop = min(self.visibletop, self._selected_index)
         maximumpos = min(len(items), self.visibletop + self.shownitems )        
-        height = self.sysdata.get_font_normal().get_height()
+        height = self.sysdata.getFontNormal().get_height()
         spaceheight = height + 10
-        font = None
+        
+        self._itemrects = []
+        # Draw the items
         for x in xrange(self.visibletop,maximumpos):
             i,cb,args = items[x]
-            #print i      
-            if x == self._selected_index:                 
-                del font # Close the font file
-                font = self.sysdata.get_font_normal_b()
+            
+            id = i
+            if x == self._selected_index:
+                font = self.sysdata.getFontNormalBold
                 color = ITEM_SELECTED_TEXT
                 bgcolor = ITEM_SELECTED
+                id = "_"+id
             else:
-                del font # Close the font file
-                font = self.sysdata.get_font_normal()
+                font = self.sysdata.getFontNormal
                 color = ITEM_UNSELECTED_TEXT
                 bgcolor = ITEM_UNSELECTED
-            
+                
             s = ( size[0]-startposx*2- 15, spaceheight )
             pos  = ( startposx, startposy )
-                        
-            # Draw text
-            text = font.render(i, 1, color)
+            
+            # Use the text cache for speed.
+            text = self.textcache.render( id, i, font, ( 1, color))
             textpos = text.get_rect()
             textpos.centerx = self.parent.get_rect().centerx
             textpos.centery = pos[1] + s[1] / 2
             
-            # Add to list            
+            # Add to list
             surf.blit( text, textpos )
             startposy = startposy + height
-        
+            
+            self._itemrects.append(textpos)
+            
         self.items_changed = False
         
-    
-    def update_title(self):
+    def updateTitle(self):
         """ Update title surface """
         if not self.title_changed: return
         
-        self.text = text = self.sysdata.get_font_title().render(self._title, 1, (0, 255, 0))
+        # Render the title text
+        text = self.sysdata.getFontTitle().render(self._title, 1, (0, 255, 0))
         self.textpos = textpos = text.get_rect()
         textpos.centerx = self.parent.get_rect().centerx
         textpos.centery = textpos.size[1] / 2 + 7
         self.size = size = self.parent.get_size()
         size = ( size[0], 40 )
 
-        surf = titlebg = pygame.Surface(size, SRCALPHA)
-        self.titlebgrect = titlebgrect = pygame.Rect( 5, 5, size[0]-10, size[1]-10)
+        # Render the final title surface with combined background and text 
+        surf = pygame.Surface(size, SRCALPHA)
+        titlebgrect = pygame.Rect( 5, 5, size[0]-10, size[1]-10)
         pygame.draw.rect(surf, TITLE_BG, titlebgrect)
         pygame.draw.rect(surf, TITLE_STROKE, titlebgrect, 2)
-        surf.blit(self.text, textpos )
+        surf.blit(text, textpos )
         
+        # Update data
         self.title_changed = False
+        self.titlesurface = surf
         
-        self.titlesurface = surf
         # Position on parent
         self.titlepos = (0,0)
-        
-    def update(self):                    
-        self.update_title()
-        self.update_items()
-        
-        self.parent.blit( self.titlesurface, self.titlepos )
-        self.parent.blit( self.itemsurface,  self.itemspos )
-        
+    
+    def updateVisible(self):
+        """ Updates position of the topmost visible item in the list """
+        diff = abs(self.visibletop - self._selected_index)
+        if diff >= self.shownitems:
+            self.visibletop = max(0,min( self._selected_index - self.shownitems+1, self._selected_index ))
+         
 class Application(object):
+    """ Main application handler.
+    Takes care of system initialization and main application states.
+    """
     def __init__(self):
         if os.name == "e32":
             size = pygame.display.list_modes()[0]
         self.app_to_run = None
         
         #: Updated by foreground event
-        self.isForeground = True
+        self.is_foreground = True
         
     def initialize(self):
         pass
         while self.running:
             
             for event in pygame.event.get():
-                print event
+                #print event
                 eventhandler(event)
             
-            if self.isForeground:
+            #print "update", self.is_foreground
+            if self.is_foreground:
                 self.sprites.update()
-                
-                self.screen.blit(self.background.background, (0,0))
-                
-                pygame.display.flip()
-                
-                self.clock.tick(22)
-                
+            
+            self.screen.blit(self.background.background, (0,0))
+            
+            pygame.display.flip()
+            
+            if self.is_foreground:
+                self.clock.tick(20)
             else:
                 # Longer delay when in backround
-                self.clock.tick(1)
+                self.clock.tick(5)
 
             tics = pygame.time.get_ticks()
             self.sysdata.ticdiff = tics - self.sysdata.tics
         self.focused = Menu(self.background.background, self.sysdata, 
                         title = "Applications",
                         items = items,
-                        cancel_callback = ( self.mhLaunchApplication, (None,) )
+                        cancel_callback = ( self.mhLaunchApplication, (None,) ),
                         )
+        self.focused.textcache.max_size = 24
+        
         self.sprites.add(self.focused)
         
     def mhExit(self):
         www.pygame.org
         www.launchpad.net/pys60community
         
-        Author: Jussi Toivola
-        
         """
         
         self.sprites.remove(self.focused)
         
     def handleEvent(self, event ):
         if self.isExitEvent(event):
-            print "Exit event received!"
+            #print "Exit event received!"
             self.running = 0
             return
         
-        if self.isForeground:
+        # Update foreground state
+        if event.type == pygame.ACTIVEEVENT and os.name == "e32":
+            self.is_foreground = event.gain
+        
+        if self.is_foreground:
             if self.focused is not None:
                 handled = self.focused.handleEvent(event)
                 if not handled:
          
     pygame.init() 
     while True:
-                            
+        
+        # Don't handle events given for launched application
+        pygame.event.clear()
+        
         a = Application()
         # The executable is received
         path_to_app = a.run()

symbian/app/pygame_app.cpp

 
 void CSDLWin::Draw(const TRect& /*aRect*/) const
 {
-	// Be transparent until app is ready. I don't want to force anyone use black initial screen.
-#if(0)	
+	// Draw black( it will be white otherwise and that's even worse looking )
+	// TODO: Take a screenshot and maybe do some kind of fade thingy
 	CWindowGc& gc = SystemGc();
 	gc.SetDrawMode( CGraphicsContext::EDrawModeWriteAlpha );
 	gc.Clear();
 	gc.SetPenStyle(CGraphicsContext::ENullPen);
 	gc.SetPenColor(0x000000);
 	gc.SetBrushStyle(CGraphicsContext::ESolidBrush);
-	gc.SetBrushColor(0x80010101);
+	gc.SetBrushColor(0x000000);
 	gc.DrawRect(Rect());
-#endif
+
 }
 /*
  TKeyResponse CSDLWin::OfferKeyEventL(const TKeyEvent &aKeyEvent, TEventCode aType)