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