1 module document;
2 
3 import PixelPerfectEngine.map.mapdata;
4 import PixelPerfectEngine.map.mapformat;
5 import editorevents;
6 import windows.rasterwindow;
7 import PixelPerfectEngine.concrete.eventChainSystem;
8 import PixelPerfectEngine.concrete.interfaces : MouseEventReceptor;
9 import PixelPerfectEngine.concrete.types : CursorType;
10 import PixelPerfectEngine.graphics.common : Color, Coordinate;
11 import PixelPerfectEngine.graphics.bitmap;
12 import PixelPerfectEngine.graphics.layers;
13 import PixelPerfectEngine.system.input : MouseButton, ButtonState, MouseClickEvent, MouseMotionEvent, MouseWheelEvent, 
14 		MouseEventCommons, MouseButtonFlags;
15 import std.stdio;
16 
17 import app;
18 ///Individual document for parallel editing
19 public class MapDocument : MouseEventReceptor {
20 	/**
21 	 * Specifies the current edit mode
22 	 */
23 	public enum EditMode {
24 		selectDragScroll,
25 		tilePlacement,
26 		boxPlacement,
27 		spritePlacement,
28 	}
29 	UndoableStack		events;		///Per document event stack
30 	MapFormat			mainDoc;	///Used to reduce duplicate data as much as possible
31 	//ABitmap[] delegate() imageReturnFunc;
32 	Color[] delegate(MapDocument sender) paletteReturnFunc;	///Used for adding the palette for the document
33 	int					selectedLayer;	///Indicates the currently selected layer
34 	Box					mapSelection;	///Contains the selected map area parameters
35 	Box					areaSelection;	///Contains the selected layer area parameters
36 	RasterWindow		outputWindow;	///Window used to output the screen data
37 	EditMode			mode;			///Mose event mode selector
38 	LayerInfo[]			layerList;		///Local layerinfo for data lookup
39 	string				filename;		///Null if not yet saved, otherwise name of the target file
40 	protected int		prevMouseX;		///Previous mouse X position
41 	protected int		prevMouseY;		///Previous mouse Y position
42 	public int			sXAmount;		///Continuous X scroll amount
43 	public int			sYAmount;		///Continuous Y scroll amount
44 	protected uint		flags;			///Various status flags combined into one.
45 	protected static enum	VOIDFILL_EN = 1<<0;	///If set, tilePlacement overrides only transparent (0xFFFF) tiles.
46 	protected static enum	LAYER_SCROLL = 1<<1;///If set, then layer is being scrolled.
47 	protected static enum	PLACEMENT = 1<<2;	///To solve some "debounce" issues around mouse click releases
48 	protected MappingElement	selectedMappingElement;	///Currently selected mapping element to write, including mirroring properties, palette selection, and priority attributes
49 	//public bool			voidfill;		///If true, tilePlacement overrides only transparent (0xFFFF) tiles.
50 	/**
51 	 * Loads the document from disk.
52 	 */
53 	public this(string filename) @trusted {
54 		mainDoc = new MapFormat(File(filename));
55 		events = new UndoableStack(20);
56 		mode = EditMode.selectDragScroll;
57 	}
58 	///New from scratch
59 	public this(string docName, int resX, int resY) @trusted {
60 		events = new UndoableStack(20);
61 		mainDoc = new MapFormat(docName, resX, resY);
62 		mode = EditMode.selectDragScroll;
63 	}
64 	///Returns the next available layer number.
65 	public int nextLayerNumber() @safe {
66 		int result = selectedLayer;
67 		do {
68 			if (mainDoc[result] is null)
69 				return result;
70 			result++;
71 		} while (true);
72 	}
73 	///Puts the loaded tiles onto a TileLayer
74 	public void addTileSet(int layer, ABitmap[ushort] tiles) @trusted {
75 		ITileLayer itl = cast(ITileLayer)mainDoc[layer];
76 		foreach (i ; tiles.byKey) {
77 			itl.addTile(tiles[i], i);
78 		}
79 	}
80 	///Ditto
81 	public void addTileSet(int layer, ABitmap[] tiles) @trusted {
82 		ITileLayer itl = cast(ITileLayer)mainDoc[layer];
83 		for (ushort i ; i < tiles.length ; i++) {
84 			itl.addTile(tiles[i], i);
85 		}
86 	}
87 	/+/**
88 	 * Pass mouse events here. DEPRECATED!
89 	 */
90 	public void passMouseEvent(int x, int y, int state, ubyte button) {
91 		//Normal mode:
92 		//left : drag layer/select ; right : menu ; middle : quick nav ; other buttons : user defined
93 		//TileLayer placement mode:
94 		//left : placement ; right : menu ; middle : delete ; other buttons : user defined
95 		final switch (mode) {
96 			case EditMode.selectDragScroll:
97 				/+if (state == ButtonState.Pressed) {
98 					prevMouseX = x;
99 					prevMouseY = y;
100 				}+/
101 				switch (button) {
102 					case MouseButton.Left:
103 						//Initialize drag select
104 						if (state == ButtonState.Pressed) {
105 							prevMouseX = x;
106 							prevMouseY = y;
107 						} else {
108 							Box b;
109 
110 							if (prevMouseX > x) {
111 								b.right = prevMouseX;
112 								b.left = x;
113 							} else {
114 								b.right = x;
115 								b.left = prevMouseX;
116 							}
117 
118 							if (prevMouseY > y) {
119 								b.bottom = prevMouseY;
120 								b.top = y;
121 							} else {
122 								b.bottom = y;
123 								b.top = prevMouseY;
124 							}
125 						}
126 						break;
127 					case MouseButton.Mid:
128 						//Enable quicknav mode. Scroll the layer by delta/10 for each frame. Stop if button is released.
129 						if (state == ButtonState.Pressed) {
130 							outputWindow.requestCursor(CursorType.Hand);
131 							prevMouseX = x;
132 							prevMouseY = y;
133 						} else {
134 							outputWindow.requestCursor(CursorType.Arrow);
135 						}
136 						scrollSelectedLayer(prevMouseX - x, prevMouseY - y);
137 
138 						prevMouseX = x;
139 						prevMouseY = y;
140 						break;
141 					default:
142 						break;
143 				}
144 				break;
145 			case EditMode.tilePlacement:
146 				if(mainDoc.layeroutput.length) {
147 					switch (button) {
148 						case MouseButton.Left:
149 							//Record the first cursor position upon mouse button press, then initialize either a single or zone write for the selected tile layer.
150 							if (state == ButtonState.Pressed) {
151 								prevMouseX = x;
152 								prevMouseY = y;
153 							} else {
154 								ITileLayer target = cast(ITileLayer)(mainDoc[selectedLayer]);
155 								x = (x + mainDoc[selectedLayer].getSX) / target.getTileWidth;
156 								y = (y + mainDoc[selectedLayer].getSY) / target.getTileHeight;
157 								prevMouseX = (prevMouseX + mainDoc[selectedLayer].getSX) / target.getTileWidth;
158 								prevMouseY = (prevMouseY + mainDoc[selectedLayer].getSY) / target.getTileHeight;
159 								Coordinate c;
160 								
161 								if (x > prevMouseX){
162 									c.left = prevMouseX;
163 									c.right = x;
164 								} else {
165 									c.left = x;
166 									c.right = prevMouseX;
167 								}
168 								if (y > prevMouseY){
169 									c.top = prevMouseY;
170 									c.bottom = y;
171 								} else {
172 									c.top = y;
173 									c.bottom = prevMouseY;
174 								}
175 
176 								if (voidfill) {
177 									if (c.width == 0 && c.height == 0) {
178 										if (target.readMapping(c.left, c.top).tileID == 0xFFFF)
179 											events.addToTop(new WriteToMapSingle(target, c.left, c.top, selectedMappingElement));
180 									} else {
181 										events.addToTop(new WriteToMapVoidFill(target, c, selectedMappingElement));
182 									}
183 								} else {
184 									if (c.width == 0 && c.height == 0) {
185 										events.addToTop(new WriteToMapSingle(target, c.left, c.top, selectedMappingElement));
186 
187 									} else {
188 										events.addToTop(new WriteToMapOverwrite(target, c, selectedMappingElement));
189 									}
190 								}
191 							}
192 							break;
193 						case MouseButton.Mid:
194 							//Record the first cursor position upon mouse button press, then initialize either a single or zone delete for the selected tile layer.
195 							if (state == ButtonState.Pressed) {
196 								prevMouseX = x;
197 								prevMouseY = y;
198 							} else {
199 								ITileLayer target = cast(ITileLayer)(mainDoc[selectedLayer]);
200 								x = (x + mainDoc[selectedLayer].getSX) / target.getTileWidth;
201 								y = (y + mainDoc[selectedLayer].getSY) / target.getTileHeight;
202 								prevMouseX = (prevMouseX + mainDoc[selectedLayer].getSX) / target.getTileWidth;
203 								prevMouseY = (prevMouseY + mainDoc[selectedLayer].getSY) / target.getTileHeight;
204 								Coordinate c;
205 								if (x > prevMouseX){
206 									c.left = prevMouseX;
207 									c.right = x;
208 								} else {
209 									c.left = x;
210 									c.right = prevMouseX;
211 								}
212 								if (y > prevMouseY){
213 									c.top = prevMouseY;
214 									c.bottom = y;
215 								} else {
216 									c.top = y;
217 									c.bottom = prevMouseY;
218 								}
219 								if (c.width == 0 && c.height == 0) {
220 									events.addToTop(new WriteToMapSingle(target, c.left, c.top, MappingElement(0xFFFF)));
221 								} else {
222 									events.addToTop(new WriteToMapOverwrite(target, c, MappingElement(0xFFFF)));
223 								}
224 							}
225 							break;
226 						case MouseButton.Right:
227 							//Open quick menu with basic edit options and ability of toggling both vertically and horizontally.
228 							break;
229 						default:
230 							break;
231 					}
232 					outputWindow.draw();
233 				}
234 				//outputWindow.updateRaster();
235 				break;
236 			case EditMode.spritePlacement:
237 				break;
238 			case EditMode.boxPlacement:
239 				break;
240 		}
241 	}+/
242 	/**
243 	 * Scrolls the selected layer by a given amount.
244 	 */
245 	public void scrollSelectedLayer (int x, int y) {
246 		if (mainDoc[selectedLayer] !is null) {
247 			mainDoc[selectedLayer].relScroll(x, y);
248 		}
249 		outputWindow.updateRaster();
250 	}
251 	/**
252 	 * Sets the continuous scroll amounts.
253 	 */
254 	public void setContScroll (int x, int y) {
255 		sXAmount = x;
256 		sYAmount = y;
257 	}
258 	/**
259 	 * Updates the selection on the raster window.
260 	 */
261 	public void updateSelection() {
262 		if (mainDoc[selectedLayer] !is null) {
263 			const int sX = mainDoc[selectedLayer].getSX, sY = mainDoc[selectedLayer].getSY;
264 			Box onscreen = Box(sX, sY, sX + outputWindow.rasterX - 1, sY + outputWindow.rasterY - 1);
265 			if (onscreen.isBetween(areaSelection.cornerUL) || onscreen.isBetween(areaSelection.cornerUR) || 
266 					onscreen.isBetween(areaSelection.cornerLL) || onscreen.isBetween(areaSelection.cornerLR)) {
267 				outputWindow.selection = areaSelection;
268 				outputWindow.selection.move(sX, sY);
269 
270 				outputWindow.selection.left = outputWindow.selection.left < 0 ? 0 : outputWindow.selection.left;
271 				outputWindow.selection.left = outputWindow.selection.left >= outputWindow.rasterX ? outputWindow.rasterX - 1 : 
272 						outputWindow.selection.left;
273 				outputWindow.selection.right = outputWindow.selection.right < 0 ? 0 : outputWindow.selection.right;
274 				outputWindow.selection.right = outputWindow.selection.right >= outputWindow.rasterX ? outputWindow.rasterX - 1 : 
275 						outputWindow.selection.right;
276 				
277 				outputWindow.selection.top = outputWindow.selection.top < 0 ? 0 : outputWindow.selection.top;
278 				outputWindow.selection.top = outputWindow.selection.top >= outputWindow.rasterY ? outputWindow.rasterY - 1 : 
279 						outputWindow.selection.top;
280 				outputWindow.selection.bottom = outputWindow.selection.bottom < 0 ? 0 : outputWindow.selection.bottom;
281 				outputWindow.selection.bottom = outputWindow.selection.bottom >= outputWindow.rasterX ? outputWindow.rasterX - 1 : 
282 						outputWindow.selection.bottom;
283 			} else {
284 				outputWindow.selection = Box (0, 0, -1, -1);
285 			}
286 		}
287 	}
288 	/**
289 	 * Scrolls the selected layer by the amount set.
290 	 * Should be called for every frame.
291 	 */
292 	public void contScrollLayer () {
293 		if(sXAmount || sYAmount){
294 			scrollSelectedLayer (sXAmount, sYAmount);
295 		}
296 	}
297 	/**
298 	 * Updates the material list for the selected layer.
299 	 */
300 	public void updateMaterialList () {
301 		if (mainDoc[selectedLayer] !is null) {
302 			if (prg.materialList !is null) {
303 				TileInfo[] list = mainDoc.getTileInfo(selectedLayer);
304 				//writeln(list.length);
305 				prg.materialList.updateMaterialList(list);
306 			}
307 		}
308 	}
309 	/**
310 	 * Updates the layers for this document.
311 	 */
312 	public void updateLayerList () {
313 		layerList = mainDoc.getLayerInfo;
314 		if (prg.layerList !is null) {
315 			prg.layerList.updateLayerList(layerList);
316 			//prg.wh.layerlist
317 		}
318 	}
319 	public void onSelection () {
320 		updateLayerList;
321 		updateMaterialList;
322 	}
323 	public void tileMaterial_FlipHorizontal(bool pos) {
324 		selectedMappingElement.attributes.horizMirror = pos;
325 	}
326 	public void tileMaterial_FlipVertical(bool pos) {
327 		selectedMappingElement.attributes.vertMirror = pos;
328 	}
329 	public void tileMaterial_Select(wchar id) {
330 		selectedMappingElement.tileID = id;
331 		mode = EditMode.tilePlacement;
332 
333 	}
334 	public ushort tileMaterial_PaletteUp() {
335 		selectedMappingElement.paletteSel++;
336 		return selectedMappingElement.paletteSel;
337 	}
338 	public ushort tileMaterial_PaletteDown() {
339 		selectedMappingElement.paletteSel--;
340 		return selectedMappingElement.paletteSel;
341 	}
342 	public @property bool voidfill() @nogc @safe pure nothrow {
343 		return flags & VOIDFILL_EN;
344 	}
345 	public @property bool voidfill(bool val) @nogc @safe pure nothrow {
346 		if (val) flags |= VOIDFILL_EN;
347 		else flags &= ~VOIDFILL_EN;
348 		return flags & VOIDFILL_EN;
349 	}
350 	
351 	public void passMCE(MouseEventCommons mec, MouseClickEvent mce) {
352 		int x = mce.x, y = mce.y;
353 
354 		final switch (mode) with (EditMode) {
355 			case tilePlacement:
356 				switch (mce.button) {
357 					case MouseButton.Left:
358 						//Record the first cursor position upon mouse button press, then initialize either a single or zone write for the selected tile layer.
359 						if (mce.state) {
360 							prevMouseX = x;
361 							prevMouseY = y;
362 							flags ^= PLACEMENT;
363 						} else if (flags & PLACEMENT) {
364 							flags ^= PLACEMENT;
365 							ITileLayer target = cast(ITileLayer)(mainDoc[selectedLayer]);
366 							const int tileWidth = target.getTileWidth, tileHeight = target.getTileHeight;
367 							const int hScroll = mainDoc[selectedLayer].getSX, vScroll = mainDoc[selectedLayer].getSX;
368 							x = (x + hScroll) / tileWidth;
369 							y = (y + vScroll) / tileHeight;
370 							prevMouseX = (prevMouseX + hScroll) / tileWidth;
371 							prevMouseY = (prevMouseY + vScroll) / tileHeight;
372 							Box c;
373 							
374 							if (x > prevMouseX){
375 								c.left = prevMouseX;
376 								c.right = x;
377 							} else {
378 								c.left = x;
379 								c.right = prevMouseX;
380 							}
381 
382 							if (y > prevMouseY){
383 								c.top = prevMouseY;
384 								c.bottom = y;
385 							} else {
386 								c.top = y;
387 								c.bottom = prevMouseY;
388 							}
389 
390 							if (voidfill) {
391 								if (c.width == 1 && c.height == 1) {
392 									if (target.readMapping(c.left, c.top).tileID == 0xFFFF)
393 										events.addToTop(new WriteToMapSingle(target, c.left, c.top, selectedMappingElement));
394 								} else {
395 									events.addToTop(new WriteToMapVoidFill(target, c, selectedMappingElement));
396 								}
397 							} else {
398 								if (c.width == 1 && c.height == 1) {
399 									events.addToTop(new WriteToMapSingle(target, c.left, c.top, selectedMappingElement));
400 								} else {
401 									events.addToTop(new WriteToMapOverwrite(target, c, selectedMappingElement));
402 								}
403 							}
404 						}
405 						outputWindow.updateRaster();
406 						break;
407 					case MouseButton.Mid:
408 						//Record the first cursor position upon mouse button press, then initialize either a single or zone delete for the selected tile layer.
409 						if (mce.state) {
410 							prevMouseX = x;
411 							prevMouseY = y;
412 							flags ^= PLACEMENT;
413 						} else if (flags & PLACEMENT) {
414 							flags ^= PLACEMENT;
415 							ITileLayer target = cast(ITileLayer)(mainDoc[selectedLayer]);
416 							x = (x + mainDoc[selectedLayer].getSX) / target.getTileWidth;
417 							y = (y + mainDoc[selectedLayer].getSY) / target.getTileHeight;
418 							prevMouseX = (prevMouseX + mainDoc[selectedLayer].getSX) / target.getTileWidth;
419 							prevMouseY = (prevMouseY + mainDoc[selectedLayer].getSY) / target.getTileHeight;
420 
421 							Box c;
422 
423 							if (x > prevMouseX){
424 								c.left = prevMouseX;
425 								c.right = x;
426 							} else {
427 								c.left = x;
428 								c.right = prevMouseX;
429 							}
430 
431 							if (y > prevMouseY){
432 								c.top = prevMouseY;
433 								c.bottom = y;
434 							} else {
435 								c.top = y;
436 								c.bottom = prevMouseY;
437 							}
438 
439 							if (c.width == 1 && c.height == 1) {
440 								events.addToTop(new WriteToMapSingle(target, c.left, c.top, MappingElement(0xFFFF)));
441 							} else {
442 								events.addToTop(new WriteToMapOverwrite(target, c, MappingElement(0xFFFF)));
443 							}
444 						}
445 						outputWindow.updateRaster();
446 						break;
447 					default:
448 						break;
449 				}
450 				break;
451 			case selectDragScroll:
452 				switch (mce.button) {
453 					case MouseButton.Left:
454 						
455 						//Initialize drag select
456 						if (mce.state) {
457 							prevMouseX = x;
458 							prevMouseY = y;
459 							outputWindow.armSelection;
460 							outputWindow.selection.left = x;
461 							outputWindow.selection.top = y;
462 							outputWindow.selection.right = x;
463 							outputWindow.selection.bottom = y;
464 						} else {
465 							outputWindow.disarmSelection;
466 							Box c;
467 
468 							if (x > prevMouseX){
469 								c.left = prevMouseX;
470 								c.right = x;
471 							} else {
472 								c.left = x;
473 								c.right = prevMouseX;
474 							}
475 
476 							if (y > prevMouseY){
477 								c.top = prevMouseY;
478 								c.bottom = y;
479 							} else {
480 								c.top = y;
481 								c.bottom = prevMouseY;
482 							}
483 							
484 							if (getLayerInfo(selectedLayer).type != LayerType.init) {
485 								Layer l = mainDoc.layeroutput[selectedLayer];
486 								areaSelection = c;
487 								areaSelection.move(l.getSX, l.getSY);
488 								
489 								switch (getLayerInfo(selectedLayer).type) {
490 									case LayerType.Tile: //If TileLayer is selected, recalculate coordinates to the nearest valid points
491 										TileLayer tl = cast(TileLayer)l;
492 										areaSelection.left = (areaSelection.left / tl.getTileWidth) * tl.getTileWidth;
493 										areaSelection.top = (areaSelection.top / tl.getTileHeight) * tl.getTileHeight;
494 										areaSelection.right = (areaSelection.right / tl.getTileWidth) * tl.getTileWidth/+ + 
495 												(areaSelection.right % tl.getTileWidth ? 1 : 0)+/;
496 										areaSelection.bottom = (areaSelection.bottom / tl.getTileHeight) * tl.getTileHeight/+ +
497 												(areaSelection.bottom % tl.getTileHeight ? 1 : 0)+/;
498 										break;
499 									
500 									default:
501 										break;
502 								}
503 							}
504 						}
505 						break;
506 					case MouseButton.Mid:
507 						
508 						if (mce.state) {
509 							outputWindow.requestCursor(CursorType.Hand);
510 							prevMouseX = x;
511 							prevMouseY = y;
512 							outputWindow.moveEn = true;
513 						} else {
514 							outputWindow.requestCursor(CursorType.Arrow);
515 							outputWindow.moveEn = false;
516 						}
517 						//scrollSelectedLayer(prevMouseX - x, prevMouseY - y);
518 
519 						prevMouseX = x;
520 						prevMouseY = y;
521 						break;
522 					default:
523 						break;
524 				}
525 				break;
526 			case boxPlacement:
527 				break;
528 			case spritePlacement:
529 				break;
530 		}
531 	}
532 	
533 	public void passMME(MouseEventCommons mec, MouseMotionEvent mme) {
534 		final switch (mode) with (EditMode) {
535 			case selectDragScroll:
536 				switch (mme.buttonState) {
537 					case MouseButtonFlags.Mid:
538 						scrollSelectedLayer(prevMouseX - mme.x, prevMouseY - mme.y);
539 						prevMouseX = mme.x;
540 						prevMouseY = mme.y;
541 						outputWindow.updateRaster();
542 						break;
543 					default:
544 						break;
545 				}
546 				break;
547 			case tilePlacement:
548 				break;
549 			case boxPlacement:
550 				break;
551 			case spritePlacement:
552 				break;
553 		}
554 	}
555 	
556 	public void passMWE(MouseEventCommons mec, MouseWheelEvent mwe) {
557 		if (outputWindow.isSelectionArmed())
558 			scrollSelectedLayer(mwe.x, mwe.y);
559 	}
560 	
561 	protected LayerInfo getLayerInfo(int pri) nothrow {
562 		foreach (key; layerList) {
563 			if (key.pri == pri)
564 				return key;
565 		}
566 		return LayerInfo.init;
567 	}
568 }