changeset 27:0aa621b3e070

Some GUI work, plus a small fix in the paths module. Implemented GUI code to load windows from file with a basic widget and draw. Fixed a bug in mde.resource.paths.mdeDirectory.makeMTReader when called with readOrder == PRIORITY.HIGH_ONLY. committer: Diggory Hardy <diggory.hardy@gmail.com>
author Diggory Hardy <diggory.hardy@gmail.com>
date Fri, 04 Apr 2008 17:07:38 +0100
parents 611f7b9063c6
children b5fadd8d930b
files codeDoc/jobs.txt data/conf/gui.mtt mde/SDL.d mde/gl.d mde/gui/Widget.d mde/gui/exception.d mde/gui/gui.d mde/mde.d mde/options.d mde/resource/paths.d mde/scheduler/InitFunctions.d
diffstat 11 files changed, 344 insertions(+), 21 deletions(-) [+]
line wrap: on
line diff
--- a/codeDoc/jobs.txt	Thu Apr 03 18:15:02 2008 +0100
+++ b/codeDoc/jobs.txt	Fri Apr 04 17:07:38 2008 +0100
@@ -3,6 +3,7 @@
 
 
 In progress:
+GUI work...
 
 
 To do:
@@ -40,3 +41,4 @@
 
 
 Done (for git log message):
+Fixed a bug in paths.mdeDirectory.makeMTReader when called with readOrder == PRIORITY.HIGH_ONLY.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/data/conf/gui.mtt	Fri Apr 04 17:07:38 2008 +0100
@@ -0,0 +1,5 @@
+{MT01}
+{W1}
+<int|x=10>
+<int|y=50>
+<int[][int]|widgetData=[0:[1,200,200]]>
--- a/mde/SDL.d	Thu Apr 03 18:15:02 2008 +0100
+++ b/mde/SDL.d	Fri Apr 04 17:07:38 2008 +0100
@@ -30,7 +30,7 @@
 import derelict.opengl.gl;
 import derelict.util.exception;
 
-Logger logger;
+private Logger logger;
 static this() {
     logger = Log.getLogger ("mde.SDL");
     
--- a/mde/gl.d	Thu Apr 03 18:15:02 2008 +0100
+++ b/mde/gl.d	Fri Apr 04 17:07:38 2008 +0100
@@ -13,7 +13,9 @@
 You should have received a copy of the GNU General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>. */
 
-/** Simple OpenGL functions. */
+/** Some basic OpenGL code.
+*
+* Everything here is really intended as makeshift code to enable GUI development. */
 module mde.gl;
 
 import mde.scheduler.runTime;
@@ -25,6 +27,7 @@
     Scheduler.perRequest (RF_KEYS.DRAW, &mde.gl.draw);
 }
 
+//BEGIN GL & window setup
 void glSetup () {
     glClearColor (0.0f, 0.0f, 0.0f, 0.0f);
 }
@@ -34,31 +37,47 @@
     glLoadIdentity ();
     
     glViewport (0,0,w,h);
-    //glOrtho (0.0,w, 0.0,h, -1.0, 1.0);
-    glOrtho (0.0,1.0,0.0,1.0,-1.0,1.0);
+    glOrtho (0.0,w, 0.0,h, -1.0, 1.0);
+    //glOrtho (0.0,1.0,0.0,1.0,-1.0,1.0);
     
     glMatrixMode(GL_MODELVIEW);
     glLoadIdentity();
 }
+//END GL & window setup
 
+//BEGIN Drawing utils
+// Simple drawing commands for use by GUI
+// (temporary)
+void setColor (float r, float g, float b) {
+    glColor3f (r, g, b);
+}
+void drawBox (int l, int r, int b, int t) {
+    glBegin (GL_QUADS);
+    {
+        glVertex2i (l, b);
+        glVertex2i (r, b);
+        glVertex2i (r, t);
+        glVertex2i (l, t);
+    }
+    glEnd();
+}
+//END Drawing utils
+
+//BEGIN Drawing loop
 // Temporary draw function
 void draw () {
     glClear(GL_COLOR_BUFFER_BIT);
     
-    glBegin (GL_QUADS);
-    {
-        glColor3f (0.2f, 0.6f, 0.8f);
-        /+glVertex2i (40, 40);
-        glVertex2i (200, 40);
-        glVertex2i (200, 200);
-        glVertex2i (40, 200);+/
-        glVertex2f (0.1f, 0.1f);
-        glVertex2f (0.9f, 0.1f);
-        glVertex2f (0.9f, 0.9f);
-        glVertex2f (0.1f, 0.9f);
-    }
-    glEnd();
+    foreach (func; drawCallbacks)
+        func();
     
     glFlush();
     SDL_GL_SwapBuffers();
 }
