Roku Developer Program

Join our online forum to talk to Roku developers and fellow channel creators. Ask questions, share tips with the community, and find helpful resources.
cancel
Showing results for 
Show  only  | Search instead for 
Did you mean: 
NewManLiving
Visitor

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
My Channels: 2D API Framework Presentation: https://owner.roku.com/add/2M9LCVC
Updated: 11-11-2015 - Completed Keyboard interface
The Joel Channel ( Final Beta )
0 Kudos
7 REPLIES 7
NewManLiving
Visitor

Re: 2D API Wisdom

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
My Channels: 2D API Framework Presentation: https://owner.roku.com/add/2M9LCVC
Updated: 11-11-2015 - Completed Keyboard interface
The Joel Channel ( Final Beta )
0 Kudos
NewManLiving
Visitor

Re: 2D API Wisdom

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






My Channels: 2D API Framework Presentation: https://owner.roku.com/add/2M9LCVC
Updated: 11-11-2015 - Completed Keyboard interface
The Joel Channel ( Final Beta )
0 Kudos
TheEndless
Channel Surfer

Re: 2D API Wisdom

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.
My Channels: http://roku.permanence.com - Twitter: @TheEndlessDev
Instant Watch Browser (NetflixIWB), Aquarium Screensaver (AQUARIUM), Clever Clocks Screensaver (CLEVERCLOCKS), iTunes Podcasts (ITPC), My Channels (MYCHANNELS)
0 Kudos
NewManLiving
Visitor

Re: 2D API Wisdom

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
My Channels: 2D API Framework Presentation: https://owner.roku.com/add/2M9LCVC
Updated: 11-11-2015 - Completed Keyboard interface
The Joel Channel ( Final Beta )
0 Kudos
squirreltown
Roku Guru

Re: 2D API Wisdom

Thanks for posting this, I certainly appreciate it.
Kinetics Screensavers
0 Kudos
NewManLiving
Visitor

Re: 2D API Wisdom

The code below is an example of one method for creating a virtual listview. The maximum memory used for the largest dataset which is 5000 strings is < 3mb
Use all the paging keys to experiment with the scrolling. Use the rw (that circle-arrow key) to change data sets. It is ROKU 3 HI-def so if you are using anything else
your on your own. I double display the data horizontally so that you can see how fast it is even with large strings. The l_scrollSpeed value puts some brakes on. Please
let me know its weaknesses, mistakes, don't think that you are offending me. I'm of average intelligence , I makes mistakes, and I don't always have the best
solution. But I try hard and I always get it right eventually (esp when someone helps) Just copy the code into a file an run it. No server or resources are used. Add
or change the testing values and make sure the scrolling is correct.





Function Main() As Void

' Create the Global channel object and initialize. This must always be done first
m.APP = NewChannel()
m.APP.Initialize()

' Open the ReadBook module.
' Ideally, ReadBook should itself be an instantiated object
' Create, Implemented, and Destroyed
ReadBook()

' Good habit to get into needed or not (at least not here)
m.APP.Clear()
m.APP = Invalid

End Function

' ASSERT is used only to short-circuit my own exception handler
' It is only meant for development testing and example code, NOT production
' It would be impossible to bring in my entire framework for one example
' Exception handling is your framework's job. The listview expects data
' to be in order. It does some minor checking for you but that is it
Function ASSERT(a_boolean As Boolean, l_object = "" As String ) As Void

if not a_boolean

print "=========================="
print l_object+" Failed ASSERT"
print "=========================="

stop
end if

End Function

' ************** APPLICATION OBJECT ***************************
' I use this to emulate my own environment. To make is easier for me to
' Bring in code from my own production application. It is not meant
' As a template. My own channel object controls theme (color, fonts, overhang composite)
' exception handling, loading screens with various wait cursors and progress bars
' the log viewer and message boxes
Function NewChannel() As Object

nc = CreateObject("roAssociativeArray")

' I use one roScreen swapping in/out composites to emulate stacked screens
' and one port as I employ the roSystemLog component.
nc.Screen = Invalid
nc.Port = Invalid
nc.Device = Invalid
nc.FontRegistry = Invalid

nc.Font20 = Invalid
nc.Font28 = Invalid

