view mde/gui/widget/Window.d @ 34:6b4116e6355c

Work on the Gui: some of the framework for drag & drop. Also made Window an IWidget. Implemented getWidget(x,y) to find the widget under this location for IWidgets (but not Gui). Made Window an IWidget and made it work a little more similarly to widgets. Implemented callbacks on the Gui for mouse events (enabling drag & drop, etc.). committer: Diggory Hardy <diggory.hardy@gmail.com>
author Diggory Hardy <diggory.hardy@gmail.com>
date Fri, 02 May 2008 16:03:52 +0100
parents 316b0230a849
children 928db3c75ed3
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 Window class. Hopefully eventually this will become a widget to make things a bit more
* generic. */
module mde.gui.widget.Window;

import mde.gui.widget.Ifaces;
import mde.gui.widget.createWidget;

import mde.gui.IGui;
import mde.gui.exception;

import mt = mde.mergetag.DataSet;
import tango.scrapple.text.convert.parseTo : parseTo;
// not yet implemented:
//import tango.scrapple.text.convert.parseFrom : parseFrom;

import tango.util.log.Log : Log, Logger;

private Logger logger;
static this () {
    logger = Log.getLogger ("mde.gui.widget.Window");
}

/** GUI Window class
 *
 * A window class instance does two things: (1) specify a region of the screen upon which the window
 * and its associated widgets are drawn, and (2) load, save, and generally manage all its widgets.
 *
 * Let the window load a table of widget data, of type int[][widgetID]. Each widget will, when
 * created, be given its int[] of data, which this() must confirm is valid (or throw).
 */
class Window : mt.IDataSection, IWindow
{
    //BEGIN Methods for GUI
    this (char[] id) {
        name = id;
    }
    
    /** Call after loading is finished to setup the window and confirm that it's valid.
     *
     * Throws: WindowLoadException. Do not use the instance in this case! */
    void finalise (IGui gui)
    in {
        assert (gui !is null, "Window.finalise ("~name~"): gui is null");
    } body {
        // Check data was loaded:
        if (widgetData is null) throw new WindowLoadException ("No widget data");
        
        gui_ = gui;
        rend = gui.renderer;
        
        // Create the primary widget (and indirectly all sub-widgets), throwing on error:
        widget = makeWidget (0, this);// primary widget always has ID 0.
        
        widgetData = null;          // data is no longer needed: allow GC to collect (cannot safely delete)
        
        widgetX = x + rend.windowBorder;  // widget position
        widgetY = y + rend.windowBorder;  // must be updated if the window is moved
        widget.setPosition (widgetX, widgetY);
        
        widget.getCurrentSize (w,h);// Find the initial size
        w += rend.windowBorder * 2;       // Adjust for border
        h += rend.windowBorder * 2;
        
        xw = x+w;
        yh = y+h;
    }
    //BEGIN Mergetag code
    void addTag (char[] tp, mt.ID id, char[] dt) {
        if (tp == "int[][int]") {
            if (id == "widgetData") {
                widgetData = cast(int[][widgetID]) parseTo!(int[][int]) (dt);
            }
        } else if (tp == "int") {
            if (id == "x") {
                x = parseTo!(int) (dt);
            } else if (id == "y") {
                y = parseTo!(int) (dt);
            }
        }
    }
    void writeAll (ItemDelg dlg) {
    }
    //END Mergetag code
    //END Methods for GUI
    
    //BEGIN IWindow methods
    /** Get/create a widget by ID.
     *
     * Should $(I only) be called internally and by sub-widgets! */
    IWidget makeWidget (widgetID i, IWidget parent)
    in {
        // widgetData is normally left to be garbage collected after widgets have been created:
        assert (widgetData !is null, "Window.makeWidget ("~name~"): widgetData is null");
    } body {
        // See if it's already been created:
        IWidget* p = i in widgets;
        if (p !is null) {   // yes
            char[128] tmp;
            logger.warn (logger.format (tmp, "Window.makeWidget ("~name~"): widget {} has multiple uses!", i));
            return *p;
        }
        else {              // no
            int[]* d = i in widgetData;
            if (d is null) throw new WindowLoadException ("Window.makeWidget ("~name~"): Widget not found");
            
            // Throws WidgetDataException (a WindowLoadException) if bad data:
            IWidget widg = createWidget (this, parent, *d);
            widgets[i] = widg;
            return widg;
        }
    }
    
    IGui gui () {
        return gui_;
    }
    
    /+void requestRedraw () {
    }+/
    
    IRenderer renderer () {
        return rend;
    }
    //END IWindow methods
    
    //BEGIN IWidget methods
    void getMinimumSize (out int w, out int h) {
        widget.getMinimumSize (x,y);
        w += rend.windowBorder * 2;       // Adjust for border
        h += rend.windowBorder * 2;
    }
    void getCurrentSize (out int cw, out int ch) {
        cw = w;
        ch = h;
    }
    
    void setPosition (int x, int y) {
        /+ Note: this is currently unused. Maybe only use it internally?
        this.x = x;
        this.y = y;
        
        widgetX = x + rend.windowBorder;
        widgetY = y + rend.windowBorder;
        
        widget.setPosition (widgetX, widgetY);
        +/
    }
    
    IWidget getWidget (int cx, int cy) {
        if (cx < x || cx >= xw || cy < y || cy >= yh)   // not over window
            return null;
        if (cx >= widgetX && cx < xw-rend.windowBorder && cy >= widgetY && cy < yh-rend.windowBorder)
                                                        // over the widget
            return widget.getWidget (cx, cy);
        else                                            // over the window border
            return this;
    }
    void clickEvent (ushort cx, ushort cy, ubyte b, bool state) {
        //if (cx >= x && cx < xw && cy >= y && cy < yh) { // click on window?
        // FIXME: repositioning?
    }
    
    void draw () {
        // background
        rend.drawWindow (x,y, w,h);
        
        // Tell the widget to draw itself:
        widget.draw();
    }
    //END IWidget methods
    
private:
    char[] name;                    // The window's name (id from config file)
    IGui gui_;                      // The gui managing this window
    
    int[][widgetID] widgetData;     // Data for all widgets under this window (deleted after loading)
    IWidget[widgetID] widgets;      // List of all widgets under this window (created on demand).
    IWidget widget;                 // The primary widget in this window.
    
    IRenderer rend;                 // The window's renderer
    // FIXME: revise which parameters are stored once Gui knows window position
    int x,y;                        // Window position
    int w,h;                        // Window size (calculated from Widgets)
    int xw, yh;                     // x+w, y+h (frequent use by clickEvent)
    int widgetX, widgetY;           // Widget position (= window position plus BORDER_WIDTH)
}