@adhiban/three-mesh-ui
Version:
a library on top of three.js to help in creating 3D user interfaces, with minor changes ;)
344 lines (232 loc) • 7.16 kB
JavaScript
import { TextureLoader } from 'three';
import { Object3D } from 'three';
import BoxComponent from './core/BoxComponent.js';
import MeshUIComponent from './core/MeshUIComponent.js';
import Block from './Block.js';
import Text from './Text.js';
import InlineBlock from './InlineBlock.js';
import keymaps from '../utils/Keymaps.js';
import { mix } from '../utils/mix.js';
//
const textureLoader = new TextureLoader();
//
/**
* Job: high-level component that returns a keyboard
*/
export default class Keyboard extends mix.withBase( Object3D )( BoxComponent, MeshUIComponent ) {
constructor( options ) {
// DEFAULTS
if ( !options ) options = {};
if ( !options.width ) options.width = 1;
if ( !options.height ) options.height = 0.4;
if ( !options.margin ) options.margin = 0.003;
if ( !options.padding ) options.padding = 0.01;
//
super( options );
this.currentPanel = 0;
this.isLowerCase = true;
this.charsetCount = 1;
//////////
// KEYMAP
//////////
// ../utils/Keymaps contains information about various keyboard layouts
// We select one depending on the user's browser language if
// there is no options.language
let keymap;
if ( options.language || navigator.language ) {
switch ( options.language || navigator.language ) {
case 'fr' :
case 'fr-CH' :
case 'fr-CA' :
keymap = keymaps.fr;
break;
case 'ru' :
this.charsetCount = 2;
keymap = keymaps.ru;
break;
case 'de' :
case 'de-DE' :
case 'de-AT' :
case 'de-LI' :
case 'de-CH' :
keymap = keymaps.de;
break;
case 'es' :
case 'es-419' :
case 'es-AR' :
case 'es-CL' :
case 'es-CO' :
case 'es-ES' :
case 'es-CR' :
case 'es-US' :
case 'es-HN' :
case 'es-MX' :
case 'es-PE' :
case 'es-UY' :
case 'es-VE' :
keymap = keymaps.es;
break;
case 'el' :
this.charsetCount = 2;
keymap = keymaps.el;
break;
case 'nord' :
keymap = keymaps.nord;
break;
default :
keymap = keymaps.eng;
break;
}
} else {
keymap = keymaps.eng;
}
////////////////////
// BLOCKS CREATION
////////////////////
// PANELS
this.keys = [];
this.panels = keymap.map( ( panel ) => {
const lineHeight = ( options.height / panel.length ) - ( options.margin * 2 );
const panelBlock = new Block( {
width: options.width + ( options.padding * 2 ),
height: options.height + ( options.padding * 2 ),
offset: 0,
padding: options.padding,
fontFamily: options.fontFamily,
fontTexture: options.fontTexture,
backgroundColor: options.backgroundColor,
backgroundOpacity: options.backgroundOpacity
} );
panelBlock.charset = 0;
panelBlock.add( ...panel.map( ( line ) => {
const lineBlock = new Block( {
width: options.width,
height: lineHeight,
margin: options.margin,
contentDirection: 'row',
justifyContent: 'center'
} );
lineBlock.frame.visible = false;
const keys = [];
line.forEach( ( keyItem ) => {
const key = new Block( {
width: ( options.width * keyItem.width ) - ( options.margin * 2 ),
height: lineHeight,
margin: options.margin,
justifyContent: 'center',
offset: 0
} );
const char = keyItem.chars[ panelBlock.charset ].lowerCase || keyItem.chars[ panelBlock.charset ].icon || 'undif';
if (
( char === 'enter' && options.enterTexture ) ||
( char === 'shift' && options.shiftTexture ) ||
( char === 'backspace' && options.backspaceTexture )
) {
const url = ( () => {
switch ( char ) {
case 'backspace':
return options.backspaceTexture;
case 'enter':
return options.enterTexture;
case 'shift':
return options.shiftTexture;
default:
console.warn( 'There is no icon image for this key' );
}
} )();
textureLoader.load( url, ( texture ) => {
key.add(
new InlineBlock( {
width: key.width * 0.65,
height: key.height * 0.65,
backgroundSize: 'contain',
backgroundTexture: texture
} )
);
} );
} else {
key.add(
new Text( {
content: char,
offset: 0
} )
);
}
key.type = 'Key';
key.info = keyItem;
key.info.input = char;
key.panel = panelBlock;
// line's keys
keys.push( key );
// keyboard's keys
this.keys.push( key );
} );
lineBlock.add( ...keys );
return lineBlock;
} ) );
return panelBlock;
} );
this.add( this.panels[ 0 ] );
// Lastly set the options parameters to this object, which will trigger an update
this.set( options );
}
/**
* Used to switch to an entirely different panel of this keyboard,
* with potentially a completely different layout
*/
setNextPanel() {
this.panels.forEach( ( panel ) => {
this.remove( panel );
} );
this.currentPanel = ( this.currentPanel + 1 ) % ( this.panels.length );
this.add( this.panels[ this.currentPanel ] );
this.update( true, true, true );
}
/*
* Used to change the keys charset. Some layout support this,
* like the Russian or Greek keyboard, to be able to switch to
* English layout when necessary
*/
setNextCharset() {
this.panels[ this.currentPanel ].charset = ( this.panels[ this.currentPanel ].charset + 1 ) % this.charsetCount;
this.keys.forEach( ( key ) => {
// Here we sort the keys, we only keep the ones that are part of the current panel.
const isInCurrentPanel = this.panels[ this.currentPanel ].getObjectById( key.id );
if ( !isInCurrentPanel ) return;
//
const char = key.info.chars[ key.panel.charset ] || key.info.chars[ 0 ];
const newContent = this.isLowerCase || !char.upperCase ? char.lowerCase : char.upperCase;
if ( !key.childrenTexts.length ) return;
const textComponent = key.childrenTexts[0];
key.info.input = newContent;
textComponent.set( {
content: newContent
} );
textComponent.update( true, true, true );
} );
}
/** Toggle case for characters that support it. */
toggleCase() {
this.isLowerCase = !this.isLowerCase;
this.keys.forEach( ( key ) => {
const char = key.info.chars[ key.panel.charset ] || key.info.chars[ 0 ];
const newContent = this.isLowerCase || !char.upperCase ? char.lowerCase : char.upperCase;
if ( !key.childrenTexts.length ) return;
const textComponent = key.childrenTexts[0];
key.info.input = newContent;
textComponent.set( {
content: newContent
} );
textComponent.update( true, true, true );
} );
}
////////////
// UPDATE
////////////
parseParams() {
}
updateLayout() {
}
updateInner() {
}
}