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 }