Problems with AS3 scrollbar script - actionscript-3

I have this symbol for a scrollbar in adobe flash:
The instance name of the dark part is handle, and that of the lighter bar is bar. The symbol is an instance of the Scroll class:
package ui {
import flash.display.MovieClip;
import flash.events.MouseEvent;
public class Scroll extends MovieClip{
public static const VERTICAL = 0;
public static const HORIZONTAL = 1;
protected var _handleRatio = 1;
protected var _orientation = VERTICAL;
protected var _mousePrevPos:int;
protected var _handleMargin:int;
protected var _container:MovieClip;
protected var _containerInitialPosition:int; //added after update #2
public function Scroll(container:MovieClip, visibleLength:int, orientation:int = VERTICAL) {
_orientation = orientation;
_container = container;
var containerLength:int;
switch(_orientation){
case VERTICAL:
rotation = 0;
containerLength = container.height;
_containerInitialPosition = container.y; //added after update #2
break
case HORIZONTAL:
rotation = - 90;
containerLength = container.width;
_containerInitialPosition = container.x; //added after update #2
break;
default:
throw new Error('Unknown orientation');
break;
}
if((_handleRatio = visibleLength/containerLength) > 1)
_handleRatio = 1;
handle.height = _handleRatio*height;
_handleMargin = handle.y;
handle.addEventListener(MouseEvent.MOUSE_DOWN, _startDrag, false, 0, true);
}
protected function _startDrag(e:MouseEvent){
stage.addEventListener(MouseEvent.MOUSE_UP, _stopDrag, false, 0, true);
stage.addEventListener(MouseEvent.MOUSE_MOVE, _dragHandle, false, 0, true);
_mousePrevPos = mouseY;
}
protected function _stopDrag(e:MouseEvent = null){
stage.removeEventListener(MouseEvent.MOUSE_UP, _stopDrag);
stage.removeEventListener(MouseEvent.MOUSE_MOVE, _dragHandle);
_mousePrevPos = NaN;
}
protected function _dragHandle(e:MouseEvent){
_moveHandle(mouseY - _mousePrevPos);
_mousePrevPos = mouseY;
}
protected function _moveHandle(moveDifference:int){
if(moveDifference > 0 && !(handle.y + handle.height + _handleMargin > bar.height)){
if(!(handle.y + handle.height + moveDifference + _handleMargin > bar.height))
handle.y = handle.y + moveDifference;
else
handle.y = bar.height - handle.height - _handleMargin;
}
if(moveDifference < 0 && !(handle.y < _handleMargin)){
if(!(handle.y + moveDifference < _handleMargin))
handle.y += moveDifference;
else
handle.y = _handleMargin;
}
switch(_orientation){
case VERTICAL:
_container.y = _containerInitialPosition -((handle.y-_handleMargin)/(bar.height-_handleMargin*2)*_container.height);
// since update #2, the calculated y position is subtracted from the initial y position
break
case HORIZONTAL:
_container.x = _containerInitialPosition -((handle.y-_handleMargin)/(bar.height-_handleMargin*2)*_container.width);
// since update #2, the calculated x position is subtracted from the initial x position
break;
default:
throw new Error('Unknown orientation');
break;
}
}
public function resize(newWidth, newHeight){
switch(_orientation){
case VERTICAL:
width = newWidth;
height = newHeight;
break
case HORIZONTAL:
rotation = 0;
width = newHeight;
height = newWidth;
rotation = -90;
break;
default:
throw new Error('Unknown orientation');
break;
}
}
public function scrollHandle(e:MouseEvent){
_moveHandle(-e.delta);
}
}
}
As you can see, you can create both horizontal and vertical scrollbars. Now, there are two problems with the scrollbar:
For some reason, when dragging the handle up/left with the mouse, the handle moves far slower than the cursor
Also, when you scroll a horizontal scrollbar a little to the right, the left part of the container is cut off, and you can't scroll back to it anymore solved now, see update #2
I really don't know what causes these problems, so can anyone please help me or at least point out where the errors in my code are?
Update
Some extra information to help explain the code:
This is a sketch of a situation where a vertical scrollbar would be needed. The scrollbar would be initiated like so:
var scrollBar:Scroll = new Scroll(container, mask.height, Scroll.VERTICAL);
When the handle of the scrollbar is dragged/scrolled down, the container is moved upwards, so that you get to see a lower part of the contaienr - and vice versa: if you scroll up, the container is moved down.
If you'd want to create a horizontal scrollbar, apart from changing Scroll.VERTICAL into Scroll.HORIZONTAL, you'd pass mask.width as visibleLength instead of mask.height.
I suppose container doesn't neccessarily have to be a MovieClip but can be any DisplayObject.
Also, I'm not using startDrag() so that I can drag the handle and scroll the handle up and down with just one method (_moveHandle()).
Update #2
I solved problem #2: the left part of the container was cut off because when the container hadn't been scrolled yet, the container's x position was somewhere in the middle of the stage. When it was scrolled a little bit to the right, I forgot to set the container's x position to the calculated x position + the initital x position. I updated the code above, with comments behind the new parts so you can see what I changed.
Update #3
You can see a flash file making use of the scrollbar here: http://host.undeadzone.net/scrollBarTest.swf
To reproduce issue #1, do the following:
grab the handle with the mouse and slowly drag the slider down, one notices that the mouse position relative to the slider stays constant. (if you clicked at the top most edge of the handle to start dragging, the mouse will still be at the top most edge when the handle reaches the bottom of the screen. this is the expected behaviour, all good.
Now do the opposite (it doesn't matter if you release the mouse
after doing step 1) and drag the handle upwards slowly. This time,
the position of the mouse relative to the handle changes. If one initiated the drag at the top most edge of the handle and starts
dragging up, the handle does not keep up with the mouse and the
mouse position (relative to the handle) will be above the handle
after dragging, even though it started the dragging within the
handle.
The source code:
http://host.undeadzone.net/scrollBarTest.zip

To fix 1-st issue add e.updateAfterEvent:
protected function _dragHandle(e:MouseEvent){
_moveHandle(mouseY - _mousePrevPos);
_mousePrevPos = mouseY;
e.updateAfterEvent();
}
UPDATE
mouseY returns Number not int.
Replace this line:
protected var _mousePrevPos:int;
with this:
protected var _mousePrevPos:Number;
in Scroll class.
UPDATE #2
Replace
protected function _moveHandle(moveDifference:int){
with
protected function _moveHandle(moveDifference:Number){

Related

Stretch and rotate a Movieclip without distortion

i'm building a flash desktop app, where the user needs to link two Movieclips on stage (a computer and a router) using a line (or whatever can do the job), i want to achieve this same exact effect: image1. I searched and found this solution, i tried the code and did some modifications:
link.addEventListener(MouseEvent.CLICK, linkOnClick);
function linkOnClick(e:MouseEvent){
this.addEventListener(Event.ENTER_FRAME, enterFrame);
var linkPoint:Point = new Point(link.x, link.y);
var mousePoint:Point = new Point();
var distance:Number;
var radians:Number;
function enterFrame(e:Event):void {
//Distance
mousePoint.x = stage.mouseX;
mousePoint.y = stage.mouseY;
distance = Point.distance(linkPoint, mousePoint);
link.width = distance;
//Rotation
radians = Math.atan2(stage.mouseY - link.y, stage.mouseX - link.x);
link.rotation = radians * (180/ Math.PI);
if(link.hitTestObject(router)){trace("Success");}
}
When i compiled the code i got this: image2, so as you may remark, the problems i found are:
1-the edge of the line follows the direction of the mouse, but sometimes it goes beyond the cursor, i want the cursor to drag the edge of the line.
2-the line changes it's width, if it's 90° degrees the line width is so remarkable, i want the line to have a constant width.
how can i acheive the same exact effect shown in image1 ?
// First, lets create mouse-transparent container for drawing.
var DrawingLayer:Shape = new Shape;
addChild(DrawingLayer);
// Hook the event for starting.
stage.addEventListener(MouseEvent.MOUSE_DOWN, onDown);
// Define a storage for keeping the initial coordinates.
var mouseOrigin:Point = new Point;
function onDown(e:MouseEvent):void
{
// Save the initial coordinates.
mouseOrigin.x = DrawingLayer.mouseX;
mouseOrigin.y = DrawingLayer.mouseY;
// Hook the events for drawing and finishing.
stage.addEventListener(MouseEvent.MOUSE_UP, onUp);
stage.addEventListener(MouseEvent.MOUSE_MOVE, onDraw);
}
function onDraw(e:MouseEvent):void
{
// Remove the previous line.
DrawingLayer.graphics.clear();
// Draw a new line.
DrawingLayer.graphics.lineStyle(5, 0xFF6600);
DrawingLayer.graphics.moveTo(mouseOrigin.x, mouseOrigin.y);
DrawingLayer.graphics.lineTo(DrawingLayer.mouseX, DrawingLayer.mouseY);
}
function onUp(e:MouseEvent):void
{
// Unhook the events for drawing and finishing.
stage.removeEventListener(MouseEvent.MOUSE_UP, onUp);
stage.removeEventListener(MouseEvent.MOUSE_MOVE, onDraw);
}
It's because of that the actionscript is trying to stretch the line thickness by changing its container MovieClip's scale. But you can prevent this by setting the line Scale option to None.
To do that, select your line and open the properties menu and then select None from the drop down menu of the Scale option.
But,
I recommend you to draw a line by a code: Draw line from object to Mouse (AS3)
Write below code:
this.graphic.clear ();
this.graphic.lineStyle(0x000000);
this.moveTo(startPoint.x,startPoint.y);
this.lineTo(endpoint.X,endpoint.y);

Keep item at same monitor position regardless of window size/position

I am trying to create a game with fullscreen.
When I add an object to the stage in full screen mode, I would like it to stay at the same coordinates (for example 1000 pixels) relative to the monitor when I exit fullscreen mode.
How can I make the object move to the same location when leaving fullscreen mode?
To get your started:
Something along these lines is what you'll need to do:
stage.align = StageAlign.TOP_LEFT; //you'll need to running a top-left no-scale swf for this to work
stage.scaleMode = StageScaleMode.NO_SCALE;
var itemPoint:Point = new Point(150,150); //the point on the monitor the object should reside
//call this anytime the item needs to be redrawn (eg when the window changes size or position)
function updatePos(e:Event = null){
//We need to also account for the chrome of the window
var windowMargin:Number = (stage.nativeWindow.bounds.width - stage.stageWidth) * .5; //this is the margin or padding that between the window and the content of the window
var windowBarHeight:Number = stage.nativeWindow.bounds.height - stage.stageHeight - windowMargin; //we have to assume equal margin on the left/right/bottom of the window
item.x = itemPoint.x - stage.nativeWindow.x - windowMargin;
item.y = itemPoint.y - stage.nativeWindow.y - windowBarHeight;
}
stage.nativeWindow.addEventListener(NativeWindowBoundsEvent.MOVE, updatePos); //we need to listen for changes in the window position
stage.nativeWindow.addEventListener(NativeWindowBoundsEvent.RESIZE, updatePos); //and changes in the window size
//a click listener to test with
stage.addEventListener(MouseEvent.CLICK, function(e:Event):void {
if(stage.displayState == StageDisplayState.FULL_SCREEN_INTERACTIVE){
stage.displayState = StageDisplayState.NORMAL;
}else{
stage.displayState = StageDisplayState.FULL_SCREEN_INTERACTIVE;
}
});
updatePos();

Match scroll bar scrolling to content scrolling - AS3

I apologize in advanced if this is a little confusing to understand, but I'll do the best I can to explain it.
Basically I've created a scrolling page that allows you have content that extends beyond the stage and swipe up or down to view it.
In this case, a movieclip the size of the stage masks an object under it (taller than the stage) and swiping up/down affects the objects y position, revealing more of it.
The part I'm stuck on is getting the "scroll bar" object on the side to match the position of the top and bottom of the overflow object;
Since the scroll bar has to stay on the stage, when the bottom of the object is reached the scroll bar ends up being off stage because they move at the same speed, so in this case, the scroll bar would need to move slower in order to be at the bottom when the other object is.
The way it's coded right now I can only seem to match either the top with
scrollbar.y = (e.target.y - barY);
the opposite is achieved by
scrollbar.y = (e.target.y + barY);
but I can't seem to achieve both simultaneously.
I'm coding this in AS3 (flash CC) with mobile being my desired platform to publish, and I'll attach my code as well as some screenshots below.
var ease:int = 6;
var targY:int = dragMe.y;
var barY:int = scrollbar.y;
var drag:Boolean = false;
var pos:Number = 0;
var minY:Number = 0 + dragMe.height / 2; // how low the top can go
var maxY:Number = stage.stageHeight - dragMe.height / 2; // how high the bottom can go
var barMax:Number = 0 + scrollbar.height; // how high the bar can go
var barMin:Number = stage.stageHeight - scrollbar.height; // how low the bar can go
dragMe.mask = mcMask;
mcMask.visible = false;
dragMe.addEventListener(Event.ENTER_FRAME, dragging);
dragMe.addEventListener(MouseEvent.MOUSE_DOWN, mouseDown);
stage.addEventListener(MouseEvent.MOUSE_UP, mouseUp);
function dragging(e:Event):void {
if (drag) {
targY = mouseY + pos;
}
// restrict scrolling to stage
targY = Math.min(targY, minY); // how low the top can go
targY = Math.max(targY, maxY); // how high the bottom can go
barY = Math.min(barY, barMin); // how low the bar can go
barY = Math.max(barY, barMax); // how high the bar can go
// Movement of the text
e.target.y += (targY - e.currentTarget.y) / ease;
// Movement of the bar
scrollbar.y = (e.target.y - barY);
}
function mouseUp(e:MouseEvent):void {
drag = false;
}
function mouseDown(e:MouseEvent):void {
pos = e.currentTarget.y - mouseY;
drag = true;
}
Any help would be greatly appreciated!
Grey = stage
Black = outside stage
Good:
http://i.stack.imgur.com/qvAoz.png
Bad:
http://i.stack.imgur.com/FvHIm.png
The part I'm stuck on is getting the "scroll bar" object on the side to match the position of the top and bottom of the overflow object;
Well, there's no need to get the scrollbar object to match the position of the overflow object, you should bind it to the coordinates of the masker object. Btw I usually gather all UI elements right in the IDE, and then parse them. For example, if I need to add a scroll bar, I need only three elements:
container - all content will be added right in it;
masker
Scrollbar movieclip, which consists of two MCs: bg and a bar.
In this case I don't need to place this elements in code, but just call in a function to handle this elements. The code itself is simple enough. You know the height of the masker, the height and the position of the container, and also the height of the scrollbar background and current position of the bar. All you need is to handle the events and react accordingly.
E.g. in case you are scrolling using your scrollbar, just translate current coordinates of the bar and update container's position. If you are dragging your content, update your bar's position accordingly.
Also, there's a better way to drag the bar:
private static function drag(e:MouseEvent):void
{
var myBounds:Rectangle = new Rectangle(bar.x, 0, 0, barBg.height-bar.height+8);
bar.startDrag(false, myBounds);
bar.addEventListener(Event.ENTER_FRAME, update);
}
static private function stopdrag(e:*):void
{
bar.stopDrag();
update(null);
bar.removeEventListener(Event.ENTER_FRAME, update);
}
static private function update(e:Event):void
{
var scroll_run_length:Number = barBg.height - bar.height + 8;
var modificator:Number = (content.height-masker.height) / scroll_run_length;
content.y = startContY -(bar.y * modificator);
}

Dart SVG Drag and Drop Slipping Mouse

I try to create drag and drop behavior in Dart using SVG. I have BasicUnits that are basically GElement (<g>)s. Right now it has a body, which is a RectElement. I am using that element to move the unit around. Here is the definition:
class BasicUnit {
SvgSvgElement canvas;
GElement group;
RectElement body;
bool dragging;
num dragOffsetX, dragOffsetY, width, height;
BasicUnit(SvgSvgElement this.canvas, num x, num y, num this.width, num this.height) {
this.body = new RectElement();
this.body.setAttribute('x', '$x');
this.body.setAttribute('y', '$y');
this.body.setAttribute('width', '$width');
this.body.setAttribute('height', '$height');
this.body.classes.add('processing_body');
this.body.onMouseDown.listen(select);
this.body.onMouseMove.listen(moveStarted);
this.body.onMouseUp.listen(moveCompleted);
this.body.onMouseLeave.listen(moveCompleted);
this.group = new GElement();
this.group.append(this.body);
this.dragging = false;
}
void select(MouseEvent e) {
e.preventDefault();
this.dragging = true;
var mouseCoordinates = getMouseCoordinates(e);
this.dragOffsetX = mouseCoordinates['x'] - body.getCtm().e; //double.parse(body.attributes['x']);
this.dragOffsetY = mouseCoordinates['y'] - body.getCtm().f;
}
void moveStarted(MouseEvent e) {
e.preventDefault();
if (dragging) {
var mouseCoordinates = getMouseCoordinates(e);
num newX = mouseCoordinates['x'] - dragOffsetX;
num newY = mouseCoordinates['y'] - dragOffsetY;
this.body.setAttribute('transform', 'translate($newX, $newY)');
}
}
void moveCompleted(MouseEvent e) {
e.preventDefault();
this.dragging = false;
}
dynamic getMouseCoordinates(e) {
return {'x': (e.offset.x - this.canvas.currentTranslate.x)/this.canvas.currentScale,
'y': (e.offset.y - this.canvas.currentTranslate.y)/this.canvas.currentScale};
}
}
I have an Application object. It gets the svg element for given id. Here is the definition:
class Application {
int WIDTH = 80, HEIGHT = 60;
SvgSvgElement canvas;
Application(canvas_id) {
this.canvas = document.querySelector(canvas_id);
this.canvas.onDoubleClick.listen((MouseEvent e) => addUnit(e));
}
void addUnit(MouseEvent e) {
num x = e.offset.x - WIDTH/2;
num y = e.offset.y - HEIGHT/2;
BasicUnit newUnit = new BasicUnit(this.canvas, x, y, WIDTH, HEIGHT);
this.canvas.append(newUnit.group);
}
}
My problem is that my mouse is slipping over BasicUnit or <g> element. When you select the element close to its edges and try to drag, suddenly element gets dropped. If you try to drag and drop fast, that is the case as well. I tried to follow the example on this webpage, but couldn't figure out what the problem is.
UPDATE
Full source code available here.
UPDATE II
Here is a demo.
The browser is trying to drag the text around that is selected after you double click on the page. This can be fixed easily by preventing the default behaviour for the onMouseDown event (your select method):
void select(MouseEvent e) {
e.preventDefault();
this.dragging = true;
this.group.parentNode.append(this.group);
var mouseCoordinates = this.getMouseCoordinates(e);
this.dragOffsetX = mouseCoordinates['x'] - this.body.getCtm().e;
this.dragOffsetY = mouseCoordinates['y'] - this.body.getCtm().f;
}
The other problem is caused by the svg not drawing fast enough to keep up with the mouse coordinates. So, when moved fast enough the mouse coordinates move outside the body of the SVG element, so the element will no longer get mouse move events. This can be solved by listening for the onMouseMove and onMouseLeave events on the background SVG element (canvas in your example) instead of the elements being moved:
body.onMouseDown.listen(select);
canvas.onMouseMove.listen(moveStarted);
body.onMouseUp.listen(moveCompleted);
canvas.onMouseLeave.listen(moveCompleted);
EDIT:
Using the above method works unless the onMouseUp occurs when the element being dragged is underneath another element, in which case the element is still being dragged until another onMouseUp event occurs on that element, or the cursor moves outside of the viewing area. This can be fixed by just listening for onMouseUp events on the canvas instead of body:
canvas.onMouseUp.listen(moveCompleted);
See this example.
Additionally it might be better to turn on the onMouseMove, onMouseUp, and onMouseLeave listeners when the element is clicked (in your select method), then disable the listeners on onMouseUp and onMouseLeave (the moveCompleted method). I have done this in the example above.

AS3 - removing childs in parent container doesn't reposition childs correctly

I made this plugin wich positions all child objects of a container in a grid formation. Now I added a clickhandler to the childs in the container, and when I click one of them, it is removed. When I remove a full row (from top to bottom) on the right hand side, all goes well, but when I remove a full row on the left hand side, the position of all the items in the container stay on their place but the container itself will be moved to x = 0 and y = 0. What I want is that all childs in the container are moved to x:0, y:0 as one group.
some pictures on what I get and what I want:
1) What I get:
2) What I get when I remove a full row on the left:
3) What I want:
The code I use:
private function clickHandler(event:MouseEvent):void {
var name:String = event.currentTarget.name;
if(container.getChildByName(name) != null)
container.removeChild(container.getChildByName(name));
trace(name, "container.width: ", container.width);
trace(name, "container.width: ", container.height);
trace(name, "container.x: ", container.x);
container.graphics.clear();
container.graphics.beginFill(0x2C2C2C);
container.graphics.drawRect(container.x ,container.y, container.width, container.height);
container.graphics.endFill();
}
Anyone got an idea on how to fix this? :)
EDIT: Code for creating the grid:
private function generateGrid(rows:int, cols:int, spacing:int):void
{
for (var py:int = 0; py <rows; py++)
{
for (var px:int = 0; px <cols; px++)
{
if(childs.length > 0)
{
var child:* = childs.shift();
child.x = (child.width + spacing) * px;
child.y = (child.height + spacing) * py;
} else {
break;
}
}
}
}
There's not really anything to 'fix', you just haven't written any code to move those objects, so they're not moving. You're redrawing the background of the container based on the new width, but you haven't actually moved any of the objects inside that container.
There are a lot of ways you could do this. The simplest solution that comes to mind would be something like this:
- loop through all the children of the container and find the lowest x position.
- loop through again and subtract that lowest x value from the x position of each child.
(You probably want to do this for the y position too, so they'll move up when you remove the top row.)
You would run that entire process each time a child is removed. If the lowest position is 0, meaning there is an object in the leftmost position, then nothing moves, if there is a blank row, the lowest x will be greater than 0, and everything will move over by that amount.
Disclaimer:
It would probably be 'better' to not muddle the graphical views with the data structure so much, but that's the best suggestion I can offer without seeing the rest of your code.
package {
import flash.display.Sprite;
import flash.events.MouseEvent;
public class Grid extends Sprite{
private var _squ:Square;
private var _cont:Sprite;
public function Grid() {
// constructor code
_cont = new Sprite();
addChild(_cont);
for( var i:uint = 0;i< 20; i++){
_squ = new Square(50,50,2,Math.random() * 0xFFFFFF);
_cont.addChild(_squ);
_squ.name = "square_"+i;
_squ.x = 100 + 52 * Math.round(i%5);
_squ.y = 50 + 52 * Math.floor(i/5);
_squ.buttonMode = true;
}
this.addEventListener(MouseEvent.CLICK, onClickAction);
}
public function gridAlign(cont:Sprite):void{
for( var i:uint = 0;i< cont.numChildren; i++){
_cont.getChildAt(i).x = 100 + 52 * Math.round(i%5);
_cont.getChildAt(i).y = 50 + 52 * Math.floor(i/5);
}
}
private function onClickAction(e:MouseEvent):void{
_cont.removeChild(_cont.getChildByName(e.target.name));
this.gridAlign(_cont);
}
}
}
try this.