diff --git a/examples/hover/app.js b/examples/hover/app.js
new file mode 100644
index 0000000..c884aab
--- /dev/null
+++ b/examples/hover/app.js
@@ -0,0 +1,93 @@
+/** @jsx React.DOM */
+
+var React = require('react');
+var ReactDOM = require('react-dom');
+var ReactCanvas = require('react-canvas');
+
+var Surface = ReactCanvas.Surface;
+var Group = ReactCanvas.Group;
+var Image = ReactCanvas.Image;
+var Text = ReactCanvas.Text;
+var FontFace = ReactCanvas.FontFace;
+
+var App = React.createClass({
+ // Hover declaration
+ // =================
+ getInitialState: function () {
+ return {hovered: false};
+ },
+ handleMouseEnter: function () {
+ this.setState({hovered: true});
+ },
+ handleMouseLeave: function () {
+ this.setState({hovered: false});
+ },
+ componentDidMount: function () {
+ window.addEventListener('resize', this.handleResize, true);
+ },
+ render: function () {
+ var size = this.getSize();
+ return (
+
+
+
+
+ Hover me!
+
+
+
+
+ );
+ },
+
+ // Styles
+ // ======
+
+ getSize: function () {
+ return document.getElementById('main').getBoundingClientRect();
+ },
+
+ getPageStyle: function () {
+ var size = this.getSize();
+ return {
+ position: 'relative',
+ padding: 14,
+ width: size.width,
+ height: size.height,
+ backgroundColor: '#f7f7f7',
+ flexDirection: 'column'
+ };
+ },
+
+ getBoxStyle: function () {
+ return {
+ position: 'relative',
+ margin: 50,
+ flex: 1,
+ backgroundColor: this.state.hovered ? 'red' : '#eee'
+ };
+ },
+
+
+ getBoxTitleStyle: function () {
+ return {
+ fontFace: FontFace('Georgia'),
+ fontSize: 22,
+ lineHeight: 28,
+ height: 28,
+ marginTop: 10,
+ color: '#333',
+ textAlign: 'center'
+ };
+ },
+
+ // Events
+ // ======
+
+ handleResize: function () {
+ this.forceUpdate();
+ }
+
+});
+
+ReactDOM.render(, document.getElementById('main'));
diff --git a/examples/hover/index.html b/examples/hover/index.html
new file mode 100644
index 0000000..5cb10d8
--- /dev/null
+++ b/examples/hover/index.html
@@ -0,0 +1,13 @@
+
+
+
+
+
+ ReactCanvas: hover
+
+
+
+
+
+
+
diff --git a/lib/EventTypes.js b/lib/EventTypes.js
index f8bc1cc..9406d4f 100755
--- a/lib/EventTypes.js
+++ b/lib/EventTypes.js
@@ -9,5 +9,7 @@ module.exports = {
onTouchCancel: 'touchcancel',
onClick: 'click',
onContextMenu: 'contextmenu',
- onDoubleClick: 'dblclick'
+ onDoubleClick: 'dblclick',
+ onMouseEnter: 'mouseenter',
+ onMouseLeave: 'mouseleave'
};
diff --git a/lib/FrameUtils.js b/lib/FrameUtils.js
index c243552..f30466c 100644
--- a/lib/FrameUtils.js
+++ b/lib/FrameUtils.js
@@ -105,6 +105,17 @@ function union (frame, otherFrame) {
return make(x1, y1, x2 - x1, y2 - y1);
}
+
+/**
+ * Get unique ID. TODO: Rewrite with yeild on es7.
+ *
+ * @return {Number}
+ */
+var iterator = 0;
+function generateId () {
+ return iterator++;
+}
+
/**
* Determine if 2 frames intersect each other
*
@@ -126,6 +137,7 @@ module.exports = {
inset: inset,
intersection: intersection,
intersects: intersects,
- union: union
+ union: union,
+ generateId: generateId
};
diff --git a/lib/RenderLayer.js b/lib/RenderLayer.js
index 9d3cac4..83c2bc9 100644
--- a/lib/RenderLayer.js
+++ b/lib/RenderLayer.js
@@ -5,6 +5,7 @@ var DrawingUtils = require('./DrawingUtils');
var EventTypes = require('./EventTypes');
function RenderLayer () {
+ this.id = FrameUtils.generateId();
this.children = [];
this.frame = FrameUtils.zero();
}
diff --git a/lib/Surface.js b/lib/Surface.js
index 79beaf7..c9c457e 100755
--- a/lib/Surface.js
+++ b/lib/Surface.js
@@ -107,6 +107,9 @@ var Surface = React.createClass({
onTouchEnd: this.handleTouchEnd,
onTouchCancel: this.handleTouchEnd,
onClick: this.handleClick,
+ onMouseOver: this.handleMouseOver,
+ onMouseOut: this.handleMouseOut,
+ onMouseMove: this.handleMouseMove,
onContextMenu: this.handleContextMenu,
onDoubleClick: this.handleDoubleClick})
);
@@ -216,6 +219,53 @@ var Surface = React.createClass({
this.hitTest(e);
},
+ hitEvent: function (hitTarget, type) {
+ var type = hitTest.getHitHandle(type);
+ if (typeof hitTarget[type] === "function") {
+ hitTarget[type]();
+ }
+ },
+
+ handleMouseMove: function (e) {
+ var hitTarget = hitTest(e, this.node, this.refs.canvas);
+ var oldHoveredSet = this._hovered || {}
+ var self = this;
+ var newHoveredSet = {};
+ if (hitTarget) {
+ var id;
+ do {
+ id = hitTarget.id;
+ if (oldHoveredSet.hasOwnProperty(id)) {
+ // remove from mouseout
+ delete oldHoveredSet[id];
+ } else {
+ this.hitEvent(hitTarget, 'mouseenter');
+ }
+ newHoveredSet[id] = hitTarget;
+ hitTarget = hitTarget.parentLayer;
+ } while (hitTarget);
+ }
+ this._hovered = newHoveredSet;
+ Object.keys(oldHoveredSet).forEach(function (id) {
+ self.hitEvent(oldHoveredSet[id], 'mouseleave');
+ });
+ },
+
+ handleMouseOut: function (e) {
+ var self = this;
+ if (this._hovered) {
+ Object.keys(this._hovered).forEach(function (id) {
+ self.hitEvent(self._hovered[id], 'mouseleave');
+ });
+ }
+ this._hovered = {};
+ },
+
+ handleMouseOver: function (e) {
+ // just mousemove run mousemove event
+ this.handleMouseMove(e);
+ },
+
handleContextMenu: function (e) {
this.hitTest(e);
},
diff --git a/lib/hitTest.js b/lib/hitTest.js
index 053d779..38f8131 100755
--- a/lib/hitTest.js
+++ b/lib/hitTest.js
@@ -103,7 +103,7 @@ function getLayerAtPoint (root, type, point, tx, ty) {
}
// No child layer at the given point. Try the parent layer.
- if (!layer && root[hitHandle] && FrameUtils.intersects(hitFrame, point)) {
+ if (!layer && (root[hitHandle] || type === "mousemove") && FrameUtils.intersects(hitFrame, point)) {
layer = root;
}
diff --git a/webpack.config.js b/webpack.config.js
index c188a50..393fbd3 100644
--- a/webpack.config.js
+++ b/webpack.config.js
@@ -7,7 +7,8 @@ module.exports = {
'listview': ['./examples/listview/app.js'],
'timeline': ['./examples/timeline/app.js'],
'gradient': ['./examples/gradient/app.js'],
- 'css-layout': ['./examples/css-layout/app.js']
+ 'css-layout': ['./examples/css-layout/app.js'],
+ 'hover': ['./examples/hover/app.js']
},
output: {