浏览代码

GUI: Add support for InputText
Fixes #2545

David Catuhe 8 年之前
父节点
当前提交
03bc8b36ef

+ 1 - 0
Tools/Gulp/config.json

@@ -1424,6 +1424,7 @@
         "libraries": [
             {
                 "files": [
+                    "../../gui/src/interfaces/focusableControl.ts",
                     "../../gui/src/advancedDynamicTexture.ts",
                     "../../gui/src/measure.ts",
                     "../../gui/src/math2D.ts",

文件差异内容过多而无法显示
+ 4811 - 4811
dist/preview release/babylon.d.ts


文件差异内容过多而无法显示
+ 14 - 14
dist/preview release/babylon.js


+ 10 - 12
dist/preview release/babylon.max.js

@@ -7758,14 +7758,12 @@ var BABYLON;
                         _this._performanceMonitor.disable();
                     }
                     _this._windowIsBackground = true;
-                    _this.onCanvasBlurObservable.notifyObservers(_this);
                 };
                 this._onFocus = function () {
                     if (_this.disablePerformanceMonitorInBackground) {
                         _this._performanceMonitor.enable();
                     }
                     _this._windowIsBackground = false;
-                    _this.onCanvasFocusObservable.notifyObservers(_this);
                 };
                 this._onCanvasPointerOut = function () {
                     _this.onCanvasPointerOutObservable.notifyObservers(_this);
@@ -13542,7 +13540,7 @@ var BABYLON;
             enumerable: true,
             configurable: true
         });
-        AbstractMesh.prototype.moveWithCollisions = function (direction) {
+        AbstractMesh.prototype.moveWithCollisions = function (displacement) {
             var globalPosition = this.getAbsolutePosition();
             globalPosition.subtractFromFloatsToRef(0, this.ellipsoid.y, 0, this._oldPositionForCollisions);
             this._oldPositionForCollisions.addInPlace(this.ellipsoidOffset);
@@ -13550,7 +13548,7 @@ var BABYLON;
                 this._collider = new BABYLON.Collider();
             }
             this._collider.radius = this.ellipsoid;
-            this.getScene().collisionCoordinator.getNewPosition(this._oldPositionForCollisions, direction, this._collider, 3, this, this._onCollisionPositionChange, this.uniqueId);
+            this.getScene().collisionCoordinator.getNewPosition(this._oldPositionForCollisions, displacement, this._collider, 3, this, this._onCollisionPositionChange, this.uniqueId);
             return this;
         };
         // Submeshes octree
@@ -34846,7 +34844,7 @@ var BABYLON;
             enumerable: true,
             configurable: true
         });
