2D API Wisdom

For any of you who use the 2D API extensively a word of wisdom ( which may be obvious to others, and now is quite obvious to me, but was not ) make sure you REMOVE your temporary sprites from the compositor when you are finished with them, especially ones that are created locally and frequently for animation effects. If not, you will quickly consume memory even though the bitmap and region were created locally. If the Compositor is not local it maintains a reference or a copy. Calling the local function numerous times just keeps adding another sprite to the compositor and you will notice your animations quickly SLOW down. The following code is used to scale/fade out. It clones the region and creates another sprite to do the animation:

Function aud_fade_scale(a_speed = 70 As Integer) As Void

   ' Get current coordinates/dimensions of the selection sprite
   l_x = m.ListSelectSprite.GetX()
   l_y = m.ListSelectSprite.GetY()
   l_w = m.ListSelectBmp.GetWidth()
   l_h = m.ListSelectBmp.GetHeight()
   
   ' Set the region to best quality rendering
   m.ListSelectRegion.SetScaleMode(1)
   
   ' Draw the region into the bitmap used for the animation
   l_bitmap = CreateObject("roBitmap", {width: l_w, height: l_h , AlphaEnable: False})
   l_bitmap.DrawObject(0, 0, m.ListSelectRegion)
   
   ' Create a region into the animation bitmap the size of the entire bitmap
   l_region = CreateObject("roRegion", l_bitmap, 0, 0, l_w, l_h)
   
   ' Create the clone sprite at the current position of the original
   l_sprite = m.Compositor.NewSprite(l_x, l_y, l_region, m.ZStart + 2)
   
   ' Hide the original sprite
   m.ListSelectSprite.SetZ(-1)
 
   ' Set initial alhpa value -- Thanks MARK !!!
   l_fade = &hFFFFFF00
 
   ' Perform the scale/fade
   for l_i = a_speed to 0 step -2
     
      ' Calculate percent offsets
      l_scale   = l_i / a_speed
      l_newFade = int(100 * l_scale)
      
      ' Keep the scaled clone centered as it's size changes
      l_x = (l_w - int(l_w * l_scale)) / 2 
      l_y = (l_h - int(l_h * l_scale)) / 2
       
      ' Draw the scaled clone
      l_bitmap.Clear(m.Transparent)
      l_bitmap.DrawScaledObject(l_x, l_y, l_scale, l_scale,  m.ListSelectRegion, l_newFade )
        
      m.DrawAll()
    
   end for  

  ' Make sure to remove the clone sprite. Even though everything appears local
  ' The Compositor is not local and keeps a reference  or copy of it.  Each time you re-create the
  ' sprite a new one is actually added (makes sense) quickly consuming memory

  l_sprite.Remove()

  ' Show the permanent sprite
  m.ListSelectSprite.SetZ(m.ZStart + 3)
  
  m.DrawAll()
   
End Function

Correction for fade. This is the first time I’m trying out the undocumented alpha feature of the drawing functions. Apparently only the (A)lpha is used on the RGBA
value . For a perfect fade you only need to use 0 - 255 or 255 - 0 (&hFF) depending on what direction you are going.
So Fading In would be: l_newFade = int(255 * l_scale). Changing the RGB values does nothing. Would be nice if it added a hue to the fade. Of course after I discover this further I may be changing it again. But so far this works very well

Well I’ll try this and see how well it goes. This sample uses the api to create a listview with some effects. It was tested under ROKU 3 latest everything. All you have to do to use it is copy the entire code section of the post into a file and run it. It does not use resources or access any servers.
If you have any questions feel free to ask. It gives a good overview of many of the api features


Function Main() As Void

  ' You only need one roScreen.  Use the setdrawto feature of roCompositor to
  ' Emulate a screen stack. In this example 
   m.APPScreen = CreateObject("roScreen", True)
   m.APPBkgClr = &hE0DFDFFF
   m.APPScreen.Clear(&hE0DFDFFF)
   
   ' Create the list view passing along the handle to the screen
   ' A composite will be attached to the entire screen which gives the
   ' appearance of opening a new roScreen object
   ne = NewListView(m.APPScreen)
   ne.Initialize()
   ne.Show()
   
   ne.Clear()
   ne = Invalid

