|
fillGradients What's in this article?
IntroductionFillGradient is a marvellous way to colorize block graphics, but because the 'from', 'to' and 'via' coordinate properties are based on screen points, it only works if the graphic is in exactly the right place. This means that we cannot use fillGradients directly as template definitions, or even "set the fillgradient of grc 2 to the fillGradient of grc 1". That's the problem... What if we want to store different fillGradients, and then apply them to re-skin any object in a project? FillGradient stores the coordinates in screen not object terms, so knowing the FillGradient without the original graphic rectangle is next to useless. To prove a point, try this: 1. Create a graphic called "My new grc", any size you like and placed anywhere on the card. Make sure it is opaque. 2. Now put this fillGradient spec into a button script... on mouseUp put "linear" into myGradient["type"] put "0.0,242,242,242" &return& "1.0,192,192,192" into \ myGradient["ramp"] put "230,100" into myGradient["from"] put "574,281" into myGradient["to"] put "230,281" into myGradient["via"] put "good" into myGradient["quality"] put "1" into myGradient["repeat"] put "false" into myGradient["mirror"] put "false" into myGradient["wrap"] set the fillGradient of graphic "My new grc" to myGradient end mouseUp 3. Click the button. You might expect the graphic to display as I designed it. But you don't. You don't know where my original was located. And until you do, you won't see my lovely gradient fill. Bummer, huh? Delete the button; it doesn't work. Finding a solutionOkay, we know why it doesn't work and what's missing: screen not object coordinates, and no reference coordinates. The easiest thing would be if the 'from', 'via' and 'to' coordinate properties in fillGradient were stored in a way that any graphic could use. But it doesn't work that way so we may as well roll up our sleeves and do a bit of coding. But that's fine... it's what we do, and it gives us a sense of satisfaction. There are two ways to deal with this problem: The hard way - Calculate and store the relative offset values of all coordinates in 'from', 'via' and 'to', adjusting for any change in dimension, then replace the old coordinates with new values before applying the adjusted array. The easy way - Cheat whenever possible and look for the path of least resistance. If you'd like to try the first way, be my guest. What follows is the easy way. Applying the solution in 4 stepsStep 1: Design a graphic with a fillGradient Step 2: Grab its fillGradient array and include the original rectangle Step 3: Save this as a definition spec where we can easily access it Step 4: Apply the definition spec to another graphic and let LiveCode work out the details Brief note on coding conventions: Use t for temporary variable names, p for parameters and a for array names. This makes understanding the code later very much easier, avoids conflicting names and is generally 'a good thing to do'. Step 1: Design a graphic with a fillGradientUsing the same stack, create a second opaque graphic. Call it "original" and set its fillGradient as you wish. You can use the LiveCode IDE to help you… double-click the graphic, drop down the menu, select 'Gradient' and play with the options. Step 2: Grab the array and include the original rectangleInstead of storing the FillGradient in its native form, we are going to save a modified copy of it that includes the original reference coordinates. We tell LiveCode the name of the designed graphic (pSourceGrc) and what we want to call our spec (aSpecName): --| Duplicate the fillGradient array of the chosen graphic and -- call it 'aSpec' put the fillGradient of grc pSourceGrc into aSpec --| Add the source rectangle coordinates put the rect of grc pSourceGrc into aSpec["rect"] We now have a modified version of the fillGradient array containing what we need. Step 3: Save the text specThe code in step 2 extracts a fillGradient spec and modifies it. We now have to store the modified spec somewhere persistent. Although there are lots of data storage options (a variable, a global, a script variable, a text file, a hidden field somewhere, a custom property, a custom property set, a file on the web), we have to choose one that can not only store arrays but also persist the next time we use the stack. The only options are a custom property or a custom property set. Knowing what your data storage options are and which one suits best will save you a lot of code re-writing later on when you realize you chose the wrong one and have to change it. Thinking ahead, we will want to store lots of different fillGradients and possibly choose from different sorts of fill specs such as green fill specs, red fill specs, or blue fill specs. Our choice is therefore a custom property set. This saves the spec (aSpec) by name (pSpecName) in a persistent custom property set called 'myFillGradients': --| Save the spec as a named array in a custom property set set the myFillGradients[pSpecName] of this stack to aSpec Put these together in one handler and put the handler in the stack script: on saveFillGradientSpec pSourceGrc,pSpecName --| Duplicate the fillGradient array of the chosen graphic -- and call it 'aSpec' put the fillGradient of grc pSourceGrc into aSpec --| Add the source rectangle coordinates put the rect of grc pSourceGrc into aSpec["rect"] --| Save the spec as a named array in a custom property set set the myFillGradients[pSpecName] of this stack to aSpec end saveFillGradientSpec Step 4: Apply the definition spec to another graphicNow we find the line of least resistance. This handler resizes the required graphic to the dimensions of the original so the fillGradient will apply as expected. It then restores the graphic to its original place and we let LiveCode do the work of re-calculating the 'from', 'via' and 'to' coordinates which are, frankly, of no real interest to us any more: on setFillGradientSpec pDestGrc,pSpecName --| Temporarily store the graphic's position so it can be -- restored later put the rect of grc pDestGrc into tRestoreRect --| Lock the screen to avoid any flicker lock screen --| Get the named spec from the required custom property set put the myFillGradients[pSpecName] of this stack into aSpec --| Use the spec's 'rect' to set the graphic's rectangle -- coordinates so the --| fillGradient will display as expected set the rect of grc pDestGrc to aSpec["rect"] --| Apply the fillGradient to the named graphic. LiveCode -- will use the spec --| and ignore the 'rect' component set the fillGradient of grc pDestGrc to aSpec --| Put the graphic back to its original position set the rect of grc pDestGrc to tRestoreRect unlock screen end setFillGradientSpec Finally... All the preparatory work is now done and we can actually use it. You already have an opaque graphic called "Original" with its fill gradient displayed. Now create a field called "SpecName" and a button called "SaveSpec". Put this handler into the button script to save the fillGradient: on mouseUp --| Use the text in a field as the spec name put fld "specName" into tSpecName --| Specify the name of the graphic whose fillGradient is to -- be used put "Original" into tSourceGrc --| Save the spec saveFillgradientSpec tSourceGrc,tSpecName end mouseUp You already have an opaque graphic called "My new grc". Now create a button called "SetSpec". Put this handler into the button script to set the fillGradient: on mouseUp --| Use the text in a field as the spec name put fld "specName" into tSpec --| Specify the name of the graphic whose fillGradient is to -- used put "My new grc" into tGrc setFillGradientSpec tGrc,tSpec end mouseUp That's it. You can now grab and store the fillGradient of any graphic as a usable spec, then retrieve the spec and apply it to any other graphic. To store different specs and apply them at any time, change the fillGradient of the original and call the spec something different. Of course you will probably have a SaveSpec utility card somewhere and use "setFillSpec" in a more convenient way than "mouseUp", but you have all you need to skin objects based on fillGradients. Something for the weekend?Custom property sets are a marvellous way to store data, not least because it immediately offers two tiers of storage: As many custom property sets as you want, and for each one as many custom properties as you want (called the custom keys). That's why I chose this method of storage for fillGradients. With extension, different spec sets of specs can be available and all very quickly implemented. What to do next to learn more about fillGradient and custom property sets…
on mouseDown --| Specify the custom property set set the custompropertyset of this stack to "myFillGradients" --| Grab the spec names get the customkeys of this stack --| Reset the default custom property set set the custompropertyset of this stack to "" --| Build the menu list put it into me end mouseDown on menuPick what --| Display the selected spec put what into fld "SpecName" --| btn "setSpec" has the instructions send "mouseUp" to btn "SetSpec" end menuPick Over to you…
Hugh Senior www.FlexibleLearning.com
|
|