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 }