Refactor to use a variable arrow size and an anchor point when computing geometry

This allows more user customisation and flexibility for positioning the arrow
This commit is contained in:
Jean Regisser
2015-07-30 14:12:11 +02:00
parent c097154278
commit c544bfc62b

View File

@@ -14,14 +14,18 @@ var flattenStyle = require('react-native/Libraries/StyleSheet/flattenStyle');
var Easing = require('react-native/Libraries/Animation/Animated/Easing');
var noop = () => {};
var SCREEN_HEIGHT = Dimensions.get('window').height;
var SCREEN_WIDTH = Dimensions.get('window').width;
var {height: SCREEN_HEIGHT, width: SCREEN_WIDTH} = Dimensions.get('window');
function Point(x, y) {
this.x = x;
this.y = y;
}
function Size(width, height) {
this.width = width;
this.height = height;
}
function Rect(x, y, width, height) {
this.x = x;
this.y = y;
@@ -37,8 +41,8 @@ var Popover = React.createClass({
getInitialState() {
return {
contentSize: {},
anchorPoint: {},
popoverOrigin: {},
arrowOrigin: {},
placement: 'auto',
isTransitioning: false,
transform: new Animated.Value(0),
@@ -50,18 +54,19 @@ var Popover = React.createClass({
return {
isVisible: false,
displayArea: new Rect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT),
arrowSize: new Size(5, 5),
placement: 'auto',
onClose: noop,
};
},
measureContent(x) {
var {width, height} = x.nativeEvent.layout;
var contentSize = {width: width, height: height};
var geom = this.computeGeometry({contentSize: contentSize});
var contentSize = {width, height};
var geom = this.computeGeometry({contentSize});
var awaitingShowHandler = this.state.awaitingShowHandler;
this.setState(Object.assign(geom,
{contentSize: contentSize, awaitingShowHandler: undefined}), () => {
this.setState(Object.assign(geom,
{contentSize, awaitingShowHandler: undefined}), () => {
// Once state is set, call the showHandler so it can access all the geometry
// from the state
awaitingShowHandler && awaitingShowHandler();
@@ -69,11 +74,12 @@ var Popover = React.createClass({
},
computeGeometry({contentSize, placement}) {
placement = placement || this.props.placement;
var options = {
displayArea: this.props.displayArea,
fromRect: this.props.fromRect,
contentSize: contentSize,
arrowSize: this.props.arrowSize,
contentSize,
}
switch (placement) {
@@ -89,57 +95,57 @@ var Popover = React.createClass({
return this.computeAutoGeometry(options);
}
},
computeTopGeometry({displayArea, fromRect, contentSize}) {
computeTopGeometry({displayArea, fromRect, contentSize, arrowSize}) {
var popoverOrigin = new Point(
Math.min(displayArea.x + displayArea.width - contentSize.width,
Math.max(displayArea.x, fromRect.x + (fromRect.width - contentSize.width) / 2)),
fromRect.y - contentSize.height - 5);
var arrowOrigin = new Point(fromRect.x - popoverOrigin.x + (fromRect.width - 10) / 2.0, contentSize.height);
Math.min(displayArea.x + displayArea.width - contentSize.width,
Math.max(displayArea.x, fromRect.x + (fromRect.width - contentSize.width) / 2)),
fromRect.y - contentSize.height - arrowSize.height);
var anchorPoint = new Point(fromRect.x + fromRect.width / 2.0, fromRect.y);
return {
popoverOrigin: popoverOrigin,
arrowOrigin: arrowOrigin,
popoverOrigin,
anchorPoint,
placement: 'top',
}
},
computeBottomGeometry({displayArea, fromRect, contentSize}) {
computeBottomGeometry({displayArea, fromRect, contentSize, arrowSize}) {
var popoverOrigin = new Point(
Math.min(displayArea.x + displayArea.width - contentSize.width,
Math.max(displayArea.x, fromRect.x + (fromRect.width - contentSize.width) / 2)),
fromRect.y + fromRect.height + 5);
var arrowOrigin = new Point(fromRect.x - popoverOrigin.x + (fromRect.width - 10) / 2.0, -10);
Math.min(displayArea.x + displayArea.width - contentSize.width,
Math.max(displayArea.x, fromRect.x + (fromRect.width - contentSize.width) / 2)),
fromRect.y + fromRect.height + arrowSize.height);
var anchorPoint = new Point(fromRect.x + fromRect.width / 2.0, fromRect.y + fromRect.height);
return {
popoverOrigin: popoverOrigin,
arrowOrigin: arrowOrigin,
popoverOrigin,
anchorPoint,
placement: 'bottom',
}
},
computeLeftGeometry({displayArea, fromRect, contentSize}) {
var popoverOrigin = new Point(fromRect.x - contentSize.width - 5,
Math.min(displayArea.y + displayArea.height - contentSize.height,
computeLeftGeometry({displayArea, fromRect, contentSize, arrowSize}) {
var popoverOrigin = new Point(fromRect.x - contentSize.width - arrowSize.width,
Math.min(displayArea.y + displayArea.height - contentSize.height,
Math.max(displayArea.y, fromRect.y + (fromRect.height - contentSize.height) / 2)));
var arrowOrigin = new Point(contentSize.width, fromRect.y - popoverOrigin.y + (fromRect.height - 10) / 2.0);
var anchorPoint = new Point(fromRect.x, fromRect.y + fromRect.height / 2.0);
return {
popoverOrigin: popoverOrigin,
arrowOrigin: arrowOrigin,
popoverOrigin,
anchorPoint,
placement: 'left',
}
},
computeRightGeometry({displayArea, fromRect, contentSize}) {
var popoverOrigin = new Point(fromRect.x + fromRect.width + 5,
Math.min(displayArea.y + displayArea.height - contentSize.height,
computeRightGeometry({displayArea, fromRect, contentSize, arrowSize}) {
var popoverOrigin = new Point(fromRect.x + fromRect.width + arrowSize.width,
Math.min(displayArea.y + displayArea.height - contentSize.height,
Math.max(displayArea.y, fromRect.y + (fromRect.height - contentSize.height) / 2)));
var arrowOrigin = new Point(-10, fromRect.y - popoverOrigin.y + (fromRect.height - 10) / 2.0);
var anchorPoint = new Point(fromRect.x + fromRect.width, fromRect.y + fromRect.height / 2.0);
return {
popoverOrigin: popoverOrigin,
arrowOrigin: arrowOrigin,
popoverOrigin,
anchorPoint,
placement: 'right',
}
},
computeAutoGeometry({displayArea, fromRect, contentSize}) {
computeAutoGeometry({displayArea, contentSize}) {
var placementsToTry = ['left', 'right', 'bottom', 'top'];
for (var i = 0; i < placementsToTry.length; i++) {
@@ -147,9 +153,9 @@ var Popover = React.createClass({
var geom = this.computeGeometry({contentSize: contentSize, placement: placement});
var {popoverOrigin} = geom;
if (popoverOrigin.x >= displayArea.x
if (popoverOrigin.x >= displayArea.x
&& popoverOrigin.x <= displayArea.x + displayArea.width - contentSize.width
&& popoverOrigin.y >= displayArea.y
&& popoverOrigin.y >= displayArea.y
&& popoverOrigin.y <= displayArea.y + displayArea.height - contentSize.height) {
break;
}
@@ -169,6 +175,27 @@ var Popover = React.createClass({
return { borderRightColor: color };
}
},
getArrowDynamicStyle() {
var {width, height} = this.props.arrowSize;
var {anchorPoint, popoverOrigin} = this.state;
return {
left: anchorPoint.x - popoverOrigin.x - width,
top: anchorPoint.y - popoverOrigin.y - height,
width: width * 2,
height: height * 2,
borderTopWidth: height,
borderRightWidth: width,
borderBottomWidth: height,
borderLeftWidth: width,
}
},
getTranslateOrigin() {
var {contentSize, popoverOrigin, anchorPoint} = this.state;
var popoverCenter = new Point(popoverOrigin.x + contentSize.width / 2,
popoverOrigin.y + contentSize.height / 2);
return new Point(anchorPoint.x - popoverCenter.x, anchorPoint.y - popoverCenter.y);
},
componentWillReceiveProps(nextProps:any) {
var willBeVisible = nextProps.isVisible;
var {
@@ -179,14 +206,6 @@ var Popover = React.createClass({
if (willBeVisible !== isVisible) {
var animDuration = 300;
var getTranslateOrigin = () => {
var {contentSize, popoverOrigin, arrowOrigin} = this.state;
var popoverCenter = new Point(popoverOrigin.x + contentSize.width / 2,
popoverOrigin.y + contentSize.height / 2);
var arrowTip = new Point(popoverOrigin.x + arrowOrigin.x + 5,
popoverOrigin.y + arrowOrigin.y + 5);
return new Point(arrowTip.x - popoverCenter.x, arrowTip.y - popoverCenter.y);
}
var config = {velocity: 3, bounciness: 18};
var defaultShowHandler = (t) => {
/*var easing = Transitions.Easings.easeOutBack;
@@ -196,14 +215,14 @@ var Popover = React.createClass({
t('popover.transform.translateY', {duration: animDuration, easing: easing, begin: translateOrigin.y, end: 0,});
t('popover.transform.scaleXY', {duration: animDuration, easing: easing, begin: 0, end: 1,});*/
var translateOrigin = getTranslateOrigin();
var translateOrigin = this.getTranslateOrigin();
this.state.translate.setValue(translateOrigin);
var commonConfig = {
duration: animDuration,
easing: Easing.out(Easing.back()),
}
Animated.parallel([
Animated.timing(this.state.fade, {
toValue: 1,
@@ -227,13 +246,13 @@ var Popover = React.createClass({
t('popover.transform.translateX', {duration: animDuration, easing: easing, end: translateOrigin.x,});
t('popover.transform.translateY', {duration: animDuration, easing: easing, end: translateOrigin.y,});
*/
var commonConfig = {
duration: animDuration,
easing: Easing.inOut(Easing.quad),
}
var translateOrigin = getTranslateOrigin();
var translateOrigin = this.getTranslateOrigin();
this.setState({isTransitioning: true});
@@ -274,9 +293,10 @@ var Popover = React.createClass({
return null;
}
var {popoverOrigin, arrowOrigin, placement} = this.state;
var {popoverOrigin, placement} = this.state;
var arrowColor = flattenStyle(styles.content).backgroundColor;
var arrowColorStyle = this.getArrowColorStyle(placement, arrowColor);
var arrowDynamicStyle = this.getArrowDynamicStyle();
var contentSizeAvailable = this.state.contentSize.width;
var backgroundAnimatedStyle = {
@@ -299,10 +319,7 @@ var Popover = React.createClass({
top: popoverOrigin.y,
left: popoverOrigin.x,
}, popoverAnimatedStyle /*, this.transitionStyles('popover')*/]}>
<View style={[styles.arrow, arrowColorStyle, {
top: arrowOrigin.y,
left: arrowOrigin.x,
}]}/>
<View style={[styles.arrow, arrowDynamicStyle, arrowColorStyle]}/>
<View ref='content' onLayout={this.measureContent} style={styles.content}>
{this.props.children}
</View>
@@ -335,7 +352,7 @@ var DefaultStyles = StyleSheet.create({
position: 'absolute',
backgroundColor: 'rgba(0,0,0,0.5)',
},
popover: {
popover: {
backgroundColor: 'transparent',
position: 'absolute',
shadowColor: 'black',
@@ -344,24 +361,17 @@ var DefaultStyles = StyleSheet.create({
shadowOpacity: 0.8,
},
content: {
//margin: 10,
borderRadius: 3,
padding: 6,
backgroundColor: '#fff',
},
arrow: {
position: 'absolute',
width: 10,
height: 10,
borderTopWidth: 5,
borderTopColor: 'transparent',
borderRightWidth: 5,
borderRightColor: 'transparent',
borderBottomWidth: 5,
borderBottomColor: 'transparent',
borderLeftWidth: 5,
borderLeftColor: 'transparent',
},
});
module.exports = Popover;
module.exports = Popover;