I'm trying to implement a rubber-band selection box in Dojo's gfx with Canvas as the renderer. My intention is to have the selection box drawn when the mouse is clicked down and dragged, then disappear once the mouse is released. Unfortunately I've run into a problem.
jsFiddle example: http://jsfiddle.net/7F9fy/
The main problem is somewhere in onmousemove (or related to it):
whiteRect.connect("onmousemove",function(e) {
if(isMouseDown) {
if(whiteRect.groupSelector_) {
pStat.innerHTML = "dragging...";
console.debug("dragging...");
e.stopImmediatePropagation();
e.preventDefault();
var ex = (e.x ? e.x : e.clientX);
var ey = (e.y ? e.y : e.clientY);
if(groupSelector) {
// Also tried getShape, editing that shape, and setShape on groupSelector--same
// behavior, though.
var rectX = (ex - cnvDiv.offsetLeft < whiteRect.groupSelector_.x ? ex - cnvDiv.offsetLeft : whiteRect.groupSelector_.x);
var rectY = (ey - cnvDiv.offsetTop < whiteRect.groupSelector_.y ? ey - cnvDiv.offsetTop : whiteRect.groupSelector_.y);
surface.remove(groupSelector);
groupSelector = surface.createRect({
x: rectX,
y: rectY,
width: Math.abs(ex - cnvDiv.offsetLeft - whiteRect.groupSelector_.x),
height: Math.abs(ey - cnvDiv.offsetTop - whiteRect.groupSelector_.y)
}).setStroke({color: "blue", width: 3});
} else {
groupSelector = surface.createRect({
x: whiteRect.groupSelector_.x,
y: whiteRect.groupSelector_.y,
width: Math.abs(ex - cnvDiv.offsetLeft - whiteRect.groupSelector_.x),
height: Math.abs(ey - cnvDiv.offsetTop - whiteRect.groupSelector_.y)
}).setStroke({color: "blue", width: 3});
}
e.stopPropagation();
}
}
});
If I hold down the left mouse button in the shape/group (the white square in the above example) to which my mouse events are connected and start dragging, the box begins to draw, following my drag motion. When I release the mouse, sometimes the box disappears, and sometimes, it doesn't. When it doesn't, the box keeps being drawn and follows mouse movements as defined to do when I'm dragging.
In the jsFiddle, if you watch console.debug or the paragraph reporter under the canvas, you'll see that on occasion, onmouseup doesn't fire when you release the mouse (I checked for mouseup as well, but that has the same issue). In cases where onmouseup never fires, onmousemove continues to fire. If you click again, sometimes a full mouse click series fires (down, up, click, and move), which then makes the drawn rectangle disappear. Sometimes this doesn't happen, though, and onmousemove keeps firing. If you click after the drag/onmousemove becomes 'stuck' and nothing happens, there are no debug lines or changes to reporters for those events, so it's as if all mouse events except onmousemove are being squelched. I tried adding in stopPropagation, stopImmediatePropagation, and preventDefault, but that didn't help. I also tried using Dojo event's stop, but that didn't change the behavior.
For re-drawing the box in onmousemove, I've tried both 'getShape -> edit properties -> setShape' as well as deleting the shape and making a whole new one; neither of these methods stopped the problem and there wasn't any appreciable difference between them.
I'm using Dojo 1.8.3, and this happens in both Chrome (v25) and Firefox (v19), with either Canvas or SVG as the renderer.
Thoughts? Am I missing something obvious here?
Sorted it out. The problem is the onmouseup event when you decide to stop dragging out the shape can fire on the underlying/attached shape, or on the shape you're dragging. It's random, depending on the cursor position, but favors the drawn shape if you don't have an offset or delay in your drag. (Moveable.js and Mover.js in dojox/gfx pointed me in the right direction.)
I changed my box to a path in the course of trying to make it work, and this seems to perform better, but isn't necessary.
The key was to make a general 'onMouseUp' function, then call that from both the originator-shape's onmouseup as well as the dragged shape's onmouseup. My example is sloppy, but I hope it gets the point across.
jsFiddle: http://jsfiddle.net/n3KGY/1/
Key code:
// General method to clear out a selector if
// one was being drawn.
var selectorMouseUp = function(e) {
reporter.innerHTML = "onmouseup";
isMouseDown = false;
whiteRect.groupSelector_ = null;
if(groupSelector) {
if(selectorUp) {
groupSelector.disconnect(selectorUp);
}
surface.remove(groupSelector);
groupSelector = null;
}
e.stopImmediatePropagation();
e.stopPropagation();
e.preventDefault();
};
// Mouseup event for the background/workspace
whiteRect.connect("onmouseup",function(e){
selectorMouseUp(e);
});
// Make a selector as a path on the surface
// and attach a mouseup to it
var makeSelector = function(x,y,w,h) {
groupSelector = surface.createPath()
.moveTo(x,y)
.hLineTo(x+w).vLineTo(y+h).hLineTo(x).vLineTo(y)
.setStroke({color: "blue", width: 3})
.closePath();
// Attach the same mouseup method as the workspace/background
selectorUp = groupSelector.connect("onmouseup",function(e){
reporter.innerHTML = "onmouseup (selector)";
selectorMouseUp(e);
});
};
bigRect.connect("onmousemove",function(e){
if(isMouseDown) {
if(bigRect.groupSelector_) {
var ex = e.clientX;
var ey = e.clientY;
reporter.innerHTML = "dragging at " + ex+","+ey;
var downX = bigRect.groupSelector_.x;
var downY = bigRect.groupSelector_.y;
var leadingX = (ex - grn.offsetLeft < downX ? ex - grn.offsetLeft : downX);
var leadingY = (ey - grn.offsetTop < downY ? ey - grn.offsetTop : downY);
var selWidth = Math.abs(ex - grn.offsetLeft - downX);
var selHeight = Math.abs(ey - grn.offsetTop - downY);
if(groupSelector) {
// If there's already a selector being drawn, get rid of it.
groupSelector.disconnect(selectorUp);
surface.remove(groupSelector);
}
// Draw the current selector
makeSelector(leadingX,leadingY,selWidth,selHeight);
e.stopImmediatePropagation();
e.stopPropagation();
e.preventDefault();
}
}
});
Related
I have an application where I need an information card to follow the position of the mouse.
I have used the following code:
stage.addEventListener("tick", fl_CustomMouseCursor.bind(this));
function fl_CustomMouseCursor() {
this.NameTag.x = stage.mouseX;
this.NameTag.y = stage.mouseY;
}
It works perfectly in Firefox but in Chrome and Safari the further away the mouse gets from 0,0 on the canvas the larger the distance between NameTag and the mouse (by a factor of 2).
I look forward to any comments.
I see the issue in Chrome as well as Firefox - I don't believe it is a browser issue.
The problem is that the stage itself is being scaled. This means that the coordinates are multiplied by that value. You can get around it by using globalToLocal on the stage coordinates, which brings them into the coordinate space of the exportRoot (this in your function). I answered a similar question here, which was caused by Animate's responsive layout support.
Here is a modified function:
function fl_CustomMouseCursor() {
var p = this.globalToLocal(stage.mouseX, stage.mouseY);
this.NameTag.x = p.x;
this.NameTag.y = p.y;
}
You can also clean up the code using the "stagemousemove" event (which is fired only on mouse move events on the stage), and the on() method, which can do function binding for you (among other things):
stage.on("stagemousemove", function(event) {
var p = this.globalToLocal(stage.mouseX, stage.mouseY);
this.NameTag.x = p.x;
this.NameTag.y = p.y;
}, this);
Cheers,
Good Day all,
I've been working with Cesium for a bit now and I started when Primitive Collections were the thing to use. I had click and drag primitive rendering working, but now I want to upgrade Cesium and move on to entities. I moved over the code, refactored, and can click and drag to draw shapes; however, before I was able to flip the asynchronous flag and it would render as I moved the mouse. Now, I'm unable to do that. I tried setting 'allowDataSourcesToSuspendAnimation' on the viewer to false, but to no avail. Any help would be extremely appreciated.
In my naivety I forgot to add a code snippet to my question. This is in a clock tick event listener that only fires when mouse down is happening(Boolean value set to true)
var radius = Cesium.Cartesian3.distance(cartesianStartMousePosition, cartesianMousePosition);
if (radius > 0) {
if (currentEntity && currentEntity.id) {
currentEntity.position = cartesianStartMousePosition;
currentEntity.ellipse = {
semiMinorAxis: radius,
semiMajorAxis: radius,
material: new Cesium.ColorMaterialProperty(myColor)
};
currentEntity.label = {
text: 'New Overlay',
scale: 0.35
};
overlayEntities.resumeEvents();
}
else {
currentEntity = new Cesium.Entity({
position: cartesianStartMousePosition,
ellipse: {
semiMinorAxis: radius,
semiMajorAxis: radius,
material: new Cesium.ColorMaterialProperty(myColor)
},
label: {
text: 'New Overlay',
scale: 0.35
},
isSaved: false
});
overlayEntities.add(currentEntity);
}
bDrewPrim = true;
}
It looks to me like you're doing too much work to update the entity. You only need to set the values that have changed, and you should only do that if the change was substantial enough to warrant a graphics update. Try replacing the top half of your if statement with something like this:
var lastRadius = 0;
...
if (radius > 0 && !Cesium.Math.equalsEpsilon(radius, lastRadius, Cesium.Math.EPSILON2)) {
lastRadius = radius;
if (currentEntity && currentEntity.id) {
currentEntity.ellipse.semiMinorAxis = radius;
currentEntity.ellipse.semiMajorAxis = radius;
} else {
// Same as before...
I believe the ellipsoid primitive is being built on a worker thread, so this code tries to avoid setting the new radius every tick unless a real change has been applied to it.
Also, you don't show your mouse down handler, but make sure that you're setting this flag, if you aren't already setting it:
viewer.scene.screenSpaceCameraController.enableInputs = false;
This stops the globe from spinning while you drag-select the ellipse. You can reset this to true on mouse up.
I have simple code to "draw" in canvas element:
function doFirst(){
var x = document.getElementById('canvas');
canvas = x.getContext('2d');
window.addEventListener("mousemove", rys, false);
}
function rys(e){
var xPos = e.clientX;
var yPos = e.clientY;
canvas.fillStyle="#000";
canvas.beginPath();
canvas.arc(xPos-7,yPos-7,10,0,Math.PI*2,true);
canvas.closePath();
canvas.fill();
}
window.addEventListener("load", doFirst, false);
As you can see, the function is working only when the mouse is over the canvas element. Now i want to make it "draw" when the mouse is clicked (just like in paint). Can someone tell me how to do it (with code)?
Thx for help
You need to keep track of the current mouse button state (pressed or not pressed) independently of the mouse movements.
You can do this by attaching event handlers to the "mousedown" and "mouseup" events, similar to how you attached to the "mousemove" event.
In these event handlers, you could keep track of the current state of the first mouse button by updating a global variable indicating whether or not the button is currently pressed. Then, in your "mousemove" handler, you can check this global variable and determine whether or not to paint when the mouse is moved.
When using the "mouseup" and "mousedown" events, you may want to limit your handling to only when the first mouse button is pressed. You can do this by checking that the event property "button" is 0. Note, that you can check for other buttons and keep track of them, also.
You can see a working example of this here: http://jsfiddle.net/mQtKz/
I have a slight problem here I try to solve. As I start to do animation with HTML5 and Canvas I want to have a constant animation flow and also be able to capture mouse movement without interrupting the animation flow. This right now seems like a problem. Ill bring
some code from my test code here.
function mainInit()
{
canvas = document.getElementById('canvas');
context = canvas.getContext('2d');
ballArray.push(new Ball(30,30,2,4,15,"#EEEEEE"));
setInterval(drawScene,20);
}
function drawScene()
{
// main drawScene function
clear(); // clear canvas
// draw animated objects
for(var i = 0; i < ballArray.length; i++)
{
ballArray[i].draw();
}
Event_MouseMove();
}
var messageMousePos = '';
function Event_MouseMove()
{
canvas.addEventListener('mousemove', function(evt)
{
var mousePos = getMousePos(canvas, evt);
messageMousePos = "Mouse position: " + mousePos.x + "," + mousePos.y;
context.font = '10pt Calibri';
context.fillStyle = 'black';
context.fillText(messageMousePos , 5, 15);
}, false);
}
The problem is that when I move the eventListner for mouse movement overrides the draw interval and makes it go much slower. How/where should I put the code for the mouse event so it do not interrupt this draw interval, but still draw the mouse events according to the interval?
At a glance, it looks like the code will try to add an event listener every frame...While JS will dump duplicate handlers, it will slow your code down. It's unclear whether you are trying to only capture mouse movement every interval, or constantly, because your code is kinda trying to do both. Here's the best of both worlds solution:
Call addEventListener once outside the loop, and have the function it calls save the mouse position in messageMousePos. Then, within the drawScene function, put your font/fillstyle/filltext code if you really do only want the text outputting every 20ms. This might look choppy compared to how smoothly the text would change if you were constantly rendering the mouse position text.
Here is an example of constantly capturing and displaying the mouse position, as you might actually want to do.
There is an event which is dispatched when the context menu (right click menu) in actionscript for flash has been opened:
ContextMenuEvent.MENU_SELECT
Now, is there an event which is dispatched when the menu has been closed?
Good question.
That would make a nice feature request, an ContextMenuEvent.MENU_CLOSED event :)
I think I have half you're answer. Here's my idea:
var myContextMenu:ContextMenu = new ContextMenu();
var menuLabel:String = "Custom Item";
var rightClicking:Boolean;
addCustomMenuItems();
myContextMenu.addEventListener(ContextMenuEvent.MENU_SELECT, menuSelectHandler);
stage.addEventListener(MouseEvent.MOUSE_DOWN, mouseUpHandler);
var redRectangle = makeRedRectangle();
redRectangle.contextMenu = myContextMenu;
function makeRedRectangle():Sprite{
redRectangle = new Sprite();
redRectangle.graphics.beginFill(0x990000,.2);
redRectangle.graphics.drawRect(0, 0, stage.stageWidth, stage.stageHeight);
redRectangle.mouseChildren = false;
addChild(redRectangle);
return redRectangle;
}
function addCustomMenuItems():void {
myContextMenu.hideBuiltInItems();
var item:ContextMenuItem = new ContextMenuItem(menuLabel);
myContextMenu.customItems.push(item);
item.addEventListener(ContextMenuEvent.MENU_ITEM_SELECT, menuItemSelectHandler);
}
function menuSelectHandler(event:ContextMenuEvent):void {
trace("menuSelectHandler: " + event);
rightClicking = true;
}
function menuItemSelectHandler(event:ContextMenuEvent):void {
trace("menuItemSelectHandler: " + event);
}
function mouseUpHandler(event:MouseEvent):void{
if(rightClicking){
trace('ContextMenu Closed\nThank You! Come Again!');
rightClicking = false;
}
}
Basically I create a sprite that sits on top of everything, but has mouseChildren set to false, so clips bellow it can get the clicks. You might want to have this one transparent. I used this so you get an event fired when you right click over it. When that happens I set rightClicking to true, meaning, I know the right click was pressed, I'm just waiting for something else to happen. There are two options:
The user selects an item from the menu.
The user click away to make the menu go away.
For option 1, if the user selects any of your custom items, that's cool, you can handle that, if not, at least you know what might happen.
For option 2 I've setup the listener for the MOUSE_DOWN event, so if the rightClicking was turned on and you got to the mouse down, that's your menu closing.
Hope this helps!
I know, it looks like hacky old school as2, and the code is modified from the documentation example, but it's a thought :)