/* --------------- // ZoomPane Component // --------------- // // AUTHOR: Sammy Joe Osborne // DATE: January 21, 2008 // // DESCRIPTION: Creates a Pane with the desired content. Content then becomes zoomable with provided or specified zoom-in/zoom-out buttons. Content becomes draggable in Pane once zoomed in. All aspects can be handled in the parameters panel or set with actionscript. Listed below are the accesible properties: ------------------------------------------ content:MovieClip - get the MovieClip object currently serving as content in the ZoomPane contentPath:String - get/set the content of the ZoomPane using the desired objects ID path zoomFactor:Number - get/set the zoom factor autoSize:String - get/set (values: "true" or "false"). If true, auto sizes the ZoomPane to fit the content ZoomInButton_Name:String - get/set the zoom-in button instance name if you dont want the default buttons zoomInButton:MovieClip - get the MovieClip representing the zoom-in button ZoomOutButton_Name:String - get/set the zoom-out button instance name if you dont want the default buttons zoomOutButton:MovieClip - get the MovieClip representing the zoom-out button ZoomInIcon:String - get/sets the identifier path of the zoom-in mouse icon */ //import UIComponent so I can access it's methods and stuff directly import mx.core.UIComponent; import mx.transitions.Tween; import mx.transitions.easing.*; import mx.utils.Delegate; //Event metadata tag...I don't know its purpose, but it's in all the other components [Event("change")] class ZoomPane extends UIComponent { //All Components must declare these to be proper components //in the components framework. This must be some standard that you "should" follow. static var symbolName:String = "ZoomPane"; //so the name of the component... static var symbolOwner:Object = ZoomPane; //A reference to this class/object? var className:String = "ZoomPane"; //to hold the components instance name //----Do I really need this, or does this act as a movieclip and have a _name var? private var myInstanceName:String; //Graphical elements of the ZoomPane private var borderFrame:MovieClip; private var contentMask_mc:MovieClip; private var content_mc:MovieClip; private var zoomPane_mc:MovieClip; private var btnZoomIn_mc:MovieClip; private var btnZoomOut_mc:MovieClip; private var zoomInIcon_mc:MovieClip; private var boundingBox_mc:MovieClip; var initializing:Boolean = true; //For the dragging marquee var marquee_mc:MovieClip; var xZoomPoint; var yZoomPoint; var validClick:Boolean = false; var validRelease:Boolean = false; //These are accessible through getters/setters to set the content movie clip and button instance names private var __contentPath:String = ""; private var __btnZoomInName:String = ""; private var __btnZoomOutName:String = ""; private var __zoomInIconPath:String =""; private var __autoSize:Boolean = false; private var __zoomFactor:Number = 2.5; //Flags for the state of the ZoomPane. //Can be retrieved through getters //Will be set on events and cleared afterwards. private var __zoomedIn:Boolean = false; private var __panning:Boolean = false; //Sizing variables private var paneOriginalWidth; private var paneOriginalHeight; private var contentOriginalWidth; private var contentOriginalHeight; //Constructor function ZoomPane(){ } //Initialization. Required for all v2 components. Must also call it's //parents init() method with super.init(), which would be UIComponent. function init(Void):Void { super.init(); boundingBox_mc._visible = false; boundingBox_mc._width = 0; boundingBox_mc._height = 0; setUserDefinedMovieClips(); myInstanceName = this._name; //Call to addButtonCode() has to be done in createChildren(), //since nothing has been added to the stage at this point } function setUserDefinedMovieClips():Void { //Attach the zoom in mouse icon. If the user didn't specify one, //or if it's invalid, use the default zoomInIcon clip provided zoomInIcon_mc = _parent.attachMovie(__zoomInIconPath, "zoomInIcon_mc", _parent.getNextHighestDepth()); if(zoomInIcon_mc == undefined) { trace("Mouse Zoom-In icon not provded. Default icon is being used."); __zoomInIconPath = "defaultZoomInIcon"; zoomInIcon_mc = _parent.attachMovie(__zoomInIconPath, "zoomInIcon_mc", _parent.getNextHighestDepth()); } zoomInIcon_mc._visible = false; //Assigns the button movieclips, HOWEVER, in createChildren() these are checked to see if they've both //been specified. If not, default buttons are added to the lower right corner. btnZoomIn_mc = _parent[__btnZoomInName]; btnZoomOut_mc = _parent[__btnZoomOutName]; } //Create children objects needed at start up. The //createChildren() method is required for components //extending UIComponent. public function createChildren():Void { trace("createChildren"); initializing = false; //add the content to the stage so we can get its width/height for autosizing purposes content_mc = createObject(__contentPath, "content", 10); if(__autoSize){ setSize(content_mc._width, content_mc._height); } zoomPane_mc = createObject("borderFrame", "zoomPane_mc", 12); //This is just an attachMovie command!!! why macromedia, why... contentMask_mc = createObject("contentMask", "contentMask_mc", 11); establishContentSettings(); /* //This creates an object in the content_mc with alpha set to zero so that all areas of the content_mc //become clickable. Otherwise, there might be gaps in the content_mc, so when the user tried to pan //around, they'd have a hard time clicking on it. content_mc.createObject("contentMask", "alphaBackground", 30); content_mc.alphaBackground._width = width; content_mc.alphaBackground._height = height; content_mc.alphaBackground._alpha = 0; contentOriginalWidth = content_mc._width; contentOriginalHeight = content_mc._height; content_mc.setMask(contentMask_mc);*/ //See comment in setUserDefinedMovieClips() method //This checks to see if both button instance names were provided and valid, and if not, use the default buttons if((btnZoomIn_mc == undefined)||(btnZoomOut_mc == undefined)) { trace("Zoom in/out buttons not provided or not found. Default icons are being used."); btnZoomIn_mc = createObject("btnDefaultZoomIn", "btnDefaultZoomIn", getNextHighestDepth()); btnZoomOut_mc = createObject("btnDefaultZoomOut", "btnDefaultZoomOut", getNextHighestDepth()); } //must call this here instead of in init() because the neccessary movieclips aren't added until now addButtonCode(); size(); } private function establishContentSettings():Void { //This creates an object in the content_mc with alpha set to zero so that all areas of the content_mc //become clickable. Otherwise, there might be gaps in the content_mc, so when the user tried to pan //around, they'd have a hard time clicking on it. content_mc.createObject("contentMask", "alphaBackground", 30); content_mc.alphaBackground._width = width; content_mc.alphaBackground._height = height; content_mc.alphaBackground._alpha = 0; //record the original width and height so it can return //back to its normal size after it's been zoomed contentOriginalWidth = content_mc._width; contentOriginalHeight = content_mc._height; content_mc.setMask(contentMask_mc); } //draw() function. Ok, so this redraws the component and is only called //from within an invalidate() call. The alternative would be to put it in //the set() method for value, but that would be inefficient since there might be //multiple things that change in some components, causing a zillion draw()'s for //each set() method instead of just one draw() with all the changes function draw():Void { super.draw(); size(); } //Invoked when the size changes //Also, resizes the children including the content function size():Void { super.size(); drawFrame(zoomPane_mc, width, height); //draws the border for the zoompane contentMask_mc._width = width; contentMask_mc._height = height; if(btnZoomIn_mc._name == "btnDefaultZoomIn"){ relocateButtons(); } //This causes a redraw, if neccessary invalidate(); } //repositions the default buttons to the bottom right corner function relocateButtons():Void { btnZoomIn_mc._x = width - btnZoomIn_mc._width; btnZoomIn_mc._y = height - btnZoomIn_mc._height; btnZoomOut_mc._x = width - btnZoomIn_mc._width - btnZoomOut_mc._width; btnZoomOut_mc._y = height - btnZoomOut_mc._height; } function drawFrame(target, x2, y2){ var strokeSize = 1; target.clear(); target.lineStyle(strokeSize, 0x6F7777, 100, true, "normal", "square", "miter"); target.moveTo(0, 0); target.lineTo(x2, 0); target.lineStyle(strokeSize, 0xD5DDDD, 100, true, "normal", "square", "miter"); target.moveTo(x2, y2); target.lineTo(0, y2); target.lineStyle(strokeSize, 0x919999, 100, true, "normal", "square", "miter"); target.moveTo(x2, 0); target.lineTo(x2, y2); target.lineStyle(strokeSize, 0x919999, 100, true, "normal", "square", "miter"); target.moveTo(0, y2); target.lineTo(0, 0); //----------------------- target.lineStyle(strokeSize, 0xD5DDDD, 100, true, "normal", "square", "miter"); target.moveTo(x2-strokeSize, strokeSize); target.lineTo(x2-strokeSize, y2-strokeSize); target.lineStyle(strokeSize, 0xD5DDDD, 100, true, "normal", "square", "miter"); target.moveTo(strokeSize, y2-strokeSize); target.lineTo(strokeSize, strokeSize); target.lineStyle(strokeSize, 0xC4CCCC, 100, true, "normal", "square", "miter"); target.moveTo(strokeSize, strokeSize); target.lineTo(x2-strokeSize, strokeSize); target.lineStyle(strokeSize, 0xEEEEEE, 100, true, "normal", "square", "miter"); target.moveTo(x2-strokeSize, y2-strokeSize); target.lineTo(strokeSize, y2-strokeSize); } //ok, the getter/setters for contentPath, autoSize, btnZoomInName and btnZoomOutName. //It Forces a call to invalidate() to redraw everything when the value changes. //The metatags are for the property inspector and binds the //values the user types to this value, or something like that? [Bindable("writeonly")] [Inspectable(defaultValue="")] function set contentPath(path:String) { trace("set contentPath"); if(!initializing) { content_mc = createObject(path, "content", 10); //This sets the mask and alpha background for the content //and also records the content's original width/height establishContentSettings(); } __contentPath = path; invalidate(); } function get contentPath():String { return __contentPath; } function get content():MovieClip { return content_mc; } [Bindable] [ChangeEvent("change")] [Inspectable(defaultValue="default")] function set ZoomInButton_Name(instanceName:String) { __btnZoomInName = instanceName; invalidate(); } function get ZoomInButton_Name():String { return __btnZoomInName; } function get zoomInButton():MovieClip { return btnZoomIn_mc; } [Bindable] [ChangeEvent("change")] [Inspectable(defaultValue="default")] function set ZoomOutButton_Name(instanceName:String) { __btnZoomOutName = instanceName; invalidate(); } function get ZoomOutButton_Name():String { return __btnZoomOutName; } function get zoomOutButton():MovieClip { return btnZoomOut_mc; } [Bindable] [ChangeEvent("change")] [Inspectable(defaultValue="default")] function set ZoomInIcon(path:String) { if(path = "default") path = "defaultZoomInIcon"; __zoomInIconPath = path; invalidate(); } function get ZoomInIcon():String { return __zoomInIconPath; } function get zoomInIcon():MovieClip { return zoomInIcon_mc; } [Bindable] [ChangeEvent("change")] [Inspectable(enumeration="false, true", defaultValue="false")] [Inspectable(defaultValue=false)] function set autoSize(val:String) { if(val == "true") __autoSize = true; if(val == "false") __autoSize = false; invalidate(); } function get autoSize():Boolean { return __autoSize; } [Bindable] [ChangeEvent("change")] [Inspectable(defaultValue=3)] function set zoomFactor(val:Number) { __zoomFactor = val; invalidate(); } function get zoomFactor():Number { return __zoomFactor; } //------------------------- Gets pretty messy from here on ---------------------------- //----------------- This is all the zoom in button code, and Senocular's Marquee Code (thanks!) ---------------------- function addButtonCode():Void { //Delegate keeps it in scope. Otherwise the buttons have no access to the button movieclips //or any of the component variables for that matter btnZoomIn_mc.onRelease = Delegate.create(this, zoomInButtonCode); //this fixes the scope issue btnZoomOut_mc.onRelease = Delegate.create(this, zoomOutButtonCode); } function zoomInButtonCode():Void { Mouse.hide(); btnZoomIn_mc.enabled = false; btnZoomIn_mc._alpha = 20; zoomInIcon_mc._x = _root._xmouse; zoomInIcon_mc._y = _root._ymouse; zoomInIcon_mc.startDrag(); zoomInIcon_mc._visible = true; //turn on content's zooming ability //Delegate keeps it in scope content_mc.onRelease = Delegate.create(this, zoomIn); //---------------------------------------- var marquee_mc:MovieClip; if(_root.marquee_mc._name == null){ content_mc.createEmptyMovieClip("marquee_mc", 1); } marquee_mc = content_mc.marquee_mc; var antsbmp = flash.display.BitmapData.loadBitmap("ants"); // define a matrix to control the shifting of // the ants pattern when its drawn var shift = new flash.geom.Matrix(); var contentClickLoc; // retains location mouse is clicked var contentReleaseLoc; // retains location mouse is released var currLoc; // when user presses mouse, start drawing the marquee if it's within the zoomPane // and we're not already zoomed in onMouseDown = function(){ var hit_X = _parent._xmouse; var hit_Y = _parent._ymouse; if((this.hitTest(hit_X, hit_Y, false))&&(!__zoomedIn)){ validClick = true; marquee_mc.clear(); marquee_mc._visible = true; // set mouse locations // nothing for release yet contentReleaseLoc = null; // for click make a point based on the mouse location contentClickLoc = new flash.geom.Point(content_mc._xmouse, content_mc._ymouse); } } // when user releases the mouse, stop drawing the marquee onMouseUp = function(){ //make the marquee disapear for now marquee_mc._visible = false; //if the user didn't click within the zoomPane, exit the function if(!validClick) return; // set release to a point based on the mouse location contentReleaseLoc = new flash.geom.Point(content_mc._xmouse, content_mc._ymouse); //make sure release point is in the bounds of the content clip, and if not set it to be if(contentReleaseLoc.x > content_mc._width){ contentReleaseLoc.x = content_mc._width; } if(contentReleaseLoc.x < 0){ contentClickLoc.x = 0; } if(contentReleaseLoc.y > content_mc._height){ contentReleaseLoc.y = content_mc._height; } if(contentReleaseLoc.y < 0){ contentReleaseLoc.y = 0; } xZoomPoint = (contentClickLoc.x + contentReleaseLoc.x)/2; yZoomPoint = (contentClickLoc.y + contentReleaseLoc.y)/2; //trace(xZoomPoint + ", " + yZoomPoint); //if(!this.hitTest(content_mc._xmouse, content_mc._ymouse, false)){ //delete onMouseDown; //delete onMouseUp; //} validClick = false; } // constantly update the marquee onEnterFrame = function(){ // if the user hasnt clicked the mouse // to create a marquee, exit the function if (!contentClickLoc) return; // set currloc, the location the marquee will // be drawn to, to either releaseloc if it isnt null // or a point that is the location of the mouse - that // means the user is currently drawing the marquee currLoc = (contentReleaseLoc) ? contentReleaseLoc : new flash.geom.Point(content_mc._xmouse, content_mc._ymouse); // shift the matrix used for the ants down by // a value of one - this makes them march shift.translate(0, 1); // the marquee is dynamically drawn into marquee_mc // with the ants as a bitmap fill // clear marquee_mc each frame to update marquee_mc.clear(); // start a bitmap fill with the ants // using the shift matrix to offset their position marquee_mc.beginBitmapFill(antsbmp, shift); // draw two squares to make a 1 pix thick hollow square // that will contain the marquee. Draw around // clickloc and currloc drawSquare(marquee_mc, contentClickLoc.x, contentClickLoc.y, currLoc.x, currLoc.y); drawSquare(marquee_mc, contentClickLoc.x+1, contentClickLoc.y+1, currLoc.x-1, currLoc.y-1); // end the fill marquee_mc.endFill(); } // method for drawing a square in target function drawSquare(target, x1, y1, x2, y2){ target.moveTo(x1, y1); target.lineTo(x2, y1); target.lineTo(x2, y2); target.lineTo(x1, y2); target.lineTo(x1, y1); } } function zoomOutButtonCode():Void { //set state to zoomed out __zoomedIn = false; delete content_mc.onPress; delete content_mc.onRelease; delete content_mc.onReleaseOutside; delete onEnterFrame; //change size back to original size new Tween(content_mc, "_width", Regular.easeInOut, content_mc._width, contentOriginalWidth, 1, true); new Tween(content_mc, "_height", Regular.easeInOut, content_mc._height, contentOriginalHeight, 1, true); //move back to original location (0, 0) new Tween(content_mc, "_x", Regular.easeInOut, content_mc._x, 0, 1, true); new Tween(content_mc, "_y", Regular.easeInOut, content_mc._y, 0, 1, true); //btnZoomOut_mc._alpha = 20; btnZoomOut_mc.enabled = false; btnZoomIn_mc._alpha = 100; btnZoomIn_mc.enabled = true; } //Code to actually make the content scale up and appear to be "zooming in" function zoomIn():Void { //set state to zoomed in __zoomedIn = true; //change mouse icon back to normal zoomInIcon_mc._visible = false; Mouse.show(); //set vars for where it should move / resize to var midX = width/2; //midpoint of the zoomPane var midY = height/2; //midpoint of the zoomPane // Center the content based on where the user clicked. // It moves it to the center point as it would exist // within the scale factor var newX = midX - xZoomPoint*__zoomFactor; var newY = midY - yZoomPoint*__zoomFactor; //scale content (to zoom in) new Tween(content_mc, "_xscale", Regular.easeInOut, content_mc._xscale, __zoomFactor*100, 1, true); new Tween(content_mc, "_yscale", Regular.easeInOut, content_mc._yscale, __zoomFactor*100, 1, true); //move the content to where they clicked new Tween(content_mc, "_x", Regular.easeInOut, content._x, newX, 1, true); new Tween(content_mc, "_y", Regular.easeInOut, content._y, newY, 1, true); //make the content dragable once zoomed in content_mc.onPress = function(){ this.startDrag(); //set state to panning __panning = true; } content_mc.onRelease = function(){ this.stopDrag(); //set state to not panning __panning = false; } content_mc.onReleaseOutside = function() { this.stopDrag(); //set state to not panning __panning = false; } //allow zoom out functionality btnZoomOut_mc.enabled = true; btnZoomOut_mc._alpha = 100; //delete the zoom in functionality delete this.onRelease; } }