Articles Archive
Articles Search
Director Wiki
 

Using RGB colors in behaviors

August 15, 2000
by Zac Belado

Director 7 introduced many new features, and one of the more useful was the RGB color object. It finally allowed Director developers to specify exact colors in their projects. And while it is very useful in that respect, there are two major problems with it. First, it's almost impossible to relate an RGB color to an actual color. For instance, what color is rgb (32,56,117)? Not many people would guess that it's a medium blue. So, while it's very handy to be able to specify exact red, green and blue values, its utility is limited because it's difficult to relate those values to descriptive names, like blood red, olive green or navy blue. The other, more serious, problem is that Director doesn't handle RGB colors in behaviors very well.

For example, let's assume you are writing a behavior for a Text member. The behavior will change the color of each line of text as the user rolls over it, and also will change the color of a line to a separate color when the user clicks on it. You want to be able to specify an RGB color for both of these effects in a GPDL dialog. You could always try this by defining a list of colors and then saving them as string properties.

on getPropertyDescriptionList

  pList = [:]
  colorNames = [rgb(0,0,0), rgb(255,255,255), rgb(255,0,0), rgb(0,255,0), rgb(0,0,255)]

  addProp pList, #pRolloverColor, [#default:#black, #format:#string, #comment:"Rollover color?", #range:colorNames]
  addProp pList, #pSelectedColor, [#default:#black, #format:#string, #comment:"Selected color?", #range:colorNames]

  return pList

end

Then, in the beginSprite handler for the behavior you could use the value() function to convert the string to a color object. This would certainly work, but it has a few problems. First, the color list is hard-coded, so if you need to be able to specify new colors you will have to edit the behavior. Secondly, the GPDL dialogs are almost unreadable.

So, while you can specify a color, the pop-up menus contain text that might as well be in Latin or hexadecimal (which, to a degree, they actually are).

An alternative approach

Instead, let's examine a system that will allow you to:

The system requires two components: first, two lists, one containing the names of the colors as symbols, and the second (property) list containing the name/color pairs; and second, a set of scripts that will:

The process is as follows:

The lists that the system needs are initialised by making a call to a handler called buildColorLists. This handler is called in one of two ways. The first way it can be called is directly, in a prepareMovie handler. This code needs to be added to ensure that the data is available at runtime. The second would be in authoring, where you need to ensure that the lists (the color name list and the color property list) exist when you apply a behavior to a sprite. In the GPDL handler there is a call to a method, checkColorLists, that tests to see that both lists have been created; if not, it calls buildColorLists to create them.

Let's look at these two handlers. The buildColorLists handler reads the color data from a Field member called colorNames. The data in the Field member looks like this:

black, 0,0,0
red, 255,0,0
blue, 0,0,255
olive,153, 204, 51
purple,102, 0, 153
light_green, 0, 238, 0 
light_brown,102, 102, 0
pale_blue,0, 153, 255
blood_red,170, 0, 0 
suede,204, 204, 153
lavender,204, 153, 204 

and is in the format:

<colorname>, <red color value>, <green color value>, <blue color value>

Note: the author is red/green colorblind, so his idea of light green might be your idea of tan...