End Function

' Create an example object
Function NewListView(a_roScreen As Object) As Object

   lv = CreateObject("roAssociativeArray")
   
   ' Base component objects
   lv.Screen     = a_roScreen
   lv.Device     = Invalid
   lv.Timer      = Invalid
   lv.Port       = Invalid
   lv.Compositor = Invalid
   
   ' Registry and fonts declarations
   lv.DefaultRegistry = Invalid
   lv.FontMedium = Invalid
  
   ' Data for the example
   lv.DataList = Invalid
   
   ' The Buffer bitmap is the drawing area
   ' for the datalist.  If interested I'll demonstrate
   ' How to create a virtual buffer for large data lists
   ' This example uses a fixed buffer the size of the data list
   ' If your datalists are very large then you can quickly run into 
   ' memory problems or exceed the size of a bitmap.  
   ' Each bitmap has a region or viewable area
   ' into the bitmap.  The region lets you define how many lines of the
   ' list you want to display, while the rest are hidden and scrolled in/out view
   ' The sprite lets you pin the menu anywhere on the screen that you want to
   ' Both the region and the sprite are useful in creating animation and effects
   
   lv.BufferBitmap = Invalid
   lv.BufferRegion = Invalid
   lv.BufferSprite = Invalid
   
   ' Background of the list.  
   lv.BackgroundBitmap = Invalid
   lv.BackgroundRegion = Invalid
   lv.BackgroundSprite = Invalid
   
   ' Size of menu and item size
   lv.ListWidth     = 0
   lv.ListHeight    = 0
   lv.ListRowHeight = 0
   lv.ListLineSpace = 0
   lv.ListPageSize  = 5
   
   ' Colors used in the application
   lv.Transparent = &h00000000
   lv.ScrBkgClr   = &hE0DFDFFF
   lv.DlgBkgClr   = &hCECECEFF
   lv.DlgFrameClr = &h808080FF
   lv.DlgBlueClr  = &h85AEFFFF
   lv.TextClr     = &h003366FF
   lv.DarkRedClr  = &h7A0000FF
   
   lv.Initialize = list_view_initialize
   lv.Show       = list_view_show
   lv.EventLoop  = list_view_eventloop
   
   lv.ScrollLine = list_view_scroll_line
   lv.ScrollPage = list_view_scroll_page
   
   lv.FadeScale = list_view_fade_scale
   
   lv.DrawAll = list_view_drawall
   
   return lv
   
End Function

