Mercurial > projects > mde
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 } |