+
+alias void delegate() DrawingFunc;
+void addDrawCallback (DrawingFunc f) {
+    drawCallbacks ~= f;
+}
+private DrawingFunc[] drawCallbacks;
+//END Drawing loop
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mde/gui/Widget.d	Fri Apr 04 17:07:38 2008 +0100
@@ -0,0 +1,76 @@
+/* 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/>. */
+
+/// GUI Widget module.
+module mde.gui.Widget;
+
+import mde.gui.exception;
+
+import gl = mde.gl;
+
+/** Interface for widgets (may become a class).
+*
+* Variable loading/saving efficiency and code-reuse need to be revised later!
+* Give each Widget an int[] of data which it should check in this() and throw if bad?
+*/
+interface Widget
+{
+    /** Draw, starting from given x and y.
+    *
+    * Maybe replace later with drawClipped, especially for cases where only part of the widget is
+    * visible behind a scrolling window or hidden window. */
+    void draw (int x, int y);
+    
+    /** Calculate the size of the widget, taking into account child-widgets.
+    *
+    * Later will work out how to make this more flexible. */
+    void getSize (out int w, out int h);
+}
+
+/// Draws a box. That's it.
+class BoxWidget : Widget
+{
+    int w, h;   // size
+    
+    this (int[] data) {
+        if (data.length != 2) throw new WidgetDataException;
+        
+        w = data[0];
+        h = data[1];
+    }
+    
+    void draw (int x, int y) {
+        gl.setColor (1.0f, 1.0f, 0.0f);
+        gl.drawBox (x,x+w, y,y+h);
+    }
+    
+    void getSize (out int w, out int h) {
+        w = this.w;
+        h = this.h;
+    }
+}
+
+enum WIDGET_TYPES : int {
+    BOX = 1
+}
+
+Widget createWidget (int[] data) {
+    if (data.length < 1) throw new WidgetDataException ("No widget data");
+    int type = data[0];     // type is first element of data
+    data = data[1..$];      // the rest is passed to the Widget
+    
+    if (type == WIDGET_TYPES.BOX) return new BoxWidget (data);
+    else throw new WidgetDataException ("Bad widget type");
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mde/gui/exception.d	Fri Apr 04 17:07:38 2008 +0100
@@ -0,0 +1,49 @@
+/* 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/>. */
+
+/// All GUI Exception classes
+module mde.gui.exception;
+
+import mde.exception;
+
+class GuiException : mdeException
+{
+    char[] getSymbol () {
+        return super.getSymbol ~ ".gui";
+    }
+    
+    this (char[] msg) {
+        super(msg);
+    }
+}
+
+/// Thrown when something goes wrong while loading a window (usually a data error).
+class WindowLoadException : GuiException
+{
+    this (char[] msg) {
+        super(msg);
+    }
+}
+
+/// Thrown when or createWidget a Widget class's this() is called with invalid data.
+class WidgetDataException : WindowLoadException
+{
+    this () {   // Default, by Widget class's this
+        super ("Bad widget data");
+    }
+    this (char[] msg) { // From createWidget
+        super (msg);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mde/gui/gui.d	Fri Apr 04 17:07:38 2008 +0100
@@ -0,0 +1,158 @@
+/* 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.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;
+
+private Logger logger;
+static this () {
+    logger = Log.getLogger ("mde.gui.gui");
+    
+    init.addFunc (&GUI.load);
+}
+
+struct GUI {
+static:
+    private const fileName = "gui";
+    void load() {
+        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;
+            if (w !is null) {                       // extra safety
+                windows ~= w;
+                try {
+                    w.finalise();
+                } catch (WindowLoadException e) {
+                    logger.error ("Window failed to load: " ~ e.msg);
+                }
+                
+                gl.addDrawCallback (&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
+{
+    alias int widgetID;     // Widget ID type. Each ID is unique under this window. Type is int since this is the widget data type.
+    private int[][widgetID] widgetData;     // Data for all widgets under this window.
+    private Widget[widgetID] widgets;       // List of all widgets under this window (created on demand).
+    
+    Widget widget;                  // The primary widget in this window.
+    int x,y;                        // Window position
+    int w,h;                        // Window size (calculated from Widgets)
+    
+    const BORDER_WIDTH = 5;         // Temporary way to handle window decorations
+    
+    
+    // Call after loading is finished to setup the window and confirm that it's valid.
+    void finalise () {
+        // Create the widget, throwing on error:
+        widget = getWidget (0);     // primary widget always has ID 0.
+        widget.getSize (w,h);       // Find the initial size
+        w += BORDER_WIDTH * 2;      // Adjust for border
+        h += BORDER_WIDTH * 2;
+    }
+    
+    Widget getWidget (widgetID i) {
+        // See if it's already been created:
+        Widget* 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:
+            Widget w = createWidget (*d);
+            widgets[i] = w;
+            return w;
+        }
+    }
+    
+    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
+}
--- a/mde/mde.d	Thu Apr 03 18:15:02 2008 +0100
+++ b/mde/mde.d	Fri Apr 04 17:07:38 2008 +0100
@@ -24,6 +24,7 @@
 import global = mde.global;             // global.run
 import mde.SDL;                         // unused (but must be linked in)
 import mde.events;                      // unused (but must be linked in)
+import mde.gui.gui;                     // unused (but must be linked in)
 
 import mde.scheduler.Init;
 import mde.scheduler.runTime;           // Scheduler.run()
--- a/mde/options.d	Thu Apr 03 18:15:02 2008 +0100
+++ b/mde/options.d	Fri Apr 04 17:07:38 2008 +0100
@@ -134,7 +134,7 @@
             logger.error (ERR_MSG);
         }
     }
-        
+    
     // Track all sections for saving/loading/other generic handling.
     static Options[ID] subClasses;
     static OptionsGeneric[ID] subClassChanges;
--- a/mde/resource/paths.d	Thu Apr 03 18:15:02 2008 +0100
+++ b/mde/resource/paths.d	Fri Apr 04 17:07:38 2008 +0100
@@ -71,7 +71,15 @@
     */
     IReader makeMTReader (char[] file, PRIORITY readOrder, DataSet ds = null, bool rdHeader = false)
     {
-        if (readOrder == PRIORITY.HIGH_ONLY) return makeReader (paths[pathsLen-1] ~ file, ds, rdHeader);
+        if (readOrder == PRIORITY.HIGH_ONLY) {
+            foreach_reverse (path; paths) {     // starting with highest-priority path...
+                try {
+                    return makeReader (path~file, ds, rdHeader);
+                }
+                catch (MTFileIOException) {}    // Ignore errors regarding no file for now.
+            }
+            throw new MTFileIOException ("Unable to find the file: "~file~"[.mtt|mtb]");
+        }
         else return new mdeReader (file, readOrder, ds, rdHeader, paths);
     }
     
@@ -246,7 +254,7 @@
         }
         
         if (readersLen == 0) {          // totally failed to find any valid files
-            throw new MTFileIOException ("Unable to find the file: "~file[1..$]~"[.mtt|mtb]");
+            throw new MTFileIOException ("Unable to find the file: "~file~"[.mtt|mtb]");
         }
         
         // This is simply the easiest way of adjusting the reading order:
--- a/mde/scheduler/InitFunctions.d	Thu Apr 03 18:15:02 2008 +0100
+++ b/mde/scheduler/InitFunctions.d	Fri Apr 04 17:07:38 2008 +0100
@@ -15,7 +15,12 @@
 
 /** This module is responsible for calling all init functions.
 *
-* It is also responsible for setting up all scheduled functions for now. */
+* It is also responsible for setting up all scheduled functions for now.
+
+* Idea: change import direction so this module adds all init functions. All init functions are
+* wrapped in another function before being run in a thread (i.e. run indirectly). Functions fail
+* either by throwing an exception or by returning a boolean. Functions may take parameters, e.g.
+* "out cleanupFunc[]". */
 module mde.scheduler.InitFunctions;
 
 static import mde.gl;