Function list_view_initialize() As Void

   ' Create all objects to be used
   m.Device     = CreateObject("roDeviceInfo")
   m.Timer      = CreateObject("roTimeSpan")
   m.Port       = CreateObject("roMessagePort")
   m.Compositor = CreateObject("roCompositor")
  
   ' Create a default registry and get the font
   m.DefaultRegistry = CreateObject("roFontRegistry")
   m.FontMedium = m.DefaultRegistry.GetDefaultFont(28, False, False) 
   
    ' Get the sample data list and save its count
   m.DataList = GetData()
   l_count = m.DataList.Count()
   
   ' Get the constant one-line-height of the font. This value does not change
   ' The row height will be the combination of any extra line spacing plus text height
   l_oneLineHeight = m.FontMedium.GetOneLineHeight()
   m.ListLineSpace = 12
   ' Save the row height for building the buffer and regions, and to offset the regions
   ' for scrolling
   m.ListRowHeight = l_oneLineHeight + m.ListLineSpace
   
   ' Calculate a Length for the BitmapBuffer based upon the number of items
   ' times the height of the font plus any additional line spacing you wish
   ' This could get large if you have a very large list.  A virtual buffer is
   ' a little more complex, but it is fast and has a very small memory imprint
   ' I can demonstrate a virtual buffer if there is enough interest
   l_height = l_count * m.ListRowHeight
   
   ' Calculate the maximum width.  This is not the best way to do this as it
   ' is redundant and expensive in processing but for this example, to keep it
   ' short I am going to use it.  But there is a better way, for example saving 
   ' l_width to a parallel array or associative data container class which is what
   ' I use. I also center it and just offset it by the difference between the approximated
   ' and actual size when I write it to the bitmap
   l_maxWidth = 0
   for each l_text in m.DataList
      l_width = m.FontMedium.GetOneLineWidth(l_text, 1000)
      if l_maxWidth < l_width then l_maxWidth = l_width
   end for
  
   ' Create the buffer bitmap according to the precalulated maximum width and height
   ' Clear it to the desired color
   m.BufferBitmap = CreateObject("roBitmap", {width: l_maxWidth, height: l_height, AlphaEnable: False})
   m.BufferBitmap.Clear(m.DlgBkgClr)
   
   ' Draw the text into the buffer centered.  Increment the y pos by  m.ListRowHeight
   ' This is all redundant as mentioned earlier. I do not use this method in my own application
   l_x = 0
   l_y = 0
   l_halfWidth = l_maxWidth / 2 
  
   for l_i = 0 to l_count - 1
   
      l_text = m.DataList[l_i]
      
      ' Paint the first line red
      l_clr  = m.TextClr
      if l_i = 0 then l_clr = m.DarkRedClr
     
      l_width = m.FontMedium.GetOneLineWidth(l_text, l_maxWidth)
      l_x = int(l_maxWidth / 2 - l_width / 2)
      m.BufferBitmap.DrawText(l_text, l_x, l_y, l_clr, m.FontMedium)
      l_y = l_y + m.ListRowHeight
      
   end for
   
   ' Now I want to display only listPageSize of data at one time. This is what
   ' a region is for (among many other good things as you will see).  Calculate the
   ' same way as you would for the bitmap. If you want a more level scrolling
   ' leave off the bottom linespace. the 0,0 offset means to start the view at
   ' the 0,0 offsets in the bitmap, continue the entire width of the bitmap, but only
   ' show the first 5 lines.  Then you just offset this region for scroling
   l_height = m.ListPageSize * m.ListRowHeight - m.ListLineSpace
   m.BufferRegion = CreateObject("roRegion", m.BufferBitmap, 0, 0, l_maxWidth, l_height)
   ' Set the regions wrap to true, this way it handles all the scrolling for you
   m.BufferRegion.SetWrap(True)
   
   ' Create the background bitmap.  Increase by 30 for a frame and inset border.  Note that
   ' the frame should be surrounding the buffers region not the buffer bitmap as the region's
   ' length is smaller only showing a page size of rows
   l_width  = m.BufferRegion.GetWidth()  + 30
   l_height = m.BufferRegion.GetHeight() + 30
   m.BackgroundBitmap = CreateObject("roBitmap", {width: l_width, height: l_height, AlphaEnable: False})
   
   ' Create a Frame - You could use drawline but it will only be done once. Color with frame color, offset
   ' and rectangle in the dialog color.
   m.BackgroundBitmap.Clear(m.DlgFrameClr)
   m.BackgroundBitmap.DrawRect(5, 5, l_width - 10, l_height - 10, m.DlgBkgClr)
   ' Create the region which will be the entire frame.  Many times you only create regions once
   ' or can disgard their handles after the compositor creates the sprite.  You only save them
   ' when you want to maniuplate them or copy them later
   m.BackgroundRegion = CreateObject("roRegion", m.BackgroundBitmap, 0, 0, l_width, l_height)
 
   ' Ok lets pin all this in the center with a couple of sprites
   ' Get the display size and calculate and center the listview frame. Then you
   ' can just drop in the buffer bitmap right over it using the 30/2 pixel offsets used above
   ' You can continue using the l_width, l_height since the region and bitmap are the same size
   ' for the background
   l_device_rect = m.Device.GetDisplaySize()
   l_x = int(l_device_rect.w / 2 - l_width / 2)
   l_y = int(l_device_rect.h / 2 - l_height / 2)
  
   ' Place the frame at the lower z order
   m.BackgroundSprite = m.Compositor.NewSprite(l_x, l_y, m.BackgroundRegion,  2)
   ' Then drop in the bitmap buffer on top of it at the 30/2 pixel offsets
   m.BufferSprite = m.Compositor.NewSprite(l_x + 15, l_y + 15, m.BufferRegion,  3)
   
   ' Create a header and footer all this can be placed in functions. It is left here for example
   ' Don't need to remove from the compositor as they are created once and reamin for the program
   l_font = m.DefaultRegistry.GetDefaultFont(32, False, True) 
   l_headerText = "PROVERBS CHAPTER 2"
   l_width  = l_font.GetOneLineWidth(l_headerText, 800)
   l_height = l_font.GetOneLineHeight()
   l_bitmap = CreateObject("roBitmap", {width: l_width, height: l_height, AlphaEnable: False})
   l_bitmap.Clear(m.ScrBkgClr)
   l_bitmap.DrawText(l_headerText, 0, 0, &h000000FF, l_font)
   l_region = CreateObject("roRegion", l_bitmap, 0, 0, l_width, l_height)
   l_x = int(l_device_rect.w / 2 - l_width / 2)
   m.Compositor.NewSprite(l_x, 150, l_region, 1)
    
   
   l_font = m.DefaultRegistry.GetDefaultFont(32, False, False) 
   l_footerText = "Use The Page And Arrow Keys To Scroll,  Info Key To Toggle Scale/Fade"
   l_width  = m.FontMedium.GetOneLineWidth( l_footerText, 1000)
   l_height = m.FontMedium.GetOneLineHeight()
   l_bitmap = CreateObject("roBitmap", {width: l_width, height: l_height, AlphaEnable: False})
   l_bitmap.Clear(m.ScrBkgClr)
   l_bitmap.DrawText(l_footerText, 0, 0,  &h222835FF, m.FontMedium)
   l_region = CreateObject("roRegion", l_bitmap, 0, 0, l_width, l_height)
   l_x = int(l_device_rect.w / 2 - l_width / 2)
   m.Compositor.NewSprite(l_x, l_device_rect.h - 175, l_region, 1)
   
   
   return
   