nc.DisplayWidth = 0
nc.DisplayHeight = 0

' Default Colors
nc.Transparent = &h00000000
nc.ScrBkgClr = &hE0DFDFFF
nc.DlgBkgClr = &hCECECEFF
nc.ItemTxtClr = &h383838FF

' Remote (you can use bslCore for these if you like)
nc.kp_BK = 0
nc.kp_UP = 2
nc.kp_DN = 3
nc.kp_LT = 4
nc.kp_RT = 5
nc.kp_OK = 6
nc.kp_RW = 7
nc.kp_REV = 8
nc.kp_FWD = 9
nc.kp_INFO = 10

nc.Initialize = channel_initialize

' can include interface to create the default
' roCompositor with your overhang at the top
return nc

End Function


Function channel_initialize() As Void

m.Port = CreateObject("roMessagePort")
m.Device = CreateObject("roDeviceInfo")
m.FontRegistry = CreateObject("roFontRegistry")

m.Font20 = m.FontRegistry.GetDefaultFont(20, False, False)
ASSERT(type(m.Font20) = "roFont", "m.Font20")

m.Font28 = m.FontRegistry.GetDefaultFont(28, False, False)
ASSERT(type(m.Font28) = "roFont", "m.Font28")

m.Screen = CreateObject("roScreen", True)
ASSERT(type(m.Screen) = "roScreen", "m.Screen")

l_wh = m.Device.GetDisplaySize()
m.DisplayWidth = l_wh.w
m.DisplayHeight = l_wh.h

m.Screen.SetPort(m.Port)
m.Screen.Clear(m.ScrBkgClr)
m.Screen.SwapBuffers()

End Function

'****************** END PPLICATION OBJECT ************************************


'****************** BEGIN READER MODULE ************************************
' Testing module for the example. My own modules are actually objects that contain
' an roCompositior. They are created, implemented and destroyed as the user navigates
' the main menu. For me this is the most efficient way to manage memory using the API

Function ReadBook() As Void

' Create the composite for this module. Set it to the applications screen
' and default color. Ideally your application object should create a compositor
' for you with your overhang already set at zorder 0 eg Compositor = m.APP.NewCompositor()
Compositor = CreateObject("roCompositor")
Compositor.SetDrawTo(m.APP.Screen, m.APP.ScrBkgClr)

' Create the listview passing it the compositor, an ideal page size
' and the zorder to place the listview sprite
Viewer = NewVListView(Compositor, 2)

l_index = 0
l_lastKey = -1
l_scrollSpeed = 120

l_kp_BK = m.APP.kp_BK
l_kp_UP = m.APP.kp_UP
l_kp_DN = m.APP.kp_DN
l_kp_LT = m.APP.kp_LT
l_kp_RT = m.APP.kp_RT
l_kp_OK = m.APP.kp_OK
l_kp_RW = m.APP.kp_RW
l_kp_REV = m.APP.kp_REV
l_kp_FWD = m.APP.kp_FWD

l_running = True
l_port = m.APP.Port

' Create some test data
l_test_values = [
[305, 10],
[1000, -10],
[105, 2],
[45, 167],
[3000, 20],
[68, 27],
[143, 27],
[5000,1027],
]

l_test_count = l_test_values.Count()
l_test_index = 0
l_test = l_test_values[l_test_index]

' Lets start with 3000 data items, at a 20 row page size
Viewer.SetList( GetData(l_test[0]), l_test[1] )

l_x = int(m.APP.DisplayWidth / 2 - Viewer.GetWidth() / 2)
l_y = int(m.APP.DisplayHeight / 2 - Viewer.GetHeight() / 2)
Viewer.MoveTo(l_x, l_y, False)
Viewer.Show()

while(l_running)

l_msg = wait(l_scrollSpeed, l_port)

if type(l_msg) = "roUniversalControlEvent"

l_index = l_msg.GetInt()
l_lastKey = l_index
print l_index

if l_index = l_kp_RW

l_test_index = l_test_index + 1
if l_test_index >= l_test_count then l_test_index = 0

l_test = l_test_values[l_test_index]

