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: {