2

I've gone through the handy Famo.us University tutorials and am prototyping a drag & drop interface. It's the typical UI where the user can drag an icon and drop it onto a target to do something. I've gotten the drag part down, but detecting the drop is getting very hairy. Is there built-in collision detection in Famo.us?

Edit: I've looked at the Collision API but it's not clear whether this would work across views.

Here's how I've organized the project:

AppView (overall container)
 |
 |__ MenuView (sidebar) --> VizView (icons in MenuView)
 |
 |__ PageView (workspace where the drop targets live)

This may not be the best way to go about this. I'm not sure. Hooking up input events across the views seems to be painful.

VizView source:

/*** VizView.js ***/

define(function(require, exports, module) {
    var View          = require('famous/core/View');
    var Surface       = require('famous/core/Surface');
    var Transform     = require('famous/core/Transform');
    var Modifier = require('famous/core/Modifier');
    var ImageSurface  = require('famous/surfaces/ImageSurface');

    var Transitionable = require("famous/transitions/Transitionable");
    var SnapTransition = require("famous/transitions/SnapTransition");
    Transitionable.registerMethod("spring", SnapTransition);

    var GenericSync     = require('famous/inputs/GenericSync');
    var MouseSync       = require('famous/inputs/MouseSync');
    var TouchSync       = require('famous/inputs/TouchSync');
    GenericSync.register({'mouse': MouseSync, 'touch': TouchSync});

    function VizView() {
        View.apply(this, arguments);

        _createIcon.call(this);
    }

    VizView.prototype = Object.create(View.prototype);
    VizView.prototype.constructor = VizView;

    VizView.DEFAULT_OPTIONS = {
        width: 200,
        height: 100,
        angle: -0.2,
        iconSize: 98,
        iconUrl: '',
        title: 'Empty',
        fontSize: 26
    };

    function _createIcon() {
        this.zIndex = 0;
        var me = this;

        var iconSurface = new ImageSurface({
            size: [this.options.iconSize, this.options.iconSize],
            content : this.options.iconUrl,
            properties: {
                cursor: 'pointer'
            }
        });

        var initModifier = new Modifier({
            // places the icon in the proper location
            transform: Transform.translate(24, 2, 0)
        });

        this.position = new Transitionable([0, 0]);
        var positionModifier = new Modifier({
            transform : function(){
                var currentPosition = me.position.get();
                return Transform.translate(currentPosition[0], currentPosition[1], me.zIndex);
            },
        });

        var sync = new GenericSync(
            ['mouse', 'touch']
        );
        sync.on('start', function(data){
            me.zIndex = 1;
        });
        sync.on('update', function(data){
            me.updateIcon(data);
        });
        sync.on('end', function(data){
            var velocity = data.velocity;
            me.position.set([0, 0], {
                method : 'spring',
                period : 150,
                velocity : velocity
            });
            me.zIndex = 0;
        });

        iconSurface.pipe(sync);
        this.add(positionModifier).add(initModifier).add(iconSurface);

        this.updateIcon = function (data) {
            if (this.zIndex == 0) return;
            var currentPosition = this.position.get();
            this.position.set([
                currentPosition[0] + data.delta[0],
                currentPosition[1] + data.delta[1]
            ]);
        }
    }

    module.exports = VizView;
});

A VizView is instantiated in MenuView as such:

        var vizView = new VizView({
            iconUrl: "path/to/iconUrl",
            title: "Viz Title"
        });

        var vizModifier = new StateModifier({
            transform: Transform.translate(0, yOffset, 0)
        });

        this.add(vizModifier).add(vizView);
