1 /*
2  * Copyright (C) 2015-2017, by Laszlo Szeremi under the Boost license.
3  *
4  * Pixel Perfect Engine, concrete.window module
5  */
6 
7 module PixelPerfectEngine.concrete.window;
8 
9 import PixelPerfectEngine.graphics.bitmap;
10 import PixelPerfectEngine.graphics.draw;
11 import PixelPerfectEngine.graphics.layers;
12 
13 public import PixelPerfectEngine.concrete.elements;
14 public import PixelPerfectEngine.concrete.stylesheet;
15 import PixelPerfectEngine.system.etc;
16 import PixelPerfectEngine.system.inputHandler;
17 
18 import std.algorithm.mutation;
19 import std.stdio;
20 import std.conv;
21 import std.file;
22 import std.path;
23 import std.datetime;
24 
25 /**
26  * Basic window. All other windows are inherited from this class.
27  */
28 public class Window : ElementContainer{
29 	protected WindowElement[] elements, mouseC, keyboardC, scrollC;
30 	protected dstring title;
31 	protected WindowElement draggedElement;
32 	public IWindowHandler parent;
33 	//public Bitmap16Bit[int] altStyleBrush;
34 	protected BitmapDrawer output;
35 	public int header;//, sizeX, sizeY;
36 	protected int moveX, moveY;
37 	protected bool fullUpdate, isActive;
38 	protected string[] extraButtons;
39 	public Coordinate position;
40 	public StyleSheet customStyle;
41 	public static StyleSheet defaultStyle;
42 	public static void delegate() onDrawUpdate;
43 	/**
44 	 * If the current window doesn't contain a custom StyleSheet, it gets from it's parent.
45 	 */
46 	public StyleSheet getStyleSheet(){
47 		if(customStyle is null){
48 			if(parent is null){
49 				return defaultStyle;
50 			}
51 			return parent.getStyleSheet();
52 		}
53 		return customStyle;
54 	}
55 	/**
56 	 * Updates the output of the elements.
57 	 */
58 	public void drawUpdate(WindowElement sender){
59 		/*if(!fullUpdate){
60 			this.draw();
61 		}*/
62 		output.insertBitmap(sender.getPosition().left,sender.getPosition().top,sender.output.output);
63 	}
64 
65 
66 	/**
67 	 * Standard constructor. "size" sets both the initial position and the size of the window.
68 	 * Extra buttons are handled from the StyleSheet, currently unimplemented.
69 	 */
70 	public this(Coordinate size, dstring title, string[] extraButtons = []){
71 		position = size;
72 		output = new BitmapDrawer(position.width(), position.height());
73 		this.title = title;
74 		//sizeX = position.width();
75 		//sizeY = position.height();
76 		//style = 0;
77 		//closeButton = 2;
78 		this.extraButtons = extraButtons;
79 	}
80 	public void addElement(WindowElement we){
81 		addElement(we, EventProperties.MOUSE | EventProperties.SCROLL);
82 	}
83 	/**
84 	 * Adds a new WindowElement with the given event properties.
85 	 */
86 	public void addElement(WindowElement we, int eventProperties){
87 		elements ~= we;
88 		we.elementContainer = this;
89 		if((eventProperties & EventProperties.KEYBOARD) == EventProperties.KEYBOARD){
90 			keyboardC ~= we;
91 		}
92 		if((eventProperties & EventProperties.MOUSE) == EventProperties.MOUSE){
93 			mouseC ~= we;
94 		}
95 		if((eventProperties & EventProperties.SCROLL) == EventProperties.SCROLL){
96 			scrollC ~= we;
97 		}
98 		we.draw();
99 	}
100 	/**
101 	 * Removes the WindowElement if 'we' is found within its ranges, does nothing otherwise.
102 	 */
103 	public void removeElement(WindowElement we){
104 		int i;
105 		while(elements.length > i){
106 			if(elements[i] == we){
107 				elements = remove(elements, i);
108 				break;
109 			}else{
110 				i++;
111 			}
112 		}
113 		i = 0;
114 		while(mouseC.length > i){
115 			if(mouseC[i] == we){
116 				mouseC = remove(mouseC, i);
117 				break;
118 			}else{
119 				i++;
120 			}
121 		}
122 		i = 0;
123 		while(scrollC.length > i){
124 			if(scrollC[i] == we){
125 				scrollC = remove(scrollC, i);
126 				break;
127 			}else{
128 				i++;
129 			}
130 		}
131 		i = 0;
132 		while(keyboardC.length > i){
133 			if(keyboardC[i] == we){
134 				keyboardC = remove(keyboardC, i);
135 				break;
136 			}else{
137 				i++;
138 			}
139 		}
140 		draw();
141 	}
142 	/**
143 	 * Draws the window. Intended to be used by the WindowHandler.
144 	 */
145 	public void draw(bool drawHeaderOnly = false){
146 		if(output.output.width != position.width || output.output.height != position.height){
147 			output = new BitmapDrawer(position.width(), position.height());
148 
149 		}
150 		//drawing the header
151 		drawHeader();
152 		if(drawHeaderOnly)
153 			return;
154 		output.drawFilledRectangle(0, position.width() - 1, getStyleSheet().getImage("closeButtonA").height,
155 				position.height() - 1, getStyleSheet().getColor("window"));
156 		int y1 = getStyleSheet().getImage("closeButtonA").height;
157 		/*output.drawRectangle(x1, sizeX - 1, 0, y1, getStyleBrush(header));
158 		output.drawFilledRectangle(x1 + (x1/2), sizeX - 1 - (x1/2), y1/2, y1 - (y1/2), getStyleBrush(header).readPixel(x1/2, y1/2));*/
159 
160 		//int headerLength = cast(int)(extraButtons.length == 0 ? position.width - 1 : position.width() - 1 - ((extraButtons.length>>2) * x1) );
161 
162 		//drawing the border of the window
163 		output.drawLine(0, position.width() - 1, y1, y1, getStyleSheet().getColor("windowascent"));
164 		output.drawLine(0, 0, y1, position.height() - 1, getStyleSheet().getColor("windowascent"));
165 		output.drawLine(0, position.width() - 1, position.height() - 1, position.height() - 1,
166 				getStyleSheet().getColor("windowdescent"));
167 		output.drawLine(position.width() - 1, position.width() - 1, y1, position.height() - 1,
168 				getStyleSheet().getColor("windowdescent"));
169 
170 		//output.drawText(x1+1, 1, title, getFontSet(0), 1);
171 
172 		fullUpdate = true;
173 		foreach(WindowElement we; elements){
174 			we.draw();
175 			//output.insertBitmap(we.getPosition().xa,we.getPosition().ya,we.output.output);
176 		}
177 		fullUpdate = false;
178 		parent.drawUpdate(this);
179 		if(onDrawUpdate !is null){
180 			onDrawUpdate();
181 		}
182 	}
183 	/**
184 	 * Draws the header.
185 	 */
186 	protected void drawHeader(){
187 		const ushort colorC = isActive ? getStyleSheet().getColor("WHAtop") : getStyleSheet().getColor("window");
188 		output.drawFilledRectangle(0, position.width() - 1, 0, getStyleSheet().getImage("closeButtonA").height - 1, colorC);
189 		output.insertBitmap(0,0,getStyleSheet().getImage("closeButtonA"));
190 		const int x1 = getStyleSheet().getImage("closeButtonA").width, y1 = getStyleSheet().getImage("closeButtonA").height;
191 		int x2 = position.width;
192 		int headerLength = cast(int)(extraButtons.length == 0 ? position.width() - 1 : position.width() - 1 -
193 				(extraButtons.length * x1));
194 		foreach(s ; extraButtons){
195 			x2 -= x1;
196 			output.insertBitmap(x2,0,getStyleSheet().getImage(s));
197 		}
198 		const ushort colorA = isActive ? getStyleSheet().getColor("WHAascent") : getStyleSheet().getColor("windowascent"),
199 				colorB = isActive ? getStyleSheet().getColor("WHAdescent") : getStyleSheet().getColor("windowdescent");
200 		output.drawLine(x1, headerLength, 0, 0, colorA);
201 		output.drawLine(x1, x1, 0, y1 - 1, colorA);
202 		output.drawLine(x1, headerLength, y1 - 1, y1 - 1, colorB);
203 		output.drawLine(headerLength, headerLength, 0, y1 - 1, colorB);
204 		output.drawColorText(x1,(y1-getStyleSheet().getFontset("default").getSize())/2,title,getStyleSheet().getFontset("default"),
205 				isActive ? getStyleSheet().getColor("WHTextActive") : getStyleSheet().getColor("WHTextInactive"), 0);
206 	}
207 	public @nogc @property bool active(){
208 		return isActive;
209 	}
210 	public @nogc @property bool active(bool val){
211 		return isActive = val;
212 	}
213 	public void setTitle(dstring s){
214 		title = s;
215 		drawHeader;
216 	}
217 	public dstring getTitle(){
218 		return title;
219 	}
220 	/**
221 	 * Detects where the mouse is clicked, then it either passes to an element, or tests whether the close button,
222 	 * an extra button was clicked, also tests for the header, which creates a drag event for moving the window.
223 	 */
224 	public void passMouseEvent(int x, int y, int state, ubyte button){
225 		if(state == ButtonState.PRESSED){
226 			if(getStyleSheet.getImage("closeButtonA").width > x && getStyleSheet.getImage("closeButtonA").height > y && button == MouseButton.LEFT){
227 				close();
228 				return;
229 			}else if(getStyleSheet.getImage("closeButtonA").height > y){
230 				if(y > position.width - (getStyleSheet.getImage("closeButtonA").width * extraButtons.length)){
231 					y -= position.width - (getStyleSheet.getImage("closeButtonA").width * extraButtons.length);
232 					extraButtonEvent(y / getStyleSheet.getImage("closeButtonA").width, button, state);
233 					return;
234 				}
235 				parent.moveUpdate(this);
236 				return;
237 			}
238 			//x -= position.xa;
239 			//y -= position.ya;
240 
241 			foreach(WindowElement e; mouseC){
242 				if(e.getPosition().left < x && e.getPosition().right > x && e.getPosition().top < y && e.getPosition().bottom > y){
243 					e.onClick(x - e.getPosition().left, y - e.getPosition().top, state, button);
244 					draggedElement = e;
245 
246 					return;
247 				}
248 			}
249 		}else{
250 			if(draggedElement){
251 				draggedElement.onClick(x - draggedElement.getPosition().left, y - draggedElement.getPosition().top, state, button);
252 				draggedElement = null;
253 			}
254 		}
255 	}
256 	/**
257 	 * Passes a mouseDragEvent if the user clicked on an element, held down the button, and moved the mouse.
258 	 */
259 	public void passMouseDragEvent(int x, int y, int relX, int relY, ubyte button){
260 		if(draggedElement){
261 			draggedElement.onDrag(x, y, relX, relY, button);
262 		}
263 	}
264 	/**
265 	 * Passes a mouseMotionEvent if the user moved the mouse.
266 	 */
267 	public void passMouseMotionEvent(int x, int y, int relX, int relY, ubyte button){
268 
269 	}
270 	/**
271 	 * Closes the window by calling the WindowHandler's closeWindow function.
272 	 */
273 	public void close(){
274 		parent.closeWindow(this);
275 	}
276 	/**
277 	 * Passes the scroll event to the element where the mouse pointer currently stands.
278 	 */
279 	public void passScrollEvent(int wX, int wY, int x, int y){
280 		foreach(WindowElement e; scrollC){
281 			if(e.getPosition().left < wX && e.getPosition().right > wX && e.getPosition().top < wX && e.getPosition().bottom > wY){
282 
283 				e.onScroll(x, y, wX, wY);
284 				return;
285 			}
286 		}
287 	}
288 	/**
289 	 * Called if an extra button was pressed.
290 	 */
291 	public void extraButtonEvent(int num, ubyte button, int state){
292 
293 	}
294 	/**
295 	 * Passes a keyboard event.
296 	 */
297 	public void passKeyboardEvent(wchar c, int type, int x, int y){
298 
299 	}
300 	/**
301 	 * Adds a WindowHandler to the window.
302 	 */
303 	public void addParent(IWindowHandler wh){
304 		parent = wh;
305 	}
306 	public void getFocus(){
307 
308 	}
309 	public void dropFocus(){
310 
311 	}
312 	public Coordinate getAbsolutePosition(WindowElement sender){
313 		return Coordinate(sender.position.left + position.left, sender.position.top + position.top, sender.position.right + position.right, sender.position.bottom + position.bottom);
314 	}
315 	/**
316 	* Moves the window to the exact location.
317 	*/
318 	public void move(int x, int y){
319 		parent.moveWindow(x, y, this);
320 		position.move(x,y);
321 	}
322 	/**
323 	* Moves the window by the given values.
324 	*/
325 	public void relMove(int x, int y){
326 		parent.relMoveWindow(x, y, this);
327 		position.relMove(x,y);
328 	}
329 	/**
330 	 * Sets the height of the window, also issues a redraw.
331 	 */
332 	public void setHeight(int y){
333 		position.bottom = position.top + y;
334 		draw();
335 		parent.refreshWindow(this);
336 	}
337 	/**
338 	 * Sets the width of the window, also issues a redraw.
339 	 */
340 	public void setWidth(int x){
341 		position.right = position.left + x;
342 		draw();
343 		parent.refreshWindow(this);
344 	}
345 	/**
346 	 * Sets the size of the window, also issues a redraw.
347 	 */
348 	public void setSize(int x, int y){
349 		position.right = position.left + x;
350 		position.bottom = position.top + y;
351 		draw();
352 		parent.refreshWindow(this);
353 	}
354 	/**
355 	 * Returns the outputted bitmap.
356 	 * Can be overridden for 32 bit outputs.
357 	 */
358 	public @property ABitmap getOutput(){
359 		return output.output;
360 	}
361 	/**
362 	 * Clears the background where the element is being drawn.
363 	 */
364 	public void clearArea(WindowElement sender){
365 		Coordinate c = sender.position;
366 		output.drawFilledRectangle(c.left, c.right, c.top, c.bottom, getStyleSheet.getColor("window"));
367 	}
368 }
369 
370 /**
371  * Standard text input form for various applications.
372  */
373 public class TextInputDialog : Window{
374 	//public ActionListener[] al;
375 	private TextBox textInput;
376 	private string source;
377 	public void function(dstring text) textOutput;
378 	/**
379 	 * Creates a TextInputDialog. Auto-sizing version is not implemented yet.
380 	 */
381 	public this(Coordinate size, string source, dstring title, dstring message, dstring text = ""){
382 		super(size, title);
383 		Label msg = new Label(message, "null", Coordinate(8, 20, size.width()-8, 39));
384 		addElement(msg, EventProperties.MOUSE);
385 
386 		textInput = new TextBox(text, "textInput", Coordinate(8, 40, size.width()-8, 59));
387 		addElement(textInput, EventProperties.MOUSE);
388 
389 		Button ok = new Button("Ok","ok", Coordinate(size.width()-48,65,size.width()-8,84));
390 		ok.onMouseLClickRel = &button_onClick;
391 		addElement(ok,EventProperties.MOUSE);
392 		this.source = source;
393 	}
394 
395 	public void button_onClick(Event ev){
396 		if(textOutput !is null){
397 			textOutput(textInput.getText);
398 		}
399 
400 		close();
401 
402 	}
403 }
404 /**
405  * Default dialog for simple messageboxes.
406  */
407 public class DefaultDialog : Window{
408 	private string source;
409 	public void delegate(Event ev) output;
410 
411 	public this(Coordinate size, string source, dstring title, dstring[] message, dstring[] options = ["Ok"],
412 			string[] values = ["close"]){
413 		this(size, title);
414 		//generate text
415 		this.source = source;
416 		int x1, x2, y1 = 20, y2 = getStyleSheet.drawParameters["TextSpacingTop"] + getStyleSheet.drawParameters["TextSpacingBottom"]
417 								+ getStyleSheet.getFontset("default").getSize;
418 		//Label msg = new Label(message[0], "null", Coordinate(5, 20, size.width()-5, 40));
419 		//addElement(msg, EventProperties.MOUSE);
420 
421 		//generate buttons
422 
423 		x1 = size.width() - 10;
424 		Button[] buttons;
425 		int button1 = size.height - getStyleSheet.drawParameters["WindowBottomPadding"];
426 		int button2 = button1 - getStyleSheet.drawParameters["ComponentHeight"];
427 		for(int i; i < options.length; i++){
428 			x2 = x1 - ((getStyleSheet().getFontset("default").getTextLength(options[i]) + 16));
429 			buttons ~= new Button(options[i], values[i], Coordinate(x2, button2, x1, button1));
430 			buttons[i].onMouseLClickRel = &actionEvent;
431 			addElement(buttons[i], EventProperties.MOUSE);
432 			x1 = x2;
433 		}
434 		//add labels
435 		for(int i; i < message.length; i++){
436 			Label msg = new Label(message[i], "null", Coordinate(getStyleSheet.drawParameters["WindowLeftPadding"],
437 								y1, size.width()-getStyleSheet.drawParameters["WindowRightPadding"], y1 + y2));
438 			addElement(msg, EventProperties.MOUSE);
439 			y1 += y2;
440 		}
441 	}
442 
443 	/*public this(string source, wstring title, wstring[] message, wstring[] options = ["Ok"], string[] values = ["ok"]){
444 		this(size, title);
445 	}*/
446 
447 	public this(Coordinate size, dstring title){
448 		super(size, title);
449 	}
450 	public void actionEvent(Event ev){
451 		if(ev.source == "close"){
452 			close();
453 		}else{
454 			if(output !is null){
455 				ev.subsource = source;
456 				output(ev);
457 			}
458 		}
459 	}
460 }
461 /**
462  * File dialog window for opening files.
463  * Returns the selected filetype as an int value of the position of the types that were handled to the ctor.
464  */
465 public class FileDialog : Window{
466 	/**
467 	 * Defines file association descriptions
468 	 */
469 	public struct FileAssociationDescriptor{
470 		public dstring description;		/// Describes the file type. Eg. "PPE map files"
471 		public string[] types;			/// The extensions associated with a given file format. Eg. ["*.htm","*.html"]. First is preferred one at saving, if no filetype is described when typing.
472 		/**
473 		 * Creates a single FileAssociationDescriptor
474 		 */
475 		public this(dstring description, string[] types){
476 			this.description = description;
477 			this.types = types;
478 		}
479 		/**
480 		 * Returns the types as a single string.
481 		 */
482 		public dstring getTypesForSelector(){
483 			dstring result;
484 			foreach(string s ; types){
485 				result ~= to!dstring(s);
486 				result ~= ";";
487 			}
488 			result.length--;
489 			return result;
490 		}
491 	}
492 
493 	//private ActionListener al;
494 	private string source;
495 	private string[] pathList, driveList;
496 	private string directory, filename;
497 	private ListBox lb;
498 	private TextBox tb;
499 
500 	private bool save;
501 	private FileAssociationDescriptor[] filetypes;
502 	public static const string subsourceID = "filedialog";
503 	private int selectedType;
504 	public void delegate(Event ev) onFileselect;
505 	private Button button_up;
506 	private Button button_drv;
507 	private Button button_ok;
508 	private Button button_close;
509 	private Button button_type;
510 
511 	/**
512 	 * Creates a file dialog with the given parameters.
513 	 * File types are given in the format '*.format'.
514 	 */
515 	public this(dstring title, string source, void delegate(Event ev) onFileselect, FileAssociationDescriptor[] filetypes,
516 			string startDir, bool save = false, string filename = ""){
517 		this(Coordinate(20,20,240,198), title);
518 		this.source = source;
519 		this.filetypes = filetypes;
520 		this.save = save;
521 		this.onFileselect = onFileselect;
522 		//al = a;
523 		directory = startDir;
524 
525 		button_up = new Button("Up"d,"up",Coordinate(4, 154, 54, 174));
526 		button_up.onMouseLClickRel = &up;
527 		addElement(button_up);
528 		button_drv = new Button("Drive"d,"drv",Coordinate(58, 154, 108, 174));
529 		button_drv.onMouseLClickRel = &changeDrive;
530 		addElement(button_drv);
531 		button_ok = new Button(save ? "Save"d : "Load"d,"ok",Coordinate(112, 154, 162, 174));
532 		button_ok.onMouseLClickRel = &fileEvent;
533 		addElement(button_ok);
534 		button_close = new Button("Close"d,"close",Coordinate(166, 154, 216, 174));
535 		button_close.onMouseLClickRel = &button_close_onMouseLClickRel;
536 		addElement(button_close);
537 		button_type = new Button("Type","type",Coordinate(166, 130, 216, 150));
538 		button_type.onMouseLClickRel = &button_type_onMouseLClickRel;
539 		addElement(button_type);
540 		/+for(int i; i < buttons.length; i++){
541 			buttons[i].onMouseLClickRel = &actionEvent;
542 			addElement(buttons[i], EventProperties.MOUSE);
543 		}+/
544 		//generate textbox
545 		tb = new TextBox(to!dstring(filename), "filename", Coordinate(4, 130, 162, 150));
546 		//tb.addTextInputHandler(tih);
547 		//tb.onTextInput = &actionEvent;
548 		addElement(tb, EventProperties.MOUSE);
549 		//generate listbox
550 
551 		//test parameters
552 
553 		//Date format: yyyy-mm-dd hh:mm:ss
554 		lb = new ListBox("lb", Coordinate(4, 20, 216, 126),null, new ListBoxHeader(["Name", "Type", "Date"], [160, 40, 176]),
555 				15);
556 		lb.onItemSelect = &listBox_onItemSelect;
557 		addElement(lb, EventProperties.MOUSE | EventProperties.SCROLL);
558 		spanDir();
559 		//scrollC ~= lb;
560 		//lb.onItemSelect = &actionEvent;
561 		detectDrive();
562 	}
563 
564 	public this(Coordinate size, dstring title){
565 		super(size, title);
566 	}
567 	/**
568 	 * Iterates throught a directory for listing.
569 	 */
570 	private void spanDir(){
571 		pathList.length = 0;
572 		ListBoxItem[] items;
573 		foreach(DirEntry de; dirEntries(directory, SpanMode.shallow)){
574 			if(de.isDir){
575 				pathList ~= de.name;
576 				/*columns[0].elements ~= to!wstring(getFilenameFromPath(de.name));
577 				columns[1].elements ~= "<DIR>";
578 				columns[2].elements ~= formatDate(de.timeLastModified);*/
579 				items ~= new ListBoxItem([to!dstring(getFilenameFromPath(de.name)),"<DIR>"d,formatDate(de.timeLastModified)]);
580 			}
581 		}
582 		//foreach(f; filetypes){
583 		foreach(ft; filetypes[selectedType].types){
584 			foreach(DirEntry de; dirEntries(directory, ft, SpanMode.shallow)){
585 				if(de.isFile){
586 					pathList ~= de.name;
587 					/*columns[0].elements ~= to!wstring(getFilenameFromPath(de.name, true));
588 					columns[1].elements ~= to!wstring(ft);
589 					columns[2].elements ~= formatDate(de.timeLastModified);*/
590 					items ~= new ListBoxItem([to!dstring(getFilenameFromPath(de.name)),to!dstring(ft),formatDate(de.timeLastModified)]);
591 				}
592 			}
593 		}
594 		lb.updateColumns(items);
595 		lb.draw();
596 
597 	}
598 	/**
599 	 * Standard date formatting tool.
600 	 */
601 	private dstring formatDate(SysTime time){
602 		dstring s;
603 		s ~= to!dstring(time.year());
604 		s ~= "-";
605 		s ~= to!dstring(time.month());
606 		s ~= "-";
607 		s ~= to!dstring(time.day());
608 		s ~= " ";
609 		s ~= to!dstring(time.hour());
610 		s ~= ":";
611 		s ~= to!dstring(time.minute());
612 		s ~= ":";
613 		s ~= to!dstring(time.second());
614 		return s;
615 	}
616 	/**
617 	 * Detects the available drives, currently only used under windows.
618 	 */
619 	private void detectDrive(){
620 		version(Windows){
621 			driveList.length = 0;
622 			for(char c = 'A'; c <='Z'; c++){
623 				string s;
624 				s ~= c;
625 				s ~= ":\x5c";
626 				if(exists(s)){
627 					driveList ~= (s);
628 				}
629 			}
630 		}else{
631 
632 		}
633 	}
634 	/**
635 	 * Returns the filename from the path.
636 	 */
637 	private string getFilenameFromPath(string p, bool b = false){
638 		size_t n, m = p.length;
639 		string s;
640 		for(size_t i ; i < p.length ; i++){
641 			if(std.path.isDirSeparator(p[i])){
642 				n = i;
643 			}
644 		}
645 		//n++;
646 		if(b){
647 			for(size_t i ; i < p.length ; i++){
648 				if(p[i] == '.'){
649 					m = i;
650 				}
651 			}
652 		}
653 		for( ; n < m ; n++){
654 			if(p[n] < 128 && p[n] > 31)
655 				s ~= p[n];
656 		}
657 		return s;
658 	}
659 	/**
660 	 * Called when the up button is pressed. Goes up in the folder hiearchy.
661 	 */
662 	private void up(Event ev){
663 		int n;
664 		for(int i ; i < directory.length ; i++){
665 			if(std.path.isDirSeparator(directory[i])){
666 				n = i;
667 			}
668 		}
669 		/+string newdir;
670 		for(int i ; i < n ; i++){
671 			newdir ~= directory[i];
672 		}+/
673 		//directory = newdir;
674 		directory = directory[0..n];
675 		spanDir();
676 
677 	}
678 	/**
679 	 * Displays the drives. Under Linux, it goes into the /dev/ folder.
680 	 */
681 	private void changeDrive(Event ev){
682 		version(Windows){
683 			pathList.length = 0;
684 			ListBoxItem[] items;
685 			foreach(string drive; driveList){
686 				pathList ~= drive;
687 				items ~= new ListBoxItem([to!dstring(drive),"<DRIVE>"d,""d]);
688 			}
689 			lb.updateColumns(items);
690 			lb.draw();
691 		}else version(Posix){
692 			directory = "/dev/";
693 			spanDir();
694 		}
695 	}
696 	/**
697 	 * Creates an action event, then closes the window.
698 	 */
699 	private void fileEvent(Event ev) {
700 		//wstring s = to!wstring(directory);
701 		filename = to!string(tb.getText);
702 		//al.actionEvent("file", EventType.FILEDIALOGEVENT, 0, s);
703 		if(onFileselect !is null)
704 			onFileselect(new Event(source, "", directory, filename, null, selectedType, EventType.FILEDIALOGEVENT));
705 		parent.closeWindow(this);
706 	}
707 	private void event_fileSelector(Event ev) {
708 		selectedType = ev.value;
709 		spanDir();
710 	}
711 	private void button_type_onMouseLClickRel(Event ev) {
712 		PopUpMenuElement[] e;
713 		for(int i ; i < filetypes.length ; i++){
714 			e ~= new PopUpMenuElement(filetypes[i].types[0],filetypes[i].description, filetypes[i].getTypesForSelector());
715 		}
716 		PopUpMenu p = new PopUpMenu(e,"fileSelector");
717 		p.onMouseClick = &event_fileSelector;
718 		parent.addPopUpElement(p);
719 	}
720 	private void button_close_onMouseLClickRel(Event ev) {
721 		parent.closeWindow(this);
722 	}
723 	private void listBox_onItemSelect(Event ev) {
724 		try{
725 			if(pathList.length == 0) return;
726 			if(isDir(pathList[ev.value])){
727 				directory = pathList[ev.value];
728 				spanDir();
729 			}else{
730 				filename = getFilenameFromPath(pathList[ev.value]);
731 				tb.setText(to!dstring(filename));
732 			}
733 		}catch(Exception e){
734 			DefaultDialog d = new DefaultDialog(Coordinate(10,10,256,80),"null",to!dstring("Error!"),
735 					PixelPerfectEngine.system.etc.stringArrayConv([e.msg]));
736 			parent.addWindow(d);
737 		}
738 	}
739 	/+public void actionEvent(Event event){
740 
741 		/+if(event.subsource == "fileSelector"){
742 			selectedType = event.value;
743 			spanDir();
744 		}+/
745 		switch(event.source){
746 			case "lb":
747 				try{
748 					if(pathList.length == 0) return;
749 					if(isDir(pathList[event.value])){
750 						directory = pathList[event.value];
751 						spanDir();
752 
753 
754 					}else{
755 						filename = getFilenameFromPath(pathList[event.value]);
756 						tb.setText(to!dstring(filename));
757 					}
758 				}catch(Exception e){
759 					DefaultDialog d = new DefaultDialog(Coordinate(10,10,256,80),"null",to!dstring("Error!"),
760 							PixelPerfectEngine.system.etc.stringArrayConv([e.msg]));
761 					parent.addWindow(d);
762 				}
763 				break;
764 			case "up": up(); break;
765 			case "drv": changeDrive(); break;
766 			case "ok": fileEvent(); break;
767 			case "close": parent.closeWindow(this); break;
768 			case "type":
769 				PopUpMenuElement[] e;
770 				for(int i ; i < filetypes.length ; i++){
771 					e ~= new PopUpMenuElement(filetypes[i].types[0],filetypes[i].description, filetypes[i].getTypesForSelector());
772 				}
773 				PopUpMenu p = new PopUpMenu(e,"fileSelector");
774 				p.onMouseClick = &event_fileSelector;
775 				parent.addPopUpElement(p);
776 				break;
777 			default: break;
778 		}
779 	}+/
780 }
781 /**
782  * Handles windows as well as PopUpElements.
783  */
784 public class WindowHandler : InputListener, MouseListener, IWindowHandler{
785 	private Window[] windows;
786 	private PopUpElement[] popUpElements;
787 	private int numOfPopUpElements;
788 	private int[] priorities;
789 	public int screenX, screenY, rasterX, rasterY, moveX, moveY, mouseX, mouseY;
790 	//public Bitmap16Bit[wchar] basicFont, altFont, alarmFont;
791 	public StyleSheet defaultStyle;
792 	//public Bitmap16Bit[int] styleBrush;
793 	private ABitmap background;
794 	private ISpriteLayer spriteLayer;
795 	private bool moveState, dragEventState;
796 	private Window windowToMove, dragEventDest;
797 	private PopUpElement dragEventDestPopUp;
798 	private ubyte lastMouseButton;
799 
800 	public this(int sx, int sy, int rx, int ry,ISpriteLayer sl){
801 		screenX = sx;
802 		screenY = sy;
803 		rasterX = rx;
804 		rasterY = ry;
805 		spriteLayer = sl;
806 	}
807 
808 	public void addWindow(Window w){
809 		windows ~= w;
810 		w.addParent(this);
811 		//priorities ~= 666;
812 		w.draw();
813 		setWindowToTop(w);
814 
815 		/*for(int i ; i < windows.length ; i++){
816 			spriteLayer.addSprite(windows[i].output.output, i, windows[i].position);
817 		}*/
818 	}
819 
820 	/**
821 	 * Adds a DefaultDialog as a message box
822 	 */
823 	public void messageWindow(dstring title, dstring message, int width = 256){
824 		StyleSheet ss = getDefaultStyleSheet();
825 		dstring[] formattedMessage = ss.getFontset(ss.fontTypes["Label"]).breakTextIntoMultipleLines(message, width -
826 				ss.drawParameters["WindowLeftPadding"] - ss.drawParameters["WindowRightPadding"]);
827 		int height = cast(int)(formattedMessage.length * (ss.getFontset(ss.fontTypes["Label"]).getSize() +
828 				ss.drawParameters["TextSpacingTop"] + ss.drawParameters["TextSpacingBottom"]));
829 		height += ss.drawParameters["WindowTopPadding"] + ss.drawParameters["WindowBottomPadding"] +
830 				ss.drawParameters["ComponentHeight"];
831 		Coordinate c = Coordinate(mouseX - width / 2, mouseY - height / 2, mouseX + width / 2, mouseY + height / 2);
832 		addWindow(new DefaultDialog(c, null, title, formattedMessage));
833 	}
834 	public void addBackground(ABitmap b){
835 		background = b;
836 		spriteLayer.addSprite(background, 65536, 0, 0);
837 	}
838 
839 	private int whichWindow(Window w){
840 		for(int i ; i < windows.length ; i++){
841 			if(windows[i] == w){
842 				return i;
843 			}
844 		}
845 		return -1;
846 	}
847 
848 	public void setWindowToTop(Window sender){
849 		for(size_t s; s < windows.length; s++){
850 			if(windows[s] == sender){
851 				windows[0].active = false;
852 				windows[0].draw(true);
853 				Window ww = windows[s];
854 				ww.active = true;
855 				ww.draw(true);
856 				windows[s] = windows[0];
857 				windows[0] = ww;
858 				updateSpriteOrder();
859 				break;
860 			}
861 		}
862 	}
863 
864 	private void updateSpriteOrder(){
865 		for(int i ; i < windows.length ; i++){
866 			spriteLayer.removeSprite(i);
867 			spriteLayer.addSprite(windows[i].getOutput, i, windows[i].position);
868 
869 		}
870 	}
871 
872 	/*public Bitmap16Bit[wchar] getFontSet(int style){
873 		switch(style){
874 			case 0: return basicFont;
875 			case 1: return altFont;
876 			case 3: return alarmFont;
877 			default: break;
878 		}
879 		return basicFont;
880 
881 	}*/
882 	public StyleSheet getStyleSheet(){
883 		return defaultStyle;
884 	}
885 	public void closeWindow(Window sender){
886 		//writeln(sender);
887 		dragEventState = false;
888 		dragEventDest = null;
889 		int p = whichWindow(sender);
890 		for(int i ; i < windows.length ; i++)
891 			spriteLayer.removeSprite(i);
892 		//spriteLayer.removeSprite(p);
893 		windows = remove(windows, p);
894 
895 		updateSpriteOrder();
896 	}
897 
898 	public void moveUpdate(Window sender){
899 		moveState = true;
900 		windowToMove = sender;
901 	}
902 	public void keyPressed(string ID, uint timestamp, uint devicenumber, uint devicetype){
903 
904 	}
905 	public void keyReleased(string ID, uint timestamp, uint devicenumber, uint devicetype){
906 
907 	}
908 	public void mouseButtonEvent(uint which, uint timestamp, uint windowID, ubyte button, ubyte state, ubyte clicks, int x, int y){
909 
910 		//converting the dimensions
911 		double xR = to!double(rasterX) / to!double(screenX) , yR = to!double(rasterY) / to!double(screenY);
912 		x = to!int(x * xR);
913 		y = to!int(y * yR);
914 		mouseX = x;
915 		mouseY = y;
916 		//if(button == MouseButton.LEFT){
917 		if(state == ButtonState.PRESSED){
918 			if(numOfPopUpElements < 0){
919 				foreach(p ; popUpElements){
920 				if(y >= p.coordinates.top && y <= p.coordinates.bottom && x >= p.coordinates.left && x <= p.coordinates.right){
921 					p.onClick(x - p.coordinates.left, y - p.coordinates.top);
922 					return;
923 				}
924 			}
925 			//removeAllPopUps();
926 			removeTopPopUp();
927 			}else{
928 				moveX = x;
929 				moveY = y;
930 				for(int i ; i < windows.length ; i++){
931 					if(x >= windows[i].position.left && x <= windows[i].position.right && y >= windows[i].position.top && y <= windows[i].position.bottom){
932 						//if(i == 0){
933 						dragEventState = true;
934 						windows[i].passMouseEvent(x - windows[i].position.left, y - windows[i].position.top, state, button);
935 						if(dragEventState)
936 							dragEventDest = windows[i];
937 					/*if(windows.length !=0){
938 						dragEventState = true;
939 						dragEventDest = windows[0];
940 					}*/
941 				//return;
942 					//}else{
943 						if(i != 0){
944 							setWindowToTop(windows[i]);
945 
946 						}
947 						lastMouseButton = button;
948 						return;
949 					}
950 				}
951 				passMouseEvent(x,y,state,button);
952 
953 			}
954 		}else{
955 			if(moveState){
956 				moveState = false;
957 			}else if(dragEventDest){
958 				dragEventDest.passMouseEvent(x - dragEventDest.position.left, y - dragEventDest.position.top, state, button);
959 				dragEventDest = null;
960 			}else{
961 				passMouseEvent(x,y,state,button);
962 			}
963 		}
964 	}
965 	public void passMouseEvent(int x, int y, int state, ubyte button){
966 
967 	}
968 	public void passMouseDragEvent(int x, int y, int relX, int relY, ubyte button){
969 	}
970 	public void passMouseMotionEvent(int x, int y, int relX, int relY, ubyte button){
971 	}
972 	public void mouseWheelEvent(uint type, uint timestamp, uint windowID, uint which, int x, int y, int wX, int wY){
973 		double xR = to!double(rasterX) / to!double(screenX) , yR = to!double(rasterY) / to!double(screenY);
974 		wX = to!int(wX * xR);
975 		wY = to!int(wY * yR);
976 		if(windows.length != 0)
977 			windows[0].passScrollEvent(wX - windows[0].position.left, wY - windows[0].position.top, y, x);
978 		passScrollEvent(wX,wY,x,y);
979 	}
980 	public void passScrollEvent(int wX, int wY, int x, int y){
981 
982 	}
983 	public void mouseMotionEvent(uint timestamp, uint windowID, uint which, uint state, int x, int y, int relX, int relY){
984 		//coordinate conversion
985 		double xR = to!double(rasterX) / to!double(screenX) , yR = to!double(rasterY) / to!double(screenY);
986 		x = to!int(x * xR);
987 		y = to!int(y * yR);
988 		relX = to!int(relX * xR);
989 		relY = to!int(relY * yR);
990 		//passing mouseMovementEvent onto PopUps
991 		if(numOfPopUpElements < 0){
992 			PopUpElement p = popUpElements[popUpElements.length - 1];
993 			if(p.coordinates.top < y && p.coordinates.bottom > y && p.coordinates.left < x && p.coordinates.right > x){
994 				p.onMouseMovement(x - p.coordinates.left, y - p.coordinates.top);
995 				return;
996 			}else{
997 				p.onMouseMovement(-1,-1);
998 			}
999 
1000 		}
1001 		if(state == ButtonState.PRESSED && moveState){
1002 			windowToMove.relMove(relX, relY);
1003 		}else if(state == ButtonState.PRESSED && dragEventDest){
1004 			dragEventDest.passMouseDragEvent(x, y, relX, relY, lastMouseButton);
1005 		}else{
1006 			if(windows.length){
1007 				windows[0].passMouseMotionEvent(x, y, relX, relY, lastMouseButton);
1008 			}
1009 		}
1010 	}
1011 	public void moveWindow(int x, int y, Window w){
1012 		spriteLayer.relMoveSprite(whichWindow(w), x, y);
1013 
1014 	}
1015 	public void refreshWindow(Window w){
1016 		int n = whichWindow(w);
1017 		spriteLayer.replaceSprite(windows[n].output.output, n, windows[n].position);
1018 	}
1019 	public void relMoveWindow(int x, int y, Window w){
1020 		spriteLayer.relMoveSprite(whichWindow(w), x, y);
1021 	}
1022 	public void addPopUpElement(PopUpElement p){
1023 		popUpElements ~= p;
1024 		p.addParent(this);
1025 		p.draw;
1026 		mouseX -= (p.coordinates.width/2);
1027 		mouseY -= (p.coordinates.height/2);
1028 		p.coordinates.move(mouseX,mouseY);
1029 		numOfPopUpElements--;
1030 		spriteLayer.addSprite(p.output.output,numOfPopUpElements,mouseX,mouseY);
1031 
1032 	}
1033 	public void addPopUpElement(PopUpElement p, int x, int y){
1034 		popUpElements ~= p;
1035 		p.addParent(this);
1036 		p.draw;
1037 		p.coordinates.move(x, y);
1038 		numOfPopUpElements--;
1039 		spriteLayer.addSprite(p.output.output,numOfPopUpElements, x, y);
1040 	}
1041 	private void removeAllPopUps(){
1042 		for( ; numOfPopUpElements < 0 ; numOfPopUpElements++){
1043 			spriteLayer.removeSprite(numOfPopUpElements);
1044 		}
1045 		popUpElements.length = 0;
1046 	}
1047 	private void removeTopPopUp(){
1048 
1049 		spriteLayer.removeSprite(numOfPopUpElements++);
1050 
1051 		popUpElements.length--;
1052 	}
1053 	public StyleSheet getDefaultStyleSheet(){
1054 		return defaultStyle;
1055 	}
1056 	public void endPopUpSession(){
1057 		removeAllPopUps();
1058 	}
1059 	public void closePopUp(PopUpElement p){
1060 
1061 	}
1062 	public void drawUpdate(WindowElement sender){}
1063 	public void getFocus(WindowElement sender){}
1064 	public void dropFocus(WindowElement sender){}
1065 	public void drawUpdate(Window sender){
1066 		/*int p = whichWindow(sender);
1067 		spriteLayer.removeSprite(p);
1068 		spriteLayer.addSprite(sender.output.output,p,sender.position);*/
1069 	}
1070 	/*public Coordinate getAbsolutePosition(PopUpElement sender){
1071 		for(int i ; i < popUpElements.length ; i++){
1072 			if(popUpElements[i] = sender){
1073 
1074 			}
1075 		}
1076 		return Coordinate();
1077 	}*/
1078 }
1079 
1080 public interface IWindowHandler : PopUpHandler{
1081 	//public Bitmap16Bit[wchar] getFontSet(int style);
1082 	public StyleSheet getStyleSheet();
1083 	public void closeWindow(Window sender);
1084 	public void moveUpdate(Window sender);
1085 	public void setWindowToTop(Window sender);
1086 	public void addWindow(Window w);
1087 	public void refreshWindow(Window w);
1088 	public void moveWindow(int x, int y, Window w);
1089 	public void relMoveWindow(int x, int y, Window w);
1090 	public void drawUpdate(Window sender);
1091 	public void messageWindow(dstring title, dstring message, int width = 256);
1092 }
1093 
1094 public enum EventProperties : uint{
1095 	KEYBOARD		=	1,
1096 	MOUSE			=	2,
1097 	SCROLL			=	4
1098 }