Marching Icons
I’ve been asked about this by a few people now: how does the icon display on the Main Menu work, and can I use it in my plugin? Well, the answers are ‘reasonably easily’ and ‘yes’, and today’s tutorial will show you how to do it.
Download the sample code for this tutorial
Important Note:
The code in this tutorial was written for BackRow version 2.0. Now that Apple has released AppleTV software version 1.1, some changes need to be made. In the tutorial and in the sample code, you will need to replace the [[self list] selection] call with [[self list] renderSelection].
The class which we need to use is BRMarchingIconLayer. That’s right, it’s a layer, not a control, which means you have a little more work to do than just the usual ’set frame and forget’ required for BRControl subclasses. Its method of getting data uses the datasource method, which we are familiar with by now from using lists and menus. The data source needs to implement the following protocol, declared in <BackRow/BRIconSourceProtocolProtocol.h>:
@protocol BRIconSourceProtocol <NSObject>
- (long) iconCount;
- (BRTexture *) iconAtIndex: (long) index;
@end
You will call -setDatasource: self on the newly created icon layer, and it will call your callback functions to retrieve its data. In this tutorial, we will be collecting icons from the installed plugins, via the BRApplianceManager, and supplying those via an array of BRBlurryImageLayer objects. To store this data, we will have a member variable called _pluginDicts, leaving our class declaration looking something like this:
@interface QuMarchingIconsApplianceController : BRMenuController
{
NSArray * _pluginDicts;
BRMarchingIconLayer * _iconMarch;
}
Let’s start by looking at how we’ll load the icons themselves.
Collecting the Icons
Our first task is to get a list of all the appliances with icons. Normally they all would have icons, but since my tutorials don’t have any (yeah, I know, I’m lazy) we’ll filter those out. To do this we will request the list of appliance info property lists from the BRApplianceManager. This class conveniently converts the bundle-relative paths into fully-qualified paths when it loads the appliances, so we have everything we need handed to us already.
- (void) loadAppliances
{
NSArray * list = [[BRApplianceManager sharedManager] applianceInfoList];
NSMutableArray * prunedList = [NSMutableArray array];
// go through the list, filtering based on the appliances which have
// creatable icons...
unsigned i, count = [list count];
for ( i = 0; i < count; i++ )
{
NSDictionary * info = [list objectAtIndex: i];
// try to create an image for the icon
BRBlurryImageLayer * iconLayer = [self iconFromPluginInfo: info];
if ( iconLayer != nil )
{
// that worked, so copy the appliance dictionary and add
// the icon layer for easy retrieval
NSMutableDictionary * dict = [NSMutableDictionary dictionaryWithDictionary: info];
[dict setObject: iconLayer forKey: @"QuApplianceIcon"];
[prunedList addObject: dict];
}
}
// the -copy method has an implicit retain, and returns an
// immutable variant (i.e. an NSArray)
_pluginDicts = [prunedList copy];
}
This code in itself is fairly basic. The real work of creating the BRBlurryImageLayer is implemented in our -iconFromPluginInfo: method. Inside this method, we need to load the image from disk then render it into a texture object. For the purposes of this tutorial we won’t look into the customised kerning for the icons or the custom blurry images, we’ll simply use some provided routines to create the blurry image for us, and accept the defaults for everything else.
So, first of all, we create a CGImageRef for the icon:
NSString * iconPath = [pluginInfo objectForKey: @"FRApplianceIconPath"];
if ( iconPath == nil )
return ( nil );
NSURL * iconURL = [NSURL fileURLWithPath: iconPath];
if ( iconURL == nil )
return ( nil );
CGImageRef image = CreateImageForURL( iconURL );
if ( image == NULL )
return ( nil );
Next we need to create a BRBitmapTexture object from that image, which requires a little OpenGL setup and another call to a BackRow C API. The BRBitmapDataInfo structure is defined in <BackRow/CDStructures.h>.
struct BRBitmapDataInfo info;
info.internalFormat = GL_RGBA;
info.dataFormat = GL_BGRA;
info.dataType = GL_UNSIGNED_INT_8_8_8_8_REV;
info.width = 512;
info.height = 512;
BRRenderContext * context = [[self scene] resourceContext];
NSData * data = CreateBitmapDataFromImage( image, info.width, info.height );
BRBitmapTexture * lucid = [[BRBitmapTexture alloc] initWithBitmapData: data
bitmapInfo: &info context: context mipmap: YES];
[data release];
The blurry image is created through a method provided by the BRBlurryImageLayer class; this uses CIImage and a gaussian blur filter to create the blurred image texture. After creating this, we can dispose of the CGImageRef.
BRBitmapTexture * blur = [BRBlurryImageLayer blurredImageForImage: image
inContext: context
size: NSMakeSize(100.0f, 100.0f)];
CFRelease( image );
Lastly, we use the lucid and blurred textures to initialise our BRBlurryImageLayer object, and we let it create the reflections itself (it will actually draw the main image with inverted coordinates to achieve this). Note that the blurred image is autoreleased, but the lucid image needs to be released properly, since it was created using alloc/init.
BRBlurryImageLayer * result = [BRBlurryImageLayer layerWithScene: [self scene]];
[result setLucidImage: lucid withReflection: nil];
[result setBlurryImage: blur withReflection: nil];
[lucid release];
return ( result );
Having implemented this, we need a couple of calls in our object’s -initWithScene: method to get it up & running:
[self loadAppliances];
_iconMarch = [[BRMarchingIconLayer alloc] initWithScene: scene];
[_iconMarch setIconSource: self];
No Responses