pmont
  • 2,083
  • 22
  • 43
  • 1
    I'm also trying to figure out an ideal solution for what you're trying to achieve @pmont. I can't guarantee I'll come up with something, but if I do I'll definitely post it here. Many minds make light work aye? – Kraig Walker Aug 05 '14 at 09:10
  • @KraigWalker I appreciate your contributions. Teamwork FTW! We can [listen for HTML5 DOM events on Famous surfaces](http://stackoverflow.com/questions/24614367/droppable-in-famous-js). I'm having trouble getting Famo.us Draggable surfaces to fire off those events. The event listeners appear to be swallowing them. – pmont Aug 05 '14 at 15:49
  • For the time being, I'm setting aside the Famous event handlers and using HTML5 drag/drop events. They can be used on surfaces as described in the link above. Here's a [primer on HTML drag/drop](http://www.html5rocks.com/en/tutorials/dnd/basics/). This unfortunately means that I won't be able to leverage Famous's physics engine for transition effects. – pmont Aug 05 '14 at 18:10
  • Not sure of I'm right on this, but I was under the belief that the drag and drop API was for *files* - ie allow a user to drag a photo file from their desktop and upload it to Facebook. – Kraig Walker Aug 11 '14 at 10:26
  • HTML5 drag/drop was created with that in mind (filesList member points to that). However it can be used with any element, dragging from or dropping onto. – pmont Aug 11 '14 at 14:00

1 Answers1

1

A draggable Surface in Famo.us is not really a DOM draggable element although it can be setup to work in a browser using the mouse. I have not been able to get GenericSync and touch to work with this solution yet.

Reading the pitfalls on the Famo.us site, there are hints to drag and drop with surface draggables being an issue.

How do I find the absolute position of a Surface on the screen?

By design this is not possible. It is something the developer should not care about. For the time being, this means that interactions such as drag and drop are harder to implement, but this is intended and we are working on an elegant solution for these use-cases.

Although: When not using GenericSync, you can use the DOM draggable events with a Famo.us Surface as you stated in the comments and link to the John Traver solution.

But: This solution will not work on mobile touch devices using Famo.us at the time of this answer. Getting this to work with touch may prove to be more difficult as stated in the pitfalls. Let's hope this gets solved in versions following 0.3.5 or in MixedMode (WebGL and DOM)

define('main', function(require, exports, module) {
  var Engine = require('famous/core/Engine');
  var Surface = require('famous/core/Surface');
  var ImageSurface = require('famous/surfaces/ImageSurface');
  var Transform = require('famous/core/Transform');
  var Modifier = require('famous/core/Modifier');
  var StateModifier = require('famous/modifiers/StateModifier');
  var Draggable = require('famous/modifiers/Draggable');
  var TransitionableTransform = require('famous/transitions/TransitionableTransform');

  var mainContext = Engine.createContext();

  var transTransform = new TransitionableTransform();
  transTransform.set(Transform.translate(100, 0, 0));

  var captureSurface = new Surface({
    content: 'Drag to Here',
    size: [300, 300],
    properties: {
      textAlign: 'center',
      lineHeight: '300px',
      backgroundColor: 'rgba(255,255,0,0.4)',
      cursor: 'pointer'
    },
    attributes: {
      dropzone: 'copy file:image/png file:image/gif file:image/jpeg'
    }
  });
  captureSurface.on('dragenter', function(evt) {
    console.log('dragenter', evt);
    evt.preventDefault();
    return false;
  });

  captureSurface.on('dragleave', function(evt) {
    console.log('dragleave', evt);
    captureSurface.setProperties({
      border: 'none'
    });
    evt.preventDefault();
    return false;
  });

  captureSurface.on('dragover', function(evt) {
    console.log('dragover', evt);
    captureSurface.setProperties({
      border: '4px dashed black'
    });
    evt.preventDefault();
    return false;
  });

  captureSurface.on('drop', function(evt) {
    console.log('drop', evt);

    evt.preventDefault();
    evt.stopPropagation();

    captureSurface.setProperties({
      border: '4px solid red'
    });

    files = evt.dataTransfer.files;
    console.log(files);
  });
  mainContext.add(new Modifier({
    origin: [0.5, 0.5],
    align: [0.5, 0.5]
  })).add(captureSurface);

  var surface = new Surface({
    content: 'DOM Draggable',
    size: [300, 100],
    properties: {
      backgroundColor: 'rgba(255,0,0,0.4)',
      cursor: 'move'
    },
    attributes: {
      draggable: 'true'
    }
  });
  surface.on('drag', function(evt) {
    console.log('surface drag', evt)
  });
  var imageSurface = new ImageSurface({
    content: 'http://i.imgur.com/NGOwZeT.png',
    size: [100, 100],
    properties: {
      cursor: 'copy'
    },
    attributes: {
      draggable: 'true'
    }
  });
  imageSurface.on('drag', function(evt) {
    console.log('imageSurface drag', evt)
  });
  imageSurface.on('dragend', function(evt) {
    console.log('imageSurface dragend', evt)
  });

  var dragSurface = new Surface({
    content: 'Drag Me',
    size: [100, 100],
    properties: {
      backgroundColor: 'rgba(0,0,0,0.1)',
      cursor: 'move'
    },
    attributes: {
      draggable: 'true'
    }
  });
  dragSurface.on('dragstart', function(evt) {
    console.log('dragSurface dragstart', event, evt);
  });
  dragSurface.on('drag', function(evt) {
    console.log('dragSurface dragstart', event, evt);
  });

  var modifier = new Modifier({
    origin: [0, 0],
    align: [0, 0],
    transform: transTransform
  });
  var imageModifier = new Modifier({
    origin: [0, 0.5],
    align: [0, 0.5]
  });

  var draggable = new Draggable();

  draggable.subscribe(dragSurface);

  mainContext.add(modifier).add(surface);
  mainContext.add(imageModifier).add(imageSurface);
  mainContext.add(draggable).add(dragSurface);

  draggable.on('update', function(e) {
    console.log('draggable update', e, event);

    var pos = e.position;
    surface.setContent('Draggable Position is ' + pos);
    transTransform.set(Transform.translate(pos[0] + 100, pos[1], 0));
  });

  draggable.on('end', function(e) {
    var pos = e.position;
    surface.setContent('Draggable End Position is ' + pos);
    transTransform.set(Transform.translate(pos[0] + 100, pos[1], 0));
  });

  //draggable.deactivate();

});
require(['main']);
<script src="http://requirejs.org/docs/release/2.1.16/minified/require.js"></script>
<script src="http://code.famo.us/lib/requestAnimationFrame.js"></script>
<script src="http://code.famo.us/lib/classList.js"></script>
<script src="http://code.famo.us/lib/functionPrototypeBind.js"></script>

<link rel="stylesheet" type="text/css" href="http://code.famo.us/famous/0.3.5/famous.css" />

<script src="http://code.famo.us/famous/0.3.5/famous.min.js"></script>
talves
  • 13,993
  • 5
  • 40
  • 63