You aren't limited to the colors that I've included. You can add your own colors to this field (as many as you'd like), but you need to make sure that you call buildColorLists before you try to access them.

The buildColorLists handler simply reads through each of these lines and creates a symbol and an RGB color object from the data.

on buildColorLists

  -- repeat though all the values in the colorNames member
  -- and make a property list of the values

  thisText = member("colorNames").text
  limit = thisText.lines.count
  gColorNames = []
  gColorList = [:]

  repeat with index = 1 to limit
    
    thisLine = thisText.line[index]
    
    if thisLine <> "" then
      
      -- make a symbol of the colour name
      thisName = symbol(thisLine.item[1])
      
      -- get the red, green and blue values
      aRed = value(thisLine.item[2])
      aGreen = value(thisLine.item[3])
      aBlue = value(thisLine.item[4])
      
      -- make a colour
      thisColor = rgb(aRed,aGreen,aBlue)
      
      -- append the values to the appropriate lists
      append gColorNames, thisName
      addProp gColorList, thisName, thisColor
      
    end if
    
  end repeat

end

The checkColorLists handler just tests to see if either of the two global lists is VOID (i.e. it hasn't been declared); if either is, it calls buildColorLists to create them.

on checkColorLists

  if voidP(gColorNames) OR voidP(gColorList) then
    buildColorLists
  end if

end

The core of the system is two functions: returnColorList() and returnColor(). The returnColorList() function returns the global list gColorNames.

on returnColorList
  -- return the list of parsed colors
  return gColorNames
end

This might seem rather pointless, but its value is that it encapsulates the task of returning the color names so that behaviors aren't relying on direct knowledge of any of the global variables the system requires. This allows you to safely change the names of the global variables, or change the way the data is stored, without ever having to modify your behaviors.

The second function, returnColor(), takes a color name, a symbol, and returns the RGB color object that is associated with that name. The gColorList property list looks like this:

put gColorList
-- [#black: rgb( 0, 0, 0 ), #red: rgb( 255, 0, 0 ), #blue: rgb( 0, 0, 255 ), 
#olive: rgb( 153, 204, 51 ), #purple: rgb( 102, 0, 153 ), 
#light_green: rgb( 0, 238, 0 ), #light_brown: rgb( 102, 102, 0 ), 
#pale_blue: rgb( 0, 153, 255 ), #blood_red: rgb( 170, 0, 0 ), 
#suede: rgb( 204, 204, 153 ), #lavender: rgb( 204, 153, 204 )]

So, if you wanted to get the color value for the suede color you would write:

thisColor = returnColor (#suede)

And you would get back the appropriate color:

put thisColor
-- rgb( 204, 204, 153 )

Putting it into practice

Let's use this system in a small behavior. The behavior will use three colors, which are defined in the GPDL: a default color for the text; a color used for a rollover effect; and a final color, used to indicate which line the user has selected. These are all defined in the behavior's getPropertyDescriptionList handler.

on getPropertyDescriptionList

  pList = [:]

  checkColorLists
  colorNames = returnColorList()

  addProp pList, #pRolloverColor, [#default:#black, #format:#string, #comment:"Rollover color?", #range:colorNames]
  addProp pList, #pSelectedColor, [#default:#black, #format:#string, #comment:"Selected color?", #range:colorNames]
  addProp pList, #pDefaultColor, [#default:#black, #format:#string, #comment:"Default color?", #range:colorNames]

  return pList

end

The handler first calls checkColorLists to make sure that the color system has been initialised. It then gets a list of the color names by calling returnColorList(). This list is then used in the #range parameter of each of the properties to make a pop-up menu of the colors that are available

At this point, the colors are stored as symbols in the behavior's properties. Once the sprite "begins", the behavior will change those symbols into actual RGB color objects by passing the symbol to the returnColor() function.

on beginSprite me

  pMySprite = sprite(me.spriteNum)
  pMyMember = pMySprite.member

  pLastRollover = 0
  pLastSelect = 0

  -- get the color value as an rgb colour
  pRolloverColor = returnColor (pRolloverColor)
  pSelectedColor = returnColor (pSelectedColor)
  pDefaultColor = returnColor(pDefaultColor)

  -- set the color of the Text member
  pMyMember.color = pDefaultColor

end

The code has safely converted a symbol into a color object that it can use through the rest of the behavior.

on turnOffRollover me

  if pLastRollover <> 0 then
    
    -- make sure that we set any selected line back to the selected colour instead
    if pLastRollover <> pLastSelect then
      pMyMember.line[pLastRollover].color = pDefaultColor
    else
      pMyMember.line[pLastRollover].color = pSelectedColor
    end if
    
  end if

end


on setLine me, thisLine

  if pLastSelect<> 0 then
    pMyMember.line[pLastSelect].color = pDefaultColor
  end if

  pLastSelect = thisLine
  pMyMember.line[pLastSelect].color = pSelectedColor

end

Check out the Director 7 sample movie (Mac or PC format) to see the full behavior.

Wrapping up

This system can also be used in other handlers and objects in your movies. The only concern in using this color system in anything other than a behavior is that you will need to check that a color exists before you try to use it. Since the behaviors are only displaying color names that are in the system, there isn't any need for error checking. This wouldn't be the case if you were just passing a symbol that was hand-coded. The current code will return VOID if the color doesn't actually exist in gColorList.

There are some ways that you can expand this system. One example that comes to mind immediately is that the color names could be stored in an external text file that is read in when the movie starts. This would make authoring use problematic, but you could always write code that checks for the existence of an internal Field or Text member and, if it doesn't exist, would read in the color names from an external file. This would make sharing colors among a team of developers very easy.

Have fun with the code, and don't forget that there are always methods that you can use to get around some of the inconveniences in Director.

Zac Belado is a programmer, web developer and rehabilitated ex-designer based in Vancouver, British Columbia. He currently works as an Application Developer for a Vancouver software company. His primary focus is web applications built using ColdFusion. He has been involved in multimedia and web-based development, producing work for clients such as Levi Straus, Motorola and Adobe Systems. As well, he has written for the Macromedia Users Journal and been a featured speaker at the Macromedia Users Convention.

Copyright 1997-2024, Director Online. Article content copyright by respective authors.