changeset 99:5de5810e3516

Implemented an editable TextContent widget; it's now possible to edit text options using the GUI. The widget supports moving the text entry-point using arrows and home/end, but there's no visual indicator or edit-point setting using the mouse.
author Diggory Hardy <diggory.hardy@gmail.com>
date Fri, 14 Nov 2008 12:44:32 +0000
parents 49e7cfed4b34
children 0ea4a3e651ae
files data/conf/gui.mtt mde/gui/WidgetManager.d mde/gui/content/Content.d mde/gui/renderer/IRenderer.d mde/gui/renderer/SimpleRenderer.d mde/gui/widget/Floating.d mde/gui/widget/Ifaces.d mde/gui/widget/TextWidget.d mde/gui/widget/Widget.d mde/gui/widget/createWidget.d mde/gui/widget/layout.d mde/gui/widget/miscContent.d mde/gui/widget/textContent.d mde/input/Input.d
diffstat 14 files changed, 307 insertions(+), 119 deletions(-) [+]
line wrap: on
line diff
--- a/data/conf/gui.mtt	Wed Nov 12 13:18:51 2008 +0000
+++ b/data/conf/gui.mtt	Fri Nov 14 12:44:32 2008 +0000
@@ -14,8 +14,8 @@
 <WidgetData|optDesc={0:[0x4020, 2, 0xaf6000]}>
 <WidgetData|optVal={0:[0x6030]}>
 <WidgetData|optSep={0:[0x21, 0xff],1:["="]}>
-<WidgetData|floating={0:[0x8200,1,6,14],1:["text","button","blank"]}>
-<WidgetData|text={0:[0x21,0xE0E000],1:["Floating text"]}>
+<WidgetData|floating={0:[0x8200,13,6,14],1:["text","button","blank"]}>
+<WidgetData|text={0:[0x4032]}>
 <WDims|content=[736,221,272,79]>
 <WDims|root=[6,736,50,6,580,6]>
 <WDims|floating=[25,0,103,27,572,26,74,74,242,128,168,93]>
--- a/mde/gui/WidgetManager.d	Wed Nov 12 13:18:51 2008 +0000
+++ b/mde/gui/WidgetManager.d	Fri Nov 14 12:44:32 2008 +0000
@@ -102,8 +102,17 @@
         }
         IChildWidget widg = child.getWidget (cast(wdabs)cx,cast(wdabs)cy);
         //debug logger.trace ("Click on {}", widg);