Viewer.SetList( GetData(l_test[0]), l_test[1] )
l_x = int(m.APP.DisplayWidth / 2 - Viewer.GetWidth() / 2)
l_y = int(m.APP.DisplayHeight / 2 - Viewer.GetHeight() / 2)
Viewer.MoveTo(l_x, l_y, False)
Viewer.Show()

else if l_index = l_kp_UP or l_index = l_kp_DN

Viewer.ScrollRow(l_index = l_kp_DN)

else if l_index = l_kp_LT or l_index = l_kp_RT

l_scrollSpeed = 300
Viewer.ScrollRow(l_index = l_kp_RT, 0)

else if l_index = l_kp_FWD or l_index = l_kp_REV

l_scrollSpeed = 220
Viewer.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

end if

else

if l_lastKey = l_kp_UP or l_lastKey = l_kp_DN

l_scrollSpeed = 5
Viewer.ScrollRow(l_lastKey = l_kp_DN, 6)

else if l_lastKey = l_kp_LT or l_lastKey = l_kp_RT

l_scrollSpeed = 5
Viewer.ScrollRow(l_lastKey = l_kp_RT, 0)

else if l_lastKey = l_kp_FWD or l_lastKey = l_kp_REV

l_scrollSpeed = 150
Viewer.ScrollPage(l_index = l_kp_FWD)

end if

end if


end while

End Function

'****************** END READER MODULE ************************************


' ************** BEGIN LISTVIEW OBJECT ***************************
' Virtual listview is an abbreviated, object extracted in part from my production code
' The engine is a more simplifed form of virtual management that I am experimenting with.
' This one uses index offsetting

' This example expects a one-dimensional array of strings. You can override or modify it to present
' data in a much richer format. My devlopment channel is called New Man Living and is dedicated to
' my Lord and Savior Jesus Christ whom I love with all my heart, soul and mind. He is the very breath of
' my life. It consists of all custom audio, video and e-book modules. The e-book module employs a form
' of the listview that contains rich-text features. I would like to bring back some of the out-of-print
' writings of what I consider 'true' men of God. It is quite doable to create a very rich, very fast reader
' using a virtual type of viewer.
' -- To God the Eternal Father be Glory and Honor and Power through Jesus Christ forever - John


' Hopefully I have extracted everything here. If you can make it better please do so, I'm not the brightest
' cog in the wheel. If there are any problems
' that you come across let me know so I could fix them. I'm a very busy guy so I quickly put together things
' If something works great in my environment for the purpose it was intended I leave it alone. This does not
' mean it is totally bug proof. You may use it for something different and discover other things I did not
' anticipate. Please share your improvements with the rest of us who use the API - all four of us
Function NewVListView(a_compositor As Object, a_zOrder As Integer) As Object

lv = CreateObject("roAssociativeArray")

' Holds a reference to the application object
' If you don't use one just replace all ref members with your own
' stuff - fonts, colors just look at it above
lv.APP = m.APP

' A reference to the one and only roScreen
lv.Screen = m.APP.Screen

' A reference to the implementor's compositor
lv.Compositor = a_compositor

' The Zorder to place the listview
lv.ZOrder = a_zOrder

' Make sure they are valid - see the ASSERT method
ASSERT(type(lv.Compositor) = "roCompositor", "lv.Compositor")
ASSERT(type(lv.Screen) = "roScreen", "lv.Screen")

' Holds a reference to your data container. In this example it must be
' an roArray of Strings. If you change this, obviously you need to change other interface
lv.Data = Invalid
lv.DataCount = 0
lv.DataIndex = 0

' The paging information. I don't believe I implement all members in this example
' I'm mostly trying to express the virtual aspect rather than anything else
lv.PageSize = 0 ' used
lv.PageHeight = 0 ' used
lv.PageNumber = 0
lv.TotalPages = 0
' datacount mod pagesize > 0
lv.IsPartialPage = False
' Flag to alert me that the user has changed scrolling directions
lv.IsPagingDown = True

' Total row height includes the linespace + onelineHeight of the font
' These are fixed I already have over 700 lines to copy into the code post
lv.RowHeight = 0
lv.LineSpace = 0