End Function

' Hook up the compositor to the screen and drawall previously 
' created above.  Poll the message port for input
Function list_view_show() As Void
   
   m.Screen.SetMessagePort(m.Port)
   m.Compositor.SetDrawTo(m.Screen, m.ScrBkgClr)
   
   m.DrawAll()
   
   m.EventLoop()
   
End Function

' The composite creates and maintains sprites. Sprites are based upon
' regions within bitmaps.  The region could be the entire bitmap if desired
' The compositor draws all its sprites as defined above. 
' It simplifies and optimizes for applications that
' have alot of bitmaps
Function list_view_drawall() As Void

   m.Compositor.DrawAll()
   m.Screen.SwapBuffers()
   
End Function

' Slide up/down by offseting the region by rowheight in the desired direction
' You dont have to worry about any bounds as setwrap is true
Function list_view_scroll_line(a_goDown = True As Boolean, a_frames = 16 As Integer) As Void

   l_offset  = 0
   l_offdiff = 0
   l_prevset = 0

    for l_i = 1 to a_frames
   
      l_offset  = int(m.ListRowHeight * l_i / a_frames)
      l_offdiff = l_offset - l_prevset
      
      if l_offdiff > 0 
        
         if not a_goDown l_offdiff = -l_offdiff
         
         m.BufferRegion.Offset(0, l_offdiff, 0, 0)
        
         m.DrawAll()
      
         l_prevset = l_offset
      end if
     
   end for
   
End Function

' Since the setwrap is on for the bufferregion.  You just offset it
' by the page size in either direction and it will wrap itself around
' This paging example does not slide like the rows do but you can
' just use the code above with the l_height variable
Function list_view_scroll_page(a_goDown = True As Boolean) As Void

   l_height =  m.ListPageSize * m.ListRowHeight
   
   if not a_goDown l_height = -l_height
  
   m.BufferRegion.Offset(0, l_height, 0, 0)
        
   m.DrawAll()
 
End Function