-        if (widg !is null)
-            widg.clickEvent (cast(wdabs)cx,cast(wdabs)cy,b,state);
+	if (keyFocus && keyFocus !is widg) {
+	    keyFocus.keyFocusLost;
+	    keyFocus = null;
+	    imde.input.setLetterCallback (null);
+	}
+        if (widg !is null) {
+	    if (widg.clickEvent (cast(wdabs)cx,cast(wdabs)cy,b,state) & 1) {
+		keyFocus = widg;
+		imde.input.setLetterCallback (&widg.keyEvent);
+	    }
+	}
     }
     
     /** For mouse motion events.
@@ -182,13 +191,22 @@
         child.setPosition (0,0);
     }
     
+    void preSave () {
+	if (keyFocus) {
+	    keyFocus.keyFocusLost;
+	    keyFocus = null;
+	    imde.input.setLetterCallback (null);
+	}
+    }
+    
 private:
+    IRenderer rend;
+    wdim w,h;       // area available to the widgets
+    wdim mw,mh;     // minimal area available to the widgets
     // callbacks indexed by their frame pointers:
     bool delegate(wdabs cx, wdabs cy, ubyte b, bool state) [void*] clickCallbacks;
     void delegate(wdabs cx, wdabs cy) [void*] motionCallbacks;
-    IRenderer rend;
-    wdim w,h;       // area available to the widgets
-    wdim mw,mh;     // minimal area available to the widgets
+    IChildWidget keyFocus;	// widget receiving keyboard input when non-null
 }
 
 
@@ -286,10 +304,10 @@
     *  name = Design to load. If null, the default will be loaded.
     */
     void loadDesign (char[] name = null) {
-        if (changes !is null)
+        if (changes !is null)	// A design was previously loaded
             save;       // own lock
             
-            mutex.lock;
+	mutex.lock;
         scope(exit) mutex.unlock;
         
         // Load data (loadData tests if it's already loaded first):
@@ -315,8 +333,7 @@
             changesDS = new mt.DataSet;
         
         mt.IDataSection* q = name in changesDS.sec;
-        if (q && ((changes = cast(WidgetDataChanges) *q) !is null)) {}
-        else {
+        if (!q || ((changes = cast(WidgetDataChanges) *q) is null)) {
             changes = new WidgetDataChanges (curData);
             changesDS.sec[name] = changes;
         }
@@ -329,6 +346,8 @@
     * 
     * Is run when the manager is destroyed, but could be run at other times too. */
     void save () {
+	preSave;
+	
         mutex.lock;
         scope(exit) mutex.unlock;
         
@@ -454,20 +473,24 @@
     */
     void createRootWidget();
     
-    protected:
-        final char[] fileName;
-        char[] defaultDesign;       // The design specified in the file header.
-        char[] rendName;    // Name of renderer; for saving and creating renderers
-        
-        // Loaded data, indexed by design name. May not be loaded for all gui designs:
-        scope WidgetDataSet[char[]] data;
-        private bool allLoaded = false;  // applies to data
-        WidgetDataSet curData;      // Current data
-        WidgetDataChanges changes;  // Changes for the current design.
-        scope mt.DataSet changesDS; // changes and sections from user file (used for saving)
-        bool loadUserFile = true;   // still need to load user file for saving?
-        
-        scope IChildWidget child;   // The primary widget.
-        
-        Mutex mutex;    // lock on methods for use outside the package.
+    /** Called before saving (usually when the GUI is about to be destroyed, although not
+     *  necessarily). */
+    void preSave () {}
+    
+protected:
+    final char[] fileName;
+    char[] defaultDesign;		// The design specified in the file header.
+    char[] rendName;			// Name of renderer; for saving and creating renderers
+    
+    // Loaded data, indexed by design name. May not be loaded for all gui designs:
+    scope WidgetDataSet[char[]] data;
+    private bool allLoaded = false;	// applies to data
+    WidgetDataSet curData;		// Current data
+    WidgetDataChanges changes;		// Changes for the current design.
+    scope mt.DataSet changesDS;		// changes and sections from user file (used for saving)
+    bool loadUserFile = true;		// still need to load user file for saving?
+    
+    scope IChildWidget child;		// The primary widget.
+    
+    Mutex mutex;			// lock on methods for use outside the package.
 }
--- a/mde/gui/content/Content.d	Wed Nov 12 13:18:51 2008 +0000
+++ b/mde/gui/content/Content.d	Fri Nov 14 12:44:32 2008 +0000
@@ -20,6 +20,7 @@
 //FIXME: efficient conversions? Need to dup result when formatting a string anyway?
 import Int = tango.text.convert.Integer;
 import Float = tango.text.convert.Float;
+import derelict.sdl.keysym;
 
 debug {
     import tango.util.log.Log : Log, Logger;
@@ -151,6 +152,7 @@
     this (char[] symbol, char[] val = null) {
         symb = symbol;
         v = val;
+	pos = v.length;
     }
     
     /** Adds cb to the list of callback functions called when the value is changed. Returns this. */
@@ -177,9 +179,62 @@
     }
     alias opCall opCast;
     
+    /** Acts on a keystroke and returns the new value.
+     *
+     * Supports one-line editing: left/right, home/end, backspace/delete. */
+    char[] keyStroke (ushort sym, char[] i) {
+	debug assert (i.length, "TextContent.keyStroke: no value (??)");	// impossible?
+	char k = *i;
+	if (k > 0x20) {
+	    if (k == 0x7f) {		// delete
+		size_t p = pos;
+		if (p < v.length) ++p;
+		while (p < v.length && (v[p] & 0x80) && !(v[p] & 0x40))
+		    ++p;
+		v = v[0..pos] ~ v[p..$];
+	    } else {			// insert character
+		char[] tail = v[pos..$];
+		v.length = v.length + i.length;
+		size_t npos = pos+i.length;
+		if (tail) v[npos..$] = tail.dup;	// cannot assign with overlapping ranges
+		    v[pos..npos] = i;
+		pos = npos;
+	    }
+	} else {			// use sym; many keys output 0
+	    if (sym == SDLK_BACKSPACE) {	// backspace; k == 0x8
+		char[] tail = v[pos..$];
+		if (pos) --pos;
+		while (pos && (v[pos] & 0x80) && !(v[pos] & 0x40))
+		    --pos;
+		v = v[0..pos] ~ tail;
+	    } else if (sym == SDLK_LEFT) {
+		if (pos) --pos;
+		while (pos && (v[pos] & 0x80) && !(v[pos] & 0x40))
+		    --pos;
+	    } else if (sym == SDLK_RIGHT) {
+		if (pos < v.length) ++pos;
+		while (pos < v.length && (v[pos] & 0x80) && !(v[pos] & 0x40))
+		    ++pos;
+	    } else if (sym == SDLK_HOME || sym == SDLK_UP) {
+		pos = 0;
+	    } else if (sym == SDLK_END || sym == SDLK_DOWN) {
+		pos = v.length;
+	    } else
+		debug logger.trace ("Symbol: {}", sym);
+	}
+	return v;
+    }
+    
+    /// Gives all callbacks the modified value
+    void endEdit () {
+	foreach (cb; cngCb)
+	    cb(symb, v);
+    }
+    
     char[] v;
 protected:
     char[] symb;
+    size_t pos;		// editing position; used by keyStroke
     void delegate (char[],char[])[] cngCb;
 }
 
--- a/mde/gui/renderer/IRenderer.d	Wed Nov 12 13:18:51 2008 +0000
+++ b/mde/gui/renderer/IRenderer.d	Fri Nov 14 12:44:32 2008 +0000
@@ -87,10 +87,14 @@
      * NOTE: currently inflexible. Could use (function) pointers, class interfaces or struct
      * interfaces when available to allow flexibility. */
     struct TextAdapter {
-        void set (char[] c, int col) {
+        void setText (char[] c) {
             content = c;
-            colour = Colour (col);
+	    textCache.cacheVer = -1;	// force update
         }
+	
+	void setColour (int col = 0xFFFFFF) {
+	    colour = Colour (col);
+	}
         
         void getDimensions (out wdsize w, out wdsize h) {
             font.updateBlock (content, textCache);
--- a/mde/gui/renderer/SimpleRenderer.d	Wed Nov 12 13:18:51 2008 +0000
+++ b/mde/gui/renderer/SimpleRenderer.d	Fri Nov 14 12:44:32 2008 +0000
@@ -133,7 +133,8 @@
     TextAdapter getAdapter (char[] text, int col) {
         TextAdapter a;
         a.font = defaultFont;
-        a.set (text, col);
+        a.content = text;
+	a.colour = Colour (col);
         return a;
     }
     
--- a/mde/gui/widget/Floating.d	Wed Nov 12 13:18:51 2008 +0000
+++ b/mde/gui/widget/Floating.d	Fri Nov 14 12:44:32 2008 +0000
@@ -149,8 +149,8 @@
         return this;    // no match
     }
     
-    void clickEvent (wdabs cx, wdabs cy, ubyte b, bool state) {
-        if (event > subWidgets.length) return;
+    int clickEvent (wdabs cx, wdabs cy, ubyte b, bool state) {
+        if (event > subWidgets.length) return 0;
         if (b == 1 && state == true) {
             active = event;
             with (sWData[active]) {
@@ -180,6 +180,7 @@
                 }
             }
         }
+	return 0;
     }
     
 protected:
--- a/mde/gui/widget/Ifaces.d	Wed Nov 12 13:18:51 2008 +0000
+++ b/mde/gui/widget/Ifaces.d	Fri Nov 14 12:44:32 2008 +0000
@@ -103,8 +103,6 @@
      * gui. */
     void addMotionCallback (void delegate (wdabs cx, wdabs cy) dg);
     
-    // FIXME: keyboard callback (letter only, for text input? Also used for setting keybindings though...)
-    
     /** Remove all event callbacks on this widget (according to the delegate's .ptr). */
     // Note: don't try to pass a reference and cast to void* in the function; it's a different address.
     void removeCallbacks (void* frame);
@@ -236,13 +234,23 @@
      * Note: use global coordinates (x,y) not coordinates relative to the widget. */
     IChildWidget getWidget (wdim x, wdim y);
     
-    /** Receive a mouse click event.
+    /** Receive a mouse click event at cx,cy from button b (1-5 correspond to L,M,B, wheel up,down)
+     * which is a down-click if state is true.
+     *
+     * Widget may assume coordinates are on the widget (caller must check).
      *
-     * See mde.input.input.Input.MouseClickCallback for parameters. However, cx and cy are adjusted
-     * to the Widget's local coordinates.
+     * The return value has the following flags: 1 to request keyboard input. */
+    int clickEvent (wdabs cx, wdabs cy, ubyte b, bool state);
+    
+    /** Receives keyboard events when requested.
      *
-     * Widget may assume coordinates are on the widget (caller must check). */
-    void clickEvent (wdabs cx, wdabs cy, ubyte b, bool state);
+     * Params:
+     *	sym	SDLKey key sym, useful for keys with no character code such as arrow keys
+     *	letter	The character input, in UTF-8 */
+    void keyEvent (ushort sym, char[] letter);
+    
+    /** Called when keyboard input focus is lost. */
+    void keyFocusLost ();
 //END Events
     
     /** Draw, using the stored values of x and y.
--- a/mde/gui/widget/TextWidget.d	Wed Nov 12 13:18:51 2008 +0000
+++ b/mde/gui/widget/TextWidget.d	Fri Nov 14 12:44:32 2008 +0000
@@ -76,7 +76,6 @@
 class ContentLabelWidget : ATextWidget
 {
     this (IWidgetManager mgr, widgetID id, WidgetData data, IContent c) {
-        debug assert (c, "content is null (code error)");
         WDCheck (data, 3, 0);
         content = c;
         if (!content) throw new ContentException ();
--- a/mde/gui/widget/Widget.d	Wed Nov 12 13:18:51 2008 +0000
+++ b/mde/gui/widget/Widget.d	Fri Nov 14 12:44:32 2008 +0000
@@ -123,7 +123,13 @@
     }
     
     /* Dummy event method (suitable for all widgets which don't respond to events). */
-    void clickEvent (wdabs cx, wdabs cy, ubyte b, bool state) {}
+    int clickEvent (wdabs cx, wdabs cy, ubyte b, bool state) {
+	return 0;
+    }
+    
+    /* Dummy functions: suitable for widgets with no text input. */
+    void keyEvent (ushort, char[]) {}
+    void keyFocusLost () {}
 //END Events
     
     /* Basic draw method: draw the background (all widgets should do this). */
@@ -221,13 +227,14 @@
     }
     
     /// Handles the down-click
-    void clickEvent (wdabs, wdabs, ubyte b, bool state) {
+    int clickEvent (wdabs, wdabs, ubyte b, bool state) {
         if (b == 1 && state == true) {
             pushed = true;
             mgr.requestRedraw;
             mgr.addClickCallback (&clickWhilePushed);
             mgr.addMotionCallback (&motionWhilePushed);
         }
+	return 0;
     }
     
     /// Called when a mouse click event occurs while held; handles up-click
--- a/mde/gui/widget/createWidget.d	Wed Nov 12 13:18:51 2008 +0000
+++ b/mde/gui/widget/createWidget.d	Fri Nov 14 12:44:32 2008 +0000
@@ -28,6 +28,7 @@
 import mde.gui.widget.miscWidgets;
 import mde.gui.widget.TextWidget;
 import mde.gui.widget.miscContent;
+import mde.gui.widget.textContent;
 import mde.gui.widget.Floating;
 import tango.util.log.Log : Log, Logger;
 
@@ -82,34 +83,35 @@
 private:
 /// Widget types.
 enum WIDGET_TYPE : int {
-    FUNCTION                = 0x2000,   // Function called instead of widget created (no "Widget" appended to fct name)
-    TAKES_CONTENT           = 0x4000,   // Flag indicates widget's this should be passed an IContent reference.
-    PARENT                  = 0x8000,   // widget can have children; not used by code (except in data files)
+    FUNCTION		= 0x2000,   // Function called instead of widget created (no "Widget" appended to fct name)
+    TAKES_CONTENT	= 0x4000,   // Flag indicates widget's this should be passed an IContent reference.
+    PARENT		= 0x8000,   // widget can have children; not used by code (except in data files)
     
     // Use widget names rather than usual capitals convention
-    Unnamed                 = 0x0,      // Only for use by widgets not created with createWidget
+    Unnamed		= 0x0,      // Only for use by widgets not created with createWidget
     
     // blank: 0x1
-    FixedBlank              = 0x1,
-    SizableBlank            = 0x2,
-    Debug                   = 0xF,
+    FixedBlank		= 0x1,
+    SizableBlank	= 0x2,
+    Debug		= 0xF,
     
     // buttons: 0x10
-    Button                  = 0x10,
+    Button		= 0x10,
     
     // labels: 0x20
-    ContentLabel	    = TAKES_CONTENT | 0x20,
-    TextLabel               = 0x21,
+    ContentLabel	= TAKES_CONTENT | 0x20,
+    TextLabel		= 0x21,
     
     // content editables: 0x30
-    editContent             = FUNCTION | TAKES_CONTENT | 0x30,
-    DisplayContent          = TAKES_CONTENT | 0x30,
-    BoolContent             = TAKES_CONTENT | 0x31,
+    editContent		= FUNCTION | TAKES_CONTENT | 0x30,
+    DisplayContent	= TAKES_CONTENT | 0x30,
+    BoolContent		= TAKES_CONTENT | 0x31,
+    TextContent		= TAKES_CONTENT | 0x32,
     
-    GridLayout              = TAKES_CONTENT | PARENT | 0x100,
-    TrialContentLayout      = PARENT | 0x110,
+    GridLayout		= TAKES_CONTENT | PARENT | 0x100,
+    TrialContentLayout	= PARENT | 0x110,
     
-    FloatingArea            = PARENT | 0x200,
+    FloatingArea	= PARENT | 0x200,
 }
 
 //const char[][int] WIDGET_NAMES;
@@ -124,6 +126,7 @@
         "ContentLabel",
         "DisplayContent",
         "BoolContent",
+	"TextContent",
         "editContent",
         "TrialContentLayout",
         "FloatingArea",
--- a/mde/gui/widget/layout.d	Wed Nov 12 13:18:51 2008 +0000
+++ b/mde/gui/widget/layout.d	Fri Nov 14 12:44:32 2008 +0000
@@ -247,7 +247,7 @@
     }
     
     // Resizing columns & rows
-    void clickEvent (wdabs cx, wdabs cy, ubyte b, bool state) {
+    int clickEvent (wdabs cx, wdabs cy, ubyte b, bool state) {
         debug scope (failure)
                 logger.warn ("clickEvent: failure");
         if (b == 1 && state == true) {
@@ -257,7 +257,7 @@
             
             // find col/row's resizeD & resizeU
             if (col.findResizeCols (cx - x) && row.findResizeCols (cy - y))
-                return;		// unable to resize
+                return 0;		// unable to resize
             
             dragX = cx;
             dragY = cy;
@@ -265,6 +265,7 @@
             mgr.addClickCallback (&endCallback);
             mgr.addMotionCallback (&resizeCallback);
         }
+	return 0;
     }
     
     void draw () {
--- a/mde/gui/widget/miscContent.d	Wed Nov 12 13:18:51 2008 +0000
+++ b/mde/gui/widget/miscContent.d	Fri Nov 14 12:44:32 2008 +0000
@@ -16,6 +16,7 @@
 /** Some content widgets. */
 module mde.gui.widget.miscContent;
 
+import mde.gui.widget.textContent;
 import mde.gui.widget.Widget;
 import mde.gui.widget.TextWidget;
 import mde.gui.exception;
@@ -27,6 +28,8 @@
 IChildWidget editContent (IWidgetManager mgr, widgetID id, WidgetData data, IContent c) {
     if (cast(BoolContent) c)
         return new BoolContentWidget(mgr,id,data,c);
+    else if (cast(TextContent) c)
+	return new TextContentWidget(mgr,id,data,c);
     else        // generic uneditable option
         return new DisplayContentWidget(mgr,id,data,c);
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mde/gui/widget/textContent.d	Fri Nov 14 12:44:32 2008 +0000
@@ -0,0 +1,54 @@
+/* LICENSE BLOCK
+Part of mde: a Modular D game-oriented Engine
+Copyright © 2007-2008 Diggory Hardy
+
+This program is free software: you can redistribute it and/or modify it under the terms
+of the GNU General Public License as published by the Free Software Foundation, either
+version 2 of the License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program.  If not, see <http://www.gnu.org/licenses/>. */
+
+/** Content widgets based on a text block. */
+module textContent;
+
+import mde.gui.widget.Widget;
+import mde.gui.widget.TextWidget;
+import mde.gui.exception;
+import mde.gui.renderer.IRenderer;
+
+import mde.gui.content.Content;
+
+
+class TextContentWidget : ATextWidget
+{
+    this (IWidgetManager mgr, widgetID id, WidgetData data, IContent c) {
+        WDCheck(data, 1);
+        content = cast(TextContent) c;
+        if (!content) content = new TextContent (null, null);
+	    //throw new ContentException ();
+        adapter = mgr.renderer.getAdapter (content.toString(0));
+        super (mgr, id, data);
+    }
+    
+    /** On click, request keyboard input. */
+    int clickEvent (wdabs, wdabs, ubyte, bool state) {
+	return 1;	// get keyboard input via keyEvent
+    }
+    
+    void keyEvent (ushort s, char[] i) {
+	adapter.setText (content.keyStroke (s, i));
+	adapter.getDimensions (mw, mh);	// FIXME: only passively change size: next resize will see new minimal size
+	mgr.requestRedraw;
+    }
+    void keyFocusLost () {
+	content.endEdit;	// update other users of content relying on callbacks
+    }
+    
+protected:
+    TextContent content;
+}
--- a/mde/input/Input.d	Wed Nov 12 13:18:51 2008 +0000
+++ b/mde/input/Input.d	Fri Nov 14 12:44:32 2008 +0000
@@ -25,58 +25,79 @@
 
 // sdl imports
 import derelict.sdl.events;
+import derelict.sdl.keyboard;
 import derelict.sdl.types;	// only SDL_PRESSED
 import derelict.sdl.joystick;	// SDL_HAT_*
 
+import Utf = tango.text.convert.Utf;
 import tango.util.log.Log : Log, Logger;
 
-/** Class encapsulating all input functionality.
+/**************************************************************************************************
+ * Class encapsulating all input functionality.
+ *
+ * This class has several modes which affect output: interaction mode (default), text input mode,
+ * mouse gui mode and axis/button binding modes.
+ *
+ * TODO: Gui mode and button capture and axis capture modes for key binding, disabling all
+ * other modes (except gui-type mouse info?).
+ * TODO: Possible revisions: remove by-index lookup, only providing callbacks?
+ * TODO: Make callbacks send the time of the event?
+ * TODO: Adjusters, e.g. double-press, hold/click differences. Axis output: via short or double?
+ * TODO: add an Axis1Callback similar to getAxis1? Or remove getAxis1 and provide a conversion function?
+ * TODO: allow callbacks to be removed. Currently not needed.
+ * TODO: modifiers in text-input mode: shortcut handling? Global shortcuts - either mode?
  *
- * The following methods are provided for Gui mouse input:
+ * The primary mode is the interaction mode, mapping each button and axis to a configurable index,
+ * and allowing event callback functions to be bound per index as well as allowing the state to be
+ * looked up directly.
+ * ---
+ * // For keyboard, joystick and mouse button input
+ * bool getButton (inputID id);
+ * void addButtonCallback (inputID id, ButtonCallback dg);  // callback receives both up and down events
+ *
+ * // For joystick axis input
+ * short getAxis (inputID id);      // range: -32767 .. 32767
+ * double getAxis1 (inputID id);    // range: -1.0 .. 1.0
+ * void addAxisCallback (inputID id, AxisCallback dg);
+ *
+ * // For mouse (and joystick ball) relative motion input
+ * void getRelMotion (inputID id, out double x, out double y);
+ * void addRelMotionCallback (inputID id, RelMotionCallback dg);
+ * ---
+ *
+ * The keyboard can be put in text input mode, disabling interaction-mode keyboard access and
+ * providing a callback called on each letter press with it's UTF-8 code. Setting a LetterCallback
+ * activates text input mode and removing it disables this mode; only one may be active at once.
+ * ---
+ * void setLetterCallback (LetterCallback dg);
+ * ---
+ *
+ * Mouse input can be recieved via gui-oriented click/coordinate callbacks in both interaction
+ * mode and gui mode, however interaction-mode button and relative motion input is not received in
+ * gui mode.
  * ---
  * void getMouseScreenPos (out uint x, out uint y);
  * void addMouseClickCallback (MouseClickCallback dg);
  * void addMouseMotionCallback (MouseMotionCallback dg);
  * ---
  *
- * The following methods are provided for mouse (and joystick ball) relative motion input:
- * ---
- * void getRelMotion (inputID id, out real x = 0.0, out real y = 0.0);
- * void addRelMotionCallback (inputID id, RelMotionCallback dg);
- * ---
- *
- * The following methods are provided for joystick axis input:
- * ---
- * short getAxis (inputID id);
- * real getAxis1 (inputID id);
- * void addAxisCallback (inputID id, AxisCallback dg);
- * ---
- *
- * The following methods are provided for keyboard, joystick and mouse button input:
- * ---
- * bool getButton (inputID id);
- * void addButtonCallback (inputID id, ButtonCallback dg)
- * ---
- *
  * The following methods are provided for setup & posting events:
  * ---
- * bool opCall (ref SDL_Event event);
- * void frameReset ();
- * void loadConfig (char[] profile = "Default");
+ * bool opCall (ref SDL_Event event);   // Handles an event, making all the above work
+ * void frameReset ();  // Needs to be called once per frame for correct relative input
+ * void loadConfig (char[] profile = "Default");    // Configuration for interaction-mode indexes
  * ---
  ***************************************************/
-// FIXME: remove getMouseScreenPos (no use)?
-// FIXME: add an Axis1Callback similar to getAxis1? Or remove getAxis1 and provide a conversion
-// function?
 class Input
 {
     /// Typedef for all indexes (type is uint).
     typedef uint				inputID;
     alias void delegate(inputID, bool)		ButtonCallback;
     alias void delegate(inputID, short)		AxisCallback;
-    alias void delegate(inputID, real,real)	RelMotionCallback;
-    alias void delegate(ushort, ushort, ubyte, bool)    MouseClickCallback;
-    alias void delegate(ushort, ushort)                 MouseMotionCallback;
+    alias void delegate(inputID, double,double)	RelMotionCallback;
+    alias void delegate(ushort, ushort, ubyte, bool)	MouseClickCallback;
+    alias void delegate(ushort, ushort)		MouseMotionCallback;
+    alias void delegate(ushort, char[])		LetterCallback;
 
     /** Get key status at this ID.
     *
@@ -97,38 +118,27 @@
     }
     /** Get axis status at this ID.
     *
-    * Returns: value (real; range roughly -1.0 .. 1.0) or 0 if no value at this ID. */
-    real getAxis1 (inputID id) {
+    * Returns: value (double; range roughly -1.0 .. 1.0) or 0 if no value at this ID. */
+    double getAxis1 (inputID id) {
         short* retp = id in axis;
         if (retp) return (*retp) * 3.0518509475997192e-05;
         else return 0.0;
     }
-            
+    
     /** Get the relative motion of the mouse or a joystick ball (since last frameReset() call).
     *
-    * Future: Converts to a real via sensitivity settings (defaults may be set and overriden per item).
+    * Future: Converts to a double via sensitivity settings (defaults may be set and overriden per item).
     *
     * To avoid confusion over the ID here, the idea is for the input-layer upward to support
     * multiple mice, in case future platforms do.
     * Also joystick balls (supported by SDL) can be used in the same way as a mouse for relative
     * positions. */
-    void getRelMotion (inputID id, out real x = 0.0, out real y = 0.0) {
+    void getRelMotion (inputID id, out double x = 0.0, out double y = 0.0) {
         RelPair* rp = id in relMotion;
         if (rp) {
             x = rp.x;	y = rp.y;
         }
     }
-    /** Get mouse pointer position in screen coordinates.
-    *
-    * Window managers only support one mouse, so there will only be one screen coordinate.
-    * Unlike nearly everything else, this is not configurable.
-    *
-    * Also see addMouseMotionCallback. */
-    void getMouseScreenPos (out uint x, out uint y) {
-        x = mouse_x;	y = mouse_y;
-    }
-    // /// Is this modifier on?
-    //bool modifierStatus (inputID id);
 
     /** Adds a callback delegate for key events (both DOWN and UP) with this ID.
     *
@@ -176,6 +186,24 @@
     void addMouseMotionCallback (MouseMotionCallback dg) {
         mouseMotionCallbacks ~= dg;
     }
+    
+    /** Sets a callback delegate to recieve key presses as a Utf-8 char[].
+    *
+    * Since it is normal to type into only one location at once, setting a new LetterCallback
+    * removes the last set one (however active ButtonCallbacks will still receive events).
+    * Supplying a null delegate will turn off the slight overhead of unicode conversion.
+    * 
+    * The char[] received by the delegate must be copied and not stored or edited directly. */
+    void setLetterCallback (LetterCallback dg = null) {
+        if (dg) {
+	    SDL_EnableUNICODE (1);
+	    SDL_EnableKeyRepeat(SDL_DEFAULT_REPEAT_DELAY, SDL_DEFAULT_REPEAT_INTERVAL);
+	} else {
+	    SDL_EnableUNICODE (0);
+	    SDL_EnableKeyRepeat(0, SDL_DEFAULT_REPEAT_INTERVAL);
+	}
+        letterCallback = dg;
+    }
 
     /** Feed an SDL_Event struct (only uses if it's a key, mouse or joystick event).
     *
@@ -207,9 +235,6 @@
                 break;
             
             case SDL_MOUSEMOTION:
-                mouse_x = event.motion.x;
-                mouse_y = event.motion.y;
-                
                 foreach (dg; mouseMotionCallbacks) {
                     try
                         dg (event.motion.x, event.motion.y);
@@ -229,7 +254,10 @@
         switch (event.type) {
             // Keyboard events:
             case SDL_KEYDOWN:
+                if (letterCallback)
+		    letterCallback (event.key.keysym.sym, Utf.toString ([cast(wchar)event.key.keysym.unicode], cast(char[])utfBuf));
             case SDL_KEYUP:
+		if (letterCallback) break;	// text input mode; no keyboard input from mappings
                 outQueue[]* p = (Config.B.SDLKEY | event.key.keysym.sym) in config.button;
                 if (p) foreach (outQueue q; *p) {
                     bEvent (this, event.key.state == SDL_PRESSED, readOutQueue(q));
@@ -370,8 +398,8 @@
     }
     
     struct RelPair {	// for mouse/joystick ball motion
-        real x, y;
-        static RelPair opCall (real a, real b) {
+        double x, y;
+        static RelPair opCall (double a, double b) {
             RelPair ret;
             ret.x = a;	ret.y = b;
             return ret;
@@ -382,12 +410,12 @@
     
     static Logger logger;
 
-    Config config;			// Configuration
+    Config config;		// Configuration
+    char[6] utfBuf;		// Buffer for Utf.toString; reallocates if less than 5.
     
-    bool[inputID] button;		// Table of button states
-    short[inputID] axis;		// Table of axes states
-    ushort mouse_x, mouse_y;		// Current screen coords of the window manager mouse
-    RelPair[inputID] relMotion;		// Table of relative mouse / joystick ball motions
+    bool[inputID] button;	// Table of button states
+    short[inputID] axis;	// Table of axes states
+    RelPair[inputID] relMotion;	// Table of relative mouse / joystick ball motions
     
     // NOTE: currently no means of removal
     ButtonCallback[][inputID]	buttonCallbacks;
@@ -395,7 +423,8 @@
     RelMotionCallback[][inputID] relMotionCallbacks;
     MouseClickCallback[]	mouseClickCallbacks;
     MouseMotionCallback[]	mouseMotionCallbacks;
-        
+    LetterCallback              letterCallback;
+    
     //BEGIN Event stream functionality
     /* This section contains functions called on an event, which may modify the event (adjuster
     * functions), and finally output to one (or more) of the state tables (the event stream).
@@ -578,7 +607,7 @@
             assert (x == (counters[3] ? 0 : 32767));
             counters[3] += 1;
         });
-        ut.addRelMotionCallback (0x11F0, delegate void(inputID id, real x, real y) {
+        ut.addRelMotionCallback (0x11F0, delegate void(inputID id, double x, double y) {
             assert (x == 14.0);
             assert (y == -1.0);
             counters[4] += 1;
@@ -660,7 +689,7 @@
         assert (ut.getAxis(0x21F0) == -32767);
         assert (ut.getAxis1(0x22F0) == 0.0);
             
-        real s,t;
+        double s,t;
         ut.getRelMotion(0x10F0, s,t);
         assert (s == 14.0);
         assert (t == -1.0);