1 module windows.rasterwindow; 2 3 /* 4 * rasterWindow.d 5 * 6 * Outputs layers to a window with the capability of temporarily removing them 7 */ 8 import pixelperfectengine.concrete.window; 9 import pixelperfectengine.graphics.layers; 10 import pixelperfectengine.graphics.raster : PaletteContainer; 11 import CPUblit.composing; 12 static import CPUblit.draw; 13 import CPUblit.colorlookup; 14 import pixelperfectengine.system.input.types : MouseButton, ButtonState; 15 import collections.sortedlist; 16 17 import document; 18 debug import std.stdio; 19 20 /** 21 * Implements a subraster using a window. Has the capability of skipping over individual layers. 22 */ 23 public class RasterWindow : Window, PaletteContainer { 24 alias DisplayList = SortedList!(int, "a < b", false); 25 public DisplayList hiddenLayers; ///List of hidden layers 26 public DisplayList soloedLayers; ///List of soloed layers 27 protected Bitmap32Bit trueOutput, rasterOutput; 28 protected Color[] paletteLocal; 29 protected Color* paletteShared; 30 public Color selColor; ///Selection invert color (red by default) 31 public Color selArmColor; ///Selection armed color (blue by default) 32 protected uint statusFlags; 33 protected static enum MOVE_ARMED = 1 << 0; ///Redirect mouse events to document 34 protected static enum CLOSE_PROTECT = 1 << 1; 35 protected static enum SELECTION_ARMED = 1 << 2; ///Selection is armed, draw box, and redirect event to document 36 protected static enum SHOW_SELECTION = 1 << 3; ///Shows selection 37 //protected int[] layerList; 38 public int rasterX, rasterY; ///Raster sizes 39 protected dstring documentName; 40 protected MapDocument document; 41 ///The selection area on the screen. Converted from the parent document's own absolute values 42 public Box selection; 43 protected RadioButtonGroup modeSel; 44 /** 45 * Creates a new RasterWindow. 46 */ 47 public this(int x, int y, Color* paletteShared, dstring documentName, MapDocument document){ 48 rasterX = x; 49 rasterY = y; 50 trueOutput = new Bitmap32Bit(x + 2,y + 18); 51 rasterOutput = new Bitmap32Bit(x + 2, y + 18); 52 ISmallButton[] smallButtons; 53 const int windowHeaderHeight = getStyleSheet.drawParameters["WindowHeaderHeight"] - 1; 54 modeSel = new RadioButtonGroup(); 55 smallButtons ~= closeButton(); 56 smallButtons ~= new SmallButton("settingsButtonB", "settingsButtonA", "settings", Box(0, 0, windowHeaderHeight, 57 windowHeaderHeight)); 58 smallButtons ~= new SmallButton("paletteButtonB", "paletteButtonA", "palette", Box(0, 0, windowHeaderHeight, 59 windowHeaderHeight)); 60 smallButtons ~= new RadioButton("selMoveButtonB", "selMoveButtonA", "selMove", 61 Box(0, 0, windowHeaderHeight, windowHeaderHeight), modeSel); 62 smallButtons ~= new RadioButton("tilePlacementButtonB", "tilePlacementButtonA", "tile", 63 Box(0, 0, windowHeaderHeight, windowHeaderHeight), modeSel); 64 smallButtons ~= new RadioButton("objPlacementButtonB", "objPlacementButtonA", "obj", 65 Box(0, 0, windowHeaderHeight, windowHeaderHeight), modeSel); 66 smallButtons ~= new RadioButton("sprtPlacementButtonB", "sprtPlacementButtonA", "sprt", 67 Box(0, 0, windowHeaderHeight, windowHeaderHeight), modeSel); 68 modeSel.onToggle = &onModeToggle; 69 70 //smallButtons ~= new SmallButton("settingsButtonB", "settingsButtonA", "settings", Box(0, 0, 16, 16)); 71 super(Box(0, 0, x + 1, y + 17), documentName, smallButtons); 72 foreach (ISmallButton key; smallButtons) { 73 WindowElement we = cast(WindowElement)key; 74 we.onDraw = &clrLookup; 75 } 76 this.paletteShared = paletteShared; 77 this.documentName = documentName; 78 this.document = document; 79 statusFlags |= CLOSE_PROTECT; 80 modeSel.latchPos(0); 81 selColor = Color(0xff,0x00,0x00,0x00); 82 selArmColor = Color(0x00,0x00,0xff,0x00); 83 } 84 /** 85 * Overrides the original getOutput function to return a 32 bit bitmap instead. 86 */ 87 public override @property ABitmap getOutput(){ 88 return trueOutput; 89 } 90 /** 91 * Returns the palette of the object. 92 */ 93 public @property Color[] palette() @safe pure nothrow @nogc { 94 return paletteLocal; 95 } 96 ///Returns the given palette index. 97 public Color getPaletteIndex(ushort index) @safe pure nothrow @nogc const { 98 return paletteLocal[index]; 99 } 100 ///Sets the given palette index to the given value. 101 public Color setPaletteIndex(ushort index, Color value) @safe pure nothrow @nogc { 102 return paletteLocal[index] = value; 103 } 104 /** 105 * Adds a palette chunk to the end of the main palette. 106 */ 107 public Color[] addPaletteChunk(Color[] paletteChunk) @safe { 108 return paletteLocal ~= paletteChunk; 109 } 110 /** 111 * Loads a palette into the object. 112 * Returns the new palette of the object. 113 */ 114 public Color[] loadPalette(Color[] palette) @safe { 115 return paletteLocal = palette; 116 } 117 /** 118 * Loads a palette chunk into the object. 119 * The offset determines where the palette should be loaded. 120 * If it points to an existing place, the indices after that will be overwritten until the whole palette will be copied. 121 * If it points to the end or after it, then the palette will be made longer, and will pad with values #00000000 if needed. 122 * Returns the new palette of the object. 123 */ 124 public Color[] loadPaletteChunk(Color[] paletteChunk, ushort offset) @safe { 125 if (paletteLocal.length < offset + paletteChunk.length) { 126 paletteLocal.length = paletteLocal.length + (offset - paletteLocal.length) + paletteChunk.length; 127 } 128 assert(paletteLocal.length >= offset + paletteChunk.length, "Palette error!"); 129 for (int i ; i < paletteChunk.length ; i++) { 130 paletteLocal[i + offset] = paletteChunk[i]; 131 } 132 return paletteLocal; 133 } 134 /** 135 * Clears an area of the palette with zeroes. 136 * Returns the original area. 137 */ 138 public Color[] clearPaletteChunk(ushort lenght, ushort offset) @safe { 139 Color[] backup = paletteLocal[offset..offset + lenght].dup; 140 for (int i = offset ; i < offset + lenght ; i++) { 141 paletteLocal[i] = Color(0); 142 } 143 return backup; 144 } 145 //public override void passMouseEvent(int x, int y, int state, ubyte button) { 146 ///Passes mouse click event 147 public override void passMCE(MouseEventCommons mec, MouseClickEvent mce) { 148 StyleSheet ss = getStyleSheet; 149 if (mce.y >= position.top + ss.drawParameters["WindowHeaderHeight"] && mce.y < position.bottom && 150 mce.x > position.left && mce.x < position.right) { 151 mce.y -= ss.drawParameters["WindowHeaderHeight"] + position.top; 152 mce.x -= position.left - 1; 153 document.passMCE(mec, mce); 154 } else { 155 super.passMCE(mec, mce); 156 } 157 } 158 ///Passes mouse move event 159 public override void passMME(MouseEventCommons mec, MouseMotionEvent mme) { 160 StyleSheet ss = getStyleSheet; 161 if (statusFlags & (MOVE_ARMED | SELECTION_ARMED) && /+mme.buttonState == (1<<MouseButton.Mid) &&+/ 162 mme.y >= position.top + ss.drawParameters["WindowHeaderHeight"] && mme.y < position.bottom && 163 mme.x > position.left && mme.x < position.right) { 164 mme.y -= ss.drawParameters["WindowHeaderHeight"] + position.top; 165 mme.x -= position.left - 1; 166 document.passMME(mec, mme); 167 168 } else { 169 super.passMME(mec, mme); 170 } 171 } 172 /** 173 * Copy 8 bit bitmap with color lookup. 174 */ 175 protected void clrLookup() { 176 for(int y ; y < 16 ; y++){ // 177 colorLookup(output.output.getPtr + (y * position.width), trueOutput.getPtr + (y * position.width), paletteShared, 178 position.width); 179 } 180 } 181 public override void draw(bool drawHeaderOnly = false){ 182 if(output.output.width != position.width || output.output.height != position.height){ 183 output = new BitmapDrawer(position.width(), position.height()); 184 trueOutput = new Bitmap32Bit(position.width(), position.height()); 185 rasterOutput = new Bitmap32Bit(position.width() - 2, position.height() - 18); 186 } 187 188 drawHeader(); 189 clrLookup(); 190 updateRaster(); 191 if (statusFlags & SELECTION_ARMED) { 192 193 } 194 /*if(drawHeaderOnly) 195 return;*/ 196 //draw the borders. we do not need fills or drawing elements 197 uint* ptr = cast(uint*)trueOutput.getPtr; 198 StyleSheet ss = getStyleSheet; 199 CPUblit.draw.drawLine!uint(0, 16, 0, position.height - 1, paletteShared[ss.getColor("windowascent")].base, ptr, 200 trueOutput.width); 201 CPUblit.draw.drawLine!uint(0, 16, position.width - 1, 16, paletteShared[ss.getColor("windowascent")].base, ptr, 202 trueOutput.width); 203 CPUblit.draw.drawLine!uint(position.width - 1, 16, position.width - 1, position.height - 1, 204 paletteShared[ss.getColor("windowdescent")].base, ptr, trueOutput.width); 205 CPUblit.draw.drawLine!uint(0, position.height - 1, position.width - 1, position.height - 1, 206 paletteShared[ss.getColor("windowdescent")].base, ptr, trueOutput.width); 207 } 208 /** 209 * Clears both displaylists. 210 */ 211 public void clearDisplayLists() { 212 hiddenLayers = DisplayList([]); 213 soloedLayers = DisplayList([]); 214 } 215 /** 216 * Updates the raster of the window. 217 */ 218 public void updateRaster() { 219 //clear raster screen 220 for (int y = 16 ; y < trueOutput.height - 1 ; y++) { 221 for (int x = 1 ; x < trueOutput.width - 1 ; x++) { 222 trueOutput.writePixel (x, y, Color(0,0,0,0)); 223 } 224 } 225 //update each layer individually 226 foreach (int i, Layer l ; document.mainDoc.layeroutput) { 227 if ((i !in hiddenLayers && !soloedLayers.length) || (i in soloedLayers && soloedLayers.length)) 228 l.updateRaster((trueOutput.getPtr + (17 * trueOutput.width) + 1), trueOutput.width * 4, paletteLocal.ptr); 229 } 230 /+for(int i ; i < layerList.length ; i++){ 231 //document.mainDoc[layerList[i]].updateRaster(rasterOutput.getPtr, rasterX * 4, paletteLocal.ptr); 232 document.mainDoc[layerList[i]].updateRaster((trueOutput.getPtr + (17 * trueOutput.width) + 1), trueOutput.width * 4, 233 paletteLocal.ptr); 234 }+/ 235 for (int i = 16 ; i < trueOutput.height - 1 ; i++) { 236 helperFunc(trueOutput.getPtr + 1 + trueOutput.width * i, trueOutput.width - 2); 237 } 238 import CPUblit.composing.specblt : xorBlitter; 239 uint* p = cast(uint*)trueOutput.getPtr; 240 if (statusFlags & SELECTION_ARMED) { 241 for (int y = selection.top + 16 ; y <= selection.bottom + 16 ; y++) { 242 xorBlitter!uint(p + 1 + trueOutput.width * y + selection.left, selection.width, selArmColor.base); 243 } 244 } else if (statusFlags & SHOW_SELECTION) { 245 for (int y = selection.top + 16 ; y <= selection.bottom + 16 ; y++) { 246 xorBlitter!uint(p + 1 + trueOutput.width * y + selection.left, selection.width, selColor.base); 247 } 248 } 249 } 250 /+/** 251 * Adds a new layer then reorders the display list. 252 */ 253 public void addLayer(int p) { 254 import std.algorithm.sorting : sort; 255 layerList ~= p; 256 layerList.sort(); 257 } 258 /** 259 * Removes a layer then reorders the display list. 260 */ 261 public void removeLayer(int p) { 262 import std.algorithm.mutation : remove; 263 for (int i ; i < layerList.length ; i++) { 264 if (layerList[i] == p) { 265 layerList.remove(i); 266 return; 267 } 268 } 269 }+/ 270 /** 271 * Copies and sets all alpha values to 255 to avoid transparency issues 272 */ 273 protected @nogc void helperFunc(void* src, size_t length) pure nothrow { 274 import pixelperfectengine.system.platform; 275 static if(USE_INTEL_INTRINSICS){ 276 import inteli.emmintrin; 277 immutable ubyte[16] ALPHA_255_VEC = [255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0]; 278 while(length > 4){ 279 _mm_storeu_si128(cast(__m128i*)src, _mm_loadu_si128(cast(__m128i*)src) | 280 _mm_loadu_si128(cast(__m128i*)(cast(void*)ALPHA_255_VEC.ptr))); 281 src += 16; 282 //dest += 16; 283 length -= 4; 284 } 285 while(length){ 286 *cast(uint*)src = *cast(uint*)src | 0x00_00_00_FF; 287 src += 4; 288 //dest += 4; 289 length--; 290 } 291 }else{ 292 while(length){ 293 *cast(uint*)src = *cast(uint*)src | 0x00_00_00_FF; 294 src += 4; 295 dest += 4; 296 length--; 297 } 298 } 299 } 300 /** 301 * Overrides the original onExit function for safe close. 302 */ 303 public override void close() { 304 if (statusFlags & CLOSE_PROTECT) { 305 306 } else { 307 super.close; 308 } 309 } 310 311 ///Called when selection needs to be armed. 312 public void armSelection() @nogc @safe pure nothrow { 313 statusFlags |= SELECTION_ARMED; 314 } 315 ///Called when selection needs to be disarmed. 316 public void disarmSelection() @nogc @safe pure nothrow { 317 statusFlags &= ~SELECTION_ARMED; 318 } 319 ///Returns true if selection is armed 320 public bool isSelectionArmed() const @nogc @safe pure nothrow { 321 return statusFlags & SELECTION_ARMED ? true : false; 322 } 323 ///Called when selection needs to be disarmed. 324 public bool showSelection(bool val) @nogc @safe pure nothrow { 325 if (val) statusFlags |= SHOW_SELECTION; 326 else statusFlags &= ~SHOW_SELECTION; 327 return statusFlags & SHOW_SELECTION ? true : false; 328 } 329 ///Returns true if selection is displayed 330 public bool showSelection() const @nogc @safe pure nothrow { 331 return statusFlags & SHOW_SELECTION ? true : false; 332 } 333 ///Enables or disables move 334 public bool moveEn(bool val) @nogc @safe pure nothrow { 335 if (val) statusFlags |= MOVE_ARMED; 336 else statusFlags &= ~MOVE_ARMED; 337 return statusFlags & MOVE_ARMED; 338 } 339 ///Called when the document settings window is needed to be opened. 340 public void onDocSettings(Event ev) { 341 342 } 343 ///Called when any of the modes are selected. 344 public void onModeToggle(Event ev) { 345 switch (modeSel.value) { 346 case "tile": 347 document.mode = MapDocument.EditMode.tilePlacement; 348 break; 349 case "obj": 350 document.mode = MapDocument.EditMode.boxPlacement; 351 break; 352 case "sprt": 353 document.mode = MapDocument.EditMode.spritePlacement; 354 break; 355 default: 356 document.mode = MapDocument.EditMode.selectDragScroll; 357 break; 358 } 359 document.clearSelection(); 360 } 361 362 public void loadLayers () { 363 foreach (key; document.mainDoc.layeroutput) { 364 key.setRasterizer(rasterX, rasterY); 365 //addLayer(key); 366 } 367 } 368 }