view mde/content/AStringContent.d @ 105:08651e8a8c51

Quit button, big changes to content system. Moved mde.gui.content to mde.content to reflect it's not only used by the gui. Split Content module into Content and AStringContent. New AContent and EventContent class. Callbacks are now generic and implemented in AContent. Renamed TextContent to StringContent and ValueContent to AStringContent.
author Diggory Hardy <diggory.hardy@gmail.com>
date Sat, 29 Nov 2008 12:36:39 +0000
parents
children fe061009029d
line wrap: on
line source

/* 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/>. */

/** The content system − string-based editable content.
 */
module mde.content.AStringContent;
public import mde.content.Content;

//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;
    private Logger logger;
    static this () {
        logger = Log.getLogger ("mde.content.AStringContent");
    }
}

// Used by Options
template ContentN(T) {
    static if (is(T == bool)) {
	const char[] ContentN = "BoolContent";
    } else static if (is(T == int)) {
	const char[] ContentN = "IntContent";
    } else static if (is(T == double)) {
	const char[] ContentN = "DoubleContent";
    } else static if (is(T == char[])) {
	const char[] ContentN = "StringContent";
    } else
	static assert (false, "No Content of type "~T.stringof);
}

/** Base class for content containing a simple value editable as text. All content types used by
 * Options extend this class.
 *
 * All derived classes should have the following functions:
 * ---
 *  void endEdit ();	// Should convert sv and assign to self, then call endEvent
 * // Used by Options:
 *  BoolContent changeCb (void delegate (char[] symbol,T value) cb);	// The callback used by Options
 *  void assignNoCb (T val);	// assign val, but without calling callbacks
 * ---
 * On any assignation (by this, assignNoCb, opAssign) the value should be converted to a string and
 * assigned to sv, and pos should be clamped to [0,sv.length] (i.e. enforce pos <= sv.length). */
abstract class AStringContent : AContent
{
    protected this (char[] symbol) {
	super (symbol);
    }
    
    /// Get the text.
    char[] toString (uint i) {
        return i == 0 ? sv
             : i == 1 ? name_
             : i == 2 ? desc_
             : null;
    }
    
    /** 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, "StringContent.keyStroke: no value (??)");	// impossible?
	char k = *i;
	if (k > 0x20) {
	    if (k == 0x7f) {		// delete
		size_t p = pos;
		if (p < sv.length) ++p;
		while (p < sv.length && (sv[p] & 0x80) && !(sv[p] & 0x40))
		    ++p;
		sv = sv[0..pos] ~ sv[p..$];
	    } else {			// insert character
		char[] tail = sv[pos..$];
		sv.length = sv.length + i.length;
		size_t npos = pos+i.length;
		if (tail) sv[npos..$] = tail.dup;	// cannot assign with overlapping ranges
		    sv[pos..npos] = i;
		pos = npos;
	    }
	} else {			// use sym; many keys output 0
	    if (sym == SDLK_BACKSPACE) {	// backspace; k == 0x8
		char[] tail = sv[pos..$];
		if (pos) --pos;
		while (pos && (sv[pos] & 0x80) && !(sv[pos] & 0x40))
		    --pos;
		sv = sv[0..pos] ~ tail;
	    } else if (sym == SDLK_LEFT) {
		if (pos) --pos;
		while (pos && (sv[pos] & 0x80) && !(sv[pos] & 0x40))
		    --pos;
	    } else if (sym == SDLK_RIGHT) {
		if (pos < sv.length) ++pos;
		while (pos < sv.length && (sv[pos] & 0x80) && !(sv[pos] & 0x40))
		    ++pos;
	    } else if (sym == SDLK_HOME || sym == SDLK_UP) {
		pos = 0;
	    } else if (sym == SDLK_END || sym == SDLK_DOWN) {
		pos = sv.length;
	    } else
		debug logger.trace ("Unhandled symbol: {}", sym);
	}
	return sv;
    }
    
    /// Get the character the edit cursor is in front of
    size_t editIndex () {
	size_t i = 0;
	for (size_t p = 0; p < pos; ++p)
	    if (!(sv[p] & 0x80) || sv[p] & 0x40)
		++i;
	return i;
    }
    
    /// Call after editing a string
    void endEdit ();
    
protected:
    char[] sv;		// string of value; updated on assignment for displaying and editing
    size_t pos;		// editing position; used by keyStroke
}

class BoolContent : AStringContent
{
    /** Create a content with _symbol name symbol. */
    this (char[] symbol, bool val = false) {
	assignNoCb (val);
	super (symbol);
    }
    
    void assignNoCb (bool val) {
	v = val;
	sv = v ? "true" : "false";
	if (pos > sv.length) pos = sv.length;
    }
    void opAssign (bool val) {
	assignNoCb (val);
	endEvent;
    }
    bool opCall () {
        return v;
    }
    alias opCall opCast;
    
    override void endEdit () {
	v = sv && (sv[0] == 't' || sv[0] == 'T' || sv[0] == '1');
	endEvent;
    }
    
protected:
    bool v;
}

/** Text content. */
class StringContent : AStringContent
{
    this (char[] symbol, char[] val = null) {
        v = val;
	super (symbol);
    }
    
    void assignNoCb (char[] val) {
	v = val;
	if (pos > sv.length) pos = sv.length;
    }
    void opAssign (char[] val) {
	assignNoCb (val);
	endEvent;
    }
    char[] opCall () {
        return v;
    }
    alias opCall opCast;
    
    override void endEdit () {
	endEvent;
    }
    
protected:
    alias sv v;		// don't need separate v and sv in this case
}

/** Integer content. */
class IntContent : AStringContent
{
    /** Create a content with _symbol name symbol. */
    this (char[] symbol, int val = 0) {
        assignNoCb (val);
	super (symbol);
    }
    
    void assignNoCb (int val) {
	v = val;
	sv = Int.toString (v);
	if (pos > sv.length) pos = sv.length;
    }
    void opAssign (int val) {
	assignNoCb (val);
	endEvent;
    }
    int opCall () {
        return v;
    }
    alias opCall opCast;
    
    override void endEdit () {
	v = Int.toInt (sv);
	endEvent;
    }
    
protected:
    int v;
}

/** Double content. */
class DoubleContent : AStringContent
{
    /** Create a content with _symbol name symbol. */
    this (char[] symbol, double val = 0) {
        assignNoCb (val);
	super (symbol);
    }
    
    void assignNoCb (double val) {
	v = val;
	sv = Float.toString (v);
	if (pos > sv.length) pos = sv.length;
    }
    void opAssign (double val) {
	assignNoCb (val);
	endEvent;
    }
    double opCall () {
        return v;
    }
    alias opCall opCast;
    
    override void endEdit () {
	v = Float.toFloat (sv);
	endEvent;
    }
    
protected:
    double v;
}