How do I access the object I am declaring with a lambda, inside the lambda?
E.g.
final Marker marker = Marker(
position: LatLng(
center.latitude + sin(_markerIdCounter * pi / 6.0) / 20.0,
center.longitude + cos(_markerIdCounter * pi / 6.0) / 20.0,
),
onDragEnd: (LatLng newPosition) async {
print('Old position: ${marker.position}');
},
);
gives error: Local variable 'marker' can't be referenced before it is declared.
And
print('Old position: ${position}');
gives error: Undefined name 'position'.
Quite frankly you can't do this. As the error states you cannot reference the variable marker before it has been declared. This makes sense because you are trying to reference a pointer to an object that hasn't been been constructed and therefore has no pointer pointing to it.
I would think about why exactly you need to reference the the marker variable inside one of it's constructor parameters. You may be trying to work against the design of the object using the lambda. If you decide you really need to access the marker's data inside the lambda, you could try wrapping the Marker in a factory like this:
class MarkerFactory {
Marker _marker;
Marker buildMarker() => _marker ??= Marker(
position: LatLng(
center.latitude + sin(_markerIdCounter * pi / 6.0) / 20.0,
center.longitude + cos(_markerIdCounter * pi / 6.0) / 20.0,
),
onDragEnd: (LatLng newPosition) async {
print('Old position: ${marker.position}');
},
);
}
Let me know if this is helpful.
Related
Basically the problem is that after you rotate the camera, the points that are given as arguments in the callback for dragging are not what I expected. I'm guessing I have to Rotate the given points also but I just couldn't.
Can Someone explain what is going on, is this some kind of bug or what should I do in order the sprite to follow the mouse cursor?
Easiest way to explain the problem is to reproduce it:
1) Go to Phaser Example Runner
2) Copy- Paste this code:
var config = {
type: Phaser.WEBGL,
parent: 'phaser-example',
scene: {
preload: preload,
create: create
}
};
var game = new Phaser.Game(config);
function preload ()
{
this.load.image('eye', 'assets/pics/lance-overdose-loader-eye.png');
}
function create ()
{
var image = this.add.sprite(200, 300, 'eye').setInteractive();
this.cameras.main.setRotation(Math.PI);
image.on('pointerover', function () {
this.setTint(0x00ff00);
});
image.on('pointerout', function () {
this.clearTint();
});
this.input.setDraggable(image);
this.input.on('dragstart', function (pointer, gameObject) {
gameObject.setTint(0xff0000);
});
this.input.on('drag', function (pointer, gameObject, dragX, dragY) {
console.log(`x: ${dragX}, y: ${dragY}`);
gameObject.x = dragX;
gameObject.y = dragY;
});
this.input.on('dragend', function (pointer, gameObject) {
gameObject.clearTint();
});
}
3) Open the console, drag around the Eye and look at what coordinates are given.
4) If you remove line 24 (the rotation of the camera) Everything works as expected.
(The example is taken from Phaser 3 Official examples and a bit changed for the bug)
According to Phaser's API Documentation on the setRotation() method, the rotation given in radians applies to everything rendered by the camera. Unfortunately, your pointer isn't rendered by the camera so it doesn't get the same rotated coordinates. Not sure if this is a bug with the library or just a poorly documented exception, but I believe there is a workaround.
Create 2 variables to hold an initial position and a final position:
var image = this.add.sprite(200, 300, 'eye').setInteractive();
var initial = [];
var final = [];
Populate the initial position in your .on('dragstart') method:
this.input.on('dragstart', function (pointer, gameObject) {
initial = [
gameObject.x,
gameObject.y,
pointer.x,
pointer.y
];
gameObject.setTint(0xff0000);
});
Then, populate the final variable in your .on('drag') method:
this.input.on('drag', function (pointer, gameObject, dragX, dragY) {
final = [
gameObject.x, // not necessary but keeping for variable shape consistency
gameObject.y, // not necessary but keeping for variable shape consistency
pointer.x,
pointer.y
];
gameObject.x = initial[0] + (initial[2] - final[2]);
gameObject.y = initial[1] + (initial[3] - final[3]);
});
All we're doing here is keeping track of the change in pointer position and mimicking that change in our gameObject.
I am new to fabtric js. How can I add auto generating id and custom attribute to fabric js object.
For example, I would like to add an id that is autogenerated with increment integer of type of object and category: 'new' when an object is added
As far as I am concerned, fabric js does not support this by default.
var text = new fabric.IText('Enter text here', {
id: 'text-i' -> where i increment according to number of IText in the canvas
left: 150,
top: 50,
type: 'new'
});
one way you can achive this is set some extra object in your canvas:
canvas.ObjectCounter = {};
then add all the tipes to it:
canvas.ObjectCounter.['text'] = 0;
canvas.ObjectCounter.['rect'] = 0;
canvas.ObjectCounter.['i-text'] = 0;
and so on...
when creating object:
var text = new fabric.IText('Enter text here', {
id: 'text-' + canvas.objectCounter['i-text']++,
left: 150,
top: 50
});
be aware that 'type' is a property that needs to stay as it is, do not change it.
This will be just number of objects created if you handle like this.
If you want to know how many of them are on the canvas you have to use something different in the events
canvas.on('object:added', function(e){});
canvas.on('object:removed', function(e){});
another ThreeJS question:
How can I make a hovered (intersected) object scale smooth to a defined size? My current code is
INTERSECTED.scale.x *= 1.5;
INTERSECTED.scale.y *= 1.5;
but this only works like on/off.
Edit: My Solution (with tweenJS)
While mouseOver I scale up the element to 200% :
function scaleUp(){
new TWEEN.Tween( INTERSECTED.scale ).to( {
x: 2,
y: 2
}, 350 ).easing( TWEEN.Easing.Bounce.EaseOut).start();
}
While mouseOut I scale down the element to 100% :
function scaleDown(){
new TWEEN.Tween( INTERSECTED.scale ).to( {
x: 1,
y: 1
}, 350 ).easing( TWEEN.Easing.Bounce.EaseOut).start();
}
Finally I call the functions in the mouse function.
if (intersects[0].object != INTERSECTED)
scaleUp();
else
scaleDown();
That's all. Very useful for UIs I think.
Using the tween library (found in three.js/examples/js/libs/tween.min.js) you can animate the scale like so:
function setupObjectScaleAnimation( object, source, target, duration, delay, easing )
{
var l_delay = ( delay !== undefined ) ? delay : 0;
var l_easing = ( easing !== undefined ) ? easing : TWEEN.Easing.Linear.None;
new TWEEN.Tween( source )
.to( target, duration )
.delay( l_delay )
.easing( l_easing )
.onUpdate( function() { object.scale.copy( source ); } )
.start();
}
and then call it like so:
setupObjectScaleAnimation( INTERSECTED,
{ x: 1, y: 1, z: 1 }, { x: 2, y: 2, z: 2 },
2000, 500, TWEEN.Easing.Linear.None );
Or if you want to use the render loop:
clock = new THREE.Clock();
time = clock.getElapsedTime();
INSPECTED.scale.x = time / 1000;
INSPECTED.scale.y = time / 1000;
You can change the divisor based on how fast or slow you want the animation to happen.
I am trying to zoom and pan a text which is draggable already. All the examples are on images or shapes and it seems I cannot adapt it to a text object. My questions are:
Do I have to use the anchors or is any simpler way zoom a text with Kineticjs?
I found an example regarding zooming a shape and the code crashes here:
var layer = new Kinetic.Layer({
drawFunc : drawTriangle //drawTriangle is a function defined already
});
Can we call a function while we are creating a layer?
I usually create a layer and then add the outcome of the function in it.
Any idea would be great, thanks.
I thought of many ways you could do this but this is the one I ended up implementing: jsfiddle
Basically, you have an anchor (which doesn't always have to be there, you can hide and show it if you would like. Let me know if you need help with that) and if you drag the anchor down it increases the fontSize, and if you drag the anchor up it decreases the fontSize.
I followed the exact same anchor tutorial but instead I added a dragBoundFunc to limit dragging to the Y-axis:
var anchor = new Kinetic.Circle({
x: x,
y: y,
stroke: '#666',
fill: '#ddd',
strokeWidth: 2,
radius: 8,
name: name,
draggable: true,
dragOnTop: false,
dragBoundFunc: function (pos) {
return {
x: this.getAbsolutePosition().x,
y: pos.y
};
}
});
And then I updated the updateAnchor() function to only detect the single anchor I added to the group named sizeAnchor:
var mY = 0;
function update(activeAnchor, event) {
var group = activeAnchor.getParent();
var sizeAnchor = group.get('.sizeAnchor')[0];
var text = group.get('.text')[0];
if (event.pageY < mY) {
text.setFontSize(text.getFontSize()-1);
} else {
text.setFontSize(text.getFontSize()+1);
}
sizeAnchor.setPosition(-10, 0);
mY = event.pageY;
}
Basically mY is used compared to the e.PageY to see if the mouse is moving up or down. Once we can determine the mouse direction, then we can decide if we should increase or decrease the fontSize!
Alternatively, you can use the mousewheel to do the exact same thing! I didn't implement it myself but it's definitely doable. Something like:
Mousewheel down and the fontSize decreases
Mousewheel up and the fontSize increases
Hopefully this emulates "Zooming" a text for you. And I guess being able to drag the text acts as "panning" right?
UPDATE (based on comment below)
This is how you would limit dragging to the Y-Axis using dragBoundFunc:
var textGroup = new Kinetic.Group({
x: 100,
y: 100,
draggable: true,
dragBoundFunc: function (pos) {
return {
x: this.getAbsolutePosition().x,
y: pos.y
};
}
});
See the updated jsfiddle (same jsfiddle as above)
Is there a possibility of adding an element or set that exists within a paper to another paper, without creating each element twice from scratch?
Background for this: I visualize a large node graph and want to create an "overview map" in a separate paper.
The following set of codes adds a nw function to Raphael Set and Raphael Elements. The usage is to simply call .cloneToPaper(targetPaper) on any set or element.
(function (R) {
var cloneSet; // to cache set cloning function for optimisation
/**
* Clones Raphael element from one paper to another
*
* #param {Paper} targetPaper is the paper to which this element
* has to be cloned
*
* #return RaphaelElement
*/
R.el.cloneToPaper = function (targetPaper) {
return (!this.removed &&
targetPaper[this.type]().attr(this.attr()));
};
/**
* Clones Raphael Set from one paper to another
*
* #param {Paper} targetPaper is the paper to which this element
* has to be cloned
*
* #return RaphaelSet
*/
R.st.cloneToPaper = function (targetPaper) {
targetPaper.setStart();
this.forEach(cloneSet || (cloneSet = function (el) {
el.cloneToPaper(targetPaper);
}));
return targetPaper.setFinish();
};
}(Raphael));
For a sample implementation, you may check out this fiddle: http://jsfiddle.net/shamasis/39yTS/
Note that if you have events on the source elements, they will not be cloned to the target paper.
Raphael don't allow to move element from one paper to another directly.
So it is better to create a new element with same property in the target paper.
I have created following sample method. you can add the code in your page and use cloneToPaper function to clone a element or a set to another paper.
function extractJSON(element) {
var attr = element.attr(),
newNodeJSON = {type: element.type},
key;
for (key in attr) {
newNodeJSON[key] = attr[key];
}
return newNodeJSON;
}
/*
* #param {Object} element: raphael element or set
* #param {Object} paper: Target paper where to clone
* #return {object}: newly created set or element
*/
function cloneToPaper(element, paper) {
var isSet = element.type === 'set',
elementJSONArr = [],
i, ln, newSet;
if (isSet) {
ln = element.items.length;
for (i = 0; i < ln; i += 1) {
elementJSONArr.push(extractJSON(element.items[i]));
}
}
else {
elementJSONArr.push(extractJSON(element));
}
newSet = paper.add(elementJSONArr);
return isSet ? newSet : newSet[0];
}