瀏覽代碼

finalized h2d.TextInput, use it for h2d.Console

ncannasse 8 年之前
父節點
當前提交
27f7852a22
共有 3 個文件被更改,包括 213 次插入76 次删除
  1. 14 45
      h2d/Console.hx
  2. 179 25
      h2d/TextInput.hx
  3. 20 6
      samples/Input.hx

+ 14 - 45
h2d/Console.hx

@@ -17,10 +17,8 @@ class Console extends h2d.Sprite {
 	var width : Int;
 	var height : Int;
 	var bg : h2d.Bitmap;
-	var tf : h2d.Text;
+	var tf : h2d.TextInput;
 	var logTxt : h2d.HtmlText;
-	var cursor : h2d.Bitmap;
-	var cursorPos(default, set) : Int;
 	var lastLogTime : Float;
 	var commands : Map < String, { help : String, args : Array<{ name : String, t : ConsoleArg, ?opt : Bool }>, callb : Dynamic } > ;
 	var aliases : Map<String,String>;
@@ -41,11 +39,12 @@ class Console extends h2d.Sprite {
 		logIndex = -1;
 		bg = new h2d.Bitmap(h2d.Tile.fromColor(0,1,1,0.5), this);
 		bg.visible = false;
-		tf = new h2d.Text(font, bg);
+		tf = new h2d.TextInput(font, bg);
+		tf.onKeyDown = handleKey;
+		tf.onFocusLost = function(_) hide();
 		tf.x = 2;
 		tf.y = 1;
 		tf.textColor = 0xFFFFFFFF;
-		cursor = new h2d.Bitmap(h2d.Tile.fromColor(tf.textColor, 1, font.lineHeight), tf);
 		commands = new Map();
 		aliases = new Map();
 		addCommand("help", "Show help", [ { name : "command", t : AString, opt : true } ], showHelp);
@@ -88,8 +87,8 @@ class Console extends h2d.Sprite {
 				e.propagate = false;
 			}
 		case EKeyDown:
-			handleKey(e);
-			if( bg.visible ) e.propagate = false;
+			if( e.charCode == shortKeyChar && !bg.visible )
+				show();
 		default:
 		}
 	}
