Mercurial > projects > mde
changeset 75:25cb7420dc91
A massive overhaul/rewrite for the gui's data management and setup code. Currently much that was working is broken.
imde's classes are created in a static this instead of mde's main.
gl setup code moved from gl/basic.d to gl/draw.d
mergetag.DefaultData: now HIGH_LOW priority instead of LOW_HIGH. Reduced type list to only used types; small fix for indent function.
setup.paths: new NoFileException thrown instead of MTFileIOException
author | Diggory Hardy <diggory.hardy@gmail.com> |
---|---|
date | Mon, 28 Jul 2008 18:17:48 +0100 |
parents | cee261eba249 |
children | 65780e0e48e6 |
files | codeDoc/gui/GUI notes.txt codeDoc/jobs.txt codeDoc/todo.txt data/conf/gui.mtt mde/gl/basic.d mde/gl/draw.d mde/gui/Gui.d mde/gui/IGui.d mde/gui/WidgetData.d mde/gui/WidgetManager.d mde/gui/exception.d mde/gui/renderer/IRenderer.d mde/gui/widget/Ifaces.d mde/gui/widget/TextWidget.d mde/gui/widget/Widget.d mde/gui/widget/Window.d mde/gui/widget/createWidget.d mde/gui/widget/miscWidgets.d mde/imde.d mde/lookup/Options.d mde/lookup/Translation.d mde/mde.d mde/mergetag/DefaultData.d mde/setup/init2.d mde/setup/paths.d mde/setup/sdl.d |
diffstat | 26 files changed, 847 insertions(+), 583 deletions(-) [+] |
line wrap: on
line diff
--- a/codeDoc/gui/GUI notes.txt Mon Jul 07 15:54:47 2008 +0100 +++ b/codeDoc/gui/GUI notes.txt Mon Jul 28 18:17:48 2008 +0100 @@ -5,18 +5,12 @@ * means done GUI: --> Basic OpenGL code to: - ->* create orthographic projection - ->* draw boxes - -> maybe more (text, textures, ...) -> Windows with size & position -> position from Gui -> Widgets: ->* minimum size but expandable, auto-set -> no ability to resize yet except from config files -> scripted widgets --> Text rendering - -> text library? -> Drag & drop -> click/drag start triggers a callback on the widget -> when button is released, callback:
--- a/codeDoc/jobs.txt Mon Jul 07 15:54:47 2008 +0100 +++ b/codeDoc/jobs.txt Mon Jul 28 18:17:48 2008 +0100 @@ -9,6 +9,7 @@ To do (importance 0-5: 0 pointless, 1 no obvious impact now, 2 todo sometime, 3 useful, 4 important, 5 urgent): Also see todo.txt and FIXME/NOTE comment marks. +4 struct (via tuple) support for parseTo/From. Requires rewriting with static if (is T : type). 4 Try to correlate names of option sections more. (i.e. symbol name, class name, name of i18n translation file) 4 Not guaranteed to catch up-click ending callback! Appears not to be a problem... 4 OutOfMemoryException is not currently checked for − it should be at least in critical places (use high-level catching of all errors?).
--- a/codeDoc/todo.txt Mon Jul 07 15:54:47 2008 +0100 +++ b/codeDoc/todo.txt Mon Jul 28 18:17:48 2008 +0100 @@ -2,13 +2,9 @@ License: GNU General Public License version 2 or later (see COPYING) -* means done: - GUI: -> Widgets: -> rethink how widgets are created and receive creation data, so that they don't have to be created by the Window - ->* minimum size but expandable, auto-set - ->* grid "layout" widgets -> scripted widgets -> decent rendering/theme system -> lists from content lists @@ -23,7 +19,7 @@ -> widgets can receive int and string data types -> possibilities -> per-widget merging (i.e. separate tag(s) for each widget's data)? - -> if a widget with sub-widgets is defined in a base file, but redesigned in a derived file, any unused widgets with data resolting are not created + -> if a widget with sub-widgets is defined in a base file, but redesigned in a derived file, any unused widgets with data resulting are not created -> if design changes in a base file, while the old design was modified in a derived file, will the result be sane? -> if a locally modified gui is updated upstream (so the base files change), should: -> the local modifications persist?
--- a/data/conf/gui.mtt Mon Jul 07 15:54:47 2008 +0100 +++ b/data/conf/gui.mtt Mon Jul 28 18:17:48 2008 +0100 @@ -1,5 +1,9 @@ {MT01} -!<char[]|Renderer="Simple"> +<char[]|Renderer="Simple"> +<char[]|Design="Basic"> +{Basic} +<WidgetData|root=[0x4010,200,200],""> +!{ {W1} <int|x=30> <int|y=80> @@ -13,3 +17,4 @@ <int|x=20> <int|y=100> <int[][int]|widgetData=[0:[0xB005,0,0xB04000]]> +} \ No newline at end of file
--- a/mde/gl/basic.d Mon Jul 07 15:54:47 2008 +0100 +++ b/mde/gl/basic.d Mon Jul 28 18:17:48 2008 +0100 @@ -13,7 +13,7 @@ You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. */ -/** Some basic OpenGL code for setting up a projection and drawing. +/** Some basic OpenGL code for drawing. * * Everything here is really intended as makeshift code to enable GUI development. */ module mde.gl.basic; @@ -22,43 +22,6 @@ import tango.time.Time; // TimeSpan (type only; unused) -//BEGIN GL & window setup -void glSetup () { - glDisable(GL_LIGHTING); - glDisable(GL_DEPTH_TEST); - glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP); - glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP); - glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - glEnable(GL_TEXTURE_2D); - glShadeModel(GL_SMOOTH); - - glClearColor (0.0f, 0.0f, 0.0f, 0.0f); - - glMatrixMode(GL_MODELVIEW); - glLoadIdentity(); - - // Used for font rendering: - glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - //NOTE: wrap mode may have an effect, but shouldn't be noticed... -} - -void setProjection (int w, int h) { - glMatrixMode (GL_PROJECTION); - glLoadIdentity (); - - glViewport (0,0,w,h); - - // Make the top-left the origin (see gui/GUI notes.txt): - // Note that this only affects vertex operations − direct rasterisation operations are - // unaffected! - glOrtho (0.0,w, h,0.0, -1.0, 1.0); - - glMatrixMode(GL_MODELVIEW); -} -//END GL & window setup - //BEGIN Drawing utils // Simple drawing commands for use by GUI // (temporary)
--- a/mde/gl/draw.d Mon Jul 07 15:54:47 2008 +0100 +++ b/mde/gl/draw.d Mon Jul 28 18:17:48 2008 +0100 @@ -13,12 +13,13 @@ 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 OpenGL draw loop. +/** The OpenGL draw loop and some basic OpenGL code to set up a projection. * * Everything here is really intended as makeshift code to enable GUI development. */ module mde.gl.draw; -import mde.gui.Gui; +import mde.gui.WidgetManager; +import mde.imde; import derelict.sdl.sdl; import derelict.opengl.gl; @@ -32,6 +33,46 @@ logger = Log.getLogger ("mde.gl.draw"); } +//BEGIN GL & window setup +void glSetup () { + glDisable(GL_LIGHTING); + glDisable(GL_DEPTH_TEST); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glEnable(GL_TEXTURE_2D); + glShadeModel(GL_SMOOTH); + + glClearColor (0.0f, 0.0f, 0.0f, 0.0f); + + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + + // Used for font rendering: + glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + //NOTE: wrap mode may have an effect, but shouldn't be noticed... +} + +void setProjection (int w, int h) { + glMatrixMode (GL_PROJECTION); + glLoadIdentity (); + + glViewport (0,0,w,h); + + // Make the top-left the origin (see gui/GUI notes.txt): + // Note that this only affects vertex operations − direct rasterisation operations are + // unaffected! + glOrtho (0.0,w, h,0.0, -1.0, 1.0); + + glMatrixMode(GL_MODELVIEW); + + // The gui is tied to this viewport. + gui.setSize (w,h); +} +//END GL & window setup + //BEGIN Drawing loop // Temporary draw function void draw (TimeSpan) {
--- a/mde/gui/Gui.d Mon Jul 07 15:54:47 2008 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,206 +0,0 @@ -/* 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 Gui class. -* -* This is the module to use externally to create a graphical user interface (likely also with -* content modules). -* -* Possibly add a GuiManager to update all active GUIs and pass coordinates (remapping if necessary). */ -module mde.gui.Gui; - -import mde.gui.IGui; -import mde.gui.widget.Ifaces; -import mde.gui.widget.Window; -import mde.gui.exception; - -// For adding the input event callbacks and requesting redraws: -import imde = mde.imde; -import mde.input.Input; -import mde.scheduler.Scheduler; - -// For loading from file: -import mt = mde.mergetag.DataSet; -import mt = mde.mergetag.DefaultData; -import mt = mde.mergetag.exception; -import mde.mergetag.Reader; -import mde.mergetag.Writer; -import mde.setup.paths; - -import tango.util.log.Log : Log, Logger; - -private Logger logger; -static this () { - logger = Log.getLogger ("mde.gui.gui"); - - gui = new Gui; // until Guis are handled otherwise, this may as well be the case -} - -Gui gui; // Currently just one instance; handle differently later. -// Handle externally or with a GUI Manager? - -/** A GUI handles a bunch of windows, all to be drawn to the same device. */ -class Gui : IGui { - //BEGIN Methods for external use - //BEGIN Loading code - /** Load all windows from the file gui. */ - void load (char[] fileName) { - 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_LOW, null, true); - reader.dataSecCreator = delegate mt.IDataSection(mt.ID id) { - return new Window (id); - }; - reader.read; - } catch (Exception e) { - logger.error ("Unable to load GUI: errors parsing config file ("~confDir.getFileName(fileName,PRIORITY.HIGH_LOW)~"):"); - logger.error (e.msg); - throw new GuiException ("Failure parsing config file"); - } - - // Get the renderer - char[]* p = "Renderer" in reader.dataset.header.Arg!(char[]); - if (p is null || *p is null) { - logger.warn ("no renderer specified: defaulting to Simple"); - rendName = "Simple"; - } - else - rendName = *p; - - // 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 { - w.finalise (this); // if this fails, the window (but nothing else) won't work - windows ~= w; // only add if load successful - } catch (Exception e) { - logger.error ("Window failed to load: " ~ e.msg); - } - } - - imde.input.addMouseClickCallback(&clickEvent); - imde.input.addMouseMotionCallback(&motionEvent); - } - - void save (char[] fileName) { - mt.DataSet ds = new mt.DataSet; - - // Add header: - ds.header = new mt.DefaultData; - ds.header.Arg!(char[])["Renderer"] = rendName; - - // Add windows to be saved: - foreach (window; windows) - ds.sec [window.name] = window; - - try { // Save - IWriter writer; - writer = confDir.makeMTWriter (fileName, ds); - writer.write; - } catch (mt.MTException e) { - logger.error ("Saving GUI failed:"); - logger.error (e.msg); - - return; - } - } - //END Loading code - - /** 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_reverse (w; windows) // Draw, starting with back-most window. - w.draw; - } - - /** For mouse click events. - * - * Sends the event on to the relevant windows and all click callbacks. */ - void clickEvent (ushort cx, ushort cy, ubyte b, bool state) { - debug scope (failure) - logger.warn ("clickEvent: failed!"); - - // NOTE: buttons receive the up-event even when drag-callbacks are in place. - foreach (dg; clickCallbacks) - if (dg (cast(wdabs)cx, cast(wdabs)cy, b, state)) return; // See IGui.addClickCallback's documentation - - foreach (i,w; windows) { - IWidget widg = w.getWidget (cast(wdabs)cx,cast(wdabs)cy); - if (widg !is null) { - // Bring to front - windows = w ~ windows[0..i] ~ windows[i+1..$]; - - widg.clickEvent (cast(wdabs)cx,cast(wdabs)cy,b,state); - requestRedraw; // in case we've only moved to front - return; // only pass to first window - } - } - } - - /** For mouse motion events. - * - * Sends the event on to all motion callbacks. */ - void motionEvent (ushort cx, ushort cy) { - debug scope (failure) - logger.warn ("motionEvent: failed!"); - foreach (dg; motionCallbacks) - dg (cast(wdabs)cx, cast(wdabs)cy); - } - - //END Methods for external use - - //BEGIN IGui methods - char[] rendererName () { - return rendName; - } - - void requestRedraw () { - imde.mainSchedule.request(imde.SCHEDULE.DRAW); - } - - void addClickCallback (bool delegate(wdabs, wdabs, ubyte, bool) dg) { - clickCallbacks[dg.ptr] = dg; - } - void addMotionCallback (void delegate(wdabs, wdabs) dg) { - motionCallbacks[dg.ptr] = dg; - } - void removeCallbacks (void* frame) { - clickCallbacks.remove(frame); - motionCallbacks.remove(frame); - } - //END IGui methods - -private: - Window[] windows; // Windows. First window is "on top", others may be obscured. - - char[] rendName; // Name of renderer; for saving and creating renderers - - // callbacks indexed by their frame pointers: - bool delegate(wdabs cx, wdabs cy, ubyte b, bool state) [void*] clickCallbacks; - void delegate(wdabs cx, wdabs cy) [void*] motionCallbacks; -}
--- a/mde/gui/IGui.d Mon Jul 07 15:54:47 2008 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,68 +0,0 @@ -/* 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/>. */ - -/** Contains the IGui interface and some basic types used by the gui package. */ -module mde.gui.IGui; - -/** Window coordinate and dimension/size type (int). - * - * Used to disambiguate between general integers and coordinates; all widget positions/sizes should - * use this type (or one of the aliases below). */ -typedef int wdim; - -/** Aliases of wdim providing extra information about what their contents hold: absolute position, - * position relative to the containing widget (wdrel should not be used if relative to anything - * else), or size. Their use instead of wdim is optional (and in some cases wdim values aren't of - * any of these types). Also don't use these aliases for variables which may also be used to other - * effects, e.g. if they can have special values with special meanings. */ -alias wdim wdabs; -alias wdim wdrel; /// ditto -alias wdim wdsize; /// ditto - -/** A pair of wdim variables, and strictly no other data (methods may be added if deemed useful). - * - * Potentially usable to return two wdim variables, e.g. width and height, from a function. - * However, the current usage of out variables looks like it's better. */ -struct wdimPair { - wdim x, y; /// data -} - -/** The Gui interface. -* -* This contains the functions for use by Windows, not those for external use (use Gui directly for -* that). */ -interface IGui -{ - /** Get the GUI's rendererName for creating a renderer (may be overridden by the window). */ - char[] rendererName (); - - /** Called by a sub-widget when a redraw is necessary (since drawing may sometimes be done on - * event. - * - * Currently forces the whole Gui to be redrawn. */ - void requestRedraw (); - - /** Add a mouse click callback: delegate will be called for all mouse click events recieved. - * - * The delegate should return true if it accepts the event and no further processing is - * required (i.e. the event should not be handled by anything else), false otherwise. - * Note that this is not a mechanism to prevent unwanted event handling, and in the future may - * be removed (so event handling cannot be cut short). */ - void addClickCallback (bool delegate (wdabs cx, wdabs cy, ubyte b, bool state) dg); - /** Add a mouse motion callback: delegate will be called for all motion events recieved. */ - void addMotionCallback (void delegate (wdabs cx, wdabs cy) dg); - /** Remove all event callbacks with _frame pointer frame. */ - void removeCallbacks (void* frame); -}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mde/gui/WidgetData.d Mon Jul 28 18:17:48 2008 +0100 @@ -0,0 +1,351 @@ +/* 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/>. */ + +/************************************************************************************************* + * Code to manage the data used to create widgets and save changes to it. + * + * When loading, a WidgetDataSet instance is loaded from file and its data used to create the + * widgets. An empty WidgetDataChanges instance is also created. + * + * If any data requires changing, it is added to the WidgetDataChanges instance, which also + * updates the the WidgetDataSet instance used to load the widgets (in case of a re-load from this + * data). When the data should be saved, if the WidgetDataChanges instance is not empty, data from + * the highest priority (i.e. the user) file is merged into it, preserving both the current + * changes and previous changes saved to the use file, before saving to the user file. + *************************************************************************************************/ +module mde.gui.WidgetData; + +import mde.gui.exception; +import mde.gui.widget.Ifaces; +import mde.gui.widget.createWidget; + +// For loading from file: +import mt = mde.mergetag.DataSet; +import mt = mde.mergetag.DefaultData; +import mt = mde.mergetag.exception; +import mde.mergetag.Reader; +import mde.mergetag.Writer; +import mde.setup.paths; +import mde.mergetag.parse.parseTo; +import mde.mergetag.parse.parseFrom : parseFrom; + +import tango.core.sync.Mutex; +import tango.util.log.Log : Log, Logger; + +private Logger logger; +static this () { + logger = Log.getLogger ("mde.gui.WidgetData"); +} + + +/************************************************************************************************* + * Contains the code for loading and saving the gui, but not the code for drawing it or handling + * user input. + * + * This abstract class exists solely for separating out some of the functionality. + *************************************************************************************************/ +abstract scope class WidgetLoader : IWidgetManager +{ + /** Construct a new widget loader. + * + * params: + * fileName = Name of file specifying the gui, excluding path and extension. + */ + protected this (char[] file) { + mutex = new Mutex; // Used on functions intended to be called from outside the gui package. + fileName = file; + } + ~this () { + save; + } + + /* Load the widgets' data from the file specified to the CTOR. + * + * params: + * allDesigns = Load all sections + */ + private void loadData (bool allDesigns = false) { + if (allLoaded || (defaultDesign !is null && allDesigns == false)) + return; // test if already loaded + + 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!) + } + + // Set up a reader + scope IReader reader; + try { + reader = confDir.makeMTReader (fileName, PRIORITY.HIGH_LOW, null, true); + + // Read from the HEADER: + // Get the renderer + char[]* p = "Renderer" in reader.dataset.header.Arg!(char[]); + if (p is null || *p is null) { + logger.warn ("No renderer specified: using \"Simple\""); + rendName = "Simple"; + } + else + rendName = *p; + + // Get which section to use + p = "Design" in reader.dataset.header.Arg!(char[]); + if (p is null || *p is null) { + logger.warn ("No gui design specified: trying \"Default\""); + defaultDesign = "Default"; + } + else + defaultDesign = *p; + + // Read the body: + // Load the chosen design + reader.dataSecCreator = delegate mt.IDataSection(mt.ID id) { + WidgetDataSet* p = id in data; + if (p is null) { + data[id] = new WidgetDataSet; + return *(id in data); + } + return *p; + }; + + if (allDesigns) { + reader.read; + allLoaded = true; + } else + reader.read([defaultDesign]); + } catch (Exception e) { + logger.error ("Unable to load GUI: errors parsing config file ("~confDir.getFileName(fileName,PRIORITY.HIGH_LOW)~"):"); + logger.error (e.msg); + throw new GuiException ("Failure parsing config file"); + } + } + + /** Load the gui from some design. + * + * If a design was previously loaded, its changes are saved first. + * + * Params: + * name = Design to load. If null, the default will be loaded. + */ + void loadDesign (char[] name = null) { + if (changes !is null) + save; // own lock + + mutex.lock; + scope(exit) mutex.unlock; + + // Load data (loadData tests if it's already loaded first): + if (name is null) { + loadData (false); + name = defaultDesign; + } else + loadData (true); + + + // Get data: + curData = data[name]; // NOTE: may throw + + // Get/create a changes section: + if (changesDS is null) + changesDS = new mt.DataSet; + + mt.IDataSection* p = name in changesDS.sec; + if (p && ((changes = cast(WidgetDataChanges) *p) !is null)) {} + else { + changes = new WidgetDataChanges (curData); + changesDS.sec[name] = changes; + } + + // Create the widgets: + createRootWidget; + } + + /** Save changes, if any exist. + * + * Is run when the manager is destroyed, but could be run at other times too. */ + void save () { + mutex.lock; + scope(exit) mutex.unlock; + + if (noChanges) + return; + + if (loadUserFile) { // merge entries from user file into current changes + try { + scope IReader reader = confDir.makeMTReader ( + fileName, PRIORITY.HIGH_ONLY, changesDS, true); + + // Create if necessary, only corresponding to existing designs read: + reader.dataSecCreator = delegate mt.IDataSection(mt.ID id) { + WidgetDataSet* p = id in data; + if (p is null) + throw new Exception ("File has changed since it was loaded!"); + return new WidgetDataChanges (*p); + }; + + reader.read; + } catch (NoFileException) { + // No user file exists; not an error. + } catch (Exception e) { + logger.error ("Error reading "~confDir.getFileName(fileName,PRIORITY.HIGH_ONLY)~" prior to saving:"); + logger.error (e.msg); + logger.error ("Overwriting the file."); + // Continue... + } + loadUserFile = false; // don't need to do it again + } + + try { // Save + IWriter writer; + writer = confDir.makeMTWriter (fileName, changesDS); + writer.write; + } catch (Exception e) { + logger.error ("Saving to "~confDir.getFileName(fileName,PRIORITY.HIGH_ONLY)~" failed:"); + logger.error (e.msg); + // No point in throwing since it doesn't affect anything else. + } + } + + /** Get the names of all designs available. */ + char[][] designs() { + synchronized(mutex) { + loadData (true); + return data.keys; + } + } + + + /** Create a widget by ID. */ + IChildWidget makeWidget (widgetID id) { + return createWidget (this, curData[id]); + } + + /** For making changes. */ + void setData (widgetID id, WidgetData d) { + noChanges = false; + changes[id] = d; // also updates WidgetDataSet in data. + } + + /** Second stage of loading the widgets. + * + * loadDesign handles the data; this method needs to: + * --- + * // 1. Create the root widget: + * child = makeWidget ("root"); + * // 2. Set the setSize, e.g.: + * child.setWidth (child.minWidth, 1); + * child.setHeight (child.minHeight, 1); + * --- + */ + // FIXME: abstract? + void createRootWidget(); + +protected: + final char[] fileName; + char[] defaultDesign; // The design specified in the file header. + char[] rendName; // Name of renderer; for saving and creating renderers + + // Loaded data, indexed by design name. May not be loaded for all gui designs: + scope WidgetDataSet[char[]] data; + private bool allLoaded = false; // applies to data + WidgetDataSet curData; // Current data + WidgetDataChanges changes; // Changes for the current design. + scope mt.DataSet changesDS; // changes and sections from user file (used for saving) + bool loadUserFile = true; // still need to load user file for saving? + bool noChanges = true; // do we have any changes to save? + + scope IChildWidget child; // The primary widget. + + Mutex mutex; // lock on methods for use outside the package. +} + + +package: +/************************************************************************************************* + * Contains data for all widgets in a GUI. + *************************************************************************************************/ +class WidgetDataSet : mt.IDataSection +{ + //BEGIN Mergetag code + void addTag (char[] tp, mt.ID id, char[] dt) { + // Priority is HIGH_LOW. Only load tag if it doesn't already exist. + if (tp == "WidgetData" && (id in widgetData) is null) { + // Note: is a very simple form of struct deserialization + WidgetData data; + with(data) { + char[][] strs = split (dt); + if (strs.length != 2) + throw new ParseException ("Not two components"); + ints = parseTo!(int[]) (strs[0]); + str = parseTo!(char[]) (strs[1]); + } + widgetData[id] = data; + } + } + // Only WidgetDataChanges is used for writing. + void writeAll (ItemDelg dlg) {} + //END Mergetag code + + /** Get the widget data for widget i. */ + WidgetData opIndex (widgetID i) { + return widgetData[i]; + } + + // Per-widget data: + protected WidgetData[widgetID] widgetData; +} + +/************************************************************************************************* + * Contains changes to widget data. + * + * Can be read as normal and written. + *************************************************************************************************/ +class WidgetDataChanges : WidgetDataSet +{ + /** + * Params: + * wds = The base WidgetDataSet these changes are applied against. */ + this (WidgetDataSet wds) { + base = wds; + } + + //BEGIN Mergetag code + // HIGH_LOW priority of addTag allows existing entries (i.e. the changes) to be preserved while + // other entries are read from files. + void writeAll (ItemDelg dlg) { + foreach (id,data; widgetData) { + // Note: is a very simple form of struct serialization + with(data) { + dlg ("WidgetData", id, + parseFrom!(int[]) (ints) ~ ',' ~ parseFrom!(char[]) (str) ); + } + } + } + //END Mergetag code + + /** Set the widget data for widget i. + */ + void opIndexAssign (WidgetData d, widgetID i) { + widgetData[i] = d; + base.widgetData[i] = d; + } + + /** Do any changes exist? True if no changes have been stored. */ + bool none () { + return widgetData.length == 0; + } + + protected WidgetDataSet base; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mde/gui/WidgetManager.d Mon Jul 28 18:17:48 2008 +0100 @@ -0,0 +1,179 @@ +/* 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 gui manager class. + * + * This is the module to use externally to create a graphical user interface (likely also with + * content modules). + *************************************************************************************************/ +module mde.gui.WidgetManager; + +public import mde.gui.WidgetData; +import mde.gui.widget.Ifaces; +import mde.gui.renderer.createRenderer; + +// For adding the input event callbacks and requesting redraws: +import imde = mde.imde; +import mde.input.Input; +import mde.scheduler.Scheduler; + +import tango.core.sync.Mutex; +import tango.util.log.Log : Log, Logger; + +private Logger logger; +static this () { + logger = Log.getLogger ("mde.gui.WidgetManager"); + + gui = new WidgetManager ("gui"); +} + +WidgetManager gui; + + +/************************************************************************************************* + * The widget manager. + * + * This is responsible for loading and saving an entire gui (although more than one may exist), + * controlling the rendering device (e.g. the screen or a texture), and providing user input. + * + * Currently mouse coordinates are passed to widgets untranslated. It may make sense to translate + * them and possibly drop events for some uses, such as if the gui is drawn to a texture. + * + * Aside from the IWidgetManager methods, this class should be thread-safe. + *************************************************************************************************/ +class WidgetManager : WidgetLoader { + /** Construct a new widget manager. + * + * params: + * fileName = Name of file specifying the gui, excluding path and extension. + */ + this (char[] file) { + super(file); + + // Events we want to know about: + imde.input.addMouseClickCallback(&clickEvent); + imde.input.addMouseMotionCallback(&motionEvent); + } + + + /** Draw the gui. */ + void draw() { + synchronized(mutex) + child.draw; + } + + + /** For mouse click events. + * + * Sends the event on to the relevant windows and all click callbacks. */ + void clickEvent (ushort cx, ushort cy, ubyte b, bool state) { + debug scope (failure) + logger.warn ("clickEvent: failed!"); + mutex.lock; + scope(exit) mutex.unlock; + + // NOTE: buttons receive the up-event even when drag-callbacks are in place. + foreach (dg; clickCallbacks) + // See IWidgetManager.addClickCallback's documentation: + if (dg (cast(wdabs)cx, cast(wdabs)cy, b, state)) return; + + /+ + foreach (i,w; windows) { + IWidget widg = w.getWidget (cast(wdabs)cx,cast(wdabs)cy); + if (widg !is null) { + // Bring to front + windows = w ~ windows[0..i] ~ windows[i+1..$]; + + widg.clickEvent (cast(wdabs)cx,cast(wdabs)cy,b,state); + requestRedraw; // in case we've only moved to front + return; // only pass to first window + } + }+/ + } + + /** For mouse motion events. + * + * Sends the event on to all motion callbacks. */ + void motionEvent (ushort cx, ushort cy) { + debug scope (failure) + logger.warn ("motionEvent: failed!"); + mutex.lock; + scope(exit) mutex.unlock; + + foreach (dg; motionCallbacks) + dg (cast(wdabs)cx, cast(wdabs)cy); + } + + + void setSize (int x, int y) { + mutex.lock; + scope(exit) mutex.unlock; + + w = cast(wdim) x; + h = cast(wdim) y; + + if (child is null) + return; // May not have been created before this is first run. + child.setWidth (w, -1); + child.setHeight (h, -1); + child.setPosition (0,0); + } + + //BEGIN IWidgetManager methods + // These methods are only intended for use within the gui package. They are not necessarily + // thread-safe. + IRenderer renderer () { + assert (rend !is null, "WidgetManager.renderer: rend is null"); + return rend; + } + + void requestRedraw () { + imde.mainSchedule.request(imde.SCHEDULE.DRAW); + } + + void addClickCallback (bool delegate(wdabs, wdabs, ubyte, bool) dg) { + clickCallbacks[dg.ptr] = dg; + } + void addMotionCallback (void delegate(wdabs, wdabs) dg) { + motionCallbacks[dg.ptr] = dg; + } + void removeCallbacks (IChildWidget frame) { + clickCallbacks.remove(cast(void*) frame); + motionCallbacks.remove(cast(void*) frame); + } + //END IWidgetManager methods + +protected: + /* Second stage of widget loading. */ + void createRootWidget () { + // The renderer needs to be created on the first load, but not after this. + if (rend is null) + rend = createRenderer (rendName); + + child = makeWidget ("root"); + + child.setWidth (w, -1); + child.setHeight (h, -1); + child.setPosition (0,0); + } + +private: + // callbacks indexed by their frame pointers: + bool delegate(wdabs cx, wdabs cy, ubyte b, bool state) [void*] clickCallbacks; + void delegate(wdabs cx, wdabs cy) [void*] motionCallbacks; + IRenderer rend; + wdim w,h; // area available to the widgets +}
--- a/mde/gui/exception.d Mon Jul 07 15:54:47 2008 +0100 +++ b/mde/gui/exception.d Mon Jul 28 18:17:48 2008 +0100 @@ -29,16 +29,8 @@ } } -/// Thrown when something goes wrong while loading a window (usually a data error). -class WindowLoadException : GuiException -{ - this (char[] msg) { - super(msg); - } -} - /// Thrown when createWidget or a Widget class's this() is called with invalid data. -class WidgetDataException : WindowLoadException +class WidgetDataException : GuiException { this () { // Default, by Widget class's this super ("Bad widget data");
--- a/mde/gui/renderer/IRenderer.d Mon Jul 07 15:54:47 2008 +0100 +++ b/mde/gui/renderer/IRenderer.d Mon Jul 28 18:17:48 2008 +0100 @@ -16,7 +16,8 @@ /** Interface for the renderer. This is planned to replace decoration.d */ module mde.gui.renderer.IRenderer; -public import mde.gui.IGui; // wdim type is used by just about everything including this +// Put here to avoid circular import. +typedef int wdim; /** Interface for renderers. *
--- a/mde/gui/widget/Ifaces.d Mon Jul 07 15:54:47 2008 +0100 +++ b/mde/gui/widget/Ifaces.d Mon Jul 28 18:17:48 2008 +0100 @@ -13,46 +13,91 @@ You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. */ -/** Window and widget interfaces. */ +/************************************************************************************************* + * Widget interfaces. + * + * Widgets are connected as the nodes of a tree. Widgets know their parent as a IParentWidget + * class and their children as IChildWidget classes. The gui manager is a special widget only + * implementing IParentWidget; all other widgets must implement IChildWidget and optionally + * IParentWidget. + *************************************************************************************************/ module mde.gui.widget.Ifaces; public import mde.gui.renderer.IRenderer; -public import mde.gui.IGui; + + +/** Widget ID type. Each ID is unique under this window. + * + * Type is int since this is the widget data type. */ +alias char[] widgetID; -/** Interface for Window, allowing widgets to call some of Window's methods. +/** Window coordinate and dimension/size type (int). * - * Contains the methods in Window available for widgets to call on their root. + * Used to disambiguate between general integers and coordinates; all widget positions/sizes should + * use this type (or one of the aliases below). + * + * --- + * typedef int wdim; // Declared in IRenderer to avoid a circular import. + * --- + * + * Aliases of wdim providing extra information about what their contents hold: absolute position, + * position relative to the containing widget (wdrel should not be used if relative to anything + * else), or size. Their use instead of wdim is optional (and in some cases wdim values aren't of + * any of these types). Also don't use these aliases for variables which may also be used to other + * effects, e.g. if they can have special values with special meanings. */ +alias wdim wdabs; +alias wdim wdrel; /// ditto +alias wdim wdsize; /// ditto + +/** A pair of wdim variables, and strictly no other data (methods may be added if deemed useful). + * + * Potentially usable to return two wdim variables, e.g. width and height, from a function. + * However, the current usage of out variables looks like it's better. */ +struct wdimPair { + wdim x, y; /// data +} + + +/************************************************************************************************* + * Common interface for all widgets. * * Notation: * Positive/negative direction: along the x/y axis in this direction. - * Layout widget: a widget containing multiple sub-widges (which hence controls how they are laid - * out). */ -interface IWindow : IWidget + * Layout widget: a widget containing multiple sub-widges (which hence controls how they are + * laid out). + *************************************************************************************************/ +//NOTE: keep this? +interface IWidget { - /** Widget ID type. Each ID is unique under this window. - * - * Type is int since this is the widget data type. */ - alias int widgetID; - - /** Get a widget by ID. +} + + +/************************************************************************************************* + * Interface for the widget manager. + * + * This class handles widget rendering, input, loading and saving. + *************************************************************************************************/ +interface IWidgetManager : IParentWidget +{ + // Loading/saving: + /** Create a widget by ID. * - * Returns the widget with the given ID from the Window's widget list. If the widget hasn't yet - * been created, creates it using the Window's widget creation data. */ - IWidget makeWidget (widgetID i); - - /** Get a string from the widgetString associative array. */ - char[] getWidgetString (int i); + * Creates a widget, using the widget data with index id. Widget data is loaded from files, + * and per design (multiple gui layouts, called designs, may exist; data is per design). + * + * Note: this method is only for "named" widgets; generic widgets instanciated in lists are + * created differently. */ + IChildWidget makeWidget (widgetID id); - /** Add widget's saveData to the data to be saved, returning it's widgetID. */ - widgetID addCreationData (IWidget widget); + /** Record some changes, for saving. */ + void setData (widgetID id, WidgetData); - /** Add a string to the widgetString associative array, returning it's index. */ - int addWidgetString (char[] str); - - /** Returns the window's gui. */ - IGui gui (); - - /** The widget/window needs redrawing. */ + // Rendering: + /** For when a widget needs redrawing. + * + * Must be called because rendering may only be done on events. + * + * Currently it just causes everything to be redrawn next frame. */ void requestRedraw (); /** Get the window's renderer. @@ -60,67 +105,84 @@ * Normally specific to the GUI, but widgets have no direct contact with the GUI and this * provides the possibility of per-window renderers (if desired). */ IRenderer renderer (); + + + // User input: + /** Add a mouse click callback: delegate will be called for all mouse click events recieved. + * + * The delegate should return true if it accepts the event and no further processing is + * required (i.e. the event should not be handled by anything else), false otherwise. + * + * Note that this is not a mechanism to prevent unwanted event handling, and in the future + * may be removed (so event handling cannot be cut short). */ + void addClickCallback (bool delegate (wdabs cx, wdabs cy, ubyte b, bool state) dg); + + /** Add a mouse motion callback: delegate will be called for all motion events recieved. */ + void addMotionCallback (void delegate (wdabs cx, wdabs cy) dg); + + // FIXME: keyboard callback (letter only, for text input? Also used for setting keybindings though...) + + /** Remove all event callbacks on this widget (according to the delegate's .ptr). */ + void removeCallbacks (IChildWidget frame); } -/** Interface for widgets. + +/************************************************************************************************* + * Interface for parent widgets, including the gui manager. * - * Note that Window also implements this interface so that widgets can interact with their parent - * in a uniform way. + * A widget may call these methods on its parent, and on the gui manager. + *************************************************************************************************/ +interface IParentWidget : IWidget +{ + // NOTE: Likely some day this interface will really be used. + // NOTE: What widget is NOT going to implement this? It will probably be inherited. +} + + +/************************************************************************************************* + * Interface for (child) widgets, i.e. all widgets other than the manager. * * A widget is a region of a GUI window which handles rendering and user-interaction for itself - * and is able to communicate with it's window and parent/child widgets as necessary. + * and is able to communicate with its manager and parent/child widgets as necessary. * - * If a widget is to be creatable by Window.makeWidget, it must be listed in the createWidget - * module, have a constructor of the following form, and should implement getCreationData(). - * Use Ddoc to explain what initialization data is used. + * If a widget is to be creatable by IWidgetManager.makeWidget, it must be listed in the + * createWidget module, have a constructor of the following form, and should update it's + * creation data as necessary via IWidgetManager.setData(). + * It should use Ddoc to explain what initialization data is used. * ---------------------------------- * /++ Constructor for a ... widget. * + * + Widget uses the initialisation data: * + [widgetID, x, y] * + where x is ... and y is ... +/ - * this (IWindow window, int[] data); + * this (IWidgetManager mgr, WidgetData data); * ---------------------------------- - * Where window is the root window (the window to which the widget belongs) and data is an array of - * initialisation data. The method should throw a WidgetDataException (created without parameters) - * if the data has wrong length or is otherwise invalid. + * Where mgr is the widget manager and data is + * initialisation data. The method should throw a WidgetDataException (created without + * parameters) if the data has wrong length or is otherwise invalid. * - * The widget's size should be set either by it's this() method or by the first call to - * setSize(). setSize() is called on all widgets immediately after their creation, and throwing an - * exception at this point (but not on later calls to setSize) is an acceptible method of failure. - */ + * A parent widget is responsible for setting the size of its children widgets, however it must + * satisfy their minimal sizes as available from minWidth() and minHeight(). setWidth() and + * setHeight() are called on all widgets after creation. + *************************************************************************************************/ //NOTE: add another this() without the data for default initialization, for the GUI editor? -interface IWidget +interface IChildWidget : IWidget { //BEGIN Load and save - /** Called after creating widgets to adjust size & other mutable attributes from saved data. - * - * As for setSize, setPosition should be called afterwards. - * - * Each widget should call adjust on each of its sub-widgets in turn with data, each time - * replacing data by the return value of the call. It should then take its own mutable data - * from the beginning of the array and return the remainder of the array. - * - * Adjust should handle errors gracefully by reverting to default values and not throwing. - * This is because the creation data and the user's mutable data may be stored separately and - * become out-of-sync during an update. */ - int[] adjust (int[] data); + /** Called when the renderer is changed (at least when the changes affect dimensions). + * Also called after widget creation, before any other methods are called. + * + * Returns: true when widget's dimensions (may) have changed. + * + * Should be propegated down to all child widgets. */ + bool rendererChanged (); - /** Output data suitible for recreating the widget (data to be passed to this()). - * - * Function may need to call Window's addCreationData() and addWidgetString() methods to save - * other data. - * - * Creation data is data only changed when the gui is edited. */ - int[] getCreationData (); - - /** Output data containing the widget's current adjustments (data to be passed to adjust()). - * - * Mutable data is data which can be changed during normal gui use, such as the size of - * resizible widgets or current tab of a tab widget. - * - * Should be a concatenation of each sub-widget's mutable data and the widget's own. */ - int[] getMutableData (); + /+ Use when widget editing is available? Requires widgets to know their parents. + /** Called when a child widget's size has changed. + * + * Should be propegated up to parents. */ + void childChanged (); + +/ //END Load and save //BEGIN Size and position @@ -136,8 +198,11 @@ wdim minWidth (); wdim minHeight (); /// ditto - /** Get the current size of the widget. */ - void getCurrentSize (out wdim w, out wdim h); + /** Get the current size of the widget. + * + * Deprecated: is it needed now? + */ + deprecated void getCurrentSize (out wdim w, out wdim h); /** Used to adjust the size. * @@ -148,15 +213,12 @@ * index as necessary), or +1 (resize from the lowest index, i.e. 0). * Most widgets can simply ignore it. * - * Implementation: - * The size should be clamped to the widget's minimal size, i.e. the size set may be larger - * than that given by the parameters. Conversely, the size should not be reduced to the - * widget's maximal size (if any) but expanded as necessary (alignment to be implemented). - * This should be true for both resizable and fixed widgets; fixed widgets may still be scaled - * to fill a whole row/column in a layout widget. + * If called with dimensions less than minWidth/minHeight return: the widget may set its size + * to either the dimension given or its minimal dimension (even though this is larger). If the + * larger size is set, events won't be received in the extra area. FIXME: sort out rendering. + * Otherwise, the dimensions should always be set exactly. * - * If the actual size is needed, call getCurrentSize afterwards. setPosition must be called - * afterwards if the widget might be a layout widget. */ + * setPosition must be called after calling either setWidth or setHeight. */ void setWidth (wdim nw, int dir); void setHeight (wdim nh, int dir); /// ditto @@ -191,3 +253,16 @@ * part of the widget is visible: scroll bars or a hidden window. */ void draw (); } + + +/************************************************************************************************* + * The data type all widgets creatable by the widget manager receive on creation. + * + * Conversion code to/from MT tags is contained in the addTag and writeAll methods of + * WidgetDataSet and WidgetDataChanges. + *************************************************************************************************/ +struct WidgetData +{ + int[] ints; // An array of integer data + char[] str; // One string +}
--- a/mde/gui/widget/TextWidget.d Mon Jul 07 15:54:47 2008 +0100 +++ b/mde/gui/widget/TextWidget.d Mon Jul 28 18:17:48 2008 +0100 @@ -68,11 +68,11 @@ * [widgetID, contentID, colour] * where contentID is an ID for the string ID of the contained content * and colour is an 8-bit-per-channel RGB colour of the form 0xRRGGBB. */ - this (IWindow wind, int[] data) { - if (data.length != 3) throw new WidgetDataException; - text.set (wind.getWidgetString(data[1]), data[2]); + this (IWidgetManager mgr, WidgetData data) { + if (data.length != 2) throw new WidgetDataException; + text.set (data.str, data[1]); text.getDimensions (mw, mh); - super (wind,data); + super (mgr,data); } void draw () {
--- a/mde/gui/widget/Widget.d Mon Jul 07 15:54:47 2008 +0100 +++ b/mde/gui/widget/Widget.d Mon Jul 28 18:17:48 2008 +0100 @@ -28,32 +28,17 @@ * This abstract class, and the more concrete FixedWidget and ScalableWidget classes provides a * useful basic implementation for widgets. Widgets need not inherit these (they only need implement * IWidget); they are simply provided for convenience and to promote code reuse. */ -abstract class Widget : IWidget +abstract class Widget : IChildWidget { //BEGIN Load and save - // Base this(). All widgets must check data.length is correct before calling this method. - // The widget ID is saved to widgetType, for correct saving. - this (IWindow wind, int[] data) { - window = wind; - widgetType = data[0]; + // Base this() for child Widgets. + this (IWidgetManager mgr, WidgetData data) { + this.mgr = mgr; } - // Most widgets don't need to do adjustments based on mutable data, however they usually do - // still need to set their size. - int[] adjust (int[] data) { - setWidth (0,-1); - setHeight (0,-1); - return data; - } - - // Widget type should always be the first value. Any widget using extra creation data will need - // to reimplemnt this method. - int[] getCreationData () { - return [widgetType]; - } - // Most widgets don't use mutable data. - int[] getMutableData () { - return []; + // Very basic implementation which assumes the renderer cannot affect the widget's size. + bool rendererChanged () { + return false; } //END Load and save @@ -69,7 +54,7 @@ return mh; } - void getCurrentSize (out wdim cw, out wdim ch) { + deprecated void getCurrentSize (out wdim cw, out wdim ch) { cw = w; ch = h; } @@ -102,12 +87,11 @@ /* Basic draw method: draw the background (all widgets should do this). */ void draw () { - window.renderer.drawWidgetBack (x,y, w,h); + mgr.renderer.drawWidgetBack (x,y, w,h); } protected: - final int widgetType; // the type (stored for saving) - IWindow window; // the enclosing window + IWidgetManager mgr; // the enclosing window wdim x, y; // position wdim w, h; // size wdim mw = 0, mh = 0; // minimal or fixed size, depending on whether the widget is @@ -122,24 +106,21 @@ * Widget uses the initialisation data: * [widgetID, w, h] * where w, h is the fixed size. */ - this (IWindow wind, int[] data) { - mw = cast(wdim) data[1]; - mh = cast(wdim) data[2]; - super (wind, data); + this (IWidgetManager mgr, WidgetData data) { + super (mgr, data); + mw = cast(wdim) data.ints[1]; + mh = cast(wdim) data.ints[2]; w = mw; h = mh; } - - int[] getCreationData () { - return [widgetType, mw, mh]; - } } + /** A base for resizable widgets. */ class SizableWidget : Widget { // Check data.length is at least 1 before calling! /// Constructor for a completely resizable [blank] widget. - this (IWindow wind, int[] data) { - super (wind, data); + this (IWidgetManager mgr, WidgetData data) { + super (mgr, data); } bool isWSizable () { return true; }
--- a/mde/gui/widget/Window.d Mon Jul 07 15:54:47 2008 +0100 +++ b/mde/gui/widget/Window.d Mon Jul 28 18:17:48 2008 +0100 @@ -97,46 +97,6 @@ xw = x+w; yh = y+h; } - //BEGIN Mergetag code - void addTag (char[] tp, mt.ID id, char[] dt) { - // Priority is HIGH_LOW, so don't overwrite data which has already been loaded. - if (tp == "int[][int]") { - if (id == "widgetData" && widgetData == null) { - widgetData = cast(int[][widgetID]) parseTo!(int[][int]) (dt); - } - } else if (tp == "char[][int]") { - if (id == "widgetStrings" && widgetStrings == null) { - widgetStrings = parseTo!(char[][int]) (dt); - } - } else if (tp == "int[]") { - if (id == "mutableData" && mutableData == null) { - mutableData = parseTo!(int[]) (dt); - } - } else if (tp == "int") { - if (id == "x" && x == -1) { - x = cast(wdim) parseTo!(int) (dt); - } else if (id == "y" && y == -1) { - y = cast(wdim) parseTo!(int) (dt); - } - } - } - void writeAll (ItemDelg dlg) - in { - assert (widgetData is null, "Window.writeAll: widgetData !is null"); - } body { - /+ NOTE: currently editing is impossible... - if (edited) { // only save the widget creation data if it's been adjusted: - addCreationData (widget); // generate widget save data - dlg ("int[][int]", "widgetData", parseFrom!(int[][int]) (widgetData)); - dlg ("char[][int]", "widgetStrings", parseFrom!(char[][int]) (widgetStrings)); - }+/ - // Save mutable data: - dlg ("int[]", "mutableData", parseFrom!(int[]) (widget.getMutableData)); - // Save the window position: - dlg ("int", "x", parseFrom!(int) (x)); - dlg ("int", "y", parseFrom!(int) (y)); - } - //END Mergetag code //END Methods for GUI //BEGIN IWindow methods @@ -385,11 +345,6 @@ public char[] name; // The window's name (id from config file) //bool edited = false; // True if any widgets have been edited (excluding scaling) - // Data used for saving and loading (null in between): - int[][widgetID] widgetData = null;// Data for all widgets under this window - int[] mutableData = null; // Widget's mutable data (adjusted sizes, etc.) - char[][int] widgetStrings = null;// Strings by ID; string-typed creation data - IGui gui_; // The gui managing this window IRenderer rend; // The window's renderer
--- a/mde/gui/widget/createWidget.d Mon Jul 07 15:54:47 2008 +0100 +++ b/mde/gui/widget/createWidget.d Mon Jul 28 18:17:48 2008 +0100 @@ -20,21 +20,20 @@ import mde.gui.exception : WidgetDataException; // Widgets to create: -import mde.gui.widget.layout; +//import mde.gui.widget.layout; import mde.gui.widget.miscWidgets; -import mde.gui.widget.TextWidget; +//import mde.gui.widget.TextWidget; -/** Create a widget of type data[0] (see enum WIDGET_TYPES) for _window window, with initialisation +/** Create a widget of type data[0] (see enum WIDGET_TYPES) under manager mgr, with initialisation * data [1..$]. */ -IWidget createWidget (IWindow window, int[] data) +IChildWidget createWidget (IWidgetManager mgr, WidgetData data) in { - assert (window !is null, "createWidget: window is null"); + assert (mgr !is null, "createWidget: mgr is null"); } body { - if (data.length < 1) throw new WidgetDataException ("No widget data"); - int type = data[0]; // type is first element of data - // the whole of data is passed to the Widget + if (data.ints.length < 1) throw new WidgetDataException ("No widget data"); + int type = data.ints[0]; // type is first element of data - mixin (binarySearch ("type", WIDGETS)); + mixin (binarySearch ("type", WIDGETS)); // creates widget by type as new *Widget (mgr, data); throw new WidgetDataException ("Bad widget type"); } @@ -78,39 +77,27 @@ // Only used for binarySearch algorithm generation; must be ordered by numerical values. const char[][] WIDGETS = [ "FixedBlank", - "Text", - "Int", + //"Text", + //"Int", "SizableBlank", "Button", - "GridLayout", - "TrialContentLayout"]; - -// Purely to add indentation. Could just return "" without affecting functionality. -static char[] indent (uint i) { - char[] ret; - for (; i > 0; --i) ret ~= " "; - // This is not executable at compile time: - //ret.length = i * 4; // number of characters for each indentation - //ret[] = ' '; // character to indent with - return ret; -} -//char[] indent (uint) { return ""; } + //"GridLayout", + /+"TrialContentLayout"+/]; /* Generates a binary search algorithm. */ -char[] binarySearch (char[] var, char[][] consts, int indents = 0) { +char[] binarySearch (char[] var, char[][] consts) { if (consts.length > 3) { - return indent(indents) ~ "if (" ~ var ~ " <= WIDGET_TYPE." ~ consts[$/2 - 1] ~ ") {\n" ~ - binarySearch (var, consts[0 .. $/2], indents + 1) ~ - indent(indents) ~ "} else {\n" ~ - binarySearch (var, consts[$/2 .. $], indents + 1) ~ - indent(indents) ~ "}\n"; + return "if (" ~ var ~ " <= WIDGET_TYPE." ~ consts[$/2 - 1] ~ ") {\n" ~ + binarySearch (var, consts[0 .. $/2]) ~ + "} else {\n" ~ + binarySearch (var, consts[$/2 .. $]) ~ + "}\n"; } else { char[] ret; - ret ~= indent(indents); foreach (c; consts) { ret ~= "if (" ~ var ~ " == WIDGET_TYPE." ~ c ~ ") {\n" ~ - indent(indents+1) ~ "return new " ~ c ~ "Widget (window, data);\n" ~ - indent(indents) ~ "} else "; + "return new " ~ c ~ "Widget (mgr, data);\n" ~ + "} else "; } ret = ret[0..$-6] ~ '\n'; // remove last else return ret;
--- a/mde/gui/widget/miscWidgets.d Mon Jul 07 15:54:47 2008 +0100 +++ b/mde/gui/widget/miscWidgets.d Mon Jul 28 18:17:48 2008 +0100 @@ -26,30 +26,30 @@ /// A fixed-size blank widget. class FixedBlankWidget : FixedWidget { - this (IWindow wind, int[] data) { - if (data.length != 3) throw new WidgetDataException; - super (wind, data); + this (IWidgetManager mgr, WidgetData data) { + if (data.ints.length != 3) throw new WidgetDataException; + super (mgr, data); } void draw () { super.draw; - window.renderer.drawBlank (x,y, w,h); + mgr.renderer.drawBlank (x,y, w,h); } } /// A completely resizable blank widget (initial size zero). class SizableBlankWidget : SizableWidget { - this (IWindow wind, int[] data) { - if (data.length != 1) throw new WidgetDataException; - super (wind, data); + this (IWidgetManager mgr, WidgetData data) { + if (data.ints.length != 1) throw new WidgetDataException; + super (mgr, data); } void draw () { super.draw; - window.renderer.drawBlank (x,y, w,h); + mgr.renderer.drawBlank (x,y, w,h); } } @@ -60,21 +60,21 @@ // pushed is not the same as the button being clicked but not yet released. // it is whether the mouse is over the button after being clicked. - this (IWindow wind, int[] data) { - if (data.length != 3) throw new WidgetDataException; - super (wind, data); + this (IWidgetManager mgr, WidgetData data) { + if (data.ints.length != 3) throw new WidgetDataException; + super (mgr, data); } void draw () { - window.renderer.drawButton (x,y, w,h, pushed); + mgr.renderer.drawButton (x,y, w,h, pushed); } void clickEvent (wdabs, wdabs, ubyte b, bool state) { if (b == 1 && state == true) { pushed = true; - window.requestRedraw; - window.gui.addClickCallback (&clickWhileHeld); - window.gui.addMotionCallback (&motionWhileHeld); + mgr.requestRedraw; + mgr.addClickCallback (&clickWhileHeld); + mgr.addMotionCallback (&motionWhileHeld); } } // Called when a mouse motion/click event occurs while (held == true) @@ -84,8 +84,8 @@ Stdout ("Button clicked!").newline; pushed = false; - window.requestRedraw; - window.gui.removeCallbacks (cast(void*) this); + mgr.requestRedraw; + mgr.removeCallbacks (this); return true; } @@ -96,6 +96,6 @@ if (cx >= x && cx < x+w && cy >= y && cy < y+h) pushed = true; else pushed = false; if (oldPushed != pushed) - window.requestRedraw; + mgr.requestRedraw; } }
--- a/mde/imde.d Mon Jul 07 15:54:47 2008 +0100 +++ b/mde/imde.d Mon Jul 28 18:17:48 2008 +0100 @@ -19,6 +19,13 @@ import mde.input.Input; import mde.scheduler.Scheduler; +static this () { + // Make these available to all importing modules' static CTORs, as well as during init. + input = new Input(); + mainSchedule = new Scheduler; +} + + Scheduler mainSchedule; /// The schedule used by the main loop. /** Some enums used by per request scheduled functions. */
--- a/mde/lookup/Options.d Mon Jul 07 15:54:47 2008 +0100 +++ b/mde/lookup/Options.d Mon Jul 28 18:17:48 2008 +0100 @@ -169,9 +169,8 @@ return null; // All recognised sections are already in the dataset. }; reader.read; - } catch (MTFileIOException) { - // File either didn't exist or couldn't be opened. - // Presuming the former, this is not a problem. + } catch (NoFileException) { + // No user file exists; not an error. } catch (MTException e) { // Log a message and continue, overwriting the file: logger.error ("Loading options aborted:");
--- a/mde/lookup/Translation.d Mon Jul 07 15:54:47 2008 +0100 +++ b/mde/lookup/Translation.d Mon Jul 28 18:17:48 2008 +0100 @@ -128,7 +128,7 @@ while (secsToLoad.length) { // while we have sections left to load ID sec = secsToLoad[0]; // take current section secsToLoad = secsToLoad[1..$]; // and remove from list - + if (sec in loadedSecs) continue; // skip if it's already been loaded loadedSecs[sec] = true;
--- a/mde/mde.d Mon Jul 07 15:54:47 2008 +0100 +++ b/mde/mde.d Mon Jul 28 18:17:48 2008 +0100 @@ -45,10 +45,6 @@ return 0; } - // Create instances now, so they can be used during init (if necessary) - input = new Input(); - mainSchedule = new Scheduler; - scope Init init; try { init = new Init(args); // initialisation
--- a/mde/mergetag/DefaultData.d Mon Jul 07 15:54:47 2008 +0100 +++ b/mde/mergetag/DefaultData.d Mon Jul 28 18:17:48 2008 +0100 @@ -25,15 +25,20 @@ import mde.mergetag.parse.parseFrom : parseFrom; -/** -* Default DataSection class. -* -* Supports most of the basic types supported by D (excluding cent/ucent and imaginary/complex -* types) and array versions of each of these types, plus arrays of strings. -* -* Extending the class to support more types, even custom types, shouldn't be particularly difficult -* provided mde.text.parseTo and mde.text.parseFrom are extended to support the new types. -*/ +/************************************************************************************************* + * Default DataSection class. + * + * Currently this is only used for headers, and thus the list of supported types has been + * reduced to just those used in headers. Load order is HIGH_LOW, i.e. existing entries aren't + * overwritten. + * + * It did supports most of the basic types supported by D (excluding cent/ucent and + * imaginary/complex types) and array versions of each of these types, plus arrays of strings. + * + * Extending the class to support more types, even custom types, shouldn't be particularly + * difficult provided mde.text.parseTo and mde.text.parseFrom are extended to support the new + * types. + *************************************************************************************************/ /* The implementation now uses a fair bit of generic programming. Adjusting the types supported * should be as simple as adjusting the list dataTypes, and possibly implemting new conversions in * parseFrom and parseTo if you add new types (e.g. for cent or imaginary/complex types, or user types). @@ -78,7 +83,7 @@ // Purely to add indentation. Could just return "" without affecting functionality. static char[] indent (uint i) { - char[] ret; + char[] ret = ""; for (; i > 0; --i) ret ~= " "; // This is not executable at compile time: //ret.length = i * 4; // number of characters for each indentation @@ -101,7 +106,9 @@ ret ~= indent(indents); foreach (c; consts) { ret ~= "if (" ~ var ~ " == \"" ~ c ~ "\") {\n" ~ - indent(indents+1) ~ varName(c) ~ "[id] = parseTo!(" ~ c ~ ") (dt);\n" ~ + //indent(indents+1) ~ varName(c) ~ "[id] = parseTo!(" ~ c ~ ") (dt);\n" ~ + indent(indents+1) ~ "if ((id in "~varName(c)~") is null)\n" ~ + indent(indents+2) ~ varName(c)~"[id] = parseTo!(" ~ c ~ ") (dt);\n" ~ indent(indents) ~ "} else "; } ret = ret[0..$-6] ~ '\n'; // remove last else @@ -139,6 +146,7 @@ * Note: trying to use Arg!(type) to implicitly refer to Arg!(type).Arg causes compiler errors * due to the "alias Name Arg;" statement actually being a mixin. */ + /+ All types previously supported. Most of these weren't used. const char[][] dataTypes = ["bool","bool[]", "byte","byte[]", "char","char[]","char[][]", @@ -152,6 +160,8 @@ "uint","uint[]", "ulong","ulong[]", "ushort","ushort[]"]; + +/ + const char[][] dataTypes = ["char[]", "char[][]"]; mixin (declerations (dataTypes)); // Declare all the variables.
--- a/mde/setup/init2.d Mon Jul 07 15:54:47 2008 +0100 +++ b/mde/setup/init2.d Mon Jul 28 18:17:48 2008 +0100 @@ -35,7 +35,7 @@ // Modules requiring init code running: import imde = mde.imde; -import mde.gui.Gui; +import mde.gui.WidgetManager; import mde.input.Input; import font = mde.font.font; @@ -52,7 +52,7 @@ void guiLoad () { // init func try { font.FontStyle.initialize; - gui.load (GUI); + gui.loadDesign(); cleanup.addFunc (&guiSave, "guiSave"); } catch (Exception e) { logger.fatal ("guiLoad failed: " ~ e.msg); @@ -61,13 +61,12 @@ } void guiSave () { // cleanup func try { - gui.save (GUI); + gui.save; // NOTE: can save() just be called from DTOR? } catch (Exception e) { logger.fatal ("guiSave failed: " ~ e.msg); setInitFailure; } } -private const GUI = "gui"; void initInput () { // init func try {
--- a/mde/setup/paths.d Mon Jul 07 15:54:47 2008 +0100 +++ b/mde/setup/paths.d Mon Jul 28 18:17:48 2008 +0100 @@ -73,7 +73,7 @@ { FilePath[] files = getFiles (file, readOrder); if (files is null) - throw new MTFileIOException ("Unable to find the file: "~file~"[.mtt|mtb]"); + throw new NoFileException ("Unable to find the file: "~file~"[.mtt|mtb]"); return new mdeReader (files, ds, rdHeader); } @@ -125,11 +125,7 @@ private: FilePath[] getFiles (char[] filename, PRIORITY readOrder) - in { - assert (readOrder == PRIORITY.LOW_HIGH || - readOrder == PRIORITY.HIGH_LOW || - readOrder == PRIORITY.HIGH_ONLY ); - } body { + { FilePath[] ret; debug (mdeUnitTest) { @@ -150,6 +146,9 @@ ret ~= file; } } else { + assert (readOrder == PRIORITY.HIGH_LOW || + readOrder == PRIORITY.HIGH_ONLY ); + for (int i = pathsLen - 1; i >= 0; --i) { FilePath file = findFile (paths[i]~filename); if (file !is null) { @@ -367,4 +366,11 @@ PRIORITY rdOrder; } +} + +/// Thrown when makeMTReader couldn't find a file. +class NoFileException : MTFileIOException { + this (char[] msg) { + super(msg); + } } \ No newline at end of file
--- a/mde/setup/sdl.d Mon Jul 07 15:54:47 2008 +0100 +++ b/mde/setup/sdl.d Mon Jul 28 18:17:48 2008 +0100 @@ -20,7 +20,7 @@ import mde.setup.initFunctions; import mde.input.joystick; import mde.lookup.Options; -import mde.gl.basic; +import mde.gl.draw; import imde = mde.imde; import tango.util.log.Log : Log, Logger;