-        FreeCamera.prototype._collideWithWorld = function (direction) {
+        FreeCamera.prototype._collideWithWorld = function (displacement) {
             var globalPosition;
             if (this.parent) {
                 globalPosition = BABYLON.Vector3.TransformCoordinates(this.position, this.parent.getWorldMatrix());
@@ -34861,13 +34859,13 @@ var BABYLON;
             this._collider.radius = this.ellipsoid;
             this._collider.collisionMask = this._collisionMask;
             //no need for clone, as long as gravity is not on.
-            var actualDirection = direction;
+            var actualDisplacement = displacement;
             //add gravity to the direction to prevent the dual-collision checking
             if (this.applyGravity) {
                 //this prevents mending with cameraDirection, a global variable of the free camera class.
-                actualDirection = direction.add(this.getScene().gravity);
+                actualDisplacement = displacement.add(this.getScene().gravity);
             }
-            this.getScene().collisionCoordinator.getNewPosition(this._oldPosition, actualDirection, this._collider, 3, null, this._onCollisionPositionChange, this.uniqueId);
+            this.getScene().collisionCoordinator.getNewPosition(this._oldPosition, actualDisplacement, this._collider, 3, null, this._onCollisionPositionChange, this.uniqueId);
         };
         FreeCamera.prototype._checkInputs = function () {
             if (!this._localDirection) {
@@ -40460,13 +40458,13 @@ var BABYLON;
             this._toRemoveGeometryArray = [];
             this._toRemoveMeshesArray = [];
         }
-        CollisionCoordinatorWorker.prototype.getNewPosition = function (position, velocity, collider, maximumRetry, excludedMesh, onNewPosition, collisionIndex) {
+        CollisionCoordinatorWorker.prototype.getNewPosition = function (position, displacement, collider, maximumRetry, excludedMesh, onNewPosition, collisionIndex) {
             if (!this._init)
                 return;
             if (this._collisionsCallbackArray[collisionIndex] || this._collisionsCallbackArray[collisionIndex + 100000])
                 return;
             position.divideToRef(collider.radius, this._scaledPosition);
-            velocity.divideToRef(collider.radius, this._scaledVelocity);
+            displacement.divideToRef(collider.radius, this._scaledVelocity);
             this._collisionsCallbackArray[collisionIndex] = onNewPosition;
             var payload = {
                 collider: {
@@ -40571,9 +40569,9 @@ var BABYLON;
             this._scaledVelocity = BABYLON.Vector3.Zero();
             this._finalPosition = BABYLON.Vector3.Zero();
         }
-        CollisionCoordinatorLegacy.prototype.getNewPosition = function (position, velocity, collider, maximumRetry, excludedMesh, onNewPosition, collisionIndex) {
+        CollisionCoordinatorLegacy.prototype.getNewPosition = function (position, displacement, collider, maximumRetry, excludedMesh, onNewPosition, collisionIndex) {
             position.divideToRef(collider.radius, this._scaledPosition);
-            velocity.divideToRef(collider.radius, this._scaledVelocity);
+            displacement.divideToRef(collider.radius, this._scaledVelocity);
             collider.collidedMesh = null;
             collider.retry = 0;
             collider.initialVelocity = this._scaledVelocity;

文件差异内容过多而无法显示
+ 4811 - 4811
dist/preview release/babylon.module.d.ts


文件差异内容过多而无法显示
+ 14 - 14
dist/preview release/babylon.worker.js


文件差异内容过多而无法显示
+ 5496 - 5496
dist/preview release/customConfigurations/minimalGLTFViewer/babylon.d.ts


文件差异内容过多而无法显示
+ 25 - 25
dist/preview release/customConfigurations/minimalGLTFViewer/babylon.js


+ 2 - 4
dist/preview release/customConfigurations/minimalGLTFViewer/babylon.max.js

@@ -7758,14 +7758,12 @@ var BABYLON;
                         _this._performanceMonitor.disable();
                     }
                     _this._windowIsBackground = true;
-                    _this.onCanvasBlurObservable.notifyObservers(_this);
                 };
                 this._onFocus = function () {
                     if (_this.disablePerformanceMonitorInBackground) {
                         _this._performanceMonitor.enable();
                     }
                     _this._windowIsBackground = false;
-                    _this.onCanvasFocusObservable.notifyObservers(_this);
                 };
                 this._onCanvasPointerOut = function () {
                     _this.onCanvasPointerOutObservable.notifyObservers(_this);
@@ -13542,7 +13540,7 @@ var BABYLON;
             enumerable: true,
             configurable: true
         });
-        AbstractMesh.prototype.moveWithCollisions = function (direction) {
+        AbstractMesh.prototype.moveWithCollisions = function (displacement) {
             var globalPosition = this.getAbsolutePosition();
             globalPosition.subtractFromFloatsToRef(0, this.ellipsoid.y, 0, this._oldPositionForCollisions);
             this._oldPositionForCollisions.addInPlace(this.ellipsoidOffset);
@@ -13550,7 +13548,7 @@ var BABYLON;
                 this._collider = new BABYLON.Collider();
             }
             this._collider.radius = this.ellipsoid;
-            this.getScene().collisionCoordinator.getNewPosition(this._oldPositionForCollisions, direction, this._collider, 3, this, this._onCollisionPositionChange, this.uniqueId);
+            this.getScene().collisionCoordinator.getNewPosition(this._oldPositionForCollisions, displacement, this._collider, 3, this, this._onCollisionPositionChange, this.uniqueId);
             return this;
         };
         // Submeshes octree

文件差异内容过多而无法显示
+ 5496 - 5496
dist/preview release/customConfigurations/minimalGLTFViewer/babylon.module.d.ts


+ 25 - 1
dist/preview release/gui/babylon.gui.d.ts

@@ -1,14 +1,24 @@
+declare module BABYLON.GUI {
+    interface IFocusableControl {
+        onFocus(): void;
+        onBlur(): void;
+        processKeyboard(evt: KeyboardEvent): void;
+    }
+}
+
 
 declare module BABYLON.GUI {
     class AdvancedDynamicTexture extends DynamicTexture {
         private _isDirty;
         private _renderObserver;
         private _resizeObserver;
+        private _preKeyboardObserver;
         private _pointerMoveObserver;
         private _pointerObserver;
         private _canvasPointerOutObserver;
         private _background;
         _rootContainer: Container;
+        _lastPickedControl: Control;
         _lastControlOver: Control;
         _lastControlDown: Control;
         _capturingControl: Control;
@@ -20,12 +30,14 @@ declare module BABYLON.GUI {
         private _idealWidth;
         private _idealHeight;
         private _renderAtIdealSize;
+        private _focusedControl;
         background: string;
         idealWidth: number;
         idealHeight: number;
         renderAtIdealSize: boolean;
         readonly layer: Layer;
         readonly rootContainer: Container;
+        focusedControl: IFocusableControl;
         constructor(name: string, width: number, height: number, scene: Scene, generateMipMaps?: boolean, samplingMode?: number);
         executeOnAllControls(func: (control: Control) => void, container?: Container): void;
         markAsDirty(): void;
@@ -584,22 +596,34 @@ declare module BABYLON.GUI {
 
 
 declare module BABYLON.GUI {
-    class InputText extends Control {
+    class InputText extends Control implements IFocusableControl {
         name: string;
         private _text;
         private _background;
+        private _focusedBackground;
         private _thickness;
         private _margin;
         private _autoStretchWidth;
         private _maxWidth;
+        private _isFocused;
+        private _blinkTimeout;
+        private _blinkIsEven;
+        private _cursorOffset;
+        private _scrollLeft;
         maxWidth: string | number;
         margin: string;
         autoStretchWidth: boolean;
         thickness: number;
+        focusedBackground: string;
         background: string;
         text: string;
         constructor(name?: string, text?: string);
+        onBlur(): void;
+        onFocus(): void;
         protected _getTypeName(): string;
+        processKeyboard(evt: KeyboardEvent): void;
         _draw(parentMeasure: Measure, context: CanvasRenderingContext2D): void;
+        protected _onPointerDown(coordinates: Vector2): boolean;
+        protected _onPointerUp(coordinates: Vector2): void;
     }
 }

+ 208 - 10
dist/preview release/gui/babylon.gui.js

@@ -1,3 +1,7 @@
+
+
+//# sourceMappingURL=focusableControl.js.map
+
 /// <reference path="../../dist/preview release/babylon.d.ts"/>
 var __extends = (this && this.__extends) || (function () {
     var extendStatics = Object.setPrototypeOf ||
@@ -30,6 +34,15 @@ var BABYLON;
                 _this._idealHeight = 0;
                 _this._renderAtIdealSize = false;
                 _this._renderObserver = _this.getScene().onBeforeCameraRenderObservable.add(function (camera) { return _this._checkUpdate(camera); });
+                _this._preKeyboardObserver = _this.getScene().onPreKeyboardObservable.add(function (info) {
+                    if (!_this._focusedControl) {
+                        return;
+                    }
+                    if (info.type === BABYLON.KeyboardEventTypes.KEYDOWN) {
+                        _this._focusedControl.processKeyboard(info.event);
+                    }
+                    info.skipOnPointerObservable = true;
+                });
                 _this._rootContainer._link(null, _this);
                 _this.hasAlpha = true;
                 if (!width || !height) {
@@ -111,6 +124,25 @@ var BABYLON;
                 enumerable: true,
                 configurable: true
             });
+            Object.defineProperty(AdvancedDynamicTexture.prototype, "focusedControl", {
+                get: function () {
+                    return this._focusedControl;
+                },
+                set: function (control) {
+                    if (this._focusedControl === control) {
+                        return;
+                    }
+                    if (!this._focusedControl) {
+                        control.onFocus();
+                    }
+                    else {
+                        this._focusedControl.onBlur();
+                    }
+                    this._focusedControl = control;
+                },
+                enumerable: true,
+                configurable: true
+            });
             AdvancedDynamicTexture.prototype.executeOnAllControls = function (func, container) {
                 if (!container) {
                     container = this._rootContainer;
@@ -262,6 +294,12 @@ var BABYLON;
                         this._lastControlOver = null;
                     }
                 }
+                // Focus management
+                if (this._focusedControl) {
+                    if (this._focusedControl !== this._lastPickedControl) {
+                        this.focusedControl = null;
+                    }
+                }
             };
             AdvancedDynamicTexture.prototype.attach = function () {
                 var _this = this;
@@ -1429,6 +1467,7 @@ var BABYLON;
                 if (type === BABYLON.PointerEventTypes.POINTERDOWN) {
                     this._onPointerDown(this._dummyVector2);
                     this._host._lastControlDown = this;
+                    this._host._lastPickedControl = this;
                     return true;
                 }
                 if (type === BABYLON.PointerEventTypes.POINTERUP) {
@@ -3339,8 +3378,6 @@ var BABYLON;
     })(GUI = BABYLON.GUI || (BABYLON.GUI = {}));
 })(BABYLON || (BABYLON = {}));
 
-//# sourceMappingURL=button.js.map
-
 /// <reference path="../../../dist/preview release/babylon.d.ts"/>
 var __extends = (this && this.__extends) || (function () {
     var extendStatics = Object.setPrototypeOf ||
@@ -3719,10 +3756,14 @@ var BABYLON;
                 _this.name = name;
                 _this._text = "";
                 _this._background = "black";
+                _this._focusedBackground = "black";
                 _this._thickness = 1;
                 _this._margin = new GUI.ValueAndUnit(10, GUI.ValueAndUnit.UNITMODE_PIXEL);
                 _this._autoStretchWidth = true;
                 _this._maxWidth = new GUI.ValueAndUnit(1, GUI.ValueAndUnit.UNITMODE_PERCENTAGE, false);
+                _this._isFocused = false;
+                _this._blinkIsEven = false;
+                _this._cursorOffset = 0;
                 _this.text = text;
                 return _this;
             }
@@ -3784,6 +3825,20 @@ var BABYLON;
                 enumerable: true,
                 configurable: true
             });
+            Object.defineProperty(InputText.prototype, "focusedBackground", {
+                get: function () {
+                    return this._focusedBackground;
+                },
+                set: function (value) {
+                    if (this._focusedBackground === value) {
+                        return;
+                    }
+                    this._focusedBackground = value;
+                    this._markAsDirty();
+                },
+                enumerable: true,
+                configurable: true
+            });
             Object.defineProperty(InputText.prototype, "background", {
                 get: function () {
                     return this._background;
@@ -3812,29 +3867,162 @@ var BABYLON;
                 enumerable: true,
                 configurable: true
             });
+            InputText.prototype.onBlur = function () {
+                this._isFocused = false;
+                this._scrollLeft = null;
+                this._cursorOffset = 0;
+                clearTimeout(this._blinkTimeout);
+                this._markAsDirty();
+            };
+            InputText.prototype.onFocus = function () {
+                this._scrollLeft = null;
+                this._isFocused = true;
+                this._blinkIsEven = false;
+                this._cursorOffset = 0;
+                this._markAsDirty();
+            };
             InputText.prototype._getTypeName = function () {
                 return "InputText";
             };
+            InputText.prototype.processKeyboard = function (evt) {
+                // Specific cases
+                switch (evt.keyCode) {
+                    case 8:// BACKSPACE
+                        if (this._text && this._text.length > 0) {
+                            if (this._cursorOffset === 0) {
+                                this.text = this._text.substr(0, this._text.length - 1);
+                            }
+                            else {
+                                var deletePosition = this._text.length - this._cursorOffset;
+                                if (deletePosition > 0) {
+                                    this.text = this._text.slice(0, deletePosition - 1) + this._text.slice(deletePosition);
+                                }
+                            }
+                        }
+                        return;
+                    case 46:// DELETE
+                        if (this._text && this._text.length > 0) {
+                            var deletePosition = this._text.length - this._cursorOffset;
+                            this.text = this._text.slice(0, deletePosition) + this._text.slice(deletePosition + 1);
+                            this._cursorOffset--;
+                        }
+                        return;
+                    case 13:// RETURN
+                        this._host.focusedControl = null;
+                        return;
+                    case 35:// END
+                        this._cursorOffset = 0;
+                        this._blinkIsEven = false;
+                        this._markAsDirty();
+                        return;
+                    case 36:// HOME
+                        this._cursorOffset = this._text.length;
+                        this._blinkIsEven = false;
+                        this._markAsDirty();
+                        return;
+                    case 37:// LEFT
+                        this._cursorOffset++;
+                        if (this._cursorOffset > this._text.length) {
+                            this._cursorOffset = this._text.length;
+                        }
+                        this._blinkIsEven = false;
+                        this._markAsDirty();
+                        return;
+                    case 39:// RIGHT
+                        this._cursorOffset--;
+                        if (this._cursorOffset < 0) {
+                            this._cursorOffset = 0;
+                        }
+                        this._blinkIsEven = false;
+                        this._markAsDirty();
+                        return;
+                }
+                // Printable characters
+                if ((evt.keyCode === 32) ||
+                    (evt.keyCode > 47 && evt.keyCode < 58) ||
+                    (evt.keyCode > 64 && evt.keyCode < 91) ||
+                    (evt.keyCode > 185 && evt.keyCode < 193) ||
+                    (evt.keyCode > 218 && evt.keyCode < 223) ||
+                    (evt.keyCode > 95 && evt.keyCode < 112)) {
+                    if (this._cursorOffset === 0) {
+                        this.text += evt.key;
+                    }
+                    else {
+                        var insertPosition = this._text.length - this._cursorOffset;
+                        this.text = this._text.slice(0, insertPosition) + evt.key + this._text.slice(insertPosition);
+                    }
+                }
+            };
             InputText.prototype._draw = function (parentMeasure, context) {
+                var _this = this;
                 context.save();
                 this._applyStates(context);
                 if (this._processMeasures(parentMeasure, context)) {
                     // Background
-                    if (this._background) {
+                    if (this._isFocused) {
+                        if (this._focusedBackground) {
+                            context.fillStyle = this._focusedBackground;
+                            context.fillRect(this._currentMeasure.left, this._currentMeasure.top, this._currentMeasure.width, this._currentMeasure.height);
+                        }
+                    }
+                    else if (this._background) {
                         context.fillStyle = this._background;
                         context.fillRect(this._currentMeasure.left, this._currentMeasure.top, this._currentMeasure.width, this._currentMeasure.height);
                     }
+                    if (!this._fontOffset) {
+                        this._fontOffset = GUI.Control._GetFontOffset(context.font);
+                    }
                     // Text
-                    if (this._text) {
-                        if (this.color) {
-                            context.fillStyle = this.color;
+                    var clipTextLeft = this._currentMeasure.left + this._margin.getValueInPixel(this._host, parentMeasure.width);
+                    if (this.color) {
+                        context.fillStyle = this.color;
+                    }
+                    var textWidth = context.measureText(this._text).width;
+                    var marginWidth = this._margin.getValueInPixel(this._host, parentMeasure.width) * 2;
+                    if (this._autoStretchWidth) {
+                        this.width = Math.min(this._maxWidth.getValueInPixel(this._host, parentMeasure.width), textWidth + marginWidth) + "px";
+                    }
+                    var rootY = this._fontOffset.ascent + (this._currentMeasure.height - this._fontOffset.height) / 2;
+                    var availableWidth = this._width.getValueInPixel(this._host, parentMeasure.width) - marginWidth;
+                    context.save();
+                    context.beginPath();
+                    context.rect(clipTextLeft, this._currentMeasure.top + (this._currentMeasure.height - this._fontOffset.height) / 2, availableWidth + 2, this._currentMeasure.height);
+                    context.clip();
+                    if (this._isFocused && textWidth > availableWidth) {
+                        var textLeft = clipTextLeft - textWidth + availableWidth;
+                        if (!this._scrollLeft) {
+                            this._scrollLeft = textLeft;
                         }
-                        var rootY = this._fontOffset.ascent + (this._currentMeasure.height - this._fontOffset.height) / 2;
-                        context.fillText(this._text, this._currentMeasure.left + this._margin.getValueInPixel(this._host, parentMeasure.width), this._currentMeasure.top + rootY);
-                        if (this._autoStretchWidth) {
-                            this.width = Math.min(this._maxWidth.getValueInPixel(this._host, parentMeasure.width), context.measureText(this._text).width + this._margin.getValueInPixel(this._host, parentMeasure.width) * 2) + "px";
+                    }
+                    else {
+                        this._scrollLeft = clipTextLeft;
+                    }
+                    context.fillText(this._text, this._scrollLeft, this._currentMeasure.top + rootY);
+                    // Cursor
+                    if (this._isFocused) {
+                        if (!this._blinkIsEven) {
+                            var cursorOffsetText = this.text.substr(this._text.length - this._cursorOffset);
+                            var cursorOffsetWidth = context.measureText(cursorOffsetText).width;
+                            var cursorLeft = this._scrollLeft + textWidth - cursorOffsetWidth;
+                            if (cursorLeft < clipTextLeft) {
+                                this._scrollLeft += (clipTextLeft - cursorLeft);
+                                cursorLeft = clipTextLeft;
+                                this._markAsDirty();
+                            }
+                            else if (cursorLeft > clipTextLeft + availableWidth) {
+                                this._scrollLeft += (clipTextLeft + availableWidth - cursorLeft);
+                                cursorLeft = clipTextLeft + availableWidth;
+                                this._markAsDirty();
+                            }
+                            context.fillRect(cursorLeft, this._currentMeasure.top + (this._currentMeasure.height - this._fontOffset.height) / 2, 2, this._fontOffset.height);
                         }
+                        clearTimeout(this._blinkTimeout);
+                        this._blinkTimeout = setTimeout(function () {
+                            _this._blinkIsEven = !_this._blinkIsEven;
+                            _this._markAsDirty();
+                        }, 500);
                     }
+                    context.restore();
                     // Border
                     if (this._thickness) {
                         if (this.color) {
@@ -3846,6 +4034,16 @@ var BABYLON;
                 }
                 context.restore();
             };
+            InputText.prototype._onPointerDown = function (coordinates) {
+                if (!_super.prototype._onPointerDown.call(this, coordinates)) {
+                    return false;
+                }
+                this._host.focusedControl = this;
+                return true;
+            };
+            InputText.prototype._onPointerUp = function (coordinates) {
+                _super.prototype._onPointerUp.call(this, coordinates);
+            };
             return InputText;
         }(GUI.Control));
         GUI.InputText = InputText;

文件差异内容过多而无法显示
+ 3 - 3
dist/preview release/gui/babylon.gui.min.js


+ 25 - 1
dist/preview release/gui/babylon.gui.module.d.ts

@@ -1,14 +1,24 @@
+declare module BABYLON.GUI {
+    interface IFocusableControl {
+        onFocus(): void;
+        onBlur(): void;
+        processKeyboard(evt: KeyboardEvent): void;
+    }
+}
+
 /// <reference path="../../dist/preview release/babylon.d.ts" />
 declare module BABYLON.GUI {
     class AdvancedDynamicTexture extends DynamicTexture {
         private _isDirty;
         private _renderObserver;
         private _resizeObserver;
+        private _preKeyboardObserver;
         private _pointerMoveObserver;
         private _pointerObserver;
         private _canvasPointerOutObserver;
         private _background;
         _rootContainer: Container;
+        _lastPickedControl: Control;
         _lastControlOver: Control;
         _lastControlDown: Control;
         _capturingControl: Control;
@@ -20,12 +30,14 @@ declare module BABYLON.GUI {
         private _idealWidth;
         private _idealHeight;
         private _renderAtIdealSize;
+        private _focusedControl;
         background: string;
         idealWidth: number;
         idealHeight: number;
         renderAtIdealSize: boolean;
         readonly layer: Layer;
         readonly rootContainer: Container;
+        focusedControl: IFocusableControl;
         constructor(name: string, width: number, height: number, scene: Scene, generateMipMaps?: boolean, samplingMode?: number);
         executeOnAllControls(func: (control: Control) => void, container?: Container): void;
         markAsDirty(): void;
@@ -584,23 +596,35 @@ declare module BABYLON.GUI {
 
 /// <reference path="../../../dist/preview release/babylon.d.ts" />
 declare module BABYLON.GUI {
-    class InputText extends Control {
+    class InputText extends Control implements IFocusableControl {
         name: string;
         private _text;
         private _background;
+        private _focusedBackground;
         private _thickness;
         private _margin;
         private _autoStretchWidth;
         private _maxWidth;
+        private _isFocused;
+        private _blinkTimeout;
+        private _blinkIsEven;
+        private _cursorOffset;
+        private _scrollLeft;
         maxWidth: string | number;
         margin: string;
         autoStretchWidth: boolean;
         thickness: number;
+        focusedBackground: string;
         background: string;
         text: string;
         constructor(name?: string, text?: string);
+        onBlur(): void;
+        onFocus(): void;
         protected _getTypeName(): string;
+        processKeyboard(evt: KeyboardEvent): void;
         _draw(parentMeasure: Measure, context: CanvasRenderingContext2D): void;
+        protected _onPointerDown(coordinates: Vector2): boolean;
+        protected _onPointerUp(coordinates: Vector2): void;
     }
 }
 

+ 39 - 0
gui/src/advancedDynamicTexture.ts

@@ -5,11 +5,13 @@ module BABYLON.GUI {
         private _isDirty = false;
         private _renderObserver: Observer<Camera>;
         private _resizeObserver: Observer<Engine>;
+        private _preKeyboardObserver: Observer<KeyboardInfoPre>;
         private _pointerMoveObserver: Observer<PointerInfoPre>;
         private _pointerObserver: Observer<PointerInfo>;
         private _canvasPointerOutObserver: Observer<Engine>;
         private _background: string;
         public _rootContainer = new Container("root");
+        public _lastPickedControl: Control;
         public _lastControlOver: Control;
         public _lastControlDown: Control;
         public _capturingControl: Control;
@@ -21,6 +23,7 @@ module BABYLON.GUI {
         private _idealWidth = 0;
         private _idealHeight = 0;
         private _renderAtIdealSize = false;
+        private _focusedControl: IFocusableControl;
 
         public get background(): string {
             return this._background;
@@ -83,11 +86,40 @@ module BABYLON.GUI {
         public get rootContainer(): Container {
             return this._rootContainer;
         }
+
+        public get focusedControl(): IFocusableControl {
+            return this._focusedControl;
+        }
+
+        public set focusedControl(control: IFocusableControl) {
+            if (this._focusedControl === control) {
+                return;
+            }
+
+            if (!this._focusedControl) {
+                control.onFocus();
+            } else {
+                this._focusedControl.onBlur();
+            }
+
+            this._focusedControl = control;
+        }
        
         constructor(name: string, width = 0, height = 0, scene: Scene, generateMipMaps = false, samplingMode = Texture.NEAREST_SAMPLINGMODE) {
             super(name, {width: width, height: height}, scene, generateMipMaps, samplingMode, Engine.TEXTUREFORMAT_RGBA);
 
             this._renderObserver = this.getScene().onBeforeCameraRenderObservable.add((camera: Camera) => this._checkUpdate(camera));
+            this._preKeyboardObserver = this.getScene().onPreKeyboardObservable.add(info => {
+                if (!this._focusedControl) {
+                    return;
+                }
+
+                if (info.type === KeyboardEventTypes.KEYDOWN) {
+                    this._focusedControl.processKeyboard(info.event);
+                }
+
+                info.skipOnPointerObservable = true;
+            });
 
             this._rootContainer._link(null, this);
 
@@ -286,6 +318,13 @@ module BABYLON.GUI {
                     this._lastControlOver = null;
                 }
             }
+
+            // Focus management
+            if (this._focusedControl) {
+                if (this._focusedControl !== (<any>this._lastPickedControl)) {
+                    this.focusedControl = null;
+                }
+            }
         }
 
         public attach(): void {

+ 1 - 0
gui/src/controls/control.ts

@@ -834,6 +834,7 @@ module BABYLON.GUI {
             if (type === BABYLON.PointerEventTypes.POINTERDOWN) {
                 this._onPointerDown(this._dummyVector2);
                 this._host._lastControlDown = this;
+                this._host._lastPickedControl = this;
                 return true;
             }
 

+ 185 - 11
gui/src/controls/inputText.ts

@@ -1,13 +1,19 @@
 /// <reference path="../../../dist/preview release/babylon.d.ts"/>
 
 module BABYLON.GUI {
-    export class InputText extends Control {
+    export class InputText extends Control implements IFocusableControl {
         private _text = "";
         private _background = "black";   
+        private _focusedBackground = "black";   
         private _thickness = 1;
         private _margin = new ValueAndUnit(10, ValueAndUnit.UNITMODE_PIXEL);
         private _autoStretchWidth = true;        
         private _maxWidth = new ValueAndUnit(1, ValueAndUnit.UNITMODE_PERCENTAGE, false);
+        private _isFocused = false;
+        private _blinkTimeout: number;
+        private _blinkIsEven = false;
+        private _cursorOffset = 0;        
+        private _scrollLeft: number;
 
         public get maxWidth(): string | number {
             return this._maxWidth.toString(this._host);
@@ -63,6 +69,19 @@ module BABYLON.GUI {
             this._markAsDirty();
         }          
 
+        public get focusedBackground(): string {
+            return this._focusedBackground;
+        }
+
+        public set focusedBackground(value: string) {
+            if (this._focusedBackground === value) {
+                return;
+            }
+
+            this._focusedBackground = value;
+            this._markAsDirty();
+        }  
+
         public get background(): string {
             return this._background;
         }
@@ -74,7 +93,7 @@ module BABYLON.GUI {
 
             this._background = value;
             this._markAsDirty();
-        }  
+        }          
 
         public get text(): string {
             return this._text;
@@ -94,37 +113,178 @@ module BABYLON.GUI {
             this.text = text;
         }
 
+        public onBlur(): void {
+            this._isFocused = false;
+            this._scrollLeft = null;
+            this._cursorOffset = 0;
+            clearTimeout(this._blinkTimeout);
+            this._markAsDirty();
+        }
+
+        public onFocus(): void {
+            this._scrollLeft = null;
+            this._isFocused = true;
+            this._blinkIsEven = false;
+            this._cursorOffset = 0;
+            this._markAsDirty();
+        }
+
         protected _getTypeName(): string {
             return "InputText";
         }
 
-       public _draw(parentMeasure: Measure, context: CanvasRenderingContext2D): void {
+        public processKeyboard(evt: KeyboardEvent): void {
+            // Specific cases
+            switch (evt.keyCode) {
+                case 8: // BACKSPACE
+                    if (this._text && this._text.length > 0) {
+                        if (this._cursorOffset === 0) {
+                            this.text = this._text.substr(0, this._text.length - 1);
+                        } else {
+                            let deletePosition = this._text.length - this._cursorOffset;
+                            if (deletePosition > 0) {
+                                this.text = this._text.slice(0, deletePosition - 1) + this._text.slice(deletePosition);
+                            }
+                        }
+                    }
+                    return;
+                case 46: // DELETE
+                    if (this._text && this._text.length > 0) {
+                        let deletePosition = this._text.length - this._cursorOffset;
+                        this.text = this._text.slice(0, deletePosition) + this._text.slice(deletePosition + 1);
+                        this._cursorOffset--;
+                    }
+                    return;                    
+                case 13: // RETURN
+                    this._host.focusedControl = null;
+                    return;
+                case 35: // END
+                    this._cursorOffset = 0;
+                    this._blinkIsEven = false;
+                    this._markAsDirty();                
+                    return;
+                case 36: // HOME
+                    this._cursorOffset = this._text.length;
+                    this._blinkIsEven = false;
+                    this._markAsDirty();                
+                return;
+                case 37: // LEFT
+                    this._cursorOffset++;
+                    if (this._cursorOffset > this._text.length) {
+                        this._cursorOffset = this._text.length;
+                    }
+                    this._blinkIsEven = false;
+                    this._markAsDirty();
+                    return;
+                case 39: // RIGHT
+                    this._cursorOffset--;
+                    if (this._cursorOffset < 0) {
+                        this._cursorOffset = 0;
+                    }
+                    this._blinkIsEven = false;
+                    this._markAsDirty();
+                    return;
+            }
+
+            // Printable characters
+            if (
+                (evt.keyCode === 32) ||                         // Space
+                (evt.keyCode > 47 && evt.keyCode < 58) ||       // Numbers
+                (evt.keyCode > 64 && evt.keyCode < 91) ||       // Letters
+                (evt.keyCode > 185 && evt.keyCode < 193) ||     // Special characters
+                (evt.keyCode > 218  && evt.keyCode < 223) ||    // Special characters
+                (evt.keyCode > 95 && evt.keyCode < 112)) {      // Numpad
+                    if (this._cursorOffset === 0) {
+                        this.text += evt.key;
+                    } else {
+                        let insertPosition = this._text.length - this._cursorOffset;
+
+                        this.text = this._text.slice(0, insertPosition) + evt.key + this._text.slice(insertPosition);
+                    }
+                }
+        }
+
+        public _draw(parentMeasure: Measure, context: CanvasRenderingContext2D): void {
             context.save();
 
             this._applyStates(context);
             if (this._processMeasures(parentMeasure, context)) {
                 
                 // Background
-                if (this._background) {
+                if (this._isFocused) {
+                    if (this._focusedBackground) {
+                        context.fillStyle = this._focusedBackground;
+    
+                        context.fillRect(this._currentMeasure.left, this._currentMeasure.top, this._currentMeasure.width, this._currentMeasure.height);
+                    }                        
+                } else if (this._background) {
                     context.fillStyle = this._background;
 
                     context.fillRect(this._currentMeasure.left, this._currentMeasure.top, this._currentMeasure.width, this._currentMeasure.height);
                 }
 
+                if (!this._fontOffset) {
+                    this._fontOffset = Control._GetFontOffset(context.font);
+                }
+
                 // Text
-                if (this._text) {
-                    if (this.color) {
-                        context.fillStyle = this.color;
+                let clipTextLeft = this._currentMeasure.left + this._margin.getValueInPixel(this._host, parentMeasure.width);
+                if (this.color) {
+                    context.fillStyle = this.color;
+                }
+
+                let textWidth = context.measureText(this._text).width;   
+                let marginWidth = this._margin.getValueInPixel(this._host, parentMeasure.width) * 2;
+                if (this._autoStretchWidth) {
+                    this.width = Math.min(this._maxWidth.getValueInPixel(this._host, parentMeasure.width), textWidth + marginWidth) + "px";
+                }
+
+                let rootY = this._fontOffset.ascent + (this._currentMeasure.height - this._fontOffset.height) / 2;
+                let availableWidth = this._width.getValueInPixel(this._host, parentMeasure.width) - marginWidth;
+                context.save();
+                context.beginPath();
+                context.rect(clipTextLeft, this._currentMeasure.top + (this._currentMeasure.height - this._fontOffset.height) / 2, availableWidth + 2, this._currentMeasure.height);
+                context.clip();
+
+                if (this._isFocused && textWidth > availableWidth) {      
+                    let textLeft = clipTextLeft - textWidth + availableWidth;
+                    if (!this._scrollLeft) {
+                        this._scrollLeft = textLeft;
                     }
+                } else {
+                    this._scrollLeft = clipTextLeft;
+                }
 
-                    let rootY = this._fontOffset.ascent + (this._currentMeasure.height - this._fontOffset.height) / 2;
-                    context.fillText(this._text, this._currentMeasure.left + this._margin.getValueInPixel(this._host, parentMeasure.width), this._currentMeasure.top + rootY);
+                context.fillText(this._text, this._scrollLeft, this._currentMeasure.top + rootY);
 
-                    if (this._autoStretchWidth) {
-                        this.width = Math.min(this._maxWidth.getValueInPixel(this._host, parentMeasure.width), context.measureText(this._text).width + this._margin.getValueInPixel(this._host, parentMeasure.width) * 2) + "px";
+                // Cursor
+                if (this._isFocused) {         
+                    if (!this._blinkIsEven) {
+                        let cursorOffsetText = this.text.substr(this._text.length - this._cursorOffset);
+                        let cursorOffsetWidth = context.measureText(cursorOffsetText).width;   
+                        let cursorLeft = this._scrollLeft  + textWidth - cursorOffsetWidth;
+    
+                        if (cursorLeft < clipTextLeft) {
+                            this._scrollLeft += (clipTextLeft - cursorLeft);
+                            cursorLeft = clipTextLeft;
+                            this._markAsDirty();
+                        } else if (cursorLeft > clipTextLeft + availableWidth) {
+                            this._scrollLeft += (clipTextLeft  + availableWidth - cursorLeft);
+                            cursorLeft = clipTextLeft + availableWidth;
+                            this._markAsDirty();
+                        }                   
+                        context.fillRect(cursorLeft, this._currentMeasure.top + (this._currentMeasure.height - this._fontOffset.height) / 2, 2, this._fontOffset.height);
                     }
+
+                    clearTimeout(this._blinkTimeout);
+                    this._blinkTimeout = setTimeout(() => {
+                        this._blinkIsEven = !this._blinkIsEven;
+                        this._markAsDirty();
+                    }, 500);
                 }
 
+                context.restore();
+
                 // Border
                 if (this._thickness) {
                     if (this.color) {
@@ -138,5 +298,19 @@ module BABYLON.GUI {
             }
             context.restore();
         }
+
+        protected _onPointerDown(coordinates: Vector2): boolean {
+            if (!super._onPointerDown(coordinates)) {
+                return false;
+            }
+
+            this._host.focusedControl = this;
+
+            return true;
+        }
+
+        protected _onPointerUp(coordinates: Vector2): void {
+            super._onPointerUp(coordinates);
+        }  
     }
 }

+ 7 - 0
gui/src/interfaces/focusableControl.ts

@@ -0,0 +1,7 @@
+module BABYLON.GUI {
+    export interface IFocusableControl {
+        onFocus(): void;
+        onBlur(): void;
+        processKeyboard(evt: KeyboardEvent): void;
+    }
+}

+ 4 - 4
src/Cameras/babylon.freeCamera.ts

@@ -114,7 +114,7 @@
             this._collisionMask = !isNaN(mask) ? mask : -1;
         }
 	 
-        public _collideWithWorld(direction: Vector3): void {
+        public _collideWithWorld(displacement: Vector3): void {
             var globalPosition: Vector3;
 
             if (this.parent) {
@@ -133,15 +133,15 @@
             this._collider.collisionMask = this._collisionMask;
 		
             //no need for clone, as long as gravity is not on.
-            var actualDirection = direction;
+            var actualDisplacement = displacement;
 			
             //add gravity to the direction to prevent the dual-collision checking
             if (this.applyGravity) {
                 //this prevents mending with cameraDirection, a global variable of the free camera class.
-                actualDirection = direction.add(this.getScene().gravity);
+                actualDisplacement = displacement.add(this.getScene().gravity);
             }
 
-            this.getScene().collisionCoordinator.getNewPosition(this._oldPosition, actualDirection, this._collider, 3, null, this._onCollisionPositionChange, this.uniqueId);
+            this.getScene().collisionCoordinator.getNewPosition(this._oldPosition, actualDisplacement, this._collider, 3, null, this._onCollisionPositionChange, this.uniqueId);
 
         }
 

+ 5 - 5
src/Collisions/babylon.collisionCoordinator.ts

@@ -4,7 +4,7 @@ module BABYLON {
     export var CollisionWorker = "";
 
     export interface ICollisionCoordinator {
-        getNewPosition(position: Vector3, velocity: Vector3, collider: Collider, maximumRetry: number, excludedMesh: AbstractMesh, onNewPosition: (collisionIndex: number, newPosition: Vector3, collidedMesh?: AbstractMesh) => void, collisionIndex: number): void;
+        getNewPosition(position: Vector3, displacement: Vector3, collider: Collider, maximumRetry: number, excludedMesh: AbstractMesh, onNewPosition: (collisionIndex: number, newPosition: Vector3, collidedMesh?: AbstractMesh) => void, collisionIndex: number): void;
         init(scene: Scene): void;
         destroy(): void;
 
@@ -186,12 +186,12 @@ module BABYLON {
             }
         }
 
-        public getNewPosition(position: Vector3, velocity: Vector3, collider: Collider, maximumRetry: number, excludedMesh: AbstractMesh, onNewPosition: (collisionIndex: number, newPosition: Vector3, collidedMesh?: AbstractMesh) => void, collisionIndex: number): void {
+        public getNewPosition(position: Vector3, displacement: Vector3, collider: Collider, maximumRetry: number, excludedMesh: AbstractMesh, onNewPosition: (collisionIndex: number, newPosition: Vector3, collidedMesh?: AbstractMesh) => void, collisionIndex: number): void {
             if (!this._init) return;
             if (this._collisionsCallbackArray[collisionIndex] || this._collisionsCallbackArray[collisionIndex + 100000]) return;
 
             position.divideToRef(collider.radius, this._scaledPosition);
-            velocity.divideToRef(collider.radius, this._scaledVelocity);
+            displacement.divideToRef(collider.radius, this._scaledVelocity);
 
             this._collisionsCallbackArray[collisionIndex] = onNewPosition;
 
@@ -347,9 +347,9 @@ module BABYLON {
 
         private _finalPosition = Vector3.Zero();
 
-        public getNewPosition(position: Vector3, velocity: Vector3, collider: Collider, maximumRetry: number, excludedMesh: AbstractMesh, onNewPosition: (collisionIndex: number, newPosition: Vector3, collidedMesh?: AbstractMesh) => void, collisionIndex: number): void {
+        public getNewPosition(position: Vector3, displacement: Vector3, collider: Collider, maximumRetry: number, excludedMesh: AbstractMesh, onNewPosition: (collisionIndex: number, newPosition: Vector3, collidedMesh?: AbstractMesh) => void, collisionIndex: number): void {
             position.divideToRef(collider.radius, this._scaledPosition);
-            velocity.divideToRef(collider.radius, this._scaledVelocity);
+            displacement.divideToRef(collider.radius, this._scaledVelocity);
             collider.collidedMesh = null;
             collider.retry = 0;
             collider.initialVelocity = this._scaledVelocity;

+ 2 - 2
src/Mesh/babylon.abstractMesh.ts

@@ -1533,7 +1533,7 @@
             }
         }
 
-        public moveWithCollisions(direction: Vector3): AbstractMesh {
+        public moveWithCollisions(displacement: Vector3): AbstractMesh {
             var globalPosition = this.getAbsolutePosition();
 
             globalPosition.subtractFromFloatsToRef(0, this.ellipsoid.y, 0, this._oldPositionForCollisions);
@@ -1545,7 +1545,7 @@
 
             this._collider.radius = this.ellipsoid;
 
-            this.getScene().collisionCoordinator.getNewPosition(this._oldPositionForCollisions, direction, this._collider, 3, this, this._onCollisionPositionChange, this.uniqueId);
+            this.getScene().collisionCoordinator.getNewPosition(this._oldPositionForCollisions, displacement, this._collider, 3, this, this._onCollisionPositionChange, this.uniqueId);
             return this;
         }
 

+ 0 - 2
src/babylon.engine.ts

@@ -812,7 +812,6 @@
                         this._performanceMonitor.disable();
                     }
                     this._windowIsBackground = true;
-                    this.onCanvasBlurObservable.notifyObservers(this);
                 };
 
                 this._onFocus = () => {
@@ -820,7 +819,6 @@
                         this._performanceMonitor.enable();
                     }
                     this._windowIsBackground = false;
-                    this.onCanvasFocusObservable.notifyObservers(this);
                 };
 
                 this._onCanvasPointerOut = () => {