' The bitmap that will buffer the data. The bufferbitmap must not exceed the size of a bitmap
' However, your datalists can be as long as the limits imposed by the framework. You will see
' that this is more than you will ever need
lv.BufferBitmap = Invalid
lv.BufferRegion = Invalid
lv.BufferSprite = Invalid
' Always dynamically calculated
lv.BufferWidth = 0
lv.BufferHeight = 0

' Holds the virtual row index which can change because of how setwrap works. Picture a magnifying glass
' Going up and down over a stationary piece of paper. The paper stays still but what is in focus changes
' And so does the virtual row, which makes this implementation far more difficult
lv.VirtualRowIndex = 0

' Default Fonts are fixed for this example.
lv.TextFont = m.APP.Font20

' Default Colors
lv.BkgClr = m.APP.ScrBkgClr
lv.TextClr = m.APP.ItemTxtClr

' PUBLIC
lv.SetList = list_view_setlist
lv.Show = list_view_show
lv.Hide = list_view_hide
lv.MoveTo = list_view_moveto
lv.GetWidth = list_view_getwidth
lv.GetHeight = list_view_getheight
lv.ScrollRow = list_view_scrollrow
lv.ScrollPage = list_view_scrollpage

' PROTECTED
lv.DrawRow = list_view_drawrow
lv.DrawPage = list_view_drawpage
lv.DrawAll = list_view_drawall
lv.Reset = list_view_reset

return lv

End Function

' Releases any sprite memory and resets all members to default
Function list_view_reset() As Void

if type(m.BufferSprite) = "roSprite" then m.BufferSprite.Remove()

m.Data = Invalid
m.DataCount = 0
m.DataIndex = 0

m.PageSize = 0
m.PageHeight = 0
m.PageNumber = 0
m.TotalPages = 0
m.IsPagingDown = True
m.IsPartialPage = False

m.RowHeight = 0
m.LineSpace = 0

m.BufferBitmap = Invalid
m.BufferRegion = Invalid
m.BufferSprite = Invalid
m.BufferWidth = 0
m.BufferHeight = 0

m.VirtualRowIndex = 0

return

End Function

' See notes on ASSERT above. Generally components should be insulated from checking
' your data for you. The implementor should guarantee that the data is acceptable for use

' In this example an roArray of type String is expected. The caller sends a data list
' and a desired page size
Function list_view_SetList(a_data As Object, a_pageSize As Integer) As Boolean

' Release resources and re-initialize
m.Reset()

' Assign the a_data reference to the m.Data member
ASSERT(type(a_data) = "roArray", "a_data")
m.Data = a_Data

' Get the data count and save it, must be at least one
m.DataCount = m.Data.Count()
ASSERT(m.DataCount > 0, "m.DataCount")

' Determine actual page size. It cannot be 0, or greater than the data count,
' nor can it be the same size as the datacount (not in this example too much code, all stripped out)
' Though I do allow for (1) single row/item for a scrolling vertical banner. My version has a non-virtual
' Interface as well and permits the same page size as the datacount.

' Guaranteed to have at least one data item and a one-row page
m.PageSize = a_pageSize
if m.PageSize >= m.DataCount then m.PageSize = m.DataCount - 1
if m.PageSize <= 0 then m.PageSize = 1

' Get row height = onelneheight + linespace
' The font and linespacing are fixed, private members in this example. This is only
' to keep the example short. I am demonstrating one way to implement a virtual engine
' not how to create a rich-text reader. I will share the code to my own reader
' when I have perfected it.
m.LineSpace = 6
m.RowHeight = m.TextFont.GetOneLineHeight() + m.LineSpace

' Finally, determine the page height, I do not allow it to exceed the display height
' not only because it's not necessary, it also protects the bitmapbuffer from exceeding its own limits
m.PageHeight = m.PageSize * m.RowHeight
l_maxHeight = m.APP.DisplayHeight

' Make adjustments if needed. Calculate max num rows and resize
if m.PageHeight > l_maxHeight

m.PageSize = int(l_maxHeight / m.RowHeight) - 2
m.PageHeight = m.PageSize * m.RowHeight

end if

m.PageNumber = 0

' Total number of pages - I dont use them in this example
m.TotalPages = int(m.DataCount / m.PageSize)
l_diff = m.DataCount MOD m.PageSize
m.IsPartialPage = l_diff > 0
if m.IsPartialPage then m.TotalPages = m.TotalPages + 1

