# HG changeset patch # User Diggory Hardy # Date 1218104727 -3600 # Node ID ea58f277f48736a2b161fc456a3f119719d1359f # Parent 61ea26abe4dd57f271b954c3fed7813e7347b159 Gui reorganization and changes; partial implementation of floating widgets. Moved contents of mde/gui/WidgetData.d elsewhere; new gui/WidgetDataSet.d and gui/types.d modules. Changes to widget/createWidget.d Partially implemented FloatingAreaWidget to provide an area for floating "window" widgets. New DebugWidget and some uses of it (e.g. bad widget data). Decoupled OptionChanges from Options. diff -r 61ea26abe4dd -r ea58f277f487 codeDoc/jobs.txt --- a/codeDoc/jobs.txt Tue Aug 05 11:51:51 2008 +0100 +++ b/codeDoc/jobs.txt Thu Aug 07 11:25:27 2008 +0100 @@ -8,7 +8,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). +5 setting widgets' default size? setMinSize/setDefaultSize fct? 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?). diff -r 61ea26abe4dd -r ea58f277f487 codeDoc/mergetag/new-models.vym Binary file codeDoc/mergetag/new-models.vym has changed diff -r 61ea26abe4dd -r ea58f277f487 data/L10n/OptionsMisc.mtt --- a/data/L10n/OptionsMisc.mtt Tue Aug 05 11:51:51 2008 +0100 +++ b/data/L10n/OptionsMisc.mtt Thu Aug 07 11:25:27 2008 +0100 @@ -1,7 +1,7 @@ {MT01} {en-GB} - - - - - + + + + + diff -r 61ea26abe4dd -r ea58f277f487 data/conf/gui.mtt --- a/data/conf/gui.mtt Tue Aug 05 11:51:51 2008 +0100 +++ b/data/conf/gui.mtt Thu Aug 07 11:25:27 2008 +0100 @@ -2,26 +2,13 @@ {Working} - - - - - - + + + + + + + + {Basic} - -!{ -{W1} - - - -{W2} - - - - -{WEmbedded} - - - -} \ No newline at end of file + diff -r 61ea26abe4dd -r ea58f277f487 mde/gui/WidgetData.d --- a/mde/gui/WidgetData.d Tue Aug 05 11:51:51 2008 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,349 +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 . */ - -/************************************************************************************************* - * 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.serialize; - -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._charA; - 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._charA; - 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; - - // Make all widgets save any changed data; return if no changes: - if (!child.saveChanges ("root")) - 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) { - 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? - - 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]); - strings = 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[][]) (strings) ); - } - } - } - //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; -} diff -r 61ea26abe4dd -r ea58f277f487 mde/gui/WidgetDataSet.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mde/gui/WidgetDataSet.d Thu Aug 07 11:25:27 2008 +0100 @@ -0,0 +1,110 @@ +/* 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 . */ + +/************************************************************************************************* + * 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.WidgetDataSet; + +public import mde.gui.types; + +// For loading from file: +import mt = mde.mergetag.DataSet; +import mt = mde.mergetag.DefaultData; +import mt = mde.mergetag.exception; +import mde.mergetag.serialize; +import tango.util.log.Log : Log, Logger; + +private Logger logger; +static this () { + logger = Log.getLogger ("mde.gui.WidgetDataSet"); +} + +/************************************************************************************************* + * 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) { + widgetData[id] = deserialize!(WidgetData) (dt); + } + } + // Only WidgetDataChanges is used for writing. + void writeAll (ItemDelg dlg) {} + //END Mergetag code + + /** Get the widget data for widget i. */ + WidgetData opIndex (widgetID id) { + auto p = id in widgetData; + if (p is null) { + logger.error ("No data for widget "~id~"; creating a debug widget instead."); + return WidgetData.dbg; + } + return *p; + } + + // 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) + dlg ("WidgetData", id, serialize!(WidgetData) (data)); + } + //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; +} diff -r 61ea26abe4dd -r ea58f277f487 mde/gui/WidgetManager.d --- a/mde/gui/WidgetManager.d Tue Aug 05 11:51:51 2008 +0100 +++ b/mde/gui/WidgetManager.d Thu Aug 07 11:25:27 2008 +0100 @@ -21,7 +21,7 @@ *************************************************************************************************/ module mde.gui.WidgetManager; -public import mde.gui.WidgetData; +import mde.gui.WidgetDataSet; import mde.gui.widget.Ifaces; import mde.gui.renderer.createRenderer; @@ -181,3 +181,239 @@ IRenderer rend; wdim w,h; // area available to the widgets } + + +import mde.gui.exception; +import mde.gui.widget.Ifaces; +import mde.gui.widget.createWidget; + +import mde.mergetag.Reader; +import mde.mergetag.Writer; +import mde.setup.paths; + +/************************************************************************************************* +* 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._charA; + 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._charA; + 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: + auto p = name in data; + while (p is null) { + if (name == defaultDesign) + throw new GuiException ("Unable to load [specified or] default design"); + name = defaultDesign; // try again with the default + p = name in data; + } + curData = *p; + + // Get/create a changes section: + if (changesDS is null) + changesDS = new mt.DataSet; + + mt.IDataSection* q = name in changesDS.sec; + if (q && ((changes = cast(WidgetDataChanges) *q) !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; + + // Make all widgets save any changed data; return if no changes: + if (!child.saveChanges ("root")) + 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, IParentWidget parent = null) { + debug logger.trace ("Creating widget \""~id~'"'); + return createWidget (this, curData[id], parent); + } + + /** For making changes. */ + void setData (widgetID id, WidgetData d) { + 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); + * --- + */ + 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? + + scope IChildWidget child; // The primary widget. + + Mutex mutex; // lock on methods for use outside the package. +} diff -r 61ea26abe4dd -r ea58f277f487 mde/gui/renderer/IRenderer.d --- a/mde/gui/renderer/IRenderer.d Tue Aug 05 11:51:51 2008 +0100 +++ b/mde/gui/renderer/IRenderer.d Thu Aug 07 11:25:27 2008 +0100 @@ -16,8 +16,7 @@ /** Interface for the renderer. This is planned to replace decoration.d */ module mde.gui.renderer.IRenderer; -// Put here to avoid circular import. -typedef int wdim; +public import mde.gui.types; /** Interface for renderers. * @@ -71,6 +70,15 @@ //END Get dimensions //BEGIN draw routines + /** Restrict following draw operations to given box. + * + * Restrict "pushes" a restriction onto a stack; relax must be called afterwards to "pop" the + * restriction. */ + void restrict (wdim x, wdim y, wdim w, wdim h); + + /** See restrict. */ + void relax (); + /** Draw a window border plus background. */ void drawWindow (wdim x, wdim y, wdim w, wdim h); diff -r 61ea26abe4dd -r ea58f277f487 mde/gui/renderer/SimpleRenderer.d --- a/mde/gui/renderer/SimpleRenderer.d Tue Aug 05 11:51:51 2008 +0100 +++ b/mde/gui/renderer/SimpleRenderer.d Thu Aug 07 11:25:27 2008 +0100 @@ -79,6 +79,10 @@ } + //FIXME - make these do something + void restrict (wdim x, wdim y, wdim w, wdim h) {} + void relax () {} + void drawWindow (wdim x, wdim y, wdim w, wdim h) { gl.setColor (0f, 0f, .7f); gl.drawBox (x,y, w,h); diff -r 61ea26abe4dd -r ea58f277f487 mde/gui/types.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mde/gui/types.d Thu Aug 07 11:25:27 2008 +0100 @@ -0,0 +1,64 @@ +/* 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 . */ + +/************************************************************************************************* + * Basic data types used by widgets. + *************************************************************************************************/ +module mde.gui.types; + + +/** Widget ID type. Each ID is unique under this window. +* +* Type is int since this is the widget data type. */ +alias char[] widgetID; + +/** 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). +* +* 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. */ +typedef int wdim; +alias wdim wdabs; /// ditto +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 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; /// Integer data + char[][] strings; /// char[] data + + /** For creating a DebugWidget. */ + static WidgetData dbg = { ints : [0xF] }; // 0xf is current code for debug widget +} diff -r 61ea26abe4dd -r ea58f277f487 mde/gui/widget/Floating.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mde/gui/widget/Floating.d Thu Aug 07 11:25:27 2008 +0100 @@ -0,0 +1,412 @@ +/* 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 . */ + +/** The Window class. Becoming a widget. */ +module mde.gui.widget.Floating; + +import mde.gui.widget.Widget; +import mde.gui.exception; +/+import mde.gui.widget.createWidget; + +import mde.gui.IGui; +import mde.gui.renderer.createRenderer; + +import mt = mde.mergetag.DataSet; +import mde.mergetag.parse.parseTo : parseTo; +import mde.mergetag.parse.parseFrom : parseFrom; ++/ +import tango.util.log.Log : Log, Logger; + +private Logger logger; +static this () { + logger = Log.getLogger ("mde.gui.widget.Window"); +} +//FIXME - documentation + +/** 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). + */ +/** An area to contain floating widgets. + * + * The position of each sub-widget is set from data, but not the size. + * Rationale: parents' need to set subwidgets' positions when its position is set, so it needs to + * know their positions. Size setting is still under work FIXME. + * + * Data: Each string item is interpreted as a subwidget widgetID. + * Ints supplied may consist of just the widget type or + * additionally an (x,y) coordinate for each subwidget (all x coords first, then all y coords). + */ +class FloatingAreaWidget : SizableWidget, IParentWidget +{ + this (IWidgetManager mgr, WidgetData data) { + subWidgets.length = data.strings.length; + foreach (i,s; data.strings) + subWidgets[i] = mgr.makeWidget (s, this); + sWCoords.length = subWidgets.length; + + if (data.ints.length != 1) { + if (data.ints.length != 2*subWidgets.length + 1) { + throw new WidgetDataException; + } + foreach (i, ref c; sWCoords) { + c.x = cast(wdim) data.ints[i + 1]; + c.y = cast(wdim) data.ints[i + 1 + sWCoords.length]; + } + } + + super (mgr, data); + + foreach (w; subWidgets) { + //FIXME: set default size + w.setWidth (w.minWidth, -1); + w.setHeight (w.minHeight, -1); + } + } + + void setPosition (wdim x, wdim y) { + super.setPosition (x,y); + + foreach (i,c; sWCoords) + subWidgets[i].setPosition (x+c.x, y+c.y); + } + + void draw () { + super.draw; + + mgr.renderer.restrict (x,y, w,h); + + foreach (w; subWidgets) + w.draw; + } + +protected: + IChildWidget[] subWidgets; // all subwidgets, framed or not + wdimPair[] sWCoords; // coords for subwidgets, relative to this widget + + /+ + /** Call after loading is finished to setup the window and confirm that it's valid. + * + * Throws: WindowLoadException (or possibly other exceptions). Do not use the instance if an + * exception occurs! */ + 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 creation data"); + + // Save gui and create the renderer: + gui_ = gui; + rend = createRenderer (gui.rendererName); + + // Create the primary widget (and indirectly all sub-widgets), throwing on error: + // Note: GridLayoutWidget's this relies on rend.layoutSpacing. + widget = makeWidget (0); // primary widget always has ID 0. + // This data is no longer needed by Window, although its sub-arrays may still be used, so + // let the GC collect what it can: + widgetData = null; + widgetStrings = null; + + // get border sizes: + border = rend.setSizable (isWSizable, isHSizable); // depends on widget + + // Note: this should return an empty array, but we shouldn't make a fuss if it's not empty: + if ((widget.adjust (mutableData)).length != 0) // adjust/set size, etc., depends on rend + logger.warn ("Local widget position data is invalid!"); + mutableData = null; // no longer needed + + widget.getCurrentSize (w,h); // and get this size + w += border.l + border.r; // Adjust for border + h += border.t + border.b; + + widgetX = x + border.l; // widget position + widgetY = y + border.t; // must be updated if the window is moved + widget.setPosition (widgetX, widgetY); + + // Calculate mw/mh and xw/yh (cached data): + mw = widget.minWidth + border.l + border.r; + mh = widget.minHeight + border.t + border.b; + + xw = x+w; + yh = y+h; + } + //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) + 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 { + /* Each widget returned should be a unique object; if multiple widgets are requested with + * the same ID, a new widget is created each time. */ + + 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: + return createWidget (this, *d); + } + + char[] getWidgetString (int i) + in { + // widgetStrings is freed at same time as widgetData + // but widgetData is guaranteed to be read + assert (widgetData !is null, "getWidgetString called after widget creation finished"); + } body { + char[]* p; + if (widgetStrings is null || + (p = i in widgetStrings) is null ) + throw new WindowLoadException ("Needed widgetStrings not set for Window"); + + return *p; + } + + /** Add this widget's data to that to be saved, returning it's widgetID. */ + widgetID addCreationData (IWidget widget) + { + widgetID i; + if (widgetData is null) + i = 0; + else + i = widgetData.keys[$-1] + 1; + + /+ Doesn't this have no effect except when getCreationData throws, in which case the data + + isn't used anyway? I'm sure it was added for a reason... FIXME and below + widgetData[i] = null; // Make sure the same ID doesn't get used by a recursive call + +/ + widgetData[i] = widget.getCreationData; + + return i; + } + + int addWidgetString (char[] str) + { + int i; + if (widgetStrings is null) + i = 0; + else + i = widgetStrings.keys[$-1] + 1; + + /+ See above. FIXME + widgetStrings[i] = null; // Make sure the same ID doesn't get used by a recursive call + +/ + widgetStrings[i] = str; + + return i; + } + + IGui gui () { + return gui_; + } + + void requestRedraw () { + gui_.requestRedraw; + } + + IRenderer renderer () + in { + assert (rend !is null, "Window.renderer: rend is null"); + } body { + return rend; + } + //END IWindow methods + + //BEGIN IWidget methods + //FIXME: how many of these methods are actually needed/used? + int[] adjust (int[]) { // simply not relevant (never used) + return []; + } + int[] getCreationData () { // simply not relevant (never used) + return []; + } + int[] getMutableData () { // simply not relevant (never used) + return []; + } + + bool isWSizable () { + return widget.isWSizable; + } + bool isHSizable () { + return widget.isHSizable; + } + + wdim minWidth () { + return mw; + } + wdim minHeight () { + return mh; + } + void getCurrentSize (out wdim cw, out wdim ch) { + cw = w; + ch = h; + } + + void setWidth (wdim nw, int dir) { + if (nw < mw) w = mw; // clamp + else w = nw; + xw = x + w; + widget.setWidth (w - border.l - border.r, dir); + } + void setHeight (wdim nh, int dir) { + if (nh < mh) h = mh; // clamp + else h = nh; + yh = y + h; + widget.setHeight (h - border.t - border.b, dir); + } + + void setPosition (wdim nx, wdim ny) { + x = nx; + y = ny; + + xw = x+w; + yh = y+h; + + widgetX = x + border.l; + widgetY = y + border.t; + + widget.setPosition (widgetX, widgetY); + + gui_.requestRedraw (); // necessary whenever the window is moved; setPosition is called after resizes and moves + } + + IWidget getWidget (wdim cx, wdim cy) { + if (cx < x || cx >= xw || cy < y || cy >= yh) // not over window + return null; + if (cx >= widgetX && cx < xw-border.r && cy >= widgetY && cy < yh-border.b) + // over the widget + return widget.getWidget (cx, cy); + else // over the window border + return this; + } + void clickEvent (wdabs cx, wdabs cy, ubyte b, bool state) { + if (b == 1 && state == true) { + resizeType = rend.getResizeType (cx-x, cy-y, w,h); + + if (resizeType != RESIZE_TYPE.NONE) { // Some type of resize + // Set x/yDrag (unfortunately these need to be different for each edge) + if (resizeType & RESIZE_TYPE.L) + xDrag = w + cx; + else if (resizeType & RESIZE_TYPE.R) + xDrag = w - cx; + + if (resizeType & RESIZE_TYPE.T) + yDrag = h + cy; + else if (resizeType & RESIZE_TYPE.B) + yDrag = h - cy; + + // Add the callbacks (they use resizeType which has already been set) + gui_.addClickCallback (&endCallback); + gui_.addMotionCallback (&resizeCallback); + } else { // window is being moved + xDrag = cx - x; + yDrag = cy - y; + + gui_.addClickCallback (&endCallback); + gui_.addMotionCallback (&moveCallback); + } + } + } + + void draw () { + // background + rend.drawWindow (x,y, w,h); + + // Tell the widget to draw itself: + widget.draw(); + } + //END IWidget methods + +private: + alias IRenderer.BorderDimensions BorderDimensions; + alias IRenderer.RESIZE_TYPE RESIZE_TYPE; + + //BEGIN Window moving and resizing + void moveCallback (wdabs cx, wdabs cy) { + setPosition (cx-xDrag, cy-yDrag); + } + void resizeCallback (wdabs cx, wdabs cy) { + debug scope(failure) + logger.trace ("resizeCallback: failure"); + + // This function is only called if some resize is going to happen. + // x,y are used as parameters to setPosition as well as being affected by it; somewhat + // pointless but fairly effective. + + if (resizeType & RESIZE_TYPE.L) { + wdim xSize = xDrag - cx; + if (xSize < mw) xSize = mw; // clamp + x += w - xSize; + setWidth (xSize, 1); + } + else if (resizeType & RESIZE_TYPE.R) { + setWidth (xDrag + cx, -1); + } + if (resizeType & RESIZE_TYPE.T) { + wdim ySize = yDrag - cy; + if (ySize < mh) ySize = mh; + y += h - ySize; + setHeight (ySize, 1); + } + else if (resizeType & RESIZE_TYPE.B) { + setHeight (yDrag + cy, -1); + } + + // Moves lower (x,y) coordinate if necessary and repositions any sub-widgets moved by the + // resizing: + setPosition (x, y); + } + bool endCallback (wdabs cx, wdabs cy, ubyte b, bool state) { + if (b == 1 && state == false) { + // The mouse shouldn't have moved since the motion callback + // was last called, so there's nothing else to do now. + gui_.removeCallbacks (cast(void*) this); + + return true; // we've handled the up-click + } + return false; // we haven't handled it + } + wdim xDrag, yDrag; // where a drag starts relative to x and y + IRenderer.RESIZE_TYPE resizeType; // Type of current resize + //END Window moving and resizing + + // Load/save data: + public char[] name; // The window's name (id from config file) + //bool edited = false; // True if any widgets have been edited (excluding scaling) + + IGui gui_; // The gui managing this window + IRenderer rend; // The window's renderer + + IWidget widget; // The primary widget in this window. + + wdim x = -1, y = -1; // Window position + wdsize w,h; // Window size (calculated from Widgets) + wdim xw, yh; // x+w, y+h (frequent use by clickEvent) + wdim widgetX, widgetY; // Widget position (= window position plus BORDER_WIDTH) + wdim mw = -1, mh = -1; // minimal size (negative means requires calculation) + + BorderDimensions border; // Total border size (move plus resize) + +/ +} diff -r 61ea26abe4dd -r ea58f277f487 mde/gui/widget/Ifaces.d --- a/mde/gui/widget/Ifaces.d Tue Aug 05 11:51:51 2008 +0100 +++ b/mde/gui/widget/Ifaces.d Thu Aug 07 11:25:27 2008 +0100 @@ -23,41 +23,10 @@ *************************************************************************************************/ module mde.gui.widget.Ifaces; +public import mde.gui.types; public import mde.gui.renderer.IRenderer; -/** Widget ID type. Each ID is unique under this window. - * - * Type is int since this is the widget data type. */ -alias char[] widgetID; - -/** 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; // 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. * @@ -82,12 +51,16 @@ // Loading/saving: /** Create a widget by ID. * + * Params: + * id = Identifier, within data files, of the data for the widget. + * parent = Reference to the widget's parent, passed if the widget supports recieving it. + * * 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); + * Note: this method is only for widgets with an identifier; generic widgets instanciated in + * lists are created directly and have no WidgetData of their own. */ + IChildWidget makeWidget (widgetID id, IParentWidget parent = null); /** Record some changes, for saving. */ void setData (widgetID id, WidgetData); @@ -266,16 +239,3 @@ * 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; - char[][] strings; -} diff -r 61ea26abe4dd -r ea58f277f487 mde/gui/widget/Widget.d --- a/mde/gui/widget/Widget.d Tue Aug 05 11:51:51 2008 +0100 +++ b/mde/gui/widget/Widget.d Thu Aug 07 11:25:27 2008 +0100 @@ -23,7 +23,6 @@ module mde.gui.widget.Widget; public import mde.gui.widget.Ifaces; -import mde.gui.renderer.IRenderer; import mde.gui.exception; diff -r 61ea26abe4dd -r ea58f277f487 mde/gui/widget/Window.d --- a/mde/gui/widget/Window.d Tue Aug 05 11:51:51 2008 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,360 +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 . */ - -/** 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 mde.gui.renderer.createRenderer; - -import mt = mde.mergetag.DataSet; -import mde.mergetag.parse.parseTo : parseTo; -import mde.mergetag.parse.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 (or possibly other exceptions). Do not use the instance if an - * exception occurs! */ - 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 creation data"); - - // Save gui and create the renderer: - gui_ = gui; - rend = createRenderer (gui.rendererName); - - // Create the primary widget (and indirectly all sub-widgets), throwing on error: - // Note: GridLayoutWidget's this relies on rend.layoutSpacing. - widget = makeWidget (0); // primary widget always has ID 0. - // This data is no longer needed by Window, although its sub-arrays may still be used, so - // let the GC collect what it can: - widgetData = null; - widgetStrings = null; - - // get border sizes: - border = rend.setSizable (isWSizable, isHSizable); // depends on widget - - // Note: this should return an empty array, but we shouldn't make a fuss if it's not empty: - if ((widget.adjust (mutableData)).length != 0) // adjust/set size, etc., depends on rend - logger.warn ("Local widget position data is invalid!"); - mutableData = null; // no longer needed - - widget.getCurrentSize (w,h); // and get this size - w += border.l + border.r; // Adjust for border - h += border.t + border.b; - - widgetX = x + border.l; // widget position - widgetY = y + border.t; // must be updated if the window is moved - widget.setPosition (widgetX, widgetY); - - // Calculate mw/mh and xw/yh (cached data): - mw = widget.minWidth + border.l + border.r; - mh = widget.minHeight + border.t + border.b; - - xw = x+w; - yh = y+h; - } - //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) - 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 { - /* Each widget returned should be a unique object; if multiple widgets are requested with - * the same ID, a new widget is created each time. */ - - 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: - return createWidget (this, *d); - } - - char[] getWidgetString (int i) - in { - // widgetStrings is freed at same time as widgetData - // but widgetData is guaranteed to be read - assert (widgetData !is null, "getWidgetString called after widget creation finished"); - } body { - char[]* p; - if (widgetStrings is null || - (p = i in widgetStrings) is null ) - throw new WindowLoadException ("Needed widgetStrings not set for Window"); - - return *p; - } - - /** Add this widget's data to that to be saved, returning it's widgetID. */ - widgetID addCreationData (IWidget widget) - { - widgetID i; - if (widgetData is null) - i = 0; - else - i = widgetData.keys[$-1] + 1; - - /+ Doesn't this have no effect except when getCreationData throws, in which case the data - + isn't used anyway? I'm sure it was added for a reason... FIXME and below - widgetData[i] = null; // Make sure the same ID doesn't get used by a recursive call - +/ - widgetData[i] = widget.getCreationData; - - return i; - } - - int addWidgetString (char[] str) - { - int i; - if (widgetStrings is null) - i = 0; - else - i = widgetStrings.keys[$-1] + 1; - - /+ See above. FIXME - widgetStrings[i] = null; // Make sure the same ID doesn't get used by a recursive call - +/ - widgetStrings[i] = str; - - return i; - } - - IGui gui () { - return gui_; - } - - void requestRedraw () { - gui_.requestRedraw; - } - - IRenderer renderer () - in { - assert (rend !is null, "Window.renderer: rend is null"); - } body { - return rend; - } - //END IWindow methods - - //BEGIN IWidget methods - //FIXME: how many of these methods are actually needed/used? - int[] adjust (int[]) { // simply not relevant (never used) - return []; - } - int[] getCreationData () { // simply not relevant (never used) - return []; - } - int[] getMutableData () { // simply not relevant (never used) - return []; - } - - bool isWSizable () { - return widget.isWSizable; - } - bool isHSizable () { - return widget.isHSizable; - } - - wdim minWidth () { - return mw; - } - wdim minHeight () { - return mh; - } - void getCurrentSize (out wdim cw, out wdim ch) { - cw = w; - ch = h; - } - - void setWidth (wdim nw, int dir) { - if (nw < mw) w = mw; // clamp - else w = nw; - xw = x + w; - widget.setWidth (w - border.l - border.r, dir); - } - void setHeight (wdim nh, int dir) { - if (nh < mh) h = mh; // clamp - else h = nh; - yh = y + h; - widget.setHeight (h - border.t - border.b, dir); - } - - void setPosition (wdim nx, wdim ny) { - x = nx; - y = ny; - - xw = x+w; - yh = y+h; - - widgetX = x + border.l; - widgetY = y + border.t; - - widget.setPosition (widgetX, widgetY); - - gui_.requestRedraw (); // necessary whenever the window is moved; setPosition is called after resizes and moves - } - - IWidget getWidget (wdim cx, wdim cy) { - if (cx < x || cx >= xw || cy < y || cy >= yh) // not over window - return null; - if (cx >= widgetX && cx < xw-border.r && cy >= widgetY && cy < yh-border.b) - // over the widget - return widget.getWidget (cx, cy); - else // over the window border - return this; - } - void clickEvent (wdabs cx, wdabs cy, ubyte b, bool state) { - if (b == 1 && state == true) { - resizeType = rend.getResizeType (cx-x, cy-y, w,h); - - if (resizeType != RESIZE_TYPE.NONE) { // Some type of resize - // Set x/yDrag (unfortunately these need to be different for each edge) - if (resizeType & RESIZE_TYPE.L) - xDrag = w + cx; - else if (resizeType & RESIZE_TYPE.R) - xDrag = w - cx; - - if (resizeType & RESIZE_TYPE.T) - yDrag = h + cy; - else if (resizeType & RESIZE_TYPE.B) - yDrag = h - cy; - - // Add the callbacks (they use resizeType which has already been set) - gui_.addClickCallback (&endCallback); - gui_.addMotionCallback (&resizeCallback); - } else { // window is being moved - xDrag = cx - x; - yDrag = cy - y; - - gui_.addClickCallback (&endCallback); - gui_.addMotionCallback (&moveCallback); - } - } - } - - void draw () { - // background - rend.drawWindow (x,y, w,h); - - // Tell the widget to draw itself: - widget.draw(); - } - //END IWidget methods - -private: - alias IRenderer.BorderDimensions BorderDimensions; - alias IRenderer.RESIZE_TYPE RESIZE_TYPE; - - //BEGIN Window moving and resizing - void moveCallback (wdabs cx, wdabs cy) { - setPosition (cx-xDrag, cy-yDrag); - } - void resizeCallback (wdabs cx, wdabs cy) { - debug scope(failure) - logger.trace ("resizeCallback: failure"); - - // This function is only called if some resize is going to happen. - // x,y are used as parameters to setPosition as well as being affected by it; somewhat - // pointless but fairly effective. - - if (resizeType & RESIZE_TYPE.L) { - wdim xSize = xDrag - cx; - if (xSize < mw) xSize = mw; // clamp - x += w - xSize; - setWidth (xSize, 1); - } - else if (resizeType & RESIZE_TYPE.R) { - setWidth (xDrag + cx, -1); - } - if (resizeType & RESIZE_TYPE.T) { - wdim ySize = yDrag - cy; - if (ySize < mh) ySize = mh; - y += h - ySize; - setHeight (ySize, 1); - } - else if (resizeType & RESIZE_TYPE.B) { - setHeight (yDrag + cy, -1); - } - - // Moves lower (x,y) coordinate if necessary and repositions any sub-widgets moved by the - // resizing: - setPosition (x, y); - } - bool endCallback (wdabs cx, wdabs cy, ubyte b, bool state) { - if (b == 1 && state == false) { - // The mouse shouldn't have moved since the motion callback - // was last called, so there's nothing else to do now. - gui_.removeCallbacks (cast(void*) this); - - return true; // we've handled the up-click - } - return false; // we haven't handled it - } - wdim xDrag, yDrag; // where a drag starts relative to x and y - IRenderer.RESIZE_TYPE resizeType; // Type of current resize - //END Window moving and resizing - - // Load/save data: - public char[] name; // The window's name (id from config file) - //bool edited = false; // True if any widgets have been edited (excluding scaling) - - IGui gui_; // The gui managing this window - IRenderer rend; // The window's renderer - - IWidget widget; // The primary widget in this window. - - wdim x = -1, y = -1; // Window position - wdsize w,h; // Window size (calculated from Widgets) - wdim xw, yh; // x+w, y+h (frequent use by clickEvent) - wdim widgetX, widgetY; // Widget position (= window position plus BORDER_WIDTH) - wdim mw = -1, mh = -1; // minimal size (negative means requires calculation) - - BorderDimensions border; // Total border size (move plus resize) -} diff -r 61ea26abe4dd -r ea58f277f487 mde/gui/widget/createWidget.d --- a/mde/gui/widget/createWidget.d Tue Aug 05 11:51:51 2008 +0100 +++ b/mde/gui/widget/createWidget.d Thu Aug 07 11:25:27 2008 +0100 @@ -23,18 +23,41 @@ import mde.gui.widget.layout; import mde.gui.widget.miscWidgets; import mde.gui.widget.TextWidget; +import mde.gui.widget.Floating; +import tango.util.log.Log : Log, Logger; -/** Create a widget of type data[0] (see enum WIDGET_TYPES) under manager mgr, with initialisation -* data [1..$]. */ -IChildWidget createWidget (IWidgetManager mgr, WidgetData data) +private Logger logger; +static this () { + logger = Log.getLogger ("mde.gui.widget.createWidget"); +} + +/** Create a widget. + * + * Usually called by the widget manager's makeWidget function. + * + * Widget created of type data.ints[0] (see enum WIDGET_TYPES), with one of the following CTORs: + * --- + * this (IWidgetManager mgr, WidgetData data); + * // Called if (data.ints[0] & WIDGET_TYPES.TAKES_PARENT): + * this (IWidgetManager mgr, WidgetData data, IParentWidget parent); + * --- + *************************************************************************************************/ +IChildWidget createWidget (IWidgetManager mgr, WidgetData data, IParentWidget parent) in { assert (mgr !is null, "createWidget: mgr is null"); } body { - if (data.ints.length < 1) throw new WidgetDataException ("No widget data"); + if (data.ints.length < 1) { + logger.error ("No int data; creating a debug widget"); + data.ints = [WIDGET_TYPE.Debug]; + } int type = data.ints[0]; // type is first element of data + //pragma (msg, binarySearch ("type", WIDGETS)); mixin (binarySearch ("type", WIDGETS)); // creates widget by type as new *Widget (mgr, data); - throw new WidgetDataException ("Bad widget type"); + + // Not returned a new widget... + logger.error ("Bad widget type: {}; creating a debug widget instead.",type); + return new DebugWidget (mgr, data); } /+ for converting to a char[] name (unused) @@ -50,27 +73,28 @@ private: /// Widget types. enum WIDGET_TYPE : int { - WSIZABLE = 0x1000, // horizontally resizable - HSIZABLE = 0x2000, // vertically resizable - INTERACTIBLE = 0x4000, // any specific interaction - LAYOUT = 0x8000, // is a layout widget (i.e. has sub-widgets)? + TAKES_PARENT = 0x4000, // CTOR takes reference to its parent + PARENT = 0x8000, // widget can have children // Use widget names rather than usual capitals convention Unnamed = 0x0, // Only for use by widgets not created with createWidget // blank: 0x1 FixedBlank = 0x1, - SizableBlank = WSIZABLE | HSIZABLE | 0x1, + SizableBlank = 0x2, + Debug = 0xF, // buttons: 0x10 - Button = INTERACTIBLE | 0x10, + Button = 0x10, // content: 0x20 Text = 0x21, Int = 0x22, - GridLayout = LAYOUT | WSIZABLE | HSIZABLE | 0x4, - TrialContentLayout = LAYOUT | WSIZABLE | HSIZABLE | 0x5 + GridLayout = PARENT | 0x100, + TrialContentLayout = PARENT | 0x110, + + FloatingArea = PARENT | 0x200, } //const char[][int] WIDGET_NAMES; @@ -78,12 +102,14 @@ // Only used for binarySearch algorithm generation; must be ordered by numerical values. const char[][] WIDGETS = [ "FixedBlank", + "SizableBlank", + "Debug", + "Button", "Text", "Int", - "SizableBlank", - "Button", "GridLayout", - "TrialContentLayout"]; + "TrialContentLayout", + "FloatingArea"]; /* Generates a binary search algorithm. */ char[] binarySearch (char[] var, char[][] consts) { @@ -96,8 +122,12 @@ } else { char[] ret; foreach (c; consts) { - ret ~= "if (" ~ var ~ " == WIDGET_TYPE." ~ c ~ ") {\n" ~ - "return new " ~ c ~ "Widget (mgr, data);\n" ~ + ret ~= "if (" ~ var ~ " == WIDGET_TYPE." ~ c ~ ") {\n" ~ + " debug logger.trace (\"Creating new "~c~"Widget.\");\n" ~ + " static if (WIDGET_TYPE."~c~" & WIDGET_TYPE.TAKES_PARENT)\n" ~ + " return new " ~ c ~ "Widget (mgr, data, parent);\n" ~ + " else\n" ~ + " return new " ~ c ~ "Widget (mgr, data);\n" ~ "} else "; } ret = ret[0..$-6] ~ '\n'; // remove last else diff -r 61ea26abe4dd -r ea58f277f487 mde/gui/widget/miscWidgets.d --- a/mde/gui/widget/miscWidgets.d Tue Aug 05 11:51:51 2008 +0100 +++ b/mde/gui/widget/miscWidgets.d Thu Aug 07 11:25:27 2008 +0100 @@ -53,6 +53,28 @@ } } +/// A debug widget. Essentially as SizableBlankWidget but doesn't mind any amount of data and prints it. +class DebugWidget : SizableWidget +{ + this (IWidgetManager mgr, WidgetData data) { + super (mgr, data); + + Stdout ("Debug widget - parameters: int: ["); + foreach (x; data.ints) + Stdout (" ")(x); + Stdout (" ], char[]: ["); + foreach (x; data.strings) + Stdout (" \"")(x)('"'); + Stdout (" ]").newline; + } + + void draw () { + super.draw; + + mgr.renderer.drawBlank (x,y, w,h); + } +} + /// First interactible widget class ButtonWidget : FixedWidget { diff -r 61ea26abe4dd -r ea58f277f487 mde/lookup/Options.d --- a/mde/lookup/Options.d Tue Aug 05 11:51:51 2008 +0100 +++ b/mde/lookup/Options.d Thu Aug 07 11:25:27 2008 +0100 @@ -369,32 +369,42 @@ //END Templates: impl & optionsThis } -/* Special class to store all locally changed options, whatever the section. */ -class OptionChanges : Options { +/** Special class to store all locally changed options, whatever the section. */ +class OptionChanges : IDataSection +{ //BEGIN Templates private { + alias Options.TName TName; + alias Options.TYPES TYPES; template Vars(A...) { static if (A.length) { - const char[] Vars = A[0].stringof~`[] `~TName!(A[0])~`s;` ~ Vars!(A[1..$]); + const char[] Vars = A[0].stringof~`[ID] `~TName!(A[0])~`s;` ~ Vars!(A[1..$]); } else const char[] Vars = ``; } - // For addTag; different to Options.addTag(). - // Reverse priority: only load symbols not currently existing + // For addTag template addTagMixin(T, A...) { const char[] ifBlock = `if (tp == "`~T.stringof~`") { - if ((id in opts`~TName!(T)~`) is null) { - `~TName!(T)~`s ~= parseTo!(`~T.stringof~`) (dt); - opts`~TName!(T)~`[id] = &`~TName!(T)~`s[$-1]; - } + if ((id in `~TName!(T)~`s) is null) + `~TName!(T)~`s[id] = parseTo!(`~T.stringof~`) (dt); }`; static if (A.length) - const char[] addTagMixin = ifBlock~` else `~addTagMixin!(A).addTagMixin; + const char[] addTagMixin = ifBlock~` else `~addTagMixin!(A).addTagMixin; else const char[] addTagMixin = ifBlock; } - + // For writeAll + template writeAllMixin(A...) { + static if (A.length) { + const char[] writeAllMixin = + `foreach (id, val; `~TName!(A[0])~`s) + dlg ("`~A[0].stringof~`", id, serialize (val)); + ` ~ writeAllMixin!(A[1..$]); + } else + const char[] writeAllMixin = ``; + } + } //END Templates // These store the actual values, but are never accessed directly except when initially added. @@ -404,24 +414,20 @@ this () {} void set(T) (ID id, T x) { - static assert (TIsIn!(T,TYPES), "Options does not support type "~T.stringof); - - mixin (`alias opts`~TName!(T)~` optsVars;`); - mixin (`alias `~TName!(T)~`s vars;`); + static assert (Options.TIsIn!(T,TYPES), "Options does not support type "~T.stringof); - T** p = id in optsVars; - if (p !is null) **p = x; - else { - vars ~= x; - optsVars[id] = &vars[$-1]; - } + mixin (`alias `~TName!(T)~`s vars;`); + vars[id] = x; } //BEGIN Mergetag loading/saving code - // Reverse priority: only load symbols not currently existing + // HIGH_LOW priority: only load symbols not currently existing void addTag (char[] tp, ID id, char[] dt) { mixin (addTagMixin!(TYPES).addTagMixin); } + void writeAll (ItemDelg dlg) { + mixin(writeAllMixin!(TYPES)); + } //END Mergetag loading/saving code } diff -r 61ea26abe4dd -r ea58f277f487 mde/mergetag/Reader.d --- a/mde/mergetag/Reader.d Tue Aug 05 11:51:51 2008 +0100 +++ b/mde/mergetag/Reader.d Thu Aug 07 11:25:27 2008 +0100 @@ -425,6 +425,7 @@ catch (TextException e) { logger.error ("TextException while reading " ~ ErrFile ~ ":"); // following a parse error logger.error (e.msg); + logger.error ("Tag ignored: <"~type~"|"~tagID~"="~data~">"); // No throw: tag is just ignored } catch (Exception e) { diff -r 61ea26abe4dd -r ea58f277f487 mde/setup/sdl.d --- a/mde/setup/sdl.d Tue Aug 05 11:51:51 2008 +0100 +++ b/mde/setup/sdl.d Thu Aug 07 11:25:27 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 . */ -/** Just a temporary place to put SDL Init and Video stuff. +/** Just a temporary place to put SDL init stuff. */ module mde.setup.sdl; @@ -178,7 +178,7 @@ void cleanupSDL () { // cleanup func closeJoysticks(); - logger.trace ("cleanupSDL - middle"); + debug logger.trace ("cleanupSDL - joysticks closed"); SDL_Quit(); }