view mde/gui/gui.d @ 30:467c74d4804d

Major changes to the scheduler, previously only used by the main loop. Revamped Scheduler. Functions can be removed, have multiple schedules, have their scheduling changed, etc. Scheduler has a unittest. Checked all pass. Main loop scheduler moved to mde. Draw-on-demand currently disabled, simplifying this. Made mtunitest.d remove the temporary file it uses afterwards. committer: Diggory Hardy <diggory.hardy@gmail.com>
author Diggory Hardy <diggory.hardy@gmail.com>
date Mon, 28 Apr 2008 10:59:47 +0100
parents f985c28c0ec9
children baa87e68d7dc
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/>. */

/// Base GUI module.
module mde.gui.gui;

import mde.gui.IWindow;
import mde.gui.Widget;
import mde.gui.exception;

import mt = mde.mergetag.DataSet;
import mt = mde.mergetag.exception;
import mde.mergetag.Reader;

import mde.resource.paths;
import mde.scheduler.InitFunctions;

import tango.scrapple.text.convert.parseTo : parseTo;
import tango.scrapple.text.convert.parseFrom : parseFrom;

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

private Logger logger;
static this () {
    logger = Log.getLogger ("mde.gui.gui");
    
    init.addFunc (&loadGUI, "loadGUI");
}

GUI gui;    // Currently just one instance; handle differently later.
// Wrap gui.load, since init doesn't handle delegates
// (do it this way since GUI handling will eventually be changed)
void loadGUI () {
    gui.load();
}

/** A GUI handles a bunch of windows, all to be drawn to the same device. */
struct GUI {
    /** Load all windows from the file gui. */
    void load() {
        static const fileName = "gui";
        if (!confDir.exists (fileName)) {
            logger.error ("Unable to load GUI: no config file!");
            return; // not a fatal error (so long as the game can run without a GUI!)
        }
        
        IReader reader;
        try {
            reader = confDir.makeMTReader (fileName, PRIORITY.HIGH_ONLY);
            reader.dataSecCreator = delegate mt.IDataSection(mt.ID) {
                return new Window;
            };
            reader.read;
        } catch (mt.MTException e) {
            logger.error ("Loading GUI aborted:");
            logger.error (e.msg);
            
            return;
        }
        
        // get list
        windows.length = reader.dataset.sec.length; // pre-allocate
        windows.length = 0;
        foreach (sec; reader.dataset.sec) {
            Window w = cast(Window) sec;
            debug if (w is null) {
                logger.error (__FILE__ ~ "(GUI.load): code error (w is null)");
                continue;
            }
            try {
                //logger.trace ("1");
                int x;
                w.finalise();
                x = 6;
                windows ~= w;       // only add if load successful
            } catch (WindowLoadException e) {
                logger.error ("Window failed to load: " ~ e.msg);
            }
        }
    }
    
    /** Draw each window.
    *
    * Currently no concept of how to draw overlapping windows, or how to not bother drawing windows
    * which don't need redrawing. */
    void draw() {
        foreach (w; windows)
            w.draw();
    }
    
    private:
    Window[] windows;
}

package:    // Nothing else here is for external use.

/** 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
{
    alias int widgetID;     // Widget ID type. Each ID is unique under this window. Type is int since this is the widget data type.
    
    /** 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 () {
        // Check data was loaded:
        if (widgetData is null) throw new WindowLoadException ("No widget data");
        
        // Create the primary widget (and indirectly all sub-widgets), throwing on error:
        widget = getWidget (0);     // primary widget always has ID 0.
        
        widgetData = null;          // data is no longer needed: allow GC to collect (cannot safely delete)
        
        widget.getCurrentSize (w,h);// Find the initial size
        w += BORDER_WIDTH * 2;      // Adjust for border
        h += BORDER_WIDTH * 2;
    }
    
    /** Get/create a widget by ID.
    *
    * Should $(I only) be called internally and by sub-widgets! */
    IWidget getWidget (widgetID i)
    in {
        // widgetData is normally left to be garbage collected after widgets have been created:
        assert (widgetData !is null, "getWidget: widgetData is null");
    } body {
        // See if it's already been created:
        IWidget* p = i in widgets;
        if (p !is null) return *p;  // yes
        else {                      // no
            int[]* d = i in widgetData;
            if (d is null) throw new WindowLoadException ("Widget not found");
            
            // Throws WidgetDataException (a WindowLoadException) if bad data:
            IWidget w = createWidget (this, *d);
            widgets[i] = w;
            return w;
        }
    }
    
    void requestRedraw () {
    //FIXME
    }
    
    void draw () {
        //BEGIN Window border/back
        gl.setColor (0.0f, 0.0f, 0.5f);
        gl.drawBox (x,x+w, y,y+h);
        //END Window border/back
        
        // Tell the widget to draw itself:
        widget.draw(x + BORDER_WIDTH, y + BORDER_WIDTH);
    }
    
    //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
    
    private:
    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.
    int x,y;                        // Window position
    int w,h;                        // Window size (calculated from Widgets)
    
    const BORDER_WIDTH = 8;         // Temporary way to handle window decorations

}