' From the above calculations we arrive at the buffers base height, and to this add the virtual row
m.BufferHeight = m.PageHeight + m.RowHeight

' Now for the width
' Determine the buffer width, limited to the display width
l_maxWidth = m.APP.DisplayWidth
m.BufferWidth = 0

for each l_str in m.Data

ASSERT(type(l_str) = "String", "String")
l_w = m.TextFont.GetOneLineWidth(l_str, l_maxWidth)
if m.BufferWidth < l_w then m.BufferWidth = l_w

end for

' Can ASSERT the buffer side if you wish. But the above code pretty much resolves an over-buffer
' I have had failures with slightly < 2048 so 2000 is a safer bet
ASSERT(m.BufferHeight < 2000, "m.BufferHeight = "+m.BufferHeight.ToStr())
ASSERT(m.BufferWidth < 2000, "m.BufferWidth = "+m.BufferWidth.ToStr())

' Finally, create the bitmap buffer
m.BufferBitmap = CreateObject("roBitmap", {width: m.BufferWidth, height: m.BufferHeight , AlphaEnable: False})
ASSERT(type(m.BufferBitmap) = "roBitmap", "m.BufferBitmap")

' Create the viewable page region, I remove the end linespace
' for a more even bottom border when scrolling. The width of the region
' is the same as the bitmap.
l_height = m.PageHeight - m.LineSpace
m.BufferRegion = CreateObject("roRegion", m.BufferBitmap, 0, 0, m.BufferWidth, l_height)
ASSERT(type(m.BufferRegion) = "roRegion", "m.BufferRegion")
' Set the regions wrap to true for scrolling. This is where the difficulty comes in
m.BufferRegion.SetWrap(True)

' Create the listview components sprite at 0,0 pos on the screen and hide it for now (zOrder < 0)
' The Interface members show, moveto, will handle this later
m.BufferSprite = m.Compositor.NewSprite(0, 0, m.BufferRegion, -1)
ASSERT(type(m.BufferSprite) = "roSprite", "m.BufferSprite")

' Draw the first page, setting the flag to true as you are always going down when
' drawing a page
m.IsPagingDown = True
m.DrawPage()

m.DrawAll()

return True

End Function

' Instructs the compositor to draw all sprites and then the screen swaps out the back buffer
' Dont need to call Flush it is called for you in SwapBuffers
Function list_view_drawall() As Void

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

End Function

' Move the sprite to where you like. The drawall arg allows you to stack drawing before dumping
Function list_view_Moveto(a_x = 0 As Integer, a_y = 0 As Integer, a_isDrawAll = True As Boolean ) As Void

ASSERT(type(m.BufferSprite) = "roSprite", "m.BufferSprite")
m.BufferSprite.MoveTo(a_x, a_y)

if a_isDrawAll then m.DrawAll()

End Function

' Show or hide it
Function list_view_show(a_isDrawAll = True As Boolean) As Void

ASSERT(type(m.BufferSprite) = "roSprite", "m.BufferSprite")
m.BufferSprite.SetZ(m.ZOrder)

if a_isDrawAll then m.DrawAll()

End Function


Function list_view_hide(a_isDrawAll = True As Boolean) As Void

ASSERT(type(m.BufferSprite) = "roSprite", "m.BufferSprite")
m.BufferSprite.SetZ(-1)

if a_isDrawAll then m.DrawAll()

End Function

Function list_view_getwidth() As Integer

ASSERT(type(m.BufferRegion) = "roRegion", "m.BufferRegion")
return m.BufferRegion.GetWidth()

End Function


Function list_view_getheight() As Integer

ASSERT(type(m.BufferRegion) = "roRegion", "m.BufferRegion")
return m.BufferRegion.GetHeight()

End Function


' Clears the virtual row with background color and writes the data to the virtual row. DataIndex and VirtualRowIndex
' are controlled in the list_view_scrollrow member. You do not want to be bounds-checking in the drawing
' members. If the indexes are calculated correctly in the callers then you dont have to worry about it
Function list_view_drawrow() As Void

