1 /*
2  * Copyright (C) 2016-2017, by Laszlo Szeremi under the Boost license.
3  *
4  * Pixel Perfect Editor, graphics.outputScreen module
5  */
6 
7 module editor;
8 
9 import pixelperfectengine.graphics.outputscreen;
10 import pixelperfectengine.graphics.raster;
11 import pixelperfectengine.graphics.layers;
12 import pixelperfectengine.graphics.paletteman;
13 //import pixelperfectengine.extbmp.extbmp;
14 
15 import pixelperfectengine.graphics.bitmap;
16 import pixelperfectengine.graphics.draw;
17 //import collision;
18 import pixelperfectengine.system.input;
19 import pixelperfectengine.system.file;
20 import pixelperfectengine.system.etc;
21 import pixelperfectengine.system.config;
22 import pixelperfectengine.system.systemutility;
23 import std.stdio;
24 import std.conv;
25 import core.stdc.string : memcpy;
26 //import derelict.sdl2.sdl;
27 import bindbc.sdl;
28 import pixelperfectengine.concrete.window;
29 import pixelperfectengine.concrete.eventchainsystem;
30 import pixelperfectengine.map.mapformat;
31 import pixelperfectengine.system.timer;
32 
33 //import converterdialog;
34 import windows.resizemap;
35 import windows.about;
36 import editorevents;
37 public import windows.layerlist;
38 public import windows.materiallist;
39 import document;
40 import windows.rasterwindow;
41 import windows.newtilelayer;
42 import clipboard;
43 
44 
45 
46 public class NewDocumentDialog : Window{
47 	public Editor ie;
48 	private TextBox[] textBoxes;
49 	public this(Coordinate size, dstring title){
50 		super(size, title);
51 	}
52 	public this(Editor ie){
53 		this(Box(10,10,220,150),"New Document"d);
54 		this.ie = ie;
55 		Button[] buttons;
56 		Label[] labels;
57 		buttons ~= new Button("Ok", "ok", Box(150,110,200,130));
58 
59 		labels ~= new Label("Name:","",Box(5,20,80,39));
60 		labels ~= new Label("RasterX:","",Box(5,40,80,59));
61 		labels ~= new Label("RasterY:","",Box(5,60,80,79));
62 		//labels ~= new Label("N. of colors:","",Coordinate(5,80,120,99));
63 		textBoxes ~= new TextBox("newdocument","name",Box(81,20,200,39));
64 		textBoxes ~= new TextBox("424","rX",Box(121,40,200,59));
65 		textBoxes ~= new TextBox("240","rY",Box(121,60,200,79));
66 		//textBoxes ~= new TextBox("","pal",Coordinate(121,80,200,99));
67 		addElement(buttons[0]);
68 		foreach(WindowElement we; labels){
69 			addElement(we);
70 		}
71 		foreach(TextBox we; textBoxes){
72 			//we.addTextInputHandler(inputhandler);
73 			addElement(we);
74 		}
75 		buttons[0].onMouseLClick = &buttonOn_onMouseLClickRel;
76 	}
77 
78 	public void buttonOn_onMouseLClickRel(Event event){
79 		ie.createNewDocument(textBoxes[0].getText().text, to!int(textBoxes[1].getText().text), to!int(textBoxes[2].getText().text));
80 
81 		close();
82 	}
83 }
84 
85 public class TopLevelWindow : Window {
86 	public this(int width, int height, Editor prg) {
87 		Text mt(dstring text) @safe nothrow {
88 			return new Text(text, globalDefaultStyle.getChrFormatting("menuBar"));
89 		}
90 		super(Box(0, 0, width, height), ""d, [], null);
91 		MenuBar mb;
92 		{
93 			PopUpMenuElement[] menuElements;
94 			menuElements ~= new PopUpMenuElement("file", mt("FILE"));
95 
96 			menuElements[0].setLength(7);
97 			menuElements[0][0] = new PopUpMenuElement("new", "New PPE map");
98 			menuElements[0][1] = new PopUpMenuElement("newTemp", "New PPE map from template");
99 			menuElements[0][2] = new PopUpMenuElement("load", "Load PPE map");
100 			menuElements[0][3] = new PopUpMenuElement("save", "Save PPE map");
101 			menuElements[0][4] = new PopUpMenuElement("saveAs", "Save PPE map as");
102 			menuElements[0][5] = new PopUpMenuElement("saveTemp", "Save PPE map as template");
103 			menuElements[0][6] = new PopUpMenuElement("exit", "Exit application");
104 
105 			menuElements ~= new PopUpMenuElement("edit", mt("EDIT"));
106 
107 			menuElements[1].setLength(7);
108 			menuElements[1][0] = new PopUpMenuElement("undo", "Undo");
109 			menuElements[1][1] = new PopUpMenuElement("redo", "Redo");
110 			menuElements[1][2] = new PopUpMenuElement("copy", "Copy");
111 			menuElements[1][3] = new PopUpMenuElement("cut", "Cut");
112 			menuElements[1][4] = new PopUpMenuElement("paste", "Paste");
113 			menuElements[1][5] = new PopUpMenuElement("editorSetup", "Editor settings");
114 			menuElements[1][6] = new PopUpMenuElement("docSetup", "Document settings");
115 
116 			menuElements ~= new PopUpMenuElement("view", mt("VIEW"));
117 
118 			//menuElements[2].setLength(2);
119 			menuElements[2] ~= new PopUpMenuElement("layerList", "Layers");
120 			menuElements[2] ~= new PopUpMenuElement("materialList", "Materials");
121 			menuElements[2] ~= new PopUpMenuElement("viewgrid", "Grid");
122 			menuElements[2] ~= new PopUpMenuElement("viewobj", "Objects");
123 			menuElements[2] ~= new PopUpMenuElement("resetLayers", "Reset layer display");
124 			//menuElements[2][2] = new PopUpMenuElement("layerTools", "Layer tools", "Alt + T");
125 
126 			menuElements ~= new PopUpMenuElement("layers", mt("LAYERS"));
127 
128 			//menuElements[3].setLength(5);
129 			menuElements[3] ~= new PopUpMenuElement("newLayer", "New layer");
130 			menuElements[3] ~= new PopUpMenuElement("delLayer", "Delete layer");
131 			menuElements[3] ~= new PopUpMenuElement("\\submenu\\", "Import layerdata", ">");
132 			menuElements[3][2] ~= new PopUpMenuElement("tiledcsvi", "Tiled CSV file");
133 			menuElements[3][2] ~= new PopUpMenuElement("ppebinmapi", "PPE binary map file");
134 			menuElements[3] ~= new PopUpMenuElement("\\submenu\\", "Export layerdata", ">");
135 			menuElements[3][3] ~= new PopUpMenuElement("tiledcsve", "Tiled CSV file");
136 			menuElements[3][3] ~= new PopUpMenuElement("ppebinmape", "PPE binary map file");
137 			menuElements[3] ~= new PopUpMenuElement("layerSrc", "Layer resources");
138 			menuElements[3] ~= new PopUpMenuElement("resizeLayer", "Resize layer");
139 
140 			menuElements ~= new PopUpMenuElement("tools", mt("TOOLS"));
141 
142 			menuElements[4].setLength(2);
143 			menuElements[4][0] = new PopUpMenuElement("tgaTool", "TGA Toolkit");
144 			menuElements[4][1] = new PopUpMenuElement("bmfontTool", "BMFont Toolkit");
145 
146 			menuElements ~= new PopUpMenuElement("help", mt("HELP"));
147 
148 			menuElements[5].setLength(2);
149 			menuElements[5][0] = new PopUpMenuElement("helpFile", "Content");
150 			menuElements[5][1] = new PopUpMenuElement("about", "About");
151 
152 			mb = new MenuBar("mb", Box(0,0, width - 1, 15), menuElements);
153 
154 			mb.onMenuEvent = &prg.menuEvent;
155 		}
156 		addElement(mb);
157 	}
158 	public override void draw(bool drawHeaderOnly = false) {
159 		output.drawFilledBox(position, 0);
160 		foreach (WindowElement we; elements) {
161 			we.draw();
162 		}
163 	}
164 	public override void drawHeader() {
165 
166 	}
167 	///Passes mouse click event
168 	public override void passMCE(MouseEventCommons mec, MouseClickEvent mce) {
169 		lastMousePos = Point(mce.x - position.left, mce.y - position.top);
170 		foreach (WindowElement we; elements) {
171 			if (we.getPosition.isBetween(lastMousePos)) {
172 				lastMouseEventTarget = we;
173 				mce.x = lastMousePos.x;
174 				mce.y = lastMousePos.y;
175 				we.passMCE(mec, mce);
176 				return;
177 			}
178 		}
179 		foreach (ISmallButton sb; smallButtons) {
180 			WindowElement we = cast(WindowElement)sb;
181 			if (we.getPosition.isBetween(lastMousePos)) {
182 				lastMouseEventTarget = we;
183 				mce.x = lastMousePos.x;
184 				mce.y = lastMousePos.y;
185 				we.passMCE(mec, mce);
186 				return;
187 			}
188 		}
189 		lastMouseEventTarget = null;
190 	}
191 	///Passes mouse move event
192 	public override void passMME(MouseEventCommons mec, MouseMotionEvent mme) {
193 		lastMousePos = Point(mme.x - position.left, mme.y - position.top);
194 		if (lastMouseEventTarget) {
195 			mme.x = lastMousePos.x;
196 			mme.y = lastMousePos.y;
197 			lastMouseEventTarget.passMME(mec, mme);
198 			if (!lastMouseEventTarget.getPosition.isBetween(mme.x, mme.y)) {
199 				lastMouseEventTarget = null;
200 			}
201 		} else {
202 			foreach (WindowElement we; elements) {
203 				if (we.getPosition.isBetween(lastMousePos)) {
204 					lastMouseEventTarget = we;
205 					mme.x = lastMousePos.x;
206 					mme.y = lastMousePos.y;
207 					we.passMME(mec, mme);
208 					return;
209 				}
210 			}
211 		}
212 	}
213 }
214 
215 public class Editor : InputListener, SystemEventListener {
216 	public OutputScreen[] ow;
217 	public Raster rasters;
218 	public InputHandler input;
219 	public wchar selectedTile;
220 	public BitmapAttrib selectedTileAttrib;
221 	public int selectedLayer;
222 	public SpriteLayer windowing;
223 	public SpriteLayer bitmapPreview;
224 	public bool onexit, exitDialog, newLayerDialog, mouseState, rasterRefresh;
225 	public Window test;
226 	public WindowHandler wh;
227 	//public EffectLayer selectionLayer;
228 	//public ForceFeedbackHandler ffb;
229 	//private uint[5] framecounter;
230 	public char[40] windowTitle;
231 	public ConfigurationProfile configFile;
232 	private int mouseX, mouseY;
233 	private Coordinate selection, selectedTiles;
234 	//public PlacementMode pm;
235 	//public UndoableStack undoStack;
236 	//public PaletteManager palman;
237 	public MapDocument[dstring] documents;
238 	public MapDocument selDoc;
239 	public LayerList layerList;
240 	public MaterialList materialList;
241 	public MapClipboard mapClipboard;
242 	
243 	public this(string[] args){
244 		ConfigurationProfile.setVaultPath("ZILtoid1991","PixelPerfectEditor");
245 		if (args.length > 1) {
246 			if (args[1] == "--restore") {
247 				ConfigurationProfile.restoreDefaults;
248 			}
249 		}
250 		configFile = new ConfigurationProfile();
251 
252 		windowing = new SpriteLayer(RenderingMode.Copy);
253 		bitmapPreview = new SpriteLayer();
254 
255 		wh = new WindowHandler(1696,960,848,480,windowing);
256 		//wh.ie = this;
257 
258 		//Initialize the Concrete framework
259 		INIT_CONCRETE();
260 		//writeln(globalDefaultStyle.drawParameters);
261 		//Initialize custom GUI elements
262 		{
263 			Bitmap8Bit[] customGUIElems = loadBitmapSheetFromFile!Bitmap8Bit("../system/concreteGUIE1.tga", 16, 16);
264 			globalDefaultStyle.setImage(customGUIElems[0], "menuButtonA");
265 			globalDefaultStyle.setImage(customGUIElems[1], "menuButtonB");
266 			globalDefaultStyle.setImage(customGUIElems[2], "fullSizeButtonA");
267 			globalDefaultStyle.setImage(customGUIElems[3], "fullSizeButtonB");
268 			globalDefaultStyle.setImage(customGUIElems[4], "smallSizeButtonA");
269 			globalDefaultStyle.setImage(customGUIElems[5], "smallSizeButtonB");
270 			globalDefaultStyle.setImage(customGUIElems[6], "newDocumentButtonA");
271 			globalDefaultStyle.setImage(customGUIElems[7], "newDocumentButtonB");
272 			globalDefaultStyle.setImage(customGUIElems[8], "saveDocumentButtonA");
273 			globalDefaultStyle.setImage(customGUIElems[9], "saveDocumentButtonB");
274 			globalDefaultStyle.setImage(customGUIElems[10], "loadDocumentButtonA");
275 			globalDefaultStyle.setImage(customGUIElems[11], "loadDocumentButtonB");
276 			globalDefaultStyle.setImage(customGUIElems[12], "settingsButtonA");
277 			globalDefaultStyle.setImage(customGUIElems[13], "settingsButtonB");
278 			globalDefaultStyle.setImage(customGUIElems[14], "blankButtonA");
279 			globalDefaultStyle.setImage(customGUIElems[15], "blankButtonB");
280 		}
281 		{
282 			Bitmap8Bit[] customGUIElems = loadBitmapSheetFromFile!Bitmap8Bit("../system/concreteGUIE4.tga", 16, 16);
283 			globalDefaultStyle.setImage(customGUIElems[0], "addMaterialA");
284 			globalDefaultStyle.setImage(customGUIElems[1], "addMaterialB");
285 			globalDefaultStyle.setImage(customGUIElems[2], "removeMaterialA");
286 			globalDefaultStyle.setImage(customGUIElems[3], "removeMaterialB");
287 			globalDefaultStyle.setImage(customGUIElems[4], "horizMirrorA");
288 			globalDefaultStyle.setImage(customGUIElems[5], "horizMirrorB");
289 			globalDefaultStyle.setImage(customGUIElems[6], "vertMirrorA");
290 			globalDefaultStyle.setImage(customGUIElems[7], "vertMirrorB");
291 			globalDefaultStyle.setImage(customGUIElems[8], "ovrwrtInsA");
292 			globalDefaultStyle.setImage(customGUIElems[9], "ovrwrtInsB");
293 			//globalDefaultStyle.setImage(customGUIElems[10], "");
294 			//globalDefaultStyle.setImage(customGUIElems[11], "");
295 			globalDefaultStyle.setImage(customGUIElems[12], "paletteDownA");
296 			globalDefaultStyle.setImage(customGUIElems[13], "paletteDownB");
297 			globalDefaultStyle.setImage(customGUIElems[14], "paletteUpA");
298 			globalDefaultStyle.setImage(customGUIElems[15], "paletteUpB");
299 		}
300 		{
301 			Bitmap8Bit[] customGUIElems = loadBitmapSheetFromFile!Bitmap8Bit("../system/concreteGUIE3.tga", 16, 16);
302 			globalDefaultStyle.setImage(customGUIElems[0], "trashButtonA");
303 			globalDefaultStyle.setImage(customGUIElems[1], "trashButtonB");
304 			globalDefaultStyle.setImage(customGUIElems[2], "visibilityButtonA");
305 			globalDefaultStyle.setImage(customGUIElems[3], "visibilityButtonB");
306 			globalDefaultStyle.setImage(customGUIElems[4], "newTileLayerButtonA");
307 			globalDefaultStyle.setImage(customGUIElems[5], "newTileLayerButtonB");
308 			globalDefaultStyle.setImage(customGUIElems[6], "newSpriteLayerButtonA");
309 			globalDefaultStyle.setImage(customGUIElems[7], "newSpriteLayerButtonB");
310 			globalDefaultStyle.setImage(customGUIElems[8], "newTransformableTileLayerButtonA");
311 			globalDefaultStyle.setImage(customGUIElems[9], "newTransformableTileLayerButtonB");
312 			globalDefaultStyle.setImage(customGUIElems[10], "importLayerDataButtonA");
313 			globalDefaultStyle.setImage(customGUIElems[11], "importLayerDataButtonB");
314 			globalDefaultStyle.setImage(customGUIElems[12], "importMaterialDataButtonA");
315 			globalDefaultStyle.setImage(customGUIElems[13], "importMaterialDataButtonB");
316 			globalDefaultStyle.setImage(customGUIElems[14], "paletteButtonA");
317 			globalDefaultStyle.setImage(customGUIElems[15], "paletteButtonB");
318 		}
319 		{
320 			Bitmap8Bit[] customGUIElems = loadBitmapSheetFromFile!Bitmap8Bit("../system/concreteGUIE5.tga", 16, 16);
321 			globalDefaultStyle.setImage(customGUIElems[0], "percentButtonA");
322 			globalDefaultStyle.setImage(customGUIElems[1], "percentButtonB");
323 			globalDefaultStyle.setImage(customGUIElems[2], "tileButtonA");
324 			globalDefaultStyle.setImage(customGUIElems[3], "tileButtonB");
325 			globalDefaultStyle.setImage(customGUIElems[4], "selMoveButtonA");
326 			globalDefaultStyle.setImage(customGUIElems[5], "selMoveButtonB");
327 			globalDefaultStyle.setImage(customGUIElems[6], "tilePlacementButtonA");
328 			globalDefaultStyle.setImage(customGUIElems[7], "tilePlacementButtonB");
329 			globalDefaultStyle.setImage(customGUIElems[8], "objPlacementButtonA");
330 			globalDefaultStyle.setImage(customGUIElems[9], "objPlacementButtonB");
331 			globalDefaultStyle.setImage(customGUIElems[10], "sprtPlacementButtonA");
332 			globalDefaultStyle.setImage(customGUIElems[11], "sprtPlacementButtonB");
333 			//globalDefaultStyle.setImage(customGUIElems[12], "importMaterialDataButtonA");
334 			//globalDefaultStyle.setImage(customGUIElems[13], "importMaterialDataButtonB");
335 			globalDefaultStyle.setImage(customGUIElems[14], "soloButtonA");
336 			globalDefaultStyle.setImage(customGUIElems[15], "soloButtonB");
337 		}
338 
339 		//wh.initGUI();
340 
341 		input = new InputHandler();
342 		//input.ml ~= this;
343 		input.mouseListener = wh;
344 		input.inputListener = this;
345 		input.systemEventListener = this;
346 		//input.kb ~= KeyBinding(0, SDL_SCANCODE_ESCAPE, 0, "sysesc", Devicetype.KEYBOARD);
347 		//input.kb ~= configFile.keyBindingList;
348 		input.addBinding(InputHandler.getSysEscKey, InputBinding(InputHandler.sysescCode));
349 		configFile.loadBindings(input);
350 		
351 		WindowElement.inputHandler = input;
352 		
353 		ow ~= new OutputScreen("Pixel Perfect Editor", 1696, 960);
354 
355 		rasters = new Raster(848, 480, ow[0], 0, 2);
356 		ow[0].setMainRaster(rasters);
357 		rasters.addLayer(windowing, 0);
358 		rasters.addLayer(bitmapPreview, 1);
359 		rasters.loadPalette(loadPaletteFromFile("../system/concreteGUIE1.tga"));
360 		wh.setBaseWindow(new TopLevelWindow(848, 480, this));
361 		wh.addBackground(loadBitmapFromFile!Bitmap32Bit("../system/background.png"));
362 		mapClipboard = new MapClipboard(10);
363 		openMaterialList();
364 		openLayerList();
365 	}
366 	public void menuEvent(Event ev) {
367 		if (ev.type == EventType.Menu){
368 			MenuEvent mev = cast(MenuEvent)ev;
369 			switch (mev.itemSource) {
370 				case "save":
371 					onSave();
372 					break;
373 				case "saveAs":
374 					onSaveAs();
375 					break;
376 				case "load":
377 					onLoad();
378 					break;
379 				case "newLayer":
380 					initNewTileLayer();
381 					break;
382 				case "new":
383 					//TileLayerEditor tle = new TileLayerEditor(this);
384 					//wh.addWindow(tle);
385 					onNewDocument();
386 					break;
387 				case "resizeLayer":
388 					initResizeLayer();
389 					break;
390 				case "undo":
391 					onUndo();
392 					break;
393 				case "redo":
394 					onRedo();
395 					break;
396 				case "exit":
397 					onQuit();
398 					break;
399 				case "layerList":
400 					openLayerList();
401 					break;
402 				case "materialList":
403 					openMaterialList();
404 					break;
405 				case "tiledcsvi":
406 					if (selDoc) {
407 						if (selDoc.getLayerInfo(selDoc.selectedLayer).type == LayerType.Tile || 
408 								selDoc.getLayerInfo(selDoc.selectedLayer).type == LayerType.TransformableTile) {
409 							import pixelperfectengine.concrete.dialogs.filedialog;
410 							wh.addWindow(new FileDialog("Import layer from CSV", "tiledcsvi", &tiledCSVImport, 
411 									[FileDialog.FileAssociationDescriptor("Tiled CSV file", ["*.csv"])], "./",));
412 						}
413 					}
414 					break;
415 				case "tiledcsve":
416 					if (selDoc) {
417 						if (selDoc.getLayerInfo(selDoc.selectedLayer).type == LayerType.Tile || 
418 								selDoc.getLayerInfo(selDoc.selectedLayer).type == LayerType.TransformableTile) {
419 							import pixelperfectengine.concrete.dialogs.filedialog;
420 							wh.addWindow(new FileDialog("Export layer as CSV", "tiledcsve", &tiledCSVExport, 
421 									[FileDialog.FileAssociationDescriptor("Tiled CSV file", ["*.csv"])], "./", true));
422 						}
423 					}
424 					break;
425 				case "ppebinmapi":
426 					if (selDoc) {
427 						if (selDoc.getLayerInfo(selDoc.selectedLayer).type == LayerType.Tile || 
428 								selDoc.getLayerInfo(selDoc.selectedLayer).type == LayerType.TransformableTile) {
429 							import pixelperfectengine.concrete.dialogs.filedialog;
430 							wh.addWindow(new FileDialog("Import layer from MBF", "ppebinmapi", &ppeBinImport, 
431 									[FileDialog.FileAssociationDescriptor("PixelPerfectEngine map binary file", ["*.mbf"])], "./",));
432 						}
433 					}
434 					break;
435 				case "ppebinmape":
436 					if (selDoc) {
437 						if (selDoc.getLayerInfo(selDoc.selectedLayer).type == LayerType.Tile || 
438 								selDoc.getLayerInfo(selDoc.selectedLayer).type == LayerType.TransformableTile) {
439 							import pixelperfectengine.concrete.dialogs.filedialog;
440 							wh.addWindow(new FileDialog("Export layer as MBF", "ppebinmape", &ppeBinExport, 
441 									[FileDialog.FileAssociationDescriptor("PixelPerfectEngine map binary file", ["*.mbf"])], "./", true));
442 						}
443 					}
444 					break;
445 				case "resetLayers":
446 					if (selDoc) {
447 						selDoc.outputWindow.clearDisplayLists();
448 					}
449 					break;
450 				case "copy":
451 					onCopy();
452 					break;
453 				case "cut":
454 					onCut();
455 					break;
456 				case "paste":
457 					onPaste();
458 					break;
459 				default:
460 					break;
461 			}
462 		}
463 	}
464 	private void tiledCSVImport(Event ev) {
465 		import csvconv : fromCSV;
466 		try {
467 			if (selDoc) {
468 				if (selDoc.getLayerInfo(selDoc.selectedLayer).type == LayerType.Tile || 
469 						selDoc.getLayerInfo(selDoc.selectedLayer).type == LayerType.TransformableTile) {
470 					ITileLayer target = cast(ITileLayer)(selDoc.mainDoc.layeroutput[selDoc.selectedLayer]);
471 					FileEvent fev = cast(FileEvent)ev;
472 					fromCSV(fev.getFullPath, selDoc);
473 				}
474 			}
475 		} catch (Exception e) {
476 			wh.message("CSV Import Error!", to!dstring(e.msg));
477 		}
478 	}
479 	private void tiledCSVExport(Event ev) {
480 		import csvconv : toCSV;
481 		try {
482 			if (selDoc) {
483 				if (selDoc.getLayerInfo(selDoc.selectedLayer).type == LayerType.Tile || 
484 						selDoc.getLayerInfo(selDoc.selectedLayer).type == LayerType.TransformableTile) {
485 					ITileLayer target = cast(ITileLayer)(selDoc.mainDoc.layeroutput[selDoc.selectedLayer]);
486 					FileEvent fev = cast(FileEvent)ev;
487 					toCSV(fev.getFullPath, target);
488 				}
489 			}
490 		} catch (Exception e) {
491 			wh.message("CSV Export Error!", to!dstring(e.msg));
492 		}
493 	}
494 	private void ppeBinImport(Event ev) {
495 		import pixelperfectengine.map.mapdata;
496 		try {
497 			if (selDoc) {
498 				if (selDoc.getLayerInfo(selDoc.selectedLayer).type == LayerType.Tile || 
499 						selDoc.getLayerInfo(selDoc.selectedLayer).type == LayerType.TransformableTile) {
500 					FileEvent fev = cast(FileEvent)ev;
501 					File source = File(fev.getFullPath, "rb");
502 					MapDataHeader header;
503 					MappingElement[] map = loadMapFile(source, header);
504 					selDoc.assignImportedTilemap(map, header.sizeX, header.sizeY);
505 				}
506 			}
507 		} catch (Exception e) {
508 			wh.message("MBF Import Error!", to!dstring(e.msg));
509 		}
510 	}
511 	private void ppeBinExport(Event ev) {
512 		import pixelperfectengine.map.mapdata;
513 		try {
514 			if (selDoc) {
515 				if (selDoc.getLayerInfo(selDoc.selectedLayer).type == LayerType.Tile || 
516 						selDoc.getLayerInfo(selDoc.selectedLayer).type == LayerType.TransformableTile) {
517 					ITileLayer source = cast(ITileLayer)(selDoc.mainDoc.layeroutput[selDoc.selectedLayer]);
518 					FileEvent fev = cast(FileEvent)ev;
519 					File target = File(fev.getFullPath, "wb");
520 					MapDataHeader header = MapDataHeader(source.getMX, source.getMY);
521 					saveMapFile(header, source.getMapping, target);
522 				}
523 			}
524 		} catch (Exception e) {
525 			wh.message("MBF Export Error!", to!dstring(e.msg));
526 		}
527 	}
528 	/**
529 	 * Called when a keybinding event is generated.
530 	 * The `id` should be generated from a string, usually the name of the binding.
531 	 * `code` is a duplicate of the code used for fast lookup of the binding, which also contains other info (deviceID, etc).
532 	 * `timestamp` is the time lapsed since the start of the program, can be used to measure time between keypresses.
533 	 * NOTE: Hat events on joysticks don't generate keyReleased events, instead they generate keyPressed events on release.
534 	 */
535 	public void keyEvent(uint id, BindingCode code, uint timestamp, bool isPressed) {
536 		import pixelperfectengine.system.etc : hashCalc;
537 		switch (id) {
538 			case hashCalc("copy"):
539 				if (!isPressed)
540 					onCopy;
541 				break;
542 			case hashCalc("cut"):
543 				if (!isPressed)
544 					onCut;
545 				break;
546 			case hashCalc("paste"):
547 				if (!isPressed)
548 					onPaste;
549 				break;
550 			case hashCalc("undo"):
551 				if (!isPressed)
552 					onUndo;
553 				break;
554 			case hashCalc("redo"):
555 				if (!isPressed)
556 					onRedo;
557 				break;
558 			case hashCalc("save"):
559 				if (!isPressed)
560 					onSave;
561 				break;
562 			case hashCalc("saveAs"):
563 				if (!isPressed)
564 					onSaveAs;
565 				break;
566 			case hashCalc("insert"):
567 				if (!isPressed){
568 					if (materialList)
569 						materialList.ovrwrtIns.toggle();
570 					else if (selDoc)
571 						selDoc.voidfill = !selDoc.voidfill;
572 				}
573 				break;
574 			case hashCalc("delArea"):
575 				if (selDoc && isPressed) 
576 					selDoc.deleteArea();
577 				break;
578 			case hashCalc("palUp"):
579 				if (isPressed) {
580 					if (materialList)
581 						materialList.palUp_onClick(null);
582 					else if (selDoc)
583 						selDoc.tileMaterial_PaletteUp;
584 				}
585 				break;
586 			case hashCalc("palDown"):
587 				if (isPressed) {
588 					if (materialList)
589 						materialList.palDown_onClick(null);
590 					else if (selDoc)
591 						selDoc.tileMaterial_PaletteDown;
592 				}
593 				break;
594 			case hashCalc("hMirror"):
595 				if (selDoc && !isPressed) {
596 					if (materialList)
597 						materialList.horizMirror.toggle;
598 					else
599 						selDoc.tileMaterial_FlipHorizontal;
600 				}
601 				break;
602 			case hashCalc("selFlipHoriz"):
603 				if (selDoc && !isPressed) {
604 					selDoc.flipTilesHoriz();
605 				}
606 				break;
607 			case hashCalc("selFlipVert"):
608 				if (selDoc && !isPressed) {
609 					selDoc.flipTilesVert();
610 				}
611 				break;
612 			case hashCalc("selMirrorHoriz"):
613 				if (selDoc && !isPressed) {
614 					selDoc.selMirrorHoriz();
615 				}
616 				break;
617 			case hashCalc("selMirrorVert"):
618 				if (selDoc && !isPressed) {
619 					selDoc.selMirrorVert();
620 				}
621 				break;
622 			case hashCalc("vMirror"):
623 				if (selDoc && !isPressed) {
624 					if (materialList)
625 						materialList.vertMirror.toggle;
626 					else 
627 						selDoc.tileMaterial_FlipVertical;
628 				}
629 				break;
630 			case hashCalc("place"):
631 				if (selDoc && !isPressed)
632 					selDoc.fillSelectedArea();
633 				break;
634 			case hashCalc("nextTile"):
635 				if (selDoc && isPressed) {
636 					if (materialList) {
637 						materialList.nextTile();
638 					} else {
639 						selDoc.tileMaterial_Up();
640 					}
641 				}
642 				break;
643 			case hashCalc("prevTile"):
644 				if (selDoc && isPressed) {
645 					if (materialList) {
646 						materialList.prevTile();
647 					} else {
648 						selDoc.tileMaterial_Down();
649 					}
650 				}
651 				break;
652 			case hashCalc("moveUp"):
653 				if (selDoc && isPressed)
654 					selDoc.moveSelection(0, -1);
655 				break;
656 			case hashCalc("moveDown"):
657 				if (selDoc && isPressed)
658 					selDoc.moveSelection(0, 1);
659 				break;
660 			case hashCalc("moveLeft"):
661 				if (selDoc && isPressed)
662 					selDoc.moveSelection(-1, 0);
663 				break;
664 			case hashCalc("moveRight"):
665 				if (selDoc && isPressed)
666 					selDoc.moveSelection(1, 0);
667 				break;
668 			case hashCalc("scrollUp"):
669 				if (selDoc) {
670 					if (isPressed) 
671 						selDoc.sYAmount = -1;
672 					else
673 						selDoc.sYAmount = 0;
674 				}
675 				break;
676 			case hashCalc("scrollDown"):
677 				if (selDoc) {
678 					if (isPressed) 
679 						selDoc.sYAmount = 1;
680 					else
681 						selDoc.sYAmount = 0;
682 				}
683 				break;
684 			case hashCalc("scrollLeft"):
685 				if (selDoc) {
686 					if (isPressed) 
687 						selDoc.sXAmount = 1;
688 					else
689 						selDoc.sXAmount = 0;
690 				}
691 				break;
692 			case hashCalc("scrollRight"):
693 				if (selDoc) {
694 					if (isPressed) 
695 						selDoc.sXAmount = -1;
696 					else
697 						selDoc.sXAmount = 0;
698 				}
699 				break;
700 			case hashCalc("resetLayers"):
701 				if (selDoc && !isPressed) {
702 					selDoc.outputWindow.clearDisplayLists();
703 				}
704 				break;
705 			case hashCalc("nextLayer"):
706 				if (selDoc && !isPressed) {
707 					if (layerList)
708 						layerList.nextLayer();
709 				}
710 				break;
711 			case hashCalc("prevLayer"):
712 				if (selDoc && !isPressed) {
713 					if (layerList)
714 						layerList.prevLayer();
715 				}
716 				break;
717 			case hashCalc("hideLayer"):
718 				if (selDoc && !isPressed) {
719 					if (layerList)
720 						layerList.checkBox_Hide.toggle();
721 				}
722 				break;
723 			case hashCalc("soloLayer"):
724 				if (selDoc && !isPressed) {
725 					if (layerList)
726 						layerList.checkBox_Solo.toggle();
727 				}
728 				break;
729 			default:
730 				break;
731 		}
732 	}
733 	/**
734 	 * Called when an axis is being operated.
735 	 * The `id` should be generated from a string, usually the name of the binding.
736 	 * `code` is a duplicate of the code used for fast lookup of the binding, which also contains other info (deviceID, etc).
737 	 * `timestamp` is the time lapsed since the start of the program, can be used to measure time between keypresses.
738 	 * `value` is the current position of the axis normalized between -1.0 and +1.0 for joysticks, and 0.0 and +1.0 for analog
739 	 * triggers.
740 	 */
741 	public void axisEvent(uint id, BindingCode code, uint timestamp, float value) {
742 
743 	}
744 	public void onUndo () {
745 		if(selDoc !is null){
746 			selDoc.events.undo;
747 			selDoc.outputWindow.updateRaster;
748 		}
749 	}
750 	public void onRedo () {
751 		if(selDoc !is null){
752 			selDoc.events.redo;
753 			selDoc.outputWindow.updateRaster;
754 		}
755 	}
756 	public void onLoad () {
757 		import pixelperfectengine.concrete.dialogs.filedialog;
758 		FileDialog fd = new FileDialog("Load document","docLoad",&onLoadDialog,[FileDialog.FileAssociationDescriptor(
759 			"PPE map file", ["*.xmf"])],"./",false);
760 		wh.addWindow(fd);
761 	}
762 	public void onNewDocument () {
763 		wh.addWindow(new NewDocumentDialog(this));
764 	}
765 	public void onLoadDialog (Event ev) {
766 		import std.utf : toUTF32;
767 		try {
768 			FileEvent event = cast(FileEvent)ev;
769 			selDoc = new MapDocument(event.getFullPath);
770 			dstring name = toUTF32(selDoc.mainDoc.getName);
771 			RasterWindow w = new RasterWindow(selDoc.mainDoc.getHorizontalResolution, selDoc.mainDoc.getVerticalResolution, 
772 					rasters.palette.ptr, name, selDoc);
773 			selDoc.outputWindow = w;
774 			wh.addWindow(w);
775 			documents[name] = selDoc;
776 			selDoc.updateLayerList();
777 			selDoc.updateMaterialList();
778 			selDoc.mainDoc.loadTiles(w);
779 			selDoc.mainDoc.loadMappingData();
780 			w.loadLayers();
781 			w.updateRaster();
782 		} catch (Exception e) {
783 			debug writeln(e);
784 		}
785 		
786 	}
787 	public void onSave () {
788 		if (selDoc) {
789 			if (selDoc.filename) {
790 				try {
791 					selDoc.mainDoc.save(selDoc.filename);
792 				} catch (Exception e) {
793 					debug writeln(e);
794 				}
795 			} else {
796 				onSaveAs();
797 			}
798 		}
799 	}
800 	public void onSaveAs () {
801 		import pixelperfectengine.concrete.dialogs.filedialog;
802 		FileDialog fd = new FileDialog("Save document as","docSave",&onSaveDialog,[FileDialog.FileAssociationDescriptor(
803 			"PPE map file", ["*.xmf"])],"./",true);
804 		wh.addWindow(fd);
805 	}
806 	public void onSaveDialog(Event ev) {
807 		import std.path : extension;
808 		import std.ascii : toLower;
809 		FileEvent event = cast(FileEvent)ev;
810 		selDoc.filename = event.getFullPath();
811 		if(extension(selDoc.filename) != ".xmf"){
812 			selDoc.filename ~= ".xmf";
813 		}
814 		try {
815 			selDoc.mainDoc.save(selDoc.filename);
816 		} catch (Exception e) {
817 			debug writeln(e);
818 		}
819 	}
820 	public void onCopy() {
821 		if (selDoc !is null) {
822 			selDoc.copy();
823 		}
824 	}
825 	public void onCut() {
826 		if (selDoc !is null) {
827 			selDoc.cut();
828 		}
829 	}
830 	public void onPaste() {
831 		if (selDoc !is null) {
832 			selDoc.paste();
833 		}
834 	}
835 	
836 	public void onQuit(){onExit();}
837 	public void controllerRemoved(uint ID){}
838 	public void controllerAdded(uint ID){}
839 	public void initResizeLayer() {
840 		//import resizeMap;
841 		if (selDoc !is null) {
842 			wh.addWindow(new ResizeMap(selDoc));
843 		}
844 	}
845 	
846 	
847 	/**
848 	 * Opens a window to ask the user for the data on the new tile layer
849 	 */
850 	public void initNewTileLayer(){
851 		if (selDoc !is null)
852 			wh.addWindow(new NewTileLayerDialog(this));
853 	}
854 	/**
855 	 * Opens a window to ask the user for input on materials to be added
856 	 */
857 	public void initAddMaterials() {
858 		import windows.addtiles;
859 		if (selDoc !is null) {
860 			if (selDoc.mainDoc.getLayerInfo(selDoc.selectedLayer).type == LayerType.Tile || 
861 					selDoc.mainDoc.getLayerInfo(selDoc.selectedLayer).type == LayerType.TransformableTile) {
862 				ITileLayer itl = cast(ITileLayer)selDoc.mainDoc.layeroutput[selDoc.selectedLayer];
863 				const int tileX = itl.getTileWidth, tileY = itl.getTileHeight;
864 				wh.addWindow(new AddTiles(this, tileX, tileY));
865 			}
866 		}
867 	}
868 	/**
869 	 * Creates a new tile layer with the given data.
870 	 *
871 	 * file: Optional field. If given, it specifies the external file for binary map data. If it specifies an already
872 	 * existing file, then that file will be loaded. If null, then the map data will be embedded as a BASE64 chunk.
873 	 * tmplt: Optional field. Specifies the initial tile source data from a map file alongside with the name of the layer
874 	 */
875 	public void newTileLayer(int tX, int tY, int mX, int mY, dstring name, string file, bool embed) {
876 		selDoc.events.addToTop(new CreateTileLayerEvent(selDoc, tX, tY, mX, mY, name, file, embed));
877 	}
878 	public void setRasterRefresh(){
879 		rasterRefresh = true;
880 	}
881 	public void whereTheMagicHappens(){
882 		//rasters.refresh();
883 		while(!onexit){
884 			input.test();
885 			timer.test();
886 			rasters.refresh();
887 			if (selDoc) {
888 				selDoc.contScrollLayer();
889 			}
890 		}
891 		configFile.store();
892 	}
893 	public void onExit(){
894 		import pixelperfectengine.concrete.dialogs.defaultdialog;
895 		exitDialog=true;
896 		DefaultDialog dd = new DefaultDialog(Coordinate(10,10,220,75), "exitdialog","Exit application", ["Are you sure?"],
897 				["Yes","No","Pls save"],["ok","close","save"]);
898 
899 		dd.output = &confirmExit;
900 		wh.addWindow(dd);
901 
902 	}
903 	private void confirmExit(Event ev) {
904 		WindowElement we = cast(WindowElement)ev.sender;
905 		if (we.getSource == "ok") {
906 			onexit = true;
907 		}
908 	}
909 	/+public void newDocument(){
910 		NewDocumentDialog ndd = new NewDocumentDialog(input);
911 		ndd.ie = this;
912 		wh.addWindow(ndd);
913 	}+/
914 	public void createNewDocument(dstring name, int rX, int rY){
915 		import std.utf : toUTF8;
916 		MapDocument md = new MapDocument(toUTF8(name), rX, rY);
917 		RasterWindow w = new RasterWindow(rX, rY, rasters.palette.ptr, name, md);
918 		md.outputWindow = w;
919 		wh.addWindow(w);
920 		documents[name] = md;
921 		selDoc = md;
922 	}
923 	public void openLayerList() {
924 		if (!layerList) {
925 			layerList = new LayerList(0, 16, &onLayerListClosed);
926 			wh.addWindow(layerList);
927 		}
928 	}
929 	private void onLayerListClosed() {
930 		layerList = null;
931 	}
932 	public void openMaterialList() {
933 		if (!materialList) {
934 			materialList = new MaterialList(0, 230, &onMaterialListClosed);
935 			wh.addWindow(materialList);
936 		}
937 	}
938 	private void onMaterialListClosed() {
939 		materialList = null;
940 	}
941 }