Introduction To Custom Controls

From AwkwardTV

Jump to: navigation, search

<Google>WIKI</Google>

This article presents the first of what I hope will be a growing number of more in-depth tutorials on the Back Row system, gleaned through lots of repetitive experimentation. I will be specifically concentrating on the creation of custom control and controllers, and a look at the layer and animation systems. In doing so, we will learn some of the internals of the system itself, such as the use of the BRImageManager class and some less-used BRControllerStack functions.

Contents

Prerequisites

Note: In my setup, I have BackRow.framework and iPhoto.framework installed in their native locations, within /System/Library/PrivateFrameworks/, and I have symlinked to their private header folders within the 10.4 Universal SDK itself. I can't provide guidance for people who don't want to modify those directories directly, although it might be possible to simply add the frameworks to your home folder, in ~/Library/Frameworks.

The base class: BRControl

The BRControl class is the basis for all controls. It doesn't implement any functionality itself, but it does provide a skeleton which uses the functionality added in subclasses. For instance, the -frame method calls [self layer] to fetch the layer's frame, although the base BRControl class does not provide a layer.

The Class Header

@interface BRControl : NSObject <BREventResponder>
{
    BRRenderScene *_scene;	// 4 = 0x4
}

+ (BRControl *)controlWithScene: (BRRenderScene *) scene;
- (id)initWithScene: (BRRenderScene *) scene;
- (BRRenderScene *) scene;
- (void) dealloc;
- (BRRenderLayer *) layer;
- (BOOL) brEventAction: (BREvent *) event;
- (void) setFrame: (NSRect) frameRect;
- (NSRect) frame;
- (void)setAlphaValue: (float) alpha;
- (float) alphaValue;
- (void) setHidden: (BOOL) hidden;
- (BOOL) hidden;

@end

Deconstructing The Class

The BRControl class mostly serves as a wrapper around a BRRenderLayer. Generally, the layer objects contain all the OpenGL code, and are the targets for animations. The control objects are higher-level, mostly providing either an abstraction from the raw layer code (as in the BRTextControl and BRTextLayer) or they make use of more than one layer in their creation -- BRButtonControl uses a BRTextLayer and a BRSelectionLozengeLayer to draw itself.

The most important routines here are:

-(BRRenderLayer *) layer;
-(BOOL) brEventAction: (BREvent *) event;
-(void) setFrame: (NSRect) frameRect;

The -layer routine is most important, because each control must have a single layer containing any others, and it is the subclasses' responsibility to provide that by implementing this routine. If this is not implemented, then the layer will never be put on screen.

The -brEventAction: routine handles events, which will be the focus of a later tutorial. At this point, the control can analyse the event to find out which button was pressed, and can return YES if it handled the event, or NO if it did not.

The -setFrame: routine is important because it can be overridden by subclasses to lay out their own layers within their new frame.

A Simple BRControl Subclass: ATVProgressControl

The BackRow framework includes a progress bar, but only in layer form, used within a control such as the BRSyncProgressControl, which links to the media synchronization engine to update itself. As such, it makes a nice, simple object to wrap in a control. First of all let's look at our new control's interface:

The Header

#import <Cocoa/Cocoa.h>
#import <BackRow/BRControl.h>

@class BRRenderLayer, BRProgressBarWidget, BRRenderScene;

@interface ATVProgressControl : BRControl
{
    BRRenderLayer *         _layer;
    BRProgressBarWidget *   _widget;
    float                   _maxValue;
    float                   _minValue;
}

- (id) initWithScene: (BRRenderScene *) scene;
- (void) dealloc;

- (void) setFrame: (NSRect) frame;
- (BRRenderLayer *) layer;

- (void) setMaxValue: (float) maxValue;
- (float) maxValue;

- (void) setMinValue: (float) minValue;
- (float) minValue;

- (void) setCurrentValue: (float) currentValue;
- (float) currentValue;

- (void) setPercentage: (float) percentage;
- (float) percentage;

@end

BRProgressBarWidget

BRProgressBarWidget itself provides only two methods (aside from init and dealloc):

- (float) percentage;
- (void) setPercentage: (float) amount;