m.BufferBitmap.DrawRect(0, m.VirtualRowIndex, m.BufferWidth, m.RowHeight, m.BkgClr)
m.BufferBitmap.DrawText( m.Data[m.DataIndex], 0, m.VirtualRowIndex, m.TextClr, m.TextFont )

End Function

' Draws the complete page starting at y pos 0 in the bitmap buffer. DataIndex is calculated in the
' list_view_scrollpage handler. To insure that no calculations or conditionals are performed in the drawing
' functions I set the start index at 2 to m.PageSize. Then once I exit the loop I write the final line.
' which has already been indexd by the final iteration in the loop
' This member does not wirte to the the virtual row, but it always resets the virtual index pointer
' To the last row in the buffer.
Function list_view_drawpage() As Void

l_y = 0

m.BufferBitmap.Clear(m.BkgClr)

for l_i = 2 to m.PageSize

m.BufferBitmap.DrawText( m.Data[m.DataIndex], 0, l_y, m.TextClr, m.TextFont )

l_y = l_y + m.RowHeight

' Wrap around if needed
m.DataIndex = m.DataIndex + 1
if m.DataIndex >= m.DataCount then m.DataIndex = 0

end for

' Draw the final row and set the virtual index
m.BufferBitmap.DrawText( m.Data[m.DataIndex], 0, l_y, m.TextClr, m.TextFont )

m.VirtualRowIndex = l_y

End Function


' Here is where it gets messy, much easier if you don't employ region wrapping
Function list_view_scrollrow(a_isDown = True As Boolean, a_frames = 16 As Integer) As Void

' If user transitions from scrolling up/down you must adjust the dataindex and virtualindex
' by a page size since the indexes are either at the top or bottom of the visible region
' depending on the direction the user is moving. You can then resume row scrolling in the
' new direction

' In this case, the user wants to move downward
if a_isDown

' Transition adjustment made proceed with row-based indexint
if m.IsPagingDown

m.DataIndex = m.DataIndex + 1
if m.DataIndex >= m.DataCount then m.DataIndex = 0

m.VirtualRowIndex = m.VirtualRowIndex + m.RowHeight
if m.VirtualRowIndex > m.PageHeight then m.VirtualRowIndex = 0

else

' User just changed directions
' So both indexes have to be bumped up a page. There are other ways to do this
' in my past experience I have seen indexes which keep track of the top and bottom rows
' I just offset and correct

' Data Index
m.DataIndex = m.DataIndex + m.PageSize
if m.DataIndex >= m.DataCount then m.DataIndex = m.DataIndex - m.DataCount

' Virtual row index. Remember that the virtual row index is always one row beyond the
' page height
m.VirtualRowIndex = m.VirtualRowIndex + m.PageHeight
if m.VirtualRowIndex > m.PageHeight
m.VirtualRowIndex = m.VirtualRowIndex - m.PageHeight - m.RowHeight
end if

' Adjustment made, set the flag to true
m.IsPagingDown = True

end if

else
' The user is now scrolling down and the transition has been made proceed row-based indexing
if not m.IsPagingDown

m.DataIndex = m.DataIndex - 1
if m.DataIndex < 0 then m.DataIndex = m.DataCount - 1

m.VirtualRowIndex = m.VirtualRowIndex - m.RowHeight
if m.VirtualRowIndex < 0 then m.VirtualRowIndex = m.PageHeight

else
' Need to transition and turn the flag off - note that you are adding a negative number here
' if < 0
m.DataIndex = m.DataIndex - m.PageSize
if m.DataIndex < 0 then m.DataIndex = m.DataCount + m.DataIndex

m.VirtualRowIndex = m.VirtualRowIndex - m.PageHeight
if m.VirtualRowIndex < 0
m.VirtualRowIndex = m.VirtualRowIndex + m.PageHeight + m.RowHeight
end if

m.IsPagingDown = False

end if

end if

' You may safely remove the ASSERTS once your code has been fully tested
l_isState = m.DataIndex >= 0 and m.DataIndex < m.DataCount
ASSERT(l_isState, "dataindex = "+m.DataIndex.ToStr())

l_isState = m.VirtualRowIndex >= 0 and m.VirtualRowIndex <= m.PageHeight
ASSERT(l_isState, "virtualrowindex = "+m.VirtualRowIndex.ToStr())