' This example takes a real-time snapshot of list view to perform its effect
' to do this is pretty easy once you understand it
Function list_view_fade_scale(a_in = True, a_speed = 20 As Integer) As Void

   ' Alpha enable the screen
   l_alphaEnable = m.Screen.GetAlphaEnable()
   m.Screen.SetAlphaEnable(True)
   
  ' Get background x,y width and height 
  ' The sprite gives the position
  ' The bitmap (or the reigon if it is created the same size) gives the 
  ' width and height
   l_x = m.BackgroundSprite.GetX()
   l_y = m.BackgroundSprite.GetY()
   l_w = m.BackgroundRegion.GetWidth()
   l_h = m.BackgroundRegion.GetHeight()
   
   
   ' Create a temporary clone bitmap the same size and draw the backgrounds region
   ' into it.  Note that the background's region is the same size as the bitmap
   ' So in this case the bitmap or the region can be used as the source for drawobject
   l_cloneBitmap = CreateObject("roBitmap", {width: l_w, height: l_h , AlphaEnable: False})
   l_cloneBitmap.DrawObject(0, 0, m.BackgroundRegion)
   ' Draw the buffer bitmap's region on top of that at the same x, y inset when ititialized.  
  ' Since the region is smaller than the bitmap you would not use the bufferbitmap as a source
   l_cloneBitmap.DrawObject(15, 15, m.BufferRegion)
  
   ' Create a region into the cloned bitmap that exposes the entire length, width of the bitmap
   ' Set its scale mode to 1 for best rendering
   l_cloneRegion = CreateObject("roRegion", l_cloneBitmap, 0, 0, l_w, l_h)
   l_cloneRegion.SetScaleMode(1)
   
   ' Now that there is a perfect picture of the current state of the list view
   ' create a scrap bitmap and have some fun with it.  The cloned region will
   ' be drawn into the scrap bitmap at various scaling/fading percentages
   l_scrapBitmap = CreateObject("roBitmap", {width: l_w, height: l_h , AlphaEnable: False})
   ' Optional - perform the first draw at normal dimensions
   l_scrapBitmap.DrawObject(0,0, l_cloneRegion)
   ' Create a region in the scrap.  This is only needed because the compositor requires
   ' a region to create a sprite
   l_region = CreateObject("roRegion",  l_scrapBitmap, 0, 0, l_w, l_h)
   ' Position the scrap bitmap directly over the original sprites
   ' KEEP the handle you need to explicitly release it or the memory WIL NOT be released
   l_sprite = m.Compositor.NewSprite(l_x, l_y, l_region, 3)
   
   ' Hide the original sprites
   m.BufferSprite.SetZ(-1)
   m.BackgroundSprite.SetZ(-1)
  
   ' Set up the rgb value. 
   l_rgb = &hFFFFFF00
   
   ' Perform the scale/fade
   for l_i = 0 to a_speed 
      
      ' Just reverse everything when doing the opposite
      if a_in
         l_scale = l_i / a_speed
      else
         l_scale = (a_speed - l_i) / a_speed
      end if
     
      ' Add the alpha percentage to the rgb value
      l_rgba = int(255 * l_scale) + l_rgb
     
      ' Keep the scaled region centered as it's size changes
      ' always using the background bitmaps dimensions as a guide
      ' You could also use the scrap bitmap since its parameters
      ' are the same but why the extra code
      l_x = (l_w - int(l_w * l_scale)) / 2 
      l_y = (l_h - int(l_h * l_scale)) / 2
      
      ' Clear out the old 
      'Draw int the new region centered in the bitmap with the new scale and fade percentages
      l_scrapBitmap.Clear(m.Transparent)
      l_scrapBitmap.DrawScaledObject(l_x, l_y, l_scale, l_scale,  l_cloneRegion,  l_rgba )
      
      ' Dump to the screen
      m.DrawAll()
     
   end for
   
   ' Make sure to REMOVE the clone sprite. Even though everything appears local
   ' The Compositor is not local and keeps a reference to it.  Each time you re-create the
   ' sprite a new one is actually added (makes sense) quickly consuming memory
   ' Thankfully somone was sensible enough to let us have r2d2_bitmaps telnet command
   ' Removing the sprite removes it from the screen as well
   l_sprite.Remove()
  
   ' reshow the permanent sprite if fading in
   if a_in then
     
      m.BackgroundSprite.SetZ(2)
      m.BufferSprite.SetZ(3)
     
      m.DrawAll()
   end if
   
   ' Restore the original value to the screen
   m.Screen.SetAlphaEnable(l_alphaEnable)
   
