view mde/gui/content/Content.d @ 103:42e241e7be3e

ContentList content type; getting content items/lists from Options generically via content.Items, and a new addContent widget function. Several improvements to generic handling of content. New button-with-text widget. Some tidy-up. Some name changes, to increase uniformity. Bug-fix: floating widgets of fixed size could previously be made larger than intended from config dimdata.
author Diggory Hardy <diggory.hardy@gmail.com>
date Tue, 25 Nov 2008 18:01:44 +0000
parents 71f0f1f83620
children ee209602770d
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 − common types.
 */
module mde.gui.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.gui.content.Content");
    }
}

/** IContent − interface for all Content classes.
 *
 * Services like copy/paste could work on universal content. However, they would need to run a
 * conversion to the appropriate type (or try next-oldest item on clipboard?).
 *
 * Currently Content instances can only have their data set on creation.
 * Each Content class should provide a method to get it's content, e.g. the method text().
 *
 * Extensions to content:
 * 
 * These extensions require that a content can notify any dependants of changes.
 * 
 * Use as a state switch (one option from an enumeration). E.g. a button/selection box could set a
 * state, and a tabbed box could show a tab based on this. Or could represent an option.
 */
interface IContent
{
    /** Generically return strings.
     *
     * This serves two purposes: generically returning a string of/related to the content (i == 0),
     * and returning associated descriptors. Functions should adhere to (or add to) this table.
     *
     *  $(TABLE
     *  $(TR $(TH i) $(TH returns))
     *  $(TR $(TD 0) $(TD value))
     *  $(TR $(TD 1) $(TD Translated name or null))
     *  $(TR $(TD 2) $(TD Translated description or null))
     *  $(TR $(TD other) $(TD null))
     *  ) */
    char[] toString (uint i);
}

/** A generic way to handle a list of type IContent. */
class ContentList : IContent
{
    this (IContent[] list = null) {
	list_ = list;
    }
    this (ValueContent[char[]] l) {
	list_.length = l.length;
	size_t i;
	foreach (c; l)
	    list_[i++] = c;
    }
    
    char[] toString (uint i) {
	return i == 0 ? Int.toString (list_.length) ~ " elements"
	: i == 1 ? "ContentList"
	: null;
    }
    
    IContent[] list () {
	return list_;
    }
    ContentList list (IContent[] list) {
	list_ = list;
	return this;
    }
    
protected:
    IContent[] list_;
}

/** Created on errors to display and log a message. */
class ErrorContent : IContent
{
    this (char[] msg) {
	msg_ = msg;
    }
    
    char[] toString (uint i) {
	return i == 0 ? msg_
	     : i == 1 ? "Error"
	     : null;
    }
    
protected:
    char[] msg_;
}

/** Base class for content containing a simple value.
 *
 * All derived classes should have the following functions:
 * ---
 *  this (char[] symbol, T val = /+ default value +/);
 *  BoolContent addChangeCb (void delegate (char[] symbol,T value) cb);	// add a callback called on any change
 *  void assignNoCB (T val);	// assign val, but without calling callbacks (for Options)
 *  void opAssign (T val);	// assign val, calling callbacks
 *  T opCall ();		// return value
 *  alias opCall opCast;
 *  void endEdit ();		// Converts sv and assigns to self, 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 ValueContent : IContent
{
    protected this () {}
    
    void name (char[] n, char[] d = null) {
        name_ = n;
        desc_ = d;
    }
    
    /// 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, "TextContent.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 ("Symbol: {}", sym);
	}
	return sv;
    }
    
    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 at the end of an edit to convert sv to v and call callbacks
    void endEdit ();
protected:
    char[] sv;		// string of value; updated on assignment for displaying and editing
    size_t pos;		// editing position; used by keyStroke
    char[] symb;
    char[] name_, desc_;// name and description, loaded by lookup.Translation
}

template VContentN(T) {
    static if (is(T == bool)) {
        const char[] VContentN = "BoolContent";
    } else static if (is(T == int)) {
        const char[] VContentN = "IntContent";
    } else static if (is(T == double)) {
        const char[] VContentN = "DoubleContent";
    } else static if (is(T == char[])) {
        const char[] VContentN = "TextContent";
    } else
        static assert (false, "No ValueContent of type "~T.stringof);
}

class BoolContent : ValueContent
{
    /** Create a content with _symbol name symbol. */
    this (char[] symbol, bool val = false) {
        symb = symbol;
	assignNoCB (val);
    }
    
    /** Adds cb to the list of callback functions called when the value is changed. Returns this. */
    BoolContent addChangeCb (void delegate (char[] symbol,bool value) cb) {
        cngCb ~= cb;
        return this;
    }
    
    void assignNoCB (bool val) {
	v = val;
	sv = v ? "true" : "false";
	if (pos > sv.length) pos = sv.length;
    }
    void opAssign (bool val) {
	assignNoCB (val);
	foreach (cb; cngCb)
            cb(symb, val);
    }
    bool opCall () {
        return v;
    }
    alias opCall opCast;
    
    override void endEdit () {
	v = sv && (sv[0] == 't' || sv[0] == 'T' || sv[0] == '1');
	foreach (cb; cngCb)
	    cb(symb, v);
    }
    