Our BRControl version provides pass-throughs for those two methods, as well as some more useful methods on top of this, to set a specific min & max value. This allows the control's user to think in terms of bytes downloaded, for example, rather than simple percentages.

We also override -layer and -setFrame:, for the very reasons outlined above.

Implementation

First of all, we have the initializer and destructor for the new object:

Initializer/Deallocator

- (id) initWithScene: (BRRenderScene *) scene
{
    if ( [super initWithScene: scene] == nil )
        return ( nil );

    _layer = [[BRRenderLayer alloc] initWithScene: scene];
    _widget = [[BRProgressBarWidget alloc] initWithScene: scene];

    [_layer addSublayer: _widget];

    // defaults
    _maxValue = 100.0f;
    _minValue = 0.0f;

    return ( self );
}

- (void) dealloc
{
    [_widget release];
    [_layer release];

    [super dealloc];
}

These are fairly straightforward. The control creates its main layer, then creates the progress bar widget. It then adds the widget (a subclass of BRRenderLayer) to its own layer as a sublayer. This means that the widget's frame rectangle is now considered to be relative to that of the control's main layer. It then sets the min and max values to those of a straightforward percentage meter.

The Layer

- (BRRenderLayer *) layer
{
    return ( _layer );
}

Again, nice and simple. In order for the control to show up on the screen, it must provide its layer to any containers. This is then used to add the control's layer to the main layer of whichever control or BRLayerController instance contains this one.

Setting the Widget's Frame

- (void) setFrame: (NSRect) frame
{
    [super setFrame: frame];

    NSRect widgetFrame = NSZeroRect;
    widgetFrame.size.width = frame.size.width;
    widgetFrame.size.height = ceilf( frame.size.width * 0.068f );
    [_widget setFrame: widgetFrame];
}

Here we setup the widget itself within the bounds of the control. Firstly we let the superclass know about the new frame: BRControl will pass this on to our main layer for us, leaving us free to think about our contents. We start off with a zero sized frame, and since we contain nothing else we leave the widget's origin at {0,0}, the lower-left corner of our main layer. We set it to be the same width as ourself, and then we tweak the height such that it remains at the correct width-to-height ratio. This value was gleaned by looking through the BackRow framework, and is the same value used by Apple.

The Extras

The remaining entries simply handle the member variables and convert between the control's n-m range to the widget's 0-100 range:

- (void) setMaxValue: (float) maxValue
{
    @synchronized(self)
    {
        _maxValue = maxValue;
    }
}

- (float) maxValue
{
    return ( _maxValue );
}

- (void) setMinValue: (float) minValue
{
    @synchronized(self)
    {
        _minValue = minValue;
    }
}

- (float) minValue
{
    return ( _minValue );
}

- (void) setCurrentValue: (float) currentValue
{
    @synchronized(self)
    {
        float range = _maxValue - _minValue;
        float value = currentValue - _minValue;
        float percentage = (value / range) * 100.0f;
        [_widget setPercentage: percentage];
    }
}

- (float) currentValue
{
    float result = 0.0f;

    @synchronized(self)
    {
        float percentage = [_widget percentage];
        float range = _maxValue - _minValue;
        result = (percentage / 100.0f) * range;
    }

    return ( result );
}

- (void) setPercentage: (float) percentage
{
    [_widget setPercentage: percentage];
}

- (float) percentage
{
    return ( [_widget percentage] );
}

That's All There Is To It

There we have our first custom control. Okay, it's not a great deal of use on its own, but in our next tutorial we'll look at a custom BRLayerController subclass, which will show us how to lay out controls on a master layer, and how to respond to BRControllerStack callbacks. Armed with that knowledge, our third tutorial will detail the creation of another custom layer controller, making use of this progress bar control. Beyond that, who knows? Perhaps we'll look at BRAnimation, perhaps we'll do some more controls using our own OpenGL rendering code. The sky really is the limit.

Now you can go on to the next tutorial in the series: A Custom Layer Controller.

Contact Details

You can reach me on irc.moofspeak.net #awkwardtv, username alan_quatermain, or at http://forum.awkwardtv.org/, username Quatermain.

Personal tools