@@ -140,56 +139,29 @@ class Console extends h2d.Sprite {
 	public function hide() {
 		bg.visible = false;
 		tf.text = "";
-		cursorPos = 0;
+		tf.cursorIndex = -1;
 	}
 
 	public function show() {
 		bg.visible = true;
+		tf.focus();
+		tf.cursorIndex = tf.text.length;
 		logIndex = -1;
 	}
 
-	function set_cursorPos(v:Int) {
-		if( v > tf.text.length ) v = tf.text.length;
-		cursor.x = tf.calcTextWidth(tf.text.substr(0, v));
-		return cursorPos = v;
-	}
-
 	function handleKey( e : hxd.Event ) {
-		if( e.charCode == shortKeyChar && !bg.visible ) {
-			show();
-		}
 		if( !bg.visible )
 			return;
 		switch( e.keyCode ) {
-		case Key.LEFT:
-			if( cursorPos > 0 )
-				cursorPos--;
-		case Key.RIGHT:
-			if( cursorPos < tf.text.length )
-				cursorPos++;
-		case Key.HOME:
-			cursorPos = 0;
-		case Key.END:
-			cursorPos = tf.text.length;
-		case Key.DELETE:
-			tf.text = tf.text.substr(0, cursorPos) + tf.text.substr(cursorPos + 1);
-			return;
-		case Key.BACKSPACE:
-			if( cursorPos > 0 ) {
-				tf.text = tf.text.substr(0, cursorPos - 1) + tf.text.substr(cursorPos);
-				cursorPos--;
-			}
-			return;
 		case Key.ENTER:
 			var cmd = tf.text;
 			tf.text = "";
-			cursorPos = 0;
 			handleCommand(cmd);
 			if( !logTxt.visible ) bg.visible = false;
+			e.cancel = true;
 			return;
 		case Key.ESCAPE:
 			hide();
-			return;
 		case Key.UP:
 			if(logs.length == 0 || logIndex == 0) return;
 			if(logIndex == -1) {
@@ -198,22 +170,18 @@ class Console extends h2d.Sprite {
 			}
 			else logIndex--;
 			tf.text = logs[logIndex];
-			cursorPos = tf.text.length;
+			tf.cursorIndex = tf.text.length;
 		case Key.DOWN:
 			if(tf.text == curCmd) return;
 			if(logIndex == logs.length - 1) {
 				tf.text = curCmd;
-				cursorPos = tf.text.length;
+				tf.cursorIndex = tf.text.length;
 				logIndex = -1;
 				return;
 			}
 			logIndex++;
 			tf.text = logs[logIndex];
-			cursorPos = tf.text.length;
-		}
-		if( e.charCode != 0 ) {
-			tf.text = curCmd = tf.text.substr(0, cursorPos) + String.fromCharCode(e.charCode) + tf.text.substr(cursorPos);
-			cursorPos++;
+			tf.cursorIndex = tf.text.length;
 		}
 	}
 
@@ -366,6 +334,7 @@ class Console extends h2d.Sprite {
 			x = 0;
 			y = scene.height - height;
 			width = scene.width;
+			tf.maxWidth = width;
 			bg.tile.scaleToSize(width, height);
 		}
 		var log = logTxt;

+ 179 - 25
h2d/TextInput.hx

@@ -1,17 +1,21 @@
 package h2d;
 import hxd.Key in K;
 
+private typedef TextHistoryElement = { t : String, c : Int, sel : { start : Int, length : Int } };
 
 class TextInput extends Text {
 
-	public var interactive : h2d.Interactive;
 	public var cursorIndex : Int = -1;
 	public var cursorTile : h2d.Tile;
 	public var selectionTile : h2d.Tile;
 	public var cursorBlinkTime = 0.5;
 	public var inputWidth : Null<Int>;
 	public var selectionRange : { start : Int, length : Int };
+	public var canEdit = true;
 
+	public var backgroundColor(get, set) : Null<Int>;
+
+	var interactive : h2d.Interactive;
 	var cursorText : String;
 	var cursorX : Int;
 	var cursorXIndex : Int;
@@ -20,18 +24,55 @@ class TextInput extends Text {
 	var scrollX = 0;
 	var selectionPos : Int;
 	var selectionSize : Int;
+	var undo : Array<TextHistoryElement> = [];
+	var redo : Array<TextHistoryElement> = [];
+	var lastChange = 0.;
+	var maxHistorySize = 100;
 
 	public function new(font, ?parent) {
 		super(font, parent);
 		interactive = new h2d.Interactive(0, 0);
 		interactive.cursor = TextInput;
-		interactive.onClick = function(e:hxd.Event) {
-			interactive.focus();
-			cursorBlink = 0;
-			cursorIndex = textPos(e.relX, e.relY);
+		interactive.onPush = function(e:hxd.Event) {
+			onPush(e);
+			if( !e.cancel && e.button == 0 ) {
+				if( !interactive.hasFocus() ) {
+					e.kind = EFocus;
+					onFocus(e);
+					e.kind = EPush;
+					if( e.cancel ) return;
+					interactive.focus();
+				}
+				cursorBlink = 0;
+				var startIndex = textPos(e.relX, e.relY);
+				cursorIndex = startIndex;
+				selectionRange = null;
+
+				var pt = new h2d.col.Point();
+				var scene = getScene();
+				scene.startDrag(function(e) {
+					pt.x = e.relX;
+					pt.y = e.relY;
+					globalToLocal(pt);
+					var index = textPos(pt.x, pt.y);
+					if( index == startIndex )
+						selectionRange = null;
+					else if( index < startIndex )
+						selectionRange = { start : index, length : startIndex - index };
+					else
+						selectionRange = { start : startIndex, length : index - startIndex };
+					selectionSize = 0;
+					cursorIndex = index;
+					if( e.kind == ERelease || getScene() != scene )
+						scene.stopDrag();
+				});
+			}
 		};
 		interactive.onKeyDown = function(e:hxd.Event) {
-			if( cursorIndex < 0 )
+
+			onKeyDown(e);
+
+			if( e.cancel || cursorIndex < 0 )
 				return;
 
 			var oldIndex = cursorIndex;
@@ -48,25 +89,43 @@ class TextInput extends Text {
 				cursorIndex = 0;
 			case K.END:
 				cursorIndex = text.length;
-			case K.DELETE:
-				text = text.substr(0, cursorIndex) + text.substr(cursorIndex + 1 , text.length - (cursorIndex + 1));
+			case K.BACKSPACE, K.DELETE if( selectionRange != null ):
+				if( !canEdit ) return;
+				beforeChange();
+				cursorIndex = selectionRange.start;
+				var end = cursorIndex + selectionRange.length;
+				text = text.substr(0, cursorIndex) + text.substr(end);
+				selectionRange = null;
 				onChange();
+			case K.DELETE:
+				if( cursorIndex < text.length && canEdit ) {
+					beforeChange();
+					text = text.substr(0, cursorIndex) + text.substr(cursorIndex + 1);
+					onChange();
+				}
 			case K.BACKSPACE:
-				if( cursorIndex > 0 ) {
-					if( selectionRange == null ) {
-						text = text.substr(0, cursorIndex - 1) + text.substr(cursorIndex, text.length - cursorIndex);
-						cursorIndex--;
-					} else {
-						cursorIndex = selectionRange.start;
-						var end = cursorIndex + selectionRange.length;
-						text = text.substr(0, cursorIndex) + text.substr(end, text.length - end);
-						selectionRange = null;
-					}
+				if( cursorIndex > 0 && canEdit ) {
+					beforeChange();
+					text = text.substr(0, cursorIndex - 1) + text.substr(cursorIndex);
+					cursorIndex--;
 					onChange();
 				}
+			case K.Z if( K.isDown(K.CTRL) ):
+				if( undo.length > 0 && canEdit ) {
+					redo.push(curHistoryState());
+					setState(undo.pop());
+				}
+				return;
+			case K.Y if( K.isDown(K.CTRL) ):
+				if( redo.length > 0 && canEdit ) {
+					undo.push(curHistoryState());
+					setState(redo.pop());
+				}
+				return;
 			default:
-				if( e.charCode != 0 ) {
-					text = text.substr(0, cursorIndex) + String.fromCharCode(e.charCode) + text.substr(cursorIndex, text.length - cursorIndex);
+				if( e.charCode != 0 && canEdit ) {
+					beforeChange();
+					text = text.substr(0, cursorIndex) + String.fromCharCode(e.charCode) + text.substr(cursorIndex);
 					cursorIndex++;
 					onChange();
 				}
@@ -76,19 +135,73 @@ class TextInput extends Text {
 
 			if( K.isDown(K.SHIFT) && text == oldText ) {
 
+				if( cursorIndex == oldIndex ) return;
+
 				if( selectionRange == null )
 					selectionRange = oldIndex < cursorIndex ? { start : oldIndex, length : cursorIndex - oldIndex } : { start : cursorIndex, length : oldIndex - cursorIndex };
-				else {
-					// TODO
+				else if( oldIndex == selectionRange.start ) {
+					selectionRange.length += oldIndex - cursorIndex;
+					selectionRange.start = cursorIndex;
+				} else
+					selectionRange.length += cursorIndex - oldIndex;
+
+				if( selectionRange.length == 0 )
+					selectionRange = null;
+				else if( selectionRange.length < 0 ) {
+					selectionRange.start += selectionRange.length;
+					selectionRange.length = -selectionRange.length;
 				}
+				selectionSize = 0;
+
 			} else
 				selectionRange = null;
 
 		};
-		interactive.onFocusLost = function(_) cursorIndex = -1;
+		interactive.onFocusLost = function(e) {
+			cursorIndex = -1;
+			onFocusLost(e);
+		};
+
+		interactive.onKeyUp = function(e) onKeyUp(e);
+		interactive.onRelease = function(e) onRelease(e);
+		interactive.onFocus = function(e) onFocus(e);
+		interactive.onKeyUp = function(e) onKeyUp(e);
+		interactive.onClick = function(e) onClick(e);
+		interactive.onMove = function(e) onMove(e);
+		interactive.onOver = function(e) onOver(e);
+		interactive.onOut = function(e) onOut(e);
+
 		addChildAt(interactive, 0);
 	}
 
+	function setState(h:TextHistoryElement) {
+		text = h.t;
+		cursorIndex = h.c;
+		selectionRange = h.sel;
+		if( selectionRange != null )
+			cursorIndex = selectionRange.start + selectionRange.length;
+	}
+
+	function curHistoryState() : TextHistoryElement {
+		return { t : text, c : cursorIndex, sel : selectionRange == null ? null : { start : selectionRange.start, length : selectionRange.length } };
+	}
+
+	function beforeChange() {
+		var t = haxe.Timer.stamp();
+		if( t - lastChange < 1 ) {
+			lastChange = t;
+			return;
+		}
+		lastChange = t;
+		undo.push(curHistoryState());
+		redo = [];
+		while( undo.length > maxHistorySize ) undo.shift();
+	}
+
+	public function getSelectedText() {
+		return selectionRange == null ? null : text.substr(selectionRange.start, selectionRange.length);
+	}
+
 	override function set_font(f) {
 		super.set_font(f);
 		cursorTile = h2d.Tile.fromColor(0xFFFFFF, 1, font.size);
@@ -138,10 +251,10 @@ class TextInput extends Text {
 				selectionPos = calcTextWidth(text.substr(0, selectionRange.start));
 				selectionSize = calcTextWidth(text.substr(selectionRange.start, selectionRange.length));
 			}
-			selectionTile.dx += selectionPos - scrollX;
+			selectionTile.dx += selectionPos;
 			selectionTile.width += selectionSize;
 			emitTile(ctx, selectionTile);
-			selectionTile.dx -= selectionPos - scrollX;
+			selectionTile.dx -= selectionPos;
 			selectionTile.width -= selectionSize;
 		}
 
@@ -162,6 +275,44 @@ class TextInput extends Text {
 			ctx.clearRenderZone();
 	}
 
+	public function focus() {
+		interactive.focus();
+	}
+
+	public function hasFocus() {
+		return interactive.hasFocus();
+	}
+
+	public dynamic function onOut(e:hxd.Event) {
+	}
+
+	public dynamic function onOver(e:hxd.Event) {
+	}
+
+	public dynamic function onMove(e:hxd.Event) {
+	}
+
+	public dynamic function onClick(e:hxd.Event) {
+	}
+
+	public dynamic function onPush(e:hxd.Event) {
+	}
+
+	public dynamic function onRelease(e:hxd.Event) {
+	}
+
+	public dynamic function onKeyDown(e:hxd.Event) {
+	}
+
+	public dynamic function onKeyUp(e:hxd.Event) {
+	}
+
+	public dynamic function onFocus(e:hxd.Event) {
+	}
+
+	public dynamic function onFocusLost(e:hxd.Event) {
+	}
+
 	public dynamic function onChange() {
 	}
 
@@ -173,4 +324,7 @@ class TextInput extends Text {
 		interactive.visible = true;
 	}
 
+	function get_backgroundColor() return interactive.backgroundColor;
+	function set_backgroundColor(v) return interactive.backgroundColor = v;
+
 }

+ 20 - 6
samples/Input.hx

@@ -1,25 +1,39 @@
 class Input extends hxd.App {
 
 	var input : h2d.TextInput;
+	var debug : h2d.Text;
 
 	override function init() {
 
-		input = new h2d.TextInput(hxd.res.DefaultFont.get(), s2d);
-		input.interactive.backgroundColor = 0x80808080;
+		engine.backgroundColor = 0x202020;
+
+		var font = hxd.res.DefaultFont.get();
+		var console = new h2d.Console(font, s2d);
+		console.addCommand("hello", "Prints the correct answer", [], function() console.log("World", 0xFF00FF));
+
+		debug = new h2d.Text(font, s2d);
+		debug.x = debug.y = 5;
+
+		input = new h2d.TextInput(font, s2d);
+		input.backgroundColor = 0x80808080;
 		input.inputWidth = 100;
 
-		input.text = "Click to edit";
+		input.text = "Click to édit";
 		input.textColor = 0xAAAAAA;
 
 		input.scale(2);
 		input.x = input.y = 50;
 
-		input.interactive.onFocus = function(_) {
-			//input.text = "";
+		input.onFocus = function(_) {
 			input.textColor = 0xFFFFFF;
-			input.interactive.onFocus = function(_) {};
 		}
+		input.onFocusLost = function(_) {
+			input.textColor = 0xAAAAAA;
+		}
+	}
 
+	override function update(dt:Float) {
+		debug.text = "Cursor: " + input.cursorIndex + ", Sel: " + input.getSelectedText();
 	}
 
 	static function main() {