End Function

' This event loop is primarily for the example.  You would want to use
' a timer for more accurate speed regulation.  But this does a pretty good job
' I created the timer but I dont use it here.  I do in my own code
Function list_view_eventloop() As Void

   l_running = True
   l_fadeIn  = False
   l_index   = 0
   l_lastKey = -1
   l_scrollSpeed = 120
   
   l_kp_BK   = 0
   l_kp_UP   = 2
   l_kp_DN   = 3
   l_kp_OK   = 6
   l_kp_RW   = 7
   l_kp_REV  = 8
   l_kp_FWD  = 9
   l_kp_INFO = 10 
   
   l_kp_UP_REL = 102
   l_kp_DN_REL = 103
  
   
   while(l_running)
   
      l_msg = wait(l_scrollSpeed, m.port) 
      
      if type(l_msg) = "roUniversalControlEvent" 
      
         l_index   = l_msg.GetInt()
         l_lastKey = l_index
        
         if l_index = l_kp_UP or l_index = l_kp_DN
             
            m.ScrollLine(l_index = l_kp_DN)
         
         else if l_index = l_kp_FWD or l_index = l_kp_REV
         
            m.ScrollPage(l_index = l_kp_FWD)
         
         else if l_index > 100 ' All key ups are value + 100
           
            l_lastKey = -1
            l_scrollSpeed = 120
          
         else if l_index = l_kp_BK
          
            l_running = False
            
         else if l_index = l_kp_INFO
         
            m.FadeScale(l_fadeIn)
            l_fadeIn = not l_fadeIn
            
         end if
         
      else
     
        if l_lastKey = l_kp_UP or l_lastKey = l_kp_DN 
        
           l_scrollSpeed = 5
           m.ScrollLine(l_lastKey = l_kp_DN)
    
        end if
        
      end if
         
      
   end while
  

End Function

Function GetData() As Object

   l_proverbs = [
   
      "Make Your Ear Attentive To Wisdom",
      "Incline Your Heart To Understanding",
      "Cry Out For Discernment",
      "Lift Up Your Voice For Understanding",
      "If You Seek Her Like Silver And",
      "Search For Her Like Hidden Treasures",
      "Then You Will Understand The Fear",
      "Of Jehovah And Find The Knowledge Of God",
      "For Jehovah Gives Wisdom; From His Mouth",
      "Come Knowledge And Understanding",
      "He Stores Up Sound Wisdom For The Upright",
      "He Is A Shield To Those Who Walk In Integrity",
      "Guarding The Paths Of Justice And Keeping",
      "The Way Of His Faithful Ones",
     
     ]

   return l_proverbs
   
End Function

Great example, NewManLiving. Thanks for sharing!

One thing worth noting, is that with your buffer bitmap and set wrap technique, the number of entries you can add to the list are limited (~42 using the font you use in the example), because the maximum height for a single bitmap is 2048.
I also noticed that the example doesn’t work if there are fewer than five items in the list. I haven’t dug in too deep, but I suspect it’s because your BufferRegion is invalid, because it’s bigger than the BufferBitmap.

Yes I did mention that twice about the size of the bitmap. I have created virtual buffers for my own applications and I am willing to share the code as I said in my comments. Sorry I did not test
it I threw it together quickly . You are right about the page size. In my own applications the page size Is set dynamically according to the size of the container. And the buffer and regions follow along accordingly. This sample is old code that I developed without a virtual buffer mechanism. I will make the necessary corrections, but it is after all a small example. The region cannot exceed the size of the bitmap or it will fail in creation. Thanks much for your input as always
You know, I still get drawn to that aquarium from time to time

Thanks for posting this, I certainly appreciate it.

1 Change so far in the Function list_view_SetList(a_data As Object, a_pageSize As Integer) As Boolean
Delete the DrawAll(() at the end of the function. SetList is not responsible for drawing + the temp flash you get when selecting other datasets is eliminated
The caller shows it when ready