├── .babelrc ├── .gitignore ├── README.md ├── __tests__ └── client.js ├── app.json ├── archive └── textInpuOld.js ├── dist └── maps.js ├── examples └── index.vr.js ├── index.js ├── package-lock.json ├── package.json ├── rn-cli.config.js ├── server └── app.js ├── src ├── keyboard.js ├── keyboardButton.js ├── layout.js ├── scroll.js └── textInput.js ├── start.js ├── static_assets ├── chess-world.jpg ├── down.png └── up.png └── vr ├── build ├── client.bundle.js ├── client.bundle.js.meta └── index.bundle.js.meta ├── client.bundle.js ├── client.js ├── index.bundle.js ├── index.html └── static_assets └── chess-world.jpg /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "react-native" 4 | ] 5 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # react-vr-textinput 2 | 3 | > Text Input and Virtual Keyboard for React VR 4 | 5 | ## Team 6 | 7 | - Arseniy Kotov 8 | - Sim Kang 9 | - Rishi Raje 10 | 11 | ## Installation 12 | 13 | ``` 14 | npm install react-vr-textinput 15 | ``` 16 | 17 | 18 | ## Usage 19 | 20 | > ``` import TextInput from 'react-vr-textinput' ``` 21 | 22 | - Exposed Props 23 | 1. onSubmit() - provide a call back function that will accept the text from the text input component 24 | 2. rows - provide the number of lines you wish the text box to be 25 | 3. cols - provide the number of columns you wish the tex box to be 26 | 4. x - coordinate for positioning of all 3 elements (all related off the placement of the text input area) 27 | 5. y - coordinate for above 28 | 6. z - coordinate for above 29 | 7. textColor - color for the text inside the input field 30 | 8. backgroundColor - color for the background of the input field 31 | 9. keyboardColor - color for keys on keyboard 32 | 10. keyboardOnHover - color for the keys when the mouse or vr controller hover over. 33 | 11. rotateX - provide the amount of rotate needed on the x axis as a number 34 | 12. rotateY - same as #11 35 | 13. defaultInput - provide a default string to be displayed on the input 36 | 37 | If not specified, all props except onSubmit will use default values. 38 | - Click the TextBox Component to activate it. 39 | - When the user is finished, the keyboard will hide itself after the submit button has been pressed. 40 | 41 | 42 | ## Sample code 43 | 44 | The following example palces the textInput inside the View Component. Note the props that can be passed in. Beyond these, the text input box can be fully customized by directly accessinf the component source code. 45 | 46 | ```js 47 | import React from 'react'; 48 | import {View} from 'react-vr'; 49 | import TextInput from 'react-vr-textinput' 50 | 51 | export default class Example extends React.Component { 52 | 53 | constructor(props) { 54 | super(props); 55 | } 56 | 57 | submitHandler(string) { 58 | console.log('the text received by the submitHandler is ' + string); 59 | } 60 | 61 | render() { 62 | return ( 63 | 64 | 66 | 67 | ); 68 | } 69 | }; 70 | 71 | AppRegistry.registerComponent('Example', () => Example); 72 | 73 | ``` 74 | 75 | ## Requirements 76 | 77 | > React VR 78 | 79 | ## Current Issues 80 | 81 | - This module is fully functional. Our next goal is to refactor for speed and also further enhance customizability through providing users even more props. We are also working on adding auto-complete support for faster typing. This will be an optional component that users can select to enable with their text input box and virtual keyboard. Please feel free to ask for additional functionality and we will try to add features as soon as possible. 82 | 83 | ## Contributing 84 | 85 | - Fork the repository. 86 | - npm install 87 | - Create a pull request 88 | - Provide info on changes/updates on pull request 89 | 90 | ## Tips 91 | - Any additional styling options can be applied to the component. Remember that rotate can be used to help place the entire component anywhere in 3D space. 92 | - Scroll keys show after only if the number of typed in rows exceeds the number of rows specified for the text box 93 | - The cursor can be moved via forward and backward keys. 94 | -------------------------------------------------------------------------------- /__tests__/client.js: -------------------------------------------------------------------------------- 1 | import 'react-native'; 2 | import 'react-vr'; 3 | import React from 'react'; 4 | import renderer from 'react-test-renderer'; 5 | import TextInput from '../src/textInput.js'; 6 | 7 | // Note: test renderer must be required after react-native. 8 | 9 | it('renders correctly', () => { 10 | const tree = renderer.create(); 11 | }); 12 | 13 | describe('handleAllLetters', () => { 14 | it('should add one letter to output string', () => {}); 15 | it('should add multiple letters correctly', () => {}); 16 | it('should add symbols correctly', () => {}); 17 | it('should add character at correct cursor position', () => {}); 18 | }); 19 | describe('handleDelete', () => { 20 | it('should delete characters when cursor is at last position', () => {}); 21 | it('should delete characters when the cursor is located somewhere in the string', () => {}); 22 | }); 23 | describe('handleShift', () => { 24 | it('should on first press return an all upper case keyboard', () => {}); 25 | it('should on re-press give you an all lower case keyboard', () => {}); 26 | it('should not fire when pressed when keyboard is showing symbols', () => {}); 27 | }); 28 | describe('handleBack', () => { 29 | it('should on one press move the cursor one position back', () => {}); 30 | it('should not move the cursor past position 0', () => {}); 31 | }); 32 | describe('handleForward', () => { 33 | it('should on one press move the cursor one position forward', () => {}); 34 | it('should not move the cursor beyond the last position', () => {}); 35 | }); 36 | describe('handleDown', () => { 37 | it('should shows you the correct displayed text', () => {}); 38 | }); 39 | describe('handleUp', () => { 40 | it('should shows you the correct displayed text', () => {}); 41 | }); 42 | describe('paginate', () => { 43 | it('should return the right array when text < display area', () => {}); 44 | it('should return right array when text > display area', () => {}); 45 | }); 46 | describe('client', () => { 47 | it('should generate the specified height and width for the textbox', () => {}); 48 | it('should fall back to defaults if no specified height or width for the textbox', () => {}); 49 | it('should show keyboard only when the textbox is selected', () => {}); 50 | it('should call the submithandler callback after the text is submitted', () => {}); 51 | it('should only show column items in the text box if there is no cursor on the line', () => {}); 52 | it('should have the number of characters equal to the columns if cursor is not present', () => {}); 53 | }); 54 | -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-vr-textbox", 3 | "scripts": {}, 4 | "env": {}, 5 | "formation": {}, 6 | "addons": [], 7 | "buildpacks": [] 8 | } 9 | -------------------------------------------------------------------------------- /archive/textInpuOld.js: -------------------------------------------------------------------------------- 1 | /*import React, { Component } from 'react'; 2 | import { View, VrButton, StyleSheet, Text } from 'react-vr'; 3 | import Keyboard from './keyboard'; 4 | import Scroll from './scroll' 5 | 6 | class TextInput extends Component { 7 | constructor(props){ 8 | super(props); 9 | this.state = { 10 | cursorPosition: 0, 11 | textArrayCursorYes: '|', 12 | textArrayCursorNo: ' ', 13 | text: '', 14 | selected: false, 15 | rows: this.props.rows || 4, 16 | columns: this.props.cols || 50, 17 | submitHandler: this.props.onSubmit || null, 18 | showScroll: false, 19 | toggleCursor: true, 20 | x: -1, 21 | y: 0.2, 22 | z: -1.5, 23 | pages : 0, 24 | start : 0, 25 | end: (this.props.rows || 4) * (this.props.cols || 50), 26 | focus: false, 27 | counter: 0, 28 | opacity: 0 29 | } 30 | } 31 | 32 | focus() { 33 | this.setState({focus: true}); 34 | } 35 | generateText() { 36 | var string = ''; 37 | for(var i =0; i < this.state.textArrayCursorYes.length; i++) { 38 | if(this.state.textArrayCursorYes[i] !== '|') string += this.state.textArrayCursorYes[i]; 39 | } 40 | return string; 41 | } 42 | 43 | handleSpace() { 44 | var newArrYes = this.state.textArrayCursorYes.slice(0, this.state.cursorPosition) + ' ' + this.state.textArrayCursorYes.slice(this.state.cursorPosition); 45 | var newArrNo = this.state.textArrayCursorNo.slice(0, this.state.cursorPosition) + ' ' + this.state.textArrayCursorNo.slice(this.state.cursorPosition); 46 | this.setState({ 47 | textArrayCursorYes: newArrYes, 48 | textArrayCursorNo: newArrNo, 49 | cursorPosition: this.state.cursorPosition + 1, 50 | text: this.generateText() 51 | }, () => { 52 | this.handleCursorFollow.bind(this)(); 53 | }); 54 | } 55 | 56 | handleSubmit() { 57 | var string = JSON.parse(JSON.stringify(this.state.textArrayCursorYes)); 58 | string = string.substring(0, string.length-1); 59 | this.setState({cursorPosition: 0, 60 | textArrayCursorYes: '|', 61 | textArrayCursorNo: ' ', 62 | text: '', 63 | selected: false, 64 | rows: this.props.rows || 4, 65 | columns: this.props.cols || 50, 66 | submitHandler: this.props.onSubmit || null, 67 | showScroll: false, 68 | toggleCursor: true, 69 | x: -1, 70 | y: 0.2, 71 | z: -1.5, 72 | pages : 0, 73 | start : 0, 74 | end: (this.props.rows || 4) * (this.props.cols || 50), 75 | focus: false, 76 | opacity: 0 77 | }); 78 | 79 | this.props.onSubmit(string); 80 | } 81 | 82 | handleAllLetters(value) { 83 | var newArrYes = this.state.textArrayCursorYes.slice(0, this.state.cursorPosition) + value.trim() + this.state.textArrayCursorYes.slice(this.state.cursorPosition); 84 | var newArrNo = this.state.textArrayCursorNo.slice(0, this.state.cursorPosition) + value.trim() + this.state.textArrayCursorNo.slice(this.state.cursorPosition); 85 | this.setState({ 86 | textArrayCursorYes: newArrYes, 87 | textArrayCursorNo: newArrNo, 88 | cursorPosition: this.state.cursorPosition + 1, 89 | text: this.generateText() 90 | }, () => { 91 | this.handleCursorFollow.bind(this)(); 92 | }); 93 | } 94 | 95 | handleDelete() { 96 | var arr = this.state.textArrayCursorYes.slice(0, this.state.cursorPosition - 1) + this.state.textArrayCursorYes.slice(this.state.cursorPosition); 97 | var arr2 = this.state.textArrayCursorNo.slice(0, this.state.cursorPosition - 1) + this.state.textArrayCursorNo.slice(this.state.cursorPosition); 98 | 99 | this.setState({ 100 | textArrayCursorYes: arr, 101 | textArrayCursorNo: arr2, 102 | cursorPosition: this.state.cursorPosition - 1 103 | }, () => { 104 | if (this.paginate(this.state.textArrayCursorYes.slice(this.state.start, this.state.end)).length < this.state.columns * this.state.rows) { 105 | if ((this.state.cursorPosition - this.state.start + 1) % this.state.columns === 0) { 106 | var start = this.state.start - this.state.columns; 107 | var end = this.state.end - this.state.columns; 108 | if (start < 0) { 109 | start = 0; 110 | end = this.state.rows * this.state.columns; 111 | } 112 | this.setState({ 113 | start: start, 114 | end: end 115 | }); 116 | } 117 | } else { 118 | this.setState({ 119 | start: 0, 120 | end: (this.props.rows || 4) * (this.props.cols || 50) 121 | }) 122 | 123 | } 124 | }); 125 | 126 | } 127 | 128 | handleForward() { 129 | if (this.state.cursorPosition < this.state.columns * this.state.rows - 1) { 130 | var cp = this.state.cursorPosition; 131 | var s = this.state.textArrayCursorYes; 132 | var s2 = this.state.textArrayCursorNo; 133 | 134 | s = s.slice(0, cp) + s.slice(cp + 1, cp + 2) + s.slice(cp, cp + 1) + s.slice(cp + 2); 135 | s2 = s2.slice(0, cp) + s2.slice(cp + 1, cp + 2) + s2.slice(cp, cp + 1) + s2.slice(cp + 2); 136 | 137 | this.setState({ 138 | textArrayCursorYes: s, 139 | textArrayCursorNo: s2, 140 | cursorPosition: this.state.cursorPosition + 1 141 | }, () => { 142 | if (this.state.cursorPosition > this.state.rows * this.state.columns) { 143 | if ((this.state.cursorPosition - 1) % this.state.columns === 0) { 144 | this.setState({ 145 | start: this.state.start + this.state.columns, 146 | end: this.state.end + this.state.columns 147 | }); 148 | } 149 | } else { 150 | this.setState({ 151 | start: 0, 152 | end: (this.props.rows || 4) * (this.props.cols || 50) 153 | }) 154 | } 155 | }); 156 | } 157 | } 158 | 159 | handleBack() { 160 | 161 | if (this.state.cursorPosition !== 0) { 162 | var cp = this.state.cursorPosition; 163 | var s = this.state.textArrayCursorYes; 164 | var s2 = this.state.textArrayCursorNo; 165 | 166 | s = s.slice(0, cp-1) + s.slice(cp, cp+1) + s.slice(cp-1, cp) + s.slice(cp+1); 167 | s2 = s2.slice(0, cp - 1) + s2.slice(cp, cp + 1) + s2.slice(cp - 1, cp) + s2.slice(cp + 1); 168 | 169 | this.setState({ 170 | textArrayCursorYes : s, 171 | textArrayCursorNo: s2, 172 | cursorPosition: this.state.cursorPosition - 1 173 | }, () => { 174 | if (this.paginate(this.state.textArrayCursorYes.slice(this.state.start, this.state.end)).length < this.state.columns * this.state.rows) { 175 | if ((this.state.cursorPosition - this.state.start + 3) % this.state.columns === 0) { 176 | var start = this.state.start - this.state.columns; 177 | var end = this.state.end - this.state.columns; 178 | if (start < 0) { 179 | start = 0; 180 | end = this.state.rows * this.state.columns; 181 | } 182 | this.setState({ 183 | start: start, 184 | end: end 185 | }); 186 | } 187 | } else { 188 | this.setState({ 189 | start: 0, 190 | end: (this.props.rows || 4) * (this.props.cols || 50) 191 | }) 192 | 193 | } 194 | }); 195 | } 196 | } 197 | 198 | 199 | 200 | handleUp() { 201 | if(this.state.start!== 0) { 202 | if (this.state.pages !== 0) { 203 | var pages = this.state.pages - 1; 204 | var start = this.state.start - this.state.columns; 205 | var end = this.state.end - this.state.columns; 206 | this.setState({ 207 | pages: pages, 208 | start: start, 209 | end : end 210 | }) 211 | } 212 | } 213 | } 214 | 215 | handleDown() { 216 | if(this.state.end < this.state.textArrayCursorYes.length) { 217 | var pages = this.state.pages + 1; 218 | var start = this.state.start + this.state.columns; 219 | var end = this.state.end + this.state.columns; 220 | this.setState({ 221 | pages: pages, 222 | start: start, 223 | end: end 224 | }) 225 | } 226 | } 227 | 228 | 229 | paginate(s) { 230 | 231 | var columns = this.state.columns; 232 | var cols; 233 | var results = []; 234 | var temp = s.split(' '); 235 | var string = ''; 236 | for (var i = 0; i < temp.length; i++) { 237 | if (string.includes('|')) { 238 | cols = columns + 1; 239 | } else { 240 | cols = columns; 241 | } 242 | if (string.length === cols) { 243 | string = string.slice(0, string.length - 1); 244 | results.push(string); 245 | string = temp[i] + ' '; 246 | } 247 | 248 | else if (string.length + temp[i].length > cols) { 249 | string = string.slice(0, string.length - 1); 250 | results.push(string); 251 | string = temp[i] + ' '; 252 | } else { 253 | string += temp[i] + ' ' 254 | } 255 | } 256 | if (string !== '') results.push(string.slice(0, string.length - 1)); 257 | return results; 258 | } 259 | 260 | handleCursorFollow() { 261 | 262 | // if (this.state.cursorPosition > this.state.end) { 263 | if (this.paginate(this.state.textArrayCursorYes.slice(this.state.start, this.state.end)).length > this.state.rows) { 264 | var start = this.state.start; 265 | var end = this.state.end; 266 | var pages = this.state.pages; 267 | 268 | start = start + this.state.columns; 269 | end = end + this.state.columns; 270 | 271 | while (end <= this.state.cursorPosition) { 272 | pages = pages + 1; 273 | start = start + this.state.columns; 274 | end = end + this.state.columns; 275 | } 276 | if (this.state.textArrayCursorYes[start] === ' ') { 277 | start = start + 2; 278 | end = end + 1; 279 | } else { 280 | var temp = start; 281 | while(this.state.textArrayCursorYes[temp] !== ' ' && temp !== 0) { 282 | temp-- 283 | } 284 | if (temp === 0) { 285 | start = this.state.start + this.state.columns 286 | end = this.state.end + this.state.columns 287 | } else { 288 | start = temp + 1; 289 | end = start + (this.state.columns * this.state.rows); 290 | } 291 | } 292 | } else { 293 | var pages = this.state.pages; 294 | start = this.state.start; 295 | end = this.state.end; 296 | } 297 | 298 | this.setState({ 299 | start: start, 300 | end: end, 301 | pages: pages 302 | }) 303 | 304 | } 305 | 306 | render() { 307 | var arrayCursorYes = this.paginate(this.state.textArrayCursorYes); 308 | var arrayCursorNo = this.paginate(this.state.textArrayCursorNo); 309 | var displayString = ''; 310 | var displayArray = this.paginate(this.state.textArrayCursorYes.slice(this.state.start, this.state.end)); 311 | displayArray.forEach(function (element, index) { 312 | displayString += element + '\n'; 313 | }) 314 | 315 | displayString = displayString.slice(0, displayString.length - 1); 316 | 317 | return( 318 | 319 | 320 | 321 | 322 | {displayString} 323 | 324 | 325 | 326 | 327 | (this.state.rows * this.state.columns) + 1 ? 1 : this.state.opacity} 329 | handleUp={this.handleUp.bind(this)} 330 | handleDown={this.handleDown.bind(this)} 331 | /> 332 | 333 | {this.state.focus ? ( 334 | 335 | 343 | ) : ()} 344 | ); 345 | } 346 | } 347 | 348 | export default TextInput;*/ -------------------------------------------------------------------------------- /dist/maps.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamRocketThesis/react-vr-textinput/51445d1cb6c814720f55e39b3db580c276cfbdea/dist/maps.js -------------------------------------------------------------------------------- /examples/index.vr.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { AppRegistry, asset, Pano, Text, View} from 'react-vr'; 3 | import TextInput from '../src/textInput' 4 | 5 | export default class Example extends React.Component { 6 | 7 | constructor(props) { 8 | super(props); 9 | this.state = { 10 | text: '' 11 | } 12 | } 13 | 14 | submitHandler(string) { 15 | this.setState({ 16 | text: string 17 | }); 18 | } 19 | 20 | render() { 21 | const { text } = this.state; 22 | 23 | return ( 24 | 25 | 26 | 30 | {text ? `The Submit Handler received ${text}` : ''} 31 | 32 | ); 33 | } 34 | }; 35 | 36 | AppRegistry.registerComponent('Example', () => Example); 37 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./src/textInput'); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-vr-textinput", 3 | "version": "1.3.8", 4 | "private": false, 5 | "author": "Arseniy Kotov, Rishi Raje, Sim Kang", 6 | "keywords": [ 7 | "react", 8 | "vr", 9 | "text input" 10 | ], 11 | "repository": { 12 | "type": "git", 13 | "url": "https://github.com/TeamRocketThesis/react-vr-textinput" 14 | }, 15 | "scripts": { 16 | "start": "node start.js", 17 | "bundle": "node node_modules/react-vr/scripts/bundle.js", 18 | "open": "node -e \"require('xopen')('http://localhost:8081/vr/')\"", 19 | "devtools": "react-devtools", 20 | "test": "jest" 21 | }, 22 | "dependencies": { 23 | "chai": "^4.0.2", 24 | "express": "^4.15.3", 25 | "mocha": "^3.4.2", 26 | "nodemon": "^1.11.0", 27 | "ovrui": "^1.1.0", 28 | "path": "^0.12.7", 29 | "prop-types": "15.5.10", 30 | "react": "~15.4.1", 31 | "react-native": "~0.42.0", 32 | "react-vr": "^1.1.0", 33 | "react-vr-web": "^1.1.0", 34 | "shelljs": "^0.7.7", 35 | "three": "^0.80.1" 36 | }, 37 | "devDependencies": { 38 | "babel-jest": "^19.0.0", 39 | "babel-preset-react-native": "^1.9.1", 40 | "chai": "^4.0.2", 41 | "eslint": "^4.19.1", 42 | "eslint-config-airbnb": "^17.0.0", 43 | "eslint-plugin-import": "^2.13.0", 44 | "eslint-plugin-jsx-a11y": "^6.1.1", 45 | "eslint-plugin-react": "^7.10.0", 46 | "jest": "^19.0.2", 47 | "mocha": "^3.4.2", 48 | "react-devtools": "^2.1.3", 49 | "react-test-renderer": "~15.4.1", 50 | "xopen": "1.0.0" 51 | }, 52 | "jest": { 53 | "preset": "react-vr" 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /rn-cli.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const blacklist = require('./node_modules/react-native/packager/blacklist'); 3 | 4 | const config = { 5 | getProjectRoots() { 6 | return getRoots(); 7 | }, 8 | 9 | getBlacklistRE() { 10 | return blacklist([]); 11 | }, 12 | 13 | getAssetExts() { 14 | return ['obj', 'mtl']; 15 | }, 16 | 17 | getPlatforms() { 18 | return ['vr']; 19 | }, 20 | 21 | getProvidesModuleNodeModules() { 22 | return ['react-native', 'react-vr']; 23 | }, 24 | }; 25 | 26 | function getRoots() { 27 | const root = process.env.REACT_NATIVE_APP_ROOT; 28 | if (root) { 29 | return [path.resolve(root)]; 30 | } 31 | return [path.resolve(__dirname)]; 32 | } 33 | 34 | module.exports = config; 35 | -------------------------------------------------------------------------------- /server/app.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const path = require('path'); 3 | 4 | const app = express(); 5 | app.set('port', process.env.PORT || 9000); 6 | 7 | app.use(express.static(path.join(__dirname, '../vr/'))); 8 | 9 | if (!module.parent) { 10 | app.listen(app.get('port')); 11 | console.log(`Server listening on port ${app.get('port')}`); 12 | } 13 | -------------------------------------------------------------------------------- /src/keyboard.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { 3 | View, VrButton, StyleSheet, Text, 4 | } from 'react-vr'; 5 | import KeyboardButton from './keyboardButton'; 6 | 7 | import layout from './layout'; 8 | 9 | const styles = StyleSheet.create({ 10 | container: {}, 11 | numbers: {}, 12 | row1: {}, 13 | row2: {}, 14 | row3: {}, 15 | bottom: {}, 16 | row: { 17 | flexDirection: 'row', 18 | }, 19 | }); 20 | 21 | class Keyboard extends Component { 22 | constructor(props) { 23 | super(props); 24 | this.state = { 25 | cursorPosition: 0, 26 | textString: '', 27 | isShiftSelected: false, 28 | isSymbolSelected: false, 29 | }; 30 | } 31 | 32 | handleAllValues(value) { 33 | this.props.handleAllLetters(value); 34 | } 35 | 36 | handleDelete() { 37 | this.props.handleDelete(); 38 | } 39 | 40 | handleShift() { 41 | this.setState({ 42 | isShiftSelected: !this.state.isShiftSelected, 43 | }); 44 | } 45 | 46 | handleSymbolSelector() { 47 | this.setState({ 48 | isSymbolSelected: !this.state.isSymbolSelected, 49 | isShiftSelected: false, 50 | }); 51 | } 52 | 53 | handleBack() { 54 | this.props.handleBack(); 55 | } 56 | 57 | handleForward() { 58 | this.props.handleForward(); 59 | } 60 | 61 | handleSpacebar() { 62 | this.props.handleSpace(); 63 | } 64 | 65 | handleSubmit() { 66 | this.props.handleSubmit(); 67 | } 68 | 69 | getLayout() { 70 | if (this.state.isSymbolSelected) return layout.symbol.layout; 71 | 72 | if (!this.state.isShiftSelected) return layout.alphabet.layout; 73 | 74 | return layout.alphabet.layout.map(keyRow => keyRow.map(key => key.toUpperCase())); 75 | } 76 | 77 | render() { 78 | const layoutArray = this.getLayout(); 79 | numberArray = ['1', '2', '3', '4', '5', '6', '7', '8', '9', '0']; 80 | return ( 81 | 82 | 83 | {numberArray.map(number => ( 84 | 93 | ))} 94 | 101 | 102 | 103 | {layoutArray[0].map(value => ( 104 | 112 | ))} 113 | 114 | 115 | {layoutArray[1].map(value => ( 116 | 124 | ))} 125 | 126 | 127 | 134 | {layoutArray[2].map(value => ( 135 | 143 | ))} 144 | 155 | 156 | 157 | 164 | '} 168 | clickHandler={this.handleForward.bind(this)} 169 | isDisabled={false} 170 | /> 171 | 179 | 186 | 187 | 188 | ); 189 | } 190 | } 191 | 192 | export default Keyboard; 193 | -------------------------------------------------------------------------------- /src/keyboardButton.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { 3 | View, VrButton, StyleSheet, Text, Animated, 4 | } from 'react-vr'; 5 | 6 | const styles = StyleSheet.create({ 7 | text: { 8 | fontSize: 0.04, 9 | textAlign: 'center', 10 | color: '#ffffff', 11 | opacity: 3, 12 | fontFamily: 'HelveticaNeue-Light', 13 | fontWeight: 'normal', 14 | }, 15 | button: { 16 | height: 0.15, 17 | padding: 0.05, 18 | borderWidth: 0.005, 19 | flex: 1, 20 | alignItems: 'center', 21 | }, 22 | }); 23 | class KeyboardButton extends Component { 24 | constructor(props) { 25 | super(props); 26 | this.state = { 27 | backgroundColor: this.props.keyboardColor || '#0d0d0d', 28 | opacity: 0.5, 29 | keyboardOnHover: this.props.keyboardOnHover, 30 | keyboardColor: this.props.keyboardColor, 31 | }; 32 | } 33 | 34 | handleTheClick() { 35 | this.setState( 36 | { backgroundColor: 'white' }, 37 | (() => { 38 | setTimeout((() => this.test.bind(this))(), 1); 39 | })(), 40 | ); 41 | if (this.props.isDisabled === false) { 42 | this.props.clickHandler(this.props.value); 43 | } 44 | } 45 | 46 | test() { 47 | this.setState({ backgroundColor: this.state.keyboardColor || '#0d0d0d' }); 48 | } 49 | 50 | render() { 51 | return ( 52 | this.setState({ backgroundColor: this.state.keyboardOnHover || 'green' })} 60 | onExit={() => this.setState({ backgroundColor: this.state.keyboardColor || '#0d0d0d' })} 61 | > 62 | 63 | {this.props.value} 64 | 65 | 66 | ); 67 | } 68 | } 69 | 70 | export default KeyboardButton; 71 | -------------------------------------------------------------------------------- /src/layout.js: -------------------------------------------------------------------------------- 1 | export default { 2 | symbol: { 3 | displayValue: '.?!&', 4 | layout: [ 5 | ['=', '+', '%', ' *', '[', ']', '{', '}', '<', '>'], 6 | ['@', ':', ';', '_', '-', '#', '(', ')', '/', '&'], 7 | ['.', ',', '?', '!', '\\', ' "', '$'], 8 | ], 9 | }, 10 | alphabet: { 11 | displayValue: 'Abc', 12 | layout: [ 13 | ['q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p'], 14 | ['a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l'], 15 | ['z', 'x', 'c', 'v', 'b', 'n', 'm'], 16 | ], 17 | }, 18 | }; 19 | -------------------------------------------------------------------------------- /src/scroll.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { 3 | View, VrButton, StyleSheet, Text, Image, 4 | } from 'react-vr'; 5 | 6 | class Scroll extends Component { 7 | constructor(props) { 8 | super(props); 9 | this.state = { 10 | backgroundColor1: 'rgba(0,0,0,0.5)', 11 | backgroundColor2: 'rgba(0,0,0,0.5)', 12 | }; 13 | } 14 | 15 | componentWillMount() { 16 | this.props.coordx; 17 | this.props.coordy; 18 | this.props.coordx; 19 | } 20 | 21 | flash() { 22 | this.setState( 23 | { backgroundColor1: 'green' }, 24 | (() => { 25 | setTimeout((() => this.flash1Follow.bind(this))(), 1); 26 | })(), 27 | ); 28 | } 29 | 30 | flash1Follow() { 31 | this.setState({ backgroundColor1: 'rgba(0,0,0,0.5)' }); 32 | } 33 | 34 | flash2() { 35 | this.setState( 36 | { backgroundColor2: 'green' }, 37 | (() => { 38 | setTimeout((() => this.flash2Follow.bind(this))(), 1); 39 | })(), 40 | ); 41 | } 42 | 43 | flash2Follow() { 44 | this.setState({ backgroundColor2: 'rgba(0,0,0,0.5)' }); 45 | } 46 | 47 | handleClick1() { 48 | this.flash(); 49 | this.props.handleUp(); 50 | } 51 | 52 | handleClick2() { 53 | this.flash2(); 54 | this.props.handleDown(); 55 | } 56 | 57 | render() { 58 | return ( 59 | 60 | 69 | 73 | 74 | 83 | 87 | 88 | 89 | ); 90 | } 91 | } 92 | 93 | export default Scroll; 94 | -------------------------------------------------------------------------------- /src/textInput.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { 4 | View, VrButton, StyleSheet, Text, 5 | } from 'react-vr'; 6 | import Keyboard from './keyboard'; 7 | import Scroll from './scroll'; 8 | 9 | class TextInput extends Component { 10 | constructor(props) { 11 | super(props); 12 | this.state = { 13 | start: 0, 14 | end: this.props.rows - 1, 15 | displayArray: [`${props.defaultInput}|`], 16 | text: '', 17 | rows: this.props.rows || 4, 18 | columns: this.props.cols || 50, 19 | submitHandler: this.props.onSubmit || null, 20 | showScroll: false, 21 | toggleCursor: true, 22 | x: this.props.x || -1, 23 | y: this.props.y || 0.2, 24 | z: this.props.z || -1.5, 25 | pages: 0, 26 | focus: false, 27 | counter: 0, 28 | opacity: 0, 29 | textColor: this.props.textColor || 'white', 30 | backgroundColor: this.props.backgroundColor || 'grey', 31 | }; 32 | } 33 | 34 | componentWillReceiveProps(nextProps) { 35 | this.setState({ 36 | displayArray: [`${nextProps.defaultInput}|`], 37 | }); 38 | } 39 | 40 | calculateAdd(s) { 41 | let index; 42 | const results = []; 43 | for (let i = s.length - 1; i >= 0; i--) { 44 | if (s[i] === ' ') { 45 | index = i; 46 | if (s.slice(0, index + 1).length > this.state.columns + 1) { 47 | // 11 = cols + 1 48 | continue; 49 | } else { 50 | index = i; 51 | break; 52 | } 53 | } 54 | } 55 | if (index) { 56 | results[0] = s.slice(0, index + 1); 57 | results[1] = s.slice(index + 1); 58 | } else { 59 | results[0] = `${s.slice(0, s.length - 2)}-`; 60 | results[1] = s.slice(s.length - 2); 61 | } 62 | return results; 63 | } 64 | 65 | // ------------ 66 | 67 | calculateDelete(s) { 68 | let index; 69 | const results = []; 70 | 71 | for (let i = 0; i < s.length; i++) { 72 | if (s[i] === ' ') { 73 | index = i; 74 | break; 75 | } 76 | } 77 | 78 | if (index) { 79 | return [s.slice(0, index + 1), s.slice(index + 1)]; 80 | } 81 | return [s, null]; 82 | } 83 | 84 | // ------------- 85 | 86 | findPosition(start, end) { 87 | let index; 88 | // find the string that has the cursor 89 | const arr = this.state.displayArray; 90 | for (var i = 0; i < arr.length; i++) { 91 | if (arr[i]) { 92 | if (arr[i].includes('|')) { 93 | index = i; 94 | break; 95 | } 96 | } 97 | } 98 | 99 | // check if the string with the cursor is in the window of start and end, if not move them in the correct direction till the string with the cursor shows up 100 | 101 | if (index > end) { 102 | var found = false; 103 | while (!found) { 104 | start++; 105 | end++; 106 | 107 | for (i = start; i <= end; i++) { 108 | if (arr[i].includes('|')) { 109 | found = true; 110 | break; 111 | } 112 | } 113 | } 114 | } else if (index < start) { 115 | found = false; 116 | while (!found) { 117 | start--; 118 | end--; 119 | 120 | for (i = start; i <= end; i++) { 121 | if (arr[i].includes('|')) { 122 | found = true; 123 | break; 124 | } 125 | } 126 | } 127 | } 128 | 129 | const results = []; 130 | results[0] = index; 131 | results[1] = start; 132 | results[2] = end; 133 | 134 | return results; 135 | } 136 | 137 | // --------------- 138 | 139 | focus() { 140 | this.setState({ focus: true }); 141 | } 142 | 143 | // --------------- 144 | 145 | handleAllLetters(val) { 146 | if (val === ' ') { 147 | val = ' '; 148 | } 149 | let start = this.state.start; 150 | let end = this.state.end; 151 | const cols = this.state.columns; 152 | const arr = this.state.displayArray; 153 | 154 | let index; 155 | 156 | // get the current cursor position string, and move start and end accordingly 157 | 158 | let tempArray = this.findPosition(start, end); 159 | 160 | console.log('item ', tempArray); 161 | 162 | index = tempArray[0]; 163 | start = tempArray[1]; 164 | end = tempArray[2]; 165 | 166 | // add the current character at position CP 167 | 168 | const cp = arr[index].indexOf('|'); 169 | arr[index] = arr[index].slice(0, cp) + val + arr[index].slice(cp); 170 | console.log(arr); 171 | 172 | // if the current string has exceeded its length after adding a character ... 173 | 174 | if (arr[index].length > cols + 1) { 175 | // in a loop pass the values of the array to the calculate function to get back properly trimmed strings 176 | let done = false; 177 | while (!done) { 178 | // get back an array called list which will have the two parts that the string is split up in 179 | const list = this.calculateAdd(arr[index]); 180 | // add the first item of list to the array 181 | 182 | arr[index] = list[0]; 183 | index++; 184 | 185 | // if there is a second item in list then append the next entry in the array to it and repeat the calculate function 186 | if (list[1]) { 187 | if (arr[index]) { 188 | var s = list[1] + arr[index]; 189 | console.log('s ', s); 190 | } else { 191 | s = list[1]; 192 | console.log('s in else ', s); 193 | } 194 | 195 | arr.splice(index, 0, s); 196 | tempArray = this.findPosition(start, end); 197 | index = tempArray[0]; 198 | start = tempArray[1]; 199 | end = tempArray[2]; 200 | 201 | if (s.length <= cols + 1) { 202 | done = true; 203 | } 204 | console.log(`done is ${done} for ${s}`); 205 | } 206 | } 207 | } 208 | 209 | this.setState( 210 | { 211 | displayArray: arr, 212 | start, 213 | end, 214 | }, 215 | function () { 216 | console.log('new value of start ', this.state.start); 217 | console.log('new value of end ', this.state.end); 218 | }, 219 | ); 220 | } 221 | 222 | // ------------ 223 | 224 | handleDelete() { 225 | console.log('this.state.displayArray'); 226 | if (this.state.displayArray[0][0] !== '|') { 227 | let arr = this.state.displayArray; 228 | let start = this.state.start; 229 | let end = this.state.end; 230 | const cols = this.state.columns; 231 | let tempArray = this.findPosition(start, end); 232 | // console.log(tempArray); 233 | let index = tempArray[0]; 234 | start = tempArray[1]; 235 | end = tempArray[2]; 236 | 237 | // find the appropriate string where the cursor is and move start and end accordingly 238 | // find the CP 239 | let cp = arr[index].indexOf('|'); 240 | 241 | // if cp is the first character of the string, then move the cursor to the previous string 242 | if (cp === 0) { 243 | arr[index] = arr[index].slice(1); 244 | arr[index - 1] = `${arr[index - 1]}|`; 245 | cp = arr[index - 1].length - 1; 246 | index--; 247 | // see if the display window needs to be moved 248 | tempArray = this.findPosition(start, end); 249 | index = tempArray[0]; 250 | start = tempArray[1]; 251 | end = tempArray[2]; 252 | // console.log(tempArray); 253 | } 254 | // delete the character at cp-1 255 | arr[index] = arr[index].slice(0, cp - 1) + arr[index].slice(cp); 256 | console.log(arr); 257 | // if the string with the cursor is lesser in length than cols + 1 then find the next string, append it etc. till all strings have moved correctly ... 258 | 259 | if (arr[index].length < cols + 1) { 260 | let done = false; 261 | while (!done) { 262 | // if the cursor is in the last string of the array, then there is nothing to do 263 | console.log('index, arr.length -1 ', index, arr.length - 1); 264 | if (!arr[index + 1]) { 265 | done = true; 266 | tempArray = this.findPosition(start, end); 267 | index = tempArray[0]; 268 | start = tempArray[1]; 269 | end = tempArray[2]; 270 | // find the new window 271 | break; 272 | } 273 | 274 | // find the sub-word till after the first space in the string at index +1 and add it to string at index 275 | // console.log('next string ', arr[index + 1]); 276 | const list = this.calculateDelete(arr[index + 1]); 277 | // console.log('calculated list ', list); 278 | // if the length of this new string is = cols + 1 then index++ and find the new window - might not need this 279 | 280 | // if the length of this new string is > cols + 1 then index++ and find the new window 281 | if ((arr[index] + list[0]).length > cols + 1) { 282 | index++; 283 | } else { 284 | arr[index] = arr[index] + list[0]; 285 | arr[index + 1] = list[1]; 286 | } 287 | 288 | // console.log('arr in last line of while loop ', arr); 289 | // if the length of this new string is < cols + 1, then arr[index] becomes this new string. arr[index + 1]; arr[index + 1] becomes the truncated part of the string 290 | } 291 | } 292 | 293 | if (arr[arr.length - 1] === null) { 294 | arr = arr.slice(0, arr.length - 1); 295 | } 296 | 297 | this.setState({ 298 | displayArray: arr, 299 | start, 300 | end, 301 | }); 302 | } 303 | } 304 | 305 | // ------ 306 | 307 | handleBack() { 308 | if (this.state.displayArray[0][0] !== '|') { 309 | const arr = this.state.displayArray; 310 | let start = this.state.start; 311 | let end = this.state.end; 312 | const cols = this.state.columns; 313 | let index; 314 | 315 | let tempArray = this.findPosition(start, end); 316 | 317 | index = tempArray[0]; 318 | start = tempArray[1]; 319 | end = tempArray[2]; 320 | 321 | const cp = arr[index].indexOf('|'); 322 | 323 | if (cp === 0) { 324 | console.log('cp = 0 in handleBack'); 325 | arr[index] = arr[index].slice(1); 326 | arr[index - 1] = `${arr[index - 1]}|`; 327 | tempArray = this.findPosition(start, end); 328 | index = tempArray[0]; 329 | start = tempArray[1]; 330 | end = tempArray[2]; 331 | } else { 332 | arr[index] = `${arr[index].slice(0, cp - 1)}|${arr[index][cp - 1]}${arr[index].slice( 333 | cp + 1, 334 | )}`; 335 | } 336 | 337 | this.setState({ 338 | displayArray: arr, 339 | start, 340 | end, 341 | }); 342 | } 343 | } 344 | 345 | // ------ 346 | 347 | handleForward() { 348 | if ( 349 | this.state.displayArray[this.state.displayArray.length - 1][ 350 | this.state.displayArray[this.state.displayArray.length - 1].length - 1 351 | ] !== '|' 352 | && this.state.displayArray[this.state.end + 1] !== '' 353 | ) { 354 | const arr = this.state.displayArray; 355 | let start = this.state.start; 356 | let end = this.state.end; 357 | const cols = this.state.columns; 358 | let index; 359 | 360 | let tempArray = this.findPosition(start, end); 361 | 362 | index = tempArray[0]; 363 | start = tempArray[1]; 364 | end = tempArray[2]; 365 | 366 | const cp = arr[index].indexOf('|'); 367 | 368 | if (cp === arr[index].length - 1) { 369 | console.log('cp = length - 1 in handleForward'); 370 | arr[index] = arr[index].slice(0, arr[index].length - 1); 371 | arr[index + 1] = `|${arr[index + 1]}`; 372 | console.log('array[index] ', arr[index]); 373 | tempArray = this.findPosition(start, end); 374 | index = tempArray[0]; 375 | start = tempArray[1]; 376 | end = tempArray[2]; 377 | } else { 378 | arr[index] = `${arr[index].slice(0, cp) + arr[index][cp + 1]}|${arr[index].slice(cp + 2)}`; 379 | } 380 | 381 | this.setState({ 382 | displayArray: arr, 383 | start, 384 | end, 385 | }); 386 | } 387 | } 388 | 389 | // ------ 390 | 391 | handleSubmit() { 392 | const submitArray = this.state.displayArray; 393 | for (let i = 0; i < submitArray.length; i++) { 394 | submitArray[i] = submitArray[i] 395 | .split('') 396 | .filter(element => element !== '|') 397 | .join(''); 398 | } 399 | this.setState({ 400 | start: 0, 401 | end: this.props.rows - 1, 402 | displayArray: ['|'], 403 | text: '', 404 | submitHandler: this.props.onSubmit || null, 405 | showScroll: false, 406 | toggleCursor: true, 407 | x: -1, 408 | y: 0.2, 409 | z: -1.5, 410 | pages: 0, 411 | focus: false, 412 | counter: 0, 413 | opacity: 0, 414 | }); 415 | this.props.onSubmit(submitArray.join('')); 416 | } 417 | 418 | // ------ 419 | 420 | handleUp() { 421 | if (this.state.start !== 0) { 422 | this.setState({ 423 | start: this.state.start - 1, 424 | end: this.state.end - 1, 425 | }); 426 | } 427 | } 428 | 429 | // ------ 430 | 431 | handleDown() { 432 | if (this.state.end !== this.state.displayArray.length - 1) { 433 | this.setState({ 434 | start: this.state.start + 1, 435 | end: this.state.end + 1, 436 | }); 437 | } 438 | } 439 | 440 | // ------ 441 | 442 | showScroll() { 443 | let total = 0; 444 | for (let i = 0; i < this.state.displayArray.length; i++) { 445 | if (this.state.displayArray[i] != '') total++; 446 | } 447 | 448 | return total > this.state.rows ? 1 : this.state.opacity; 449 | } 450 | 451 | render() { 452 | const arr = this.state.displayArray; 453 | const start = this.state.start; 454 | const end = this.state.end; 455 | let displayString = ''; 456 | 457 | for (let i = start; i <= end; i++) { 458 | if (arr[i]) { 459 | displayString += `${arr[i]}\n`; 460 | } 461 | } 462 | 463 | displayString = displayString.slice(0, displayString.length - 1); 464 | 465 | return ( 466 | 471 | 472 | 473 | 486 | {displayString} 487 | 488 | 489 | 490 | 495 | 500 | 501 | {this.state.focus ? ( 502 | 510 | 519 | 520 | ) : ( 521 | 522 | )} 523 | 524 | ); 525 | } 526 | } 527 | 528 | TextInput.propTypes = { 529 | defaultInput: PropTypes.string, 530 | }; 531 | 532 | TextInput.defaultProps = { 533 | defaultInput: '', 534 | }; 535 | 536 | export default TextInput; 537 | -------------------------------------------------------------------------------- /start.js: -------------------------------------------------------------------------------- 1 | const shell = require('shelljs'); 2 | 3 | shell.exec('node -e "console.log(\'open browser at http://localhost:8081/vr/\\n\\n\');"', { 4 | async: true, 5 | }); 6 | shell.exec('nodemon server/app.js', { async: true }); 7 | shell.exec('node node_modules/react-native/local-cli/cli.js start', { async: true }); 8 | -------------------------------------------------------------------------------- /static_assets/chess-world.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamRocketThesis/react-vr-textinput/51445d1cb6c814720f55e39b3db580c276cfbdea/static_assets/chess-world.jpg -------------------------------------------------------------------------------- /static_assets/down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamRocketThesis/react-vr-textinput/51445d1cb6c814720f55e39b3db580c276cfbdea/static_assets/down.png -------------------------------------------------------------------------------- /static_assets/up.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamRocketThesis/react-vr-textinput/51445d1cb6c814720f55e39b3db580c276cfbdea/static_assets/up.png -------------------------------------------------------------------------------- /vr/build/client.bundle.js.meta: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamRocketThesis/react-vr-textinput/51445d1cb6c814720f55e39b3db580c276cfbdea/vr/build/client.bundle.js.meta -------------------------------------------------------------------------------- /vr/build/index.bundle.js.meta: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamRocketThesis/react-vr-textinput/51445d1cb6c814720f55e39b3db580c276cfbdea/vr/build/index.bundle.js.meta -------------------------------------------------------------------------------- /vr/client.js: -------------------------------------------------------------------------------- 1 | // Auto-generated content. 2 | // This file contains the boilerplate to set up your React app. 3 | // If you want to modify your application, start in "index.vr.js" 4 | 5 | // Auto-generated content. 6 | import { VRInstance } from 'react-vr-web'; 7 | 8 | function init(bundle, parent, options) { 9 | const vr = new VRInstance(bundle, 'Example', parent, { 10 | // Add custom options here 11 | enableHotReload: true, 12 | ...options, 13 | }); 14 | vr.render = function () { 15 | // Any custom behavior you want to perform on each frame goes here 16 | }; 17 | // Begin the animation loop 18 | vr.start(); 19 | return vr; 20 | } 21 | 22 | window.ReactVR = { init }; 23 | -------------------------------------------------------------------------------- /vr/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | VrTextInput 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /vr/static_assets/chess-world.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamRocketThesis/react-vr-textinput/51445d1cb6c814720f55e39b3db580c276cfbdea/vr/static_assets/chess-world.jpg --------------------------------------------------------------------------------