protected:
    bool v;
    void delegate (char[],bool)[] cngCb;       // change callbacks
}

/** Text content. */
class TextContent : ValueContent
{
    this (char[] symbol, char[] val = null) {
        symb = symbol;
	v = val;
    }
    
    /** Adds cb to the list of callback functions called when the value is changed. Returns this. */
    TextContent addChangeCb (void delegate (char[] symbol,char[] value) cb) {
        cngCb ~= cb;
        return this;
    }
    
    void assignNoCB (char[] val) {
	v = val;
	if (pos > sv.length) pos = sv.length;
    }
    void opAssign (char[] val) {
	assignNoCB (val);
        foreach (cb; cngCb)
            cb(symb, val);
    }
    char[] opCall () {
        return v;
    }
    alias opCall opCast;
    
    override void endEdit () {
	foreach (cb; cngCb)
	    cb(symb, v);
    }
    
protected:
    alias sv v;		// don't need separate v and sv in this case
    void delegate (char[],char[])[] cngCb;
}

/** Integer content. */
class IntContent : ValueContent
{
    /** Create a content with _symbol name symbol. */
    this (char[] symbol, int val = 0) {
        symb = symbol;
	assignNoCB (val);
    }
    
    /** Adds cb to the list of callback functions called when the value is changed. Returns this. */
    IntContent addChangeCb (void delegate (char[] symbol,int value) cb) {
        cngCb ~= cb;
        return this;
    }
    
    void assignNoCB (int val) {
	v = val;
	sv = Int.toString (v);
	if (pos > sv.length) pos = sv.length;
    }
    void opAssign (int val) {
	assignNoCB (val);
	foreach (cb; cngCb)
            cb(symb, val);
    }
    int opCall () {
        return v;
    }
    alias opCall opCast;
    
    override void endEdit () {
	v = Int.toInt (sv);
	foreach (cb; cngCb)
	    cb(symb, v);
    }
    
protected:
    int v;
    void delegate (char[],int)[] cngCb;
}

/** Double content. */
class DoubleContent : ValueContent
{
    /** Create a content with _symbol name symbol. */
    this (char[] symbol, double val = 0) {
        symb = symbol;
	assignNoCB (val);
    }
    
    /** Adds cb to the list of callback functions called when the value is changed. Returns this. */
    DoubleContent addChangeCb (void delegate (char[] symbol,double value) cb) {
        cngCb ~= cb;
        return this;
    }
    
    void assignNoCB (double val) {
	v = val;
	sv = Float.toString (v);
	if (pos > sv.length) pos = sv.length;
    }
    void opAssign (double val) {
	assignNoCB (val);
	foreach (cb; cngCb)
            cb(symb, val);
    }
    double opCall () {
        return v;
    }
    alias opCall opCast;
    
    override void endEdit () {
	v = Float.toFloat (sv);
	foreach (cb; cngCb)
	    cb(symb, v);
    }
    
protected:
    double v;
    void delegate (char[],double)[] cngCb;
}