comparison mde/gui/WidgetManager.d @ 80:ea58f277f487

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.
author Diggory Hardy <diggory.hardy@gmail.com>
date Thu, 07 Aug 2008 11:25:27 +0100
parents 65780e0e48e6
children d8fccaa45d5f
comparison
equal deleted inserted replaced
79:61ea26abe4dd 80:ea58f277f487
19 * This is the module to use externally to create a graphical user interface (likely also with 19 * This is the module to use externally to create a graphical user interface (likely also with
20 * content modules). 20 * content modules).
21 *************************************************************************************************/ 21 *************************************************************************************************/
22 module mde.gui.WidgetManager; 22 module mde.gui.WidgetManager;
23 23
24 public import mde.gui.WidgetData; 24 import mde.gui.WidgetDataSet;
25 import mde.gui.widget.Ifaces; 25 import mde.gui.widget.Ifaces;
26 import mde.gui.renderer.createRenderer; 26 import mde.gui.renderer.createRenderer;
27 27
28 // For adding the input event callbacks and requesting redraws: 28 // For adding the input event callbacks and requesting redraws:
29 import imde = mde.imde; 29 import imde = mde.imde;
179 bool delegate(wdabs cx, wdabs cy, ubyte b, bool state) [void*] clickCallbacks; 179 bool delegate(wdabs cx, wdabs cy, ubyte b, bool state) [void*] clickCallbacks;
180 void delegate(wdabs cx, wdabs cy) [void*] motionCallbacks; 180 void delegate(wdabs cx, wdabs cy) [void*] motionCallbacks;
181 IRenderer rend; 181 IRenderer rend;
182 wdim w,h; // area available to the widgets 182 wdim w,h; // area available to the widgets
183 } 183 }
184
185
186 import mde.gui.exception;
187 import mde.gui.widget.Ifaces;
188 import mde.gui.widget.createWidget;
189
190 import mde.mergetag.Reader;
191 import mde.mergetag.Writer;
192 import mde.setup.paths;
193
194 /*************************************************************************************************
195 * Contains the code for loading and saving the gui, but not the code for drawing it or handling
196 * user input.
197 *
198 * This abstract class exists solely for separating out some of the functionality.
199 *************************************************************************************************/
200 abstract scope class WidgetLoader : IWidgetManager
201 {
202 /** Construct a new widget loader.
203 *
204 * params:
205 * fileName = Name of file specifying the gui, excluding path and extension.
206 */
207 protected this (char[] file) {
208 mutex = new Mutex; // Used on functions intended to be called from outside the gui package.
209 fileName = file;
210 }
211 ~this () {
212 save;
213 }
214
215 /* Load the widgets' data from the file specified to the CTOR.
216 *
217 * params:
218 * allDesigns = Load all sections
219 */
220 private void loadData (bool allDesigns = false) {
221 if (allLoaded || (defaultDesign !is null && allDesigns == false))
222 return; // test if already loaded
223
224 if (!confDir.exists (fileName)) {
225 logger.error ("Unable to load GUI: no config file!");
226 return; // not a fatal error (so long as the game can run without a GUI!)
227 }
228
229 // Set up a reader
230 scope IReader reader;
231 try {
232 reader = confDir.makeMTReader (fileName, PRIORITY.HIGH_LOW, null, true);
233
234 // Read from the HEADER:
235 // Get the renderer
236 char[]* p = "Renderer" in reader.dataset.header._charA;
237 if (p is null || *p is null) {
238 logger.warn ("No renderer specified: using \"Simple\"");
239 rendName = "Simple";
240 }
241 else
242 rendName = *p;
243
244 // Get which section to use
245 p = "Design" in reader.dataset.header._charA;
246 if (p is null || *p is null) {
247 logger.warn ("No gui design specified: trying \"Default\"");
248 defaultDesign = "Default";
249 }
250 else
251 defaultDesign = *p;
252
253 // Read the body:
254 // Load the chosen design
255 reader.dataSecCreator = delegate mt.IDataSection(mt.ID id) {
256 WidgetDataSet* p = id in data;
257 if (p is null) {
258 data[id] = new WidgetDataSet;
259 return *(id in data);
260 }
261 return *p;
262 };
263
264 if (allDesigns) {
265 reader.read;
266 allLoaded = true;
267 } else
268 reader.read([defaultDesign]);
269 } catch (Exception e) {
270 logger.error ("Unable to load GUI: errors parsing config file ("~confDir.getFileName(fileName,PRIORITY.HIGH_LOW)~"):");
271 logger.error (e.msg);
272 throw new GuiException ("Failure parsing config file");
273 }
274 }
275
276 /** Load the gui from some design.
277 *
278 * If a design was previously loaded, its changes are saved first.
279 *
280 * Params:
281 * name = Design to load. If null, the default will be loaded.
282 */
283 void loadDesign (char[] name = null) {
284 if (changes !is null)
285 save; // own lock
286
287 mutex.lock;
288 scope(exit) mutex.unlock;
289
290 // Load data (loadData tests if it's already loaded first):
291 if (name is null) {
292 loadData (false);
293 name = defaultDesign;
294 } else
295 loadData (true);
296
297
298 // Get data:
299 auto p = name in data;
300 while (p is null) {
301 if (name == defaultDesign)
302 throw new GuiException ("Unable to load [specified or] default design");
303 name = defaultDesign; // try again with the default
304 p = name in data;
305 }
306 curData = *p;
307
308 // Get/create a changes section:
309 if (changesDS is null)
310 changesDS = new mt.DataSet;
311
312 mt.IDataSection* q = name in changesDS.sec;
313 if (q && ((changes = cast(WidgetDataChanges) *q) !is null)) {}
314 else {
315 changes = new WidgetDataChanges (curData);
316 changesDS.sec[name] = changes;
317 }
318
319 // Create the widgets:
320 createRootWidget;
321 }
322
323 /** Save changes, if any exist.
324 *
325 * Is run when the manager is destroyed, but could be run at other times too. */
326 void save () {
327 mutex.lock;
328 scope(exit) mutex.unlock;
329
330 // Make all widgets save any changed data; return if no changes:
331 if (!child.saveChanges ("root"))
332 return;
333
334 if (loadUserFile) { // merge entries from user file into current changes
335 try {
336 scope IReader reader = confDir.makeMTReader (
337 fileName, PRIORITY.HIGH_ONLY, changesDS, true);
338
339 // Create if necessary, only corresponding to existing designs read:
340 reader.dataSecCreator = delegate mt.IDataSection(mt.ID id) {
341 WidgetDataSet* p = id in data;
342 if (p is null)
343 throw new Exception ("File has changed since it was loaded!");
344 return new WidgetDataChanges (*p);
345 };
346
347 reader.read;
348 } catch (NoFileException) {
349 // No user file exists; not an error.
350 } catch (Exception e) {
351 logger.error ("Error reading "~confDir.getFileName(fileName,PRIORITY.HIGH_ONLY)~" prior to saving:");
352 logger.error (e.msg);
353 logger.error ("Overwriting the file.");
354 // Continue...
355 }
356 loadUserFile = false; // don't need to do it again
357 }
358
359 try { // Save
360 IWriter writer;
361 writer = confDir.makeMTWriter (fileName, changesDS);
362 writer.write;
363 } catch (Exception e) {
364 logger.error ("Saving to "~confDir.getFileName(fileName,PRIORITY.HIGH_ONLY)~" failed:");
365 logger.error (e.msg);
366 // No point in throwing since it doesn't affect anything else.
367 }
368 }
369
370 /** Get the names of all designs available. */
371 char[][] designs() {
372 synchronized(mutex) {
373 loadData (true);
374 return data.keys;
375 }
376 }
377
378
379 /** Create a widget by ID. */
380 IChildWidget makeWidget (widgetID id, IParentWidget parent = null) {
381 debug logger.trace ("Creating widget \""~id~'"');
382 return createWidget (this, curData[id], parent);
383 }
384
385 /** For making changes. */
386 void setData (widgetID id, WidgetData d) {
387 changes[id] = d; // also updates WidgetDataSet in data.
388 }
389
390 /** Second stage of loading the widgets.
391 *
392 * loadDesign handles the data; this method needs to:
393 * ---
394 * // 1. Create the root widget:
395 * child = makeWidget ("root");
396 * // 2. Set the setSize, e.g.:
397 * child.setWidth (child.minWidth, 1);
398 * child.setHeight (child.minHeight, 1);
399 * ---
400 */
401 void createRootWidget();
402
403 protected:
404 final char[] fileName;
405 char[] defaultDesign; // The design specified in the file header.
406 char[] rendName; // Name of renderer; for saving and creating renderers
407
408 // Loaded data, indexed by design name. May not be loaded for all gui designs:
409 scope WidgetDataSet[char[]] data;
410 private bool allLoaded = false; // applies to data
411 WidgetDataSet curData; // Current data
412 WidgetDataChanges changes; // Changes for the current design.
413 scope mt.DataSet changesDS; // changes and sections from user file (used for saving)
414 bool loadUserFile = true; // still need to load user file for saving?
415
416 scope IChildWidget child; // The primary widget.
417
418 Mutex mutex; // lock on methods for use outside the package.
419 }