-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathNavButton.js
executable file
·196 lines (184 loc) · 6.73 KB
/
NavButton.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
/**
* The examples provided by Oculus are for non-commercial testing and
* evaluation purposes only.
*
* Oculus reserves all rights not expressly granted.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL
* OCULUS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
*/
'use strict';
import React from 'react';
import { Animated, Image, Text, View, VrButton, VrSoundEffects,asset } from 'react-360';
import LoadingSpinner from './LoadingSpinner';
const Easing = require('Easing');
/**
* NavButton is activated either by selecting or by prolonged hovering.
* On hover, a text label appears, and a fill-circle exapnds around the button.
* Once selected, the button disappears and a loading spinner takes its place.
*
* When using with CylinderLayer, set pixelsPerMeter to convert units, otherise
* set translateZ to specify distance between camera and button.
*/
class NavButton extends React.Component {
static defaultProps = {
delay: 2000,
height: 0.3,
innerWidth: 0.3,
isLoading: false,
outerWidth: 0.5,
onInput: null,
pixelsPerMeter: 1,
rotateY: 0,
scaleFactor: 1.3,
textLabel: 'go',
translateX: 0,
translateZ: 0,
};
constructor(props) {
super();
// innerWidth is image diameter, outerWidth is diameter of gaze-and-fill ring,
// so initial border is half the different between the two.
const PPM = props.pixelsPerMeter;
this._ringWidth = 0.025 * PPM;
this._initialBorderWidth =
(props.outerWidth * PPM - props.innerWidth * PPM) / 2 - this._ringWidth;
this.state = {
borderWidthAnim: new Animated.Value(this._initialBorderWidth),
hasFocus: false,
lastTimeoutId: 0,
};
}
componentWillUnmount() {
if (this.state.lastTimeoutId) {
clearTimeout(this.state.lastTimeoutId);
}
}
_startFill() {
Animated.timing(this.state.borderWidthAnim, {
toValue: this._ringWidth / 2,
easing: Easing.linear,
duration: this.props.delay,
}).start();
}
_removeFill() {
this.state.borderWidthAnim.stopAnimation();
this.state.borderWidthAnim.setValue(this._initialBorderWidth);
}
_selected() {
// Disable focus once button is selected.
this.setState({ hasFocus: false });
this.props.onInput();
}
render() {
// Set alpha channel to zero for 'no color' to make a transparent view.
const transparent = 'rgba(255, 255, 255, 0.0)';
const fillColor = 'rgba(255, 255, 255, 0.8)';
const PPM = this.props.pixelsPerMeter;
return (
<VrButton
style={{
// Use 'row' so label gets placed to right of the button.
flexDirection: 'row',
layoutOrigin: [0.5, 0.5],
position: 'absolute',
transform: [
{ rotateY: this.props.rotateY },
{ translateX: this.props.translateX },
{ translateZ: this.props.translateZ },
],
}}
ignoreLongClick={true}
onClick={() => this._selected()}
onEnter={() => {
if (!this.props.isLoading) {
this.setState({ hasFocus: true });
// Remember id, so we can remove this timeout if cusor exits.
const id = setTimeout(() => {
// Play click sound on gaze timeout. Audio was loaded by VrButton.
VrSoundEffects.play(this.props.onClickSound);
this._selected();
}, this.props.delay);
this.state.lastTimeoutId = id;
this._startFill();
}
}}
onExit={() => {
this.setState({ hasFocus: false });
clearTimeout(this.state.lastTimeoutId);
this.state.lastTimeoutId = 0;
this._removeFill();
}}
// onClickSound={this.props.onClickSound}
// onEnterSound={this.props.onEnterSound}
onExitSound={this.props.onExitSound}
onLongClickSound={this.props.onLongClickSound}>
<View
style={{
// Make ring, using rounded borders, which appears on hover.
alignItems: 'center',
backgroundColor: transparent,
borderColor: this.state.hasFocus ? 'white' : transparent,
borderRadius: this.props.outerWidth / 2 * PPM,
borderWidth: this._ringWidth,
height: this.props.outerWidth * PPM,
justifyContent: 'center',
width: this.props.outerWidth * PPM,
}}>
{!this.props.isLoading &&
<View>
<Animated.View
style={{
// This view has a border that appears on hover to fill the space
// between the ring and the image. Animation decreases the border
// width for a gaze-and-fill effect.
backgroundColor: transparent,
borderColor: this.state.hasFocus ? fillColor : transparent,
borderRadius: this.props.outerWidth / 2 * PPM,
borderWidth: this.state.borderWidthAnim,
height: this.props.outerWidth * PPM - this._ringWidth * 2,
position: 'absolute',
// Position directly on top of the above view.
transform: [
{ translateX: -this._initialBorderWidth },
{ translateY: this._initialBorderWidth },
],
width: this.props.outerWidth * PPM - this._ringWidth * 2,
}}
/>
<Image
style={{
height: this.props.innerWidth * PPM,
width: this.props.innerWidth * PPM,
}}
source={this.props.source}
/>
</View>}
{this.props.isLoading && <LoadingSpinner pixelsPerMeter={PPM} />}
</View>
{this.state.hasFocus &&
<Text
style={{
backgroundColor: 'black',
color: 'white',
fontSize: this.props.height * PPM * 0.7,
height: this.props.height * PPM,
marginLeft: 0.05 * PPM,
marginTop: (this.props.outerWidth - this.props.innerWidth) / 2 * PPM,
padding: 0.1 * this.props.pixelsPerMeter,
left: this.props.outerWidth * this.props.pixelsPerMeter + 0.05 * PPM,
textAlign: 'center',
textAlignVertical: 'auto',
}}>
{this.props.textLabel}
</Text>}
</VrButton>
);
}
}
module.exports = NavButton;