Changeset 193

Show
Ignore:
Timestamp:
07/05/10 23:15:46 (2 years ago)
Author:
JoeCoder
Message:

More work on editable text
Added onClick event.
Fixed an event propagation bug.
Added ArrayBuilder?.splice

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • trunk/build/buildyage.d

    r192 r193  
    1010 */ 
    1111 
    12 const char[] app = "demo2"; // set which program to build against yage. 
     12const char[] app = "demo1"; // set which program to build against yage. 
    1313//const char[] app = "tests/integration/main.d"; 
    1414 
     
    427427        if (co.run) 
    428428        {   System.execute("./" ~ co.of, run_args); 
    429             version(Windows) // give dmd windows time to release the lock. 
     429            version(Windows) // Hack: give dmd windows time to release the lock. 
    430430                if (compiler=="dmd") 
    431431                    System.sleep(.1); 
  • trunk/src/demo1/main.d

    r189 r193  
    159159         
    160160        scene.ship.keyDown(key); 
    161         return true; 
     161        return false; 
    162162    }; 
    163163     
    164164    view.onKeyUp = (Surface self, int key, int modifier){ 
    165165        scene.ship.keyUp(key); 
    166         return true; 
    167     }; 
    168      
    169     view.onMouseDown = (Surface self, byte buttons, Vec2i coordinates, char[] href){ 
     166        return false; 
     167    }; 
     168     
     169    view.onMouseDown = (Surface self, byte buttons, Vec2i coordinates){ 
    170170        scene.ship.acceptInput = !scene.ship.acceptInput; 
    171171        self.grabMouse(scene.ship.acceptInput); 
    172         return true; 
    173     }; 
    174     view.onMouseMove = (Surface self, byte buttons, Vec2i rel, char[] href){ 
     172        return false; 
     173    }; 
     174    view.onMouseMove = (Surface self, byte buttons, Vec2i rel){ 
    175175        if(scene.ship.acceptInput) 
    176176            scene.ship.input.mouseDelta += rel; 
    177         return true; 
     177        return false; 
    178178    }; 
    179179         
     
    184184 
    185185    //window.style.backgroundImage = scene.camera.getTexture(); 
    186     info.onMouseDown = delegate bool(Surface self, byte buttons, Vec2i coordinates, char[] href) { 
     186    info.onMouseDown = delegate bool(Surface self, byte buttons, Vec2i coordinates) { 
    187187        self.raise(); 
    188188        self.focus(); 
    189         return true; 
    190     }; 
    191     info.onMouseMove = delegate bool(Surface self, byte buttons, Vec2i amount, char[] href) { 
     189        return false; // don't propagate upward 
     190    }; 
     191    info.onMouseMove = delegate bool(Surface self, byte buttons, Vec2i amount) { 
    192192        if(buttons == 1)  
    193193            self.move(cast(Vec2f)amount, true); 
    194         return true; 
    195     }; 
    196     info.onMouseUp = delegate bool(Surface self, byte buttons, Vec2i coordinates, char[] href) { 
     194        return false; 
     195    }; 
     196    info.onMouseUp = delegate bool(Surface self, byte buttons, Vec2i coordinates) { 
    197197        self.blur(); 
    198         return true; 
     198        return false; 
    199199    }; 
    200200    info.onMouseOver = delegate bool(Surface self, byte buttons, Vec2i coordinates) { 
    201201        self.style.set("border-image: url('gui/skin/clear3.png')"); 
    202         return true; 
     202        return false; 
    203203    }; 
    204204    info.onMouseOut = delegate bool(Surface self, byte buttons, Vec2i coordinates) { 
    205205        self.style.set("border-image: url('gui/skin/clear2.png')"); 
    206         return true; 
     206        return false; 
    207207    }; 
    208208 
  • trunk/src/demo2/main.d

    r191 r193  
    5353        return true; 
    5454    }; 
    55     view.onMouseDown = delegate bool(Surface self, byte buttons, Vec2i coordinates, char[] href) { 
     55    view.onMouseDown = delegate bool(Surface self, byte buttons, Vec2i coordinates) { 
    5656        self.grabMouse(!self.getGrabbedMouse()); 
    5757        return true; 
     
    7070    info.style.overflowY = Style.Overflow.HIDDEN; 
    7171     
    72     info.onMouseDown = delegate bool(Surface self, byte buttons, Vec2i coordinates, char[] href){ 
     72    info.onMouseDown = delegate bool(Surface self, byte buttons, Vec2i coordinates){ 
    7373        self.raise(); 
    7474        self.focus(); 
    75         return true; 
     75        return false; 
    7676    }; 
    77     info.onMouseMove = delegate bool(Surface self, byte buttons, Vec2i amount, char[] href) { 
     77    info.onMouseMove = delegate bool(Surface self, byte buttons, Vec2i amount) { 
    7878        if(buttons == 1)  
    7979            self.move(cast(Vec2f)amount, true); 
    80         return true; 
     80        return false; 
    8181    }; 
    82     info.onMouseUp = delegate bool(Surface self, byte buttons, Vec2i coordinates, char[] href) { 
     82    info.onMouseUp = delegate bool(Surface self, byte buttons, Vec2i coordinates) { 
    8383        self.blur(); 
    84         return true; 
     84        return false; 
    8585    }; 
    8686    info.onMouseOver = delegate bool(Surface self, byte buttons, Vec2i coordinates) { 
    8787        self.style.set("border-image: url('gui/skin/clear3.png')"); 
    88         return true; 
     88        return false; 
    8989    }; 
    9090    info.onMouseOut = delegate bool(Surface self, byte buttons, Vec2i coordinates) { 
    9191        self.style.set("border-image: url('gui/skin/clear2.png')"); 
    92         return true; 
     92        return false; 
    9393    }; 
    9494    //info.style.transform = Matrix().scale(Vec3f(.5, .5, .5)); 
     
    109109    Timer frame = new Timer(true); 
    110110    while(!System.isAborted()) 
    111     {       
     111    {    
    112112        Input.processAndSendTo(view); 
    113113        auto stats = Render.scene(camera, window); 
  • trunk/src/yage/core/array.d

    r188 r193  
    1717module yage.core.array; 
    1818 
     19import tango.stdc.stdlib : malloc, free; 
     20import tango.core.Traits; 
     21import tango.math.Math; 
     22import tango.text.convert.Format; 
    1923import yage.core.format; 
    2024import yage.core.math.math; 
    2125import yage.core.types; 
    2226import yage.core.timer; 
    23 import tango.stdc.stdlib : malloc, free; 
    24 import tango.core.Traits; 
    25 import tango.text.convert.Format; 
     27import yage.system.log; 
    2628 
    2729/** 
     
    482484    /// TODO: This returns a copy, so a[i].b = 3; doesn't work!! 
    483485    T* opIndex(size_t i) 
    484     {   assert(i<size); 
     486    {   assert(i<size, format("array index %s out of bounds", i)); 
    485487        return &array[i]; 
    486488    } 
     
    521523    {   array.reverse; 
    522524        return *this; 
     525    } 
     526     
     527    /** 
     528     * Add and remove elements from the array, in-place 
     529     * Params: 
     530     *     index =  
     531     *     remove = Number of elements to remove, including and after index 
     532     *     insert = Element to insert before index, after elements have been removed. */ 
     533    void splice(size_t index, size_t remove, T[] insert ...) 
     534    {   assert(index+remove <= size, format("%s index + %s remove is greater than %s size", index, remove, size)); 
     535     
     536        int difference = insert.length - remove; 
     537        if (difference > 0) // if array will be longer 
     538        {   length(size+difference); // grow to fit 
     539            long i = (cast(long)size)-difference-1; 
     540            for (; i>=index; i--) // shift elements 
     541            //  if (i>=0 && i+difference < size) 
     542                    data[i + difference] = data[i]; 
     543             
     544        } 
     545         
     546        if (difference < 0) // if array will be shorter 
     547        {   for (int i=index; i<size+difference; i++) // shift elements 
     548                data[i] = data[i - difference];          
     549            length(size + difference); // shrink to fit 
     550        } 
     551         
     552        // Insert new elements 
     553        for (int i=0; i<insert.length; i++) 
     554            data[i+index] = insert[i]; 
     555    } 
     556    unittest { 
     557        auto test = ArrayBuilder!(int)([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);      
     558        test.splice(3, 3); 
     559        assert(test.data == [0, 1, 2, 6, 7, 8, 9]); 
     560        test.splice(3, 0, [3, 4, 5]); 
     561        assert(test.data == [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]); 
     562        test.splice(0, 3, [2, 1, 0]); 
     563        assert(test.data == [2, 1, 0, 3, 4, 5, 6, 7, 8, 9]); 
     564        test.splice(test.length, 0, 10); 
     565        assert(test.data == [2, 1, 0, 3, 4, 5, 6, 7, 8, 9, 10]); 
     566         
     567        auto test2 = ArrayBuilder!(int)(); 
     568        test2.splice(0, 0, 1); 
     569        assert(test2.data == [1]); 
    523570    } 
    524571     
  • trunk/src/yage/core/types.d

    r160 r193  
    77module yage.core.types; 
    88 
     9import tango.stdc.string : memcpy; 
     10import tango.math.Math; 
    911import yage.core.math.math; 
    1012import yage.core.parse; 
    1113import yage.core.math.vector; 
    1214import yage.core.math.vector:Vec4f; 
    13 import tango.stdc.string : memcpy; 
    1415 
    1516/** 
     
    3435    static word opCall(T)(T i) 
    3536    {   word res; 
    36         static if(T.sizeof < 2) 
    37             throw new Exception("Variable must be at least 2 bytes to be converted to a word."); 
    38         memcpy(&res.s, &i, 2); 
     37        memcpy(&res.s, &i, min(2, T.sizeof)); 
    3938        return res; 
    4039    } 
     
    6564    static dword opCall(T)(T i) 
    6665    {   dword res; 
    67         static if(T.sizeof < 4) 
    68             throw new Exception("Variable must be at least 4 bytes to be converted to a dword.");        
    69         memcpy(&res.i, &i, 4); 
    70          
     66        memcpy(&res.i, &i, min(4, T.sizeof));        
    7167        return res; 
    7268    } 
     
    9995    static qword opCall(T)(T i) 
    10096    {   qword res; 
    101         static if(T.sizeof < 8) 
    102             throw new Exception("Variable must be at least 8 bytes to be converted to a qword."); 
    103         memcpy(&res.l, &i, 8); 
     97        memcpy(&res.l, &i, min(8, T.sizeof)); 
    10498        return res; 
    10599    } 
  • trunk/src/yage/gui/all.d

    r192 r193  
    55 *  
    66 * Import every module in the yage.gui package. 
     7 *  
     8 * Yage's GUI framework allows building a heirarchy of Surfaces which accept CSS for positioning/style and XHTML 
     9 * for their content.  From there, Geometry and Materials are created that can be used by the engine's Renderer. 
    710 */ 
    811 
  • trunk/src/yage/gui/style.d

    r192 r193  
    507507    {   static if (is (T==Style.TextAlign)) 
    508508            switch (string) 
    509             {   case "left": return TextAlign.LEFT;  
     509            {   case "auto": return TextAlign.AUTO;  
     510                case "left": return TextAlign.LEFT;  
    510511                case "center": return TextAlign.CENTER;  
    511512                case "right": return TextAlign.RIGHT;  
    512513                case "justify": return TextAlign.JUSTIFY; 
     514                default: throw new CSSException("Unsupported text-alignment %s", string); 
    513515            } 
    514516        static if (is (T==Style.TextDecoration)) 
    515517            switch(string) 
    516             {   case "none": return TextDecoration.NONE;  
     518            {   case "auto": return TextDecoration.AUTO;  
     519                case "none": return TextDecoration.NONE;  
    517520                case "underline": return TextDecoration.UNDERLINE;  
    518521                case "overline": return TextDecoration.OVERLINE;  
    519522                case "line-through": return TextDecoration.LINETHROUGH; 
     523                default: throw new CSSException("Unsupported text-decoration %s", string); 
    520524            } 
    521525        static if (is (T==Style.FontStyle)) 
    522526            switch(string) 
    523             {   case "normal":return FontStyle.NORMAL;  
    524                 case "italic": return FontStyle.ITALIC;  
     527            {   case "auto": return FontStyle.AUTO;  
     528                case "normal":return FontStyle.NORMAL;  
     529                case "italic": 
    525530                case "oblique": return FontStyle.ITALIC; 
     531                default: throw new CSSException("Unsupported font-style %s", string); 
    526532            } 
    527533        static if (is (T==Style.FontWeight)) 
    528             return string=="normal" ? FontWeight.NORMAL : FontWeight.BOLD; 
     534            switch(string) 
     535            {   case "auto": return FontWeight.AUTO;  
     536                case "normal":return FontWeight.NORMAL;  
     537                case "bold": return FontWeight.BOLD; 
     538                default: throw new CSSException("Unsupported font-weight %s", string); 
     539            } 
    529540    } 
    530541 
  • trunk/src/yage/gui/surface.d

    r192 r193  
    3434    Style style; 
    3535     
    36     char[] text; /// This html text will be rendered inside the surface. 
    37      
    38     bool editable = true; /// The text of this surface is editable. 
     36    char[] text; /// This html text will be rendered inside the surface.     
     37    bool editable = false; /// The text of this surface is editable. 
    3938    bool mouseChildren = true; /// Allow the mouse to interact with this Surface's children. 
    4039    TextCursor textCursor; /// 
     
    4342    bool delegate(Surface self) onBlur; /// 
    4443    bool delegate(Surface self) onFocus; /// 
    45     bool delegate(Surface self, byte buttons, Vec2i coordinates) onClick; /// unfinished 
     44    bool delegate(Surface self, byte buttons, Vec2i coordinates) onClick; /// When a mouse button is pressed and released without moving the mouse. 
    4645    bool delegate(Surface self, byte buttons, Vec2i coordinates) onDblCick; /// unfinished 
    4746    bool delegate(Surface self, int key, int modifier) onKeyDown; /// Triggered once when a key is pressed down 
     
    5251     * Unlike onKeyDown and onKeyUp, key is the unicode value of the key press, instead of the sdl key code. */ 
    5352    bool delegate(Surface self, dchar key, int modifier) onKeyPress;  
    54     bool delegate(Surface self, byte buttons, Vec2i coordinates, char[] href) onMouseDown; /// 
    55     bool delegate(Surface self, byte buttons, Vec2i coordinates, char[] href) onMouseUp; /// 
    56     bool delegate(Surface self, byte buttons, Vec2i amount, char[] href) onMouseMove; /// 
     53    bool delegate(Surface self, byte buttons, Vec2i coordinates) onMouseDown; /// 
     54    bool delegate(Surface self, byte buttons, Vec2i coordinates) onMouseUp; /// 
     55    bool delegate(Surface self, byte buttons, Vec2i amount) onMouseMove; /// 
    5756    bool delegate(Surface self, byte buttons, Vec2i coordinates) onMouseOver; /// 
    5857    bool delegate(Surface self, byte buttons, Vec2i coordinates) onMouseOut; /// 
     
    8988     
    9089    protected bool mouseIn;         // used to track mouseover/mouseout 
     90    protected bool mouseMoved;      // used for click() event. 
    9191    protected bool resizeDirty = true; 
    9292     
    9393    protected SurfaceGeometry geometry; // geometry used to render this surface 
    94     protected TextBlock textLayout
     94    public TextBlock textBlock
    9595     
    9696    protected static Style defaultStyle; // Used as a cache by getDefaultStyle()     
     
    102102    this() 
    103103    {   geometry = new SurfaceGeometry(); 
    104         updateDimensions(getCalculatedStyle()); 
     104        updateDimensions(getComputedStyle()); 
    105105        if (!focusSurface) 
    106106            focus(); 
     
    192192    /** 
    193193     * Get a style with all auto/null/inherit/% values replaced with absolute values. */ 
    194     Style getCalculatedStyle() 
     194    Style getComputedStyle() 
    195195    {    
    196         Style cs = style;  // calculated style 
    197         Style pcs = parent ? parent.getCalculatedStyle() : getDefaultStyle(); 
     196        Style cs = style;  // computed style 
     197        Style pcs = parent ? parent.getComputedStyle() : getDefaultStyle(); 
    198198         
    199199        // Font and text properties 
     
    343343        } 
    344344         
    345         updateDimensions(getCalculatedStyle()); // dragging breaks w/o this. 
     345        updateDimensions(getComputedStyle()); // dragging breaks w/o this. 
    346346    } 
    347347     
     
    350350    void update() 
    351351    { 
    352         Style cs = getCalculatedStyle(); 
    353         //alias calculatedStyle cs; 
     352        Style cs = getComputedStyle(); 
     353        //alias computedStyle cs; 
    354354        updateDimensions(cs); 
    355355        if (resizeDirty) 
     
    370370            int height = cast(int)height(); 
    371371             
    372             textLayout.update(text, cs, width, height);             
    373             Image textImage = textLayout.render(cs, true); // TODO: Change true to Probe.NextPow2 
     372            textBlock.update(text, cs, width, height); 
     373            Image textImage = textBlock.render(cs, true); // TODO: Change true to Probe.NextPow2 
    374374            assert(textImage !is null); 
    375375             
     
    398398         
    399399        resizeDirty = false; 
     400    }    
     401     
     402    /** 
     403     * Release focus from this surface and call the onBlur callback function if set. */ 
     404    void blur()  
     405    {   if (this is focusSurface) 
     406        {   if(onBlur) 
     407                onBlur(this); 
     408            Surface.focusSurface = null; 
     409        } 
    400410    } 
    401411 
     
    414424    } 
    415425     
    416     /** 
    417      * Release focus from this surface and call the onBlur callback function if set. */ 
    418     void blur()  
    419     {   if (this==focusSurface) 
    420         {   if(onBlur) 
    421                 onBlur(this); 
    422             Surface.focusSurface = null; 
    423         } 
    424     } 
     426    /// 
     427    void click(byte buttons, Vec2i coordinates, bool allowFocus=true) 
     428    {   bool propagate = true; 
     429        if(onClick) 
     430            propagate = onClick(this, buttons, coordinates); 
     431        if (allowFocus && Surface.focusSurface !is this) 
     432            focus(); // give focus on click if not already focussed. 
     433        if(parent && propagate)  
     434            parent.click(buttons, coordinates, false); 
     435    } 
     436 
    425437     
    426438    /** 
     
    431443        if(onKeyDown) 
    432444            propagate = onKeyDown(this, key, mod); 
    433         else if(parent && propagate)  
     445        if(parent && propagate)  
    434446            parent.keyDown(key, mod); 
    435447    } 
     
    442454        if(onKeyUp) 
    443455            propagate = onKeyUp(this, key, mod); 
    444         else if(parent && propagate)  
     456        if(parent && propagate)  
    445457            parent.keyUp(key, mod); 
    446458    } 
     
    456468        {   if (editable) 
    457469            {    
    458                 text = textLayout.input(key, mod, unicode, textCursor); 
     470                textBlock.input(key, mod, unicode, textCursor, getComputedStyle()); 
     471                text = textBlock.toString(); // TODO: Do this lazily? 
    459472            } 
    460473            if(parent)  
     
    466479     * Trigger a mouseDown event and call the onMouseDown callback function if set.  
    467480     * If the onMouseDown function is not set, call the parent's mouseDown function.*/  
    468     void mouseDown(byte buttons, Vec2i coordinates, char[] href=null){  
     481    void mouseDown(byte buttons, Vec2i coordinates){  
     482        mouseMoved = false; 
    469483        bool propagate = true; 
    470484        if(onMouseDown) 
    471             propagate = onMouseDown(this, buttons, coordinates, href); 
    472         else if(parent && propagate)  
    473             parent.mouseDown(buttons, coordinates, href); 
     485            propagate = onMouseDown(this, buttons, coordinates); 
     486        if(parent && propagate)  
     487            parent.mouseDown(buttons, coordinates); 
    474488    } 
    475489     
     
    477491     * Trigger a mouseUp event and call the onMouseUp callback function if set.  
    478492     * If the onMouseUp function is not set, call the parent's mouseUp function.*/  
    479     void mouseUp(byte buttons, Vec2i coordinates, char[] href=null){  
     493    void mouseUp(byte buttons, Vec2i coordinates){  
    480494        bool propagate = true; 
    481495        if(onMouseUp) 
    482             propagate = onMouseUp(this, buttons, coordinates, href); 
    483         else if(parent && propagate)  
    484             parent.mouseUp(buttons, coordinates, href); 
     496            propagate = onMouseUp(this, buttons, coordinates); 
     497        if (!mouseMoved) // trigger the click event if the mouse button went down and up without the mouse moving. 
     498            click(buttons, coordinates);         
     499        if(parent && propagate)  
     500            parent.mouseUp(buttons, coordinates); 
    485501    } 
    486502     
    487503    /** 
    488504     * Trigger a mouseMove event and call the onMouseMove callback function if set. */  
    489     void mouseMove(byte buttons, Vec2i amount, char[] href=null){ 
     505    void mouseMove(byte buttons, Vec2i amount){ 
     506        mouseMoved = true; 
    490507        bool propagate = true; 
    491508        if(onMouseMove) 
    492             propagate = onMouseMove(this, buttons, amount, href); 
    493         else if(parent && propagate)  
    494             parent.mouseMove(buttons, amount, href); 
     509            propagate = onMouseMove(this, buttons, amount); 
     510        if(parent && propagate)  
     511            parent.mouseMove(buttons, amount); 
    495512    } 
    496513 
     
    498515     * Trigger a mouseOver event and call the onMouseOver callback function if set. */  
    499516    void mouseOver(byte buttons, Vec2i coordinates) { 
    500         if(!mouseIn
     517        if(!mouseIn
    501518        {   bool propagate = true; 
    502519                 
     
    661678            resize(size-old_size); // trigger resize event. 
    662679            foreach (c; children) 
    663                 c.updateDimensions(c.getCalculatedStyle()); 
     680                c.updateDimensions(c.getComputedStyle()); 
    664681        } 
    665682    } 
  • trunk/src/yage/gui/textblock.d

    r192 r193  
    5353     
    5454    private char[] text; 
    55     private InlineStyle style; // base style of entire text block 
     55    package InlineStyle style; // base style of entire text block 
    5656    private int width; 
    5757    private int height; 
    5858     
    59     // Deprecated in favor of TextCursor 
    60     int cursorPosition; 
    61     int selectionStart; 
    62     int selectionEnd; 
    63      
    64     // Previous settings 
    65     /* 
    66     struct Previous 
    67     {   char[] text; 
    68         InlineStyle style; 
    69         int width; 
    70         int height; 
    71     } 
    72     Previous previous; 
    73     */ 
    74      
    7559    private ArrayBuilder!(Line) lines; 
    7660    private ArrayBuilder!(Letter) letters; 
    77     private ArrayBuilder!(InlineStyle) styles;  // A style for each letter 
     61    private ArrayBuilder!(InlineStyle) styles;  // Styles pointed to by the letters 
     62     
     63    /** 
     64     * Get a line number and the position in that line from a position relative to the start of the TextBlock 
     65     * Params: 
     66     *     position = Character position from the beginning of the TextBlock 
     67     *     After the function executes, this will be the position on the line returned. 
     68     * Returns:  The line number.  If position is after the last line, then the number of the last line +1 is returned.*/ 
     69    int positionToLine(inout int position) 
     70    {   foreach (i, line; lines.data) 
     71        {   if (position < line.letters.length) 
     72                return i; 
     73            position -= line.letters.length; 
     74        } 
     75        return lines.length; 
     76    } 
     77     
     78    /** 
     79     * Get cursor position from the beginning of the TextBlock based on a line number and the position in that line. 
     80     * Params: 
     81     *     line =  
     82     *     position = Position from the beginning of the line. 
     83     * Returns: */ 
     84    int lineToPosition(int line, int position) 
     85    {   int m = min(line, lines.length); 
     86        for (int i=0; i<m; i++) 
     87            position += lines[i].letters.length; 
     88        return position; 
     89    } 
    7890     
    7991    /** 
     
    8395     *     mod = modifier key. 
    8496     *     unicode = Unicode value of the pressed key. 
    85      *     cursor 
    86      * Returns: new html text. 
     97     *     cursor = The Surface's TextCursor. 
    8798     */ 
    88     char[] input(int key, int mod, dchar unicode, inout TextCursor cursor
    89     { 
    90         Log.trace("%s %s", cursor.position, letters.length); 
     99    void input(int key, int mod, dchar unicode, inout TextCursor cursor, Style computedStyle
     100    {   
     101        style = InlineStyle(computedStyle); 
    91102        assert(cursor.position <= letters.length); 
    92103         
     104        // Get the x position in pixels of a character on a line. 
     105        int positionToX(int line, int position) 
     106        {   assert (line < lines.length);                
     107            int x; 
     108            int last = min(lines[line].letters.length, position); 
     109            for (int i=0; i<last; i++) 
     110                x+= lines[line].letters[i].advanceX; 
     111            return x; 
     112        } 
     113         
     114        // Get the nearest character position on a line from an x poxition in pixels 
     115        int xToPosition(int line, int x) 
     116        {   int position; 
     117            for (int i=0; i<lines[line].letters.length; i++) 
     118            {   x-= lines[line].letters[i].advanceX; 
     119                if (x<0) // TODO: More accurate rounding 
     120                    return i; 
     121            } 
     122        } 
     123         
    93124        // Position cursor 
     125        int position = cursor.position; 
     126        int currentLine = positionToLine(position); 
    94127        switch(key)  
    95         { 
    96             case SDLK_LEFT: if (cursorPosition>0) cursorPosition--; break; 
    97             case SDLK_RIGHT: if (cursorPosition<letters.length) cursorPosition++; break; 
    98             case SDLK_UP: break; 
    99             case SDLK_DOWN: break; 
    100             case SDLK_HOME: break; 
    101             case SDLK_END: break; 
    102              
    103             default: break;          
     128        {    
     129            // Positioning keys 
     130            case SDLK_LEFT: if (cursor.position>0) cursor.position--; break; 
     131            case SDLK_RIGHT: if (cursor.position<letters.length) cursor.position++; break; 
     132            case SDLK_UP:  
     133                if (currentLine > 0) 
     134                {   int x = positionToX(currentLine, position); 
     135                    currentLine--; 
     136                    cursor.position = lineToPosition(currentLine, xToPosition(currentLine, x));                  
     137                } 
     138                break; 
     139            case SDLK_DOWN:  
     140                if (currentLine < lines.length-1) 
     141                {   int x = positionToX(currentLine, position); 
     142                    currentLine++; 
     143                    cursor.position = lineToPosition(currentLine, xToPosition(currentLine, x));                  
     144                } 
     145                break; 
     146            case SDLK_HOME:  
     147                positionToLine(position); // position is now relativeto the beginning of the line. 
     148                cursor.position -= position; 
     149                break; 
     150            case SDLK_END:  
     151                positionToLine(position); 
     152                cursor.position += (lines[currentLine].letters.length - position); 
     153                break; 
     154             
     155            // Editing Keys 
     156            case SDLK_INSERT: break;             
     157            case SDLK_BACKSPACE:  
     158                if (cursor.position > 0) 
     159                {   letters.splice(cursor.position-1, 1); 
     160                    cursor.position--; 
     161                }            
     162                break; 
     163            case SDLK_DELETE:  break; 
     164                if (cursor.position < letters.length)  // doesn't work 
     165                    letters.splice(cursor.position, 1); 
     166            // New letters 
     167            default:  
     168                if (unicode) 
     169                {    
     170                    // Get the style to use for a new letter 
     171                    InlineStyle *style; 
     172                    if (cursor.position > 0) // get style from previous letter 
     173                        style = cast(InlineStyle*)letters[cursor.position-1].extra; 
     174                    else if (letters.length && cursor.position < letters.length-1) // get style from next letter 
     175                        style = cast(InlineStyle*)letters[cursor.position].extra; 
     176                    else // get base style of the TextBlock. 
     177                        style = &this.style; 
     178                 
     179                    Letter l = style.fontFamily.getLetter(unicode, style.fontSize); 
     180                    l.extra = style; 
     181                    letters.splice(cursor.position, 0, l); 
     182                    cursor.position++; 
     183                } 
     184            break;           
    104185            // ctrl+a, z, x, c, v 
    105         } 
    106          
    107          
    108         if (unicode) 
    109         {   InlineStyle style; 
    110          
    111             /* 
    112             case SDLK_INSERT: break;             
    113             case SDLK_BACKSPACE: break; 
    114             case SDLK_DELETE:  break; 
    115              */ 
    116              
    117             Letter l = style.fontFamily.getLetter(unicode, 10, 10); // style.fontFamily is null. 
    118             letters ~= l; 
    119         } 
    120          
    121         lines = lettersToLines(letters.data, width, lines); 
    122          
    123         return toString(); 
     186        }        
     187         
     188         
     189         
     190        //lines = lettersToLines(letters.data, width, lines); 
     191         
     192        //return toString(); 
    124193    } 
    125194     
     
    213282    } 
    214283     
    215      
    216284    /** 
    217285     * Reverse the normal function of TextLayout and convert letters[] back to a string of html text. 
    218      * The text may use different tags (since some information is lost)  
    219      * but will be functionally the same.  
    220      * TODO: Move this to lettersToHtml(), since it's the opposite of htmlToLetters()     
    221      */ 
     286     * The text may use different tags than the original html, (since some information is lost)  
     287     * but will be functionally the same.  */ 
    222288    char[] toString() 
    223289    { 
     
    241307                if (style.fontFamily && style.fontFamily != newStyle.fontFamily) 
    242308                    styleString ~= swritef(`font-family: url('%s')`, newStyle.fontFamily); 
    243                 if (dword(style.fontSize) != dword(newStyle.fontSize)) // BUG: converts % font size to px. 
     309                if (dword(style.fontSize) != dword(newStyle.fontSize)) 
    244310                    styleString ~= swritef(`font-size: %spx`, newStyle.fontSize); 
    245311                if (style.color != newStyle.color) 
     
    274340     *     height =  
    275341     * Returns: True if the text will need to be re-rendered, false otherwise. 
    276      * TODO: Should this be a constructor to maintain RAII?  Doing so will cause more allocations of letters and lines! 
    277      */ 
     342     * TODO: Should this be a constructor to maintain RAII? */ 
    278343    bool update(char[] text, Style style, int width, int height) 
    279344    { 
     
    306371     
    307372    /* 
    308      *  
    309373     * Params: 
    310374     *     letters =