' Draw the new virtual row/data
m.DrawRow()

l_offset = 0
l_prevset = 0
l_offdiff = 0

' Some amount of sliding: slide in the new row and return
if a_frames > 1

if a_frames > 200 then a_frames = 16

for l_i = 1 to a_frames

l_offset = int(m.RowHeight * l_i / a_frames)
l_offdiff = l_offset - l_prevset

if l_offdiff > 0

if not a_isDown l_offdiff = -l_offdiff

m.BufferRegion.Offset(0, l_offdiff, 0, 0)

m.DrawAll()

l_prevset = l_offset

end if

end for

return

end if


' If the frame value < 2 just offset by m.RowHeight
l_offset = m.RowHeight
if not a_isDown l_offset = -l_offset

m.BufferRegion.Offset(0, l_offset, 0, 0)

m.DrawAll()

return

End Function


Function list_view_scrollpage(a_isDown = True As Boolean) As Void

' Paging does not involve use of the VirtualRow. This method calls the drawpage function
' which always resets the virtual index to be one rowheight beyond the pagesize
' So, if the region's offset > 0 by the user previously row-scrolling, you need to
' reset it to 0 so that not only is the virtual row hidden, but the correct data is displayed
l_getY = m.BufferRegion.GetY()
if l_getY > 0 then m.BufferRegion.Offset(0, -l_getY, 0, 0)

' This is exactly the same as above, but instead of moving the indexes by row, we are
' moving by page
if a_isDown

if m.IsPagingDown

m.DataIndex = m.DataIndex + 1
if m.DataIndex >= m.DataCount then m.DataIndex = m.DataIndex - m.DataCount

else

m.DataIndex = m.DataIndex + m.PageSize
if m.DataIndex >= m.DataCount then m.DataIndex = m.DataIndex - m.DataCount

m.IsPagingDown = True

end if

else

' Once transitioning has taken place indexing is at
' he bottom of the page so to get back a page it would be pagesize * 2 - 1
if m.IsPagingDown
m.DataIndex = m.DataIndex - (m.PageSize * 2 - 1)
else
' This will only occur once between transitions since the indexing is at the top
m.DataIndex = m.DataIndex - m.PageSize
m.IsPagingDown = True
end if

' if you have overstepped bounds then correct. Keep in mind that you are adding a
' negative number. Also a page size can be as large as m.DataCount - 1 so your
' correction has to account for the * 2 multiplier. You could use the pagecount
' or is partialpageflag and do something brilliant but here I just add the negative offset and
' check the index again, if it is still negative I repeat the addition.
if m.DataIndex < 0

m.DataIndex = m.DataCount + m.DataIndex
if m.DataIndex < 0 then m.DataIndex = m.DataCount + m.DataIndex

end if

end if

' You may safely remove the ASSERTS once your code has been thouroughly tested
' You do not need to test the virtualrowindex since it is not changed here
l_isState = m.DataIndex >= 0 and m.DataIndex < m.DataCount
ASSERT(l_isState, "dataindex = "+m.DataIndex.ToStr())

m.DrawPage()

m.DrawAll()


End Function



' ************** END LISTVIEW OBJECT ***************************

' Generate some dummy data
Function GetData(a_number As Integer) As Object

' Emulate an empty buffer
if a_number <= 0 then return CreateObject("roArray", 1, False)

l_data = CreateObject("roArray", a_number, False)
l_text = " Down Row Number Up Row Number "

for l_i = 0 to a_number - 1

l_dn = l_i + 1
l_up = a_number - l_i

l_str = l_dn.ToStr()+l_text+l_up.ToStr()
l_str = l_str+" "+l_str
l_data.Push(l_str)

end for

return l_data

End Function













My Channels: 2D API Framework Presentation: https://owner.roku.com/add/2M9LCVC
Updated: 11-11-2015 - Completed Keyboard interface
The Joel Channel ( Final Beta )
0 Kudos
NewManLiving
Visitor

Re: 2D API Wisdom

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
My Channels: 2D API Framework Presentation: https://owner.roku.com/add/2M9LCVC
Updated: 11-11-2015 - Completed Keyboard interface
The Joel Channel ( Final Beta )
0 Kudos